From 728e83d7ffaee6d0d2e74cfa59241d9b0db400a7 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Mon, 15 Jan 2024 15:46:31 +0100 Subject: [PATCH 1/4] add defineScopedStore helper function --- client/src/stores/scopedStore.ts | 11 + .../src/stores/workflowEditorCommentStore.ts | 361 +++++++++--------- .../src/stores/workflowEditorToolbarStore.ts | 125 +++--- 3 files changed, 249 insertions(+), 248 deletions(-) create mode 100644 client/src/stores/scopedStore.ts diff --git a/client/src/stores/scopedStore.ts b/client/src/stores/scopedStore.ts new file mode 100644 index 000000000000..9c07f9155f05 --- /dev/null +++ b/client/src/stores/scopedStore.ts @@ -0,0 +1,11 @@ +import { defineStore } from "pinia"; + +import { useScopePointerStore } from "./scopePointerStore"; + +export function defineScopedStore(id: ID, setup: (scopeId: string) => S) { + return (scopeId: string) => { + const { scope } = useScopePointerStore(); + + return defineStore<`${ID}-${typeof scopeId}`, S>(`${id}-${scope(scopeId)}`, () => setup(scopeId))(); + }; +} diff --git a/client/src/stores/workflowEditorCommentStore.ts b/client/src/stores/workflowEditorCommentStore.ts index f193e64d4157..4ccf3085e1f4 100644 --- a/client/src/stores/workflowEditorCommentStore.ts +++ b/client/src/stores/workflowEditorCommentStore.ts @@ -1,4 +1,3 @@ -import { defineStore } from "pinia"; import { computed, del, ref, set } from "vue"; import type { Color } from "@/components/Workflow/Editor/Comments/colors"; @@ -15,7 +14,7 @@ import { import { assertDefined } from "@/utils/assertions"; import { hasKeys, match } from "@/utils/utils"; -import { useScopePointerStore } from "./scopePointerStore"; +import { defineScopedStore } from "./scopedStore"; import { useWorkflowStateStore } from "./workflowEditorStateStore"; import { Step, useWorkflowStepStore } from "./workflowStepStore"; @@ -92,220 +91,216 @@ function assertCommentDataValid( export type WorkflowCommentStore = ReturnType; -export const useWorkflowCommentStore = (workflowId: string) => { - const { scope } = useScopePointerStore(); +export const useWorkflowCommentStore = defineScopedStore("workflowCommentStore", (workflowId) => { + const commentsRecord = ref>({}); + const localCommentsMetadata = ref>({}); - return defineStore(`workflowCommentStore${scope(workflowId)}`, () => { - const commentsRecord = ref>({}); - const localCommentsMetadata = ref>({}); + const comments = computed(() => Object.values(commentsRecord.value)); - const comments = computed(() => Object.values(commentsRecord.value)); + function $reset() { + commentsRecord.value = {}; + localCommentsMetadata.value = {}; + } - function $reset() { - commentsRecord.value = {}; - localCommentsMetadata.value = {}; - } + const addComments = (commentsArray: WorkflowComment[], defaultPosition: [number, number] = [0, 0]) => { + commentsArray.forEach((comment) => { + const newComment = structuredClone(comment); + newComment.position[0] += defaultPosition[0]; + newComment.position[1] += defaultPosition[1]; - const addComments = (commentsArray: WorkflowComment[], defaultPosition: [number, number] = [0, 0]) => { - commentsArray.forEach((comment) => { - const newComment = structuredClone(comment); - newComment.position[0] += defaultPosition[0]; - newComment.position[1] += defaultPosition[1]; + set(commentsRecord.value, newComment.id, newComment); + }); + }; - set(commentsRecord.value, newComment.id, newComment); - }); - }; + const highestCommentId = computed(() => comments.value[comments.value.length - 1]?.id ?? -1); - const highestCommentId = computed(() => comments.value[comments.value.length - 1]?.id ?? -1); + const isJustCreated = computed(() => (id: number) => localCommentsMetadata.value[id]?.justCreated ?? false); - const isJustCreated = computed(() => (id: number) => localCommentsMetadata.value[id]?.justCreated ?? false); + const getComment = computed(() => (id: number) => { + const comment = commentsRecord.value[id]; + assertDefined(comment); + return comment; + }); - const getComment = computed(() => (id: number) => { - const comment = commentsRecord.value[id]; - assertDefined(comment); - return comment; - }); + function changePosition(id: number, position: [number, number]) { + const comment = getComment.value(id); + set(comment, "position", vecReduceFigures(position)); + } - function changePosition(id: number, position: [number, number]) { - const comment = getComment.value(id); - set(comment, "position", vecReduceFigures(position)); - } + function changeSize(id: number, size: [number, number]) { + const comment = getComment.value(id); + set(comment, "size", vecReduceFigures(size)); + } - function changeSize(id: number, size: [number, number]) { - const comment = getComment.value(id); - set(comment, "size", vecReduceFigures(size)); - } + function changeData(id: number, data: unknown) { + const comment = getComment.value(id); + assertCommentDataValid(comment.type, data); + set(comment, "data", data); + } - function changeData(id: number, data: unknown) { - const comment = getComment.value(id); - assertCommentDataValid(comment.type, data); - set(comment, "data", data); - } + function changeColor(id: number, color: WorkflowCommentColor) { + const comment = getComment.value(id); + set(comment, "color", color); + } - function changeColor(id: number, color: WorkflowCommentColor) { - const comment = getComment.value(id); - set(comment, "color", color); + function addPoint(id: number, point: [number, number]) { + const comment = getComment.value(id); + if (!(comment.type === "freehand")) { + throw new Error("Can only add points to freehand comment"); } - function addPoint(id: number, point: [number, number]) { - const comment = getComment.value(id); - if (!(comment.type === "freehand")) { - throw new Error("Can only add points to freehand comment"); - } - - comment.data.line.push(point); + comment.data.line.push(point); - comment.size = vecMax(comment.size, vecSubtract(point, comment.position)); + comment.size = vecMax(comment.size, vecSubtract(point, comment.position)); - const prevPosition = comment.position; - comment.position = vecMin(comment.position, point); + const prevPosition = comment.position; + comment.position = vecMin(comment.position, point); - const diff = vecSubtract(prevPosition, comment.position); - comment.size = vecAdd(comment.size, diff); - } + const diff = vecSubtract(prevPosition, comment.position); + comment.size = vecAdd(comment.size, diff); + } - function deleteComment(id: number) { - del(commentsRecord.value, id); - } + function deleteComment(id: number) { + del(commentsRecord.value, id); + } - /** - * Adds a single comment. Sets the `userCreated` flag. - * Meant to be used when a user adds an comment. - * @param comment - */ - function createComment(comment: BaseWorkflowComment) { - markJustCreated(comment.id); - addComments([comment as WorkflowComment]); - } + /** + * Adds a single comment. Sets the `userCreated` flag. + * Meant to be used when a user adds an comment. + * @param comment + */ + function createComment(comment: BaseWorkflowComment) { + markJustCreated(comment.id); + addComments([comment as WorkflowComment]); + } - function markJustCreated(id: number) { - const metadata = localCommentsMetadata.value[id]; + function markJustCreated(id: number) { + const metadata = localCommentsMetadata.value[id]; - if (metadata) { - set(metadata, "justCreated", true); - } else { - set(localCommentsMetadata.value, id, { justCreated: true }); - } + if (metadata) { + set(metadata, "justCreated", true); + } else { + set(localCommentsMetadata.value, id, { justCreated: true }); } + } - function clearJustCreated(id: number) { - const metadata = localCommentsMetadata.value[id]; + function clearJustCreated(id: number) { + const metadata = localCommentsMetadata.value[id]; - if (metadata) { - del(metadata, "justCreated"); - } + if (metadata) { + del(metadata, "justCreated"); } + } - function deleteFreehandComments() { - Object.values(commentsRecord.value).forEach((comment) => { - if (comment.type === "freehand") { - deleteComment(comment.id); - } - }); - } + function deleteFreehandComments() { + Object.values(commentsRecord.value).forEach((comment) => { + if (comment.type === "freehand") { + deleteComment(comment.id); + } + }); + } - function commentToRectangle(comment: WorkflowComment): Rectangle { - return { - x: comment.position[0], - y: comment.position[1], - width: comment.size[0], - height: comment.size[1], - }; - } + function commentToRectangle(comment: WorkflowComment): Rectangle { + return { + x: comment.position[0], + y: comment.position[1], + width: comment.size[0], + height: comment.size[1], + }; + } - /** Calculates which comments are within frames and attaches that information to the parent comments */ - function resolveCommentsInFrames() { - // reverse to give frames on top of other frames higher precedence - const frameComments = comments.value.filter((comment) => comment.type === "frame").reverse(); - let candidates = [...comments.value]; - - frameComments.forEach((frame) => { - const bounds = new AxisAlignedBoundingBox(); - bounds.fitRectangle(commentToRectangle(frame)); - frame.child_comments = []; - - // remove when matched, so each comment can only be linked to one frame - candidates = candidates.flatMap((comment) => { - const rect: Rectangle = commentToRectangle(comment); - - if (comment !== frame && bounds.contains(rect)) { - // push id and remove from candidates when in bounds - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - frame.child_comments!.push(comment.id); - return []; - } else { - // otherwise do nothing and keep in list - return [comment]; - } - }); + /** Calculates which comments are within frames and attaches that information to the parent comments */ + function resolveCommentsInFrames() { + // reverse to give frames on top of other frames higher precedence + const frameComments = comments.value.filter((comment) => comment.type === "frame").reverse(); + let candidates = [...comments.value]; + + frameComments.forEach((frame) => { + const bounds = new AxisAlignedBoundingBox(); + bounds.fitRectangle(commentToRectangle(frame)); + frame.child_comments = []; + + // remove when matched, so each comment can only be linked to one frame + candidates = candidates.flatMap((comment) => { + const rect: Rectangle = commentToRectangle(comment); + + if (comment !== frame && bounds.contains(rect)) { + // push id and remove from candidates when in bounds + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + frame.child_comments!.push(comment.id); + return []; + } else { + // otherwise do nothing and keep in list + return [comment]; + } }); - } + }); + } - const stateStore = useWorkflowStateStore(workflowId); + const stateStore = useWorkflowStateStore(workflowId); - function stepToRectangle(step: Step): Rectangle | null { - const rect = stateStore.stepPosition[step.id]; + function stepToRectangle(step: Step): Rectangle | null { + const rect = stateStore.stepPosition[step.id]; - if (rect && step.position) { - return { - x: step.position.left, - y: step.position.top, - width: rect.width, - height: rect.height, - }; - } else { - return null; - } + if (rect && step.position) { + return { + x: step.position.left, + y: step.position.top, + width: rect.width, + height: rect.height, + }; + } else { + return null; } + } - const stepStore = useWorkflowStepStore(workflowId); - - /** Calculates which steps are within frames and attaches that information to the parent comments */ - function resolveStepsInFrames() { - // reverse to give frames on top of other frames higher precedence - const frameComments = comments.value.filter((comment) => comment.type === "frame").reverse(); - let candidates = [...Object.values(stepStore.steps)]; - - frameComments.forEach((frame) => { - const bounds = new AxisAlignedBoundingBox(); - bounds.fitRectangle(commentToRectangle(frame)); - frame.child_steps = []; - - // remove when matched, so each step can only be linked to one frame - candidates = candidates.flatMap((step) => { - const rect = stepToRectangle(step); - - if (rect && bounds.contains(rect)) { - // push id and remove from candidates when in bounds - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - frame.child_steps!.push(step.id); - return []; - } else { - // otherwise do nothing and keep in list - return [step]; - } - }); + const stepStore = useWorkflowStepStore(workflowId); + + /** Calculates which steps are within frames and attaches that information to the parent comments */ + function resolveStepsInFrames() { + // reverse to give frames on top of other frames higher precedence + const frameComments = comments.value.filter((comment) => comment.type === "frame").reverse(); + let candidates = [...Object.values(stepStore.steps)]; + + frameComments.forEach((frame) => { + const bounds = new AxisAlignedBoundingBox(); + bounds.fitRectangle(commentToRectangle(frame)); + frame.child_steps = []; + + // remove when matched, so each step can only be linked to one frame + candidates = candidates.flatMap((step) => { + const rect = stepToRectangle(step); + + if (rect && bounds.contains(rect)) { + // push id and remove from candidates when in bounds + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + frame.child_steps!.push(step.id); + return []; + } else { + // otherwise do nothing and keep in list + return [step]; + } }); - } + }); + } - return { - commentsRecord, - comments, - addComments, - highestCommentId, - isJustCreated, - changePosition, - changeSize, - changeData, - changeColor, - addPoint, - deleteComment, - createComment, - clearJustCreated, - deleteFreehandComments, - resolveCommentsInFrames, - resolveStepsInFrames, - $reset, - }; - })(); -}; + return { + commentsRecord, + comments, + addComments, + highestCommentId, + isJustCreated, + changePosition, + changeSize, + changeData, + changeColor, + addPoint, + deleteComment, + createComment, + clearJustCreated, + deleteFreehandComments, + resolveCommentsInFrames, + resolveStepsInFrames, + $reset, + }; +}); diff --git a/client/src/stores/workflowEditorToolbarStore.ts b/client/src/stores/workflowEditorToolbarStore.ts index 30b3680bfd68..54dda39dddb4 100644 --- a/client/src/stores/workflowEditorToolbarStore.ts +++ b/client/src/stores/workflowEditorToolbarStore.ts @@ -1,10 +1,9 @@ import { useMagicKeys } from "@vueuse/core"; -import { defineStore } from "pinia"; import { computed, onScopeDispose, reactive, ref, watch } from "vue"; import { useUserLocalStorage } from "@/composables/userLocalStorage"; -import { useScopePointerStore } from "./scopePointerStore"; +import { defineScopedStore } from "./scopedStore"; import { WorkflowCommentColor } from "./workflowEditorCommentStore"; export type CommentTool = "textComment" | "markdownComment" | "frameComment" | "freehandComment" | "freehandEraser"; @@ -28,74 +27,70 @@ export interface InputCatcherEvent { export type WorkflowEditorToolbarStore = ReturnType; -export const useWorkflowEditorToolbarStore = (workflowId: string) => { - const { scope } = useScopePointerStore(); - - return defineStore(`workflowEditorToolbarStore${scope(workflowId)}`, () => { - const snapActive = useUserLocalStorage("workflow-editor-toolbar-snap-active", false); - const currentTool = ref("pointer"); - const inputCatcherActive = ref(false); - const inputCatcherEventListeners = new Set(); - const snapDistance = ref<10 | 20 | 50 | 100 | 200>(10); - - const commentOptions = reactive({ - bold: false, - italic: false, - color: "none" as WorkflowCommentColor, - textSize: 2, - lineThickness: 5, - smoothing: 2, - }); +export const useWorkflowEditorToolbarStore = defineScopedStore("workflowEditorToolbarStore", () => { + const snapActive = useUserLocalStorage("workflow-editor-toolbar-snap-active", false); + const currentTool = ref("pointer"); + const inputCatcherActive = ref(false); + const inputCatcherEventListeners = new Set(); + const snapDistance = ref<10 | 20 | 50 | 100 | 200>(10); + + const commentOptions = reactive({ + bold: false, + italic: false, + color: "none" as WorkflowCommentColor, + textSize: 2, + lineThickness: 5, + smoothing: 2, + }); + + const inputCatcherPressed = ref(false); + + function onInputCatcherEvent(type: InputCatcherEventType, callback: InputCatcherEventListener["callback"]) { + const listener = { + type, + callback, + }; - const inputCatcherPressed = ref(false); + inputCatcherEventListeners.add(listener); - function onInputCatcherEvent(type: InputCatcherEventType, callback: InputCatcherEventListener["callback"]) { - const listener = { - type, - callback, - }; + onScopeDispose(() => { + inputCatcherEventListeners.delete(listener); + }); + } - inputCatcherEventListeners.add(listener); + onInputCatcherEvent("pointerdown", () => (inputCatcherPressed.value = true)); + onInputCatcherEvent("pointerup", () => (inputCatcherPressed.value = false)); + onInputCatcherEvent("temporarilyDisabled", () => (inputCatcherPressed.value = false)); - onScopeDispose(() => { - inputCatcherEventListeners.delete(listener); - }); + watch( + () => inputCatcherActive.value, + () => { + if (!inputCatcherActive.value) { + inputCatcherPressed.value = false; + } } + ); - onInputCatcherEvent("pointerdown", () => (inputCatcherPressed.value = true)); - onInputCatcherEvent("pointerup", () => (inputCatcherPressed.value = false)); - onInputCatcherEvent("temporarilyDisabled", () => (inputCatcherPressed.value = false)); + const { shift, space, alt, ctrl } = useMagicKeys(); + const inputCatcherEnabled = computed(() => !(shift?.value || space?.value || alt?.value || ctrl?.value)); - watch( - () => inputCatcherActive.value, - () => { - if (!inputCatcherActive.value) { - inputCatcherPressed.value = false; - } + function emitInputCatcherEvent(type: InputCatcherEventType, event: InputCatcherEvent) { + inputCatcherEventListeners.forEach((listener) => { + if (listener.type === type) { + listener.callback(event); } - ); - - const { shift, space, alt, ctrl } = useMagicKeys(); - const inputCatcherEnabled = computed(() => !(shift?.value || space?.value || alt?.value || ctrl?.value)); - - function emitInputCatcherEvent(type: InputCatcherEventType, event: InputCatcherEvent) { - inputCatcherEventListeners.forEach((listener) => { - if (listener.type === type) { - listener.callback(event); - } - }); - } - - return { - snapActive, - snapDistance, - currentTool, - inputCatcherActive, - inputCatcherEnabled, - commentOptions, - inputCatcherPressed, - onInputCatcherEvent, - emitInputCatcherEvent, - }; - })(); -}; + }); + } + + return { + snapActive, + snapDistance, + currentTool, + inputCatcherActive, + inputCatcherEnabled, + commentOptions, + inputCatcherPressed, + onInputCatcherEvent, + emitInputCatcherEvent, + }; +}); From 75675992e6348ef8327765bcd0c49de57a120c00 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Mon, 15 Jan 2024 16:05:33 +0100 Subject: [PATCH 2/4] refactor workflowConnectionStore to composition --- client/src/stores/workflowConnectionStore.ts | 186 ++++++++++--------- 1 file changed, 102 insertions(+), 84 deletions(-) diff --git a/client/src/stores/workflowConnectionStore.ts b/client/src/stores/workflowConnectionStore.ts index ba201dab52fe..6349abb5f14f 100644 --- a/client/src/stores/workflowConnectionStore.ts +++ b/client/src/stores/workflowConnectionStore.ts @@ -1,10 +1,9 @@ -import { defineStore } from "pinia"; -import Vue from "vue"; +import { computed, del, ref, set } from "vue"; import { useWorkflowStepStore } from "@/stores/workflowStepStore"; import { pushOrSet } from "@/utils/pushOrSet"; -import { useScopePointerStore } from "./scopePointerStore"; +import { defineScopedStore } from "./scopedStore"; interface InvalidConnections { [index: ConnectionId]: string | undefined; @@ -44,87 +43,6 @@ interface TerminalToOutputTerminals { [index: string]: OutputTerminal[]; } -export const useConnectionStore = (workflowId: string) => { - const { scope } = useScopePointerStore(); - - return defineStore(`workflowConnectionStore${scope(workflowId)}`, { - state: (): State => ({ - connections: [] as Array>, - invalidConnections: {} as InvalidConnections, - inputTerminalToOutputTerminals: {} as TerminalToOutputTerminals, - terminalToConnection: {} as { [index: string]: Connection[] }, - stepToConnections: {} as { [index: number]: Connection[] }, - }), - getters: { - getOutputTerminalsForInputTerminal(state: State) { - return (terminalId: string): OutputTerminal[] => { - return state.inputTerminalToOutputTerminals[terminalId] || []; - }; - }, - getConnectionsForTerminal(state: State) { - return (terminalId: string): Connection[] => { - return state.terminalToConnection[terminalId] || []; - }; - }, - getConnectionsForStep(state: State) { - return (stepId: number): Connection[] => state.stepToConnections[stepId] || []; - }, - }, - actions: { - addConnection(this, _connection: Connection) { - const connection = Object.freeze(_connection); - this.connections.push(connection); - const stepStore = useWorkflowStepStore(workflowId); - stepStore.addConnection(connection); - this.terminalToConnection = updateTerminalToConnection(this.connections); - this.inputTerminalToOutputTerminals = updateTerminalToTerminal(this.connections); - this.stepToConnections = updateStepToConnections(this.connections); - }, - markInvalidConnection(this: State, connectionId: string, reason: string) { - Vue.set(this.invalidConnections, connectionId, reason); - }, - dropFromInvalidConnections(this: State, connectionId: string) { - Vue.delete(this.invalidConnections, connectionId); - }, - removeConnection(this, terminal: InputTerminal | OutputTerminal | ConnectionId) { - const stepStore = useWorkflowStepStore(workflowId); - this.connections = this.connections.filter((connection) => { - const id = getConnectionId(connection); - - if (typeof terminal === "string") { - if (id === terminal) { - stepStore.removeConnection(connection); - Vue.delete(this.invalidConnections, id); - return false; - } else { - return true; - } - } else if (terminal.connectorType === "input") { - if (connection.input.stepId == terminal.stepId && connection.input.name == terminal.name) { - stepStore.removeConnection(connection); - Vue.delete(this.invalidConnections, id); - return false; - } else { - return true; - } - } else { - if (connection.output.stepId == terminal.stepId && connection.output.name == terminal.name) { - stepStore.removeConnection(connection); - Vue.delete(this.invalidConnections, id); - return false; - } else { - return true; - } - } - }); - this.terminalToConnection = updateTerminalToConnection(this.connections); - this.inputTerminalToOutputTerminals = updateTerminalToTerminal(this.connections); - this.stepToConnections = updateStepToConnections(this.connections); - }, - }, - })(); -}; - function updateTerminalToTerminal(connections: Connection[]) { const inputTerminalToOutputTerminals: TerminalToOutputTerminals = {}; connections.forEach((connection) => { @@ -170,3 +88,103 @@ export function getTerminals(item: Connection): { input: InputTerminal; output: export function getConnectionId(item: Connection): ConnectionId { return `${item.input.stepId}-${item.input.name}-${item.output.stepId}-${item.output.name}`; } + +export const useConnectionStore = defineScopedStore("workflowConnectionStore", (workflowId) => { + const connections = ref[]>([]); + const invalidConnections = ref({}); + const inputTerminalToOutputTerminals = ref({}); + const terminalToConnection = ref<{ [index: string]: Connection[] }>({}); + const stepToConnections = ref<{ [index: number]: Connection[] }>({}); + + function $reset() { + connections.value = []; + invalidConnections.value = {}; + inputTerminalToOutputTerminals.value = {}; + terminalToConnection.value = {}; + stepToConnections.value = {}; + } + + const getOutputTerminalsForInputTerminal = computed( + () => (terminalId: string) => inputTerminalToOutputTerminals.value[terminalId] || ([] as OutputTerminal[]) + ); + + const getConnectionsForTerminal = computed( + () => (terminalId: string) => terminalToConnection.value[terminalId] || ([] as Connection[]) + ); + + const getConnectionsForStep = computed( + () => (stepId: number) => stepToConnections.value[stepId] || ([] as Connection[]) + ); + + const stepStore = useWorkflowStepStore(workflowId); + + function addConnection(connection: Connection) { + Object.freeze(connection); + connections.value.push(connection); + stepStore.addConnection(connection); + + terminalToConnection.value = updateTerminalToConnection(connections.value); + inputTerminalToOutputTerminals.value = updateTerminalToTerminal(connections.value); + stepToConnections.value = updateStepToConnections(connections.value); + } + + function removeConnection(terminal: InputTerminal | OutputTerminal | ConnectionId) { + connections.value = connections.value.filter((connection) => { + const id = getConnectionId(connection); + + if (typeof terminal === "string") { + if (id === terminal) { + stepStore.removeConnection(connection); + del(invalidConnections.value, id); + return false; + } else { + return true; + } + } else if (terminal.connectorType === "input") { + if (connection.input.stepId == terminal.stepId && connection.input.name == terminal.name) { + stepStore.removeConnection(connection); + del(invalidConnections.value, id); + return false; + } else { + return true; + } + } else { + if (connection.output.stepId == terminal.stepId && connection.output.name == terminal.name) { + stepStore.removeConnection(connection); + del(invalidConnections.value, id); + return false; + } else { + return true; + } + } + }); + + terminalToConnection.value = updateTerminalToConnection(connections.value); + inputTerminalToOutputTerminals.value = updateTerminalToTerminal(connections.value); + stepToConnections.value = updateStepToConnections(connections.value); + } + + function markInvalidConnection(connectionId: string, reason: string) { + set(invalidConnections.value, connectionId, reason); + } + + function dropFromInvalidConnections(connectionId: string) { + del(invalidConnections.value, connectionId); + } + + return { + connections, + invalidConnections, + inputTerminalToOutputTerminals, + terminalToConnection, + stepToConnections, + $reset, + getOutputTerminalsForInputTerminal, + getConnectionsForTerminal, + getConnectionsForStep, + addConnection, + removeConnection, + markInvalidConnection, + dropFromInvalidConnections, + }; +}); From 09b3abdb277b3894d34b234edebbcae69e4ec2a4 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Mon, 15 Jan 2024 16:24:39 +0100 Subject: [PATCH 3/4] refactor workflowEditorStateStore to composition --- .../src/components/Workflow/Editor/Index.vue | 10 +- .../Workflow/Editor/WorkflowGraph.vue | 8 +- .../Workflow/Published/WorkflowPublished.vue | 2 +- client/src/stores/workflowEditorStateStore.ts | 176 ++++++++++-------- 4 files changed, 105 insertions(+), 91 deletions(-) diff --git a/client/src/components/Workflow/Editor/Index.vue b/client/src/components/Workflow/Editor/Index.vue index 4347dd48b733..6f6109bdd559 100644 --- a/client/src/components/Workflow/Editor/Index.vue +++ b/client/src/components/Workflow/Editor/Index.vue @@ -492,7 +492,7 @@ export default { label: null, position: defaultPosition(this.graphOffset, this.transform), }); - this.stateStore.setActiveNode(id); + this.stateStore.activeNodeId = id; }, onInsertTool(tool_id, tool_name) { this._insertStep(tool_id, tool_name, "tool"); @@ -570,11 +570,11 @@ export default { }, onAttributes() { this._ensureParametersSet(); - this.stateStore.setActiveNode(null); + this.stateStore.activeNodeId = null; this.showInPanel = "attributes"; }, onWorkflowTextEditor() { - this.stateStore.setActiveNode(null); + this.stateStore.activeNodeId = null; this.showInPanel = "attributes"; }, onAnnotation(nodeId, newAnnotation) { @@ -667,7 +667,7 @@ export default { }, onLint() { this._ensureParametersSet(); - this.stateStore.setActiveNode(null); + this.stateStore.activeNodeId = null; this.showInPanel = "lint"; }, onUpgrade() { @@ -752,7 +752,7 @@ export default { outputs: response.outputs, config_form: response.config_form, }); - this.stateStore.setActiveNode(stepData.id); + this.stateStore.activeNodeId = stepData.id; } ); }, diff --git a/client/src/components/Workflow/Editor/WorkflowGraph.vue b/client/src/components/Workflow/Editor/WorkflowGraph.vue index 464139ba6631..467ad460dd29 100644 --- a/client/src/components/Workflow/Editor/WorkflowGraph.vue +++ b/client/src/components/Workflow/Editor/WorkflowGraph.vue @@ -153,7 +153,7 @@ function onZoom(zoomLevel: number, panTo: XYPosition | null = null) { if (panTo) { panBy({ x: panTo.x - transform.value.x, y: panTo.y - transform.value.y }); } - stateStore.setScale(zoomLevel); + stateStore.scale = zoomLevel; } function onStopDragging() { stateStore.draggingPosition = null; @@ -167,16 +167,16 @@ function onDragConnector(position: TerminalPosition, draggingTerminal: OutputTer } function onActivate(nodeId: number | null) { if (activeNodeId.value !== nodeId) { - stateStore.setActiveNode(nodeId); + stateStore.activeNodeId = nodeId; } } function onDeactivate() { - stateStore.setActiveNode(null); + stateStore.activeNodeId = null; } watch( () => transform.value.k, - () => stateStore.setScale(transform.value.k) + () => (stateStore.scale = transform.value.k) ); watch(transform, () => emit("transform", transform.value)); diff --git a/client/src/components/Workflow/Published/WorkflowPublished.vue b/client/src/components/Workflow/Published/WorkflowPublished.vue index 62e06a487776..45ffdd2fc5f4 100644 --- a/client/src/components/Workflow/Published/WorkflowPublished.vue +++ b/client/src/components/Workflow/Published/WorkflowPublished.vue @@ -89,7 +89,7 @@ const { stateStore } = provideScopedWorkflowStores(props.id); watch( () => props.zoom, - () => stateStore.setScale(props.zoom), + () => (stateStore.scale = props.zoom), { immediate: true } ); diff --git a/client/src/stores/workflowEditorStateStore.ts b/client/src/stores/workflowEditorStateStore.ts index 6cba836b721d..731153b1ce1b 100644 --- a/client/src/stores/workflowEditorStateStore.ts +++ b/client/src/stores/workflowEditorStateStore.ts @@ -1,11 +1,10 @@ import type { UseElementBoundingReturn } from "@vueuse/core"; -import { defineStore } from "pinia"; import type { UnwrapRef } from "vue"; -import Vue, { reactive } from "vue"; +import { computed, reactive, ref, set } from "vue"; import type { OutputTerminals } from "@/components/Workflow/Editor/modules/terminals"; -import { useScopePointerStore } from "./scopePointerStore"; +import { defineScopedStore } from "./scopedStore"; export interface InputTerminalPosition { endX: number; @@ -24,82 +23,97 @@ export interface XYPosition { y: number; } -interface State { - inputTerminals: { [index: number]: { [index: string]: InputTerminalPosition } }; - outputTerminals: { [index: number]: { [index: string]: OutputTerminalPosition } }; - draggingPosition: TerminalPosition | null; - draggingTerminal: OutputTerminals | null; - activeNodeId: number | null; - scale: number; - stepPosition: { [index: number]: UnwrapRef }; - stepLoadingState: { [index: number]: { loading?: boolean; error?: string } }; -} +type InputTerminalPositions = { [index: number]: { [index: string]: InputTerminalPosition } }; +type OutputTerminalPositions = { [index: number]: { [index: string]: OutputTerminalPosition } }; +type StepPosition = { [index: number]: UnwrapRef }; +type StepLoadingState = { [index: number]: { loading?: boolean; error?: string } }; + +export const useWorkflowStateStore = defineScopedStore("workflowStateStore", () => { + const inputTerminals = ref({}); + const outputTerminals = ref({}); + const draggingPosition = ref(null); + const draggingTerminal = ref(null); + const activeNodeId = ref(null); + const scale = ref(1); + const stepPosition = ref({}); + const stepLoadingState = ref({}); + + function $reset() { + inputTerminals.value = {}; + outputTerminals.value = {}; + draggingPosition.value = null; + draggingTerminal.value = null; + activeNodeId.value = null; + scale.value = 1; + stepPosition.value = {}; + stepLoadingState.value = {}; + } + + const getInputTerminalPosition = computed( + () => (stepId: number, inputName: string) => inputTerminals.value[stepId]?.[inputName] + ); + + const getOutputTerminalPosition = computed( + () => (stepId: number, outputName: string) => outputTerminals.value[stepId]?.[outputName] + ); + + const getStepLoadingState = computed(() => (stepId: number) => stepLoadingState.value[stepId]); + + function setInputTerminalPosition(stepId: number, inputName: string, position: InputTerminalPosition) { + if (!inputTerminals.value[stepId]) { + set(inputTerminals.value, stepId, {}); + } + + set(inputTerminals.value[stepId]!, inputName, position); + } + + function setOutputTerminalPosition(stepId: number, outputName: string, position: OutputTerminalPosition) { + if (!outputTerminals.value[stepId]) { + set(outputTerminals.value, stepId, reactive({})); + } + + set(outputTerminals.value[stepId]!, outputName, position); + } + + function deleteInputTerminalPosition(stepId: number, inputName: string) { + delete inputTerminals.value[stepId]?.[inputName]; + } + + function deleteOutputTerminalPosition(stepId: number, outputName: string) { + delete outputTerminals.value[stepId]?.[outputName]; + } + + function setStepPosition(stepId: number, position: UnwrapRef) { + set(stepPosition.value, stepId, position); + } + + function deleteStepPosition(stepId: number) { + delete stepPosition.value[stepId]; + } + + function setLoadingState(stepId: number, loading: boolean, error: string | undefined) { + set(stepLoadingState.value, stepId, { loading, error }); + } -export const useWorkflowStateStore = (workflowId: string) => { - const { scope } = useScopePointerStore(); - - return defineStore(`workflowStateStore${scope(workflowId)}`, { - state: (): State => ({ - inputTerminals: {}, - outputTerminals: {}, - draggingPosition: null, - draggingTerminal: null, - activeNodeId: null, - scale: 1, - stepPosition: {}, - stepLoadingState: {}, - }), - getters: { - getInputTerminalPosition(state: State) { - return (stepId: number, inputName: string) => { - return state.inputTerminals[stepId]?.[inputName] as InputTerminalPosition | undefined; - }; - }, - getOutputTerminalPosition(state: State) { - return (stepId: number, outputName: string) => { - return state.outputTerminals[stepId]?.[outputName] as OutputTerminalPosition | undefined; - }; - }, - getStepLoadingState(state: State) { - return (stepId: number) => state.stepLoadingState[stepId]; - }, - }, - actions: { - setInputTerminalPosition(stepId: number, inputName: string, position: InputTerminalPosition) { - if (!this.inputTerminals[stepId]) { - Vue.set(this.inputTerminals, stepId, {}); - } - - Vue.set(this.inputTerminals[stepId]!, inputName, position); - }, - setOutputTerminalPosition(stepId: number, outputName: string, position: OutputTerminalPosition) { - if (!this.outputTerminals[stepId]) { - Vue.set(this.outputTerminals, stepId, reactive({})); - } - - Vue.set(this.outputTerminals[stepId]!, outputName, position); - }, - deleteInputTerminalPosition(stepId: number, inputName: string) { - delete this.inputTerminals[stepId]?.[inputName]; - }, - deleteOutputTerminalPosition(stepId: number, outputName: string) { - delete this.outputTerminals[stepId]?.[outputName]; - }, - setActiveNode(nodeId: number | null) { - this.activeNodeId = nodeId; - }, - setScale(scale: number) { - this.scale = scale; - }, - setStepPosition(stepId: number, position: UnwrapRef) { - Vue.set(this.stepPosition, stepId, position); - }, - deleteStepPosition(stepId: number) { - delete this.stepPosition[stepId]; - }, - setLoadingState(stepId: number, loading: boolean, error: string | undefined) { - Vue.set(this.stepLoadingState, stepId, { loading, error }); - }, - }, - })(); -}; + return { + inputTerminals, + outputTerminals, + draggingPosition, + draggingTerminal, + activeNodeId, + scale, + stepPosition, + stepLoadingState, + $reset, + getInputTerminalPosition, + getOutputTerminalPosition, + getStepLoadingState, + setInputTerminalPosition, + setOutputTerminalPosition, + deleteInputTerminalPosition, + deleteOutputTerminalPosition, + deleteStepPosition, + setStepPosition, + setLoadingState, + }; +}); From b6018a1525d8bbac2e4bd6fd53142f0ad939bcde Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Tue, 16 Jan 2024 12:51:47 +0100 Subject: [PATCH 4/4] refactor workflowStepStore to composition --- client/src/stores/workflowStepStore.ts | 415 ++++++++++++++----------- 1 file changed, 226 insertions(+), 189 deletions(-) diff --git a/client/src/stores/workflowStepStore.ts b/client/src/stores/workflowStepStore.ts index 1358e7b028da..c696f3141ad9 100644 --- a/client/src/stores/workflowStepStore.ts +++ b/client/src/stores/workflowStepStore.ts @@ -1,19 +1,10 @@ -import { defineStore } from "pinia"; -import Vue from "vue"; +import { computed, del, ref, set } from "vue"; import type { CollectionTypeDescriptor } from "@/components/Workflow/Editor/modules/collectionTypeDescription"; import { type Connection, getConnectionId, useConnectionStore } from "@/stores/workflowConnectionStore"; import { assertDefined } from "@/utils/assertions"; -import { useScopePointerStore } from "./scopePointerStore"; - -interface State { - steps: { [index: string]: Step }; - stepIndex: number; - stepMapOver: { [index: number]: CollectionTypeDescriptor }; - stepInputMapOver: StepInputMapOver; - stepExtraInputs: { [index: number]: InputTerminalSource[] }; -} +import { defineScopedStore } from "./scopedStore"; interface StepPosition { top: number; @@ -146,191 +137,237 @@ interface StepInputMapOver { [index: number]: { [index: string]: CollectionTypeDescriptor }; } -export const useWorkflowStepStore = (workflowId: string) => { - const { scope } = useScopePointerStore(); - - return defineStore(`workflowStepStore${scope(workflowId)}`, { - state: (): State => ({ - steps: {} as Steps, - stepMapOver: {} as { [index: number]: CollectionTypeDescriptor }, - stepInputMapOver: {} as StepInputMapOver, - stepIndex: -1, - stepExtraInputs: {} as { [index: number]: InputTerminalSource[] }, - }), - getters: { - getStep(state: State) { - return (stepId: number): Step | undefined => { - return state.steps[stepId.toString()]; - }; - }, - getStepExtraInputs(state: State) { - return (stepId: number) => state.stepExtraInputs[stepId] || []; - }, - getStepIndex(state: State) { - return Math.max(...Object.values(state.steps).map((step) => step.id), state.stepIndex); - }, - hasActiveOutputs(state: State) { - return Boolean(Object.values(state.steps).find((step) => step.workflow_outputs?.length)); - }, - workflowOutputs(state: State) { - const workflowOutputs: WorkflowOutputs = {}; - Object.values(state.steps).forEach((step) => { - if (step.workflow_outputs?.length) { - step.workflow_outputs.forEach((workflowOutput) => { - if (workflowOutput.label) { - workflowOutputs[workflowOutput.label] = { - outputName: workflowOutput.output_name, - stepId: step.id, - }; - } - }); +export const useWorkflowStepStore = defineScopedStore("workflowStepStore", (workflowId) => { + const steps = ref({}); + const stepMapOver = ref<{ [index: number]: CollectionTypeDescriptor }>({}); + const stepInputMapOver = ref({}); + const stepIndex = ref(-1); + const stepExtraInputs = ref<{ [index: number]: InputTerminalSource[] }>({}); + + function $reset() { + steps.value = {}; + stepMapOver.value = {}; + stepInputMapOver.value = {}; + stepIndex.value = -1; + stepExtraInputs.value = {}; + } + + const getStep = computed(() => (stepId: number) => steps.value[stepId.toString()]); + + const getStepExtraInputs = computed(() => (stepId: number) => stepExtraInputs.value[stepId] || []); + + const getStepIndex = computed(() => + Math.max(...Object.values(steps.value).map((step) => step.id), stepIndex.value) + ); + + const hasActiveOutputs = computed(() => + Boolean(Object.values(steps.value).find((step) => step.workflow_outputs?.length)) + ); + + const workflowOutputs = computed(() => { + const workflowOutputs: WorkflowOutputs = {}; + + Object.values(steps.value).forEach((step) => { + if (step.workflow_outputs?.length) { + step.workflow_outputs.forEach((workflowOutput) => { + if (workflowOutput.label) { + workflowOutputs[workflowOutput.label] = { + outputName: workflowOutput.output_name, + stepId: step.id, + }; } }); - return workflowOutputs; - }, - duplicateLabels(state: State) { - const duplicateLabels: Set = new Set(); - const labels: Set = new Set(); - Object.values(state.steps).forEach((step) => { - if (step.workflow_outputs?.length) { - step.workflow_outputs.forEach((workflowOutput) => { - if (workflowOutput.label) { - if (labels.has(workflowOutput.label)) { - duplicateLabels.add(workflowOutput.label); - } - labels.add(workflowOutput.label); - } - }); + } + }); + + return workflowOutputs; + }); + + const duplicateLabels = computed(() => { + const duplicateLabels: Set = new Set(); + const labels: Set = new Set(); + + Object.values(steps.value).forEach((step) => { + if (step.workflow_outputs?.length) { + step.workflow_outputs.forEach((workflowOutput) => { + if (workflowOutput.label) { + if (labels.has(workflowOutput.label)) { + duplicateLabels.add(workflowOutput.label); + } + labels.add(workflowOutput.label); } }); - return duplicateLabels; - }, - }, - actions: { - addStep(newStep: NewStep): Step { - const stepId = newStep.id ? newStep.id : this.getStepIndex + 1; - const step = Object.freeze({ ...newStep, id: stepId } as Step); - Vue.set(this.steps, stepId.toString(), step); - const connectionStore = useConnectionStore(workflowId); - stepToConnections(step).map((connection) => connectionStore.addConnection(connection)); - this.stepExtraInputs[step.id] = getStepExtraInputs(step); - return step; - }, - insertNewStep( - contentId: NewStep["content_id"], - name: NewStep["name"], - type: NewStep["type"], - position: NewStep["position"] - ) { - const stepData: NewStep = { - name: name, - content_id: contentId, - input_connections: {}, - type: type, - inputs: [], - outputs: [], - position: position, - post_job_actions: {}, - tool_state: {}, - }; - return this.addStep(stepData); - }, - updateStep(this: State, step: Step) { - const workflow_outputs = step.workflow_outputs?.filter((workflowOutput) => - step.outputs.find((output) => workflowOutput.output_name == output.name) - ); - this.steps[step.id.toString()] = Object.freeze({ ...step, workflow_outputs }); - this.stepExtraInputs[step.id] = getStepExtraInputs(step); - }, - changeStepMapOver(stepId: number, mapOver: CollectionTypeDescriptor) { - Vue.set(this.stepMapOver, stepId, mapOver); - }, - resetStepInputMapOver(stepId: number) { - Vue.set(this.stepInputMapOver, stepId, {}); - }, - changeStepInputMapOver(stepId: number, inputName: string, mapOver: CollectionTypeDescriptor) { - if (this.stepInputMapOver[stepId]) { - Vue.set(this.stepInputMapOver[stepId]!, inputName, mapOver); - } else { - Vue.set(this.stepInputMapOver, stepId, { [inputName]: mapOver }); - } - }, - addConnection(connection: Connection) { - const inputStep = this.getStep(connection.input.stepId); - assertDefined( - inputStep, - `Failed to add connection, because step with id ${connection.input.stepId} is undefined` - ); - const input = inputStep.inputs.find((input) => input.name === connection.input.name); - const connectionLink: ConnectionOutputLink = { - output_name: connection.output.name, - id: connection.output.stepId, - }; - if (input && "input_subworkflow_step_id" in input && input.input_subworkflow_step_id !== undefined) { - connectionLink["input_subworkflow_step_id"] = input.input_subworkflow_step_id; - } - let connectionLinks: ConnectionOutputLink[] = [connectionLink]; - let inputConnection = inputStep.input_connections[connection.input.name]; - if (inputConnection) { - if (!Array.isArray(inputConnection)) { - inputConnection = [inputConnection]; - } - inputConnection = inputConnection.filter( - (connection) => - !( - connection.id === connectionLink.id && - connection.output_name === connectionLink.output_name - ) - ); - connectionLinks = [...connectionLinks, ...inputConnection]; - } - const updatedStep = { - ...inputStep, - input_connections: { - ...inputStep.input_connections, - [connection.input.name]: connectionLinks.sort((a, b) => - a.id === b.id ? a.output_name.localeCompare(b.output_name) : a.id - b.id - ), - }, - }; - this.updateStep(updatedStep); + } + }); + + return duplicateLabels; + }); + + const connectionStore = useConnectionStore(workflowId); + + function addStep(newStep: NewStep): Step { + const stepId = newStep.id ? newStep.id : getStepIndex.value + 1; + const step = Object.freeze({ ...newStep, id: stepId } as Step); + + set(steps.value, stepId.toString(), step); + stepToConnections(step).map((connection) => connectionStore.addConnection(connection)); + stepExtraInputs.value[step.id] = findStepExtraInputs(step); + + return step; + } + + function insertNewStep( + contentId: NewStep["content_id"], + name: NewStep["name"], + type: NewStep["type"], + position: NewStep["position"] + ) { + const stepData: NewStep = { + name: name, + content_id: contentId, + input_connections: {}, + type: type, + inputs: [], + outputs: [], + position: position, + post_job_actions: {}, + tool_state: {}, + }; + + return addStep(stepData); + } + + function updateStep(step: Step) { + const workflow_outputs = step.workflow_outputs?.filter((workflowOutput) => + step.outputs.find((output) => workflowOutput.output_name == output.name) + ); + + steps.value[step.id.toString()] = Object.freeze({ ...step, workflow_outputs }); + stepExtraInputs.value[step.id] = findStepExtraInputs(step); + } + + function changeStepMapOver(stepId: number, mapOver: CollectionTypeDescriptor) { + set(stepMapOver.value, stepId, mapOver); + } + + function resetStepInputMapOver(stepId: number) { + set(stepInputMapOver.value, stepId, {}); + } + + function changeStepInputMapOver(stepId: number, inputName: string, mapOver: CollectionTypeDescriptor) { + if (stepInputMapOver.value[stepId]) { + set(stepInputMapOver.value[stepId]!, inputName, mapOver); + } else { + set(stepInputMapOver.value, stepId, { [inputName]: mapOver }); + } + } + + function addConnection(connection: Connection) { + const inputStep = getStep.value(connection.input.stepId); + + assertDefined( + inputStep, + `Failed to add connection, because step with id ${connection.input.stepId} is undefined` + ); + + const input = inputStep.inputs.find((input) => input.name === connection.input.name); + const connectionLink: ConnectionOutputLink = { + output_name: connection.output.name, + id: connection.output.stepId, + }; + + if (input && "input_subworkflow_step_id" in input && input.input_subworkflow_step_id !== undefined) { + connectionLink["input_subworkflow_step_id"] = input.input_subworkflow_step_id; + } + + let connectionLinks: ConnectionOutputLink[] = [connectionLink]; + let inputConnection = inputStep.input_connections[connection.input.name]; + + if (inputConnection) { + if (!Array.isArray(inputConnection)) { + inputConnection = [inputConnection]; + } + inputConnection = inputConnection.filter( + (connection) => + !(connection.id === connectionLink.id && connection.output_name === connectionLink.output_name) + ); + connectionLinks = [...connectionLinks, ...inputConnection]; + } + + const updatedStep = { + ...inputStep, + input_connections: { + ...inputStep.input_connections, + [connection.input.name]: connectionLinks.sort((a, b) => + a.id === b.id ? a.output_name.localeCompare(b.output_name) : a.id - b.id + ), }, - removeConnection(connection: Connection) { - const inputStep = this.getStep(connection.input.stepId); - assertDefined( - inputStep, - `Failed to remove connection, because step with id ${connection.input.stepId} is undefined` + }; + + updateStep(updatedStep); + } + + function removeConnection(connection: Connection) { + const inputStep = getStep.value(connection.input.stepId); + + assertDefined( + inputStep, + `Failed to remove connection, because step with id ${connection.input.stepId} is undefined` + ); + + const inputConnections = inputStep.input_connections[connection.input.name]; + + if (getStepExtraInputs.value(inputStep.id).find((input) => connection.input.name === input.name)) { + inputStep.input_connections[connection.input.name] = undefined; + } else { + if (Array.isArray(inputConnections)) { + inputStep.input_connections[connection.input.name] = inputConnections.filter( + (outputLink) => + !(outputLink.id === connection.output.stepId, outputLink.output_name === connection.output.name) ); + } else { + del(inputStep.input_connections, connection.input.name); + } + } - const inputConnections = inputStep.input_connections[connection.input.name]; - if (this.getStepExtraInputs(inputStep.id).find((input) => connection.input.name === input.name)) { - inputStep.input_connections[connection.input.name] = undefined; - } else { - if (Array.isArray(inputConnections)) { - inputStep.input_connections[connection.input.name] = inputConnections.filter( - (outputLink) => - !(outputLink.id === connection.output.stepId, - outputLink.output_name === connection.output.name) - ); - } else { - Vue.delete(inputStep.input_connections, connection.input.name); - } - } - this.updateStep(inputStep); - }, - removeStep(this: State, stepId: number) { - const connectionStore = useConnectionStore(workflowId); - connectionStore - .getConnectionsForStep(stepId) - .forEach((connection) => connectionStore.removeConnection(getConnectionId(connection))); - Vue.delete(this.steps, stepId.toString()); - Vue.delete(this.stepExtraInputs, stepId); - }, - }, - })(); -}; + updateStep(inputStep); + } + + function removeStep(stepId: number) { + connectionStore + .getConnectionsForStep(stepId) + .forEach((connection) => connectionStore.removeConnection(getConnectionId(connection))); + + del(steps.value, stepId.toString()); + del(stepExtraInputs.value, stepId); + } + + return { + steps, + stepMapOver, + stepInputMapOver, + stepIndex, + stepExtraInputs, + $reset, + getStep, + getStepExtraInputs, + getStepIndex, + hasActiveOutputs, + workflowOutputs, + duplicateLabels, + addStep, + insertNewStep, + updateStep, + changeStepMapOver, + resetStepInputMapOver, + changeStepInputMapOver, + addConnection, + removeConnection, + removeStep, + }; +}); -export function stepToConnections(step: Step): Connection[] { +function stepToConnections(step: Step): Connection[] { const connections: Connection[] = []; if (step.input_connections) { Object.entries(step?.input_connections).forEach(([inputName, outputArray]) => { @@ -364,7 +401,7 @@ export function stepToConnections(step: Step): Connection[] { return connections; } -function getStepExtraInputs(step: Step) { +function findStepExtraInputs(step: Step) { const extraInputs: InputTerminalSource[] = []; if (step.when !== undefined) { Object.keys(step.input_connections).forEach((inputName) => {