Compare commits

...
Author SHA1 Message Date
CrossPr0duct e99baccdb2 Brush works with colors need size slider. 2025-02-24 21:07:47 -05:00
CrossPr0duct f821d12cdf Fixed State Issues, should be good to go. 2025-02-24 21:02:19 -05:00
CrossPr0duct ffd52845e5 Fix rerender during undo and redo stack. 2025-02-24 20:31:31 -05:00
CrossPr0duct 4aece4045f Fixed bug with Transform manager. 2025-02-24 19:32:21 -05:00
CrossPr0duct b08731305d Painting Mode seems to work.
Need to hook into the transform tool and make sure rendering happens on changes.
2025-02-24 18:53:23 -05:00
CrossPr0duct ad078ed9f4 Refactor a bit now manage states. 2025-02-24 17:58:39 -05:00
CrossPr0duct 8250ea0b4a Ok fixing some code.
Need to ensure transform works.
2025-02-24 17:24:02 -05:00
CrossPr0duct fb87cdaee4 Painting Works. 2025-02-24 17:20:22 -05:00
CrossPr0duct 0f8e7dae82 Change Paint Color 2025-02-24 14:12:40 -05:00
CrossPr0duct 5668a43fc5 Adding Paint Mode.
Need to add an eraser.
2025-02-24 00:20:48 -05:00
7 changed files with 399 additions and 52 deletions
+57 -13
View File
@@ -2,6 +2,7 @@ import Konva from "konva";
import { uiAccess, uiEvents } from "~/signals";
import { ShapeNode } from "./Nodes";
import { PaintNode } from "./Nodes/PaintNode";
import { UndoStackManager } from "./UndoRedo";
import { CommandManager, MatteBox, SceneManager } from "./EngineUtitlities";
@@ -110,17 +111,8 @@ export class Engine {
});
// core layer for all the work done.
this.offScreenCanvas = new OffscreenCanvas(0, 0);
this.realTimeDrawEngine = new RealTimeDrawEngine({
width: VideoResolutions.SQUARE_1024.width,
height: VideoResolutions.SQUARE_1024.height,
mediaLayerRef: this.mediaLayer,
bgLayerRef: this.bgLayer,
offScreenCanvas: this.offScreenCanvas,
});
// Collection of all Nodes
this.nodesManager = new NodesManager();
// Partial Collection of selected Nodes
@@ -128,6 +120,18 @@ export class Engine {
nodeTransformerRef: this.nodeTransformer,
mediaLayerRef: this.mediaLayer,
});
this.realTimeDrawEngine = new RealTimeDrawEngine({
width: VideoResolutions.SQUARE_1024.width,
height: VideoResolutions.SQUARE_1024.height,
mediaLayerRef: this.mediaLayer,
offScreenCanvas: this.offScreenCanvas,
onDraw:async (canvas, lineBounds) => {
await this.addPaintNode(canvas, lineBounds);
},
});
// Selector Square to select Nodes
this.selectorSquare = new SelectorSquare({
captureCanvasRef: this.realTimeDrawEngine.captureCanvas,
@@ -177,14 +181,31 @@ export class Engine {
// some of the managers has events
// hence, lastly, setup these events
this.setupEventSystem();
this.setAppMode(AppModes.SELECT);
this.setAppMode(this.appMode);
}
private setAppMode(newAppMode: AppModes) {
this.appMode = newAppMode;
switch (this.appMode) {
case AppModes.PAINT: {
console.log("APPMODE: PAINT");
this.selectorSquare.disable(); // this breaks the paint if you use it in the wrong spot
this.selectionManager.disable(); // this prevents selection.
this.realTimeDrawEngine.enablePaintMode();
this.realTimeDrawEngine.disableDragging();
uiAccess.toolbarMain.enable();
uiAccess.toolbarMain.changeButtonState(ToolbarMainButtonNames.SELECT, {
active: false,
});
this.matteBox.disable();
return;
}
case AppModes.SELECT: {
console.log("APPMODE: SELECT");
this.realTimeDrawEngine.disablePaintMode();
this.realTimeDrawEngine.enableDragging();
this.selectorSquare.enable();
this.selectionManager.enable();
uiAccess.toolbarMain.enable();
@@ -358,21 +379,24 @@ 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();
});
uiEvents.toolbarMain.SELECT.onClick(() => {
console.log("Toolbar Main >> Select");
this.setAppMode(AppModes.SELECT)
});
uiEvents.toolbarMain.PAINT.onClick(() => {
console.log("Toolbar Main >> Paint");
this.setAppMode(AppModes.PAINT)
});
uiEvents.toolbarMain.ERASER.onClick(() => {
console.log("Toolbar Main >> Eraser");
});
uiEvents.toolbarMain.PREVIEW.onClick(async () => {
@@ -443,6 +467,7 @@ export class Engine {
});
uiEvents.onAddShapeToEngine((shapeData) => {
this.setAppMode(AppModes.SELECT);
switch (shapeData.shape) {
case "circle":
this.addShape(ShapeType.CIRCLE, 100);
@@ -468,6 +493,9 @@ export class Engine {
}
this.changeNodeColor(nodeColor);
});
uiEvents.toolbarMain.onPaintColorChanged((color)=> {
this.realTimeDrawEngine.paintColor = color;
});
}
disableAllButtons() {
@@ -575,6 +603,9 @@ export class Engine {
color: nodeColor.color,
mediaLayerRef: this.mediaLayer,
selectionManagerRef: this.selectionManager,
loaded: async ()=> {
this.realTimeDrawEngine.render();
}
});
newShapeNode.kNode.position(shapeNode.kNode.position());
newShapeNode.kNode.zIndex(shapeNode.kNode.zIndex());
@@ -618,7 +649,6 @@ export class Engine {
console.debug("Added shapenode:", shapeNode);
console.debug("Added node's ID:", shapeNode.kNode._id);
this.realTimeDrawEngine.addNodes(shapeNode);
}
public addImage(imageFile: File) {
@@ -635,8 +665,22 @@ export class Engine {
imageNode.kNode.zIndex(1);
this.commandManager.createNode(imageNode);
this.realTimeDrawEngine.addNodes(imageNode);
}
public addPaintNode(canvas: HTMLCanvasElement,lineBounds:{
width: number;
height: number;
x: number;
y: number;}) {
var node = new PaintNode({
canvasElement:canvas,
lineBounds:lineBounds,
mediaLayerRef: this.mediaLayer,
selectionManagerRef: this.selectionManager,
loaded: async () => {
await this.realTimeDrawEngine.render()
}})
this.commandManager.createNode(node);
}
public addVideo(
videNodeData: Partial<VideoNodeData> & { mediaFileUrl: string },
@@ -20,14 +20,15 @@ import {
NodeTransformer,
SelectionManager,
} from "../NodesManagers";
import { RenderEngine } from "../RenderingPrimitives/RenderEngine";
import { RealTimeDrawEngine } from "../RenderingPrimitives/RealTimeDrawEngine";
import { PaintNode } from "../Nodes/PaintNode";
interface EngineReferences {
mediaLayerRef: Konva.Layer;
nodesManagerRef: NodesManager;
nodeTransformerRef: NodeTransformer;
selectionManagerRef: SelectionManager;
renderEngineRef: RenderEngine;
renderEngineRef: RealTimeDrawEngine;
undoStackManagerRef: UndoStackManager;
}
@@ -36,7 +37,7 @@ export class CommandManager {
private nodesManagerRef: NodesManager;
private nodeTransformerRef: NodeTransformer;
private selectionManagerRef: SelectionManager;
private renderEngineRef: RenderEngine;
private renderEngineRef: RealTimeDrawEngine;
private undoStackManagerRef: UndoStackManager;
constructor(engineRefs: EngineReferences) {
@@ -48,7 +49,8 @@ export class CommandManager {
this.undoStackManagerRef = engineRefs.undoStackManagerRef;
}
createNode(node: VideoNode | ImageNode | TextNode | ShapeNode) {
createNode(node: MediaNode) {
const command = new CreateCommand({
nodes: new Set<MediaNode>([node]),
mediaLayerRef: this.mediaLayerRef,
@@ -62,6 +64,7 @@ export class CommandManager {
// 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());
this.renderEngineRef.render();
}
deleteNodes() {
const nodes = this.selectionManagerRef.getSelectedNodes();
@@ -74,6 +77,7 @@ export class CommandManager {
renderEngineRef: this.renderEngineRef,
});
this.undoStackManagerRef.executeCommand(command);
this.renderEngineRef.render();
}
toggleLockNodes() {
const nodes = this.selectionManagerRef.getSelectedNodes();
@@ -101,6 +105,7 @@ export class CommandManager {
mediaLayerRef: this.mediaLayerRef,
});
this.undoStackManagerRef.executeCommand(command);
this.renderEngineRef.render();
}
moveNodesDown() {
const command = new MoveLayerDown({
@@ -109,6 +114,7 @@ export class CommandManager {
mediaLayerRef: this.mediaLayerRef,
});
this.undoStackManagerRef.executeCommand(command);
this.renderEngineRef.render();
}
translateNodes(props: {
nodes: Set<MediaNode>;
@@ -120,6 +126,7 @@ export class CommandManager {
layerRef: this.mediaLayerRef,
});
this.undoStackManagerRef.pushCommand(command);
}
transformNodes(props: {
nodes: Set<MediaNode>;
@@ -131,6 +138,7 @@ export class CommandManager {
layerRef: this.mediaLayerRef,
});
this.undoStackManagerRef.pushCommand(command);
this.renderEngineRef.render();
}
addChromaKey(props: { videoNode: VideoNode; newChromaColor: RGBColor }) {
const command = new AddChromaKey(props);
@@ -0,0 +1,92 @@
import Konva from "konva";
import { SelectionManager } from "../NodesManagers";
import { Position, NodeData } from "../types";
import { NodeType } from "./constants";
import { NetworkedNode } from "./NetworkedNode";
interface PaintNodeConstructor {
canvasElement:HTMLCanvasElement;
lineBounds: {
width: number;
height: number;
x: number;
y: number;
};
mediaLayerRef: Konva.Layer;
selectionManagerRef: SelectionManager;
loaded: () => Promise<void>;
}
export class PaintNode extends NetworkedNode {
retry(): void {
throw new Error("Method not implemented.");
}
constructor({
canvasElement,
lineBounds,
mediaLayerRef,
selectionManagerRef,
loaded,
}: PaintNodeConstructor) {
const cl = canvasElement
// Convert circle to image
const dataURL = cl.toDataURL();
const imageObj = new Image();
imageObj.src = dataURL;
const paintShape = new Konva.Image({
x: lineBounds.x,
y: lineBounds.y,
width: lineBounds.width,
height: lineBounds.height,
image: imageObj,
listening: false, // Disable event listening to allow captureCanvas to receive events
draggable: true,
// stroke: '#000000',
// strokeWidth: 2,
// dash: [5, 5], // Creates dotted outline THIS IS FOR DEBUG
globalCompositeOperation: 'source-over',
fill: 'transparent'
});
imageObj.onload = () => {
this.mediaLayerRef.draw();
loaded();
};
super({
selectionManagerRef: selectionManagerRef,
mediaLayerRef: mediaLayerRef,
kNode: paintShape,
});
this.mediaLayerRef.add(this.kNode);
this.listenToBaseKNode();
this.mediaLayerRef.draw();
}
public getNodeData(canvasPosition: Position) {
const data: NodeData = {
type: NodeType.SHAPE,
transform: {
position: {
x: this.kNode.position().x - canvasPosition.x,
y: this.kNode.position().y - canvasPosition.y,
},
size: this.kNode.size(),
rotation: this.kNode.rotation(),
scale: {
x: this.kNode.scaleX(),
y: this.kNode.scaleY(),
},
zIndex: this.kNode.getZIndex(),
},
};
return data;
}
}
@@ -11,12 +11,15 @@ import { MediaNode } from "../types";
import { RenderTask } from "./RenderTask";
import { OffScreenSceneCanvas } from "./OffScreenSceneCanvas";
import { Image } from "@tauri-apps/api/image";
import { PaintNode } from "../Nodes/PaintNode";
// https://www.aiseesoft.com/resource/phone-aspect-ratio-screen-resolution.html#:~:text=16%3A9%20Aspect%20Ratio
export class RealTimeDrawEngine {
private videoNodes: VideoNode[];
private imageNodes: (ImageNode | TextNode | ShapeNode)[];
private imageNodes: (ImageNode | TextNode | ShapeNode | PaintNode)[];
private offScreenCanvas: OffscreenCanvas;
private outputBitmap: ImageBitmap | undefined;
@@ -26,6 +29,7 @@ export class RealTimeDrawEngine {
// capturing composite within window
private mediaLayerRef: Konva.Layer;
private drawingsLayer: Konva.Layer; // New Layer for Drawings
private height: number;
private width: number;
@@ -46,20 +50,40 @@ export class RealTimeDrawEngine {
public currentPrompt: string;
public currentStrength: number;
// Paint Color
// paint Brush Size
// has to exit out of paint mode when shape or image are used.
public paintColor: string = "#000000";
public paintBrushSize: number = 5;
public isPaintMode: boolean = false;
private onDrawCallback?: (canvas: HTMLCanvasElement,lineBounds:{
width: number;
height: number;
x: number;
y: number;}) => void;
constructor({
width,
height,
mediaLayerRef,
offScreenCanvas,
onDraw,
}: {
width: number;
height: number;
mediaLayerRef: Konva.Layer;
offScreenCanvas: OffscreenCanvas;
onDraw?: (canvas: HTMLCanvasElement,lineBounds:{
width: number;
height: number;
x: number;
y: number;}) => void;
}) {
this.videoLoadingCanvas = undefined;
this.videoNodes = [];
this.imageNodes = [];
this.onDrawCallback = onDraw
// TODO: Make this dynamic and update this on change of canvas.
@@ -69,7 +93,8 @@ export class RealTimeDrawEngine {
this.positionX = window.innerWidth / 2 - this.width / 2 - this.width;
this.positionY = window.innerHeight / 2 - this.height / 2;
this.positionPreviewX = window.innerWidth / 2 - this.width / 2 + this.width;
this.positionPreviewX =
window.innerWidth / 2 - this.width / 2 + this.width;
this.positionPreviewY = window.innerHeight / 2 - this.height / 2;
this.offScreenCanvas = offScreenCanvas;
@@ -79,6 +104,12 @@ export class RealTimeDrawEngine {
// this is the whole canvas
this.mediaLayerRef = mediaLayerRef;
// Create a separate layer for drawings
this.drawingsLayer = new Konva.Layer({
clearBeforeDraw: true, // Ensures transparent background
});
this.mediaLayerRef.getStage()?.add(this.drawingsLayer);// to od pass in stage
// Set background layer to red and media layer to green for visibility
this.port = undefined;
@@ -123,15 +154,178 @@ export class RealTimeDrawEngine {
}
private isEnabled: boolean = false;
private cleanupFunction: (() => void) | null = null;
public paintMode() {
let isDrawing = false;
let currentLine: Konva.Line | null = null;
const startDrawing = (pos: { x: number; y: number }) => {
if (!this.isEnabled) return;
const stage = this.mediaLayerRef.getStage();
if (!stage) return;
// Convert pointer position to relative position within capture canvas
const captureBox = this.captureCanvas.getClientRect();
const relativeX = pos.x - captureBox.x;
const relativeY = pos.y - captureBox.y;
currentLine = new Konva.Line({
points: [relativeX, relativeY],
stroke: this.paintColor,
strokeWidth: 5,
lineCap: "round",
lineJoin: "round",
x: this.captureCanvas.x(),
y: this.captureCanvas.y(),
draggable: false,
});
this.drawingsLayer.add(currentLine); // Add to drawingsLayer
isDrawing = true;
};
const draw = (pos: { x: number; y: number }) => {
if (!this.isEnabled) return;
if (!isDrawing || !currentLine) return;
// Convert pointer position to relative position
const captureBox = this.captureCanvas.getClientRect();
const relativeX = pos.x - captureBox.x;
const relativeY = pos.y - captureBox.y;
const newPoints = currentLine.points().concat([relativeX, relativeY]);
currentLine.points(newPoints);
this.drawingsLayer.batchDraw();
};
const stopDrawing = () => {
if (!this.isEnabled) return;
if (!isDrawing || !currentLine) return;
// Store current line before resetting state
const lineToConvert = currentLine;
// Reset drawing state immediately so we can start a new stroke
isDrawing = false;
currentLine = null;
// Create a temporary layer to render the line
const tempLayer = new Konva.Layer({
clearBeforeDraw: true,
});
this.mediaLayerRef.getStage()?.add(tempLayer);
tempLayer.add(lineToConvert);
// Get the line canvas with transparent background
// Get the bounding box of the line
const lineBounds = lineToConvert.getClientRect();
// Create canvas with just enough size to contain the line
const lineCanvas = tempLayer.toCanvas({
x: lineBounds.x,
y: lineBounds.y,
width: lineBounds.width,
height: lineBounds.height,
pixelRatio: 1,
});
if (this.onDrawCallback) {
this.onDrawCallback(lineCanvas, lineBounds);
}
// Clean up
lineToConvert.destroy();
tempLayer.destroy();
this.drawingsLayer.batchDraw();
};
// Check if point is within capture canvas bounds
const isWithinCaptureCanvas = (pos: { x: number; y: number }) => {
const captureBox = this.captureCanvas.getClientRect();
return (
pos.x >= captureBox.x &&
pos.x <= captureBox.x + captureBox.width &&
pos.y >= captureBox.y &&
pos.y <= captureBox.y + captureBox.height
);
};
// Add event listeners
const stage = this.mediaLayerRef.getStage();
if (!stage) return;
stage.on("mousedown touchstart", (e) => {
const pos = stage.getPointerPosition();
if (pos && isWithinCaptureCanvas(pos)) {
startDrawing(pos);
}
});
stage.on("mousemove touchmove", (e) => {
const pos = stage.getPointerPosition();
if (pos && isWithinCaptureCanvas(pos)) {
draw(pos);
}
});
stage.on("mouseup touchend", async () => {
stopDrawing();
await this.render();
});
// Store cleanup function
this.cleanupFunction = () => {
stage.off("mousedown touchstart");
stage.off("mousemove touchmove");
stage.off("mouseup touchend");
};
}
public enablePaintMode() {
this.isEnabled = true;
if (!this.cleanupFunction) {
this.paintMode();
}
}
public enableDragging() {
// Enable dragging for all nodes in media layer
this.imageNodes?.forEach((node) => {
node.kNode.draggable(true);
node.kNode.listening(true);
});
this.mediaLayerRef.batchDraw();
}
public disableDragging() {
// Disable dragging for all nodes in media layer
this.imageNodes?.forEach((node) => {
node.kNode.draggable(false);
node.kNode.listening(false);
});
this.mediaLayerRef.batchDraw();
}
public disablePaintMode() {
this.isEnabled = false;
if (this.cleanupFunction) {
this.cleanupFunction();
this.cleanupFunction = null;
}
}
public previewCopyListener() {
this.previewCanvas.on('mousedown touchstart', () => {
this.previewCanvas.on("mousedown touchstart", () => {
if (!this.outputBitmap) {
console.log("No preview image to copy");
console.log("No preview image to copy");
return;
}
// TODO create it as a node.
// const imageNode = new ImageNode({
// mediaLayerRef: this.mediaLayer,
// canvasPosition: this.renderEngine.captureCanvas.position(),
@@ -148,42 +342,41 @@ export class RealTimeDrawEngine {
height: this.height,
image: this.outputBitmap,
draggable: true,
listening: true
listening: true,
});
this.mediaLayerRef.add(previewCopy);
previewCopy.moveToTop();
this.mediaLayerRef.draw();
this.drawingsLayer.add(previewCopy); // Add to drawingsLayer instead of mediaLayerRef
this.drawingsLayer.batchDraw();
// Start dragging immediately
previewCopy.startDrag();
// Handle drag events
previewCopy.on('dragmove', () => {
this.mediaLayerRef.draw();
previewCopy.on("dragmove", () => {
this.drawingsLayer.draw();
});
previewCopy.on('dragend', () => {
previewCopy.on("dragend", () => {
const previewBox = previewCopy.getClientRect();
const captureBox = this.captureCanvas.getClientRect();
if (Konva.Util.haveIntersection(previewBox, captureBox)) {
// Snap to capture canvas position
previewCopy.position({
x: this.captureCanvas.x(),
y: this.captureCanvas.y()
y: this.captureCanvas.y(),
});
this.mediaLayerRef.batchDraw();
this.drawingsLayer.batchDraw();
} else {
// Remove if dropped outside capture area
previewCopy.destroy();
this.mediaLayerRef.batchDraw();
this.drawingsLayer.batchDraw();
}
});
});
}
public findImageNodeById(id: string): (ImageNode | TextNode | ShapeNode | undefined) {
public findImageNodeById(id: string): (ImageNode | TextNode | ShapeNode | PaintNode | undefined) {
return this.imageNodes.find(node => {
if (node.kNode) {
return node.kNode.id() === id;
@@ -336,14 +529,23 @@ export class RealTimeDrawEngine {
};
public async addNodes(node: MediaNode) {
if (node instanceof ImageNode || node instanceof TextNode || node instanceof ShapeNode) {
if (node instanceof ImageNode ||
node instanceof TextNode ||
node instanceof ShapeNode ||
node instanceof PaintNode)
{
console.debug("Adding node:", node);
this.imageNodes.push(node);
console.log(this.imageNodes)
node.kNode.on("dragend", this.handleNodeDragEnd);
}
// ensure the layer doesn't move if added while painting.
if (this.isEnabled) {
this.disableDragging();
}
await this.render();
}
public removeNodes(node: MediaNode) {
@@ -353,7 +555,7 @@ export class RealTimeDrawEngine {
node.kNode.off("dragend", this.handleNodeDragEnd);
this.videoNodes.splice(index, 1);
}
} else if (node instanceof ImageNode || node instanceof TextNode) {
} else if (node instanceof ImageNode || node instanceof TextNode || node instanceof ShapeNode || node instanceof PaintNode) {
const index = this.imageNodes.indexOf(node);
if (index > -1) {
node.kNode.off("dragend", this.handleNodeDragEnd);
@@ -536,12 +738,12 @@ export class RealTimeDrawEngine {
})) as ImageBitmap;
// Test code
if (true) {
this.outputBitmap = bitmap;
this.previewCanvas.image(bitmap);
this.isProcessing = false;
return;
}
// if (true) {
// this.outputBitmap = bitmap;
// this.previewCanvas.image(bitmap);
// this.isProcessing = false;
// return;
// }
try {
const base64Bitmap = await this.imageBitmapToBase64(bitmap);
@@ -6,7 +6,7 @@ import {
NodeTransformer,
SelectionManager,
} from "../NodesManagers";
import { RenderEngine } from "../RenderingPrimitives/RenderEngine";
import { RealTimeDrawEngine } from "../RenderingPrimitives/RealTimeDrawEngine";
import { toolbarNode } from "~/signals/uiAccess/toolbarNode";
@@ -19,7 +19,7 @@ export class CreateCommand implements ICommand {
private nodeTransformerRef: NodeTransformer;
private selectionManagerRef: SelectionManager;
private renderEngineRef: RenderEngine;
private renderEngineRef: RealTimeDrawEngine;
constructor({
nodes,
@@ -33,7 +33,7 @@ export class CreateCommand implements ICommand {
mediaLayerRef: Konva.Layer;
nodesManagerRef: NodesManager;
nodeTransformerRef: NodeTransformer;
renderEngineRef: RenderEngine;
renderEngineRef: RealTimeDrawEngine;
selectionManagerRef: SelectionManager;
}) {
this.nodes = nodes;
@@ -53,6 +53,7 @@ export class CreateCommand implements ICommand {
this.renderEngineRef.addNodes(node);
});
this.mediaLayerRef.draw();
}
undo() {
@@ -71,5 +72,7 @@ export class CreateCommand implements ICommand {
this.nodeTransformerRef.clear();
toolbarNode.hide();
this.mediaLayerRef.draw();
this.renderEngineRef.render();
}
}
@@ -3,10 +3,11 @@ import { NodeType, BaseNode, ImageNode, TextNode, VideoNode, ShapeNode } from ".
import { TransformationData } from "./Transformation";
import { TextNodeData } from "./Text";
import { PaintNode } from "../Nodes/PaintNode";
export type MediaNode =
// | NetworkedNode
BaseNode | VideoNode | ImageNode | TextNode | ShapeNode;
BaseNode | VideoNode | ImageNode | TextNode | ShapeNode | PaintNode;
export type ImageNodeData = {
mediaFileUrl: string;
@@ -13,9 +13,6 @@ import {
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 {