From a8d0541c3169656e714e1ccdd0effaffa298ce3c Mon Sep 17 00:00:00 2001 From: wil Date: Sun, 7 Jul 2024 21:19:07 -0400 Subject: [PATCH] Feature/ Sets Tab; Fixes/ Tabs Improvements (#578) Tabs Improvements: - refactor tabs so no more race condition revolving unneccessary signals - clean up signals - refactor upload modal movement to be non-signaled and generalized for more files types - better naming for files and components, better error handling, eslint type any fix - upload file screen can use less state rerenders and better error messages Object Uploader - adapt the old object upload modal with 3d object preview into the updated best version of upload modal - preview loader code improvements, better flow and organization Sets Tab: - create a set tab, it currently show objects - hide it behind a FeatureFlag --------- Co-authored-by: wil --- .env | 1 + .../reusable/FileWrapper/FileWrapper.tsx | 4 +- .../UploadModal}/DragAndDropZone.tsx | 0 .../UploadModal/FileUploader.tsx} | 8 +- .../UploadModal}/UploadAssetError.tsx | 14 +- .../reusable/UploadModal/UploadFiles.tsx | 137 +++++++++++ .../UploadModal/UploadModal.tsx} | 84 ++++--- .../UploadModal}/UploadSuccess.tsx | 0 .../UploadModal/index.ts} | 0 .../UploadModal3DPreview/UploadFiles3D.tsx | 185 ++++++++++++++ .../UploadModal3DPreview.tsx | 193 +++++++++++++++ .../reusable/UploadModal3DPreview/index.ts | 1 + .../UploadModal3DPreview/utilities/index.ts | 2 + .../utilities/loadPreviewOnCanvas.ts | 226 ++++++++++++++++++ .../utilities/uploadAsset.ts | 53 ++++ app/components/reusable/index.ts | 2 + .../UploadModalMovement/UploadFiles.tsx | 108 --------- .../signaled/UploadModalMovement/index.tsx | 1 - .../UploadModal.scss | 0 .../UploadModalObject.tsx} | 17 +- .../fitCameraToCenteredObject.ts | 2 +- .../signaled/UploadModalObject/index.tsx | 1 + app/components/signaled/index.ts | 3 +- app/enums/AssetType.ts | 1 - app/enums/FeatureFlags.ts | 1 + app/enums/FileTypes.ts | 12 + app/enums/Tabs.ts | 9 + app/enums/UploaderState.ts | 10 + app/enums/index.ts | 2 + .../PageEnigma/comps/SidePanel/SidePanel.tsx | 25 +- .../PageEnigma/comps/SidePanel/tabList.tsx | 64 +++++ .../comps/SidePanelMenu/SidePanelMenu.tsx | 86 +++++++ .../PageEnigma/comps/SidePanelMenu/index.ts | 1 + .../comps/SidePanelTabs/SidePanelMenu.tsx | 89 ------- .../comps/SidePanelTabs/SidePanelTabs.tsx | 21 +- .../itemTabs/hooks/usePagination.ts | 12 - .../{itemTabs => sharedComps}/ItemElement.tsx | 0 .../ItemElements.tsx | 0 .../{comps => sharedComps}/TabTitle.tsx | 8 +- .../comps/SidePanelTabs/sharedComps/index.ts | 3 + .../{itemTabs => tabComps}/AnimationTab.tsx | 31 +-- .../AudioTab/AudioTab.tsx | 0 .../AudioTab/audioInferenceElement.tsx | 0 .../AudioTab/audioItemElement.tsx | 0 .../AudioTab/audioItemElements.tsx | 0 .../AudioTab/audioTypePills.tsx | 0 .../{itemTabs => tabComps}/AudioTab/index.ts | 0 .../AudioTab/pageAudioGeneration.tsx | 2 +- .../AudioTab/pageAudioLibrary.tsx | 2 +- .../AudioTab/pageSelectTtsModel.tsx | 2 +- .../AudioTab/pageSelectV2VModel.tsx | 2 +- .../AudioTab/pageTTS.tsx | 0 .../AudioTab/pageVoiceToVoice.tsx | 0 .../{itemTabs => tabComps}/AudioTab/values.ts | 0 .../AudioTab/voiceModelElement.tsx | 0 .../{itemTabs => tabComps}/CameraTab.tsx | 11 +- .../{itemTabs => tabComps}/CharactersTab.tsx | 6 +- .../{itemTabs => tabComps}/ExpressionTab.tsx | 16 +- .../{itemTabs => tabComps}/ObjectsTab.tsx | 6 +- .../comps/SidePanelTabs/tabComps/SetsTab.tsx | 172 +++++++++++++ .../StylizeTab/GenerateMovieButton.tsx | 0 .../StylizeTab/IPAdapter.tsx | 0 .../StylizeTab/ItemPicker.tsx | 0 .../StylizeTab/PageStyleSelection.tsx | 2 +- .../StylizeTab/Prompts.tsx | 0 .../StylizeTab/StyleButtons.tsx | 0 .../StylizeTab/StyleSelection.tsx | 0 .../StylizeTab/StyleSelectionButton.tsx | 0 .../StylizeTab/StylizeTab.tsx | 2 +- .../comps/SidePanelTabs/tabList.tsx | 55 ----- .../utilities/fetchMediaItems.ts | 85 +++++++ .../comps/SidePanelTabs/utilities/index.ts | 2 + .../comps/SidePanelTabs/utilities/misc.ts | 46 ++++ app/pages/PageEnigma/signals/sidePanel.ts | 3 - 74 files changed, 1454 insertions(+), 377 deletions(-) rename app/components/{signaled/UploadModalMovement => reusable/UploadModal}/DragAndDropZone.tsx (100%) rename app/components/{signaled/UploadModalMovement/UploadFile.tsx => reusable/UploadModal/FileUploader.tsx} (69%) rename app/components/{signaled/UploadModalMovement => reusable/UploadModal}/UploadAssetError.tsx (60%) create mode 100644 app/components/reusable/UploadModal/UploadFiles.tsx rename app/components/{signaled/UploadModalMovement/UploadModalMovement.tsx => reusable/UploadModal/UploadModal.tsx} (65%) rename app/components/{signaled/UploadModalMovement => reusable/UploadModal}/UploadSuccess.tsx (100%) rename app/components/{signaled/UploadModal/index.tsx => reusable/UploadModal/index.ts} (100%) create mode 100644 app/components/reusable/UploadModal3DPreview/UploadFiles3D.tsx create mode 100644 app/components/reusable/UploadModal3DPreview/UploadModal3DPreview.tsx create mode 100644 app/components/reusable/UploadModal3DPreview/index.ts create mode 100644 app/components/reusable/UploadModal3DPreview/utilities/index.ts create mode 100644 app/components/reusable/UploadModal3DPreview/utilities/loadPreviewOnCanvas.ts create mode 100644 app/components/reusable/UploadModal3DPreview/utilities/uploadAsset.ts delete mode 100644 app/components/signaled/UploadModalMovement/UploadFiles.tsx delete mode 100644 app/components/signaled/UploadModalMovement/index.tsx rename app/components/signaled/{UploadModal => UploadModalObject}/UploadModal.scss (100%) rename app/components/signaled/{UploadModal/UploadModal.tsx => UploadModalObject/UploadModalObject.tsx} (98%) rename app/components/signaled/{UploadModal => UploadModalObject}/fitCameraToCenteredObject.ts (97%) create mode 100644 app/components/signaled/UploadModalObject/index.tsx create mode 100644 app/enums/Tabs.ts create mode 100644 app/enums/UploaderState.ts create mode 100644 app/pages/PageEnigma/comps/SidePanel/tabList.tsx create mode 100644 app/pages/PageEnigma/comps/SidePanelMenu/SidePanelMenu.tsx create mode 100644 app/pages/PageEnigma/comps/SidePanelMenu/index.ts delete mode 100644 app/pages/PageEnigma/comps/SidePanelTabs/SidePanelMenu.tsx delete mode 100644 app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/hooks/usePagination.ts rename app/pages/PageEnigma/comps/SidePanelTabs/{itemTabs => sharedComps}/ItemElement.tsx (100%) rename app/pages/PageEnigma/comps/SidePanelTabs/{itemTabs => sharedComps}/ItemElements.tsx (100%) rename app/pages/PageEnigma/comps/SidePanelTabs/{comps => sharedComps}/TabTitle.tsx (84%) create mode 100644 app/pages/PageEnigma/comps/SidePanelTabs/sharedComps/index.ts rename app/pages/PageEnigma/comps/SidePanelTabs/{itemTabs => tabComps}/AnimationTab.tsx (90%) rename app/pages/PageEnigma/comps/SidePanelTabs/{itemTabs => tabComps}/AudioTab/AudioTab.tsx (100%) rename app/pages/PageEnigma/comps/SidePanelTabs/{itemTabs => tabComps}/AudioTab/audioInferenceElement.tsx (100%) rename app/pages/PageEnigma/comps/SidePanelTabs/{itemTabs => tabComps}/AudioTab/audioItemElement.tsx (100%) rename app/pages/PageEnigma/comps/SidePanelTabs/{itemTabs => tabComps}/AudioTab/audioItemElements.tsx (100%) rename app/pages/PageEnigma/comps/SidePanelTabs/{itemTabs => tabComps}/AudioTab/audioTypePills.tsx (100%) rename app/pages/PageEnigma/comps/SidePanelTabs/{itemTabs => tabComps}/AudioTab/index.ts (100%) rename app/pages/PageEnigma/comps/SidePanelTabs/{itemTabs => tabComps}/AudioTab/pageAudioGeneration.tsx (99%) rename app/pages/PageEnigma/comps/SidePanelTabs/{itemTabs => tabComps}/AudioTab/pageAudioLibrary.tsx (99%) rename app/pages/PageEnigma/comps/SidePanelTabs/{itemTabs => tabComps}/AudioTab/pageSelectTtsModel.tsx (98%) rename app/pages/PageEnigma/comps/SidePanelTabs/{itemTabs => tabComps}/AudioTab/pageSelectV2VModel.tsx (98%) rename app/pages/PageEnigma/comps/SidePanelTabs/{itemTabs => tabComps}/AudioTab/pageTTS.tsx (100%) rename app/pages/PageEnigma/comps/SidePanelTabs/{itemTabs => tabComps}/AudioTab/pageVoiceToVoice.tsx (100%) rename app/pages/PageEnigma/comps/SidePanelTabs/{itemTabs => tabComps}/AudioTab/values.ts (100%) rename app/pages/PageEnigma/comps/SidePanelTabs/{itemTabs => tabComps}/AudioTab/voiceModelElement.tsx (100%) rename app/pages/PageEnigma/comps/SidePanelTabs/{itemTabs => tabComps}/CameraTab.tsx (87%) rename app/pages/PageEnigma/comps/SidePanelTabs/{itemTabs => tabComps}/CharactersTab.tsx (97%) rename app/pages/PageEnigma/comps/SidePanelTabs/{itemTabs => tabComps}/ExpressionTab.tsx (94%) rename app/pages/PageEnigma/comps/SidePanelTabs/{itemTabs => tabComps}/ObjectsTab.tsx (98%) create mode 100644 app/pages/PageEnigma/comps/SidePanelTabs/tabComps/SetsTab.tsx rename app/pages/PageEnigma/comps/SidePanelTabs/{itemTabs => tabComps}/StylizeTab/GenerateMovieButton.tsx (100%) rename app/pages/PageEnigma/comps/SidePanelTabs/{itemTabs => tabComps}/StylizeTab/IPAdapter.tsx (100%) rename app/pages/PageEnigma/comps/SidePanelTabs/{itemTabs => tabComps}/StylizeTab/ItemPicker.tsx (100%) rename app/pages/PageEnigma/comps/SidePanelTabs/{itemTabs => tabComps}/StylizeTab/PageStyleSelection.tsx (96%) rename app/pages/PageEnigma/comps/SidePanelTabs/{itemTabs => tabComps}/StylizeTab/Prompts.tsx (100%) rename app/pages/PageEnigma/comps/SidePanelTabs/{itemTabs => tabComps}/StylizeTab/StyleButtons.tsx (100%) rename app/pages/PageEnigma/comps/SidePanelTabs/{itemTabs => tabComps}/StylizeTab/StyleSelection.tsx (100%) rename app/pages/PageEnigma/comps/SidePanelTabs/{itemTabs => tabComps}/StylizeTab/StyleSelectionButton.tsx (100%) rename app/pages/PageEnigma/comps/SidePanelTabs/{itemTabs => tabComps}/StylizeTab/StylizeTab.tsx (99%) delete mode 100644 app/pages/PageEnigma/comps/SidePanelTabs/tabList.tsx create mode 100644 app/pages/PageEnigma/comps/SidePanelTabs/utilities/fetchMediaItems.ts create mode 100644 app/pages/PageEnigma/comps/SidePanelTabs/utilities/index.ts create mode 100644 app/pages/PageEnigma/comps/SidePanelTabs/utilities/misc.ts diff --git a/.env b/.env index b17f26e29b..39a7c3654a 100644 --- a/.env +++ b/.env @@ -7,3 +7,4 @@ DEPLOY_PRIME_URL="https://studio-staging.studio.storyteller.ai" REACT_APP_PUBLIC_POSTHOG_KEY="phc_jBFgac0mVALAFk3negnSfYCcHgvkT00yBLQDmCDYNBb" REACT_APP_PUBLIC_POSTHOG_UI="https://us.i.posthog.com" CONTEXT="DEVELOPMENT" +# CONTEXT="PRODUCTION" diff --git a/app/components/reusable/FileWrapper/FileWrapper.tsx b/app/components/reusable/FileWrapper/FileWrapper.tsx index 0e8764fe4f..3cab26f232 100644 --- a/app/components/reusable/FileWrapper/FileWrapper.tsx +++ b/app/components/reusable/FileWrapper/FileWrapper.tsx @@ -1,6 +1,6 @@ import React, { ReactNode, useId, useRef, useState } from "react"; import { AssetType } from "~/enums"; -import { UploadModal } from "~/components"; +import { UploadModalObject } from "~/components"; import { twMerge } from "tailwind-merge"; interface Props { @@ -83,7 +83,7 @@ export function FileWrapper({ {...rest} /> {render(id)} - void; } -export const UploadFile = ({ fileTypes, title, file, setFile }: Props) => { +export const FileUploader = ({ fileTypes, title, file, setFile }: Props) => { return (
{title} - { setFile(file); }} @@ -21,7 +21,7 @@ export const UploadFile = ({ fileTypes, title, file, setFile }: Props) => { maxSize={50} > - +
); }; diff --git a/app/components/signaled/UploadModalMovement/UploadAssetError.tsx b/app/components/reusable/UploadModal/UploadAssetError.tsx similarity index 60% rename from app/components/signaled/UploadModalMovement/UploadAssetError.tsx rename to app/components/reusable/UploadModal/UploadAssetError.tsx index 72a52f4da4..c5f9611667 100644 --- a/app/components/signaled/UploadModalMovement/UploadAssetError.tsx +++ b/app/components/reusable/UploadModal/UploadAssetError.tsx @@ -1,21 +1,23 @@ -import { Button } from "~/components"; +import { Button, P, H3 } from "~/components"; +import { FilterEngineCategories } from "~/enums"; interface Props { onCancel: () => void; onRetry: () => void; - isAssetError: boolean; + type: FilterEngineCategories | string; + errorMessage?: string; } export const UploadAssetError = ({ onCancel, onRetry, - isAssetError, + type, + errorMessage, }: Props) => { return ( <> - {isAssetError - ? "There was a problem uploading your file." - : "There was a problem uploading the thumbnail for your file."} +

Error in Uploading {type}

+

{errorMessage ?? "Unknown Error"}

+ +
+ + + ); +}; diff --git a/app/components/signaled/UploadModalMovement/UploadModalMovement.tsx b/app/components/reusable/UploadModal/UploadModal.tsx similarity index 65% rename from app/components/signaled/UploadModalMovement/UploadModalMovement.tsx rename to app/components/reusable/UploadModal/UploadModal.tsx index 8b6cd3260e..503323b7d5 100644 --- a/app/components/signaled/UploadModalMovement/UploadModalMovement.tsx +++ b/app/components/reusable/UploadModal/UploadModal.tsx @@ -7,7 +7,11 @@ import { UploadAssetError } from "./UploadAssetError"; import { UploadSuccess } from "./UploadSuccess"; import { UploadFiles } from "./UploadFiles"; import { MediaFilesApi, MediaUploadApi } from "~/Classes/ApiManager"; -import { FilterEngineCategories, MediaFileAnimationType } from "~/enums"; +import { + FilterEngineCategories, + MediaFileAnimationType, + UploaderState, +} from "~/enums"; import { getFileName } from "~/utilities"; interface Props { @@ -16,34 +20,34 @@ interface Props { isOpen: boolean; title: string; fileTypes: string[]; - typeOptions: { [key: string]: string }[]; type: FilterEngineCategories; + options?: { + fileSubtypes?: { [key: string]: string }[]; + hasLength?: boolean; + hasThumbnailUpload?: boolean; + }; } -enum UploaderState { - ready, - uploadingAsset, - uploadingCover, - settingCover, - success, - assetError, - coverCreateError, - coverSetError, -} +const initialState = { + status: UploaderState.ready, +}; -export function UploadModalMovement({ +export function UploadModal({ isOpen, onClose, onSuccess, title, fileTypes, - typeOptions, type, + options, }: Props) { - const [objUploadStatus, setObjUploadStatus] = useState(UploaderState.ready); + const [uploaderState, setUploaderState] = useState<{ + status: UploaderState; + errorMessage?: string; + }>(initialState); const resetModalState = () => { - setObjUploadStatus(UploaderState.ready); + setUploaderState(initialState); }; useEffect(() => { @@ -60,12 +64,12 @@ export function UploadModalMovement({ thumbnailFile, }: { title: string; - typeOption: MediaFileAnimationType; + typeOption?: MediaFileAnimationType; assetFile: File; length: number; thumbnailFile: File | null; }) => { - setObjUploadStatus(UploaderState.uploadingAsset); + setUploaderState({ status: UploaderState.uploadingAsset }); const mediaUploadApi = new MediaUploadApi(); const assetReponse = await mediaUploadApi.UploadNewEngineAsset({ file: assetFile, @@ -78,16 +82,19 @@ export function UploadModalMovement({ }); if (!assetReponse.success || !assetReponse.data) { - setObjUploadStatus(UploaderState.assetError); + setUploaderState({ + status: UploaderState.assetError, + errorMessage: assetReponse.errorMessage, + }); return; } const assetToken = assetReponse.data; if (!thumbnailFile) { - setObjUploadStatus(UploaderState.success); + setUploaderState({ status: UploaderState.success }); return; } - setObjUploadStatus(UploaderState.uploadingCover); + setUploaderState({ status: UploaderState.uploadingCover }); const thumbnailResponse = await mediaUploadApi.UploadImage({ uuid: uuidv4(), blob: thumbnailFile, @@ -95,11 +102,14 @@ export function UploadModalMovement({ maybe_title: "thumbnail_" + title, }); if (!thumbnailResponse.success || !thumbnailResponse.data) { - setObjUploadStatus(UploaderState.coverCreateError); + setUploaderState({ + status: UploaderState.coverCreateError, + errorMessage: thumbnailResponse.errorMessage, + }); return; } - setObjUploadStatus(UploaderState.settingCover); + setUploaderState({ status: UploaderState.settingCover }); const thumbnailToken = thumbnailResponse.data; const mediaFilesApi = new MediaFilesApi(); const setThumbnailResponse = await mediaFilesApi.UpdateCoverImage({ @@ -107,21 +117,24 @@ export function UploadModalMovement({ imageToken: thumbnailToken, }); if (!setThumbnailResponse.success) { - setObjUploadStatus(UploaderState.coverSetError); + setUploaderState({ + status: UploaderState.coverSetError, + errorMessage: setThumbnailResponse.errorMessage, + }); return; } - setObjUploadStatus(UploaderState.success); + setUploaderState({ status: UploaderState.success }); }; - const ObjUploaderContent = () => { - switch (objUploadStatus) { + const UploaderModalContent = () => { + switch (uploaderState.status) { case UploaderState.ready: return ( ); @@ -145,6 +158,16 @@ export function UploadModalMovement({ /> ); case UploaderState.assetError: + return ( + { + resetModalState(); + }} + type={type} + errorMessage={uploaderState.errorMessage} + /> + ); case UploaderState.coverCreateError: case UploaderState.coverSetError: return ( @@ -153,7 +176,8 @@ export function UploadModalMovement({ onRetry={() => { resetModalState(); }} - isAssetError={objUploadStatus === UploaderState.assetError} + type={"Thumbnail"} + errorMessage={uploaderState.errorMessage} /> ); } @@ -161,7 +185,7 @@ export function UploadModalMovement({ return ( - + ); } diff --git a/app/components/signaled/UploadModalMovement/UploadSuccess.tsx b/app/components/reusable/UploadModal/UploadSuccess.tsx similarity index 100% rename from app/components/signaled/UploadModalMovement/UploadSuccess.tsx rename to app/components/reusable/UploadModal/UploadSuccess.tsx diff --git a/app/components/signaled/UploadModal/index.tsx b/app/components/reusable/UploadModal/index.ts similarity index 100% rename from app/components/signaled/UploadModal/index.tsx rename to app/components/reusable/UploadModal/index.ts diff --git a/app/components/reusable/UploadModal3DPreview/UploadFiles3D.tsx b/app/components/reusable/UploadModal3DPreview/UploadFiles3D.tsx new file mode 100644 index 0000000000..0842adcec3 --- /dev/null +++ b/app/components/reusable/UploadModal3DPreview/UploadFiles3D.tsx @@ -0,0 +1,185 @@ +import { useCallback, useEffect, useState, useRef } from "react"; + +import { MediaFileAnimationType } from "~/enums"; + +import { Button, H6, Input, ListDropdown } from "~/components"; +import { FileUploader } from "../UploadModal/FileUploader"; +import { loadPreviewOnCanvas } from "./utilities"; + +interface Props { + title: string; + fileTypes: string[]; + onClose: () => void; + options?: { + fileSubtypes?: { [key: string]: string }[]; + hasLength?: boolean; + hasThumbnailUpload?: boolean; + }; + onSubmit: (options: { + title: string; + typeOption?: MediaFileAnimationType; + assetFile: File; + length: number; + thumbnailFile: File | null; + }) => void; +} + +export const UploadFiles3D = ({ + fileTypes, + onClose, + title, + options, + onSubmit, +}: Props) => { + const fileSubtypes = options?.fileSubtypes; + const hasLength = options?.hasLength; + const hasThumbnailUpload = options?.hasThumbnailUpload; + + const canvasRef = useRef(undefined); + const canvasCallbackRef = useCallback((node: HTMLCanvasElement) => { + if (node !== null) { + canvasRef.current = node; + } + }, []); + + const [typeOption, setTypeOption] = useState< + MediaFileAnimationType | undefined + >( + fileSubtypes + ? (Object.values(fileSubtypes[0])[0] as MediaFileAnimationType) + : undefined, + ); + const [uploadTitle, setUploadTitle] = useState<{ + value: string; + error?: string; + }>({ + value: "", + }); + + const [assetFile, setAssetFile] = useState<{ + value: File | null; + error?: string; + }>({ value: null }); + + const [previewStatus, setPreviewStatus] = useState<{ + type: string; + message?: string; + }>({ type: "init" }); + + const [uploadLength, setUploadLength] = useState(); + const [thumbnailFile, setThumbnailFile] = useState(null); + + const handleSubmit = () => { + if (!uploadTitle.value) { + setUploadTitle((curr) => ({ + ...curr, + error: "Please enter a title.", + })); + return; + } + + if (!assetFile.value) { + setAssetFile((curr) => ({ + ...curr, + error: "Please select a file to upload.", + })); + return; + } + + onSubmit({ + title: uploadTitle.value, + assetFile: assetFile.value, + thumbnailFile: thumbnailFile, + length: uploadLength ?? 1000, + typeOption, + }); + }; + + useEffect(() => { + if (canvasRef.current && assetFile.value) { + loadPreviewOnCanvas({ + file: assetFile.value, + canvas: canvasRef.current, + statusCallback: (statusObject: { type: string; message?: string }) => { + setPreviewStatus(statusObject); + }, + }); + } + }, [assetFile.value]); + + return ( + <> +
+ setUploadTitle({ value: event.target.value })} + className={uploadTitle.error ? "mb-3" : ""} + /> + {fileSubtypes && fileSubtypes.length > 1 && ( + setTypeOption(value as MediaFileAnimationType)} + /> + )} + {hasLength && ( + setUploadLength(parseInt(event.target.value))} + /> + )} + { + setAssetFile({ + value: file, + }); + }} + /> + {assetFile.error && ( +
{assetFile.error}
+ )} + +
+ + {!assetFile.value && ( +
+ File Preivew +
+ )} + {previewStatus.type.includes("Error") && ( + <> +
+ {previewStatus.type} + {previewStatus.message &&
} + {previewStatus.message} +
+ + )} +
+ + {hasThumbnailUpload && ( + + )} +
+ + +
+
+ + ); +}; diff --git a/app/components/reusable/UploadModal3DPreview/UploadModal3DPreview.tsx b/app/components/reusable/UploadModal3DPreview/UploadModal3DPreview.tsx new file mode 100644 index 0000000000..1b0d912964 --- /dev/null +++ b/app/components/reusable/UploadModal3DPreview/UploadModal3DPreview.tsx @@ -0,0 +1,193 @@ +import { useEffect, useState } from "react"; +import { v4 as uuidv4 } from "uuid"; + +import { TransitionDialogue, LoadingDots } from "~/components"; + +import { UploadAssetError } from "../UploadModal/UploadAssetError"; +import { UploadSuccess } from "../UploadModal/UploadSuccess"; +import { UploadFiles3D } from "./UploadFiles3D"; +import { MediaFilesApi, MediaUploadApi } from "~/Classes/ApiManager"; +import { FilterEngineCategories, MediaFileAnimationType } from "~/enums"; +import { getFileName } from "~/utilities"; +import { uploadAsset } from "./utilities"; + +interface Props { + onClose: () => void; + onSuccess: () => void; + isOpen: boolean; + title: string; + fileTypes: string[]; + type: FilterEngineCategories; + options?: { + fileSubtypes?: { [key: string]: string }[]; + hasLength?: boolean; + hasThumbnailUpload?: boolean; + }; +} + +enum UploaderState { + ready, + uploadingAsset, + uploadingCover, + settingCover, + success, + assetError, + coverCreateError, + coverSetError, +} + +const initialState = { + status: UploaderState.ready, +}; + +export function UploadModal3DPreview({ + isOpen, + onClose, + onSuccess, + title, + fileTypes, + type, + options, +}: Props) { + const [uploaderState, setUploaderState] = useState<{ + status: UploaderState; + errorMessage?: string; + }>(initialState); + + const resetModalState = () => { + setUploaderState(initialState); + }; + + useEffect(() => { + if (isOpen) { + resetModalState(); + } + }, [isOpen]); + + const onSubmit = async ({ + title, + typeOption, + assetFile, + thumbnailFile, + }: { + title: string; + typeOption?: MediaFileAnimationType; + assetFile: File; + thumbnailFile: File | null; + }) => { + setUploaderState({ status: UploaderState.uploadingAsset }); + const mediaUploadApi = new MediaUploadApi(); + const assetReponse = await uploadAsset({ + file: assetFile, + title: title, + animationType: typeOption, + }); + + if (!assetReponse.success || !assetReponse.data) { + setUploaderState({ + status: UploaderState.assetError, + errorMessage: assetReponse.errorMessage, + }); + return; + } + const assetToken = assetReponse.data; + if (!thumbnailFile) { + setUploaderState({ status: UploaderState.success }); + return; + } + + setUploaderState({ status: UploaderState.uploadingCover }); + const thumbnailResponse = await mediaUploadApi.UploadImage({ + uuid: uuidv4(), + blob: thumbnailFile, + fileName: getFileName(thumbnailFile), + maybe_title: "thumbnail_" + title, + }); + if (!thumbnailResponse.success || !thumbnailResponse.data) { + setUploaderState({ + status: UploaderState.coverCreateError, + errorMessage: thumbnailResponse.errorMessage, + }); + return; + } + + setUploaderState({ status: UploaderState.settingCover }); + const thumbnailToken = thumbnailResponse.data; + const mediaFilesApi = new MediaFilesApi(); + const setThumbnailResponse = await mediaFilesApi.UpdateCoverImage({ + mediaFileToken: assetToken, + imageToken: thumbnailToken, + }); + if (!setThumbnailResponse.success) { + setUploaderState({ + status: UploaderState.coverSetError, + errorMessage: setThumbnailResponse.errorMessage, + }); + return; + } + setUploaderState({ status: UploaderState.success }); + }; + + const UploaderModalContent = () => { + switch (uploaderState.status) { + case UploaderState.ready: + return ( + + ); + case UploaderState.uploadingAsset: + case UploaderState.uploadingCover: + case UploaderState.settingCover: + return ( + <> + +
Uploading...
+ + ); + case UploaderState.success: + return ( + { + onSuccess(); + onClose(); + }} + /> + ); + case UploaderState.assetError: + return ( + { + resetModalState(); + }} + type={type} + errorMessage={uploaderState.errorMessage} + /> + ); + case UploaderState.coverCreateError: + case UploaderState.coverSetError: + return ( + { + resetModalState(); + }} + type={"Thumbnail"} + errorMessage={uploaderState.errorMessage} + /> + ); + } + }; + + return ( + + + + ); +} diff --git a/app/components/reusable/UploadModal3DPreview/index.ts b/app/components/reusable/UploadModal3DPreview/index.ts new file mode 100644 index 0000000000..a81eb4fdf0 --- /dev/null +++ b/app/components/reusable/UploadModal3DPreview/index.ts @@ -0,0 +1 @@ +export * from "./UploadModal3DPreview"; diff --git a/app/components/reusable/UploadModal3DPreview/utilities/index.ts b/app/components/reusable/UploadModal3DPreview/utilities/index.ts new file mode 100644 index 0000000000..028bb27d1a --- /dev/null +++ b/app/components/reusable/UploadModal3DPreview/utilities/index.ts @@ -0,0 +1,2 @@ +export * from "./loadPreviewOnCanvas"; +export * from "./uploadAsset"; diff --git a/app/components/reusable/UploadModal3DPreview/utilities/loadPreviewOnCanvas.ts b/app/components/reusable/UploadModal3DPreview/utilities/loadPreviewOnCanvas.ts new file mode 100644 index 0000000000..9647f8ad4c --- /dev/null +++ b/app/components/reusable/UploadModal3DPreview/utilities/loadPreviewOnCanvas.ts @@ -0,0 +1,226 @@ +import * as THREE from "three"; +import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"; +import { FBXLoader } from "three/addons/loaders/FBXLoader.js"; +import { FontLoader } from "three/addons/loaders/FontLoader.js"; +import { TextGeometry } from "three/addons/geometries/TextGeometry.js"; + +interface LoaderInterface { + file: File; + camera: THREE.PerspectiveCamera; + scene: THREE.Scene; + renderer: THREE.WebGLRenderer; + statusCallback: (statusObject: { type: string; message?: string }) => void; +} + +export const loadPreviewOnCanvas = ({ + file, + canvas, + statusCallback, +}: { + file: File; + canvas: HTMLCanvasElement; + statusCallback: (error: { type: string; message?: string }) => void; +}) => { + // Setup of scene, camera, and renderer in the canvas + const scene = new THREE.Scene(); + const camera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000); + camera.position.z = 2; + + const renderer = new THREE.WebGLRenderer({ + alpha: true, + antialias: true, + canvas: canvas, + preserveDrawingBuffer: true, + }); + + renderer.setSize( + canvas.getBoundingClientRect().width || 0, + canvas.getBoundingClientRect().height || 0, + ); + + const color = 0xfcece7; + const light = new THREE.HemisphereLight(color, 0x8d8d8d, 3.0); + + scene.add(light); + + // load the file into the preview mini-scene depending of the file's type + if (file.name.includes(".glb")) { + glbLoader({ file, scene, camera, renderer, statusCallback }); + } else if (file.name.includes(".fbx")) { + fbxLoader({ file, scene, camera, renderer, statusCallback }); + } else if (file.name.includes(".pmd")) { + pmdLoader({ file, scene, camera, renderer, statusCallback }); + } else if ( + file.name.includes(".png") || + file.name.includes(".jpg") || + file.name.includes(".jpeg") || + file.name.includes(".gif") + ) { + imagePlaneLoader({ file, scene, camera, renderer, statusCallback }); + } else if (file.name.includes(".vmd")) { + statusCallback({ + type: "Preview Error", + message: "Sorry, Preview is not available to VMD files yet", + }); + } else { + statusCallback({ + type: "Preview Error", + message: "Unknown file type for loader", + }); + } + + // Render the loaded data + const animate = function () { + renderer.render(scene, camera); + }; + renderer.setAnimationLoop(animate); +}; + +const glbLoader = ({ + file, + camera, + scene, + renderer, + statusCallback, +}: LoaderInterface) => { + const loader = new GLTFLoader(); + loader.load( + URL.createObjectURL(file), + (data) => { + console.log("preview object loader: onLoaded"); + data.scene.children.forEach((child) => { + child.userData["color"] = "#FFFFFF"; + scene.add(child); + + let maxSize = 2; + if (scene.children.length > 0) { + scene.children.forEach((child) => { + child.traverse((object: THREE.Object3D | THREE.Mesh) => { + // Assuming `object` is your Three.js object and you know it's a Mesh + if (object instanceof THREE.Mesh) { + object.geometry.computeBoundingBox(); + const boundingBox = object.geometry.boundingBox; + const center = new THREE.Vector3(); + boundingBox.getCenter(center); + const dimensions = new THREE.Vector3(); + boundingBox.getSize(dimensions); + const maxDim = Math.max( + dimensions.x, + dimensions.y, + dimensions.z, + ); + if (maxSize < maxDim) { + maxSize = maxDim; + camera.position.set(-maxDim, maxDim, maxDim); + camera.lookAt(center); + camera.updateProjectionMatrix(); + } + } + }); + }); + } + renderer.render(scene, camera); + }); + }, + undefined, // nothing to-do onProgress + (loaderError) => { + /*onError*/ + statusCallback({ + type: "GLB Loader Error", + message: String(loaderError), + }); + }, + ); +}; + +const fbxLoader = ({ file, statusCallback }: LoaderInterface) => { + const loader = new FBXLoader(); + loader.load( + URL.createObjectURL(file), + (data) => { + console.log(data); + statusCallback({ + type: "FBX Loader Error", + message: "Sorry, FBX Loader is still under development", + }); + }, + undefined, // nothing to-do onProgress + (loaderError) => { + /*onError*/ + statusCallback({ + type: "FBX Loader Error", + message: String(loaderError), + }); + }, + ); +}; + +const pmdLoader = ({ + camera, + scene, + renderer, + statusCallback, +}: LoaderInterface) => { + camera.position.z = 30; + const loader = new FontLoader(); + loader.load( + "https://threejs.org/examples/fonts/helvetiker_regular.typeface.json", + (font) => { + const textGeometry = new TextGeometry("MMD", { + font: font, + size: 100, + depth: 5, + curveSegments: 12, + bevelEnabled: true, + bevelThickness: 1, + bevelSize: 1, + bevelOffset: 0, + bevelSegments: 5, + }); + textGeometry.computeBoundingBox(); + const textMaterial = new THREE.MeshPhongMaterial({ + color: 0xffffff, + }); + const textMesh = new THREE.Mesh(textGeometry, textMaterial); + textMesh.scale.set(0.15, 0.15, 0.01); + textMesh.position.set(-22, -5, 0); + scene.add(textMesh); + renderer.render(scene, camera); + }, + undefined, // nothing on Progress + (loaderError) => { + /*onError*/ + statusCallback({ + type: "PMD Loader Error", + message: String(loaderError), + }); + }, + ); +}; + +const imagePlaneLoader = ({ file, scene, statusCallback }: LoaderInterface) => { + const geometry = new THREE.PlaneGeometry(1, 1); + const loader = new THREE.TextureLoader(); + const texture = loader.load( + URL.createObjectURL(file), + undefined, // nothing to-do onLoad + undefined, // nothing to-do onProgress + (loaderError) => { + /*onError*/ + statusCallback({ + type: "Image Plane Loader Error", + message: String(loaderError), + }); + }, + ); + texture.colorSpace = THREE.SRGBColorSpace; + + const image_material = new THREE.MeshBasicMaterial({ + color: 0xffffff, + map: texture, + }); + const obj = new THREE.Mesh(geometry, image_material); + obj.receiveShadow = true; + obj.castShadow = true; + scene.add(obj); +}; diff --git a/app/components/reusable/UploadModal3DPreview/utilities/uploadAsset.ts b/app/components/reusable/UploadModal3DPreview/utilities/uploadAsset.ts new file mode 100644 index 0000000000..3f7cdb2ad4 --- /dev/null +++ b/app/components/reusable/UploadModal3DPreview/utilities/uploadAsset.ts @@ -0,0 +1,53 @@ +import { MediaUploadApi } from "~/Classes/ApiManager"; +import { FilterEngineCategories, MediaFileAnimationType } from "~/enums"; +import { getFileExtension } from "~/utilities"; +import { v4 as uuidv4 } from "uuid"; + +export const uploadAsset = async ({ + file, + title, + animationType, +}: { + file: File; + title: string; + animationType?: MediaFileAnimationType; +}) => { + const mediaUploadApi = new MediaUploadApi(); + const fileExtension = getFileExtension(file); + const assetReponse = await (async () => { + switch (fileExtension) { + case ".zip": + return mediaUploadApi.UploadPmx({ + file: file, + fileName: file.name, + engine_category: FilterEngineCategories.CHARACTER, + maybe_title: title, + maybe_animation_type: animationType, + uuid: uuidv4(), + }); + case ".png": + case ".gif": + case ".jpg": + case ".jpeg": + return mediaUploadApi.UploadNewEngineAsset({ + file: file, + fileName: file.name, + engine_category: FilterEngineCategories.IMAGE_PLANE, + maybe_animation_type: animationType, + maybe_title: title, + uuid: uuidv4(), + }); + default: + return mediaUploadApi.UploadNewEngineAsset({ + file: file, + fileName: file.name, + engine_category: FilterEngineCategories.OBJECT, + maybe_animation_type: animationType, + maybe_title: title, + uuid: uuidv4(), + }); + } + })(); + + return assetReponse; +}; diff --git a/app/components/reusable/index.ts b/app/components/reusable/index.ts index 6d4c1b1f0b..f31055a664 100644 --- a/app/components/reusable/index.ts +++ b/app/components/reusable/index.ts @@ -25,6 +25,8 @@ export * from "./Textarea"; export * from "./Tooltip"; export * from "./TransitionDialogue"; //need to decouple hotkey signal export * from "./Typography"; +export * from "./UploadModal"; +export * from "./UploadModal3DPreview"; export * from "./WaveformPlayer"; //TODO: need to rewrite because it's against tailwind diff --git a/app/components/signaled/UploadModalMovement/UploadFiles.tsx b/app/components/signaled/UploadModalMovement/UploadFiles.tsx deleted file mode 100644 index 4dd3454657..0000000000 --- a/app/components/signaled/UploadModalMovement/UploadFiles.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { Button, H6, Input, ListDropdown } from "~/components"; -import { UploadFile } from "./UploadFile"; -import { useState } from "react"; -import { MediaFileAnimationType } from "~/enums"; - -interface Props { - title: string; - fileTypes: string[]; - onClose: () => void; - typeOptions: { [key: string]: string }[]; - onSubmit: (options: { - title: string; - typeOption: MediaFileAnimationType; - assetFile: File; - length: number; - thumbnailFile: File | null; - }) => void; -} - -export const UploadFiles = ({ - fileTypes, - onClose, - title, - typeOptions, - onSubmit, -}: Props) => { - const [typeOption, setTypeOption] = useState( - Object.values(typeOptions[0])[0] as MediaFileAnimationType, - ); - const [uploadTitle, setUploadTitle] = useState(""); - const [uploadLength, setUploadLength] = useState(); - const [titleError, setTitleError] = useState(""); - const [assetFileError, setAssetFileError] = useState(""); - const [assetFile, setAssetFile] = useState(null); - const [thumbnailFile, setThumbnailFile] = useState(null); - - const handleSubmit = () => { - if (!uploadTitle) { - setTitleError("Please enter the title"); - } else { - setTitleError(""); - } - - if (!assetFile) { - setAssetFileError("Please select a file to upload"); - } else { - setAssetFileError(""); - } - - if (!uploadTitle || !assetFile) { - return; - } - onSubmit({ - title: uploadTitle, - assetFile: assetFile, - thumbnailFile: thumbnailFile, - length: uploadLength ?? 1000, - typeOption, - }); - }; - - return ( - <> -
- setUploadTitle(event.target.value)} - className={titleError ? "mb-3" : ""} - /> - {typeOptions.length > 1 && ( - setTypeOption(value as MediaFileAnimationType)} - /> - )} - setUploadLength(parseInt(event.target.value))} - /> - - {assetFileError &&
{assetFileError}
} - -
- - -
-
- - ); -}; diff --git a/app/components/signaled/UploadModalMovement/index.tsx b/app/components/signaled/UploadModalMovement/index.tsx deleted file mode 100644 index 5240cf11da..0000000000 --- a/app/components/signaled/UploadModalMovement/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export * from "./UploadModalMovement"; diff --git a/app/components/signaled/UploadModal/UploadModal.scss b/app/components/signaled/UploadModalObject/UploadModal.scss similarity index 100% rename from app/components/signaled/UploadModal/UploadModal.scss rename to app/components/signaled/UploadModalObject/UploadModal.scss diff --git a/app/components/signaled/UploadModal/UploadModal.tsx b/app/components/signaled/UploadModalObject/UploadModalObject.tsx similarity index 98% rename from app/components/signaled/UploadModal/UploadModal.tsx rename to app/components/signaled/UploadModalObject/UploadModalObject.tsx index 46490d90f1..468b4a019a 100644 --- a/app/components/signaled/UploadModal/UploadModal.tsx +++ b/app/components/signaled/UploadModalObject/UploadModalObject.tsx @@ -47,7 +47,7 @@ enum UploaderState { loaderError, } -export function UploadModal({ +export function UploadModalObject({ closeModal, file, isOpen, @@ -190,13 +190,13 @@ export function UploadModal({ file.name.includes(".jpeg") || file.name.includes(".gif") ) { - const geometry = new THREE.PlaneGeometry( 1, 1 ); + const geometry = new THREE.PlaneGeometry(1, 1); const loader = new THREE.TextureLoader(); const texture = loader.load(URL.createObjectURL(file)); texture.colorSpace = THREE.SRGBColorSpace; - console.log(texture) + console.log(texture); const image_material = new THREE.MeshBasicMaterial({ - color: 0xFFFFFF, + color: 0xffffff, map: texture, }); const obj = new THREE.Mesh(geometry, image_material); @@ -204,10 +204,10 @@ export function UploadModal({ obj.castShadow = true; scene.add(obj); } - const animate = function() { - renderer.render( scene, camera ); - } - renderer.setAnimationLoop( animate ); + const animate = function () { + renderer.render(scene, camera); + }; + renderer.setAnimationLoop(animate); } } }, [file, targetNode, resetModal, resetModalState]); @@ -309,7 +309,6 @@ export function UploadModal({ }); } })(); - if (!assetReponse.success || !assetReponse.data) { objUploadStatusSet(UploaderState.assetError); diff --git a/app/components/signaled/UploadModal/fitCameraToCenteredObject.ts b/app/components/signaled/UploadModalObject/fitCameraToCenteredObject.ts similarity index 97% rename from app/components/signaled/UploadModal/fitCameraToCenteredObject.ts rename to app/components/signaled/UploadModalObject/fitCameraToCenteredObject.ts index 692652df8d..b07a679bda 100644 --- a/app/components/signaled/UploadModal/fitCameraToCenteredObject.ts +++ b/app/components/signaled/UploadModalObject/fitCameraToCenteredObject.ts @@ -6,7 +6,7 @@ export const fitCameraToCenteredObject = function ( camera: THREE.PerspectiveCamera, object: THREE.Object3D, offset?: number, - orbitControls?, + orbitControls?: { target: THREE.Vector3; maxDistance: number }, ) { const boundingBox = new THREE.Box3(); boundingBox.setFromObject(object); diff --git a/app/components/signaled/UploadModalObject/index.tsx b/app/components/signaled/UploadModalObject/index.tsx new file mode 100644 index 0000000000..c3ed9ff0db --- /dev/null +++ b/app/components/signaled/UploadModalObject/index.tsx @@ -0,0 +1 @@ +export * from "./UploadModalObject"; diff --git a/app/components/signaled/index.ts b/app/components/signaled/index.ts index 0c8e4f2462..f090877988 100644 --- a/app/components/signaled/index.ts +++ b/app/components/signaled/index.ts @@ -6,5 +6,4 @@ export * from "./PremiumLock"; // all the Upload stuff will depend on the API Manager Signal export * from "./UploadAudioButtonDialogue"; export * from "./UploadAudioComponent"; -export * from "./UploadModal"; //need to rid of scss -export * from "./UploadModalMovement"; +export * from "./UploadModalObject"; //need to rid of scss diff --git a/app/enums/AssetType.ts b/app/enums/AssetType.ts index c3fbea58b1..7940de7e38 100644 --- a/app/enums/AssetType.ts +++ b/app/enums/AssetType.ts @@ -6,5 +6,4 @@ export enum AssetType { EXPRESSION = "expression", OBJECT = "object", SHAPE = "shape", - STYLE = "style", // TODO Remove } diff --git a/app/enums/FeatureFlags.ts b/app/enums/FeatureFlags.ts index 3102f6aa22..74a01ccfb3 100644 --- a/app/enums/FeatureFlags.ts +++ b/app/enums/FeatureFlags.ts @@ -1,3 +1,4 @@ export enum FeatureFlags { SHOW_SEARCH_OBJECTS = "show-search-objects", + SHOW_SETS_TAB = "show-sets-tab", } diff --git a/app/enums/FileTypes.ts b/app/enums/FileTypes.ts index 2046e802af..121064b509 100644 --- a/app/enums/FileTypes.ts +++ b/app/enums/FileTypes.ts @@ -4,3 +4,15 @@ export enum AUDIO_FILE_TYPE { FLAC = "FLAC", OGG = "OGG", } + +export enum IMAGE_FILE_TYPE { + PNG = "PNG", + JPG = "JPG", + JPEG = "JPEG", + GIF = "GIF", +} + +export enum OBJECT_FILE_TYPE { + GLB = "GLB", + FBX = "FBX", +} diff --git a/app/enums/Tabs.ts b/app/enums/Tabs.ts new file mode 100644 index 0000000000..bfe6477c0c --- /dev/null +++ b/app/enums/Tabs.ts @@ -0,0 +1,9 @@ +export enum TabTitles { + SET_OBJECTS = "Film Sets", + CHARACTERS = "Character", + ANIMATION = "Animation", + EXPRESSIONS = "Facial Expression", + OBJECTS = "Objects", + AUDIO = "Audio", + STYLIZE = "AI Stylize", +} diff --git a/app/enums/UploaderState.ts b/app/enums/UploaderState.ts new file mode 100644 index 0000000000..1f70a0b02d --- /dev/null +++ b/app/enums/UploaderState.ts @@ -0,0 +1,10 @@ +export enum UploaderState { + ready, + uploadingAsset, + uploadingCover, + settingCover, + success, + assetError, + coverCreateError, + coverSetError, +} diff --git a/app/enums/index.ts b/app/enums/index.ts index 1dceb0a238..444ef6e95d 100644 --- a/app/enums/index.ts +++ b/app/enums/index.ts @@ -10,5 +10,7 @@ export * from "./FileTypes"; export * from "./JobStatus"; export * from "./MediaFileAnimationTypes"; export * from "./QueryFilters"; +export * from "./Tabs"; export * from "./toasts"; +export * from "./UploaderState"; export * from "./Visibility"; diff --git a/app/pages/PageEnigma/comps/SidePanel/SidePanel.tsx b/app/pages/PageEnigma/comps/SidePanel/SidePanel.tsx index f1d3ad071b..290c8729ec 100644 --- a/app/pages/PageEnigma/comps/SidePanel/SidePanel.tsx +++ b/app/pages/PageEnigma/comps/SidePanel/SidePanel.tsx @@ -1,14 +1,25 @@ +import { useState } from "react"; +import { usePosthogFeatureFlag } from "~/hooks/usePosthogFeatureFlag"; +import { FeatureFlags } from "~/enums"; +import { useSignals } from "@preact/signals-react/runtime"; import { dndSidePanelWidth, sidePanelVisible, sidePanelWidth, } from "~/pages/PageEnigma/signals"; + import { SidePanelTabs } from "~/pages/PageEnigma/comps/SidePanelTabs"; -import { SidePanelMenu } from "~/pages/PageEnigma/comps/SidePanelTabs/SidePanelMenu"; -import { useSignals } from "@preact/signals-react/runtime"; +import { SidePanelMenu } from "~/pages/PageEnigma/comps/SidePanelMenu"; +import { TabItem, tabList } from "./tabList"; export const SidePanel = () => { useSignals(); + const initialTabIdx = usePosthogFeatureFlag(FeatureFlags.SHOW_SETS_TAB) + ? 0 + : 1; + const [selectedTab, setSelectedTab] = useState( + tabList[initialTabIdx], + ); const displayWidth = dndSidePanelWidth.value > -1 @@ -31,10 +42,16 @@ export const SidePanel = () => { }} >
- +
- + { + setSelectedTab(newSelectedTab); + }} + /> ); }; diff --git a/app/pages/PageEnigma/comps/SidePanel/tabList.tsx b/app/pages/PageEnigma/comps/SidePanel/tabList.tsx new file mode 100644 index 0000000000..b5ca5ec329 --- /dev/null +++ b/app/pages/PageEnigma/comps/SidePanel/tabList.tsx @@ -0,0 +1,64 @@ +import { ReactNode } from "react"; +import { FontAwesomeIconProps } from "@fortawesome/react-fontawesome"; +import { + faGlobeSnow, + faPeople, + faRabbitRunning, + faFaceSmileWink, + faCube, + faVolume, + faBrush, +} from "@fortawesome/pro-solid-svg-icons"; + +import { TabTitles } from "~/enums"; +import { ObjectsTab } from "~/pages/PageEnigma/comps/SidePanelTabs/tabComps/ObjectsTab"; +import { AnimationTab } from "~/pages/PageEnigma/comps/SidePanelTabs/tabComps/AnimationTab"; +import { AudioTab } from "~/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab"; +import { StylizeTab } from "~/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/StylizeTab"; +import { ExpressionTab } from "~/pages/PageEnigma/comps/SidePanelTabs/tabComps/ExpressionTab"; +import { CharactersTab } from "~/pages/PageEnigma/comps/SidePanelTabs/tabComps/CharactersTab"; +import { SetsTab } from "~/pages/PageEnigma/comps/SidePanelTabs/tabComps/SetsTab"; + +export interface TabItem { + icon: FontAwesomeIconProps["icon"]; + title: string; + component: ReactNode; +} + +export const tabList = [ + { + icon: faGlobeSnow, + title: TabTitles.SET_OBJECTS, + component: , + }, + { + icon: faPeople, + title: TabTitles.CHARACTERS, + component: , + }, + { + icon: faRabbitRunning, + title: TabTitles.ANIMATION, + component: , + }, + { + icon: faFaceSmileWink, + title: TabTitles.EXPRESSIONS, + component: , + }, + { + icon: faCube, + title: TabTitles.OBJECTS, + component: , + }, + { + icon: faVolume, + title: TabTitles.AUDIO, + component: , + }, + { + icon: faBrush, + title: TabTitles.STYLIZE, + component: , + }, +]; diff --git a/app/pages/PageEnigma/comps/SidePanelMenu/SidePanelMenu.tsx b/app/pages/PageEnigma/comps/SidePanelMenu/SidePanelMenu.tsx new file mode 100644 index 0000000000..3462765fbf --- /dev/null +++ b/app/pages/PageEnigma/comps/SidePanelMenu/SidePanelMenu.tsx @@ -0,0 +1,86 @@ +import { useSignals } from "@preact/signals-react/runtime"; +import { twMerge } from "tailwind-merge"; +import { usePosthogFeatureFlag } from "~/hooks/usePosthogFeatureFlag"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { FeatureFlags, TabTitles } from "~/enums"; +import { EditorStates } from "~/pages/PageEnigma/enums"; +import { editorState } from "~/pages/PageEnigma/signals/engine"; +import { sidePanelVisible } from "~/pages/PageEnigma/signals/sidePanel"; + +import Queue from "~/pages/PageEnigma/Queue/Queue"; +import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; +import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions"; + +import { TabItem } from "../SidePanel/tabList"; +import { pageHeight } from "~/signals"; + +export const SidePanelMenu = ({ + tabs, + selectTab, + selectedTab, +}: { + tabs: TabItem[]; + selectTab: (newSelectedTab: TabItem) => void; + selectedTab: TabItem; +}) => { + useSignals(); + const showSetsTab = usePosthogFeatureFlag(FeatureFlags.SHOW_SETS_TAB); + return ( +
+
+ {(tabs ?? []).map((tab) => { + if (tab.title === TabTitles.SET_OBJECTS && !showSetsTab) { + return; + } + return ( + + ); + })} +
+
+ ); +}; diff --git a/app/pages/PageEnigma/comps/SidePanelMenu/index.ts b/app/pages/PageEnigma/comps/SidePanelMenu/index.ts new file mode 100644 index 0000000000..070b61351d --- /dev/null +++ b/app/pages/PageEnigma/comps/SidePanelMenu/index.ts @@ -0,0 +1 @@ +export * from "./SidePanelMenu"; diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/SidePanelMenu.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/SidePanelMenu.tsx deleted file mode 100644 index 0cb86f4776..0000000000 --- a/app/pages/PageEnigma/comps/SidePanelTabs/SidePanelMenu.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { useEffect, useLayoutEffect, useState } from "react"; -import { useSignals } from "@preact/signals-react/runtime"; -import { twMerge } from "tailwind-merge"; - -import { AssetType } from "~/enums"; -import { EditorStates } from "~/pages/PageEnigma/enums"; -import { editorState } from "~/pages/PageEnigma/signals/engine"; -import { - selectedTab, - sidePanelVisible, -} from "~/pages/PageEnigma/signals/sidePanel"; - -import Queue from "~/pages/PageEnigma/Queue/Queue"; -import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; -import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions"; - -import { TabItem, tabList } from "./tabList"; -import { pageHeight } from "~/signals"; - -export const SidePanelMenu = () => { - useSignals(); - const [tabs, setTabs] = useState(); - useLayoutEffect(() => { - setTabs(tabList); - }, []); - - useEffect(() => { - if (tabs) { - selectedTab.value = tabs[0]; - } - }, [tabs]); - - return ( -
-
- {(tabs ?? []).map((tab) => ( - - ))} -
-
- ); -}; diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/SidePanelTabs.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/SidePanelTabs.tsx index 653dc565d9..cba74b6ce8 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/SidePanelTabs.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/SidePanelTabs.tsx @@ -1,20 +1,27 @@ import { useSignals } from "@preact/signals-react/runtime"; -import { selectedTab, sidePanelHeight } from "~/pages/PageEnigma/signals"; -import { tabList } from "~/pages/PageEnigma/comps/SidePanelTabs/tabList"; -import { useMouseEventsSidePanel } from "~/pages/PageEnigma/comps/Timeline/utils/useMouseEventsSidePanel"; +import { sidePanelHeight } from "~/pages/PageEnigma/signals"; -export const SidePanelTabs = () => { +import { useMouseEventsSidePanel } from "~/pages/PageEnigma/comps/Timeline/utils/useMouseEventsSidePanel"; +import { TabItem } from "../SidePanel/tabList"; + +export const SidePanelTabs = ({ + selectedTab, + tabs, +}: { + selectedTab: TabItem; + tabs: TabItem[]; +}) => { useSignals(); const { onPointerDown } = useMouseEventsSidePanel(); return ( <>
- {tabList.map((tab) => ( + {tabs.map((tab, index) => (
void) => { - const [page, setPage] = useState(0); - const pageChange = (page: number) => { - setPage(page); - setStatus(FetchStatus.READY); - }; - - return { page, pageChange }; -}; diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ItemElement.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/sharedComps/ItemElement.tsx similarity index 100% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ItemElement.tsx rename to app/pages/PageEnigma/comps/SidePanelTabs/sharedComps/ItemElement.tsx diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ItemElements.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/sharedComps/ItemElements.tsx similarity index 100% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ItemElements.tsx rename to app/pages/PageEnigma/comps/SidePanelTabs/sharedComps/ItemElements.tsx diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/comps/TabTitle.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/sharedComps/TabTitle.tsx similarity index 84% rename from app/pages/PageEnigma/comps/SidePanelTabs/comps/TabTitle.tsx rename to app/pages/PageEnigma/comps/SidePanelTabs/sharedComps/TabTitle.tsx index 2c5dcc0df5..1dcc3f47e9 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/comps/TabTitle.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/sharedComps/TabTitle.tsx @@ -1,10 +1,6 @@ import { faChevronLeft, faClose } from "@fortawesome/pro-solid-svg-icons"; import { ButtonIcon } from "~/components"; -import { - lastSelectedTab, - selectedTab, - sidePanelVisible, -} from "~/pages/PageEnigma/signals"; +import { sidePanelVisible } from "~/pages/PageEnigma/signals"; interface Props { title: string; @@ -13,8 +9,6 @@ interface Props { export function TabTitle({ title, onBack }: Props) { const onClose = () => { - lastSelectedTab.value = selectedTab.value; - selectedTab.value = null; sidePanelVisible.value = false; }; diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/sharedComps/index.ts b/app/pages/PageEnigma/comps/SidePanelTabs/sharedComps/index.ts new file mode 100644 index 0000000000..ef7385dff7 --- /dev/null +++ b/app/pages/PageEnigma/comps/SidePanelTabs/sharedComps/index.ts @@ -0,0 +1,3 @@ +export * from "./ItemElement"; +export * from "./ItemElements"; +export * from "./TabTitle"; diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AnimationTab.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AnimationTab.tsx similarity index 90% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AnimationTab.tsx rename to app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AnimationTab.tsx index 5f3dbe0c47..0f2ee045ee 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AnimationTab.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AnimationTab.tsx @@ -14,14 +14,11 @@ import { import { animationItems } from "~/pages/PageEnigma/signals"; import { addToast } from "~/signals"; -import { ItemElements } from "~/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ItemElements"; +import { Button, FilterButtons, Pagination, UploadModal } from "~/components"; import { - Button, - FilterButtons, - Pagination, - UploadModalMovement, -} from "~/components"; -import { TabTitle } from "~/pages/PageEnigma/comps/SidePanelTabs/comps/TabTitle"; + TabTitle, + ItemElements, +} from "~/pages/PageEnigma/comps/SidePanelTabs/sharedComps"; import { MediaFilesApi } from "~/Classes/ApiManager"; import { FetchStatus } from "~/pages/PageEnigma/enums"; @@ -201,19 +198,23 @@ export const AnimationTab = () => { }} /> )} - setOpen(false)} onSuccess={fetchUserAnimations} isOpen={open} fileTypes={["GLB", "FBX", "VMD"]} title="Upload Animation" - typeOptions={[ - { Mixamo: MediaFileAnimationType.Mixamo }, - { MikuMikuDance: MediaFileAnimationType.MikuMikuDance }, - { MoveAi: MediaFileAnimationType.MoveAi }, - { Rigify: MediaFileAnimationType.Rigify }, - { Rokoko: MediaFileAnimationType.Rokoko }, - ]} + options={{ + fileSubtypes: [ + { Mixamo: MediaFileAnimationType.Mixamo }, + { MikuMikuDance: MediaFileAnimationType.MikuMikuDance }, + { MoveAi: MediaFileAnimationType.MoveAi }, + { Rigify: MediaFileAnimationType.Rigify }, + { Rokoko: MediaFileAnimationType.Rokoko }, + ], + hasLength: true, + hasThumbnailUpload: true, + }} type={FilterEngineCategories.ANIMATION} /> diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/AudioTab.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/AudioTab.tsx similarity index 100% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/AudioTab.tsx rename to app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/AudioTab.tsx diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/audioInferenceElement.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/audioInferenceElement.tsx similarity index 100% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/audioInferenceElement.tsx rename to app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/audioInferenceElement.tsx diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/audioItemElement.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/audioItemElement.tsx similarity index 100% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/audioItemElement.tsx rename to app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/audioItemElement.tsx diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/audioItemElements.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/audioItemElements.tsx similarity index 100% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/audioItemElements.tsx rename to app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/audioItemElements.tsx diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/audioTypePills.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/audioTypePills.tsx similarity index 100% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/audioTypePills.tsx rename to app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/audioTypePills.tsx diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/index.ts b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/index.ts similarity index 100% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/index.ts rename to app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/index.ts diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/pageAudioGeneration.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/pageAudioGeneration.tsx similarity index 99% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/pageAudioGeneration.tsx rename to app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/pageAudioGeneration.tsx index 4a5709957f..584432f283 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/pageAudioGeneration.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/pageAudioGeneration.tsx @@ -5,7 +5,7 @@ import { V2VState, } from "~/pages/PageEnigma/models/voice"; -import { TabTitle } from "~/pages/PageEnigma/comps/SidePanelTabs/comps/TabTitle"; +import { TabTitle } from "~/pages/PageEnigma/comps/SidePanelTabs/sharedComps/TabTitle"; import { PageTTS } from "./pageTTS"; import { PageVoicetoVoice } from "./pageVoiceToVoice"; import { AudioTabPages } from "~/pages/PageEnigma/enums"; diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/pageAudioLibrary.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/pageAudioLibrary.tsx similarity index 99% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/pageAudioLibrary.tsx rename to app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/pageAudioLibrary.tsx index b71ab8780e..b4c8f6fc04 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/pageAudioLibrary.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/pageAudioLibrary.tsx @@ -11,7 +11,7 @@ import { } from "~/components"; import { AudioItemElements } from "./audioItemElements"; -import { TabTitle } from "~/pages/PageEnigma/comps/SidePanelTabs/comps/TabTitle"; +import { TabTitle } from "~/pages/PageEnigma/comps/SidePanelTabs/sharedComps/TabTitle"; import { InferenceElement } from "./audioInferenceElement"; import { AssetFilterOption } from "~/enums"; import { AudioTabPages } from "~/pages/PageEnigma/enums"; diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/pageSelectTtsModel.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/pageSelectTtsModel.tsx similarity index 98% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/pageSelectTtsModel.tsx rename to app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/pageSelectTtsModel.tsx index 58ae9e400c..8cc01adf0c 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/pageSelectTtsModel.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/pageSelectTtsModel.tsx @@ -1,7 +1,7 @@ import { useCallback, useState } from "react"; import { Input } from "~/components"; import { Weight } from "~/models"; -import { TabTitle } from "~/pages/PageEnigma/comps/SidePanelTabs/comps/TabTitle"; +import { TabTitle } from "~/pages/PageEnigma/comps/SidePanelTabs/sharedComps/TabTitle"; import { VoiceModelElement } from "./voiceModelElement"; import { AudioTabPages } from "~/pages/PageEnigma/enums"; diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/pageSelectV2VModel.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/pageSelectV2VModel.tsx similarity index 98% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/pageSelectV2VModel.tsx rename to app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/pageSelectV2VModel.tsx index 70f0633789..58d683be01 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/pageSelectV2VModel.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/pageSelectV2VModel.tsx @@ -2,7 +2,7 @@ import { useCallback, useState } from "react"; import { Weight } from "~/models"; import { Input } from "~/components"; -import { TabTitle } from "~/pages/PageEnigma/comps/SidePanelTabs/comps/TabTitle"; +import { TabTitle } from "~/pages/PageEnigma/comps/SidePanelTabs/sharedComps/TabTitle"; import { VoiceModelElement } from "./voiceModelElement"; import { AudioTabPages } from "~/pages/PageEnigma/enums"; diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/pageTTS.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/pageTTS.tsx similarity index 100% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/pageTTS.tsx rename to app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/pageTTS.tsx diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/pageVoiceToVoice.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/pageVoiceToVoice.tsx similarity index 100% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/pageVoiceToVoice.tsx rename to app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/pageVoiceToVoice.tsx diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/values.ts b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/values.ts similarity index 100% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/values.ts rename to app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/values.ts diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/voiceModelElement.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/voiceModelElement.tsx similarity index 100% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/voiceModelElement.tsx rename to app/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab/voiceModelElement.tsx diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/CameraTab.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/CameraTab.tsx similarity index 87% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/CameraTab.tsx rename to app/pages/PageEnigma/comps/SidePanelTabs/tabComps/CameraTab.tsx index e451c4fcc9..49d64695a7 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/CameraTab.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/CameraTab.tsx @@ -1,10 +1,12 @@ import { cameraFilter, cameraItems } from "~/pages/PageEnigma/signals"; import { useSignals } from "@preact/signals-react/runtime"; -import { ItemElements } from "~/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ItemElements"; import { Button } from "~/components"; import { faCirclePlus } from "@fortawesome/pro-solid-svg-icons"; import { twMerge } from "tailwind-merge"; -import { TabTitle } from "~/pages/PageEnigma/comps/SidePanelTabs/comps/TabTitle"; +import { + ItemElements, + TabTitle, +} from "~/pages/PageEnigma/comps/SidePanelTabs/sharedComps"; import { AssetFilterOption } from "~/enums"; export const CameraTab = () => { @@ -58,10 +60,7 @@ export const CameraTab = () => {
- +
); diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/CharactersTab.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/CharactersTab.tsx similarity index 97% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/CharactersTab.tsx rename to app/pages/PageEnigma/comps/SidePanelTabs/tabComps/CharactersTab.tsx index f12e69245d..d6c768ab12 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/CharactersTab.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/CharactersTab.tsx @@ -15,9 +15,11 @@ import { addToast } from "~/signals"; import { BucketConfig } from "~/api/BucketConfig"; -import { ItemElements } from "~/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ItemElements"; import { Button, FileWrapper, FilterButtons, Pagination } from "~/components"; -import { TabTitle } from "~/pages/PageEnigma/comps/SidePanelTabs/comps/TabTitle"; +import { + ItemElements, + TabTitle, +} from "~/pages/PageEnigma/comps/SidePanelTabs/sharedComps"; import { MediaFilesApi } from "~/Classes/ApiManager"; diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ExpressionTab.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/ExpressionTab.tsx similarity index 94% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ExpressionTab.tsx rename to app/pages/PageEnigma/comps/SidePanelTabs/tabComps/ExpressionTab.tsx index 81d9b2d2f9..43b505e7d6 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ExpressionTab.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/ExpressionTab.tsx @@ -11,14 +11,16 @@ import { MediaFileAnimationType, } from "~/enums"; -import { ItemElements } from "~/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ItemElements"; import { Button, FilterButtons, Pagination, - UploadModalMovement, + UploadModal, } from "~/components"; -import { TabTitle } from "~/pages/PageEnigma/comps/SidePanelTabs/comps/TabTitle"; +import { + ItemElements, + TabTitle, +} from "~/pages/PageEnigma/comps/SidePanelTabs/sharedComps"; import { MediaFilesApi } from "~/Classes/ApiManager"; import { addToast } from "~/signals"; @@ -168,13 +170,17 @@ export const ExpressionTab = () => { }} /> )} - setOpen(false)} onSuccess={fetchUserExpressions} isOpen={open} fileTypes={["CSV"]} title="Upload Expression" - typeOptions={[{ ARKit: MediaFileAnimationType.ArKit }]} + options={{ + fileSubtypes: [{ ARKit: MediaFileAnimationType.ArKit }], + hasLength: true, + hasThumbnailUpload: true, + }} type={FilterEngineCategories.EXPRESSION} /> diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ObjectsTab.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/ObjectsTab.tsx similarity index 98% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ObjectsTab.tsx rename to app/pages/PageEnigma/comps/SidePanelTabs/tabComps/ObjectsTab.tsx index 5e1d481917..e271e65158 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ObjectsTab.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/ObjectsTab.tsx @@ -17,7 +17,10 @@ import { shapeItems } from "~/pages/PageEnigma/signals"; import { BucketConfig } from "~/api/BucketConfig"; -import { ItemElements } from "~/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ItemElements"; +import { + TabTitle, + ItemElements, +} from "~/pages/PageEnigma/comps/SidePanelTabs/sharedComps"; import { Button, FileWrapper, @@ -25,7 +28,6 @@ import { SearchFilter, Pagination, } from "~/components"; -import { TabTitle } from "~/pages/PageEnigma/comps/SidePanelTabs/comps/TabTitle"; import { MediaFilesApi } from "~/Classes/ApiManager"; import { addToast } from "~/signals"; diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/SetsTab.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/SetsTab.tsx new file mode 100644 index 0000000000..8fa8bbbef0 --- /dev/null +++ b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/SetsTab.tsx @@ -0,0 +1,172 @@ +import { useCallback, useEffect, useState } from "react"; +import { faCirclePlus } from "@fortawesome/pro-solid-svg-icons"; + +import { + AssetFilterOption, + FilterEngineCategories, + IMAGE_FILE_TYPE, + OBJECT_FILE_TYPE, +} from "~/enums"; +import { FetchStatus } from "~/pages/PageEnigma/enums"; + +import { + Button, + FilterButtons, + Pagination, + UploadModal3DPreview, +} from "~/components"; + +import { + TabTitle, + ItemElements, +} from "~/pages/PageEnigma/comps/SidePanelTabs/sharedComps"; + +import { + fetchFeaturedMediaItems, + fetchUserMediaItems, + FetchMediaItemStates, + isAnyStatusFetching, +} from "../utilities"; + +export const SetsTab = () => { + const [openUploadModal, setOpenUploadModal] = useState(false); + + const [ + { mediaItems: userSetObjects, status: userFetchStatus }, + setUserFetch, + ] = useState({ + mediaItems: undefined, + status: FetchStatus.READY, + }); + const [ + { mediaItems: featuredSetObjects, status: feateredFetchStatus }, + setFeaturedFetch, + ] = useState({ + mediaItems: undefined, + status: FetchStatus.READY, + }); + + const [selectedFilter, setSelectedFilter] = useState( + AssetFilterOption.FEATURED, + ); + const diasplayedItems = + selectedFilter === AssetFilterOption.FEATURED + ? featuredSetObjects ?? [] + : userSetObjects ?? []; + + const [currentPage, setCurrentPage] = useState(0); + + const pageSize = 21; + const totalPages = Math.ceil(diasplayedItems.length / pageSize); + + const isFetching = isAnyStatusFetching([ + userFetchStatus, + feateredFetchStatus, + ]); + + const fetchUserSetObjects = useCallback( + () => + fetchUserMediaItems({ + filterEngineCategories: [ + FilterEngineCategories.OBJECT, + FilterEngineCategories.IMAGE_PLANE, + ], + setState: (newState: FetchMediaItemStates) => { + setUserFetch((curr) => ({ + status: newState.status, + mediaItems: newState.mediaItems + ? newState.mediaItems + : curr.mediaItems, + })); + }, + defaultErrorMessage: "Unknown Error in Fetching User Set Objects", + }), + [], + ); + + const fetchFeaturedSetObjects = useCallback( + () => + fetchFeaturedMediaItems({ + filterEngineCategories: [FilterEngineCategories.OBJECT], + setState: (newState: FetchMediaItemStates) => { + setFeaturedFetch((curr) => ({ + status: newState.status, + mediaItems: newState.mediaItems + ? newState.mediaItems + : curr.mediaItems, + })); + }, + defaultErrorMessage: "Unknown Error in Fetching Featured Set Objects", + }), + [], + ); + + useEffect(() => { + if (!userSetObjects) { + fetchUserSetObjects(); + } + if (!featuredSetObjects) { + fetchFeaturedSetObjects(); + } + }, [ + userSetObjects, + fetchUserSetObjects, + featuredSetObjects, + fetchFeaturedSetObjects, + ]); + + return ( + <> + + + { + setSelectedFilter(Number(buttonIdx)); + }} + /> + +
+ +
+
+ +
+ {totalPages > 1 && ( + { + setCurrentPage(newPage); + }} + /> + )} + setOpenUploadModal(false)} + onSuccess={fetchUserSetObjects} + isOpen={openUploadModal} + fileTypes={[ + ...Object.values(OBJECT_FILE_TYPE), + "PMD", + ...Object.values(IMAGE_FILE_TYPE), + ]} + title="Upload Set Objects" + type={FilterEngineCategories.OBJECT} + /> + + ); +}; diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/StylizeTab/GenerateMovieButton.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/GenerateMovieButton.tsx similarity index 100% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/StylizeTab/GenerateMovieButton.tsx rename to app/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/GenerateMovieButton.tsx diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/StylizeTab/IPAdapter.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/IPAdapter.tsx similarity index 100% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/StylizeTab/IPAdapter.tsx rename to app/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/IPAdapter.tsx diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/StylizeTab/ItemPicker.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/ItemPicker.tsx similarity index 100% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/StylizeTab/ItemPicker.tsx rename to app/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/ItemPicker.tsx diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/StylizeTab/PageStyleSelection.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/PageStyleSelection.tsx similarity index 96% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/StylizeTab/PageStyleSelection.tsx rename to app/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/PageStyleSelection.tsx index b165d850b8..311551def8 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/StylizeTab/PageStyleSelection.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/PageStyleSelection.tsx @@ -4,7 +4,7 @@ import { ArtStyle } from "~/pages/PageEnigma/Editor/api_manager"; import { styleList } from "~/pages/PageEnigma/styleList"; import { ItemPicker } from "./ItemPicker"; import { useSignals } from "@preact/signals-react/runtime"; -import { TabTitle } from "../../comps/TabTitle"; +import { TabTitle } from "../../sharedComps/TabTitle"; import { StylizeTabPages } from "~/pages/PageEnigma/enums"; interface Props { diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/StylizeTab/Prompts.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/Prompts.tsx similarity index 100% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/StylizeTab/Prompts.tsx rename to app/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/Prompts.tsx diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/StylizeTab/StyleButtons.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/StyleButtons.tsx similarity index 100% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/StylizeTab/StyleButtons.tsx rename to app/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/StyleButtons.tsx diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/StylizeTab/StyleSelection.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/StyleSelection.tsx similarity index 100% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/StylizeTab/StyleSelection.tsx rename to app/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/StyleSelection.tsx diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/StylizeTab/StyleSelectionButton.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/StyleSelectionButton.tsx similarity index 100% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/StylizeTab/StyleSelectionButton.tsx rename to app/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/StyleSelectionButton.tsx diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/StylizeTab/StylizeTab.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/StylizeTab.tsx similarity index 99% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/StylizeTab/StylizeTab.tsx rename to app/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/StylizeTab.tsx index 9ff837b638..b8ef2e532c 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/StylizeTab/StylizeTab.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/StylizeTab.tsx @@ -1,4 +1,4 @@ -import { TabTitle } from "~/pages/PageEnigma/comps/SidePanelTabs/comps/TabTitle"; +import { TabTitle } from "~/pages/PageEnigma/comps/SidePanelTabs/sharedComps/TabTitle"; import { PageStyleSelection } from "./PageStyleSelection"; import { Prompts } from "./Prompts"; import { StyleButtons } from "./StyleButtons"; diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/tabList.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/tabList.tsx deleted file mode 100644 index 068b19e1ac..0000000000 --- a/app/pages/PageEnigma/comps/SidePanelTabs/tabList.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { ObjectsTab } from "~/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ObjectsTab"; -import { AnimationTab } from "~/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AnimationTab"; -import { AudioTab } from "~/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab"; -import { StylizeTab } from "~/pages/PageEnigma/comps/SidePanelTabs/itemTabs/StylizeTab/StylizeTab"; -import { ExpressionTab } from "~/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ExpressionTab"; -import { ReactNode } from "react"; -import { AssetType } from "~/enums"; -import { CharactersTab } from "~/pages/PageEnigma/comps/SidePanelTabs/itemTabs/CharactersTab"; - -export interface TabItem { - icon: string; - title: string; - value: AssetType; - component: ReactNode; -} - -export const tabList = [ - { - icon: "/resources/icons/characters.png", - title: "Characters", - value: AssetType.CHARACTER, - component: , - }, - { - icon: "/resources/icons/animations.png", - title: "Animation", - value: AssetType.ANIMATION, - component: , - }, - { - icon: "/resources/icons/expressions.png", - title: "Facial Expression", - value: AssetType.EXPRESSION, - component: , - }, - { - icon: "/resources/icons/objects.png", - title: "Objects", - value: AssetType.OBJECT, - component: , - }, - - { - icon: "/resources/icons/audios.png", - title: "Audio", - value: AssetType.AUDIO, - component: , - }, - { - icon: "/resources/icons/brush.png", - title: "AI Stylize", - value: AssetType.STYLE, - component: , - }, -]; diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/utilities/fetchMediaItems.ts b/app/pages/PageEnigma/comps/SidePanelTabs/utilities/fetchMediaItems.ts new file mode 100644 index 0000000000..0eae87aabd --- /dev/null +++ b/app/pages/PageEnigma/comps/SidePanelTabs/utilities/fetchMediaItems.ts @@ -0,0 +1,85 @@ +import { FilterEngineCategories, ToastTypes } from "~/enums"; +import { addToast } from "~/signals"; +import { FetchStatus } from "~/pages/PageEnigma/enums"; +import { MediaFilesApi } from "~/Classes/ApiManager"; +import { MediaItem } from "~/pages/PageEnigma/models"; + +import { responseMapping } from "./misc"; + +export interface FetchMediaItemStates { + mediaItems?: MediaItem[]; + status: FetchStatus; +} +interface fetchMediaItemsInterface { + setState: ({ mediaItems, status }: FetchMediaItemStates) => void; + filterEngineCategories: FilterEngineCategories[]; + defaultErrorMessage?: string; +} + +export const fetchUserMediaItems = async ({ + setState, + filterEngineCategories, + defaultErrorMessage, +}: fetchMediaItemsInterface) => { + setState({ status: FetchStatus.IN_PROGRESS }); + const mediaFilesApi = new MediaFilesApi(); + + const response = await mediaFilesApi.ListUserMediaFiles({ + page_size: 100, + filter_engine_categories: filterEngineCategories, + }); + + if (response.success && response.data) { + const newSetObjects = responseMapping( + response.data, + filterEngineCategories, + ); + setState({ + mediaItems: newSetObjects, + status: FetchStatus.SUCCESS, + }); + return; + } + addToast( + ToastTypes.ERROR, + response.errorMessage ?? + defaultErrorMessage ?? + "Unknown Error in Fetching Media Items", + ); + setState({ status: FetchStatus.ERROR }); + return; +}; + +export const fetchFeaturedMediaItems = async ({ + setState, + filterEngineCategories, + defaultErrorMessage, +}: fetchMediaItemsInterface) => { + setState({ status: FetchStatus.IN_PROGRESS }); + const mediaFilesApi = new MediaFilesApi(); + + const response = await mediaFilesApi.ListFeaturedMediaFiles({ + page_size: 100, + filter_engine_categories: filterEngineCategories, + }); + + if (response.success && response.data) { + const newSetObjects = responseMapping( + response.data, + filterEngineCategories, + ); + setState({ + mediaItems: newSetObjects, + status: FetchStatus.SUCCESS, + }); + return; + } + addToast( + ToastTypes.ERROR, + response.errorMessage ?? + defaultErrorMessage ?? + "Unknown Error in Fetching Media Items", + ); + setState({ status: FetchStatus.ERROR }); + return; +}; diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/utilities/index.ts b/app/pages/PageEnigma/comps/SidePanelTabs/utilities/index.ts new file mode 100644 index 0000000000..eb34614149 --- /dev/null +++ b/app/pages/PageEnigma/comps/SidePanelTabs/utilities/index.ts @@ -0,0 +1,2 @@ +export * from "./fetchMediaItems"; +export * from "./misc"; diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/utilities/misc.ts b/app/pages/PageEnigma/comps/SidePanelTabs/utilities/misc.ts new file mode 100644 index 0000000000..4f8f84992c --- /dev/null +++ b/app/pages/PageEnigma/comps/SidePanelTabs/utilities/misc.ts @@ -0,0 +1,46 @@ +import { AssetType, FilterEngineCategories } from "~/enums"; +import { FetchStatus } from "~/pages/PageEnigma/enums"; +import { MediaInfo, MediaItem } from "~/pages/PageEnigma/models"; +import { BucketConfig } from "~/api/BucketConfig"; + +export const isAnyStatusFetching = (statuses: FetchStatus[]): boolean => { + return statuses.some((status) => { + return status === FetchStatus.READY || status === FetchStatus.IN_PROGRESS; + }); +}; + +export const responseMapping = ( + data: MediaInfo[], + filterEngineCategories: FilterEngineCategories[], +): MediaItem[] => { + //TODO: ASSET TYPES and ENGINE CATEGORIES NEED TO MATCH!!!! + //TODO: GET RID OF ASSET TYPES!! + const assetType = + filterEngineCategories[0] === FilterEngineCategories.IMAGE_PLANE + ? AssetType.OBJECT + : filterEngineCategories[0]; + + return data.map((item) => { + const bucketConfig = new BucketConfig(); + const itemThumb = bucketConfig.getCdnUrl( + item.cover_image.maybe_cover_image_public_bucket_path ?? "", + 600, + 100, + ); + return { + colorIndex: item.cover_image.default_cover.color_index, + imageIndex: item.cover_image.default_cover.image_index, + media_id: item.token, + name: item.maybe_title ?? "Unknown", + type: assetType as AssetType, + media_type: item.media_type, + length: ((item.maybe_duration_millis ?? 1000) / 1000) * 60, + version: 1, + ...(item.cover_image.maybe_cover_image_public_bucket_path + ? { + thumbnail: itemThumb, + } + : {}), + }; + }); +}; diff --git a/app/pages/PageEnigma/signals/sidePanel.ts b/app/pages/PageEnigma/signals/sidePanel.ts index d3e5591ef4..8b96e847b9 100644 --- a/app/pages/PageEnigma/signals/sidePanel.ts +++ b/app/pages/PageEnigma/signals/sidePanel.ts @@ -7,9 +7,6 @@ export const sidePanelVisible = signal(true); export const dndSidePanelWidth = signal(-1); -export const selectedTab = signal(null); -export const lastSelectedTab = signal(null); - export const cameraFilter = signal(AssetFilterOption.ALL); export const sidePanelHeight = computed(() => {