Compare commits

...
5 changed files with 145 additions and 57 deletions
@@ -4,7 +4,7 @@ import { Shape } from "konva/lib/Shape";
import { Group } from "konva/lib/Group";
import { invoke } from "@tauri-apps/api/core";
import { listen } from '@tauri-apps/api/event';
import { listen } from "@tauri-apps/api/event";
import { FileUtilities } from "../FileUtilities/FileUtilities";
import { ImageNode, VideoNode, TextNode, ShapeNode, ShapeType } from "../Nodes";
@@ -31,7 +31,6 @@ interface ServerSettings {
lora_path?: string;
}
interface GenerateImageParams {
image: string;
prompt: string;
@@ -210,16 +209,18 @@ export class RealTimeDrawEngine {
console.log(event);
// Handle model download events
const payload = event.payload as any;
// Model download started
if (payload.model_download_started) {
const modelInfo = payload.model_download_started;
console.log(`Model download started: ${modelInfo.model_name} (${modelInfo.model_type})`);
console.log(
`Model download started: ${modelInfo.model_name} (${modelInfo.model_type})`,
);
// Set up loading indicator
isLoadingVisible.value = true;
loadingProgress.value = 0;
// Create fake progress updates
const downloadTimer = setInterval(() => {
loadingProgress.value += 2;
@@ -230,23 +231,23 @@ export class RealTimeDrawEngine {
}
}, 500);
}
// Model download completed
if (payload.model_download_complete) {
const modelInfo = payload.model_download_complete;
console.log(`Model download completed: ${modelInfo.model_name} (${modelInfo.model_type})`);
console.log(
`Model download completed: ${modelInfo.model_name} (${modelInfo.model_type})`,
);
// Complete the loading progress
loadingProgress.value = 100;
// Hide loading indicator after a short delay
setTimeout(() => {
isLoadingVisible.value = false;
}, 1000);
}
});
}
private isEnabled: boolean = false;
@@ -320,9 +321,16 @@ export class RealTimeDrawEngine {
const ctx = cursorCanvas.getContext("2d");
if (!ctx) return;
// Draw the circle
// Draw the outer circle with a light stroke
ctx.beginPath();
ctx.arc(size, size, size / 2, 0, Math.PI * 2);
ctx.strokeStyle = "white";
ctx.lineWidth = 3;
ctx.stroke();
// Draw the inner circle with a dark stroke
ctx.beginPath();
ctx.arc(size, size, size / 2 - 1, 0, Math.PI * 2);
ctx.strokeStyle = "black";
ctx.lineWidth = 1;
ctx.stroke();
+27
View File
@@ -202,3 +202,30 @@
background-position: -200% 0;
}
}
.react-colorful__saturation {
margin: 16px 0;
border-radius: 10px !important;
border-bottom: none !important;
}
.react-colorful__saturation-pointer {
width: 24px !important;
height: 24px !important;
}
.react-colorful__hue,
.react-colorful__alpha {
height: 20px !important;
border-radius: 50px !important;
}
.react-colorful__hue {
margin-bottom: 12px;
}
.react-colorful__hue-pointer,
.react-colorful__alpha-pointer {
width: 20px !important;
height: 20px !important;
}
@@ -1,12 +1,13 @@
import { MouseEvent, MouseEventHandler, useState } from "react";
import { HexColorPicker } from "react-colorful";
import { EyeDropper } from "react-eyedrop";
import { Button } from "./Button";
import { faEyedropper } from "@fortawesome/pro-solid-svg-icons";
import { twMerge } from "tailwind-merge";
type ColorPickerHTMLAttributes = Omit<React.HTMLAttributes<HTMLDivElement>, "color" | "onChange" | "onChangeCapture">;
interface ColorPickerBaseProps<T extends string> extends ColorPickerHTMLAttributes {
type ColorPickerHTMLAttributes = Omit<
React.HTMLAttributes<HTMLDivElement>,
"color" | "onChange" | "onChangeCapture"
>;
interface ColorPickerBaseProps<T extends string>
extends ColorPickerHTMLAttributes {
color: T;
onChange: (newColor: T) => void;
}
@@ -17,7 +18,7 @@ export const HexEyedropPicker = ({
dropperClassName,
pickerClassName,
onPickerChange,
Picker
Picker,
}: {
color: string;
onDropperChange?: (newColor: string) => void;
@@ -26,9 +27,8 @@ export const HexEyedropPicker = ({
onPickerChange: (newColor: string) => void;
Picker: (props: Partial<ColorPickerBaseProps<string>>) => JSX.Element;
}) => {
const handleDropperClick = () => {
if (!('EyeDropper' in window)) {
if (!("EyeDropper" in window)) {
// NOTE: This shouldn't happen cuz we're using Chromium
console.error("EyeDropper not found in window");
return;
@@ -46,42 +46,40 @@ export const HexEyedropPicker = ({
// User cancelled the picker
return null;
}
}
};
startEyeDrop().then((newColor) => {
if (newColor) {
onDropperChange?.(newColor);
}
});
}
};
const [isDragging, setIsDragging] = useState(false);
const handleOnMouseDown = () => {
setIsDragging(true);
}
const handleOnMouseUp = () => {
setIsDragging(false);
}
// const [isDragging, setIsDragging] = useState(false);
// const handleOnMouseDown = () => {
// setIsDragging(true);
// };
// const handleOnMouseUp = () => {
// setIsDragging(false);
// };
return (
<div className={"relative"}>
<Picker
className={pickerClassName}
color={color}
onChange={onPickerChange}
onMouseDown={handleOnMouseDown}
onMouseUp={handleOnMouseUp}
/>
<Button
className={twMerge([
"rounded-full m-1 size-8 text-ui-controls bg-transparent hover:bg-black/70 hover:text-white z-20 shadow-none hover:shadow-xl duration-200",
"z-20 size-8 rounded-xl bg-ui-background/30 text-white shadow-none duration-200 hover:bg-white/10 hover:text-white hover:shadow-xl",
dropperClassName,
"absolute left-0 top-0",
isDragging ? "opacity-0 -z-10" : ""
])}
icon={faEyedropper}
onClick={handleDropperClick}
/>
<Picker
className={pickerClassName}
color={color}
onChange={onPickerChange}
// onMouseDown={handleOnMouseDown}
// onMouseUp={handleOnMouseUp}
/>
</div>
)
}
);
};
@@ -91,7 +91,7 @@ export const ColorPicker = ({
if (streamChanges) {
onChange(color);
}
}
};
return (
<Popover className="relative">
@@ -124,7 +124,7 @@ export const ColorPicker = ({
anchor={anchor}
className={twMerge(
paperWrapperStyles,
"flex flex-col items-center gap-2 overflow-hidden",
"flex flex-col items-center gap-2 overflow-hidden p-4",
)}
style={
{
@@ -134,13 +134,14 @@ export const ColorPicker = ({
static
>
<HexEyedropPicker
pickerClassName="overflow-hidden"
color={currColor}
onPickerChange={handleColorChange}
onDropperChange={handleColorChange}
Picker={HexAlphaColorPicker}
/>
<Input
className="mt-2"
inputClassName="bg-ui-background/30"
style={{ width: "198px" }}
icon={faHashtag}
value={textInput}
@@ -5,13 +5,18 @@ import {
} from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
import { ChangeEvent, useEffect, useState } from "react";
import { ChangeEvent, useEffect, useState, useRef } from "react";
import { HexAlphaColorPicker } from "react-colorful";
import { twMerge } from "tailwind-merge";
import { useSignals } from "@preact/signals-react/runtime";
import { paperWrapperStyles } from "~/components/styles";
import { BRUSH_MAX_SIZE, BRUSH_MIN_SIZE, paintBrushSize, setPaintBrushSize } from "~/signals/uiEvents/toolbarMain/paintMode";
import {
BRUSH_MAX_SIZE,
BRUSH_MIN_SIZE,
paintBrushSize,
setPaintBrushSize,
} from "~/signals/uiEvents/toolbarMain/paintMode";
import { Button } from "../Button";
import { Input } from "../Input";
import { Slider } from "../Slider";
@@ -52,16 +57,58 @@ export const PaintModeMenu = ({
currColor: prevColor,
textInput: prevColor.substring(1),
});
const [isMouseDown, setIsMouseDown] = useState(false);
const [isMouseOutside, setIsMouseOutside] = useState(false);
const closeRef = useRef<(() => void) | null>(null);
const closeTimeoutRef = useRef<NodeJS.Timeout | null>(null);
useEffect(() => {
setIsOpen(defaultOpen);
}, [defaultOpen]);
// Global mouse up handler
useEffect(() => {
const handleGlobalMouseUp = () => {
if (isMouseDown) {
setIsMouseDown(false);
// If mouse is outside when released, close the panel after a short delay
if (isMouseOutside && closeOnMouseLeave && closeRef.current) {
closeTimeoutRef.current = setTimeout(() => {
onChange(currColor);
setIsOpen(false);
closeRef.current?.();
}, 500);
}
}
};
window.addEventListener("mouseup", handleGlobalMouseUp);
return () => {
window.removeEventListener("mouseup", handleGlobalMouseUp);
};
}, [isMouseDown, isMouseOutside, closeOnMouseLeave, currColor, onChange]);
const handleMouseLeave = (close: () => void) => {
if (closeOnMouseLeave) {
onChange(currColor);
setIsOpen(false);
close();
closeRef.current = close;
setIsMouseOutside(true);
// Only close if mouse is not down and closeOnMouseLeave is true
if (closeOnMouseLeave && !isMouseDown) {
closeTimeoutRef.current = setTimeout(() => {
onChange(currColor);
setIsOpen(false);
close();
}, 200);
}
};
const handleMouseEnter = () => {
setIsMouseOutside(false);
if (closeTimeoutRef.current) {
clearTimeout(closeTimeoutRef.current);
closeTimeoutRef.current = null;
}
};
@@ -92,7 +139,7 @@ export const PaintModeMenu = ({
if (streamChanges) {
onChange(color);
}
}
};
// Rerender the component when these signals change:
useSignals();
@@ -123,12 +170,16 @@ export const PaintModeMenu = ({
)}
</PopoverButton>
{(open || isOpen) && (
<div onMouseLeave={() => handleMouseLeave(close)}>
<div
onMouseLeave={() => handleMouseLeave(close)}
onMouseEnter={handleMouseEnter}
onMouseDown={() => setIsMouseDown(true)}
>
<PopoverPanel
anchor={anchor}
className={twMerge(
paperWrapperStyles,
"flex flex-col items-center gap-2 overflow-hidden",
"flex flex-col items-center gap-2 overflow-hidden p-4",
)}
style={
{
@@ -138,20 +189,23 @@ export const PaintModeMenu = ({
static
>
<HexEyedropPicker
pickerClassName="overflow-hidden"
color={currColor}
onPickerChange={handleColorChange}
onDropperChange={handleColorChange}
Picker={HexAlphaColorPicker}
/>
<Input
className="mt-2"
inputClassName="bg-ui-background/30"
style={{ width: "198px" }}
icon={faHashtag}
value={textInput}
onChange={handleTextInput}
/>
<div className="flex flex-col w-full mt-2 gap-2">
<p className="text-white font-medium text-sm w-full justify-start">Brush Size:</p>
<div className="mt-2 flex w-full flex-col gap-2">
<p className="w-full justify-start text-sm font-medium text-white">
Brush Size:
</p>
<Slider
min={BRUSH_MIN_SIZE}
max={BRUSH_MAX_SIZE}