From 2902db074e39d622631c55e5f598ffd258e7377a Mon Sep 17 00:00:00 2001 From: BFlat Date: Tue, 22 Jul 2025 20:25:07 +0700 Subject: [PATCH] Upload asset ux update (#737) - image upload modal added - everything under + button popover image --- .../reusable/UploadModal3D/UploadFiles3D.tsx | 37 +-- .../reusable/UploadModal3D/UploadModal3D.tsx | 104 ++------- .../UploadModalImage/UploadFilesImage.tsx | 81 +++++++ .../UploadModalImage/UploadModalImage.tsx | 134 +++++++++++ .../reusable/UploadModalImage/index.ts | 1 + .../UploadModalImage/utilities/uploadImage.ts | 56 +++++ .../PageEnigma/comps/AssetMenu/AssetModal.tsx | 39 ++-- .../comps/Controls3D/Controls3D.tsx | 145 +++++++++--- .../components/popover/src/lib/popover.tsx | 221 ++++++++++-------- 9 files changed, 548 insertions(+), 270 deletions(-) create mode 100644 frontend/apps/editor3d/app/src/components/reusable/UploadModalImage/UploadFilesImage.tsx create mode 100644 frontend/apps/editor3d/app/src/components/reusable/UploadModalImage/UploadModalImage.tsx create mode 100644 frontend/apps/editor3d/app/src/components/reusable/UploadModalImage/index.ts create mode 100644 frontend/apps/editor3d/app/src/components/reusable/UploadModalImage/utilities/uploadImage.ts 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 && ( -
- - { {currentTab.label}
- {activeAssetTab !== "skybox" && + {/* {activeAssetTab !== "skybox" && activeAssetTab !== "all" && ( - )} + )} */}
)} {renderContent()} diff --git a/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/Controls3D/Controls3D.tsx b/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/Controls3D/Controls3D.tsx index 7ad92eb48b..1cc70466ea 100644 --- a/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/Controls3D/Controls3D.tsx +++ b/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/Controls3D/Controls3D.tsx @@ -5,6 +5,7 @@ import { faPlus, faUpRightAndDownLeftFromCenter, faCube, + faImages, faArrowUpFromBracket, } from "@fortawesome/pro-solid-svg-icons"; import { ButtonIconSelect } from "@storyteller/ui-button-icon-select"; @@ -33,10 +34,17 @@ import { AssetModal } from "../AssetMenu/AssetModal"; import { selectedMode } from "../../signals/selectedMode"; import { useSignals } from "@preact/signals-react/runtime"; import { outlinerState } from "../../signals/outliner/outliner"; -import { twMerge } from "tailwind-merge"; // eslint-disable-next-line import/no-unresolved import { setLogoutStates } from "~/signals/authentication/utilities"; import { UploadModal3D } from "../../../../components/reusable/UploadModal3D/UploadModal3D"; +import { PopoverMenu } from "@storyteller/ui-popover"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { + galleryModalVisibleViewMode, + galleryModalVisibleDuringDrag, +} from "@storyteller/ui-gallery-modal"; +import { twMerge } from "tailwind-merge"; +import { UploadModalImage } from "../../../../components/reusable/UploadModalImage"; export const Controls3D = () => { useSignals(); @@ -45,6 +53,8 @@ export const Controls3D = () => { // Action reminder is now handled through signals const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false); const [upload3DIsShowing, setUpload3DIsShowing] = useState(false); + const [isAddAssetPopoverOpen, setIsAddAssetPopoverOpen] = useState(false); + const [uploadImageIsShowing, setUploadImageIsShowing] = useState(false); // Track processed 3D models by their media token to prevent duplicates const processedModelsRef = useRef>({}); @@ -53,7 +63,12 @@ export const Controls3D = () => { // Check if scene is empty and onboarding helper is not visible const checkSceneEmpty = () => { const isSceneEmpty = - outlinerState.items.value.length === 0 && !assetModalVisible.value; + outlinerState.items.value.length === 0 && + !assetModalVisible.value && + !galleryModalVisibleViewMode.value && + !isAddAssetPopoverOpen && + !upload3DIsShowing && + !uploadImageIsShowing; setShowEmptySceneTooltip(isSceneEmpty); }; @@ -68,7 +83,13 @@ export const Controls3D = () => { unsubscribe(); }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [assetModalVisible.value]); + }, [ + assetModalVisible.value, + galleryModalVisibleViewMode.value, + isAddAssetPopoverOpen, + upload3DIsShowing, + uploadImageIsShowing, + ]); const handleModeChange = (value: string) => { selectedMode.value = value; @@ -169,8 +190,29 @@ export const Controls3D = () => { } }; - const handleOpenUpload3DModal = () => { - setUpload3DIsShowing(true); + // Open global Gallery modal + const handleOpenGalleryModal = () => { + galleryModalVisibleViewMode.value = true; + galleryModalVisibleDuringDrag.value = true; + }; + + const handleAddAssetAction = (action: string) => { + switch (action) { + case "presets": + handleOpenModal(); + break; + case "library": + handleOpenGalleryModal(); + break; + case "upload-3d": + setUpload3DIsShowing(true); + break; + case "upload-image": + setUploadImageIsShowing(true); + break; + default: + break; + } }; const openSettingsModal = () => { @@ -219,14 +261,14 @@ export const Controls3D = () => {
{showEmptySceneTooltip && (
-
+
Click + to add your first 3D asset! -
+
)} { showEmptySceneTooltip ? "hidden" : "block", )} > -
- -
- {mode === "toggle" && ( - - {item.selected && ( - - )} - + + {item.divider && ( +
)} - +
))} {showAddButton && onAdd && (