diff --git a/app/components/Button.tsx b/app/components/Button.tsx index 0ae06e6ab5..67eafce6b6 100644 --- a/app/components/Button.tsx +++ b/app/components/Button.tsx @@ -5,7 +5,7 @@ import { twMerge } from "tailwind-merge"; export interface ButtonPropsI extends React.ButtonHTMLAttributes { icon?: IconDefinition; - variant?: "primary" | "secondary"; + variant?: "primary" | "secondary" | "action"; } export const Button = ({ @@ -25,6 +25,9 @@ export const Button = ({ case "secondary": { return " bg-brand-secondary hover:bg-brand-secondary-900 text-white focus-visible:outline-brand-secondary"; } + case "action": { + return " bg-action hover:bg-action-500 text-white focus-visible:outline-action"; + } case "primary": default: { return " bg-brand-primary hover:bg-brand-primary-400 text-white focus-visible:outline-brand-primary-600"; diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/SidePanelMenu.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/SidePanelMenu.tsx index dac751a162..9d3767e1c0 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/SidePanelMenu.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/SidePanelMenu.tsx @@ -1,7 +1,6 @@ import { useEffect } from "react"; import { useSignals } from "@preact/signals-react/runtime"; import { - lastSelectedTab, selectedTab, sidePanelHeight, sidePanelVisible, @@ -18,7 +17,7 @@ export const SidePanelMenu = () => { return (
{ {tabList.map((tab) => (
{tab.component}
diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/TabStylization.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/TabStylization.tsx index eeb5b4ee4d..59aa38d8b1 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/TabStylization.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/TabStylization.tsx @@ -1,6 +1,6 @@ import { useContext, useState } from "react"; -import { Button, ItemPicker, Label, H4, Textarea } from "~/components"; +import { Button, ItemPicker, Label, Textarea } from "~/components"; import { AppUiContext } from "../../contexts/AppUiContext"; import { APPUI_ACTION_TYPES, APPUI_VIEW_MODES } from "../../reducers"; @@ -60,9 +60,9 @@ export const TabStylization = () => { }; return ( -
+
-

Select Base Style

+
{ useSignals(); @@ -9,39 +12,36 @@ export const AnimationTab = () => { return ( <>
-
+
-
- +
-
+
{ useSignals(); @@ -9,39 +12,36 @@ export const AudioTab = () => { return ( <>
-
+
-
- +
+
-
+
{ useSignals(); @@ -9,39 +12,36 @@ export const CameraTab = () => { return ( <>
-
+
-
- +
+
-
+
{ ); return ( -
+
{item.name}
{ + const displayWidth = + dndSidePanelWidth.value > -1 + ? dndSidePanelWidth.value + : sidePanelWidth.value; + const displayItems = items.filter((item) => { if (assetFilter === AssetFilterOption.ALL) { return true; @@ -16,8 +22,23 @@ export const ItemElements = ({ items, assetFilter }: Props) => { } return item.isBookmarked; }); + + function getGridColumnsClass(displayWidth: number): string { + if (displayWidth <= 280) { + return "grid-cols-2"; + } else if (displayWidth <= 360) { + return "grid-cols-3"; + } else if (displayWidth <= 440) { + return "grid-cols-4"; + } else { + return "grid-cols-4"; + } + } + + const gridColumnsClass = getGridColumnsClass(displayWidth); + return ( -
+
{displayItems.map((item) => ( ))} diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ObjectsTab.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ObjectsTab.tsx index 7eab081528..8212560290 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ObjectsTab.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ObjectsTab.tsx @@ -7,6 +7,9 @@ import { objectItems, } from "~/pages/PageEnigma/store"; import { ItemElements } from "~/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ItemElements"; +import { Button } from "~/components"; +import { faCirclePlus } from "@fortawesome/pro-solid-svg-icons"; +import { twMerge } from "tailwind-merge"; interface Props { type: AssetType; @@ -22,39 +25,36 @@ export const ObjectsTab = ({ type }: Props) => { return ( <>
-
+
-
- +
-
+
diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ShapeTab.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ShapeTab.tsx index 353663f461..ed2cabfb75 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ShapeTab.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ShapeTab.tsx @@ -7,8 +7,8 @@ export const ShapeTab = () => { return ( <> -
Shapes
-
+
Shapes
+
, }, { - icon: "resources/icons/characters.png", + icon: "resources/icons/objects.png", title: "Style (Temporary being moved)", value: AssetType.STYLE, - component: , - } + component: , + }, ]; diff --git a/app/pages/PageEnigma/comps/Timeline/utils/useQueueHandler.ts b/app/pages/PageEnigma/comps/Timeline/utils/useQueueHandler.ts index 9fb7d82c95..edf88049e3 100644 --- a/app/pages/PageEnigma/comps/Timeline/utils/useQueueHandler.ts +++ b/app/pages/PageEnigma/comps/Timeline/utils/useQueueHandler.ts @@ -2,23 +2,19 @@ import { fromEngineActions } from "~/pages/PageEnigma/Queue/fromEngineActions"; import { useSignals } from "@preact/signals-react/runtime"; import { useCallback, useContext, useEffect } from "react"; import { - audioGroup, - cameraGroup, - characterGroups, + addNewCharacter, currentTime, - objectGroup, + loadAudioData, + loadCameraData, + loadCharacterData, + loadObjectData, } from "~/pages/PageEnigma/store"; import Queue from "~/pages/PageEnigma/Queue/Queue"; import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions"; import { - CharacterGroup, - Clip, ClipGroup, - ClipType, - Keyframe, MediaItem, - ObjectTrack, QueueClip, QueueKeyframe, UpdateTime, @@ -32,69 +28,16 @@ interface Arguments { data: QueueClip | UpdateTime | QueueKeyframe | ClipUI[] | MediaItem; } -function addCharacter(item: ClipUI) { - const existingCharacter = characterGroups.value.find( - (character) => character.id === item.object_uuid, - ); - - if (existingCharacter) { - return existingCharacter; - } - - const newCharacter = { - id: item.object_uuid, - name: item.object_name, - muted: false, - animationClips: [], - positionKeyframes: [], - lipSyncClips: [], - } as CharacterGroup; - - characterGroups.value = [ - ...characterGroups.value.filter( - (character) => character.id !== item.object_uuid, - ), - newCharacter, - ].sort((charA, charB) => (charA.id < charB.id ? -1 : 1)); - - return characterGroups.value.find( - (character) => character.id === item.object_uuid, - ) as CharacterGroup; -} - -function addObject(item: ClipUI) { - const existingObject = objectGroup.value.objects.find( - (obj) => obj.object_uuid === item.object_uuid, - ); - - if (existingObject) { - return existingObject; - } - - const newObject = { - object_uuid: item.object_uuid, - name: item.object_name, - keyframes: [] as Keyframe[], - } as ObjectTrack; - - objectGroup.value = { - id: "OB1", - objects: [ - ...objectGroup.value.objects.filter( - (obj) => obj.object_uuid !== item.object_uuid, - ), - newObject, - ].sort((objA, objB) => (objA.object_uuid < objB.object_uuid ? -1 : 1)), - }; - - return objectGroup.value.objects.find( - (obj) => obj.object_uuid === item.object_uuid, - ) as ObjectTrack; -} +const LOADING_FUNCTIONS: Record void> = { + [ClipGroup.CHARACTER]: loadCharacterData, + [ClipGroup.OBJECT]: loadObjectData, + [ClipGroup.CAMERA]: loadCameraData, + [ClipGroup.GLOBAL_AUDIO]: loadAudioData, +}; export function useQueueHandler() { useSignals(); - const { addKeyframe } = useContext(TrackContext); + const { addKeyframe, clearExistingData } = useContext(TrackContext); const handleFromEngineActions = useCallback(({ action, data }: Arguments) => { console.log("FROM ENGINE", action, data); @@ -103,120 +46,13 @@ export function useQueueHandler() { currentTime.value = (data as UpdateTime).currentTime; break; case fromEngineActions.UPDATE_TIME_LINE: - console.log(data); + clearExistingData(); (data as ClipUI[]).forEach((item) => { - if (item.group === ClipGroup.CHARACTER) { - const existingCharacter = addCharacter(item); - if (item.type === ClipType.ANIMATION) { - const newItem = { - version: item.version, - clip_uuid: item.clip_uuid, - type: item.type, - group: item.group, - object_uuid: item.object_uuid, - media_id: item.media_id, - name: item.name, - offset: item.offset, - length: item.length, - } as Clip; - existingCharacter.animationClips.push(newItem); - existingCharacter.animationClips.sort( - (clipA, clipB) => clipA.offset - clipB.offset, - ); - } - if (item.type === ClipType.TRANSFORM) { - const newKeyframe = { - version: item.version, - keyframe_uuid: item.clip_uuid, - group: item.group, - object_uuid: item.object_uuid, - offset: item.keyframe_offset, - } as Keyframe; - existingCharacter.positionKeyframes.push(newKeyframe); - existingCharacter.positionKeyframes.sort( - (keyframeA, keyframeB) => keyframeA.offset - keyframeB.offset, - ); - } - if (item.type === ClipType.AUDIO) { - const newItem = { - version: item.version, - clip_uuid: item.clip_uuid, - type: item.type, - group: item.group, - object_uuid: item.object_uuid, - media_id: item.media_id, - name: item.name, - offset: item.offset, - length: item.length, - } as Clip; - existingCharacter.lipSyncClips.push(newItem); - existingCharacter.lipSyncClips.sort( - (clipA, clipB) => clipA.offset - clipB.offset, - ); - } - } - if (item.group === ClipGroup.OBJECT) { - const existingObject = addObject(item); - const newKeyframe = { - version: item.version, - keyframe_uuid: item.clip_uuid, - group: item.group, - object_uuid: item.object_uuid, - offset: item.keyframe_offset, - } as Keyframe; - existingObject.keyframes.push(newKeyframe); - existingObject.keyframes.sort( - (keyframeA, keyframeB) => keyframeA.offset - keyframeB.offset, - ); - } - if (item.group === ClipGroup.CAMERA) { - const existingCamera = cameraGroup.value; - const newKeyframe = { - version: item.version, - keyframe_uuid: item.clip_uuid, - group: item.group, - object_uuid: item.object_uuid, - offset: item.keyframe_offset, - } as Keyframe; - existingCamera.keyframes.push(newKeyframe); - existingCamera.keyframes.sort( - (keyframeA, keyframeB) => keyframeA.offset - keyframeB.offset, - ); - // cameraGroup.value = { ...cameraGroup.value }; - } - if (item.group === ClipGroup.GLOBAL_AUDIO) { - const existingAudio = audioGroup.value; - const newItem = { - version: item.version, - clip_uuid: item.clip_uuid, - type: item.type, - group: item.group, - object_uuid: item.object_uuid, - media_id: item.media_id, - name: item.name, - offset: item.offset, - length: item.length, - } as Clip; - existingAudio.clips.push(newItem); - existingAudio.clips.sort( - (clipA, clipB) => clipA.offset - clipB.offset, - ); - } + LOADING_FUNCTIONS[item.group](item); }); break; case fromEngineActions.UPDATE_CHARACTER_ID: { - const newCharacter = { - id: (data as MediaItem).object_uuid, - name: (data as MediaItem).name, - muted: false, - animationClips: [], - positionKeyframes: [], - lipSyncClips: [], - } as CharacterGroup; - - characterGroups.value = [...characterGroups.value, newCharacter].sort( - (charA, charB) => (charA.id < charB.id ? -1 : 1), - ); + addNewCharacter(data as MediaItem); break; } default: @@ -224,16 +60,19 @@ export function useQueueHandler() { } }, []); - const handleToTimelineActions = useCallback(({ action, data }: Arguments) => { - console.log("TO TIMELINE", action, data); - switch (action) { - case toTimelineActions.ADD_KEYFRAME: - addKeyframe(data as QueueKeyframe, currentTime.value); - break; - default: - throw new Error(`Unknown action ${action}`); - } - }, []); + const handleToTimelineActions = useCallback( + ({ action, data }: Arguments) => { + console.log("TO TIMELINE", action, data); + switch (action) { + case toTimelineActions.ADD_KEYFRAME: + addKeyframe(data as QueueKeyframe, currentTime.value); + break; + default: + throw new Error(`Unknown action ${action}`); + } + }, + [addKeyframe], + ); useEffect(() => { Queue.subscribe(QueueNames.FROM_ENGINE, handleFromEngineActions); diff --git a/app/pages/PageEnigma/contexts/TrackContext/TrackContext.tsx b/app/pages/PageEnigma/contexts/TrackContext/TrackContext.tsx index f1105ec7c6..0c8d6b0b80 100644 --- a/app/pages/PageEnigma/contexts/TrackContext/TrackContext.tsx +++ b/app/pages/PageEnigma/contexts/TrackContext/TrackContext.tsx @@ -6,6 +6,9 @@ export const TrackContext = createContext<{ addKeyframe: (keyframe: QueueKeyframe, offset: number) => void; deleteKeyframe: (keyframe: Keyframe) => void; + // misc + clearExistingData: () => void; + // drag and drop startDrag: (item: MediaItem) => void; endDrag: () => void; @@ -13,6 +16,8 @@ export const TrackContext = createContext<{ addKeyframe: () => {}, deleteKeyframe: () => {}, + clearExistingData: () => {}, + startDrag: () => {}, endDrag: () => {}, }); diff --git a/app/pages/PageEnigma/contexts/TrackContext/TrackProvider.tsx b/app/pages/PageEnigma/contexts/TrackContext/TrackProvider.tsx index d251604506..f8348d1c5a 100644 --- a/app/pages/PageEnigma/contexts/TrackContext/TrackProvider.tsx +++ b/app/pages/PageEnigma/contexts/TrackContext/TrackProvider.tsx @@ -12,6 +12,10 @@ import { dropId, dropOffset, addObject, + characterGroups, + cameraGroup, + audioGroup, + objectGroup, } from "~/pages/PageEnigma/store"; import useUpdateKeyframe from "~/pages/PageEnigma/contexts/TrackContext/utils/useUpdateKeyframe"; @@ -26,12 +30,9 @@ export const TrackProvider = ({ children }: Props) => { // cross group functions const dropClip = useCallback(() => { - console.log(`${JSON.stringify(dragItem)}`); - - if (dragItem.value != null && dragItem != null) { - // should be able to drag into the timeline ... TODO FIX? - - if (dragItem.value.type === AssetType.CHARACTER) { + if (dragItem.value) { + const mediaItem = dragItem.value; + if (mediaItem.type === AssetType.CHARACTER) { addCharacter(dragItem.value); } // if (dragItem.value.type === AssetType.CAMERA) { @@ -48,7 +49,7 @@ export const TrackProvider = ({ children }: Props) => { if (canDrop.value && dragItem.value) { if (dragItem.value.type === AssetType.ANIMATION) { addCharacterAnimation({ - dragItem: dragItem.value!, + dragItem: dragItem.value, characterId: dropId.value, offset: dropOffset.value, }); @@ -56,12 +57,12 @@ export const TrackProvider = ({ children }: Props) => { if (dragItem.value.type === AssetType.AUDIO) { console.log("add audio", dropId.value); addCharacterAudio({ - dragItem: dragItem.value!, + dragItem: dragItem.value, characterId: dropId.value, offset: dropOffset.value, }); addGlobalAudio({ - dragItem: dragItem.value!, + dragItem: dragItem.value, audioId: dropId.value, offset: dropOffset.value, }); @@ -70,14 +71,34 @@ export const TrackProvider = ({ children }: Props) => { endDrag(); }, [endDrag]); + const clearExistingData = useCallback(() => { + characterGroups.value = []; + cameraGroup.value = { + id: "CG1", + keyframes: [], + }; + audioGroup.value = { + id: "AG-1", + clips: [], + muted: false, + }; + objectGroup.value = { + id: "OG1", + objects: [], + }; + }, []); + const values = useMemo(() => { return { ...keyframes, + clearExistingData, + ...dragDrop, endDrag: dropClip, }; - }, [keyframes, dragDrop, dropClip]); + }, [keyframes, dragDrop, dropClip, clearExistingData]); + return ( {children} ); diff --git a/app/pages/PageEnigma/js/editor.ts b/app/pages/PageEnigma/js/editor.ts index fd3ff7b0da..af0707f0b9 100644 --- a/app/pages/PageEnigma/js/editor.ts +++ b/app/pages/PageEnigma/js/editor.ts @@ -311,7 +311,7 @@ class Editor { this.timeline.scene = this.activeScene; - //this._test_demo(); + this._test_demo(); this.renderer.domElement.addEventListener( "mousedown", @@ -1076,6 +1076,7 @@ class Editor { this.raycaster.setFromCamera(this.mouse, this.camera); const interactable: any[] = []; this.activeScene.scene.children.forEach((child: THREE.Object3D) => { + // console.log(child); if (child.name != "") { if (child.type == "Mesh" || child.type == "Object3D" || child.type == "Group") { interactable.push(child); diff --git a/app/pages/PageEnigma/store/audioGroup.ts b/app/pages/PageEnigma/store/audioGroup.ts index 9a6d7fc2dd..630778507c 100644 --- a/app/pages/PageEnigma/store/audioGroup.ts +++ b/app/pages/PageEnigma/store/audioGroup.ts @@ -10,6 +10,7 @@ import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions"; import * as uuid from "uuid"; import { signal } from "@preact/signals-core"; +import { ClipUI } from "~/pages/PageEnigma/datastructures/clips/clip_ui"; export const audioGroup = signal({ id: "AG-1", @@ -140,3 +141,20 @@ export function deleteAudioClip(clip: Clip) { ], }; } + +export function loadAudioData(item: ClipUI) { + const existingAudio = audioGroup.value; + const newItem = { + version: item.version, + clip_uuid: item.clip_uuid, + type: item.type, + group: item.group, + object_uuid: item.object_uuid, + media_id: item.media_id, + name: item.name, + offset: item.offset, + length: item.length, + } as Clip; + existingAudio.clips.push(newItem); + existingAudio.clips.sort((clipA, clipB) => clipA.offset - clipB.offset); +} diff --git a/app/pages/PageEnigma/store/cameraGroup.ts b/app/pages/PageEnigma/store/cameraGroup.ts index 7062e1fd21..8dd9eaf5e4 100644 --- a/app/pages/PageEnigma/store/cameraGroup.ts +++ b/app/pages/PageEnigma/store/cameraGroup.ts @@ -8,6 +8,7 @@ import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions"; import * as uuid from "uuid"; import { signal } from "@preact/signals-core"; +import { ClipUI } from "~/pages/PageEnigma/datastructures/clips/clip_ui"; export const cameraGroup = signal({ id: "CG1", keyframes: [] }); @@ -50,10 +51,14 @@ export function updateCamera({ }; } -export function addCameraKeyframe(keyframe: QueueKeyframe, offset: number) { +export function addCameraKeyframe( + keyframe: QueueKeyframe, + offset: number, + addToast: (type: "error" | "warning" | "success", message: string) => void, +) { const oldCameraGroup = cameraGroup.value; if (oldCameraGroup.keyframes.some((row) => row.offset === offset)) { - //toast.error("There can only be one keyframe at this offset."); + addToast("warning", "There can only be one keyframe at this offset."); return; } const newKeyframe = { @@ -119,3 +124,18 @@ export function deleteCameraKeyframe(deleteKeyframe: Keyframe) { ], }; } + +export function loadCameraData(item: ClipUI) { + const existingCamera = cameraGroup.value; + const newKeyframe = { + version: item.version, + keyframe_uuid: item.clip_uuid, + group: item.group, + object_uuid: item.object_uuid, + offset: item.keyframe_offset, + } as Keyframe; + existingCamera.keyframes.push(newKeyframe); + existingCamera.keyframes.sort( + (keyframeA, keyframeB) => keyframeA.offset - keyframeB.offset, + ); +} diff --git a/app/pages/PageEnigma/store/characterGroups/addCharacter.ts b/app/pages/PageEnigma/store/characterGroups/addCharacter.ts new file mode 100644 index 0000000000..c68a300002 --- /dev/null +++ b/app/pages/PageEnigma/store/characterGroups/addCharacter.ts @@ -0,0 +1,28 @@ +import { CharacterGroup, MediaItem } from "~/pages/PageEnigma/models"; +import Queue from "~/pages/PageEnigma/Queue/Queue"; +import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; +import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions"; +import { characterGroups } from "~/pages/PageEnigma/store"; + +export function addCharacter(character: MediaItem) { + Queue.publish({ + queueName: QueueNames.TO_ENGINE, + action: toEngineActions.ADD_CHARACTER, + data: character, + }); +} + +export function addNewCharacter(data: MediaItem) { + const newCharacter = { + id: data.object_uuid, + name: data.name, + muted: false, + animationClips: [], + positionKeyframes: [], + lipSyncClips: [], + } as CharacterGroup; + + characterGroups.value = [...characterGroups.value, newCharacter].sort( + (charA, charB) => (charA.id < charB.id ? -1 : 1), + ); +} diff --git a/app/pages/PageEnigma/store/characterGroups.ts b/app/pages/PageEnigma/store/characterGroups/characterGroups.ts similarity index 68% rename from app/pages/PageEnigma/store/characterGroups.ts rename to app/pages/PageEnigma/store/characterGroups/characterGroups.ts index 024149c05c..9c3f8e6b62 100644 --- a/app/pages/PageEnigma/store/characterGroups.ts +++ b/app/pages/PageEnigma/store/characterGroups/characterGroups.ts @@ -15,95 +15,6 @@ import * as uuid from "uuid"; export const characterGroups = signal([]); -export function updateCharacters({ - type, - id, - offset, - length, -}: { - type: ClipType; - id: string; - length?: number; - offset: number; -}) { - const oldCharacterGroups = characterGroups.value; - if (type === ClipType.ANIMATION) { - characterGroups.value = oldCharacterGroups.map((character) => { - const newAnimationClips = [...character.animationClips]; - const clipIndex = newAnimationClips.findIndex( - (row) => row.clip_uuid === id, - ); - if (clipIndex === -1) { - return { ...character }; - } - const clip = newAnimationClips[clipIndex]; - clip.offset = offset; - clip.length = length!; - - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: toEngineActions.UPDATE_CLIP, - data: clip, - }); - - return { - ...character, - animationClips: newAnimationClips, - }; - }); - } - - if (type === ClipType.TRANSFORM) { - characterGroups.value = oldCharacterGroups.map((character) => { - const newPositionKeyframes = [...character.positionKeyframes]; - const keyframeIndex = newPositionKeyframes.findIndex( - (row) => row.keyframe_uuid === id, - ); - if (keyframeIndex === -1) { - return { ...character }; - } - const keyframe = newPositionKeyframes[keyframeIndex]; - keyframe.offset = offset; - - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: toEngineActions.UPDATE_CLIP, - data: keyframe, - }); - - return { - ...character, - positionKeyframes: newPositionKeyframes, - }; - }); - } - if (type === ClipType.AUDIO) { - characterGroups.value = oldCharacterGroups.map((character) => { - const newLipSyncClips = [...character.lipSyncClips]; - const clipIndex = newLipSyncClips.findIndex( - (row) => row.clip_uuid === id, - ); - if (clipIndex === -1) { - return { ...character }; - } - const clip = newLipSyncClips[clipIndex]; - clip.offset = offset; - clip.length = length!; - - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: toEngineActions.UPDATE_CLIP, - data: clip, - }); - - return { - ...character, - lipSyncClips: newLipSyncClips, - }; - }); - } -} - export function addCharacterAnimation({ dragItem, characterId, @@ -331,25 +242,3 @@ export function deleteCharacterKeyframe(keyframe: Keyframe) { }), })); } - -// probably not to much to send imo -export function addCharacter(character: MediaItem) { - //{"version":1,"media_id":"m_r7w1tmkx2jg8nznr3hyzj4k6zhfh7d ", - // "type":"character","name":"Female Doll", - // "thumbnail":"resources/characters/img03.png"} - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: toEngineActions.ADD_CHARACTER, - data: character, - }); -} -export function addObject(object: MediaItem) { - //{"version":1,"media_id":"m_r7w1tmkx2jg8nznr3hyzj4k6zhfh7d ", - // "type":"character","name":"Female Doll", - // "thumbnail":"resources/characters/img03.png"} - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: toEngineActions.ADD_OBJECT, - data: object, - }); -} diff --git a/app/pages/PageEnigma/store/characterGroups/index.ts b/app/pages/PageEnigma/store/characterGroups/index.ts new file mode 100644 index 0000000000..eb200150c0 --- /dev/null +++ b/app/pages/PageEnigma/store/characterGroups/index.ts @@ -0,0 +1,4 @@ +export * from "./addCharacter"; +export * from "./characterGroups"; +export * from "./loadCharacterData"; +export * from "./updateCharacters"; diff --git a/app/pages/PageEnigma/store/characterGroups/loadCharacterData.ts b/app/pages/PageEnigma/store/characterGroups/loadCharacterData.ts new file mode 100644 index 0000000000..db7722abe1 --- /dev/null +++ b/app/pages/PageEnigma/store/characterGroups/loadCharacterData.ts @@ -0,0 +1,89 @@ +import { ClipUI } from "~/pages/PageEnigma/datastructures/clips/clip_ui"; +import { + CharacterGroup, + Clip, + ClipType, + Keyframe, +} from "~/pages/PageEnigma/models"; +import { characterGroups } from "~/pages/PageEnigma/store"; + +function getAddCharacter(item: ClipUI) { + const existingCharacter = characterGroups.value.find( + (character) => character.id === item.object_uuid, + ); + + if (existingCharacter) { + return existingCharacter; + } + + const newCharacter = { + id: item.object_uuid, + name: item.object_name, + muted: false, + animationClips: [], + positionKeyframes: [], + lipSyncClips: [], + } as CharacterGroup; + + characterGroups.value = [ + ...characterGroups.value.filter( + (character) => character.id !== item.object_uuid, + ), + newCharacter, + ].sort((charA, charB) => (charA.id < charB.id ? -1 : 1)); + + return characterGroups.value.find( + (character) => character.id === item.object_uuid, + ) as CharacterGroup; +} + +export function loadCharacterData(item: ClipUI) { + const existingCharacter = getAddCharacter(item); + if (item.type === ClipType.ANIMATION) { + const newItem = { + version: item.version, + clip_uuid: item.clip_uuid, + type: item.type, + group: item.group, + object_uuid: item.object_uuid, + media_id: item.media_id, + name: item.name, + offset: item.offset, + length: item.length, + } as Clip; + existingCharacter.animationClips.push(newItem); + existingCharacter.animationClips.sort( + (clipA, clipB) => clipA.offset - clipB.offset, + ); + } + if (item.type === ClipType.TRANSFORM) { + const newKeyframe = { + version: item.version, + keyframe_uuid: item.clip_uuid, + group: item.group, + object_uuid: item.object_uuid, + offset: item.keyframe_offset, + } as Keyframe; + existingCharacter.positionKeyframes.push(newKeyframe); + existingCharacter.positionKeyframes.sort( + (keyframeA, keyframeB) => keyframeA.offset - keyframeB.offset, + ); + } + if (item.type === ClipType.AUDIO) { + const newItem = { + version: item.version, + clip_uuid: item.clip_uuid, + type: item.type, + group: item.group, + object_uuid: item.object_uuid, + media_id: item.media_id, + name: item.name, + offset: item.offset, + length: item.length, + } as Clip; + existingCharacter.lipSyncClips.push(newItem); + existingCharacter.lipSyncClips.sort( + (clipA, clipB) => clipA.offset - clipB.offset, + ); + } +} diff --git a/app/pages/PageEnigma/store/characterGroups/updateCharacters.ts b/app/pages/PageEnigma/store/characterGroups/updateCharacters.ts new file mode 100644 index 0000000000..93de4af12a --- /dev/null +++ b/app/pages/PageEnigma/store/characterGroups/updateCharacters.ts @@ -0,0 +1,94 @@ +import { ClipType } from "~/pages/PageEnigma/models"; +import Queue from "~/pages/PageEnigma/Queue/Queue"; +import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; +import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions"; +import { characterGroups } from "~/pages/PageEnigma/store"; + +export function updateCharacters({ + type, + id, + offset, + length, +}: { + type: ClipType; + id: string; + length?: number; + offset: number; +}) { + const oldCharacterGroups = characterGroups.value; + if (type === ClipType.ANIMATION) { + characterGroups.value = oldCharacterGroups.map((character) => { + const newAnimationClips = [...character.animationClips]; + const clipIndex = newAnimationClips.findIndex( + (row) => row.clip_uuid === id, + ); + if (clipIndex === -1) { + return { ...character }; + } + const clip = newAnimationClips[clipIndex]; + clip.offset = offset; + clip.length = length!; + + Queue.publish({ + queueName: QueueNames.TO_ENGINE, + action: toEngineActions.UPDATE_CLIP, + data: clip, + }); + + return { + ...character, + animationClips: newAnimationClips, + }; + }); + } + + if (type === ClipType.TRANSFORM) { + characterGroups.value = oldCharacterGroups.map((character) => { + const newPositionKeyframes = [...character.positionKeyframes]; + const keyframeIndex = newPositionKeyframes.findIndex( + (row) => row.keyframe_uuid === id, + ); + if (keyframeIndex === -1) { + return { ...character }; + } + const keyframe = newPositionKeyframes[keyframeIndex]; + keyframe.offset = offset; + + Queue.publish({ + queueName: QueueNames.TO_ENGINE, + action: toEngineActions.UPDATE_CLIP, + data: keyframe, + }); + + return { + ...character, + positionKeyframes: newPositionKeyframes, + }; + }); + } + if (type === ClipType.AUDIO) { + characterGroups.value = oldCharacterGroups.map((character) => { + const newLipSyncClips = [...character.lipSyncClips]; + const clipIndex = newLipSyncClips.findIndex( + (row) => row.clip_uuid === id, + ); + if (clipIndex === -1) { + return { ...character }; + } + const clip = newLipSyncClips[clipIndex]; + clip.offset = offset; + clip.length = length!; + + Queue.publish({ + queueName: QueueNames.TO_ENGINE, + action: toEngineActions.UPDATE_CLIP, + data: clip, + }); + + return { + ...character, + lipSyncClips: newLipSyncClips, + }; + }); + } +} diff --git a/app/pages/PageEnigma/store/objectGroup/index.ts b/app/pages/PageEnigma/store/objectGroup/index.ts new file mode 100644 index 0000000000..43c1cdb480 --- /dev/null +++ b/app/pages/PageEnigma/store/objectGroup/index.ts @@ -0,0 +1,3 @@ +export * from "./loadObjectData"; +export * from "./objectGroup"; +export * from "./updateObjectGroup"; diff --git a/app/pages/PageEnigma/store/objectGroup/loadObjectData.ts b/app/pages/PageEnigma/store/objectGroup/loadObjectData.ts new file mode 100644 index 0000000000..8831681799 --- /dev/null +++ b/app/pages/PageEnigma/store/objectGroup/loadObjectData.ts @@ -0,0 +1,48 @@ +import { ClipUI } from "~/pages/PageEnigma/datastructures/clips/clip_ui"; +import { Keyframe, ObjectTrack } from "~/pages/PageEnigma/models"; +import { objectGroup } from "~/pages/PageEnigma/store"; + +function getAddObject(item: ClipUI) { + const existingObject = objectGroup.value.objects.find( + (obj) => obj.object_uuid === item.object_uuid, + ); + + if (existingObject) { + return existingObject; + } + + const newObject = { + object_uuid: item.object_uuid, + name: item.object_name, + keyframes: [] as Keyframe[], + } as ObjectTrack; + + objectGroup.value = { + id: "OB1", + objects: [ + ...objectGroup.value.objects.filter( + (obj) => obj.object_uuid !== item.object_uuid, + ), + newObject, + ].sort((objA, objB) => (objA.object_uuid < objB.object_uuid ? -1 : 1)), + }; + + return objectGroup.value.objects.find( + (obj) => obj.object_uuid === item.object_uuid, + ) as ObjectTrack; +} + +export function loadObjectData(item: ClipUI) { + const existingObject = getAddObject(item); + const newKeyframe = { + version: item.version, + keyframe_uuid: item.clip_uuid, + group: item.group, + object_uuid: item.object_uuid, + offset: item.keyframe_offset, + } as Keyframe; + existingObject.keyframes.push(newKeyframe); + existingObject.keyframes.sort( + (keyframeA, keyframeB) => keyframeA.offset - keyframeB.offset, + ); +} diff --git a/app/pages/PageEnigma/store/objectGroup.ts b/app/pages/PageEnigma/store/objectGroup/objectGroup.ts similarity index 70% rename from app/pages/PageEnigma/store/objectGroup.ts rename to app/pages/PageEnigma/store/objectGroup/objectGroup.ts index d39331ec31..5de11c02a8 100644 --- a/app/pages/PageEnigma/store/objectGroup.ts +++ b/app/pages/PageEnigma/store/objectGroup/objectGroup.ts @@ -1,6 +1,7 @@ import { signal } from "@preact/signals-core"; import { Keyframe, + MediaItem, ObjectGroup, QueueKeyframe, } from "~/pages/PageEnigma/models"; @@ -8,70 +9,12 @@ import * as uuid from "uuid"; import Queue from "~/pages/PageEnigma/Queue/Queue"; import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions"; -// import { toast } from "react-hot-toast"; export const objectGroup = signal({ id: "OB1", objects: [], }); -export function updateObject({ - id, - offset, - addToast, -}: { - id: string; - offset: number; - addToast: (type: "error" | "warning" | "success", message: string) => void; -}): void { - const oldObjectGroup = objectGroup.value; - const obj = oldObjectGroup.objects.find((objectTrack) => - objectTrack.keyframes.some((row) => row.keyframe_uuid === id), - ); - - if (!obj) { - return; - } - - const existingKeyframe = obj.keyframes.some((row) => { - return row.offset === offset && row.keyframe_uuid !== id; - }); - - if (existingKeyframe) { - addToast("warning", "There can only be one keyframe at this offset."); - return; - } - - objectGroup.value = { - id: oldObjectGroup.id, - objects: oldObjectGroup.objects.map((object) => ({ - object_uuid: object.object_uuid, - name: object.name, - keyframes: object.keyframes.map((keyframe) => { - if (keyframe.keyframe_uuid !== id) { - return { - ...keyframe, - }; - } - - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: toEngineActions.UPDATE_KEYFRAME, - data: { - ...keyframe, - offset, - }, - }); - - return { - ...keyframe, - offset, - }; - }), - })), - }; -} - export function addObjectKeyframe( keyframe: QueueKeyframe, offset: number, @@ -172,3 +115,11 @@ export function deleteObjectKeyframe(keyframe: Keyframe) { ], }; } + +export function addObject(object: MediaItem) { + Queue.publish({ + queueName: QueueNames.TO_ENGINE, + action: toEngineActions.ADD_OBJECT, + data: object, + }); +} diff --git a/app/pages/PageEnigma/store/objectGroup/updateObjectGroup.ts b/app/pages/PageEnigma/store/objectGroup/updateObjectGroup.ts new file mode 100644 index 0000000000..0db35259af --- /dev/null +++ b/app/pages/PageEnigma/store/objectGroup/updateObjectGroup.ts @@ -0,0 +1,59 @@ +import Queue from "~/pages/PageEnigma/Queue/Queue"; +import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; +import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions"; +import { objectGroup } from "~/pages/PageEnigma/store"; + +export function updateObject({ + id, + offset, +}: { + id: string; + offset: number; +}): void { + const oldObjectGroup = objectGroup.value; + const obj = oldObjectGroup.objects.find((objectTrack) => + objectTrack.keyframes.some((row) => row.keyframe_uuid === id), + ); + + if (!obj) { + return; + } + + const existingKeyframe = obj.keyframes.some((row) => { + return row.offset === offset && row.keyframe_uuid !== id; + }); + + if (existingKeyframe) { + //toast.error("There can only be one keyframe at this offset."); + return; + } + + objectGroup.value = { + id: oldObjectGroup.id, + objects: oldObjectGroup.objects.map((object) => ({ + object_uuid: object.object_uuid, + name: object.name, + keyframes: object.keyframes.map((keyframe) => { + if (keyframe.keyframe_uuid !== id) { + return { + ...keyframe, + }; + } + + Queue.publish({ + queueName: QueueNames.TO_ENGINE, + action: toEngineActions.UPDATE_KEYFRAME, + data: { + ...keyframe, + offset, + }, + }); + + return { + ...keyframe, + offset, + }; + }), + })), + }; +} diff --git a/app/styles/tailwind.css b/app/styles/tailwind.css index b5c61c9567..d980c43d3a 100644 --- a/app/styles/tailwind.css +++ b/app/styles/tailwind.css @@ -1,3 +1,17 @@ @tailwind base; @tailwind components; @tailwind utilities; + +@layer components { + .filter-tab { + @apply text-nowrap rounded-lg border-2 border-assets-background bg-assets-background px-3 py-2 text-sm font-medium transition-all duration-150 hover:border-assets-background/0 hover:bg-assets-background/80; + } + + .filter-tab.active { + @apply border-2 border-brand-primary hover:border-brand-primary hover:bg-assets-background; + } + + .filter-tab.disabled { + @apply hover:bg-assets-background disabled:text-white/50; + } +} diff --git a/package-lock.json b/package-lock.json index 04f0f64514..afe29ba415 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "react-cookie": "^7.1.4", "react-dom": "^18.2.0", "tailwind-merge": "^2.2.2", + "terser": "^5.30.3", "three": "^0.162.0", "uuid": "^9.0.1", "webm-writer": "^1.0.0" @@ -56,6 +57,7 @@ "prettier": "^3.2.5", "prettier-plugin-tailwindcss": "^0.5.12", "tailwindcss": "^3.4.1", + "terser": "^5.30.3", "typescript": "^5.1.6", "vite": "^5.0.0", "vite-tsconfig-paths": "^4.2.1" @@ -1494,14 +1496,14 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -1517,14 +1519,24 @@ } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, "engines": { "node": ">=6.0.0" } }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", @@ -1532,9 +1544,9 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", - "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -11975,6 +11987,30 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/terser": { + "version": "5.30.3", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.30.3.tgz", + "integrity": "sha512-STdUgOUx8rLbMGO9IOwHLpCqolkDITFFQSMYYwKE1N2lY6MVSaeoi10z/EhWxRc6ybqoVmKSkhKYH/XUpl7vSA==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", diff --git a/package.json b/package.json index 10422691ce..509db7cff2 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "scripts": { "build": "remix vite:build", "dev": "remix vite:dev", + "staging-build": "remix vite:build", "lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .", "start": "netlify serve", "typecheck": "tsc", @@ -36,6 +37,7 @@ "react-cookie": "^7.1.4", "react-dom": "^18.2.0", "tailwind-merge": "^2.2.2", + "terser": "^5.30.3", "three": "^0.162.0", "uuid": "^9.0.1", "webm-writer": "^1.0.0" @@ -62,6 +64,7 @@ "prettier": "^3.2.5", "prettier-plugin-tailwindcss": "^0.5.12", "tailwindcss": "^3.4.1", + "terser": "^5.30.3", "typescript": "^5.1.6", "vite": "^5.0.0", "vite-tsconfig-paths": "^4.2.1" diff --git a/public/resources/icons/animations.png b/public/resources/icons/animations.png index fabaf7f681..430647afa9 100644 Binary files a/public/resources/icons/animations.png and b/public/resources/icons/animations.png differ diff --git a/public/resources/icons/audios.png b/public/resources/icons/audios.png index e2c3c0fa52..96979fc9c3 100644 Binary files a/public/resources/icons/audios.png and b/public/resources/icons/audios.png differ diff --git a/public/resources/icons/camera.png b/public/resources/icons/camera.png index 9e162a9e4a..3e5ff43ffe 100644 Binary files a/public/resources/icons/camera.png and b/public/resources/icons/camera.png differ diff --git a/public/resources/icons/characters.png b/public/resources/icons/characters.png index 11f2ef919d..4a5c484c47 100644 Binary files a/public/resources/icons/characters.png and b/public/resources/icons/characters.png differ diff --git a/public/resources/icons/objects.png b/public/resources/icons/objects.png index 11f2ef919d..0f79e3d075 100644 Binary files a/public/resources/icons/objects.png and b/public/resources/icons/objects.png differ diff --git a/public/resources/icons/shapes.png b/public/resources/icons/shapes.png index 0657f36870..aca5d2aec9 100644 Binary files a/public/resources/icons/shapes.png and b/public/resources/icons/shapes.png differ diff --git a/public/resources/models/pose/blend_pose.glb b/public/resources/models/pose/blend_pose.glb deleted file mode 100644 index 3cce1fa632..0000000000 Binary files a/public/resources/models/pose/blend_pose.glb and /dev/null differ diff --git a/tailwind.config.ts b/tailwind.config.ts index f69f1008b2..caad8b5b76 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -94,6 +94,22 @@ export default { "950": "#0c270f", }, + action: { + //comet + DEFAULT: "#55556b", //600 + "50": "#f7f7f8", + "100": "#eeedf1", + "200": "#d8d8df", + "300": "#b5b5c4", + "400": "#8d8da3", + "500": "#6f7088", + "600": "#55556b", + "700": "#49495b", + "800": "#3f3f4d", + "900": "#373743", + "950": "#26252c", + }, + character: { unselected: "#46527C", selected: "#6384F4", diff --git a/vite.config.ts b/vite.config.ts index 39b9a2db97..d7d62064d9 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -19,4 +19,12 @@ export default defineConfig({ }); }, },], + build: { + minify: "terser", + }, + terserOptions: { + format: { + comments: false, + }, + }, });