diff --git a/crates/vendor/tauri-plugin-http/permissions/schemas/schema.json b/crates/vendor/tauri-plugin-http/permissions/schemas/schema.json index ea774399d5..31640bee16 100644 --- a/crates/vendor/tauri-plugin-http/permissions/schemas/schema.json +++ b/crates/vendor/tauri-plugin-http/permissions/schemas/schema.json @@ -343,10 +343,10 @@ "markdownDescription": "Denies the fetch_send command without any pre-configured scope." }, { - "description": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n\n#### This default permission set includes:\n\n- `allow-fetch`\n- `allow-fetch-cancel`\n- `allow-fetch-read-body`\n- `allow-fetch-send`", + "description": "This permission set configures what kind of\r\nfetch operations are available from the http plugin.\r\n\r\nThis enables all fetch operations but does not\r\nallow explicitly any origins to be fetched. This needs to\r\nbe manually configured before usage.\r\n\r\n#### Granted Permissions\r\n\r\nAll fetch operations are enabled.\r\n\r\n\n#### This default permission set includes:\n\n- `allow-fetch`\n- `allow-fetch-cancel`\n- `allow-fetch-read-body`\n- `allow-fetch-send`", "type": "string", "const": "default", - "markdownDescription": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n\n#### This default permission set includes:\n\n- `allow-fetch`\n- `allow-fetch-cancel`\n- `allow-fetch-read-body`\n- `allow-fetch-send`" + "markdownDescription": "This permission set configures what kind of\r\nfetch operations are available from the http plugin.\r\n\r\nThis enables all fetch operations but does not\r\nallow explicitly any origins to be fetched. This needs to\r\nbe manually configured before usage.\r\n\r\n#### Granted Permissions\r\n\r\nAll fetch operations are enabled.\r\n\r\n\n#### This default permission set includes:\n\n- `allow-fetch`\n- `allow-fetch-cancel`\n- `allow-fetch-read-body`\n- `allow-fetch-send`" } ] } diff --git a/frontend/apps/artcraft/app/src/Classes/ApiManager/BetaKeysApi.ts b/frontend/apps/artcraft/app/src/Classes/ApiManager/BetaKeysApi.ts index c9be914986..3ee28a61f2 100644 --- a/frontend/apps/artcraft/app/src/Classes/ApiManager/BetaKeysApi.ts +++ b/frontend/apps/artcraft/app/src/Classes/ApiManager/BetaKeysApi.ts @@ -1,6 +1,6 @@ import { ApiManager, ApiResponse } from "./ApiManager"; import { BetaKey } from "~/models"; -import { Pagination } from "~/pages/PageEnigma/models"; +import { Pagination } from "~/pages/PageScene/models"; export class BetaKeysApi extends ApiManager { public CreateBetaKey({ diff --git a/frontend/apps/artcraft/app/src/Classes/ApiManager/MediaFilesApi.ts b/frontend/apps/artcraft/app/src/Classes/ApiManager/MediaFilesApi.ts index 79d857ffb3..55ac7e2f67 100644 --- a/frontend/apps/artcraft/app/src/Classes/ApiManager/MediaFilesApi.ts +++ b/frontend/apps/artcraft/app/src/Classes/ApiManager/MediaFilesApi.ts @@ -3,7 +3,7 @@ import { MediaInfo, Pagination, PaginationInfinite, -} from "~/pages/PageEnigma/models"; +} from "~/pages/PageScene/models"; import { ApiManager, ApiResponse } from "./ApiManager"; import { authentication } from "~/signals"; import type { Property } from "csstype"; diff --git a/frontend/apps/artcraft/app/src/Classes/ApiManager/PromptsApi.ts b/frontend/apps/artcraft/app/src/Classes/ApiManager/PromptsApi.ts index bda14090b5..1081cf1a3e 100644 --- a/frontend/apps/artcraft/app/src/Classes/ApiManager/PromptsApi.ts +++ b/frontend/apps/artcraft/app/src/Classes/ApiManager/PromptsApi.ts @@ -1,5 +1,5 @@ import { ApiManager, ApiResponse } from "./ApiManager"; -import { Prompts } from "~/pages/PageEnigma/models"; +import { Prompts } from "~/pages/PageScene/models"; export class PromptsApi extends ApiManager { public GetPromptsByToken({ diff --git a/frontend/apps/artcraft/app/src/Classes/ApiManager/TtsApi.ts b/frontend/apps/artcraft/app/src/Classes/ApiManager/TtsApi.ts index b15a8afaa3..e7c0d4ee79 100644 --- a/frontend/apps/artcraft/app/src/Classes/ApiManager/TtsApi.ts +++ b/frontend/apps/artcraft/app/src/Classes/ApiManager/TtsApi.ts @@ -1,5 +1,5 @@ import { ApiManager, ApiResponse } from "./ApiManager"; -import { GenerateTtsAudioErrorType } from "~/pages/PageEnigma/enums"; +import { GenerateTtsAudioErrorType } from "~/pages/PageScene/enums"; import { Visibility } from "~/enums"; export interface GenerateTtsAudioRequest { diff --git a/frontend/apps/artcraft/app/src/Classes/ApiManager/UserBookmarksApi.ts b/frontend/apps/artcraft/app/src/Classes/ApiManager/UserBookmarksApi.ts index 3e5e25e391..6aba3877d7 100644 --- a/frontend/apps/artcraft/app/src/Classes/ApiManager/UserBookmarksApi.ts +++ b/frontend/apps/artcraft/app/src/Classes/ApiManager/UserBookmarksApi.ts @@ -4,7 +4,7 @@ import { UserBookmarkBatch, UserBookmarkByEntity, UserBookmarkByUser, -} from "~/pages/PageEnigma/models"; +} from "~/pages/PageScene/models"; import { authentication } from "~/signals"; export enum ScopedEntityTypes { diff --git a/frontend/apps/artcraft/app/src/Classes/ApiManager/VoiceDesignerApi.ts b/frontend/apps/artcraft/app/src/Classes/ApiManager/VoiceDesignerApi.ts index 4256c9848f..6b1efe251f 100644 --- a/frontend/apps/artcraft/app/src/Classes/ApiManager/VoiceDesignerApi.ts +++ b/frontend/apps/artcraft/app/src/Classes/ApiManager/VoiceDesignerApi.ts @@ -1,5 +1,5 @@ import { ApiManager, ApiResponse } from "./ApiManager"; -import { Pagination } from "~/pages/PageEnigma/models"; +import { Pagination } from "~/pages/PageScene/models"; import { authentication } from "~/signals"; import { ZsDataset } from "~/models/Dataset"; diff --git a/frontend/apps/artcraft/app/src/Classes/ApiManager/WeightsApi.ts b/frontend/apps/artcraft/app/src/Classes/ApiManager/WeightsApi.ts index 010d7965c0..4f64fd93da 100644 --- a/frontend/apps/artcraft/app/src/Classes/ApiManager/WeightsApi.ts +++ b/frontend/apps/artcraft/app/src/Classes/ApiManager/WeightsApi.ts @@ -1,7 +1,7 @@ import { ApiManager, ApiResponse } from "./ApiManager"; import { authentication } from "~/signals"; import { Weight } from "~/models"; -import { Pagination, PaginationInfinite } from "~/pages/PageEnigma/models"; +import { Pagination, PaginationInfinite } from "~/pages/PageScene/models"; import { FilterEngineCategories, FilterMediaClasses, diff --git a/frontend/apps/artcraft/app/src/components/experiences/ImageTo3DExperience.tsx b/frontend/apps/artcraft/app/src/components/experiences/ImageTo3DExperience.tsx index d6afe06184..1ba99b2436 100644 --- a/frontend/apps/artcraft/app/src/components/experiences/ImageTo3DExperience.tsx +++ b/frontend/apps/artcraft/app/src/components/experiences/ImageTo3DExperience.tsx @@ -25,10 +25,11 @@ import { import { toast } from "react-hot-toast"; import { v4 as uuidv4 } from "uuid"; import { useTabStore } from "../../pages/Stores/TabState"; -import { addObject } from "../../pages/PageEnigma/signals/objectGroup/addObject"; -import { set3DPageMounted } from "../../pages/PageEnigma/Editor/editor"; +import { addObject } from "../../pages/PageScene/actions"; +import { getActiveEditor } from "../../pages/PageScene/contexts/EngineContext"; +import { set3DPageMounted } from "../../pages/PageScene/engine/editor"; import { AssetType } from "~/enums"; -import type { MediaItem } from "../../pages/PageEnigma/models"; +import type { MediaItem } from "../../pages/PageScene/models"; import { SPLAT_MODELS } from "@storyteller/model-list"; import { ClassyModelSelector, @@ -986,8 +987,13 @@ export const ImageTo3DExperience = ({ } as MediaItem & { position: { x: number; y: number; z: number }; }; - addObject(mediaItem); - toast.success("Added to 3D scene"); + const editor = getActiveEditor(); + if (editor) { + void addObject(editor, mediaItem); + toast.success("Added to 3D scene"); + } else { + toast.error("3D editor isn't ready yet"); + } }, 500); }} > diff --git a/frontend/apps/artcraft/app/src/components/reusable/UploadModal3D/UploadModal3D.tsx b/frontend/apps/artcraft/app/src/components/reusable/UploadModal3D/UploadModal3D.tsx index 9f6783cd31..95d12088b6 100644 --- a/frontend/apps/artcraft/app/src/components/reusable/UploadModal3D/UploadModal3D.tsx +++ b/frontend/apps/artcraft/app/src/components/reusable/UploadModal3D/UploadModal3D.tsx @@ -73,17 +73,6 @@ export function UploadModal3D(props: Props) { } }, [isOpen]); - useEffect(() => { - if (uploaderState.status === UploaderStates.success) { - // Automatically open the global Gallery modal after a successful upload - galleryModalVisibleViewMode.value = true; - galleryModalVisibleDuringDrag.value = true; - - onSuccess(selectedCategory); - onClose(); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [uploaderState.status]); const UploaderModalContent = () => { switch (uploaderState.status) { @@ -130,8 +119,12 @@ export function UploadModal3D(props: Props) { { - onClose(); + // Open the gallery so the user can drag the freshly + // uploaded model onto the canvas. + galleryModalVisibleViewMode.value = true; + galleryModalVisibleDuringDrag.value = true; onSuccess(selectedCategory); + onClose(); }} /> ); @@ -172,7 +165,8 @@ export function UploadModal3D(props: Props) { className="max-w-xl" showClose={true} > - + {/* Inline call — `` would be a fresh component reference each render, remounting the dropzone mid-click and breaking the file picker. */} + {UploaderModalContent()} ); } diff --git a/frontend/apps/artcraft/app/src/components/reusable/UploadModalImage/UploadModalImage.tsx b/frontend/apps/artcraft/app/src/components/reusable/UploadModalImage/UploadModalImage.tsx index df25115a24..6df6b2f134 100644 --- a/frontend/apps/artcraft/app/src/components/reusable/UploadModalImage/UploadModalImage.tsx +++ b/frontend/apps/artcraft/app/src/components/reusable/UploadModalImage/UploadModalImage.tsx @@ -48,17 +48,6 @@ export function UploadModalImage(props: Props) { } }, [isOpen]); - useEffect(() => { - if (uploaderState.status === UploaderStates.success) { - // Automatically open the global Gallery modal after a successful upload - galleryModalVisibleViewMode.value = true; - galleryModalVisibleDuringDrag.value = true; - - onSuccess(selectedCategory); - onClose(); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [uploaderState.status]); const UploaderModalContent = () => { switch (uploaderState.status) { @@ -94,8 +83,12 @@ export function UploadModalImage(props: Props) { { - onClose(); + // Open the gallery so the user can drag the freshly + // uploaded image onto the canvas. + galleryModalVisibleViewMode.value = true; + galleryModalVisibleDuringDrag.value = true; onSuccess(selectedCategory); + onClose(); }} /> ); @@ -136,7 +129,8 @@ export function UploadModalImage(props: Props) { className="max-w-xl" showClose={true} > - + {/* Inline call — `` would be a fresh component reference each render, remounting the dropzone mid-click and breaking the file picker. */} + {UploaderModalContent()} ); } diff --git a/frontend/apps/artcraft/app/src/components/reusable/UploadModalMedia/UploadImageMediaModal.tsx b/frontend/apps/artcraft/app/src/components/reusable/UploadModalMedia/UploadImageMediaModal.tsx index 34e373287c..94ff4dfebc 100644 --- a/frontend/apps/artcraft/app/src/components/reusable/UploadModalMedia/UploadImageMediaModal.tsx +++ b/frontend/apps/artcraft/app/src/components/reusable/UploadModalMedia/UploadImageMediaModal.tsx @@ -113,7 +113,8 @@ export function UploadImageMediaModal({ return ( - + {/* Inline call — `` would be a fresh component reference each render, remounting the dropzone mid-click and breaking the file picker. */} + {UploaderModalContent()} ); } diff --git a/frontend/apps/artcraft/app/src/components/reusable/UploadModalMedia/UploadModalMedia.tsx b/frontend/apps/artcraft/app/src/components/reusable/UploadModalMedia/UploadModalMedia.tsx index 53c75ebc99..2c3c528e56 100644 --- a/frontend/apps/artcraft/app/src/components/reusable/UploadModalMedia/UploadModalMedia.tsx +++ b/frontend/apps/artcraft/app/src/components/reusable/UploadModalMedia/UploadModalMedia.tsx @@ -100,7 +100,8 @@ export function UploadModalMedia({ return ( - + {/* Inline call — `` would be a fresh component reference each render, remounting the dropzone mid-click and breaking the file picker. */} + {UploaderModalContent()} ); } diff --git a/frontend/apps/artcraft/app/src/components/reusable/UploadModalSplat/UploadFilesSplat.tsx b/frontend/apps/artcraft/app/src/components/reusable/UploadModalSplat/UploadFilesSplat.tsx index ee2d797bd3..7a1084e190 100644 --- a/frontend/apps/artcraft/app/src/components/reusable/UploadModalSplat/UploadFilesSplat.tsx +++ b/frontend/apps/artcraft/app/src/components/reusable/UploadModalSplat/UploadFilesSplat.tsx @@ -40,7 +40,6 @@ interface Props { }; onClose: () => void; onUploadProgress: (newState: UploaderState) => void; - onLocalBytes: (bytes: ArrayBuffer, shouldFlip: boolean) => void; } export const UploadFilesSplat = ({ diff --git a/frontend/apps/artcraft/app/src/components/reusable/UploadModalSplat/UploadModalSplat.tsx b/frontend/apps/artcraft/app/src/components/reusable/UploadModalSplat/UploadModalSplat.tsx index 8e92ea7785..8179f8e592 100644 --- a/frontend/apps/artcraft/app/src/components/reusable/UploadModalSplat/UploadModalSplat.tsx +++ b/frontend/apps/artcraft/app/src/components/reusable/UploadModalSplat/UploadModalSplat.tsx @@ -18,7 +18,7 @@ import { UploadFilesSplat } from "./UploadFilesSplat"; interface Props { onClose: () => void; - onSuccess: (splatArrayBuffer: ArrayBuffer, shouldFlip: boolean) => void; + onSuccess: (category: FilterEngineCategories) => void; isOpen: boolean; title: string; titleIcon: IconDefinition; @@ -52,17 +52,6 @@ export function UploadModalSplat(props: Props) { } }, [isOpen]); - useEffect(() => { - if (uploaderState.status === UploaderStates.success) { - // Automatically open the global Gallery modal after a successful upload - // galleryModalVisibleViewMode.value = true; - // galleryModalVisibleDuringDrag.value = true; - - // onSuccess(selectedCategory); - onClose(); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [uploaderState.status]); const UploaderModalContent = () => { switch (uploaderState.status) { @@ -79,7 +68,6 @@ export function UploadModalSplat(props: Props) { }} onClose={onClose} onUploadProgress={updateUploaderState} - onLocalBytes={(buffer, shouldFlip) => { onSuccess(buffer, shouldFlip); }} /> ); @@ -103,8 +91,12 @@ export function UploadModalSplat(props: Props) { { + // Open the gallery so the user can drag the freshly + // uploaded splat onto the canvas. + galleryModalVisibleViewMode.value = true; + galleryModalVisibleDuringDrag.value = true; + onSuccess(selectedCategory); onClose(); - // onSuccess(selectedCategory); }} /> ); @@ -145,7 +137,8 @@ export function UploadModalSplat(props: Props) { className="max-w-xl" showClose={true} > - + {/* Inline call — `` would be a fresh component reference each render, remounting the dropzone mid-click and breaking the file picker. */} + {UploaderModalContent()} ); } diff --git a/frontend/apps/artcraft/app/src/components/signaled/ErrorDialog.tsx b/frontend/apps/artcraft/app/src/components/signaled/ErrorDialog.tsx index 97859e5590..ac26e5cdca 100644 --- a/frontend/apps/artcraft/app/src/components/signaled/ErrorDialog.tsx +++ b/frontend/apps/artcraft/app/src/components/signaled/ErrorDialog.tsx @@ -1,32 +1,36 @@ -import { useSignals } from "@preact/signals-react/runtime"; import { faTriangleExclamation } from "@fortawesome/pro-solid-svg-icons"; +import { useShallow } from "zustand/shallow"; import { Modal } from "@storyteller/ui-modal"; import { Button } from "@storyteller/ui-button"; -import { - showErrorDialog, - errorDialogMessage, - errorDialogTitle, -} from "~/pages/PageEnigma/signals"; +import { usePageSceneStore } from "~/pages/PageScene/PageSceneStore"; export function ErrorDialog() { - useSignals(); + const { showErrorDialog, errorDialogTitle, errorDialogMessage, setShowErrorDialog } = + usePageSceneStore( + useShallow((s) => ({ + showErrorDialog: s.showErrorDialog, + errorDialogTitle: s.errorDialogTitle, + errorDialogMessage: s.errorDialogMessage, + setShowErrorDialog: s.setShowErrorDialog, + })), + ); return ( (showErrorDialog.value = false)} + isOpen={showErrorDialog} + onClose={() => setShowErrorDialog(false)} showClose={false} >
- {errorDialogMessage.value} + {errorDialogMessage}
-
0 && "visible", - )} - > - {activeCount} -
-
- ); -}; diff --git a/frontend/apps/artcraft/app/src/components/signaled/TopBar/SceneTitleInput.tsx b/frontend/apps/artcraft/app/src/components/signaled/TopBar/SceneTitleInput.tsx index 6318708731..83d22293f7 100644 --- a/frontend/apps/artcraft/app/src/components/signaled/TopBar/SceneTitleInput.tsx +++ b/frontend/apps/artcraft/app/src/components/signaled/TopBar/SceneTitleInput.tsx @@ -4,11 +4,7 @@ import { useSignals } from "@preact/signals-react/runtime"; import { faPencil, faSpinnerThird } from "@fortawesome/pro-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { scene, signalScene, authentication } from "~/signals"; -import { - showErrorDialog, - errorDialogMessage, - errorDialogTitle, -} from "~/pages/PageEnigma/signals"; +import { usePageSceneStore } from "~/pages/PageScene/PageSceneStore"; import { Input } from "@storyteller/ui-input"; import { MediaFilesApi } from "~/Classes/ApiManager/MediaFilesApi"; import { getCreatorIcon, ModelCreator } from "@storyteller/model-list"; @@ -36,9 +32,7 @@ export const SceneTitleInput = ({ pageName }: Props) => { }; const handleShowErrorDialog = (errorMessage: string) => { - errorDialogTitle.value = "Error"; - errorDialogMessage.value = errorMessage; - showErrorDialog.value = true; + usePageSceneStore.getState().setErrorDialog("Error", errorMessage); }; const handleChangeSceneTitle = (e: React.ChangeEvent) => { diff --git a/frontend/apps/artcraft/app/src/components/signaled/TopBar/TopBar.tsx b/frontend/apps/artcraft/app/src/components/signaled/TopBar/TopBar.tsx index 2845457590..dcfa71c922 100644 --- a/frontend/apps/artcraft/app/src/components/signaled/TopBar/TopBar.tsx +++ b/frontend/apps/artcraft/app/src/components/signaled/TopBar/TopBar.tsx @@ -82,11 +82,8 @@ import toast from "react-hot-toast"; import { APP_DESCRIPTORS, goToApp } from "~/config/appMenu"; import { useStoryboardStore } from "~/pages/PageStoryboard"; import { useSceneStore } from "@storyteller/ui-pagedraw"; -import { - is3DEditorInitialized, - is3DSceneLoaded, - set3DPageMounted, -} from "~/pages/PageEnigma/Editor/editor"; +import { set3DPageMounted } from "~/pages/PageScene/engine/editor"; +import { usePageSceneStore } from "~/pages/PageScene/PageSceneStore"; import { useImageTo3DStore } from "~/pages/PageImageTo3DObject/ImageTo3DStore"; import { useImageTo3DWorldStore } from "~/pages/PageImageTo3DWorld/ImageTo3DWorldStore"; import { useRemoveBackgroundStore } from "~/pages/PageRemoveBackground/RemoveBackgroundStore"; @@ -243,8 +240,8 @@ export const TopBar = ({ pageName }: Props) => { const tabStore = useTabStore(); - const is3DSceneReady = is3DSceneLoaded.value; - const is3DEditorReady = is3DEditorInitialized.value; + const is3DSceneReady = usePageSceneStore((s) => s.is3DSceneLoaded); + const is3DEditorReady = usePageSceneStore((s) => s.is3DEditorInitialized); const [disableSwitcher, setDisableSwitcher] = useState(false); const switcherThrottle = useRef(false); diff --git a/frontend/apps/artcraft/app/src/config/appMenu.tsx b/frontend/apps/artcraft/app/src/config/appMenu.tsx index 07035ab532..2e4a7df2a5 100644 --- a/frontend/apps/artcraft/app/src/config/appMenu.tsx +++ b/frontend/apps/artcraft/app/src/config/appMenu.tsx @@ -17,7 +17,7 @@ import { useStoryboardPageEnabled, } from "@storyteller/ui-settings-modal"; import { useTabStore, TabId } from "~/pages/Stores/TabState"; -import { set3DPageMounted } from "~/pages/PageEnigma/Editor/editor"; +import { set3DPageMounted } from "~/pages/PageScene/engine/editor"; export type AppId = | "IMAGE" diff --git a/frontend/apps/artcraft/app/src/index.tsx b/frontend/apps/artcraft/app/src/index.tsx index c13e2c0a7c..f23ebb59bd 100644 --- a/frontend/apps/artcraft/app/src/index.tsx +++ b/frontend/apps/artcraft/app/src/index.tsx @@ -1,7 +1,7 @@ import { StrictMode, useEffect } from "react"; import { useSignals, useSignalEffect } from "@preact/signals-react/runtime"; import { BrowserRouter } from "react-router-dom"; -import { PageEnigma } from "./pages/PageEnigma/PageEnigma"; +import { PageScene } from "./pages/PageScene/PageScene"; import { GlobalFileDropHandler } from "./components/GlobalFileDropHandler/GlobalFileDropHandler"; import { createRoot } from "react-dom/client"; import "./styles/normalize.css"; @@ -13,6 +13,7 @@ import EnvironmentVariables from "~/Classes/EnvironmentVariables"; import { pageHeight, pageWidth, persistLogin } from "~/signals"; import { SyncStorytellerApiConfig } from "./api/SyncStorytellerApiConfig"; import { posthog } from "posthog-js"; +import { SoundManager } from "@storyteller/soundboard"; config.autoAddCss = false; /* eslint-disable import/first */ @@ -84,6 +85,10 @@ const GlobalSettingsManager = ({ env }: { env: Record }) => { }; }, []); + useEffect(() => { + SoundManager.install(); + }, []); + return null; }; @@ -94,7 +99,7 @@ createRoot(document.getElementById("root")!).render(
- + diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/DragAndDrop/DndAsset.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/DragAndDrop/DndAsset.ts deleted file mode 100644 index ac1a49c3c4..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/DragAndDrop/DndAsset.ts +++ /dev/null @@ -1,188 +0,0 @@ -import React from "react"; -import { MediaItem } from "~/pages/PageEnigma/models"; -import { - addCharacter, - addCharacterAnimation, - addCharacterAudio, - addCharacterExpression, - addGlobalAudio, - addObject, - canDrop, - currPosition, - dragItem, - overTimeline, - timelineHeight, - assetModalVisibleDuringDrag, - reopenAfterDragSignal, -} from "~/pages/PageEnigma/signals"; -import { pageHeight, pageWidth } from "~/signals"; -import { addShape } from "~/pages/PageEnigma/signals/shape"; -import { AssetType } from "~/enums"; - -class DndAsset { - public dropId: string = ""; - public overElement: DOMRect | null = null; - public dropOffset = 0; - public initX = 0; - public initY = 0; - public notDropText = ""; - public isDragging: boolean = false; - public dragThreshold: number = 5; - - constructor() { - this.onPointerMove = this.onPointerMove.bind(this); - this.onPointerUp = this.onPointerUp.bind(this); - } - - onPointerDown(event: React.PointerEvent, item: MediaItem) { - if (event.button === 0) { - dragItem.value = item; - currPosition.value = { - currX: event.pageX, - currY: event.pageY, - }; - this.initX = event.pageX; - this.initY = event.pageY; - this.isDragging = false; - canDrop.value = false; - this.notDropText = ""; - assetModalVisibleDuringDrag.value = false; - window.addEventListener("pointerup", this.onPointerUp); - window.addEventListener("pointermove", this.onPointerMove); - } - } - - endDrag() { - if (dragItem.value) { - dragItem.value = null; - canDrop.value = false; - this.overElement = null; - overTimeline.value = false; - this.notDropText = ""; - assetModalVisibleDuringDrag.value = reopenAfterDragSignal.value; - } - } - - overCanvas(positionX: number, positionY: number) { - if (positionY < 69) { - return false; - } - if (positionY > pageHeight.value) { - return false; - } - return positionX <= pageWidth.value; - } - - onPointerUp(event: PointerEvent) { - window.removeEventListener("pointerup", this.onPointerUp); - window.removeEventListener("pointermove", this.onPointerMove); - - if (!this.isDragging) { - // It's a click, not a drag - assetModalVisibleDuringDrag.value = true; - dragItem.value = null; - currPosition.value = { currX: 0, currY: 0 }; - return; - } - - if (dragItem.value) { - const positionX = event.pageX; - const positionY = event.pageY; - if (this.overCanvas(positionX, positionY)) { - const mediaItem = dragItem.value; - if (mediaItem.type === AssetType.CHARACTER) { - addCharacter(dragItem.value); - } - // if (dragItem.value.type === AssetType.CAMERA) { - // console.log("Dragged In Camera Type") - // } - /* - FIXME: - THIS IS A TEMPARARY SOLUTION TO A LONG PROBLEM WITH SKYBOXES - UPDATE THIS WHEN UPLOADING SKYBOXES ARE FULLY IMPLEMENTED. - THIS IS JUST TEMPARARY!!! - */ - if (mediaItem.type === AssetType.OBJECT || mediaItem.type === AssetType.SPLAT || mediaItem.type === AssetType.SKYBOX) { - addObject(mediaItem); - } - - if (mediaItem.type === AssetType.SHAPE) { - addShape(mediaItem); - } - - this.endDrag(); - return; - } - } - - if (canDrop.value && dragItem.value) { - if (dragItem.value.type === AssetType.ANIMATION) { - addCharacterAnimation({ - dragItem: dragItem.value, - characterId: this.dropId, - offset: this.dropOffset, - }); - } - if (dragItem.value.type === AssetType.EXPRESSION) { - addCharacterExpression({ - dragItem: dragItem.value, - characterId: this.dropId, - offset: this.dropOffset, - }); - } - if (dragItem.value.type === AssetType.AUDIO) { - addCharacterAudio({ - dragItem: dragItem.value, - characterId: this.dropId, - offset: this.dropOffset, - }); - addGlobalAudio({ - dragItem: dragItem.value, - audioId: this.dropId, - offset: this.dropOffset, - }); - } - } - this.endDrag(); - } - - onPointerMove(event: MouseEvent) { - if (dragItem.value) { - event.stopPropagation(); - event.preventDefault(); - const deltaX = event.pageX - this.initX; - const deltaY = event.pageY - this.initY; - if ( - Math.abs(deltaX) > this.dragThreshold || - Math.abs(deltaY) > this.dragThreshold - ) { - this.isDragging = true; - } - currPosition.value = { - currX: this.initX + deltaX, - currY: this.initY + deltaY, - }; - overTimeline.value = - event.pageY > pageHeight.value - timelineHeight.value; - if (this.overElement) { - const pos = this.overElement; - const eventY = event.pageY; - const inHeight = eventY >= pos.top && eventY <= pos.top + pos.height; - const eventX = event.pageX; - const inWidth = eventX >= pos.left && eventX <= pos.left + pos.width; - - if (inHeight && inWidth) { - return; - } - canDrop.value = false; - this.dropId = ""; - this.overElement = null; - this.notDropText = ""; - } - } - } -} - -const dragAndDrop = new DndAsset(); - -export default dragAndDrop; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/APIManagerResponseError.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/APIManagerResponseError.ts deleted file mode 100644 index 5915e4c77f..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/APIManagerResponseError.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * This is designed to surface user customer facing messages as errors. - * Errors shouldn't be 404 or something confusing should be - */ -class APIManagerResponseError extends Error { - constructor(message?: string) { - super(message); - this.name = "APIManagerResponseError"; - } -} - -export { APIManagerResponseError }; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/Commands.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/Commands.ts deleted file mode 100644 index 50bbe60715..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/Commands.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { SceneManager } from "./scene_manager_api"; -import * as THREE from "three"; - -export interface ICommand { - undo(): Promise; - redo(): Promise; -} - -export interface TransformData { - object_uuid: string; - position: THREE.Vector3; - rotation: THREE.Euler; - scale: THREE.Vector3; -} - -export interface UserData { - object_uuid: string; - color: string; - visible: boolean; - locked: boolean; -} - -export interface CreationData { - object_uuid: string; - name: string; - position: THREE.Vector3; - userdata: Record; -} - -export interface DeletionData { - object_uuid: string; - name: string; - position: THREE.Vector3; - userdata: Record; -} - -export interface SceneState { - scene_objects: string[]; - scene_names: string[]; - scene_userdata: string[]; - scene_positions: string[]; - scene_rotations: string[]; - scene_scales: string[]; -} - -export type CommandInputTypes = TransformData | CreationData | DeletionData; - -export class CreationSceneItem implements ICommand { - private component: SceneManager; - private creationData: CreationData; - - public constructor(sceneManager: SceneManager, creationData: CreationData) { - this.component = sceneManager; - this.creationData = creationData; - } - - public async undo(): Promise { - this.component.delete(this.creationData.object_uuid); - } - - public async redo(): Promise { - const object = await this.component.create( - this.creationData.userdata["media_id"], - this.creationData.name, - this.creationData.position, - ); - object.position.copy(this.creationData.position); - object.uuid = this.creationData.object_uuid; - if (this.creationData.userdata["visible"] !== undefined) { - object.visible = this.creationData.userdata["visible"]; - } - this.component.scene.setColor( - object.uuid, - this.creationData.userdata["color"], - ); - } -} - -export class DeletionSceneItem implements ICommand { - private component: SceneManager; - private deletionData: DeletionData; - - public constructor(sceneManager: SceneManager, creationData: DeletionData) { - this.component = sceneManager; - this.deletionData = creationData; - } - - public async undo(): Promise { - const object = await this.component.create( - this.deletionData.userdata["media_id"], - this.deletionData.name, - this.deletionData.position, - ); - object.uuid = this.deletionData.object_uuid; - object.visible = this.deletionData.userdata["visible"]; - this.component.scene.setColor( - object.uuid, - this.deletionData.userdata["color"], - ); - } - - public async redo(): Promise { - this.component.delete(this.deletionData.object_uuid); - } -} - -export class TransformSceneItem implements ICommand { - private component: SceneManager; - private transformDataStart: TransformData; - private transformDataEnd: TransformData; - - public constructor( - sceneManager: SceneManager, - transformDataStart: TransformData, - transformDataEnd: TransformData, - ) { - this.component = sceneManager; - this.transformDataStart = transformDataStart; - this.transformDataEnd = transformDataEnd; - } - - public async undo(): Promise { - this.component.update( - this.transformDataStart.object_uuid, - this.transformDataStart.position, - this.transformDataStart.rotation, - this.transformDataStart.scale, - ); - } - - public async redo(): Promise { - this.component.update( - this.transformDataEnd.object_uuid, - this.transformDataEnd.position, - this.transformDataEnd.rotation, - this.transformDataEnd.scale, - ); - } -} - -export class UserDataSceneItem implements ICommand { - private component: SceneManager; - private startUserData: UserData; - private endUserData: UserData; - - public constructor( - sceneManager: SceneManager, - startUserData: UserData, - endUserData: UserData, - ) { - this.component = sceneManager; - this.startUserData = startUserData; - this.endUserData = endUserData; - } - - public async undo(): Promise { - this.component.scene.setColor( - this.startUserData.object_uuid, - this.startUserData.color, - ); - } - - public async redo(): Promise { - this.component.scene.setColor( - this.endUserData.object_uuid, - this.endUserData.color, - ); - } -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/EditorState/EditorStateJSON/timeline_state_json.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/EditorState/EditorStateJSON/timeline_state_json.ts deleted file mode 100644 index cebd5cd322..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/EditorState/EditorStateJSON/timeline_state_json.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface TimelineStateJson { - version: number; - timelineItemsJson: string; - transformJson: string; - animationJson: string; - audioJson: string; - lipsyncJson: string; - emotionJson: string; -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/EditorState/TimelineState.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/EditorState/TimelineState.ts deleted file mode 100644 index e61bcd6540..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/EditorState/TimelineState.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { deepCopySerializableObjects as deepCopy } from "~/utilities"; -import { ClipUI } from "../../clips/clip_ui"; -import AudioEngine from "../Engines/audio_engine"; -import EmotionEngine from "../Engines/emotion_engine"; -import LipSyncEngine from "../Engines/lip_sync_engine"; -import { TimeLine } from "../timeline"; -import TransformEngine from "../Engines/transform_engine"; -import { TimelineStateJson } from "./EditorStateJSON/timeline_state_json"; -import { CharacterAnimationEngine } from "../Engines/CharacterAnimationEngine"; - - -export class TimelineState { - version: number; - timeline_items: ClipUI[] | undefined; - transform_engine: TransformEngine | undefined; - animation_engine: CharacterAnimationEngine | undefined; - audio_engine: AudioEngine | undefined; - lipsync_engine: LipSyncEngine | undefined; - emotion_engine: EmotionEngine | undefined; - - constructor({ editorVersion }: { editorVersion: number; }) { - this.version = editorVersion; - } - - public async initializeFromTimeline(timeline: TimeLine): Promise { - return new Promise((resolve, reject) => { - try { - this.timeline_items = deepCopy(timeline.timeline_items); - this.transform_engine = deepCopy(timeline.transform_engine); - this.animation_engine = deepCopy(timeline.animation_engine); - this.audio_engine = deepCopy(timeline.audio_engine); - this.lipsync_engine = deepCopy(timeline.lipSync_engine); - this.emotion_engine = deepCopy(timeline.emotion_engine); - resolve(); - } catch (e) { - reject(e); - } - }); - } - public async initializeFromTimelineStateJson( - jsonObject: TimelineStateJson - ): Promise { - return new Promise((resolve, reject) => { - try { - console.log(jsonObject); - //TODO: parse json object back into each engine object - resolve(); - } catch (e) { - reject(e); - } - }); - } - public async toJSON() { - const result: TimelineStateJson = { - version: this.version, - timelineItemsJson: JSON.stringify(this.timeline_items), - transformJson: JSON.stringify(this.transform_engine), - animationJson: JSON.stringify(this.animation_engine), - audioJson: JSON.stringify(this.audio_engine), - lipsyncJson: JSON.stringify(this.lipsync_engine), - emotionJson: JSON.stringify(this.emotion_engine), - }; - return result; - } -} - diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/Engines/CharacterAnimationEngine.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/Engines/CharacterAnimationEngine.ts deleted file mode 100644 index 83015964ad..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/Engines/CharacterAnimationEngine.ts +++ /dev/null @@ -1,308 +0,0 @@ -import * as THREE from "three"; -import { Retarget } from "../retargeting"; -import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader.js"; -import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; -import { get_media_url } from "~/Classes/ApiHelpers"; -import { MMDLoader } from "three/examples/jsm/loaders/MMDLoader.js"; -import { ClipUI } from "../../clips/clip_ui"; -import Ijson from "~/interfaces/Ijson"; - -export const START_FRAME_LABEL = "CharacterStartFrameLabel"; - -export class CharacterAnimationEngine implements Ijson { - version: number; - characterAnimations: Map, ClipUI[]> = new Map(); - characterMixers = new Map, THREE.AnimationMixer>(); - - constructor(version: number) { - this.version = version; - } - - toJSON(): unknown { - // Return just an array of character object IDs - const characterIds = Array.from(this.characterAnimations.keys()).map((character) => character.uuid); - return { characters: characterIds }; - } - - /** - * A single character model is capable of tracking multiple animations, in its own track - * These tracks can be blended with weights or additively - * The weight change can be done via custom interpolation logic - * - * @param objectUUID The UUID of the object. - * @param clip The 3JS animation clip - * @param clipUI The clip info - */ - addCharacterAnimation(character: THREE.Object3D, clip: THREE.AnimationClip, clipUI: ClipUI) { - // If character doesn't exist, create a clip object - // If it does exist, add a new animation to the existing character by extracting it - // TODO: FIXME: Duplicate prevention? - clip.name = clipUI.media_id; - character.animations.push(clip); - console.log("Adding animation track:") - console.log(clip); - - if (!this.characterAnimations.has(character)) { - this.characterAnimations.set(character, []); - this.#ensureMixerExists(character); - } - - // Add the clip info to the character map and sort the clips - const characterClips = this.characterAnimations.get(character)!; - characterClips.push(clipUI); - characterClips.sort((a, b) => a.offset - b.offset); - } - - /** - * @param objectUUID The UUID of the object. - * @param mediaId The media ID of the animation. - * @param clipUI The clip info - */ - async addCharacterAnimationMedia(character: THREE.Object3D, mediaId: string, clipUI: ClipUI) { - // If character doesn't exist, create a clip object - // If it does exist, add a new animation to the existing character by extracting it - // TODO: FIXME: Duplicate prevention? - const animationTrack = await this.#load_animation(character, mediaId); - this.addCharacterAnimation(character, animationTrack, clipUI); - } - - #ensureMixerExists(character: THREE.Object3D) { - if (!this.characterMixers.has(character)) { - this.characterMixers.set(character, new THREE.AnimationMixer(character)); - } - } - - getMixer(character: THREE.Object3D) { - this.#ensureMixerExists(character); - return this.characterMixers.get(character)!; - } - - #interpolateClips(character: THREE.Object3D, timestamp: number, maxTime: number) { - const clips = this.characterAnimations.get(character); - - const mixer = this.getMixer(character); - - // Find the two clips we're interpolating between - let prevClip: ClipUI | null = null; - let nextClip: ClipUI | null = null; - const firstClip = clips ? clips[0] : null; - const lastClip = clips ? clips[clips.length - 1] : null; - - // If we're before the first clip, interpolate it in from base pose - // TODO: Replace with starting frame - if (firstClip && firstClip.offset > timestamp) { - prevClip = null; - nextClip = firstClip; - } else if (lastClip && (lastClip.offset + lastClip.length) < timestamp) { // After last clip, interpolate it out - prevClip = lastClip; - nextClip = null; - } else if (clips) { - let prevClipIndex = 0; - - // Loop until the next clip is after the timestamp - while (prevClipIndex < clips.length - 1 && clips[prevClipIndex + 1].offset < timestamp) { - prevClipIndex++; - } - - prevClip = clips[prevClipIndex]; - nextClip = clips[prevClipIndex + 1]; - } - - // If the clips exist, fetch their actions. If not, evaluation is either at the start or the end. - // If evalution is on either sides, check the start/end frame tracks - // Otherwise just mark it null - const prevAnimationTrack = prevClip ? - this.getCharacterAnimationTrack(character, prevClip.media_id) : - this.getCharacterAnimationTrack(character, START_FRAME_LABEL); - - // TODO: Replace with ending frame - const nextAnimationTrack = nextClip ? this.getCharacterAnimationTrack(character, nextClip.media_id) : null; - - const prevAction = prevAnimationTrack ? mixer.clipAction(prevAnimationTrack) : null; - const nextAction = nextAnimationTrack ? mixer.clipAction(nextAnimationTrack) : null; - - // Calculate the progress of timestamp from end of prev action to start of next action - const left = (prevClip?.offset ?? 0) + (prevClip?.length ?? 0); - const right = nextClip?.offset ?? maxTime; - - // Simple Linear interpolation for now - // TODO: Take an interpolation dependency, or better yet, write a transition engine - const progress = (timestamp - left) / (right - left); - prevAction?.setEffectiveWeight(1 - progress); - nextAction?.setEffectiveWeight(progress); - - // Make sure we hold that last frame for the previous action - if (prevAction) { - prevAction.clampWhenFinished = true; - } - - // The next action should stay at the first frame - if (nextAction) { - nextAction.paused = true; - } - - // Necessary to ensure the actions are active - the default is inactive, mixer won't do anything - prevAction?.play(); - nextAction?.play(); - - // The clip time would still be relative to the previous clip - const clipTime = timestamp - (prevClip?.offset ?? 0); - mixer.setTime(clipTime / 1000); - } - - evaluateCharacter(character: THREE.Object3D, timestamp: number, maxTime: number) { - const mixer = this.getMixer(character); - const clips = this.characterAnimations.get(character); - - // Find the clip we're in right now - const currentClip = clips?.find((clip) => { - return clip.offset <= timestamp && clip.offset + clip.length >= timestamp; - }); - - // If timestamp not in any clip, do nothing. - // If timestamp in clip, set mixer to the timestamp inside the clip - if (!currentClip) { - // Let the interpolation function handle this actions - this.#interpolateClips(character, timestamp, maxTime) - return; - } - - const clipTime = timestamp - currentClip.offset; - const animationTrack = this.getCharacterAnimationTrack(character, currentClip.media_id); - - // NOTE: This shouldn't really happen unless the UI was desynced at some point from the engine - if (!animationTrack) { - return; - } - - // Play only this action - const animationAction = mixer.clipAction(animationTrack); - - // Since it's the only clip in this timestamp, make it full weight and make sure it's not paused (from interpolation or otherwise) - animationAction.setEffectiveWeight(1); - animationAction.paused = false; - - // Necessary to ensure the actions are active - the default is inactive, mixer won't do anything - animationAction.play(); - - mixer.setTime(clipTime / 1000); - } - - getCharacterAnimationTrack(character: THREE.Object3D, mediaId: string) { - return character.animations.find((clip) => clip.name === mediaId); - } - - /** Evaluate all character animations at a given timestamp (milliseconds) */ - evaluate(timestamp: number, maxTime: number) { - this.characterMixers.forEach((_, character) => { - this.evaluateCharacter(character, timestamp, maxTime); - }) - } - - stopCharacter(character: THREE.Object3D) { - const mixer = this.characterMixers.get(character); - console.debug("Stopping all actions for character", character); - console.debug(mixer); - mixer?.stopAllAction(); - console.debug(mixer); - } - - stop() { - this.characterMixers.forEach((mixer) => { - mixer.stopAllAction(); - }); - } - - async #load_animation(character: THREE.Object3D, mediaId: string): Promise { - // Get the file URL and extract the (first) animation track from it - // TODO: Support for multiple animations in a single file? - const url = await get_media_url(mediaId); - - return await new Promise((resolve) => { - if (url.includes(".glb")) { - const glbLoader = new GLTFLoader(); - - glbLoader.load(url, (gltf) => { - const animationClip = gltf.animations[0]; - resolve(animationClip); - }); - } else if (url.includes(".fbx")) { - const fbxLoader = new FBXLoader(); - fbxLoader.load(url, (fbx) => { - const animationClip_1 = fbx.animations[0]; - animationClip_1.tracks.forEach((track) => { - const retarget = new Retarget(); - const retarget_value = retarget.retarget(track.name); - track.name = retarget_value.bone; - console.log(track); - if (retarget_value.is_special) { - // TODO: Revisit special properties later - // this.special_properties.push(retarget_value); - } - }); - resolve(animationClip_1); - }); - } else if (url.includes(".vmd")) { - const mmdLoader = new MMDLoader(); - mmdLoader.loadAnimation(url, character as THREE.SkinnedMesh, (mmd) => { - mmd.name = url; - resolve(mmd as THREE.AnimationClip); - }); - } - }); - } - - removeCharacter(character: THREE.Object3D) { - console.debug("Removing character from animation engine", character); - this.characterAnimations.delete(character); - this.characterMixers.delete(character); - } - - removeAnimation(character: THREE.Object3D, clip: ClipUI) { - console.debug("Removing animation from character", character, clip); - const clips = this.characterAnimations.get(character)!; - - const clipIndex = clips.findIndex((c) => c.clip_uuid === clip.clip_uuid); - if (clipIndex < 0) { - return; - } - - // Remove the clip from character's tracked clips - clips.splice(clipIndex, 1); - - // Stop all character animations to cleanly reset - this.stopCharacter(character); - } - - clearStartFrame(character: THREE.Object3D, resetTime?: number, maxTime = 1) { - // Stop all character animations - this.stopCharacter(character); - - // Check the animations and remove any labelled START_FRAME_LABEL - character.animations = character.animations.filter((clip) => clip.name !== START_FRAME_LABEL); - - // If resetTime is passed, evaluate the character to that time - if (resetTime) { - this.evaluateCharacter(character, resetTime, maxTime); - } - } - - /** resetTime: The time to reset the character to. This should ideally be the current timeline time. */ - async createStartFrameAnimation(character: THREE.Object3D, poseHelper: any, url: string, resetTime: number = 0, maxTime: number = 1) { - //// Clear any existing start frame - //// This will also reset the animation to default pose - //this.clearStartFrame(character); - - //// We must create the track data now after resetting to default - //const poseData = await poseHelper.extractPoseData(url); - //const tracks = poseHelper.inflatePoseDataToTracks(character, poseData); - - //// Create the animation track from the rotation values and hip position - //// then add it to the character - //const startFrameClip = new THREE.AnimationClip(START_FRAME_LABEL, 0, tracks); - //character.animations.push(startFrameClip); - - //// Reset the character evaluation to what was passed in - //this.evaluateCharacter(character, resetTime, maxTime); - } -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/Engines/Mappers/MixamoPoseMapper.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/Engines/Mappers/MixamoPoseMapper.ts deleted file mode 100644 index e1749f6cd3..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/Engines/Mappers/MixamoPoseMapper.ts +++ /dev/null @@ -1,48 +0,0 @@ -// A mapping between the model bones names and the Mediapipe output index -export const MixamoPoseMap: Record = { - "mixamorigLeftArm": 11, - "mixamorigRightArm": 12, - "mixamorigLeftForeArm": 13, - "mixamorigRightForeArm": 14, - "mixamorigLeftHand": 15, - "mixamorigRightHand": 16, - "mixamorigLeftUpLeg": 23, - "mixamorigRightUpLeg": 24, - "mixamorigLeftLeg": 25, - "mixamorigRightLeg": 26, - "mixamorigLeftFoot": 27, - "mixamorigRightFoot": 28, - "mixamorigLeftHandThumb2": 21, - "mixamorigRightHandThumb2": 22, -} - -export const MixamoInterpolationBoneNames = { - Spine: "mixamorigSpine", - Spine1: "mixamorigSpine1", - Spine2: "mixamorigSpine2", - Neck: "mixamorigNeck", - LeftShoulderBlade: "mixamorigLeftShoulder", - DefLeftUpperArm: "DEF-upper_armL001", - DefLeftForeArm: "DEF-forearmL001", - RightShoulderBlade: "mixamorigRightShoulder", - DefRightUpperArm: "DEF-upper_armR001", - DefRightForeArm: "DEF-forearmR001", - DefLeftThigh: "DEF-thighL001", - DefRightThigh: "DEF-thighR001", -} - -export const PoseMap = { - NOSE: 0, - LEFT_SHOULDER: 11, - RIGHT_SHOULDER: 12, - LEFT_ELBOW: 13, - RIGHT_ELBOW: 14, - LEFT_WRIST: 15, - RIGHT_WRIST: 16, - LEFT_HIP: 23, - RIGHT_HIP: 24, - LEFT_KNEE: 25, - RIGHT_KNEE: 26, - LEFT_ANKLE: 27, - RIGHT_ANKLE: 28, -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/Engines/animation_engine.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/Engines/animation_engine.ts deleted file mode 100644 index f735250d8e..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/Engines/animation_engine.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { AnimationClip } from "../../clips/animation_clip"; - -export class AnimationEngine { - clips: { [key: string]: AnimationClip } = {}; - version: number; - - constructor(version: number) { - this.clips = {}; - this.version = version; - } - - load_object(object_uuid: string, media_id: string, clip_name: string) { - console.debug("load_object", object_uuid, media_id, clip_name); - this.clips[object_uuid + media_id] = new AnimationClip( - this.version, - media_id, - "glb", - object_uuid, - 1.0, - 1.0, - clip_name, - 0.0, - 0.0, - ); - //this.clips[object_uuid + media_id]._load_animation(); - } -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/Engines/audio_engine.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/Engines/audio_engine.ts deleted file mode 100644 index 0aecc1ebbd..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/Engines/audio_engine.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { AudioClip } from "../../clips/audio_clip"; - -class AudioEngine { - clips: { [key: string]: AudioClip } = {}; - audio_sources: { [key: string]: AudioBufferSourceNode } = {}; - version: number; - playing: string[]; - last_frame: number; - - constructor() { - this.clips = {}; - this.playing = []; - this.audio_sources = {}; - this.version = 1.0; - this.last_frame = 0; - } - - // loads clips into the engine to cache - loadClip(audio_media_id: string) { - if (this.clips[audio_media_id] != null) { - return; - } - this.clips[audio_media_id] = new AudioClip( - this.version, - audio_media_id, - 1.0, - ); - } - - // plays from the timeline. - playClip(audio_media_id: string) { - if (this.clips[audio_media_id] == null) { - this.loadClip(audio_media_id); - } - } - - async step(audio_media_id: string, frame: number, offset: number) { - const frameBuffer = 12; - - const pos = frame - offset; - - let doPlay = true; - if ( - Math.abs(this.last_frame - frame) < frameBuffer - 1 && - this.last_frame !== 0 - ) { - doPlay = false; - } - if (pos <= 1) { - doPlay = true; - } - - const clip = this.clips[audio_media_id]; - - if (doPlay && clip.audio_data?.audioContext) { - const startTime = pos / 60.0; - const endTime = frameBuffer / 60.0; - clip.audio_data.source = - clip.audio_data.audioContext.createBufferSource(); - clip.audio_data.source.buffer = clip.audio_data.audioBuffer; - clip.audio_data.source.connect(clip.audio_data.audioContext.destination); - clip.audio_data.source.start(0, startTime, endTime); - this.last_frame = frame; - } - } - - // stops the clips - stopClip(audio_media_id: string) { - const clip = this.clips[audio_media_id]; - if (clip.audio_data?.source) { - clip.audio_data.source.stop(); - clip.audio_data.source.disconnect(); - } else { - console.warn( - `AudioManager: AudioClip with id "${audio_media_id}" not found.`, - ); - } - } -} - -export default AudioEngine; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/Engines/emotion_engine.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/Engines/emotion_engine.ts deleted file mode 100644 index afef730f69..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/Engines/emotion_engine.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { EmotionClip } from "../../clips/emotion_clip"; - -class EmotionEngine { - clips: { [key: string]: EmotionClip } = {}; - version: number; - - constructor(version: number) { - this.clips = {}; - this.version = version; - } - - // loads clips into the engine to cache - loadClip(object_uuid: string, csv_media_id: string) { - if (this.clips[object_uuid + csv_media_id] != null) { - return; - } - this.clips[object_uuid + csv_media_id] = new EmotionClip( - this.version, - csv_media_id, - ); - } -} - -export default EmotionEngine; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/Engines/lip_sync_engine.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/Engines/lip_sync_engine.ts deleted file mode 100644 index 8f43d6f951..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/Engines/lip_sync_engine.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { LipSyncClip } from "../../clips/lipsync_clip"; - -// needs to be apart of the editor window. -export default class LipSyncEngine { - clips: { [key: string]: LipSyncClip } = {}; - audio_sources: { [key: string]: AudioBufferSourceNode } = {}; - version: number; - - constructor() { - this.clips = {}; - this.audio_sources = {}; - this.version = 1.0; - } - - load_object(object_uuid: string, audio_media_id: string) { - this.clips[object_uuid + audio_media_id] = new LipSyncClip( - this.version, - audio_media_id, - 1.0, - ); - } -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/Engines/transform_engine.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/Engines/transform_engine.ts deleted file mode 100644 index 160b38d17a..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/Engines/transform_engine.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { TransformClip } from "../../clips/transform_clip"; -import * as THREE from "three"; - -class TransformEngine { - clips: { [key: string]: TransformClip } = {}; - version: number; - - constructor(version: number) { - this.version = version; - this.clips = {}; - } - - loadObject(object_uuid: string, clip_length: number = 2) { - this.clips[object_uuid] = new TransformClip( - this.version, - object_uuid, - clip_length, - object_uuid, - ); // replace the last object_uuid with the media ID when its ready. - return this.clips[object_uuid]; - } - - addFrame( - object_uuid: string, - clip_length: number = 2, - position: THREE.Vector3, - rotation: THREE.Vector3, - scale: THREE.Vector3, - offset: number, - keyframe_uuid: string, - ): boolean { - if (this.clips[object_uuid] == null) { - this.clips[object_uuid] = new TransformClip( - this.version, - object_uuid, - clip_length, - ); - this.clips[object_uuid].add_frame( - position, - rotation, - scale, - offset, - keyframe_uuid, - ); - return false; - } - this.clips[object_uuid].add_frame( - position, - rotation, - scale, - offset, - keyframe_uuid, - ); - return true; - } -} - -export default TransformEngine; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/SceneMetadata.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/SceneMetadata.ts deleted file mode 100644 index b6815a41c3..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/SceneMetadata.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { getArtStyle } from "~/enums/ArtStyle"; -import { - cameraAspectRatio, - cinematic, - enginePreProcessing, - faceDetail, - globalIPAMediaToken, - lipSync, - styleStrength, - upscale, -} from "~/pages/PageEnigma/signals"; -import { SceneGenereationMetaData as SceneGenerationMetaData } from "~/pages/PageEnigma/models/sceneGenerationMetadata"; -import Editor from "./editor"; - -export const getSceneGenerationMetaData = ( - editorEngine: Editor, -): SceneGenerationMetaData => { - // when this is called, editor engine is guarunteed by it's caller - return { - positivePrompt: editorEngine.positive_prompt, - negativePrompt: editorEngine.negative_prompt, - artisticStyle: getArtStyle(editorEngine.art_style.toString()), - cameraAspectRatio: cameraAspectRatio.value, - globalIPAMediaToken: globalIPAMediaToken.value || undefined, - upscale: upscale.value, - faceDetail: faceDetail.value, - styleStrength: styleStrength.value, - lipSync: lipSync.value, - cinematic: cinematic.value, - enginePreProcessing: enginePreProcessing.value, - }; -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/api_manager.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/api_manager.ts deleted file mode 100644 index 2619856df2..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/api_manager.ts +++ /dev/null @@ -1,460 +0,0 @@ -import { v4 as uuidv4 } from "uuid"; -import * as THREE from "three"; -import { GLTFExporter } from "three/addons/exporters/GLTFExporter.js"; -import { signalScene, startPollingActiveJobs } from "~/signals"; -import { updateExistingScene, uploadNewScene } from "./api_fetchers"; -import environmentVariables from "~/Classes/EnvironmentVariables"; -import { GetCdnOrigin } from "~/api/GetCdnOrigin"; -import { StorytellerApiHostStore } from "@storyteller/api"; - -interface StylizeVideoParams { - media_token: string; - style: ArtStyle; - positive_prompt: string; - negative_prompt: string; - visibility: Visibility; - use_face_detailer?: boolean; - use_upscaler?: boolean; - use_strength?: number; - use_lipsync?: boolean; - use_cinematic?: boolean; - use_global_ipa_media_token?: string | null; - input_depth_file?: string | null; - input_normal_file?: string | null; - input_outline_file?: string | null; -} - -/** - * Storyteller Studio API Manager - * The source of truth of all these media items is the database in the cloud - */ -export class MediaFile { - public_bucket_path: string; - media_type: string; - media_token: string; - - constructor( - public_bucket_path: string, - media_type: string, - media_token: string, - ) { - this.media_token = media_token; - this.public_bucket_path = public_bucket_path; - this.media_type = media_type; - } -} - -export enum ArtStyle { - Anime2_5D = "anime_2_5d", - Anime2DFlat = "anime_2d_flat", - Cartoon3D = "cartoon_3d", - ComicBook = "comic_book", - AnimeGhibli = "anime_ghibli", - InkPunk = "ink_punk", - InkSplash = "ink_splash", - InkBWStyle = "ink_bw_style", - JojoStyle = "jojo_style", - PaperOrigami = "paper_origami", - PixelArt = "pixel_art", - PopArt = "pop_art", - Realistic1 = "realistic_1", - Realistic2 = "realistic_2", - AnimeRetroNeon = "anime_retro_neon", - AnimeStandard = "anime_standard", - HRGiger = "hr_giger", - Simpsons = "simpsons", - Carnage = "carnage", - PastelCuteAnime = "pastel_cute_anime", - Bloom = "bloom_lighting", - Horror2_5D = "25d_horror", - Creepy = "creepy", - CreepyVHS = "creepy_vhs", - TrailCamFootage = "trail_cam_footage", - OldBWMovie = "old_black_white_movie", - HorrorNoirBW = "horror_noir_black_white", - TechnoNoirBW = "techno_noir_black_white", - BW20s = "black_white_20s", - AnimeCyberpunk = "cyberpunk_anime", - Dragonball = "dragonball", - RealisticMatrix = "realistic_matrix", - RealisticCyberpunk = "realistic_cyberpunk", -} - -export enum Visibility { - Public = "public", - Hidden = "hidden", - Private = "private", -} - -/** - * This is designed to surface user customer facing messages as errors. - */ -// type Data = { [key: string]: any }; -// class APIManagerResponseSuccess { -// public user_message: string; -// public data: Data | null; -// constructor(user_message: string = "", data: Data | null = null) { -// this.data = data; -// this.user_message = user_message; -// } -// } - -/** - * This is designed to surface user customer facing messages as errors. - * Errors shouldn't be 404 or something confusing should be - */ -class APIManagerResponseError extends Error { - constructor(message?: string) { - super(message); - this.name = "APIManagerResponseError"; - } -} - -export class APIManager { - - constructor() { - // NB(bt,2025-07-06): Cleaning this up. The code was duplicated a half dozen times. - // I think all of the code in this file is legacy, but to be safe I'm continuing to update it. - } - - protected getApiSchemeAndHost() { - return StorytellerApiHostStore.getInstance().getApiSchemeAndHost(); - } - - /** - * @param scene The 3JS Scene we want to save. - * @param scene_name The Scene name we want to display - * @param scene_media_file_token If null we will we will create a new save or copy the scene, if provided we will overwrite the scene. - * @returns APIManagerResponseMessage - */ - public async saveSceneState({ - saveJson, - sceneTitle, - sceneToken, - sceneThumbnail, - }: { - saveJson: string; - sceneTitle: string; - sceneToken?: string; - sceneThumbnail: Blob | undefined; - }): Promise { - const file = new File([saveJson], `${sceneTitle}.glb`, { - type: "application/json", - }); - - const uploadSceneResponse = sceneToken - ? await updateExistingScene(file, sceneToken) - : await uploadNewScene(file, sceneTitle); - - // failed so catch this. - if (uploadSceneResponse["success"] == false) { - return ""; - } - - if (sceneThumbnail) { - const image_resp = await this.uploadMediaSceneThumbnail( - sceneThumbnail, - "render.png", - ); - - if (image_resp["success"] == false) { - return ""; - } - - if (image_resp["media_file_token"]) { - const image_token = image_resp["media_file_token"]; - const endpoint = `${this.getApiSchemeAndHost()}/v1/media_files/cover_image/${uploadSceneResponse["media_file_token"]}`; - - await fetch(endpoint, { - method: "POST", - credentials: "include", - headers: { - Accept: "application/json", - "Content-Type": "application/json", - }, - body: JSON.stringify({ cover_image_media_file_token: image_token }), - }); - } - } - - console.log(uploadSceneResponse); - return uploadSceneResponse["media_file_token"]; - } - - public async loadSceneState( - scene_media_file_token: string | null, - ): Promise { - const url = `${this.getApiSchemeAndHost()}/v1/media_files/file/${scene_media_file_token}`; - const response = await fetch(url); - if (response.status > 200) { - throw new APIManagerResponseError("Failed to load scene"); - } - - const json = await response.json(); - if (json && json.media_file) { - if (json.media_file.maybe_title === null) { - console.warn(`Scene /w Token: ${scene_media_file_token} has no title`); - } - signalScene({ - title: json.media_file.maybe_title || "Untitled Scene", - token: scene_media_file_token || undefined, - ownerToken: json.media_file.maybe_creator_user.user_token, - isModified: false, - }); - } - const bucket_path = json["media_file"]["public_bucket_path"]; - const media_base_url = GetCdnOrigin(); - //const media_url = `${media_base_url}/vocodes-public${bucket_path}`; // gets you a bucket path - const media_url = `${media_base_url}${bucket_path}`; // gets you a bucket path - - const file_response = await fetch(media_url); - - if (!file_response.ok) { - throw new APIManagerResponseError("Failed to download file"); - } - // Convert the response from a blob to json text - const blob = await file_response.blob(); - const json_result: string = await new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onloadend = () => resolve(JSON.parse(reader.result as string)); - reader.onerror = reject; - reader.readAsText(blob); - }); - - return json_result; - } - - /** - * This recieves the bucket path of a media file - * @param media_file_token - * @returns - */ - public async getMediaFile(media_file_token: string): Promise { - const url = `${this.getApiSchemeAndHost()}/v1/media_files/file/${media_file_token}`; - const response = await fetch(url); - const json = await JSON.parse(await response.text()); - const bucketPath = json["media_file"]["public_bucket_path"]; - const media_base_url = GetCdnOrigin(); - const media_url = `${media_base_url}${bucketPath}`; // gets you a bucket path - return media_url; - } - - /** - This will save the scene to keep ids positions. - It will also give the file a name which will be a uuidv4() - @param scene The 3JS Scene we want to make a file to be uploaded as multipart form. - */ - private async gltfExport(scene: THREE.Scene): Promise { - const gltfExporter = new GLTFExporter(); - const uuid = uuidv4(); - const result = await gltfExporter.parseAsync(scene); - const file = new File([JSON.stringify(result)], `${uuid}.glb`, { - type: "application/json", - }); - return file; - } - - public async uploadMediaSceneThumbnail(blob: Blob | File, fileName: string) { - const url = `${this.getApiSchemeAndHost()}/v1/media_files/upload/image`; - const uuid = uuidv4(); - - const formData = new FormData(); - formData.append("uuid_idempotency_token", uuid); - formData.append("is_intermediate_system_file", "true"); - formData.append("maybe_title", "Screenshot"); - formData.append("file", blob, fileName); - //formData.append("source", "file"); - //formData.append("type", "video"); - //formData.append("source", "file"); - - const response = await fetch(url, { - method: "POST", - credentials: "include", - headers: { - Accept: "application/json", - }, - body: formData, - }); - if (!response.ok) { - throw new APIManagerResponseError("Upload Media Failed to send file"); - } else { - const json_data = await response.json(); - //console.log(`uploadMedia: ${JSON.stringify(json_data)}`); - return json_data; - } - } - - public async uploadMedia({ - blob, - fileName, - title, - styleName, - maybe_scene_source_media_file_token, - }: { - blob: Blob; - fileName: string; - title: string; - styleName?: string; - maybe_scene_source_media_file_token: string | undefined; - }) { - // Promise - //TODO: UPDATE ENDPOINT!!!! - const url = `${this.getApiSchemeAndHost()}/v1/media_files/upload/new_video`; - const uuid = uuidv4(); - - const formData = new FormData(); - formData.append("uuid_idempotency_token", uuid); - - formData.append("file", blob, fileName); - // formData.append("source", "file"); - // formData.append("type", "video"); - if (styleName) formData.append("maybe_style_name", styleName); - formData.append("maybe_title", title); - - // This signals to the backend to hide the video from view - formData.append("is_intermediate_system_file", "true"); - if (maybe_scene_source_media_file_token !== undefined) { - formData.append( - "maybe_scene_source_media_file_token", - maybe_scene_source_media_file_token, - ); - } - const response = await fetch(url, { - method: "POST", - credentials: "include", - headers: { - Accept: "application/json", - }, - body: formData, - }); - - if (!response.ok) { - throw new APIManagerResponseError("Upload Media Failed to send file"); - } else { - const json_data = await response.json(); - //console.log(`uploadMedia: ${JSON.stringify(json_data)}`); - return json_data; - } - } - - public async uploadMediaFrameGeneration( - blob: Blob | File, - fileName: string, - style: string = "comic_book", - positive_prompt: string, - negative_prompt: string, - ): Promise { - const url = `${environmentVariables.values.FUNNEL_API}/preview/`; - - const payload = { - style: style, - positive_prompt: positive_prompt, - negative_prompt: negative_prompt, - }; - - const formData = new FormData(); - formData.append("input_file", blob, fileName); - formData.append("request", JSON.stringify(payload)); - - const response = await fetch(url, { - method: "POST", - credentials: "include", - headers: { - Accept: "application/json", - }, - body: formData, - }); - - if (!response.ok) { - throw new APIManagerResponseError("Upload Media Failed to send file"); - } else { - return URL.createObjectURL(await response.blob()); - } - } - - public async getMediaBatch(media_tokens: string[]): Promise { - const tokens = media_tokens; - const url = new URL(`${this.getApiSchemeAndHost()}/v1/media_files/batch`); - tokens.forEach((token) => url.searchParams.append("tokens", token)); - const result = await fetch(url) - .then((response) => response.json()) - .then((data) => { - const result = data["media_files"].map((element: any) => { - return new MediaFile( - element["public_bucket_path"], - element["media_type"], - element["token"], - ); - }); - return result; - }) - .catch((error) => console.error("Error:", error)); - return result; - } - - public async stylizeVideo({ - media_token, - style, - positive_prompt, - negative_prompt, - visibility, - use_face_detailer = false, - use_upscaler = false, - use_strength = 1.0, - use_lipsync = false, - use_cinematic = false, - use_global_ipa_media_token = null, - input_depth_file = null, - input_normal_file = null, - input_outline_file = null, - }: StylizeVideoParams) { - const uuid = uuidv4(); - - const data = { - uuid_idempotency_token: uuid, - style: style, - input_file: media_token, - prompt: positive_prompt, - negative_prompt: negative_prompt, - trim_start_millis: 0, - trim_end_millis: 7000, - enable_lipsync: use_lipsync, - creator_set_visibility: visibility, - use_face_detailer: use_face_detailer, - use_upscaler: use_upscaler, - use_strength: use_strength, - use_lipsync: use_lipsync, - global_ipa_media_token: use_global_ipa_media_token, - use_cinematic: use_cinematic, - input_depth_file: input_depth_file, - input_normal_file: input_normal_file, - input_outline_file: input_outline_file, - }; - - const json_data = JSON.stringify(data); - - const response = await fetch( - `${this.getApiSchemeAndHost()}/v1/workflows/enqueue_studio`, - { - method: "POST", - credentials: "include", - headers: { - Accept: "application/json", - "Content-Type": "application/json", - }, - body: json_data, - }, - ); - - startPollingActiveJobs(); - - if (!response.ok) { - // Handle HTTP error responses - const errorData = await response.json(); - throw new Error(`API Error: ${response.status} ${errorData.message}`); - } - - // Assuming the response is JSON and matches the EnqueueVideoStyleTransferSuccessResponse interface - return await response.json(); - } -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/editor.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/editor.ts deleted file mode 100644 index 1594d5fe20..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/editor.ts +++ /dev/null @@ -1,1864 +0,0 @@ -import * as THREE from "three"; -import { signal } from "@preact/signals-react"; -import { OrbitControls } from "three/addons/controls/OrbitControls.js"; -import { FreeCam } from "./free_cam"; -import { TransformControls } from "./TransformControls.js"; -import Scene from "./scene.js"; -import { APIManager, ArtStyle } from "./api_manager.js"; -import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js"; -import { RenderPass } from "three/addons/postprocessing/RenderPass.js"; -import { OutlinePass } from "three/addons/postprocessing/OutlinePass.js"; -import { OutputPass } from "three/addons/postprocessing/OutputPass.js"; -import { SMAAPass } from "three/addons/postprocessing/SMAAPass.js"; -import { SAOPass } from "three/addons/postprocessing/SAOPass.js"; -import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js"; -import AudioEngine from "./Engines/audio_engine.js"; -import TransformEngine from "./Engines/transform_engine.js"; -import EmotionEngine from "./Engines/emotion_engine"; -import { TimeLine } from "./timeline.js"; -import LipSyncEngine from "./Engines/lip_sync_engine.js"; -import { PointerLockControls } from "three/addons/controls/PointerLockControls.js"; -import { EditorStates, CameraAspectRatio } from "~/pages/PageEnigma/enums"; -import { AssetType, ClipGroup } from "~/enums"; -import { XYZ } from "../datastructures/common"; -import Queue from "~/pages/PageEnigma/Queue/Queue"; -import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; -import { fromEngineActions } from "~/pages/PageEnigma/Queue/fromEngineActions"; -import { MediaItem } from "~/pages/PageEnigma/models"; -import { - editorState, - cameraAspectRatio, - gridVisibility, -} from "../signals/engine"; -import { SceneUtils } from "./helper"; -import { VideoGeneration } from "./video_generation"; -import { MouseControls } from "./keybinds_controls"; -import { SaveManager } from "./save_manager"; -import { - authentication, - loadingBarData, - loadingBarIsShowing, - signalScene, -} from "~/signals"; - -import { outlinerState, updateObjectPanel } from "../signals"; -import { transformSpace } from "../signals/selectedMode"; -import { IGenerationOptions } from "../models/generationOptions"; -import { toEngineActions } from "../Queue/toEngineActions"; -import { SceneGenereationMetaData } from "../models/sceneGenerationMetadata"; -import { MediaUploadApi } from "~/Classes/ApiManager"; -import { SceneManager } from "./scene_manager_api"; -import { CustomOutlinePass } from "./CustomOutlinePass.js"; -import FindSurfaces from "./FindSurfaces.js"; - -import Stats from "three/examples/jsm/libs/stats.module.js"; -import { CharacterAnimationEngine } from "./Engines/CharacterAnimationEngine"; -import { cameras, selectedCameraId } from "~/pages/PageEnigma/signals/camera"; -import { SparkRenderer } from "@sparkjsdev/spark"; -import { SSAOPass } from "three/examples/jsm/Addons.js"; - -export type EditorInitializeConfig = { - sceneToken: string; - editorCanvasEl: HTMLCanvasElement; - camViewCanvasEl: HTMLCanvasElement; - sceneContainerEl: HTMLDivElement; - cacheJsonString?: string; -}; - -// ONLY USED IN THIS CLASS -// Need this one to ensure the canvas is inflated and non-zero sized -const is3DPageMounted = signal(false); -export const set3DPageMounted = (isMounted: boolean) => { - is3DPageMounted.value = isMounted; -}; - -// Need this one to know when the engine has initialized (but not necessarily loaded saves) -export const is3DEditorInitialized = signal(false); -export const setIs3DEditorInitialized = (isInitialized: boolean) => { - is3DEditorInitialized.value = isInitialized; -}; - -// Need this one to know when the engine is done loading stuff into the scene for display -export const is3DSceneLoaded = signal(false); -export const setIs3DSceneLoaded = (isLoaded: boolean) => { - is3DSceneLoaded.value = isLoaded; -}; - -class Editor { - version: number; - activeScene: Scene; - camera: THREE.PerspectiveCamera | null = null; - render_camera: THREE.PerspectiveCamera | null = null; - render_camera_aspect_ratio: CameraAspectRatio = - CameraAspectRatio.HORIZONTAL_3_2; - renderer: THREE.WebGLRenderer | undefined; - sparkRenderer: SparkRenderer | null = null; - rawRenderer: THREE.WebGLRenderer | undefined; - clock: THREE.Clock | undefined; - canvReference: HTMLCanvasElement | null = null; - canvasRenderCamReference: HTMLCanvasElement | null = null; - - composer: EffectComposer | undefined; - render_composer: EffectComposer | undefined; - outlinePass: OutlinePass | undefined; - last_cam_pos: THREE.Vector3; - last_cam_rot: THREE.Euler; - ssaoPass: SSAOPass | undefined; - outputPass: OutputPass | undefined; - renderOutputPass: OutputPass | undefined; - bloomPass: UnrealBloomPass | undefined; - smaaPass: SMAAPass | undefined; - control: TransformControls | undefined; - raycaster: THREE.Raycaster | undefined; - mouse: THREE.Vector2 | undefined; - selected: THREE.Object3D | undefined; - last_selected: THREE.Object3D | undefined; - last_selected_sum: number | undefined; - transform_interaction = false; - rendering: boolean; - api_manager: APIManager; - cameraViewControls: FreeCam | undefined; - orbitControls: OrbitControls | undefined; - locked: boolean; - - render_timer: number; - fps_number: number; - cap_fps: number; - can_playback: boolean; - playback_location: number; - audio_engine: AudioEngine; - transform_engine: TransformEngine; - emotion_engine: EmotionEngine; - lipsync_engine: LipSyncEngine; - animation_engine: CharacterAnimationEngine; - timeline: TimeLine; - current_frame: number; - lockControls: PointerLockControls | undefined; - cam_obj: THREE.Object3D | undefined; - camera_last_pos: THREE.Vector3; - renderPass: RenderPass | undefined; - generating_preview: boolean; - frames: number; - lastFrameTime: number; - - camera_person_mode: boolean; - current_scene_media_token: string | null; - current_scene_glb_media_token: string | null; - - can_initialize: boolean; - switchPreviewToggle: boolean; - - // dispatchAppUiState: React.Dispatch; - // userToken: string; - // signalScene: (data: any) => void; - // getSceneSignals: () => SceneSignal; - render_width: number; - render_height: number; - containerResizeObserver: ResizeObserver | undefined; - - positive_prompt: string; - negative_prompt: string; - art_style: ArtStyle; - rawRenderPass: RenderPass | undefined; - - last_scrub: number; - recorder: MediaRecorder | undefined; - container: HTMLElement | null = null; - - selectedCanvas: boolean; - startRenderHeight: number; - startRenderWidth: number; - lastCanvasSize: number; - // Default params. - - // global names of scene entities - camera_name: string; - - utils: SceneUtils; - videoGeneration: VideoGeneration; - mouse_controls: MouseControls | undefined; - save_manager: SaveManager; - - generation_options: IGenerationOptions; - - // just a passive the image to be uploaded. we store the token and use that in snapshots. - globalIpAdapterImage: File | undefined; - - media_upload: MediaUploadApi; - - sceneManager: SceneManager | undefined; - - /////////////////////////////////////////////// - /////////////////////////////////////////////// - - public outliner_feature_flag: boolean; - public engine_preprocessing: boolean = false; // this is to do preprocessing also called render fast. - - /////////////////////////////////////////////// - /////////////////////////////////////////////// - - focused: boolean = false; - - customOutlinerPass: CustomOutlinePass | undefined; - surfaceFinder: FindSurfaces | undefined; - - // New Rendering Pipeline Engine Work - //videoAudioPreProcessor: VideoAudioPreProcessor | undefined; - - engineFrameBuffers: EngineFrameBuffers; - renderIndex: number; - // this should be set in the future to extend the lenght of the track for rendering engine - globalSetTrackLengthSeconds: number; - - // this is to prevent recording processing from happening twice there is an update loop bug at its core. - processingRecording: boolean; - - // this is to catch and ensure that caching doesn't break the app. - // this happens because we can error out during the video generation process and things will cache despite that failing. - processingHasFailed: boolean; - stats: Stats; - - // Allows us to cancel the queued render - private renderEventToken: number; - private shouldRender: boolean; - private isMounted: boolean = false; - private _isEngineDataLoaded: boolean = false; - isEngineDataLoaded() { - return this._isEngineDataLoaded; - } - - constructor() { - this.processingHasFailed = false; - console.log( - "If you see this message twice! then it rendered twice, if you see it once it's all good.", - ); - this.can_initialize = true; - this.processingRecording = false; - this.stats = new Stats(); - // TODO: REMOVE LATER WITH BETTER FIX FOR IMPORTING AMMOJS - document.body.appendChild( - Object.assign(document.createElement("script"), { - src: "jsm/libs/ammo.wasm.js", - }), - ); - - const newElement = document.createElement("div"); - newElement.id = "created-one-element"; - document.body.appendChild(newElement); - // life cycle fix - - // Version and name. - this.version = 2.0; - // Clock, scene and camera essentials. - // global names - this.camera_name = "::CAM::"; - - this.activeScene = new Scene( - "" + this.version, - this.camera_name, - this.updateSurfaceIdAttributeToMesh.bind(this), - this.version, - ); - this.activeScene.initialize(); - this.generating_preview = false; - this.last_cam_pos = new THREE.Vector3(0, 0, 0); - this.last_cam_rot = new THREE.Euler(0, 0, 0); - this.camera_last_pos = new THREE.Vector3(0, 0, 0); - this.startRenderWidth = 0; - this.startRenderHeight = 0; - this.rendering = false; - this.lastCanvasSize = 0; - this.switchPreviewToggle = false; - // API. - this.api_manager = new APIManager(); - // Debug & Movement. - this.camera_person_mode = false; - this.locked = false; - // Recording params. - this.render_timer = 0; - this.fps_number = 60; - this.cap_fps = 60; - // Timeline settings. - this.can_playback = false; - this.playback_location = 0; - this.last_scrub = 0; - this.frames = 0; - this.lastFrameTime = 0; - this.last_selected_sum = 0; - this.selectedCanvas = false; - this.renderEventToken = -1; - this.shouldRender = false; - // Audio Engine Test. - - this.render_camera_aspect_ratio = CameraAspectRatio.HORIZONTAL_3_2; - this.render_width = this.getRenderDimensions().width; - this.render_height = this.getRenderDimensions().height; - - this.audio_engine = new AudioEngine(); - this.emotion_engine = new EmotionEngine(this.version); - this.transform_engine = new TransformEngine(this.version); - this.lipsync_engine = new LipSyncEngine(); - this.animation_engine = new CharacterAnimationEngine(this.version); - - this.timeline = new TimeLine( - this, - this.audio_engine, - this.transform_engine, - this.lipsync_engine, - this.animation_engine, - this.emotion_engine, - this.activeScene, - this.camera, - this.mouse, - this.camera_name, - ); - - this.activeScene.timeline = this.timeline; - - this.utils = new SceneUtils(this, this.activeScene); - this.videoGeneration = new VideoGeneration(this); - this.save_manager = new SaveManager(this); - this.current_frame = 0; - - // Scene State - this.current_scene_media_token = null; - this.current_scene_glb_media_token = null; - - // stylization parameters - this.positive_prompt = - "((masterpiece, best quality, 8K, detailed)), colorful, epic, fantasy, (fox, red fox:1.2), no humans, 1other, ((koi pond)), outdoors, pond, rocks, stones, koi fish, ((watercolor))), lilypad, fish swimming around."; - this.negative_prompt = ""; - this.art_style = ArtStyle.Anime2DFlat; - - this.generation_options = { - faceDetail: false, - upscale: false, - styleStrength: 1.0, - lipSync: false, - cinematic: false, - globalIpAdapterImageMediaToken: null, - }; - - this.media_upload = new MediaUploadApi(); - this.globalIpAdapterImage = undefined; // used to display when loading in the app. and to serialize to an image token - // TODO REMOVE - this.outliner_feature_flag = true; - - // New Rendering Pipeline Engine Work - this.globalSetTrackLengthSeconds = 7; - - // set image type at this stage - - this.renderIndex = 0; - } - - // Add helper method to convert focal length to FOV - focalLengthToFov(focalLength: number, sensorHeight: number = 24): number { - // Using the formula: FOV = 2 * arctan(sensorHeight / (2 * focalLength)) - return 2 * Math.atan(sensorHeight / (2 * focalLength)) * (180 / Math.PI); - } - - getRenderDimensions() { - switch (this.render_camera_aspect_ratio) { - case CameraAspectRatio.HORIZONTAL_16_9: { - return { - width: 1280, - height: 720, - aspectRatio: 16 / 9, - }; - } - case CameraAspectRatio.HORIZONTAL_3_2: { - return { - width: 1200, - height: 800, - aspectRatio: 3 / 2, - }; - } - case CameraAspectRatio.VERTICAL_2_3: { - return { - width: 800, - height: 1200, - aspectRatio: 2 / 3, - }; - } - case CameraAspectRatio.VERTICAL_9_16: { - return { - width: 720, - height: 1280, - aspectRatio: 9 / 16, - }; - } - case CameraAspectRatio.SQUARE_1_1: - default: { - return { - width: 1080, - height: 1080, - aspectRatio: 1, - }; - } - } - } - isEmpty(value: string | null) { - return value === null || value.trim().length === 0; - } - - containerMayReset() { - //TODO: we should not need this, if the container is reset, - // updateSceneContainer should update the reference in the editor - if (!this.container) { - console.warn( - "Editor - Container does not exist, querying from DOM via document.getElementById", - ); - this.container = document.getElementById("video-scene-container"); - } - } - updateSceneContainer(newContainer: HTMLDivElement) { - this.container = newContainer; - this.containerResizeObserver?.disconnect(); - this.containerResizeObserver?.observe(this.container); - } - engineCanvasMayReset() { - //TODO: we should not need this, if the this canvas is reset, - // updateEngineCanvas should update the reference in the editor - if (!this.canvReference) { - console.warn( - "Editor - Engine Canbas does not exist, querying from DOM via document.getElementById", - ); - this.canvReference = document.getElementById( - "video-scene", - ) as HTMLCanvasElement; - } - } - updateEngineCanvas(newCanvas: HTMLCanvasElement) { - this.canvReference = newCanvas; - if (this.renderer) { - this.renderer.domElement = this.canvReference; - } - } - camViewCanvasMayReset() { - //TODO: we should not need this, if the this canvas is reset, - // updateCamViewCanvas should update the reference in the editor - if (!this.canvasRenderCamReference) { - console.warn( - "Editor - Cam View Canvas does not exist, querying from DOM via document.getElementById", - ); - this.canvasRenderCamReference = document.getElementById( - "camera-view", - ) as HTMLCanvasElement; - } - } - updateCamViewCanvas(newCanvas: HTMLCanvasElement) { - this.canvasRenderCamReference = newCanvas; - if (this.rawRenderer) { - this.rawRenderer.domElement = this.canvasRenderCamReference; - } - } - - changeRenderCameraAspectRatio(newAspectRatio: CameraAspectRatio) { - this.render_camera_aspect_ratio = newAspectRatio; - const { width, height, aspectRatio } = this.getRenderDimensions(); - this.render_width = width; - this.render_height = height; - if (this.render_camera) { - this.render_camera.aspect = aspectRatio; - this.render_camera.updateProjectionMatrix(); - } - - Queue.publish({ - queueName: QueueNames.FROM_ENGINE, - action: fromEngineActions.CAMERA_ASPECT_RATIO_CHANGED, - data: this.render_camera_aspect_ratio, - }); - } - - initialize({ - sceneToken, - editorCanvasEl, - camViewCanvasEl, - sceneContainerEl, - cacheJsonString: cacheJson = "", - }: EditorInitializeConfig) { - if (!this.can_initialize) { - console.log("3D editor is already initialized"); - return; - } - - this._isEngineDataLoaded = false; - this.can_initialize = false; - - // This is to prevent recording processing from happening twice there is an update loop bug at its core. - this.processingRecording = false; - - // Gets the canvas. - this.canvReference = editorCanvasEl; - this.canvasRenderCamReference = camViewCanvasEl; - - // Find the container element - this.container = sceneContainerEl; - - // Use the container's dimensions - const width = this.container.offsetWidth; - const height = this.container.offsetHeight; - - // Sets up camera and base position using camera configurations from camera.ts - const mainCameraConfig = cameras.value.find((cam) => cam.id === "main"); - if (mainCameraConfig) { - this.camera = new THREE.PerspectiveCamera( - this.focalLengthToFov(mainCameraConfig.focalLength), - width / height, - 0.1, - 2000, - ); - this.camera.position.set( - mainCameraConfig.position.x, - mainCameraConfig.position.y, - mainCameraConfig.position.z, - ); - this.camera.lookAt( - mainCameraConfig.lookAt.x, - mainCameraConfig.lookAt.y, - mainCameraConfig.lookAt.z, - ); - } - - this.camera.layers.enable(0); - this.camera.layers.enable(1); - - this.timeline.camera = this.camera; - - const otherCameras = cameras.value.filter((cam) => cam.id !== "main"); - if (otherCameras.length > 0) { - const renderCameraConfig = otherCameras[0]; - this.render_camera = new THREE.PerspectiveCamera( - this.focalLengthToFov(renderCameraConfig.focalLength), - width / height, - 0.01, - 200, - ); - this.render_camera.position.set( - renderCameraConfig.position.x, - renderCameraConfig.position.y, - renderCameraConfig.position.z, - ); - this.render_camera.lookAt( - renderCameraConfig.lookAt.x, - renderCameraConfig.lookAt.y, - renderCameraConfig.lookAt.z, - ); - } - - this.render_camera.layers.disable(1); // This camera does not see this layer ); - - // Base WebGL render and clock for delta time. - this.renderer = new THREE.WebGLRenderer({ - antialias: true, - canvas: this.canvReference, - preserveDrawingBuffer: true, - }); - - // this.sparkRenderer = new SparkRenderer({ - // renderer: this.renderer, - // autoUpdate: true, - // }); - - this.rawRenderer = new THREE.WebGLRenderer({ - antialias: true, - canvas: this.canvasRenderCamReference, - preserveDrawingBuffer: true, - }); - - this.renderer.shadowMap.enabled = true; - this.clock = new THREE.Clock(); - - // Resizes the renderer. - this.renderer.setSize(width, height); - this.renderer.setPixelRatio(window.devicePixelRatio); - - this._configurePostProcessing(); - // Controls and movement. - - this.lockControls = new PointerLockControls( - this.camera, - this.renderer.domElement, - ); - this.cameraViewControls = new FreeCam( - this.camera, - this.renderer.domElement, - ); - this.cameraViewControls.movementSpeed = 1.15; - this.cameraViewControls.domElement = this.renderer.domElement; - this.cameraViewControls.rollSpeed = Math.PI / 180; - this.cameraViewControls.autoForward = false; - this.cameraViewControls.dragToLook = true; - this.cameraViewControls.enabled = true; - - this.control = new TransformControls(this.camera, this.renderer.domElement); - this.control.space = "world"; // Default to world space for translate mode - this.control.setScaleSnap(0.01); - this.control.setTranslationSnap(0.01); - this.control.setRotationSnap(0.01); - //console.log("Control Sensitivity:", this.control.sensitivity); - - // Base control and debug stuff remove debug in prod. - if (this.control == undefined) { - return; - } - this.control.addEventListener("change", this.renderScene.bind(this)); - this.control.addEventListener("dragging-changed", (event: any) => { - //TODO: any should be the following - this.updateSelectedUI(); - this.camera_last_pos.copy(new THREE.Vector3(-99999, -99999, -99999)); - this.focused = !event.value; - // this.update_properties() - }); - this.control.setSize(0.5); // Good default value for visuals. - this.raycaster = new THREE.Raycaster(); - // Configure raycaster to check both layers - this.raycaster.layers.set(0); // Enable default layer - this.raycaster.layers.enable(1); // Also check objects on the custom layer - - this.mouse = new THREE.Vector2(); - this.activeScene.scene.add(this.control); - // Resets canvas size. - this.onWindowResize(); - - this.setupResizeObserver(); - - this.timeline.scene = this.activeScene; - - // saving state of the scene - this.current_scene_media_token = null; - this.current_scene_glb_media_token = null; - - this.cam_obj = this.activeScene.get_object_by_name(this.camera_name); - - this.mouse_controls = new MouseControls( - this.camera, - this.get_camera_person_mode.bind(this), - this.cameraViewControls, - this.lockControls, - this.camera_last_pos, - this.selectedCanvas, - this.switchPreviewToggle, - this.rendering, - this.togglePlayback.bind(this), - this.deleteObject.bind(this), - this.canvReference, - this.mouse, - this.timeline.mouse, - this.raycaster, - this.control, - this.outlinePass, - this.activeScene.scene, - this.publishSelect.bind(this), - this.updateSelectedUI.bind(this), - this.transform_interaction, - this.last_selected, - this.getAssetType.bind(this), - this.setSelected.bind(this), - this.isMovable.bind(this), - this.enable_stats.bind(this), - ); - - if (this.outliner_feature_flag) { - this.sceneManager = new SceneManager( - this.version, - this.mouse_controls, - this.activeScene, - true, - this.updateOutliner.bind(this), - this.timeline.isCharacter.bind(this.timeline), - ); // Enabled dev mode. - this.mouse_controls.sceneManager = this.sceneManager; - } - - // Attach freecam state controller - this.mouseOverEventHandler = this.mouseOverEventHandler.bind(this); - - // Add spark renderer as a child of the camera - // this.activeScene.scene.add(this.sparkRenderer); - // this.camera?.add(this.sparkRenderer); - - const onloadCallback = () => { - console.log("Setting Scene is loaded"); - this._isEngineDataLoaded = true; - - if (this.outliner_feature_flag) { - const result = this.sceneManager?.render_outliner( - this.timeline.characters, - ); - if (result) outlinerState.items.value = result.items; - } - - setIs3DSceneLoaded(true); - }; - - if (!this.utils.isEmpty(cacheJson)) { - this.loadCache(cacheJson).then(onloadCallback); - } else if (!this.utils.isEmpty(sceneToken)) { - this.loadScene(sceneToken).then(onloadCallback); - } else { - signalScene({ - title: "Untitled New Scene", - token: undefined, - ownerToken: authentication.userInfo.value?.user_token, - isModified: false, - }); - onloadCallback(); - } - - this._configurePostProcessingRaw(); - - loadingBarData.value = { - ...loadingBarData.value, - progress: 100, - }; - loadingBarIsShowing.value = false; - - setIs3DEditorInitialized(true); - - // This will enable all event and render loops - // We'll disable it here so the UI events can control is manually - this.remountEngine(); - } - - public isMovable(): boolean { - return this.focused; - } - - public enable_stats() { - document.body.appendChild(this.stats.dom); - } - - private mouseOverEventHandler(event: MouseEvent) { - if (this.cameraViewControls) { - if ( - event.target instanceof HTMLCanvasElement || - (event.target as HTMLElement).id == "letterbox" - ) { - this.cameraViewControls.enabled = true; - this.selectedCanvas = true; - this.focused = true; - } else { - this.cameraViewControls.reset(); - this.focused = false; - this.cameraViewControls.enabled = false; - this.selectedCanvas = false; - } - } - } - - public enableFreeCamControls() { - document.addEventListener("mouseover", this.mouseOverEventHandler); - } - - public disableFreeCamControls() { - document.removeEventListener("mouseover", this.mouseOverEventHandler); - } - - // Captures the scene without the grid - public snapShotOfCurrentFrame(shouldDownload: boolean = true) { - if (!this.renderer?.domElement || !this.camera) { - console.error("Error: Renderer or camera not available."); - return null; - } - - const currentAspectRatio = cameraAspectRatio.value; - - // Store grid visibility state and hide grid - const wasGridVisible = gridVisibility.value; - gridVisibility.value = false; - - // Store and hide transform controls - const wasControlVisible = this.control?.visible ?? false; - if (this.control) { - this.control.visible = false; - } - - // Store and disable outline pass - const wasOutlineEnabled = this.outlinePass?.enabled ?? false; - if (this.outlinePass) { - this.outlinePass.enabled = false; - } - - // High quality dimensions for each aspect ratio - let targetWidth: number; - let targetHeight: number; - let aspectRatio: number; - - switch (currentAspectRatio) { - case CameraAspectRatio.HORIZONTAL_16_9: - targetWidth = 1280; - targetHeight = 720; - aspectRatio = 16 / 9; - break; - case CameraAspectRatio.VERTICAL_9_16: - targetWidth = 720; - targetHeight = 1280; - aspectRatio = 9 / 16; - break; - case CameraAspectRatio.HORIZONTAL_3_2: - targetWidth = 1536; - targetHeight = 1024; - aspectRatio = 3 / 2; - break; - case CameraAspectRatio.VERTICAL_2_3: - targetWidth = 1024; - targetHeight = 1536; - aspectRatio = 2 / 3; - break; - case CameraAspectRatio.SQUARE_1_1: - default: - targetWidth = 1024; - targetHeight = 1024; - aspectRatio = 1; - break; - } - - // Store original renderer and camera state - const sizeVector = new THREE.Vector2(); - this.renderer.getSize(sizeVector); - - const originalWidth = sizeVector.x; - const originalHeight = sizeVector.y; - const originalPixelRatio = this.renderer.getPixelRatio(); - const originalCameraAspect = this.camera.aspect; - const originalRenderCameraAspect = - this.render_camera?.aspect || originalCameraAspect; - - // Temporarily set renderer to high resolution - this.renderer.setSize(targetWidth, targetHeight, false); - this.renderer.setPixelRatio(1); - - // Update camera for the new aspect ratio - this.camera.aspect = aspectRatio; - this.camera.updateProjectionMatrix(); - - // If using render camera, update it too - if (this.render_camera) { - this.render_camera.aspect = aspectRatio; - this.render_camera.updateProjectionMatrix(); - } - - // Re-render the scene at high resolution - if (this.composer) { - this.composer.setSize(targetWidth, targetHeight); - this.composer.render(); - } else { - this.renderer.render(this.activeScene.scene, this.camera); - } - - // Get the high resolution snapshot - const snapshot = this.renderer.domElement.toDataURL("image/png", 1.0); - const base64Snapshot = snapshot.split(",")[1]; - - // Restore original camera aspect - this.camera.aspect = originalCameraAspect; - this.camera.updateProjectionMatrix(); - - // Restore render camera if it exists - if (this.render_camera) { - this.render_camera.aspect = originalRenderCameraAspect; - this.render_camera.updateProjectionMatrix(); - } - - // Restore original renderer size and pixel ratio - this.renderer.setSize(originalWidth, originalHeight, false); - this.renderer.setPixelRatio(originalPixelRatio); - - // Re-render at original resolution - if (this.composer) { - this.composer.setSize(originalWidth, originalHeight); - this.composer.render(); - } else { - this.renderer.render(this.activeScene.scene, this.camera); - } - - // Restore grid visibility - gridVisibility.value = wasGridVisible; - - // Restore transform controls visibility - if (this.control) { - this.control.visible = wasControlVisible; - } - - // Restore outline pass - if (this.outlinePass) { - this.outlinePass.enabled = wasOutlineEnabled; - } - - if (shouldDownload) { - const link = document.createElement("a"); - link.download = "scene-snapshot.png"; - link.href = snapshot; - link.click(); - } - - const byteString = atob(base64Snapshot); - const mimeString = "image/png"; - const ab = new ArrayBuffer(byteString.length); - const ia = new Uint8Array(ab); - for (let i = 0; i < byteString.length; i++) { - ia[i] = byteString.charCodeAt(i); - } - const uuid = crypto.randomUUID(); - const file = new File([ab], `${uuid}.png`, { type: mimeString }); - return { base64Snapshot, file }; - } - - public async newScene(sceneTitleInput: string) { - this.activeScene.clear(); - this.audio_engine = new AudioEngine(); - this.emotion_engine = new EmotionEngine(this.version); - this.transform_engine = new TransformEngine(this.version); - this.lipsync_engine = new LipSyncEngine(); - this.animation_engine = new CharacterAnimationEngine(this.version); - - this.timeline = new TimeLine( - this, - this.audio_engine, - this.transform_engine, - this.lipsync_engine, - this.animation_engine, - this.emotion_engine, - this.activeScene, - this.camera, - this.mouse, - this.camera_name, - ); - this.cam_obj = this.activeScene.get_object_by_name(this.camera_name); - const sceneTitle = - sceneTitleInput && sceneTitleInput !== "" - ? sceneTitleInput - : "Untitled New Scene"; - signalScene({ - title: sceneTitle, - token: undefined, - ownerToken: authentication.userInfo.value?.user_token, - isModified: false, - }); - Queue.publish({ - queueName: QueueNames.FROM_ENGINE, - action: fromEngineActions.RESET_TIMELINE, - data: null, - }); - - if (this.outliner_feature_flag) { - const result = this.sceneManager?.render_outliner( - this.timeline.characters, - ); - if (result) outlinerState.items.value = result.items; - } - } - - public async loadCache(cacheJson: string) { - await this.save_manager.loadCache(cacheJson); - } - - public async loadScene(scene_media_token: string) { - await this.save_manager.loadScene(scene_media_token); - - if (this.outliner_feature_flag) { - const result = this.sceneManager?.render_outliner( - this.timeline.characters, - ); - if (result) outlinerState.items.value = result.items; - } - // publish to the UI the values for the prompts and artistic style and settings? - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: toEngineActions.UPDATE_TIME, - data: { currentTime: 1 }, - }); - } - - setSelected(object: THREE.Object3D[] | undefined) { - if (this.sceneManager) { - this.sceneManager.selected_objects = object; - } - } - - isObjectLipsync(object_uuid: string) { - return this.utils.isObjectLipsync(object_uuid); - } - - isObjectLocked(object_uuid: string): boolean { - return this.utils.isObjectLocked(object_uuid); - } - - lockUnlockObject(object_uuid: string): boolean { - const res = this.utils.lockUnlockObject(object_uuid); - if (this.outliner_feature_flag) { - this.updateSelectedUI(); - } - return res; - } - - setColor(object_uuid: string, hex_color: string) { - this.activeScene.setColor(object_uuid, hex_color); - } - - // TO UPDATE selected objects in the scene might want to add to the scene ... - async setSelectedObject(position: XYZ, rotation: XYZ, scale: XYZ) { - this.utils.setSelectedObject(position, rotation, scale); - } - - public async saveScene({ - sceneTitle, - sceneToken, - sceneGenerationMetadata, - }: { - sceneTitle: string; - sceneToken?: string; - sceneGenerationMetadata: SceneGenereationMetaData; - }): Promise { - return await this.save_manager.saveScene({ - sceneTitle: sceneTitle, - sceneToken: sceneToken, - sceneGenerationMetadata: sceneGenerationMetadata, - }); - } - - public cacheScene({ - sceneTitle, - sceneToken, - sceneGenerationMetadata, - }: { - sceneTitle: string; - sceneToken: string; - sceneGenerationMetadata: SceneGenereationMetaData; - }) { - return this.save_manager.getSceneJson({ - sceneGenerationMetadata: sceneGenerationMetadata, - }); - } - - get_camera_person_mode(): boolean { - return this.camera_person_mode; - } - - switchCameraView() { - this.utils.switchCameraView(); - } - - async showLoading() { - loadingBarIsShowing.value = true; - } - - async updateLoad({ - progress, - message, - label, - }: { - progress?: number; - message?: string; - label?: string; - }) { - loadingBarData.value = { - label: label ?? loadingBarData.value.label, - progress: progress ?? loadingBarData.value.progress, - message: message ?? loadingBarData.value.message, - }; - } - - async endLoading() { - loadingBarIsShowing.value = false; - } - - _configurePostProcessingRaw() { - const width = this.canvasRenderCamReference?.width ?? 0; - const height = this.canvasRenderCamReference?.height ?? 0; - if ( - this.rawRenderer == undefined || - this.render_camera == undefined || - this.renderer == undefined - ) { - return; - } - - const depthTexture = new THREE.DepthTexture(width, height); - depthTexture.type = THREE.UnsignedShortType; - - const renderTarget = new THREE.WebGLRenderTarget( - window.innerWidth, - window.innerHeight, - { - depthTexture: depthTexture, - depthBuffer: true, - }, - ); - - this.customOutlinerPass = new CustomOutlinePass( - new THREE.Vector2(width, height), - this.activeScene.scene, - this.render_camera, - ); - - this.render_composer = new EffectComposer(this.rawRenderer, renderTarget); - - this.surfaceFinder = new FindSurfaces(); - - this.rawRenderPass = new RenderPass( - this.activeScene.scene, - this.render_camera, - ); - - this.render_composer.addPass(this.rawRenderPass); - - this.render_composer.addPass(this.customOutlinerPass); - - this.renderOutputPass = new OutputPass(); - - this.render_composer.addPass(this.renderOutputPass); - - this.setColorMap(); - } - - setRenderDepth() { - this.updateSurfaceIdAttributeToMesh(this.activeScene.scene); - if (this.render_camera && this.customOutlinerPass) { - this.customOutlinerPass.fsQuad.material.uniforms.debugVisualize.value = 3; // Depth - } - } - - setNormalMap() { - this.updateSurfaceIdAttributeToMesh(this.activeScene.scene); - if (this.render_camera && this.customOutlinerPass) { - this.customOutlinerPass.fsQuad.material.uniforms.debugVisualize.value = 4; // Normal Map - } - } - - setColorMap() { - this.updateSurfaceIdAttributeToMesh(this.activeScene.scene); - if (this.render_camera && this.customOutlinerPass) { - this.customOutlinerPass.fsQuad.material.uniforms.debugVisualize.value = 2; // Renderd Color - } - } - - setOutlineRender() { - this.updateSurfaceIdAttributeToMesh(this.activeScene.scene); - if (this.render_camera && this.customOutlinerPass) { - this.customOutlinerPass.fsQuad.material.uniforms.debugVisualize.value = 7; // Outlines Only - } - } - - // Configure post processing. - _configurePostProcessing() { - const width = this.canvReference?.width ?? 0; - const height = this.canvReference?.height ?? 0; - - if (this.renderer == undefined || this.camera == undefined) { - return; - } - - this.composer = new EffectComposer(this.renderer); - this.renderPass = new RenderPass(this.activeScene.scene, this.camera); - - this.composer.addPass(this.renderPass); - - this.outlinePass = new OutlinePass( - new THREE.Vector2(width / 10, height / 10), - this.activeScene.scene, - this.camera, - ); - - // this.outlinePass.edgeStrength = 5.0; - // this.outlinePass.edgeGlow = 0.1; - // this.outlinePass.edgeThickness = 1.2; - // this.outlinePass.pulsePeriod = 3; - // this.outlinePass.usePatternTexture = false; - // this.outlinePass.visibleEdgeColor.set(0x4b9fff); - - // this.composer.addPass(this.outlinePass); - - this.bloomPass = new UnrealBloomPass( - new THREE.Vector2(width, height), - 1.5, - 0.4, - 0.85, - ); - this.bloomPass.strength = 0.25; - - // this.smaaPass = new SMAAPass( - // width * this.renderer.getPixelRatio(), - // height * this.renderer.getPixelRatio(), - // ); - - // this.composer.addPass(this.bloomPass); - // this.composer.addPass(this.smaaPass); - - // this.ssaoPass = new SSAOPass(this.activeScene.scene, this.camera, width, height); - // this.composer.addPass(this.ssaoPass); - - this.outputPass = new OutputPass(); - this.composer.addPass(this.outputPass); - } - - deleteObject(uuid: string) { - this.mouse_controls?.clearFKVisuals(); - this.mouse_controls?.removeTransformControls(true); - this.utils.deleteObject(uuid); - if (this.outliner_feature_flag) { - const result = this.sceneManager?.render_outliner( - this.timeline.characters, - ); - if (result) outlinerState.items.value = result.items; - } - } - - async create_parim(name: string, pos: THREE.Vector3) { - return await this.activeScene.instantiate(name, pos); - } - - updateSurfaceIdAttributeToMesh(scene: THREE.Scene) { - if (this.surfaceFinder === undefined) { - return; - } - this.surfaceFinder.surfaceId = 0; - this.customOutlinerPass?.updateMaxSurfaceId( - this.surfaceFinder.surfaceId + 1, - ); - } - - async recordScene() { - // Preconditions required to record the scene when generating the movie. - if (this.rendering && this.rawRenderer && this.clock && this.renderer) { - if (this.recorder === undefined && this.render_camera) { - const width = - this.render_camera_aspect_ratio === CameraAspectRatio.HORIZONTAL_16_9 - ? 1024 - : this.render_camera_aspect_ratio === meraAspectRatio.VERTICAL_9_16 - ? 576 - : this.render_camera_aspect_ratio === - meraAspectRatio.HORIZONTAL_3_2 - ? 900 - : this.render_camera_aspect_ratio === - meraAspectRatio.VERTICAL_2_3 - ? 600 - : 1000; - const height = - this.render_camera_aspect_ratio === CameraAspectRatio.HORIZONTAL_16_9 - ? 576 - : this.render_camera_aspect_ratio === meraAspectRatio.VERTICAL_9_16 - ? 1024 - : this.render_camera_aspect_ratio === - meraAspectRatio.HORIZONTAL_3_2 - ? 600 - : this.render_camera_aspect_ratio === - meraAspectRatio.VERTICAL_2_3 - ? 900 - : 1000; - - this.rawRenderer.setSize(width, height); - - // ensure that during a resize or change in perspective this will processes correct. - this.engineFrameBuffers.setRenderSurfaceSize(width, height); - - this.render_camera.aspect = width / height; - } - - this.render_timer += this.clock.getDelta(); - - this.playback_location++; - - this.utils.removeTransformControls(true); - - if (this.timeline.is_playing) { - this.setColorMap(); - this.render_composer?.render(); - if (this.canvReference) { - console.time("Color Frame RenderTime"); - - // reset this flag to ensure that a failure isn't cached - this.processingHasFailed = false; - // can't render without this. - - if (!this.render_camera) { - return; - } - - this.engineFrameBuffers.enqueueWork( - this.rawRenderer, - this.activeScene.scene, - this.render_camera, - ); - - this.renderIndex += 1; - console.log(`Frames Counted: ${this.renderIndex}`); - - console.timeEnd("Color Frame RenderTime"); - } else { - console.log("We lost the canvas reference."); - } - - this.render_timer += this.clock.getDelta(); - } - if (!this.timeline.is_playing) { - this.playback_location = 0; - try { - if (this.processingRecording) { - console.log( - "processingRecording is already happening this shouldn't have happened", - ); - return; - } - this.processingRecording = true; - // collect all frames here, there is some kind of race condition that is happening - // TODO BUG with the main loop,that sends multiple zip requests. - console.log(`COLLECTING COLOR FRAMES COUNTED: ${this.renderIndex}`); - await this.engineFrameBuffers.collectColorFrames(this.renderIndex); - - this.renderIndex = 0; - console.time("Stop Playback And Upload Video Time"); - await this.stopPlaybackAndUploadVideo(); - console.timeEnd("Stop Playback And Upload Video Time"); - - this.processingRecording = false; - console.log("Processing has ended"); - } catch (error) { - // don't use cache in this case. - this.processingHasFailed = true; - - console.log(`Video Generation: ${error}`); - } - } // End Timeline Playing - - // BRB ^)^ - } - } - - // Render the scene to the camera, this is called in the update. - async renderScene() { - if ( - this.composer != null && - !this.rendering && - this.rawRenderer && - this.render_composer - ) { - //const then = performance.now(); - this.composer.render(); - //const now = performance.now(); - //console.log("Performance: ", now - then); - //this.rawRenderer.render(this.activeScene.scene, this.render_camera!); - // this.render_composer.render(); - } else if (this.renderer && this.render_camera && !this.rendering) { - this.renderer.setSize(this.render_width, this.render_height); - this.renderer.render(this.activeScene.scene, this.render_camera); - } else if (this.rendering && this.renderer) { - this.renderer.setSize(this.render_width, this.render_height); - } - // await this.recordScene(); - } - - async useCachedMediaTokens(): Promise { - // if the preprecessing switch is not the same then we need to rerender - if ( - this.videoGeneration.last_position_of_preprocessing != - this.engine_preprocessing - ) { - return false; - } - // this is slower to do so do this last. - const checksum = await this.save_manager.computeSceneChecksum(); - const decision = this.videoGeneration.shouldRenderScenesAgain(checksum); - return decision; - } - - async renderSingleFrame() { - //console.timeEnd("Single Frame Time"); - //console.time("Single Frame Time"); - this.containerMayReset(); - - if (!this.rendering && this.container) { - if ( - this.container.clientWidth + this.container.clientHeight !== - this.lastCanvasSize - ) { - this.onWindowResize(); - this.lastCanvasSize = - this.container.clientWidth + this.container.clientHeight; - } - } - - if (this.clock == undefined || this.renderer == undefined) { - return; - } - - const delta_time = this.clock.getDelta(); - - // Update camera properties from signals before FreeCam update - if (selectedCameraId.value && this.camera) { - const camData = cameras.value.find( - (c) => c.id === selectedCameraId.value, - ); - if (camData) { - const fov = this.focalLengthToFov(camData.focalLength); - if (this.camera.fov !== fov) { - this.camera.fov = fov; - this.camera.updateProjectionMatrix(); - } - } - } - - this.cameraViewControls?.update(5 * delta_time); - this.activeScene.shader_objects.forEach((shader) => { - shader.material.uniforms["time"].value += 0.5 * delta_time; - }); - - if (this.cameraViewControls && this.camera_person_mode) { - if (this.cam_obj && this.camera) { - if (this.last_scrub != this.timeline.scrubber_frame_position) { - this.camera.position.copy(this.cam_obj.position); - this.camera.rotation.copy(this.cam_obj.rotation); - } else if (!this.timeline.is_playing) { - this.cam_obj.position.copy(this.camera.position); - this.cam_obj.rotation.copy(this.camera.rotation); - } else { - this.camera.position.copy(this.cam_obj.position); - this.camera.rotation.copy(this.cam_obj.rotation); - } - - this.cam_obj.visible = false; - - // const min = new THREE.Vector3(-12, -1, -12); - // const max = new THREE.Vector3(12, 24, 12); - // this.camera.position.copy(this.camera.position.clamp(min, max)); - } - } else if (this.cam_obj) { - this.cam_obj.visible = true; - } - - if (this.render_camera && this.cam_obj) { - this.render_camera.position.copy(this.cam_obj.position); - this.render_camera.rotation.copy(this.cam_obj.rotation); - this.cam_obj.scale.copy(new THREE.Vector3(1, 1, 1)); - } - - if (this.timeline.is_playing) { - const changeView = await this.timeline.update(this.rendering, delta_time); - if (changeView) { - this.switchCameraView(); - } - } else if ( - this.last_scrub === this.timeline.scrubber_frame_position && - this.utils.getSelectedSum() !== this.last_selected_sum - ) { - this.updateSelectedUI(); - } - this.last_selected_sum = this.utils.getSelectedSum(); - - await this.renderScene(); - this.last_scrub = this.timeline.scrubber_frame_position; - - this.stats.update(); - } - - // Basicly Unity 3D's update loop. - updateLoop() { - if (!this.shouldRender) { - console.debug("Stopping 3D render loop"); - return; - } - - // Performance improvement: Handle frame cap - // Request the next render already - this is necessary so the loop doesn't stop if the fps cap is hit - this.renderEventToken = requestAnimationFrame(this.updateLoop.bind(this)); - const frameTime = performance.now(); - if (frameTime - this.lastFrameTime < 1000 / this.cap_fps) { - return; - } - - this.lastFrameTime = frameTime; - this.renderSingleFrame(); - } - - startRenderLoop() { - if (this.shouldRender) { - console.warn("Render flag is already true"); - return; - } - - this.shouldRender = true; - this.updateLoop(); - } - - stopRenderLoop() { - this.shouldRender = false; - - if (this.renderEventToken) { - cancelAnimationFrame(this.renderEventToken); - this.renderEventToken = -1; - } - } - - remountEngine() { - if (!is3DEditorInitialized.peek()) { - console.log("3D mount: Wait for initialization"); - return; - } - - if (this.isMounted) { - console.log("3D already mounted, skipping"); - return; - } - - if (!is3DPageMounted.peek()) { - console.log("3D mount: Wait for DOM mount"); - return; - } - - this.isMounted = true; - this.startRenderLoop(); - this.sceneManager?.attachEventListeners(); - this.enableFreeCamControls(); - this.cameraViewControls?.attachEventListeners(); - console.log("3D Editor Engine remounted"); - } - - unmountEngine() { - setIs3DSceneLoaded(false); - this.stopRenderLoop(); - this.sceneManager?.detachEventListeners(); - this.disableFreeCamControls(); - this.cameraViewControls?.detachEventListeners(); - - // Fix: dispose 3D contexts - this.renderer?.dispose(); - this.composer?.dispose(); - this.render_composer?.dispose(); - this.rawRenderer?.dispose(); - - this.isMounted = false; - setIs3DEditorInitialized(false); - console.log("3D Editor Engine unmounted"); - } - - change_mode(type: "translate" | "rotate" | "scale") { - if (this.control == undefined) { - return; - } - this.control.mode = type; - this.control.space = type === "scale" ? "local" : transformSpace.value; - this.transform_interaction = true; - } - - toggleTransformSpace() { - if (this.control == undefined || this.control.mode === "scale") { - return; - } - transformSpace.value = transformSpace.value === "world" ? "local" : "world"; - this.control.space = transformSpace.value; - } - - async stopPlaybackAndUploadVideo() { - await this.videoGeneration.stopPlaybackAndUploadVideo(); - } - - async switchPreview() { - if (!this.switchPreviewToggle) { - this.switchPreviewToggle = true; - editorState.value = EditorStates.PREVIEW; - await this.generateFrame(); - } - } - - switchEdit() { - if ( - this.switchPreviewToggle && - this.canvasRenderCamReference && - this.rawRenderPass - ) { - this.switchPreviewToggle = false; - editorState.value = EditorStates.EDIT; - setTimeout(() => { - // if (!this.canvasRenderCamReference) { - // this.canvasRenderCamReference = - // document.getElementById("camera-view"); - // } - this.camViewCanvasMayReset(); - // this.rawRenderer = new THREE.WebGLRenderer({ - // antialias: true, - // canvas: this.canvasRenderCamReference || undefined, - // preserveDrawingBuffer: true, - // }); - // this._configurePostProcessingRaw(); - - if (this.camera_person_mode) { - this.switchCameraView(); - } - this.activeScene.renderMode(false); - }, 10); - } - } - - async generateFrame() { - this.videoGeneration.generateFrame(); - } - - // This initializes the generation of a video render scene is where the core work happens - async generateVideo() { - // cannot run this function reliably without ensuring state below doesn't blow everything up. - if (await this.checkAndUseCache()) { - console.log("Generating Video: Checking Cache"); - await this.videoGeneration.handleCachedEnqueue(); - return; - } - - // some state changes below - - this.timeline.is_playing = false; - this.timeline.scrubber_frame_position = 0; - this.timeline.current_time = 0; - - if (this.rendering || this.generating_preview) { - return; - } - - this.showLoading(); - - // This for debouncing and also trigging the toggle playback... - // has to be here or will break play back ... - this.rendering = true; - - console.log("Running without Cache"); - - this.togglePlayback(); - this.render_timer = 0; - this.activeScene.renderMode(this.rendering); - this.timeline.scrubber_frame_position = 0; - if (this.activeScene.hot_items) { - this.activeScene.hot_items.forEach((element) => { - element.visible = false; - }); - } - } - - // In first case where if it cached data this is for people to reprompt without leaving the app - // Skip performing any of this because we have not changed the scene, then exit the scene. - // This avoids any unknown state issues from the resulting code below and escapes all the random state changes. - // This sadly doesn't cover camera framing style changes, - // Reasoning is that there is no defined interface to get changes like that. - async checkAndUseCache(): Promise { - if ( - (await this.useCachedMediaTokens()) && - this.processingHasFailed == false - ) { - console.log("Using Cache"); - return true; - } else { - console.log("Not Using Cache"); - return false; - } - } - - togglePlayback() { - this.updateLoad({ - progress: 25, - label: "Starting Processing", - message: - "Please stay on this screen and do not switch tabs! while your video is being processed.", - }); - if (this.rawRenderer) { - this.startRenderWidth = this.rawRenderer.domElement.width; - this.startRenderHeight = this.rawRenderer.domElement.height; - } - if (!this.rendering && this.timeline.is_playing) { - this.timeline.is_playing = false; - - this.switchCameraView(); - if (this.activeScene.hot_items) { - this.activeScene.hot_items.forEach((element) => { - element.visible = true; - }); - } - } else { - this.timeline.is_playing = true; - this.timeline.scrubber_frame_position = 0; - if (!this.camera_person_mode) { - this.switchCameraView(); - } - if (this.activeScene.hot_items) { - this.activeScene.hot_items.forEach((element) => { - element.visible = false; - }); - } - } - } - - updateOutliner() { - const result = this.sceneManager?.render_outliner(this.timeline.characters); - if (result) outlinerState.items.value = result.items; - this.updateSelectedUI(); - } - - updateSelectedUI() { - let mainSelected; - if (this.outliner_feature_flag) { - if ( - this.sceneManager?.selected_objects === undefined || - this.timeline.is_playing - ) { - return; - } - if (this.sceneManager?.selected_objects.length <= 0) { - return 0; - } - - mainSelected = this.sceneManager?.selected_objects[0]; - } else { - if (this.timeline.is_playing) { - return; - } - - if (this.selected == undefined) { - return 0; - } - mainSelected = this.selected; - } - - this.selected = mainSelected; - const pos = mainSelected.position; - const rot = mainSelected.rotation; - const scale = mainSelected.scale; - - // TODO this is a bug we need to only show when clicked on and use UPDATE when updating. - updateObjectPanel({ - group: - mainSelected.name === this.camera_name - ? ClipGroup.CAMERA - : ClipGroup.OBJECT, // TODO: add meta data to determine what it is a camera or a object or a character into prefab clips - object_uuid: mainSelected.uuid, - object_name: mainSelected.name, - version: String(this.version), - objectVectors: { - position: { - x: parseFloat(pos.x.toFixed(2)), - y: parseFloat(pos.y.toFixed(2)), - z: parseFloat(pos.z.toFixed(2)), - }, - rotation: { - x: parseFloat(THREE.MathUtils.radToDeg(rot.x).toFixed(2)), - y: parseFloat(THREE.MathUtils.radToDeg(rot.y).toFixed(2)), - z: parseFloat(THREE.MathUtils.radToDeg(rot.z).toFixed(2)), - }, - scale: { - x: parseFloat(scale.x.toFixed(6)), - y: parseFloat(scale.y.toFixed(6)), - z: parseFloat(scale.z.toFixed(6)), - }, - }, - }); //end updateObjectPanel - } - - // Automaticly resize scene. - onWindowResize() { - this.containerMayReset(); - if (!this.container) return; - - const width = this.container.clientWidth; - const height = this.container.clientHeight; - - if (this.camera == undefined || this.renderer == undefined) { - return; - } - // Set the camera aspect to the desired aspect ratio - this.camera.aspect = width / height; - this.camera.updateProjectionMatrix(); - - // Set the renderer size to the calculated dimensions - this.renderer.setSize(width, height); - if (this.composer != null) { - this.composer.setSize(width, height); - } - - this.render_composer?.setSize(width, height); - - if (this.render_camera == undefined) { - return; - } - - this.customOutlinerPass?.setSize(width, height); - this.render_camera.aspect = this.getRenderDimensions().aspectRatio; - this.render_camera.updateProjectionMatrix(); - } - - setupResizeObserver() { - this.containerMayReset(); - - if (!this.container) { - return; - } - - this.containerResizeObserver = new ResizeObserver((entries) => { - for (const entry of entries) { - const { width, height } = entry.contentRect; - if (this.camera) { - this.camera.aspect = width / height; - this.camera.updateProjectionMatrix(); - } - this.renderer?.setSize(width, height); - this.renderer?.setPixelRatio(window.devicePixelRatio); - } - }); - - this.containerResizeObserver.observe(this.container); - } - - getAssetType(selected: THREE.Object3D): AssetType { - if (selected.type === "Mesh") { - return selected.name === "::CAM::" ? AssetType.CAMERA : AssetType.OBJECT; - } - return AssetType.CHARACTER; - } - - publishSelect() { - if ((this, this.outliner_feature_flag)) { - if ( - this.sceneManager?.selected_objects && - this.sceneManager?.selected_objects?.length > 0 - ) { - Queue.publish({ - queueName: QueueNames.FROM_ENGINE, - action: fromEngineActions.SELECT_OBJECT, - data: { - type: this.getAssetType(this.sceneManager?.selected_objects[0]), - object_uuid: this.sceneManager?.selected_objects[0].uuid, - version: 1, - media_id: this.sceneManager?.selected_objects[0].id.toString(), - name: "", - } as MediaItem, - }); - return; - } else { - Queue.publish({ - queueName: QueueNames.FROM_ENGINE, - action: fromEngineActions.DESELECT_OBJECT, - data: null, - }); - } - } else { - if (this.selected) { - Queue.publish({ - queueName: QueueNames.FROM_ENGINE, - action: fromEngineActions.SELECT_OBJECT, - data: { - type: this.getAssetType(this.selected), - object_uuid: this.selected.uuid, - version: 1, - media_id: this.selected.id.toString(), - name: "", - } as MediaItem, - }); - return; - } else { - Queue.publish({ - queueName: QueueNames.FROM_ENGINE, - action: fromEngineActions.DESELECT_OBJECT, - data: null, - }); - } - } - } -} - -export default Editor; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/free_cam.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/free_cam.ts deleted file mode 100644 index 45270b8a2c..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/free_cam.ts +++ /dev/null @@ -1,445 +0,0 @@ -import { - EventDispatcher, - Quaternion, - Vector3, - Vector2, - MathUtils, -} from "three"; -import * as THREE from "three"; -import { cameras, selectedCameraId } from "~/pages/PageEnigma/signals/camera"; -import { isPromptBoxFocused } from "~/pages/PageEnigma/signals/promptBox"; - - -export const isSafariOrWebKit = () => { - const ua = navigator.userAgent; - - const isWebKit = /AppleWebKit/i.test(ua) && !/Chrome|Chromium|Edg|OPR|CriOS|FxiOS/i.test(ua); - const isSafari = /^((?!chrome|android|crios|fxios).)*safari/i.test(ua); - - return isSafari || isWebKit; -}; - -export const hasApplePay = () => { - // @ts-expect-error Apple Pay is not defined in TypeScript - return !!window.ApplePaySession; -} - -export const isSafari = () => { - return isSafariOrWebKit() || hasApplePay(); -} - -export const isPointerLockSupported = () => { - return !isSafari(); -} - -class FreeCam extends EventDispatcher { - object: THREE.PerspectiveCamera; - domElement: HTMLElement | Document; - enabled = true; - movementSpeed = 1.0; - rollSpeed = 0.005; - dragToLook = false; - autoForward = false; - dragging = false; - xOffset = 0; - yOffset = 0; - - // Smoothing properties - smoothingFactor = 0.2; - currentVelocity = new Vector3(); - lastPosition = new Vector3(); - - lastMousePosition = new Vector2(); - mouseVelocity = new Vector2(); - tmpQuaternion = new Quaternion(); - - status = 0; - - moveState = { - up: 0, - down: 0, - left: 0, - right: 0, - forward: 0, - back: 0, - pitchUp: 0, - pitchDown: 0, - yawLeft: 0, - yawRight: 0, - rollLeft: 0, - rollRight: 0, - }; - moveVector = new Vector3(0, 0, 0); - rotationVector = new Vector3(0, 0, 0); - - constructor( - object: THREE.PerspectiveCamera, - domElement: HTMLElement | Document, - ) { - super(); - - this.object = object; - this.domElement = domElement; - this.lastPosition.copy(object.position); - - this.keydown = this.keydown.bind(this); - this.keyup = this.keyup.bind(this); - this.mousedown = this.mousedown.bind(this); - this.mouseup = this.mouseup.bind(this); - this.mousemove = this.mousemove.bind(this); - this.mousewheel = this.mousewheel.bind(this); - this.reset = this.reset.bind(this); - this.update = this.update.bind(this); - this.updateMovementVector = this.updateMovementVector.bind(this); - this.updateRotationVector = this.updateRotationVector.bind(this); - this.getContainerDimensions = this.getContainerDimensions.bind(this); - this.dispose = this.dispose.bind(this); - - this.domElement.addEventListener("contextmenu", function (event) { - event.preventDefault(); - }); - - this.updateMovementVector(); - this.updateRotationVector(); - } - - attachEventListeners() { - window.addEventListener("keydown", this.keydown); - window.addEventListener("keyup", this.keyup); - window.addEventListener("mousedown", this.mousedown); - window.addEventListener("mouseup", this.mouseup); - window.addEventListener("mousemove", this.mousemove); - window.addEventListener("wheel", this.mousewheel); - } - - detachEventListeners() { - window.removeEventListener("keydown", this.keydown); - window.removeEventListener("keyup", this.keyup); - window.removeEventListener("mousedown", this.mousedown); - window.removeEventListener("mouseup", this.mouseup); - window.removeEventListener("mousemove", this.mousemove); - window.removeEventListener("wheel", this.mousewheel); - } - - keydown(event: KeyboardEvent) { - if (!this.enabled || isPromptBoxFocused.value) { - return; - } - - switch (event.code) { - case "KeyW": - this.moveState.forward = 1; - break; - case "KeyS": - this.moveState.back = 1; - break; - - case "KeyA": - this.moveState.left = 1; - break; - case "KeyD": - this.moveState.right = 1; - break; - - case "KeyQ": - this.moveState.down = 1; - break; - case "KeyE": - this.moveState.up = 1; - break; - - case "ArrowUp": - this.moveState.pitchUp = 1; - break; - case "ArrowDown": - this.moveState.pitchDown = 1; - break; - - case "ArrowLeft": - this.moveState.yawLeft = 1; - break; - case "ArrowRight": - this.moveState.yawRight = 1; - break; - } - - this.updateMovementVector(); - this.updateRotationVector(); - } - - keyup(event: KeyboardEvent) { - if (!this.enabled || isPromptBoxFocused.value) { - return; - } - - switch (event.code) { - case "KeyW": - this.moveState.forward = 0; - break; - case "KeyS": - this.moveState.back = 0; - break; - - case "KeyA": - this.moveState.left = 0; - break; - case "KeyD": - this.moveState.right = 0; - break; - - case "ArrowUp": - this.moveState.pitchUp = 0; - break; - case "ArrowDown": - this.moveState.pitchDown = 0; - break; - - case "ArrowLeft": - this.moveState.yawLeft = 0; - break; - case "ArrowRight": - this.moveState.yawRight = 0; - break; - - case "KeyQ": - this.moveState.down = 0; - break; - case "KeyE": - this.moveState.up = 0; - break; - } - - this.updateMovementVector(); - this.updateRotationVector(); - } - - mousedown(event: MouseEvent) { - if (!this.enabled) { - return; - } - if (event.button === 2) { - this.dragging = true; - this.xOffset = event.clientX; - this.yOffset = event.clientY; - this.resetVelocity(); - } - } - - mouseup(event: MouseEvent) { - if (!this.enabled) { - return; - } - if (event.button === 2) { - this.xOffset = event.clientX; - this.yOffset = event.clientY; - this.resetVelocity(); - } - this.dragging = false; - } - - mousemove(event: MouseEvent) { - if (!this.enabled) { - return; - } - if (this.dragging) { - const mouseX = event.clientX - this.xOffset; - const mouseY = event.clientY - this.yOffset; - this.xOffset = event.clientX; - this.yOffset = event.clientY; - - if (Math.abs(mouseX + mouseY) > 0) { - // Store last position before movement - this.lastPosition.copy(this.object.position); - - // Apply movement with smoothing - const translationX = -mouseX * (this.movementSpeed * 0.01); - const translationY = mouseY * (this.movementSpeed * 0.01); - - this.currentVelocity.x = MathUtils.lerp( - this.currentVelocity.x, - translationX, - this.smoothingFactor, - ); - this.currentVelocity.y = MathUtils.lerp( - this.currentVelocity.y, - translationY, - this.smoothingFactor, - ); - - this.object.translateX(this.currentVelocity.x); - this.object.translateY(this.currentVelocity.y); - } - } - } - - mousewheel(event: WheelEvent) { - if (!this.enabled) { - return; - } - - // Store last position before zoom - this.lastPosition.copy(this.object.position); - - const zoomDelta = event.deltaY / 120; - this.currentVelocity.z = MathUtils.lerp( - this.currentVelocity.z, - zoomDelta, - this.smoothingFactor, - ); - this.object.translateZ(this.currentVelocity.z); - } - - resetVelocity() { - this.currentVelocity.set(0, 0, 0); - this.lastPosition.copy(this.object.position); - } - - reset() { - this.moveState.forward = 0; - this.moveState.back = 0; - this.moveState.left = 0; - this.moveState.right = 0; - this.moveState.pitchUp = 0; - this.moveState.pitchDown = 0; - this.moveState.yawLeft = 0; - this.moveState.yawRight = 0; - this.moveState.down = 0; - this.moveState.up = 0; - - this.resetVelocity(); - - this.updateMovementVector(); - this.updateRotationVector(); - } - - isStationary() { - return ( - this.moveVector.distanceTo(new Vector3(0, 0, 0)) === 0 && - this.currentVelocity.distanceTo(new Vector3(0, 0, 0)) < 0.0001 - ); - } - - isRotating() { - return ( - this.moveState.pitchUp || - this.moveState.pitchDown || - this.moveState.yawLeft || - this.moveState.yawRight - ); - } - - update(delta: number) { - if (!this.enabled) { - return; - } - - // Skip update if there's no movement, velocity, or rotation - if (this.isStationary() && !this.isRotating()) { - return; - } - - const moveMulti = delta * this.movementSpeed; - - // Apply WASD movement with smoothing - const targetMovement = new Vector3( - this.moveVector.x * moveMulti, - this.moveVector.y * moveMulti, - this.moveVector.z * moveMulti, - ); - - // Smooth out the movement - this.currentVelocity.lerp(targetMovement, this.smoothingFactor); - - // Apply the smoothed movement - this.object.translateX(this.currentVelocity.x); - this.object.translateY(this.currentVelocity.y); - this.object.translateZ(this.currentVelocity.z); - - // Store last position - this.lastPosition.copy(this.object.position); - - - // Done with positions - // Now handle rotations - this.object.rotateX(this.rotationVector.x); - this.object.rotateY(this.rotationVector.y); - this.object.rotateZ(this.rotationVector.z); - - // Update camera signals - if (selectedCameraId.value) { - const pos = this.object.position; - const rot = this.object.rotation; - - // Calculate lookAt point based on camera's forward direction - const lookAtPoint = new Vector3(0, 0, -1); - lookAtPoint.applyQuaternion(this.object.quaternion); - lookAtPoint.add(pos); - - cameras.value = cameras.value.map((cam) => - cam.id === selectedCameraId.value - ? { - ...cam, - position: { x: pos.x, y: pos.y, z: pos.z }, - rotation: { x: rot.x, y: rot.y, z: rot.z }, - lookAt: { x: lookAtPoint.x, y: lookAtPoint.y, z: lookAtPoint.z }, - } - : cam, - ); - } - } - - updateMovementVector() { - const forward = - this.moveState.forward || (this.autoForward && !this.moveState.back) - ? 1 - : 0; - - this.moveVector.x = -this.moveState.left + this.moveState.right; - this.moveVector.y = -this.moveState.down + this.moveState.up; - this.moveVector.z = -forward + this.moveState.back; - } - - updateRotationVector() { - this.rotationVector.x = -this.moveState.pitchDown + this.moveState.pitchUp; - this.rotationVector.y = -this.moveState.yawRight + this.moveState.yawLeft; - this.rotationVector.z = -this.moveState.rollRight + this.moveState.rollLeft; - this.rotationVector.multiplyScalar(this.rollSpeed); - } - - getContainerDimensions() { - if (this.domElement !== document) { - return { - size: [ - (this.domElement as HTMLElement).offsetWidth, - (this.domElement as HTMLElement).offsetHeight, - ], - offset: [ - (this.domElement as HTMLElement).offsetLeft, - (this.domElement as HTMLElement).offsetTop, - ], - }; - } else { - return { - size: [window.innerWidth, window.innerHeight], - offset: [0, 0], - }; - } - } - - dispose() { - // window.addEventListener('contextmenu', function(event) { - // event.preventDefault(); - // }); - - // this.domElement.removeEventListener("contextmenu", this.contextmenu); - // this.domElement.removeEventListener("pointerdown", this.pointerdown); - // this.domElement.removeEventListener("pointermove", this.pointermove); - // this.domElement.removeEventListener("pointerup", this.pointerup); - // this.domElement.removeEventListener("pointercancel", this.pointercancel); - // - window.removeEventListener("keydown", this.keydown); - window.removeEventListener("keyup", this.keyup); - } -} - -export { FreeCam }; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/helper.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/helper.ts deleted file mode 100644 index 4dec0953d6..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/helper.ts +++ /dev/null @@ -1,299 +0,0 @@ -import Scene from "./scene"; -import Editor from "./editor"; -import * as THREE from "three"; -import { XYZ } from "../datastructures/common"; -import { editorState } from "../signals/engine"; -import { EditorStates } from "../enums"; -import Queue from "~/pages/PageEnigma/Queue/Queue"; -import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; -import { fromEngineActions } from "~/pages/PageEnigma/Queue/fromEngineActions"; -import { MediaItem } from "~/pages/PageEnigma/models"; -import { AssetType } from "~/enums"; -import { hideObjectPanel } from "../signals"; - -export class SceneUtils { - scene: Scene; - editor: Editor; - constructor(editor: Editor, scene: Scene) { - this.scene = scene; - this.editor = editor; - } - - // If string is empty. - isEmpty(value: string): boolean { - return ( - value == null || (typeof value === "string" && value.trim().length === 0) - ); - } - - // For react to see if an object has lipsync capability or not. - isObjectLipsync(object_uuid: string) { - const object = this.scene.get_object_by_uuid(object_uuid); - let hasLipsync = false; - if (object) { - object.traverse((c: THREE.Object3D) => { - if (c instanceof THREE.Mesh) { - if (c.morphTargetInfluences && c.morphTargetDictionary) { - const blendShapeIndexE = c.morphTargetDictionary["E"]; - // console.log(c.morphTargetDictionary, blendShapeIndexE) - if (blendShapeIndexE !== null) { - hasLipsync = true; - } - } - } - }); - } - return hasLipsync; - } - - // Returns if the object is locked or unlocked. - isObjectLocked(object_uuid: string): boolean { - const object = this.scene.get_object_by_uuid(object_uuid); - if (object) { - if (object.userData["locked"] == undefined) { - object.userData["locked"] = false; - } - return object.userData["locked"]; - } - //console.log("No object found."); - return false; - } - - // Locks or unlocks and object and returns its new state, - lockUnlockObject(object_uuid: string): boolean { - const object = this.scene.get_object_by_uuid(object_uuid); - if (object) { - if (object.userData["locked"] == undefined) { - object.userData["locked"] = false; - } - object.userData["locked"] = !object.userData["locked"]; - - if (object.userData["locked"]) { - this.removeTransformControls(false); - } else if (this.editor.control) { - this.scene.scene.add(this.editor.control); - if (this.editor.sceneManager?.selected_objects) - this.editor.control.attach( - this.editor.sceneManager?.selected_objects[0], - ); - } - - return object.userData["locked"]; - } - //console.log("No object found."); - return false; - } - - // Removes transform controls and publishes selected. - removeTransformControls(remove_outline: boolean = true) { - if (this.editor.control == undefined) { - return; - } - if (this.editor.outlinePass == undefined) { - return; - } - if (remove_outline) { - this.editor.last_selected = this.editor.selected; - this.editor.outlinePass.selectedObjects = []; - this.editor.publishSelect(); - } - this.editor.control.detach(); - this.editor.activeScene.scene.remove(this.editor.control); - if (remove_outline) this.editor.outlinePass.selectedObjects = []; - } - - // TO UPDATE selected objects in the scene might want to add to the scene ... - async setSelectedObject(position: XYZ, rotation: XYZ, scale: XYZ) { - if (this.editor.sceneManager?.selected_objects) { - const object = this.editor.sceneManager?.selected_objects[0]; - if (object != undefined || object != null) { - object.position.x = position.x; - object.position.y = position.y; - object.position.z = position.z; - object.rotation.x = THREE.MathUtils.degToRad(rotation.x); - object.rotation.y = THREE.MathUtils.degToRad(rotation.y); - object.rotation.z = THREE.MathUtils.degToRad(rotation.z); - object.scale.x = scale.x; - object.scale.y = scale.y; - object.scale.z = scale.z; - } - } - } - - switchCameraView() { - this.editor.camera_person_mode = !this.editor.camera_person_mode; - this.editor.cameraViewControls?.reset(); - if (this.editor.cam_obj) { - if (this.editor.camera_person_mode && this.editor.camera) { - this.editor.last_cam_pos.copy(this.editor.camera.position); - this.editor.last_cam_rot.copy(this.editor.camera.rotation); - - this.editor.camera.position.copy(this.editor.cam_obj.position); - this.editor.camera.rotation.copy(this.editor.cam_obj.rotation); - - if (this.editor.lockControls) { - this.editor.activeScene.scene.add( - this.editor.lockControls.getObject(), - ); - } - if (this.editor.cameraViewControls) { - this.editor.cameraViewControls.enabled = true; - } - this.editor.cam_obj.scale.set(0, 0, 0); - - this.removeTransformControls(); - this.editor.selected = this.editor.cam_obj; - this.editor.publishSelect(); - this.editor.updateSelectedUI(); - editorState.value = EditorStates.CAMERA_VIEW; - if (this.editor.activeScene.hot_items) { - this.editor.activeScene.hot_items.forEach((element) => { - element.visible = false; - }); - } - - // Make a better solution later but for now this is so that in camera mode when you right click it does not bring up the context menu and allows you to pan. - setTimeout( - () => - document - .getElementById("letterbox") - ?.addEventListener("contextmenu", function (event) { - event.preventDefault(); - }), - 250, - ); - } else if (this.editor.camera) { - this.editor.camera.position.copy(this.editor.last_cam_pos); - this.editor.camera.rotation.copy(this.editor.last_cam_rot); - if (this.editor.lockControls) { - this.editor.activeScene.scene.remove( - this.editor.lockControls.getObject(), - ); - } - this.editor.cam_obj.scale.set(1, 1, 1); - if (this.editor.activeScene.hot_items) { - this.editor.activeScene.hot_items.forEach((element) => { - element.visible = true; - }); - } - - hideObjectPanel(); - editorState.value = EditorStates.EDIT; - } - } - } - - // Returns the "check sum" of the editors selected object. - getSelectedSum(): number { - if (this.editor.sceneManager?.selected_objects === undefined) { - return 0; - } - if (this.editor.sceneManager?.selected_objects.length <= 0) { - return 0; - } - const posCombo = - this.editor.sceneManager?.selected_objects[0].position.x + - this.editor.sceneManager?.selected_objects[0].position.y + - this.editor.sceneManager?.selected_objects[0].position.z; - const rotCombo = - this.editor.sceneManager?.selected_objects[0].rotation.x + - this.editor.sceneManager?.selected_objects[0].rotation.y + - this.editor.sceneManager?.selected_objects[0].rotation.z; - const sclCombo = - this.editor.sceneManager?.selected_objects[0].scale.x + - this.editor.sceneManager?.selected_objects[0].scale.y + - this.editor.sceneManager?.selected_objects[0].scale.z; - return posCombo + rotCombo + sclCombo; - } - - /* Will add in the future - -A good practice to remove 3D objects from Three.js scenes -function removeObject3D(object3D) { - if (!(object3D instanceof THREE.Object3D)) return false; - - // for better memory management and performance - if (object3D.geometry) object3D.geometry.dispose(); - - if (object3D.material) { - if (object3D.material instanceof Array) { - // for better memory management and performance - object3D.material.forEach(material => material.dispose()); - } else { - // for better memory management and performance - object3D.material.dispose(); - } - } - object3D.removeFromParent(); // the parent might be the scene or another Object3D, but it is sure to be removed this way - return true; -} - - */ - - deleteObject(uuid: string) { - const obj = this.scene.get_object_by_uuid(uuid); - - if (!obj) { - return - } - - this.removeTransformControls(); - if (obj.name === this.editor.camera_name) { - return; - } - - // Finally remove the object from the scene - this.scene.scene.remove(obj); - - obj.traverse(child => { - (child as THREE.Mesh)?.geometry?.dispose() - if (Array.isArray((child as THREE.Mesh).texture)) { - (child as THREE.Mesh).texture.forEach(mat => mat.dispose()); - } else if ((child as THREE.Mesh).texture) { - (child as THREE.Mesh).texture.dispose(); - } - - if (Array.isArray((child as THREE.Mesh).material)) { - (child as THREE.Mesh).material.forEach(mat => mat.dispose()); - } else if ((child as THREE.Mesh).material) { - (child as THREE.Mesh).material.dispose(); - } - }) - - if (Array.isArray((obj as THREE.Mesh).texture)) { - (obj as THREE.Mesh).texture.forEach(mat => mat.dispose()); - } else if ((obj as THREE.Mesh).texture) { - (obj as THREE.Mesh).texture.dispose(); - } - - if (Array.isArray((obj as THREE.Mesh).material)) { - (obj as THREE.Mesh).material.forEach(mat => mat.dispose()); - } else if ((obj as THREE.Mesh).material) { - (obj as THREE.Mesh).material.dispose(); - } - - if ((obj as THREE.Mesh).geometry) { - (obj as THREE.Mesh).geometry.dispose() - } - - // TODO - // FIXME: Timeline deletion is called twice, as well as a queue event. - // Why is this necessary? - this.editor.timeline.deleteObject(obj); - Queue.publish({ - queueName: QueueNames.FROM_ENGINE, - action: fromEngineActions.DELETE_OBJECT, - data: { - version: 1, - type: AssetType.OBJECT, - media_id: "", - object_uuid: uuid, - name: "", - } as MediaItem, - }); - this.editor.selected = undefined; - this.editor.publishSelect(); - hideObjectPanel(); - this.editor.timeline.deleteObject(obj); - } -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/lipsync.js b/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/lipsync.js deleted file mode 100644 index a0b59ea522..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/lipsync.js +++ /dev/null @@ -1,239 +0,0 @@ -const BoundingFrequencyMasc = [0, 400, 560, 2400, 4800]; -const BoundingFrequencyFem = [0, 500, 700, 3000, 6000]; -const IndicesFrequencyFemale = []; -const IndicesFrequencyMale = []; -const FFT_SIZE = 1024; -const samplingFrequency = 44100; - -for (let m = 0; m < BoundingFrequencyMasc.length; m++) { - IndicesFrequencyMale[m] = Math.round( - ((2 * FFT_SIZE) / samplingFrequency) * BoundingFrequencyMasc[m], - ); -} - -for (let m = 0; m < BoundingFrequencyFem.length; m++) { - IndicesFrequencyFemale[m] = Math.round( - ((2 * FFT_SIZE) / samplingFrequency) * BoundingFrequencyFem[m], - ); -} - -export class LipSync { - constructor(face) { - this.face = face; - this.buffer = null; - this.last_frame = 0; - - // WE NEED THIS TO be run at RUNTIME - // we have to modify this for our - // this.ee = this.face.morphTargetDictionary["E"]; - // this.oh = this.face.morphTargetDictionary["O"]; - // this.ah = this.face.morphTargetDictionary["aa"]; - } - - // for streaming sound? - start(stream) { - this.audioContext = new AudioContext(); - this.mediaStreamSource = this.audioContext.createMediaStreamSource(stream); - this.meter = LipSync.createAudioMeter(this.audioContext); - this.mediaStreamSource.connect(this.meter); - } - - // testing only - startFromAudioFile(file) { - if (!this.audioContext) this.audioContext = new AudioContext(); - - if (!this.userSpeechAnalyzer) - this.userSpeechAnalyzer = this.audioContext.createAnalyser(); - this.userSpeechAnalyzer.smoothingTimeConstant = 0.5; - this.userSpeechAnalyzer.fftSize = FFT_SIZE; - - if (this.mediaStreamSource) this.mediaStreamSource.stop(); - - this.audioContext.decodeAudioData(file).then((buffer) => { - this.mediaStreamSource = this.audioContext.createBufferSource(); - this.mediaStreamSource.buffer = buffer; - this.meter = LipSync.createAudioMeter(this.audioContext); - this.mediaStreamSource.connect(this.meter); - this.mediaStreamSource.connect(this.audioContext.destination); - this.mediaStreamSource.start(); - - // connect the output of mediaStreamSource to the input of userSpeechAnalyzer - this.mediaStreamSource.connect(this.userSpeechAnalyzer); - }); - } - - startLipSyncFromAudioBuffer(buffer) { - if (!this.audioContext) this.audioContext = new AudioContext(); - if (!this.userSpeechAnalyzer) - this.userSpeechAnalyzer = this.audioContext.createAnalyser(); - this.userSpeechAnalyzer.smoothingTimeConstant = 0.5; - this.userSpeechAnalyzer.fftSize = FFT_SIZE; - - if (this.mediaStreamSource) this.mediaStreamSource.stop(); - - this.buffer = buffer; - } - - destroy() { - this.meter?.shutdown(); - this.meter = null; - this.mediaStreamSource?.disconnect(); - return this.audioContext?.close().catch(() => {}) || Promise.resolve(); - } - - update(frame, offset, rendering) { - const frameBuffer = 12; - - let pos = frame - offset; - - if (Math.floor(pos) <= 1){ - this.last_frame = 0; - } - - let doPlay = true; - if ( - Math.abs(this.last_frame - frame) < frameBuffer - 1 && - this.last_frame !== 0 - ) { - doPlay = false; - } - - if (doPlay) { - const startTime = pos / 60.0; - const endTime = frameBuffer / 60.0; - this.mediaStreamSource = this.audioContext.createBufferSource(); - this.mediaStreamSource.buffer = this.buffer; - this.meter = LipSync.createAudioMeter(this.audioContext); - this.mediaStreamSource.connect(this.meter); - this.mediaStreamSource.connect(this.audioContext.destination); - this.mediaStreamSource.start(0, startTime, endTime); - this.mediaStreamSource.connect(this.userSpeechAnalyzer); - this.last_frame = frame; - } - - if (this.meter) { - const { volume } = this.meter; - let { ah, oh, ee } = this.update2(); - if (volume < 0.01) { - ah = 0; - oh = 0; - ee = 0; - } - return { ah: ah, oh: oh, ee: ee }; - } - return { ah: 0, oh: 0, ee: 0 }; - } - - update2() { - function getSensitivityMap(spectrum) { - const sensitivity_threshold = 0.5; - const stPSD = new Float32Array(spectrum.length); - for (let i = 0; i < spectrum.length; i++) { - stPSD[i] = sensitivity_threshold + (spectrum[i] + 20) / 140; - } - return stPSD; - } - - const spectrum = new Float32Array( - this.userSpeechAnalyzer.frequencyBinCount, - ); - // Populate frequency data for computing frequency intensities - this.userSpeechAnalyzer.getFloatFrequencyData(spectrum); // getByteTimeDomainData gets volumes over the sample time - // Populate time domain for calculating RMS - // userSpeechAnalyzer.getFloatTimeDomainData(spectrum); - // RMS (root mean square) is a better approximation of current input level than peak (just sampling this frame) - // spectrumRMS = getRMS(spectrum); - - const sensitivityPerPole = getSensitivityMap(spectrum); - - // Lower and higher voices have different frequency domains, so we'll separate and max them - const EnergyBinMasc = new Float32Array(BoundingFrequencyMasc.length); - const EnergyBinFem = new Float32Array(BoundingFrequencyFem.length); - - // Masc energy bins (groups of frequency-depending energy) - for (let m = 0; m < BoundingFrequencyMasc.length - 1; m++) { - for ( - let j = IndicesFrequencyMale[m]; - j <= IndicesFrequencyMale[m + 1]; - j++ - ) - if (sensitivityPerPole[j] > 0) - EnergyBinMasc[m] += sensitivityPerPole[j]; - EnergyBinMasc[m] /= IndicesFrequencyMale[m + 1] - IndicesFrequencyMale[m]; - } - - // Fem energy bin - for (let m = 0; m < BoundingFrequencyFem.length - 1; m++) { - for ( - let j = IndicesFrequencyMale[m]; - j <= IndicesFrequencyMale[m + 1]; - j++ - ) - if (sensitivityPerPole[j] > 0) EnergyBinFem[m] += sensitivityPerPole[j]; - EnergyBinMasc[m] /= IndicesFrequencyMale[m + 1] - IndicesFrequencyMale[m]; - EnergyBinFem[m] = - EnergyBinFem[m] / - (IndicesFrequencyFemale[m + 1] - IndicesFrequencyFemale[m]); - } - const oh = - Math.max(EnergyBinFem[1], EnergyBinMasc[1]) > 0.2 - ? 1 - 2 * Math.max(EnergyBinMasc[2], EnergyBinFem[2]) - : (1 - 2 * Math.max(EnergyBinMasc[2], EnergyBinFem[2])) * - 5 * - Math.max(EnergyBinMasc[1], EnergyBinFem[1]); - - const ah = 3 * Math.max(EnergyBinMasc[3], EnergyBinFem[3]); - const ee = - 0.8 * - (Math.max(EnergyBinMasc[1], EnergyBinFem[1]) - - Math.max(EnergyBinMasc[3], EnergyBinFem[3])); - return { oh, ee, ah }; - } - - static createAudioMeter(audioContext) { - const processor = audioContext.createScriptProcessor(512); - processor.onaudioprocess = (event) => { - const buf = event.inputBuffer.getChannelData(0); - const bufLength = buf.length; - let sum = 0; - let x; - - // eslint-disable-next-line no-plusplus - for (let i = 0; i < bufLength; i++) { - x = buf[i]; - if (Math.abs(x) >= processor.clipLevel) { - processor.clipping = true; - processor.lastClip = window.performance.now(); - } - sum += x * x; - } - const rms = Math.sqrt(sum / bufLength); - processor.volume = Math.max(rms, processor.volume * processor.averaging); - }; - processor.clipping = false; - processor.lastClip = 0; - processor.volume = 0; - processor.clipLevel = 0.98; - processor.averaging = 0.95; - processor.clipLag = 750; - - processor.connect(audioContext.destination); - - processor.checkClipping = () => { - if (!processor.clipping) { - return false; - } - if (processor.lastClip + processor.clipLag < window.performance.now()) { - processor.clipping = false; - } - return processor.clipping; - }; - - processor.shutdown = () => { - processor.disconnect(); - processor.onaudioprocess = null; - }; - - return processor; - } -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/preview_video_generation.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/preview_video_generation.ts deleted file mode 100644 index d16ab9151b..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/preview_video_generation.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { MediaUploadApi, VideoApi } from "~/Classes/ApiManager"; - -import { - ProcessStatus, - StreamingProgressResponse, - VideoAudioPreProcessor, -} from "./VideoProcessor/video_audio_preprocessor"; - -class PreviewVideoGeneration { - mediaUploadAPI: MediaUploadApi; - videoAPI: VideoApi; - videoAndAudioPreProcessor: VideoAudioPreProcessor; - - onProgress: (response: StreamingProgressResponse) => void; - - constructor( - onProgress: (response: StreamingProgressResponse) => void, - ) { - this.onProgress = onProgress; - this.videoAndAudioPreProcessor = new VideoAudioPreProcessor(onProgress); - this.mediaUploadAPI = new MediaUploadApi(); - this.videoAPI = new VideoApi(); - } - - // Cancel any part of the processing - public async cancel() {} - - private async stageOneProcessing() {} - - private async stageTwoProcessing() {} - - private async processVideo() { - // check if video is cached or not if not jump to 50% - // collect frames and run preprocessing in the background. - // start polling for percentage change and convert and remap to remainint % - } - - async clearFrameBuffers() {} -} - -export { PreviewVideoGeneration }; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/retargeting.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/retargeting.ts deleted file mode 100644 index 5fc6977c7b..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/retargeting.ts +++ /dev/null @@ -1,97 +0,0 @@ -export class MoveAIResult { - bone: string; - is_special: boolean; - x: number; - y: number; - z: number; - flip: boolean; - only_y: boolean; - - constructor(bone: string, is_special: boolean = false, x: number = 0, y: number = 0, z: number = 0, - flip:boolean=false, only_y:boolean=false) { - this.bone = bone; - this.is_special = is_special; - this.x = x; - this.y = y; - this.z = z; - this.flip = flip; - this.only_y = only_y; - } -} - -export class Retarget { - move_ai_options: { [Key: string]: MoveAIResult } - constructor() { - this.move_ai_options = { - "_1Head.quaternion": new MoveAIResult("mixamorigHead.quaternion"), - "_1Neck.quaternion": new MoveAIResult("mixamorigNeck.quaternion"), - "_Spine.quaternion": new MoveAIResult("mixamorigSpine.quaternion"), - "_1Spine.quaternion": new MoveAIResult("mixamorigSpine1.quaternion"), - "_3Spine.quaternion": new MoveAIResult("mixamorigSpine2.quaternion"), - - "_1Hips.quaternion": new MoveAIResult("mixamorigHips.quaternion", true, -90, 0, 0, false, true), - "_1Hips.position": new MoveAIResult("mixamorigHips.position", true, 1, 0.05, 1), - - "_1LeftFoot.quaternion": new MoveAIResult("mixamorigLeftFoot.quaternion", true, 0, 0, 0, true), - "_1RightFoot.quaternion": new MoveAIResult("mixamorigRightFoot.quaternion", true, 0, 0, 0, true), - - - "_1RightShoulder.quaternion": new MoveAIResult("mixamorigRightShoulder.quaternion", true, 0, 0, 0, false), - "_1LeftShoulder.quaternion": new MoveAIResult("mixamorigLeftShoulder.quaternion", true, 0, 0, 0, false), - "_1LeftArm.quaternion": new MoveAIResult("mixamorigLeftArm.quaternion", true, 0, 0, 0, false), - "_1RightArm.quaternion": new MoveAIResult("mixamorigRightArm.quaternion", true, 0, 0, 0, false), - "_1LeftForeArm.quaternion": new MoveAIResult("mixamorigLeftForeArm.quaternion"), - "_1RightForeArm.quaternion": new MoveAIResult("mixamorigRightForeArm.quaternion"), - - "_1LeftLeg.quaternion": new MoveAIResult("mixamorigLeftLeg.quaternion", true, 0, 0, 0, true), - "_1RightLeg.quaternion": new MoveAIResult("mixamorigRightLeg.quaternion", true, 0, 0, 0, true), - - "_1LeftUpLeg.quaternion": new MoveAIResult("mixamorigLeftUpLeg.quaternion", true, 0, 180, 0), - "_1RightUpLeg.quaternion": new MoveAIResult("mixamorigRightUpLeg.quaternion", true, 0, 180, 0), - - "_1LeftHand.quaternion": new MoveAIResult("mixamorigLeftHand.quaternion"), - "_1RightHand.quaternion": new MoveAIResult("mixamorigRightHand.quaternion"), - - "_1LeftHandThumb1.quaternion": new MoveAIResult("mixamorigLeftHandThumb1.quaternion"), - "_1LeftHandThumb2.quaternion": new MoveAIResult("mixamorigLeftHandThumb2.quaternion"), - "_1LeftHandThumb3.quaternion": new MoveAIResult("mixamorigLeftHandThumb3.quaternion"), - "_1LeftHandIndex1.quaternion": new MoveAIResult("mixamorigLeftHandIndex1.quaternion"), - "_1LeftHandIndex2.quaternion": new MoveAIResult("mixamorigLeftHandIndex2.quaternion"), - "_1LeftHandIndex3.quaternion": new MoveAIResult("mixamorigLeftHandIndex3.quaternion"), - "_1LeftHandMiddle1.quaternion": new MoveAIResult("mixamorigLeftHandMiddle1.quaternion"), - "_1LeftHandMiddle2.quaternion": new MoveAIResult("mixamorigLeftHandMiddle2.quaternion"), - "_1LeftHandMiddle3.quaternion": new MoveAIResult("mixamorigLeftHandMiddle3.quaternion"), - "_1LeftHandRing1.quaternion": new MoveAIResult("mixamorigLeftHandRing1.quaternion"), - "_1LeftHandRing2.quaternion": new MoveAIResult("mixamorigLeftHandRing2.quaternion"), - "_1LeftHandRing3.quaternion": new MoveAIResult("mixamorigLeftHandRing3.quaternion"), - "_1LeftHandPinky1.quaternion": new MoveAIResult("mixamorigLeftHandPinky1.quaternion"), - "_1LeftHandPinky2.quaternion": new MoveAIResult("mixamorigLeftHandPinky2.quaternion"), - "_1LeftHandPinky3.quaternion": new MoveAIResult("mixamorigLeftHandPinky3.quaternion"), - "_1RightHandThumb1.quaternion": new MoveAIResult("mixamorigRightHandThumb1.quaternion"), - "_1RightHandThumb2.quaternion": new MoveAIResult("mixamorigRightHandThumb2.quaternion"), - "_1RightHandThumb3.quaternion": new MoveAIResult("mixamorigRightHandThumb3.quaternion"), - "_1RightHandIndex1.quaternion": new MoveAIResult("mixamorigRightHandIndex1.quaternion"), - "_1RightHandIndex2.quaternion": new MoveAIResult("mixamorigRightHandIndex2.quaternion"), - "_1RightHandIndex3.quaternion": new MoveAIResult("mixamorigRightHandIndex3.quaternion"), - "_1RightHandMiddle1.quaternion": new MoveAIResult("mixamorigRightHandMiddle1.quaternion"), - "_1RightHandMiddle2.quaternion": new MoveAIResult("mixamorigRightHandMiddle2.quaternion"), - "_1RightHandMiddle3.quaternion": new MoveAIResult("mixamorigRightHandMiddle3.quaternion"), - "_1RightHandRing1.quaternion": new MoveAIResult("mixamorigRightHandRing1.quaternion"), - "_1RightHandRing2.quaternion": new MoveAIResult("mixamorigRightHandRing2.quaternion"), - "_1RightHandRing3.quaternion": new MoveAIResult("mixamorigRightHandRing3.quaternion"), - "_1RightHandPinky1.quaternion": new MoveAIResult("mixamorigRightHandPinky1.quaternion"), - "_1RightHandPinky2.quaternion": new MoveAIResult("mixamorigRightHandPinky2.quaternion"), - "_1RightHandPinky3.quaternion": new MoveAIResult("mixamorigRightHandPinky3.quaternion"), - } - - } - - retarget(boneName: string): MoveAIResult { - for (const [key, value] of Object.entries(this.move_ai_options)) { - if (boneName == key) { - return value; - } - } - return new MoveAIResult(boneName); - } -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/save_manager.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/save_manager.ts deleted file mode 100644 index 86da0f9de5..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/save_manager.ts +++ /dev/null @@ -1,303 +0,0 @@ -import * as THREE from "three"; -import { SceneGenereationMetaData } from "../models/sceneGenerationMetadata"; -import { StoryTellerProxyScene } from "../proxy/storyteller_proxy_scene"; - -import { - setCameraAspectRatio, - hideEditorLoader, - restoreSceneGenerationMetadata, - showEditorLoader, - globalIPAMediaToken, -} from "../signals"; -import Editor from "./editor"; -import { getArtStyle } from "~/enums"; -import { cameras, selectedCameraId, Camera } from "../signals/camera"; - -export type EditorInitializeConfig = { - sceneToken: string; -}; - -// need to move this into the -export class SaveManager { - editor: Editor; - constructor(editor: Editor) { - this.editor = editor; - } - - async computeHashForBrowser(data: string): Promise { - // Encode the string data to a Uint8Array - const encoder = new TextEncoder(); - const encodedData = encoder.encode(data); - - // Compute the hash using the SubtleCrypto.digest method - const hashBuffer = await crypto.subtle.digest("SHA-256", encodedData); - - // Convert the ArrayBuffer to a hexadecimal string - const hashArray = Array.from(new Uint8Array(hashBuffer)); - const hashHex = hashArray - .map((b) => b.toString(16).padStart(2, "0")) - .join(""); - - return hashHex; - } - - public async computeSceneChecksum(): Promise { - const jsonString = await this.checkSumData(); - return this.computeHashForBrowser(jsonString); - } - - private async checkSumData(): Promise { - const proxyScene = new StoryTellerProxyScene( - this.editor.version, - this.editor.activeScene, - ); - const scene_json = await proxyScene.saveToScene(this.editor.version); - - const check_sum_data = { - scene: scene_json, - timeline: "", - camera_data: { - position: this.editor.camera?.position, - rotation: this.editor.camera?.rotation, - }, - }; - const jsonString = JSON.stringify(check_sum_data); - return jsonString; - } - - // JSON structure should and can return snapshot - public async saveData({ - sceneGenerationMetadata, - }: { - sceneTitle: string; - sceneToken?: string; - sceneGenerationMetadata: SceneGenereationMetaData; - }): Promise { - const proxyScene = new StoryTellerProxyScene( - this.editor.version, - this.editor.activeScene, - ); - const scene_json = await proxyScene.saveToScene(this.editor.version); - - // Save all cameras data - const camerasData = cameras.value.map((cam: Camera) => ({ - id: cam.id, - label: cam.label, - focalLength: cam.focalLength, - position: cam.position, - rotation: cam.rotation, - lookAt: cam.lookAt, - })); - - const save_data = { - version: this.editor.version, - scene: scene_json, - ...sceneGenerationMetadata, - timeline: "", - skybox: this.editor.activeScene.skybox, - camera_data: { - position: this.editor.camera?.position, - rotation: this.editor.camera?.rotation, - }, - // Add cameras array and selected camera - cameras: camerasData, - selectedCameraId: selectedCameraId.value, - }; - // take json scene and figure out checksum - const jsonString = JSON.stringify(save_data); - return jsonString; - } - - public getSceneJson({ - sceneGenerationMetadata, - }: { - sceneGenerationMetadata: SceneGenereationMetaData; - }) { - const proxyScene = new StoryTellerProxyScene( - this.editor.version, - this.editor.activeScene, - ); - const scene_json = proxyScene.saveToScene(this.editor.version); - console.log(scene_json); - - // Save all cameras data - const camerasData = cameras.value.map((cam: Camera) => ({ - id: cam.id, - label: cam.label, - focalLength: cam.focalLength, - position: cam.position, - rotation: cam.rotation, - lookAt: cam.lookAt, - })); - - const save_data = { - version: this.editor.version, - scene: scene_json, - ...sceneGenerationMetadata, - timeline: "", - skybox: this.editor.activeScene.skybox, - camera_data: { - position: this.editor.camera?.position, - rotation: this.editor.camera?.rotation, - }, - // Add cameras array and selected camera - cameras: camerasData, - selectedCameraId: selectedCameraId.value, - }; - - return save_data; - } - - // TODO Move this function into scene manager. - public async saveScene({ - sceneTitle, - sceneToken, - sceneGenerationMetadata, - }: { - sceneTitle: string; - sceneToken?: string; - sceneGenerationMetadata: SceneGenereationMetaData; - }): Promise { - this.editor.generating_preview = true; // Set this to true to stop control panel from flipping out. - // remove controls when saving scene. - this.editor.utils.removeTransformControls(); - showEditorLoader(); - - const sceneJson = this.getSceneJson({ - sceneGenerationMetadata, - }); - - // TODO turn scene information into and object ... - let sceneThumbnail = undefined; - - if (this.editor.renderer) { - const imgData = this.editor.renderer.domElement.toDataURL(); - const response = await fetch(imgData); // Fetch the data URL - sceneThumbnail = await response.blob(); // Convert to Blob - } - - const result = await this.editor.api_manager.saveSceneState({ - saveJson: JSON.stringify(sceneJson), - sceneTitle, - sceneToken, - sceneThumbnail, - }); - - // this means error going to have to fix the plumbing on this because not using - // the api manager - - hideEditorLoader(); - - this.editor.generating_preview = false; // FIX THIS LATER WITH VICCCCCCCCCCCCCCCTORRRRRRRR - - console.debug("Save Scene Result: ", result); - return result; // if this is an empty string it is an error. need to migrate to api manager. - } - - private async loadFromJson(scene_json: any) { - const proxyScene = new StoryTellerProxyScene( - this.editor.version, - this.editor.activeScene, - ); - - await proxyScene.loadFromSceneJson( - scene_json["scene"], - scene_json["skybox"], - scene_json["version"], - ); - - const camera_data = scene_json["camera_data"]; - if (camera_data && this.editor.camera) { - const camera_position: THREE.Vector3 = camera_data["position"]; - const camera_rotation: THREE.Euler = camera_data["rotation"]; - - this.editor.camera.position.copy(camera_position); - this.editor.camera.rotation.copy(camera_rotation); - } - - // Restore cameras data - if (scene_json.cameras) { - cameras.value = scene_json.cameras.map((cam: Camera) => ({ - id: cam.id, - label: cam.label, - focalLength: cam.focalLength, - position: cam.position, - rotation: cam.rotation, - lookAt: cam.lookAt, - })); - } - - // Restore selected camera - if (scene_json.selectedCameraId) { - selectedCameraId.value = scene_json.selectedCameraId; - } - - // For Remixing Scenes. - // this calls the signal function to propagate the data to the UI - restoreSceneGenerationMetadata(scene_json); - // these propogate the values into the editor - if (scene_json.globalIpAdapterImageMediaToken) { - // this should be populated right after - globalIPAMediaToken.value = scene_json.globalIpAdapterImageMediaToken; - } - if (scene_json.positivePrompt) { - this.editor.positive_prompt = scene_json.positivePrompt; - } - if (scene_json.negativePrompt) { - this.editor.negative_prompt = scene_json.negativePrompt; - } - if (scene_json.artisticStyle) { - this.editor.art_style = getArtStyle(scene_json["artisticStyle"]); - } - if (scene_json.cameraAspectRatio) { - //editor propagation - this.editor.changeRenderCameraAspectRatio(scene_json.cameraAspectRatio); - //signal propagation - setCameraAspectRatio(scene_json.cameraAspectRatio); - } - - this.editor.version = scene_json["version"]; - this.editor.cam_obj = this.editor.activeScene.get_object_by_name( - this.editor.camera_name, - ); - - this.editor.cam_obj?.layers.set(1); - this.editor.cam_obj?.children.forEach((child) => { - child.layers.set(1); - }); - - console.debug("Loading Timeline", scene_json["timeline"]); - - this.editor.timeline.checkEditorCanPlay(); - } - - public async loadCache(cacheJson: string) { - showEditorLoader(); - - const scene_json = JSON.parse(cacheJson); - await this.loadFromJson(scene_json); - - hideEditorLoader(); - this.editor.timeline.scrub({ currentTime: 0 }); - } - - // TODO Refactor remove editor. - public async loadScene(scene_media_token: string) { - showEditorLoader(); - - this.editor.current_scene_media_token = scene_media_token; - - const scene_json = await this.editor.api_manager - .loadSceneState(this.editor.current_scene_media_token) - .catch((err) => { - hideEditorLoader(); - throw err; - }); - - await this.loadFromJson(scene_json); - - hideEditorLoader(); - // TODO figure out if this is a bug. - this.editor.timeline.scrub({ currentTime: 0 }); - } -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/scene_manager_api.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/scene_manager_api.ts deleted file mode 100644 index 9e134d9b1a..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/scene_manager_api.ts +++ /dev/null @@ -1,577 +0,0 @@ -import * as THREE from "three"; -import { - IconDefinition, - faCamera, - faCube, - faPerson, -} from "@fortawesome/pro-solid-svg-icons"; -import Scene from "./scene"; -import { MouseControls } from "./keybinds_controls"; -import { ClipGroup } from "~/enums"; -import Queue from "~/pages/PageEnigma/Queue/Queue"; -import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; -import { fromEngineActions } from "~/pages/PageEnigma/Queue/fromEngineActions"; -import { MediaItem } from "~/pages/PageEnigma/models"; -import { AssetType } from "~/enums"; -import { - CommandInputTypes, - CreationSceneItem, - DeletionData, - DeletionSceneItem, - ICommand, - SceneState, - TransformData, - TransformSceneItem, - UserData, - UserDataSceneItem, -} from "./Commands"; - -export type SceneObject = { - id: string; - icon: IconDefinition; - name: string; - type: string; - visible: boolean; - locked: boolean; -}; - -export interface SceneManagerAPI { - create(media_token: string, name: string, position: THREE.Vector3): void; - retrieve(object_uuid: string): void; - delete(object_uuid: string): void; - update( - object_uuid: string, - position: THREE.Vector3, - rotation: THREE.Euler, - scale: THREE.Vector3, - ): void; - undo(): void; - redo(): void; - selected(): void; - render_outliner(timeline_characters: { [key: string]: ClipGroup }): void; - onMouseMove(event: MouseEvent): void; - onMouseClick(): void; - onKeyDown(event: KeyboardEvent): void; - onMouseDown(event: MouseEvent): void; - onMouseUp(event: MouseEvent): void; - select_object(id: string): void; -} - -type UpdateOutliner = () => void; - -export class SceneManager implements SceneManagerAPI { - scene: Scene; - mouse_controls: MouseControls; - version: number; - selected_objects: THREE.Object3D[] | undefined; - private updateOutliner: UpdateOutliner; - public undoStack: ICommand[] = []; - private undoIndex: number = 0; - private lastSceneState: SceneState; - private copiedObject: THREE.Object3D | undefined; - private is_character: Function; - private devMode: boolean; - - constructor( - version: number, - mouse_controls: MouseControls, - scene: Scene, - devMode: boolean = false, - updateOutliner: UpdateOutliner, - is_character: Function, - ) { - this.mouse_controls = mouse_controls; - this.scene = scene; - this.version = version; - this.lastSceneState = this.getSceneState(); - this.updateOutliner = updateOutliner; - this.is_character = is_character; - this.devMode = devMode; - - this.onMouseMove = this.onMouseMove.bind(this); - this.onMouseClick = this.onMouseClick.bind(this); - this.onKeyDown = this.onKeyDown.bind(this); - this.onMouseDown = this.onMouseDown.bind(this); - this.onMouseUp = this.onMouseUp.bind(this); - } - - public attachEventListeners() { - if (!this.devMode) { - return; - } - window.addEventListener("mousemove", this.onMouseMove, false); - window.addEventListener("click", this.onMouseClick, false); - window.addEventListener("keydown", this.onKeyDown, false); - window.addEventListener("mousedown", this.onMouseDown, false); - window.addEventListener("mouseup", this.onMouseUp, false); - } - - public detachEventListeners() { - if (!this.devMode) { - return; - } - window.removeEventListener("mousemove", this.onMouseMove, false); - window.removeEventListener("click", this.onMouseClick, false); - window.removeEventListener("keydown", this.onKeyDown, false); - window.removeEventListener("mousedown", this.onMouseDown, false); - window.removeEventListener("mouseup", this.onMouseUp, false); - } - - public async undo() { - if (this.undoIndex >= this.undoStack.length) { - return; - } - this.undoIndex += 1; - const undoCommand = this.undoStack.at( - this.undoStack.length - this.undoIndex, - ); - await undoCommand?.undo(); - this.updateOutliner(); // In the future we will address this because of its relational issues with editor and the current class. - this.lastSceneState = this.getSceneState(); - } - - public async redo() { - if (this.undoIndex <= 0) { - return; - } - const undoCommand = this.undoStack.at( - this.undoStack.length - this.undoIndex, - ); - await undoCommand?.redo(); - this.undoIndex -= 1; - this.updateOutliner(); // In the future we will address this because of its relational issues with editor and the current class. - this.lastSceneState = this.getSceneState(); - } - - // Drop any redo-future before pushing a fresh command so the new action - // replaces the branch of history the user had undone past. - private dropRedoHistory() { - if (this.undoIndex > 0) { - this.undoStack = this.undoStack.slice( - 0, - this.undoStack.length - this.undoIndex, - ); - this.undoIndex = 0; - } - } - - public async create( - media_token: string, - name: string, - position: THREE.Vector3, - ): Promise | undefined> { - if (media_token.includes("SKY::")) { - const token = media_token.replace("SKY::", ""); - this.scene.updateSkybox(token); - } else if (media_token !== "Parim") { - // Support direct image/video URL pastes via the Image:: scheme - if (media_token.startsWith("Image::")) { - const url = media_token.replace("Image::", ""); - const obj = await this.scene.loadObjectFromUrl(url, position); - if (obj) { - obj.name = name; - obj.userData["name"] = name; - } - return obj as THREE.Object3D; - } - - const obj = await this.scene.loadObject( - media_token, - name, - true, - position, - this.version, - ); - if (obj) { - obj.name = name; - obj.userData["name"] = name; - } - return obj as THREE.Object3D | undefined; - } else { - return this.scene.instantiate(name, position); - } - } - - updateSkybox(media_id: string) { - this.scene.updateSkybox(media_id); - } - - /* NEVER CALL THIS INTERNALLY */ - public render_outliner(timeline_characters: { [key: string]: ClipGroup }) { - // needs timeline_characters to render favicons. - // Not permanent just in place until we have multi object select ability. - const selected_item = this.selected(); - const signal_items: SceneObject[] = []; - this.scene.scene.children.forEach((child) => { - const converted = this.convert_object(child, timeline_characters); - if (converted.name !== "") { - signal_items.push(converted); - } - }); - const outlinerState = { - selectedItem: selected_item, - items: signal_items, - }; - return outlinerState; - } - - public async retrieve(object_uuid: string) { - return this.scene.get_object_by_uuid(object_uuid); - } - - public async update( - object_uuid: string, - position: THREE.Vector3, - rotation: THREE.Euler, - scale: THREE.Vector3, - ) { - const object = await this.retrieve(object_uuid); - if (object) { - object.position.copy(position); - object.rotation.copy(rotation); - object.scale.copy(scale); - } - } - - public async delete(object_uuid: string) { - // Deletes an object. - this.mouse_controls.deleteObject(object_uuid); - } - - public async double_click() { - this.mouse_controls.focus(); - } - - public async hideObject(object_uuid: string) { - const object = await this.retrieve(object_uuid); - if (object?.visible !== undefined) { - object.visible = !object.visible; - object.userData["visible"] = object.visible; - } - } - - public selected() { - let selected_item = null; - if (this.selected_objects && this.selected_objects.length > 0) { - selected_item = this.selected_objects[0]; - } - if (selected_item) { - return this.convert_object(selected_item, {}); - } - return null; - } - - public select_object(id: string) { - const object = this.scene.get_object_by_uuid(id); - if (object) { - this.mouse_controls.selected = [object]; - this.mouse_controls.selectObject(object); - } - } - - // Converts a 3d object to signal item format. - private convert_object( - object: THREE.Object3D, - timeline_characters: { [key: string]: ClipGroup }, - ) { - let faicon = faCube; - let name = object.name; - if (object.name == "::CAM::") { - faicon = faCamera; - name = "Camera"; - } else if (object.uuid in timeline_characters) { - faicon = faPerson; - } - let locked = object.userData["locked"]; - if (locked == undefined) { - locked = false; - } - return { - id: object.uuid, - icon: faicon, - name: name.charAt(0).toUpperCase() + name.slice(1), - type: object.type, - visible: object.visible, - locked: object.userData["locked"], - }; - } - - public async copy() { - const object = this.mouse_controls.selected?.at(0); - if (object !== undefined && object.name !== "::CAM::") { - this.copiedObject = object; - } - } - - public async paste() { - if (this.copiedObject && this.copiedObject.name != "::CAM::") { - const userdata = this.copiedObject.userData; - const position = this.copiedObject.position.clone(); - const rotation = this.copiedObject.rotation.clone(); - const scale = this.copiedObject.scale.clone(); - - const media_id = userdata["media_id"]; - const color = userdata["color"]; - const name = this.copiedObject.name; - const wasCharacter: boolean = !!this.copiedObject.userData.isCharacter; - - const obj = await this.create(media_id, name, position); - if (!obj) { - return; - } - this.scene.setColor(obj.uuid, color); - obj.position.copy(position.add(new THREE.Vector3(0.5, 0.0, 0.5))); - obj.rotation.copy(rotation); - obj.scale.copy(scale); - - // Preserve character stuff on pasted object - if (wasCharacter) { - obj.userData.isCharacter = true; - } - - this.mouse_controls.selectObject(obj); - this.updateOutliner(); - - if (wasCharacter) { - Queue.publish({ - queueName: QueueNames.FROM_ENGINE, - action: fromEngineActions.UPDATE_CHARACTER_ID, - data: { - version: 1, - type: AssetType.CHARACTER, - media_id: media_id, - object_uuid: obj.uuid, - name: name, - } as MediaItem, - }); - } else { - Queue.publish({ - queueName: QueueNames.FROM_ENGINE, - action: fromEngineActions.ADD_OBJECT, - data: { - version: 1, - type: AssetType.OBJECT, - media_id: media_id, - object_uuid: obj.uuid, - name: name, - } as MediaItem, - }); - } - - await this.copy(); - await this.add_creation_undostack(obj); - } - } - - public onMouseMove(event: MouseEvent) { - this.mouse_controls.onMouseMove(event); - } - - public onMouseClick() { - this.mouse_controls.onMouseClick(); - } - - public onKeyDown(event: KeyboardEvent) { - this.mouse_controls.onkeydown(event); - } - - public onMouseDown(event: MouseEvent) { - this.mouse_controls.onMouseDown(event); - } - - public onMouseUp(event: MouseEvent) { - this.mouse_controls.onMouseUp(event); - this.check_scene_for_updates(); - } - - private getSceneState(): SceneState { - const scene_objects: string[] = []; - const scene_names: string[] = []; - const scene_positions: string[] = []; - const scene_userdata: string[] = []; - const scene_rotations: string[] = []; - const scene_scales: string[] = []; - - this.scene.scene.children.forEach((child) => { - if (child.name != "") { - scene_objects.push(child.uuid); - scene_names.push(child.name); - scene_userdata.push(JSON.stringify(child.userData)); - scene_positions.push(JSON.stringify(child.position.clone())); - scene_rotations.push(JSON.stringify(child.rotation.clone())); - scene_scales.push(JSON.stringify(child.scale.clone())); - } - }); - return { - scene_objects: scene_objects, - scene_names: scene_names, - scene_userdata: scene_userdata, - scene_positions: scene_positions, - scene_rotations: scene_rotations, - scene_scales: scene_scales, - }; - } - - private is_creation(sceneState: SceneState): string { - let resp = ""; - sceneState.scene_objects.forEach((object) => { - if (!this.lastSceneState.scene_objects.includes(object)) { - resp = object; - return resp; - } - }); - return resp; - } - - private is_deletion(sceneState: SceneState): string { - let resp = ""; - this.lastSceneState.scene_objects.forEach((object) => { - if (sceneState.scene_objects.includes(object) == false) { - resp = object; - return resp; - } - }); - return resp; - } - - private is_userdata(sceneState: SceneState): string { - let resp = ""; - this.lastSceneState.scene_userdata.forEach((object) => { - if (sceneState.scene_userdata.includes(object) == false) { - const respIdx = this.lastSceneState.scene_userdata.indexOf(object); - resp = sceneState.scene_objects[respIdx]; - return sceneState.scene_objects[respIdx]; - } - }); - return resp; - } - - private is_transformation(sceneState: SceneState): string { - let resp = ""; - if (resp === "") { - sceneState.scene_positions.forEach((object) => { - if (this.lastSceneState.scene_positions.includes(object) == false) { - const respIdx = sceneState.scene_positions.indexOf(object); - resp = sceneState.scene_objects[respIdx]; - return sceneState.scene_objects[respIdx]; - } - }); - } - if (resp === "") { - sceneState.scene_rotations.forEach((object) => { - if (this.lastSceneState.scene_rotations.includes(object) == false) { - const respIdx = sceneState.scene_rotations.indexOf(object); - resp = sceneState.scene_objects[respIdx]; - return sceneState.scene_objects[respIdx]; - } - }); - } - if (resp === "") { - sceneState.scene_scales.forEach((object) => { - if (this.lastSceneState.scene_scales.includes(object) == false) { - const respIdx = sceneState.scene_scales.indexOf(object); - resp = sceneState.scene_objects[respIdx]; - return sceneState.scene_objects[respIdx]; - } - }); - } - return resp; - } - - public async add_creation_undostack(object: THREE.Object3D) { - this.dropRedoHistory(); - this.undoStack.push( - new CreationSceneItem(this, { - object_uuid: object.uuid, - name: object.name, - position: object.position.clone(), - userdata: object.userData, - }), - ); - this.lastSceneState = this.getSceneState(); - } - - /* Can be called Internally or Externally for undo redo stack */ - // This function allows you to capture transform states and occurs on mouse up. - public async check_scene_for_updates() { - const sceneState = this.getSceneState(); - if (JSON.stringify(this.lastSceneState) !== JSON.stringify(sceneState)) { - // Add Scene state for undo redo here. - const is_deleted = this.is_deletion(sceneState); - const is_transform = this.is_transformation(sceneState); - const is_userdata = this.is_userdata(sceneState); - - this.dropRedoHistory(); - this.undoStack = this.undoStack.slice(-16); - - if (is_deleted != "") { - const objectIndex = - this.lastSceneState.scene_objects.indexOf(is_deleted); - const name = this.lastSceneState.scene_names[objectIndex]; - const position = this.lastSceneState.scene_positions[objectIndex]; - const userdata = JSON.parse( - this.lastSceneState.scene_userdata[objectIndex], - ); - - const undoPush: DeletionData = { - object_uuid: is_deleted, - name: name, - position: JSON.parse(position), - userdata: userdata, - }; - this.undoStack.push(new DeletionSceneItem(this, undoPush)); - } else if (is_userdata != "") { - const objectIndex = sceneState.scene_objects.indexOf(is_userdata); - const startUserdata = JSON.parse( - this.lastSceneState.scene_userdata[objectIndex], - ); - const userdata = JSON.parse(sceneState.scene_userdata[objectIndex]); - - const endPush: UserData = { - object_uuid: is_userdata, - color: userdata["color"], - locked: userdata["locked"], - visible: userdata["visible"], - }; - - const startPush: UserData = { - object_uuid: is_userdata, - color: startUserdata["color"], - locked: startUserdata["locked"], - visible: startUserdata["visible"], - }; - - this.undoStack.push(new UserDataSceneItem(this, startPush, endPush)); - } else if (is_transform != "") { - const objectIndexStart = - this.lastSceneState.scene_objects.indexOf(is_transform); - if ( - this.lastSceneState.scene_positions[objectIndexStart] !== undefined - ) { - const objectIndexEnd = sceneState.scene_objects.indexOf(is_transform); - - const startPush: TransformData = { - object_uuid: is_transform, - position: JSON.parse( - this.lastSceneState.scene_positions[objectIndexStart], - ), - rotation: JSON.parse( - this.lastSceneState.scene_rotations[objectIndexStart], - ), - scale: JSON.parse( - this.lastSceneState.scene_scales[objectIndexStart], - ), - }; - const endPush: TransformData = { - object_uuid: is_transform, - position: JSON.parse(sceneState.scene_positions[objectIndexEnd]), - rotation: JSON.parse(sceneState.scene_rotations[objectIndexEnd]), - scale: JSON.parse(sceneState.scene_scales[objectIndexEnd]), - }; - this.undoStack.push(new TransformSceneItem(this, startPush, endPush)); - } - } - } - this.lastSceneState = sceneState; - } -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/timeline.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/timeline.ts deleted file mode 100644 index 99552e4835..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/timeline.ts +++ /dev/null @@ -1,950 +0,0 @@ -import * as THREE from "three"; - -import { ClipUI } from "../clips/clip_ui"; - -import Scene from "./scene.js"; -import AudioEngine from "./Engines/audio_engine"; -import TransformEngine from "./Engines/transform_engine"; -import LipSyncEngine from "./Engines/lip_sync_engine"; - -import Queue, { - UnionedActionTypes, - UnionedDataTypes, -} from "~/pages/PageEnigma/Queue/Queue"; -import { QueueNames } from "../Queue/QueueNames"; -import { toEngineActions } from "../Queue/toEngineActions"; -import { fromEngineActions } from "../Queue/fromEngineActions"; -import { ClipGroup, ClipType, AssetType } from "~/enums"; -import { CameraAspectRatio, MediaFileType } from "~/pages/PageEnigma/enums"; -import { Keyframe, MediaItem, UpdateTime } from "~/pages/PageEnigma/models"; -import Editor from "~/pages/PageEnigma/Editor/editor"; -import EmotionEngine from "./Engines/emotion_engine"; -import { IGenerationOptions } from "~/pages/PageEnigma/models/generationOptions"; -import { Vector3 } from "three"; - -import { filmLength, outlinerState } from "../signals"; -import { CharacterAnimationEngine } from "./Engines/CharacterAnimationEngine"; -import { SplatMesh } from "@sparkjsdev/spark"; - -export class TimeLine { - editorEngine: Editor; - timeline_items: ClipUI[]; - - timeline_limit: number; - absolute_end: number; - scrubber_frame_position: number; - is_playing: boolean; - is_repeating: boolean = true; - - // plays audio - audio_engine: AudioEngine; - // key framing - transform_engine: TransformEngine; - // animation engine - animation_engine: CharacterAnimationEngine; - // lip sync engine - lipSync_engine: LipSyncEngine; - // emotion engine - emotion_engine: EmotionEngine; - - // characters - characters: { [key: string]: ClipGroup }; - - scene: Scene; - camera: THREE.Camera | null; - mouse: THREE.Vector2 | undefined; - - current_time: number; - - camera_name: string; - // ensure that the elements are loaded first. - - private debounce_generate_video: boolean; - - constructor( - editor: Editor, - audio_engine: AudioEngine, - transform_engine: TransformEngine, - lipsync_engine: LipSyncEngine, - animation_engine: CharacterAnimationEngine, - emotion_engine: EmotionEngine, - scene: Scene, - camera: THREE.Camera | null, - mouse: THREE.Vector2 | undefined, - camera_name: string, - ) { - this.editorEngine = editor; - this.timeline_items = []; - this.characters = {}; - this.absolute_end = 60 * 12; - this.timeline_limit = this.getTimelineEnd(); - this.camera = camera; - this.mouse = mouse; - - this.is_playing = false; - this.scrubber_frame_position = 0; // in frames into the tl - - // this will be used to play the audio clips - this.audio_engine = audio_engine; - this.transform_engine = transform_engine; - this.lipSync_engine = lipsync_engine; - this.animation_engine = animation_engine; - this.emotion_engine = emotion_engine; - - this.scene = scene; - this.debounce_generate_video = false; - Queue.subscribe( - QueueNames.TO_ENGINE, - "engine", - this.handleTimelineActions.bind(this), - ); - - this.current_time = 0; - this.camera_name = camera_name; - } - - public async updateUI() { - Queue.publish({ - queueName: QueueNames.FROM_ENGINE, - action: fromEngineActions.UPDATE_TIME_LINE, - data: this.timeline_items, - }); - } - - public async pushEvent(action: fromEngineActions, data: UnionedDataTypes) { - //this.current_time += 0.75; - Queue.publish({ - queueName: QueueNames.FROM_ENGINE, - action: fromEngineActions.UPDATE_TIME, - data: data, - }); - } - - public isCharacter(uuid: string): boolean { - this.timeline_items.forEach((clip) => { - if (clip.group == ClipGroup.CHARACTER) - this.characters[clip.object_uuid] = ClipGroup.CHARACTER; - }); - - let result: boolean = false; - for (const key in this.characters) { - if (key === uuid) { - result = true; - return true; - } - } - return result; - } - - public async handleTimelineActions(data: { - action: UnionedActionTypes; - data: UnionedDataTypes; - }) { - const action = data.action; - switch (action) { - case toEngineActions.ADD_KEYFRAME: - await this.addKeyFrame(data.data as Keyframe); - break; - case toEngineActions.UPDATE_KEYFRAME: - await this.updateKeyFrame(data.data as Keyframe); - break; - case toEngineActions.DELETE_KEYFRAME: - await this.deleteKeyFrame(data.data as Keyframe); - break; - case toEngineActions.ADD_CLIP: - await this.addClip(data.data as ClipUI); - break; - case toEngineActions.DELETE_CLIP: - await this.deleteClip(data.data as ClipUI); - break; - case toEngineActions.UPDATE_CLIP: - await this.updateClip(data.data as ClipUI); - break; - case toEngineActions.UPDATE_TIME: - await this.scrub(data.data as UpdateTime); - break; - case toEngineActions.MUTE: - await this.mute(data.data as ClipUI, false); - break; - case toEngineActions.UNMUTE: - await this.mute(data.data as ClipUI, true); - break; - // Create operations - case toEngineActions.ADD_CHARACTER: { - const newObject = await this.addCharacter(data.data as MediaItem); - if (newObject) - this.queueNewObjectMessage( - newObject, - data.data as MediaItem, - AssetType.CHARACTER, - ); - const result = this.editorEngine.sceneManager?.render_outliner( - this.characters, - ); - if (result) outlinerState.items.value = result.items; - break; - } - case toEngineActions.ADD_OBJECT: { - const newObject = await this.addObject(data.data as MediaItem); - if (newObject) - this.queueNewObjectMessage( - newObject, - data.data as MediaItem, - newObject.type as AssetType - ); - const result = this.editorEngine.sceneManager?.render_outliner( - this.characters, - ); - if (result) outlinerState.items.value = result.items; - break; - } - case toEngineActions.ADD_SHAPE: { - const newShape = await this.addShape(data.data as MediaItem); - this.queueNewObjectMessage( - newShape, - data.data as MediaItem, - AssetType.SHAPE, - ); - const result = this.editorEngine.sceneManager?.render_outliner( - this.characters, - ); - if (result) outlinerState.items.value = result.items; - break; - } - case toEngineActions.ENTER_PREVIEW_STATE: - await this.editorEngine.switchPreview(); - break; - case toEngineActions.ENTER_EDIT_STATE: - this.editorEngine.switchEdit(); - break; - case toEngineActions.TOGGLE_CAMERA_STATE: - this.editorEngine.switchCameraView(); - break; - case toEngineActions.TOGGLE_REPEATING: - this.is_repeating = !this.is_repeating; - break; - case toEngineActions.REFRESH_PREVIEW: - if (this.editorEngine.switchPreviewToggle) { - await this.editorEngine.generateFrame(); - } - break; - case toEngineActions.GENERATE_VIDEO: { - console.log("Calling Generate Video"); - // debounce generate video ... - if (this.debounce_generate_video == false) { - this.debounce_generate_video = true; - const options = data.data; // super overloaded talk to the devs about this. TODO... refactor - // pass this in ... rather than doing it implicitly ... - this.editorEngine.generation_options = options as IGenerationOptions; - - await this.editorEngine.generateVideo(); - this.debounce_generate_video = false; - } - console.log("Generate Video Press Event"); - break; - } - case toEngineActions.CHANGE_CAMERA_ASPECT_RATIO: { - this.editorEngine.changeRenderCameraAspectRatio( - data.data as CameraAspectRatio, - ); - break; - } - default: - console.log("Action Not Wired", action); - } - } - - public async addCharacter(data: MediaItem) { - const media_id = data.media_id; - const name = data.name; - const pos = this.getPos(); - const new_data = { ...data }; - - console.debug('Adding to engine scene manager: ', media_id, name, pos); - - const obj = await this.editorEngine.sceneManager?.create( - media_id, - name, - pos, - ); - - if (obj) { - obj.userData["name"] = name; - obj.name = name; - obj.position.copy(pos); - obj.userData.isCharacter = true; - - console.debug("Added character object to timeline; object: ", obj); - - // TODO(bt,2025-01-28): Hack for debugging only. Remove soon. - (window as any).lastCharacter = obj; - - // TODO(bt,2025-01-28): Hack for debugging only. Remove soon. - const lastRig = obj.children[0]; - if (!!lastRig && lastRig.name.includes("gamerig")) { - (window as any).lastRig = lastRig; - } - - const object_uuid = obj.uuid; - - this.characters[object_uuid] = ClipGroup.CHARACTER; // TODO: Create a class to make the idea of a character. - new_data["object_uuid"] = object_uuid; - - Queue.publish({ - queueName: QueueNames.FROM_ENGINE, - action: fromEngineActions.UPDATE_CHARACTER_ID, - data: new_data, - }); - - this.addPlayableClip( - new ClipUI( - data.version, - ClipType.FAKE, - ClipGroup.CHARACTER, - "Default", - media_id, - obj.uuid, - obj.uuid, - name, - 0, - 0, - 0, - obj.userData["media_file_type"], - ), - ); - - await this.editorEngine.sceneManager?.add_creation_undostack(obj); - } - return obj; - } - - queueNewObjectMessage( - item: THREE.Object3D, - data: MediaItem, - asset_type: AssetType.OBJECT | AssetType.CHARACTER | AssetType.SHAPE | AssetType.SPLAT, - ) { - Queue.publish({ - queueName: QueueNames.FROM_ENGINE, - action: fromEngineActions.ADD_OBJECT, - data: { - media_id: data.media_id, - type: asset_type, - name: item.name, - object_uuid: item.uuid, - version: 1, - } as MediaItem, - }); - - // this.addPlayableClip( - // new ClipUI( - // data["version"], - // ClipType.FAKE, - // ClipGroup.OBJECT, - // "Default", - // data.media_id, - // item.uuid, - // item.uuid, - // item.name, - // 0, - // 0, - // 0, - // this.scene.get_object_by_uuid(item.uuid)?.userData[ - // "media_file_type" - // ], - // ), - // ); - } - - public getPos() { - this.editorEngine.utils.removeTransformControls(true); - const raycaster = new THREE.Raycaster(); - raycaster.layers.enable(0); - raycaster.layers.enable(1); - if (this.editorEngine.mouse && this.camera) { - raycaster.setFromCamera(this.editorEngine.mouse, this.camera); - const intersects = raycaster.intersectObjects( - this.scene.scene.children, - true, - ); - if (intersects.length > 0) { - return intersects[0].point; - } - } - return new THREE.Vector3(0, 0, 0); - } - - public async addObject(data: MediaItem & { position?: { x: number; y: number; z: number } }) { - const dataPos = data.position; - const pos = dataPos - ? new THREE.Vector3(dataPos.x, dataPos.y, dataPos.z) - : this.getPos(); - const media_id = data.media_id; - const name = data.name; - - const obj = await this.editorEngine.sceneManager?.create( - media_id, - name, - pos, - ); - if (obj) { - obj.userData["name"] = name; - obj.name = name; - obj.position.copy(pos); - - await this.editorEngine.sceneManager?.add_creation_undostack(obj); - } - return obj; - } - - // This method is for local dev testing of objects - // and bypasses the queue system, that's kinda redundant anyway - public addLocalSplat(fileBytes: ArrayBuffer | Uint8Array, flipVertical: boolean = false) { - const splatMesh = new SplatMesh({ fileBytes: fileBytes }); - splatMesh.name = `{SplatMesh-${Date.now()}}`; - - if (flipVertical) { - splatMesh.rotateX(Math.PI); - } - - this.editorEngine.sceneManager?.scene.scene.add(splatMesh); - } - - public async addShape(data: MediaItem) { - const pos = this.getPos(); - const parim = await this.editorEngine.create_parim(data.media_id, pos); - await this.editorEngine.sceneManager?.add_creation_undostack(parim); - return parim; - } - - public async addKeyFrame(data: Keyframe) { - // KeyFrame Object - // version: number; - // clip_uuid: string; - // group: ClipGroup; - // object_uuid?: string; - // offset: number; - // position: XYZ - // rotation: XYZ; - // scale: XYZ; - // selected?: boolean; - const data_json = data; - const uuid = data_json.object_uuid; - const keyframe_uuid = data_json.keyframe_uuid; - - let object_name = this.scene.get_object_by_uuid(uuid)?.name; - if (object_name === undefined) { - object_name = "undefined"; - } - - this.transform_engine.addFrame( - uuid, - data_json.offset, - data_json.position as Vector3, - data_json.rotation as Vector3, - data_json.scale as Vector3, - data_json.offset, - data_json.keyframe_uuid, - ); - - await this.addPlayableClip( - new ClipUI( - data_json["version"], - ClipType.TRANSFORM, - data_json["group"], - object_name, - "", - keyframe_uuid, - uuid, - object_name, - 0, - data_json["offset"], - data_json["offset"], - MediaFileType.None, - ), - ); - - const point = this.scene.createPoint( - data_json.position as Vector3, - data_json.rotation as Vector3, - data_json.scale as Vector3, - data_json.keyframe_uuid, - ); - if (this.editorEngine.camera_person_mode) { - point.visible = false; - } - this.checkEditorCanPlay(); - } - - public checkEditorCanPlay() { - this.editorEngine.can_playback = this.getLastClipEnd() > 1; - this.editorEngine.updateSelectedUI(); - } - - // This method is NOT responsible for deletion of the object from scene or other editor parts - // It is only responsible for deleting the object from the timeline and its corresponding elements - public deleteObject(object: THREE.Object3D) { - if (!object) { - return - } - - if (object.name === this.camera_name) { - return; - } - - const object_uuid = object.uuid; - - this.timeline_items.forEach((element) => { - if ( - element.type == ClipType.TRANSFORM && - element.object_uuid == object_uuid - ) { - this.scene.deletePoint(element.clip_uuid); - } - }); - - // TODO: In the future object should have relations that can be deleted inside the engines - // and the engines handle the deletion of objects. - // Character Animation Engine already does this. - this.timeline_items = this.timeline_items.filter( - (element) => element.object_uuid !== object_uuid, - ); - - this.animation_engine.removeCharacter(object); - - // Update react land here. - } - - public async addClip(data: ClipUI) { - const object_uuid = data.object_uuid; - const media_id = data.media_id; - const name = data.name; - const group = data.group; - const version = 1; - const type = data.type; - const offset = data.offset; - const end_offset = data.length + offset; - const object = this.scene.get_object_by_uuid(object_uuid); - const object_name = - object?.name ?? "undefined"; - const clip_uuid = data.clip_uuid; - - switch (type) { - case "animation": - await this.animation_engine.addCharacterAnimationMedia(object!, media_id, data); - this.animation_engine.evaluate(this.current_time, this.timeline_limit); - break; - case "transform": - this.transform_engine.loadObject(object_uuid, data.length); - break; - case "expression": - this.emotion_engine.loadClip(object_uuid, media_id); - break; - case "audio": - if (group === "character") { - this.lipSync_engine.load_object(object_uuid, media_id); - // media id for this as well it can be downloaded - this.addPlayableClip( - new ClipUI( - version, - ClipType.AUDIO, - ClipGroup.CHARACTER, - name, - media_id, - clip_uuid, - object_uuid, - object_name, - offset, - end_offset, - 0, // length - this.scene.get_object_by_uuid(object_uuid)?.userData[ - "media_file_type" - ], - ), - ); - return; - } else { - this.audio_engine.loadClip(media_id); - } - break; - } - - // media id for this as well it can be downloaded - this.addPlayableClip( - new ClipUI( - version, - type, - group, - name, - media_id, - clip_uuid, - object_uuid, - object_name, - offset, - end_offset, // length - 0, - this.scene.get_object_by_uuid(object_uuid)?.userData["media_file_type"], - ), - ); - - this.checkEditorCanPlay(); - } - - public async addSelfAnimationClip( - data: ClipUI, - animation_clip: THREE.AnimationClip, - ) { - const object_uuid = data.object_uuid; - const media_id = data.media_id; - const name = data.name; - const group = data.group; - const version = 1; - const type = data.type; - const offset = data.offset; - const end_offset = data.length + offset; - const object = this.scene.get_object_by_uuid(object_uuid); - const object_name = - object?.name ?? "undefined"; - const clip_uuid = data.clip_uuid; - - this.animation_engine.addCharacterAnimation(object!, animation_clip, data); - - // media id for this as well it can be downloaded - this.addPlayableClip( - new ClipUI( - version, - type, - group, - name, - media_id, - clip_uuid, - object_uuid, - object_name, - offset, - end_offset, // length - 0, - this.scene.get_object_by_uuid(object_uuid)?.userData["media_file_type"], - ), - ); - - this.checkEditorCanPlay(); - this.updateUI(); - } - - public async deleteKeyFrame(data: Keyframe) { - const keyframe_uuid = data.keyframe_uuid; - const object_uuid = data.object_uuid; - this.transform_engine.clips[object_uuid].removeKeyframe(keyframe_uuid); - this.scene.deletePoint(keyframe_uuid); - for (const element of this.timeline_items) { - if ( - element.clip_uuid === keyframe_uuid && - element.object_uuid === object_uuid - ) { - this.timeline_items = this.timeline_items.filter( - (element) => - !( - element.clip_uuid === keyframe_uuid && - element.object_uuid === object_uuid - ), - ); - break; - } - } - this.checkEditorCanPlay(); - } - - public async updateKeyFrame(data: Keyframe) { - const keyframe_uuid = data.keyframe_uuid; - const keyframe_offset = data.offset; - const object_uuid = data.object_uuid; - - const keyframe_pos = data.position; - const keyframe_rot = data.rotation; - const keyframe_scl = data.scale; - - this.transform_engine.clips[object_uuid].setOffset( - keyframe_uuid, - keyframe_offset, - ); - this.transform_engine.clips[object_uuid].setTransform( - keyframe_uuid, - keyframe_pos as Vector3, - keyframe_rot as Vector3, - keyframe_scl as Vector3, - ); - this.scene.updatePoint( - keyframe_uuid, - keyframe_pos as Vector3, - keyframe_rot as Vector3, - ); - this.checkEditorCanPlay(); - } - - public async updateClip(data: ClipUI) { - // only length and offset changes here. - const object_uuid = data.object_uuid; - const media_id = data.media_id; - const offset = data.offset; - const length = data.length + offset; - const clip_uuid = data.clip_uuid; - - for (const element of this.timeline_items) { - if ( - element.media_id === media_id && - element.object_uuid === object_uuid && - element.clip_uuid == clip_uuid - ) { - element.length = length; - element.offset = offset; - } - } - this.checkEditorCanPlay(); - } - - public async deleteClip(data: ClipUI) { - const object_uuid = data.object_uuid; - const media_id = data.media_id; - const clip_uuid = data.clip_uuid; - - // Remove the clip from the items array - for (let i = 0; i < this.timeline_items.length; i++) { - const element = this.timeline_items[i]; - if ( - element.media_id === media_id && - element.object_uuid === object_uuid && - element.clip_uuid == clip_uuid - ) { - this.timeline_items.splice(i, 1); - break; - } - } - - // Once the clip is removed, we need to make sure the engines aren't tracking it either - // TODO: Ideally the engines should just handle this individually and we check them based on clip type/group - // This way we won't need to iterate over all timeline items - if (data.type === ClipType.ANIMATION) { - const object = this.scene.get_object_by_uuid(object_uuid)!; - this.animation_engine.removeAnimation(object, data) - } - - this.checkEditorCanPlay(); - } - - public async mute(data: ClipUI, isMute: boolean) { - this.timeline_items.forEach((element) => { - if (element.group === data.group) { - element.should_play = isMute; - } - }); - } - - public async addPlayableClip(clip: ClipUI): Promise { - this.timeline_items.push(clip); - } - - public async scrub(data: UpdateTime): Promise { - if (this.is_playing) { - return; - } - const value = Math.floor(data.currentTime); - await this.setScrubberPosition(value); - this.current_time = value; - await this.update(); - - if (this.editorEngine.switchPreviewToggle) { - await this.editorEngine.generateFrame(); - } - } - - // public streaming events into the timeline from - public async setScrubberPosition(offset: number) { - this.scrubber_frame_position = offset; // in ms - } - - // should play from the clip that is closest to the to scrubber - public async play(): Promise { - console.log(`Play - Starting Timeline`); - this.is_playing = true; - } - - public async resetScene() { - for (const element of this.timeline_items) { - if (element.type === ClipType.TRANSFORM) { - const object = this.scene.get_object_by_uuid(element.object_uuid); - if (object && this.transform_engine.clips[element.object_uuid]) { - this.transform_engine.clips[element.object_uuid].reset(object); - } - } else if ( - element.type === ClipType.AUDIO && - element.group !== ClipGroup.CHARACTER - ) { - this.audio_engine.loadClip(element.media_id); - this.audio_engine.stopClip(element.media_id); - } else if (element.type === ClipType.ANIMATION) { - this.animation_engine.stop(); - } else if ( - element.type === ClipType.AUDIO && - element.group === ClipGroup.CHARACTER - ) { - this.lipSync_engine.clips[ - element.object_uuid + element.media_id - ].stop(); - this.lipSync_engine.clips[ - element.object_uuid + element.media_id - ].reset(); - } else if (element.type === ClipType.EXPRESSION) { - const object = this.scene.get_object_by_uuid(element.object_uuid); - if (object) - this.emotion_engine.clips[ - element.object_uuid + element.media_id - ].reset(object); - } - } - } - - public getLastClipEnd(): number { - let longest = 0; - for (const element of this.timeline_items) { - if (longest < element.length) { - longest = element.length; - } - } - return longest; - } - - public getTimelineEnd(): number { - return filmLength.peek() * 1000; - } - - // called by the editor update loop on each frame - public async update( - isRendering = false, - delta_time: number = 0, - ): Promise { - //if (this.is_playing === false) return; // start and stop - this.timeline_limit = this.getTimelineEnd(); - if (this.is_playing) { - this.current_time += delta_time * 1000; - this.pushEvent(fromEngineActions.UPDATE_TIME, { - currentTime: this.current_time, - }); - this.scrubber_frame_position = this.current_time; - } - - if (this.scrubber_frame_position <= 0) { - for (const video_plane of this.scene.video_planes) { - video_plane.currentTime = 0; - } - await this.resetScene(); - } - - for (const video_plane of this.scene.video_planes) { - // Caps to 10fps so that the buffering issue is solved and it plays i am not sure how to fix this. - // TODO: Fix buffering and make 30 fps. - const fixedPoint = 1; - - let pb = parseFloat( - (this.scrubber_frame_position / this.editorEngine.cap_fps).toFixed( - fixedPoint, - ), - ); - pb = parseFloat((pb % video_plane.duration).toFixed(fixedPoint)); - if (video_plane.currentTime !== pb) { - video_plane.playbackRate = 6; - video_plane.currentTime = pb; - } - } - - //this.scrubber_frame_position += 1; - //2. allow stopping. - //3. smallest unit is a frame and it is set by the scene and is in fps, our videos will be 60fps but we can reprocess them using the pipeline. - - // Since the animation engine is newer we can just call an evaluation on it instead of having the timeline process it - this.animation_engine.evaluate(this.current_time, this.timeline_limit); - - // Iterate over the clips and play them as fit - for (const element of this.timeline_items) { - if ( - element.offset <= this.scrubber_frame_position && - this.scrubber_frame_position <= element.length && - element.should_play - ) { - // run async - // element.play() - // remove the element from the list - const object = this.scene.get_object_by_uuid(element.object_uuid); - if (element.type === ClipType.TRANSFORM) { - if (object && this.transform_engine.clips[element.object_uuid]) { - this.transform_engine.clips[element.object_uuid].step( - object, - element.offset, - this.scrubber_frame_position, - this.scene, - ); - element.length = - this.transform_engine.clips[element.object_uuid].length; - } - } else if ( - element.type === ClipType.AUDIO && - element.group !== ClipGroup.CHARACTER && - this.is_playing && - !isRendering - ) { - // if (this.scrubber_frame_position + 1 >= element.length) { - // this.audio_engine.playClip(element.media_id); - // } else { - // await this.audio_engine.step( - // element.object_uuid + element.media_id, - // this.scrubber_frame_position, element.offset); - // } - this.audio_engine.playClip(element.media_id); - await this.audio_engine.step( - element.media_id, - this.scrubber_frame_position, - element.offset, - ); - } else if ( - element.type === ClipType.AUDIO && - element.group === ClipGroup.CHARACTER && - this.is_playing - ) { - if (object) { - await this.lipSync_engine.clips[ - element.object_uuid + element.media_id - ].play(object); - this.lipSync_engine.clips[ - element.object_uuid + element.media_id - ].step(this.scrubber_frame_position, element.offset); - } - } else if (element.type === ClipType.EXPRESSION) { - if (object) { - await this.emotion_engine.clips[ - object.uuid + element.media_id - ].step(this.scrubber_frame_position - element.offset, object); - } - } - } - } - - if ( - this.scrubber_frame_position >= this.timeline_limit && - this.is_playing - ) { - await this.stop(); - return true; - } - - return false; - } - - private async stop(): Promise { - await this.resetScene(); - this.is_playing = false; - console.log(`Stop - Stopping Timeline`); - this.current_time = 0; - this.update(); - this.pushEvent(fromEngineActions.UPDATE_TIME, { - currentTime: this.current_time, - }); - } -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/video_generation.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/video_generation.ts deleted file mode 100644 index a1404e7a33..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/Editor/video_generation.ts +++ /dev/null @@ -1,474 +0,0 @@ -import Editor from "./editor"; -import { EditorStates } from "~/pages/PageEnigma/enums"; -import { editorState, previewSrc } from "../signals/engine"; -import Queue from "~/pages/PageEnigma/Queue/Queue"; -import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; -import { fromEngineActions } from "~/pages/PageEnigma/Queue/fromEngineActions"; -import { ToastTypes, ClipType } from "~/enums"; - -import { Visibility } from "./api_manager.js"; -import * as THREE from "three"; -import { getSceneSignals, addToast, startPollingActiveJobs } from "~/signals"; -import { v4 as uuidv4 } from "uuid"; -import { SceneGenereationMetaData as SceneGenerationMetaData } from "~/pages/PageEnigma/models/sceneGenerationMetadata"; -import { MediaUploadApi, StudioGen2Api, VideoApi } from "~/Classes/ApiManager"; -import { globalIPAMediaToken } from "../signals"; - - -// TODO THIS CLASS MAKES NO SENSE -// Refactor so we generate all the frames first. -// then pass it through this pipeline as a data structure process it. -// through this class. - -interface MediaTokens { - // NB(bt,2025-01-27): This media token was likely called "color" because we were originally - // going to send a shaded "color" render, a depth map render, and an edge detect render to - // the backend for inference. - color: string; -} - -export class VideoGeneration { - editor: Editor; - mediaUploadAPI: MediaUploadApi; - videoAPI: VideoApi; - studioGen2Api: StudioGen2Api; - - // For cached style Re-Generation - private last_scene_check_sum: string; - // Last Media token IDs - private last_media_tokens: MediaTokens; - // Last toggle position for Render - public last_position_of_preprocessing: boolean; - - constructor(editor: Editor) { - this.editor = editor; - this.mediaUploadAPI = new MediaUploadApi(); - this.videoAPI = new VideoApi(); - this.studioGen2Api = new StudioGen2Api(); - this.last_scene_check_sum = ""; - this.last_media_tokens = { color: "" }; - this.last_position_of_preprocessing = false; - } - - sleep(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); - } - - async videoProcessingPipeline(title: string): Promise { - const mediaTokens: MediaTokens = { - color: "", - }; - try { - // TODO send the audio a later time. - // const audioClips = this.editor.timeline.timeline_items.filter((clip) => { - // return clip.type == ClipType.AUDIO; - // }); - - if (!this.editor.engineFrameBuffers.payloadZip) { - await this.handleError( - `Video Processing Failed Please Try Again (zip process failed)`, - 3000, - ); - } - - const zipBlob = this.editor.engineFrameBuffers.payloadZip; - if (!zipBlob) { - await this.handleError(`"Payload blob is null."`, 3000); - } - - const video_upload_response = await this.mediaUploadAPI.UploadStudioShot({ - maybe_title: title, - uuid_idempotency_token: uuidv4(), - blob: zipBlob, - fileName: `${title}.zip`, - maybe_visibility: Visibility.Public, - }); - - if (!video_upload_response.data) { - console.log("Missing media_tokens.color Token"); - const error = { - message: "Missing Data From Video Upload Response", - code: 1, - }; - throw error; - } - - mediaTokens.color = video_upload_response.data; - - console.log(`Studio recording: https://storyteller.ai/media/${mediaTokens.color}`); - - } finally { - - } - - return mediaTokens; - } - - // this is to generate previews - async generateFrame() { - if (!this.editor.generating_preview && this.editor.rawRenderer) { - this.editor.generating_preview = true; - this.editor.utils.removeTransformControls(); - this.editor.activeScene.renderMode(true); - if (this.editor.activeScene.hot_items) { - this.editor.activeScene.hot_items.forEach((element) => { - element.visible = false; - }); - } - - previewSrc.value = ""; - - this.editor.rawRenderer.setSize( - this.editor.render_width, - this.editor.render_height, - ); - - this.editor.render_camera.aspect = - this.editor.render_width / this.editor.render_height; - - this.editor.render_camera.updateProjectionMatrix(); - - this.editor.rawRenderer.render( - this.editor.activeScene.scene, - this.editor.render_camera, - ); - - const imgData = this.editor.rawRenderer.domElement.toDataURL(); - const response = await fetch(imgData); // Fetch the data URL - const blob = await response.blob(); // Convert to Blob - - if (!this.editor.camera_person_mode) { - this.editor.switchCameraView(); - editorState.value = EditorStates.PREVIEW; - } - - this.editor.generating_preview = false; - - try { - const url = await this.editor.api_manager.uploadMediaFrameGeneration( - blob, - "render.png", - this.editor.art_style, - this.editor.positive_prompt, - this.editor.negative_prompt, - ); - - previewSrc.value = url; - return Promise.resolve(url); - } catch (err: unknown) { - const errorMessage = - err instanceof Error - ? err.message - : "Unknown Error in Generate Frame"; - Queue.publish({ - queueName: QueueNames.FROM_ENGINE, - action: fromEngineActions.POP_A_TOAST, - data: { - type: ToastTypes.ERROR, - message: errorMessage, - }, - }); - } - } - } - - // to determine if we should capture frames again or not. - async shouldRenderScenesAgain(checkSumData: string): Promise { - // Should skip to rendering the scene if already processed the frames inputs for running a new style. - if (this.last_scene_check_sum === "") { - // no token means input was not processed so lets go do the video re-render process - this.last_scene_check_sum = checkSumData; - return false; - } else { - if (this.last_scene_check_sum === checkSumData) { - return true; - } else { - this.last_scene_check_sum = checkSumData; - return false; - } - } - } - - async stopPlaybackAndUploadVideo() { - this.editor.rendering = false; // not sure about this variable ... this has so many state issues. - - // precondition checks, if we have no frames then we shouldn't try to do generations or snapshots - // This means no frames so error out - - this.editor.generating_preview = true; - - await this.editor.updateLoad({ - progress: 50, - message: - "Please stay on this screen and do not switch tabs! while your video is being processed.", - }); - - let media_tokens: MediaTokens = { - color: "", - }; - - // properties used by the external scope to the for loop. - const title = getSceneSignals().title || "Untitled"; - const style_name = this.editor.art_style.toString(); - const media_token = this.editor.current_scene_media_token || undefined; - - // TAKE A snap shot of the scene then use this token across all videos - // convert the ip adapter image and upload as a media token - const image_uuid = uuidv4(); - let ipa_image_token = undefined; - - if (this.editor.globalIpAdapterImage != undefined) { - const response = await this.mediaUploadAPI.UploadImage({ - fileName: `${image_uuid}.ipa`, - blob: this.editor.globalIpAdapterImage, - uuid: image_uuid, - }); - - if (!response.success) { - // ip adapter upload failed - await this.handleError( - "Reference Image Upload Failed Try Generating Movie Again.", - 3000, - ); - return; - } - - if (!response.data) { - console.log("IPA Image Token Missing"); - } - - ipa_image_token = response.data; - } - - // update the signal with this information - if (ipa_image_token) { - globalIPAMediaToken.value = ipa_image_token; - } - - // TODO Remove so many of these around wtf. SceneGenereationMetaData should only be one place - const metaData: SceneGenerationMetaData = { - artisticStyle: this.editor.art_style, - positivePrompt: this.editor.positive_prompt, - negativePrompt: this.editor.negative_prompt, - cameraAspectRatio: this.editor.render_camera_aspect_ratio, - upscale: this.editor.generation_options.upscale, - faceDetail: this.editor.generation_options.faceDetail, - styleStrength: this.editor.generation_options.styleStrength, - lipSync: this.editor.generation_options.lipSync, - cinematic: this.editor.generation_options.cinematic, - globalIPAMediaToken: ipa_image_token, - enginePreProcessing: this.editor.engine_preprocessing, // the only thing that will invalidate the cache - }; - - // for the one case where the engine preprecessing is turned we need to cache this. - this.last_position_of_preprocessing = this.editor.engine_preprocessing; - - // This is to save the snapshot of the scene for remixing - const uuid_snapshot = uuidv4(); - - // Save the scene - const saveData = await this.editor.save_manager.saveData({ - sceneTitle: title, - sceneToken: media_token, - sceneGenerationMetadata: metaData, - }); - - const file = new File([saveData], `${title}.glb`, { - type: "application/json", - }); - - const response = - await this.editor.media_upload.UploadSceneSnapshotMediaFileForm({ - maybe_title: title, - maybe_scene_source_media_file_token: media_token, // can be undefined or null - uuid: uuid_snapshot, - blob: file, - }); - - let immutable_media_token = undefined; - if (response.success) { - if (response.data) { - immutable_media_token = response.data; - } - } else { - await this.handleError( - "Scene Snapshot Failed Try Generating Movie Again.", - 3000, - ); - return; - } - - console.log(`Immutable Snapshot Token: ${immutable_media_token}`); - - await this.editor.updateLoad({ - progress: 50, - message: - "Please stay on this screen and do not switch tabs! while your video is being processed.", - }); - // write the end point to upload the video - // upload the color frames with end point. - try { - media_tokens = await this.videoProcessingPipeline(title); - } catch (error) { - await this.handleError(`Video Processing Failed Please Try Again`, 3000); - throw error; - } - - this.editor.onWindowResize(); - - this.editor.setColorMap(); - - await this.editor.updateLoad({ - progress: 100, - message: "Done Check Your Movies Tab On Profile.", - label: "Success", - }); - - // this is so that its a check point just encase enqueue fails, if it does we can still restylize - this.last_media_tokens = media_tokens; - - // Legacy studio: - //await this.handleEnqueue(media_tokens, ipa_image_token ?? ""); - - // NB(bt,2025-01-27): We're using a hack to set the first frame (in CharacterFrameButton) - const firstFrameMediaToken = (window as any).firstFrameMediaToken; - const videoMediaToken = media_tokens.color; - - console.log(`Enqueue Studio Gen2 Render; Video: ${videoMediaToken} Image: ${firstFrameMediaToken}`); - - await this.handleEnqueueStudioGen2(videoMediaToken, firstFrameMediaToken); - - await this.EndLoadingState(); - } - - async handleCachedEnqueue() { - const enqueue_studio_response = await this.videoAPI.EnqueueStudio({ - enqueueVideo: { - disable_lcm: false, - enable_lipsync: this.editor.generation_options.lipSync, - input_file: this.last_media_tokens.color, - negative_prompt: this.editor.negative_prompt, - prompt: this.editor.positive_prompt, - remove_watermark: false, - style: this.editor.art_style.toString(), - frame_skip: 2, - travel_prompt: "", - trim_end_millis: 7000, - trim_start_millis: 0, - use_cinematic: this.editor.generation_options.cinematic, - use_face_detailer: this.editor.generation_options.faceDetail, - use_strength: this.editor.generation_options.styleStrength, - use_upscaler: this.editor.generation_options.upscale, - uuid_idempotency_token: uuidv4(), - global_ipa_media_token: globalIPAMediaToken.value ?? "", - input_depth_file: "", - input_normal_file: "", - input_outline_file: "", - creator_set_visibility: Visibility.Public, - }, - }); - - if (enqueue_studio_response.success) { - console.log("Start Polling Active Jobs"); - startPollingActiveJobs(); - addToast( - ToastTypes.SUCCESS, - "Done Check Your Movies Tab On Profile.", - 3000, - ); - } else { - addToast(ToastTypes.ERROR, "Failed To Process Movie Try Again", 3000); - } - } - - async handleEnqueue(upload_tokens: MediaTokens, ipa_image_token: string) { - const enqueue_studio_response = await this.videoAPI.EnqueueStudio({ - enqueueVideo: { - disable_lcm: false, - enable_lipsync: this.editor.generation_options.lipSync, - input_file: upload_tokens.color, - negative_prompt: this.editor.negative_prompt, - prompt: this.editor.positive_prompt, - remove_watermark: false, - style: this.editor.art_style.toString(), - frame_skip: 2, - travel_prompt: "", - trim_end_millis: 7000, - trim_start_millis: 0, - use_cinematic: this.editor.generation_options.cinematic, - use_face_detailer: this.editor.generation_options.faceDetail, - use_strength: this.editor.generation_options.styleStrength, - use_upscaler: this.editor.generation_options.upscale, - uuid_idempotency_token: uuidv4(), - global_ipa_media_token: ipa_image_token, - input_depth_file: "", - input_normal_file: "", - input_outline_file: "", - creator_set_visibility: Visibility.Public, - }, - }); - - if (enqueue_studio_response.success) { - startPollingActiveJobs(); - } else { - await this.handleError("Failed To Process Movie Try Again", 3000); - return; - } - - this.handleSuccess("Done Check Your Movies Tab On Profile.", 3000); - } - - async handleEnqueueStudioGen2(videoMediaToken: string, firstFrameImageToken: string) { - const response = await this.studioGen2Api.EnqueueStudioGen2({ - enqueueVideo: { - uuid_idempotency_token: uuidv4(), - image_file: firstFrameImageToken, - video_file: videoMediaToken, - creator_set_visibility: Visibility.Public, - }, - }); - - if (response.success) { - startPollingActiveJobs(); - } else { - await this.handleError("Failed To Process Animation Try Again", 3000); - return; - } - - this.handleSuccess("Done Check Your Movies Tab On Profile.", 3000); - } - - - async handleSuccess(message: string, timeout: number) { - addToast(ToastTypes.SUCCESS, message, timeout); - } - async handleError(message: string, timeout: number) { - addToast(ToastTypes.ERROR, message, timeout); - await this.EndLoadingState(); - } - async EndLoadingState() { - this.editor.generating_preview = false; - this.editor.endLoading(); - this.editor.onWindowResize(); - this.editor.recorder = undefined; - if (this.editor.rawRenderer) { - this.editor.rawRenderer.setSize( - this.editor.startRenderWidth, - this.editor.startRenderHeight, - ); - } - - this.editor.camViewCanvasMayReset(); - - this.editor.rawRenderer = new THREE.WebGLRenderer({ - antialias: true, - canvas: this.editor.canvasRenderCamReference || undefined, - preserveDrawingBuffer: true, - }); - this.editor._configurePostProcessingRaw(); - - this.editor.activeScene.renderMode(false); - - this.editor.switchEdit(); - } -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/InstallSounds.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/InstallSounds.ts deleted file mode 100644 index 04e7f6efc2..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/InstallSounds.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { SoundRegistry, SoundEffect } from "@storyteller/soundboard"; - -// TODO: This is maintained in two places. Here and MiscSettingsPane. -export const InstallSounds = () => { - const r = SoundRegistry.getInstance(); - if (r.hasSound("decline_normal")) { - return; - } - - // Good for simple menu choices - r.setSoundOnce("click", new SoundEffect("resources/sound/smrpg_click.wav", { defaultVolume: 0.2 })); - r.setSoundOnce("scifi_menu_beep_1", new SoundEffect("resources/sound/metroidprime_UI_15.wav", { defaultVolume: 0.3 })); - r.setSoundOnce("scifi_menu_beep_2", new SoundEffect("resources/sound/metroidprime_UI_14.wav", { defaultVolume: 0.3 })); - r.setSoundOnce("scifi_menu_select", new SoundEffect("resources/sound/metroidprime_UI_18.wav", { defaultVolume: 0.3 })); - - // Good for simple immediate enqueue success - r.setSoundOnce("done", new SoundEffect("resources/sound/oot_dialogue_done.wav", { defaultVolume: 0.4 })); // DEFAULT - - // Good for simple immediate failure - r.setSoundOnce("error_chirp", new SoundEffect("resources/sound/goldensun_135.wav", { defaultVolume: 0.4 })); - r.setSoundOnce("spike_throw", new SoundEffect("resources/sound/smrpg_enemy_spikethrow.wav", { defaultVolume: 0.1 })); // DEFAULT - r.setSoundOnce("giant_shell_kick", new SoundEffect("resources/sound/smrpg_mario_giantshellkick.wav", { defaultVolume: 0.2 })); - r.setSoundOnce("wrong", new SoundEffect("resources/sound/smrpg_wrong.wav", { defaultVolume: 0.4 })); - - // Good for simple async success - r.setSoundOnce("special_flower", new SoundEffect("resources/sound/smrpg_specialflower.wav", { defaultVolume: 0.2 })); // DEFAULT - r.setSoundOnce("extra_power", new SoundEffect("resources/sound/smrpg_character_extrapower.wav", { defaultVolume: 0.2 })); // DEFAULT - - // Good for async errors - r.setSoundOnce("crumble", new SoundEffect("resources/sound/smrpg_drybones_crumble.wav", { defaultVolume: 0.1 })); // DEFAULT - r.setSoundOnce("ghost", new SoundEffect("resources/sound/smrpg_ghost.wav", { defaultVolume: 0.2 })); - r.setSoundOnce("special_alert", new SoundEffect("resources/sound/goldensun_214.wav", { defaultVolume: 0.2 })); - r.setSoundOnce("scifi_alert", new SoundEffect("resources/sound/metroidprime_UI_52.wav", { defaultVolume: 0.2 })); - r.setSoundOnce("scifi_shrill_alert", new SoundEffect("resources/sound/metroidprime_UI_51.wav", { defaultVolume: 0.2 })); - - // Good for menus - r.setSoundOnce("next", new SoundEffect("resources/sound/oot_dialogue_next.wav", { defaultVolume: 0.2 })); - r.setSoundOnce("select", new SoundEffect("resources/sound/goldensun_111.wav", { defaultVolume: 0.4 })); - r.setSoundOnce("scifi_menu_open", new SoundEffect("resources/sound/metroidprime_UI_12.wav", { defaultVolume: 0.3 })); - r.setSoundOnce("scifi_menu_close", new SoundEffect("resources/sound/metroidprime_UI_13.wav", { defaultVolume: 0.3 })); - - // Good for special reward / celebration - r.setSoundOnce("correct", new SoundEffect("resources/sound/smrpg_correct.wav", { defaultVolume: 0.1 })); - r.setSoundOnce("flower", new SoundEffect("resources/sound/smrpg_flower.wav", { defaultVolume: 0.1 })); - - // File delete, trash, etc. - r.setSoundOnce("trash", new SoundEffect("resources/sound/oot_scrub_crumble.wav", { defaultVolume: 0.4 })); - - // Misc / ungrouped - r.setSoundOnce("accept_chirp", new SoundEffect("resources/sound/goldensun_101.wav", { defaultVolume: 0.2 })); - r.setSoundOnce("accept_normal_level_1", new SoundEffect("resources/sound/goldensun_173.wav", { defaultVolume: 0.2 })); - r.setSoundOnce("accept_normal_level_2", new SoundEffect("resources/sound/goldensun_174.wav", { defaultVolume: 0.2 })); - r.setSoundOnce("accept_normal_level_3", new SoundEffect("resources/sound/goldensun_175.wav", { defaultVolume: 0.2 })); - r.setSoundOnce("decline_chirp", new SoundEffect("resources/sound/goldensun_102.wav", { defaultVolume: 0.2 })); - r.setSoundOnce("decline_normal", new SoundEffect("resources/sound/goldensun_113.wav", { defaultVolume: 0.2 })); - - (window as any).sounds = r; -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/Queue/Queue.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/Queue/Queue.ts deleted file mode 100644 index 6d233e354c..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/Queue/Queue.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { fromEngineActions } from "~/pages/PageEnigma/Queue/fromEngineActions"; -import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions"; - -import { CameraAspectRatio } from "../enums"; -import { - QueueClip, - QueueKeyframe, - UpdateTime, - MediaItem, -} from "~/pages/PageEnigma/models"; -import { toTimelineActions } from "./toTimelineActions"; - -import { ClipUI } from "../clips/clip_ui"; -import { IGenerationOptions } from "../models/generationOptions"; -import { ToastDataType } from "~/components"; -import { QueueNames } from "./QueueNames"; - -export type UnionedActionTypes = - | fromEngineActions - | toEngineActions - | toTimelineActions; - -export type UnionedDataTypes = - | QueueClip - | UpdateTime - | QueueKeyframe - | ClipUI[] - | MediaItem - | ToastDataType - | IGenerationOptions - | CameraAspectRatio - | null; - -export type QueueSubscribeType = { - action: UnionedActionTypes; - data: UnionedDataTypes; -}; - -export type SubscribeHandler = (entry: { - action: UnionedActionTypes; - data: UnionedDataTypes; -}) => void; - -class Queue { - private _queues: Record< - QueueNames, - Array<{ - action: UnionedActionTypes; - data: UnionedDataTypes; - }> - > = { - [QueueNames.FROM_ENGINE]: [], - [QueueNames.TO_ENGINE]: [], - [QueueNames.TO_TIMELINE]: [] - }; - - private _subscribers: Record< - QueueNames, - Map< - string, - SubscribeHandler - > - > = { - [QueueNames.FROM_ENGINE]: new Map(), - [QueueNames.TO_ENGINE]: new Map(), - [QueueNames.TO_TIMELINE]: new Map(), - }; - - // Enqueues actions to a specified queue - public publish({ - queueName, - action, - data, - }: { - queueName: QueueNames; - action: UnionedActionTypes; - data: UnionedDataTypes; - }) { - // Queues for all QueueNames should be initialized above but this is a safeguard for future enum additions - // Make sure the queue exists - if (!this._queues[queueName]) { - this._queues[queueName] = []; - } - - // Queue the task in the right queue - this._queues[queueName].push({ action, data }); - - // TODO: Ideally the publisher shouldn't deal at all with the subscribers - // It should only publish the events to the right queue - // It also doesn't make sense to fire an event on publish because the operations are then dispersed - // leading to potential race conditions - // FIXME: Add some sort of queue loop using internal event and add queue locks using promises to serialize queue publishes - if (this._subscribers[queueName]) { - this._subscribers[queueName].forEach((subcribeHandler) => - subcribeHandler(this._queues[queueName][0]), - ); - this._queues[queueName].shift(); - } - } - - public subscribe( - queueName: QueueNames, - id: string, - onMessage: (entry: QueueSubscribeType) => void, - ) { - - // Queues for all QueueNames should be initialized above but this is a safeguard for future enum additions - // Make sure the queue exists - if (!this._subscribers[queueName]) { - this._subscribers[queueName] = new Map(); - } - - // Set the handler mapped to the ID - this._subscribers[queueName].set(id, onMessage); - - while (this._queues[queueName]?.length) { - this._subscribers[queueName].forEach((subscribeHandler) => - subscribeHandler(this._queues[queueName][0]), - ); - this._queues[queueName].shift(); - } - } -} - -const queue = new Queue(); - -export default queue; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/Queue/QueueNames.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/Queue/QueueNames.ts deleted file mode 100644 index 813a726847..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/Queue/QueueNames.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum QueueNames { - FROM_ENGINE = "FROM_ENGINE", - TO_ENGINE = "TO_ENGINE", - TO_TIMELINE = "TO_TIMELINE", -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/Queue/fromEngineActions.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/Queue/fromEngineActions.ts deleted file mode 100644 index 093d913bff..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/Queue/fromEngineActions.ts +++ /dev/null @@ -1,14 +0,0 @@ -export enum fromEngineActions { - ADD_OBJECT = "add_object", - CAMERA_ASPECT_RATIO_CHANGED = "camera_aspect_ratio_changed", - // data = CameraAspectRatio - DELETE_OBJECT = "delete_object", - DESELECT_OBJECT = "deselect_object", - RESET_TIMELINE = "reset_timeline", - SELECT_OBJECT = "select_object", - UPDATE_CHARACTER_ID = "update_character_id", - UPDATE_TIME = "update_time", - UPDATE_TIME_LINE = "update_timeline", - POP_A_TOAST = "pop_a_toast", - // data={currentTime: number} -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/Queue/index.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/Queue/index.ts deleted file mode 100644 index 64ee353a42..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/Queue/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import Queue from "./Queue"; -export default Queue; -export * from "./QueueNames"; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/Queue/toEngineActions.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/Queue/toEngineActions.ts deleted file mode 100644 index 7a601329f4..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/Queue/toEngineActions.ts +++ /dev/null @@ -1,47 +0,0 @@ -export enum toEngineActions { - ADD_CLIP = "add_clip", - // data=QueueClip - ADD_KEYFRAME = "add_keyframe", - - ADD_CHARACTER = "add_character", - - ADD_OBJECT = "add_object", - - ADD_SHAPE = "add_shape", - - // data= QueueKeyframe - DELETE_CLIP = "delete_clip", - // data=QueueClip - DELETE_KEYFRAME = "delete_keyframe", - // data=QueueKeyframe - ENTER_EDIT_STATE = "enter_edit", - // data=null - ENTER_PREVIEW_STATE = "enter_preview", - // data=null - GENERATE_VIDEO = "generate_video", - // data=null - MUTE = "mute", - // data={version, type, group, object_uuid} if lipsync - // data={version, type, group} if global_audio - REFRESH_PREVIEW = "refresh_preview", - // data=QueueClip - TOGGLE_CAMERA_STATE = "toggle_camera", - // data=null - TOGGLE_REPEATING = "toggle_repeating", - // data=null - - CHANGE_CAMERA_ASPECT_RATIO = "change_camera_aspect_ratio", - // data=CamperaAspectRatio - - CAMERA_CHANGED = "camera_changed", - - UNMUTE = "unmute", - // data={version, type, group, object_uuid} if lipsync - // data={version, type, group} if global_audio - UPDATE_CLIP = "update_clip", - // data=QueueClip - UPDATE_KEYFRAME = "update_keyframe", - // data=QueueKeyframe - UPDATE_TIME = "update_time", - // data={currentTime: number} -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/Queue/toInferenceActions.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/Queue/toInferenceActions.ts deleted file mode 100644 index b3ed965a6b..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/Queue/toInferenceActions.ts +++ /dev/null @@ -1,3 +0,0 @@ -export enum toInferenceActions { - ADD_TTS_JOB = "add_tts_job" -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/Queue/toTimelineActions.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/Queue/toTimelineActions.ts deleted file mode 100644 index 1602a15177..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/Queue/toTimelineActions.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum toTimelineActions { - ADD_KEYFRAME = "add_keyframe", - GENERATE_PROGRESS = "generate_progress", -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/clips/ClipInterfaces/IAnimationClip.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/clips/ClipInterfaces/IAnimationClip.ts deleted file mode 100644 index ef939bf369..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/clips/ClipInterfaces/IAnimationClip.ts +++ /dev/null @@ -1,7 +0,0 @@ -export default interface IAnimationClip { - speed: number; - duration: number; - startTime: number; - endTime: number; - evaluate: (time: number) => void; -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/clips/animation_clip.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/clips/animation_clip.ts deleted file mode 100644 index bc15cdbc92..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/clips/animation_clip.ts +++ /dev/null @@ -1,348 +0,0 @@ -import * as THREE from "three"; -import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"; -import { - FBXLoader, - MMDAnimationHelper, - MMDAnimationHelperMixer, -} from "three/examples/jsm/Addons.js"; -import { MMDLoader } from "three/addons/loaders/MMDLoader.js"; - -import { MoveAIResult, Retarget } from "../Editor/retargeting"; -import IAnimationClip from "./ClipInterfaces/IAnimationClip"; -import { GetCdnOrigin } from "~/api/GetCdnOrigin"; -import { StorytellerApiHostStore } from "@storyteller/api"; - -export class AnimationClip implements IAnimationClip { - version: number; - media_id: string; // comes from the server - object_uuid: string; - type: "animation"; - location: "glb" | "remote"; - speed: number; - duration: number; - startTime: number; - length: number; - clip_name: string; - mixer: THREE.AnimationMixer | undefined; - animation_clip: THREE.AnimationClip | undefined; - clip_action: THREE.AnimationAction | undefined; - special_properties: MoveAIResult[]; - retargeted: boolean; - last_frame: number; - - - private isMMD: boolean = false; - private obj: MMDAnimationHelperMixer | undefined; - private helper: MMDAnimationHelper | undefined; - private mmd_anim_clip: THREE.AnimationClip | undefined; - private pose: Object | undefined; - private processingClip: boolean = false; - private mmd_url: string = ""; - - constructor( - version: number, - media_id: string, - location: "glb" | "remote", - object_uuid: string, - speed: number, - length: number, - clip_name: string, - startTime: number, - duration: number, - ) { - this.version = version; - this.media_id = media_id; - this.type = "animation"; - this.object_uuid = object_uuid; - this.location = location; - this.speed = speed; - this.length = length; - this.startTime = startTime; - this.duration = duration; - this.clip_name = clip_name; - this.animation_clip; - this.mixer; - this.clip_action; - this.special_properties = []; - this.retargeted = false; - this.last_frame = 0; - } - - get endTime() { - return this.startTime + this.duration - } - - evaluate: (time: number) => void; - - async get_media_url() { - //This is for prod when we have the proper info on the url. - const apiSchemeAndHost = StorytellerApiHostStore.getInstance().getApiSchemeAndHost(); - const url = `${apiSchemeAndHost}/v1/media_files/file/${this.media_id}`; - - const response = await fetch(url); - const json = await JSON.parse(await response.text()); - const bucketPath = json["media_file"]["public_bucket_path"]; - - const media_api_base_url = GetCdnOrigin(); - - const media_url = `${media_api_base_url}${bucketPath}`; - return media_url; - } - - _load_animation(): Promise { - // Return the promise chain starting from `this.get_media_url()` - return this.get_media_url().then((url) => { - // Return a new Promise that resolves with the animation clip - return new Promise((resolve) => { - if (url.includes(".glb")) { - const glbLoader = new GLTFLoader(); - - glbLoader.load(url, (gltf) => { - // Assuming the animation is the first one in the animations array - const animationClip = gltf.animations[0]; - resolve(animationClip); - }); - } else if (url.includes(".fbx")) { - const fbxLoader = new FBXLoader(); - fbxLoader.load(url, (fbx) => { - const animationClip = fbx.animations[0]; - this.retargeted = true; - animationClip.tracks.forEach((track) => { - const retarget = new Retarget(); - const retarget_value = retarget.retarget(track.name); - track.name = retarget_value.bone; - console.log(track); - if (retarget_value.is_special) { - this.special_properties.push(retarget_value); - } - }); - resolve(animationClip); - }); - } else if (url.includes(".vmd")) { - console.log("VMD Loader"); - const root = this.mixer?.getRoot(); - this.isMMD = true; - if (root) { - const mmdLoader = new MMDLoader(); - this.mmd_url = url; - mmdLoader.loadAnimation(url, root as THREE.SkinnedMesh, (mmd) => { - mmd.name = this.mmd_url; - resolve(mmd as THREE.AnimationClip); - }); - console.log("Loaded"); - } - console.log("Dont"); - } - }); - }); - } - - _create_mixer(object: THREE.Object3D) { - this.mixer = new THREE.AnimationMixer(object); - return this.mixer; - } - - async _get_clip() { - if (this.animation_clip == null && this.mixer !== null) { - console.log(this.media_id); - if (this.media_id === "SelfClip") { - const rootObject = this.mixer?.getRoot(); - if (rootObject) - this.animation_clip = ( - rootObject as THREE.Object3D - ).animations[0]; - } else { - this.animation_clip = await this._load_animation(); - } - } - return this.animation_clip; - } - - async play(object: THREE.Object3D) { - if (this.mixer == null) { - this.mixer = this._create_mixer(object); - const anim_clip = await this._get_clip(); - if (anim_clip == undefined) { - return; - } - this.clip_action = this.mixer?.clipAction(anim_clip); - if (this.clip_action && this.isMMD === false) { - if (this.clip_action?.isRunning() == false) { - this.clip_action.play(); - } - } - } - } - - update_bones() { - if (this.retargeted === false) { - return; - } - const rootObject = this.mixer?.getRoot(); - if (rootObject) - for ( - let index_ = 0; - index_ < - (rootObject as THREE.Object3D).children.length; - index_++ - ) { - const child_holder = ( - rootObject as THREE.Object3D - ).children[index_]; - if (child_holder.type == "Bone") { - child_holder.traverse( - (bone: THREE.Object3D) => { - for ( - let index__ = 0; - index__ < this.special_properties.length; - index__++ - ) { - const property = this.special_properties[index__]; - if (property.bone == bone.name + ".quaternion") { - const quat_y = THREE.MathUtils.degToRad(property.y); - bone.rotateY(quat_y); - if (property.only_y == false) { - const quat_x = THREE.MathUtils.degToRad(property.x); - bone.rotateX(quat_x); - const quat_z = THREE.MathUtils.degToRad(property.z); - bone.rotateZ(quat_z); - } else { - if (child_holder.parent) { - child_holder.parent.rotation.x = - THREE.MathUtils.degToRad(-90); - } - } - - if (property.flip) { - bone.rotateX(bone.rotation.x * 2 * -1); - } - } else if (property.bone == bone.name + ".position") { - bone.position.set( - bone.position.x * property.x, - bone.position.y * property.y, - bone.position.z * property.z, - ); - } - } - }, - ); - } - } - } - - private async load_vpd_wrapped(): Promise { - return new Promise((resolve) => { - const poseLoader = new MMDLoader(); - poseLoader.loadVPD("/resources/pose/7.vpd", false, (vpd) => { - resolve(vpd); - }); // End of loadVPD call - }); // End of new Promise - } - - async animate(deltatime: number) { - // Sets the frame of the animation mixer. - this.mixer?.setTime(deltatime); - // Regtargeting for Move.AI - this.update_bones(); - - // Next - if (this.isMMD && this.mixer) { - if (this.obj === undefined) { - // Sets the ik helper and object for IK and animation. - this.helper = (this.mixer.getRoot() as THREE.Object3D).parent?.userData[ - "helper" - ]; - } - - this.obj = this.helper?.objects.get( - this.mixer?.getRoot() as THREE.SkinnedMesh, - ); - - const mesh = this.mixer?.getRoot() as THREE.SkinnedMesh; - - if (this.helper) { - this.helper._restoreBones(mesh); // Privateish js function for resetting bones. - this.obj?.mixer?.setTime(deltatime); // Sets the time. - this.helper._saveBones(mesh); // Saves the bones location for restoration later. - - mesh.updateMatrixWorld(true); // Updates mesh. - this.obj?.ikSolver.update(); // Updates IK Solver. - this.obj?.grantSolver.update(); // Updates Grant which moves the bones. - } - - // Changing of animation. - if ( - this.obj && - this.helper && - this.processingClip === false && - this.obj.mixer && - this.mixer._actions[0]._clip.name !== - this.obj.mixer._actions[0]._clip.name && - this.mixer !== undefined - ) { - // On animation change. - this.processingClip = true; - - const helper_mmd = this.helper.objects.get(mesh); - if (helper_mmd?.mixer !== undefined) { - const clips = helper_mmd.mixer._actions; - clips.forEach((clip) => { - this.helper?.objects.delete(clip._clip); - }); - } - - this.helper.remove(mesh); - mesh.pose(); - - const loader = new MMDLoader(); - loader.loadAnimation(this.mmd_url, mesh, (mmd) => { - mmd.name = this.mmd_url; - this.helper?.add(mesh, { - animation: mmd, - physics: false, - }); - this.processingClip = false; - if (helper_mmd?.mixer !== undefined) { - const clips = helper_mmd.mixer._actions; - clips.forEach((clip) => { - this.helper?.objects.delete(clip._clip); - }); - } - this.obj.mixer = this.mixer; - }); - } - } - } - - async step(deltatime: number, isPlaying: boolean, frame: number) { - if (this.mixer == null) { - return; - } - if (this.retargeted) { - if (isPlaying || Math.floor(frame) != this.last_frame) { - if (Math.floor(frame) != 0) { - await this.animate(deltatime); - } - } - } else { - await this.animate(deltatime); - } - this.last_frame = frame; - } - - stop() { - this.mixer?.setTime(0); - } - - toJSON() { - return { - version: this.version, - media_id: this.media_id, - object_uuid: this.object_uuid, - type: this.type, - speed: this.speed, - length: this.length, - clip_name: this.clip_name, - }; - } -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/clips/audio_clip.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/clips/audio_clip.ts deleted file mode 100644 index 0aef20d66c..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/clips/audio_clip.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { GetCdnOrigin } from "~/api/GetCdnOrigin"; -import Ijson from "~/interfaces/Ijson"; -import { StorytellerApiHostStore } from "@storyteller/api"; - -interface AudioDataInterface { - audioContext: AudioContext; - audioBuffer: AudioBuffer; -} - -class AudioData implements AudioDataInterface { - audioContext: AudioContext; - audioBuffer: AudioBuffer; - source: AudioBufferSourceNode | undefined; - - constructor(audioContext: AudioContext, audioBuffer: AudioBuffer) { - this.audioContext = audioContext; - this.audioBuffer = audioBuffer; - } -} - -export class AudioClip implements Ijson { - version: number; - media_id: string; - type: "audio"; - volume: number; - audio_data: AudioData | undefined; - - constructor(version: number, media_id: string, volume: number) { - this.version = version; - this.media_id = media_id; - this.type = "audio"; - this.volume = volume; - this.download_audio().then((data) => { - this.audio_data = data; - }); - } - - async get_media_url() { - //This is for prod when we have the proper info on the url. - const apiSchemeAndHost = StorytellerApiHostStore.getInstance().getApiSchemeAndHost(); - const url = `${apiSchemeAndHost}/v1/media_files/file/${this.media_id}`; - - const response = await fetch(url); - const json = await JSON.parse(await response.text()); - const bucketPath = json["media_file"]["public_bucket_path"]; - const media_api_base_url = GetCdnOrigin(); - - const media_url = `${media_api_base_url}${bucketPath}`; - return media_url; - } - - async download_audio() { - const url = await this.get_media_url(); - const audioContext = new AudioContext(); - const response = await fetch(url); - const arrayBuffer = await response.arrayBuffer(); - const audioBuffer = await audioContext.decodeAudioData(arrayBuffer); - return new AudioData(audioContext, audioBuffer); - } - - toJSON() { - return { - version: this.version, - media_id: this.media_id, - type: this.type, - volume: this.volume, - }; - } -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/clips/clip_ui.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/clips/clip_ui.ts deleted file mode 100644 index 12702fd82a..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/clips/clip_ui.ts +++ /dev/null @@ -1,71 +0,0 @@ -// Clip offsets represent the state of the clip on the timeline as well as what type of clip it is. -// it is created from a media id. -import { MediaFileType } from "~/pages/PageEnigma/enums"; -import { ClipGroup, ClipType } from "~/enums"; -import Ijson from "~/interfaces/Ijson"; - -export class ClipUI implements Ijson { - version: number; - type: ClipType; - group: ClipGroup; - name: string; - media_id: string; - object_uuid: string; - object_name: string; - offset: number; // in frames - length: number; // in frames - should_play: boolean; - clip_uuid: string; - keyframe_offset: number; - media_file_type: MediaFileType; - - constructor( - version: number, - type: ClipType, - group: ClipGroup, - name: string, - media_id: string, - clip_uuid: string, - object_uuid: string, - object_name: string, - offset: number, - length: number, - keyframe_offset: number = 0, - media_file_type: MediaFileType, - ) { - this.version = version; - this.group = group; // Only needed for UI - - this.clip_uuid = clip_uuid; - - this.name = name; // UI - this.type = type; // UI and Animation / Audio / Lipsync / : Engine - - this.object_uuid = object_uuid; // Animation / Audio / Lipsync / : Engine - this.object_name = object_name; - this.media_id = media_id; // Animation / Audio / Lipsync / : Engine - this.offset = offset; - this.length = length; - this.should_play = true; - this.keyframe_offset = keyframe_offset; - this.media_file_type = media_file_type; - } - - toJSON() { - return { - version: this.version, - group: this.group, - name: this.name, - type: this.type, - clip_uuid: this.clip_uuid, - object_uuid: this.object_uuid, - object_name: this.object_name, - media_id: this.media_id, - start_offset: this.offset, - ending_offset: this.length, - keyframe_offset: this.keyframe_offset, - media_file_type: this.media_file_type, - }; - } -} - diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/clips/emotion_clip.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/clips/emotion_clip.ts deleted file mode 100644 index 363257186b..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/clips/emotion_clip.ts +++ /dev/null @@ -1,143 +0,0 @@ -import * as THREE from "three"; -import { GetCdnOrigin } from "~/api/GetCdnOrigin"; -import Ijson from "~/interfaces/Ijson"; -import { StorytellerApiHostStore } from "@storyteller/api"; - -interface CsvJson { - [key: string]: string[]; -} - -export class EmotionClip implements Ijson { - version: number; - media_id: string; - type: "expression"; - emotion_json: any; - faces: THREE.Mesh[]; - - constructor(version: number, media_id: string) { - this.version = version; - this.media_id = media_id; - this.type = "expression"; - this.faces = []; - this.download_csv().then((data) => { - this.emotion_json = data; - }); - } - - async get_media_url() { - //This is for prod when we have the proper info on the url. - const apiSchemeAndHost = StorytellerApiHostStore.getInstance().getApiSchemeAndHost(); - const url = `${apiSchemeAndHost}/v1/media_files/file/${this.media_id}`; - - const response = await fetch(url); - const json = await JSON.parse(await response.text()); - const bucketPath = json["media_file"]["public_bucket_path"]; - const media_api_base_url = GetCdnOrigin(); - const media_url = `${media_api_base_url}${bucketPath}`; - return media_url; - } - - async download_json() { - const url = await this.get_media_url(); - const response = await fetch(url); - return await response.json(); - } - - async download_csv(): Promise { - const url = await this.get_media_url(); - const response = await fetch(url); - const csvText = await response.text(); - - // Parsing the CSV text - const rows = csvText.trim().split("\n"); - const header = rows[0].split(","); - const jsonData: CsvJson = {}; - - header.forEach((key) => (jsonData[key] = [])); - - for (let i = 1; i < rows.length; i++) { - const values = rows[i].split(","); - values.forEach((value, index) => { - jsonData[header[index]].push(value); - }); - } - - return jsonData; - } - - async _detect_face(object: THREE.Object3D): Promise { - this.faces = []; - return new Promise((resolve) => { - object.traverse((c: THREE.Object3D) => { - if (c instanceof THREE.Mesh) { - if (c.morphTargetInfluences && c.morphTargetDictionary) { - const blendShapeIndexE = c.morphTargetDictionary["jawOpen"]; - if (blendShapeIndexE !== undefined) { - this.faces.push(c); - resolve(c); - } - } - } - }); - }); - } - - setBlends(shapes: { [key: string]: number }) { - this.faces.forEach((element: THREE.Mesh) => { - if (element.morphTargetInfluences && shapes) { - Object.keys(shapes).forEach((key) => { - let index = element.morphTargetDictionary?.[key]; - if (index === undefined) { - index = - element.morphTargetDictionary?.[ - key.charAt(0).toLowerCase() + key.slice(1) - ]; - } - if ( - typeof index === "number" && - element.morphTargetInfluences !== undefined - ) { - element.morphTargetInfluences[index] = shapes[key]; - } - }); - } - }); - } - - async reset(object: THREE.Object3D) { - await this._detect_face(object); - const keys: { [key: string]: number } = {}; - if (this.emotion_json === undefined || this.emotion_json === null) { - return; - } - Object.keys(this.emotion_json).forEach((key) => { - keys[key] = 0; - }); - this.setBlends(keys); - } - - async step(frame: number, object: THREE.Object3D) { - if (this.faces.length <= 0) { - await this._detect_face(object); - } - if (this.emotion_json === undefined || this.emotion_json === null) { - return; - } - const keys: { [key: string]: number } = {}; - Object.keys(this.emotion_json).forEach((key) => { - if (Math.floor(frame) > this.emotion_json[key].length) { - return; - } - keys[key] = this.emotion_json[key][Math.floor(frame)]; - }); - this.setBlends(keys); - } - - toJSON() { - return { - version: this.version, - media_id: this.media_id, - type: this.type, - }; - } -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/clips/lipsync_clip.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/clips/lipsync_clip.ts deleted file mode 100644 index 0787bb2ed9..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/clips/lipsync_clip.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { LipSync } from "../Editor/lipsync"; -import * as THREE from "three"; -import { GetCdnOrigin } from "~/api/GetCdnOrigin"; -import Ijson from "~/interfaces/Ijson"; -import { StorytellerApiHostStore } from "@storyteller/api"; - -interface AudioDataInterface { - audioContext: AudioContext; - audioBuffer: AudioBuffer; -} - -class AudioData implements AudioDataInterface { - audioContext: AudioContext; - audioBuffer: AudioBuffer; - source: AudioBufferSourceNode | undefined; - - constructor(audioContext: AudioContext, audioBuffer: AudioBuffer) { - this.audioContext = audioContext; - this.audioBuffer = audioBuffer; - } -} - -class BlendShapeHelper { - ah: number; - ee: number; - oh: number; - constructor(ah: number, ee: number, oh: number) { - this.ah = ah; - this.ee = ee; - this.oh = oh; - } -} - -export class LipSyncClip implements Ijson { - version: number; - media_id: string; - type: "lipsync"; - volume: number; - audio_data: AudioData | undefined; - lipsync: LipSync; - blendshape_helper: BlendShapeHelper; - faces: THREE.Mesh[]; - - constructor(version: number, media_id: string, volume: number) { - this.version = version; - this.media_id = media_id; - this.type = "lipsync"; - this.volume = volume; - this.download_audio().then((data) => { - this.audio_data = data; - }); - this.blendshape_helper = new BlendShapeHelper(0, 0, 0); - // we might need 3 of these one for each character ... - this.lipsync = new LipSync(); - this.faces = []; - } - - // lip sync will be generated through TTS - async get_media_url() { - //This is for prod when we have the proper info on the url. - const apiSchemeAndHost = StorytellerApiHostStore.getInstance().getApiSchemeAndHost(); - const url = `${apiSchemeAndHost}/v1/media_files/file/${this.media_id}`; - - console.log(`API BASE URL? ${apiSchemeAndHost}`); - console.log(`CALLED URL? ${url}`); - - const response = await fetch(url); - const json = await JSON.parse(await response.text()); - const bucketPath = json["media_file"]["public_bucket_path"]; - const media_api_base_url = GetCdnOrigin(); - //const media_base_url = `${media_api_base_url}/vocodes-public`; - //const media_url = `${media_base_url}${bucketPath}`; - const media_url = `${media_api_base_url}${bucketPath}`; - return media_url; - } - - async download_audio() { - const url = await this.get_media_url(); - const audioContext = new AudioContext(); - const response = await fetch(url); - const arrayBuffer = await response.arrayBuffer(); - const audioBuffer = await audioContext.decodeAudioData(arrayBuffer); - return new AudioData(audioContext, audioBuffer); - } - - async _detect_face(object: THREE.Object3D): Promise { - return new Promise((resolve) => { - object.traverse((c: THREE.Object3D) => { - if (c instanceof THREE.Mesh) { - if (c.morphTargetInfluences && c.morphTargetDictionary) { - let blendShapeIndexE = c.morphTargetDictionary["E"]; - let blendShapeIndexO = c.morphTargetDictionary["O"]; - let blendShapeIndexA = c.morphTargetDictionary["aa"]; - - if (blendShapeIndexE === undefined) { - blendShapeIndexE = c.morphTargetDictionary["お"]; // MMD OH - blendShapeIndexO = c.morphTargetDictionary["ω"]; // MMD O - blendShapeIndexA = c.morphTargetDictionary["あ"]; // MMD A - } - - if (blendShapeIndexE != null) { - this.blendshape_helper = new BlendShapeHelper( - blendShapeIndexA, - blendShapeIndexE, - blendShapeIndexO, - ); - this.faces.push(c); - resolve(c); - } - } - } - }); - }); - } - - async play(object: THREE.Object3D) { - if (this.audio_data?.audioBuffer == null) { - await this.download_audio(); - } - if (this.lipsync.face == null) { - this.lipsync = new LipSync(await this._detect_face(object)); - this.lipsync.startLipSyncFromAudioBuffer(this.audio_data?.audioBuffer); - } - } - - stop() { - if (this.lipsync == null) { - return; - } - this.lipsync.destroy(); - } - - setBlends(ah: number, ee: number, oh: number) { - this.faces.forEach((element: THREE.Mesh) => { - if (element.morphTargetInfluences) { - element.morphTargetInfluences[this.blendshape_helper.ee] = ee; - element.morphTargetInfluences[this.blendshape_helper.ah] = ah; - element.morphTargetInfluences[this.blendshape_helper.oh] = oh; - } - }); - } - - step( - frame: number, - offset: number, - // rendering: boolean - ) { - if (this.lipsync == null) { - return; - } - const positions = this.lipsync.update(frame, offset, true); - if (positions) - this.setBlends(positions["ee"], positions["ah"], positions["oh"]); - } - - reset() { - if (this.lipsync.face != undefined) { - this.setBlends(0, 0, 0); - } - this.lipsync = new LipSync(); - } - - toJSON() { - return { - version: this.version, - media_id: this.media_id, - type: this.type, - volume: this.volume, - }; - } -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/clips/transform_clip.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/clips/transform_clip.ts deleted file mode 100644 index 3fdfb7e631..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/clips/transform_clip.ts +++ /dev/null @@ -1,238 +0,0 @@ -import * as THREE from "three"; -import Scene from "../Editor/scene"; - -export class TransformFrame { - position: THREE.Vector3; - rotation: THREE.Vector3; - scale: THREE.Vector3; - offset: number; - keyframe_uuid: string; - - constructor( - position: THREE.Vector3, - rotation: THREE.Vector3, - scale: THREE.Vector3, - offset: number, - keyframe_uuid: string, - ) { - this.position = position; - this.rotation = rotation; - this.scale = scale; - this.offset = offset; - this.keyframe_uuid = keyframe_uuid; - } -} - -export class TransformClip { - version: number; - media_id: string; - object_uuid: string; - type: "transform"; - keyframes: TransformFrame[]; - length: number; - - step_frame: number; - looping: boolean; - - offset: number; - - constructor( - version: number, - object_uuid: string, - length: number, - media_id: string = "", - ) { - this.version = version; - this.media_id = media_id; - this.object_uuid = object_uuid; - this.type = "transform"; - - this.length = length; - - this.keyframes = []; - - this.step_frame = 0; - this.offset = 0; - - this.looping = false; - } - - private findNextNumber(location: number) { - // Sort the transform frames based on their offset in ascending order - this.keyframes.sort((a, b) => a.offset - b.offset); - - let currentKeyframe: TransformFrame | undefined; - let nextKeyframe: TransformFrame | undefined; - - this.length = - this.keyframes[this.keyframes.length - 1].offset + this.offset; - - for (const frame of this.keyframes) { - if (frame.offset <= location) { - currentKeyframe = frame; - } else if (frame.offset > location) { - nextKeyframe = frame; - break; - } - } - - return { currentKeyframe, nextKeyframe }; - } - - public setOffset(keyframe_uuid: string, keyframe_offset: number) { - this.keyframes.forEach((item) => { - if (item.keyframe_uuid === keyframe_uuid) { - item.offset = keyframe_offset; - return; - } - }); - } - - public setTransform( - keyframe_uuid: string, - position: THREE.Vector3, - rotation: THREE.Vector3, - scale: THREE.Vector3, - ) { - this.keyframes.forEach((item) => { - if (item.keyframe_uuid === keyframe_uuid) { - item.position = position; - item.rotation = rotation; - item.scale = scale; - return; - } - }); - } - - public removeKeyframe(keyframe_uuid: string) { - this.keyframes = this.keyframes.filter( - (item) => item.keyframe_uuid !== keyframe_uuid, - ); - } - - request_point_location(keyframe_uuid: string, scene: Scene) { - const point = scene.getPoint(keyframe_uuid); - if (point == undefined) { - console.log("Could not find point."); - return; - } - this.keyframes.forEach((keyframe) => { - if (keyframe.keyframe_uuid == keyframe_uuid) { - keyframe.position = point.position; - keyframe.rotation = new THREE.Vector3( - point.rotation.x, - point.rotation.y, - point.rotation.z, - ); - keyframe.scale = point.scale; - } - }); - return point; - } - - step(object: THREE.Object3D, offset: number, frame: number, scene: Scene) { - this.offset = offset; - this.step_frame = frame - offset; - //if (this.step_frame < 0) { this.step_frame = 0; } - //if (this.step_frame >= this.length) { return; } // Reached max frames. - if (this.keyframes.length < 1) { - return; - } // If there are enough points in the scene. - - // Find the current and next keyframes based on time_frame - const { currentKeyframe, nextKeyframe } = this.findNextNumber( - this.step_frame, - ); - if (nextKeyframe != undefined && currentKeyframe != undefined) { - const small_step_frame = this.step_frame - currentKeyframe.offset; - let location = - small_step_frame / (nextKeyframe.offset - currentKeyframe.offset); - if (location < 0) { - location = 0; - } - const pos_s = currentKeyframe?.position; - let pos_e = nextKeyframe?.position; - if (pos_e === undefined) { - this.request_point_location(nextKeyframe.keyframe_uuid, scene); - pos_e = nextKeyframe?.position; - } - const points_pos = [ - new THREE.Vector3(pos_s.x, pos_s.y, pos_s.z), - new THREE.Vector3(pos_e.x, pos_e.y, pos_e.z), - ]; - const curve_pos = new THREE.CatmullRomCurve3(points_pos); - const point_pos = curve_pos.getPoint(location); - object.position.copy(point_pos); - - const rot_s = currentKeyframe?.rotation; - const rot_e = nextKeyframe?.rotation; - const quat_s = new THREE.Quaternion().setFromEuler( - new THREE.Euler( - THREE.MathUtils.degToRad(rot_s.x), - THREE.MathUtils.degToRad(rot_s.y), - THREE.MathUtils.degToRad(rot_s.z), - ), - ); - const quat_e = new THREE.Quaternion().setFromEuler( - new THREE.Euler( - THREE.MathUtils.degToRad(rot_e.x), - THREE.MathUtils.degToRad(rot_e.y), - THREE.MathUtils.degToRad(rot_e.z), - ), - ); - const quaternion = new THREE.Quaternion(); - quaternion.slerpQuaternions(quat_s, quat_e, location); - object.quaternion.copy(quaternion); - - const scale_s = currentKeyframe?.scale; - const scale_e = nextKeyframe?.scale; - const points_scale = [ - new THREE.Vector3(scale_s.x, scale_s.y, scale_s.z), - new THREE.Vector3(scale_e.x, scale_e.y, scale_e.z), - ]; - const curve_scale = new THREE.CatmullRomCurve3(points_scale); - const point_scale = curve_scale.getPoint(location); - object.scale.copy(point_scale); - } - } - - reset(object: THREE.Object3D) { - if (this.keyframes.length > 0) { - const first_pos = this.keyframes[0].position; - const first_rot = this.keyframes[0].rotation; - const first_quat = new THREE.Euler( - THREE.MathUtils.degToRad(first_rot.x), - THREE.MathUtils.degToRad(first_rot.y), - THREE.MathUtils.degToRad(first_rot.z), - ); - const first_scl = this.keyframes[0].scale; - object.position.copy(first_pos); - object.rotation.copy(first_quat); - object.scale.copy(first_scl); - this.step_frame = 0; - } - } - - add_frame( - position: THREE.Vector3, - rotation: THREE.Vector3, - scale: THREE.Vector3, - offset: number, - keyframe_uuid: string, - ) { - this.keyframes.push( - new TransformFrame(position, rotation, scale, offset, keyframe_uuid), - ); - } - - toJSON(): any { - return { - version: this.version, - media_id: this.media_id, - object_uuid: this.object_uuid, - length: this.length, - type: this.type, - keyframes: this.keyframes, - }; - } -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/composable_engines/audio_engine.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/composable_engines/audio_engine.tsx deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/composable_engines/lipsync_engine.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/composable_engines/lipsync_engine.tsx deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/composable_engines/scene_format.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/composable_engines/scene_format.tsx deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/AspectRatioMenu/AspectRatioMenu.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/AspectRatioMenu/AspectRatioMenu.tsx deleted file mode 100644 index 30b8e08e4e..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/AspectRatioMenu/AspectRatioMenu.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { useSignals } from "@preact/signals-react/runtime"; -import { - faRectangle, - faRectangleVertical, - faSquare, -} from "@fortawesome/pro-solid-svg-icons"; -import { CameraAspectRatio } from "~/pages/PageEnigma/enums"; -import { cameraAspectRatio } from "~/pages/PageEnigma/signals"; -import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; -import Queue from "~/pages/PageEnigma/Queue/Queue"; -import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions"; -import { ButtonDropdown } from "@storyteller/ui-button-dropdown"; - -export const AspectRatioMenu = () => { - useSignals(); - const buttonIcon = - cameraAspectRatio.value === CameraAspectRatio.HORIZONTAL_16_9 - ? faRectangle - : cameraAspectRatio.value === CameraAspectRatio.VERTICAL_9_16 - ? faRectangleVertical - : cameraAspectRatio.value === CameraAspectRatio.HORIZONTAL_3_2 - ? faRectangle - : cameraAspectRatio.value === CameraAspectRatio.VERTICAL_2_3 - ? faRectangleVertical - : faSquare; - const buttonText = - cameraAspectRatio.value === CameraAspectRatio.HORIZONTAL_16_9 - ? "16:9 Horizontal" - : cameraAspectRatio.value === CameraAspectRatio.VERTICAL_9_16 - ? "9:16 Vertical" - : cameraAspectRatio.value === CameraAspectRatio.HORIZONTAL_3_2 - ? "3:2 Horizontal" - : cameraAspectRatio.value === CameraAspectRatio.VERTICAL_2_3 - ? "2:3 Vertical" - : "1:1 Squared"; - - const handleChangeAspectRatio = (newRatio: CameraAspectRatio) => { - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: toEngineActions.CHANGE_CAMERA_ASPECT_RATIO, - data: newRatio, - }); - }; - return ( -
- - handleChangeAspectRatio(CameraAspectRatio.HORIZONTAL_16_9), - }, - { - label: "3:2", - icon: faRectangle, - className: "pl-4", - description: "Horizontal", - selected: - cameraAspectRatio.value === CameraAspectRatio.HORIZONTAL_3_2, - onClick: () => - handleChangeAspectRatio(CameraAspectRatio.HORIZONTAL_3_2), - }, - { - label: "2:3", - icon: faRectangleVertical, - className: "pl-4", - description: "Vertical", - selected: - cameraAspectRatio.value === CameraAspectRatio.VERTICAL_2_3, - onClick: () => - handleChangeAspectRatio(CameraAspectRatio.VERTICAL_2_3), - }, - { - label: "9:16", - icon: faRectangleVertical, - className: "pl-4", - description: "Vertical", - selected: - cameraAspectRatio.value === CameraAspectRatio.VERTICAL_9_16, - onClick: () => - handleChangeAspectRatio(CameraAspectRatio.VERTICAL_9_16), - }, - { - label: "1:1", - icon: faSquare, - className: "pl-4", - description: "Squared", - selected: cameraAspectRatio.value === CameraAspectRatio.SQUARE_1_1, - onClick: () => - handleChangeAspectRatio(CameraAspectRatio.SQUARE_1_1), - }, - ]} - /> -
- ); -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/CameraSettingsModal/CameraSettingsModal.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/CameraSettingsModal/CameraSettingsModal.tsx deleted file mode 100644 index 82e8c9816a..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/CameraSettingsModal/CameraSettingsModal.tsx +++ /dev/null @@ -1,230 +0,0 @@ -import { Modal } from "@storyteller/ui-modal"; -import { faPlus, faTrashAlt } from "@fortawesome/pro-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { PopoverItem } from "@storyteller/ui-popover"; -import { Input } from "@storyteller/ui-input"; -import { Label } from "@storyteller/ui-label"; -import { Button } from "@storyteller/ui-button"; -import { SliderV2 } from "@storyteller/ui-sliderv2"; -import { focalLengthDragging } from "~/pages/PageEnigma/signals/camera"; -import { useState, useEffect } from "react"; -import { twMerge } from "tailwind-merge"; -import { Tooltip } from "@storyteller/ui-tooltip"; - -interface ExtendedPopoverItem extends PopoverItem { - id: string; - focalLength: number; - position: { x: number; y: number; z: number }; - rotation: { x: number; y: number; z: number }; - lookAt: { x: number; y: number; z: number }; -} - -interface CameraSettingsModalProps { - isOpen: boolean; - onClose: () => void; - cameras: ExtendedPopoverItem[]; - onCameraNameChange: (id: string, newName: string) => void; - onCameraFocalLengthChange: (id: string, value: number) => void; - onAddCamera: () => void; - selectedCameraId: string; - handleCameraSelect: (selectedItem: PopoverItem) => void; - onDeleteCamera: (id: string) => void; -} - -export const CameraSettingsModal = ({ - isOpen, - onClose, - cameras, - onCameraNameChange, - onCameraFocalLengthChange, - onAddCamera, - selectedCameraId, - handleCameraSelect, - onDeleteCamera, -}: CameraSettingsModalProps) => { - const selectedCamera = cameras.find((cam) => cam.id === selectedCameraId); - const [isDragging, setIsDragging] = useState(false); - const [tempCameraName, setTempCameraName] = useState(""); - const [editingCameraId, setEditingCameraId] = useState(null); - - useEffect(() => { - if (selectedCamera) { - setTempCameraName(selectedCamera.label); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedCamera?.id]); - - useEffect(() => { - const handlePointerUp = () => { - setIsDragging(false); - focalLengthDragging.value = { - isDragging: false, - focalLength: selectedCamera?.focalLength || 35, - }; - }; - document.addEventListener("pointerup", handlePointerUp); - return () => document.removeEventListener("pointerup", handlePointerUp); - }, [selectedCamera?.focalLength]); - - const handlePointerDown = () => { - setIsDragging(true); - focalLengthDragging.value = { - isDragging: true, - focalLength: selectedCamera?.focalLength || 35, - }; - }; - - const handleFocalLengthChange = (id: string, value: number) => { - focalLengthDragging.value = { isDragging: true, focalLength: value }; - onCameraFocalLengthChange(id, value); - }; - - const handleNameBlur = () => { - if (selectedCamera && tempCameraName.trim() !== "") { - onCameraNameChange(selectedCamera.id, tempCameraName); - } else if (selectedCamera) { - setTempCameraName(selectedCamera.label); - } - setEditingCameraId(null); - }; - - const handleNameFocus = (id: string) => { - setEditingCameraId(id); - }; - - return ( - -
-
-
-

Camera

- - - -
-
-
- {cameras.map((camera) => ( - - ))} -
-
-
-
-
-
-

- {selectedCamera?.label || "Camera"} -

- - - -
-
-
-
- - setTempCameraName(e.target.value)} - onBlur={handleNameBlur} - onFocus={() => - selectedCamera && handleNameFocus(selectedCamera.id) - } - className="text-sm" - /> -
-
- -
- - selectedCamera && - handleFocalLengthChange(selectedCamera.id, value) - } - step={1} - suffix="mm" - showDecrement={true} - showIncrement={true} - className="w-full" - /> - - {selectedCamera?.focalLength || 35}mm - -
-
-
-
-
- -
-
-
-
-
- ); -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/CameraSettingsModal/index.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/CameraSettingsModal/index.tsx deleted file mode 100644 index a4fc358dc1..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/CameraSettingsModal/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { CameraSettingsModal } from "./CameraSettingsModal"; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/ControlsTopButtons/TestFeaturesButtons.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/ControlsTopButtons/TestFeaturesButtons.tsx deleted file mode 100644 index c7c1bec484..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/ControlsTopButtons/TestFeaturesButtons.tsx +++ /dev/null @@ -1,74 +0,0 @@ -//TODO: DELETE - -import { useContext } from "react"; -import { Button } from "@storyteller/ui-button" -import { EngineContext } from "~/pages/PageEnigma/contexts/EngineContext"; - -export const TestFeaturesButtons = ({ debug }: { debug: boolean }) => { - if (!debug) return null; - - const editorEngine = useContext(EngineContext); - - const handleButtonCameraView = () => { - editorEngine?.switchCameraView(); - }; - const testStylizeRequest = () => { - // editorEngine?.testStylizeRequest(); - //console.log("editorEnging does not have testStylizeRequest"); - }; - const handleButtonRender = () => { - editorEngine?.generateVideo(); - }; - - const handleButtonTakeFrame = () => { - // editorEngine?.take_timeline_cam_clip(); - //console.log("editorEnging does not have take_timeline_cam_clip"); - }; - - const handleButtonSingleFrame = () => { - editorEngine?.generateFrame(); - }; - - const handleButtonPlayBack = () => { - editorEngine?.togglePlayback(); - }; - - const smallButtons = "text-xs p-2 h-6 "; - const tertiaryColor = - "bg-brand-tertiary hover:bg-brand-teriary-400 focus-visible:outline-brand-tertiary "; - - return ( - <> -
- -
-
- - - - -
- - ); -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/ControlsVideo/ControlsVideo.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/ControlsVideo/ControlsVideo.tsx deleted file mode 100644 index 6b5c3af191..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/ControlsVideo/ControlsVideo.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import { useContext, useState } from "react"; -import { useSignals } from "@preact/signals-react/runtime"; -import { - faBackwardStep, - faForwardStep, - faCirclePause, - faCirclePlay, - faPlay, - faRepeat, -} from "@fortawesome/pro-solid-svg-icons"; -import { ButtonIcon } from "@storyteller/ui-button-icon"; -import { Tooltip } from "@storyteller/ui-tooltip"; -import { EngineContext } from "~/pages/PageEnigma/contexts/EngineContext"; -import { - currentTime, - filmLength, - secondaryScrubber, -} from "~/pages/PageEnigma/signals"; -import Queue from "~/pages/PageEnigma/Queue/Queue"; -import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; -import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions"; -import { editorState } from "~/pages/PageEnigma/signals/engine"; -import { EditorStates } from "~/pages/PageEnigma/enums"; -import { twMerge } from "tailwind-merge"; -import { usePosthogFeatureFlag } from "~/hooks/usePosthogFeatureFlag"; -import { FeatureFlags } from "~/enums"; - -export const ControlsVideo = () => { - useSignals(); - const editorEngine = useContext(EngineContext); - const [refresh, setRefresh] = useState(0); - const showStylePage = usePosthogFeatureFlag(FeatureFlags.SHOW_STYLE_PAGE); - - function formatTime(millis: number) { - const date = new Date(millis); - const secs = String(date.getUTCSeconds()).padStart(2, "0"); - const ms = String(date.getUTCMilliseconds()).padStart(3, "0"); - return `00:${secs}:${ms}`; - } - - const isPlaying = - editorEngine !== null ? editorEngine.timeline.is_playing : false; - const isRepeating = - editorEngine !== null ? editorEngine.timeline.is_repeating : false; - - const handleToStart = () => { - currentTime.value = 0; - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: toEngineActions.UPDATE_TIME, - data: { currentTime: currentTime.value }, - }); - }; - - const handleToEnd = () => { - currentTime.value = filmLength.value * 1000; - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: toEngineActions.UPDATE_TIME, - data: { currentTime: currentTime.value }, - }); - }; - - const handleBackwardStep = () => { - currentTime.value = Math.max(currentTime.value - 1, 0); - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: toEngineActions.UPDATE_TIME, - data: { currentTime: currentTime.value }, - }); - }; - const handleRepeat = () => { - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: toEngineActions.TOGGLE_REPEATING, - data: null, - }); - setRefresh((refresh + 1) % 10); - }; - const handlePlayback = () => { - editorEngine?.togglePlayback(); - }; - const handleForwardStep = () => { - currentTime.value = Math.min( - currentTime.value + 1, - filmLength.value * 1000, - ); - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: toEngineActions.UPDATE_TIME, - data: { currentTime: currentTime.value }, - }); - }; - - if (editorState.value === EditorStates.PREVIEW) { - return null; - } - - return ( -
-
-
-
- - -
- - - -
- - -
-
-
-
- - {formatTime(secondaryScrubber.value)} - - / - - {formatTime(filmLength.value * 1000)} - -
- {showStylePage && ( - <> -
- - - - - )} -
-
-
- ); -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/ControlsVideo/index.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/ControlsVideo/index.tsx deleted file mode 100644 index d4c0c69732..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/ControlsVideo/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { ControlsVideo } from './ControlsVideo'; \ No newline at end of file diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/DragComponent/DragComponent.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/DragComponent/DragComponent.tsx deleted file mode 100644 index 39df55aa2d..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/DragComponent/DragComponent.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { - canDrop, - currPosition, - dragItem, - overTimeline, - scale, - timelineHeight, -} from "~/pages/PageEnigma/signals"; -import { useSignals } from "@preact/signals-react/runtime"; -import DndAsset from "~/pages/PageEnigma/DragAndDrop/DndAsset"; -import { MIN_ANIM_DURATION } from "~/constants"; - -export const DragComponent = () => { - useSignals(); - if (!dragItem.value) { - return null; - } - const { currX, currY } = currPosition.value; - - const thumbnail = dragItem.value.thumbnail - ? dragItem.value.thumbnail - : `/resources/images/default-covers/${dragItem.value.imageIndex || 0}.webp`; - - if (overTimeline.value) { - if (canDrop.value && DndAsset.overElement) { - return ( - <> -
-
- - ); - } - return ( -
- {DndAsset.notDropText || "Cannot drop here"} -
- ); - } - - return ( -
- {dragItem.value.name} -
- {dragItem.value.name || dragItem.value.media_id} -
-
- ); -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/GenerateModals/Activity.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/GenerateModals/Activity.tsx deleted file mode 100644 index 85ca6567c1..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/GenerateModals/Activity.tsx +++ /dev/null @@ -1,269 +0,0 @@ -import { useSignals } from "@preact/signals-react/runtime"; -import { faBell, faSpinnerThird } from "@fortawesome/pro-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { PopoverMenu } from "@storyteller/ui-popover"; -import { CompletedCard } from "./CompletedCard"; -import { InProgressCard } from "./InProgressCard"; -import { Tooltip } from "@storyteller/ui-tooltip"; -import { useEffect, useState, useRef, useCallback } from "react"; -import { JobsApi } from "~/Classes/ApiManager/JobsApi"; -import { toast } from "@storyteller/ui-toaster"; -import { ActiveJob } from "~/pages/PageEnigma/models"; -import { IsJobPollingDisabled } from "~/flags/DevConsoleFlags"; - -// TODO ensure we de-dupe all this extra code. -interface CompletedItem { - token: string; - maybe_title: string; - public_bucket_path: string; // TODO: STOP USING THIS. - maybe_media_links_thumbnail?: string; - updated_at: string; - maybe_result?: { - entity_token?: string; - }; -} - -// { -// "job_token": "jinf_421nnr6rj8wy2sj0s11v9fns32s", -// "request": { -// "inference_category": "image_generation", -// "maybe_model_type": "image_gen_api", -// "maybe_model_token": null, -// "maybe_model_title": "Image Generation", -// "maybe_raw_inference_text": null, -// "maybe_style_name": null, -// "maybe_live_portrait_details": null, -// "maybe_lipsync_details": null -// }, -// "status": { -// "status": "complete_success", -// "maybe_extra_status_description": null, -// "maybe_assigned_worker": "inference-job-4o-image-848c55d5dd-555kn", -// "maybe_assigned_cluster": "storyteller-eks-1", -// "maybe_first_started_at": "2025-04-15T04:41:01Z", -// "maybe_current_execution_duration_seconds": null, -// "attempt_count": 1, -// "requires_keepalive": false, -// "maybe_failure_category": null, -// "progress_percentage": 100 -// }, -// "maybe_result": { -// "entity_type": "media_file", -// "entity_token": "m_ytrjp1fcd5tb43ebcs3ewhq6y05tfb", -// "maybe_public_bucket_media_path": "/media/p/t/7/y/g/pt7yg2xfv4ynemssfv1mwb7yxe26e2nb/image_pt7yg2xfv4ynemssfv1mwb7yxe26e2nb.png", -// "media_links": { -// "cdn_url": "https://cdn-2.fakeyou.com/media/p/t/7/y/g/pt7yg2xfv4ynemssfv1mwb7yxe26e2nb/image_pt7yg2xfv4ynemssfv1mwb7yxe26e2nb.png", -// "maybe_thumbnail_template": "https://cdn-2.fakeyou.com/cdn-cgi/image/width={WIDTH},quality=95/media/p/t/7/y/g/pt7yg2xfv4ynemssfv1mwb7yxe26e2nb/image_pt7yg2xfv4ynemssfv1mwb7yxe26e2nb.png", -// "maybe_video_previews": null -// }, -// "maybe_successfully_completed_at": "2025-04-15T04:42:14Z" -// }, -// "created_at": "2025-04-15T04:41:01Z", -// "updated_at": "2025-04-15T04:42:14Z" -// } - -export function Activity() { - useSignals(); - const [completedItems, setCompletedItems] = useState([]); - const [jobs, setJobs] = useState([]); - const processedJobs = useRef(new Set()); - const isFirstLoad = useRef(true); - const toastTimeoutRef = useRef(null); - - // Debounced toast function - const showToast = useCallback((items: CompletedItem[]) => { - if (toastTimeoutRef.current) { - clearTimeout(toastTimeoutRef.current); - } - - toastTimeoutRef.current = setTimeout(() => { - const count = items.length; - toast.success( - count === 1 - ? `${items[0].maybe_title} completed successfully` - : `${count} images completed successfully`, - ); - toastTimeoutRef.current = null; - }, 100); // Small delay to catch potential double calls - }, []); - - useEffect(() => { - let mounted = true; - - async function fetchJobs() { - if (IsJobPollingDisabled()) { - return; - } - try { - const jobsApi = new JobsApi(); - const response = await jobsApi.ListRecentJobs(); - - if (!mounted || !response.success || !response.data) return; - - // Handle active jobs - const activeJobs = response.data - .filter((job) => { - const status = job.status?.status?.toLowerCase(); - return ( - status && status !== "complete_success" && status !== "failed" - ); - }) - .map( - (job) => - ({ - job_token: job.job_token, - request: { - maybe_model_title: - job.request?.maybe_model_title || "Image Generation", - }, - status: { - status: job.status?.status?.toUpperCase() || "", - progress_percentage: job.status?.progress_percentage || 0, - }, - created_at: job.created_at || new Date().toISOString(), - updated_at: job.updated_at || new Date().toISOString(), - maybe_result: job.maybe_result || null, - }) as ActiveJob, - ); - - // Handle completed jobs - const newlyCompleted = response.data.filter( - (job) => - job.status?.status?.toLowerCase() === "complete_success" && - !processedJobs.current.has(job.job_token), - ); - - if (newlyCompleted.length > 0) { - // Add to processed set - newlyCompleted.forEach((job) => - processedJobs.current.add(job.job_token), - ); - - // Create completed items - const newItems = newlyCompleted.map((job) => ({ - token: job.job_token, - maybe_title: job.request?.maybe_model_title || "Image Generation", - public_bucket_path: - job.maybe_result?.maybe_public_bucket_media_path || "", - maybe_media_links_thumbnail: ((job) => { - // Try the video thumbnail first, then the regular thumbnail. - const maybeVideoThumb = - job.maybe_result?.media_links?.maybe_video_previews?.animated; - const maybeGenericThumb = ( - job.maybe_result?.media_links?.maybe_thumbnail_template || "" - ).replace("{WIDTH}", "500"); - return maybeVideoThumb || maybeGenericThumb; - })(job), - updated_at: job.updated_at || new Date().toISOString(), - maybe_result: job.maybe_result, - })); - - // NB(bt,2025-07-28): Don't use eventing from here anymore. - // Tauri is sending direct events we can bind toasts to. - // // Show toast only if not first load - // if (!isFirstLoad.current) { - // showToast(newItems); - // } - - // Update state - setCompletedItems((prev) => { - const combined = [...newItems, ...prev]; - return combined - .filter( - (item, index, self) => - index === self.findIndex((t) => t.token === item.token), - ) - .sort( - (a, b) => - new Date(b.updated_at).getTime() - - new Date(a.updated_at).getTime(), - ) - .slice(0, 50); - }); - } - - setJobs(activeJobs); - isFirstLoad.current = false; - } catch (error) { - console.error("Failed to fetch jobs:", error); - } - } - - // Initial fetch - fetchJobs(); - - // Set up polling - const interval = setInterval(fetchJobs, 5000); - - // Cleanup - return () => { - mounted = false; - clearInterval(interval); - if (toastTimeoutRef.current) { - clearTimeout(toastTimeoutRef.current); - } - }; - }, [showToast]); - - return ( - - - {jobs.length > 0 ? ( - - ) : ( - - )} - {jobs.length > 0 && ( -
- )} -
- } - > - {(close) => ( -
- {jobs.length > 0 && ( -
- {jobs.map((job) => ( - - ))} -
- )} - - {isFirstLoad.current ? ( -
- -

Retrieving Activities

-
- ) : jobs.length === 0 && completedItems.length === 0 ? ( -
-

No activities yet

-
- ) : ( -
-
- {completedItems.map((item) => ( -
- -
- ))} -
-
- )} -
- )} -
-
- ); -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/GenerateModals/CompletedCard.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/GenerateModals/CompletedCard.tsx deleted file mode 100644 index 5a5c1f109a..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/GenerateModals/CompletedCard.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { useState } from "react"; -import dayjs from "dayjs"; -import { - galleryModalLightboxMediaId, - galleryModalLightboxVisible, - galleryModalLightboxImage, -} from "@storyteller/ui-gallery-modal"; -import type { GalleryItem } from "@storyteller/ui-gallery-modal"; - -interface Props { - job: { - token: string; - maybe_title: string; - updated_at: string; - public_bucket_path: string; - maybe_style_name?: string; - maybe_media_links_thumbnail?: string; - maybe_result?: { - entity_token?: string; - media_links?: { - cdn_url?: string; - }; - }; - }; -} - -export function CompletedCard({ job }: Props) { - const [loadError, setLoadError] = useState(false); - // Info: Gallery modal will handle displaying full media. Compute thumbnail only. - - // NB: Try to use the thumbnail instead of the link to the full asset. - // Passing around full asset URLs and putting them into image tags is causing CORS errors if those assets are not images. - const thumbnailUrl = job?.maybe_media_links_thumbnail; - - if (!job) return null; - - return ( - <> -
{ - galleryModalLightboxMediaId.value = - job.maybe_result?.entity_token || null; - galleryModalLightboxImage.value = { - id: job.maybe_result?.entity_token || job.token, - label: job.maybe_title || "Image", - thumbnail: thumbnailUrl, - fullImage: job.maybe_result?.media_links?.cdn_url || thumbnailUrl, - createdAt: job.updated_at, - mediaClass: undefined, - } as GalleryItem; - galleryModalLightboxVisible.value = true; - }} - > -
-
- {job.maybe_title setLoadError(true)} - loading="lazy" - /> -
-
-
{job.maybe_title || "Untitled"}
-
- {dayjs(job.updated_at).format("MMM D, YYYY HH:mm:ss")} -
-
-
- {/* e.stopPropagation()} - className="flex h-9 w-9 items-center justify-center text-lg text-white/60 transition-all hover:text-white" - > - - */} -
- - {/* LightboxModal removed – now using top level GalleryModal */} - - ); -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/GenerateModals/GenerateModals.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/GenerateModals/GenerateModals.tsx deleted file mode 100644 index 4578a1adcc..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/GenerateModals/GenerateModals.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { useCallback, useState } from "react"; -import { useSignals, useSignalEffect } from "@preact/signals-react/runtime"; - -import { MediaFile } from "~/pages/PageEnigma/models"; - -import { generateMovieId } from "~/pages/PageEnigma/signals"; -import { ToastTypes } from "~/enums"; -import { addToast } from "~/signals"; - -import { MyMovies } from "~/pages/PageEnigma/comps/GenerateModals/MyMovies"; -import { Sharing } from "~/pages/PageEnigma/comps/GenerateModals/Sharing"; -import { MediaFilesApi } from "~/Classes/ApiManager/MediaFilesApi"; - -export function GenerateModals() { - useSignals(); - const [mediaFile, setMediaFile] = useState(null); - - const GetMediaFileByToken = useCallback(async (movieId: string) => { - const mediaFilesApi = new MediaFilesApi(); - const response = await mediaFilesApi.GetMediaFileByToken({ - mediaFileToken: movieId, - }); - if (response.success && response.data) { - setMediaFile(response.data); - return; - } - addToast( - ToastTypes.ERROR, - response.errorMessage || - `Unknown Error in Getting Movie (token=${movieId}`, - ); - }, []); - const setMovieId = useCallback((movieId: string) => { - generateMovieId.value = movieId; - }, []); - useSignalEffect(() => { - if (generateMovieId.value) { - GetMediaFileByToken(generateMovieId.value); - } - }); - - if (!mediaFile) { - return ; - } - return ; -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/GenerateModals/InProgressCard.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/GenerateModals/InProgressCard.tsx deleted file mode 100644 index bffcd5465e..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/GenerateModals/InProgressCard.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { useCallback } from "react"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faClose, faSpinnerThird } from "@fortawesome/pro-solid-svg-icons"; -import { ToastTypes } from "~/enums"; -import { addToast } from "~/signals"; -import { JobState } from "~/pages/PageEnigma/enums"; -import { ActiveJob } from "~/pages/PageEnigma/models"; -import { Tooltip } from "@storyteller/ui-tooltip"; -import { JobsApi } from "~/Classes/ApiManager"; -import { PollRecentJobs } from "~/hooks/useActiveJobs/utilities"; -interface Props { - job: ActiveJob; -} - -function getPercent(status: JobState, percentage: number) { - if ( - [ - JobState.COMPLETE_FAILURE, - JobState.ATTEMPT_FAILED, - JobState.DEAD, - ].includes(status) - ) { - return 0; - } else { - return percentage; - } -} - -export function InProgressCard({ job }: Props) { - const completePercent = getPercent( - job.status.status as JobState, - job.status.progress_percentage, - ); - - const deleteJob = useCallback(async (job: ActiveJob) => { - const jobsApi = new JobsApi(); - - const response = await jobsApi.DeleteJobByToken(job.job_token); - if (response.success) { - PollRecentJobs(); - addToast(ToastTypes.SUCCESS, "File successfully deleted."); - return; - } - - addToast( - ToastTypes.ERROR, - response.errorMessage || "Error deleting the file.", - ); - }, []); - - return ( -
-
-
- - {/* {(job.status.status === JobState.STARTED || - job.status.status === JobState.PENDING) && ( - - )} - {(job.status.status === JobState.COMPLETE_FAILURE || - job.status.status === JobState.ATTEMPT_FAILED) && ( - - )} */} -
-
-
- {job.request.maybe_model_title || "Image Generation"} -
- -
- {job.status.status.replaceAll("_", " ")}... {completePercent}% -
-
-
- - {job.status.status !== JobState.STARTED && ( -
- - - -
- )} -
- ); -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/GenerateModals/MyMovies.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/GenerateModals/MyMovies.tsx deleted file mode 100644 index a8be05bae3..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/GenerateModals/MyMovies.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { useSignals } from "@preact/signals-react/runtime"; -import { faFilm, faSpinnerThird } from "@fortawesome/pro-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { CompletedCard } from "~/pages/PageEnigma/comps/GenerateModals/CompletedCard"; -import { InProgressCard } from "~/pages/PageEnigma/comps/GenerateModals/InProgressCard"; -import { Modal } from "@storyteller/ui-modal"; -import { viewMyMovies } from "~/pages/PageEnigma/signals"; -import { activeWorkflowJobs, userMovies } from "~/signals"; - -interface Props { - setMovieId: (page: string) => void; -} - -export function MyMovies({ setMovieId }: Props) { - useSignals(); - - return ( - { - setMovieId(""); - viewMyMovies.value = false; - }} - > -
- {activeWorkflowJobs.value && activeWorkflowJobs.value.length > 0 && ( -
-
In Progress
- {activeWorkflowJobs.value.map((movieJob) => ( - - ))} -
- )} - -
-
- ); -} - -const MovieList = ({ setMovieId }: Props) => { - useSignals(); - if (!userMovies.value && !activeWorkflowJobs.value) { - return ( -
- -

Retrieving Completed Movies

-
- ); - } - if ( - userMovies.value && - userMovies.value.length === 0 && - !activeWorkflowJobs.value - ) { - return ( -
-

You have not created any movies yet!

-

- Try start a new scene from our featured scenes, and generate a movie - via the AI Stylize tab. -

-
- ); - } - return ( -
-
Completed
-
- {userMovies.value?.map((movie) => ( - - ))} -
-
- ); -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/GenerateModals/Sharing.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/GenerateModals/Sharing.tsx deleted file mode 100644 index 7c843927a5..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/GenerateModals/Sharing.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import { useSignals } from "@preact/signals-react/runtime"; -import { useState } from "react"; -import { MediaFile } from "~/pages/PageEnigma/models"; -import { Input } from "@storyteller/ui-input"; -import { Label } from "@storyteller/ui-label"; -import { Button } from "@storyteller/ui-button"; -import { Modal } from "@storyteller/ui-modal"; -import { - faArrowDownToLine, - faArrowRight, - faChevronLeft, - faFilm, - faLink, -} from "@fortawesome/pro-solid-svg-icons"; -import SocialButton from "./SocialButton"; -import { generateMovieId, viewMyMovies } from "~/pages/PageEnigma/signals"; -import dayjs from "dayjs"; -import { downloadFile } from "~/pages/PageEnigma/comps/GenerateModals/utils/downloadFile"; -import { GetCdnOrigin } from "~/api/GetCdnOrigin"; - -interface Props { - mediaFile: MediaFile; - setMediaFile: (file: MediaFile | null) => void; -} - -export function Sharing({ mediaFile, setMediaFile }: Props) { - useSignals(); - - const mediaTitle = mediaFile?.maybe_title ?? mediaFile?.token; - // TODO: ApiManager should provide all endpoints - const shareUrl = `https://storyteller.ai/media/${mediaFile?.token || ""}`; - const shareText = "Check out this media on StoryTeller.ai"; - const [buttonLabel, setButtonLabel] = useState("Copy"); - const media_api_base_url = GetCdnOrigin(); - const downloadLink = `${media_api_base_url}${mediaFile?.public_bucket_path}`; - - const handleCopyLink = () => { - if (navigator.clipboard) { - navigator.clipboard.writeText(shareUrl); - } - setButtonLabel("Copied!"); - setTimeout(() => setButtonLabel("Copy"), 2000); - }; - - const generateTitle = () => { - return ( -
- {mediaTitle} - - {dayjs(mediaFile?.updated_at).format("MMM DD, YYYY HH:mm:ss")} - -
- ); - }; - - return ( - setMediaFile(null) : undefined - } - className="max-w-6xl" - childPadding={false} - isOpen={viewMyMovies.value} - width={1049} - onClose={() => { - viewMyMovies.value = false; - setMediaFile(null); - }} - > -
-
- -
-
- -
- - - - - -
-
-
- -
- - -
-
- - -
-
-
-
- ); -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/GenerateModals/SocialButton.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/GenerateModals/SocialButton.tsx deleted file mode 100644 index 02992ffc0b..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/GenerateModals/SocialButton.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { IconProp } from "@fortawesome/fontawesome-svg-core"; -import { - faFacebookF, - faRedditAlien, - faWhatsapp, - faXTwitter, -} from "@fortawesome/free-brands-svg-icons"; -import { faXmark, faEnvelope } from "@fortawesome/pro-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; - -interface SocialButtonProps { - social: "x" | "whatsapp" | "facebook" | "reddit" | "email"; - shareUrl: string; - shareText: string; -} - -export default function SocialButton({ - social, - shareUrl, - shareText, -}: SocialButtonProps) { - const socialIcons = { - x: faXTwitter, - whatsapp: faWhatsapp, - facebook: faFacebookF, - reddit: faRedditAlien, - email: faEnvelope, - }; - - const socialIcon = socialIcons[social] || faXmark; - - const getShareLink = (social: string, url: string, text: string) => { - switch (social) { - case "x": - return `https://twitter.com/intent/tweet?url=${encodeURIComponent( - url, - )}&text=${encodeURIComponent(text)}`; - case "facebook": - return `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent( - url, - )}`; - case "whatsapp": - return `https://wa.me/?text=${encodeURIComponent( - text, - )} ${encodeURIComponent(url)}`; - case "reddit": - return `https://www.reddit.com/submit?url=${encodeURIComponent( - url, - )}&title=${encodeURIComponent(text)}`; - case "email": - return `mailto:?subject=${encodeURIComponent( - text, - )}&body=${encodeURIComponent(url)}`; - default: - return "#"; - } - }; - - const handleClick = () => { - const shareLink = getShareLink(social, shareUrl, shareText); - window.open(shareLink, "_blank"); - }; - - return ( - - ); -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/GenerateModals/utils/downloadFile.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/GenerateModals/utils/downloadFile.ts deleted file mode 100644 index 703956bd76..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/GenerateModals/utils/downloadFile.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { ToastTypes } from "~/enums"; -import { addToast } from "~/signals"; - -export const downloadFile = ({ - url, - title, -}: { - url: string; - title: string; -}) => { - // console.log(url); - fetch(url) - .then((resp) => resp.blob()) - .then((blob) => { - const blobUrl = window.URL.createObjectURL(blob); - const a = document.createElement("a"); - a.style.display = "none"; - a.href = blobUrl; - a.download = `${title}.mp4`; - document.body.appendChild(a); - a.click(); - window.URL.revokeObjectURL(url); - }) - .catch(() => addToast(ToastTypes.ERROR, "Could not download file.")); -}; - - -export const downloadFileImage = ({ - url, - title, -}: { - url: string; - title: string; -}) => { - // console.log(url); - fetch(url) - .then((resp) => resp.blob()) - .then((blob) => { - const blobUrl = window.URL.createObjectURL(blob); - const a = document.createElement("a"); - a.style.display = "none"; - a.href = blobUrl; - a.download = `${title}.png`; - document.body.appendChild(a); - a.click(); - window.URL.revokeObjectURL(url); - }) - .catch(() => addToast(ToastTypes.ERROR, "Could not download file.")); -}; - -//check diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/PoseModeSelector.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/PoseModeSelector.tsx deleted file mode 100644 index a4bdd62f88..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/PoseModeSelector.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React from "react"; -import { useSignals } from "@preact/signals-react/runtime"; -import { poseMode, showPoseControls } from "../signals/selectedMode"; -import { Tooltip } from "@storyteller/ui-tooltip"; -import { Button } from "@storyteller/ui-button"; -import { faCheck, faPersonRunning } from "@fortawesome/pro-solid-svg-icons"; - -declare global { - interface Window { - __mouseControls?: { - toggleFKMode: () => void; - }; - } -} - -interface PoseModeSelectorProps {} - -export const PoseModeSelector: React.FC = () => { - useSignals(); - - const handleModeChange = () => { - if (poseMode.value === "select") { - poseMode.value = "pose"; - } else { - poseMode.value = "select"; - } - window.__mouseControls?.toggleFKMode(); - }; - - if (showPoseControls.value === false) { - return null; - } - - return ( - <> -
e.stopPropagation()} - onMouseDown={(e) => e.stopPropagation()} - > - - <> - {poseMode.value === "select" ? ( - - ) : ( - - )} - - -
- - ); -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/PrecisionSelector/PrecisionSelector.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/PrecisionSelector/PrecisionSelector.tsx deleted file mode 100644 index 57ad7dcbb9..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/PrecisionSelector/PrecisionSelector.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { Signal } from "@preact/signals-react"; -import { useSignals } from "@preact/signals-react/runtime"; - -export const PrecisionSelector = ({ - showSignal, - coordSignal, - valuesSignal, - selectedValueSignal -}: { - showSignal: Signal, - coordSignal: Signal<{ x: number; y: number }>, - valuesSignal: Signal - selectedValueSignal: Signal -}) => { - useSignals(); - - const handleMouseLeave = () => { - showSignal.value = false; - } - - const handleMouseEnterItem = (scale: number) => { - selectedValueSignal.value = scale; - console.log("selectedValueSignal", selectedValueSignal.value); - } - - return ( -
- {valuesSignal.value.map((scale, index) => ( - handleMouseEnterItem(scale)} key={index} - className="flex bg-ui-panel text-sm justify-center align-middle justify-items-center px-2 hover:bg-ui-controls" - >{scale} - ))} -
- ) -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/PreviewFrameImage.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/PreviewFrameImage.tsx deleted file mode 100644 index 812a2c09f2..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/PreviewFrameImage.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { useSignals } from "@preact/signals-react/runtime"; -import { faSpinnerThird } from "@fortawesome/pro-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { EditorStates } from "~/pages/PageEnigma/enums"; -import { pageHeight, pageWidth } from "~/signals"; -import { editorState, previewSrc } from "~/pages/PageEnigma/signals/engine"; -import { - timelineHeight, - sidePanelWidth, - sidePanelVisible, -} from "~/pages/PageEnigma/signals"; - -export const PreviewFrameImage = () => { - useSignals(); - if (editorState.value === EditorStates.PREVIEW) { - if (previewSrc.value === "") { - return ( -
-
- - -

Generating Preview...

-
-
- ); - } else { - return ( - preview of the art style that renders over the 3d scene - ); - } - } - return null; -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanel/SidePanel.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanel/SidePanel.tsx deleted file mode 100644 index 32f07c0730..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanel/SidePanel.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { useState } from "react"; -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/SidePanelMenu"; -import { TabItem, tabList } from "./tabList"; - -export const SidePanel = () => { - useSignals(); - - const initialTabIdx = 0; - const [selectedTab, setSelectedTab] = useState( - tabList[initialTabIdx], - ); - - const displayWidth = - dndSidePanelWidth.value > -1 - ? dndSidePanelWidth.value - : sidePanelWidth.value; - - return ( - <> -
-
- -
-
- { - setSelectedTab(newSelectedTab); - }} - /> - - ); -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanel/index.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanel/index.tsx deleted file mode 100644 index a48bbce375..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanel/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { SidePanel } from './SidePanel'; \ No newline at end of file diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanel/tabList.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanel/tabList.tsx deleted file mode 100644 index 93078a1920..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanel/tabList.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { ReactNode } from "react"; -import { FontAwesomeIconProps } from "@fortawesome/react-fontawesome"; -import { - faAlienMonster, - faCloud, - faCube, - faMountainCity, - faPresentationScreen, - faUserAstronaut, -} from "@fortawesome/pro-solid-svg-icons"; - -import { TabTitles } from "~/enums"; -import { CartoonsTab } from "../SidePanelTabs/tabComps/CharactersComboTabs"; -import { ObjectsTab } from "../SidePanelTabs/tabComps/ObjectsTab"; -import { SkyboxesTab } from "../SidePanelTabs/tabComps/SkyboxesTab"; -import { CreaturesTab } from "../SidePanelTabs/tabComps/CreaturesTab"; -import { PlanesTab } from "~/pages/PageEnigma/comps/SidePanelTabs/tabComps/PlanesTab"; -import { SetsTab } from "~/pages/PageEnigma/comps/SidePanelTabs/tabComps/SetsTab"; - - -export interface TabItem { - icon: FontAwesomeIconProps["icon"]; - title: string; - component: ReactNode; -} - -export const tabList: TabItem[] = [ - { - icon: faMountainCity, - title: TabTitles.OBJECTS_SETS, - component: , - }, - { - icon: faUserAstronaut, - title: TabTitles.GROUP_CARTOONS, - component: , - }, - { - icon: faCube, - title: TabTitles.OBJECTS, - component: , - }, - { - icon: faAlienMonster, - title: TabTitles.OBJECTS_CREATURES, - component: , - }, - { - icon: faPresentationScreen, - title: TabTitles.PLANES, - component: , - }, - { - icon: faCloud, - title: TabTitles.SKYBOXES, - component: , - }, -]; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelMenu/SidePanelMenu.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelMenu/SidePanelMenu.tsx deleted file mode 100644 index 49483755c9..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelMenu/SidePanelMenu.tsx +++ /dev/null @@ -1,91 +0,0 @@ -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 { currentPage, pageHeight } from "~/signals"; -import { Pages } from "~/pages/PageEnigma/constants/page"; - -export const SidePanelMenu = ({ - tabs, - selectTab, - selectedTab, -}: { - tabs: TabItem[]; - selectTab: (newSelectedTab: TabItem) => void; - selectedTab: TabItem; -}) => { - useSignals(); - const showStylePage = usePosthogFeatureFlag(FeatureFlags.SHOW_STYLE_PAGE); - return ( -
-
- {(tabs ?? []).map((tab) => { - return ( - - ); - })} -
-
- ); -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelMenu/index.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelMenu/index.ts deleted file mode 100644 index 070b61351d..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelMenu/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./SidePanelMenu"; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/SidePanelTabs.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/SidePanelTabs.tsx deleted file mode 100644 index cba74b6ce8..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/SidePanelTabs.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { useSignals } from "@preact/signals-react/runtime"; -import { sidePanelHeight } from "~/pages/PageEnigma/signals"; - -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 ( - <> -
- {tabs.map((tab, index) => ( -
- {tab.component} -
- ))} -
-
- - ); -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/hooks/index.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/hooks/index.ts deleted file mode 100644 index f3d6a2ee51..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/hooks/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from "./useUserObjects"; -export * from "./useFeaturedObjects"; -export * from "./useSearchFeaturedObjects"; -export * from "./useSearchUserObjects"; -export * from "./useFeatureFlags"; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/hooks/useFeatureFlags.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/hooks/useFeatureFlags.tsx deleted file mode 100644 index 7a11571725..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/hooks/useFeatureFlags.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { useSignals } from "@preact/signals-react/runtime"; -import { authentication } from "~/signals"; -import { usePosthogFeatureFlag } from "~/hooks/usePosthogFeatureFlag"; -import { FeatureFlags } from "~/enums"; - -export const useFeatureFlags = () => { - useSignals(); - - const showSearchObjectComponent = usePosthogFeatureFlag( - FeatureFlags.SHOW_SEARCH_OBJECTS, - ); - const showUploadButton = - usePosthogFeatureFlag(FeatureFlags.DEV_ONLY) || authentication.canUpload3D; - - return { - showSearchObjectComponent, - showUploadButton, - }; -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/hooks/useSearchFeaturedObjects.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/hooks/useSearchFeaturedObjects.tsx deleted file mode 100644 index 90547d6a50..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/hooks/useSearchFeaturedObjects.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { useCallback, useEffect, useRef, useState } from "react"; -import { FetchStatus } from "~/pages/PageEnigma/enums"; -import { - FetchMediaItemStates, - fetchFeaturedMediaItemsSearchResults, -} from "../utilities"; -import { FilterEngineCategories, FilterMediaType } from "~/enums"; -import { MediaItem } from "~/pages/PageEnigma/models"; -import deepEqual from "deep-equal"; - -interface useSearchFeaturedObjectsProps { - defaultErrorMessage: string; - filterEngineCategories: FilterEngineCategories[]; - demoFeaturedObjects?: MediaItem[]; - filterMediaTypes?: FilterMediaType[]; -} - -export const useSearchFeaturedObjects = ({ - demoFeaturedObjects, - ...props -}: useSearchFeaturedObjectsProps) => { - const [searchTerm, setSearchTerm] = useState(""); - const lastSearchTerm = useRef(searchTerm); - const updateSearchTermForFeaturedObjects = (newTerm: string) => { - setSearchTerm(newTerm); - }; - - const [ - { - mediaItems: featuredObjectsSearchResults, - status: featuredObjectsSearchFetchStatus, - }, - setFeaturedSearchFetch, - ] = useState({ - mediaItems: undefined, - status: FetchStatus.READY, - }); - - const demoItemsRef = useRef([]); - if ( - demoFeaturedObjects && - !deepEqual(demoItemsRef.current, demoFeaturedObjects) - ) { - demoItemsRef.current = demoFeaturedObjects; - } - - const fetchFeaturedObjectSearchResults = useCallback( - async (term: string) => { - const filteredObjectItems = demoItemsRef.current.filter((item) => - item.name.toLowerCase().includes(term.toLowerCase()), - ); - - if (!term || !term.trim()) { - //if after trim it's empty, do nothing - return; - } - - let breakFlag = false; - setFeaturedSearchFetch((curr) => { - if (curr.status === FetchStatus.IN_PROGRESS) { - breakFlag = true; - return curr; - } - return { - ...curr, - status: FetchStatus.IN_PROGRESS, - }; - }); - if (breakFlag) { - return; - } - - const { filterEngineCategories, filterMediaTypes, defaultErrorMessage } = - props; - const result = await fetchFeaturedMediaItemsSearchResults({ - filterEngineCategories: filterEngineCategories, - filterMediaType: filterMediaTypes, - defaultErrorMessage: defaultErrorMessage, - searchTerm: term, - }); - - setFeaturedSearchFetch({ - status: result.status, - mediaItems: [...filteredObjectItems, ...(result.mediaItems ?? [])], - }); - }, - [props], - ); - - useEffect(() => { - let timer: NodeJS.Timeout; - const KEY_DELAY = 500; - const delayedFetch = (term: string) => { - if (term === lastSearchTerm.current) { - fetchFeaturedObjectSearchResults(searchTerm); - } - }; - if (searchTerm !== lastSearchTerm.current) { - lastSearchTerm.current = searchTerm; - timer = setTimeout(() => delayedFetch(searchTerm), KEY_DELAY); - } - - return () => { - clearTimeout(timer); - }; - }, [fetchFeaturedObjectSearchResults, searchTerm]); - - return { - searchTermForFeaturedObjects: searchTerm, - featuredObjectsSearchResults, - featuredObjectsSearchFetchStatus, - fetchFeaturedObjectSearchResults, - updateSearchTermForFeaturedObjects, - }; -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/hooks/useSearchUserObjects.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/hooks/useSearchUserObjects.tsx deleted file mode 100644 index 635261eaef..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/hooks/useSearchUserObjects.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { useCallback, useEffect, useRef, useState } from "react"; -import { FetchStatus } from "~/pages/PageEnigma/enums"; -import { - FetchMediaItemStates, - fetchUserMediaItemsSearchResults, -} from "../utilities"; -import { FilterEngineCategories, FilterMediaType } from "~/enums"; - -interface useSearchUserObjectsProps { - defaultErrorMessage: string; - filterEngineCategories: FilterEngineCategories[]; - filterMediaTypes?: FilterMediaType[]; -} - -export const useSearchUserObjects = (props: useSearchUserObjectsProps) => { - const [searchTerm, setSearchTerm] = useState(""); - const lastSearchTerm = useRef(searchTerm); - const updateSearchTermForUserObjects = (newTerm: string) => { - setSearchTerm(newTerm); - }; - const [ - { - mediaItems: userObjectsSearchResults, - status: userObjectsSearchFetchStatus, - }, - setUserSearchFetch, - ] = useState({ - mediaItems: undefined, - status: FetchStatus.READY, - }); - - const fetchUserObjectsSearchResults = useCallback( - async (term: string) => { - if (!term || !term.trim()) { - //if after trim it's empty, do nothing - return; - } - - let breakFlag = false; - setUserSearchFetch((curr) => { - if (curr.status === FetchStatus.IN_PROGRESS) { - breakFlag = true; - return curr; - } - return { - ...curr, - status: FetchStatus.IN_PROGRESS, - }; - }); - if (breakFlag) { - return; - } - - const { filterEngineCategories, filterMediaTypes, defaultErrorMessage } = - props; - const result = await fetchUserMediaItemsSearchResults({ - filterEngineCategories: filterEngineCategories, - filterMediaType: filterMediaTypes, - defaultErrorMessage: defaultErrorMessage, - searchTerm: term, - }); - setUserSearchFetch({ - status: result.status, - mediaItems: result.mediaItems, - }); - }, - [props], - ); - - useEffect(() => { - let timer: NodeJS.Timeout; - const KEY_DELAY = 500; - const delayedFetch = (term: string) => { - if (term === lastSearchTerm.current) { - fetchUserObjectsSearchResults(searchTerm); - } - }; - if (searchTerm !== lastSearchTerm.current) { - lastSearchTerm.current = searchTerm; - timer = setTimeout(() => delayedFetch(searchTerm), KEY_DELAY); - } - - return () => { - clearTimeout(timer); - }; - }, [fetchUserObjectsSearchResults, searchTerm]); - - return { - searchTermForUserObjects: searchTerm, - userObjectsSearchResults, - userObjectsSearchFetchStatus, - fetchUserObjectsSearchResults, - updateSearchTermForUserObjects, - }; -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/index.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/index.tsx deleted file mode 100644 index 90aa56ba2d..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export * from "./SidePanelTabs"; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/sharedComps/SubTabButtons.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/sharedComps/SubTabButtons.tsx deleted file mode 100644 index 8a5727878b..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/sharedComps/SubTabButtons.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { IconDefinition } from "@fortawesome/fontawesome-svg-core"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { twMerge } from "tailwind-merge"; -import { TabTitles } from "~/enums"; - -export const SubTabButtons = ({ - currSubpage, - subPageTitles, - setSubpage, - subPageTitleIcons, -}: { - currSubpage: TabTitles; - subPageTitles: TabTitles[]; - subPageTitleIcons?: IconDefinition[]; - setSubpage: (newTab: TabTitles) => void; -}) => { - const leftMostButtonCss = "rounded-l-lg"; - const rightMostButtonCss = "rounded-r-lg"; - return ( -
- {subPageTitles.map((subPageTitle, idx) => { - return ( - - ); - })} -
- ); -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/sharedComps/TabTitle.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/sharedComps/TabTitle.tsx deleted file mode 100644 index 99dbdb3b57..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/sharedComps/TabTitle.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { faChevronLeft, faClose } from "@fortawesome/pro-solid-svg-icons"; -import { ButtonIcon } from "@storyteller/ui-button-icon"; -import { sidePanelVisible } from "~/pages/PageEnigma/signals"; - -interface Props { - title: string; - onBack?: () => void; -} - -export function TabTitle({ title, onBack }: Props) { - const onClose = () => { - sidePanelVisible.value = false; - }; - - return ( -
- {onBack ? ( -
- -
{title}
-
- ) : ( -
{title}
- )} - -
- ); -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/CharactersComboTabs/AnimeTab.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/CharactersComboTabs/AnimeTab.tsx deleted file mode 100644 index 858ab1e6da..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/CharactersComboTabs/AnimeTab.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { useState } from "react"; -import { faPerson, faRabbitRunning } from "@fortawesome/pro-solid-svg-icons"; - -import { MediaFileAnimationType, TabTitles } from "~/enums"; -import { TabTitle } from "../../sharedComps"; -import { AnimationsTab } from "./subtabAnimations"; -import { CharactersTab } from "./subtabCharacters"; -import { SubTabButtons } from "../../sharedComps/SubTabButtons"; - -export const AnimeTab = () => { - const [currSubpage, setCurrSubpage] = useState( - TabTitles.CHARACTERS, - ); - - return ( - <> - - { - setCurrSubpage(newPage); - }} - subPageTitles={[TabTitles.CHARACTERS]} // , TabTitles.ANIMATION - subPageTitleIcons={[faPerson]} // faRabbitRunning - /> - {currSubpage === TabTitles.CHARACTERS && ( - - )} - {/* {currSubpage === TabTitles.ANIMATION && ( - - )} */} - - ); -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/CharactersComboTabs/CartoonsTab.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/CharactersComboTabs/CartoonsTab.tsx deleted file mode 100644 index 53f54c84aa..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/CharactersComboTabs/CartoonsTab.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { useState } from "react"; -import { useSignals } from "@preact/signals-react/runtime"; -import { - faFaceSmileWink, - faPerson, - faRabbitRunning, -} from "@fortawesome/pro-solid-svg-icons"; - -import { MediaFileAnimationType, TabTitles } from "~/enums"; -import { TabTitle } from "../../sharedComps"; -import { AnimationsTab } from "./subtabAnimations"; -import { CharactersTab } from "./subtabCharacters"; -import { ExpressionTab } from "./subtabExpressions"; -import { SubTabButtons } from "../../sharedComps/SubTabButtons"; - -import { - demoCharacterItems, - demoAnimationItems, -} from "~/pages/PageEnigma/signals"; - -export const CartoonsTab = () => { - useSignals(); - - const [currSubpage, setCurrSubpage] = useState( - TabTitles.CHARACTERS, - ); - - return ( - <> - - { - setCurrSubpage(newPage); - }} - subPageTitles={[ - TabTitles.CHARACTERS, - // TabTitles.ANIMATION, - // TabTitles.EXPRESSIONS, - ]} - subPageTitleIcons={[faPerson, faRabbitRunning, faFaceSmileWink]} - /> - - {currSubpage === TabTitles.CHARACTERS && ( - - )} - - ); -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/CharactersComboTabs/filterCharacterTypes.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/CharactersComboTabs/filterCharacterTypes.ts deleted file mode 100644 index ff47436a37..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/CharactersComboTabs/filterCharacterTypes.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { FilterMediaType, MediaFileAnimationType } from "~/enums"; -import { MediaItem } from "~/pages/PageEnigma/models"; - -export const filterMMDCharacters = (item: MediaItem) => { - return ( - item.maybe_animation_type === MediaFileAnimationType.MikuMikuDance || - item.media_type === FilterMediaType.PMD || - item.media_type === FilterMediaType.PMX - ); -}; - -export const filterMixamoCharacters = (item: MediaItem) => { - return ( - item.maybe_animation_type === MediaFileAnimationType.Mixamo || - (item.media_type !== FilterMediaType.PMD && - item.media_type !== FilterMediaType.PMX) - ); -}; - -export const filterMMDAnimations = (item: MediaItem) => { - return ( - item.maybe_animation_type === MediaFileAnimationType.MikuMikuDance || - item.media_type === FilterMediaType.VMD - ); -}; - -export const filterMixamoAnimations = (item: MediaItem) => { - return ( - // item.maybe_animation_type === MediaFileAnimationType.Mixamo && - item.media_type !== FilterMediaType.VMD - ); -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/CharactersComboTabs/index.ts b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/CharactersComboTabs/index.ts deleted file mode 100644 index a8cb899772..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/CharactersComboTabs/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./AnimeTab"; -export * from "./CartoonsTab"; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/CharactersComboTabs/subtabAnimations.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/CharactersComboTabs/subtabAnimations.tsx deleted file mode 100644 index b197e6f06b..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/CharactersComboTabs/subtabAnimations.tsx +++ /dev/null @@ -1,208 +0,0 @@ -import { useEffect, useMemo, useState } from "react"; -import { faCirclePlus } from "@fortawesome/pro-solid-svg-icons"; -import { - ANIMATION_MIXAMO_FILE_TYPE, - ANIMATION_MMD_FILE_TYPE, - AssetFilterOption, - FilterEngineCategories, - MediaFileAnimationType, -} from "~/enums"; -import { - UploadModal, -} from "@storyteller/ui-upload-modal"; -import { SearchFilter } from "@storyteller/ui-search"; -import { Pagination } from "@storyteller/ui-pagination"; -import { Button, FilterButtons } from "@storyteller/ui-button"; -import { ItemElements } from "~/pages/PageEnigma/comps/SidePanelTabs/sharedComps"; -import { isAnyStatusFetching } from "../../utilities"; -import { - useUserObjects, - useFeaturedObjects, - useSearchFeaturedObjects, - useSearchUserObjects, - useFeatureFlags, -} from "../../hooks"; -import { - filterMixamoAnimations, - filterMMDAnimations, -} from "./filterCharacterTypes"; -import { MediaItem } from "~/pages/PageEnigma/models"; -import { getFileExtension, getFileName } from "~/utilities"; - -const filterEngineCategories = [FilterEngineCategories.ANIMATION]; - -export const AnimationsTab = ({ - animationType, - demoAnimationItems, -}: { - animationType: MediaFileAnimationType; - demoAnimationItems?: MediaItem[]; -}) => { - const { showSearchObjectComponent, showUploadButton } = useFeatureFlags(); - - const [openUploadModal, setOpenUploadModal] = useState(false); - - const { userObjects, userFetchStatus, fetchUserObjects } = useUserObjects({ - filterEngineCategories: filterEngineCategories, - defaultErrorMessage: "Unknown Error in Fetching User Animations", - }); - const { featuredObjects, featuredFetchStatus } = useFeaturedObjects({ - filterEngineCategories: filterEngineCategories, - defaultErrorMessage: "Unknown Error in Fetching Featured Animations", - }); - const { - searchTermForFeaturedObjects, - featuredObjectsSearchResults, - featuredObjectsSearchFetchStatus, - updateSearchTermForFeaturedObjects, - } = useSearchFeaturedObjects({ - demoFeaturedObjects: demoAnimationItems, - filterEngineCategories: filterEngineCategories, - defaultErrorMessage: - "Unknown Error in Fetching Featured Animation Search Results", - }); - - const { - searchTermForUserObjects, - userObjectsSearchResults, - userObjectsSearchFetchStatus, - updateSearchTermForUserObjects, - } = useSearchUserObjects({ - filterEngineCategories: filterEngineCategories, - defaultErrorMessage: - "Unknown Error in Fetching User Animation Search Results", - }); - - const [filterOwnership, setFilterOwnership] = useState( - AssetFilterOption.FEATURED, - ); - const filterAnimationType = useMemo( - () => - animationType === MediaFileAnimationType.Mixamo - ? filterMixamoAnimations - : filterMMDAnimations, - [animationType], - ); - const displayedItems = - filterOwnership === AssetFilterOption.FEATURED - ? searchTermForFeaturedObjects - ? featuredObjectsSearchResults ?? [] - : [...(demoAnimationItems ?? []), ...(featuredObjects ?? [])] - : searchTermForUserObjects - ? userObjectsSearchResults ?? [] - : userObjects ?? []; - const filteredDisplayItems = displayedItems.filter(filterAnimationType); - - const [currentPage, setCurrentPage] = useState(0); - const pageSize = 21; - const totalPages = Math.ceil(filteredDisplayItems.length / pageSize); - - const isFetching = isAnyStatusFetching([ - userFetchStatus, - featuredFetchStatus, - featuredObjectsSearchFetchStatus, - userObjectsSearchFetchStatus, - ]); - - useEffect(() => { - if (searchTermForUserObjects.length > 0) { - setCurrentPage(0); - } - }, [searchTermForUserObjects]); - - useEffect(() => { - if (searchTermForFeaturedObjects.length > 0) { - setCurrentPage(0); - } - }, [searchTermForFeaturedObjects]); - - return ( - <> - { - setFilterOwnership(Number(buttonIdx)); - setCurrentPage(0); - }} - /> - -
- {showUploadButton && ( - - )} - {showSearchObjectComponent && ( - - )} -
-
- -
- {totalPages > 1 && ( - { - setCurrentPage(newPage); - }} - /> - )} - setOpenUploadModal(false)} - onSuccess={fetchUserObjects} - isOpen={openUploadModal} - type={FilterEngineCategories.ANIMATION} - fileTypes={Object.values( - animationType === MediaFileAnimationType.Mixamo - ? ANIMATION_MIXAMO_FILE_TYPE - : ANIMATION_MMD_FILE_TYPE, - )} - title="Upload Animation" - options={{ - fileSubtypes: [ - { [animationType]: animationType }, - // { Mixamo: MediaFileAnimationType.Mixamo }, - // { MikuMikuDance: MediaFileAnimationType.MikuMikuDance }, - // { MoveAi: MediaFileAnimationType.MoveAi }, - // { Rigify: MediaFileAnimationType.Rigify }, - // { Rokoko: MediaFileAnimationType.Rokoko }, - ], - hasLength: true, - hasThumbnailUpload: true, - }} - /> - - ); -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/CharactersComboTabs/subtabCharacters.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/CharactersComboTabs/subtabCharacters.tsx deleted file mode 100644 index db6217076c..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/CharactersComboTabs/subtabCharacters.tsx +++ /dev/null @@ -1,221 +0,0 @@ -import { useEffect, useState } from "react"; -import { faCirclePlus } from "@fortawesome/pro-solid-svg-icons"; -import { - AssetFilterOption, - CHARACTER_MIXAMO_FILE_TYPE, - CHARACTER_MMD_FILE_TYPE, - FilterEngineCategories, - FilterMediaType, - MediaFileAnimationType, -} from "~/enums"; -import { MediaItem } from "~/pages/PageEnigma/models"; -import { - UploadModal, -} from "@storyteller/ui-upload-modal"; -import { SearchFilter } from "@storyteller/ui-search"; -import { Pagination } from "@storyteller/ui-pagination"; -import { Button, FilterButtons } from "@storyteller/ui-button"; -import { ItemElements } from "~/pages/PageEnigma/comps/SidePanelTabs/sharedComps"; -import { isAnyStatusFetching } from "../../utilities"; -import { - useUserObjects, - useFeaturedObjects, - useSearchFeaturedObjects, - useSearchUserObjects, - useFeatureFlags, -} from "../../hooks"; -import { getFileExtension, getFileName } from "~/utilities"; - -const filterEngineCategories = [FilterEngineCategories.CHARACTER]; - -export const CharactersTab = ({ - animationType, - demoCharacterItems = [], -}: { - animationType: MediaFileAnimationType; - demoCharacterItems?: MediaItem[]; -}) => { - const fileTypes = Object.values( - animationType === MediaFileAnimationType.Mixamo - ? CHARACTER_MIXAMO_FILE_TYPE - : CHARACTER_MMD_FILE_TYPE, - ); - const { showSearchObjectComponent, showUploadButton } = useFeatureFlags(); - - const [openUploadModal, setOpenUploadModal] = useState(false); - - const { userObjects, nextUserObjects, userFetchStatus, fetchUserObjects } = - useUserObjects({ - filterEngineCategories: filterEngineCategories, - filterMediaTypes: fileTypes as FilterMediaType[], - defaultErrorMessage: "Unknown Error in Fetching User Characters", - }); - - const { - featuredObjects, - nextFeaturedObjects, - fetchFeaturedObjects, - featuredFetchStatus, - } = useFeaturedObjects({ - filterEngineCategories: filterEngineCategories, - filterMediaTypes: fileTypes as FilterMediaType[], - defaultErrorMessage: "Unknown Error in Fetching Featured Characters", - }); - - const { - searchTermForFeaturedObjects, - featuredObjectsSearchResults, - featuredObjectsSearchFetchStatus, - updateSearchTermForFeaturedObjects, - } = useSearchFeaturedObjects({ - demoFeaturedObjects: demoCharacterItems, - filterEngineCategories: filterEngineCategories, - filterMediaTypes: fileTypes as FilterMediaType[], - defaultErrorMessage: - "Unknown Error in Fetching Featured Characters Search Results", - }); - - const { - searchTermForUserObjects, - userObjectsSearchResults, - userObjectsSearchFetchStatus, - updateSearchTermForUserObjects, - } = useSearchUserObjects({ - filterEngineCategories: filterEngineCategories, - filterMediaTypes: fileTypes as FilterMediaType[], - defaultErrorMessage: - "Unknown Error in Fetching User Characters Search Results", - }); - - const [filterOwnership, setFilterOwnership] = useState( - AssetFilterOption.FEATURED, - ); - - const displayedItems = - filterOwnership === AssetFilterOption.FEATURED - ? searchTermForFeaturedObjects - ? featuredObjectsSearchResults ?? [] - : [...(demoCharacterItems ?? []), ...(featuredObjects ?? [])] - : searchTermForUserObjects - ? userObjectsSearchResults ?? [] - : userObjects ?? []; - - const [currentPage, setCurrentPage] = useState(0); - const pageSize = 21; - const totalPages = Math.ceil(displayedItems.length / pageSize); - const fetchMorePages = - filterOwnership === AssetFilterOption.FEATURED - ? nextFeaturedObjects?.maybe_next - ? fetchFeaturedObjects - : undefined - : nextUserObjects && - nextUserObjects.current !== nextUserObjects.total_page_count - ? () => fetchUserObjects(nextUserObjects.current) - : undefined; - - const isFetching = isAnyStatusFetching([ - userFetchStatus, - featuredFetchStatus, - featuredObjectsSearchFetchStatus, - userObjectsSearchFetchStatus, - ]); - - useEffect(() => { - if (searchTermForUserObjects.length > 0) { - setCurrentPage(0); - } - }, [searchTermForUserObjects]); - - useEffect(() => { - if (searchTermForFeaturedObjects.length > 0) { - setCurrentPage(0); - } - }, [searchTermForFeaturedObjects]); - - return ( - <> -
- { - setFilterOwnership(Number(button)); - setCurrentPage(0); - }} - /> -
-
- {showUploadButton && ( - - )} - {showSearchObjectComponent && ( - - )} -
-
- -
- {totalPages > 1 && ( - { - setCurrentPage(newPage); - }} - onFetchMorePages={fetchMorePages} - /> - )} - setOpenUploadModal(false)} - onSuccess={fetchUserObjects} - isOpen={openUploadModal} - type={FilterEngineCategories.CHARACTER} - options={{ - hasThumbnailUpload: true, - fileSubtypes: [ - { [animationType]: animationType }, - // { Mixamo: MediaFileAnimationType.Mixamo }, - // { MikuMikuDance: MediaFileAnimationType.MikuMikuDance }, - // { MoveAi: MediaFileAnimationType.MoveAi }, - // { Rigify: MediaFileAnimationType.Rigify }, - // { Rokoko: MediaFileAnimationType.Rokoko }, - ], - }} - fileTypes={fileTypes} - title="Upload Characters" - /> - - ); -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/CharactersComboTabs/subtabExpressions.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/CharactersComboTabs/subtabExpressions.tsx deleted file mode 100644 index 299951b285..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/CharactersComboTabs/subtabExpressions.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import { useEffect, useState } from "react"; -import { faCirclePlus } from "@fortawesome/pro-solid-svg-icons"; -import { - AssetFilterOption, - FilterEngineCategories, - MediaFileAnimationType, -} from "~/enums"; -import { - UploadModal, -} from "@storyteller/ui-upload-modal"; -import { SearchFilter } from "@storyteller/ui-search"; -import { Pagination } from "@storyteller/ui-pagination"; -import { Button, FilterButtons } from "@storyteller/ui-button"; -import { ItemElements } from "~/pages/PageEnigma/comps/SidePanelTabs/sharedComps"; -import { isAnyStatusFetching } from "../../utilities"; -import { - useUserObjects, - useFeaturedObjects, - useSearchFeaturedObjects, - useSearchUserObjects, - useFeatureFlags, -} from "../../hooks"; -import { getFileExtension, getFileName } from "~/utilities"; - -const filterEngineCategories = [FilterEngineCategories.EXPRESSION]; - -export const ExpressionTab = () => { - const { showSearchObjectComponent, showUploadButton } = useFeatureFlags(); - - const [openUploadModal, setOpenUploadModal] = useState(false); - - const { userObjects, userFetchStatus, fetchUserObjects } = useUserObjects({ - filterEngineCategories: filterEngineCategories, - defaultErrorMessage: "Unknown Error in Fetching User Expressions", - }); - const { featuredObjects, featuredFetchStatus } = useFeaturedObjects({ - filterEngineCategories: filterEngineCategories, - defaultErrorMessage: "Unknown Error in Fetching Featured Expressions", - }); - const { - searchTermForFeaturedObjects, - featuredObjectsSearchResults, - featuredObjectsSearchFetchStatus, - updateSearchTermForFeaturedObjects, - } = useSearchFeaturedObjects({ - filterEngineCategories: filterEngineCategories, - defaultErrorMessage: - "Unknown Error in Fetching Featured Expressions Search Results", - }); - - const { - searchTermForUserObjects, - userObjectsSearchResults, - userObjectsSearchFetchStatus, - updateSearchTermForUserObjects, - } = useSearchUserObjects({ - filterEngineCategories: filterEngineCategories, - defaultErrorMessage: - "Unknown Error in Fetching User Expressions Search Results", - }); - - const [filterOwnership, setFilterOwnership] = useState( - AssetFilterOption.FEATURED, - ); - - const displayedItems = - filterOwnership === AssetFilterOption.FEATURED - ? searchTermForFeaturedObjects - ? featuredObjectsSearchResults ?? [] - : featuredObjects ?? [] - : searchTermForUserObjects - ? userObjectsSearchResults ?? [] - : userObjects ?? []; - - const [currentPage, setCurrentPage] = useState(0); - const pageSize = 21; - const totalPages = Math.ceil(displayedItems.length / pageSize); - - const isFetching = isAnyStatusFetching([ - userFetchStatus, - featuredFetchStatus, - featuredObjectsSearchFetchStatus, - userObjectsSearchFetchStatus, - ]); - - useEffect(() => { - if (searchTermForUserObjects.length > 0) { - setCurrentPage(0); - } - }, [searchTermForUserObjects]); - - useEffect(() => { - if (searchTermForFeaturedObjects.length > 0) { - setCurrentPage(0); - } - }, [searchTermForFeaturedObjects]); - - return ( - <> - { - setFilterOwnership(button); - setCurrentPage(0); - }} - /> -
- {showUploadButton && ( - - )} - {showSearchObjectComponent && ( - - )} -
-
- -
- {totalPages > 1 && ( - { - setCurrentPage(newPage); - }} - /> - )} - setOpenUploadModal(false)} - onSuccess={fetchUserObjects} - isOpen={openUploadModal} - type={FilterEngineCategories.EXPRESSION} - fileTypes={["CSV"]} - title="Upload Expression" - options={{ - fileSubtypes: [{ ARKit: MediaFileAnimationType.ArKit }], - hasLength: true, - hasThumbnailUpload: true, - }} - /> - - ); -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/CreaturesTab.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/CreaturesTab.tsx deleted file mode 100644 index 9315a123fe..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/CreaturesTab.tsx +++ /dev/null @@ -1,177 +0,0 @@ -import { useEffect, useState } from "react"; -import { usePosthogFeatureFlag } from "~/hooks/usePosthogFeatureFlag"; -import { faCirclePlus } from "@fortawesome/pro-solid-svg-icons"; -import { - AssetFilterOption, - FeatureFlags, - FilterEngineCategories, - OBJECT_FILE_TYPE, - TabTitles, -} from "~/enums"; -import { - TabTitle, - ItemElements, -} from "~/pages/PageEnigma/comps/SidePanelTabs/sharedComps"; -import { - UploadModal3D, -} from "~/components"; -import { SearchFilter } from "@storyteller/ui-search"; -import { Pagination } from "@storyteller/ui-pagination"; - -import { Button, FilterButtons } from "@storyteller/ui-button" - -import { isAnyStatusFetching } from "../utilities"; -import { - useUserObjects, - useFeaturedObjects, - useSearchFeaturedObjects, - useSearchUserObjects, -} from "../hooks"; - -const filterEngineCategories = [FilterEngineCategories.CREATURE]; - -export const CreaturesTab = () => { - const showSearchObjectComponent = usePosthogFeatureFlag( - FeatureFlags.SHOW_SEARCH_OBJECTS, - ); - - const [openUploadModal, setOpenUploadModal] = useState(false); - - const { userObjects, userFetchStatus, fetchUserObjects } = useUserObjects({ - filterEngineCategories: filterEngineCategories, - defaultErrorMessage: "Unknown Error in Fetching User Creature Objects", - }); - - const { featuredObjects, featuredFetchStatus } = useFeaturedObjects({ - filterEngineCategories: filterEngineCategories, - defaultErrorMessage: "Unknown Error in Fetching Featured Creature Objects", - }); - - const { - searchTermForFeaturedObjects, - featuredObjectsSearchResults, - featuredObjectsSearchFetchStatus, - updateSearchTermForFeaturedObjects, - } = useSearchFeaturedObjects({ - filterEngineCategories: filterEngineCategories, - defaultErrorMessage: - "Unknown Error in Fetching Featured Creature Objects Search Results", - }); - - const { - searchTermForUserObjects, - userObjectsSearchResults, - userObjectsSearchFetchStatus, - updateSearchTermForUserObjects, - } = useSearchUserObjects({ - filterEngineCategories: filterEngineCategories, - defaultErrorMessage: - "Unknown Error in Fetching User Creature Objects Search Results", - }); - - const [selectedFilter, setSelectedFilter] = useState( - AssetFilterOption.FEATURED, - ); - - const displayedItems = - selectedFilter === AssetFilterOption.FEATURED - ? searchTermForFeaturedObjects - ? featuredObjectsSearchResults ?? [] - : featuredObjects ?? [] - : searchTermForUserObjects - ? userObjectsSearchResults ?? [] - : userObjects ?? []; - - const [currentPage, setCurrentPage] = useState(0); - const pageSize = 21; - const totalPages = Math.ceil(displayedItems.length / pageSize); - - const isFetching = isAnyStatusFetching([ - userFetchStatus, - featuredFetchStatus, - featuredObjectsSearchFetchStatus, - userObjectsSearchFetchStatus, - ]); - - useEffect(() => { - if (searchTermForUserObjects.length > 0) { - setCurrentPage(0); - } - }, [searchTermForUserObjects]); - - useEffect(() => { - if (searchTermForFeaturedObjects.length > 0) { - setCurrentPage(0); - } - }, [searchTermForFeaturedObjects]); - - return ( - <> - - { - setSelectedFilter(Number(button)); - setCurrentPage(0); - }} - /> -
- - {showSearchObjectComponent && ( - - )} -
-
- -
- {totalPages > 1 && ( - { - setCurrentPage(newPage); - }} - /> - )} - setOpenUploadModal(false)} - onSuccess={fetchUserObjects} - isOpen={openUploadModal} - engineCategory={FilterEngineCategories.CREATURE} - fileTypes={Object.values(OBJECT_FILE_TYPE)} - title="Upload Creatures" - /> - - ); -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/ObjectsTab.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/ObjectsTab.tsx deleted file mode 100644 index 440bf07fb8..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/ObjectsTab.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import { useEffect, useState } from "react"; -import { usePosthogFeatureFlag } from "~/hooks/usePosthogFeatureFlag"; -import { faCirclePlus } from "@fortawesome/pro-solid-svg-icons"; -import { - AssetFilterOption, - FeatureFlags, - FilterEngineCategories, - OBJECT_FILE_TYPE, - TabTitles, -} from "~/enums"; -import { demoShapeItems } from "~/pages/PageEnigma/signals"; -import { - TabTitle, - ItemElements, -} from "~/pages/PageEnigma/comps/SidePanelTabs/sharedComps"; -import { - UploadModal3D, -} from "~/components"; -import { SearchFilter } from "@storyteller/ui-search"; -import { Pagination } from "@storyteller/ui-pagination"; -import { Button, FilterButtons } from "@storyteller/ui-button" -import { isAnyStatusFetching } from "../utilities"; -import { - useUserObjects, - useFeaturedObjects, - useSearchFeaturedObjects, - useSearchUserObjects, -} from "../hooks"; - -const filterEngineCategories = [ - FilterEngineCategories.SET_DRESSING, - FilterEngineCategories.OBJECT, -]; - -export const ObjectsTab = () => { - const showSearchObjectComponent = usePosthogFeatureFlag( - FeatureFlags.SHOW_SEARCH_OBJECTS, - ); - - const [openUploadModal, setOpenUploadModal] = useState(false); - - const { userObjects, userFetchStatus, fetchUserObjects } = useUserObjects({ - filterEngineCategories: filterEngineCategories, - defaultErrorMessage: "Unknown Error in Fetching User Objects", - }); - const { featuredObjects, featuredFetchStatus } = useFeaturedObjects({ - filterEngineCategories: filterEngineCategories, - defaultErrorMessage: "Unknown Error in Fetching Featured Objects", - }); - const { - searchTermForFeaturedObjects, - featuredObjectsSearchResults, - featuredObjectsSearchFetchStatus, - updateSearchTermForFeaturedObjects, - } = useSearchFeaturedObjects({ - demoFeaturedObjects: demoShapeItems.value, - filterEngineCategories: filterEngineCategories, - defaultErrorMessage: - "Unknown Error in Fetching Featured Objects Search Results", - }); - - const { - searchTermForUserObjects, - userObjectsSearchResults, - userObjectsSearchFetchStatus, - updateSearchTermForUserObjects, - } = useSearchUserObjects({ - filterEngineCategories: filterEngineCategories, - defaultErrorMessage: - "Unknown Error in Fetching User Objects Search Results", - }); - - const [selectedFilter, setSelectedFilter] = useState( - AssetFilterOption.FEATURED, - ); - - const displayedItems = - selectedFilter === AssetFilterOption.FEATURED - ? searchTermForFeaturedObjects - ? featuredObjectsSearchResults ?? [] - : [...demoShapeItems.value, ...(featuredObjects ?? [])] - : searchTermForUserObjects - ? userObjectsSearchResults ?? [] - : userObjects ?? []; - - const [currentPage, setCurrentPage] = useState(0); - const pageSize = 21; - const totalPages = Math.ceil(displayedItems.length / pageSize); - - const isFetching = isAnyStatusFetching([ - userFetchStatus, - featuredFetchStatus, - featuredObjectsSearchFetchStatus, - userObjectsSearchFetchStatus, - ]); - - useEffect(() => { - if (searchTermForUserObjects.length > 0) { - setCurrentPage(0); - } - }, [searchTermForUserObjects]); - - useEffect(() => { - if (searchTermForFeaturedObjects.length > 0) { - setCurrentPage(0); - } - }, [searchTermForFeaturedObjects]); - - return ( - <> - - { - setSelectedFilter(Number(button)); - setCurrentPage(0); - }} - /> -
- - {showSearchObjectComponent && ( - - )} -
-
- -
- {totalPages > 1 && ( - { - setCurrentPage(newPage); - }} - /> - )} - setOpenUploadModal(false)} - onSuccess={fetchUserObjects} - isOpen={openUploadModal} - engineCategory={FilterEngineCategories.SET_DRESSING} - fileTypes={Object.values(OBJECT_FILE_TYPE)} - title="Upload Props or Other Objects" - /> - - ); -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/PlanesTab/PlanesTab.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/PlanesTab/PlanesTab.tsx deleted file mode 100644 index de9762626d..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/PlanesTab/PlanesTab.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { useState } from "react"; - -import { TabTitles } from "~/enums"; -import { TabTitle } from "../../sharedComps"; -import { ImagePlanesTab } from "./subTabImagePlanes"; -import { SubTabButtons } from "../../sharedComps/SubTabButtons"; -import { VideoPlanesTab } from "./subTabVideoPlanes"; - -export const PlanesTab = () => { - const [currSubpage, setCurrSubpage] = useState( - TabTitles.PLANES_IMAGE, - ); - - return ( - <> - - - { - setCurrSubpage(newPage); - }} - subPageTitles={[TabTitles.PLANES_IMAGE, TabTitles.PLANES_VIDEO]} - /> - {currSubpage === TabTitles.PLANES_IMAGE && } - {currSubpage === TabTitles.PLANES_VIDEO && } - - ); -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/PlanesTab/index.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/PlanesTab/index.tsx deleted file mode 100644 index 7e7fce1a16..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/PlanesTab/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export * from "./PlanesTab"; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/PlanesTab/subTabImagePlanes.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/PlanesTab/subTabImagePlanes.tsx deleted file mode 100644 index 9b411c6a24..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/PlanesTab/subTabImagePlanes.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { useEffect, useState } from "react"; -import { faCirclePlus } from "@fortawesome/pro-solid-svg-icons"; -import { - FeatureFlags, - FilterEngineCategories, - IMAGEPLANE_FILE_TYPE, -} from "~/enums"; -import { - UploadModalMedia, -} from "~/components"; -import { SearchFilter } from "@storyteller/ui-search"; -import { Button } from "@storyteller/ui-button"; -import { Pagination } from "@storyteller/ui-pagination"; -import { ItemElements } from "~/pages/PageEnigma/comps/SidePanelTabs/sharedComps"; -import { isAnyStatusFetching } from "../../utilities"; -import { useUserObjects, useSearchUserObjects } from "../../hooks"; -import { usePosthogFeatureFlag } from "~/hooks/usePosthogFeatureFlag"; - -const filterEngineCategories = [FilterEngineCategories.IMAGE_PLANE]; - -export const ImagePlanesTab = () => { - const showSearchObjectComponent = usePosthogFeatureFlag( - FeatureFlags.SHOW_SEARCH_OBJECTS, - ); - - const [openUploadModal, setOpenUploadModal] = useState(false); - - const { userObjects, userFetchStatus, fetchUserObjects } = useUserObjects({ - filterEngineCategories: filterEngineCategories, - defaultErrorMessage: "Unknown Error in Fetching User Image Panels", - }); - - const { - searchTermForUserObjects, - userObjectsSearchResults, - userObjectsSearchFetchStatus, - updateSearchTermForUserObjects, - } = useSearchUserObjects({ - filterEngineCategories: filterEngineCategories, - defaultErrorMessage: - "Unknown Error in Fetching User Image Panel Search Results", - }); - - const displayedItems = searchTermForUserObjects - ? userObjectsSearchResults ?? [] - : userObjects ?? []; - - const [currentPage, setCurrentPage] = useState(0); - const pageSize = 21; - const totalPages = Math.ceil(displayedItems.length / pageSize); - - const isFetching = isAnyStatusFetching([ - userFetchStatus, - userObjectsSearchFetchStatus, - ]); - - useEffect(() => { - if (searchTermForUserObjects.length > 0) { - setCurrentPage(0); - } - }, [searchTermForUserObjects]); - - return ( - <> -
- - {showSearchObjectComponent && ( - - )} -
-
- -
- {totalPages > 1 && ( - { - setCurrentPage(newPage); - }} - /> - )} - setOpenUploadModal(false)} - onSuccess={fetchUserObjects} - isOpen={openUploadModal} - fileTypes={Object.values(IMAGEPLANE_FILE_TYPE)} - title="Upload Image Panels" - /> - - ); -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/PlanesTab/subTabVideoPlanes.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/PlanesTab/subTabVideoPlanes.tsx deleted file mode 100644 index 06ed532c24..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/PlanesTab/subTabVideoPlanes.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { useEffect, useState } from "react"; -import { faCirclePlus } from "@fortawesome/pro-solid-svg-icons"; -import { - FeatureFlags, - FilterEngineCategories, - VIDEOPLANE_FILE_TYPE, -} from "~/enums"; -import { - UploadModalMedia, -} from "~/components"; -import { SearchFilter } from "@storyteller/ui-search"; -import { Button } from "@storyteller/ui-button"; -import { Pagination } from "@storyteller/ui-pagination"; -import { ItemElements } from "~/pages/PageEnigma/comps/SidePanelTabs/sharedComps"; -import { isAnyStatusFetching } from "../../utilities"; -import { useUserObjects, useSearchUserObjects } from "../../hooks"; -import { usePosthogFeatureFlag } from "~/hooks/usePosthogFeatureFlag"; - -const filterEngineCategories = [FilterEngineCategories.VIDEO_PLANE]; - -export const VideoPlanesTab = () => { - const showSearchObjectComponent = usePosthogFeatureFlag( - FeatureFlags.SHOW_SEARCH_OBJECTS, - ); - - const [openUploadModal, setOpenUploadModal] = useState(false); - - const { userObjects, userFetchStatus, fetchUserObjects } = useUserObjects({ - filterEngineCategories: filterEngineCategories, - defaultErrorMessage: "Unknown Error in Fetching User Video Panels", - }); - - const { - searchTermForUserObjects, - userObjectsSearchResults, - userObjectsSearchFetchStatus, - updateSearchTermForUserObjects, - } = useSearchUserObjects({ - filterEngineCategories: filterEngineCategories, - defaultErrorMessage: - "Unknown Error in Fetching User Video Panel Search Results", - }); - - const displayedItems = searchTermForUserObjects - ? userObjectsSearchResults ?? [] - : userObjects ?? []; - - const [currentPage, setCurrentPage] = useState(0); - const pageSize = 21; - const totalPages = Math.ceil(displayedItems.length / pageSize); - - const isFetching = isAnyStatusFetching([ - userFetchStatus, - userObjectsSearchFetchStatus, - ]); - - useEffect(() => { - if (searchTermForUserObjects.length > 0) { - setCurrentPage(0); - } - }, [searchTermForUserObjects]); - - return ( - <> -
- - {showSearchObjectComponent && ( - - )} -
-
- -
- {totalPages > 1 && ( - { - setCurrentPage(newPage); - }} - /> - )} - setOpenUploadModal(false)} - onSuccess={fetchUserObjects} - isOpen={openUploadModal} - fileTypes={Object.values(VIDEOPLANE_FILE_TYPE)} - title="Upload Video Panels" - /> - - ); -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/SetsTab.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/SetsTab.tsx deleted file mode 100644 index 99f8135f8c..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/SetsTab.tsx +++ /dev/null @@ -1,166 +0,0 @@ -import { useEffect, useState, useCallback } from "react"; -import { faCirclePlus } from "@fortawesome/pro-solid-svg-icons"; - -import { usePosthogFeatureFlag } from "~/hooks/usePosthogFeatureFlag"; - -import { - AssetFilterOption, - FeatureFlags, - FilterEngineCategories, - OBJECT_FILE_TYPE, - TabTitles, -} from "~/enums"; - -import { - UploadModal3D, -} from "~/components"; -import { SearchFilter } from "@storyteller/ui-search"; -import { Button, FilterButtons } from "@storyteller/ui-button" - -import { - TabTitle, - ItemElements, -} from "~/pages/PageEnigma/comps/SidePanelTabs/sharedComps"; - -import { isAnyStatusFetching } from "../utilities"; -import { - useUserObjects, - useFeaturedObjects, - useSearchFeaturedObjects, - useSearchUserObjects, -} from "../hooks"; - -const filterEngineCategories = [FilterEngineCategories.LOCATION]; -const PAGE_SIZE = 42; // Load 2 pages worth of items at a time - -export const SetsTab = () => { - const showSearchObjectComponent = usePosthogFeatureFlag( - FeatureFlags.SHOW_SEARCH_OBJECTS, - ); - - const [openUploadModal, setOpenUploadModal] = useState(false); - const [loadedItemCount, setLoadedItemCount] = useState(PAGE_SIZE); - - const { userObjects, userFetchStatus, fetchUserObjects } = useUserObjects({ - filterEngineCategories: filterEngineCategories, - defaultErrorMessage: "Unknown Error in Fetching User Film Sets", - }); - const { featuredObjects, featuredFetchStatus } = useFeaturedObjects({ - filterEngineCategories: filterEngineCategories, - defaultErrorMessage: "Unknown Error in Fetching Featured Film Sets", - }); - const { - searchTermForFeaturedObjects, - featuredObjectsSearchResults, - featuredObjectsSearchFetchStatus, - updateSearchTermForFeaturedObjects, - } = useSearchFeaturedObjects({ - filterEngineCategories: filterEngineCategories, - defaultErrorMessage: - "Unknown Error in Fetching Featured Film Sets Search Results", - }); - - const { - searchTermForUserObjects, - userObjectsSearchResults, - userObjectsSearchFetchStatus, - updateSearchTermForUserObjects, - } = useSearchUserObjects({ - filterEngineCategories: filterEngineCategories, - defaultErrorMessage: - "Unknown Error in Fetching User Film Sets Search Results", - }); - - const [selectedFilter, setSelectedFilter] = useState( - AssetFilterOption.FEATURED, - ); - - const displayedItems = - selectedFilter === AssetFilterOption.FEATURED - ? searchTermForFeaturedObjects - ? (featuredObjectsSearchResults ?? []) - : (featuredObjects ?? []) - : searchTermForUserObjects - ? (userObjectsSearchResults ?? []) - : (userObjects ?? []); - - const isFetching = isAnyStatusFetching([ - userFetchStatus, - featuredFetchStatus, - featuredObjectsSearchFetchStatus, - userObjectsSearchFetchStatus, - ]); - - const handleLoadMore = useCallback(() => { - if (!isFetching && loadedItemCount < displayedItems.length) { - setLoadedItemCount((prev) => - Math.min(prev + PAGE_SIZE, displayedItems.length), - ); - } - }, [isFetching, loadedItemCount, displayedItems.length]); - - useEffect(() => { - setLoadedItemCount(PAGE_SIZE); - }, [searchTermForUserObjects, searchTermForFeaturedObjects, selectedFilter]); - - return ( - <> - - - { - setSelectedFilter(Number(buttonIdx)); - }} - /> - -
- - {showSearchObjectComponent && ( - - )} -
-
- -
- setOpenUploadModal(false)} - onSuccess={fetchUserObjects} - isOpen={openUploadModal} - engineCategory={FilterEngineCategories.LOCATION} - fileTypes={Object.values(OBJECT_FILE_TYPE)} - title="Upload Film Sets" - /> - - ); -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/SkyboxesTab.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/SkyboxesTab.tsx deleted file mode 100644 index 1ca3e7c69d..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/SkyboxesTab.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { demoSkyboxItems } from "~/pages/PageEnigma/signals"; - -import { TabTitle } from "~/pages/PageEnigma/comps/SidePanelTabs/sharedComps"; -import { ItemElements } from "../sharedComps/ItemElements"; -import { TabTitles } from "~/enums"; - -export const SkyboxesTab = () => { - const displayedItems = demoSkyboxItems.value; - - return ( - <> - -
- -
- - ); -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/GenerateMovieButton.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/GenerateMovieButton.tsx deleted file mode 100644 index f8237eae83..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/GenerateMovieButton.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import { faFilm } from "@fortawesome/pro-solid-svg-icons"; -import { useSignals } from "@preact/signals-react/runtime"; -import { Label } from "~/components"; -import { Button } from "@storyteller/ui-button"; -import { GenerationOptions } from "~/pages/PageEnigma/models/generationOptions"; -import Queue from "~/pages/PageEnigma/Queue/Queue"; -import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; -import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions"; -import { - upscale, - faceDetail, - styleStrength, - lipSync, - cinematic, - globalIPAMediaToken, - enginePreProcessing, -} from "~/pages/PageEnigma/signals/stylizeTab"; -import { useEffect, useRef } from "react"; - -interface GenerateMovieButtonProps { - setGenerateSectionHeight: (height: number) => void; -} - -export function GenerateMovieButton({ - setGenerateSectionHeight, -}: GenerateMovieButtonProps) { - useSignals(); - const ref = useRef(null); - - // Resizes Height of Generate Movie Section that's at the bottom dynamically if squeezed - useEffect(() => { - const currentElement = ref.current; - - const observer = new ResizeObserver((entries) => { - for (const entry of entries) { - const target = entry.target as HTMLElement; - if (target) { - const height = target.offsetHeight; - setGenerateSectionHeight(height); - } - } - }); - - if (currentElement) { - observer.observe(currentElement); - } - - return () => { - if (currentElement) { - observer.unobserve(currentElement); - } - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const generateMovie = async (event: React.MouseEvent) => { - event.preventDefault(); - const options = new GenerationOptions( - upscale.value, - faceDetail.value, - styleStrength.value, - lipSync.value, - cinematic.value, - globalIPAMediaToken.value, - enginePreProcessing.value, - ); - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: toEngineActions.GENERATE_VIDEO, - data: options, - }); - }; - - return ( -
- - - -
- ); -} diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/IPAdapter.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/IPAdapter.tsx deleted file mode 100644 index fc3f5ceed3..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/IPAdapter.tsx +++ /dev/null @@ -1,342 +0,0 @@ -import React, { - useState, - useCallback, - useRef, - useContext, - useEffect, -} from "react"; -import { FileUploader } from "react-drag-drop-files"; -import { - ReactCrop, - Crop, - centerCrop, - makeAspectCrop, - PixelCrop, -} from "react-image-crop"; -import "react-image-crop/dist/ReactCrop.css"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { - faCrop, - faExpand, - faFileImage, - faImage, - faXmark, -} from "@fortawesome/pro-solid-svg-icons"; -import { Label, P } from "~/components"; -import { Modal } from "@storyteller/ui-modal"; -import { Button } from "@storyteller/ui-button"; -import { EngineContext } from "~/pages/PageEnigma/contexts/EngineContext"; -import { MediaFilesApi } from "~/Classes/ApiManager/MediaFilesApi"; -import { BucketConfig } from "~/api/BucketConfig"; -import { useSignals } from "@preact/signals-react/runtime"; -import { globalIPAMediaToken, adapterImage } from "~/pages/PageEnigma/signals"; - -const base64ToFile = (base64: string, filename: string): File => { - const arr = base64.split(","); - const mime = arr[0].match(/:(.*?);/)?.[1]; - const bstr = atob(arr[1]); - let n = bstr.length; - const u8arr = new Uint8Array(n); - - while (n--) { - u8arr[n] = bstr.charCodeAt(n); - } - - return new File([u8arr], filename, { type: mime }); -}; - -export const IPAdapter: React.FC = () => { - useSignals(); - const editorEngine = useContext(EngineContext); - const [imageSrc, setImageSrc] = useState(null); - const [crop, setCrop] = useState(); - const [completedCrop, setCompletedCrop] = useState(null); - const [isDialogOpen, setIsDialogOpen] = useState(false); - const [isExpandedDialogOpen, setIsExpandedDialogOpen] = useState(false); - const imgRef = useRef(null); - const FILE_TYPES = ["JPG", "PNG", "JPEG"]; - const bucketConfig = new BucketConfig(); - - useEffect(() => { - const fetchMediaFile = async () => { - const mediaFilesApi = new MediaFilesApi(); - if ( - editorEngine && - adapterImage.value === null && - globalIPAMediaToken.value - ) { - try { - const response = await mediaFilesApi.GetMediaFileByToken({ - mediaFileToken: globalIPAMediaToken.value, - }); - if (response.success && response.data) { - const imageUrl = bucketConfig.getGcsUrl( - response.data.public_bucket_path, - ); - adapterImage.value = imageUrl; - } else { - console.error("Failed to fetch media file:", response.errorMessage); - } - } catch (error) { - console.error("Error fetching media file:", error); - } - } - }; - - fetchMediaFile(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [editorEngine, globalIPAMediaToken.value, adapterImage.value]); - - const onFileChange = (file: File) => { - const reader = new FileReader(); - reader.onloadend = () => { - setImageSrc(reader.result as string); - setIsDialogOpen(true); - }; - reader.readAsDataURL(file); - }; - - const onImageLoad = useCallback( - (e: React.SyntheticEvent) => { - const { naturalWidth: width, naturalHeight: height } = e.currentTarget; - const crop = centerCrop( - makeAspectCrop({ unit: "%", width: 100 }, 1, width, height), - width, - height, - ); - setCrop(crop); - }, - [], - ); - - const onCropComplete = (crop: PixelCrop) => { - setCompletedCrop(crop); - }; - - const createCroppedImage = async ( - image: HTMLImageElement, - crop: PixelCrop, - ) => { - if (!crop || !image) { - return; - } - - const canvas = document.createElement("canvas"); - const scaleX = image.naturalWidth / image.width; - const scaleY = image.naturalHeight / image.height; - canvas.width = 512; - canvas.height = 512; - const ctx = canvas.getContext("2d"); - - if (ctx) { - ctx.drawImage( - image, - crop.x * scaleX, - crop.y * scaleY, - crop.width * scaleX, - crop.height * scaleY, - 0, - 0, - canvas.width, - canvas.height, - ); - } - - return new Promise((resolve, reject) => { - canvas.toBlob((blob) => { - if (blob) { - const reader = new FileReader(); - reader.readAsDataURL(blob); - reader.onloadend = () => { - resolve(reader.result as string); - }; - } else { - reject(new Error("Canvas is empty")); - } - }, "image/jpeg"); - }); - }; - - // this is the function that outputs the cropped image - const onSaveCrop = async () => { - if (completedCrop && imageSrc && imgRef.current) { - const croppedImageUrl = await createCroppedImage( - imgRef.current, - completedCrop, - ); - - if (croppedImageUrl) { - // converts base64 cropped image into a file for upload - const croppedImageFile = base64ToFile( - croppedImageUrl, - "cropped-image.jpg", - ); - adapterImage.value = URL.createObjectURL(croppedImageFile); - if (editorEngine) { - editorEngine.globalIpAdapterImage = croppedImageFile; - } - } - setIsDialogOpen(false); - setCrop(undefined); - } - }; - - const onDeleteImage = (event: React.MouseEvent) => { - event.stopPropagation(); - event.preventDefault(); - setImageSrc(null); - setCrop(undefined); - adapterImage.value = null; - globalIPAMediaToken.value = null; - if (editorEngine) { - editorEngine.globalIpAdapterImage = undefined; - } - }; - - const DragAndDropZone = () => { - return ( -
- -
-

- Upload an image or drop it here -

-

- {FILE_TYPES.join(", ").toString()} supported -

-
-
- ); - }; - - return ( -
-
- -
- - {!adapterImage.value && ( - - - - )} - {adapterImage.value && ( -
- -
- IPAdapter -
-
-
- )} - - { - setIsDialogOpen(false); - setCrop(undefined); - }} - > -
- {imageSrc && ( - setCrop(newCrop)} - onComplete={(c) => onCropComplete(c)} - aspect={1} - keepSelection={true} - className="max-h-[600px]" - > - Source - - )} -
-
- - -
-
- - setIsExpandedDialogOpen(false)} - > -
- {adapterImage.value && ( - Expanded - )} -
-
- -
-
-
- ); -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/ItemPicker.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/ItemPicker.tsx deleted file mode 100644 index d393488d8b..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/ItemPicker.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { twMerge } from "tailwind-merge"; -import { ArtStyle } from "~/pages/PageEnigma/Editor/api_manager"; -import { H4 } from "~/components"; -import { ImgHTMLAttributes, useEffect, useState } from "react"; - -interface ItemPickerProps extends ImgHTMLAttributes { - label: string; - type: ArtStyle; - selected: boolean; - onSelected: (picked: ArtStyle) => void; - className?: string; - defaultImg?: string; -} - -export const ItemPicker = ({ - label, - type, - selected = false, - defaultImg = "/resources/placeholders/style_placeholder.png", - src = defaultImg, - onSelected, - width, - height, - className, - ...imgProps -}: ItemPickerProps) => { - const handleSelected = () => { - onSelected(type); - }; - - const [imageSrc, setImageSrc] = useState(defaultImg); - - useEffect(() => { - const img = new Image(); - img.onload = () => setImageSrc(src); - img.onerror = () => setImageSrc(defaultImg); - img.src = src || defaultImg; - }, [src, defaultImg]); - - return ( - - ); -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/PageStyleSelection.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/PageStyleSelection.tsx deleted file mode 100644 index 311551def8..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/PageStyleSelection.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { useContext } from "react"; -import { EngineContext } from "~/pages/PageEnigma/contexts/EngineContext"; -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 "../../sharedComps/TabTitle"; -import { StylizeTabPages } from "~/pages/PageEnigma/enums"; - -interface Props { - selection: ArtStyle; - setSelection: (art: ArtStyle) => void; - changePage: (newPage: StylizeTabPages) => void; -} -export const PageStyleSelection = ({ - setSelection, - selection, - changePage, -}: Props) => { - useSignals(); - - const editorEngine = useContext(EngineContext); - - const handlePickingStylizer = (picked: ArtStyle) => { - setSelection(picked); - if (editorEngine === null) { - console.log("Editor is null"); - return; - } - editorEngine.art_style = picked; - }; - - return ( - <> - changePage(StylizeTabPages.MAIN)} - /> -
-
-
- {styleList.map((style) => ( - - ))} -
-
-
- - ); -}; diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/Prompts.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/Prompts.tsx deleted file mode 100644 index dbec793d69..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/Prompts.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import { ChangeEvent, useContext, useEffect } from "react"; -import { Textarea } from "~/components"; -import { EngineContext } from "~/pages/PageEnigma/contexts/EngineContext"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { - faChevronDown, - faChevronUp, - faRandom, -} from "@fortawesome/pro-solid-svg-icons"; -import { RandomTextsPositive } from "~/pages/PageEnigma/constants/RandomTexts"; -import { promptsStore, selectedArtStyle } from "~/pages/PageEnigma/signals"; -import { useSignals } from "@preact/signals-react/runtime"; -import { Transition } from "@headlessui/react"; -import { currentPage } from "~/signals"; -import { Pages } from "~/pages/PageEnigma/constants/page"; - -export const Prompts = () => { - useSignals(); - const editorEngine = useContext(EngineContext); - - useEffect(() => { - if (editorEngine === null) { - return; - } - - if (!promptsStore.isUserInputPositive.value) { - const randomIndexPositive = Math.floor( - Math.random() * RandomTextsPositive[selectedArtStyle.value].length, - ); - const randomTextPositive = - RandomTextsPositive[selectedArtStyle.value][randomIndexPositive]; - editorEngine.positive_prompt = randomTextPositive; - promptsStore.textBufferPositive.value = randomTextPositive; - } - }, [editorEngine]); - - const onChangeHandlerNegative = (event: ChangeEvent) => { - if (editorEngine === null) { - console.log("Editor is null"); - return; - } - promptsStore.isUserInputNegative.value = true; - editorEngine.negative_prompt = event.target.value; - promptsStore.textBufferNegative.value = event.target.value; - }; - - const onChangeHandlerPositive = (event: ChangeEvent) => { - if (editorEngine === null) { - console.log("Editor is null"); - return; - } - promptsStore.isUserInputPositive.value = true; - editorEngine.positive_prompt = event.target.value; - promptsStore.textBufferPositive.value = event.target.value; - }; - - const generateRandomTextPositive = () => { - const randomIndex = Math.floor( - Math.random() * RandomTextsPositive[selectedArtStyle.value].length, - ); - const randomText = RandomTextsPositive[selectedArtStyle.value][randomIndex]; - if (editorEngine === null) { - console.log("Editor is null"); - return; - } - promptsStore.isUserInputPositive.value = false; - editorEngine.positive_prompt = randomText; - promptsStore.textBufferPositive.value = randomText; - }; - - return ( -
-
-