mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-06 02:00:46 +00:00
Scene - Save Load - v2.1 (#305)
Saving and Loading of Scenes - Save New scene to server implemented - Retrieving sceneToken from new scene and refreshes URL, which brings the editor to state of editing an existing Scene - Optimized scene reloading when URL changes - Save Existing scene to server implemented - Rename scene to server implemented - Optimized Scene Renaming Component and Endpoint Operation Editor Changes and Fixes: - changed the way the api_manager retrieve session tokens - changed the way api_manager uses a fetcher for the operations in saving scenes Co-authored-by: wil <wil@storyteller.ai>
This commit is contained in:
1 parent
17bf313e45
commit
ad2677a575
12 files changed
+352
-166
No files matched your search
+6
-1
@@ -25,4 +25,9 @@ export const getMediaFileByToken = (fileToken: string) =>
|
||||
`${apiHost}/v1/media_files/file/${fileToken}`;
|
||||
|
||||
// Upload File
|
||||
export const uploadMedia = `${apiHost}/v1/media_files/upload`
|
||||
export const uploadMedia = `${apiHost}/v1/media_files/upload`;
|
||||
|
||||
// Scenes
|
||||
export const uploadNewScene = `${apiHost}/v1/media_files/upload/new_scene`;
|
||||
export const updateExistingScene = (sceneToken:string)=>`${apiHost}/v1/media_files/upload/saved_scene/${sceneToken}`
|
||||
export const renameScene = (sceneToken:string)=>`${apiHost}/v1/media_files/rename/${sceneToken}`
|
||||
@@ -8,10 +8,12 @@ import { signalScene } from "~/store";
|
||||
import Editor from "~/pages/PageEnigma/js/editor";
|
||||
|
||||
interface Props {
|
||||
sceneToken?: string
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const EngineProvider = ({
|
||||
sceneToken,
|
||||
children,
|
||||
}: Props) => {
|
||||
const [editor, setEditor] = useState<Editor | null>(null);
|
||||
@@ -35,6 +37,14 @@ export const EngineProvider = ({
|
||||
// }
|
||||
}, [dispatchAppUiState, authState]);
|
||||
|
||||
useEffect(() => {
|
||||
if (editor && editor.can_initialize) {
|
||||
editor.initialize({
|
||||
sceneToken: sceneToken || ""}
|
||||
);
|
||||
}
|
||||
}, [editor, sceneToken]);
|
||||
|
||||
return (
|
||||
<EngineContext.Provider value={editor}>{children}</EngineContext.Provider>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import { useState, useContext } from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { AuthenticationContext } from "~/contexts/Authentication";
|
||||
import { faSpinnerThird } from '@fortawesome/pro-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
import { scene, signalScene } from "~/store";
|
||||
import {
|
||||
showErrorDialog,
|
||||
errorDialogMessage,
|
||||
errorDialogTitle,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
|
||||
import { Input } from '~/components';
|
||||
import { renameScene } from './utilities';
|
||||
|
||||
export const SceneTitleInput = ()=>{
|
||||
useSignals();
|
||||
const { authState } = useContext(AuthenticationContext);
|
||||
|
||||
const [{isValid, isSaving}, setState] = useState<{isValid:boolean; isSaving:boolean}>({isValid:true, isSaving:false});
|
||||
const setIsValid = (val:boolean)=>{
|
||||
setState((curr)=>({...curr, isValid:val}));
|
||||
};
|
||||
const setIsSaving = (val:boolean)=>{
|
||||
setState((curr)=>({...curr, isSaving:val}));
|
||||
};
|
||||
|
||||
const handleShowErrorDialog = () => {
|
||||
errorDialogTitle.value = "Error";
|
||||
errorDialogMessage.value = "Scene name can not be empty.";
|
||||
showErrorDialog.value = true;
|
||||
};
|
||||
|
||||
const handleChangeSceneTitle = (e: React.ChangeEvent<HTMLInputElement>)=>{
|
||||
signalScene({
|
||||
...scene.value,
|
||||
title : e.target.value,
|
||||
});
|
||||
if (scene.value.title !== "") {
|
||||
setIsValid(true);
|
||||
}
|
||||
}
|
||||
const validateSceneTitle = (e: React.FocusEvent<HTMLInputElement>)=>{
|
||||
if (scene.value.title === "") {
|
||||
setIsValid(false);
|
||||
handleShowErrorDialog();
|
||||
e.target.focus();
|
||||
}else if(scene.value.token && authState.sessionToken){
|
||||
setIsSaving(true);
|
||||
renameScene(
|
||||
scene.value.title!, //guarunteed by input
|
||||
scene.value.token,
|
||||
authState.sessionToken,
|
||||
).then((res)=>{
|
||||
// console.log(res);
|
||||
//TODO: HANDLE ERROR
|
||||
setIsSaving(false);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return(
|
||||
<div className={twMerge(
|
||||
"flex gap-3 justify-between items-center",
|
||||
isSaving && "ml-3"
|
||||
)}>
|
||||
<Input
|
||||
disabled={scene.value.ownerToken !== authState.userInfo?.user_token}
|
||||
className="-ml-2 w-96"
|
||||
inputClassName={twMerge(
|
||||
"bg-ui-panel focus:bg-brand-secondary text-ellipsis",
|
||||
isSaving ? "outline-brand-secondary" : "focus:ml-3",
|
||||
)}
|
||||
isError={!isValid}
|
||||
value={scene.value.title || ""}
|
||||
onChange={handleChangeSceneTitle}
|
||||
onBlur={validateSceneTitle}
|
||||
/>
|
||||
{isSaving &&
|
||||
<FontAwesomeIcon icon={faSpinnerThird} spin/>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,17 +1,10 @@
|
||||
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 { scene } from "~/store";
|
||||
import {
|
||||
showErrorDialog,
|
||||
errorDialogMessage,
|
||||
errorDialogTitle,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
import { MyMoviesButton } from "~/modules/TopBar/MyMoviesButton";
|
||||
|
||||
import { useLocation, useParams } from "@remix-run/react";
|
||||
import { faChevronLeft } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { ButtonLink } from "~/components";
|
||||
import { AuthButtons } from "./AuthButtons";
|
||||
import { SceneTitleInput } from "./SceneTitleInput";
|
||||
import { MyMoviesButton } from "~/modules/TopBar/MyMoviesButton";
|
||||
import { getCurrentLocationWithoutParams } from '~/utilities';
|
||||
|
||||
function isEditorPath(path:string){
|
||||
@@ -24,28 +17,8 @@ interface Props {
|
||||
}
|
||||
|
||||
export const TopBar = ({ pageName }: Props) => {
|
||||
useSignals();
|
||||
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>)=>{
|
||||
scene.value.title = e.target.value;
|
||||
if (scene.value.title !== "") {
|
||||
setIsValid(true);
|
||||
}
|
||||
}
|
||||
const validateSceneTitle = (e: React.FocusEvent<HTMLInputElement>)=>{
|
||||
if (scene.value.title === "") {
|
||||
setIsValid(false);
|
||||
handleShowErrorDialog();
|
||||
e.target.focus();
|
||||
}
|
||||
};
|
||||
return (
|
||||
<header className="fixed left-0 top-0 z-10 w-full border-b border-ui-panel-border bg-ui-panel">
|
||||
<nav
|
||||
@@ -68,16 +41,9 @@ export const TopBar = ({ pageName }: Props) => {
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-center font-medium gap-2">
|
||||
<span className="opacity-60">{pageName}</span>
|
||||
<span className="opacity-60 text-nowrap">{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}
|
||||
/>
|
||||
<SceneTitleInput />
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-2">
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { renameScene as renameSceneEndpoint } from "~/api";
|
||||
|
||||
export const renameScene = async (
|
||||
sceneTitle: string,
|
||||
sceneToken: string,
|
||||
sessionToken: string,
|
||||
) => {
|
||||
const endpoint = renameSceneEndpoint(sceneToken);
|
||||
// console.log(`rename ${sceneToken}: ${sceneTitle}`);
|
||||
return await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
session: sessionToken,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: sceneTitle,
|
||||
}),
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
if (res && res.success) {
|
||||
// console.log(res);
|
||||
return res;
|
||||
} else {
|
||||
return { success : false };
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
return { success : false };
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PageEnigmaComponent } from "./PageEnigmaComponent";
|
||||
import { PageEditor } from "~/pages/PageEnigma/PageEditor";
|
||||
import { TrackProvider } from "~/pages/PageEnigma/contexts/TrackContext/TrackProvider";
|
||||
import { DragComponent } from "~/pages/PageEnigma/comps/DragComponent/DragComponent";
|
||||
import { EngineProvider } from "~/contexts/EngineProvider";
|
||||
@@ -15,8 +15,8 @@ export const PageEnigma = ({
|
||||
useInferenceJobManager();
|
||||
return (
|
||||
<TrackProvider>
|
||||
<EngineProvider>
|
||||
<PageEnigmaComponent sceneToken={sceneToken}/>
|
||||
<EngineProvider sceneToken={sceneToken}>
|
||||
<PageEditor />
|
||||
<DragComponent />
|
||||
<GenerateModals />
|
||||
<ErrorDialog />
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
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 { signalScene } from "~/store";
|
||||
|
||||
export const PageEnigmaComponent = ({
|
||||
sceneToken
|
||||
}: {
|
||||
sceneToken?: string;
|
||||
}) => {
|
||||
const editor = useContext(EngineContext);
|
||||
|
||||
useEffect(() => {
|
||||
if (editor && editor.can_initialize) {
|
||||
editor.initialize({
|
||||
sceneToken: sceneToken || ""}
|
||||
);
|
||||
}
|
||||
}, [editor, sceneToken]);
|
||||
|
||||
return <PageEditor />;
|
||||
};
|
||||
@@ -1,15 +1,19 @@
|
||||
import { useContext, useState } from "react";
|
||||
import { useParams, useLocation, useNavigate } from "@remix-run/react";
|
||||
import { useContext, useState, useCallback } from "react";
|
||||
import { useParams, useLocation, useNavigate, } from "@remix-run/react";
|
||||
|
||||
import { useSignalEffect } from "@preact/signals-react/runtime";
|
||||
import { scene } from "~/store";
|
||||
import { scene, signalScene } from "~/store";
|
||||
|
||||
import { EngineContext } from "~/contexts/EngineContext";
|
||||
import { AuthenticationContext } from "~/contexts/Authentication";
|
||||
import { ToasterContext, ToastTypes } from "~/contexts/ToasterContext";
|
||||
import {ToasterContext, ToastTypes } from "~/contexts/ToasterContext";
|
||||
|
||||
import { faFile } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { ButtonDropdown, Input } from "~/components";
|
||||
import {
|
||||
ButtonDropdown,
|
||||
Input,
|
||||
H4,
|
||||
} from "~/components";
|
||||
import { ButtonDialogue } from "~/modules/ButtonDialogue";
|
||||
import { TestFeaturesButtons } from "./TestFeaturesButtons";
|
||||
import { Help } from "./Help";
|
||||
@@ -27,7 +31,7 @@ export const ControlsTopButtons = () => {
|
||||
const {authState} = useContext(AuthenticationContext);
|
||||
|
||||
const [sceneTitleInput, setSceneTitleInput] = useState<string>("");
|
||||
const [sceneToken, setSceneToken] = useState<string>("");
|
||||
const [sceneTokenSelected, setSceneTokenSelected] = useState<string>("");
|
||||
const { addToast } = useContext(ToasterContext);
|
||||
|
||||
const handleChangeSceneTitleInput = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
@@ -37,47 +41,77 @@ export const ControlsTopButtons = () => {
|
||||
setSceneTitleInput("");
|
||||
}
|
||||
|
||||
const handleButtonNew = ()=>{
|
||||
editorEngine?.newScene(sceneTitleInput);
|
||||
};
|
||||
|
||||
const handleButtonSave = async () => {
|
||||
console.log(`SceneName is ${scene.value.title}`);
|
||||
const sceneMediaToken = await editorEngine?.saveScene(scene.value.title || "");
|
||||
if (sceneMediaToken) {
|
||||
addToast(ToastTypes.SUCCESS, sceneMediaToken);
|
||||
// console.log(`SceneName is ${scene.value.title}`);
|
||||
const retSceneMediaToken = await editorEngine?.saveScene({
|
||||
sceneTitle : scene.value.title || "",
|
||||
sceneToken : scene.value.token
|
||||
});
|
||||
if (retSceneMediaToken) {
|
||||
addToast(ToastTypes.SUCCESS, retSceneMediaToken);
|
||||
if(!scene.value.token){
|
||||
signalScene({
|
||||
...scene.value,
|
||||
token: retSceneMediaToken
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleButtonNewFromTemplate = () => {
|
||||
editorEngine?.loadScene(sceneToken)
|
||||
.catch((err) => {
|
||||
addToast(ToastTypes.ERROR, err.message);
|
||||
const handleButtonSaveAsCopy = useCallback(async() => {
|
||||
const retSceneMediaToken = await editorEngine?.saveScene({
|
||||
sceneTitle : sceneTitleInput,
|
||||
sceneToken : undefined
|
||||
});
|
||||
if (retSceneMediaToken) {
|
||||
addToast(ToastTypes.SUCCESS, retSceneMediaToken);
|
||||
signalScene({
|
||||
...scene.value,
|
||||
token: retSceneMediaToken,
|
||||
ownerToken: authState.userInfo?.user_token,
|
||||
title: sceneTitleInput,
|
||||
});
|
||||
}
|
||||
}, [sceneTitleInput]);
|
||||
|
||||
const handleButtonNewFromTemplate = () => {
|
||||
handleButtonLoadScene();
|
||||
};
|
||||
|
||||
const handleButtonLoadScene = () => {
|
||||
editorEngine?.loadScene(sceneToken)
|
||||
editorEngine?.loadScene(sceneTokenSelected)
|
||||
.catch((err) => {
|
||||
addToast(ToastTypes.ERROR, err.message);
|
||||
});
|
||||
};
|
||||
|
||||
const handleSceneSelection = (token: string) => {
|
||||
setSceneToken(token);
|
||||
setSceneTokenSelected(token);
|
||||
};
|
||||
|
||||
useSignalEffect(()=>{
|
||||
console.log("useSignalEffect in File Buttons");
|
||||
console.log(scene.value);
|
||||
if (!scene.value.isInitializing){
|
||||
setSceneTitleInput(scene.value.title || "");
|
||||
const currentLocation = getCurrentLocationWithoutParams(location.pathname, params);
|
||||
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);
|
||||
if(params.sceneToken){
|
||||
//case of create new scene from existing scene
|
||||
history.pushState({}, "",currentLocation);
|
||||
}
|
||||
//case of create new scene from unsaved scene
|
||||
navigate(currentLocation, {replace:true});
|
||||
}else if (scene.value.token){
|
||||
if(params.sceneToken && scene.value.token !== params.sceneToken){
|
||||
//case of loading existing scene from existing scene
|
||||
history.pushState({}, "",currentLocation+scene.value.token);
|
||||
}
|
||||
//case of loading existing scene from unsaved new scene
|
||||
//or case of updating existing scene
|
||||
navigate(currentLocation+scene.value.token, {replace:true});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -108,9 +142,7 @@ export const ControlsTopButtons = () => {
|
||||
confirmButtonProps: {
|
||||
label: "Create",
|
||||
disabled: sceneTitleInput === "",
|
||||
onClick: () => editorEngine?.newScene(
|
||||
sceneTitleInput
|
||||
),
|
||||
onClick: handleButtonNew,
|
||||
},
|
||||
closeButtonProps: {
|
||||
label: "Cancel",
|
||||
@@ -129,7 +161,7 @@ export const ControlsTopButtons = () => {
|
||||
),
|
||||
confirmButtonProps: {
|
||||
label: "Create",
|
||||
disabled: sceneToken === "",
|
||||
disabled: sceneTokenSelected === "",
|
||||
onClick: handleButtonNewFromTemplate,
|
||||
},
|
||||
closeButtonProps: {
|
||||
@@ -147,7 +179,7 @@ export const ControlsTopButtons = () => {
|
||||
content: <LoadScene onSceneSelect={handleSceneSelection} />,
|
||||
confirmButtonProps: {
|
||||
label: "Load",
|
||||
disabled: sceneToken === "",
|
||||
disabled: sceneTokenSelected === "",
|
||||
onClick: handleButtonLoadScene,
|
||||
},
|
||||
closeButtonProps: {
|
||||
@@ -165,15 +197,10 @@ export const ControlsTopButtons = () => {
|
||||
dialogProps: {
|
||||
title: "Save Scene",
|
||||
content: (
|
||||
<Input
|
||||
value={sceneTitleInput}
|
||||
label="Please enter a name for your scene"
|
||||
onChange={handleChangeSceneTitleInput}
|
||||
/>
|
||||
<H4>Save scene to <b>{scene.value.title}</b>?</H4>
|
||||
),
|
||||
confirmButtonProps: {
|
||||
label: "Save",
|
||||
disabled: sceneTitleInput === "",
|
||||
onClick: handleButtonSave,
|
||||
},
|
||||
closeButtonProps: {
|
||||
@@ -202,7 +229,7 @@ export const ControlsTopButtons = () => {
|
||||
confirmButtonProps: {
|
||||
label: "Save",
|
||||
disabled: sceneTitleInput === "",
|
||||
onClick: handleButtonSave,
|
||||
onClick: handleButtonSaveAsCopy,
|
||||
},
|
||||
closeButtonProps: {
|
||||
label: "Cancel",
|
||||
|
||||
@@ -30,6 +30,7 @@ export const LoadScene = ({ onSceneSelect }: LoadSceneProps) => {
|
||||
return;
|
||||
}
|
||||
sceneLoading.current = true;
|
||||
// console.log("load scene");
|
||||
GetMediaByUser(
|
||||
authState.userInfo.username,
|
||||
{},
|
||||
@@ -38,6 +39,7 @@ export const LoadScene = ({ onSceneSelect }: LoadSceneProps) => {
|
||||
},
|
||||
)
|
||||
.then((res: GetMediaListResponse) => {
|
||||
// console.log(res);
|
||||
if (res.success && res.results) {
|
||||
setScenes(
|
||||
res.results.map((scene) => ({
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import {
|
||||
uploadNewScene as uploadNewSceneEndpoint,
|
||||
updateExistingScene as updateExistingSceneEndpoint
|
||||
} from "~/api";
|
||||
|
||||
export const uploadNewScene = async (
|
||||
file:File,
|
||||
sceneTitle: string,
|
||||
sessionToken: string,
|
||||
) => {
|
||||
const endpoint = uploadNewSceneEndpoint;
|
||||
const formData = new FormData();
|
||||
formData.append("uuid_idempotency_token", uuidv4());
|
||||
formData.append("file", file);
|
||||
formData.append("maybe_title", sceneTitle);
|
||||
formData.append("maybe_visibility", "public");
|
||||
formData.append("engine_category", "scene");
|
||||
|
||||
return await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
session: sessionToken,
|
||||
},
|
||||
body: formData,
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
if (res && res.success) {
|
||||
return res;
|
||||
} else {
|
||||
return { success : false };
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
return { success : false };
|
||||
});
|
||||
}
|
||||
|
||||
export const updateExistingScene = async (
|
||||
file:File,
|
||||
sceneToken: string,
|
||||
sessionToken: string,
|
||||
) => {
|
||||
const endpoint = updateExistingSceneEndpoint(sceneToken);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("uuid_idempotency_token", uuidv4());
|
||||
formData.append("file", file);
|
||||
|
||||
return await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
session: sessionToken,
|
||||
},
|
||||
body: formData,
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
if (res && res.success) {
|
||||
return res;
|
||||
} else {
|
||||
return { success : false };
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
return { success : false };
|
||||
});
|
||||
}
|
||||
|
||||
// function uploadEngineAsset(
|
||||
// file: File,
|
||||
// media_file_token: string | null,
|
||||
// ): Promise<any> {
|
||||
// const url = `${this.baseUrl}/v1/media_files/write/engine_asset`;
|
||||
// const uuid = uuidv4();
|
||||
// const form_data = new FormData();
|
||||
// form_data.append("uuid_idempotency_token", uuid);
|
||||
|
||||
// // update existing scene otherwise create new glb scene and use it's media_file_id
|
||||
// if (media_file_token != null) {
|
||||
// form_data.append("media_file_token", media_file_token);
|
||||
// }
|
||||
|
||||
// form_data.append("file", file);
|
||||
// form_data.append("source", "file");
|
||||
// form_data.append("media_file_subtype", "scene_import");
|
||||
// form_data.append("media_file_class", "scene");
|
||||
|
||||
// const response = await fetch(url, {
|
||||
// method: "POST",
|
||||
// // credentials: "include",
|
||||
// headers: {
|
||||
// Accept: "application/json",
|
||||
// session: this.sessionToken,
|
||||
// },
|
||||
// body: form_data,
|
||||
// });
|
||||
|
||||
// if (!response.ok) {
|
||||
// throw new Error("Failed to Send Data");
|
||||
// } else {
|
||||
// const json_data = await response.json();
|
||||
// return json_data; // or handle the response as appropriate
|
||||
// }
|
||||
// }
|
||||
@@ -3,6 +3,10 @@ import * as THREE from "three";
|
||||
import { GLTFExporter } from "three/addons/exporters/GLTFExporter.js";
|
||||
import { STORAGE_KEYS } from "~/contexts/Authentication/types";
|
||||
import { environmentVariables } from "~/store";
|
||||
import {
|
||||
updateExistingScene,
|
||||
uploadNewScene
|
||||
} from "./api_fetchers";
|
||||
|
||||
/**
|
||||
* Storyteller Studio API Manager
|
||||
@@ -80,7 +84,6 @@ export class APIManager {
|
||||
constructor() {
|
||||
this.baseUrl = environmentVariables.value.BASE_API;
|
||||
this.sessionToken = localStorage.getItem(STORAGE_KEYS.SESSION_TOKEN) || "";
|
||||
//this.baseUrl = "http://localhost:12345"
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,22 +92,23 @@ export class APIManager {
|
||||
* @param scene_media_file_token If null we will we will create a new save or copy the scene, if provided we will overwrite the scene.
|
||||
* @returns APIManagerResponseMessage
|
||||
*/
|
||||
public async saveSceneState(
|
||||
save_json: string,
|
||||
scene_name: string,
|
||||
scene_glb_media_file_token: string | null = null,
|
||||
): Promise<string> {
|
||||
const file = new File([save_json], `${scene_name}.glb`, {
|
||||
public async saveSceneState({
|
||||
saveJson, sceneTitle, sceneToken
|
||||
}:{
|
||||
saveJson: string,
|
||||
sceneTitle: string,
|
||||
sceneToken?: string,
|
||||
}): Promise<string> {
|
||||
const file = new File([saveJson], `${sceneTitle}.glb`, {
|
||||
type: "application/json",
|
||||
});
|
||||
|
||||
// will overwrite the scene on db if token exists
|
||||
const upload_glb_response = await this.uploadEngineAsset(
|
||||
file,
|
||||
scene_glb_media_file_token,
|
||||
);
|
||||
const uploadSceneResponse = sceneToken
|
||||
? await updateExistingScene(file, sceneToken, this.sessionToken)
|
||||
: await uploadNewScene(file, sceneTitle, this.sessionToken);
|
||||
|
||||
return upload_glb_response["media_file_token"];
|
||||
console.log(uploadSceneResponse)
|
||||
return uploadSceneResponse["media_file_token"];
|
||||
}
|
||||
|
||||
public async loadSceneState(
|
||||
@@ -182,43 +186,6 @@ export class APIManager {
|
||||
return file;
|
||||
}
|
||||
|
||||
private async uploadEngineAsset(
|
||||
file: File,
|
||||
media_file_token: string | null,
|
||||
): Promise<any> {
|
||||
const url = `${this.baseUrl}/v1/media_files/write/engine_asset`;
|
||||
const uuid = uuidv4();
|
||||
const form_data = new FormData();
|
||||
form_data.append("uuid_idempotency_token", uuid);
|
||||
|
||||
// update existing scene otherwise create new glb scene and use it's media_file_id
|
||||
if (media_file_token != null) {
|
||||
form_data.append("media_file_token", media_file_token);
|
||||
}
|
||||
|
||||
form_data.append("file", file);
|
||||
form_data.append("source", "file");
|
||||
form_data.append("media_file_subtype", "scene_import");
|
||||
form_data.append("media_file_class", "scene");
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
// credentials: "include",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
session: this.sessionToken,
|
||||
},
|
||||
body: form_data,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to Send Data");
|
||||
} else {
|
||||
const json_data = await response.json();
|
||||
return json_data; // or handle the response as appropriate
|
||||
}
|
||||
}
|
||||
|
||||
public async uploadMedia(
|
||||
blob: any,
|
||||
fileName: string,
|
||||
@@ -294,7 +261,7 @@ export class APIManager {
|
||||
const result = await fetch(url)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
const result = data["media_files"].map((element) => {
|
||||
const result = data["media_files"].map((element:any) => {
|
||||
return new MediaFile(
|
||||
element["public_bucket_path"],
|
||||
element["media_type"],
|
||||
|
||||
@@ -613,7 +613,13 @@ class Editor {
|
||||
}
|
||||
}
|
||||
|
||||
public async saveScene(name: string): Promise<string> {
|
||||
public async saveScene({
|
||||
sceneTitle,
|
||||
sceneToken,
|
||||
}:{
|
||||
sceneTitle: string,
|
||||
sceneToken?: string
|
||||
}): Promise<string> {
|
||||
// remove controls when saving scene.
|
||||
this.removeTransformControls();
|
||||
this.dispatchAppUiState({
|
||||
@@ -644,11 +650,11 @@ class Editor {
|
||||
};
|
||||
|
||||
// TODO turn scene information into and object ...
|
||||
const result = await this.api_manager.saveSceneState(
|
||||
JSON.stringify(save_data),
|
||||
name,
|
||||
this.current_scene_glb_media_token,
|
||||
);
|
||||
const result = await this.api_manager.saveSceneState({
|
||||
saveJson: JSON.stringify(save_data),
|
||||
sceneTitle,
|
||||
sceneToken,
|
||||
});
|
||||
|
||||
this.dispatchAppUiState({
|
||||
type: APPUI_ACTION_TYPES.HIDE_EDITOR_LOADER,
|
||||
|
||||
Reference in new issue
Block a user