mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-20 17:02:53 -04:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12bc70bac7 |
@@ -5,6 +5,7 @@ import {
|
||||
faEraser,
|
||||
faFilePlus,
|
||||
faImage,
|
||||
faImages,
|
||||
faLocationArrow,
|
||||
faPaintbrush,
|
||||
faShapes,
|
||||
@@ -27,6 +28,8 @@ import { paintColor } from "~/signals/uiEvents/toolbarMain/paintMode";
|
||||
import { ColorPicker } from "~/components/ui/TextEditor/ColorPicker";
|
||||
import { PaintModeMenu } from "~/components/ui/ToolbarMain/PaintModeMenu";
|
||||
import { EraseModeMenu } from "~/components/ui/ToolbarMain/EraseModeMenu";
|
||||
import { BackgroundMenu } from "~/components/ui/ToolbarMain/BackgroundMenu";
|
||||
import { bgColor } from "~/signals/uiEvents/toolbarMain/backgroundMenu";
|
||||
|
||||
export const ToolbarMain = ({
|
||||
disabled = false,
|
||||
@@ -226,6 +229,31 @@ export const ToolbarMain = ({
|
||||
tooltip="Save"
|
||||
/> */}
|
||||
</div>
|
||||
|
||||
<hr className="w-full border-t border-white/15" />
|
||||
|
||||
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<div className="relative">
|
||||
<BackgroundMenu
|
||||
color={bgColor.value}
|
||||
onChange={dispatchUiEvents.toolbarMain.setBgColor}
|
||||
faIcon={faImage}
|
||||
borderStyle="bg-transparent hover:bg-white/15 text-white"
|
||||
showBar={false}
|
||||
staticIconColor="white"
|
||||
streamChanges
|
||||
defaultOpen={false}
|
||||
anchor="right"
|
||||
anchorGap={12}
|
||||
closeOnMouseLeave={true}
|
||||
/>
|
||||
<div
|
||||
className="pointer-events-none absolute -bottom-1.5 -right-1.5 h-[18px] w-[18px] rounded-full border border-gray-400"
|
||||
style={{ backgroundColor: bgColor.value }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -15,4 +15,5 @@ export enum ToolbarMainButtonNames {
|
||||
SAVE = "SAVE",
|
||||
PREVIEW = "PREVIEW",
|
||||
DOWNLOAD = "DOWNLOAD",
|
||||
BACKGROUND = "BACKGROUND",
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
import {
|
||||
faFont,
|
||||
faHashtag,
|
||||
IconDefinition,
|
||||
} 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 { HexColorPicker } 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 { Button } from "../Button";
|
||||
import { Input } from "../Input";
|
||||
import { Slider } from "../Slider";
|
||||
|
||||
export const BackgroundMenu = ({
|
||||
color: prevColor,
|
||||
onChange,
|
||||
faIcon = faFont,
|
||||
borderStyle,
|
||||
showBar = true,
|
||||
anchor = "bottom",
|
||||
anchorGap = 0,
|
||||
fillBg = false,
|
||||
staticIconColor,
|
||||
streamChanges = false,
|
||||
defaultOpen = false,
|
||||
closeOnMouseLeave = false,
|
||||
}: {
|
||||
color: string;
|
||||
onChange: (newColor: string) => void;
|
||||
faIcon?: IconDefinition;
|
||||
borderStyle?: string;
|
||||
showBar?: boolean;
|
||||
anchor?: "top" | "right" | "bottom" | "left";
|
||||
anchorGap?: number;
|
||||
fillBg?: boolean;
|
||||
staticIconColor?: string;
|
||||
streamChanges?: boolean;
|
||||
defaultOpen?: boolean;
|
||||
closeOnMouseLeave?: boolean;
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(defaultOpen);
|
||||
const [{ currColor, textInput }, setStates] = useState<{
|
||||
currColor: string;
|
||||
textInput: string;
|
||||
}>({
|
||||
currColor: prevColor,
|
||||
textInput: prevColor.substring(1),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setIsOpen(defaultOpen);
|
||||
}, [defaultOpen]);
|
||||
|
||||
const handleMouseLeave = (close: () => void) => {
|
||||
if (closeOnMouseLeave) {
|
||||
onChange(currColor);
|
||||
setIsOpen(false);
|
||||
close();
|
||||
}
|
||||
};
|
||||
|
||||
const handleTextInput = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
if (isHexColorCode(e.target.value)) {
|
||||
setStates({
|
||||
currColor: `#${e.target.value}`,
|
||||
textInput: e.target.value,
|
||||
});
|
||||
} else {
|
||||
setStates((curr) => ({
|
||||
...curr,
|
||||
textInput: e.target.value,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const isHexColorCode = (color: string) => {
|
||||
const hexColorRegex = /^(?:[0-9a-fA-F]{3}){1,2}$/;
|
||||
return hexColorRegex.test(color);
|
||||
};
|
||||
|
||||
// Rerender the component when these signals change:
|
||||
useSignals();
|
||||
|
||||
return (
|
||||
<Popover className="relative">
|
||||
{({ open, close }) => (
|
||||
<>
|
||||
<PopoverButton
|
||||
className={twMerge(
|
||||
"flex size-10 items-center gap-1 rounded-lg bg-ui-controls p-2",
|
||||
borderStyle,
|
||||
showBar ? "flex-col" : "justify-center",
|
||||
)}
|
||||
style={fillBg ? { backgroundColor: prevColor } : {}}
|
||||
onMouseEnter={() => defaultOpen && setIsOpen(true)}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faIcon}
|
||||
color={staticIconColor ?? (fillBg ? undefined : prevColor)}
|
||||
/>
|
||||
{showBar && (
|
||||
<span
|
||||
className="h-1 w-full"
|
||||
style={{ backgroundColor: prevColor }}
|
||||
/>
|
||||
)}
|
||||
</PopoverButton>
|
||||
{(open || isOpen) && (
|
||||
<div onMouseLeave={() => handleMouseLeave(close)}>
|
||||
<PopoverPanel
|
||||
anchor={anchor}
|
||||
className={twMerge(
|
||||
paperWrapperStyles,
|
||||
"flex flex-col items-center gap-2 overflow-hidden",
|
||||
)}
|
||||
style={
|
||||
{
|
||||
"--anchor-gap": `${anchorGap}px`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
static
|
||||
>
|
||||
<HexColorPicker
|
||||
className="overflow-hidden"
|
||||
color={currColor}
|
||||
onChange={(color) => {
|
||||
setStates({
|
||||
currColor: color,
|
||||
textInput: color.substring(1),
|
||||
});
|
||||
if (streamChanges) {
|
||||
onChange(color);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
style={{ width: "198px" }}
|
||||
icon={faHashtag}
|
||||
value={textInput}
|
||||
onChange={handleTextInput}
|
||||
/>
|
||||
{!streamChanges && (
|
||||
<div className="flex w-full justify-center gap-2">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
setStates({
|
||||
currColor: prevColor,
|
||||
textInput: prevColor.substring(1),
|
||||
});
|
||||
setIsOpen(false);
|
||||
close();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
onChange(currColor);
|
||||
setIsOpen(false);
|
||||
close();
|
||||
}}
|
||||
>
|
||||
OK
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</PopoverPanel>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
import { signal, effect } from "@preact/signals-react";
|
||||
|
||||
export const DEFAULT_BG_COLOR: string = "#ffffff";
|
||||
export const bgColor = signal<string>(DEFAULT_BG_COLOR);
|
||||
export const setBgColor = (data: string) => {
|
||||
bgColor.value = data;
|
||||
};
|
||||
|
||||
export const onBgColorChanged = (callback: (data: string) => void) => {
|
||||
effect(() => {
|
||||
if (bgColor.value) {
|
||||
callback(bgColor.value);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -5,16 +5,19 @@ import {
|
||||
} from "./loadingBar";
|
||||
import { onEraseBrushSizeChanged, setEraseBrushSize } from "./eraseMode";
|
||||
import { onPaintColorChanged, setPaintBrushSize, setPaintColor } from "./paintMode";
|
||||
import { onBgColorChanged, setBgColor } from "./backgroundMenu";
|
||||
export const dispatchers = {
|
||||
...buttonDispatchers,
|
||||
loadingBarRetry: loadingBarRetryDispatch,
|
||||
setPaintColor,
|
||||
setPaintBrushSize,
|
||||
setEraseBrushSize
|
||||
setEraseBrushSize,
|
||||
setBgColor
|
||||
};
|
||||
export const eventsHandlers = {
|
||||
...buttonEventsHandlers,
|
||||
loadingBarRetry: loadingBarRetryEventHandler,
|
||||
onPaintColorChanged,
|
||||
onEraseBrushSizeChanged
|
||||
onEraseBrushSizeChanged,
|
||||
onBgColorChanged
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user