Feature/ add text (#44)

* can add text

retain stroke width while transforming is now possible

reorganize how the inheritance of node classes, now text can be selected, multi-selected, translate and transform, all while working with the unredo stack

organize toolbarmain and dialogues, now add text brings up a dialog, meant for accepting user text input

finished plumbing the data from user text input to adding text to baord

clean up

make the dialog less weirdly vertical

clean up, and place node in the center of canvas

block adding text with no text, make textbox wider

transformation of old node works with new transformation setup

undo redo of transformation also works for text nodes now

cleanup

geometry fix

almost done text editor ui

text formatting from text editor is sent to engine and used on text nodes

* rid of useless button
This commit is contained in:
wil authored and GitHub committed 2024-10-07 21:56:58 -04:00
1 parent e25f27cc6a
commit 19d58843d7
41 files changed
+1579 -1422

No files matched your search

+54 -564
View File
File diff suppressed because it is too large. Load diff
+1
View File
@@ -18,6 +18,7 @@
"konva": "^9.3.14",
"onnxruntime-web": "^1.19.2",
"react": "^18.3.1",
"react-colorful": "^5.6.1",
"react-dom": "^18.3.1",
"react-drag-drop-files": "^2.3.10",
"react-easy-crop": "^5.0.8",
+88 -70
View File
@@ -3,9 +3,7 @@ import Konva from "konva";
import { RenderEngine } from "./RenderingPrimitives/RenderEngine";
import { ResponseType } from "./WorkerPrimitives/SharedWorkerBase";
import { uiAccess } from "~/signals";
import { uiEvents } from "~/signals";
// import { toolbarNode } from "~/signals/uiAccess/toolbarNode";
import { uiAccess, uiEvents } from "~/signals";
import {
NodesManager,
@@ -16,9 +14,8 @@ import {
SelectionManagerEvents,
SelectorSquare,
} from "./NodesManagers";
import { ImageNode } from "./Nodes/ImageNode";
import { VideoNode } from "./Nodes/VideoNode";
import { MediaNode, Position, Transformation } from "./types";
import { ImageNode, VideoNode, TextNode } from "./Nodes";
import { MediaNode, Position, TextNodeData, Transformation } from "./types";
import {
CreateCommand,
@@ -37,11 +34,13 @@ import {
DiffusionSharedWorkerProgressData,
DiffusionSharedWorkerResponseData,
} from "./SharedWorkers/Diffusion/DiffusionSharedWorker";
import { FileUtilities } from "./FileUtilities/FileUtilities";
import { AppModes, VideoResolutions } from "./constants";
import { ToolbarMainButtonNames } from "~/components/features/ToolbarMain/enum";
// for testing loading files from system
// import { FileUtilities } from "./FileUtilities/FileUtilities";
export interface RenderingOptions {
artstyle: string;
positivePrompt: string;
@@ -177,7 +176,7 @@ export class Engine {
const videoNode = new VideoNode({
mediaLayerRef: this.mediaLayer,
position: this.renderEngine.captureCanvas.position(),
canvasPosition: this.renderEngine.captureCanvas.position(),
canvasSize: this.renderEngine.captureCanvas.size(),
videoURL: media_url,
selectionManagerRef: this.selectionManager,
@@ -208,9 +207,10 @@ export class Engine {
if (this.appMode === AppModes.SELECT) {
this.selectorSquare.enable({
captureCanvasRef: this.renderEngine.captureCanvas,
mediaLayerRef: this.mediaLayer,
nodesManagerRef: this.nodesManager,
selectionManagerRef: this.selectionManager,
stage: this.stage,
stageRef: this.stage,
});
uiAccess.toolbarMain.changeButtonState(ToolbarMainButtonNames.SELECT, {
active: true,
@@ -274,6 +274,9 @@ export class Engine {
console.log("Engine got video: " + video.url);
this.addVideo(video.url);
});
uiEvents.onAddTextToEngine((textdata) => {
this.addText(textdata);
});
uiEvents.onChromakeyRequest((chromakeyProps) => {
const node = this.selectionManager
@@ -377,10 +380,25 @@ export class Engine {
this.addKeyboardShortcuts();
}
public addText(textNodeData: TextNodeData) {
const textNode = new TextNode({
textNodeData: textNodeData,
mediaLayerRef: this.mediaLayer,
selectionManagerRef: this.selectionManager,
position: {
x: this.renderEngine.captureCanvas.x() + 20,
y:
this.renderEngine.captureCanvas.y() +
this.renderEngine.captureCanvas.height() / 3,
},
});
this.createNode(textNode);
}
public addImage(imageFile: File) {
const imageNode = new ImageNode({
mediaLayerRef: this.mediaLayer,
position: this.renderEngine.captureCanvas.position(),
canvasPosition: this.renderEngine.captureCanvas.position(),
canvasSize: this.renderEngine.captureCanvas.size(),
imageFile: imageFile,
selectionManagerRef: this.selectionManager,
@@ -393,7 +411,7 @@ export class Engine {
public addVideo(url: string) {
const videoNode = new VideoNode({
mediaLayerRef: this.mediaLayer,
position: this.renderEngine.captureCanvas.position(),
canvasPosition: this.renderEngine.captureCanvas.position(),
canvasSize: this.renderEngine.captureCanvas.size(),
videoURL: url,
selectionManagerRef: this.selectionManager,
@@ -417,7 +435,7 @@ export class Engine {
});
}
createNode(node: VideoNode | ImageNode) {
createNode(node: VideoNode | ImageNode | TextNode) {
const command = new CreateCommand({
nodes: new Set<MediaNode>([node]),
mediaLayerRef: this.mediaLayer,
@@ -488,8 +506,8 @@ export class Engine {
}
transformNodes(props: {
nodes: Set<MediaNode>;
initialTransformations: Map<MediaNode, Transformation>;
finalTransformations: Map<MediaNode, Transformation>;
initialTransformations: Map<MediaNode, Transformation[]>;
finalTransformations: Map<MediaNode, Transformation[]>;
}) {
const command = new TransformCommand({
...props,
@@ -503,68 +521,68 @@ export class Engine {
* Code for debug and Testing
*
********************************/
public async populateWithDebugItems() {
const imageFile = await FileUtilities.createImageFileFromUrl(
"https://static.miraheze.org/pgrwiki/0/0d/Dialogue-2B-Icon.png",
);
const imageNode = new ImageNode({
mediaLayerRef: this.mediaLayer,
position: this.renderEngine.captureCanvas.position(),
canvasSize: this.renderEngine.captureCanvas.size(),
imageFile: imageFile,
selectionManagerRef: this.selectionManager,
});
// public async populateWithDebugItems() {
// const imageFile = await FileUtilities.createImageFileFromUrl(
// "https://static.miraheze.org/pgrwiki/0/0d/Dialogue-2B-Icon.png",
// );
// const imageNode = new ImageNode({
// mediaLayerRef: this.mediaLayer,
// canvasPosition: this.renderEngine.captureCanvas.position(),
// canvasSize: this.renderEngine.captureCanvas.size(),
// imageFile: imageFile,
// selectionManagerRef: this.selectionManager,
// });
// this.renderEngine.addNodes(videoNode3);
// // this.renderEngine.addNodes(videoNode3);
const videoNode4 = new VideoNode({
mediaLayerRef: this.mediaLayer,
position: this.renderEngine.captureCanvas.position(),
canvasSize: this.renderEngine.captureCanvas.size(),
videoURL:
"https://storage.googleapis.com/vocodes-public/media/0/2/8/1/n/0281nc0f3kgwvxf8eprywtd01r72rfp6/video_0281nc0f3kgwvxf8eprywtd01r72rfp6.mp4",
selectionManagerRef: this.selectionManager,
});
const imageNode2 = new ImageNode({
mediaLayerRef: this.mediaLayer,
position: this.renderEngine.captureCanvas.position(),
canvasSize: this.renderEngine.captureCanvas.size(),
imageFile: imageFile,
selectionManagerRef: this.selectionManager,
});
// const videoNode4 = new VideoNode({
// mediaLayerRef: this.mediaLayer,
// canvasPosition: this.renderEngine.captureCanvas.position(),
// canvasSize: this.renderEngine.captureCanvas.size(),
// videoURL:
// "https://storage.googleapis.com/vocodes-public/media/0/2/8/1/n/0281nc0f3kgwvxf8eprywtd01r72rfp6/video_0281nc0f3kgwvxf8eprywtd01r72rfp6.mp4",
// selectionManagerRef: this.selectionManager,
// });
// const imageNode2 = new ImageNode({
// mediaLayerRef: this.mediaLayer,
// canvasPosition: this.renderEngine.captureCanvas.position(),
// canvasSize: this.renderEngine.captureCanvas.size(),
// imageFile: imageFile,
// selectionManagerRef: this.selectionManager,
// });
this.renderEngine.addNodes(videoNode4);
// this.renderEngine.addNodes(videoNode4);
const videoNode5 = new VideoNode({
mediaLayerRef: this.mediaLayer,
position: this.renderEngine.captureCanvas.position(),
canvasSize: this.renderEngine.captureCanvas.size(),
videoURL:
"https://storage.googleapis.com/vocodes-public/media/0/2/8/1/n/0281nc0f3kgwvxf8eprywtd01r72rfp6/video_0281nc0f3kgwvxf8eprywtd01r72rfp6.mp4",
selectionManagerRef: this.selectionManager,
});
// const videoNode5 = new VideoNode({
// mediaLayerRef: this.mediaLayer,
// canvasPosition: this.renderEngine.captureCanvas.position(),
// canvasSize: this.renderEngine.captureCanvas.size(),
// videoURL:
// "https://storage.googleapis.com/vocodes-public/media/0/2/8/1/n/0281nc0f3kgwvxf8eprywtd01r72rfp6/video_0281nc0f3kgwvxf8eprywtd01r72rfp6.mp4",
// selectionManagerRef: this.selectionManager,
// });
this.renderEngine.addNodes(videoNode5);
// Adding nodes here
const videoNode = new VideoNode({
mediaLayerRef: this.mediaLayer,
position: this.renderEngine.captureCanvas.position(),
canvasSize: this.renderEngine.captureCanvas.size(),
videoURL:
"https://storage.googleapis.com/vocodes-public/media/r/q/p/r/e/rqpret6mkh18dqwjqwghhdqf15x720s1/storyteller_rqpret6mkh18dqwjqwghhdqf15x720s1.mp4",
selectionManagerRef: this.selectionManager,
});
// this.renderEngine.addNodes(videoNode5);
// // Adding nodes here
// const videoNode = new VideoNode({
// mediaLayerRef: this.mediaLayer,
// canvasPosition: this.renderEngine.captureCanvas.position(),
// canvasSize: this.renderEngine.captureCanvas.size(),
// videoURL:
// "https://storage.googleapis.com/vocodes-public/media/r/q/p/r/e/rqpret6mkh18dqwjqwghhdqf15x720s1/storyteller_rqpret6mkh18dqwjqwghhdqf15x720s1.mp4",
// selectionManagerRef: this.selectionManager,
// });
// CODE TO TEST RENDER ENGINE
// Testing render engine
// this.renderEngine.addNodes(videoNode);
// await this.renderEngine.startProcessing();
//videoNode.simulatedLoading();
// TODO support Text nodes
// // CODE TO TEST RENDER ENGINE
// // Testing render engine
// // this.renderEngine.addNodes(videoNode);
// // await this.renderEngine.startProcessing();
// //videoNode.simulatedLoading();
// // TODO support Text nodes
// this.renderEngine.addNodes(imageNode2);
// // this.renderEngine.addNodes(imageNode2);
// // TODO if only image take image and just takes snapshots Edge case
// this.renderEngine.addNodes(imageNode);
}
// // // TODO if only image take image and just takes snapshots Edge case
// // this.renderEngine.addNodes(imageNode);
// }
}
+195
View File
@@ -0,0 +1,195 @@
import Konva from "konva";
import { v4 as uuidv4 } from "uuid";
import { SelectionManager } from "../NodesManagers";
import { highlightStrokeWidth, primaryOrange } from "./constants";
export class BaseNode {
protected selectionManagerRef: SelectionManager;
public kNode: Konva.Node;
protected mediaLayerRef: Konva.Layer;
protected uuid: string;
// Internal State members
// do not modify internal
protected isSelecting: boolean = false;
// This locks interaction when the render engine is rendering
protected isProcessing: boolean = false;
protected _isLocked: boolean = false;
protected _isKEventRef: boolean = false;
constructor({
selectionManagerRef,
mediaLayerRef,
kNode,
}: {
selectionManagerRef: SelectionManager;
mediaLayerRef: Konva.Layer;
kNode: Konva.Image | Konva.Group;
}) {
console.log("Node constructed");
this.uuid = uuidv4();
this.mediaLayerRef = mediaLayerRef;
this.selectionManagerRef = selectionManagerRef;
this.kNode = kNode;
}
protected delete() {
// Do any other clean up and delete the konva node.
this.kNode.destroy();
}
protected async setProcessing() {
this.isProcessing = true;
}
public isKEventRef() {
return this._isKEventRef;
}
public setIsKEventRef(flag: boolean) {
this._isKEventRef = flag;
this.removeListenToBaseKNodeTransformations();
this.removeListenToBaseKNodeDrags();
if (flag) {
this.listenToBaseKNodeTransformations();
this.listenToBaseKNodeDrags();
}
}
public highlight() {
// console.log("Highlight", this.kNode._id);
if (this.kNode instanceof Konva.Image) {
this.kNode.stroke(primaryOrange);
this.kNode.strokeWidth(highlightStrokeWidth);
return;
}
if (this.kNode instanceof Konva.Group) {
const wrapperRect = this.kNode.findOne(".wrapper") as Konva.Rect;
wrapperRect.stroke(primaryOrange);
wrapperRect.strokeWidth(highlightStrokeWidth);
return;
}
if (import.meta.env.DEV) {
console.warn(
"DEV: BaseNode Highlight Error, kNode of BaseNode should be override by MediaNodes that extends kNode",
);
}
}
public unhighlight() {
// console.log("unHighlight", this.kNode._id);
if (this.kNode instanceof Konva.Image) {
this.kNode.strokeWidth(0);
return;
}
if (this.kNode instanceof Konva.Group) {
const wrapperRect = this.kNode.findOne(".wrapper") as Konva.Rect;
wrapperRect.strokeWidth(0);
return;
}
if (import.meta.env.DEV) {
console.warn(
"DEV: BaseNode UnHighlight Error, kNode of BaseNode should be override by MediaNodes that extends kNode",
);
}
}
public moveLayerUp() {
this.kNode.moveUp();
}
public moveLayerDown() {
this.kNode.moveDown();
}
public isLocked() {
return this._isLocked;
}
public lock() {
this._isLocked = true;
this.kNode.setDraggable(false);
if (this._isKEventRef) {
this.selectionManagerRef.updateContextComponents(this);
}
}
public unlock() {
this._isLocked = false;
this.kNode.setDraggable(true);
if (this._isKEventRef) {
this.selectionManagerRef.updateContextComponents(this);
}
}
public listenToBaseKNode() {
const handleSelect = (isMultiSelect: boolean) => {
if (!isMultiSelect) {
// clear selection if not multislect
//console.log("No Shift >> no multiselect");
this.selectionManagerRef.clearSelection();
}
if (this.selectionManagerRef.isNodeSelected(this)) {
this.selectionManagerRef.deselectNode(this);
return;
}
this.selectionManagerRef.selectNode(this);
};
this.kNode.on("mousedown", (e) => {
// console.log("Mouse down");
// Selection of Node
if (!this.selectionManagerRef.isNodeSelected(this)) {
this.isSelecting = true;
//checking for multiselect
handleSelect(e.evt.shiftKey);
}
});
this.kNode.on("mouseup", (e) => {
// console.log("MOUSE UP");
// just coming out of dragging or selecting mode
// no need to handle select
if (this.selectionManagerRef.isDragging() || this.isSelecting) {
this.isSelecting = false;
return;
}
// checking for multiselect, in multiselect deselection is possible
handleSelect(e.evt.shiftKey);
});
}
public listenToBaseKNodeDrags() {
this.kNode.on("dragstart", () => {
// console.log("Drag start", this.kNode._id);
if (this.isProcessing) {
return;
}
this.selectionManagerRef.dragStart();
});
this.kNode.on("dragend", () => {
// console.log("Drag End", this.kNode._id);
this.selectionManagerRef.dragEnd(this);
});
}
public removeListenToBaseKNodeDrags() {
this.kNode.removeEventListener("dragstart");
this.kNode.removeEventListener("dragend");
}
public listenToBaseKNodeTransformations() {
this.kNode.on("transformstart", (event) => {
console.log("transformstart", event.target._id);
this.selectionManagerRef.transformStart();
});
this.kNode.on("transform", () => {
this.kNode.setAttrs({
width: this.kNode.width() * this.kNode.scaleX(),
height: this.kNode.height() * this.kNode.scaleY(),
scaleX: 1,
scaleY: 1,
});
});
this.kNode.on("transformend", (event) => {
console.log("transformend", event.target._id);
this.selectionManagerRef.transformEnd(this);
});
}
public removeListenToBaseKNodeTransformations() {
this.kNode.removeEventListener("transformend");
}
}
+26 -14
View File
@@ -1,23 +1,26 @@
import Konva from "konva";
import { NetworkedNodeContext } from "./NetworkedNodeContext";
import { NetworkedNode } from "./NetworkedNode";
import { SelectionManager } from "../NodesManagers";
import { Position, Size } from "../types";
import { minNodeSize, transparent } from "./constants";
import { NodeUtilities } from "./NodeUtilities";
interface ImageNodeContructor {
position: Position;
canvasPosition: Position;
canvasSize: Size;
imageFile: File;
mediaLayerRef: Konva.Layer;
selectionManagerRef: SelectionManager;
}
export class ImageNode extends NetworkedNodeContext {
public imageURL: string;
export class ImageNode extends NetworkedNode {
// public imageURL: string;
public kNode: Konva.Image;
private imageObject: HTMLImageElement;
constructor({
position,
canvasPosition,
canvasSize,
imageFile,
mediaLayerRef,
@@ -25,13 +28,16 @@ export class ImageNode extends NetworkedNodeContext {
}: ImageNodeContructor) {
// kNodes need to be created first to guaruntee it is not undefined in parent's context
const kNode = new Konva.Image({
x: position.x,
y: position.y,
image: undefined, // to do replace with placeholder
width: 100,
height: 100,
size: minNodeSize,
position: NodeUtilities.positionNodeOnCanvasCenter({
canvasOffset: canvasPosition,
componentSize: minNodeSize,
maxSize: canvasSize,
}),
fill: "gray",
draggable: true,
strokeScaleEnabled: false,
});
super({
@@ -39,6 +45,7 @@ export class ImageNode extends NetworkedNodeContext {
mediaLayerRef: mediaLayerRef,
kNode: kNode,
});
this.kNode = kNode;
this.mediaLayerRef.add(this.kNode);
const imageComponent = new Image();
@@ -51,19 +58,24 @@ export class ImageNode extends NetworkedNodeContext {
return;
}
const renderSize = this.calculateRenderSizeOnLoad({
const adjustedSize = NodeUtilities.adjustNodeSizeToCanvas({
componentSize: {
width: imageComponent.width,
height: imageComponent.height,
},
maxSize: canvasSize,
});
const centerPosition = NodeUtilities.positionNodeOnCanvasCenter({
canvasOffset: canvasPosition,
componentSize: adjustedSize,
maxSize: canvasSize,
});
this.kNode.image(imageComponent);
this.kNode.setSize(renderSize);
this.kNode.setSize(adjustedSize);
this.kNode.setPosition(centerPosition);
this.listenToBaseKNode();
this.kNode.fill(null);
this.kNode.fill(transparent);
this.mediaLayerRef.draw();
this.didFinishLoading = true;
@@ -82,7 +94,7 @@ export class ImageNode extends NetworkedNodeContext {
this.kNode.image(newImage);
this.kNode.width(newImage.width);
this.kNode.height(newImage.height);
this.kNode.fill(null);
this.kNode.fill(transparent);
this.kNode.draw();
};
}
+26
View File
@@ -0,0 +1,26 @@
import Konva from "konva";
import { SelectionManager } from "../NodesManagers";
import { BaseNode } from "./BaseNode";
export class NetworkedNode extends BaseNode {
public kNode: Konva.Image;
public didFinishLoading: boolean = false;
constructor({
kNode,
selectionManagerRef,
mediaLayerRef,
}: {
kNode: Konva.Image;
selectionManagerRef: SelectionManager;
mediaLayerRef: Konva.Layer;
}) {
super({
kNode,
selectionManagerRef,
mediaLayerRef,
});
this.kNode = kNode;
this.didFinishLoading = false;
}
}
-252
View File
@@ -1,252 +0,0 @@
import Konva from "konva";
import { v4 as uuidv4 } from "uuid";
import { Layer } from "konva/lib/Layer";
import { uiAccess } from "~/signals";
import { SelectionManager } from "../NodesManagers";
import { Size } from "../types";
const toolbarNode = uiAccess.toolbarNode;
const loadingBar = uiAccess.loadingBar;
export class NetworkedNodeContext {
public selectionManagerRef: SelectionManager;
public kNode: Konva.Image;
protected mediaLayerRef: Layer;
public uuid: string;
// Internal State members
// do not modify internal
public didFinishLoading: boolean = false;
// This locks interaction when the render engine is rendering
protected isProcessing: boolean = false;
protected isSelecting: boolean = false;
protected _isLocked: boolean = false;
async setProcessing() {
this.isProcessing = true;
}
constructor({
selectionManagerRef,
mediaLayerRef,
kNode,
}: {
selectionManagerRef: SelectionManager;
mediaLayerRef: Layer;
kNode: Konva.Image;
}) {
console.log("reconstructed");
this.uuid = uuidv4();
this.mediaLayerRef = mediaLayerRef;
this.selectionManagerRef = selectionManagerRef;
this.kNode = kNode;
this.didFinishLoading = false;
}
protected calculateRenderSizeOnLoad({
componentSize,
maxSize,
}: {
componentSize: Size;
maxSize: Size;
}) {
const renderSize = {
width: componentSize.width,
height: componentSize.height,
};
if (renderSize.width > maxSize.width) {
const scaleDown = maxSize.width / renderSize.width;
renderSize.width = renderSize.width * scaleDown;
renderSize.height = renderSize.height * scaleDown;
}
if (renderSize.height > maxSize.height) {
const scaleDownAgain = maxSize.height / renderSize.height;
renderSize.width = renderSize.width * scaleDownAgain;
renderSize.height = renderSize.height * scaleDownAgain;
}
return renderSize;
}
public delete() {
// Do any other clean up and delete the konva node.
this.kNode.destroy();
}
public highlight() {
this.kNode.stroke("salmon");
this.kNode.strokeWidth(10);
}
public unHighLight() {
console.log("unHighlight", this.kNode._id);
this.kNode.strokeWidth(0);
}
public moveLayerUp() {
this.kNode.moveUp();
}
public moveLayerDown() {
this.kNode.moveDown();
}
public isLocked() {
return this._isLocked;
}
public lock() {
this._isLocked = true;
toolbarNode.lock();
this.kNode.setDraggable(false);
}
public unlock() {
this._isLocked = false;
toolbarNode.unlock();
this.kNode.setDraggable(true);
}
calculateContextualsPosition(kNode: Konva.Node) {
const w = kNode.getSize().width * kNode.scaleX();
const h = kNode.getSize().height * kNode.scaleY();
const x0 = kNode.getPosition().x;
const y0 = kNode.getPosition().y;
const d = kNode.getAbsoluteRotation();
const r = d >= 0 ? (d * Math.PI) / 180 : ((360 + d) * Math.PI) / 180;
let px: number, py: number;
if (r < Math.PI / 2) {
px = x0 + (h * Math.sin(r) + w * Math.cos(r)) / 2 - h * Math.sin(r);
py = y0 + h * Math.cos(r) + w * Math.cos(Math.PI / 2 - r);
} else if (r < Math.PI) {
px = x0 - (h * Math.sin(r) - w * Math.cos(r)) / 2;
py = y0 + w * Math.cos(Math.PI / 2 - r);
} else if (r < (Math.PI * 3) / 2) {
px = x0 + h * Math.cos(r) - (h * Math.cos(r) + w * Math.sin(r)) / 2;
py = y0;
} else {
px = x0 + (-h * Math.sin(r) + w * Math.cos(r)) / 2;
py = y0 + h * Math.cos(r);
}
return { x: px, y: py };
}
updateContextMenuPosition() {
const coord = this.calculateContextualsPosition(this.kNode);
toolbarNode.setPosition({
x: coord.x,
y: coord.y,
});
}
updateLoadingBarPosition() {
const coord = this.calculateContextualsPosition(this.kNode);
loadingBar.updatePosition({
x: coord.x,
y: coord.y,
});
}
public updateContextComponents() {
this.updateContextMenuPosition();
this.updateLoadingBarPosition();
if (this.didFinishLoading == false && !loadingBar.isShowing()) {
loadingBar.show();
} else {
loadingBar.hide();
}
if (!toolbarNode.isShowing()) {
toolbarNode.show({
locked: this._isLocked,
});
} else {
toolbarNode.update({
locked: this._isLocked,
});
}
}
public listenToBaseKNode() {
const handleSelect = (isMultiSelect: boolean) => {
if (!isMultiSelect) {
// clear selection if not multislect
//console.log("No Shift >> no multiselect");
this.selectionManagerRef.clearSelection();
}
if (this.selectionManagerRef.isNodeSelected(this)) {
this.selectionManagerRef.deselectNode(this);
return;
}
this.selectionManagerRef.selectNode(this);
};
this.kNode.on("mousedown", (e) => {
// console.log("Mouse down");
// Selection of Node
if (!this.selectionManagerRef.isNodeSelected(this)) {
this.isSelecting = true;
//checking for multiselect
handleSelect(e.evt.shiftKey);
}
if (!toolbarNode.isShowing()) {
toolbarNode.show({
locked: this._isLocked,
});
}
});
this.kNode.on("mouseup", (e) => {
// console.log("MOUSE UP");
// just coming out of dragging or selecting mode
// no need to handle select
if (this.selectionManagerRef.isDragging() || this.isSelecting) {
this.isSelecting = false;
return;
}
// checking for multiselect, in multiselect deselection is possible
handleSelect(e.evt.shiftKey);
});
}
public listenToBaseKNodeDrags() {
this.kNode.on("dragstart", () => {
// console.log("Drag start", this.kNode._id);
if (this.isProcessing) {
return;
}
this.selectionManagerRef.dragStart();
this.updateContextComponents();
});
this.kNode.on("dragmove", () => {
// console.log("Drag Move");
this.updateContextComponents();
});
this.kNode.on("dragend", () => {
// console.log("Drag End", this.kNode._id);
this.selectionManagerRef.dragEnd();
this.updateContextComponents();
});
}
public removeListenToBaseKNodeDrags() {
this.kNode.removeEventListener("dragstart");
this.kNode.removeEventListener("dragmove");
this.kNode.removeEventListener("dragend");
}
public listenToBaseKNodeTransformations() {
this.kNode.on("transformstart", (event) => {
console.log("transformstart", event.target._id);
this.selectionManagerRef.transformStart();
toolbarNode.hide();
loadingBar.hide();
});
this.kNode.on("transformend", (event) => {
console.log("transformend", event.target._id);
this.selectionManagerRef.transformEnd();
this.updateContextComponents();
});
}
public removeListenToBaseKNodeTransformations() {
this.kNode.removeEventListener("transformend");
}
}
+55
View File
@@ -0,0 +1,55 @@
import Konva from "konva";
import { Position, Size } from "../types";
export const NodeUtilities = {
adjustNodeSizeToCanvas,
positionNodeOnCanvasCenter,
printKNodeAttrs,
};
function adjustNodeSizeToCanvas({
componentSize,
maxSize,
}: {
componentSize: Size;
maxSize: Size;
}) {
const adjustedSize = {
width: componentSize.width,
height: componentSize.height,
};
if (adjustedSize.width > maxSize.width) {
const scaleDown = maxSize.width / adjustedSize.width;
adjustedSize.width = adjustedSize.width * scaleDown;
adjustedSize.height = adjustedSize.height * scaleDown;
}
if (adjustedSize.height > maxSize.height) {
const scaleDownAgain = maxSize.height / adjustedSize.height;
adjustedSize.width = adjustedSize.width * scaleDownAgain;
adjustedSize.height = adjustedSize.height * scaleDownAgain;
}
return adjustedSize;
}
function positionNodeOnCanvasCenter({
canvasOffset,
componentSize,
maxSize,
}: {
canvasOffset: Position;
componentSize: Size;
maxSize: Size;
}) {
return {
x: canvasOffset.x + maxSize.width / 2 - componentSize.width / 2,
y: canvasOffset.y + maxSize.height / 2 - componentSize.height / 2,
};
}
function printKNodeAttrs(kNode: Konva.Node) {
console.log({
position: kNode.position(),
size: kNode.size(),
scale: kNode.scale(),
rotation: kNode.rotation(),
});
}
+98
View File
@@ -0,0 +1,98 @@
import Konva from "konva";
import { SelectionManager } from "../NodesManagers";
import { BaseNode } from "./BaseNode";
import { Position, Size, TextNodeData } from "../types";
import { NodeUtilities } from "./NodeUtilities";
export class TextNode extends BaseNode {
public kNode: Konva.Group;
public rectNode: Konva.Rect;
public textNode: Konva.Text;
private originalTextSize: Size;
private textNodeData: TextNodeData;
public didFinishLoading: boolean = true;
constructor({
textNodeData,
selectionManagerRef,
mediaLayerRef,
position,
}: {
textNodeData: TextNodeData;
selectionManagerRef: SelectionManager;
mediaLayerRef: Konva.Layer;
position: Position;
}) {
console.log(textNodeData);
const textNode = new Konva.Text({
...textNodeData,
x: 10,
y: 10,
width: 500,
});
const kNode = new Konva.Group({
position: position,
width: textNode.width() + 20,
height: textNode.height() + 20,
draggable: true,
});
const rectNode = new Konva.Rect({
x: 0,
y: 0,
width: textNode.width() + 20,
height: textNode.height() + 20,
strokeScaleEnabled: false,
name: "wrapper",
});
super({
selectionManagerRef: selectionManagerRef,
mediaLayerRef: mediaLayerRef,
kNode: kNode,
});
this.textNodeData = textNodeData;
this.kNode = kNode;
this.textNode = textNode;
this.rectNode = rectNode;
this.kNode.add(this.rectNode);
this.kNode.add(this.textNode);
this.mediaLayerRef.add(this.kNode);
this.originalTextSize = this.textNode.size();
this.listenToBaseKNode();
}
public listenToBaseKNodeTransformations() {
this.kNode.on("transformstart", () => {
// console.log("transformstart", event.target._id);
this.selectionManagerRef.transformStart();
});
this.kNode.on("transform", () => {
const newBoxSize = {
width: this.kNode.width() * this.kNode.scaleX(),
height: this.kNode.height() * this.kNode.scaleY(),
scaleX: 1,
scaleY: 1,
};
this.rectNode.setAttrs(newBoxSize);
this.kNode.setAttrs(newBoxSize);
const scaleX = (newBoxSize.width - 20) / this.originalTextSize.width;
const scaleY = (newBoxSize.height - 20) / this.originalTextSize.height;
this.textNode.setAttrs({
// make sure these stay the same
x: 10,
y: 10,
size: this.originalTextSize,
//while these changes in size
scaleX: scaleX,
scaleY: scaleY,
});
});
this.kNode.on("transformend", () => {
// console.log("transformend", event.target._id);
NodeUtilities.printKNodeAttrs(this.kNode);
NodeUtilities.printKNodeAttrs(this.textNode);
this.selectionManagerRef.transformEnd(this);
});
}
}
+27 -14
View File
@@ -1,22 +1,24 @@
import Konva from "konva";
import { NetworkedNodeContext } from "./NetworkedNodeContext";
import { NetworkedNode } from "./NetworkedNode";
import { uiAccess } from "~/signals";
import { SelectionManager } from "../NodesManagers";
import { Position, Size } from "../types";
import ChromaWorker from "./ChromaWorker?sharedworker";
import { minNodeSize, transparent } from "./constants";
import { NodeUtilities } from "./NodeUtilities";
// const toolbarNode = uiAccess.toolbarNode;
const loadingBar = uiAccess.loadingBar;
interface VideoNodeContructor {
mediaLayerRef: Konva.Layer;
position: Position;
canvasPosition: Position;
canvasSize: Size;
videoURL: string;
selectionManagerRef: SelectionManager;
}
export class VideoNode extends NetworkedNodeContext {
export class VideoNode extends NetworkedNode {
public kNode: Konva.Image;
public videoURL: string;
public videoComponent: HTMLVideoElement;
@@ -91,7 +93,7 @@ export class VideoNode extends NetworkedNodeContext {
constructor({
mediaLayerRef,
position,
canvasPosition,
canvasSize,
videoURL,
selectionManagerRef,
@@ -99,12 +101,16 @@ export class VideoNode extends NetworkedNodeContext {
// kNodes need to be created first to guaruntee it is not undefined in parent's context
const kNode = new Konva.Image({
image: undefined,
x: position.x,
y: position.y,
width: 200, // to do fix this with placeholder
height: 200,
draggable: true,
// to do fix this with placeholder
size: minNodeSize,
position: NodeUtilities.positionNodeOnCanvasCenter({
canvasOffset: canvasPosition,
componentSize: minNodeSize,
maxSize: canvasSize,
}),
fill: "grey",
draggable: true,
strokeScaleEnabled: false,
});
super({
@@ -112,6 +118,7 @@ export class VideoNode extends NetworkedNodeContext {
mediaLayerRef: mediaLayerRef,
kNode: kNode,
});
this.kNode = kNode;
this.mediaLayerRef.add(this.kNode);
// state manage the node
@@ -150,22 +157,28 @@ export class VideoNode extends NetworkedNodeContext {
return;
}
const renderSize = this.calculateRenderSizeOnLoad({
const adjustedSize = NodeUtilities.adjustNodeSizeToCanvas({
componentSize: {
width: this.videoComponent.videoWidth,
height: this.videoComponent.videoHeight,
},
maxSize: canvasSize,
});
const centerPosition = NodeUtilities.positionNodeOnCanvasCenter({
canvasOffset: canvasPosition,
componentSize: adjustedSize,
maxSize: canvasSize,
});
this.kNode.image(this.videoComponent);
this.kNode.setSize(renderSize);
this.kNode.setSize(adjustedSize);
this.kNode.setPosition(centerPosition);
this.videoComponent.currentTime = 0; // ensure it shows up on screen
// it might have length here which we will need to trim down to 7 seconds.
console.log(`Video Duration: ${this.videoComponent.duration}`);
this.duration = this.videoComponent.duration;
this.kNode.fill(null);
this.kNode.fill(transparent);
loadingBar.updateProgress(50);
};
@@ -390,7 +403,7 @@ export class VideoNode extends NetworkedNodeContext {
await this.updateImage(this.imageSources[this.imageIndex]);
this.imageIndex = this.imageIndex + 1;
this.updateLoadingBarPosition();
// this.updateLoadingBarPosition();
loadingBar.updateMessage("Generating");
+7
View File
@@ -0,0 +1,7 @@
export const transparent = "rgba(0,0,0,0)";
export const primaryOrange = "rgba(230,100,98,1)";
export const highlightStrokeWidth = 10;
export const minNodeSize = {
width: 200,
height: 200,
};
+3
View File
@@ -0,0 +1,3 @@
export { ImageNode } from "./ImageNode";
export { VideoNode } from "./VideoNode";
export { TextNode } from "./TextNodes";
@@ -6,6 +6,7 @@ export class NodeTransformer {
constructor() {
this.kTransformer = new Konva.Transformer({
padding: 0,
anchorStyleFunc: (anchor) => {
// anchor is Konva.Rect instance
// you manually change its styling
+127 -45
View File
@@ -2,6 +2,7 @@ import Konva from "konva";
import { NodeTransformer } from "./NodeTransformer";
import { MediaNode, Position, Transformation } from "../types";
import { uiAccess } from "~/signals";
import { NetworkedNode } from "../Nodes/NetworkedNode";
export enum SelectionManagerEvents {
NODES_TRANSLATIONS = "nodestranslation",
@@ -14,8 +15,8 @@ export interface NodesTranslationEventDetails {
}
export interface NodeTransformationEventDetails {
nodes: Set<MediaNode>;
initialTransformations: Map<MediaNode, Transformation>;
finalTransformations: Map<MediaNode, Transformation>;
initialTransformations: Map<MediaNode, Transformation[]>;
finalTransformations: Map<MediaNode, Transformation[]>;
}
export class SelectionManager {
@@ -23,7 +24,7 @@ export class SelectionManager {
private mediaLayerRef: Konva.Layer;
private nodeTransformerRef: NodeTransformer;
private initialPositions: Map<MediaNode, Position>;
private initialTransformations: Map<MediaNode, Transformation>;
private initialTransformations: Map<MediaNode, Transformation[]>;
public eventTarget: EventTarget;
private _isDragging: boolean = false;
@@ -36,12 +37,15 @@ export class SelectionManager {
}) {
this.selectedNodes = new Set();
this.initialPositions = new Map<MediaNode, Position>();
this.initialTransformations = new Map<MediaNode, Transformation>();
this.initialTransformations = new Map<MediaNode, Transformation[]>();
this.eventTarget = new EventTarget();
this.mediaLayerRef = mediaLayerRef;
this.nodeTransformerRef = nodeTransformerRef;
}
// This lets us perfom operations on the selected node.
public getSelectedNodes(): Set<MediaNode> {
return this.selectedNodes;
}
public selectNodes(nodes: MediaNode[]) {
const unlockedNodes = nodes.filter((node) => {
return !node.isLocked();
@@ -60,11 +64,9 @@ export class SelectionManager {
}
node.highlight();
if (this.selectedNodes.size === 0) {
node.removeListenToBaseKNodeTransformations();
node.listenToBaseKNodeTransformations();
node.removeListenToBaseKNodeDrags();
node.listenToBaseKNodeDrags();
node.updateContextComponents();
node.setIsKEventRef(true);
this.updateContextComponents(node);
this.showContextComponents(node);
}
this.selectedNodes.add(node);
this.updateNodeTransformer();
@@ -75,9 +77,10 @@ export class SelectionManager {
}
public deselectNode(node: MediaNode, doNotDraw?: boolean): void {
node.unHighLight();
node.removeListenToBaseKNodeTransformations();
node.removeListenToBaseKNodeDrags();
node.unhighlight();
if (node.isKEventRef()) {
node.setIsKEventRef(false);
}
this.selectedNodes.delete(node);
this.updateNodeTransformer();
if (!doNotDraw) {
@@ -88,16 +91,21 @@ export class SelectionManager {
public clearSelection(): void {
console.log("Clearing Selection");
this.selectedNodes.forEach((node) => {
node.unHighLight();
node.removeListenToBaseKNodeTransformations();
node.removeListenToBaseKNodeDrags();
node.unhighlight();
if (node.isKEventRef()) {
node.setIsKEventRef(false);
}
});
this.selectedNodes.clear();
this.nodeTransformerRef.clear();
uiAccess.toolbarNode.hide();
this.hideContextComponents();
this.mediaLayerRef.batchDraw();
}
public onToggleLock(node: MediaNode) {
this.updateContextComponents(node);
}
public isDragging() {
return this._isDragging;
}
@@ -108,13 +116,15 @@ export class SelectionManager {
public dragStart() {
// track the dragging state for mouseevents in nodes
this._isDragging = true;
this.hideContextComponents();
// track all nodes initial position when dragstart
this.selectedNodes.forEach((selectedNode) => {
this.initialPositions.set(selectedNode, selectedNode.kNode.position());
});
}
public dragEnd() {
public dragEnd(refNode: MediaNode) {
// release the dragging state for mouseevents in nodes
this._isDragging = false;
// track and map the final positions
@@ -135,36 +145,49 @@ export class SelectionManager {
},
),
);
// done, clear data and show menu
this.initialPositions.clear();
this.updateContextComponents(refNode);
this.showContextComponents(refNode);
}
private getKNodeTransformation(kNode: Konva.Node) {
return {
kNodeId: kNode._id,
position: kNode.position(),
size: kNode.size(),
rotation: kNode.rotation(),
scale: {
x: kNode.scaleX(),
y: kNode.scaleY(),
},
};
}
public transformStart() {
// track all nodes initial transformation when transformStart
this.selectedNodes.forEach((selectedNode) => {
this.initialTransformations.set(selectedNode, {
position: selectedNode.kNode.position(),
size: selectedNode.kNode.size(),
rotation: selectedNode.kNode.rotation(),
scale: {
scaleX: selectedNode.kNode.scaleX(),
scaleY: selectedNode.kNode.scaleY(),
},
});
const transformations = [this.getKNodeTransformation(selectedNode.kNode)];
if (selectedNode.kNode instanceof Konva.Group) {
const childKNodes = selectedNode.kNode.getChildren();
childKNodes.forEach((childKNode) => {
transformations.push(this.getKNodeTransformation(childKNode));
});
}
this.initialTransformations.set(selectedNode, transformations);
});
this.hideContextComponents();
}
public transformEnd() {
public transformEnd(refNode: MediaNode) {
// track and map the final positions
const finalTransformation = new Map<MediaNode, Transformation>();
const finalTransformations = new Map<MediaNode, Transformation[]>();
this.selectedNodes.forEach((selectedNode) => {
finalTransformation.set(selectedNode, {
position: selectedNode.kNode.position(),
size: selectedNode.kNode.size(),
rotation: selectedNode.kNode.rotation(),
scale: {
scaleX: selectedNode.kNode.scaleX(),
scaleY: selectedNode.kNode.scaleY(),
},
});
const transformations = [this.getKNodeTransformation(selectedNode.kNode)];
if (selectedNode.kNode instanceof Konva.Group) {
const childKNodes = selectedNode.kNode.getChildren();
childKNodes.forEach((childKNode) => {
transformations.push(this.getKNodeTransformation(childKNode));
});
}
finalTransformations.set(selectedNode, transformations);
});
// dispatch the info for engine to manage un-redo stack
this.eventTarget.dispatchEvent(
@@ -174,16 +197,13 @@ export class SelectionManager {
detail: {
nodes: this.selectedNodes,
initialTransformations: new Map(this.initialTransformations),
finalTransformations: finalTransformation,
finalTransformations: finalTransformations,
},
},
),
);
}
// This lets us perfom operations on the selected node.
public getSelectedNodes(): Set<MediaNode> {
return this.selectedNodes;
this.updateContextComponents(refNode);
this.showContextComponents(refNode);
}
private updateNodeTransformer() {
@@ -194,4 +214,66 @@ export class SelectionManager {
);
this.nodeTransformerRef.enable({ selectedNodes: transformableNodes });
}
protected calculateContextualsPosition(kNode: Konva.Node) {
const w = kNode.getSize().width * kNode.scaleX();
const h = kNode.getSize().height * kNode.scaleY();
const x0 = kNode.getPosition().x;
const y0 = kNode.getPosition().y;
const d = kNode.getAbsoluteRotation();
const r = d >= 0 ? (d * Math.PI) / 180 : ((360 + d) * Math.PI) / 180;
let px: number, py: number;
if (r < Math.PI / 2) {
px = x0 + (h * Math.sin(r) + w * Math.cos(r)) / 2 - h * Math.sin(r);
py = y0 + h * Math.cos(r) + w * Math.cos(Math.PI / 2 - r);
} else if (r < Math.PI) {
px = x0 - (h * Math.sin(r) - w * Math.cos(r)) / 2;
py = y0 + w * Math.cos(Math.PI / 2 - r);
} else if (r < (Math.PI * 3) / 2) {
px = x0 + h * Math.cos(r) - (h * Math.cos(r) + w * Math.sin(r)) / 2;
py = y0;
} else {
px = x0 + (-h * Math.sin(r) + w * Math.cos(r)) / 2;
py = y0 + h * Math.cos(r);
}
return { x: px, y: py };
}
public hideContextComponents() {
uiAccess.toolbarNode.hide();
}
public showContextComponents(node: MediaNode) {
if (!uiAccess.toolbarNode.isShowing()) {
uiAccess.toolbarNode.show({
locked: node.isLocked(),
});
} else {
uiAccess.toolbarNode.update({
locked: node.isLocked(),
});
}
if (node instanceof NetworkedNode) {
if (!node.didFinishLoading && !uiAccess.loadingBar.isShowing()) {
uiAccess.loadingBar.show();
}
}
}
public updateContextComponents(node: MediaNode) {
const coord = this.calculateContextualsPosition(node.kNode);
if (node.isLocked() !== uiAccess.toolbarNode.isLocked()) {
uiAccess.toolbarNode.setLocked(node.isLocked());
}
uiAccess.toolbarNode.setPosition({
x: coord.x,
y: coord.y,
});
if (node instanceof NetworkedNode) {
uiAccess.loadingBar.updatePosition({
x: coord.x,
y: coord.y,
});
}
}
}
+12 -10
View File
@@ -33,19 +33,21 @@ export class SelectorSquare {
}
public enable({
captureCanvasRef,
mediaLayerRef,
nodesManagerRef,
selectionManagerRef,
stage,
stageRef,
}: {
captureCanvasRef: Konva.Rect;
mediaLayerRef: Konva.Layer;
nodesManagerRef: NodesManager;
selectionManagerRef: SelectionManager;
stage: Konva.Stage;
stageRef: Konva.Stage;
}) {
stage.on("mousedown touchstart", (e) => {
const stagePointerPos = stage.getPointerPosition();
stageRef.on("mousedown touchstart", (e) => {
const stagePointerPos = stageRef.getPointerPosition();
if (
(e.target !== stage && e.target !== captureCanvasRef) || // do nothing if we mousedown on any shape
(e.target !== stageRef && e.target !== captureCanvasRef) || // do nothing if we mousedown on any shape
stagePointerPos === null || // do nothing if pointers not available
e.evt.shiftKey // do nothing so then multiselect is more forgiving in misclicks
) {
@@ -54,7 +56,7 @@ export class SelectorSquare {
// start handle mousedown
e.evt.preventDefault();
if (e.target === stage || e.target === captureCanvasRef) {
if (e.target === stageRef || e.target === captureCanvasRef) {
//moused down on empty space, clear previous selection first
selectionManagerRef.clearSelection();
}
@@ -73,9 +75,9 @@ export class SelectorSquare {
this.selecting = true;
});
stage.on("mousemove touchmove", (e) => {
stageRef.on("mousemove touchmove", (e) => {
// do nothing if we didn't start selection
const stagePointerPos = stage.getPointerPosition();
const stagePointerPos = stageRef.getPointerPosition();
if (!this.selecting || stagePointerPos === null) {
return;
}
@@ -97,7 +99,7 @@ export class SelectorSquare {
height: Math.abs(y2 - y1),
});
stage.on("mouseup touchend", (e) => {
stageRef.on("mouseup touchend", (e) => {
// do nothing if we didn't start selection
this.selecting = false;
if (!this.kSquare.visible()) {
@@ -107,7 +109,7 @@ export class SelectorSquare {
// update visibility
this.kSquare.visible(false);
// Find all the Nodes and feed them into Selectmanager
var shapes = stage.find("Image");
var shapes = mediaLayerRef.getChildren();
var box = this.kSquare.getClientRect();
var foundKNodes = shapes.filter((shape) =>
Konva.Util.haveIntersection(box, shape.getClientRect()),
@@ -206,10 +206,11 @@ export class RenderEngine {
}
// update the context menu
this.videoNodes.forEach((node) => {
// find selected node... TODO and update that position
node.updateContextComponents();
});
// TODO: find selected node and update that position
// this.videoNodes.forEach((node) => {
// node.updateContextComponents();
// });
this.mediaLayerRef.batchDraw();
}
@@ -350,7 +351,7 @@ export class RenderEngine {
return;
}
item.kNode.listening(false);
item.unHighLight();
item.unhighlight();
if (item.didFinishLoading == false) {
// error out and show error message
//this.startProcessing();
+46 -18
View File
@@ -4,8 +4,8 @@ import { MediaNode, Transformation } from "../types";
export class TransformCommand implements ICommand {
private nodes: Set<MediaNode>;
private initialTransformations: Map<MediaNode, Transformation>;
private finalTransformations: Map<MediaNode, Transformation>;
private initialTransformations: Map<MediaNode, Transformation[]>;
private finalTransformations: Map<MediaNode, Transformation[]>;
private layerRef: Konva.Layer;
constructor({
@@ -15,24 +15,54 @@ export class TransformCommand implements ICommand {
layerRef,
}: {
nodes: Set<MediaNode>;
initialTransformations: Map<MediaNode, Transformation>;
finalTransformations: Map<MediaNode, Transformation>;
initialTransformations: Map<MediaNode, Transformation[]>;
finalTransformations: Map<MediaNode, Transformation[]>;
layerRef: Konva.Layer;
}) {
this.nodes = new Set<MediaNode>(nodes);
this.initialTransformations = initialTransformations;
this.finalTransformations = finalTransformations;
this.layerRef = layerRef;
console.log("transform command", this);
}
private transformKNode(kNode: Konva.Node, transform: Transformation) {
kNode.setAttrs({
position: transform.position,
size: transform.size,
rotation: transform.rotation,
scale: transform.scale,
});
}
private findKNodeOfTransform = (
node: MediaNode,
transform: Transformation,
) => {
const parentKNode = node.kNode;
if (parentKNode._id === transform.kNodeId) {
this.transformKNode(parentKNode, transform);
return;
}
if (parentKNode instanceof Konva.Group) {
const childKNode = parentKNode
.getChildren()
.find((currChild) => currChild._id === transform.kNodeId);
if (childKNode) {
this.transformKNode(childKNode, transform);
return;
}
}
if (import.meta.env.DEV) {
console.warn("Error in Transform", node, transform);
}
};
execute() {
this.nodes.forEach((node) => {
const finalTransformation = this.finalTransformations.get(node);
if (finalTransformation) {
node.kNode.position(finalTransformation.position);
node.kNode.size(finalTransformation.size);
node.kNode.rotation(finalTransformation.rotation);
node.kNode.scaleX(finalTransformation.scale.scaleX);
node.kNode.scaleY(finalTransformation.scale.scaleY);
const finalTransformations = this.finalTransformations.get(node);
if (finalTransformations) {
finalTransformations.forEach((transform) =>
this.findKNodeOfTransform(node, transform),
);
}
});
this.layerRef.draw();
@@ -40,13 +70,11 @@ export class TransformCommand implements ICommand {
undo() {
this.nodes.forEach((node) => {
const initialTransformation = this.initialTransformations.get(node);
if (initialTransformation) {
node.kNode.position(initialTransformation.position);
node.kNode.size(initialTransformation.size);
node.kNode.rotation(initialTransformation.rotation);
node.kNode.scaleX(initialTransformation.scale.scaleX);
node.kNode.scaleY(initialTransformation.scale.scaleY);
const initialTransformations = this.initialTransformations.get(node);
if (initialTransformations) {
initialTransformations.forEach((transform) =>
this.findKNodeOfTransform(node, transform),
);
}
});
this.layerRef.draw();
+15 -9
View File
@@ -1,21 +1,27 @@
import { Vector2d } from "konva/lib/types";
import { BaseNode } from "./Nodes/BaseNode";
// import { NetworkedNode } from "./Nodes/NetworkedNode";
import { VideoNode } from "./Nodes/VideoNode";
import { ImageNode } from "./Nodes/ImageNode";
import { NetworkedNodeContext } from "./Nodes/NetworkedNodeContext";
export type MediaNode = NetworkedNodeContext | VideoNode | ImageNode;
import { TextNode } from "./Nodes/TextNodes";
export interface Scale {
scaleX: number;
scaleY: number;
export interface TextNodeData {
text: string;
}
export type MediaNode =
// | NetworkedNode
BaseNode | VideoNode | ImageNode | TextNode;
export interface Position extends Vector2d {}
export interface Scale extends Vector2d {}
export interface Size {
width: number;
height: number;
}
export interface Position {
x: number;
y: number;
}
export interface Transformation {
kNodeId: number | string;
position: Position;
size: Size;
scale: Scale;
@@ -48,9 +48,7 @@ export const KonvaRootComponent = ({ className }: { className: string }) => {
/>
<SignaledToolbarMain
layoutSignal={layoutContext.signal}
openAddImage={appUiContext.openAddImage}
openAddVideo={appUiContext.openAddVideo}
openAIStylize={appUiContext.openAIStylize}
appUiContext={appUiContext}
/>
<SignaledDialogs
appUiSignal={appUiContext.signal}
+11 -1
View File
@@ -1,11 +1,12 @@
import { useCallback } from "react";
import { Signal } from "@preact/signals-react";
import { dispatchUiEvents, uiEvents } from "~/signals";
import { dispatchUiEvents } from "~/signals";
import {
DialogAddImage,
DialogAddVideo,
DialogAiStylize,
DialogChromakey,
DialogEditText,
DialogError,
} from "~/components/features";
@@ -22,6 +23,7 @@ export const SignaledDialogs = ({
}) => {
return (
<>
{/* Dialogs that are opened from within Reactland*/}
<DialogAddImage
isOpen={appUiSignal.value.isAddImageOpen ?? false}
stagedImage={appUiSignal.value.stagedImage}
@@ -54,6 +56,14 @@ export const SignaledDialogs = ({
}}
closeCallback={resetAll}
/>
<DialogEditText
isOpen={appUiSignal.value.isEditTextOpen ?? false}
onDoneEditText={(data) => {
dispatchUiEvents.addTextToEngine(data);
}}
closeCallback={resetAll}
/>
{/* Dialogs that are opened from the Engine*/}
<SignaledDialogChromakey />
<SignaledDialogError />
</>
+18 -10
View File
@@ -9,17 +9,14 @@ import { LoadingBar } from "~/components/ui";
import { ToolbarMainButtonNames } from "~/components/features/ToolbarMain/enum";
import { LayoutSignalType } from "./contextSignals/layout";
import { AppUiContextInterface } from "./contextSignals/appUi";
export const SignaledToolbarMain = ({
layoutSignal,
openAddImage,
openAddVideo,
openAIStylize,
appUiContext,
}: {
layoutSignal: LayoutSignalType;
openAddImage: () => void;
openAddVideo: () => void;
openAIStylize: () => void;
appUiContext: AppUiContextInterface;
}) => {
//// for testing
const { isMobile } = layoutSignal;
@@ -34,12 +31,26 @@ export const SignaledToolbarMain = ({
/// end for testing
const loadingBar = toolbarMain.loadingBar.signal.value;
const getButtonDispatcher = (buttonName: ToolbarMainButtonNames) => {
switch (buttonName) {
case ToolbarMainButtonNames.ADD_TEXT:
return () => appUiContext.openEditText();
case ToolbarMainButtonNames.AI_STYLIZE:
return () => appUiContext.openAIStylize();
case ToolbarMainButtonNames.ADD_IMAGE:
return () => appUiContext.openAddImage();
case ToolbarMainButtonNames.ADD_VIDEO:
return () => appUiContext.openAddVideo();
default:
return dispatchers[buttonName];
}
};
const buttonProps = Object.values(ToolbarMainButtonNames).reduce(
(acc, buttonName) => {
acc[buttonName] = {
disabled: toolbarMain.signal.value.buttonStates[buttonName].disabled,
active: toolbarMain.signal.value.buttonStates[buttonName].active,
onClick: dispatchers[buttonName],
onClick: getButtonDispatcher(buttonName),
};
return acc;
},
@@ -74,9 +85,6 @@ export const SignaledToolbarMain = ({
<ToolbarMain
disabled={toolbarMain.signal.value.disabled}
buttonProps={buttonProps}
openAddImage={() => openAddImage()}
openAddVideo={() => openAddVideo()}
openAIStylize={() => openAIStylize()}
/>
</div>
);
@@ -1,12 +1,20 @@
import { useCallback, useRef } from "react";
import { signal } from "@preact/signals-react";
export interface AppUiContextInterface {
resetAll: () => void;
openAddImage: (file?: File) => void;
openAddVideo: (file?: File) => void;
openAIStylize: () => void;
openEditText: () => void;
}
export type AppUiSignalType = {
isAddVideoOpen: boolean;
stagedVideo: File | null;
isAddImageOpen: boolean;
stagedImage: File | null;
isAiStylizeOpen: boolean;
isEditTextOpen: boolean;
};
const appUiInitialState = {
isAddVideoOpen: false,
@@ -14,6 +22,7 @@ const appUiInitialState = {
isAddImageOpen: false,
stagedImage: null,
isAiStylizeOpen: false,
isEditTextOpen: false,
};
export const useAppUiContext = () => {
const appUiRef = useRef(signal<AppUiSignalType>(appUiInitialState));
@@ -42,11 +51,18 @@ export const useAppUiContext = () => {
isAiStylizeOpen: true,
};
}, []);
const openEditText = useCallback(() => {
appUiSignal.value = {
...appUiInitialState,
isEditTextOpen: true,
};
}, []);
return {
signal: appUiSignal,
resetAll,
openAddImage,
openAddVideo,
openAIStylize,
openEditText,
};
};
@@ -86,9 +86,7 @@ export const DialogAiStylize = ({
>
{panelState === SubPanelNames.BASIC && (
<>
<DialogTitle className="font-bold">
Use AI to Stylize{" "}
</DialogTitle>
<DialogTitle className="font-bold">Use AI to Stylize</DialogTitle>
<SubPanelBasic
selectedArtStyle={selectedArtStyle}
positivePrompt={positivePrompt}
@@ -0,0 +1,102 @@
import { useState, ChangeEvent } from "react";
import { twMerge } from "tailwind-merge";
import { Dialog, DialogPanel, DialogTitle } from "@headlessui/react";
import { Button, TextEditor } from "~/components/ui";
import { TextNodeData } from "~/KonvaApp/types";
import {
dialogBackgroundStyles,
paperWrapperStyles,
} from "~/components/styles";
import {
TextFormatData,
FontStyle,
FontWeight,
TextAlign,
TextDecoration,
} from "~/components/ui/TextEditor/types";
export const DialogEditText = ({
isOpen,
closeCallback,
onDoneEditText,
}: {
isOpen: boolean;
closeCallback: () => void;
onDoneEditText: (doneData: TextNodeData) => void;
}) => {
const [text, setText] = useState<string>("");
const [textFormatData, setTextFormatData] = useState<TextFormatData>({
color: "#000000",
fontFamily: "Arial",
fontSize: 20,
fontStyle: FontStyle.NORMAL,
fontWeight: FontWeight.NORMAL,
textAlign: TextAlign.CENTER,
textDecoration: TextDecoration.NONE,
});
const handleOnChangeText = (newText: string) => {
setText(newText);
};
const handleOnChangeFormatting = (newFormatData: Partial<TextFormatData>) => {
setTextFormatData((curr) => ({
...curr,
...newFormatData,
}));
};
const handleOnDoneEditText = () => {
const textNodeData = {
text: text,
fill: textFormatData.color,
fontFamily: textFormatData.fontFamily,
align: textFormatData.textAlign,
fontStyle:
textFormatData.fontStyle === FontStyle.NORMAL &&
textFormatData.fontWeight === FontWeight.NORMAL
? "normal"
: `${textFormatData.fontWeight !== FontWeight.NORMAL && textFormatData.fontWeight} ${textFormatData.fontStyle !== FontStyle.NORMAL && textFormatData.fontStyle}`,
textDecoration:
textFormatData.textDecoration === TextDecoration.NONE
? ""
: textFormatData.textDecoration,
};
onDoneEditText(textNodeData);
closeCallback();
};
return (
<Dialog open={isOpen} onClose={closeCallback} className="relative z-50">
<div className={dialogBackgroundStyles}>
<DialogPanel
className={twMerge(
paperWrapperStyles,
"flex w-full max-w-5xl flex-col justify-between gap-4 px-6 pb-6 pt-4",
)}
// style={{ height: "calc(100vh - 200px)" }}
>
<DialogTitle className="font-bold">Edit Text</DialogTitle>
<TextEditor
text={text}
formatData={textFormatData}
onChangeText={handleOnChangeText}
onChangeFormatting={handleOnChangeFormatting}
/>
<div className="flex w-full justify-center gap-4">
<Button onClick={closeCallback} variant="secondary">
Cancel
</Button>
<Button
className="hover:animate-pulse"
onClick={handleOnDoneEditText}
disabled={text === ""}
>
Enter
</Button>
</div>
</DialogPanel>
</div>
</Dialog>
);
};
@@ -0,0 +1 @@
export { DialogEditText } from "./DialogEditText";
@@ -0,0 +1,128 @@
import { Fragment, MouseEventHandler } from "react";
import { twMerge } from "tailwind-merge";
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faArrowRotateLeft,
faArrowRotateRight,
faBars,
faCameraRotate,
faDownload,
faFilePlus,
faFilm,
faFloppyDisk,
faHatWizard,
faImage,
faLocationArrow,
faText,
} from "@fortawesome/pro-solid-svg-icons";
import { ToolbarButton } from "../ToolbarButton";
// style and constants
import { paperWrapperStyles, toolTipStyles } from "~/components/styles";
import { ToolbarMainButtonNames } from "./enum";
export const ToolbarMain = ({
disabled = false,
buttonProps,
}: {
disabled?: boolean;
buttonProps: {
[key in ToolbarMainButtonNames]: {
disabled?: boolean;
active?: boolean;
onClick?: MouseEventHandler<HTMLButtonElement>;
};
};
}) => {
return (
<>
<div
className={twMerge(
"flex w-fit items-center divide-x divide-ui-border",
paperWrapperStyles,
disabled &&
"pointer-events-none cursor-default bg-ui-border shadow-md",
)}
>
<div className="pl-1 pr-2">
<ToolbarButton icon={faBars} buttonProps={buttonProps.MENU} />
</div>
<div className="flex items-center gap-2 px-2">
<ToolbarButton
icon={faLocationArrow}
iconProps={{ className: "fa-flip-horizontal" }}
buttonProps={buttonProps.SELECT}
tooltip="Select"
/>
<ToolbarButton
icon={faText}
buttonProps={buttonProps.ADD_TEXT}
tooltip="Add Text"
/>
<Popover className="relative">
<PopoverButton as={Fragment}>
<button
data-tooltip="Add..."
className={twMerge(
"size-10 rounded-2xl p-2 hover:bg-secondary-500 hover:text-white",
toolTipStyles,
)}
>
<FontAwesomeIcon icon={faFilePlus} />
</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={faImage} buttonProps={buttonProps.ADD_IMAGE}>
Add Image
</ToolbarButton>
<ToolbarButton icon={faFilm} buttonProps={buttonProps.ADD_VIDEO}>
Add Video
</ToolbarButton>
</PopoverPanel>
</Popover>
{/* <ToolbarButton
icon={faCameraRotate}
buttonProps={buttonProps.CHANGE_CAMERA_ORIENTATION}
tooltip="Change Orientation"
/> */}
<ToolbarButton
icon={faHatWizard}
buttonProps={buttonProps.AI_STYLIZE}
tooltip="AI Stylize"
/>
</div>
<div className="flex items-center gap-2 px-2">
<ToolbarButton
icon={faArrowRotateLeft}
buttonProps={buttonProps.UNDO}
tooltip="Undo"
/>
<ToolbarButton
icon={faArrowRotateRight}
buttonProps={buttonProps.REDO}
tooltip="Redo"
/>
<ToolbarButton
icon={faFloppyDisk}
buttonProps={buttonProps.SAVE}
tooltip="Save"
/>
<ToolbarButton
icon={faDownload}
buttonProps={buttonProps.DOWNLOAD}
tooltip="Download"
/>
</div>
</div>
</>
);
};
+4 -1
View File
@@ -1,7 +1,10 @@
export enum ToolbarMainButtonNames {
MENU = "MENU",
SELECT = "SELECT",
CHANGE_CAMERA_ORIENTATION = "CHANGE_CAMERA_ORIENTATION",
ADD_TEXT = "ADD_TEXT",
ADD_IMAGE = "ADD_IMAGE",
ADD_VIDEO = "ADD_VIDEO",
// CHANGE_CAMERA_ORIENTATION = "CHANGE_CAMERA_ORIENTATION",
AI_STYLIZE = "AI_STYLIZE",
UNDO = "UNDO",
REDO = "REDO",
+1 -129
View File
@@ -1,129 +1 @@
import { Fragment, MouseEventHandler } from "react";
import { twMerge } from "tailwind-merge";
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faArrowRotateLeft,
faArrowRotateRight,
faBars,
faCameraRotate,
faDownload,
faFilePlus,
faFilm,
faFloppyDisk,
faHatWizard,
faImage,
faLocationArrow,
} from "@fortawesome/pro-solid-svg-icons";
import { ToolbarButton } from "../ToolbarButton";
// style and constants
import { paperWrapperStyles, toolTipStyles } from "~/components/styles";
import { ToolbarMainButtonNames } from "./enum";
export const ToolbarMain = ({
disabled = false,
buttonProps,
openAddImage,
openAddVideo,
openAIStylize,
}: {
disabled?: boolean;
buttonProps: {
[key in ToolbarMainButtonNames]: {
disabled?: boolean;
active?: boolean;
onClick?: MouseEventHandler<HTMLButtonElement>;
};
};
openAddImage: () => void;
openAddVideo: () => void;
openAIStylize: () => void;
}) => {
return (
<>
<div
className={twMerge(
"flex w-fit items-center divide-x divide-ui-border",
paperWrapperStyles,
disabled &&
"pointer-events-none cursor-default bg-ui-border shadow-md",
)}
>
<div className="pl-1 pr-2">
<ToolbarButton icon={faBars} buttonProps={buttonProps.MENU} />
</div>
<div className="flex items-center gap-2 px-2">
<ToolbarButton
icon={faLocationArrow}
iconProps={{ className: "fa-flip-horizontal" }}
buttonProps={buttonProps.SELECT}
tooltip="Select"
/>
<Popover className="relative">
<PopoverButton as={Fragment}>
<button
data-tooltip="Add..."
className={twMerge(
"size-10 rounded-2xl p-2 hover:bg-secondary-500 hover:text-white",
toolTipStyles,
)}
>
<FontAwesomeIcon icon={faFilePlus} />
</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={faImage} onClick={openAddImage}>
Add Image
</ToolbarButton>
<ToolbarButton icon={faFilm} onClick={openAddVideo}>
Add Video
</ToolbarButton>
</PopoverPanel>
</Popover>
<ToolbarButton
icon={faCameraRotate}
buttonProps={buttonProps.CHANGE_CAMERA_ORIENTATION}
tooltip="Change Orientation"
/>
<ToolbarButton
icon={faHatWizard}
buttonProps={buttonProps.AI_STYLIZE}
onClick={openAIStylize}
tooltip="AI Stylize"
/>
</div>
<div className="flex items-center gap-2 px-2">
<ToolbarButton
icon={faArrowRotateLeft}
buttonProps={buttonProps.UNDO}
tooltip="Undo"
/>
<ToolbarButton
icon={faArrowRotateRight}
buttonProps={buttonProps.REDO}
tooltip="Redo"
/>
<ToolbarButton
icon={faFloppyDisk}
buttonProps={buttonProps.SAVE}
tooltip="Save"
/>
<ToolbarButton
icon={faDownload}
buttonProps={buttonProps.DOWNLOAD}
tooltip="Download"
/>
</div>
</div>
</>
);
};
export { ToolbarMain } from "./ToolbarMain";
+1
View File
@@ -3,6 +3,7 @@ export { DialogAddImage } from "./DialogAddImage";
export { DialogAddVideo } from "./DialogAddVideo";
export { DialogAiStylize } from "./DialogAiStylize";
export { DialogChromakey } from "./DialogChromakey";
export { DialogEditText } from "./DialogEditText";
export { DialogError } from "./DialogError";
export { ProfileDropdown } from "./ToolbarUserProfile/ProfileDropdown";
export { ToolbarMain } from "./ToolbarMain";
+58
View File
@@ -0,0 +1,58 @@
import { useState } from "react";
import { faChevronDown } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
Combobox as HeadlessComboBox,
ComboboxButton,
ComboboxInput,
ComboboxOption,
ComboboxOptions,
} from "@headlessui/react";
interface ComboBoxInterface {
options: string[];
value: string;
onChange: (newVal: string) => void;
}
export const Combobox = ({ options, value, onChange }: ComboBoxInterface) => {
const [query, setQuery] = useState("");
const filteredOptions =
query === ""
? options
: options.filter((option) => {
return option.toLowerCase().includes(option.toLowerCase());
});
return (
<HeadlessComboBox
value={value}
onChange={onChange}
onClose={() => setQuery("")}
>
<div className="flex">
<ComboboxInput
aria-label="Assignee"
displayValue={(font: string) => font}
onChange={(event) => setQuery(event.target.value)}
/>
<ComboboxButton className="">
<FontAwesomeIcon
icon={faChevronDown}
className="size-4 fill-black/60 group-data-[hover]:fill-black"
/>
</ComboboxButton>
</div>
<ComboboxOptions anchor="bottom" className="border empty:invisible">
{filteredOptions.map((option, idx) => (
<ComboboxOption
key={idx}
value={option}
className="data-[focus]:bg-blue-100"
>
{option}
</ComboboxOption>
))}
</ComboboxOptions>
</HeadlessComboBox>
);
};
+7 -5
View File
@@ -2,7 +2,7 @@ import { TextareaHTMLAttributes } from "react";
import { twMerge } from "tailwind-merge";
import { kebabCase } from "~/utilities";
type ResizeType =
export type ResizeType =
| "none"
| "both"
| "horizontal"
@@ -11,16 +11,18 @@ type ResizeType =
| "inline"
| undefined;
export interface TextareaInterface
extends TextareaHTMLAttributes<HTMLTextAreaElement> {
label?: string;
resize?: ResizeType;
}
export const Textarea = ({
className,
label,
resize = "vertical",
id,
...rest
}: TextareaHTMLAttributes<HTMLTextAreaElement> & {
label?: string;
resize?: ResizeType;
}) => {
}: TextareaInterface) => {
return (
<div className="flex flex-col">
{label && <label htmlFor={id ? id : kebabCase(label)}>{label}</label>}
@@ -0,0 +1,85 @@
import {
faBold,
faItalic,
faStrikethrough,
faUnderline,
} from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { buttonGroupButton, buttonGroupGrouper } from "./styles";
import { FontStyle, FontWeight, TextDecoration } from "./types";
export const ButtonsTextStyles = ({
fontStyle: currStyle,
fontWeight: currWeight,
textDecoration: currDecoration,
onChangeFontStyle,
onChangeFontWeight,
onChangeTextDecoration,
}: {
fontStyle: FontStyle;
fontWeight: FontWeight;
textDecoration: TextDecoration;
onChangeFontStyle: (newVal: FontStyle) => void;
onChangeFontWeight: (newVal: FontWeight) => void;
onChangeTextDecoration: (newVal: TextDecoration) => void;
}) => {
const isBold = currWeight === FontWeight.BOLD ? true : undefined;
const isItalic = currStyle === FontStyle.ITALIC ? true : undefined;
const isSelectedDecoration = (buttonVal: TextDecoration) =>
currDecoration === buttonVal ? true : undefined;
const toggleBold = () => {
if (currWeight === FontWeight.BOLD) {
onChangeFontWeight(FontWeight.NORMAL);
} else {
onChangeFontWeight(FontWeight.BOLD);
}
};
const handleOnChangeFontStyle = (selected: FontStyle) => {
if (selected === currStyle) {
onChangeFontStyle(FontStyle.NORMAL);
} else {
onChangeFontStyle(selected);
}
};
const handleOnChangeTextDecoration = (selected: TextDecoration) => {
if (selected === currDecoration) {
onChangeTextDecoration(TextDecoration.NONE);
} else {
onChangeTextDecoration(selected);
}
};
return (
<div className={buttonGroupGrouper}>
<button
data-selected={isBold}
className={buttonGroupButton}
onClick={toggleBold}
>
<FontAwesomeIcon icon={faBold} />
</button>
<button
data-selected={isItalic}
className={buttonGroupButton}
onClick={() => handleOnChangeFontStyle(FontStyle.ITALIC)}
>
<FontAwesomeIcon icon={faItalic} />
</button>
<button
data-selected={isSelectedDecoration(TextDecoration.STRIKETHROUGH)}
className={buttonGroupButton}
onClick={() =>
handleOnChangeTextDecoration(TextDecoration.STRIKETHROUGH)
}
>
<FontAwesomeIcon icon={faStrikethrough} />
</button>
<button
data-selected={isSelectedDecoration(TextDecoration.UNDERLINE)}
className={buttonGroupButton}
onClick={() => handleOnChangeTextDecoration(TextDecoration.UNDERLINE)}
>
<FontAwesomeIcon icon={faUnderline} />
</button>
</div>
);
};
@@ -0,0 +1,51 @@
import {
faAlignCenter,
faAlignJustify,
faAlignLeft,
faAlignRight,
} from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { TextAlign } from "./types";
import { buttonGroupButton, buttonGroupGrouper } from "./styles";
export const ButtonsAlignments = ({
value,
onChange,
}: {
value: TextAlign;
onChange: (newVal: TextAlign) => void;
}) => {
const isSelected = (alignment: TextAlign) =>
value === alignment ? true : undefined;
return (
<div className={buttonGroupGrouper}>
<button
data-selected={isSelected(TextAlign.LEFT)}
className={buttonGroupButton}
onClick={() => onChange(TextAlign.LEFT)}
>
<FontAwesomeIcon icon={faAlignLeft} />
</button>
<button
data-selected={isSelected(TextAlign.CENTER)}
className={buttonGroupButton}
onClick={() => onChange(TextAlign.CENTER)}
>
<FontAwesomeIcon icon={faAlignCenter} />
</button>
<button
data-selected={isSelected(TextAlign.RIGHT)}
className={buttonGroupButton}
onClick={() => onChange(TextAlign.RIGHT)}
>
<FontAwesomeIcon icon={faAlignRight} />
</button>
<button
data-selected={isSelected(TextAlign.JUSTIFY)}
className={buttonGroupButton}
onClick={() => onChange(TextAlign.JUSTIFY)}
>
<FontAwesomeIcon icon={faAlignJustify} />
</button>
</div>
);
};
+139
View File
@@ -0,0 +1,139 @@
import { ChangeEvent } from "react";
import { faFont } from "@fortawesome/pro-solid-svg-icons";
import {
CloseButton,
Popover,
PopoverButton,
PopoverPanel,
} from "@headlessui/react";
import { HexColorPicker } from "react-colorful";
import { Textarea, TextareaInterface, ResizeType } from "../TextArea";
import { InputNumber } from "../InputNumber";
import { Combobox } from "../ComboBox";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { ButtonsAlignments } from "./ButtonsAlignment";
import { ButtonsTextStyles } from "./ButtonTextStyles";
import {
TextFormatData,
TextAlign,
TextDecoration,
FontStyle,
FontWeight,
} from "./types";
const FontFamilies = ["Arial", "Helvetica", "Times New Roman", "Comic Sans MS"];
export const TextEditor = ({
text,
formatData,
onChangeText,
onChangeFormatting,
TextareaProps,
}: {
text: string;
formatData: TextFormatData;
onChangeText: (newText: string) => void;
onChangeFormatting: (newFormatData: Partial<TextFormatData>) => void;
TextareaProps?: TextareaInterface;
}) => {
const handleOnChangeText = (e: ChangeEvent<HTMLTextAreaElement>) => {
onChangeText(e.target.value);
};
const onChangeTextAlignment = (newAlignment: TextAlign) => {
onChangeFormatting({
textAlign: newAlignment,
});
};
const onChangeFontStyle = (newStyle: FontStyle) => {
onChangeFormatting({
fontStyle: newStyle,
});
};
const onChangeFontWeight = (newWeight: FontWeight) => {
onChangeFormatting({
fontWeight: newWeight,
});
};
const onChangeTextDecoration = (newDecor: TextDecoration) => {
onChangeFormatting({
textDecoration: newDecor,
});
};
const onChangeFontFamily = (newFontFamily: string) => {
onChangeFormatting({
fontFamily: newFontFamily,
});
};
const onChangeTextColor = (newTextColor: string) => {
onChangeFormatting({
color: newTextColor,
});
};
const onChangeFontSize = (newVal: number) => {
onChangeFormatting({
fontSize: newVal,
});
};
const TextAreaStates = {
style: {
...formatData,
width: "500px",
},
rows: 7,
placeholder: "...",
resize: "none" as ResizeType,
onChange: handleOnChangeText,
value: text,
};
const unionTextAreaProps = {
...TextareaProps,
...TextAreaStates,
};
return (
<div className="flex flex-col gap-4">
<div className="flex items-center gap-4">
<Combobox
options={FontFamilies}
value={formatData.fontFamily}
onChange={onChangeFontFamily}
/>
<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: formatData.color }}
/>
</PopoverButton>
<PopoverPanel anchor="bottom" className="flex flex-col">
<HexColorPicker
color={formatData.color}
onChange={onChangeTextColor}
/>
<p>{formatData.color}</p>
<CloseButton>OK</CloseButton>
</PopoverPanel>
</Popover>
<InputNumber value={formatData.fontSize} onChange={onChangeFontSize} />
</div>
<div className="flex items-center gap-4">
<ButtonsAlignments
value={formatData.textAlign}
onChange={onChangeTextAlignment}
/>
<ButtonsTextStyles
fontWeight={formatData.fontWeight}
fontStyle={formatData.fontStyle}
textDecoration={formatData.textDecoration}
onChangeFontWeight={onChangeFontWeight}
onChangeFontStyle={onChangeFontStyle}
onChangeTextDecoration={onChangeTextDecoration}
/>
</div>
<Textarea {...unionTextAreaProps} />
</div>
);
};
+1
View File
@@ -0,0 +1 @@
export { TextEditor } from "./TextEditor";
+4
View File
@@ -0,0 +1,4 @@
export const buttonGroupButton =
"size-10 rounded-none bg-white text-black hover:bg-gray-200 data-[selected=true]:text-primary-400";
export const buttonGroupGrouper =
"flex divide-x overflow-hidden rounded-md border";
+28
View File
@@ -0,0 +1,28 @@
export enum FontWeight {
NORMAL = "normal",
BOLD = "bold",
}
export enum FontStyle {
NORMAL = "normal",
ITALIC = "italic",
}
export enum TextAlign {
LEFT = "left",
RIGHT = "right",
CENTER = "center",
JUSTIFY = "justify",
}
export enum TextDecoration {
NONE = "none",
STRIKETHROUGH = "line-through",
UNDERLINE = "underline",
}
export type TextFormatData = {
color: string;
fontFamily: string;
fontSize: number;
fontStyle: FontStyle;
fontWeight: FontWeight;
textAlign: TextAlign;
textDecoration: TextDecoration;
};
+1
View File
@@ -9,4 +9,5 @@ export * from "./Slider";
export * from "./Spinner";
export * from "./Switches";
export * from "./TextArea";
export * from "./TextEditor";
export * from "./Tooltip";
+5 -16
View File
@@ -47,32 +47,21 @@ export const toolbarNode = {
position,
};
},
lock() {
const { buttonStates } = toolbarNodeSignal.value;
toolbarNodeSignal.value = {
...toolbarNodeSignal.value,
buttonStates: {
...buttonStates,
[ButtonNames.TRANSFORM]: {
disabled: true,
active: buttonStates[ButtonNames.TRANSFORM].active,
},
},
locked: true,
};
isLocked() {
return toolbarNodeSignal.value.locked;
},
unlock() {
setLocked(locked: boolean) {
const { buttonStates } = toolbarNodeSignal.value;
toolbarNodeSignal.value = {
...toolbarNodeSignal.value,
buttonStates: {
...buttonStates,
[ButtonNames.TRANSFORM]: {
disabled: false,
disabled: locked,
active: buttonStates[ButtonNames.TRANSFORM].active,
},
},
locked: false,
locked: locked,
};
},
show(
+19 -2
View File
@@ -1,11 +1,11 @@
import { signal, effect } from "@preact/signals-react";
import { TextNodeData } from "~/KonvaApp/types";
// ADDING IMAGES
const stagedImage = signal<File | null>(null);
const addImageToEngine = (image: File) => {
stagedImage.value = image;
};
const onGetStagedImage = (callback: (file: File) => void) => {
effect(() => {
if (stagedImage.value) {
@@ -14,6 +14,7 @@ const onGetStagedImage = (callback: (file: File) => void) => {
});
};
// ADDING VIDEOS
const stagedVideo = signal<{ url: string } | null>(null);
const addVideoToEngine = (videoData: { url: string }) => {
@@ -28,12 +29,28 @@ const onGetStagedVideo = (callback: (videoData: { url: string }) => void) => {
});
};
// ADD and EDIT TEXT
const stagedText = signal<TextNodeData | null>(null);
const addTextToEngine = (data: TextNodeData) => {
stagedText.value = data;
};
const onAddTextToEngine = (callback: (data: TextNodeData) => void) => {
effect(() => {
if (stagedText.value) {
callback(stagedText.value);
}
});
};
//EXPORTS
export const dispatchers = {
addImageToEngine,
addVideoToEngine,
addTextToEngine,
};
export const events = {
onGetStagedImage,
onGetStagedVideo,
onAddTextToEngine,
};
+110 -251
View File
@@ -28,7 +28,7 @@
resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz"
integrity sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==
"@babel/core@^7.24.5":
"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.24.5":
version "7.25.2"
resolved "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz"
integrity sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==
@@ -222,121 +222,6 @@
resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz"
integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==
"@esbuild/aix-ppc64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f"
integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==
"@esbuild/android-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052"
integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==
"@esbuild/android-arm@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28"
integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==
"@esbuild/android-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e"
integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==
"@esbuild/darwin-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a"
integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==
"@esbuild/darwin-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22"
integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==
"@esbuild/freebsd-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e"
integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==
"@esbuild/freebsd-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261"
integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==
"@esbuild/linux-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b"
integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==
"@esbuild/linux-arm@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9"
integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==
"@esbuild/linux-ia32@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2"
integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==
"@esbuild/linux-loong64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df"
integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==
"@esbuild/linux-mips64el@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe"
integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==
"@esbuild/linux-ppc64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4"
integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==
"@esbuild/linux-riscv64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc"
integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==
"@esbuild/linux-s390x@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de"
integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==
"@esbuild/linux-x64@0.21.5":
version "0.21.5"
resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz"
integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==
"@esbuild/netbsd-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047"
integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==
"@esbuild/openbsd-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70"
integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==
"@esbuild/sunos-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b"
integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==
"@esbuild/win32-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d"
integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==
"@esbuild/win32-ia32@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b"
integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==
"@esbuild/win32-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c"
integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
version "4.4.0"
resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz"
@@ -373,7 +258,7 @@
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
"@eslint/js@9.9.1", "@eslint/js@^9.9.0":
"@eslint/js@^9.9.0", "@eslint/js@9.9.1":
version "9.9.1"
resolved "https://registry.npmjs.org/@eslint/js/-/js-9.9.1.tgz"
integrity sha512-xIDQRsfg5hNBqHz04H1R3scSVwmI+KUbqjsQKHKQ1DAUSaUjYPReZZmS/5PNiKu1fUvzDd6H7DEDKACSEhu+TQ==
@@ -424,7 +309,7 @@
resolved "https://npm.fontawesome.com/@fortawesome/fontawesome-common-types/-/6.6.0/fontawesome-common-types-6.6.0.tgz"
integrity sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==
"@fortawesome/fontawesome-svg-core@^6.6.0":
"@fortawesome/fontawesome-svg-core@^6.6.0", "@fortawesome/fontawesome-svg-core@~1 || ~6":
version "6.6.0"
resolved "https://npm.fontawesome.com/@fortawesome/fontawesome-svg-core/-/6.6.0/fontawesome-svg-core-6.6.0.tgz"
integrity sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==
@@ -522,7 +407,7 @@
"@nodelib/fs.stat" "2.0.5"
run-parallel "^1.1.9"
"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5":
version "2.0.5"
resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
@@ -844,86 +729,6 @@
resolved "https://registry.npmjs.org/@remix-run/router/-/router-1.19.1.tgz"
integrity sha512-S45oynt/WH19bHbIXjtli6QmwNYvaz+vtnubvNpNDvUOoA/OWh6j1OikIP3G+v5GHdxyC6EXoChG3HgYGEUfcg==
"@rollup/rollup-android-arm-eabi@4.21.1":
version "4.21.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.1.tgz#c3a7938551273a2b72820cf5d22e54cf41dc206e"
integrity sha512-2thheikVEuU7ZxFXubPDOtspKn1x0yqaYQwvALVtEcvFhMifPADBrgRPyHV0TF3b+9BgvgjgagVyvA/UqPZHmg==
"@rollup/rollup-android-arm64@4.21.1":
version "4.21.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.1.tgz#fa3693e4674027702c42fcbbb86bbd0c635fd3b9"
integrity sha512-t1lLYn4V9WgnIFHXy1d2Di/7gyzBWS8G5pQSXdZqfrdCGTwi1VasRMSS81DTYb+avDs/Zz4A6dzERki5oRYz1g==
"@rollup/rollup-darwin-arm64@4.21.1":
version "4.21.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.1.tgz#e19922f4ac1e4552a230ff8f49d5688c5c07d284"
integrity sha512-AH/wNWSEEHvs6t4iJ3RANxW5ZCK3fUnmf0gyMxWCesY1AlUj8jY7GC+rQE4wd3gwmZ9XDOpL0kcFnCjtN7FXlA==
"@rollup/rollup-darwin-x64@4.21.1":
version "4.21.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.1.tgz#897f8d47b115ea84692a29cf2366899499d4d915"
integrity sha512-dO0BIz/+5ZdkLZrVgQrDdW7m2RkrLwYTh2YMFG9IpBtlC1x1NPNSXkfczhZieOlOLEqgXOFH3wYHB7PmBtf+Bg==
"@rollup/rollup-linux-arm-gnueabihf@4.21.1":
version "4.21.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.1.tgz#7d1e2a542f3a5744f5c24320067bd5af99ec9d62"
integrity sha512-sWWgdQ1fq+XKrlda8PsMCfut8caFwZBmhYeoehJ05FdI0YZXk6ZyUjWLrIgbR/VgiGycrFKMMgp7eJ69HOF2pQ==
"@rollup/rollup-linux-arm-musleabihf@4.21.1":
version "4.21.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.1.tgz#88bec1c9df85fc5e24d49f783e19934717dd69b5"
integrity sha512-9OIiSuj5EsYQlmwhmFRA0LRO0dRRjdCVZA3hnmZe1rEwRk11Jy3ECGGq3a7RrVEZ0/pCsYWx8jG3IvcrJ6RCew==
"@rollup/rollup-linux-arm64-gnu@4.21.1":
version "4.21.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.1.tgz#6dc60f0fe7bd49ed07a2d4d9eab15e671b3bd59d"
integrity sha512-0kuAkRK4MeIUbzQYu63NrJmfoUVicajoRAL1bpwdYIYRcs57iyIV9NLcuyDyDXE2GiZCL4uhKSYAnyWpjZkWow==
"@rollup/rollup-linux-arm64-musl@4.21.1":
version "4.21.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.1.tgz#a03b78775c129e8333aca9e1e420e8e217ee99b9"
integrity sha512-/6dYC9fZtfEY0vozpc5bx1RP4VrtEOhNQGb0HwvYNwXD1BBbwQ5cKIbUVVU7G2d5WRE90NfB922elN8ASXAJEA==
"@rollup/rollup-linux-powerpc64le-gnu@4.21.1":
version "4.21.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.1.tgz#ee3810647faf2c105a5a4e71260bb90b96bf87bc"
integrity sha512-ltUWy+sHeAh3YZ91NUsV4Xg3uBXAlscQe8ZOXRCVAKLsivGuJsrkawYPUEyCV3DYa9urgJugMLn8Z3Z/6CeyRQ==
"@rollup/rollup-linux-riscv64-gnu@4.21.1":
version "4.21.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.1.tgz#385d76a088c27db8054d9f3f28d64d89294f838e"
integrity sha512-BggMndzI7Tlv4/abrgLwa/dxNEMn2gC61DCLrTzw8LkpSKel4o+O+gtjbnkevZ18SKkeN3ihRGPuBxjaetWzWg==
"@rollup/rollup-linux-s390x-gnu@4.21.1":
version "4.21.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.1.tgz#daa2b62a6e6f737ebef6700a12a93c9764e18583"
integrity sha512-z/9rtlGd/OMv+gb1mNSjElasMf9yXusAxnRDrBaYB+eS1shFm6/4/xDH1SAISO5729fFKUkJ88TkGPRUh8WSAA==
"@rollup/rollup-linux-x64-gnu@4.21.1":
version "4.21.1"
resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.1.tgz"
integrity sha512-kXQVcWqDcDKw0S2E0TmhlTLlUgAmMVqPrJZR+KpH/1ZaZhLSl23GZpQVmawBQGVhyP5WXIsIQ/zqbDBBYmxm5w==
"@rollup/rollup-linux-x64-musl@4.21.1":
version "4.21.1"
resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.1.tgz"
integrity sha512-CbFv/WMQsSdl+bpX6rVbzR4kAjSSBuDgCqb1l4J68UYsQNalz5wOqLGYj4ZI0thGpyX5kc+LLZ9CL+kpqDovZA==
"@rollup/rollup-win32-arm64-msvc@4.21.1":
version "4.21.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.1.tgz#18349db8250559a5460d59eb3575f9781be4ab98"
integrity sha512-3Q3brDgA86gHXWHklrwdREKIrIbxC0ZgU8lwpj0eEKGBQH+31uPqr0P2v11pn0tSIxHvcdOWxa4j+YvLNx1i6g==
"@rollup/rollup-win32-ia32-msvc@4.21.1":
version "4.21.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.1.tgz#199648b68271f7ab9d023f5c077725d51d12d466"
integrity sha512-tNg+jJcKR3Uwe4L0/wY3Ro0H+u3nrb04+tcq1GSYzBEmKLeOQF2emk1whxlzNqb6MMrQ2JOcQEpuuiPLyRcSIw==
"@rollup/rollup-win32-x64-msvc@4.21.1":
version "4.21.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.1.tgz#4d3ec02dbf280c20bfeac7e50cd5669b66f9108f"
integrity sha512-xGiIH95H1zU7naUyTKEyOA/I0aexNMUdO9qRv0bLKN3qu25bBdrxZHqA3PTJ24YNN/GdMzG4xkDcd/GvjuhfLg==
"@sigstore/bundle@^2.3.2":
version "2.3.2"
resolved "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.2.tgz"
@@ -1050,7 +855,7 @@
resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz"
integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
"@types/node@>=13.7.0":
"@types/node@^18.0.0 || >=20.0.0", "@types/node@>=13.7.0":
version "22.5.5"
resolved "https://registry.npmjs.org/@types/node/-/node-22.5.5.tgz"
integrity sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==
@@ -1097,7 +902,7 @@
natural-compare "^1.4.0"
ts-api-utils "^1.3.0"
"@typescript-eslint/parser@8.3.0":
"@typescript-eslint/parser@^8.0.0 || ^8.0.0-alpha.0", "@typescript-eslint/parser@8.3.0":
version "8.3.0"
resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.3.0.tgz"
integrity sha512-h53RhVyLu6AtpUzVCYLPhZGL5jzTD9fZL+SYf/+hYOx2bDkyQXztXSc4tbvKYHzfMXExMLiL9CWqJmVz6+78IQ==
@@ -1189,7 +994,7 @@ acorn-jsx@^5.3.2:
resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
acorn@^8.12.0:
"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.12.0:
version "8.12.1"
resolved "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz"
integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==
@@ -1236,7 +1041,14 @@ ansi-styles@^3.2.1:
dependencies:
color-convert "^1.9.0"
ansi-styles@^4.0.0, ansi-styles@^4.1.0:
ansi-styles@^4.0.0:
version "4.3.0"
resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz"
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
dependencies:
color-convert "^2.0.1"
ansi-styles@^4.1.0:
version "4.3.0"
resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz"
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
@@ -1403,7 +1215,12 @@ bin-links@^4.0.4:
read-cmd-shim "^4.0.0"
write-file-atomic "^5.0.0"
binary-extensions@^2.0.0, binary-extensions@^2.3.0:
binary-extensions@^2.0.0:
version "2.3.0"
resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz"
integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
binary-extensions@^2.3.0:
version "2.3.0"
resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz"
integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
@@ -1430,7 +1247,7 @@ braces@^3.0.3, braces@~3.0.2:
dependencies:
fill-range "^7.1.1"
browserslist@^4.23.1, browserslist@^4.23.3:
browserslist@^4.23.1, browserslist@^4.23.3, "browserslist@>= 4.21.0":
version "4.23.3"
resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz"
integrity sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==
@@ -1580,16 +1397,16 @@ color-convert@^2.0.1:
dependencies:
color-name "~1.1.4"
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
commander@^4.0.0:
version "4.1.1"
resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz"
@@ -1670,7 +1487,7 @@ data-view-byte-offset@^1.0.0:
es-errors "^1.3.0"
is-data-view "^1.0.1"
debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@4:
version "4.3.6"
resolved "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz"
integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==
@@ -2000,7 +1817,12 @@ eslint-scope@^8.0.2:
esrecurse "^4.3.0"
estraverse "^5.2.0"
eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.3:
eslint-visitor-keys@^3.3.0:
version "3.4.3"
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz"
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
eslint-visitor-keys@^3.4.3:
version "3.4.3"
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz"
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
@@ -2010,7 +1832,7 @@ eslint-visitor-keys@^4.0.0:
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz"
integrity sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==
eslint@^9.9.0:
"eslint@^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7", "eslint@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^8.57.0 || ^9.0.0", eslint@^9.9.0, eslint@>=7:
version "9.9.1"
resolved "https://registry.npmjs.org/eslint/-/eslint-9.9.1.tgz"
integrity sha512-dHvhrbfr4xFQ9/dq+jcVneZMyRYLjggWjk6RVsIiHsP8Rz6yZ8LvZ//iU4TrZF+SXWG+JkNF2OyiZRvzgRDqMg==
@@ -2188,7 +2010,7 @@ fraction.js@^4.3.7:
fs-extra@^11.1.0:
version "11.2.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b"
resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz"
integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==
dependencies:
graceful-fs "^4.2.0"
@@ -2209,11 +2031,6 @@ fs-minipass@^3.0.0, fs-minipass@^3.0.3:
dependencies:
minipass "^7.0.3"
fsevents@~2.3.2, fsevents@~2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
function-bind@^1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
@@ -2259,7 +2076,7 @@ get-symbol-description@^1.0.2:
es-errors "^1.3.0"
get-intrinsic "^1.2.4"
glob-parent@^5.1.2, glob-parent@~5.1.2:
glob-parent@^5.1.2:
version "5.1.2"
resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
@@ -2273,6 +2090,13 @@ glob-parent@^6.0.2:
dependencies:
is-glob "^4.0.3"
glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
dependencies:
is-glob "^4.0.1"
glob@^10.2.2, glob@^10.3.10, glob@^10.4.5:
version "10.4.5"
resolved "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz"
@@ -2320,7 +2144,12 @@ gopd@^1.0.1:
dependencies:
get-intrinsic "^1.1.3"
graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.6:
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
version "4.2.11"
resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
graceful-fs@^4.2.11, graceful-fs@^4.2.6:
version "4.2.11"
resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
@@ -2735,7 +2564,7 @@ jackspeak@^3.1.2:
optionalDependencies:
"@pkgjs/parseargs" "^0.11.0"
jiti@^1.21.0:
jiti@*, jiti@^1.21.0:
version "1.21.6"
resolved "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz"
integrity sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==
@@ -2794,7 +2623,7 @@ json5@^2.2.3:
jsonfile@^6.0.1:
version "6.1.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz"
integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
dependencies:
universalify "^2.0.0"
@@ -3104,16 +2933,16 @@ minipass@^3.0.0:
dependencies:
yallist "^4.0.0"
minipass@^5.0.0:
version "5.0.0"
resolved "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz"
integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==
"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.2, minipass@^7.0.3, minipass@^7.1.1, minipass@^7.1.2:
version "7.1.2"
resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz"
integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
minipass@^5.0.0:
version "5.0.0"
resolved "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz"
integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==
minizlib@^2.1.1, minizlib@^2.1.2:
version "2.1.2"
resolved "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz"
@@ -3127,16 +2956,16 @@ mkdirp@^1.0.3:
resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
ms@2.1.2:
version "2.1.2"
resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
ms@^2.1.2:
version "2.1.3"
resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
ms@2.1.2:
version "2.1.2"
resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
mute-stream@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz"
@@ -3614,7 +3443,15 @@ postcss-nested@^6.0.1:
dependencies:
postcss-selector-parser "^6.1.1"
postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.1.1:
postcss-selector-parser@^6.0.10:
version "6.1.2"
resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz"
integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==
dependencies:
cssesc "^3.0.0"
util-deprecate "^1.0.2"
postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.1.1:
version "6.1.2"
resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz"
integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==
@@ -3627,7 +3464,7 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^
resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss@^8.4.23, postcss@^8.4.41:
postcss@^8.0.0, postcss@^8.1.0, postcss@^8.2.14, postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.41, postcss@>=8.0.9:
version "8.4.41"
resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz"
integrity sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==
@@ -3646,7 +3483,7 @@ prettier-plugin-tailwindcss@^0.6.6:
resolved "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.6.tgz"
integrity sha512-OPva5S7WAsPLEsOuOWXATi13QrCKACCiIonFgIR6V4lYv4QLp++UXVhZSzRbZxXGimkQtQT86CC6fQqTOybGng==
prettier@^3.3.3:
prettier@^3.0, prettier@^3.3.3:
version "3.3.3"
resolved "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz"
integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==
@@ -3733,7 +3570,12 @@ queue-microtask@^1.2.2:
resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
react-dom@^18.3.1:
react-colorful@^5.6.1:
version "5.6.1"
resolved "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz"
integrity sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==
"react-dom@^16.8.0 || ^17.0.0 || ^18.0.0", react-dom@^18, react-dom@^18.0.0, react-dom@^18.3.1, "react-dom@>= 16.8.0", react-dom@>=16.4.0, react-dom@>=16.8, react-dom@>=16.8.0:
version "18.3.1"
resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz"
integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==
@@ -3757,7 +3599,7 @@ react-easy-crop@^5.0.8:
normalize-wheel "^1.0.1"
tslib "^2.0.1"
react-is@^16.13.1, react-is@^16.7.0:
react-is@^16.13.1, react-is@^16.7.0, "react-is@>= 16.8.0":
version "16.13.1"
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -3782,7 +3624,7 @@ react-router@6.26.1:
dependencies:
"@remix-run/router" "1.19.1"
react@^18.3.1:
"react@^16.14.0 || 17.x || 18.x", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0", react@^18, react@^18.0.0, react@^18.3.1, "react@>= 16.8.0", react@>=16.3, react@>=16.4.0, react@>=16.8, react@>=16.8.0:
version "18.3.1"
resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz"
integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
@@ -3851,7 +3693,16 @@ resolve-from@^4.0.0:
resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz"
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
resolve@^1.1.7, resolve@^1.22.2:
resolve@^1.1.7:
version "1.22.8"
resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz"
integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
dependencies:
is-core-module "^2.13.0"
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
resolve@^1.22.2:
version "1.22.8"
resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz"
integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
@@ -3947,7 +3798,12 @@ semver@^6.3.1:
resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
semver@^7.1.1, semver@^7.3.5, semver@^7.3.7, semver@^7.5.3, semver@^7.6.0, semver@^7.6.3:
semver@^7.1.1, semver@^7.3.5, semver@^7.3.7, semver@^7.5.3, semver@^7.6.3:
version "7.6.3"
resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz"
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
semver@^7.6.0:
version "7.6.3"
resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz"
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
@@ -4075,9 +3931,7 @@ spdx-expression-parse@^4.0.0:
spdx-license-ids "^3.0.0"
spdx-license-ids@^3.0.0:
version "3.0.20"
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz#e44ed19ed318dd1e5888f93325cee800f0f51b89"
integrity sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==
version "3.0.18"
sprintf-js@^1.1.3:
version "1.1.3"
@@ -4205,7 +4059,7 @@ strip-json-comments@^3.1.1:
resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
styled-components@^5.3.0:
styled-components@^5.3.0, "styled-components@>= 2":
version "5.3.11"
resolved "https://registry.npmjs.org/styled-components/-/styled-components-5.3.11.tgz"
integrity sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==
@@ -4308,7 +4162,12 @@ tar@^6.1.11, tar@^6.2.1:
mkdirp "^1.0.3"
yallist "^4.0.0"
text-table@^0.2.0, text-table@~0.2.0:
text-table@^0.2.0:
version "0.2.0"
resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz"
integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
text-table@~0.2.0:
version "0.2.0"
resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz"
integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
@@ -4438,7 +4297,7 @@ typescript-eslint@^8.0.1:
"@typescript-eslint/parser" "8.3.0"
"@typescript-eslint/utils" "8.3.0"
typescript@^5.5.3:
typescript@^5.0.0, typescript@^5.5.3, typescript@>=4.2.0:
version "5.5.4"
resolved "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz"
integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==
@@ -4474,7 +4333,7 @@ unique-slug@^4.0.0:
universalify@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz"
integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
update-browserslist-db@^1.1.0:
@@ -4522,7 +4381,7 @@ validate-npm-package-name@^5.0.0, validate-npm-package-name@^5.0.1:
vite-plugin-static-copy@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/vite-plugin-static-copy/-/vite-plugin-static-copy-1.0.6.tgz#ff457c16e8cfa564472aafd1698790ac89d05508"
resolved "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-1.0.6.tgz"
integrity sha512-3uSvsMwDVFZRitqoWHj0t4137Kz7UynnJeq1EZlRW7e25h2068fyIZX4ORCCOAkfp1FklGxJNVJBkBOD+PZIew==
dependencies:
chokidar "^3.5.3"
@@ -4539,7 +4398,7 @@ vite-tsconfig-paths@^5.0.1:
globrex "^0.1.2"
tsconfck "^3.0.3"
vite@^5.4.1:
vite@*, "vite@^4.2.0 || ^5.0.0", vite@^5.0.0, vite@^5.4.1:
version "5.4.2"
resolved "https://registry.npmjs.org/vite/-/vite-5.4.2.tgz"
integrity sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==
@@ -4673,4 +4532,4 @@ yarn@^1.22.22:
yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==