Compare commits

...
Author SHA1 Message Date
Tanish Manku 187eedbea5 splat upload: wire media token to scene and persist via save manager
- Propagate assetToken through upload3DObjects progressCallback so callers receive it on success
- UploadModalSplat.onSuccess now delivers the media token instead of raw bytes
- Controls3D uses addObject() with AssetType.SPLAT to add the splat through the proper queue/undo path
- Remove unused onLocalBytes prop and dead commented-out code
- Fix ObjectJSON missing top-level media_file_type field so type is correctly restored on scene reload (previously always undefined, breaking outliner icons for all object types)
2026-03-23 22:40:22 -07:00
5 changed files with 15 additions and 63 deletions
@@ -42,7 +42,7 @@ export const upload3DObjects = async ({
const assetToken = assetReponse.data;
if (!thumbnailSnapshot) {
progressCallback({ status: UploaderStates.success });
progressCallback({ status: UploaderStates.success, data: assetToken });
return;
}
@@ -72,5 +72,5 @@ export const upload3DObjects = async ({
});
return;
}
progressCallback({ status: UploaderStates.success });
progressCallback({ status: UploaderStates.success, data: assetToken });
};
@@ -21,7 +21,6 @@ interface Props {
};
onClose: () => void;
onUploadProgress: (newState: UploaderState) => void;
onLocalBytes: (bytes: ArrayBuffer, shouldFlip) => void;
}
export const UploadFilesSplat = ({
@@ -30,7 +29,6 @@ export const UploadFilesSplat = ({
options,
onClose,
onUploadProgress,
onLocalBytes,
}: Props) => {
const canvasRef = useRef<HTMLCanvasElement | undefined>(undefined);
const canvasCallbackRef = useCallback((node: HTMLCanvasElement) => {
@@ -75,14 +73,6 @@ export const UploadFilesSplat = ({
// Use the filename as the title
const title = assetFile.value.name.split(".")[0];
// const shouldFlip = title.search("ceramic") != -1;
//
// assetFile.value.arrayBuffer().then((arrayBuffer) => {
// onLocalBytes(arrayBuffer, shouldFlip);
// }).catch((error) => {
// console.error("Error reading file as ArrayBuffer:", error);
// });
upload3DObjects({
title: title,
assetFile: assetFile.value,
@@ -1,4 +1,4 @@
import { useEffect, useMemo, useState } from "react";
import { useEffect, useState } from "react";
import { LoadingDots } from "@storyteller/ui-loading";
import { Modal } from "@storyteller/ui-modal";
import { UploadAssetError, UploadSuccess } from "@storyteller/ui-upload-modal";
@@ -6,19 +6,14 @@ import { initialUploaderState, UploaderState } from "../../../models";
import {
FilterEngineCategories,
UploaderStates,
MediaFileAnimationType,
SPLAT_FILE_TYPE,
} from "../../../enums";
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import {
galleryModalVisibleViewMode,
galleryModalVisibleDuringDrag,
} from "@storyteller/ui-gallery-modal";
import { UploadFilesSplat } from "./UploadFilesSplat";
interface Props {
onClose: () => void;
onSuccess: (splatArrayBuffer: ArrayBuffer, shouldFlip: boolean) => void;
onSuccess: (mediaToken: string) => void;
isOpen: boolean;
title: string;
titleIcon: IconDefinition;
@@ -52,12 +47,8 @@ export function UploadModalSplat(props: Props) {
}, [isOpen]);
useEffect(() => {
if (uploaderState.status === UploaderStates.success) {
// Automatically open the global Gallery modal after a successful upload
// galleryModalVisibleViewMode.value = true;
// galleryModalVisibleDuringDrag.value = true;
// onSuccess(selectedCategory);
if (uploaderState.status === UploaderStates.success && uploaderState.data) {
onSuccess(uploaderState.data);
onClose();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -77,7 +68,6 @@ export function UploadModalSplat(props: Props) {
}}
onClose={onClose}
onUploadProgress={updateUploaderState}
onLocalBytes={(buffer, shouldFlip) => { onSuccess(buffer, shouldFlip); }}
/>
</div>
);
@@ -94,10 +84,7 @@ export function UploadModalSplat(props: Props) {
return (
<UploadSuccess
title="Splat"
onOk={() => {
onClose();
// onSuccess(selectedCategory);
}}
onOk={onClose}
/>
);
}
@@ -16,11 +16,6 @@ import { EngineContext } from "../../contexts/EngineContext";
import { useContext, useEffect, useState } from "react";
import { assetModalVisibleDuringDrag, assetModalVisible } from "../../signals";
import { useTabStore } from "~/pages/Stores/TabState";
// import { v4 as uuidv4 } from "uuid";
// import { addObject } from "../../signals/objectGroup/addObject";
// import { MediaItem } from "../../models/assets";
// eslint-disable-next-line import/no-unresolved
// import { AssetType } from "~/enums";
import { AssetModal } from "../AssetMenu/AssetModal";
import { selectedMode } from "../../signals/selectedMode";
import { useSignals } from "@preact/signals-react/runtime";
@@ -37,6 +32,9 @@ import {
import { twMerge } from "tailwind-merge";
import { UploadModalImage } from "../../../../components/reusable/UploadModalImage";
import { UploadModalSplat } from "~/components/reusable/UploadModalSplat";
import { addObject } from "../../signals/objectGroup/addObject";
import { AssetType } from "~/enums";
import { v4 as uuidv4 } from "uuid";
export const Controls3D = () => {
useSignals();
@@ -120,31 +118,6 @@ export const Controls3D = () => {
editorEngine.change_mode("scale");
};
// ----- TODO LATER - BFlat for auto add 3d model to scene -----
// Function to add the generated 3D model to the scene
// const addGeneratedModelToScene = useCallback((mediaToken: string) => {
// console.log("[DEBUG] addGeneratedModelToScene called with token:", mediaToken);
// // Create a MediaItem object for the 3D model
// const mediaItem: MediaItem = {
// version: 1,
// type: AssetType.OBJECT,
// media_id: mediaToken,
// name: "3D Model",
// object_uuid: uuidv4(),
// };
// // Add the object to the scene
// console.log("[DEBUG] Calling addObject with:", mediaItem);
// try {
// addObject(mediaItem);
// console.log("[DEBUG] addObject called successfully");
// } catch (error) {
// console.error("[DEBUG] Error in addObject:", error);
// }
// }, []);
const handleOpenModal = () => {
assetModalVisibleDuringDrag.value = true;
assetModalVisible.value = true;
@@ -352,9 +325,9 @@ export const Controls3D = () => {
<UploadModalSplat
isOpen={uploadSplatIsShowing}
onClose={() => setUploadSplatIsShowing(false)}
onSuccess={(buffer, shouldFlip) => {
setUploadSplatIsShowing(false)
editorEngine?.timeline.addLocalSplat(buffer, shouldFlip);
onSuccess={(mediaToken) => {
setUploadSplatIsShowing(false);
addObject({ type: AssetType.SPLAT, media_id: mediaToken, name: "Splat", object_uuid: uuidv4() });
}}
title="Upload an spz file"
titleIcon={faCube}
@@ -22,6 +22,7 @@ export interface ObjectJSON {
object_uuid: string;
object_user_data_name: string;
media_file_token: string;
media_file_type: MediaFileType;
color: string;
metalness: number;
shininess: number;
@@ -127,6 +128,7 @@ export class StoryTellerProxy3DObject {
object_uuid: this.object_uuid,
object_user_data_name: this.object_user_data_name,
media_file_token: this.media_file_token,
media_file_type: this.userData.media_file_type ?? MediaFileType.None,
color: this.color,
metalness: this.metalness,
shininess: this.shininess,