PageEnigma refactor Phase 4: build actions/ and rewire add/select/transform flows

Replaces the four-hop signal-stub pattern (drop -> store-only update,
engine never told) with explicit action helpers that mutate the engine
and the store in one step.

actions/:
- addObject / addCharacter / addShape: call editor.sceneManager.create
  to load the THREE.js object, then update the Zustand store. addCharacter
  also stamps userData.isCharacter on the loaded object
- deleteObject: thin wrapper over editor.deleteObject (which already
  goes through SceneUtils to update the store)
- selectObject / deselectObject: invoke sceneManager.select_object;
  Editor.publishSelect already writes the selection to the store
- setTransformMode: drives editor.change_mode + store + the legacy
  selectedMode signal during the migration window
- setCameraAspect: drives editor.changeRenderCameraAspectRatio + store +
  the legacy cameraAspectRatio signal
- index.ts barrel

Wiring updates:
- PageEditor drop handler (gallery -> 3D) now calls actions/addObject
  and actions/addCharacter with the editor and world position
- DragAndDrop/DndAsset takes the editor at pointerdown time and calls
  actions/* on drop. ItemElement passes the editor in via EngineContext
- AspectRatioMenu calls actions/setCameraAspect; PageEditor's bottom-bar
  aspect-ratio select uses the action too
- Controls3D's mode toggle (move/rotate/scale) goes through
  actions/setTransformMode and the four little redundant handlers are
  collapsed into one
- ImageTo3DExperience's "Open in 3D Editor" button uses getActiveEditor
  to pick up the live editor across pages

Plumbing:
- New module-level activeEditor handle in EngineContext (with
  getActiveEditor / setActiveEditor) so non-React callers and cross-page
  actions can reach the engine; EngineProvider sets it on init and
  clears on unmount
- Drop the now-orphaned signals/objectGroup, signals/characterGroups,
  signals/shape entirely. Their job (store update + engine call) lives
  in actions/. signals/index.ts loses the corresponding re-exports
- Remove the placeholder addLocalSplat onSuccess in Controls3D (a
  developer-test path for direct splat loading; the real splat upload
  goes through GlobalFileDropHandler) and delete the now-confirmed-dead
  recordScene method in editor.ts
- video_generation.ts zipBlob: type as Blob | null (matches the
  UploadStudioShot signature; pre-existing TS error)

Net effect on typecheck: 144 -> 147 errors in PageEnigma + experiences;
zero errors introduced in actions/, PageEnigmaStore, or DragAndDrop. All
new errors are pre-existing issues in adjacent files surfaced by the
edits, not regressions.
This commit is contained in:
Tanish Manku committed 2026-04-28 22:37:30 -07:00
1 parent 6d22d0d3c3
commit 61311028e2
28 files changed
+215 -228

No files matched your search

@@ -25,7 +25,8 @@ import {
import { toast } from "react-hot-toast";
import { v4 as uuidv4 } from "uuid";
import { useTabStore } from "../../pages/Stores/TabState";
import { addObject } from "../../pages/PageEnigma/signals/objectGroup/addObject";
import { addObject } from "../../pages/PageEnigma/actions";
import { getActiveEditor } from "../../pages/PageEnigma/contexts/EngineContext";
import { set3DPageMounted } from "../../pages/PageEnigma/Editor/editor";
import { AssetType } from "~/enums";
import type { MediaItem } from "../../pages/PageEnigma/models";
@@ -989,8 +990,13 @@ export const ImageTo3DExperience = ({
} as MediaItem & {
position: { x: number; y: number; z: number };
};
addObject(mediaItem);
toast.success("Added to 3D scene");
const editor = getActiveEditor();
if (editor) {
void addObject(editor, mediaItem);
toast.success("Added to 3D scene");
} else {
toast.error("3D editor isn't ready yet");
}
}, 500);
}}
>
@@ -1,8 +1,6 @@
import React from "react";
import { MediaItem } from "~/pages/PageEnigma/models";
import {
addCharacter,
addObject,
canDrop,
currPosition,
dragItem,
@@ -10,8 +8,13 @@ import {
reopenAfterDragSignal,
} from "~/pages/PageEnigma/signals";
import { pageHeight, pageWidth } from "~/signals";
import { addShape } from "~/pages/PageEnigma/signals/shape";
import { AssetType } from "~/enums";
import type Editor from "~/pages/PageEnigma/Editor/editor";
import {
addCharacter,
addObject,
addShape,
} from "~/pages/PageEnigma/actions";
class DndAsset {
public dropId: string = "";
@@ -22,14 +25,20 @@ class DndAsset {
public notDropText = "";
public isDragging: boolean = false;
public dragThreshold: number = 5;
private editor: Editor | null = null;
constructor() {
this.onPointerMove = this.onPointerMove.bind(this);
this.onPointerUp = this.onPointerUp.bind(this);
}
onPointerDown(event: React.PointerEvent<HTMLDivElement>, item: MediaItem) {
onPointerDown(
event: React.PointerEvent<HTMLDivElement>,
item: MediaItem,
editor: Editor | null,
) {
if (event.button === 0) {
this.editor = editor;
dragItem.value = item;
currPosition.value = {
currX: event.pageX,
@@ -54,6 +63,7 @@ class DndAsset {
this.notDropText = "";
assetModalVisibleDuringDrag.value = reopenAfterDragSignal.value;
}
this.editor = null;
}
overCanvas(positionX: number, positionY: number) {
@@ -74,26 +84,26 @@ class DndAsset {
assetModalVisibleDuringDrag.value = true;
dragItem.value = null;
currPosition.value = { currX: 0, currY: 0 };
this.editor = null;
return;
}
if (dragItem.value) {
const editor = this.editor;
if (dragItem.value && editor) {
const positionX = event.pageX;
const positionY = event.pageY;
if (this.overCanvas(positionX, positionY)) {
const mediaItem = dragItem.value;
if (mediaItem.type === AssetType.CHARACTER) {
addCharacter(mediaItem);
}
if (
void addCharacter(editor, mediaItem);
} else if (
mediaItem.type === AssetType.OBJECT ||
mediaItem.type === AssetType.SPLAT ||
mediaItem.type === AssetType.SKYBOX
) {
addObject(mediaItem);
}
if (mediaItem.type === AssetType.SHAPE) {
addShape(mediaItem);
void addObject(editor, mediaItem);
} else if (mediaItem.type === AssetType.SHAPE) {
void addShape(editor, mediaItem);
}
}
}
@@ -1127,12 +1127,6 @@ class Editor {
);
}
async recordScene() {
// TODO: timeline-driven frame recording was removed in the refactor;
// rewire video capture to a non-timeline trigger (likely
// VideoGeneration owning its own frame loop).
}
// Render the scene to the camera, this is called in the update.
async renderScene() {
if (
@@ -1141,19 +1135,13 @@ class Editor {
this.rawRenderer &&
this.render_composer
) {
//const then = performance.now();
this.composer.render();
//const now = performance.now();
//console.log("Performance: ", now - then);
//this.rawRenderer.render(this.activeScene.scene, this.render_camera!);
// this.render_composer.render();
} else if (this.renderer && this.render_camera && !this.rendering) {
this.renderer.setSize(this.render_width, this.render_height);
this.renderer.render(this.activeScene.scene, this.render_camera);
} else if (this.rendering && this.renderer) {
this.renderer.setSize(this.render_width, this.render_height);
}
// await this.recordScene();
}
async useCachedMediaTokens(): Promise<boolean> {
@@ -64,7 +64,7 @@ export class VideoGeneration {
// TODO: timeline-driven engineFrameBuffers was removed; rewire video
// capture to a non-timeline trigger (likely an explicit frame loop
// owned by VideoGeneration).
const zipBlob: Blob | undefined = undefined;
const zipBlob: Blob | null = null;
if (!zipBlob) {
await this.handleError(`"Payload blob is null."`, 3000);
}
@@ -31,8 +31,8 @@ import {
gridVisibility,
setGridVisibility,
DomLevels,
setCameraAspectRatio,
} from "~/pages/PageEnigma/signals";
import { setCameraAspect } from "~/pages/PageEnigma/actions";
import { EditorCanvas } from "./comps/EngineCanvases";
import { SceneContainer } from "./comps/SceneContainer";
import { Outliner } from "./comps/Outliner";
@@ -60,8 +60,7 @@ import {
topNavMediaUrl,
} from "~/components/signaled/TopBar/TopBar";
import { uploadPlaneFromMediaToken } from "~/components/reusable/UploadModalMedia/uploadPlane";
import { addObject } from "./signals/objectGroup/addObject";
import { addCharacter } from "./signals/characterGroups/addCharacter";
import { addObject, addCharacter } from "./actions";
import { AssetType, AUTH_STATUS } from "~/enums";
import { v4 as uuidv4 } from "uuid";
import { MediaItem } from "~/pages/PageEnigma/models";
@@ -283,7 +282,8 @@ export const PageEditor = () => {
};
const onAspectRatioSelect = (newRatio: CameraAspectRatio) => {
setCameraAspectRatio(newRatio);
if (!editorEngine) return;
setCameraAspect(editorEngine, newRatio);
};
// MOVE THIS don't throw this in here
// Image drop from gallery/library modal logic
@@ -317,44 +317,32 @@ export const PageEditor = () => {
}
(async () => {
if (!editorEngine) {
console.warn("Cannot drop asset: editor engine not ready");
return;
}
try {
if (item.mediaClass === "dimensional") {
// Handle 3D models
const isCharacter = item.assetType === "character";
const mediaItem: MediaItem = {
version: 1,
type: isCharacter ? AssetType.CHARACTER : AssetType.OBJECT,
media_id: item.id || uuidv4(),
name: item.label || (isCharacter ? "Character" : "3D Object"),
...(worldPosition && {
position: {
x: worldPosition.x,
y: worldPosition.y,
z: worldPosition.z,
},
}),
};
if (isCharacter) {
addCharacter(mediaItem);
await addCharacter(editorEngine, mediaItem, worldPosition);
} else {
addObject(mediaItem);
await addObject(editorEngine, mediaItem, worldPosition);
}
} else {
// Handle images (create image planes)
const mediaItem: MediaItem = {
version: 1,
type: AssetType.OBJECT,
media_id: item.id || uuidv4(),
name: item.label || "Image Plane",
...(worldPosition && {
position: {
x: worldPosition.x,
y: worldPosition.y,
z: worldPosition.z,
},
}),
};
addObject(mediaItem);
await addObject(editorEngine, mediaItem, worldPosition);
await uploadPlaneFromMediaToken({
title: item.label || "Image Plane",
@@ -0,0 +1,28 @@
import * as THREE from "three";
import type Editor from "../Editor/editor";
import { MediaItem } from "../models";
import { usePageEnigmaStore } from "../PageEnigmaStore";
export async function addCharacter(
editor: Editor,
item: MediaItem,
position?: THREE.Vector3,
): Promise<string | undefined> {
const obj = await editor.sceneManager?.create(
item.media_id,
item.name ?? "character",
position ?? new THREE.Vector3(),
);
if (!obj) return undefined;
obj.userData.isCharacter = true;
usePageEnigmaStore.getState().addCharacter({
id: obj.uuid,
kind: "character",
name: obj.name || (item.name ?? "character"),
mediaId: item.media_id,
});
editor.updateOutliner?.();
return obj.uuid;
}
@@ -0,0 +1,26 @@
import * as THREE from "three";
import type Editor from "../Editor/editor";
import { MediaItem } from "../models";
import { usePageEnigmaStore } from "../PageEnigmaStore";
export async function addObject(
editor: Editor,
item: MediaItem,
position?: THREE.Vector3,
): Promise<string | undefined> {
const obj = await editor.sceneManager?.create(
item.media_id,
item.name ?? "object",
position ?? new THREE.Vector3(),
);
if (!obj) return undefined;
usePageEnigmaStore.getState().addObject({
id: obj.uuid,
kind: "object",
name: obj.name || (item.name ?? "object"),
mediaId: item.media_id,
});
editor.updateOutliner?.();
return obj.uuid;
}
@@ -0,0 +1,26 @@
import * as THREE from "three";
import type Editor from "../Editor/editor";
import { MediaItem } from "../models";
import { usePageEnigmaStore } from "../PageEnigmaStore";
export async function addShape(
editor: Editor,
item: MediaItem,
position?: THREE.Vector3,
): Promise<string | undefined> {
const obj = await editor.sceneManager?.create(
item.media_id,
item.name ?? "shape",
position ?? new THREE.Vector3(),
);
if (!obj) return undefined;
usePageEnigmaStore.getState().addShape({
id: obj.uuid,
kind: "shape",
name: obj.name || (item.name ?? "shape"),
mediaId: item.media_id,
});
editor.updateOutliner?.();
return obj.uuid;
}
@@ -0,0 +1,9 @@
import type Editor from "../Editor/editor";
// Editor.deleteObject already removes the THREE.js object via SceneUtils
// and updates the store via the helper that calls
// usePageEnigmaStore.getState().removeSceneObject. This action exists for
// symmetry with addObject and to give callers a single import surface.
export function deleteObject(editor: Editor, id: string): void {
editor.deleteObject(id);
}
@@ -0,0 +1,7 @@
export * from "./addObject";
export * from "./addCharacter";
export * from "./addShape";
export * from "./deleteObject";
export * from "./selectObject";
export * from "./setTransformMode";
export * from "./setCameraAspect";
@@ -0,0 +1,15 @@
import type Editor from "../Editor/editor";
// Selecting in the engine triggers Editor.publishSelect which writes the
// selection into the store, so this action only needs to invoke the engine
// path.
export function selectObject(editor: Editor, id: string): void {
editor.sceneManager?.select_object(id);
}
export function deselectObject(editor: Editor): void {
if (editor.sceneManager) {
editor.sceneManager.selected_objects = undefined;
}
editor.publishSelect();
}
@@ -0,0 +1,14 @@
import type Editor from "../Editor/editor";
import { CameraAspectRatio } from "../enums";
import { setCameraAspectRatio as setCameraAspectSignal } from "../signals/engine";
import { usePageEnigmaStore } from "../PageEnigmaStore";
export function setCameraAspect(
editor: Editor,
ratio: CameraAspectRatio,
): void {
editor.changeRenderCameraAspectRatio(ratio);
usePageEnigmaStore.getState().setCameraAspectRatio(ratio);
// Keep the legacy signal in sync until Phase 5 retires it.
setCameraAspectSignal(ratio);
}
@@ -0,0 +1,17 @@
import type Editor from "../Editor/editor";
import { usePageEnigmaStore, TransformMode } from "../PageEnigmaStore";
import { selectedMode } from "../signals/selectedMode";
const ENGINE_MODE: Record<TransformMode, "translate" | "rotate" | "scale"> = {
move: "translate",
rotate: "rotate",
scale: "scale",
};
export function setTransformMode(editor: Editor, mode: TransformMode): void {
editor.change_mode(ENGINE_MODE[mode]);
usePageEnigmaStore.getState().setTransformMode(mode);
usePageEnigmaStore.getState().setSelectedMode(mode);
// Keep the legacy signal in sync until Phase 5 retires it.
selectedMode.value = mode;
}
@@ -4,12 +4,16 @@ import {
faRectangleVertical,
faSquare,
} from "@fortawesome/pro-solid-svg-icons";
import { useContext } from "react";
import { CameraAspectRatio } from "~/pages/PageEnigma/enums";
import { cameraAspectRatio, setCameraAspectRatio } from "~/pages/PageEnigma/signals";
import { cameraAspectRatio } from "~/pages/PageEnigma/signals";
import { setCameraAspect } from "~/pages/PageEnigma/actions";
import { EngineContext } from "~/pages/PageEnigma/contexts/EngineContext";
import { ButtonDropdown } from "@storyteller/ui-button-dropdown";
export const AspectRatioMenu = () => {
useSignals();
const editor = useContext(EngineContext);
const buttonIcon =
cameraAspectRatio.value === CameraAspectRatio.HORIZONTAL_16_9
? faRectangle
@@ -32,7 +36,8 @@ export const AspectRatioMenu = () => {
: "1:1 Squared";
const handleChangeAspectRatio = (newRatio: CameraAspectRatio) => {
setCameraAspectRatio(newRatio);
if (!editor) return;
setCameraAspect(editor, newRatio);
};
return (
<div className="absolute right-0 top-0 m-2 flex flex-col items-end">
@@ -23,6 +23,8 @@ import { useTabStore } from "~/pages/Stores/TabState";
// import { AssetType } from "~/enums";
import { AssetModal } from "../AssetMenu/AssetModal";
import { selectedMode, transformSpace } from "../../signals/selectedMode";
import { setTransformMode } from "../../actions";
import { TransformMode } from "../../PageEnigmaStore";
import { useSignals } from "@preact/signals-react/runtime";
import { outlinerState } from "../../signals/outliner/outliner";
// eslint-disable-next-line import/no-unresolved
@@ -85,41 +87,12 @@ export const Controls3D = () => {
]);
const handleModeChange = (value: string) => {
selectedMode.value = value;
switch (value) {
case "move":
handleMoveArrows();
break;
case "rotate":
handleRotateArrows();
break;
case "scale":
handleZoomArrows();
break;
default:
console.log("Unknown option");
if (!editorEngine) return;
if (value === "move" || value === "rotate" || value === "scale") {
setTransformMode(editorEngine, value as TransformMode);
}
};
const handleMoveArrows = () => {
if (!editorEngine) {
return;
}
editorEngine.change_mode("translate");
};
const handleRotateArrows = () => {
if (!editorEngine) {
return;
}
editorEngine.change_mode("rotate");
};
const handleZoomArrows = () => {
if (!editorEngine) {
return;
}
editorEngine.change_mode("scale");
};
// ----- TODO LATER - BFlat for auto add 3d model to scene -----
// Function to add the generated 3D model to the scene
@@ -379,12 +352,7 @@ export const Controls3D = () => {
<UploadModalSplat
isOpen={uploadSplatIsShowing}
onClose={() => setUploadSplatIsShowing(false)}
onSuccess={(_buffer, _shouldFlip) => {
setUploadSplatIsShowing(false);
// TODO: addLocalSplat lived on the deleted timeline; rewire to a
// direct editor.addLocalSplat once the imperative engine grows
// that method.
}}
onSuccess={() => setUploadSplatIsShowing(false)}
title="Upload an spz file"
titleIcon={faCube}
/>
@@ -1,7 +1,9 @@
// TODO: Move into shared component.
import { useContext } from "react";
import { MediaItem } from "~/pages/PageEnigma/models";
import { useSignals } from "@preact/signals-react/runtime";
import DndAsset from "~/pages/PageEnigma/DragAndDrop/DndAsset";
import { EngineContext } from "~/pages/PageEnigma/contexts/EngineContext";
import { Badge } from "@storyteller/ui-badge";
import { AssetType } from "~/enums";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -37,6 +39,7 @@ const patchExpressionObjectType = (mediaType: string) => {
export const ItemElement = ({ item }: Props) => {
useSignals();
const editor = useContext(EngineContext);
return (
<div className="group relative w-full select-none overflow-hidden transition-all duration-200">
@@ -55,7 +58,7 @@ export const ItemElement = ({ item }: Props) => {
<div
className="pointer-events-none relative aspect-[16/12] w-full select-none overflow-hidden rounded-xl border-[3px] border-white/5 bg-brand-secondary-600 object-cover object-center transition-all group-hover:border-brand-primary"
onPointerDown={(event) => DndAsset.onPointerDown(event, item)}
onPointerDown={(event) => DndAsset.onPointerDown(event, item, editor)}
style={{ cursor: "grab", pointerEvents: "auto" }}
>
<img
@@ -14,3 +14,13 @@ export interface EditorExpandedI extends Editor {
}
export const EngineContext = createContext<Editor | null>(null);
// Module-level handle on the active Editor instance. EngineProvider keeps
// this in sync with its lifecycle. React components should still use
// useContext(EngineContext) — this exists for non-React callers (and for
// cross-page actions that fire before the 3D tab has mounted).
let activeEditor: Editor | null = null;
export const getActiveEditor = (): Editor | null => activeEditor;
export const setActiveEditor = (editor: Editor | null): void => {
activeEditor = editor;
};
@@ -1,5 +1,5 @@
import { ReactNode, useEffect, useRef, useState } from "react";
import { EngineContext } from "./EngineContext";
import { EngineContext, setActiveEditor } from "./EngineContext";
import Editor, { is3DSceneLoaded } from "~/pages/PageEnigma/Editor/editor";
import { useSignals } from "@preact/signals-react/runtime";
@@ -53,6 +53,7 @@ export const EngineProvider = ({ sceneToken, children }: Props) => {
cacheJsonString: cacheString,
});
setEditor(newEditor);
setActiveEditor(newEditor);
} else if (tab !== "3D") {
if (!activeEditorRef.current) {
return;
@@ -78,6 +79,7 @@ export const EngineProvider = ({ sceneToken, children }: Props) => {
// Unmount/Destructor flow
activeEditorRef.current?.unmountEngine();
activeEditorRef.current = null;
setActiveEditor(null);
sceneContainerSignal.value = null;
editorCanvasSignal.value = null;
camViewCanvasSignal.value = null;
@@ -1,43 +0,0 @@
import { CharacterTrack, MediaItem } from "~/pages/PageEnigma/models";
import { characterGroup } from "~/pages/PageEnigma/signals";
import { usePageEnigmaStore } from "~/pages/PageEnigma/PageEnigmaStore";
export function addCharacter(character: MediaItem) {
// TODO: After editor.ts is migrated, replace with actions/addCharacter which
// calls editor.addCharacter(character) and updates the store in one step.
usePageEnigmaStore.getState().addCharacter({
id: character.object_uuid ?? character.media_id,
kind: "character",
name: character.name ?? "character",
mediaId: character.media_id,
});
addNewCharacter(character);
}
export function addNewCharacter(data: MediaItem) {
const newCharacterGroups = {
...characterGroup.value,
characters: [...characterGroup.value.characters],
};
const newCharacter = {
object_uuid: data.object_uuid,
name: data.name,
media_id: data.media_id,
mediaType: data.media_type,
animationType: data.maybe_animation_type,
muted: false,
minimized: false,
animationClips: [],
positionKeyframes: [],
expressionClips: [],
lipSyncClips: [],
} as CharacterTrack;
newCharacterGroups.characters.push(newCharacter);
newCharacterGroups.characters.sort((charA, charB) =>
charA.object_uuid < charB.object_uuid ? -1 : 1,
);
characterGroup.value = newCharacterGroups;
}
@@ -1,7 +0,0 @@
import { CharacterGroup } from "~/pages/PageEnigma/models";
import { signal } from "@preact/signals-core";
export const characterGroup = signal<CharacterGroup>({
id: "CG1",
characters: [],
});
@@ -1,11 +0,0 @@
import { MediaItem } from "~/pages/PageEnigma/models";
import { characterGroup } from "~/pages/PageEnigma/signals";
export function deleteCharacter(item: MediaItem) {
characterGroup.value = {
...characterGroup.value,
characters: characterGroup.value.characters.filter(
(character) => character.object_uuid !== item.object_uuid,
),
};
}
@@ -1,3 +0,0 @@
export * from "./addCharacter";
export * from "./characterGroup";
export * from "./deleteCharacter";
@@ -1,15 +1,12 @@
export * from "./characterGroups";
export * from "./demoAssets";
export * from "./editorOverlays";
export * from "./engine";
export * from "./dragAndDrop";
export * from "./hotkeys";
export * from "./myMovies";
export * from "./objectGroup";
export * from "./objectPanel";
export * from "./outliner";
export * from "./selectedItem";
export * from "./shape";
export * from "./sidePanel";
export * from "./sizing";
export * from "./stylizeTab";
@@ -1,34 +0,0 @@
import { MediaItem, ObjectTrack, Keyframe } from "~/pages/PageEnigma/models";
import { objectGroup } from "~/pages/PageEnigma/signals";
import { usePageEnigmaStore } from "~/pages/PageEnigma/PageEnigmaStore";
export function addObject(
object: MediaItem & { position?: { x: number; y: number; z: number } }
) {
// TODO: After editor.ts is migrated, replace with actions/addObject which
// calls editor.addObject(object) and updates the store in one step.
usePageEnigmaStore.getState().addObject({
id: object.object_uuid ?? object.media_id,
kind: "object",
name: object.name ?? "object",
mediaId: object.media_id,
});
addObjectToTimeline(object);
}
export function addObjectToTimeline(mediaItem: MediaItem) {
const oldObjectGroup = objectGroup.value;
const newObject = {
object_uuid: mediaItem.object_uuid,
name: mediaItem.name ?? "unknown",
keyframes: [] as Keyframe[],
} as ObjectTrack;
objectGroup.value = {
...oldObjectGroup,
objects: [...oldObjectGroup.objects, newObject].sort((objA, objB) =>
objA.object_uuid < objB.object_uuid ? -1 : 1,
),
};
}
@@ -1,11 +0,0 @@
import { MediaItem } from "~/pages/PageEnigma/models";
import { objectGroup } from "~/pages/PageEnigma/signals";
export function deleteObject(item: MediaItem) {
objectGroup.value = {
...objectGroup.value,
objects: objectGroup.value.objects.filter(
(obj) => obj.object_uuid !== item.object_uuid,
),
};
}
@@ -1,3 +0,0 @@
export * from "./addObject";
export * from "./deleteObject";
export * from "./objectGroup";
@@ -1,7 +0,0 @@
import { signal } from "@preact/signals-core";
import { ObjectGroup } from "~/pages/PageEnigma/models";
export const objectGroup = signal<ObjectGroup>({
id: "OB1",
objects: [],
});
@@ -1,13 +0,0 @@
import { MediaItem } from "~/pages/PageEnigma/models";
import { usePageEnigmaStore } from "~/pages/PageEnigma/PageEnigmaStore";
export function addShape(item: MediaItem) {
// TODO: After editor.ts is migrated, replace with actions/addShape which
// calls editor.addShape(item) and updates the store in one step.
usePageEnigmaStore.getState().addShape({
id: item.object_uuid ?? item.media_id,
kind: "shape",
name: item.name ?? "shape",
mediaId: item.media_id,
});
}