diff --git a/app/api/media_files/GetMediaByUser.ts b/app/api/media_files/GetMediaByUser.ts index faa53ca8f9..0e47ba679d 100644 --- a/app/api/media_files/GetMediaByUser.ts +++ b/app/api/media_files/GetMediaByUser.ts @@ -19,7 +19,8 @@ export interface GetMediaParams { | "character" | "animation" | "object" - | "skybox"; + | "skybox" + | "expression"; } export const GetMediaByUser = MakeRequest< diff --git a/app/api/media_files/UploadEngineAsset.ts b/app/api/media_files/UploadEngineAsset.ts index 096d7d45b2..c191bbc72c 100644 --- a/app/api/media_files/UploadEngineAsset.ts +++ b/app/api/media_files/UploadEngineAsset.ts @@ -6,6 +6,21 @@ export enum MediaFileEngineCategory { Animation = "animation", Object = "object", Skybox = "skybox", + Expression = "expression", +} + +export enum MediaFileAnimationType { + ar_kit = "ar_kit", + miku_miku_dance = "miku_miku_dance", + miku_miku_dance_ar_kit = "miku_miku_dance_ar_kit", + mixamo = "mixamo", + mixamo_ar_kit = "mixamo_ar_kit", + mocap_net = "mocap_net", + mocap_net_ar_kit = "mocap_net_ar_kit", + move_ai = "move_ai", + move_ai_ar_kit = "move_ai_ar_kit", + rigify = "rigify", + rigify_ar_kit = "rigify_ar_kit", } export enum MediaFileSubtype { @@ -35,15 +50,17 @@ export enum MediaFileSubtype { } export interface UploadEngineAssetRequest { - file: File; - media_file_subtype?: MediaFileSubtype; - title?: string; engine_category: MediaFileEngineCategory; + file: File; + maybe_animation_type: MediaFileAnimationType | null; + maybe_title?: string; + maybe_visibility?: string; uuid_idempotency_token?: string; } export interface UploadEngineAssetResponse { media_file_token: string; + engine_category: string; success: boolean; } diff --git a/app/api/media_files/UploadMedia.ts b/app/api/media_files/UploadMedia.ts index 580ff28928..360f77e2d3 100644 --- a/app/api/media_files/UploadMedia.ts +++ b/app/api/media_files/UploadMedia.ts @@ -1,17 +1,18 @@ import MakeMultipartRequest from "../MakeMultipartRequest"; export interface UploadMediaRequest { - uuid_idempotency_token?: string, - file: any, - source: string + uuid_idempotency_token?: string; + file: File; + source: string; + title?: string; // dataset_token: string, } export interface UploadMediaResponse { - media_file_token: string, - success: boolean + media_file_token: string; + success: boolean; } export const UploadMedia = (request: UploadMediaRequest) => { - return MakeMultipartRequest("/v1/media_files/upload",request); -} \ No newline at end of file + return MakeMultipartRequest("/v1/media_files/upload", request); +}; diff --git a/app/api/media_files/UploadNewEngineAsset.ts b/app/api/media_files/UploadNewEngineAsset.ts index b5255dc0fa..06bf73ca84 100644 --- a/app/api/media_files/UploadNewEngineAsset.ts +++ b/app/api/media_files/UploadNewEngineAsset.ts @@ -21,6 +21,7 @@ export enum MediaFileAnimationType { MoveAiArKit = "move_ai_ar_kit", Rigify = "rigify", RigifyArKit = "rigify_ar_kit", + Rokoko = "rokoko", } export interface UploadNewEngineAssetRequest { @@ -29,6 +30,7 @@ export interface UploadNewEngineAssetRequest { maybe_animation_type: MediaFileAnimationType; maybe_title?: string; maybe_visibility?: "public" | "private"; + maybe_duration_millis?: number; uuid_idempotency_token?: string; } diff --git a/app/components/ListDropdown.tsx b/app/components/ListDropdown.tsx index aeb04875fc..626c692a9b 100644 --- a/app/components/ListDropdown.tsx +++ b/app/components/ListDropdown.tsx @@ -1,60 +1,54 @@ -import { Fragment, useEffect, useState } from 'react' -import { Listbox, Transition } from '@headlessui/react' -import { faCheck, faChevronDown } from '@fortawesome/pro-solid-svg-icons' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' - +import { Fragment, useEffect, useState } from "react"; +import { Listbox, Transition } from "@headlessui/react"; +import { faCheck, faChevronDown } from "@fortawesome/pro-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; interface ListDropdownProps { - list: {[key: string] : string}[]; - onSelect: (val:string)=>void; + list: { [key: string]: string }[]; + onSelect: (val: string) => void; } -export const ListDropdown = ({ - list, onSelect -}:ListDropdownProps) => { +export const ListDropdown = ({ list, onSelect }: ListDropdownProps) => { const [selected, setSelected] = useState(list[0]); - - useEffect(()=>{ + + useEffect(() => { onSelect(Object.values(selected)[0]); }, [selected]); return (
- - {selected.name} + + {Object.keys(selected)[0]} - - + leaveTo="opacity-0"> + {list.map((item, itemIdx) => ( `relative cursor-pointer select-none py-2 pl-10 pr-4 text-white ${ - active ? 'text-white' : 'text-gray-400' + active ? "text-white" : "text-gray-400" }` } - value={item} - > + value={item}> {({ selected }) => ( <> + selected ? "font-medium" : "font-normal" + }`}> {Object.values(item)[0]} {selected ? ( - ) : null} @@ -65,5 +59,5 @@ export const ListDropdown = ({
- ) -} \ No newline at end of file + ); +}; diff --git a/app/components/UploadModal/fitCameraToCenteredObject.ts b/app/components/UploadModal/fitCameraToCenteredObject.ts index f089588f0b..692652df8d 100644 --- a/app/components/UploadModal/fitCameraToCenteredObject.ts +++ b/app/components/UploadModal/fitCameraToCenteredObject.ts @@ -3,10 +3,10 @@ import * as THREE from "three"; // from here https://wejn.org/2020/12/cracking-the-threejs-object-fitting-nut/ export const fitCameraToCenteredObject = function ( - camera, - object, - offset, - orbitControls, + camera: THREE.PerspectiveCamera, + object: THREE.Object3D, + offset?: number, + orbitControls?, ) { const boundingBox = new THREE.Box3(); boundingBox.setFromObject(object); diff --git a/app/components/UploadModalMovement/DragAndDropZone.tsx b/app/components/UploadModalMovement/DragAndDropZone.tsx new file mode 100644 index 0000000000..8b350728ea --- /dev/null +++ b/app/components/UploadModalMovement/DragAndDropZone.tsx @@ -0,0 +1,53 @@ +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faFileArrowUp, faFileAudio } from "@fortawesome/pro-solid-svg-icons"; +import { P } from "~/components"; +import { getFileName } from "./utils/getFileName"; + +interface Props { + file: File | null; + fileTypes: string[]; +} + +export const DragAndDropZone = ({ file, fileTypes }: Props) => { + const fileSize = + file && file.size >= 1024 * 1024 + ? (file.size / 1024 / 1024).toFixed(2) + " MB" + : file + ? `${Math.floor(file.size / 1024)} KB` + : null; + + const fileName = file && getFileName(file).toUpperCase(); + + if (!file) { + return ( +
+ +
+

+ Upload a file or drop it here +

+

+ {fileTypes.join(", ").toString()} supported +

+
+
+ ); + } else { + return ( +
+ +
+

+ {file.name.slice(0, file.name.lastIndexOf("."))} +

+

+ + {`${fileName} file size: ${fileSize} `} + + Change File +

+
+
+ ); + } +}; diff --git a/app/components/UploadModalMovement/UploadAssetError.tsx b/app/components/UploadModalMovement/UploadAssetError.tsx new file mode 100644 index 0000000000..c722d53afa --- /dev/null +++ b/app/components/UploadModalMovement/UploadAssetError.tsx @@ -0,0 +1,37 @@ +import { Button } from "~/components"; + +interface Props { + onCancel: () => void; + onRetry: () => void; + isAssetError: boolean; +} + +export const UploadAssetError = ({ + onCancel, + onRetry, + isAssetError, +}: Props) => { + return ( + <> + {isAssetError + ? "There was a problem uploading your object." + : "There was a problem creating art for your object"} +
+ + +
+ + ); +}; diff --git a/app/components/UploadModalMovement/UploadFile.tsx b/app/components/UploadModalMovement/UploadFile.tsx new file mode 100644 index 0000000000..27b5911f62 --- /dev/null +++ b/app/components/UploadModalMovement/UploadFile.tsx @@ -0,0 +1,77 @@ +import { FileUploader } from "react-drag-drop-files"; +import { Button } from "~/components"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faSpinnerThird } from "@fortawesome/pro-solid-svg-icons"; +import { DragAndDropZone } from "./DragAndDropZone"; +import { UploadMediaResponse } from "~/components/UploadAudioButtonDialogue/utilities"; +import { useState } from "react"; + +enum UploadState { + "init", + "staged", + "uploading", + "uploaded", + "error", +} + +interface Props { + title: string; + fileTypes: string[]; + setToken?: (token: string) => void; + uploadFile?: (file: File) => Promise; + file: File | null; + setFile: (file: File | null) => void; +} + +export const UploadFile = ({ + fileTypes, + title, + setToken, + uploadFile, + file, + setFile, +}: Props) => { + const [uploadState, setUploadState] = useState(UploadState.init); + + const submitUpload = (file: File | null) => { + if (file && uploadFile) { + setUploadState(UploadState.uploading); + uploadFile(file).then((res: UploadMediaResponse) => { + if ("media_file_token" in res) { + setUploadState(UploadState.uploaded); + setToken && setToken(res.media_file_token); + } else { + setUploadState(UploadState.error); + } + }); + } + }; + + return ( +
+ {title} + { + setFile(file); + setUploadState(file ? UploadState.staged : UploadState.init); + }} + name="file" + types={fileTypes} + maxSize={50}> + + + {!!uploadFile && ( +
+ +
+ )} +
+ ); +}; diff --git a/app/components/UploadModalMovement/UploadFiles.tsx b/app/components/UploadModalMovement/UploadFiles.tsx new file mode 100644 index 0000000000..1afe7065e2 --- /dev/null +++ b/app/components/UploadModalMovement/UploadFiles.tsx @@ -0,0 +1,108 @@ +import { Button, H6, Input, ListDropdown } from "~/components"; +import { UploadFile } from "~/components/UploadModalMovement/UploadFile"; +import { useState } from "react"; +import { MediaFileAnimationType } from "~/api/media_files/UploadNewEngineAsset"; + +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/UploadModalMovement/UploadLoaderError.tsx b/app/components/UploadModalMovement/UploadLoaderError.tsx new file mode 100644 index 0000000000..60b784f23e --- /dev/null +++ b/app/components/UploadModalMovement/UploadLoaderError.tsx @@ -0,0 +1,30 @@ +import { Button } from "~/components"; + +interface Props { + onCancel: () => void; + onRetry: () => void; +} + +export const UploadLoaderError = ({ onCancel, onRetry }: Props) => { + return ( + <> + There was a problem loading your object. Try another. +
+ + +
+ + ); +}; diff --git a/app/components/UploadModalMovement/UploadModal.scss b/app/components/UploadModalMovement/UploadModal.scss new file mode 100644 index 0000000000..e6e3471016 --- /dev/null +++ b/app/components/UploadModalMovement/UploadModal.scss @@ -0,0 +1,16 @@ +.obj-uploader-modal-load-view { + .uploader-dots { + background: transparent; + margin-bottom: 1rem; + } + .uploader-message { + width: 100%; + opacity: 0.5; + text-align: center; + } +} + +.object-preview-canvas { + width: 100%; + overflow: hidden; +} diff --git a/app/components/UploadModalMovement/UploadModalMovement.tsx b/app/components/UploadModalMovement/UploadModalMovement.tsx new file mode 100644 index 0000000000..1a3efbd851 --- /dev/null +++ b/app/components/UploadModalMovement/UploadModalMovement.tsx @@ -0,0 +1,192 @@ +import { useCallback, useState } from "react"; +import { TransitionDialogue, LoadingDots } from "~/components"; +import "./UploadModal.scss"; +import { UploadLoaderError } from "~/components/UploadModalMovement/UploadLoaderError"; +import { UploadAssetError } from "~/components/UploadModalMovement/UploadAssetError"; +import { UploadSuccess } from "~/components/UploadModalMovement/UploadSuccess"; +import { UploadFiles } from "~/components/UploadModalMovement/UploadFiles"; +import { + MediaFileAnimationType, + MediaFileEngineCategory, + UploadNewEngineAsset, +} from "~/api/media_files/UploadNewEngineAsset"; +import { v4 as uuidv4 } from "uuid"; +import { UploadMedia } from "~/api/media_files/UploadMedia"; +import { EditCoverImage } from "~/api/media_files/EditCoverImage"; + +interface Props { + closeModal: () => void; + onClose: () => void; + onSuccess: () => void; + isOpen: boolean; + title: string; + fileTypes: string[]; + typeOptions: { [key: string]: string }[]; + type: MediaFileEngineCategory; +} + +enum UploaderState { + ready, + uploadingAsset, + uploadingCover, + settingCover, + success, + assetError, + coverCreateError, + coverSetError, + loaderError, +} + +export default function UploadModalMovement({ + closeModal, + isOpen, + onClose, + onSuccess, + title, + fileTypes, + typeOptions, + type, +}: Props) { + const [objUploadStatus, setObjUploadStatus] = useState(UploaderState.ready); + + const resetModalState = useCallback(() => { + setObjUploadStatus(UploaderState.ready); + }, []); + + const onSubmit = async ({ + title, + typeOption, + assetFile, + length, + thumbnailFile, + }: { + title: string; + typeOption: MediaFileAnimationType; + assetFile: File; + length: number; + thumbnailFile: File | null; + }) => { + const res = await UploadNewEngineAsset({ + engine_category: type, + file: assetFile, + maybe_animation_type: typeOption as MediaFileAnimationType, + maybe_duration_millis: length, + maybe_title: title, + uuid_idempotency_token: uuidv4(), + }); + if (!("media_file_token" in res)) { + setObjUploadStatus(UploaderState.assetError); + return; + } + const assetToken = res.media_file_token; + if (!thumbnailFile) { + setObjUploadStatus(UploaderState.success); + return; + } + const resp = await UploadMedia({ + uuid_idempotency_token: uuidv4(), + file: thumbnailFile, + source: title, + }); + if (!("media_file_token" in resp)) { + setObjUploadStatus(UploaderState.uploadingCover); + return; + } + const thumbnailToken = resp.media_file_token; + const editRes = await EditCoverImage(assetToken, { + cover_image_media_file_token: thumbnailToken, + }); + if (!editRes.success) { + setObjUploadStatus(UploaderState.uploadingCover); + return; + } + setObjUploadStatus(UploaderState.success); + }; + + const objUploaderContent = () => { + switch (objUploadStatus) { + case UploaderState.ready: + return ( + { + closeModal(); + onClose(); + }} + typeOptions={typeOptions} + onSubmit={onSubmit} + /> + ); + case UploaderState.uploadingAsset: + case UploaderState.uploadingCover: + case UploaderState.settingCover: + return ( +
+ +
Uploading...
+
+ ); + case UploaderState.success: + return ( + { + closeModal(); + onClose(); + onSuccess(); + setTimeout(() => setObjUploadStatus(UploaderState.ready), 0); + }} + /> + ); + case UploaderState.assetError: + case UploaderState.coverCreateError: + case UploaderState.coverSetError: + return ( + { + closeModal(); + onClose(); + setTimeout(() => setObjUploadStatus(UploaderState.ready), 0); + }} + onRetry={() => { + switch (objUploadStatus) { + case UploaderState.assetError: { + break; + } + case UploaderState.coverCreateError: { + break; + } + } + }} + isAssetError={objUploadStatus === UploaderState.assetError} + /> + ); + case UploaderState.loaderError: + return ( + { + closeModal(); + onClose(); + setTimeout(() => setObjUploadStatus(UploaderState.ready), 0); + }} + onRetry={() => { + setObjUploadStatus(UploaderState.ready); + resetModalState(); + }} + /> + ); + } + }; + + return ( + + {objUploaderContent()} + + ); +} diff --git a/app/components/UploadModalMovement/UploadSuccess.tsx b/app/components/UploadModalMovement/UploadSuccess.tsx new file mode 100644 index 0000000000..b751a2d4ce --- /dev/null +++ b/app/components/UploadModalMovement/UploadSuccess.tsx @@ -0,0 +1,26 @@ +import { Button } from "~/components"; + +interface Props { + title: string; + onOk: () => void; +} + +export const UploadSuccess = ({ title, onOk }: Props) => { + return ( +
+
{`Added ${title} to objects`}
+
+ +
+
+ ); +}; diff --git a/app/components/UploadModalMovement/index.tsx b/app/components/UploadModalMovement/index.tsx new file mode 100644 index 0000000000..4486ed2c7f --- /dev/null +++ b/app/components/UploadModalMovement/index.tsx @@ -0,0 +1 @@ +export { default } from "./UploadModalMovement"; diff --git a/app/components/UploadModalMovement/utils/getFileName.ts b/app/components/UploadModalMovement/utils/getFileName.ts new file mode 100644 index 0000000000..73e833b8cb --- /dev/null +++ b/app/components/UploadModalMovement/utils/getFileName.ts @@ -0,0 +1,3 @@ +export const getFileName = (file: File) => { + return file.name.split(".")[0] || ""; +}; diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AnimationTab.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AnimationTab.tsx index 0be2c38611..77644e31dc 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AnimationTab.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AnimationTab.tsx @@ -1,14 +1,73 @@ import { animationFilter, animationItems } from "~/pages/PageEnigma/store"; -import { AssetFilterOption } from "~/pages/PageEnigma/models"; +import { + AssetFilterOption, + AssetType, + MediaItem, +} from "~/pages/PageEnigma/models"; 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 UploadModalMovement from "~/components/UploadModalMovement"; +import { MediaFileAnimationType } from "~/api/media_files/UploadNewEngineAsset"; +import { MediaFileEngineCategory } from "~/api/media_files/UploadEngineAsset"; +import { useCallback, useContext, useEffect, useState } from "react"; +import { + GetMediaByUser, + GetMediaListResponse, +} from "~/api/media_files/GetMediaByUser"; +import { AuthenticationContext } from "~/contexts/Authentication"; export const AnimationTab = () => { useSignals(); + const { authState } = useContext(AuthenticationContext); + const [open, setOpen] = useState(false); + const [userAnimations, setUserAnimations] = useState([]); + + const refetchAnimations = useCallback(async () => { + if (!authState?.userInfo) { + return; + } + return GetMediaByUser( + authState?.userInfo?.username || "", + {}, + { + filter_engine_categories: MediaFileEngineCategory.Animation, + // page_size: 5, + }, + ).then((res: GetMediaListResponse) => { + if (res.success && res.results) { + setUserAnimations( + res.results.map((item, index: number) => { + return { + version: 1, + type: AssetType.ANIMATION, + media_id: item.token, + name: item.maybe_title, + publicBucketPath: item.public_bucket_path, + length: ((item.maybe_duration_millis ?? 1000) / 1000) * 60, + thumbnail: item.cover_image?.maybe_cover_image_public_bucket_path + ? "https://cdn.fakeyou.com/cdn-cgi/image/width=600,quality=100" + + item.cover_image?.maybe_cover_image_public_bucket_path + : undefined, + isMine: + item.maybe_creator_user?.user_token === + authState?.userInfo?.user_token, + imageIndex: index, + } as MediaItem; + }), + ); + } + }); + }, [authState?.userInfo]); + + useEffect(() => { + if (authState?.userInfo && !userAnimations.length) { + refetchAnimations(); + } + }, [authState?.userInfo, refetchAnimations, userAnimations]); return ( <> @@ -53,16 +112,33 @@ export const AnimationTab = () => {
+ setOpen(false)} + onClose={() => setOpen(false)} + onSuccess={refetchAnimations} + isOpen={open} + fileTypes={["GLB"]} + title="Upload Animation" + typeOptions={[ + { Mixamo: MediaFileAnimationType.Mixamo }, + { MikuMikuDance: MediaFileAnimationType.MikuMikuDance }, + { MoveAi: MediaFileAnimationType.MoveAi }, + { Rigify: MediaFileAnimationType.Rigify }, + { Rokoko: MediaFileAnimationType.Rokoko }, + ]} + type={MediaFileEngineCategory.Animation} + /> ); }; diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ExpressionTab.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ExpressionTab.tsx index 9395178e02..7f64191b27 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ExpressionTab.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ExpressionTab.tsx @@ -1,75 +1,136 @@ import { expressionFilter, expressionItems } from "~/pages/PageEnigma/store"; -import { AssetFilterOption } from "~/pages/PageEnigma/models"; +import { + AssetFilterOption, + AssetType, + MediaItem, +} from "~/pages/PageEnigma/models"; 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 "../comps/TabTitle"; +import UploadModalMovement from "~/components/UploadModalMovement"; +import { useCallback, useContext, useEffect, useState } from "react"; +import { MediaFileEngineCategory } from "~/api/media_files/UploadEngineAsset"; +import { + GetMediaByUser, + GetMediaListResponse, +} from "~/api/media_files/GetMediaByUser"; +import { AuthenticationContext } from "~/contexts/Authentication"; +import { TabTitle } from "~/pages/PageEnigma/comps/SidePanelTabs/comps/TabTitle"; +import { MediaFileAnimationType } from "~/api/media_files/UploadNewEngineAsset"; export const ExpressionTab = () => { useSignals(); + const [open, setOpen] = useState(false); + const { authState } = useContext(AuthenticationContext); + + const refetchExpressions = useCallback(async () => { + if (!authState?.userInfo) { + return; + } + return GetMediaByUser( + authState?.userInfo?.username || "", + {}, + { + filter_engine_categories: MediaFileEngineCategory.Expression, + // page_size: 5, + }, + ).then((res: GetMediaListResponse) => { + if (res.success && res.results) { + expressionItems.value = res.results.map((item, index: number) => { + return { + version: 1, + type: AssetType.EXPRESSION, + media_id: item.token, + name: item.maybe_title, + publicBucketPath: item.public_bucket_path, + length: ((item.maybe_duration_millis ?? 1000) / 1000) * 60, + thumbnail: item.cover_image?.maybe_cover_image_public_bucket_path + ? "https://cdn.fakeyou.com/cdn-cgi/image/width=600,quality=100" + + item.cover_image?.maybe_cover_image_public_bucket_path + : undefined, + isMine: + item.maybe_creator_user?.user_token === + authState?.userInfo?.user_token, + imageIndex: index, + } as MediaItem; + }); + } + }); + }, [authState?.userInfo]); + + useEffect(() => { + if (authState?.userInfo && !expressionItems.value.length) { + refetchExpressions(); + } + }, [authState?.userInfo, refetchExpressions]); return ( <> - -
-
-
- - - -
+
+
+ + +
-
+
-
+
+ setOpen(false)} + onClose={() => setOpen(false)} + onSuccess={refetchExpressions} + isOpen={open} + fileTypes={["GLB"]} + title="Upload Expression" + typeOptions={[{ ARKit: MediaFileAnimationType.ArKit }]} + type={MediaFileEngineCategory.Expression} + /> ); }; diff --git a/app/pages/PageEnigma/comps/Timeline/TrackClips.tsx b/app/pages/PageEnigma/comps/Timeline/TrackClips.tsx index 28ed3baa63..1292a20983 100644 --- a/app/pages/PageEnigma/comps/Timeline/TrackClips.tsx +++ b/app/pages/PageEnigma/comps/Timeline/TrackClips.tsx @@ -119,6 +119,7 @@ export const TrackClips = ({ id, clips, updateClip, group, type }: Props) => { ); if (clipOffset + (dragItem.value!.length ?? 0) > filmLength.value * 60) { + DndAsset.notDropText = "Item is longer than the film length"; canDrop.value = false; return; } diff --git a/app/pages/PageEnigma/models/movies.ts b/app/pages/PageEnigma/models/movies.ts index ae7d31867e..e6416c3cc6 100644 --- a/app/pages/PageEnigma/models/movies.ts +++ b/app/pages/PageEnigma/models/movies.ts @@ -26,6 +26,7 @@ export interface MediaInfo { origin_product_category: string; maybe_origin_model_type: null | string; maybe_origin_model_token: null | string; + maybe_duration_millis: number | null; public_bucket_path: string; cover_image: { maybe_cover_image_public_bucket_path: null | string; diff --git a/app/pages/PageEnigma/store/media.ts b/app/pages/PageEnigma/store/media.ts index 805f277f37..05808719c0 100644 --- a/app/pages/PageEnigma/store/media.ts +++ b/app/pages/PageEnigma/store/media.ts @@ -484,26 +484,7 @@ export const audioItems = signal([ }, ]); -export const expressionItems = signal([ - { - version: 1, - media_id: "m_yzreg1494d08aepezj74607d2ascep", - type: AssetType.EXPRESSION, - length: 100, - name: "Smile", - thumbnail: "/resources/characters/img04.png", - isMine: true, - }, - { - version: 1, - media_id: "m_qepzjytd4ahd5askdt2ngrwzj2aa3f", - type: AssetType.EXPRESSION, - length: 100, - name: "Frown", - thumbnail: "/resources/characters/img07.png", - isBookmarked: true, - }, -]); +export const expressionItems = signal([]); export const cameraItems = signal([ { diff --git a/app/root.tsx b/app/root.tsx index b337b3b345..5d11e16495 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -100,7 +100,6 @@ export default function App() { useEffect(() => { environmentVariables.value = data.ENV; - console.log(data.ENV); }, [data]); return (