PageEnigma refactor Phase 5b: migrate error, myMovies, editorOverlays signal slices

Three more signal files retired in favour of the Zustand store.

error.ts (deleted):
- ErrorDialog (in ~/components/signaled): reads showErrorDialog /
  errorDialogTitle / errorDialogMessage from the store, calls
  setShowErrorDialog
- SceneTitleInput (in TopBar): handleShowErrorDialog uses the store's
  setErrorDialog action

myMovies.ts (deleted):
- MyMoviesButton (in TopBar): writes via setGenerateMovieId /
  setViewMyMovies
- GenerateModals: useEffect-on-store replaces useSignalEffect; reads
  generateMovieId from store
- MyMovies and Sharing: read viewMyMovies from store, close via
  setViewMyMovies
- Sharing also reads generateMovieId from store

editorOverlays.ts (deleted):
- PageEditor's LoadingDots: reads editorLoader from store
- SceneContainer: reads editorLetterBox from store and passes through
  to Letterbox
- PreviewEngineCamera: reads editorLetterBox + toggleEditorLetterBox
  from store
- save_manager: showEditorLoader/hideEditorLoader become thin wrappers
  over the store actions, keeping the same call sites

signals/index.ts no longer re-exports any of the three deleted files.
This commit is contained in:
Tanish Manku committed 2026-04-28 23:11:52 -07:00
1 parent 4b0618e50b
commit 11a0ac82b6
14 files changed
+81 -121

No files matched your search

