Compare commits

...
10 changed files with 137 additions and 23 deletions
+25 -11
View File
@@ -28,6 +28,7 @@ import { LoadingVideosProvider } from "./EngineUtitlities/LoadingVideosProvider"
import { VideoExtractionHandler } from "./EngineUtitlities/VideoExtractionHandler/VideoExtractionHandler";
import { RealTimeDrawEngine, ShapeType } from "./RenderingPrimitives/RealTimeDrawEngine";
import { NodeColor } from "~/signals/uiEvents/toolbarNode";
// for testing loading files from system
// import { FileUtilities } from "./FileUtilities/FileUtilities";
@@ -180,8 +181,8 @@ export class Engine {
this.setupEventSystem();
this.setAppMode(AppModes.SELECT);
}
@@ -364,7 +365,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();
@@ -398,7 +399,7 @@ export class Engine {
this.addImage(image);
});
uiEvents.onAddTextToEngine((textdata) => {
this.addText(textdata);
@@ -430,7 +431,7 @@ export class Engine {
}
}
});
uiEvents.promptEvents.onPromptStrengthChanged((strength) => {
this.realTimeDrawEngine.currentStrength = strength;
});
@@ -439,13 +440,12 @@ export class Engine {
this.realTimeDrawEngine.currentPrompt = prompt;
});
uiEvents.onAddShapeToEngine((shapeData)=> {
uiEvents.onAddShapeToEngine((shapeData) => {
switch (shapeData.shape) {
case "circle":
this.addShape(ShapeType.Circle, 100);
break;
case "square":
case "square":
this.addShape(ShapeType.Square, 100);
break;
case "triangle":
@@ -454,6 +454,9 @@ export class Engine {
}
});
uiEvents.toolbarNode.color.onConfirmChanged((nodeColor) => {
this.changeNodeColor(nodeColor);
})
}
disableAllButtons() {
@@ -489,7 +492,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");
@@ -536,6 +539,15 @@ export class Engine {
this.addKeyboardShortcuts();
}
private changeNodeColor(nodeColor: NodeColor) {
// Find the node that is selected with the id
const idSelector = '#' + nodeColor.kNodeId;
const selectedNode = this.mediaLayer.findOne(idSelector);
// TODO: Change the color of the node
// Turns out shapes are image nodes... how do we change the image's fill??
}
public addText(textNodeData: TextNodeData) {
const textNode = new TextNode({
textNodeData: textNodeData,
@@ -558,8 +570,10 @@ export class Engine {
selectionManagerRef: this.selectionManager
});
shapeNode.kNode.zIndex(1); // Replace desiredZIndex with a number
this.commandManager.createNode(shapeNode);
console.debug("Added shapenode:", shapeNode);
console.debug("Added node's ID:", shapeNode.kNode._id);
//this.realTimeDrawEngine.addNodes(shapeNode);
}
@@ -617,7 +631,7 @@ export class Engine {
if (
longestVideoNode === undefined ||
node.videoComponent.duration >
longestVideoNode.videoComponent.duration
longestVideoNode.videoComponent.duration
) {
longestVideoNode = node;
}
@@ -58,6 +58,10 @@ export class CommandManager {
renderEngineRef: this.renderEngineRef,
});
this.undoStackManagerRef.executeCommand(command);
// Set the kNode's manual id to same as kNode's internal _id
// This helps identify the kNode in the Konva layer with find
node.kNode.id(node.kNode._id.toString());
}
deleteNodes() {
const nodes = this.selectionManagerRef.getSelectedNodes();
@@ -9,6 +9,7 @@ import {
calculateContextualsPosition,
getImageNodeButtonStates,
getMultiSelectButtonStates,
getShapeNodeButtonStates,
getTextNodeButtonStates,
getVideoNodeButtonStates,
} from "./ToolbarNodeUtilities";
@@ -271,22 +272,31 @@ export class SelectionManager {
return; // no node
}
if (this.selectedNodes.size === 1) {
if (node instanceof ImageNode || node instanceof ShapeNode) {
if (node instanceof ImageNode) {
showOrUpdate({
knodeIds: [node.kNode.id()],
locked: node.isLocked(),
buttonStates: getImageNodeButtonStates({ locked: node.isLocked() }),
});
}
else if (node instanceof ShapeNode) {
console.debug("Selected node is of type ShapeNode", node.kNode.id())
showOrUpdate({
knodeIds: [node.kNode.id()],
locked: node.isLocked(),
buttonStates: getShapeNodeButtonStates({ locked: node.isLocked() }),
color: node.getNodeData({ x: 0, y: 0 })?.textNodeData?.color ?? "#000000",
});
}
if (node instanceof TextNode) {
else if (node instanceof TextNode) {
showOrUpdate({
// TODO: Add ID to identify/mutate the color?
knodeIds: [node.kNode.id()],
locked: node.isLocked(),
buttonStates: getTextNodeButtonStates({ locked: node.isLocked() }),
color: node.getNodeData({ x: 0, y: 0 })?.textNodeData?.color ?? "#000000",
});
}
if (node instanceof VideoNode) {
else if (node instanceof VideoNode) {
showOrUpdate({
locked: node.isLocked(),
buttonStates: getVideoNodeButtonStates({ locked: node.isLocked() }),
@@ -317,7 +327,7 @@ export class SelectionManager {
this.hideContextComponents();
return; // no nodes
}
// console.log("SelectionManager > updateContextComponents for node:", node);
const coord = calculateContextualsPosition(
this.nodeTransformerRef.getKonvaNode(),
@@ -152,6 +152,50 @@ export function getVideoNodeButtonStates(
{} as ContextualToolbarProps["buttonStates"],
);
}
export function getShapeNodeButtonStates(
props: { locked?: boolean | "unknown" } = {},
) {
const ButtonNames = ToolbarNodeButtonNames;
return Object.values(ButtonNames).reduce(
(buttonStates, buttonName) => {
// transform button depends on locked state
if (props.locked !== undefined && buttonName === ButtonNames.TRANSFORM) {
buttonStates.TRANSFORM = {
disabled: props.locked === "unknown" || props.locked === true,
hidden: false,
active: true,
};
return buttonStates;
}
// hidden buttons
if (
buttonName === ButtonNames.AI_STYLIZE ||
buttonName === ButtonNames.SEGMENTATION ||
buttonName === ButtonNames.DOWNLOAD ||
buttonName === ButtonNames.CHROMA
) {
buttonStates[buttonName] = {
disabled: true,
hidden: true,
active: false,
};
return buttonStates;
}
// all other buttons
buttonStates[buttonName] = {
disabled: false,
hidden: false,
active: false,
};
return buttonStates;
},
{} as ContextualToolbarProps["buttonStates"],
);
}
export function getMultiSelectButtonStates(
props: { locked?: boolean | "unknown" } = {},
) {
@@ -26,6 +26,10 @@ export type VideoNodeData = {
extractionUrl?: string;
};
export type ShapeNodeData = {
shape: "triangle" | "square" | "circle";
}
export type NodeData = {
type: NodeType;
transform: TransformationData;
@@ -50,6 +50,20 @@ export const ContextualToolbarNode = () => {
};
}, []);
const handleColorChange = (color: string) => {
// Don't do anything if no node is known to be bound
if (uiAccess.toolbarNode.signal.value.knodeIds.length < 1) {
return;
}
// Update the UI component
uiAccess.toolbarNode.setColor(color);
// Signal the engine with the node ID (just grab the first -and expected only- node)
const nodeID = uiAccess.toolbarNode.signal.value.knodeIds[0];
dispatchUiEvents.toolbarNode.setSelectedColor({ kNodeId: nodeID, color });
}
const boundedTopPosition = Math.min(position.y, winSize.height - 164);
return (
<Transition
@@ -72,7 +86,7 @@ export const ContextualToolbarNode = () => {
dispatchUiEvents.toolbarNode.lock(e);
}}
color={uiAccess.toolbarNode.signal.value.color}
onColorChange={(color) => { uiAccess.toolbarNode.setColor(color) }}
onColorChange={handleColorChange}
/>
</Transition>
);
@@ -97,7 +97,7 @@ export const ToolbarNode = ({
);
} else {
return (
<ColorPicker color={color} onChange={onColorChange} faIcon={faPalette} borderStyle="" />
<ColorPicker color={color} onChange={onColorChange} faIcon={faPalette} borderStyle="" key={idx} />
)
}
})}
@@ -10,7 +10,7 @@ type ButtonStates = {
};
};
export interface ContextualToolbarProps extends ContextualUi {
knodeIds: number[];
knodeIds: string[];
disabled: boolean;
downloadUrl?: string;
locked: boolean | "unknown";
@@ -1,5 +1,5 @@
import { signal, effect } from "@preact/signals-react";
import { TextNodeData } from "~/KonvaApp/types";
import { ShapeNodeData, TextNodeData } from "~/KonvaApp/types";
// ADDING IMAGES
const stagedImage = signal<File | null>(null);
@@ -50,8 +50,6 @@ 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;
@@ -44,6 +44,11 @@ const buttonEventsRecords = Object.values(ButtonNames).reduce(
const buttonEventsHandlers = Object.values(ButtonNames).reduce(
(acc, buttonName) => {
// We define custom events/handlers for color changes
if (buttonName === ButtonNames.COLOR) {
return acc;
}
acc[buttonName] = {
onClick: (callback: MouseEventHandler<HTMLButtonElement>) => {
const { eventSignal, effectCleanup, lastEventTimestamp } =
@@ -111,9 +116,27 @@ const retryEventHandler = (callback: MouseEventHandler<HTMLButtonElement>) => {
const retryDispatcher = (e: React.MouseEvent<HTMLButtonElement>) => {
retryEvent.value = e;
};
export type NodeColor = {
kNodeId: string;
color: string;
}
const selectedColorEvent = signal<NodeColor | null>();
const setSelectedColor = (nodeColor: NodeColor) => {
selectedColorEvent.value = nodeColor;
}
const onSelectedColorChanged = (callback: (color: NodeColor) => void) => {
effect(() => {
if (selectedColorEvent.value) {
callback(selectedColorEvent.value);
}
});
};
export const dispatchers = {
lock: lockDispatcher,
retry: retryDispatcher,
setSelectedColor,
...buttonDispatchers,
};
export const eventsHandlers = {
@@ -123,5 +146,8 @@ export const eventsHandlers = {
retry: {
onClick: retryEventHandler,
},
color: {
onConfirmChanged: onSelectedColorChanged,
},
...buttonEventsHandlers,
};