mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-12 12:53:04 +00:00
PageScene: add concrete UndoableAction classes
Six self-contained classes under engine/editor/actions/, one per action kind. Each implements UndoableAction, captures its own state in the constructor, and runs apply/revert against subsystem refs held privately. Adding a new kind = a new file here, no edits elsewhere. - CreateAction: snapshots an obj, recreates on apply (redo), removes via utils.deleteObject on revert (undo). Uses the non-recording delete path so undo of create can't recurse. - DeleteAction: mirror — removes on apply, recreates on revert. - TransformAction: encapsulates begin/end transaction state. Caller constructs at dragstart (captures before), calls commit() at dragend to capture after + check for a no-op move. records only if commit() returns true. - ColorAction: simple before/after pair, routes through activeScene.setColor. - LockAction: routes through SelectionBridge.setLockState so the gizmo attach/detach side effects fire on undo. - VisibilityAction: writes obj.visible + userData.visible directly. No call sites use these yet — that's the next commit. HistoryManager is unchanged; it already accepts UndoableAction via record().
This commit is contained in:
1 parent
a4c3be4c99
commit
f4005603f6
6 files changed
+216
No files matched your search
@@ -0,0 +1,24 @@
|
||||
import type Editor from "../../editor";
|
||||
import type { UndoableAction } from "../HistoryManager";
|
||||
|
||||
// Records a color change. apply() and revert() both call
|
||||
// activeScene.setColor — the engine handles material magic. The action
|
||||
// captures the uuid + before/after hex pair at construction time.
|
||||
export class ColorAction implements UndoableAction {
|
||||
readonly label = "Color";
|
||||
|
||||
constructor(
|
||||
private readonly editor: Editor,
|
||||
private readonly uuid: string,
|
||||
private readonly before: string,
|
||||
private readonly after: string,
|
||||
) {}
|
||||
|
||||
apply(): void {
|
||||
this.editor.activeScene.setColor(this.uuid, this.after);
|
||||
}
|
||||
|
||||
revert(): void {
|
||||
this.editor.activeScene.setColor(this.uuid, this.before);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import * as THREE from "three";
|
||||
import type Editor from "../../editor";
|
||||
import type { UndoableAction } from "../HistoryManager";
|
||||
import {
|
||||
ObjectSnap,
|
||||
recreateFromSnapshot,
|
||||
snapshotObject,
|
||||
} from "./snapshots";
|
||||
|
||||
// Records the creation of an object. apply() re-creates from the
|
||||
// captured snapshot (used on redo). revert() removes the object using
|
||||
// the lower-level utils.deleteObject path so it does NOT recurse
|
||||
// through editor.deleteObject (which would try to record a fresh
|
||||
// delete entry).
|
||||
export class CreateAction implements UndoableAction {
|
||||
readonly label: string;
|
||||
private readonly snap: ObjectSnap;
|
||||
|
||||
constructor(
|
||||
private readonly editor: Editor,
|
||||
obj: THREE.Object3D,
|
||||
) {
|
||||
this.snap = snapshotObject(obj);
|
||||
this.label = `Create ${this.snap.name}`;
|
||||
}
|
||||
|
||||
async apply(): Promise<void> {
|
||||
await recreateFromSnapshot(this.editor, this.snap);
|
||||
this.editor.selection.refreshOutliner();
|
||||
}
|
||||
|
||||
async revert(): Promise<void> {
|
||||
this.editor.utils.deleteObject(this.snap.uuid);
|
||||
this.editor.selection.refreshOutliner();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import * as THREE from "three";
|
||||
import type Editor from "../../editor";
|
||||
import type { UndoableAction } from "../HistoryManager";
|
||||
import {
|
||||
ObjectSnap,
|
||||
recreateFromSnapshot,
|
||||
snapshotObject,
|
||||
} from "./snapshots";
|
||||
|
||||
// Records the deletion of an object. Mirror of CreateAction with
|
||||
// apply/revert swapped. apply() removes via utils.deleteObject (the
|
||||
// non-recording path) so a redo of a delete during replay can't
|
||||
// re-trigger recordDelete.
|
||||
export class DeleteAction implements UndoableAction {
|
||||
readonly label: string;
|
||||
private readonly snap: ObjectSnap;
|
||||
|
||||
constructor(
|
||||
private readonly editor: Editor,
|
||||
obj: THREE.Object3D,
|
||||
) {
|
||||
this.snap = snapshotObject(obj);
|
||||
this.label = `Delete ${this.snap.name}`;
|
||||
}
|
||||
|
||||
async apply(): Promise<void> {
|
||||
this.editor.utils.deleteObject(this.snap.uuid);
|
||||
this.editor.selection.refreshOutliner();
|
||||
}
|
||||
|
||||
async revert(): Promise<void> {
|
||||
await recreateFromSnapshot(this.editor, this.snap);
|
||||
this.editor.selection.refreshOutliner();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { SelectionBridge } from "../SelectionBridge";
|
||||
import type { UndoableAction } from "../HistoryManager";
|
||||
|
||||
// Records a lock-toggle. Routes both apply() and revert() through
|
||||
// SelectionBridge.setLockState so the gizmo attach/detach side effects
|
||||
// fire — the same path the user-toggle uses, just at a target state
|
||||
// instead of a flip.
|
||||
export class LockAction implements UndoableAction {
|
||||
readonly label: string;
|
||||
|
||||
constructor(
|
||||
private readonly selection: SelectionBridge,
|
||||
private readonly uuid: string,
|
||||
private readonly before: boolean,
|
||||
private readonly after: boolean,
|
||||
) {
|
||||
this.label = after ? "Lock" : "Unlock";
|
||||
}
|
||||
|
||||
apply(): void {
|
||||
this.selection.setLockState(this.uuid, this.after);
|
||||
}
|
||||
|
||||
revert(): void {
|
||||
this.selection.setLockState(this.uuid, this.before);
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
import type Editor from "../../editor";
|
||||
import type { UndoableAction } from "../HistoryManager";
|
||||
import {
|
||||
TransformSnap,
|
||||
snapshotTransform,
|
||||
transformsEqual,
|
||||
writeTransform,
|
||||
} from "./snapshots";
|
||||
|
||||
// Records a transform change. Caller holds the instance between the
|
||||
// drag/edit boundary callbacks (gizmo dragstart/dragend, panel input
|
||||
// focus/blur) and calls commit() at the end. If the transform didn't
|
||||
// actually change, commit() returns false and the caller drops the
|
||||
// action without recording.
|
||||
//
|
||||
// Begin/end transaction state lives on the action itself — no need for
|
||||
// a per-manager pendingTransform field.
|
||||
export class TransformAction implements UndoableAction {
|
||||
readonly label = "Transform";
|
||||
private readonly before: TransformSnap;
|
||||
private after?: TransformSnap;
|
||||
|
||||
constructor(
|
||||
private readonly editor: Editor,
|
||||
private readonly uuid: string,
|
||||
) {
|
||||
const obj = editor.activeScene.scene.getObjectByProperty("uuid", uuid);
|
||||
if (!obj) {
|
||||
this.before = {
|
||||
position: { x: 0, y: 0, z: 0 },
|
||||
rotation: { x: 0, y: 0, z: 0 },
|
||||
scale: { x: 1, y: 1, z: 1 },
|
||||
};
|
||||
return;
|
||||
}
|
||||
this.before = snapshotTransform(obj);
|
||||
}
|
||||
|
||||
// Capture the after-state from the live object. Returns true if it
|
||||
// differs from the before-state (worth recording).
|
||||
commit(): boolean {
|
||||
const obj = this.editor.activeScene.scene.getObjectByProperty(
|
||||
"uuid",
|
||||
this.uuid,
|
||||
);
|
||||
if (!obj) return false;
|
||||
this.after = snapshotTransform(obj);
|
||||
return !transformsEqual(this.before, this.after);
|
||||
}
|
||||
|
||||
apply(): void {
|
||||
if (this.after) writeTransform(this.editor, this.uuid, this.after);
|
||||
}
|
||||
|
||||
revert(): void {
|
||||
writeTransform(this.editor, this.uuid, this.before);
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import type Editor from "../../editor";
|
||||
import type { UndoableAction } from "../HistoryManager";
|
||||
|
||||
// Records a visibility toggle. Writes both obj.visible and
|
||||
// userData.visible directly. The outliner refresh fires in
|
||||
// HistoryManager's replay wrapper so the row icon stays in sync.
|
||||
export class VisibilityAction implements UndoableAction {
|
||||
readonly label: string;
|
||||
|
||||
constructor(
|
||||
private readonly editor: Editor,
|
||||
private readonly uuid: string,
|
||||
private readonly before: boolean,
|
||||
private readonly after: boolean,
|
||||
) {
|
||||
this.label = after ? "Show" : "Hide";
|
||||
}
|
||||
|
||||
apply(): void {
|
||||
this.write(this.after);
|
||||
}
|
||||
|
||||
revert(): void {
|
||||
this.write(this.before);
|
||||
}
|
||||
|
||||
private write(visible: boolean): void {
|
||||
const obj = this.editor.activeScene.scene.getObjectByProperty(
|
||||
"uuid",
|
||||
this.uuid,
|
||||
);
|
||||
if (!obj) return;
|
||||
obj.visible = visible;
|
||||
obj.userData["visible"] = visible;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user