From 9d1d605f5c1fe09f73da8afb475880e12fbdd7bc Mon Sep 17 00:00:00 2001 From: Tanish Manku Date: Wed, 19 Feb 2025 18:55:06 -0800 Subject: [PATCH 1/4] 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, }; From 2d8245cd557d530c7caf3dd6c1241148133a203a Mon Sep 17 00:00:00 2001 From: Michael Chung <6252325+ArEnSc@users.noreply.github.com> Date: Wed, 19 Feb 2025 22:29:31 -0500 Subject: [PATCH 2/4] Added Shapes. Removed Extra buttons. --- frontend/realtime/src/KonvaApp/Engine.ts | 34 ++++++++++++------- .../features/ToolbarMain/ToolbarMain.tsx | 4 +-- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/frontend/realtime/src/KonvaApp/Engine.ts b/frontend/realtime/src/KonvaApp/Engine.ts index 7be2a9ce05..a31106fbe0 100644 --- a/frontend/realtime/src/KonvaApp/Engine.ts +++ b/frontend/realtime/src/KonvaApp/Engine.ts @@ -180,10 +180,8 @@ export class Engine { this.setupEventSystem(); this.setAppMode(AppModes.SELECT); - - this.addShape(ShapeType.Circle,100); - this.addShape(ShapeType.Square,100); - this.addShape(ShapeType.Triangle,100); + + } @@ -366,7 +364,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,6 +398,7 @@ export class Engine { this.addImage(image); }); + uiEvents.onAddTextToEngine((textdata) => { this.addText(textdata); @@ -431,7 +430,7 @@ export class Engine { } } }); - + uiEvents.promptEvents.onPromptStrengthChanged((strength) => { this.realTimeDrawEngine.currentStrength = strength; }); @@ -440,10 +439,21 @@ export class Engine { this.realTimeDrawEngine.currentPrompt = prompt; }); - uiEvents.onAddShapeToEngine((shapeData) => { - // TODO: Do something with shapes - console.log("Shape Data", shapeData); - }) + uiEvents.onAddShapeToEngine((shapeData)=> { + + switch (shapeData.shape) { + case "circle": + this.addShape(ShapeType.Circle, 100); + break; + case "square": + this.addShape(ShapeType.Square, 100); + break; + case "triangle": + this.addShape(ShapeType.Triangle, 100); + break; + } + }); + } disableAllButtons() { @@ -479,7 +489,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"); @@ -607,7 +617,7 @@ export class Engine { if ( longestVideoNode === undefined || node.videoComponent.duration > - longestVideoNode.videoComponent.duration + longestVideoNode.videoComponent.duration ) { longestVideoNode = node; } diff --git a/frontend/realtime/src/components/features/ToolbarMain/ToolbarMain.tsx b/frontend/realtime/src/components/features/ToolbarMain/ToolbarMain.tsx index ef88aed47a..fd87e3d8d3 100644 --- a/frontend/realtime/src/components/features/ToolbarMain/ToolbarMain.tsx +++ b/frontend/realtime/src/components/features/ToolbarMain/ToolbarMain.tsx @@ -110,9 +110,9 @@ export const ToolbarMain = ({ Add Image - + {/* Add Video - + */} From f9c966003ae29d7a7819a5a09cef3fe621e3c539 Mon Sep 17 00:00:00 2001 From: Michael Chung <6252325+ArEnSc@users.noreply.github.com> Date: Wed, 19 Feb 2025 23:18:28 -0500 Subject: [PATCH 3/4] Fixed issue with shapes not having the context menu component. --- frontend/realtime/src/KonvaApp/Nodes/ShapeNode.ts | 9 ++++----- .../src/KonvaApp/NodesManagers/SelectionManager.ts | 6 +++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/frontend/realtime/src/KonvaApp/Nodes/ShapeNode.ts b/frontend/realtime/src/KonvaApp/Nodes/ShapeNode.ts index 6e0b7520c5..0f0d7e73ea 100644 --- a/frontend/realtime/src/KonvaApp/Nodes/ShapeNode.ts +++ b/frontend/realtime/src/KonvaApp/Nodes/ShapeNode.ts @@ -4,6 +4,7 @@ import { Position, Size, NodeData, TransformationData } from "../types"; import { BaseNode } from "./BaseNode"; import { NodeType } from "./constants"; import { NodeUtilities } from "./NodeUtilities"; +import { NetworkedNode } from "./NetworkedNode"; export enum ShapeType { CIRCLE = "circle", @@ -22,8 +23,7 @@ interface ShapeNodeConstructor { selectionManagerRef: SelectionManager; } -export class ShapeNode extends BaseNode { - public kNode: Konva.Group; +export class ShapeNode extends NetworkedNode { constructor({ canvasPosition, @@ -41,7 +41,9 @@ export class ShapeNode extends BaseNode { canvasPosition, canvasSize, }); + let shape = null; + switch (shapeType) { case ShapeType.CIRCLE: const circleShape = new Konva.Circle({ @@ -157,9 +159,6 @@ export class ShapeNode extends BaseNode { kNode: shape, }); - this.kNode = shape; - this.shapeType = shapeType; - // Add shape to group const centerPosition = NodeUtilities.positionNodeOnCanvasCenter({ canvasOffset: canvasPosition, diff --git a/frontend/realtime/src/KonvaApp/NodesManagers/SelectionManager.ts b/frontend/realtime/src/KonvaApp/NodesManagers/SelectionManager.ts index 8a7858b66e..8e4ffc1ed0 100644 --- a/frontend/realtime/src/KonvaApp/NodesManagers/SelectionManager.ts +++ b/frontend/realtime/src/KonvaApp/NodesManagers/SelectionManager.ts @@ -4,7 +4,7 @@ import { MediaNode, Position, Transformation } from "../types"; import { uiAccess, uiEvents } from "~/signals"; import { NetworkedNode } from "../Nodes/NetworkedNode"; import { LoadingBarStatus } from "~/components/ui"; -import { ImageNode, TextNode, VideoNode } from "../Nodes"; +import { ImageNode, ShapeNode, TextNode, VideoNode } from "../Nodes"; import { calculateContextualsPosition, getImageNodeButtonStates, @@ -271,7 +271,7 @@ export class SelectionManager { return; // no node } if (this.selectedNodes.size === 1) { - if (node instanceof ImageNode) { + if (node instanceof ImageNode || node instanceof ShapeNode) { showOrUpdate({ locked: node.isLocked(), buttonStates: getImageNodeButtonStates({ locked: node.isLocked() }), @@ -316,7 +316,7 @@ export class SelectionManager { this.hideContextComponents(); return; // no nodes } - + // console.log("SelectionManager > updateContextComponents for node:", node); const coord = calculateContextualsPosition( this.nodeTransformerRef.getKonvaNode(), From 1bca478621b35d68c2d5d800c2563207add80905 Mon Sep 17 00:00:00 2001 From: Michael Chung <6252325+ArEnSc@users.noreply.github.com> Date: Wed, 19 Feb 2025 23:56:45 -0500 Subject: [PATCH 4/4] Move color picker into the correct toolbar --- frontend/realtime/src/KonvaApp/Engine.ts | 2 +- .../realtime/src/KonvaApp/NodesManagers/SelectionManager.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/realtime/src/KonvaApp/Engine.ts b/frontend/realtime/src/KonvaApp/Engine.ts index a31106fbe0..c9d19facfb 100644 --- a/frontend/realtime/src/KonvaApp/Engine.ts +++ b/frontend/realtime/src/KonvaApp/Engine.ts @@ -453,7 +453,7 @@ export class Engine { break; } }); - + } disableAllButtons() { diff --git a/frontend/realtime/src/KonvaApp/NodesManagers/SelectionManager.ts b/frontend/realtime/src/KonvaApp/NodesManagers/SelectionManager.ts index 8e4ffc1ed0..999044072b 100644 --- a/frontend/realtime/src/KonvaApp/NodesManagers/SelectionManager.ts +++ b/frontend/realtime/src/KonvaApp/NodesManagers/SelectionManager.ts @@ -275,6 +275,7 @@ export class SelectionManager { showOrUpdate({ locked: node.isLocked(), buttonStates: getImageNodeButtonStates({ locked: node.isLocked() }), + color: node.getNodeData({ x: 0, y: 0 })?.textNodeData?.color ?? "#000000", }); } if (node instanceof TextNode) { @@ -282,7 +283,7 @@ export class SelectionManager { // 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) {