feature/ toolbar node 3 (#62)

* fix group selects, better the node transfomer as well

* refactor for better code organization
This commit is contained in:
wil authored and GitHub committed 2024-10-31 22:49:13 -04:00
1 parent 6741173873
commit 18dd2a3ebe
8 files changed
+317 -202

No files matched your search

+2 -2
View File
@@ -395,7 +395,7 @@ export class Engine {
await node.startSegmentation();
this.nodeIsolator.enterIsolation(node);
node.videoSegmentationMode(true);
this.selectionManager.updateContextComponents(node);
this.selectionManager.updateContextComponents();
uiAccess.loadingBar.update({
progress: 0,
message: "Start Adding Extraction Points To the Video",
@@ -425,7 +425,7 @@ export class Engine {
this.enableSelectorSquare();
this.segmentationButtonCanBePressed = true;
node.unlock();
this.selectionManager.updateContextComponents(node);
this.selectionManager.updateContextComponents();
this.selectionManager.enable();
// to close off the session.
} else {
+4 -4
View File
@@ -109,7 +109,7 @@ export abstract class BaseNode {
this.kNode.setDraggable(false);
this.selectionManagerRef.updateNodeTransformer();
if (this.isKEventRef()) {
this.selectionManagerRef.updateContextComponents(this);
this.selectionManagerRef.updateContextComponents();
}
}
public unlock() {
@@ -117,7 +117,7 @@ export abstract class BaseNode {
this.kNode.setDraggable(true);
this.selectionManagerRef.updateNodeTransformer();
if (this.isKEventRef()) {
this.selectionManagerRef.updateContextComponents(this);
this.selectionManagerRef.updateContextComponents();
}
}
@@ -181,7 +181,7 @@ export abstract class BaseNode {
this.kNode.on("dragend", () => {
// console.log("Drag End", this.kNode._id);
this.selectionManagerRef.dragEnd(this);
this.selectionManagerRef.dragEnd();
});
}
@@ -204,7 +204,7 @@ export abstract class BaseNode {
});
this.kNode.on("transformend", (event) => {
console.log("transformend", event.target._id);
this.selectionManagerRef.transformEnd(this);
this.selectionManagerRef.transformEnd();
});
}
public removeListenToBaseKNodeTransformations() {
+2 -2
View File
@@ -79,7 +79,7 @@ export abstract class NetworkedNode extends BaseNode {
protected setStatus(newStatus: UploadStatus, message?: string) {
this.mediaFileStatus = newStatus;
this.errorMessage = message;
this.selectionManagerRef.updateContextComponents(this);
this.selectionManagerRef.updateContextComponents();
}
protected setProgress(
newProgress: number,
@@ -99,6 +99,6 @@ export abstract class NetworkedNode extends BaseNode {
if (this._progress === 100) {
this.didFinishLoading = true;
}
this.selectionManagerRef.updateContextComponents(this);
this.selectionManagerRef.updateContextComponents();
}
}
+3 -3
View File
@@ -477,14 +477,14 @@ export class VideoNode extends NetworkedNode {
this.videoComponent.pause();
this.videoComponent.currentTime = 0;
this.setProgress(25, { message: "Loading Video Extractor..." });
this.selectionManagerRef.updateContextComponents(this);
this.selectionManagerRef.showContextComponents(this);
this.selectionManagerRef.updateContextComponents();
this.selectionManagerRef.showContextComponents();
console.log("loadingbar should show");
const blob = await NodeUtilities.urlToBlob(this.mediaFileUrl);
this.setProgress(75, { message: "Loading Video Extractor..." });
this.selectionManagerRef.updateContextComponents(this);
this.selectionManagerRef.updateContextComponents();
this.segmentationSession =
await this.videoSegmentationAPI.createSession(blob);
+11 -1
View File
@@ -34,7 +34,7 @@ export class NodeTransformer {
public getKonvaNode() {
return this.kTransformer;
}
public enable({ selectedNodes }: { selectedNodes: Set<MediaNode> }) {
public addNodes({ selectedNodes }: { selectedNodes: Set<MediaNode> }) {
const kNodesArray = Array.from(selectedNodes).reduce((acc, node) => {
acc.push(node.kNode);
return acc;
@@ -48,4 +48,14 @@ export class NodeTransformer {
public clear() {
this.kTransformer.nodes([]);
}
public enable() {
this.kTransformer.rotateEnabled(true);
this.kTransformer.resizeEnabled(true);
this.kTransformer.draggable(true);
}
public disable() {
this.kTransformer.rotateEnabled(false);
this.kTransformer.resizeEnabled(false);
this.kTransformer.draggable(false);
}
}
+82 -188
View File
@@ -5,8 +5,13 @@ import { uiAccess, uiEvents } from "~/signals";
import { NetworkedNode } from "../Nodes/NetworkedNode";
import { LoadingBarStatus } from "~/components/ui";
import { ImageNode, TextNode, VideoNode } from "../Nodes";
import { ToolbarNodeButtonNames } from "~/components/features/ToolbarNode";
import { ContextualToolbarProps } from "~/signals/uiAccess/toolbarNode";
import {
calculateContextualsPosition,
getImageNodeButtonStates,
getMultiSelectButtonStates,
getTextNodeButtonStates,
getVideoNodeButtonStates,
} from "./ToolbarNodeUtilities";
export enum SelectionManagerEvents {
NODES_TRANSLATIONS = "nodestranslation",
@@ -44,6 +49,7 @@ export class SelectionManager {
}) {
this.firstSelectedNode = undefined;
this.selectedNodes = new Set();
this.initialPositions = new Map<MediaNode, Position>();
this.initialTransformations = new Map<MediaNode, Transformation[]>();
this.eventTarget = new EventTarget();
@@ -76,7 +82,11 @@ export class SelectionManager {
if (
this.disabled || // no-op when disabled
this.selectedNodes.has(node) || // if the node is already selected
(this.selectedNodes.size > 0 && node.isLocked()) // if in multiselect but picked a locked item
(this.selectedNodes.size > 0 && node.isLocked()) ||
// if in multiselect but picked a locked item
(this.selectedNodes.size > 0 &&
this.selectedNodes.values().next().value?.isLocked())
// if the first node picked is a locked item
) {
return false;
}
@@ -84,11 +94,12 @@ export class SelectionManager {
node.highlight();
if (this.selectedNodes.size === 0) {
node.setIsKEventRef(true);
this.updateContextComponents(node);
this.showContextComponents(node);
}
this.selectedNodes.add(node);
this.updateNodeTransformer();
this.updateContextComponents();
this.showContextComponents();
if (!doNotDraw) {
this.mediaLayerRef.batchDraw();
}
@@ -106,6 +117,8 @@ export class SelectionManager {
}
this.selectedNodes.delete(node);
this.updateNodeTransformer();
this.updateContextComponents();
this.showContextComponents();
if (!doNotDraw) {
this.mediaLayerRef.batchDraw();
}
@@ -129,8 +142,8 @@ export class SelectionManager {
this.mediaLayerRef.batchDraw();
}
public onToggleLock(node: MediaNode) {
this.updateContextComponents(node);
public onToggleLock() {
this.updateContextComponents();
}
public isDragging() {
return this._isDragging;
@@ -150,7 +163,7 @@ export class SelectionManager {
});
}
public dragEnd(refNode: MediaNode) {
public dragEnd() {
// release the dragging state for mouseevents in nodes
this._isDragging = false;
// track and map the final positions
@@ -173,8 +186,8 @@ export class SelectionManager {
);
// done, clear data and show menu
this.initialPositions.clear();
this.updateContextComponents(refNode);
this.showContextComponents(refNode);
this.updateContextComponents();
this.showContextComponents();
}
private getKNodeTransformation(kNode: Konva.Node) {
return {
@@ -202,7 +215,7 @@ export class SelectionManager {
});
this.hideContextComponents();
}
public transformEnd(refNode: MediaNode) {
public transformEnd() {
// track and map the final positions
const finalTransformations = new Map<MediaNode, Transformation[]>();
this.selectedNodes.forEach((selectedNode) => {
@@ -228,43 +241,18 @@ export class SelectionManager {
},
),
);
this.updateContextComponents(refNode);
this.showContextComponents(refNode);
this.updateContextComponents();
this.showContextComponents();
}
public updateNodeTransformer() {
const transformableNodes = new Set(
Array.from(this.selectedNodes).filter((node) => {
return !node.isLocked();
}),
);
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;
const lockFlag = this.selectedNodes.values().next().value?.isLocked();
if (lockFlag) {
this.nodeTransformerRef.disable();
} else {
px = x0 + (-h * Math.sin(r) + w * Math.cos(r)) / 2;
py = y0 + h * Math.cos(r);
this.nodeTransformerRef.enable();
}
return { x: px, y: py };
this.nodeTransformerRef.addNodes({ selectedNodes: this.selectedNodes });
}
public hideContextComponents() {
@@ -272,42 +260,65 @@ export class SelectionManager {
uiAccess.loadingBar.hide();
}
public showContextComponents(node: MediaNode) {
public showContextComponents() {
const showOrUpdate = uiAccess.toolbarNode.isShowing()
? uiAccess.toolbarNode.update
: uiAccess.toolbarNode.show;
const node = this.selectedNodes.values().next().value;
if (node === undefined) {
this.hideContextComponents();
return; // no node
}
if (this.selectedNodes.size === 1) {
if (node instanceof ImageNode) {
showOrUpdate({
locked: node.isLocked(),
buttonStates: getImageNodeButtonStates({ locked: node.isLocked() }),
});
}
if (node instanceof TextNode) {
showOrUpdate({
locked: node.isLocked(),
buttonStates: getTextNodeButtonStates({ locked: node.isLocked() }),
});
}
if (node instanceof VideoNode) {
showOrUpdate({
locked: node.isLocked(),
buttonStates: getVideoNodeButtonStates({ locked: node.isLocked() }),
});
}
if (node instanceof ImageNode) {
showOrUpdate({
locked: node.isLocked(),
buttonStates: getImageNodeButtonStates({ locked: node.isLocked() }),
});
}
if (node instanceof TextNode) {
showOrUpdate({
locked: node.isLocked(),
buttonStates: getTextNodeButtonStates({ locked: node.isLocked() }),
});
}
if (node instanceof VideoNode) {
showOrUpdate({
locked: node.isLocked(),
buttonStates: getVideoNodeButtonStates({ locked: node.isLocked() }),
});
}
// show loading bar is the node is noding
if (node instanceof NetworkedNode) {
if (!node.didFinishLoading && !uiAccess.loadingBar.isShowing()) {
// console.log("show loading bar");
uiAccess.loadingBar.show();
// show loading bar is the node is noding
if (node instanceof NetworkedNode) {
if (!node.didFinishLoading && !uiAccess.loadingBar.isShowing()) {
// console.log("show loading bar");
uiAccess.loadingBar.show();
}
}
} else {
if (node) {
showOrUpdate({
locked: node.isLocked(),
buttonStates: getMultiSelectButtonStates({ locked: node.isLocked() }),
});
}
}
}
public updateContextComponents(node: MediaNode) {
const coord = this.calculateContextualsPosition(node.kNode);
public updateContextComponents() {
// use first node as reference
const node = this.selectedNodes.values().next().value;
if (node === undefined) {
this.hideContextComponents();
return; // no nodes
}
// console.log("SelectionManager > updateContextComponents for node:", node);
const coord = calculateContextualsPosition(
this.nodeTransformerRef.getKonvaNode(),
);
if (node instanceof VideoNode) {
if (node.isSegmentationMode && !uiAccess.toolbarNode.isLockDisabled()) {
uiAccess.toolbarNode.disableLock();
@@ -316,6 +327,7 @@ export class SelectionManager {
}
}
if (node.isLocked() !== uiAccess.toolbarNode.isLocked()) {
// console.log("setting lock");
uiAccess.toolbarNode.setLocked(node.isLocked());
}
uiAccess.toolbarNode.setPosition({
@@ -344,121 +356,3 @@ export class SelectionManager {
}
}
}
function getImageNodeButtonStates(
props: { locked?: boolean | "unknown" } = {},
) {
const ButtonNames = ToolbarNodeButtonNames;
return Object.values(ButtonNames).reduce(
(buttonStates, buttonName) => {
// transform button depends on locked state
if (props.locked !== undefined && buttonName === ButtonNames.TRANSFORM) {
buttonStates.TRANSFORM = {
disabled: props.locked === "unknown" || props.locked === true,
hidden: false,
active: true,
};
return buttonStates;
}
// hidden buttons
if (
buttonName === ButtonNames.AI_STYLIZE ||
buttonName === ButtonNames.SEGMENTATION ||
buttonName === ButtonNames.DOWNLOAD ||
buttonName === ButtonNames.CHROMA
) {
buttonStates[buttonName] = {
disabled: true,
hidden: true,
active: false,
};
return buttonStates;
}
// all other buttons
buttonStates[buttonName] = {
disabled: false,
hidden: false,
active: false,
};
return buttonStates;
},
{} as ContextualToolbarProps["buttonStates"],
);
}
function getTextNodeButtonStates(props: { locked?: boolean | "unknown" } = {}) {
const ButtonNames = ToolbarNodeButtonNames;
return Object.values(ButtonNames).reduce(
(buttonStates, buttonName) => {
// transform button depends on locked state
if (props.locked !== undefined && buttonName === ButtonNames.TRANSFORM) {
buttonStates[buttonName] = {
disabled: props.locked === "unknown" || props.locked === true,
hidden: false,
active: true,
};
return buttonStates;
}
// hidden buttons
if (
buttonName === ButtonNames.AI_STYLIZE ||
buttonName === ButtonNames.SEGMENTATION ||
buttonName === ButtonNames.DOWNLOAD ||
buttonName === ButtonNames.CHROMA
) {
buttonStates[buttonName] = {
disabled: true,
hidden: true,
active: false,
};
return buttonStates;
}
// all other buttons
buttonStates[buttonName] = {
disabled: false,
hidden: false,
active: false,
};
return buttonStates;
},
{} as ContextualToolbarProps["buttonStates"],
);
}
function getVideoNodeButtonStates(
props: { locked?: boolean | "unknown" } = {},
) {
const ButtonNames = ToolbarNodeButtonNames;
return Object.values(ButtonNames).reduce(
(buttonStates, buttonName) => {
// transform button depends on locked state
if (props.locked !== undefined && buttonName === ButtonNames.TRANSFORM) {
buttonStates.TRANSFORM = {
disabled: props.locked === "unknown" || props.locked === true,
hidden: false,
active: true,
};
return buttonStates;
}
// soon to come feature is disabled
if (buttonName === ButtonNames.AI_STYLIZE) {
buttonStates[buttonName] = {
disabled: true,
hidden: true,
active: false,
};
return buttonStates;
}
// all other buttons
buttonStates[buttonName] = {
disabled: false,
hidden: false,
active: false,
};
return buttonStates;
},
{} as ContextualToolbarProps["buttonStates"],
);
}
@@ -0,0 +1,193 @@
import Konva from "konva";
import { ToolbarNodeButtonNames } from "~/components/features/ToolbarNode";
import { ContextualToolbarProps } from "~/signals/uiAccess/toolbarNode";
export function calculateContextualsPosition(kNode: Konva.Transformer) {
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) {
//top right quadrants
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) {
//lower right quadrants
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) {
//lower left quadrants
px = x0 + h * Math.cos(r) - (h * Math.cos(r) + w * Math.sin(r)) / 2;
py = y0;
} else {
//top left quadrants
px = x0 + (-h * Math.sin(r) + w * Math.cos(r)) / 2;
py = y0 + h * Math.cos(r);
}
return { x: px, y: py };
}
export function getImageNodeButtonStates(
props: { locked?: boolean | "unknown" } = {},
) {
const ButtonNames = ToolbarNodeButtonNames;
return Object.values(ButtonNames).reduce(
(buttonStates, buttonName) => {
// transform button depends on locked state
if (props.locked !== undefined && buttonName === ButtonNames.TRANSFORM) {
buttonStates.TRANSFORM = {
disabled: props.locked === "unknown" || props.locked === true,
hidden: false,
active: true,
};
return buttonStates;
}
// hidden buttons
if (
buttonName === ButtonNames.AI_STYLIZE ||
buttonName === ButtonNames.SEGMENTATION ||
buttonName === ButtonNames.DOWNLOAD ||
buttonName === ButtonNames.CHROMA
) {
buttonStates[buttonName] = {
disabled: true,
hidden: true,
active: false,
};
return buttonStates;
}
// all other buttons
buttonStates[buttonName] = {
disabled: false,
hidden: false,
active: false,
};
return buttonStates;
},
{} as ContextualToolbarProps["buttonStates"],
);
}
export function getTextNodeButtonStates(
props: { locked?: boolean | "unknown" } = {},
) {
const ButtonNames = ToolbarNodeButtonNames;
return Object.values(ButtonNames).reduce(
(buttonStates, buttonName) => {
// transform button depends on locked state
if (props.locked !== undefined && buttonName === ButtonNames.TRANSFORM) {
buttonStates[buttonName] = {
disabled: props.locked === "unknown" || props.locked === true,
hidden: false,
active: true,
};
return buttonStates;
}
// hidden buttons
if (
buttonName === ButtonNames.AI_STYLIZE ||
buttonName === ButtonNames.SEGMENTATION ||
buttonName === ButtonNames.DOWNLOAD ||
buttonName === ButtonNames.CHROMA
) {
buttonStates[buttonName] = {
disabled: true,
hidden: true,
active: false,
};
return buttonStates;
}
// all other buttons
buttonStates[buttonName] = {
disabled: false,
hidden: false,
active: false,
};
return buttonStates;
},
{} as ContextualToolbarProps["buttonStates"],
);
}
export function getVideoNodeButtonStates(
props: { locked?: boolean | "unknown" } = {},
) {
const ButtonNames = ToolbarNodeButtonNames;
return Object.values(ButtonNames).reduce(
(buttonStates, buttonName) => {
// transform button depends on locked state
if (props.locked !== undefined && buttonName === ButtonNames.TRANSFORM) {
buttonStates.TRANSFORM = {
disabled: props.locked === "unknown" || props.locked === true,
hidden: false,
active: true,
};
return buttonStates;
}
// soon to come feature is disabled
if (buttonName === ButtonNames.AI_STYLIZE) {
buttonStates[buttonName] = {
disabled: true,
hidden: true,
active: false,
};
return buttonStates;
}
// all other buttons
buttonStates[buttonName] = {
disabled: false,
hidden: false,
active: false,
};
return buttonStates;
},
{} as ContextualToolbarProps["buttonStates"],
);
}
export function getMultiSelectButtonStates(
props: { locked?: boolean | "unknown" } = {},
) {
const ButtonNames = ToolbarNodeButtonNames;
return Object.values(ButtonNames).reduce(
(buttonStates, buttonName) => {
// transform button depends on locked state
if (props.locked !== undefined && buttonName === ButtonNames.TRANSFORM) {
buttonStates[buttonName] = {
disabled: props.locked === "unknown" || props.locked === true,
hidden: false,
active: true,
};
return buttonStates;
}
// hidden buttons
if (
buttonName === ButtonNames.AI_STYLIZE ||
buttonName === ButtonNames.SEGMENTATION ||
buttonName === ButtonNames.DOWNLOAD ||
buttonName === ButtonNames.CHROMA
) {
buttonStates[buttonName] = {
disabled: true,
hidden: true,
active: false,
};
return buttonStates;
}
// all other buttons
buttonStates[buttonName] = {
disabled: false,
hidden: false,
active: false,
};
return buttonStates;
},
{} as ContextualToolbarProps["buttonStates"],
);
}
@@ -1,4 +1,4 @@
import { MouseEventHandler } from "react";
import { MouseEventHandler, useState, useEffect } from "react";
import { Transition } from "@headlessui/react";
import { twMerge } from "tailwind-merge";
@@ -10,6 +10,10 @@ import { ToolbarNodeButtonNames } from "~/components/features/ToolbarNode/enums"
import { transitionTimingStyles } from "~/components/styles";
export const ContextualToolbarNode = () => {
const [winSize, setWinSize] = useState<{ width: number; height: number }>({
width: window.innerWidth,
height: window.innerHeight,
});
const { isShowing, position, ...rest } = uiAccess.toolbarNode.signal.value;
const buttonsProps = Object.values(ToolbarNodeButtonNames).reduce(
(acc, buttonName) => {
@@ -33,6 +37,20 @@ export const ContextualToolbarNode = () => {
};
},
);
useEffect(() => {
function resizeHandler() {
setWinSize({
width: window.innerWidth,
height: window.innerHeight,
});
}
window.addEventListener("resize", resizeHandler);
return () => {
window.removeEventListener("resize", resizeHandler);
};
}, []);
const boundedTopPosition = Math.min(position.y, winSize.height - 164);
return (
<Transition
as="div"
@@ -42,7 +60,7 @@ export const ContextualToolbarNode = () => {
"fixed -translate-x-1/2 translate-y-5 data-[closed]:opacity-0",
)}
style={{
top: position.y,
top: boundedTopPosition,
left: position.x,
}}
>