@@ -1,32 +1,36 @@
import { useSignals } from "@preact/signals-react/runtime";
import { faTriangleExclamation } from "@fortawesome/pro-solid-svg-icons";
import { useShallow } from "zustand/shallow";
import { Modal } from "@storyteller/ui-modal";
import { Button } from "@storyteller/ui-button";
import {
showErrorDialog,
errorDialogMessage,
errorDialogTitle,
} from "~/pages/PageEnigma/signals";
import { usePageEnigmaStore } from "~/pages/PageEnigma/PageEnigmaStore";
export function ErrorDialog() {
useSignals();
const { showErrorDialog, errorDialogTitle, errorDialogMessage, setShowErrorDialog } =
usePageEnigmaStore(
useShallow((s) => ({
showErrorDialog: s.showErrorDialog,
errorDialogTitle: s.errorDialogTitle,
errorDialogMessage: s.errorDialogMessage,
setShowErrorDialog: s.setShowErrorDialog,
})),
);
return (
<Modal
title={errorDialogTitle.value}
title={errorDialogTitle}
titleIcon={faTriangleExclamation}
titleIconClassName="text-brand-primary"
isOpen={showErrorDialog.value}
onClose={() => (showErrorDialog.value = false)}
isOpen={showErrorDialog}
onClose={() => setShowErrorDialog(false)}
showClose={false}
>
<div>
{errorDialogMessage.value}
{errorDialogMessage}
<div className="flex justify-end">
<Button
type="button"
onClick={() => (showErrorDialog.value = false)}
onClick={() => setShowErrorDialog(false)}
variant="secondary"
>
Close
@@ -3,7 +3,7 @@ import { twMerge } from "tailwind-merge";
import { faFilm } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Button } from "@storyteller/ui-button";
import { generateMovieId, viewMyMovies } from "~/pages/PageEnigma/signals";
import { usePageEnigmaStore } from "~/pages/PageEnigma/PageEnigmaStore";
import { activeWorkflowJobs } from "~/signals";
@@ -18,8 +18,9 @@ export const MyMoviesButton = () => {
<Button
variant="action"
onClick={() => {
generateMovieId.value = "";
viewMyMovies.value = true;
const store = usePageEnigmaStore.getState();
store.setGenerateMovieId("");
store.setViewMyMovies(true);
}}
>
<div className="relative flex items-center gap-2">
@@ -4,11 +4,7 @@ import { useSignals } from "@preact/signals-react/runtime";
import { faPencil, faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { scene, signalScene, authentication } from "~/signals";
import {
showErrorDialog,
errorDialogMessage,
errorDialogTitle,
} from "~/pages/PageEnigma/signals";
import { usePageEnigmaStore } from "~/pages/PageEnigma/PageEnigmaStore";
import { Input } from "@storyteller/ui-input";
import { MediaFilesApi } from "~/Classes/ApiManager/MediaFilesApi";
import { getCreatorIcon, ModelCreator } from "@storyteller/model-list";
@@ -36,9 +32,7 @@ export const SceneTitleInput = ({ pageName }: Props) => {
};
const handleShowErrorDialog = (errorMessage: string) => {
errorDialogTitle.value = "Error";
errorDialogMessage.value = errorMessage;
showErrorDialog.value = true;
usePageEnigmaStore.getState().setErrorDialog("Error", errorMessage);
};
const handleChangeSceneTitle = (e: React.ChangeEvent<HTMLInputElement>) => {
@@ -4,11 +4,15 @@ import { StoryTellerProxyScene } from "../proxy/storyteller_proxy_scene";
import {
setCameraAspectRatio,
hideEditorLoader,
restoreSceneGenerationMetadata,
showEditorLoader,
globalIPAMediaToken,
} from "../signals";
import { usePageEnigmaStore } from "../PageEnigmaStore";
const showEditorLoader = (message?: string) =>
usePageEnigmaStore.getState().showEditorLoader(message);
const hideEditorLoader = () =>
usePageEnigmaStore.getState().hideEditorLoader();
import Editor from "./editor";
import { getArtStyle } from "~/enums";
import { cameras, selectedCameraId, Camera } from "../signals/camera";
@@ -23,7 +23,6 @@ import { useStoryboardPageEnabled } from "@storyteller/ui-settings-modal";
import {
sidePanelWidth,
editorLoader,
cameraAspectRatio,
outlinerIsShowing,
disableHotkeyInput,
@@ -108,6 +107,7 @@ export const PageEditor = () => {
const tabStore = useTabStore();
const storyboardPageEnabled = useStoryboardPageEnabled();
const camAspect = usePageEnigmaStore((s) => s.cameraAspectRatio);
const editorLoader = usePageEnigmaStore((s) => s.editorLoader);
const handleOverlayClick = (event: React.MouseEvent<HTMLDivElement>) => {
event.stopPropagation();
};
@@ -540,9 +540,9 @@ export const PageEditor = () => {
<LoadingDots
className="absolute left-0 top-0 z-50"
isShowing={editorLoader.value.isShowing}
isShowing={editorLoader.isShowing}
type="bricks"
message={editorLoader.value.message}
message={editorLoader.message}
/>
<div className="absolute bottom-6 left-6 z-20 flex items-center gap-3">
@@ -1,9 +1,8 @@
import { useCallback, useState } from "react";
import { useSignals, useSignalEffect } from "@preact/signals-react/runtime";
import { useCallback, useEffect, useState } from "react";
import { MediaFile } from "~/pages/PageEnigma/models";
import { generateMovieId } from "~/pages/PageEnigma/signals";
import { usePageEnigmaStore } from "~/pages/PageEnigma/PageEnigmaStore";
import { ToastTypes } from "~/enums";
import { addToast } from "~/signals";
@@ -12,8 +11,9 @@ import { Sharing } from "~/pages/PageEnigma/comps/GenerateModals/Sharing";
import { MediaFilesApi } from "~/Classes/ApiManager/MediaFilesApi";
export function GenerateModals() {
useSignals();
const [mediaFile, setMediaFile] = useState<MediaFile | null>(null);
const generateMovieId = usePageEnigmaStore((s) => s.generateMovieId);
const setGenerateMovieId = usePageEnigmaStore((s) => s.setGenerateMovieId);
const GetMediaFileByToken = useCallback(async (movieId: string) => {
const mediaFilesApi = new MediaFilesApi();
@@ -30,14 +30,15 @@ export function GenerateModals() {
`Unknown Error in Getting Movie (token=${movieId}`,
);
}, []);
const setMovieId = useCallback((movieId: string) => {
generateMovieId.value = movieId;
}, []);
useSignalEffect(() => {
if (generateMovieId.value) {
GetMediaFileByToken(generateMovieId.value);
const setMovieId = useCallback(
(movieId: string) => setGenerateMovieId(movieId),
[setGenerateMovieId],
);
useEffect(() => {
if (generateMovieId) {
GetMediaFileByToken(generateMovieId);
}
});
}, [generateMovieId, GetMediaFileByToken]);
if (!mediaFile) {
return <MyMovies setMovieId={setMovieId} />;
@@ -4,7 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { CompletedCard } from "~/pages/PageEnigma/comps/GenerateModals/CompletedCard";
import { InProgressCard } from "~/pages/PageEnigma/comps/GenerateModals/InProgressCard";
import { Modal } from "@storyteller/ui-modal";
import { viewMyMovies } from "~/pages/PageEnigma/signals";
import { usePageEnigmaStore } from "~/pages/PageEnigma/PageEnigmaStore";
import { activeWorkflowJobs, userMovies } from "~/signals";
interface Props {
@@ -13,6 +13,8 @@ interface Props {
export function MyMovies({ setMovieId }: Props) {
useSignals();
const viewMyMovies = usePageEnigmaStore((s) => s.viewMyMovies);
const setViewMyMovies = usePageEnigmaStore((s) => s.setViewMyMovies);
return (
<Modal
@@ -20,10 +22,10 @@ export function MyMovies({ setMovieId }: Props) {
titleIcon={faFilm}
className="max-w-4xl"
childPadding={false}
isOpen={viewMyMovies.value}
isOpen={viewMyMovies}
onClose={() => {
setMovieId("");
viewMyMovies.value = false;
setViewMyMovies(false);
}}
>
<div className="h-[560px] overflow-y-auto overflow-x-hidden rounded-b-lg">
@@ -1,5 +1,5 @@
import { useSignals } from "@preact/signals-react/runtime";
import { useState } from "react";
import { useShallow } from "zustand/shallow";
import { MediaFile } from "~/pages/PageEnigma/models";
import { Input } from "@storyteller/ui-input";
import { Label } from "@storyteller/ui-label";
@@ -13,7 +13,7 @@ import {
faLink,
} from "@fortawesome/pro-solid-svg-icons";
import SocialButton from "./SocialButton";
import { generateMovieId, viewMyMovies } from "~/pages/PageEnigma/signals";
import { usePageEnigmaStore } from "~/pages/PageEnigma/PageEnigmaStore";
import dayjs from "dayjs";
import { downloadFile } from "~/pages/PageEnigma/comps/GenerateModals/utils/downloadFile";
import { GetCdnOrigin } from "~/api/GetCdnOrigin";
@@ -24,7 +24,13 @@ interface Props {
}
export function Sharing({ mediaFile, setMediaFile }: Props) {
useSignals();
const { generateMovieId, viewMyMovies, setViewMyMovies } = usePageEnigmaStore(
useShallow((s) => ({
generateMovieId: s.generateMovieId,
viewMyMovies: s.viewMyMovies,
setViewMyMovies: s.setViewMyMovies,
})),
);
const mediaTitle = mediaFile?.maybe_title ?? mediaFile?.token;
// TODO: ApiManager should provide all endpoints
@@ -56,17 +62,17 @@ export function Sharing({ mediaFile, setMediaFile }: Props) {
return (
<Modal
title={generateTitle()}
titleIcon={generateMovieId.value ? faChevronLeft : faFilm}
titleIcon={generateMovieId ? faChevronLeft : faFilm}
titleIconClassName="text-white/60 hover:text-white/80 transition-colors duration-150"
onTitleIconClick={
generateMovieId.value ? () => setMediaFile(null) : undefined
generateMovieId ? () => setMediaFile(null) : undefined
}
className="max-w-6xl"
childPadding={false}
isOpen={viewMyMovies.value}
isOpen={viewMyMovies}
width={1049}
onClose={() => {
viewMyMovies.value = false;
setViewMyMovies(false);
setMediaFile(null);
}}
>
@@ -9,8 +9,6 @@ import {
} from "@fortawesome/pro-solid-svg-icons";
import { useShallow } from "zustand/shallow";
import {
editorLetterBox,
toggleEditorLetterBox,
sidePanelHeight,
outlinerIsShowing,
} from "~/pages/PageEnigma/signals";
@@ -25,12 +23,15 @@ import { useEffect } from "react";
export const PreviewEngineCamera = () => {
useSignals();
const { camAspect, editorState } = usePageEnigmaStore(
useShallow((s) => ({
camAspect: s.cameraAspectRatio,
editorState: s.editorState,
})),
);
const { camAspect, editorState, editorLetterBox, toggleEditorLetterBox } =
usePageEnigmaStore(
useShallow((s) => ({
camAspect: s.cameraAspectRatio,
editorState: s.editorState,
editorLetterBox: s.editorLetterBox,
toggleEditorLetterBox: s.toggleEditorLetterBox,
})),
);
const handleButtonCameraView = () => {
// TODO: wire to editor.toggleCameraView() once editor.ts is migrated.
@@ -113,7 +114,7 @@ export const PreviewEngineCamera = () => {
{editorState === EditorStates.CAMERA_VIEW && (
<Tooltip content="Toggle Letterbox" position={"top"}>
<ButtonIcon
icon={editorLetterBox.value ? faBlinds : faBlindsRaised}
icon={editorLetterBox ? faBlinds : faBlindsRaised}
onClick={() => toggleEditorLetterBox()}
className="h-7 w-7"
/>
@@ -1,24 +1,21 @@
import { useCallback, useContext } from "react";
import { useCallback } from "react";
import { useSignals } from "@preact/signals-react/runtime";
import { pageHeight, pageWidth } from "~/signals";
import { editorLetterBox } from "~/pages/PageEnigma/signals";
import { EngineContext, sceneContainerSignal } from "~/pages/PageEnigma/contexts/EngineContext";
import { sceneContainerSignal } from "~/pages/PageEnigma/contexts/EngineContext";
import { usePageEnigmaStore } from "~/pages/PageEnigma/PageEnigmaStore";
import { Letterbox } from "./Letterbox";
export const SceneContainer = ({ children }: { children: React.ReactNode }) => {
useSignals();
const editorLetterBox = usePageEnigmaStore((s) => s.editorLetterBox);
const containerWidth = pageWidth.value;
const containerHeight = pageHeight.value - 56;
const callbackRef = useCallback(
(node: HTMLDivElement) => {
if (node) {
sceneContainerSignal.value = node;
}
},
[],
);
const callbackRef = useCallback((node: HTMLDivElement) => {
if (node) {
sceneContainerSignal.value = node;
}
}, []);
return (
<div
@@ -32,7 +29,7 @@ export const SceneContainer = ({ children }: { children: React.ReactNode }) => {
>
{children}
<Letterbox
isShowing={editorLetterBox.value}
isShowing={editorLetterBox}
width={containerWidth}
height={containerHeight}
/>
@@ -1,30 +0,0 @@
import { signal } from "@preact/signals-core";
export const editorLoader = signal<{
isShowing: boolean;
message: string | undefined;
}>({
isShowing: false,
message: "Loading Editor Engine 🦊",
});
export const showEditorLoader = (message?: string) => {
editorLoader.value = {
isShowing: true,
message,
};
};
export const hideEditorLoader = () => {
editorLoader.value = {
isShowing: false,
message: editorLoader.value.message,
// it is important to keep the message so the loader fades out nicely
};
};
export const editorLetterBox = signal<boolean>(true);
export const toggleEditorLetterBox = (newState?: boolean) => {
editorLetterBox.value =
newState !== undefined ? newState : !editorLetterBox.value;
};
@@ -1,13 +0,0 @@
import { signal } from "@preact/signals-core";
export const showErrorDialog = signal(false);
export const errorDialogTitle = signal("Error!");
export const errorDialogMessage = signal("Something went wrong.");
export function setErrorDialogTitle(errorTitle: string) {
errorDialogTitle.value = errorTitle;
}
export function setErrorDialogMessage(errorMessage: string) {
errorDialogMessage.value = errorMessage;
}
@@ -1,14 +1,11 @@
export * from "./demoAssets";
export * from "./editorOverlays";
export * from "./engine";
export * from "./dragAndDrop";
export * from "./hotkeys";
export * from "./myMovies";
export * from "./objectPanel";
export * from "./outliner";
export * from "./selectedItem";
export * from "./sidePanel";
export * from "./sizing";
export * from "./stylizeTab";
export * from "./error";
export * from "./assetModal";
@@ -1,4 +0,0 @@
import { signal } from "@preact/signals-core";
export const viewMyMovies = signal(false);
export const generateMovieId = signal("");