From 9d1d605f5c1fe09f73da8afb475880e12fbdd7bc Mon Sep 17 00:00:00 2001 From: Tanish Manku Date: Wed, 19 Feb 2025 18:55:06 -0800 Subject: [PATCH] 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 --- frontend/realtime/src/KonvaApp/Engine.ts | 16 +++++--- .../NodesManagers/SelectionManager.ts | 2 + .../NodesManagers/ToolbarNodeUtilities.ts | 10 +++-- .../ContextualToolbarNode.tsx | 2 + .../SignaledToolbarMain.tsx | 7 ++++ .../features/ToolbarMain/ToolbarMain.tsx | 33 +++++++++++++++- .../components/features/ToolbarMain/enum.ts | 3 ++ .../features/ToolbarNode/ToolbarNode.tsx | 39 ++++++++++++++----- .../components/features/ToolbarNode/data.ts | 6 +++ .../components/features/ToolbarNode/enums.ts | 1 + .../components/ui/TextEditor/ColorPicker.tsx | 18 +++++++-- .../src/signals/uiAccess/toolbarNode.ts | 13 +++++++ .../realtime/src/signals/uiEvents/addMedia.ts | 17 ++++++++ 13 files changed, 143 insertions(+), 24 deletions(-) diff --git a/frontend/realtime/src/KonvaApp/Engine.ts b/frontend/realtime/src/KonvaApp/Engine.ts index f0d1fd182c..7be2a9ce05 100644 --- a/frontend/realtime/src/KonvaApp/Engine.ts +++ b/frontend/realtime/src/KonvaApp/Engine.ts @@ -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; } diff --git a/frontend/realtime/src/KonvaApp/NodesManagers/SelectionManager.ts b/frontend/realtime/src/KonvaApp/NodesManagers/SelectionManager.ts index b903add420..8a7858b66e 100644 --- a/frontend/realtime/src/KonvaApp/NodesManagers/SelectionManager.ts +++ b/frontend/realtime/src/KonvaApp/NodesManagers/SelectionManager.ts @@ -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) { diff --git a/frontend/realtime/src/KonvaApp/NodesManagers/ToolbarNodeUtilities.ts b/frontend/realtime/src/KonvaApp/NodesManagers/ToolbarNodeUtilities.ts index 8a33cb2611..9b82c82d5d 100644 --- a/frontend/realtime/src/KonvaApp/NodesManagers/ToolbarNodeUtilities.ts +++ b/frontend/realtime/src/KonvaApp/NodesManagers/ToolbarNodeUtilities.ts @@ -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, diff --git a/frontend/realtime/src/KonvaRootComponent/ContextualToolbarNode.tsx b/frontend/realtime/src/KonvaRootComponent/ContextualToolbarNode.tsx index 5a362ade64..16d32a0c0d 100644 --- a/frontend/realtime/src/KonvaRootComponent/ContextualToolbarNode.tsx +++ b/frontend/realtime/src/KonvaRootComponent/ContextualToolbarNode.tsx @@ -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) }} /> ); diff --git a/frontend/realtime/src/KonvaRootComponent/SignaledToolbarMain.tsx b/frontend/realtime/src/KonvaRootComponent/SignaledToolbarMain.tsx index b9a546eb6d..633687fcb7 100644 --- a/frontend/realtime/src/KonvaRootComponent/SignaledToolbarMain.tsx +++ b/frontend/realtime/src/KonvaRootComponent/SignaledToolbarMain.tsx @@ -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]; } diff --git a/frontend/realtime/src/components/features/ToolbarMain/ToolbarMain.tsx b/frontend/realtime/src/components/features/ToolbarMain/ToolbarMain.tsx index 28e1401756..ef88aed47a 100644 --- a/frontend/realtime/src/components/features/ToolbarMain/ToolbarMain.tsx +++ b/frontend/realtime/src/components/features/ToolbarMain/ToolbarMain.tsx @@ -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", )} >
@@ -56,6 +60,33 @@ export const ToolbarMain = ({ buttonProps={buttonProps.ADD_TEXT} tooltip="Add Text" /> + + + + + + + + + + + +
); diff --git a/frontend/realtime/src/components/features/ToolbarNode/data.ts b/frontend/realtime/src/components/features/ToolbarNode/data.ts index 4cda8019ac..c215e5ed8b 100644 --- a/frontend/realtime/src/components/features/ToolbarNode/data.ts +++ b/frontend/realtime/src/components/features/ToolbarNode/data.ts @@ -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, diff --git a/frontend/realtime/src/components/features/ToolbarNode/enums.ts b/frontend/realtime/src/components/features/ToolbarNode/enums.ts index a79f621f31..e4c559695a 100644 --- a/frontend/realtime/src/components/features/ToolbarNode/enums.ts +++ b/frontend/realtime/src/components/features/ToolbarNode/enums.ts @@ -7,4 +7,5 @@ export enum ToolbarNodeButtonNames { MOVE_LAYER_UP = "MOVE_LAYER_UP", MOVE_LAYER_DOWN = "MOVE_LAYER_DOWN", DOWNLOAD = "DOWNLOAD", + COLOR = "COLOR", } diff --git a/frontend/realtime/src/components/ui/TextEditor/ColorPicker.tsx b/frontend/realtime/src/components/ui/TextEditor/ColorPicker.tsx index 2cdf7a0d6e..c56663236b 100644 --- a/frontend/realtime/src/components/ui/TextEditor/ColorPicker.tsx +++ b/frontend/realtime/src/components/ui/TextEditor/ColorPicker.tsx @@ -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 ( - - - + + + {showBar && } > { @@ -32,6 +33,7 @@ const toolbarNodeSignal = signal({ 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 }) { const prevButtonStates = toolbarNodeSignal.value.buttonStates; toolbarNodeSignal.value = { diff --git a/frontend/realtime/src/signals/uiEvents/addMedia.ts b/frontend/realtime/src/signals/uiEvents/addMedia.ts index e2d09e2757..c546061025 100644 --- a/frontend/realtime/src/signals/uiEvents/addMedia.ts +++ b/frontend/realtime/src/signals/uiEvents/addMedia.ts @@ -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(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, };