diff --git a/app/components/ButtonDropdown.tsx b/app/components/ButtonDropdown.tsx index bda384101a..6cbfb83cb1 100644 --- a/app/components/ButtonDropdown.tsx +++ b/app/components/ButtonDropdown.tsx @@ -19,6 +19,7 @@ interface ButtonDropdownProps { onClick?: () => void; disabled?: boolean; divider?: boolean; + onDialogOpen?: ()=>void; dialogProps?: { title: string; content: React.ReactNode; @@ -51,6 +52,9 @@ export const ButtonDropdown = ({ if (option.onClick) { option.onClick(); } + if (option.onDialogOpen) { + option.onDialogOpen(); + } if (option.dialogProps) { setSelectedOptionIndex(index); setIsOpen(true); diff --git a/app/contexts/EngineProvider.tsx b/app/contexts/EngineProvider.tsx index 2394554bba..e8f07bdd56 100644 --- a/app/contexts/EngineProvider.tsx +++ b/app/contexts/EngineProvider.tsx @@ -1,27 +1,39 @@ -import { ReactNode, useEffect, useState } from "react"; +import { ReactNode, useEffect, useContext, useState } from "react"; + +import { AuthenticationContext } from "~/contexts/Authentication"; +import { AppUiContext } from "~/contexts/AppUiContext"; +import { EngineContext } from "./EngineContext"; +import { signalScene } from "~/store"; import Editor from "~/pages/PageEnigma/js/editor"; -import { EngineContext } from "./EngineContext"; - interface Props { children: ReactNode; } -export const EngineProvider = ({ children }: Props) => { +export const EngineProvider = ({ + children, +}: Props) => { const [editor, setEditor] = useState(null); + const [, dispatchAppUiState] = useContext(AppUiContext); + const {authState} = useContext(AuthenticationContext); useEffect(() => { setEditor((curr) => { if (curr !== null) { console.warn("Editor Engine already exist"); return curr; - } else { - return new Editor(); + } else if (authState.sessionToken && authState.userInfo ){ + return new Editor({ + dispatchAppUiState, + authState, + signalScene, + }); } + return curr; }); // } - }, []); + }, [dispatchAppUiState, authState]); return ( {children} diff --git a/app/modules/TopBar/TopBar.tsx b/app/modules/TopBar/TopBar.tsx index 64c2053b5f..686fef35ab 100644 --- a/app/modules/TopBar/TopBar.tsx +++ b/app/modules/TopBar/TopBar.tsx @@ -1,10 +1,10 @@ -import { useState } from "react"; -import { useLocation } from "@remix-run/react"; +import { useState } from 'react'; +import { useLocation, useParams } from "@remix-run/react"; import { useSignals } from "@preact/signals-react/runtime"; import { faChevronLeft } from "@fortawesome/pro-solid-svg-icons"; import { ButtonLink, Input } from "~/components"; import { AuthButtons } from "./AuthButtons"; -import { sceneTitle } from "~/store"; +import { scene } from "~/store"; import { showErrorDialog, errorDialogMessage, @@ -12,27 +12,35 @@ import { } from "~/pages/PageEnigma/store"; import { MyMoviesButton } from "~/modules/TopBar/MyMoviesButton"; +import { getCurrentLocationWithoutParams } from '~/utilities'; + +function isEditorPath(path:string){ + if ( path === "/" ) return true; + if ( path === "/idealenigma/" ) return true; + return false; +} interface Props { pageName: string; } export const TopBar = ({ pageName }: Props) => { useSignals(); - const currentLocation = useLocation().pathname; + const currentLocation = getCurrentLocationWithoutParams(useLocation().pathname, useParams()); + const [isValid, setIsValid] = useState(true); const handleShowErrorDialog = () => { errorDialogTitle.value = "Error"; errorDialogMessage.value = "Scene name can not be empty."; showErrorDialog.value = true; }; - const handleChangeSceneTitle = (e: React.ChangeEvent) => { - sceneTitle.value = e.target.value; - if (sceneTitle.value !== "") { + const handleChangeSceneTitle = (e: React.ChangeEvent)=>{ + scene.value.title = e.target.value; + if (scene.value.title !== "") { setIsValid(true); } - }; - const validateSceneTitle = (e: React.FocusEvent) => { - if (sceneTitle.value === "") { + } + const validateSceneTitle = (e: React.FocusEvent)=>{ + if (scene.value.title === "") { setIsValid(false); handleShowErrorDialog(); e.target.focus(); @@ -52,26 +60,24 @@ export const TopBar = ({ pageName }: Props) => { alt="Logo FakeYou StoryTeller.ai" /> - {currentLocation !== "/" && ( + {!isEditorPath(currentLocation) && ( - Back to Dashboard + Back to Editor )} -
-
- {pageName} - / - -
+
+ {pageName} + / +
diff --git a/app/routes/$.tsx b/app/pages/Page404/index.tsx similarity index 86% rename from app/routes/$.tsx rename to app/pages/Page404/index.tsx index 78d0f29049..c9d910023d 100644 --- a/app/routes/$.tsx +++ b/app/pages/Page404/index.tsx @@ -1,13 +1,12 @@ -import { json } from "@remix-run/router"; -import { withProtectionRoute } from "~/modules/withProtectedRoute"; - import { faChevronLeft } from "@fortawesome/pro-solid-svg-icons"; import { P, H1, ButtonLink } from "~/components"; +import { json } from "@remix-run/router"; + export const loader = () => { return json(null, { status: 404 }); }; -const Page404 = withProtectionRoute(()=>{ +export const Page404 = () => { return(
{ Back to Dashboard
- ) -}); + ); +}; export default Page404; diff --git a/app/pages/PageEnigma/PageEnigma.tsx b/app/pages/PageEnigma/PageEnigma.tsx index 41dacc9b84..f0f9f5ccba 100644 --- a/app/pages/PageEnigma/PageEnigma.tsx +++ b/app/pages/PageEnigma/PageEnigma.tsx @@ -7,12 +7,16 @@ import { LoadingBar } from "~/components"; import { GenerateModals } from "~/pages/PageEnigma/comps/GenerateModals/GenerateModals"; import { ErrorDialog } from "~/components/ErrorDialog"; -export const PageEnigma = () => { +export const PageEnigma = ({ + sceneToken +}:{ + sceneToken?:string +}) => { useInferenceJobManager(); return ( - + diff --git a/app/pages/PageEnigma/PageEnigmaComponent.tsx b/app/pages/PageEnigma/PageEnigmaComponent.tsx index 2b7a176da7..5a62127df8 100644 --- a/app/pages/PageEnigma/PageEnigmaComponent.tsx +++ b/app/pages/PageEnigma/PageEnigmaComponent.tsx @@ -1,27 +1,24 @@ import { useContext, useEffect } from "react"; - +import { AuthenticationContext } from "~/contexts/Authentication"; import { AppUiContext } from "~/contexts/AppUiContext"; import { EngineContext } from "~/contexts/EngineContext"; import { PageEditor } from "~/pages/PageEnigma/PageEditor"; -import { useParams } from "@remix-run/react"; +import { signalScene } from "~/store"; -export const PageEnigmaComponent = () => { - const [, dispatchAppUiState] = useContext(AppUiContext); +export const PageEnigmaComponent = ({ + sceneToken +}: { + sceneToken?: string; +}) => { const editor = useContext(EngineContext); - const params = useParams(); useEffect(() => { - if (editor && editor.can_initialize && dispatchAppUiState !== null) { - const sceneToken = params["sceneToken"]; - - editor.initialize( - { - dispatchAppUiState, - }, - sceneToken, + if (editor && editor.can_initialize) { + editor.initialize({ + sceneToken: sceneToken || ""} ); } - }, [editor, dispatchAppUiState, params]); + }, [editor, sceneToken]); return ; }; diff --git a/app/pages/PageEnigma/comps/ControlsTopButtons/ControlsTopButtons.tsx b/app/pages/PageEnigma/comps/ControlsTopButtons/ControlsTopButtons.tsx index 73d313d8b7..34f20109e3 100644 --- a/app/pages/PageEnigma/comps/ControlsTopButtons/ControlsTopButtons.tsx +++ b/app/pages/PageEnigma/comps/ControlsTopButtons/ControlsTopButtons.tsx @@ -1,48 +1,86 @@ import { useContext, useState } from "react"; -import { sceneTitle } from "~/store"; +import { useParams, useLocation, useNavigate } from "@remix-run/react"; + +import { useSignalEffect } from "@preact/signals-react/runtime"; +import { scene } from "~/store"; + +import { EngineContext } from "~/contexts/EngineContext"; +import { AuthenticationContext } from "~/contexts/Authentication"; +import { ToasterContext, ToastTypes } from "~/contexts/ToasterContext"; + +import { faFile } from "@fortawesome/pro-solid-svg-icons"; import { ButtonDropdown, Input } from "~/components"; import { ButtonDialogue } from "~/modules/ButtonDialogue"; -import { EngineContext } from "~/contexts/EngineContext"; -import { ToasterContext, ToastTypes } from "~/contexts/ToasterContext"; -// import { APPUI_ACTION_TYPES } from "app/reducers"; import { TestFeaturesButtons } from "./TestFeaturesButtons"; import { Help } from "./Help"; -import { faFile } from "@fortawesome/pro-solid-svg-icons"; import { LoadScene } from "./LoadScene"; import { NewSceneFromTemplate } from "./NewSceneFromTemplate"; -export const ControlsTopButtons = () => { - const editorEngine = useContext(EngineContext); +import { getCurrentLocationWithoutParams } from "~/utilities"; - const [sceneName, setSceneName] = useState(""); +export const ControlsTopButtons = () => { + const params = useParams(); + const location = useLocation(); + const navigate = useNavigate(); + + const editorEngine = useContext(EngineContext); + const {authState} = useContext(AuthenticationContext); + + const [sceneTitleInput, setSceneTitleInput] = useState(""); const [sceneToken, setSceneToken] = useState(""); const { addToast } = useContext(ToasterContext); + const handleChangeSceneTitleInput = (e: React.ChangeEvent) => { + setSceneTitleInput(e.target.value); + } + const clearSceneTitleInput = ()=>{ + setSceneTitleInput(""); + } + const handleButtonSave = async () => { - // console.log(`SceneName is ${sceneName}`); - // const sceneMediaToken = await editorEngine?.saveScene(sceneName); - console.log(`SceneName is ${sceneTitle.value}`); - const sceneMediaToken = await editorEngine?.saveScene(sceneTitle.value); + console.log(`SceneName is ${scene.value.title}`); + const sceneMediaToken = await editorEngine?.saveScene(scene.value.title || ""); if (sceneMediaToken) { addToast(ToastTypes.SUCCESS, sceneMediaToken); } }; + const handleButtonNewFromTemplate = () => { + editorEngine?.loadScene(sceneToken) + .catch((err) => { + addToast(ToastTypes.ERROR, err.message); + }); + }; + const handleButtonLoadScene = () => { - console.log(`Scene Token is ${sceneToken}`); - editorEngine?.loadScene(sceneToken).catch((err) => { - addToast(ToastTypes.ERROR, err.message); - }); + editorEngine?.loadScene(sceneToken) + .catch((err) => { + addToast(ToastTypes.ERROR, err.message); + }); }; const handleSceneSelection = (token: string) => { setSceneToken(token); - console.log(`Selected Scene Token is ${token}`); }; - const handleChangeSceneTitle = (e: React.ChangeEvent) => { - sceneTitle.value = e.target.value; - }; + useSignalEffect(()=>{ + console.log("useSignalEffect in File Buttons"); + console.log(scene.value); + if (!scene.value.isInitializing){ + setSceneTitleInput(scene.value.title || ""); + if(scene.value.token === undefined){ + console.log("delete param from url if it exists"); + const currentLocation = getCurrentLocationWithoutParams(location.pathname, params); + console.log(`should navigate to ${currentLocation}`) + navigate(currentLocation); + } + if(scene.value.token && scene.value.token !== params.sceneToken){ + console.log("nav to the next param on the url"); + const currentLocation = getCurrentLocationWithoutParams(location.pathname, params); + navigate(currentLocation+scene.value.token); + } + } + }); return (
@@ -54,27 +92,31 @@ export const ControlsTopButtons = () => { { label: "New scene", description: "Ctrl+N", + onDialogOpen: ()=>{ + setSceneTitleInput("Untitled New Scene"); + }, dialogProps: { title: "Create a New Scene", content: ( { - setSceneName(e.target.value); - }} + onChange={handleChangeSceneTitleInput} autoComplete="false" /> ), confirmButtonProps: { label: "Create", - disabled: sceneName === "", - onClick: () => editorEngine?.newScene(), + disabled: sceneTitleInput === "", + onClick: () => editorEngine?.newScene( + sceneTitleInput + ), }, closeButtonProps: { label: "Cancel", }, showClose: true, - onClose: () => setSceneName(""), + onClose: clearSceneTitleInput, }, }, { @@ -88,7 +130,7 @@ export const ControlsTopButtons = () => { confirmButtonProps: { label: "Create", disabled: sceneToken === "", - onClick: handleButtonLoadScene, + onClick: handleButtonNewFromTemplate, }, closeButtonProps: { label: "Cancel", @@ -116,24 +158,22 @@ export const ControlsTopButtons = () => { }, }, { - disabled: true, // save scene should be disabled if there are no changes + disabled: !scene.value.isModified || (scene.value.ownerToken !== authState.userInfo?.user_token), + // save scene should be disabled if there are no changes label: "Save scene", description: "Ctrl+S", dialogProps: { title: "Save Scene", content: ( { - // setSceneName(e.target.value); - // }} - onChange={handleChangeSceneTitle} + onChange={handleChangeSceneTitleInput} /> ), confirmButtonProps: { label: "Save", - disabled: sceneName === "", + disabled: sceneTitleInput === "", onClick: handleButtonSave, }, closeButtonProps: { @@ -144,23 +184,24 @@ export const ControlsTopButtons = () => { divider: true, }, { + disabled: !scene.value.isModified || !scene.value.token, label: "Save scene as copy", description: "Ctrl+Shift+S", + onDialogOpen: ()=>{ + setSceneTitleInput("Copy of " + sceneTitleInput); + }, dialogProps: { title: "Save Scene as Copy", content: ( { - // setSceneName(e.target.value); - // }} - onChange={handleChangeSceneTitle} + onChange={handleChangeSceneTitleInput} /> ), confirmButtonProps: { label: "Save", - disabled: sceneName === "", + disabled: sceneTitleInput === "", onClick: handleButtonSave, }, closeButtonProps: { @@ -172,25 +213,6 @@ export const ControlsTopButtons = () => { ]} /> - {/* - { - setMediaToken(e.target.value); - }} - /> - */} - { const thumbnail = dragItem.value.thumbnail ? dragItem.value.thumbnail - : `/resources/images/default-covers/${dragItem.value.imageIndex}.webp`; + : `/resources/images/default-covers/${dragItem.value.imageIndex ||0}.webp`; if (overTimeline.value) { if (canDrop.value && DndAsset.overElement) { diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ItemElement.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ItemElement.tsx index 8a1c9101b5..a2f3ce3552 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ItemElement.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ItemElement.tsx @@ -11,7 +11,7 @@ export const ItemElement = ({ item }: Props) => { useSignals(); const thumbnail = item.thumbnail ? item.thumbnail - : `/resources/images/default-covers/${item.imageIndex}.webp`; + : `/resources/images/default-covers/${item.imageIndex || 0}.webp`; return (
void )| null, ): Promise { const api_base_url = environmentVariables.value.BASE_API; const url = `${api_base_url}/v1/media_files/file/${scene_media_file_token}`; @@ -118,6 +119,17 @@ export class APIManager { } const json = await JSON.parse(await response.text()); + if(json && json.media_file && signalScene !== null){ + if(json.media_file.maybe_title === null){ + console.warn(`Scene /w Token: ${scene_media_file_token} has no title`); + } + signalScene({ + title: json.media_file.maybe_title || "Untitled Scene", + token: scene_media_file_token, + ownerToken: json.media_file.maybe_creator_user.user_token, + isModified: false, + }); + } const bucket_path = json["media_file"]["public_bucket_path"]; const media_base_url = environmentVariables.value.GOOGLE_API; const media_url = `${media_base_url}/vocodes-public${bucket_path}`; // gets you a bucket path @@ -136,7 +148,7 @@ export class APIManager { reader.readAsText(blob); }); - console.log(`loadSceneState: ${JSON.stringify(json_result)}`); + // console.log(`loadSceneState: ${JSON.stringify(json_result)}`); return json_result; } diff --git a/app/pages/PageEnigma/js/editor.ts b/app/pages/PageEnigma/js/editor.ts index 40635eb3d9..b5baa1930a 100644 --- a/app/pages/PageEnigma/js/editor.ts +++ b/app/pages/PageEnigma/js/editor.ts @@ -24,7 +24,7 @@ import { LipSyncEngine } from "./lip_sync_engine.js"; import { AnimationEngine } from "./animation_engine.js"; import { PointerLockControls } from "three/addons/controls/PointerLockControls.js"; -import { APPUI_ACTION_TYPES } from "../../../reducers"; +import { APPUI_ACTION_TYPES, AppUiState, AppUiAction } from "../../../reducers"; import { ClipGroup, ClipType } from "~/pages/PageEnigma/models/track"; import { XYZ } from "../datastructures/common"; @@ -40,8 +40,20 @@ import { EditorStates, previewSrc, } from "~/pages/PageEnigma/store/engine"; +import { AuthState } from "~/contexts/Authentication/types"; // Main editor class that will call everything else all you need to call is " initialize() ". + +export type EditorConstructorConfig = { + dispatchAppUiState: React.Dispatch; + signalScene: (data:any)=>void; + authState: AuthState; +}; + +export type EditorInitializeConfig = { + sceneToken: string; +}; + class Editor { version: number; activeScene: Scene; @@ -105,7 +117,9 @@ class Editor { can_initialize: boolean; switchPreviewToggle: boolean; - dispatchAppUiState: any; // todo figure out the type + dispatchAppUiState: React.Dispatch; + authState: AuthState; + signalScene: ((data:any)=>void); render_width: number; render_height: number; @@ -126,7 +140,11 @@ class Editor { // global names of scene entities camera_name: string; - constructor() { + constructor({ + dispatchAppUiState, + signalScene, + authState, + }:EditorConstructorConfig) { console.log( "If you see this message twice! then it rendered twice, if you see it once it's all good.", ); @@ -231,8 +249,10 @@ class Editor { this.current_frame = 0; - // Dispatcher - this.dispatchAppUiState = null; + //setup reactland Callbacks + this.dispatchAppUiState = dispatchAppUiState; + this.signalScene = signalScene; + this.authState = authState; // Scene State this.current_scene_media_token = null; @@ -251,15 +271,9 @@ class Editor { ); } - initialize(config: any, sceneToken: any) { - //setup reactland Callbacks - this.dispatchAppUiState = config.dispatchAppUiState; - - // this is called by the parent for some reason - // this.dispatchAppUiState({ - // type: APPUI_ACTION_TYPES.SHOW_EDITOR_LOADER - // }); - + initialize({ + sceneToken + }:EditorInitializeConfig) { if (this.can_initialize == false) { console.log("Editor Already Initialized"); return; @@ -426,6 +440,13 @@ class Editor { if (this.isEmpty(sceneToken) == false) { this.loadScene(sceneToken); + }else{ + this.signalScene({ + title: "Untitled New Scene", + token: undefined, + ownerToken: this.authState.userInfo?.user_token, + isModified: false, + }); } document.addEventListener("mouseover", (event) => { @@ -455,7 +476,7 @@ class Editor { loadingBarIsShowing.value = false; } - public async newScene() { + public async newScene(sceneTitleInput:string) { this.activeScene.clear(); this.audio_engine = new AudioEngine(); this.emotion_engine = new EmotionEngine(this.version); @@ -476,6 +497,13 @@ class Editor { this.camera_name, ); this.cam_obj = this.activeScene.get_object_by_name(this.camera_name); + const sceneTitle = sceneTitleInput && sceneTitleInput!=="" ? sceneTitleInput : "Untitled New Scene"; + this.signalScene({ + title: sceneTitle, + token: undefined, + ownerToken: this.authState.userInfo?.user_token, + isModified: false + }) } // Token comes in from the front end to load the scene from the site. @@ -496,14 +524,13 @@ class Editor { this.current_scene_media_token = scene_media_token; const scene_json = await this.api_manager - .loadSceneState(this.current_scene_media_token) + .loadSceneState(this.current_scene_media_token, this.signalScene) .catch((err) => { this.dispatchAppUiState({ type: APPUI_ACTION_TYPES.HIDE_EDITOR_LOADER, }); throw err; }); - const proxyScene = new StoryTellerProxyScene( this.version, this.activeScene, diff --git a/app/routes/$sceneToken.tsx b/app/routes/$sceneToken.tsx new file mode 100644 index 0000000000..28eb13f9ed --- /dev/null +++ b/app/routes/$sceneToken.tsx @@ -0,0 +1,47 @@ +import type { HeadersFunction } from "@remix-run/deno"; +import { useParams } from "@remix-run/react"; + +import { withProtectionRoute } from "~/modules/withProtectedRoute"; +import { PageEnigma } from "~/pages/PageEnigma"; +// import { Page404 } from "~/pages/Page404"; + + +// NB(bt): Netlify's custom directives cannot set headers for pages served by Remix, +// so we must specify them here instead. Netlify header rules (in either _headers or +// netlify.toml) will apply to statically served files (images, css, js), however. +// Setting these headers in `vite.config.ts` will also fail to apply them in +// production. +export const headers: HeadersFunction = ({ + actionHeaders, + errorHeaders, + loaderHeaders, + parentHeaders, +}) => ({ + // NB(bt): The following two headers are required for enabling SharedArrayBuffer (required by + // ffmpeg.wasm) in the post-SPECTRE world. Without these headers being sent by the page, the + // page cannot use SharedArrayBuffer. Downstream services must also set relevant CORS headers. + // + // See the following documentation on how this complex system works: + // + // - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer#security_requirements + // - https://stackoverflow.com/questions/73275184/enable-shared-array-buffer-in-cross-domain + // - https://blog.logrocket.com/understanding-sharedarraybuffer-and-cross-origin-isolation/ + // - https://github.com/ffmpegwasm/react-app/issues/3 + // + "Cross-Origin-Embedder-Policy": "require-corp", + "Cross-Origin-Opener-Policy": "same-origin", +}); + +const Index = withProtectionRoute(()=>{ + const params = useParams(); + return ( ); + + //NOTE: wil: CODE SAMPLE FOR ROUTING 404 + // PLEAE DO NOT DELETE THE BELOW + // if (params.sceneToken && params.sceneToken.slice(0,2) === "m_" && params.sceneToken.length === 32){ + // return ( ); + // }else{ + // return (); + // } +}); +export default Index; diff --git a/app/routes/IdealEnigma.$sceneToken.tsx b/app/routes/IdealEnigma.$sceneToken.tsx index dc7b46676a..003b850df9 100644 --- a/app/routes/IdealEnigma.$sceneToken.tsx +++ b/app/routes/IdealEnigma.$sceneToken.tsx @@ -1,6 +1,7 @@ import type { HeadersFunction } from "@remix-run/deno"; import { withProtectionRoute } from "~/modules/withProtectedRoute"; import { PageEnigma } from "~/pages/PageEnigma"; +import { useParams } from "@remix-run/react"; // NB(bt): Netlify's custom directives cannot set headers for pages served by Remix, // so we must specify them here instead. Netlify header rules (in either _headers or @@ -28,5 +29,9 @@ export const headers: HeadersFunction = ({ "Cross-Origin-Opener-Policy": "same-origin", }); -const IdealEnigma = withProtectionRoute(()=>); + +const IdealEnigma = withProtectionRoute(()=>{ + const params= useParams(); + return +}); export default IdealEnigma; diff --git a/app/routes/IdealEnigma._index.tsx b/app/routes/IdealEnigma._index.tsx new file mode 100644 index 0000000000..2fd22cee81 --- /dev/null +++ b/app/routes/IdealEnigma._index.tsx @@ -0,0 +1,31 @@ +import type { HeadersFunction } from "@remix-run/deno"; +import { withProtectionRoute } from "~/modules/withProtectedRoute"; +import { PageEnigma } from "~/pages/PageEnigma"; +// NB(bt): Netlify's custom directives cannot set headers for pages served by Remix, +// so we must specify them here instead. Netlify header rules (in either _headers or +// netlify.toml) will apply to statically served files (images, css, js), however. +// Setting these headers in `vite.config.ts` will also fail to apply them in +// production. +export const headers: HeadersFunction = ({ + actionHeaders, + errorHeaders, + loaderHeaders, + parentHeaders, +}) => ({ + // NB(bt): The following two headers are required for enabling SharedArrayBuffer (required by + // ffmpeg.wasm) in the post-SPECTRE world. Without these headers being sent by the page, the + // page cannot use SharedArrayBuffer. Downstream services must also set relevant CORS headers. + // + // See the following documentation on how this complex system works: + // + // - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer#security_requirements + // - https://stackoverflow.com/questions/73275184/enable-shared-array-buffer-in-cross-domain + // - https://blog.logrocket.com/understanding-sharedarraybuffer-and-cross-origin-isolation/ + // - https://github.com/ffmpegwasm/react-app/issues/3 + // + "Cross-Origin-Embedder-Policy": "require-corp", + "Cross-Origin-Opener-Policy": "same-origin", +}); + +const IdealEnigma = withProtectionRoute(()=>); +export default IdealEnigma; diff --git a/app/routes/_index.($sceneToken).tsx b/app/routes/_index.tsx similarity index 99% rename from app/routes/_index.($sceneToken).tsx rename to app/routes/_index.tsx index 8535f8ba3b..97d624d01d 100644 --- a/app/routes/_index.($sceneToken).tsx +++ b/app/routes/_index.tsx @@ -29,4 +29,5 @@ export const headers: HeadersFunction = ({ }); const Index = withProtectionRoute(()=>); + export default Index; diff --git a/app/store/global.ts b/app/store/global.ts index 9684583d60..9ee4686c9f 100644 --- a/app/store/global.ts +++ b/app/store/global.ts @@ -1,7 +1,5 @@ import { signal } from "@preact/signals-core"; -export const sceneTitle = signal("Home Office"); - export const pageHeight = signal(0); export const pageWidth = signal(0); diff --git a/app/store/index.ts b/app/store/index.ts index 09370234b7..3334f4f182 100644 --- a/app/store/index.ts +++ b/app/store/index.ts @@ -1 +1,2 @@ export * from "./global"; +export * from "./scene"; diff --git a/app/store/scene.ts b/app/store/scene.ts new file mode 100644 index 0000000000..2ced749992 --- /dev/null +++ b/app/store/scene.ts @@ -0,0 +1,27 @@ +import { signal } from "@preact/signals-core"; + +export interface SceneSignal { + isInitializing: boolean; + title: string | undefined; + token: string | undefined; + ownerToken: string | undefined; + isModified: boolean | undefined; +} +export const scene = signal({ + isInitializing: true, + title: undefined, + token: undefined, + ownerToken: undefined, + isModified: undefined, +}); + + +export const signalScene = (data:SceneSignal)=>{ + scene.value = { + ...data, + isInitializing: false, + isModified: true, + //TODO: MILES: implement flagging of isModified + // from editor side, and take this out after + }; +} \ No newline at end of file diff --git a/app/utilities.tsx b/app/utilities.tsx index 2edfe517a7..ef2de60001 100644 --- a/app/utilities.tsx +++ b/app/utilities.tsx @@ -1,4 +1,18 @@ +import { Params} from "@remix-run/react"; + export const kebabCase = (str:string) => str .replace(/([a-z])([A-Z])/g, "$1-$2") .replace(/[\s_]+/g, '-') - .toLowerCase(); \ No newline at end of file + .toLowerCase(); + +export const getCurrentLocationWithoutParams = ( + path:string, + params:Readonly> +)=>{ + let result = path; + Object.keys(params).forEach((key)=>{ + result = result.replace(("/"+params[key]) || "", "") + }); + if (result[result.length -1] !== "/") result = result + "/"; + return result; +} \ No newline at end of file