mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-10-24 12:13:33 +08:00
261 lines
8.8 KiB
TypeScript
261 lines
8.8 KiB
TypeScript
import simplify from "simplify-js";
|
|
import { ref, watch } from "vue";
|
|
|
|
import { useWorkflowStores } from "@/composables/workflowStores";
|
|
import type { BaseWorkflowComment } from "@/stores/workflowEditorCommentStore";
|
|
import { assertDefined } from "@/utils/assertions";
|
|
import {
|
|
AxisAlignedBoundingBox,
|
|
vecMax,
|
|
vecMin,
|
|
vecReduceFigures,
|
|
vecSnap,
|
|
vecSubtract,
|
|
type Vector,
|
|
} from "@/utils/geometry";
|
|
import { match } from "@/utils/utils";
|
|
|
|
import { AddCommentAction } from "../Actions/commentActions";
|
|
import { AddToSelectionAction, RemoveFromSelectionAction } from "../Actions/workflowActions";
|
|
|
|
export function useToolLogic() {
|
|
const comment = ref<BaseWorkflowComment | null>(null);
|
|
let start: Vector | null = null;
|
|
|
|
const { toolbarStore, commentStore, stepStore, stateStore, undoRedoStore } = useWorkflowStores();
|
|
const { commentOptions } = toolbarStore;
|
|
|
|
watch(
|
|
() => toolbarStore.currentTool,
|
|
() => {
|
|
if (comment.value?.type === "freehand") {
|
|
finalizeFreehandComment(comment.value);
|
|
} else {
|
|
comment.value = null;
|
|
}
|
|
},
|
|
);
|
|
|
|
watch(
|
|
() => comment.value,
|
|
(newComment, oldComment) => {
|
|
if (newComment === null && oldComment !== null) {
|
|
const comment = commentStore.commentsRecord[oldComment.id];
|
|
assertDefined(comment);
|
|
undoRedoStore.applyAction(new AddCommentAction(commentStore, comment));
|
|
}
|
|
|
|
toolbarStore.resetBoxSelect();
|
|
},
|
|
);
|
|
|
|
toolbarStore.onInputCatcherEvent("pointerdown", ({ position }) => {
|
|
start = position;
|
|
|
|
if (toolbarStore.currentTool === "freehandEraser") {
|
|
return;
|
|
}
|
|
|
|
if (toolbarStore.currentTool === "boxSelect") {
|
|
return;
|
|
}
|
|
|
|
if (comment.value?.type === "freehand" || !comment.value) {
|
|
const baseComment = {
|
|
id: commentStore.highestCommentId + 1,
|
|
position: start,
|
|
size: [0, 0] as [number, number],
|
|
color: commentOptions.color,
|
|
};
|
|
|
|
comment.value = match(toolbarStore.currentTool, {
|
|
textComment: () => ({
|
|
...baseComment,
|
|
type: "text",
|
|
data: {
|
|
size: commentOptions.textSize,
|
|
text: "Enter Text",
|
|
bold: commentOptions.bold || undefined,
|
|
italic: commentOptions.italic || undefined,
|
|
} as BaseWorkflowComment["data"],
|
|
}),
|
|
markdownComment: () => ({
|
|
...baseComment,
|
|
type: "markdown",
|
|
data: {
|
|
text: "*Enter Text*",
|
|
},
|
|
}),
|
|
frameComment: () => ({
|
|
...baseComment,
|
|
type: "frame",
|
|
data: {
|
|
title: "Frame",
|
|
},
|
|
}),
|
|
freehandComment: () => ({
|
|
...baseComment,
|
|
type: "freehand",
|
|
data: {
|
|
thickness: commentOptions.lineThickness,
|
|
line: [position],
|
|
},
|
|
}),
|
|
pointer: () => {
|
|
throw new Error("Tool logic should not be active when pointer tool is selected");
|
|
},
|
|
});
|
|
|
|
commentStore.createComment(comment.value);
|
|
}
|
|
});
|
|
|
|
toolbarStore.onInputCatcherEvent("pointermove", ({ position }) => {
|
|
if (toolbarStore.currentTool === "freehandEraser") {
|
|
return;
|
|
}
|
|
|
|
if (toolbarStore.currentTool === "boxSelect" && start) {
|
|
const [boxPosition, boxSize] = pointsToPositionSize(start, position);
|
|
toolbarStore.boxSelectRect = {
|
|
x: boxPosition[0],
|
|
y: boxPosition[1],
|
|
width: boxSize[0],
|
|
height: boxSize[1],
|
|
};
|
|
return;
|
|
}
|
|
|
|
if (comment.value && start) {
|
|
if (comment.value.type === "freehand") {
|
|
commentStore.addPoint(comment.value.id, position);
|
|
} else {
|
|
positionComment(start, position, comment.value);
|
|
}
|
|
}
|
|
});
|
|
|
|
toolbarStore.onInputCatcherEvent("pointerup", () => {
|
|
if (toolbarStore.currentTool === "freehandEraser") {
|
|
return;
|
|
} else if (comment.value?.type === "freehand") {
|
|
finalizeFreehandComment(comment.value);
|
|
} else if (toolbarStore.currentTool === "boxSelect") {
|
|
finalizeBoxSelect();
|
|
} else if (toolbarStore.currentTool !== "freehandComment") {
|
|
toolbarStore.currentTool = "pointer";
|
|
}
|
|
|
|
comment.value = null;
|
|
start = null;
|
|
});
|
|
|
|
toolbarStore.onInputCatcherEvent("pointerleave", () => {
|
|
if (comment.value?.type === "freehand") {
|
|
finalizeFreehandComment(comment.value);
|
|
comment.value = null;
|
|
}
|
|
});
|
|
|
|
toolbarStore.onInputCatcherEvent("temporarilyDisabled", () => {
|
|
if (comment.value?.type === "freehand") {
|
|
finalizeFreehandComment(comment.value);
|
|
comment.value = null;
|
|
}
|
|
});
|
|
|
|
const finalizeFreehandComment = (comment: BaseWorkflowComment) => {
|
|
const freehandComment = commentStore.commentsRecord[comment.id];
|
|
assertDefined(freehandComment);
|
|
if (freehandComment.type !== "freehand") {
|
|
throw new Error("Comment is not of type freehandComment");
|
|
}
|
|
|
|
// smooth
|
|
const xyLine = freehandComment.data.line.map((point) => ({ x: point[0], y: point[1] }));
|
|
const simpleLine = simplify(xyLine, commentOptions.smoothing).map((point) => [
|
|
point.x,
|
|
point.y,
|
|
]) as Array<Vector>;
|
|
|
|
// normalize
|
|
const normalized = simpleLine.map((p) => vecSubtract(p, freehandComment.position));
|
|
|
|
// reduce significant figures
|
|
const line = normalized.map((p) => vecReduceFigures(p) as Vector);
|
|
|
|
commentStore.changeData(freehandComment.id, { ...freehandComment.data, line });
|
|
commentStore.changePosition(freehandComment.id, vecReduceFigures(freehandComment.position));
|
|
commentStore.changeSize(freehandComment.id, vecReduceFigures(freehandComment.size));
|
|
commentStore.clearJustCreated(freehandComment.id);
|
|
};
|
|
|
|
function finalizeBoxSelect() {
|
|
const boxAABB = new AxisAlignedBoundingBox();
|
|
boxAABB.fitRectangle(toolbarStore.boxSelectRect);
|
|
|
|
const commentsInRect = commentStore.comments.filter((comment) =>
|
|
boxAABB.contains({
|
|
x: comment.position[0],
|
|
y: comment.position[1],
|
|
width: comment.size[0],
|
|
height: comment.size[1],
|
|
}),
|
|
);
|
|
|
|
const stepsInRect = Object.values(stepStore.steps).filter((step) => {
|
|
const rect = stateStore.stepPosition[step.id];
|
|
|
|
if (rect && step.position) {
|
|
const stepRect = {
|
|
x: step.position.left,
|
|
y: step.position.top,
|
|
width: rect.width,
|
|
height: rect.height,
|
|
};
|
|
|
|
return boxAABB.contains(stepRect);
|
|
} else {
|
|
return false;
|
|
}
|
|
});
|
|
|
|
toolbarStore.resetBoxSelect();
|
|
|
|
if (commentsInRect.length > 0 || stepsInRect.length > 0) {
|
|
const changedSelection = {
|
|
comments: commentsInRect.map((comment) => comment.id),
|
|
steps: stepsInRect.map((step) => step.id),
|
|
};
|
|
|
|
if (toolbarStore.boxSelectMode === "add") {
|
|
undoRedoStore.applyAction(new AddToSelectionAction(commentStore, stateStore, changedSelection));
|
|
} else {
|
|
undoRedoStore.applyAction(new RemoveFromSelectionAction(commentStore, stateStore, changedSelection));
|
|
}
|
|
}
|
|
}
|
|
|
|
const positionComment = (pointA: Vector, pointB: Vector, comment: BaseWorkflowComment) => {
|
|
if (toolbarStore.snapActive) {
|
|
pointA = vecSnap(pointA, toolbarStore.snapDistance);
|
|
pointB = vecSnap(pointB, toolbarStore.snapDistance);
|
|
}
|
|
|
|
const [position, size] = pointsToPositionSize(pointA, pointB);
|
|
|
|
commentStore.changePosition(comment.id, position);
|
|
commentStore.changeSize(comment.id, size);
|
|
};
|
|
|
|
function pointsToPositionSize(pointA: Vector, pointB: Vector): [Vector, Vector] {
|
|
const pointMin = vecMin(pointA, pointB);
|
|
const pointMax = vecMax(pointA, pointB);
|
|
|
|
const position = pointMin;
|
|
const size = vecSubtract(pointMax, pointMin);
|
|
|
|
return [position, size];
|
|
}
|
|
}
|