mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-24 10:39:28 -04:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
036bc3d5a6 |
@@ -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();
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user