From 1a0f471f547e3a150aa2ed7e7a8bd8d43c1c2bfb Mon Sep 17 00:00:00 2001 From: FromTheLOLAshes <153381408+FromTheLOLAshes@users.noreply.github.com> Date: Thu, 2 May 2024 14:06:26 -0400 Subject: [PATCH] Scroll timeline (#299) scrolls selected item in the editor into view in the timeline --------- Co-authored-by: Christopher Oliphant --- .../itemTabs/StylizeTab/StyleButtons.tsx | 2 - app/pages/PageEnigma/comps/Timeline/Audio.tsx | 2 + .../PageEnigma/comps/Timeline/Camera.tsx | 2 + .../PageEnigma/comps/Timeline/Character.tsx | 3 +- .../comps/Timeline/ObjectGroups.tsx | 10 ++-- .../PageEnigma/comps/Timeline/Timeline.tsx | 36 +++++++++++-- app/pages/PageEnigma/js/editor.ts | 54 +++++++++++++------ 7 files changed, 85 insertions(+), 24 deletions(-) diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/StylizeTab/StyleButtons.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/StylizeTab/StyleButtons.tsx index 6aead1af5f..57e667ac1f 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/StylizeTab/StyleButtons.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/StylizeTab/StyleButtons.tsx @@ -33,8 +33,6 @@ export function StyleButtons() { }); }; - console.log("state", editorState.value); - return (
diff --git a/app/pages/PageEnigma/comps/Timeline/Audio.tsx b/app/pages/PageEnigma/comps/Timeline/Audio.tsx index 2014659b89..018013044b 100644 --- a/app/pages/PageEnigma/comps/Timeline/Audio.tsx +++ b/app/pages/PageEnigma/comps/Timeline/Audio.tsx @@ -18,6 +18,7 @@ export const Audio = () => { if (audioMinimized.value) { return (
{objectGroup.value.objects.map((object) => ( -
+
))} diff --git a/app/pages/PageEnigma/comps/Timeline/Timeline.tsx b/app/pages/PageEnigma/comps/Timeline/Timeline.tsx index 935f7391f5..ecafa32b2f 100644 --- a/app/pages/PageEnigma/comps/Timeline/Timeline.tsx +++ b/app/pages/PageEnigma/comps/Timeline/Timeline.tsx @@ -1,4 +1,4 @@ -import { UIEvent, useCallback, useEffect, useState } from "react"; +import { UIEvent, useCallback, useEffect, useRef, useState } from "react"; import { LowerPanel } from "~/modules/LowerPanel"; import { Camera } from "./Camera"; @@ -9,12 +9,14 @@ import { deleteCharacterClip, filmLength, ignoreKeyDelete, + isHotkeyDisabled, + objectsMinimized, scale, selectedItem, + selectedObject, timelineHeight, timelineScrollX, timelineScrollY, - isHotkeyDisabled, } from "~/pages/PageEnigma/store"; import { useQueueHandler } from "~/pages/PageEnigma/comps/Timeline/utils/useQueueHandler"; import { useSignals } from "@preact/signals-react/runtime"; @@ -23,7 +25,7 @@ import { Scrubber } from "~/pages/PageEnigma/comps/Timeline/Scrubber"; import { Characters } from "~/pages/PageEnigma/comps/Timeline/Characters"; import { ObjectGroups } from "~/pages/PageEnigma/comps/Timeline/ObjectGroups"; import useUpdateKeyframe from "~/pages/PageEnigma/contexts/TrackContext/utils/useUpdateKeyframe"; -import { Clip, Keyframe } from "~/pages/PageEnigma/models"; +import { AssetType, Clip, Keyframe } from "~/pages/PageEnigma/models"; import { RowHeaders } from "~/pages/PageEnigma/comps/Timeline/RowHeaders/RowHeaders"; import { pageWidth } from "~/store"; import { Pages } from "~/pages/PageEnigma/constants/page"; @@ -36,10 +38,38 @@ function getItemType(item: Clip | Keyframe | null) { return (item as Clip).clip_uuid ? "clip" : "keyframe"; } +function scrollItem(itemId: string) { + const element = document.getElementById(itemId); + if (!element) { + return; + } + element.scrollIntoView({ behavior: "smooth", block: "nearest" }); +} + export const Timeline = () => { useSignals(); const [dialogOpen, setDialogOpen] = useState(false); const { deleteKeyframe } = useUpdateKeyframe(); + const lastSelectedObject = useRef(selectedObject.value); + + if (selectedObject.value !== lastSelectedObject.current) { + lastSelectedObject.current = selectedObject.value; + switch (selectedObject.value?.type) { + case AssetType.CHARACTER: + scrollItem(`track-character-${selectedObject.value?.id}`); + break; + case AssetType.CAMERA: + scrollItem("track-camera"); + break; + case AssetType.OBJECT: + if (objectsMinimized.value) { + scrollItem("track-objects"); + } else { + scrollItem(`track-object-${selectedObject.value?.id}`); + } + break; + } + } const onScroll = useCallback((event: UIEvent) => { timelineScrollX.value = event.currentTarget.scrollLeft; diff --git a/app/pages/PageEnigma/js/editor.ts b/app/pages/PageEnigma/js/editor.ts index 6362606790..40635eb3d9 100644 --- a/app/pages/PageEnigma/js/editor.ts +++ b/app/pages/PageEnigma/js/editor.ts @@ -35,7 +35,11 @@ import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; import { fromEngineActions } from "~/pages/PageEnigma/Queue/fromEngineActions"; import { AssetType, MediaItem } from "~/pages/PageEnigma/models"; import { loadingBarData, loadingBarIsShowing } from "~/store/loadingBar"; -import { editorState, EditorStates, previewSrc } from "~/pages/PageEnigma/store/engine"; +import { + editorState, + EditorStates, + previewSrc, +} from "~/pages/PageEnigma/store/engine"; // Main editor class that will call everything else all you need to call is " initialize() ". class Editor { @@ -317,7 +321,10 @@ class Editor { this.renderer.setPixelRatio(window.devicePixelRatio); //document.body.appendChild(this.renderer.domElement) window.addEventListener("resize", this.onWindowResize.bind(this)); - this.renderer.domElement.addEventListener("resize", this.onWindowResize.bind(this)); + this.renderer.domElement.addEventListener( + "resize", + this.onWindowResize.bind(this), + ); this._configurePostProcessing(); // Controls and movement. @@ -479,7 +486,7 @@ class Editor { ]); } - public async testTestTimelineEvents() { } + public async testTestTimelineEvents() {} public async loadScene(scene_media_token: string) { this.dispatchAppUiState({ @@ -564,7 +571,11 @@ class Editor { ); const timeline_json = await proxyTimeline.saveToJson(); - const save_data = { version: this.version, scene: scene_json, timeline: timeline_json }; + const save_data = { + version: this.version, + scene: scene_json, + timeline: timeline_json, + }; // TODO turn scene information into and object ... const result = await this.api_manager.saveSceneState( @@ -833,23 +844,29 @@ class Editor { // Basicly Unity 3D's update loop. async updateLoop() { - setTimeout(() => { - requestAnimationFrame(this.updateLoop.bind(this)); - }, 1000 / (this.cap_fps * 2)); // Get the most FPS we can out of the renderer. + setTimeout( + () => { + requestAnimationFrame(this.updateLoop.bind(this)); + }, + 1000 / (this.cap_fps * 2), + ); // Get the most FPS we can out of the renderer. if (this.container === undefined) { this.container = document.getElementById("video-scene-container"); } if (!this.rendering && this.container !== undefined) { - if (this.container.clientWidth + this.container.clientHeight !== this.lastCanvasSize) { + if ( + this.container.clientWidth + this.container.clientHeight !== + this.lastCanvasSize + ) { this.onWindowResize(); - this.lastCanvasSize = this.container.clientWidth + this.container.clientHeight; + this.lastCanvasSize = + this.container.clientWidth + this.container.clientHeight; } } if (this.cam_obj == undefined) { this.cam_obj = this.activeScene.get_object_by_name(this.camera_name); - } // Updates debug stats. @@ -969,10 +986,10 @@ class Editor { audioSegment, "-filter_complex", "[1:a]adelay=" + - startTime * 1000 + - "|" + - startTime * 1000 + - "[a1];[0:a][a1]amix=inputs=2[a]", + startTime * 1000 + + "|" + + startTime * 1000 + + "[a1];[0:a][a1]amix=inputs=2[a]", "-map", "[a]", `${itteration}final_tmp.wav`, @@ -1461,6 +1478,13 @@ class Editor { } } + getAssetType(selected: THREE.Object3D): AssetType { + if (selected.type === "Mesh") { + return selected.name === "::CAM::" ? AssetType.CAMERA : AssetType.OBJECT; + } + return AssetType.CHARACTER; + } + publishSelect() { if (this.selected) { console.log("publish", this.selected); @@ -1468,7 +1492,7 @@ class Editor { queueName: QueueNames.FROM_ENGINE, action: fromEngineActions.SELECT_OBJECT, data: { - type: this.selected.type, + type: this.getAssetType(this.selected), object_uuid: this.selected.uuid, version: 1, media_id: this.selected.id.toString(),