142 lines
3.7 KiB
TypeScript
142 lines
3.7 KiB
TypeScript
import { createOpaqueId } from "../core/ids";
|
|
import { cloneEditorSelection, type EditorSelection } from "../core/selection";
|
|
import type { ToolMode } from "../core/tool-mode";
|
|
import {
|
|
cloneScenePath,
|
|
cloneScenePathPoint,
|
|
getScenePathPointIndex,
|
|
MIN_SCENE_PATH_POINT_COUNT,
|
|
type ScenePathPoint
|
|
} from "../document/paths";
|
|
|
|
import type { EditorCommand } from "./command";
|
|
|
|
interface DeletePathPointCommandOptions {
|
|
pathId: string;
|
|
pointId: string;
|
|
label?: string;
|
|
}
|
|
|
|
function setSinglePathSelection(pathId: string): EditorSelection {
|
|
return {
|
|
kind: "paths",
|
|
ids: [pathId]
|
|
};
|
|
}
|
|
|
|
function setSelectedPathPointSelection(
|
|
pathId: string,
|
|
pointId: string
|
|
): EditorSelection {
|
|
return {
|
|
kind: "pathPoint",
|
|
pathId,
|
|
pointId
|
|
};
|
|
}
|
|
|
|
export function createDeletePathPointCommand(
|
|
options: DeletePathPointCommandOptions
|
|
): EditorCommand {
|
|
let deletedPoint: ScenePathPoint | null = null;
|
|
let deletedPointIndex: number | null = null;
|
|
let previousSelection: EditorSelection | null = null;
|
|
let previousToolMode: ToolMode | null = null;
|
|
|
|
return {
|
|
id: createOpaqueId("command"),
|
|
label: options.label ?? "Delete path point",
|
|
execute(context) {
|
|
const currentDocument = context.getDocument();
|
|
const path = currentDocument.paths[options.pathId];
|
|
|
|
if (path === undefined) {
|
|
throw new Error(`Path ${options.pathId} does not exist.`);
|
|
}
|
|
|
|
if (path.points.length <= MIN_SCENE_PATH_POINT_COUNT) {
|
|
throw new Error(
|
|
`Paths must keep at least ${MIN_SCENE_PATH_POINT_COUNT} points.`
|
|
);
|
|
}
|
|
|
|
const pointIndex = getScenePathPointIndex(path, options.pointId);
|
|
|
|
if (pointIndex === -1) {
|
|
throw new Error(`Path point ${options.pointId} does not exist on path ${options.pathId}.`);
|
|
}
|
|
|
|
if (deletedPoint === null) {
|
|
deletedPoint = cloneScenePathPoint(path.points[pointIndex]);
|
|
}
|
|
|
|
if (deletedPointIndex === null) {
|
|
deletedPointIndex = pointIndex;
|
|
}
|
|
|
|
if (previousSelection === null) {
|
|
previousSelection = cloneEditorSelection(context.getSelection());
|
|
}
|
|
|
|
if (previousToolMode === null) {
|
|
previousToolMode = context.getToolMode();
|
|
}
|
|
|
|
const nextPoints = path.points.filter((point) => point.id !== options.pointId);
|
|
const fallbackPoint =
|
|
nextPoints[Math.min(pointIndex, nextPoints.length - 1)] ?? null;
|
|
|
|
context.setDocument({
|
|
...currentDocument,
|
|
paths: {
|
|
...currentDocument.paths,
|
|
[path.id]: cloneScenePath({
|
|
...path,
|
|
points: nextPoints
|
|
})
|
|
}
|
|
});
|
|
context.setSelection(
|
|
fallbackPoint === null
|
|
? setSinglePathSelection(options.pathId)
|
|
: setSelectedPathPointSelection(options.pathId, fallbackPoint.id)
|
|
);
|
|
context.setToolMode("select");
|
|
},
|
|
undo(context) {
|
|
if (deletedPoint === null || deletedPointIndex === null) {
|
|
return;
|
|
}
|
|
|
|
const currentDocument = context.getDocument();
|
|
const path = currentDocument.paths[options.pathId];
|
|
|
|
if (path === undefined) {
|
|
throw new Error(`Path ${options.pathId} does not exist.`);
|
|
}
|
|
|
|
const nextPoints = [...path.points];
|
|
nextPoints.splice(deletedPointIndex, 0, cloneScenePathPoint(deletedPoint));
|
|
|
|
context.setDocument({
|
|
...currentDocument,
|
|
paths: {
|
|
...currentDocument.paths,
|
|
[path.id]: cloneScenePath({
|
|
...path,
|
|
points: nextPoints
|
|
})
|
|
}
|
|
});
|
|
|
|
if (previousSelection !== null) {
|
|
context.setSelection(previousSelection);
|
|
}
|
|
|
|
if (previousToolMode !== null) {
|
|
context.setToolMode(previousToolMode);
|
|
}
|
|
}
|
|
};
|
|
}
|