mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-11 04:16:55 +00:00
Add shapes to global toolbar and color picker to contextual (#417)
Shapes scaffolding is fully set up, color picker isn't tied up to the nodes so it doesn't actually change the color yet
This commit is contained in:
1 parent
1e38fbad5c
commit
9d1d605f5c
13 files changed
+143
-24
No files matched your search
@@ -366,7 +366,7 @@ export class Engine {
|
||||
// Listen to Toolbar Main
|
||||
uiEvents.toolbarMain.UNDO.onClick(() => this.undoStackManager.undo());
|
||||
uiEvents.toolbarMain.REDO.onClick(() => this.undoStackManager.redo());
|
||||
|
||||
|
||||
uiEvents.toolbarMain.SAVE.onClick(async (/*event*/) => {
|
||||
await this.realTimeDrawEngine.saveOutput();
|
||||
|
||||
@@ -400,8 +400,7 @@ export class Engine {
|
||||
this.addImage(image);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
uiEvents.onAddTextToEngine((textdata) => {
|
||||
this.addText(textdata);
|
||||
});
|
||||
@@ -432,7 +431,7 @@ export class Engine {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
uiEvents.promptEvents.onPromptStrengthChanged((strength) => {
|
||||
this.realTimeDrawEngine.currentStrength = strength;
|
||||
});
|
||||
@@ -440,6 +439,11 @@ export class Engine {
|
||||
uiEvents.promptEvents.onPromptTextChanged((prompt) => {
|
||||
this.realTimeDrawEngine.currentPrompt = prompt;
|
||||
});
|
||||
|
||||
uiEvents.onAddShapeToEngine((shapeData) => {
|
||||
// TODO: Do something with shapes
|
||||
console.log("Shape Data", shapeData);
|
||||
})
|
||||
}
|
||||
|
||||
disableAllButtons() {
|
||||
@@ -475,7 +479,7 @@ export class Engine {
|
||||
}
|
||||
|
||||
// Sandbox is quickly a way to test your idea.
|
||||
public async sandbox() {}
|
||||
public async sandbox() { }
|
||||
|
||||
public onMessage(event: MessageEvent) {
|
||||
console.log("Message From Shared Worker");
|
||||
@@ -603,7 +607,7 @@ export class Engine {
|
||||
if (
|
||||
longestVideoNode === undefined ||
|
||||
node.videoComponent.duration >
|
||||
longestVideoNode.videoComponent.duration
|
||||
longestVideoNode.videoComponent.duration
|
||||
) {
|
||||
longestVideoNode = node;
|
||||
}
|
||||
|
||||
@@ -279,8 +279,10 @@ export class SelectionManager {
|
||||
}
|
||||
if (node instanceof TextNode) {
|
||||
showOrUpdate({
|
||||
// TODO: Add ID to identify/mutate the color?
|
||||
locked: node.isLocked(),
|
||||
buttonStates: getTextNodeButtonStates({ locked: node.isLocked() }),
|
||||
color: node.getNodeData({ x: 0, y: 0 }).textNodeData?.color ?? "#000000",
|
||||
});
|
||||
}
|
||||
if (node instanceof VideoNode) {
|
||||
|
||||
@@ -51,7 +51,8 @@ export function getImageNodeButtonStates(
|
||||
buttonName === ButtonNames.AI_STYLIZE ||
|
||||
buttonName === ButtonNames.SEGMENTATION ||
|
||||
buttonName === ButtonNames.DOWNLOAD ||
|
||||
buttonName === ButtonNames.CHROMA
|
||||
buttonName === ButtonNames.CHROMA ||
|
||||
buttonName === ButtonNames.COLOR
|
||||
) {
|
||||
buttonStates[buttonName] = {
|
||||
disabled: true,
|
||||
@@ -130,7 +131,9 @@ export function getVideoNodeButtonStates(
|
||||
return buttonStates;
|
||||
}
|
||||
// soon to come feature is disabled
|
||||
if (buttonName === ButtonNames.AI_STYLIZE) {
|
||||
if (buttonName === ButtonNames.AI_STYLIZE ||
|
||||
buttonName === ButtonNames.COLOR
|
||||
) {
|
||||
buttonStates[buttonName] = {
|
||||
disabled: true,
|
||||
hidden: true,
|
||||
@@ -170,7 +173,8 @@ export function getMultiSelectButtonStates(
|
||||
buttonName === ButtonNames.AI_STYLIZE ||
|
||||
buttonName === ButtonNames.SEGMENTATION ||
|
||||
buttonName === ButtonNames.DOWNLOAD ||
|
||||
buttonName === ButtonNames.CHROMA
|
||||
buttonName === ButtonNames.CHROMA ||
|
||||
buttonName === ButtonNames.COLOR
|
||||
) {
|
||||
buttonStates[buttonName] = {
|
||||
disabled: true,
|
||||
|
||||
@@ -71,6 +71,8 @@ export const ContextualToolbarNode = () => {
|
||||
onLockClicked={(e) => {
|
||||
dispatchUiEvents.toolbarNode.lock(e);
|
||||
}}
|
||||
color={uiAccess.toolbarNode.signal.value.color}
|
||||
onColorChange={(color) => { uiAccess.toolbarNode.setColor(color) }}
|
||||
/>
|
||||
</Transition>
|
||||
);
|
||||
|
||||
@@ -12,6 +12,7 @@ import { LayoutSignalType } from "./contextSignals/layout";
|
||||
import { AppUiContextInterface } from "./contextSignals/appUi";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { paperWrapperStyles } from "~/components/styles";
|
||||
import { dispatchUiEvents } from "~/signals";
|
||||
|
||||
export const SignaledToolbarMain = ({
|
||||
// layoutSignal,
|
||||
@@ -43,6 +44,12 @@ export const SignaledToolbarMain = ({
|
||||
return () => appUiContext.openAddImage();
|
||||
case ToolbarMainButtonNames.ADD_VIDEO:
|
||||
return () => appUiContext.openAddVideo();
|
||||
case ToolbarMainButtonNames.ADD_CIRCLE:
|
||||
return () => dispatchUiEvents.addShapeToEngine({ shape: "circle" });
|
||||
case ToolbarMainButtonNames.ADD_TRIANGLE:
|
||||
return () => dispatchUiEvents.addShapeToEngine({ shape: "triangle" });
|
||||
case ToolbarMainButtonNames.ADD_SQUARE:
|
||||
return () => dispatchUiEvents.addShapeToEngine({ shape: "square" });
|
||||
default:
|
||||
return dispatchers[buttonName];
|
||||
}
|
||||
|
||||
@@ -5,13 +5,17 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faArrowRotateLeft,
|
||||
faArrowRotateRight,
|
||||
faCircle,
|
||||
faFilePlus,
|
||||
faFilm,
|
||||
faFloppyDisk,
|
||||
faImage,
|
||||
faLocationArrow,
|
||||
faMagicWandSparkles,
|
||||
faShapes,
|
||||
faSquare,
|
||||
faText,
|
||||
faTriangle,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
import { ToolbarButton } from "../ToolbarButton";
|
||||
@@ -41,7 +45,7 @@ export const ToolbarMain = ({
|
||||
"flex w-fit items-center divide-x divide-ui-border",
|
||||
paperWrapperStyles,
|
||||
disabled &&
|
||||
"pointer-events-none cursor-default bg-ui-border shadow-md",
|
||||
"pointer-events-none cursor-default bg-ui-border shadow-md",
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2 pr-2">
|
||||
@@ -56,6 +60,33 @@ export const ToolbarMain = ({
|
||||
buttonProps={buttonProps.ADD_TEXT}
|
||||
tooltip="Add Text"
|
||||
/>
|
||||
|
||||
<Popover className="relative">
|
||||
<PopoverButton as={Fragment}>
|
||||
<button
|
||||
data-tooltip="Add Shapes"
|
||||
className={twMerge(
|
||||
"size-10 rounded-lg p-2 hover:bg-gray-200/50",
|
||||
toolTipStyles,
|
||||
)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faShapes} />
|
||||
</button>
|
||||
</PopoverButton>
|
||||
<PopoverPanel
|
||||
anchor="bottom"
|
||||
className={twMerge(
|
||||
// "absolute bottom-full left-1/2 z-10 mb-2 -translate-x-1/2",
|
||||
"flex flex-col [--anchor-gap:16px]",
|
||||
paperWrapperStyles,
|
||||
)}
|
||||
>
|
||||
<ToolbarButton icon={faSquare} buttonProps={buttonProps.ADD_SQUARE} />
|
||||
<ToolbarButton icon={faCircle} buttonProps={buttonProps.ADD_CIRCLE} />
|
||||
<ToolbarButton icon={faTriangle} buttonProps={buttonProps.ADD_TRIANGLE} />
|
||||
</PopoverPanel>
|
||||
</Popover>
|
||||
|
||||
<Popover className="relative">
|
||||
<PopoverButton as={Fragment}>
|
||||
<button
|
||||
|
||||
@@ -4,6 +4,9 @@ export enum ToolbarMainButtonNames {
|
||||
ADD_TEXT = "ADD_TEXT",
|
||||
ADD_IMAGE = "ADD_IMAGE",
|
||||
ADD_VIDEO = "ADD_VIDEO",
|
||||
ADD_CIRCLE = "ADD_CIRCLE",
|
||||
ADD_TRIANGLE = "ADD_TRIANGLE",
|
||||
ADD_SQUARE = "ADD_SQUARE",
|
||||
AI_STYLIZE = "AI_STYLIZE",
|
||||
UNDO = "UNDO",
|
||||
REDO = "REDO",
|
||||
|
||||
@@ -4,14 +4,19 @@ import {
|
||||
faLockKeyholeOpen,
|
||||
faLockKeyhole,
|
||||
faUnlockKeyhole,
|
||||
faPalette,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import {
|
||||
ToolbarButton,
|
||||
ToolbarButtonProps,
|
||||
} from "~/components/features/ToolbarButton";
|
||||
import { paperWrapperStyles } from "~/components/styles";
|
||||
import { paperWrapperStyles, toolTipStyles } from "~/components/styles";
|
||||
import { ToolbarNodeButtonNames } from "./enums";
|
||||
import { ToolbarNodeButtonData } from "./data";
|
||||
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
|
||||
import { Fragment } from "react/jsx-runtime";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { ColorPicker } from "~/components/ui/TextEditor/ColorPicker";
|
||||
|
||||
export interface ToolbarNodeProps {
|
||||
disabled?: boolean;
|
||||
@@ -24,6 +29,8 @@ export interface ToolbarNodeProps {
|
||||
buttonsProps?: {
|
||||
[key in ToolbarNodeButtonNames]: ToolbarButtonProps;
|
||||
};
|
||||
color?: string;
|
||||
onColorChange?: (color: string) => void;
|
||||
}
|
||||
export const ToolbarNode = ({
|
||||
disabled,
|
||||
@@ -31,6 +38,8 @@ export const ToolbarNode = ({
|
||||
lockDisabled,
|
||||
onLockClicked,
|
||||
buttonsProps,
|
||||
color = "#000000",
|
||||
onColorChange = () => { },
|
||||
}: ToolbarNodeProps) => {
|
||||
const handleOnLockClicked: React.MouseEventHandler<HTMLButtonElement> = (
|
||||
e,
|
||||
@@ -53,7 +62,7 @@ export const ToolbarNode = ({
|
||||
className: twMerge(
|
||||
locked && "text-primary hover:bg-primary hover:text-white",
|
||||
lockDisabledOrUnknown &&
|
||||
"text-secondary-300 hover:text-secondary-300",
|
||||
"text-secondary-300 hover:text-secondary-300",
|
||||
),
|
||||
disabled: lockDisabledOrUnknown,
|
||||
}}
|
||||
@@ -73,14 +82,24 @@ export const ToolbarNode = ({
|
||||
{ToolbarNodeButtonData.map((buttonDatum, idx) => {
|
||||
const buttonProps = buttonsProps?.[buttonDatum.name];
|
||||
|
||||
return (
|
||||
<ToolbarButton
|
||||
icon={buttonDatum.icon}
|
||||
tooltip={buttonDatum.tooltip}
|
||||
key={idx}
|
||||
buttonProps={buttonProps}
|
||||
/>
|
||||
);
|
||||
if (!buttonProps || buttonProps.hidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (buttonDatum.name !== ToolbarNodeButtonNames.COLOR) {
|
||||
return (
|
||||
<ToolbarButton
|
||||
icon={buttonDatum.icon}
|
||||
tooltip={buttonDatum.tooltip}
|
||||
key={idx}
|
||||
buttonProps={buttonProps}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<ColorPicker color={color} onChange={onColorChange} faIcon={faPalette} borderStyle="" />
|
||||
)
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
faTrashCan,
|
||||
faVectorSquare,
|
||||
faDownload,
|
||||
faPalette,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
export const ToolbarNodeButtonData = [
|
||||
@@ -16,6 +17,11 @@ export const ToolbarNodeButtonData = [
|
||||
icon: faVectorSquare,
|
||||
tooltip: "Move",
|
||||
},
|
||||
{
|
||||
name: ButtonNames.COLOR,
|
||||
icon: faPalette,
|
||||
tooltip: "Color",
|
||||
},
|
||||
{
|
||||
name: ButtonNames.CHROMA,
|
||||
icon: faTransporter,
|
||||
|
||||
@@ -7,4 +7,5 @@ export enum ToolbarNodeButtonNames {
|
||||
MOVE_LAYER_UP = "MOVE_LAYER_UP",
|
||||
MOVE_LAYER_DOWN = "MOVE_LAYER_DOWN",
|
||||
DOWNLOAD = "DOWNLOAD",
|
||||
COLOR = "COLOR",
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ChangeEvent, useState } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { faFont, faHashtag } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { faFont, faHashtag, IconDefinition } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
|
||||
import { HexColorPicker } from "react-colorful";
|
||||
@@ -12,9 +12,15 @@ import { paperWrapperStyles } from "~/components/styles";
|
||||
export const ColorPicker = ({
|
||||
color: prevColor,
|
||||
onChange,
|
||||
faIcon = faFont,
|
||||
borderStyle = "border",
|
||||
showBar = true,
|
||||
}: {
|
||||
color: string;
|
||||
onChange: (newColor: string) => void;
|
||||
faIcon?: IconDefinition;
|
||||
borderStyle?: string;
|
||||
showBar?: boolean;
|
||||
}) => {
|
||||
const [{ currColor, textInput }, setStates] = useState<{
|
||||
currColor: string;
|
||||
@@ -44,11 +50,15 @@ export const ColorPicker = ({
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover className="relative">
|
||||
<PopoverButton className="flex size-10 flex-col items-center gap-1 rounded-md border p-2">
|
||||
<FontAwesomeIcon icon={faFont} />
|
||||
<span className="h-1 w-full" style={{ backgroundColor: prevColor }} />
|
||||
<PopoverButton className={twMerge(
|
||||
"flex size-10 flex-col items-center gap-1 rounded-md p-2",
|
||||
borderStyle
|
||||
)}>
|
||||
<FontAwesomeIcon icon={faIcon} color={prevColor} />
|
||||
{showBar && <span className="h-1 w-full" style={{ backgroundColor: prevColor }} />}
|
||||
</PopoverButton>
|
||||
<PopoverPanel
|
||||
anchor="bottom"
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface ContextualToolbarProps extends ContextualUi {
|
||||
locked: boolean | "unknown";
|
||||
lockDisabled: boolean;
|
||||
buttonStates: ButtonStates;
|
||||
color: string;
|
||||
}
|
||||
interface PartialContextualToolbarProps
|
||||
extends Partial<Omit<ContextualToolbarProps, "buttonStates">> {
|
||||
@@ -32,6 +33,7 @@ const toolbarNodeSignal = signal<ContextualToolbarProps>({
|
||||
locked: false,
|
||||
lockDisabled: false,
|
||||
buttonStates: initButtonStates(),
|
||||
color: "#000000",
|
||||
});
|
||||
|
||||
export const toolbarNode = {
|
||||
@@ -77,6 +79,11 @@ export const toolbarNode = {
|
||||
hidden: false,
|
||||
active: buttonStates[ButtonNames.TRANSFORM].active,
|
||||
},
|
||||
[ButtonNames.COLOR]: {
|
||||
disabled: locked,
|
||||
hidden: false,
|
||||
active: buttonStates[ButtonNames.COLOR].active,
|
||||
},
|
||||
},
|
||||
locked: locked,
|
||||
};
|
||||
@@ -135,6 +142,12 @@ export const toolbarNode = {
|
||||
},
|
||||
};
|
||||
},
|
||||
setColor(color: string) {
|
||||
toolbarNodeSignal.value = {
|
||||
...toolbarNodeSignal.value,
|
||||
color,
|
||||
}
|
||||
},
|
||||
batchButtonStates({ buttonStates }: { buttonStates: Partial<ButtonStates> }) {
|
||||
const prevButtonStates = toolbarNodeSignal.value.buttonStates;
|
||||
toolbarNodeSignal.value = {
|
||||
|
||||
@@ -49,15 +49,32 @@ const onAddTextToEngine = (callback: (data: TextNodeData) => void) => {
|
||||
});
|
||||
};
|
||||
|
||||
// Add shapes
|
||||
// TODO: Remove this line and replace with actual shape data that engine needs
|
||||
type ShapeNodeData = any;
|
||||
const stagedShape = signal<ShapeNodeData | null>(null);
|
||||
const addShapeToEngine = (data: ShapeNodeData) => {
|
||||
stagedShape.value = data;
|
||||
}
|
||||
const onAddShapeToEngine = (callback: (data: ShapeNodeData) => void) => {
|
||||
effect(() => {
|
||||
if (stagedShape.value) {
|
||||
callback(stagedShape.value);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
//EXPORTS
|
||||
export const dispatchers = {
|
||||
addImageToEngine,
|
||||
addVideoToEngine,
|
||||
addTextToEngine,
|
||||
addShapeToEngine,
|
||||
};
|
||||
|
||||
export const events = {
|
||||
onGetStagedImage,
|
||||
onGetStagedVideo,
|
||||
onAddTextToEngine,
|
||||
onAddShapeToEngine,
|
||||
};
|
||||
Reference in new issue
Block a user