auto-git:
[unlink] playwright.config.js [unlink] src/app/App.js [unlink] src/app/editor-store.js [unlink] src/app/use-editor-store.js [unlink] src/assets/audio-assets.js [unlink] src/assets/gltf-model-import.js [unlink] src/assets/image-assets.js [unlink] src/assets/model-instance-labels.js [unlink] src/assets/model-instance-rendering.js [unlink] src/assets/model-instances.js [unlink] src/assets/project-asset-storage.js [unlink] src/assets/project-assets.js [unlink] src/commands/brush-command-helpers.js [unlink] src/commands/command-history.js [unlink] src/commands/command.js [unlink] src/commands/commit-transform-session-command.js [unlink] src/commands/create-box-brush-command.js [unlink] src/commands/delete-box-brush-command.js [unlink] src/commands/delete-entity-command.js [unlink] src/commands/delete-interaction-link-command.js [unlink] src/commands/delete-model-instance-command.js [unlink] src/commands/duplicate-selection-command.js [unlink] src/commands/import-audio-asset-command.js [unlink] src/commands/import-background-image-asset-command.js [unlink] src/commands/import-model-asset-command.js [unlink] src/commands/move-box-brush-command.js [unlink] src/commands/resize-box-brush-command.js [unlink] src/commands/rotate-box-brush-command.js [unlink] src/commands/set-box-brush-face-material-command.js [unlink] src/commands/set-box-brush-face-uv-state-command.js [unlink] src/commands/set-box-brush-name-command.js [unlink] src/commands/set-box-brush-transform-command.js [unlink] src/commands/set-box-brush-volume-settings-command.js [unlink] src/commands/set-entity-name-command.js [unlink] src/commands/set-model-instance-name-command.js [unlink] src/commands/set-player-start-command.js [unlink] src/commands/set-scene-name-command.js [unlink] src/commands/set-world-settings-command.js [unlink] src/commands/upsert-entity-command.js [unlink] src/commands/upsert-interaction-link-command.js [unlink] src/commands/upsert-model-instance-command.js [unlink] src/core/ids.js [unlink] src/core/selection.js [unlink] src/core/tool-mode.js [unlink] src/core/transform-session.js [unlink] src/core/vector.js [unlink] src/core/whitebox-selection-feedback.js [unlink] src/core/whitebox-selection-mode.js [unlink] src/document/brushes.js [unlink] src/document/migrate-scene-document.js [unlink] src/document/scene-document-validation.js [unlink] src/document/scene-document.js [unlink] src/document/world-settings.js [unlink] src/entities/entity-instances.js [unlink] src/entities/entity-labels.js [unlink] src/geometry/box-brush-components.js [unlink] src/geometry/box-brush-mesh.js [unlink] src/geometry/box-brush.js [unlink] src/geometry/box-face-uvs.js [unlink] src/geometry/grid-snapping.js [unlink] src/geometry/model-instance-collider-debug-mesh.js [unlink] src/geometry/model-instance-collider-generation.js [unlink] src/interactions/interaction-links.js [unlink] src/main.js [unlink] src/materials/starter-material-library.js [unlink] src/materials/starter-material-textures.js [unlink] src/rendering/advanced-rendering.js [unlink] src/rendering/fog-material.js [unlink] src/rendering/planar-reflection.js [unlink] src/rendering/water-material.js [unlink] src/runner-web/RunnerCanvas.js [unlink] src/runtime-three/first-person-navigation-controller.js [unlink] src/runtime-three/navigation-controller.js [unlink] src/runtime-three/orbit-visitor-navigation-controller.js [unlink] src/runtime-three/player-collision.js [unlink] src/runtime-three/rapier-collision-world.js [unlink] src/runtime-three/runtime-audio-system.js [unlink] src/runtime-three/runtime-host.js [unlink] src/runtime-three/runtime-interaction-system.js [unlink] src/runtime-three/runtime-scene-build.js [unlink] src/runtime-three/runtime-scene-validation.js [unlink] src/runtime-three/underwater-fog.js [unlink] src/serialization/local-draft-storage.js [unlink] src/serialization/scene-document-json.js [unlink] src/shared-ui/HierarchicalMenu.js [unlink] src/shared-ui/Panel.js [unlink] src/shared-ui/world-background-style.js [unlink] src/viewport-three/ViewportCanvas.js [unlink] src/viewport-three/ViewportPanel.js [unlink] src/viewport-three/viewport-entity-markers.js [unlink] src/viewport-three/viewport-focus.js [unlink] src/viewport-three/viewport-host.js [unlink] src/viewport-three/viewport-layout.js [unlink] src/viewport-three/viewport-transient-state.js [unlink] src/viewport-three/viewport-view-modes.js [unlink] vite.config.js [unlink] vitest.config.js
This commit is contained in:
@@ -1,82 +0,0 @@
|
||||
import { cloneEditorSelection } from "../core/selection";
|
||||
import { cloneFaceUvState } from "../document/brushes";
|
||||
export function getBoxBrushOrThrow(document, brushId) {
|
||||
const brush = document.brushes[brushId];
|
||||
if (brush === undefined) {
|
||||
throw new Error(`Box brush ${brushId} does not exist.`);
|
||||
}
|
||||
if (brush.kind !== "box") {
|
||||
throw new Error(`Brush ${brushId} is not a supported box brush.`);
|
||||
}
|
||||
return brush;
|
||||
}
|
||||
export function setSingleBrushSelection(brushId) {
|
||||
return {
|
||||
kind: "brushes",
|
||||
ids: [brushId]
|
||||
};
|
||||
}
|
||||
export function setSingleBrushFaceSelection(brushId, faceId) {
|
||||
return {
|
||||
kind: "brushFace",
|
||||
brushId,
|
||||
faceId
|
||||
};
|
||||
}
|
||||
export function setSingleBrushEdgeSelection(brushId, edgeId) {
|
||||
return {
|
||||
kind: "brushEdge",
|
||||
brushId,
|
||||
edgeId
|
||||
};
|
||||
}
|
||||
export function setSingleBrushVertexSelection(brushId, vertexId) {
|
||||
return {
|
||||
kind: "brushVertex",
|
||||
brushId,
|
||||
vertexId
|
||||
};
|
||||
}
|
||||
export function cloneSelectionForCommand(selection) {
|
||||
return cloneEditorSelection(selection);
|
||||
}
|
||||
export function replaceBrush(document, brush) {
|
||||
return {
|
||||
...document,
|
||||
brushes: {
|
||||
...document.brushes,
|
||||
[brush.id]: brush
|
||||
}
|
||||
};
|
||||
}
|
||||
export function removeBrush(document, brushId) {
|
||||
const remainingBrushes = {
|
||||
...document.brushes
|
||||
};
|
||||
delete remainingBrushes[brushId];
|
||||
return {
|
||||
...document,
|
||||
brushes: remainingBrushes
|
||||
};
|
||||
}
|
||||
export function getBoxBrushFaceOrThrow(document, brushId, faceId) {
|
||||
const brush = getBoxBrushOrThrow(document, brushId);
|
||||
const face = brush.faces[faceId];
|
||||
if (face === undefined) {
|
||||
throw new Error(`Box brush ${brushId} does not contain face ${faceId}.`);
|
||||
}
|
||||
return face;
|
||||
}
|
||||
export function replaceBoxBrushFace(document, brushId, faceId, face) {
|
||||
const brush = getBoxBrushOrThrow(document, brushId);
|
||||
return replaceBrush(document, {
|
||||
...brush,
|
||||
faces: {
|
||||
...brush.faces,
|
||||
[faceId]: {
|
||||
materialId: face.materialId,
|
||||
uv: cloneFaceUvState(face.uv)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
export class CommandHistory {
|
||||
undoStack = [];
|
||||
redoStack = [];
|
||||
execute(command, context) {
|
||||
command.execute(context);
|
||||
this.undoStack.push(command);
|
||||
this.redoStack.length = 0;
|
||||
}
|
||||
undo(context) {
|
||||
const command = this.undoStack.pop();
|
||||
if (command === undefined) {
|
||||
return null;
|
||||
}
|
||||
command.undo(context);
|
||||
this.redoStack.push(command);
|
||||
return command;
|
||||
}
|
||||
redo(context) {
|
||||
const command = this.redoStack.pop();
|
||||
if (command === undefined) {
|
||||
return null;
|
||||
}
|
||||
command.execute(context);
|
||||
this.undoStack.push(command);
|
||||
return command;
|
||||
}
|
||||
clear() {
|
||||
this.undoStack.length = 0;
|
||||
this.redoStack.length = 0;
|
||||
}
|
||||
canUndo() {
|
||||
return this.undoStack.length > 0;
|
||||
}
|
||||
canRedo() {
|
||||
return this.redoStack.length > 0;
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export {};
|
||||
@@ -1,200 +0,0 @@
|
||||
import { createMoveBoxBrushCommand } from "./move-box-brush-command";
|
||||
import { createResizeBoxBrushCommand } from "./resize-box-brush-command";
|
||||
import { createRotateBoxBrushCommand } from "./rotate-box-brush-command";
|
||||
import { createSetBoxBrushTransformCommand } from "./set-box-brush-transform-command";
|
||||
import { createUpsertEntityCommand } from "./upsert-entity-command";
|
||||
import { createUpsertModelInstanceCommand } from "./upsert-model-instance-command";
|
||||
import { createModelInstance } from "../assets/model-instances";
|
||||
import { createInteractableEntity, createPlayerStartEntity, createPointLightEntity, createSoundEmitterEntity, createSpotLightEntity, createTeleportTargetEntity, createTriggerVolumeEntity } from "../entities/entity-instances";
|
||||
import { getTransformOperationLabel } from "../core/transform-session";
|
||||
function createTransformCommandLabel(session) {
|
||||
return `${getTransformOperationLabel(session.operation)} ${session.target.kind === "brush"
|
||||
? "whitebox box"
|
||||
: session.target.kind === "brushFace"
|
||||
? "whitebox face"
|
||||
: session.target.kind === "brushEdge"
|
||||
? "whitebox edge"
|
||||
: session.target.kind === "brushVertex"
|
||||
? "whitebox vertex"
|
||||
: session.target.kind === "entity"
|
||||
? session.target.entityKind === "playerStart"
|
||||
? "player start"
|
||||
: session.target.entityKind === "pointLight"
|
||||
? "point light"
|
||||
: session.target.entityKind === "spotLight"
|
||||
? "spot light"
|
||||
: session.target.entityKind === "soundEmitter"
|
||||
? "sound emitter"
|
||||
: session.target.entityKind === "triggerVolume"
|
||||
? "trigger volume"
|
||||
: session.target.entityKind === "teleportTarget"
|
||||
? "teleport target"
|
||||
: "interactable"
|
||||
: "model instance"}`;
|
||||
}
|
||||
export function createCommitTransformSessionCommand(document, session) {
|
||||
switch (session.target.kind) {
|
||||
case "brush":
|
||||
if (session.preview.kind !== "brush") {
|
||||
throw new Error("Brush transform preview is invalid.");
|
||||
}
|
||||
switch (session.operation) {
|
||||
case "translate":
|
||||
return createMoveBoxBrushCommand({
|
||||
brushId: session.target.brushId,
|
||||
center: session.preview.center,
|
||||
snapToGrid: false,
|
||||
label: createTransformCommandLabel(session)
|
||||
});
|
||||
case "rotate":
|
||||
return createRotateBoxBrushCommand({
|
||||
brushId: session.target.brushId,
|
||||
rotationDegrees: session.preview.rotationDegrees,
|
||||
label: createTransformCommandLabel(session)
|
||||
});
|
||||
case "scale":
|
||||
return createResizeBoxBrushCommand({
|
||||
brushId: session.target.brushId,
|
||||
size: session.preview.size,
|
||||
snapToGrid: false,
|
||||
label: createTransformCommandLabel(session)
|
||||
});
|
||||
}
|
||||
case "brushFace":
|
||||
if (session.preview.kind !== "brush") {
|
||||
throw new Error("Whitebox face transform preview is invalid.");
|
||||
}
|
||||
return createSetBoxBrushTransformCommand({
|
||||
selection: {
|
||||
kind: "brushFace",
|
||||
brushId: session.target.brushId,
|
||||
faceId: session.target.faceId
|
||||
},
|
||||
center: session.preview.center,
|
||||
rotationDegrees: session.preview.rotationDegrees,
|
||||
size: session.preview.size,
|
||||
geometry: session.preview.geometry,
|
||||
label: createTransformCommandLabel(session)
|
||||
});
|
||||
case "brushEdge":
|
||||
if (session.preview.kind !== "brush") {
|
||||
throw new Error("Whitebox edge transform preview is invalid.");
|
||||
}
|
||||
return createSetBoxBrushTransformCommand({
|
||||
selection: {
|
||||
kind: "brushEdge",
|
||||
brushId: session.target.brushId,
|
||||
edgeId: session.target.edgeId
|
||||
},
|
||||
center: session.preview.center,
|
||||
rotationDegrees: session.preview.rotationDegrees,
|
||||
size: session.preview.size,
|
||||
geometry: session.preview.geometry,
|
||||
label: createTransformCommandLabel(session)
|
||||
});
|
||||
case "brushVertex":
|
||||
if (session.preview.kind !== "brush") {
|
||||
throw new Error("Whitebox vertex transform preview is invalid.");
|
||||
}
|
||||
return createSetBoxBrushTransformCommand({
|
||||
selection: {
|
||||
kind: "brushVertex",
|
||||
brushId: session.target.brushId,
|
||||
vertexId: session.target.vertexId
|
||||
},
|
||||
center: session.preview.center,
|
||||
rotationDegrees: session.preview.rotationDegrees,
|
||||
size: session.preview.size,
|
||||
geometry: session.preview.geometry,
|
||||
label: createTransformCommandLabel(session)
|
||||
});
|
||||
case "modelInstance": {
|
||||
if (session.preview.kind !== "modelInstance") {
|
||||
throw new Error("Model instance transform preview is invalid.");
|
||||
}
|
||||
const modelInstance = document.modelInstances[session.target.modelInstanceId];
|
||||
if (modelInstance === undefined) {
|
||||
throw new Error(`Model instance ${session.target.modelInstanceId} does not exist.`);
|
||||
}
|
||||
return createUpsertModelInstanceCommand({
|
||||
modelInstance: createModelInstance({
|
||||
...modelInstance,
|
||||
position: session.preview.position,
|
||||
rotationDegrees: session.preview.rotationDegrees,
|
||||
scale: session.preview.scale
|
||||
}),
|
||||
label: createTransformCommandLabel(session)
|
||||
});
|
||||
}
|
||||
case "entity": {
|
||||
if (session.preview.kind !== "entity") {
|
||||
throw new Error("Entity transform preview is invalid.");
|
||||
}
|
||||
const entity = document.entities[session.target.entityId];
|
||||
if (entity === undefined) {
|
||||
throw new Error(`Entity ${session.target.entityId} does not exist.`);
|
||||
}
|
||||
switch (entity.kind) {
|
||||
case "pointLight":
|
||||
return createUpsertEntityCommand({
|
||||
entity: createPointLightEntity({
|
||||
...entity,
|
||||
position: session.preview.position
|
||||
}),
|
||||
label: createTransformCommandLabel(session)
|
||||
});
|
||||
case "spotLight":
|
||||
return createUpsertEntityCommand({
|
||||
entity: createSpotLightEntity({
|
||||
...entity,
|
||||
position: session.preview.position,
|
||||
direction: session.preview.rotation.kind === "direction" ? session.preview.rotation.direction : entity.direction
|
||||
}),
|
||||
label: createTransformCommandLabel(session)
|
||||
});
|
||||
case "playerStart":
|
||||
return createUpsertEntityCommand({
|
||||
entity: createPlayerStartEntity({
|
||||
...entity,
|
||||
position: session.preview.position,
|
||||
yawDegrees: session.preview.rotation.kind === "yaw" ? session.preview.rotation.yawDegrees : entity.yawDegrees
|
||||
}),
|
||||
label: createTransformCommandLabel(session)
|
||||
});
|
||||
case "soundEmitter":
|
||||
return createUpsertEntityCommand({
|
||||
entity: createSoundEmitterEntity({
|
||||
...entity,
|
||||
position: session.preview.position
|
||||
}),
|
||||
label: createTransformCommandLabel(session)
|
||||
});
|
||||
case "triggerVolume":
|
||||
return createUpsertEntityCommand({
|
||||
entity: createTriggerVolumeEntity({
|
||||
...entity,
|
||||
position: session.preview.position
|
||||
}),
|
||||
label: createTransformCommandLabel(session)
|
||||
});
|
||||
case "teleportTarget":
|
||||
return createUpsertEntityCommand({
|
||||
entity: createTeleportTargetEntity({
|
||||
...entity,
|
||||
position: session.preview.position,
|
||||
yawDegrees: session.preview.rotation.kind === "yaw" ? session.preview.rotation.yawDegrees : entity.yawDegrees
|
||||
}),
|
||||
label: createTransformCommandLabel(session)
|
||||
});
|
||||
case "interactable":
|
||||
return createUpsertEntityCommand({
|
||||
entity: createInteractableEntity({
|
||||
...entity,
|
||||
position: session.preview.position
|
||||
}),
|
||||
label: createTransformCommandLabel(session)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import { createBoxBrush, DEFAULT_BOX_BRUSH_CENTER, DEFAULT_BOX_BRUSH_SIZE } from "../document/brushes";
|
||||
import { DEFAULT_GRID_SIZE, snapPositiveSizeToGrid, snapVec3ToGrid } from "../geometry/grid-snapping";
|
||||
import { createOpaqueId } from "../core/ids";
|
||||
import { cloneSelectionForCommand, removeBrush, setSingleBrushSelection } from "./brush-command-helpers";
|
||||
export function createCreateBoxBrushCommand(options = {}) {
|
||||
const snapToGrid = options.snapToGrid ?? true;
|
||||
const brush = createBoxBrush({
|
||||
center: snapToGrid === false
|
||||
? options.center ?? DEFAULT_BOX_BRUSH_CENTER
|
||||
: snapVec3ToGrid(options.center ?? DEFAULT_BOX_BRUSH_CENTER, options.gridSize ?? DEFAULT_GRID_SIZE),
|
||||
size: snapToGrid === false
|
||||
? options.size ?? DEFAULT_BOX_BRUSH_SIZE
|
||||
: snapPositiveSizeToGrid(options.size ?? DEFAULT_BOX_BRUSH_SIZE, options.gridSize ?? DEFAULT_GRID_SIZE)
|
||||
});
|
||||
let previousSelection = null;
|
||||
let previousToolMode = null;
|
||||
return {
|
||||
id: createOpaqueId("command"),
|
||||
label: "Create box brush",
|
||||
execute(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
if (currentDocument.brushes[brush.id] !== undefined) {
|
||||
throw new Error(`Box brush ${brush.id} already exists.`);
|
||||
}
|
||||
if (previousSelection === null) {
|
||||
previousSelection = cloneSelectionForCommand(context.getSelection());
|
||||
}
|
||||
if (previousToolMode === null) {
|
||||
previousToolMode = context.getToolMode();
|
||||
}
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
brushes: {
|
||||
...currentDocument.brushes,
|
||||
[brush.id]: brush
|
||||
}
|
||||
});
|
||||
context.setSelection(setSingleBrushSelection(brush.id));
|
||||
context.setToolMode("select");
|
||||
},
|
||||
undo(context) {
|
||||
context.setDocument(removeBrush(context.getDocument(), brush.id));
|
||||
if (previousSelection !== null) {
|
||||
context.setSelection(previousSelection);
|
||||
}
|
||||
if (previousToolMode !== null) {
|
||||
context.setToolMode(previousToolMode);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
import { createOpaqueId } from "../core/ids";
|
||||
import { cloneBoxBrush } from "../document/brushes";
|
||||
import { cloneSelectionForCommand, removeBrush } from "./brush-command-helpers";
|
||||
function selectionIncludesBrush(selection, brushId) {
|
||||
return ((selection.kind === "brushes" && selection.ids.includes(brushId)) ||
|
||||
((selection.kind === "brushFace" || selection.kind === "brushEdge" || selection.kind === "brushVertex") &&
|
||||
selection.brushId === brushId));
|
||||
}
|
||||
export function createDeleteBoxBrushCommand(brushId) {
|
||||
let previousBrush = null;
|
||||
let previousSelection = null;
|
||||
let previousToolMode = null;
|
||||
return {
|
||||
id: createOpaqueId("command"),
|
||||
label: "Delete box brush",
|
||||
execute(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
const currentBrush = currentDocument.brushes[brushId];
|
||||
if (currentBrush === undefined) {
|
||||
throw new Error(`Box brush ${brushId} does not exist.`);
|
||||
}
|
||||
if (previousBrush === null) {
|
||||
previousBrush = cloneBoxBrush(currentBrush);
|
||||
}
|
||||
if (previousSelection === null) {
|
||||
previousSelection = cloneSelectionForCommand(context.getSelection());
|
||||
}
|
||||
if (previousToolMode === null) {
|
||||
previousToolMode = context.getToolMode();
|
||||
}
|
||||
context.setDocument(removeBrush(currentDocument, brushId));
|
||||
if (selectionIncludesBrush(context.getSelection(), brushId)) {
|
||||
context.setSelection({
|
||||
kind: "none"
|
||||
});
|
||||
}
|
||||
context.setToolMode("select");
|
||||
},
|
||||
undo(context) {
|
||||
if (previousBrush === null) {
|
||||
return;
|
||||
}
|
||||
const currentDocument = context.getDocument();
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
brushes: {
|
||||
...currentDocument.brushes,
|
||||
[previousBrush.id]: cloneBoxBrush(previousBrush)
|
||||
}
|
||||
});
|
||||
if (previousSelection !== null) {
|
||||
context.setSelection(previousSelection);
|
||||
}
|
||||
if (previousToolMode !== null) {
|
||||
context.setToolMode(previousToolMode);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
import { createOpaqueId } from "../core/ids";
|
||||
import { cloneEditorSelection } from "../core/selection";
|
||||
import { cloneEntityInstance } from "../entities/entity-instances";
|
||||
function selectionIncludesEntity(selection, entityId) {
|
||||
return selection.kind === "entities" && selection.ids.includes(entityId);
|
||||
}
|
||||
export function createDeleteEntityCommand(entityId) {
|
||||
let previousEntity = null;
|
||||
let previousSelection = null;
|
||||
let previousToolMode = null;
|
||||
return {
|
||||
id: createOpaqueId("command"),
|
||||
label: "Delete entity",
|
||||
execute(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
const currentEntity = currentDocument.entities[entityId];
|
||||
if (currentEntity === undefined) {
|
||||
throw new Error(`Entity ${entityId} does not exist.`);
|
||||
}
|
||||
if (previousEntity === null) {
|
||||
previousEntity = cloneEntityInstance(currentEntity);
|
||||
}
|
||||
if (previousSelection === null) {
|
||||
previousSelection = cloneEditorSelection(context.getSelection());
|
||||
}
|
||||
if (previousToolMode === null) {
|
||||
previousToolMode = context.getToolMode();
|
||||
}
|
||||
const nextEntities = {
|
||||
...currentDocument.entities
|
||||
};
|
||||
delete nextEntities[entityId];
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
entities: nextEntities
|
||||
});
|
||||
if (selectionIncludesEntity(context.getSelection(), entityId)) {
|
||||
context.setSelection({
|
||||
kind: "none"
|
||||
});
|
||||
}
|
||||
context.setToolMode("select");
|
||||
},
|
||||
undo(context) {
|
||||
if (previousEntity === null) {
|
||||
return;
|
||||
}
|
||||
const currentDocument = context.getDocument();
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
entities: {
|
||||
...currentDocument.entities,
|
||||
[previousEntity.id]: cloneEntityInstance(previousEntity)
|
||||
}
|
||||
});
|
||||
if (previousSelection !== null) {
|
||||
context.setSelection(previousSelection);
|
||||
}
|
||||
if (previousToolMode !== null) {
|
||||
context.setToolMode(previousToolMode);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import { createOpaqueId } from "../core/ids";
|
||||
import { cloneInteractionLink } from "../interactions/interaction-links";
|
||||
export function createDeleteInteractionLinkCommand(linkId) {
|
||||
let previousLink = null;
|
||||
return {
|
||||
id: createOpaqueId("command"),
|
||||
label: "Delete interaction link",
|
||||
execute(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
const currentLink = currentDocument.interactionLinks[linkId];
|
||||
if (currentLink === undefined) {
|
||||
throw new Error(`Interaction link ${linkId} does not exist.`);
|
||||
}
|
||||
if (previousLink === null) {
|
||||
previousLink = cloneInteractionLink(currentLink);
|
||||
}
|
||||
const nextInteractionLinks = {
|
||||
...currentDocument.interactionLinks
|
||||
};
|
||||
delete nextInteractionLinks[linkId];
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
interactionLinks: nextInteractionLinks
|
||||
});
|
||||
},
|
||||
undo(context) {
|
||||
if (previousLink === null) {
|
||||
return;
|
||||
}
|
||||
const currentDocument = context.getDocument();
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
interactionLinks: {
|
||||
...currentDocument.interactionLinks,
|
||||
[previousLink.id]: cloneInteractionLink(previousLink)
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
import { createOpaqueId } from "../core/ids";
|
||||
import { cloneEditorSelection } from "../core/selection";
|
||||
import { cloneModelInstance } from "../assets/model-instances";
|
||||
function selectionIncludesModelInstance(selection, modelInstanceId) {
|
||||
return selection.kind === "modelInstances" && selection.ids.includes(modelInstanceId);
|
||||
}
|
||||
export function createDeleteModelInstanceCommand(modelInstanceId) {
|
||||
let previousModelInstance = null;
|
||||
let previousSelection = null;
|
||||
let previousToolMode = null;
|
||||
return {
|
||||
id: createOpaqueId("command"),
|
||||
label: "Delete model instance",
|
||||
execute(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
const currentModelInstance = currentDocument.modelInstances[modelInstanceId];
|
||||
if (currentModelInstance === undefined) {
|
||||
throw new Error(`Model instance ${modelInstanceId} does not exist.`);
|
||||
}
|
||||
if (previousModelInstance === null) {
|
||||
previousModelInstance = cloneModelInstance(currentModelInstance);
|
||||
}
|
||||
if (previousSelection === null) {
|
||||
previousSelection = cloneEditorSelection(context.getSelection());
|
||||
}
|
||||
if (previousToolMode === null) {
|
||||
previousToolMode = context.getToolMode();
|
||||
}
|
||||
const nextModelInstances = {
|
||||
...currentDocument.modelInstances
|
||||
};
|
||||
delete nextModelInstances[modelInstanceId];
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
modelInstances: nextModelInstances
|
||||
});
|
||||
if (selectionIncludesModelInstance(context.getSelection(), modelInstanceId)) {
|
||||
context.setSelection({
|
||||
kind: "none"
|
||||
});
|
||||
}
|
||||
context.setToolMode("select");
|
||||
},
|
||||
undo(context) {
|
||||
if (previousModelInstance === null) {
|
||||
return;
|
||||
}
|
||||
const currentDocument = context.getDocument();
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
modelInstances: {
|
||||
...currentDocument.modelInstances,
|
||||
[previousModelInstance.id]: cloneModelInstance(previousModelInstance)
|
||||
}
|
||||
});
|
||||
if (previousSelection !== null) {
|
||||
context.setSelection(previousSelection);
|
||||
}
|
||||
if (previousToolMode !== null) {
|
||||
context.setToolMode(previousToolMode);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,237 +0,0 @@
|
||||
import { cloneModelInstance } from "../assets/model-instances";
|
||||
import { createOpaqueId } from "../core/ids";
|
||||
import { cloneEditorSelection } from "../core/selection";
|
||||
import { cloneBoxBrush } from "../document/brushes";
|
||||
import { cloneEntityInstance } from "../entities/entity-instances";
|
||||
|
||||
function duplicateBrush(brush) {
|
||||
const duplicatedBrush = cloneBoxBrush(brush);
|
||||
duplicatedBrush.id = createOpaqueId("brush");
|
||||
return duplicatedBrush;
|
||||
}
|
||||
|
||||
function duplicateEntity(entity) {
|
||||
const duplicatedEntity = cloneEntityInstance(entity);
|
||||
duplicatedEntity.id = createOpaqueId(`entity-${duplicatedEntity.kind}`);
|
||||
return duplicatedEntity;
|
||||
}
|
||||
|
||||
function duplicateModelInstance(modelInstance) {
|
||||
const duplicatedModelInstance = cloneModelInstance(modelInstance);
|
||||
duplicatedModelInstance.id = createOpaqueId("model-instance");
|
||||
return duplicatedModelInstance;
|
||||
}
|
||||
|
||||
function resolveDuplicatableBrushIds(selection) {
|
||||
switch (selection.kind) {
|
||||
case "brushes":
|
||||
return selection.ids;
|
||||
case "brushFace":
|
||||
case "brushEdge":
|
||||
case "brushVertex":
|
||||
return [selection.brushId];
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function createDuplicateSelectionResult(currentDocument, selection) {
|
||||
const duplicatableBrushIds = resolveDuplicatableBrushIds(selection);
|
||||
|
||||
if (duplicatableBrushIds !== null) {
|
||||
if (duplicatableBrushIds.length === 0) {
|
||||
throw new Error("Select at least one whitebox solid to duplicate.");
|
||||
}
|
||||
|
||||
const duplicatedBrushes = duplicatableBrushIds.map((brushId) => {
|
||||
const sourceBrush = currentDocument.brushes[brushId];
|
||||
|
||||
if (sourceBrush === undefined) {
|
||||
throw new Error(`Box brush ${brushId} does not exist.`);
|
||||
}
|
||||
|
||||
if (sourceBrush.kind !== "box") {
|
||||
throw new Error(`Brush ${brushId} is not a supported box brush.`);
|
||||
}
|
||||
|
||||
return duplicateBrush(sourceBrush);
|
||||
});
|
||||
|
||||
return {
|
||||
selection: {
|
||||
kind: "brushes",
|
||||
ids: duplicatedBrushes.map((brush) => brush.id)
|
||||
},
|
||||
brushes: duplicatedBrushes,
|
||||
entities: null,
|
||||
modelInstances: null
|
||||
};
|
||||
}
|
||||
|
||||
if (selection.kind === "entities") {
|
||||
if (selection.ids.length === 0) {
|
||||
throw new Error("Select at least one entity to duplicate.");
|
||||
}
|
||||
|
||||
const duplicatedEntities = selection.ids.map((entityId) => {
|
||||
const sourceEntity = currentDocument.entities[entityId];
|
||||
|
||||
if (sourceEntity === undefined) {
|
||||
throw new Error(`Entity ${entityId} does not exist.`);
|
||||
}
|
||||
|
||||
return duplicateEntity(sourceEntity);
|
||||
});
|
||||
|
||||
return {
|
||||
selection: {
|
||||
kind: "entities",
|
||||
ids: duplicatedEntities.map((entity) => entity.id)
|
||||
},
|
||||
brushes: null,
|
||||
entities: duplicatedEntities,
|
||||
modelInstances: null
|
||||
};
|
||||
}
|
||||
|
||||
if (selection.kind === "modelInstances") {
|
||||
if (selection.ids.length === 0) {
|
||||
throw new Error("Select at least one model instance to duplicate.");
|
||||
}
|
||||
|
||||
const duplicatedModelInstances = selection.ids.map((modelInstanceId) => {
|
||||
const sourceModelInstance = currentDocument.modelInstances[modelInstanceId];
|
||||
|
||||
if (sourceModelInstance === undefined) {
|
||||
throw new Error(`Model instance ${modelInstanceId} does not exist.`);
|
||||
}
|
||||
|
||||
return duplicateModelInstance(sourceModelInstance);
|
||||
});
|
||||
|
||||
return {
|
||||
selection: {
|
||||
kind: "modelInstances",
|
||||
ids: duplicatedModelInstances.map((modelInstance) => modelInstance.id)
|
||||
},
|
||||
brushes: null,
|
||||
entities: null,
|
||||
modelInstances: duplicatedModelInstances
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error("Selection must contain whitebox solids, entities, or model instances to duplicate.");
|
||||
}
|
||||
|
||||
export function createDuplicateSelectionCommand() {
|
||||
let previousSelection = null;
|
||||
let previousToolMode = null;
|
||||
let duplicateSelectionResult = null;
|
||||
|
||||
return {
|
||||
id: createOpaqueId("command"),
|
||||
label: "Duplicate selection",
|
||||
execute(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
|
||||
if (previousSelection === null) {
|
||||
previousSelection = cloneEditorSelection(context.getSelection());
|
||||
}
|
||||
|
||||
if (previousToolMode === null) {
|
||||
previousToolMode = context.getToolMode();
|
||||
}
|
||||
|
||||
if (duplicateSelectionResult === null) {
|
||||
duplicateSelectionResult = createDuplicateSelectionResult(currentDocument, context.getSelection());
|
||||
}
|
||||
|
||||
if (duplicateSelectionResult.brushes !== null) {
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
brushes: {
|
||||
...currentDocument.brushes,
|
||||
...Object.fromEntries(duplicateSelectionResult.brushes.map((brush) => [brush.id, cloneBoxBrush(brush)]))
|
||||
}
|
||||
});
|
||||
} else if (duplicateSelectionResult.entities !== null) {
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
entities: {
|
||||
...currentDocument.entities,
|
||||
...Object.fromEntries(duplicateSelectionResult.entities.map((entity) => [entity.id, cloneEntityInstance(entity)]))
|
||||
}
|
||||
});
|
||||
} else if (duplicateSelectionResult.modelInstances !== null) {
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
modelInstances: {
|
||||
...currentDocument.modelInstances,
|
||||
...Object.fromEntries(
|
||||
duplicateSelectionResult.modelInstances.map((modelInstance) => [modelInstance.id, cloneModelInstance(modelInstance)])
|
||||
)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
context.setSelection(cloneEditorSelection(duplicateSelectionResult.selection));
|
||||
context.setToolMode("select");
|
||||
},
|
||||
undo(context) {
|
||||
if (duplicateSelectionResult === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentDocument = context.getDocument();
|
||||
|
||||
if (duplicateSelectionResult.brushes !== null) {
|
||||
const nextBrushes = {
|
||||
...currentDocument.brushes
|
||||
};
|
||||
|
||||
for (const duplicatedBrush of duplicateSelectionResult.brushes) {
|
||||
delete nextBrushes[duplicatedBrush.id];
|
||||
}
|
||||
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
brushes: nextBrushes
|
||||
});
|
||||
} else if (duplicateSelectionResult.entities !== null) {
|
||||
const nextEntities = {
|
||||
...currentDocument.entities
|
||||
};
|
||||
|
||||
for (const duplicatedEntity of duplicateSelectionResult.entities) {
|
||||
delete nextEntities[duplicatedEntity.id];
|
||||
}
|
||||
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
entities: nextEntities
|
||||
});
|
||||
} else if (duplicateSelectionResult.modelInstances !== null) {
|
||||
const nextModelInstances = {
|
||||
...currentDocument.modelInstances
|
||||
};
|
||||
|
||||
for (const duplicatedModelInstance of duplicateSelectionResult.modelInstances) {
|
||||
delete nextModelInstances[duplicatedModelInstance.id];
|
||||
}
|
||||
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
modelInstances: nextModelInstances
|
||||
});
|
||||
}
|
||||
|
||||
if (previousSelection !== null) {
|
||||
context.setSelection(previousSelection);
|
||||
}
|
||||
|
||||
if (previousToolMode !== null) {
|
||||
context.setToolMode(previousToolMode);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import { createOpaqueId } from "../core/ids";
|
||||
import { cloneProjectAssetRecord } from "../assets/project-assets";
|
||||
export function createImportAudioAssetCommand(options) {
|
||||
const nextAsset = cloneProjectAssetRecord(options.asset);
|
||||
return {
|
||||
id: createOpaqueId("command"),
|
||||
label: options.label ?? `Import ${nextAsset.sourceName}`,
|
||||
execute(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
if (currentDocument.assets[nextAsset.id] !== undefined) {
|
||||
throw new Error(`Asset ${nextAsset.id} already exists.`);
|
||||
}
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
assets: {
|
||||
...currentDocument.assets,
|
||||
[nextAsset.id]: cloneProjectAssetRecord(nextAsset)
|
||||
}
|
||||
});
|
||||
},
|
||||
undo(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
const nextAssets = {
|
||||
...currentDocument.assets
|
||||
};
|
||||
delete nextAssets[nextAsset.id];
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
assets: nextAssets
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import { createOpaqueId } from "../core/ids";
|
||||
import { cloneWorldSettings } from "../document/world-settings";
|
||||
import { cloneProjectAssetRecord } from "../assets/project-assets";
|
||||
export function createImportBackgroundImageAssetCommand(options) {
|
||||
const nextAsset = cloneProjectAssetRecord(options.asset);
|
||||
const nextWorld = cloneWorldSettings(options.world);
|
||||
let previousWorld = null;
|
||||
return {
|
||||
id: createOpaqueId("command"),
|
||||
label: options.label ?? `Import ${nextAsset.sourceName} as background`,
|
||||
execute(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
if (currentDocument.assets[nextAsset.id] !== undefined) {
|
||||
throw new Error(`Asset ${nextAsset.id} already exists.`);
|
||||
}
|
||||
if (previousWorld === null) {
|
||||
previousWorld = cloneWorldSettings(currentDocument.world);
|
||||
}
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
assets: {
|
||||
...currentDocument.assets,
|
||||
[nextAsset.id]: cloneProjectAssetRecord(nextAsset)
|
||||
},
|
||||
world: cloneWorldSettings(nextWorld)
|
||||
});
|
||||
},
|
||||
undo(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
const nextAssets = {
|
||||
...currentDocument.assets
|
||||
};
|
||||
delete nextAssets[nextAsset.id];
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
assets: nextAssets,
|
||||
world: previousWorld === null ? cloneWorldSettings(currentDocument.world) : cloneWorldSettings(previousWorld)
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
import { createOpaqueId } from "../core/ids";
|
||||
import { cloneEditorSelection } from "../core/selection";
|
||||
import { cloneModelInstance } from "../assets/model-instances";
|
||||
import { cloneProjectAssetRecord } from "../assets/project-assets";
|
||||
function setSingleModelInstanceSelection(modelInstanceId) {
|
||||
return {
|
||||
kind: "modelInstances",
|
||||
ids: [modelInstanceId]
|
||||
};
|
||||
}
|
||||
export function createImportModelAssetCommand(options) {
|
||||
const nextAsset = cloneProjectAssetRecord(options.asset);
|
||||
const nextModelInstance = cloneModelInstance(options.modelInstance);
|
||||
let previousSelection = null;
|
||||
let previousToolMode = null;
|
||||
return {
|
||||
id: createOpaqueId("command"),
|
||||
label: options.label ?? `Import ${nextAsset.sourceName}`,
|
||||
execute(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
if (currentDocument.assets[nextAsset.id] !== undefined) {
|
||||
throw new Error(`Asset ${nextAsset.id} already exists.`);
|
||||
}
|
||||
if (currentDocument.modelInstances[nextModelInstance.id] !== undefined) {
|
||||
throw new Error(`Model instance ${nextModelInstance.id} already exists.`);
|
||||
}
|
||||
if (previousSelection === null) {
|
||||
previousSelection = cloneEditorSelection(context.getSelection());
|
||||
}
|
||||
if (previousToolMode === null) {
|
||||
previousToolMode = context.getToolMode();
|
||||
}
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
assets: {
|
||||
...currentDocument.assets,
|
||||
[nextAsset.id]: cloneProjectAssetRecord(nextAsset)
|
||||
},
|
||||
modelInstances: {
|
||||
...currentDocument.modelInstances,
|
||||
[nextModelInstance.id]: cloneModelInstance(nextModelInstance)
|
||||
}
|
||||
});
|
||||
context.setSelection(setSingleModelInstanceSelection(nextModelInstance.id));
|
||||
context.setToolMode("select");
|
||||
},
|
||||
undo(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
const nextAssets = {
|
||||
...currentDocument.assets
|
||||
};
|
||||
const nextModelInstances = {
|
||||
...currentDocument.modelInstances
|
||||
};
|
||||
delete nextAssets[nextAsset.id];
|
||||
delete nextModelInstances[nextModelInstance.id];
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
assets: nextAssets,
|
||||
modelInstances: nextModelInstances
|
||||
});
|
||||
if (previousSelection !== null) {
|
||||
context.setSelection(previousSelection);
|
||||
}
|
||||
if (previousToolMode !== null) {
|
||||
context.setToolMode(previousToolMode);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
import { DEFAULT_GRID_SIZE, snapVec3ToGrid } from "../geometry/grid-snapping";
|
||||
import { createOpaqueId } from "../core/ids";
|
||||
import { cloneSelectionForCommand, getBoxBrushOrThrow, replaceBrush, setSingleBrushSelection } from "./brush-command-helpers";
|
||||
export function createMoveBoxBrushCommand(options) {
|
||||
const resolvedCenter = options.snapToGrid === false ? options.center : snapVec3ToGrid(options.center, options.gridSize ?? DEFAULT_GRID_SIZE);
|
||||
let previousCenter = null;
|
||||
let previousSelection = null;
|
||||
let previousToolMode = null;
|
||||
return {
|
||||
id: createOpaqueId("command"),
|
||||
label: options.label ?? "Move box brush",
|
||||
execute(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
const brush = getBoxBrushOrThrow(currentDocument, options.brushId);
|
||||
if (previousCenter === null) {
|
||||
previousCenter = {
|
||||
...brush.center
|
||||
};
|
||||
}
|
||||
if (previousSelection === null) {
|
||||
previousSelection = cloneSelectionForCommand(context.getSelection());
|
||||
}
|
||||
if (previousToolMode === null) {
|
||||
previousToolMode = context.getToolMode();
|
||||
}
|
||||
context.setDocument(replaceBrush(currentDocument, {
|
||||
...brush,
|
||||
center: {
|
||||
...resolvedCenter
|
||||
}
|
||||
}));
|
||||
context.setSelection(setSingleBrushSelection(options.brushId));
|
||||
context.setToolMode("select");
|
||||
},
|
||||
undo(context) {
|
||||
if (previousCenter === null) {
|
||||
return;
|
||||
}
|
||||
const currentDocument = context.getDocument();
|
||||
const brush = getBoxBrushOrThrow(currentDocument, options.brushId);
|
||||
context.setDocument(replaceBrush(currentDocument, {
|
||||
...brush,
|
||||
center: {
|
||||
...previousCenter
|
||||
}
|
||||
}));
|
||||
if (previousSelection !== null) {
|
||||
context.setSelection(previousSelection);
|
||||
}
|
||||
if (previousToolMode !== null) {
|
||||
context.setToolMode(previousToolMode);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
import { DEFAULT_GRID_SIZE, snapPositiveSizeToGrid } from "../geometry/grid-snapping";
|
||||
import { createOpaqueId } from "../core/ids";
|
||||
import { cloneBoxBrushGeometry, scaleBoxBrushGeometryToSize } from "../document/brushes";
|
||||
import { cloneSelectionForCommand, getBoxBrushOrThrow, replaceBrush, setSingleBrushSelection } from "./brush-command-helpers";
|
||||
export function createResizeBoxBrushCommand(options) {
|
||||
const resolvedSize = options.snapToGrid === false ? options.size : snapPositiveSizeToGrid(options.size, options.gridSize ?? DEFAULT_GRID_SIZE);
|
||||
let previousSize = null;
|
||||
let previousGeometry = null;
|
||||
let previousSelection = null;
|
||||
let previousToolMode = null;
|
||||
return {
|
||||
id: createOpaqueId("command"),
|
||||
label: options.label ?? "Resize box brush",
|
||||
execute(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
const brush = getBoxBrushOrThrow(currentDocument, options.brushId);
|
||||
if (previousSize === null) {
|
||||
previousSize = {
|
||||
...brush.size
|
||||
};
|
||||
previousGeometry = cloneBoxBrushGeometry(brush.geometry);
|
||||
}
|
||||
if (previousSelection === null) {
|
||||
previousSelection = cloneSelectionForCommand(context.getSelection());
|
||||
}
|
||||
if (previousToolMode === null) {
|
||||
previousToolMode = context.getToolMode();
|
||||
}
|
||||
const nextGeometry = scaleBoxBrushGeometryToSize(brush.geometry, resolvedSize);
|
||||
context.setDocument(replaceBrush(currentDocument, {
|
||||
...brush,
|
||||
size: {
|
||||
...resolvedSize
|
||||
},
|
||||
geometry: nextGeometry
|
||||
}));
|
||||
context.setSelection(setSingleBrushSelection(options.brushId));
|
||||
context.setToolMode("select");
|
||||
},
|
||||
undo(context) {
|
||||
if (previousSize === null || previousGeometry === null) {
|
||||
return;
|
||||
}
|
||||
const currentDocument = context.getDocument();
|
||||
const brush = getBoxBrushOrThrow(currentDocument, options.brushId);
|
||||
context.setDocument(replaceBrush(currentDocument, {
|
||||
...brush,
|
||||
size: {
|
||||
...previousSize
|
||||
},
|
||||
geometry: cloneBoxBrushGeometry(previousGeometry)
|
||||
}));
|
||||
if (previousSelection !== null) {
|
||||
context.setSelection(previousSelection);
|
||||
}
|
||||
if (previousToolMode !== null) {
|
||||
context.setToolMode(previousToolMode);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import { createOpaqueId } from "../core/ids";
|
||||
import { cloneSelectionForCommand, getBoxBrushOrThrow, replaceBrush, setSingleBrushSelection } from "./brush-command-helpers";
|
||||
export function createRotateBoxBrushCommand(options) {
|
||||
let previousRotationDegrees = null;
|
||||
let previousSelection = null;
|
||||
let previousToolMode = null;
|
||||
return {
|
||||
id: createOpaqueId("command"),
|
||||
label: options.label ?? "Rotate box brush",
|
||||
execute(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
const brush = getBoxBrushOrThrow(currentDocument, options.brushId);
|
||||
if (previousRotationDegrees === null) {
|
||||
previousRotationDegrees = {
|
||||
...brush.rotationDegrees
|
||||
};
|
||||
}
|
||||
if (previousSelection === null) {
|
||||
previousSelection = cloneSelectionForCommand(context.getSelection());
|
||||
}
|
||||
if (previousToolMode === null) {
|
||||
previousToolMode = context.getToolMode();
|
||||
}
|
||||
context.setDocument(replaceBrush(currentDocument, {
|
||||
...brush,
|
||||
rotationDegrees: {
|
||||
...options.rotationDegrees
|
||||
}
|
||||
}));
|
||||
context.setSelection(setSingleBrushSelection(options.brushId));
|
||||
context.setToolMode("select");
|
||||
},
|
||||
undo(context) {
|
||||
if (previousRotationDegrees === null) {
|
||||
return;
|
||||
}
|
||||
const currentDocument = context.getDocument();
|
||||
const brush = getBoxBrushOrThrow(currentDocument, options.brushId);
|
||||
context.setDocument(replaceBrush(currentDocument, {
|
||||
...brush,
|
||||
rotationDegrees: {
|
||||
...previousRotationDegrees
|
||||
}
|
||||
}));
|
||||
if (previousSelection !== null) {
|
||||
context.setSelection(previousSelection);
|
||||
}
|
||||
if (previousToolMode !== null) {
|
||||
context.setToolMode(previousToolMode);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
import { createOpaqueId } from "../core/ids";
|
||||
import { cloneSelectionForCommand, getBoxBrushFaceOrThrow, replaceBoxBrushFace, setSingleBrushFaceSelection } from "./brush-command-helpers";
|
||||
export function createSetBoxBrushFaceMaterialCommand(options) {
|
||||
let previousMaterialId;
|
||||
let previousSelection = null;
|
||||
let previousToolMode = null;
|
||||
return {
|
||||
id: createOpaqueId("command"),
|
||||
label: options.materialId === null ? `Clear ${options.faceId} face material` : `Apply material to ${options.faceId} face`,
|
||||
execute(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
const currentFace = getBoxBrushFaceOrThrow(currentDocument, options.brushId, options.faceId);
|
||||
if (options.materialId !== null && currentDocument.materials[options.materialId] === undefined) {
|
||||
throw new Error(`Material ${options.materialId} does not exist in the document registry.`);
|
||||
}
|
||||
if (previousMaterialId === undefined) {
|
||||
previousMaterialId = currentFace.materialId;
|
||||
}
|
||||
if (previousSelection === null) {
|
||||
previousSelection = cloneSelectionForCommand(context.getSelection());
|
||||
}
|
||||
if (previousToolMode === null) {
|
||||
previousToolMode = context.getToolMode();
|
||||
}
|
||||
context.setDocument(replaceBoxBrushFace(currentDocument, options.brushId, options.faceId, {
|
||||
...currentFace,
|
||||
materialId: options.materialId
|
||||
}));
|
||||
context.setSelection(setSingleBrushFaceSelection(options.brushId, options.faceId));
|
||||
context.setToolMode("select");
|
||||
},
|
||||
undo(context) {
|
||||
if (previousMaterialId === undefined) {
|
||||
return;
|
||||
}
|
||||
const currentDocument = context.getDocument();
|
||||
const currentFace = getBoxBrushFaceOrThrow(currentDocument, options.brushId, options.faceId);
|
||||
context.setDocument(replaceBoxBrushFace(currentDocument, options.brushId, options.faceId, {
|
||||
...currentFace,
|
||||
materialId: previousMaterialId
|
||||
}));
|
||||
if (previousSelection !== null) {
|
||||
context.setSelection(previousSelection);
|
||||
}
|
||||
if (previousToolMode !== null) {
|
||||
context.setToolMode(previousToolMode);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import { createOpaqueId } from "../core/ids";
|
||||
import { cloneFaceUvState } from "../document/brushes";
|
||||
import { cloneSelectionForCommand, getBoxBrushFaceOrThrow, replaceBoxBrushFace, setSingleBrushFaceSelection } from "./brush-command-helpers";
|
||||
export function createSetBoxBrushFaceUvStateCommand(options) {
|
||||
let previousUvState = null;
|
||||
let previousSelection = null;
|
||||
let previousToolMode = null;
|
||||
return {
|
||||
id: createOpaqueId("command"),
|
||||
label: options.label ?? `Update ${options.faceId} face UVs`,
|
||||
execute(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
const currentFace = getBoxBrushFaceOrThrow(currentDocument, options.brushId, options.faceId);
|
||||
if (previousUvState === null) {
|
||||
previousUvState = cloneFaceUvState(currentFace.uv);
|
||||
}
|
||||
if (previousSelection === null) {
|
||||
previousSelection = cloneSelectionForCommand(context.getSelection());
|
||||
}
|
||||
if (previousToolMode === null) {
|
||||
previousToolMode = context.getToolMode();
|
||||
}
|
||||
context.setDocument(replaceBoxBrushFace(currentDocument, options.brushId, options.faceId, {
|
||||
...currentFace,
|
||||
uv: cloneFaceUvState(options.uvState)
|
||||
}));
|
||||
context.setSelection(setSingleBrushFaceSelection(options.brushId, options.faceId));
|
||||
context.setToolMode("select");
|
||||
},
|
||||
undo(context) {
|
||||
if (previousUvState === null) {
|
||||
return;
|
||||
}
|
||||
const currentDocument = context.getDocument();
|
||||
const currentFace = getBoxBrushFaceOrThrow(currentDocument, options.brushId, options.faceId);
|
||||
context.setDocument(replaceBoxBrushFace(currentDocument, options.brushId, options.faceId, {
|
||||
...currentFace,
|
||||
uv: cloneFaceUvState(previousUvState)
|
||||
}));
|
||||
if (previousSelection !== null) {
|
||||
context.setSelection(previousSelection);
|
||||
}
|
||||
if (previousToolMode !== null) {
|
||||
context.setToolMode(previousToolMode);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import { createOpaqueId } from "../core/ids";
|
||||
import { normalizeBrushName } from "../document/brushes";
|
||||
import { getBoxBrushOrThrow, replaceBrush } from "./brush-command-helpers";
|
||||
export function createSetBoxBrushNameCommand(options) {
|
||||
const normalizedName = normalizeBrushName(options.name);
|
||||
let previousName;
|
||||
return {
|
||||
id: createOpaqueId("command"),
|
||||
label: normalizedName === undefined ? "Clear box brush name" : `Rename box brush to ${normalizedName}`,
|
||||
execute(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
const brush = getBoxBrushOrThrow(currentDocument, options.brushId);
|
||||
if (previousName === undefined) {
|
||||
previousName = brush.name;
|
||||
}
|
||||
context.setDocument(replaceBrush(currentDocument, {
|
||||
...brush,
|
||||
name: normalizedName
|
||||
}));
|
||||
},
|
||||
undo(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
const brush = getBoxBrushOrThrow(currentDocument, options.brushId);
|
||||
context.setDocument(replaceBrush(currentDocument, {
|
||||
...brush,
|
||||
name: previousName
|
||||
}));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
import { createOpaqueId } from "../core/ids";
|
||||
import { cloneBoxBrushGeometry, deriveBoxBrushSizeFromGeometry, scaleBoxBrushGeometryToSize } from "../document/brushes";
|
||||
import { cloneSelectionForCommand, getBoxBrushOrThrow, replaceBrush, setSingleBrushEdgeSelection, setSingleBrushFaceSelection, setSingleBrushSelection, setSingleBrushVertexSelection } from "./brush-command-helpers";
|
||||
function cloneVec3(vector) {
|
||||
return {
|
||||
x: vector.x,
|
||||
y: vector.y,
|
||||
z: vector.z
|
||||
};
|
||||
}
|
||||
function selectionToEditorSelection(selection) {
|
||||
switch (selection.kind) {
|
||||
case "brush":
|
||||
return setSingleBrushSelection(selection.brushId);
|
||||
case "brushFace":
|
||||
return setSingleBrushFaceSelection(selection.brushId, selection.faceId);
|
||||
case "brushEdge":
|
||||
return setSingleBrushEdgeSelection(selection.brushId, selection.edgeId);
|
||||
case "brushVertex":
|
||||
return setSingleBrushVertexSelection(selection.brushId, selection.vertexId);
|
||||
}
|
||||
}
|
||||
function getBrushId(selection) {
|
||||
return selection.brushId;
|
||||
}
|
||||
function assertPositiveSize(size) {
|
||||
if (!(size.x > 0 && size.y > 0 && size.z > 0)) {
|
||||
throw new Error("Whitebox box size must remain positive on every axis.");
|
||||
}
|
||||
if (!Number.isFinite(size.x) || !Number.isFinite(size.y) || !Number.isFinite(size.z)) {
|
||||
throw new Error("Whitebox box size values must be finite numbers.");
|
||||
}
|
||||
}
|
||||
export function createSetBoxBrushTransformCommand(options) {
|
||||
assertPositiveSize(options.size);
|
||||
let previousSnapshot = null;
|
||||
let previousSelection = null;
|
||||
let previousToolMode = null;
|
||||
return {
|
||||
id: createOpaqueId("command"),
|
||||
label: options.label ?? "Set box brush transform",
|
||||
execute(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
const brushId = getBrushId(options.selection);
|
||||
const brush = getBoxBrushOrThrow(currentDocument, brushId);
|
||||
if (previousSnapshot === null) {
|
||||
previousSnapshot = {
|
||||
center: cloneVec3(brush.center),
|
||||
rotationDegrees: cloneVec3(brush.rotationDegrees),
|
||||
size: cloneVec3(brush.size),
|
||||
geometry: cloneBoxBrushGeometry(brush.geometry)
|
||||
};
|
||||
}
|
||||
if (previousSelection === null) {
|
||||
previousSelection = cloneSelectionForCommand(context.getSelection());
|
||||
}
|
||||
if (previousToolMode === null) {
|
||||
previousToolMode = context.getToolMode();
|
||||
}
|
||||
const nextGeometry = options.geometry === undefined ? scaleBoxBrushGeometryToSize(brush.geometry, options.size) : cloneBoxBrushGeometry(options.geometry);
|
||||
const nextSize = deriveBoxBrushSizeFromGeometry(nextGeometry);
|
||||
assertPositiveSize(nextSize);
|
||||
context.setDocument(replaceBrush(currentDocument, {
|
||||
...brush,
|
||||
center: cloneVec3(options.center),
|
||||
rotationDegrees: cloneVec3(options.rotationDegrees),
|
||||
size: nextSize,
|
||||
geometry: nextGeometry
|
||||
}));
|
||||
context.setSelection(selectionToEditorSelection(options.selection));
|
||||
context.setToolMode("select");
|
||||
},
|
||||
undo(context) {
|
||||
if (previousSnapshot === null) {
|
||||
return;
|
||||
}
|
||||
const currentDocument = context.getDocument();
|
||||
const brushId = getBrushId(options.selection);
|
||||
const brush = getBoxBrushOrThrow(currentDocument, brushId);
|
||||
context.setDocument(replaceBrush(currentDocument, {
|
||||
...brush,
|
||||
center: cloneVec3(previousSnapshot.center),
|
||||
rotationDegrees: cloneVec3(previousSnapshot.rotationDegrees),
|
||||
size: cloneVec3(previousSnapshot.size),
|
||||
geometry: cloneBoxBrushGeometry(previousSnapshot.geometry)
|
||||
}));
|
||||
if (previousSelection !== null) {
|
||||
context.setSelection(previousSelection);
|
||||
}
|
||||
if (previousToolMode !== null) {
|
||||
context.setToolMode(previousToolMode);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import { createOpaqueId } from "../core/ids";
|
||||
import { cloneBoxBrushVolumeSettings } from "../document/brushes";
|
||||
import { getBoxBrushOrThrow, replaceBrush } from "./brush-command-helpers";
|
||||
export function createSetBoxBrushVolumeSettingsCommand(options) {
|
||||
const nextVolume = cloneBoxBrushVolumeSettings(options.volume);
|
||||
let previousVolume = null;
|
||||
return {
|
||||
id: createOpaqueId("command"),
|
||||
label: options.label ?? "Set box volume settings",
|
||||
execute(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
const brush = getBoxBrushOrThrow(currentDocument, options.brushId);
|
||||
if (previousVolume === null) {
|
||||
previousVolume = cloneBoxBrushVolumeSettings(brush.volume);
|
||||
}
|
||||
context.setDocument(replaceBrush(currentDocument, {
|
||||
...brush,
|
||||
volume: cloneBoxBrushVolumeSettings(nextVolume)
|
||||
}));
|
||||
},
|
||||
undo(context) {
|
||||
if (previousVolume === null) {
|
||||
return;
|
||||
}
|
||||
const currentDocument = context.getDocument();
|
||||
const brush = getBoxBrushOrThrow(currentDocument, options.brushId);
|
||||
context.setDocument(replaceBrush(currentDocument, {
|
||||
...brush,
|
||||
volume: cloneBoxBrushVolumeSettings(previousVolume)
|
||||
}));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import { createOpaqueId } from "../core/ids";
|
||||
import { cloneEntityInstance, normalizeEntityName } from "../entities/entity-instances";
|
||||
export function createSetEntityNameCommand(options) {
|
||||
const normalizedName = normalizeEntityName(options.name);
|
||||
let previousName;
|
||||
return {
|
||||
id: createOpaqueId("command"),
|
||||
label: normalizedName === undefined ? "Clear entity name" : `Rename entity to ${normalizedName}`,
|
||||
execute(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
const entity = currentDocument.entities[options.entityId];
|
||||
if (entity === undefined) {
|
||||
throw new Error(`Entity ${options.entityId} does not exist.`);
|
||||
}
|
||||
if (previousName === undefined) {
|
||||
previousName = entity.name;
|
||||
}
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
entities: {
|
||||
...currentDocument.entities,
|
||||
[entity.id]: cloneEntityInstance({
|
||||
...entity,
|
||||
name: normalizedName
|
||||
})
|
||||
}
|
||||
});
|
||||
},
|
||||
undo(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
const entity = currentDocument.entities[options.entityId];
|
||||
if (entity === undefined) {
|
||||
throw new Error(`Entity ${options.entityId} does not exist.`);
|
||||
}
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
entities: {
|
||||
...currentDocument.entities,
|
||||
[entity.id]: cloneEntityInstance({
|
||||
...entity,
|
||||
name: previousName
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import { createOpaqueId } from "../core/ids";
|
||||
import { cloneModelInstance, normalizeModelInstanceName } from "../assets/model-instances";
|
||||
export function createSetModelInstanceNameCommand(options) {
|
||||
const normalizedName = normalizeModelInstanceName(options.name);
|
||||
let previousName;
|
||||
return {
|
||||
id: createOpaqueId("command"),
|
||||
label: normalizedName === undefined ? "Clear model instance name" : `Rename model instance to ${normalizedName}`,
|
||||
execute(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
const modelInstance = currentDocument.modelInstances[options.modelInstanceId];
|
||||
if (modelInstance === undefined) {
|
||||
throw new Error(`Model instance ${options.modelInstanceId} does not exist.`);
|
||||
}
|
||||
if (previousName === undefined) {
|
||||
previousName = modelInstance.name;
|
||||
}
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
modelInstances: {
|
||||
...currentDocument.modelInstances,
|
||||
[modelInstance.id]: cloneModelInstance({
|
||||
...modelInstance,
|
||||
name: normalizedName
|
||||
})
|
||||
}
|
||||
});
|
||||
},
|
||||
undo(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
const modelInstance = currentDocument.modelInstances[options.modelInstanceId];
|
||||
if (modelInstance === undefined) {
|
||||
throw new Error(`Model instance ${options.modelInstanceId} does not exist.`);
|
||||
}
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
modelInstances: {
|
||||
...currentDocument.modelInstances,
|
||||
[modelInstance.id]: cloneModelInstance({
|
||||
...modelInstance,
|
||||
name: previousName
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import { createPlayerStartEntity } from "../entities/entity-instances";
|
||||
import { createUpsertEntityCommand } from "./upsert-entity-command";
|
||||
export function createSetPlayerStartCommand(options) {
|
||||
return createUpsertEntityCommand({
|
||||
entity: createPlayerStartEntity({
|
||||
id: options.entityId,
|
||||
position: options.position,
|
||||
yawDegrees: options.yawDegrees
|
||||
}),
|
||||
label: options.entityId === undefined ? "Place player start" : "Move player start"
|
||||
});
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { createOpaqueId } from "../core/ids";
|
||||
export function createSetSceneNameCommand(nextName) {
|
||||
const normalizedName = nextName.trim() || "Untitled Scene";
|
||||
let previousName = null;
|
||||
return {
|
||||
id: createOpaqueId("command"),
|
||||
label: `Rename scene to ${normalizedName}`,
|
||||
execute(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
if (previousName === null) {
|
||||
previousName = currentDocument.name;
|
||||
}
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
name: normalizedName
|
||||
});
|
||||
},
|
||||
undo(context) {
|
||||
if (previousName === null) {
|
||||
return;
|
||||
}
|
||||
const currentDocument = context.getDocument();
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
name: previousName
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import { createOpaqueId } from "../core/ids";
|
||||
import { cloneWorldSettings } from "../document/world-settings";
|
||||
export function createSetWorldSettingsCommand(options) {
|
||||
const nextWorld = cloneWorldSettings(options.world);
|
||||
let previousWorld = null;
|
||||
return {
|
||||
id: createOpaqueId("command"),
|
||||
label: options.label,
|
||||
execute(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
if (previousWorld === null) {
|
||||
previousWorld = cloneWorldSettings(currentDocument.world);
|
||||
}
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
world: cloneWorldSettings(nextWorld)
|
||||
});
|
||||
},
|
||||
undo(context) {
|
||||
if (previousWorld === null) {
|
||||
return;
|
||||
}
|
||||
const currentDocument = context.getDocument();
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
world: cloneWorldSettings(previousWorld)
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
import { cloneEditorSelection } from "../core/selection";
|
||||
import { createOpaqueId } from "../core/ids";
|
||||
import { cloneEntityInstance, getEntityKindLabel } from "../entities/entity-instances";
|
||||
function setSingleEntitySelection(entityId) {
|
||||
return {
|
||||
kind: "entities",
|
||||
ids: [entityId]
|
||||
};
|
||||
}
|
||||
function createDefaultEntityCommandLabel(entity, isNewEntity) {
|
||||
const action = isNewEntity ? "Place" : "Update";
|
||||
return `${action} ${getEntityKindLabel(entity.kind).toLowerCase()}`;
|
||||
}
|
||||
export function createUpsertEntityCommand(options) {
|
||||
const nextEntity = cloneEntityInstance(options.entity);
|
||||
let previousEntity = null;
|
||||
let previousSelection = null;
|
||||
let previousToolMode = null;
|
||||
return {
|
||||
id: createOpaqueId("command"),
|
||||
label: options.label ?? createDefaultEntityCommandLabel(nextEntity, true),
|
||||
execute(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
const currentEntity = currentDocument.entities[nextEntity.id];
|
||||
if (currentEntity !== undefined && currentEntity.kind !== nextEntity.kind) {
|
||||
throw new Error(`Entity ${nextEntity.id} is a ${currentEntity.kind}, not a ${nextEntity.kind}.`);
|
||||
}
|
||||
if (previousSelection === null) {
|
||||
previousSelection = cloneEditorSelection(context.getSelection());
|
||||
}
|
||||
if (previousToolMode === null) {
|
||||
previousToolMode = context.getToolMode();
|
||||
}
|
||||
if (previousEntity === null && currentEntity !== undefined) {
|
||||
previousEntity = cloneEntityInstance(currentEntity);
|
||||
}
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
entities: {
|
||||
...currentDocument.entities,
|
||||
[nextEntity.id]: cloneEntityInstance(nextEntity)
|
||||
}
|
||||
});
|
||||
context.setSelection(setSingleEntitySelection(nextEntity.id));
|
||||
context.setToolMode("select");
|
||||
},
|
||||
undo(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
const nextEntities = {
|
||||
...currentDocument.entities
|
||||
};
|
||||
if (previousEntity === null) {
|
||||
delete nextEntities[nextEntity.id];
|
||||
}
|
||||
else {
|
||||
nextEntities[nextEntity.id] = cloneEntityInstance(previousEntity);
|
||||
}
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
entities: nextEntities
|
||||
});
|
||||
if (previousSelection !== null) {
|
||||
context.setSelection(previousSelection);
|
||||
}
|
||||
if (previousToolMode !== null) {
|
||||
context.setToolMode(previousToolMode);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import { cloneInteractionLink } from "../interactions/interaction-links";
|
||||
import { createOpaqueId } from "../core/ids";
|
||||
export function createUpsertInteractionLinkCommand(options) {
|
||||
const nextLink = cloneInteractionLink(options.link);
|
||||
let previousLink = null;
|
||||
return {
|
||||
id: createOpaqueId("command"),
|
||||
label: options.label ?? "Update interaction link",
|
||||
execute(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
const currentLink = currentDocument.interactionLinks[nextLink.id];
|
||||
if (previousLink === null && currentLink !== undefined) {
|
||||
previousLink = cloneInteractionLink(currentLink);
|
||||
}
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
interactionLinks: {
|
||||
...currentDocument.interactionLinks,
|
||||
[nextLink.id]: cloneInteractionLink(nextLink)
|
||||
}
|
||||
});
|
||||
},
|
||||
undo(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
const nextInteractionLinks = {
|
||||
...currentDocument.interactionLinks
|
||||
};
|
||||
if (previousLink === null) {
|
||||
delete nextInteractionLinks[nextLink.id];
|
||||
}
|
||||
else {
|
||||
nextInteractionLinks[nextLink.id] = cloneInteractionLink(previousLink);
|
||||
}
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
interactionLinks: nextInteractionLinks
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
import { createOpaqueId } from "../core/ids";
|
||||
import { cloneEditorSelection } from "../core/selection";
|
||||
import { cloneModelInstance, getModelInstanceKindLabel } from "../assets/model-instances";
|
||||
import { getProjectAssetKindLabel } from "../assets/project-assets";
|
||||
function setSingleModelInstanceSelection(modelInstanceId) {
|
||||
return {
|
||||
kind: "modelInstances",
|
||||
ids: [modelInstanceId]
|
||||
};
|
||||
}
|
||||
function createDefaultModelInstanceCommandLabel(isNewModelInstance) {
|
||||
const action = isNewModelInstance ? "Place" : "Update";
|
||||
return `${action} ${getModelInstanceKindLabel().toLowerCase()}`;
|
||||
}
|
||||
export function createUpsertModelInstanceCommand(options) {
|
||||
const nextModelInstance = cloneModelInstance(options.modelInstance);
|
||||
let previousModelInstance = null;
|
||||
let previousSelection = null;
|
||||
let previousToolMode = null;
|
||||
return {
|
||||
id: createOpaqueId("command"),
|
||||
label: options.label ?? createDefaultModelInstanceCommandLabel(true),
|
||||
execute(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
const currentAsset = currentDocument.assets[nextModelInstance.assetId];
|
||||
if (currentAsset === undefined) {
|
||||
throw new Error(`Model instance ${nextModelInstance.id} cannot reference missing asset ${nextModelInstance.assetId}.`);
|
||||
}
|
||||
if (currentAsset.kind !== "model") {
|
||||
throw new Error(`Model instance ${nextModelInstance.id} must reference a model asset, not ${getProjectAssetKindLabel(currentAsset.kind).toLowerCase()}.`);
|
||||
}
|
||||
const currentModelInstance = currentDocument.modelInstances[nextModelInstance.id];
|
||||
if (previousSelection === null) {
|
||||
previousSelection = cloneEditorSelection(context.getSelection());
|
||||
}
|
||||
if (previousToolMode === null) {
|
||||
previousToolMode = context.getToolMode();
|
||||
}
|
||||
if (previousModelInstance === null && currentModelInstance !== undefined) {
|
||||
previousModelInstance = cloneModelInstance(currentModelInstance);
|
||||
}
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
modelInstances: {
|
||||
...currentDocument.modelInstances,
|
||||
[nextModelInstance.id]: cloneModelInstance(nextModelInstance)
|
||||
}
|
||||
});
|
||||
context.setSelection(setSingleModelInstanceSelection(nextModelInstance.id));
|
||||
context.setToolMode("select");
|
||||
},
|
||||
undo(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
const nextModelInstances = {
|
||||
...currentDocument.modelInstances
|
||||
};
|
||||
if (previousModelInstance === null) {
|
||||
delete nextModelInstances[nextModelInstance.id];
|
||||
}
|
||||
else {
|
||||
nextModelInstances[nextModelInstance.id] = cloneModelInstance(previousModelInstance);
|
||||
}
|
||||
context.setDocument({
|
||||
...currentDocument,
|
||||
modelInstances: nextModelInstances
|
||||
});
|
||||
if (previousSelection !== null) {
|
||||
context.setSelection(previousSelection);
|
||||
}
|
||||
if (previousToolMode !== null) {
|
||||
context.setToolMode(previousToolMode);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user