Skip to content

Commit

Permalink
Merge pull request #17615 from ElectronicBlueberry/workflow-editor-st…
Browse files Browse the repository at this point in the history
…ep-multiselect

Workflow Editor Item Selection
  • Loading branch information
mvdbeek authored May 8, 2024
2 parents 890a47b + 406ea9c commit 6b0ff83
Show file tree
Hide file tree
Showing 34 changed files with 1,325 additions and 188 deletions.
1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"linkify-html": "^4.1.1",
"linkifyjs": "^4.1.1",
"lodash.isequal": "^4.5.0",
"lucide-vue": "^0.344.0",
"markdown-it": "^13.0.2",
"markdown-it-regexp": "^0.4.0",
"object-hash": "^3.0.0",
Expand Down
112 changes: 87 additions & 25 deletions client/src/components/Workflow/Editor/Actions/actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
LazyChangeDataAction,
LazyChangePositionAction,
LazyChangeSizeAction,
LazyMoveMultipleAction,
ToggleCommentSelectedAction,
} from "./commentActions";
import { mockComment, mockToolStep, mockWorkflow } from "./mockData";
import {
Expand All @@ -24,9 +24,19 @@ import {
LazySetLabelAction,
LazySetOutputLabelAction,
RemoveStepAction,
ToggleStepSelectedAction,
UpdateStepAction,
} from "./stepActions";
import { CopyIntoWorkflowAction, LazySetValueAction } from "./workflowActions";
import {
AddToSelectionAction,
ClearSelectionAction,
CopyIntoWorkflowAction,
DeleteSelectionAction,
DuplicateSelectionAction,
LazyMoveMultipleAction,
LazySetValueAction,
RemoveFromSelectionAction,
} from "./workflowActions";

const workflowId = "mock-workflow";

Expand Down Expand Up @@ -74,13 +84,19 @@ describe("Workflow Undo Redo Actions", () => {

const { commentStore, undoRedoStore, stepStore, stateStore, connectionStore } = stores;

describe("Comment Actions", () => {
function addComment() {
const comment = mockComment(commentStore.highestCommentId + 1);
commentStore.addComments([comment]);
return comment;
}
function addComment() {
const comment = mockComment(commentStore.highestCommentId + 1);
commentStore.addComments([comment]);
return comment;
}

function addStep() {
const step = mockToolStep(stepStore.getStepIndex + 1);
stepStore.addStep(step);
return step;
}

describe("Comment Actions", () => {
it("AddCommentAction", () => {
expect(commentStore.comments.length).toBe(0);

Expand Down Expand Up @@ -120,16 +136,9 @@ describe("Workflow Undo Redo Actions", () => {
testUndoRedo(action);
});

it("LazyMoveMultipleAction", () => {
addComment();
const action = new LazyMoveMultipleAction(
commentStore,
stores.stepStore,
commentStore.comments,
Object.values(stores.stepStore.steps) as any,
{ x: 0, y: 0 },
{ x: 500, y: 500 }
);
it("ToggleCommentSelectedAction", () => {
const comment = addComment();
const action = new ToggleCommentSelectedAction(commentStore, comment);
testUndoRedo(action);
});
});
Expand All @@ -153,15 +162,61 @@ describe("Workflow Undo Redo Actions", () => {
const action = new CopyIntoWorkflowAction(workflowId, other, { left: 10, top: 20 });
testUndoRedo(action);
});
});

describe("Step Actions", () => {
function addStep() {
const step = mockToolStep(stepStore.getStepIndex + 1);
stepStore.addStep(step);
return step;
it("LazyMoveMultipleAction", () => {
addComment();
const action = new LazyMoveMultipleAction(
commentStore,
stores.stepStore,
commentStore.comments,
Object.values(stores.stepStore.steps) as any,
{ x: 0, y: 0 },
{ x: 500, y: 500 }
);
testUndoRedo(action);
});

function setupSelected() {
addComment();
addComment();
addStep();
addStep();
commentStore.setCommentMultiSelected(0, true);
stateStore.setStepMultiSelected(2, true);
}

it("ClearSelectionAction", () => {
setupSelected();
const action = new ClearSelectionAction(commentStore, stateStore);
testUndoRedo(action);
});

it("AddToSelectionAction", () => {
setupSelected();
const action = new AddToSelectionAction(commentStore, stateStore, { comments: [1], steps: [0] });
testUndoRedo(action);
});

it("RemoveFromSelectionAction", () => {
setupSelected();
const action = new RemoveFromSelectionAction(commentStore, stateStore, { comments: [0], steps: [2] });
testUndoRedo(action);
});

it("DuplicateSelectionAction", () => {
setupSelected();
const action = new DuplicateSelectionAction(workflowId);
testUndoRedo(action);
});

it("DeleteSelectionAction", () => {
setupSelected();
const action = new DeleteSelectionAction(workflowId);
testUndoRedo(action);
});
});

describe("Step Actions", () => {
it("LazyMutateStepAction", () => {
const step = addStep();
const action = new LazyMutateStepAction(stepStore, step.id, "annotation", "", "hello world");
Expand Down Expand Up @@ -228,6 +283,12 @@ describe("Workflow Undo Redo Actions", () => {

testUndoRedo(action);
});

it("ToggleStepSelectedAction", () => {
const step = addStep();
const action = new ToggleStepSelectedAction(stateStore, stepStore, step.id);
testUndoRedo(action);
});
});
});

Expand Down Expand Up @@ -277,6 +338,7 @@ function getWorkflowSnapshot(workflow: Workflow, id = workflowId): object {
"stepPosition",
"stepLoadingState",
"report",
"multiSelectedStepIds",
]),
connectionStoreState: extractKeys(connectionStore, [
"connections",
Expand All @@ -285,7 +347,7 @@ function getWorkflowSnapshot(workflow: Workflow, id = workflowId): object {
"terminalToConnection",
"stepToConnections",
]),
commentStoreState: extractKeys(commentStore, ["commentsRecord"]),
commentStoreState: extractKeys(commentStore, ["commentsRecord", "multiSelectedCommentIds"]),
workflowState: workflow,
});

Expand Down
36 changes: 36 additions & 0 deletions client/src/components/Workflow/Editor/Actions/cloneStep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { Step, WorkflowStepStore } from "@/stores/workflowStepStore";

/**
* Copies a step and increments a trailing number in it's label,
* or adds said trailing number
*
* @param step step to clone
* @param labelSet set containing all labels. Will me mutated to include new label
* @returns cloned step
*/
export function cloneStepWithUniqueLabel(step: Readonly<Step>, labelSet: Set<string>): Step {
const newStep = structuredClone(step) as Step;

if (newStep.label) {
while (labelSet.has(newStep.label)) {
const number = newStep.label.match(/[0-9]+$/)?.[0];

if (number) {
const count = parseInt(number);
newStep.label = newStep.label?.replace(/[0-9]+$/, `${count + 1}`);
} else {
newStep.label += " 2";
}
}

labelSet.add(newStep.label);
}

return newStep;
}

export function getLabelSet(store: WorkflowStepStore): Set<string> {
const steps = Object.values(store.steps);
const labels = steps.flatMap((step) => (step.label ? [step.label] : []));
return new Set(labels);
}
88 changes: 19 additions & 69 deletions client/src/components/Workflow/Editor/Actions/commentActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import type {
WorkflowCommentColor,
WorkflowCommentStore,
} from "@/stores/workflowEditorCommentStore";
import type { Step, WorkflowStepStore } from "@/stores/workflowStepStore";

class CommentAction extends UndoRedoAction {
protected store: WorkflowCommentStore;
Expand Down Expand Up @@ -147,83 +146,34 @@ export class LazyChangeSizeAction extends LazyMutateCommentAction<"size"> {
}
}

type StepWithPosition = Step & { position: NonNullable<Step["position"]> };
export class ToggleCommentSelectedAction extends UndoRedoAction {
store;
commentId;
type;
toggleTo: boolean;

export class LazyMoveMultipleAction extends LazyUndoRedoAction {
private commentStore;
private stepStore;
private comments;
private steps;

private stepStartOffsets = new Map<number, [number, number]>();
private commentStartOffsets = new Map<number, [number, number]>();

private positionFrom;
private positionTo;

get name() {
return "move multiple nodes";
}

constructor(
commentStore: WorkflowCommentStore,
stepStore: WorkflowStepStore,
comments: WorkflowComment[],
steps: StepWithPosition[],
position: { x: number; y: number },
positionTo?: { x: number; y: number }
) {
constructor(store: WorkflowCommentStore, comment: WorkflowComment) {
super();
this.commentStore = commentStore;
this.stepStore = stepStore;
this.comments = [...comments];
this.steps = [...steps];

this.steps.forEach((step) => {
this.stepStartOffsets.set(step.id, [step.position.left - position.x, step.position.top - position.y]);
});

this.comments.forEach((comment) => {
this.commentStartOffsets.set(comment.id, [
comment.position[0] - position.x,
comment.position[1] - position.y,
]);
});

this.positionFrom = { ...position };
this.positionTo = positionTo ? { ...positionTo } : { ...position };
}

changePosition(position: { x: number; y: number }) {
this.setPosition(position);
this.positionTo = { ...position };
this.store = store;
this.commentId = comment.id;
this.type = comment.type;
this.toggleTo = !store.getCommentMultiSelected(this.commentId);
}

private setPosition(position: { x: number; y: number }) {
this.steps.forEach((step) => {
const stepPosition = { left: 0, top: 0 };
const offset = this.stepStartOffsets.get(step.id) ?? [0, 0];
stepPosition.left = position.x + offset[0];
stepPosition.top = position.y + offset[1];
this.stepStore.updateStep({ ...step, position: stepPosition });
});

this.comments.forEach((comment) => {
const offset = this.commentStartOffsets.get(comment.id) ?? [0, 0];
const commentPosition = [position.x + offset[0], position.y + offset[1]] as [number, number];
this.commentStore.changePosition(comment.id, commentPosition);
});
get name() {
if (this.toggleTo === true) {
return `add ${this.type} comment to selection`;
} else {
return `remove ${this.type} comment from selection`;
}
}

queued() {
this.setPosition(this.positionTo);
run() {
this.store.setCommentMultiSelected(this.commentId, this.toggleTo);
}

undo() {
this.setPosition(this.positionFrom);
}

redo() {
this.setPosition(this.positionTo);
this.store.setCommentMultiSelected(this.commentId, !this.toggleTo);
}
}
Loading

0 comments on commit 6b0ff83

Please sign in to comment.