mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-07 10:31:46 +00:00
Scene - Save Load - v2.0 (#303)
Saving and Loading of Scenes: - loading scenes from URL (aka the scenetoken) is fixed - URL changes according to the changing of loaded scene - scene signal is created for storing the metadata of a scene - editor can hydrate the scene signal data - options in the file menu changes states according to scene status Editor Changes and Fixes: - fixes the way Editor Engine is constructed and initialized for stability - fixes typing issues on all that is passed to Editor Engine from react-land Other Fixes, Bells & Whistles: - changed "back to dashboard" button to "back to editor", and the correct behavior of it - cleaned up routes and page files so routes designation are actually mutually exclusive Co-authored-by: wil <wil@storyteller.ai>
This commit is contained in:
1 parent
ecb1540863
commit
d5e3b9af97
19 files changed
+344
-137
No files matched your search
@@ -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);
|
||||
|
||||
@@ -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<Editor | null>(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 (
|
||||
<EngineContext.Provider value={editor}>{children}</EngineContext.Provider>
|
||||
|
||||
@@ -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<boolean>(true);
|
||||
const handleShowErrorDialog = () => {
|
||||
errorDialogTitle.value = "Error";
|
||||
errorDialogMessage.value = "Scene name can not be empty.";
|
||||
showErrorDialog.value = true;
|
||||
};
|
||||
const handleChangeSceneTitle = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
sceneTitle.value = e.target.value;
|
||||
if (sceneTitle.value !== "") {
|
||||
const handleChangeSceneTitle = (e: React.ChangeEvent<HTMLInputElement>)=>{
|
||||
scene.value.title = e.target.value;
|
||||
if (scene.value.title !== "") {
|
||||
setIsValid(true);
|
||||
}
|
||||
};
|
||||
const validateSceneTitle = (e: React.FocusEvent<HTMLInputElement>) => {
|
||||
if (sceneTitle.value === "") {
|
||||
}
|
||||
const validateSceneTitle = (e: React.FocusEvent<HTMLInputElement>)=>{
|
||||
if (scene.value.title === "") {
|
||||
setIsValid(false);
|
||||
handleShowErrorDialog();
|
||||
e.target.focus();
|
||||
@@ -52,26 +60,24 @@ export const TopBar = ({ pageName }: Props) => {
|
||||
alt="Logo FakeYou StoryTeller.ai"
|
||||
/>
|
||||
</a>
|
||||
{currentLocation !== "/" && (
|
||||
{!isEditorPath(currentLocation) && (
|
||||
<ButtonLink to={"/"} variant="secondary" icon={faChevronLeft}>
|
||||
Back to Dashboard
|
||||
Back to Editor
|
||||
</ButtonLink>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-center gap-2 font-medium">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<span className="opacity-60">{pageName}</span>
|
||||
<span className="opacity-60">/</span>
|
||||
<Input
|
||||
className="-ml-2"
|
||||
inputClassName="bg-ui-panel hover:bg-white/5 border-2 border-transparent hover:border-brand-secondary hover:ml-1.5 focus:bg-brand-secondary focus:ml-1.5 h-9 p-2.5"
|
||||
isError={!isValid}
|
||||
value={sceneTitle.value}
|
||||
onChange={handleChangeSceneTitle}
|
||||
onBlur={validateSceneTitle}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-center font-medium gap-2">
|
||||
<span className="opacity-60">{pageName}</span>
|
||||
<span className="opacity-60">/</span>
|
||||
<Input
|
||||
className="-ml-2 w-96"
|
||||
inputClassName="bg-ui-panel focus:bg-brand-secondary focus:ml-3 text-ellipsis"
|
||||
isError={!isValid}
|
||||
value={scene.value.title || ""}
|
||||
onChange={handleChangeSceneTitle}
|
||||
onBlur={validateSceneTitle}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-2">
|
||||
|
||||
@@ -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(
|
||||
<div
|
||||
className="fixed w-full overflow-scroll"
|
||||
@@ -22,6 +21,6 @@ const Page404 = withProtectionRoute(()=>{
|
||||
<ButtonLink to="/" icon={faChevronLeft}>Back to Dashboard</ButtonLink>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
});
|
||||
);
|
||||
};
|
||||
export default Page404;
|
||||
@@ -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 (
|
||||
<TrackProvider>
|
||||
<EngineProvider>
|
||||
<PageEnigmaComponent />
|
||||
<PageEnigmaComponent sceneToken={sceneToken}/>
|
||||
<DragComponent />
|
||||
<GenerateModals />
|
||||
<ErrorDialog />
|
||||
|
||||
@@ -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 <PageEditor />;
|
||||
};
|
||||
@@ -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<string>("");
|
||||
export const ControlsTopButtons = () => {
|
||||
const params = useParams();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const editorEngine = useContext(EngineContext);
|
||||
const {authState} = useContext(AuthenticationContext);
|
||||
|
||||
const [sceneTitleInput, setSceneTitleInput] = useState<string>("");
|
||||
const [sceneToken, setSceneToken] = useState<string>("");
|
||||
const { addToast } = useContext(ToasterContext);
|
||||
|
||||
const handleChangeSceneTitleInput = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
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<HTMLInputElement>) => {
|
||||
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 (
|
||||
<div className="flex flex-col gap-2 pl-3 pt-3">
|
||||
@@ -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: (
|
||||
<Input
|
||||
value={sceneTitleInput}
|
||||
label="Please enter a name for your new scene"
|
||||
onChange={(e) => {
|
||||
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: (
|
||||
<Input
|
||||
value={sceneTitle.value}
|
||||
value={sceneTitleInput}
|
||||
label="Please enter a name for your scene"
|
||||
// onChange={(e) => {
|
||||
// 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: (
|
||||
<Input
|
||||
value={sceneTitle.value}
|
||||
value={sceneTitleInput}
|
||||
label="Please enter a name for your scene"
|
||||
// onChange={(e) => {
|
||||
// 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 = () => {
|
||||
]}
|
||||
/>
|
||||
|
||||
{/* <ButtonDialogue
|
||||
buttonProps={{
|
||||
variant: "secondary",
|
||||
label: "Add Scene Object (Test)",
|
||||
}}
|
||||
confirmButtonProps={{
|
||||
label: "Save",
|
||||
disabled: mediaToken === "",
|
||||
onClick: handleMediaToken,
|
||||
}}
|
||||
title="Add Scene Object via Media Token">
|
||||
<Input
|
||||
label="Please Enter a Media Token"
|
||||
onChange={(e) => {
|
||||
setMediaToken(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</ButtonDialogue> */}
|
||||
|
||||
<ButtonDialogue
|
||||
buttonProps={{
|
||||
variant: "secondary",
|
||||
|
||||
@@ -19,7 +19,7 @@ export const DragComponent = () => {
|
||||
|
||||
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) {
|
||||
|
||||
@@ -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 (
|
||||
<div
|
||||
|
||||
@@ -109,6 +109,7 @@ export class APIManager {
|
||||
|
||||
public async loadSceneState(
|
||||
scene_media_file_token: string | null,
|
||||
signalScene: ((data:any)=>void )| null,
|
||||
): Promise<any> {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<AppUiAction>;
|
||||
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<AppUiAction>;
|
||||
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,
|
||||
|
||||
@@ -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 ( <PageEnigma sceneToken={params.sceneToken} />);
|
||||
|
||||
//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 ( <PageEnigma sceneToken={params.sceneToken} />);
|
||||
// }else{
|
||||
// return (<Page404 />);
|
||||
// }
|
||||
});
|
||||
export default Index;
|
||||
@@ -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(()=><PageEnigma />);
|
||||
|
||||
const IdealEnigma = withProtectionRoute(()=>{
|
||||
const params= useParams();
|
||||
return <PageEnigma sceneToken={params.sceneToken}/>
|
||||
});
|
||||
export default IdealEnigma;
|
||||
@@ -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(()=><PageEnigma />);
|
||||
export default IdealEnigma;
|
||||
@@ -29,4 +29,5 @@ export const headers: HeadersFunction = ({
|
||||
});
|
||||
|
||||
const Index = withProtectionRoute(()=><PageEnigma />);
|
||||
|
||||
export default Index;
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./global";
|
||||
export * from "./scene";
|
||||
@@ -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<SceneSignal>({
|
||||
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
|
||||
};
|
||||
}
|
||||
+15
-1
@@ -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();
|
||||
.toLowerCase();
|
||||
|
||||
export const getCurrentLocationWithoutParams = (
|
||||
path:string,
|
||||
params:Readonly<Params<string>>
|
||||
)=>{
|
||||
let result = path;
|
||||
Object.keys(params).forEach((key)=>{
|
||||
result = result.replace(("/"+params[key]) || "", "")
|
||||
});
|
||||
if (result[result.length -1] !== "/") result = result + "/";
|
||||
return result;
|
||||
}
|
||||
Reference in new issue
Block a user