From 3d707b9911a39fa60a170b59cd432e24188f8e09 Mon Sep 17 00:00:00 2001 From: wil Date: Mon, 8 Jul 2024 20:46:23 -0400 Subject: [PATCH] Feature/Tabs improvement 2 - Image Panels... Panel (#592) Co-authored-by: wil --- .../UploadModalImages/UploadFilesImages.tsx | 134 +++++++++++++ .../UploadModalImages/UploadModalImages.tsx | 186 ++++++++++++++++++ .../reusable/UploadModalImages/index.ts | 1 + app/components/reusable/index.ts | 1 + app/enums/FeatureFlags.ts | 1 + app/enums/Tabs.ts | 1 + .../PageEnigma/comps/SidePanel/tabList.tsx | 7 + .../comps/SidePanelMenu/SidePanelMenu.tsx | 6 + .../SidePanelTabs/tabComps/ImagePlanesTab.tsx | 154 +++++++++++++++ .../SidePanelTabs/tabComps/ObjectsTab.tsx | 2 +- app/root.tsx | 2 - 11 files changed, 492 insertions(+), 3 deletions(-) create mode 100644 app/components/reusable/UploadModalImages/UploadFilesImages.tsx create mode 100644 app/components/reusable/UploadModalImages/UploadModalImages.tsx create mode 100644 app/components/reusable/UploadModalImages/index.ts create mode 100644 app/pages/PageEnigma/comps/SidePanelTabs/tabComps/ImagePlanesTab.tsx diff --git a/app/components/reusable/UploadModalImages/UploadFilesImages.tsx b/app/components/reusable/UploadModalImages/UploadFilesImages.tsx new file mode 100644 index 0000000000..ce9e2c2b1b --- /dev/null +++ b/app/components/reusable/UploadModalImages/UploadFilesImages.tsx @@ -0,0 +1,134 @@ +import { useState } from "react"; + +import { MediaFileAnimationType } from "~/enums"; + +import { Button, H6, Input, ListDropdown } from "~/components"; +import { FileUploader } from "../UploadModal/FileUploader"; + +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 UploadFilesImages = ({ + fileTypes, + onClose, + title, + options, + onSubmit, +}: Props) => { + const fileSubtypes = options?.fileSubtypes; + + 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 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, + typeOption, + }); + }; + + return ( + <> +
+ setUploadTitle({ value: event.target.value })} + className={uploadTitle.error ? "mb-3" : ""} + /> + {fileSubtypes && fileSubtypes.length > 1 && ( + setTypeOption(value as MediaFileAnimationType)} + /> + )} + { + setAssetFile({ + value: file, + }); + }} + /> + {assetFile.error && ( +
{assetFile.error}
+ )} + +
+ {!assetFile.value && ( +
+ File Preivew +
+ )} + {assetFile.value && ( + file upload preview + )} +
+ +
+ + +
+
+ + ); +}; diff --git a/app/components/reusable/UploadModalImages/UploadModalImages.tsx b/app/components/reusable/UploadModalImages/UploadModalImages.tsx new file mode 100644 index 0000000000..ac691b3706 --- /dev/null +++ b/app/components/reusable/UploadModalImages/UploadModalImages.tsx @@ -0,0 +1,186 @@ +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 { UploadFilesImages } from "./UploadFilesImages"; +import { MediaFilesApi, MediaUploadApi } from "~/Classes/ApiManager"; +import { FilterEngineCategories, MediaFileAnimationType } from "~/enums"; + +import { getFileName } from "~/utilities"; + +interface Props { + onClose: () => void; + onSuccess: () => void; + isOpen: boolean; + title: string; + fileTypes: string[]; + type: FilterEngineCategories; + options?: { + fileSubtypes?: { [key: string]: string }[]; + }; +} + +enum UploaderState { + ready, + uploadingAsset, + uploadingCover, + settingCover, + success, + assetError, + coverCreateError, + coverSetError, +} + +const initialState = { + status: UploaderState.ready, +}; + +export function UploadModalImages({ + 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, + assetFile, + }: { + title: string; + typeOption?: MediaFileAnimationType; + assetFile: File; + thumbnailFile: File | null; + }) => { + setUploaderState({ status: UploaderState.uploadingAsset }); + const mediaUploadApi = new MediaUploadApi(); + const assetReponse = await mediaUploadApi.UploadNewEngineAsset({ + file: assetFile, + fileName: assetFile.name, + engine_category: FilterEngineCategories.IMAGE_PLANE, + maybe_title: title, + uuid: uuidv4(), + }); + + if (!assetReponse.success || !assetReponse.data) { + setUploaderState({ + status: UploaderState.assetError, + errorMessage: assetReponse.errorMessage, + }); + return; + } + + setUploaderState({ status: UploaderState.uploadingCover }); + const thumbnailResponse = await mediaUploadApi.UploadImage({ + uuid: uuidv4(), + blob: assetFile, + fileName: getFileName(assetFile), + maybe_title: "thumbnail_" + title, + }); + if (!thumbnailResponse.success || !thumbnailResponse.data) { + setUploaderState({ + status: UploaderState.coverCreateError, + errorMessage: thumbnailResponse.errorMessage, + }); + return; + } + + setUploaderState({ status: UploaderState.settingCover }); + + const mediaFilesApi = new MediaFilesApi(); + const setThumbnailResponse = await mediaFilesApi.UpdateCoverImage({ + mediaFileToken: assetReponse.data, + imageToken: thumbnailResponse.data, + }); + 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/UploadModalImages/index.ts b/app/components/reusable/UploadModalImages/index.ts new file mode 100644 index 0000000000..8a683d28e9 --- /dev/null +++ b/app/components/reusable/UploadModalImages/index.ts @@ -0,0 +1 @@ +export * from "./UploadModalImages"; diff --git a/app/components/reusable/index.ts b/app/components/reusable/index.ts index f31055a664..b27527c247 100644 --- a/app/components/reusable/index.ts +++ b/app/components/reusable/index.ts @@ -27,6 +27,7 @@ export * from "./TransitionDialogue"; //need to decouple hotkey signal export * from "./Typography"; export * from "./UploadModal"; export * from "./UploadModal3DPreview"; +export * from "./UploadModalImages"; export * from "./WaveformPlayer"; //TODO: need to rewrite because it's against tailwind diff --git a/app/enums/FeatureFlags.ts b/app/enums/FeatureFlags.ts index 74a01ccfb3..e828292c96 100644 --- a/app/enums/FeatureFlags.ts +++ b/app/enums/FeatureFlags.ts @@ -1,4 +1,5 @@ export enum FeatureFlags { SHOW_SEARCH_OBJECTS = "show-search-objects", + SHOW_IMAGEPLANE_TAB = "show-imageplane-tab", SHOW_SETS_TAB = "show-sets-tab", } diff --git a/app/enums/Tabs.ts b/app/enums/Tabs.ts index bfe6477c0c..ae553389ab 100644 --- a/app/enums/Tabs.ts +++ b/app/enums/Tabs.ts @@ -1,5 +1,6 @@ export enum TabTitles { SET_OBJECTS = "Film Sets", + IMAGE_PLANE = "Image Panels", CHARACTERS = "Character", ANIMATION = "Animation", EXPRESSIONS = "Facial Expression", diff --git a/app/pages/PageEnigma/comps/SidePanel/tabList.tsx b/app/pages/PageEnigma/comps/SidePanel/tabList.tsx index b5ca5ec329..9f53dc199a 100644 --- a/app/pages/PageEnigma/comps/SidePanel/tabList.tsx +++ b/app/pages/PageEnigma/comps/SidePanel/tabList.tsx @@ -8,6 +8,7 @@ import { faCube, faVolume, faBrush, + faPresentationScreen, } from "@fortawesome/pro-solid-svg-icons"; import { TabTitles } from "~/enums"; @@ -18,6 +19,7 @@ import { StylizeTab } from "~/pages/PageEnigma/comps/SidePanelTabs/tabComps/Styl 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"; +import { ImagePlanesTab } from "~/pages/PageEnigma/comps/SidePanelTabs/tabComps/ImagePlanesTab"; export interface TabItem { icon: FontAwesomeIconProps["icon"]; @@ -51,6 +53,11 @@ export const tabList = [ title: TabTitles.OBJECTS, component: , }, + { + icon: faPresentationScreen, + title: TabTitles.IMAGE_PLANE, + component: , + }, { icon: faVolume, title: TabTitles.AUDIO, diff --git a/app/pages/PageEnigma/comps/SidePanelMenu/SidePanelMenu.tsx b/app/pages/PageEnigma/comps/SidePanelMenu/SidePanelMenu.tsx index 3462765fbf..9c8dc1b0d4 100644 --- a/app/pages/PageEnigma/comps/SidePanelMenu/SidePanelMenu.tsx +++ b/app/pages/PageEnigma/comps/SidePanelMenu/SidePanelMenu.tsx @@ -26,6 +26,9 @@ export const SidePanelMenu = ({ }) => { useSignals(); const showSetsTab = usePosthogFeatureFlag(FeatureFlags.SHOW_SETS_TAB); + const showImagePlaneTab = usePosthogFeatureFlag( + FeatureFlags.SHOW_IMAGEPLANE_TAB, + ); return (
{ + const [openUploadModal, setOpenUploadModal] = useState(false); + + const [{ mediaItems: userImages, status: userFetchStatus }, setUserFetch] = + useState({ + mediaItems: undefined, + status: FetchStatus.READY, + }); + const [ + { mediaItems: featuredImages, status: feateredFetchStatus }, + setFeaturedFetch, + ] = useState({ + mediaItems: undefined, + status: FetchStatus.READY, + }); + + const [selectedFilter, setSelectedFilter] = useState(AssetFilterOption.MINE); + const diasplayedItems = + selectedFilter === AssetFilterOption.FEATURED + ? featuredImages ?? [] + : userImages ?? []; + + const [currentPage, setCurrentPage] = useState(0); + + const pageSize = 21; + const totalPages = Math.ceil(diasplayedItems.length / pageSize); + + const isFetching = isAnyStatusFetching([ + userFetchStatus, + feateredFetchStatus, + ]); + + const fetchUserImages = useCallback( + () => + fetchUserMediaItems({ + filterEngineCategories: [FilterEngineCategories.IMAGE_PLANE], + setState: (newState: FetchMediaItemStates) => { + setUserFetch((curr) => ({ + status: newState.status, + mediaItems: newState.mediaItems + ? newState.mediaItems + : curr.mediaItems, + })); + }, + defaultErrorMessage: "Unknown Error in Fetching User Images", + }), + [], + ); + + const fetchFeaturedImages = useCallback( + () => + fetchFeaturedMediaItems({ + filterEngineCategories: [FilterEngineCategories.IMAGE_PLANE], + setState: (newState: FetchMediaItemStates) => { + setFeaturedFetch((curr) => ({ + status: newState.status, + mediaItems: newState.mediaItems + ? newState.mediaItems + : curr.mediaItems, + })); + }, + defaultErrorMessage: "Unknown Error in Fetching Featured Images", + }), + [], + ); + + useEffect(() => { + if (!userImages) { + fetchUserImages(); + } + if (!featuredImages) { + fetchFeaturedImages(); + } + }, [userImages, fetchUserImages, featuredImages, fetchFeaturedImages]); + + return ( + <> + + + { + setSelectedFilter(Number(buttonIdx)); + }} + /> + +
+ +
+
+ +
+ {totalPages > 1 && ( + { + setCurrentPage(newPage); + }} + /> + )} + setOpenUploadModal(false)} + onSuccess={fetchUserImages} + isOpen={openUploadModal} + fileTypes={Object.values(IMAGE_FILE_TYPE)} + title="Upload Image Panels" + type={FilterEngineCategories.IMAGE_PLANE} + /> + + ); +}; diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/ObjectsTab.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/ObjectsTab.tsx index e271e65158..4e017a4f57 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/ObjectsTab.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/tabComps/ObjectsTab.tsx @@ -120,7 +120,7 @@ export const ObjectsTab = () => { const mediaFilesApi = new MediaFilesApi(); const response = await mediaFilesApi.SearchFeaturedMediaFiles({ search_term: searchTerm, - filter_engine_categories: [FilterEngineCategories.OBJECT], + filter_engine_categories: [FilterEngineCategories.IMAGE_PLANE], }); if (response.success && response.data) { diff --git a/app/root.tsx b/app/root.tsx index f874ff7957..12b6800d89 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -23,10 +23,8 @@ import { config } from "@fortawesome/fontawesome-svg-core"; import EnvironmentVariables from "~/Classes/EnvironmentVariables"; import { pageHeight, pageWidth } from "~/signals"; -import { Toaster } from "~/components"; import { showWizard } from "~/pages/PageEnigma/Wizard/signals/wizard"; - config.autoAddCss = false; /* eslint-disable import/first */ export const links: LinksFunction = () => [