diff --git a/src/commands/command.ts b/src/commands/command.ts index e1aeb2d2..c6dd27dd 100644 --- a/src/commands/command.ts +++ b/src/commands/command.ts @@ -1,10 +1,12 @@ import type { EditorSelection } from "../core/selection"; import type { ToolMode } from "../core/tool-mode"; -import type { SceneDocument } from "../document/scene-document"; +import type { ProjectDocument, SceneDocument } from "../document/scene-document"; export interface CommandContext { getDocument(): SceneDocument; setDocument(document: SceneDocument): void; + getProjectDocument(): ProjectDocument; + setProjectDocument(document: ProjectDocument): void; getSelection(): EditorSelection; setSelection(selection: EditorSelection): void; getToolMode(): ToolMode; diff --git a/src/commands/create-scene-command.ts b/src/commands/create-scene-command.ts new file mode 100644 index 00000000..f63d8c4e --- /dev/null +++ b/src/commands/create-scene-command.ts @@ -0,0 +1,68 @@ +import { createOpaqueId } from "../core/ids"; +import { createEmptyProjectScene } from "../document/scene-document"; + +import type { EditorCommand } from "./command"; + +function createUniqueSceneName(existingNames: string[]): string { + const normalizedNames = new Set( + existingNames.map((name) => name.trim().toLowerCase()) + ); + let suffix = existingNames.length + 1; + + while (normalizedNames.has(`scene ${suffix}`)) { + suffix += 1; + } + + return `Scene ${suffix}`; +} + +export function createCreateSceneCommand(): EditorCommand { + const createdSceneId = createOpaqueId("scene"); + let createdSceneName: string | null = null; + let previousProjectDocumentSerialized: string | null = null; + + return { + id: createOpaqueId("command"), + label: "Create scene", + execute(context) { + const currentProjectDocument = context.getProjectDocument(); + + if (previousProjectDocumentSerialized === null) { + previousProjectDocumentSerialized = JSON.stringify(currentProjectDocument); + } + + if (createdSceneName === null) { + createdSceneName = createUniqueSceneName( + Object.values(currentProjectDocument.scenes).map((scene) => scene.name) + ); + } + + context.setProjectDocument({ + ...currentProjectDocument, + activeSceneId: createdSceneId, + scenes: { + ...currentProjectDocument.scenes, + [createdSceneId]: createEmptyProjectScene({ + id: createdSceneId, + name: createdSceneName + }) + } + }); + context.setSelection({ kind: "none" }); + context.setToolMode("select"); + }, + undo(context) { + if (previousProjectDocumentSerialized === null) { + return; + } + + context.setProjectDocument( + JSON.parse(previousProjectDocumentSerialized) as ReturnType< + typeof context.getProjectDocument + > + ); + context.setSelection({ kind: "none" }); + context.setToolMode("select"); + } + }; +} diff --git a/src/commands/set-active-scene-command.ts b/src/commands/set-active-scene-command.ts new file mode 100644 index 00000000..90749a8f --- /dev/null +++ b/src/commands/set-active-scene-command.ts @@ -0,0 +1,48 @@ +import { createOpaqueId } from "../core/ids"; + +import type { EditorCommand } from "./command"; + +export function createSetActiveSceneCommand(nextSceneId: string): EditorCommand { + let previousSceneId: string | null = null; + + return { + id: createOpaqueId("command"), + label: "Switch active scene", + execute(context) { + const currentProjectDocument = context.getProjectDocument(); + + if (currentProjectDocument.scenes[nextSceneId] === undefined) { + throw new Error(`Cannot activate missing scene ${nextSceneId}.`); + } + + if (previousSceneId === null) { + previousSceneId = currentProjectDocument.activeSceneId; + } + + context.setProjectDocument({ + ...currentProjectDocument, + activeSceneId: nextSceneId + }); + context.setSelection({ kind: "none" }); + context.setToolMode("select"); + }, + undo(context) { + if (previousSceneId === null) { + return; + } + + const currentProjectDocument = context.getProjectDocument(); + + if (currentProjectDocument.scenes[previousSceneId] === undefined) { + throw new Error(`Cannot restore missing scene ${previousSceneId}.`); + } + + context.setProjectDocument({ + ...currentProjectDocument, + activeSceneId: previousSceneId + }); + context.setSelection({ kind: "none" }); + context.setToolMode("select"); + } + }; +}