diff --git a/frontend/apps/editor3d/app/src/components/reusable/UploadModal3D/UploadFiles3D.tsx b/frontend/apps/editor3d/app/src/components/reusable/UploadModal3D/UploadFiles3D.tsx
index 4fbad4cea7..d4aac54682 100644
--- a/frontend/apps/editor3d/app/src/components/reusable/UploadModal3D/UploadFiles3D.tsx
+++ b/frontend/apps/editor3d/app/src/components/reusable/UploadModal3D/UploadFiles3D.tsx
@@ -1,8 +1,6 @@
import { useCallback, useEffect, useState, useRef } from "react";
import { ListDropdown } from "@storyteller/ui-list-dropdown";
import { Button } from "@storyteller/ui-button";
-import { Input } from "@storyteller/ui-input";
-import { Label } from "@storyteller/ui-label";
import { FileUploader } from "@storyteller/ui-file-uploader";
import { loadPreviewOnCanvas, snapshotCanvasAsThumbnail } from "./utilities";
import { upload3DObjects } from "./utilities/upload3DObjects";
@@ -48,13 +46,6 @@ export const UploadFiles3D = ({
: undefined,
);
- const [uploadTitle, setUploadTitle] = useState<{
- value: string;
- error?: string;
- }>({
- value: "",
- });
-
const [assetFile, setAssetFile] = useState<{
value: File | null;
error?: string;
@@ -70,14 +61,6 @@ export const UploadFiles3D = ({
);
const handleSubmit = () => {
- if (!uploadTitle.value) {
- setUploadTitle((curr) => ({
- ...curr,
- error: "Please enter a title.",
- }));
- return;
- }
-
if (!assetFile.value) {
setAssetFile((curr) => ({
...curr,
@@ -86,8 +69,11 @@ export const UploadFiles3D = ({
return;
}
+ // Use the filename as the title
+ const title = assetFile.value.name.split(".")[0];
+
upload3DObjects({
- title: uploadTitle.value,
+ title: title,
assetFile: assetFile.value,
thumbnailSnapshot: thumbnailFile,
engineCategory: engineCategory,
@@ -123,18 +109,7 @@ export const UploadFiles3D = ({
return (
<>
-
-
-
- setUploadTitle({ value: event.target.value })}
- className={uploadTitle.error ? "mb-3" : ""}
- />
-
-
+
Upload
diff --git a/frontend/apps/editor3d/app/src/components/reusable/UploadModal3D/UploadModal3D.tsx b/frontend/apps/editor3d/app/src/components/reusable/UploadModal3D/UploadModal3D.tsx
index a54f373a18..15be5f9f2e 100644
--- a/frontend/apps/editor3d/app/src/components/reusable/UploadModal3D/UploadModal3D.tsx
+++ b/frontend/apps/editor3d/app/src/components/reusable/UploadModal3D/UploadModal3D.tsx
@@ -1,19 +1,19 @@
import { useEffect, useState } from "react";
import { LoadingDots } from "@storyteller/ui-loading";
import { Modal } from "@storyteller/ui-modal";
-import { Label } from "@storyteller/ui-label";
import { UploadAssetError, UploadSuccess } from "@storyteller/ui-upload-modal";
import { UploadFiles3D } from "./UploadFiles3D";
import { initialUploaderState, UploaderState } from "../../../models";
-import { Select } from "@storyteller/ui-select";
import {
FilterEngineCategories,
UploaderStates,
OBJECT_FILE_TYPE,
- CHARACTER_MIXAMO_FILE_TYPE,
- IMAGEPLANE_FILE_TYPE,
} from "../../../enums";
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
+import {
+ galleryModalVisibleViewMode,
+ galleryModalVisibleDuringDrag,
+} from "@storyteller/ui-gallery-modal";
interface Props {
onClose: () => void;
@@ -26,63 +26,17 @@ interface Props {
hasLength?: boolean;
hasThumbnailUpload?: boolean;
};
- preselectedCategory?: FilterEngineCategories;
- isSelectVisible: boolean;
}
-// Map categories to their allowed file types
-const categoryFileTypes: Partial> = {
- [FilterEngineCategories.OBJECT]: Object.values(OBJECT_FILE_TYPE),
- [FilterEngineCategories.CHARACTER]: [
- ...Object.values(CHARACTER_MIXAMO_FILE_TYPE),
- ],
- [FilterEngineCategories.IMAGE_PLANE]: Object.values(IMAGEPLANE_FILE_TYPE),
- [FilterEngineCategories.LOCATION]: Object.values(OBJECT_FILE_TYPE),
- [FilterEngineCategories.CREATURE]: Object.values(OBJECT_FILE_TYPE),
-};
-// Excluded category options for the upload modal
-const categoryOptions = Object.entries(FilterEngineCategories)
- .filter(
- ([, value]) =>
- ![
- "audio",
- "video_plane",
- "skybox",
- "set_dressing",
- "expression",
- "animation",
- "image_plane",
- "scene",
- ].includes(value),
- )
- .sort(([, a], [, b]) => (a === "object" ? -1 : b === "object" ? 1 : 0))
- .map(([key, value]) => ({
- // This is because sets is location i guess - BFlat
- label:
- key === "LOCATION"
- ? "Set"
- : key.charAt(0) + key.slice(1).toLowerCase().replace(/_/g, " "),
- value: value,
- }));
+const objectFileTypes = Object.values(OBJECT_FILE_TYPE);
-export function UploadModal3D({
- isOpen,
- onClose,
- onSuccess,
- title,
- titleIcon,
- options,
- preselectedCategory,
- isSelectVisible,
-}: Props & { preselectedCategory?: FilterEngineCategories }) {
+export function UploadModal3D(props: Props) {
+ const { isOpen, onClose, onSuccess, title, titleIcon, options } = props;
const [uploaderState, setUploaderState] =
useState(initialUploaderState);
- const [selectedCategory, setSelectedCategory] =
- useState(
- preselectedCategory !== undefined
- ? preselectedCategory
- : FilterEngineCategories.OBJECT,
- );
+
+ // Category fixed to OBJECT
+ const selectedCategory = FilterEngineCategories.OBJECT;
const updateUploaderState = (newState: UploaderState) => {
setUploaderState(newState);
@@ -98,19 +52,12 @@ export function UploadModal3D({
}
}, [isOpen]);
- useEffect(() => {
- if (preselectedCategory !== undefined) {
- setSelectedCategory(preselectedCategory);
- }
- }, [preselectedCategory]);
-
- useEffect(() => {
- console.log("Preselected category:", preselectedCategory);
- console.log("Initial selected category:", selectedCategory);
- }, [preselectedCategory, selectedCategory]);
-
useEffect(() => {
if (uploaderState.status === UploaderStates.success) {
+ // Automatically open the global Gallery modal after a successful upload
+ galleryModalVisibleViewMode.value = true;
+ galleryModalVisibleDuringDrag.value = true;
+
onSuccess(selectedCategory);
onClose();
}
@@ -122,31 +69,10 @@ export function UploadModal3D({
case UploaderStates.ready:
return (
- {isSelectVisible && (
-
-
-
- )}
void;
+ onUploadProgress: (newState: UploaderState) => void;
+}
+
+export const UploadFilesImage = ({
+ fileTypes,
+ onClose,
+ onUploadProgress,
+}: Props) => {
+ const [assetFile, setAssetFile] = useState<{
+ value: File | null;
+ error?: string;
+ }>({ value: null });
+
+ const handleSubmit = () => {
+ if (!assetFile.value) {
+ setAssetFile((curr) => ({
+ ...curr,
+ error: "Please select an image to upload.",
+ }));
+ return;
+ }
+
+ // Use the filename as the title
+ const title = assetFile.value.name.split(".")[0];
+
+ uploadImage({
+ title: title,
+ assetFile: assetFile.value,
+ progressCallback: onUploadProgress,
+ });
+ };
+
+ return (
+ <>
+
+
{
+ setAssetFile({
+ value: file,
+ });
+ }}
+ />
+
+ {assetFile.error && (
+ {assetFile.error}
+ )}
+
+ {assetFile.value && (
+
+
})
+
+ )}
+
+
+
+
+
+
+ >
+ );
+};
diff --git a/frontend/apps/editor3d/app/src/components/reusable/UploadModalImage/UploadModalImage.tsx b/frontend/apps/editor3d/app/src/components/reusable/UploadModalImage/UploadModalImage.tsx
new file mode 100644
index 0000000000..15c1d06c72
--- /dev/null
+++ b/frontend/apps/editor3d/app/src/components/reusable/UploadModalImage/UploadModalImage.tsx
@@ -0,0 +1,134 @@
+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";
+import { UploadFilesImage } from "./UploadFilesImage";
+import { initialUploaderState, UploaderState } from "../../../models";
+import {
+ FilterEngineCategories,
+ UploaderStates,
+ IMAGEPLANE_FILE_TYPE,
+} from "../../../enums";
+import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
+import {
+ galleryModalVisibleViewMode,
+ galleryModalVisibleDuringDrag,
+} from "@storyteller/ui-gallery-modal";
+
+interface Props {
+ onClose: () => void;
+ onSuccess: (category: FilterEngineCategories) => void;
+ isOpen: boolean;
+ title: string;
+ titleIcon: IconDefinition;
+}
+
+const imageFileTypes = Object.values(IMAGEPLANE_FILE_TYPE);
+
+export function UploadModalImage(props: Props) {
+ const { isOpen, onClose, onSuccess, title, titleIcon } = props;
+ const [uploaderState, setUploaderState] =
+ useState(initialUploaderState);
+
+ // Category fixed to IMAGE_PLANE
+ const selectedCategory = FilterEngineCategories.IMAGE_PLANE;
+
+ const updateUploaderState = (newState: UploaderState) => {
+ setUploaderState(newState);
+ };
+
+ const resetModalState = () => {
+ setUploaderState(initialUploaderState);
+ };
+
+ useEffect(() => {
+ if (isOpen) {
+ resetModalState();
+ }
+ }, [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);
+ onClose();
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [uploaderState.status]);
+
+ const UploaderModalContent = () => {
+ switch (uploaderState.status) {
+ case UploaderStates.ready:
+ return (
+
+
+
+ );
+ case UploaderStates.uploadingAsset:
+ case UploaderStates.uploadingCover:
+ case UploaderStates.settingCover:
+ return (
+ <>
+
+ Uploading...
+ >
+ );
+ case UploaderStates.success: {
+ return (
+ {
+ onClose();
+ onSuccess(selectedCategory);
+ }}
+ />
+ );
+ }
+ case UploaderStates.assetError:
+ return (
+ {
+ resetModalState();
+ }}
+ type={selectedCategory}
+ errorMessage={uploaderState.errorMessage}
+ />
+ );
+ case UploaderStates.coverCreateError:
+ case UploaderStates.coverSetError:
+ return (
+ {
+ resetModalState();
+ }}
+ type={"Thumbnail"}
+ errorMessage={uploaderState.errorMessage}
+ />
+ );
+ }
+ return undefined;
+ };
+
+ return (
+
+
+
+ );
+}
diff --git a/frontend/apps/editor3d/app/src/components/reusable/UploadModalImage/index.ts b/frontend/apps/editor3d/app/src/components/reusable/UploadModalImage/index.ts
new file mode 100644
index 0000000000..08b63b0d62
--- /dev/null
+++ b/frontend/apps/editor3d/app/src/components/reusable/UploadModalImage/index.ts
@@ -0,0 +1 @@
+export { UploadModalImage } from "./UploadModalImage";
diff --git a/frontend/apps/editor3d/app/src/components/reusable/UploadModalImage/utilities/uploadImage.ts b/frontend/apps/editor3d/app/src/components/reusable/UploadModalImage/utilities/uploadImage.ts
new file mode 100644
index 0000000000..c3ce8bb22a
--- /dev/null
+++ b/frontend/apps/editor3d/app/src/components/reusable/UploadModalImage/utilities/uploadImage.ts
@@ -0,0 +1,56 @@
+import { v4 as uuidv4 } from "uuid";
+import { MediaUploadApi } from "../../../../Classes/ApiManager";
+import { UploaderStates } from "../../../../enums";
+import { UploaderState } from "../../../../models";
+import { getFileName } from "../../../../utilities";
+import { EIntermediateFile } from "~/enums/EIntermediateFile";
+
+export const uploadImage = async ({
+ title,
+ assetFile,
+ progressCallback,
+}: {
+ title: string;
+ assetFile: File;
+ progressCallback: (newState: UploaderState) => void;
+}) => {
+ const mediaUploadApi = new MediaUploadApi();
+
+ console.log("Uploading image plane:", assetFile);
+
+ progressCallback({ status: UploaderStates.uploadingAsset });
+
+ const assetResponse = await mediaUploadApi.UploadImage({
+ blob: assetFile,
+ fileName: getFileName(assetFile),
+ uuid: uuidv4(),
+ maybe_title: title,
+ is_intermediate_system_file: EIntermediateFile.false,
+ });
+
+ console.log("Image plane response:", assetResponse);
+
+ if (assetResponse == undefined) {
+ console.log("Error: Could not upload image plane!");
+ progressCallback({
+ status: UploaderStates.assetError,
+ errorMessage: "Could not upload image plane!",
+ });
+ return;
+ }
+
+ if (!assetResponse.success || !assetResponse.data) {
+ console.log("Error:", assetResponse.errorMessage);
+ progressCallback({
+ status: UploaderStates.assetError,
+ errorMessage: assetResponse.errorMessage,
+ });
+ return;
+ }
+
+ console.log("Upload successful:", assetResponse.data);
+ progressCallback({
+ status: UploaderStates.success,
+ data: assetResponse.data,
+ });
+};
diff --git a/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/AssetMenu/AssetModal.tsx b/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/AssetMenu/AssetModal.tsx
index 9ac91f6990..e8cd102cff 100644
--- a/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/AssetMenu/AssetModal.tsx
+++ b/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/AssetMenu/AssetModal.tsx
@@ -10,14 +10,13 @@ import {
faChevronRight,
faMountainCity,
faDog,
- faImage,
faUpFromLine,
} from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Button } from "@storyteller/ui-button";
import { CloseButton } from "@storyteller/ui-close-button";
import { Input } from "@storyteller/ui-input";
-import { TabSelector } from "@storyteller/ui-tab-selector";
+// import { TabSelector } from "@storyteller/ui-tab-selector";
import React, {
useState,
useEffect,
@@ -306,17 +305,17 @@ export const AssetModal = () => {
icon: faSun,
items: activeLibraryTab === "library" ? demoSkyboxItems.value : [],
},
- {
- id: "image-planes",
- label: "Image Planes",
- labelSingle: "Image Plane",
- icon: faImage,
- engineCategory: FilterEngineCategories.IMAGE_PLANE,
- items:
- activeLibraryTab === "library"
- ? (featuredImagePlanes ?? [])
- : (userImagePlanes ?? []),
- },
+ // {
+ // id: "image-planes",
+ // label: "Image Planes",
+ // labelSingle: "Image Plane",
+ // icon: faImage,
+ // engineCategory: FilterEngineCategories.IMAGE_PLANE,
+ // items:
+ // activeLibraryTab === "library"
+ // ? (featuredImagePlanes ?? [])
+ // : (userImagePlanes ?? []),
+ // },
],
// eslint-disable-next-line react-hooks/exhaustive-deps
[
@@ -500,7 +499,7 @@ export const AssetModal = () => {
- 3D Assets
+ ArtCraft Presets
{/*
-
+ */}
{assetTabs.map((tab) => (