@editframe/elements 0.30.1-beta.0 → 0.31.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/EF_FRAMEGEN.d.ts +5 -0
- package/dist/EF_FRAMEGEN.js +20 -4
- package/dist/EF_FRAMEGEN.js.map +1 -1
- package/dist/EF_INTERACTIVE.js.map +1 -1
- package/dist/_virtual/rolldown_runtime.js +27 -0
- package/dist/canvas/EFCanvas.d.ts +311 -0
- package/dist/canvas/EFCanvas.js +1089 -0
- package/dist/canvas/EFCanvas.js.map +1 -0
- package/dist/canvas/EFCanvasItem.d.ts +55 -0
- package/dist/canvas/EFCanvasItem.js +72 -0
- package/dist/canvas/EFCanvasItem.js.map +1 -0
- package/dist/canvas/api/CanvasAPI.d.ts +115 -0
- package/dist/canvas/api/CanvasAPI.js +182 -0
- package/dist/canvas/api/CanvasAPI.js.map +1 -0
- package/dist/canvas/api/types.d.ts +42 -0
- package/dist/canvas/coordinateTransform.js +90 -0
- package/dist/canvas/coordinateTransform.js.map +1 -0
- package/dist/canvas/getElementBounds.js +40 -0
- package/dist/canvas/getElementBounds.js.map +1 -0
- package/dist/canvas/overlays/SelectionOverlay.js +265 -0
- package/dist/canvas/overlays/SelectionOverlay.js.map +1 -0
- package/dist/canvas/overlays/overlayState.js +153 -0
- package/dist/canvas/overlays/overlayState.js.map +1 -0
- package/dist/canvas/selection/SelectionController.js +105 -0
- package/dist/canvas/selection/SelectionController.js.map +1 -0
- package/dist/canvas/selection/SelectionModel.d.ts +98 -0
- package/dist/canvas/selection/SelectionModel.js +229 -0
- package/dist/canvas/selection/SelectionModel.js.map +1 -0
- package/dist/canvas/selection/selectionContext.d.ts +31 -0
- package/dist/canvas/selection/selectionContext.js +12 -0
- package/dist/canvas/selection/selectionContext.js.map +1 -0
- package/dist/elements/ContainerInfo.d.ts +29 -0
- package/dist/elements/ContainerInfo.js +30 -0
- package/dist/elements/ContainerInfo.js.map +1 -0
- package/dist/elements/EFAudio.d.ts +13 -3
- package/dist/elements/EFAudio.js +64 -10
- package/dist/elements/EFAudio.js.map +1 -1
- package/dist/elements/EFCaptions.d.ts +18 -16
- package/dist/elements/EFCaptions.js +110 -19
- package/dist/elements/EFCaptions.js.map +1 -1
- package/dist/elements/EFImage.d.ts +16 -6
- package/dist/elements/EFImage.js +79 -9
- package/dist/elements/EFImage.js.map +1 -1
- package/dist/elements/EFMedia/AssetIdMediaEngine.js +51 -4
- package/dist/elements/EFMedia/AssetIdMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/AssetMediaEngine.js +125 -52
- package/dist/elements/EFMedia/AssetMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/BaseMediaEngine.js +24 -6
- package/dist/elements/EFMedia/BaseMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/JitMediaEngine.js +12 -8
- package/dist/elements/EFMedia/JitMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +46 -7
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +98 -73
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +28 -5
- package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +18 -6
- package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +8 -2
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +31 -6
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +28 -5
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +97 -72
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js.map +1 -1
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js +3 -1
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js.map +1 -1
- package/dist/elements/EFMedia/shared/BufferUtils.js +1 -1
- package/dist/elements/EFMedia/shared/BufferUtils.js.map +1 -1
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +25 -14
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js.map +1 -1
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +47 -16
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js +37 -19
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +65 -21
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +8 -3
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js +32 -9
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +33 -10
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js +23 -8
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js +34 -10
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js +31 -8
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +31 -114
- package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +44 -8
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js.map +1 -1
- package/dist/elements/EFMedia.d.ts +18 -7
- package/dist/elements/EFMedia.js +23 -3
- package/dist/elements/EFMedia.js.map +1 -1
- package/dist/elements/EFPanZoom.d.ts +96 -0
- package/dist/elements/EFPanZoom.js +290 -0
- package/dist/elements/EFPanZoom.js.map +1 -0
- package/dist/elements/EFSourceMixin.js +7 -6
- package/dist/elements/EFSourceMixin.js.map +1 -1
- package/dist/elements/EFSurface.d.ts +6 -6
- package/dist/elements/EFSurface.js +7 -2
- package/dist/elements/EFSurface.js.map +1 -1
- package/dist/elements/EFTemporal.d.ts +2 -1
- package/dist/elements/EFTemporal.js +192 -71
- package/dist/elements/EFTemporal.js.map +1 -1
- package/dist/elements/EFText.d.ts +5 -4
- package/dist/elements/EFText.js +102 -13
- package/dist/elements/EFText.js.map +1 -1
- package/dist/elements/EFTextSegment.d.ts +32 -6
- package/dist/elements/EFTextSegment.js +53 -15
- package/dist/elements/EFTextSegment.js.map +1 -1
- package/dist/elements/EFThumbnailStrip.d.ts +118 -56
- package/dist/elements/EFThumbnailStrip.js +522 -358
- package/dist/elements/EFThumbnailStrip.js.map +1 -1
- package/dist/elements/EFTimegroup.d.ts +223 -27
- package/dist/elements/EFTimegroup.js +851 -148
- package/dist/elements/EFTimegroup.js.map +1 -1
- package/dist/elements/EFVideo.d.ts +42 -5
- package/dist/elements/EFVideo.js +165 -11
- package/dist/elements/EFVideo.js.map +1 -1
- package/dist/elements/EFWaveform.d.ts +6 -6
- package/dist/elements/EFWaveform.js +2 -1
- package/dist/elements/EFWaveform.js.map +1 -1
- package/dist/elements/ElementPositionInfo.d.ts +35 -0
- package/dist/elements/ElementPositionInfo.js +49 -0
- package/dist/elements/ElementPositionInfo.js.map +1 -0
- package/dist/elements/FetchMixin.js +16 -1
- package/dist/elements/FetchMixin.js.map +1 -1
- package/dist/elements/SessionThumbnailCache.js +152 -0
- package/dist/elements/SessionThumbnailCache.js.map +1 -0
- package/dist/elements/TargetController.js +3 -1
- package/dist/elements/TargetController.js.map +1 -1
- package/dist/elements/TimegroupController.js +9 -3
- package/dist/elements/TimegroupController.js.map +1 -1
- package/dist/elements/findRootTemporal.js +30 -0
- package/dist/elements/findRootTemporal.js.map +1 -0
- package/dist/elements/renderTemporalAudio.js +18 -5
- package/dist/elements/renderTemporalAudio.js.map +1 -1
- package/dist/elements/updateAnimations.js +492 -109
- package/dist/elements/updateAnimations.js.map +1 -1
- package/dist/getRenderInfo.d.ts +2 -2
- package/dist/gui/ContextMixin.js +4 -2
- package/dist/gui/ContextMixin.js.map +1 -1
- package/dist/gui/Controllable.js +74 -1
- package/dist/gui/Controllable.js.map +1 -1
- package/dist/gui/EFActiveRootTemporal.d.ts +50 -0
- package/dist/gui/EFActiveRootTemporal.js +94 -0
- package/dist/gui/EFActiveRootTemporal.js.map +1 -0
- package/dist/gui/EFConfiguration.d.ts +11 -5
- package/dist/gui/EFConfiguration.js.map +1 -1
- package/dist/gui/EFControls.d.ts +2 -2
- package/dist/gui/EFControls.js +109 -13
- package/dist/gui/EFControls.js.map +1 -1
- package/dist/gui/EFDial.d.ts +4 -4
- package/dist/gui/EFFilmstrip.d.ts +11 -214
- package/dist/gui/EFFilmstrip.js +53 -1152
- package/dist/gui/EFFilmstrip.js.map +1 -1
- package/dist/gui/EFFitScale.d.ts +3 -3
- package/dist/gui/EFFitScale.js +39 -12
- package/dist/gui/EFFitScale.js.map +1 -1
- package/dist/gui/EFFocusOverlay.d.ts +4 -4
- package/dist/gui/EFOverlayItem.d.ts +48 -0
- package/dist/gui/EFOverlayItem.js +97 -0
- package/dist/gui/EFOverlayItem.js.map +1 -0
- package/dist/gui/EFOverlayLayer.d.ts +70 -0
- package/dist/gui/EFOverlayLayer.js +104 -0
- package/dist/gui/EFOverlayLayer.js.map +1 -0
- package/dist/gui/EFPause.d.ts +4 -4
- package/dist/gui/EFPlay.d.ts +4 -4
- package/dist/gui/EFPreview.d.ts +4 -4
- package/dist/gui/EFResizableBox.d.ts +12 -16
- package/dist/gui/EFResizableBox.js +109 -451
- package/dist/gui/EFResizableBox.js.map +1 -1
- package/dist/gui/EFScrubber.d.ts +30 -5
- package/dist/gui/EFScrubber.js +224 -31
- package/dist/gui/EFScrubber.js.map +1 -1
- package/dist/gui/EFTimeDisplay.d.ts +4 -4
- package/dist/gui/EFTimeDisplay.js +4 -1
- package/dist/gui/EFTimeDisplay.js.map +1 -1
- package/dist/gui/EFTimelineRuler.d.ts +71 -0
- package/dist/gui/EFTimelineRuler.js +320 -0
- package/dist/gui/EFTimelineRuler.js.map +1 -0
- package/dist/gui/EFToggleLoop.d.ts +4 -4
- package/dist/gui/EFTogglePlay.d.ts +4 -4
- package/dist/gui/EFTransformHandles.d.ts +91 -0
- package/dist/gui/EFTransformHandles.js +393 -0
- package/dist/gui/EFTransformHandles.js.map +1 -0
- package/dist/gui/EFWorkbench.d.ts +182 -4
- package/dist/gui/EFWorkbench.js +2067 -22
- package/dist/gui/EFWorkbench.js.map +1 -1
- package/dist/gui/FitScaleHelpers.d.ts +31 -0
- package/dist/gui/FitScaleHelpers.js +41 -0
- package/dist/gui/FitScaleHelpers.js.map +1 -0
- package/dist/gui/PlaybackController.d.ts +2 -1
- package/dist/gui/PlaybackController.js +46 -15
- package/dist/gui/PlaybackController.js.map +1 -1
- package/dist/gui/TWMixin.js +1 -1
- package/dist/gui/TWMixin.js.map +1 -1
- package/dist/gui/hierarchy/EFHierarchy.d.ts +65 -0
- package/dist/gui/hierarchy/EFHierarchy.js +338 -0
- package/dist/gui/hierarchy/EFHierarchy.js.map +1 -0
- package/dist/gui/hierarchy/EFHierarchyItem.d.ts +118 -0
- package/dist/gui/hierarchy/EFHierarchyItem.js +551 -0
- package/dist/gui/hierarchy/EFHierarchyItem.js.map +1 -0
- package/dist/gui/hierarchy/hierarchyContext.d.ts +38 -0
- package/dist/gui/hierarchy/hierarchyContext.js +8 -0
- package/dist/gui/hierarchy/hierarchyContext.js.map +1 -0
- package/dist/gui/icons.js +34 -0
- package/dist/gui/icons.js.map +1 -0
- package/dist/gui/panZoomTransformContext.js +12 -0
- package/dist/gui/panZoomTransformContext.js.map +1 -0
- package/dist/gui/previewSettingsContext.js +12 -0
- package/dist/gui/previewSettingsContext.js.map +1 -0
- package/dist/gui/timeline/EFTimeline.d.ts +270 -0
- package/dist/gui/timeline/EFTimeline.js +1369 -0
- package/dist/gui/timeline/EFTimeline.js.map +1 -0
- package/dist/gui/timeline/EFTimelineRow.js +374 -0
- package/dist/gui/timeline/EFTimelineRow.js.map +1 -0
- package/dist/gui/timeline/TrimHandles.d.ts +36 -0
- package/dist/gui/timeline/TrimHandles.js +204 -0
- package/dist/gui/timeline/TrimHandles.js.map +1 -0
- package/dist/gui/timeline/flattenHierarchy.js +31 -0
- package/dist/gui/timeline/flattenHierarchy.js.map +1 -0
- package/dist/gui/timeline/timelineStateContext.d.ts +26 -0
- package/dist/gui/timeline/timelineStateContext.js +42 -0
- package/dist/gui/timeline/timelineStateContext.js.map +1 -0
- package/dist/gui/timeline/tracks/AudioTrack.js +264 -0
- package/dist/gui/timeline/tracks/AudioTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/CaptionsTrack.js +595 -0
- package/dist/gui/timeline/tracks/CaptionsTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/HTMLTrack.js +19 -0
- package/dist/gui/timeline/tracks/HTMLTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/ImageTrack.js +53 -0
- package/dist/gui/timeline/tracks/ImageTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/TextTrack.js +250 -0
- package/dist/gui/timeline/tracks/TextTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/TimegroupTrack.js +143 -0
- package/dist/gui/timeline/tracks/TimegroupTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/TrackItem.js +269 -0
- package/dist/gui/timeline/tracks/TrackItem.js.map +1 -0
- package/dist/gui/timeline/tracks/VideoTrack.js +265 -0
- package/dist/gui/timeline/tracks/VideoTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/WaveformTrack.js +19 -0
- package/dist/gui/timeline/tracks/WaveformTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/ensureTrackItemInit.js +1 -0
- package/dist/gui/timeline/tracks/preloadTracks.js +9 -0
- package/dist/gui/timeline/tracks/renderTrackChildren.js +119 -0
- package/dist/gui/timeline/tracks/renderTrackChildren.js.map +1 -0
- package/dist/gui/timeline/tracks/waveformUtils.js +80 -0
- package/dist/gui/timeline/tracks/waveformUtils.js.map +1 -0
- package/dist/gui/transformCalculations.js +217 -0
- package/dist/gui/transformCalculations.js.map +1 -0
- package/dist/gui/transformUtils.d.ts +37 -0
- package/dist/gui/transformUtils.js +77 -0
- package/dist/gui/transformUtils.js.map +1 -0
- package/dist/gui/tree/EFTree.d.ts +59 -0
- package/dist/gui/tree/EFTree.js +174 -0
- package/dist/gui/tree/EFTree.js.map +1 -0
- package/dist/gui/tree/EFTreeItem.d.ts +38 -0
- package/dist/gui/tree/EFTreeItem.js +146 -0
- package/dist/gui/tree/EFTreeItem.js.map +1 -0
- package/dist/gui/tree/treeContext.d.ts +60 -0
- package/dist/gui/tree/treeContext.js +23 -0
- package/dist/gui/tree/treeContext.js.map +1 -0
- package/dist/index.d.ts +32 -8
- package/dist/index.js +30 -6
- package/dist/index.js.map +1 -1
- package/dist/node_modules/react/cjs/react-jsx-runtime.development.js +688 -0
- package/dist/node_modules/react/cjs/react-jsx-runtime.development.js.map +1 -0
- package/dist/node_modules/react/cjs/react.development.js +1521 -0
- package/dist/node_modules/react/cjs/react.development.js.map +1 -0
- package/dist/node_modules/react/index.js +13 -0
- package/dist/node_modules/react/index.js.map +1 -0
- package/dist/node_modules/react/jsx-runtime.js +13 -0
- package/dist/node_modules/react/jsx-runtime.js.map +1 -0
- package/dist/preview/AdaptiveResolutionTracker.js +228 -0
- package/dist/preview/AdaptiveResolutionTracker.js.map +1 -0
- package/dist/preview/RenderProfiler.js +135 -0
- package/dist/preview/RenderProfiler.js.map +1 -0
- package/dist/preview/previewSettings.js +131 -0
- package/dist/preview/previewSettings.js.map +1 -0
- package/dist/preview/previewTypes.js +64 -0
- package/dist/preview/previewTypes.js.map +1 -0
- package/dist/preview/renderTimegroupPreview.js +656 -0
- package/dist/preview/renderTimegroupPreview.js.map +1 -0
- package/dist/preview/renderTimegroupToCanvas.d.ts +37 -0
- package/dist/preview/renderTimegroupToCanvas.js +840 -0
- package/dist/preview/renderTimegroupToCanvas.js.map +1 -0
- package/dist/preview/renderTimegroupToVideo.d.ts +39 -0
- package/dist/preview/renderTimegroupToVideo.js +274 -0
- package/dist/preview/renderTimegroupToVideo.js.map +1 -0
- package/dist/preview/renderers.js +16 -0
- package/dist/preview/renderers.js.map +1 -0
- package/dist/preview/statsTrackingStrategy.js +201 -0
- package/dist/preview/statsTrackingStrategy.js.map +1 -0
- package/dist/preview/thumbnailCacheSettings.js +52 -0
- package/dist/preview/thumbnailCacheSettings.js.map +1 -0
- package/dist/preview/workers/WorkerPool.js +178 -0
- package/dist/preview/workers/WorkerPool.js.map +1 -0
- package/dist/sandbox/PlaybackControls.js +10 -0
- package/dist/sandbox/PlaybackControls.js.map +1 -0
- package/dist/sandbox/ScenarioRunner.js +1 -0
- package/dist/sandbox/index.js +2 -0
- package/dist/style.css +66 -69
- package/dist/transcoding/types/index.d.ts +2 -1
- package/dist/transcoding/utils/UrlGenerator.d.ts +6 -1
- package/dist/transcoding/utils/UrlGenerator.js +12 -3
- package/dist/transcoding/utils/UrlGenerator.js.map +1 -1
- package/dist/utils/LRUCache.js +1 -375
- package/dist/utils/LRUCache.js.map +1 -1
- package/dist/utils/frameTime.js +14 -0
- package/dist/utils/frameTime.js.map +1 -0
- package/package.json +3 -3
- package/test/profilingPlugin.ts +223 -0
- package/test/recordReplayProxyPlugin.js +22 -27
- package/test/thumbnail-performance-test.html +116 -0
- package/test/visualRegressionUtils.ts +286 -0
- package/types.json +1 -1
- package/dist/elements/TimegroupController.d.ts +0 -18
- package/dist/msToTimeCode.js +0 -17
- package/dist/msToTimeCode.js.map +0 -1
|
@@ -0,0 +1,1369 @@
|
|
|
1
|
+
import { currentTimeContext } from "../currentTimeContext.js";
|
|
2
|
+
import { durationContext } from "../durationContext.js";
|
|
3
|
+
import { loopContext, playingContext } from "../playingContext.js";
|
|
4
|
+
import { __decorate } from "../../_virtual/_@oxc-project_runtime@0.94.0/helpers/decorate.js";
|
|
5
|
+
import { isEFTemporal } from "../../elements/EFTemporal.js";
|
|
6
|
+
import { targetTemporalContext } from "../ContextMixin.js";
|
|
7
|
+
import { TWMixin } from "../TWMixin2.js";
|
|
8
|
+
import { TargetController } from "../../elements/TargetController.js";
|
|
9
|
+
import { selectionContext } from "../../canvas/selection/selectionContext.js";
|
|
10
|
+
import { findRootTemporal } from "../../elements/findRootTemporal.js";
|
|
11
|
+
import { shouldRenderElement } from "../hierarchy/EFHierarchyItem.js";
|
|
12
|
+
import { createDirectTemporalSubscription } from "../Controllable.js";
|
|
13
|
+
import { DEFAULT_PIXELS_PER_MS, pixelsPerMsToZoom, pxToTime, timeToPx, timelineStateContext } from "./timelineStateContext.js";
|
|
14
|
+
import "../../elements/EFThumbnailStrip.js";
|
|
15
|
+
import "./tracks/preloadTracks.js";
|
|
16
|
+
import { flattenHierarchy } from "./flattenHierarchy.js";
|
|
17
|
+
import "./EFTimelineRow.js";
|
|
18
|
+
import { calculateFrameIntervalMs, calculatePixelsPerFrame, quantizeToFrameTimeMs, shouldShowFrameMarkers } from "../EFTimelineRuler.js";
|
|
19
|
+
import { EFTimegroup } from "../../elements/EFTimegroup.js";
|
|
20
|
+
import { consume, provide } from "@lit/context";
|
|
21
|
+
import { LitElement, css, html, nothing } from "lit";
|
|
22
|
+
import { customElement, eventOptions, property, state } from "lit/decorators.js";
|
|
23
|
+
import { styleMap } from "lit/directives/style-map.js";
|
|
24
|
+
import { createRef, ref } from "lit/directives/ref.js";
|
|
25
|
+
import { repeat } from "lit/directives/repeat.js";
|
|
26
|
+
|
|
27
|
+
//#region src/gui/timeline/EFTimeline.ts
|
|
28
|
+
var _EFTimeline;
|
|
29
|
+
let EFTimeline = class EFTimeline$1 extends TWMixin(LitElement) {
|
|
30
|
+
static {
|
|
31
|
+
_EFTimeline = this;
|
|
32
|
+
}
|
|
33
|
+
constructor(..._args) {
|
|
34
|
+
super(..._args);
|
|
35
|
+
this.target = "";
|
|
36
|
+
this.pixelsPerMs = DEFAULT_PIXELS_PER_MS;
|
|
37
|
+
this.minZoom = .1;
|
|
38
|
+
this.maxZoom = 10;
|
|
39
|
+
this.enableTrim = false;
|
|
40
|
+
this.showControls = true;
|
|
41
|
+
this.showRuler = true;
|
|
42
|
+
this.showHierarchy = true;
|
|
43
|
+
this.showPlayhead = true;
|
|
44
|
+
this.showPlaybackControls = true;
|
|
45
|
+
this.showZoomControls = true;
|
|
46
|
+
this.showTimeDisplay = true;
|
|
47
|
+
this.controlTarget = "";
|
|
48
|
+
this.hide = "";
|
|
49
|
+
this.show = "";
|
|
50
|
+
this.targetElement = null;
|
|
51
|
+
this.currentTimeMs = 0;
|
|
52
|
+
this.isPlaying = false;
|
|
53
|
+
this.isLooping = false;
|
|
54
|
+
this.viewportScrollLeft = 0;
|
|
55
|
+
this._timelineState = {
|
|
56
|
+
pixelsPerMs: DEFAULT_PIXELS_PER_MS,
|
|
57
|
+
currentTimeMs: 0,
|
|
58
|
+
durationMs: 0,
|
|
59
|
+
viewportScrollLeft: 0,
|
|
60
|
+
viewportWidth: 800,
|
|
61
|
+
seek: () => {},
|
|
62
|
+
zoomIn: () => {},
|
|
63
|
+
zoomOut: () => {}
|
|
64
|
+
};
|
|
65
|
+
this.tracksScrollRef = createRef();
|
|
66
|
+
this.containerRef = createRef();
|
|
67
|
+
this.playheadRef = createRef();
|
|
68
|
+
this.playheadHandleRef = createRef();
|
|
69
|
+
this.frameHighlightRef = createRef();
|
|
70
|
+
this.isDraggingPlayhead = false;
|
|
71
|
+
this.cachedViewportWidth = 800;
|
|
72
|
+
this.saveZoomScrollDebounceTimer = null;
|
|
73
|
+
this.lastContextUpdateTime = 0;
|
|
74
|
+
this.lastPlayheadPx = 0;
|
|
75
|
+
this.isFollowingPlayhead = false;
|
|
76
|
+
}
|
|
77
|
+
static {
|
|
78
|
+
this.styles = [css`
|
|
79
|
+
:host {
|
|
80
|
+
display: block;
|
|
81
|
+
width: 100%;
|
|
82
|
+
height: 100%;
|
|
83
|
+
min-height: 100px;
|
|
84
|
+
|
|
85
|
+
/* Layout coordination via CSS custom properties */
|
|
86
|
+
--timeline-hierarchy-width: 200px;
|
|
87
|
+
--timeline-row-height: 24px;
|
|
88
|
+
--timeline-track-height: 24px;
|
|
89
|
+
|
|
90
|
+
/* Theme */
|
|
91
|
+
--timeline-bg: rgb(30 41 59);
|
|
92
|
+
--timeline-border: rgb(71 85 105);
|
|
93
|
+
--timeline-header-bg: rgb(51 65 85);
|
|
94
|
+
--timeline-text: rgb(226 232 240);
|
|
95
|
+
--timeline-ruler-bg: rgb(51 65 85);
|
|
96
|
+
--timeline-track-bg: rgb(51 65 85);
|
|
97
|
+
--timeline-track-hover: rgb(71 85 105);
|
|
98
|
+
--timeline-playhead: rgb(239 68 68);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
:host(.light) {
|
|
102
|
+
--timeline-bg: rgb(241 245 249);
|
|
103
|
+
--timeline-border: rgb(203 213 225);
|
|
104
|
+
--timeline-header-bg: rgb(226 232 240);
|
|
105
|
+
--timeline-text: rgb(30 41 59);
|
|
106
|
+
--timeline-ruler-bg: rgb(226 232 240);
|
|
107
|
+
--timeline-track-bg: rgb(226 232 240);
|
|
108
|
+
--timeline-track-hover: rgb(203 213 225);
|
|
109
|
+
--timeline-playhead: rgb(185 28 28);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.timeline-container {
|
|
113
|
+
display: flex;
|
|
114
|
+
flex-direction: column;
|
|
115
|
+
width: 100%;
|
|
116
|
+
height: 100%;
|
|
117
|
+
background: var(--timeline-bg);
|
|
118
|
+
color: var(--timeline-text);
|
|
119
|
+
overflow: hidden;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* === HEADER / CONTROLS === */
|
|
123
|
+
.header {
|
|
124
|
+
display: flex;
|
|
125
|
+
align-items: center;
|
|
126
|
+
justify-content: space-between;
|
|
127
|
+
gap: 16px;
|
|
128
|
+
padding: 12px 16px;
|
|
129
|
+
background: var(--timeline-header-bg);
|
|
130
|
+
border-bottom: 1px solid var(--timeline-border);
|
|
131
|
+
flex-shrink: 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.controls {
|
|
135
|
+
display: flex;
|
|
136
|
+
align-items: center;
|
|
137
|
+
gap: 12px;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.playback-controls {
|
|
141
|
+
display: flex;
|
|
142
|
+
align-items: center;
|
|
143
|
+
gap: 8px;
|
|
144
|
+
padding-right: 12px;
|
|
145
|
+
border-right: 1px solid var(--timeline-border);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.control-btn {
|
|
149
|
+
min-width: 32px;
|
|
150
|
+
height: 32px;
|
|
151
|
+
padding: 6px 10px;
|
|
152
|
+
background: rgba(255, 255, 255, 0.1);
|
|
153
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
154
|
+
border-radius: 6px;
|
|
155
|
+
color: inherit;
|
|
156
|
+
font-size: 14px;
|
|
157
|
+
cursor: pointer;
|
|
158
|
+
transition: all 0.2s ease;
|
|
159
|
+
display: flex;
|
|
160
|
+
align-items: center;
|
|
161
|
+
justify-content: center;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.control-btn:hover:not(:disabled) {
|
|
165
|
+
background: rgba(255, 255, 255, 0.2);
|
|
166
|
+
border-color: rgba(255, 255, 255, 0.3);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.control-btn:disabled {
|
|
170
|
+
opacity: 0.5;
|
|
171
|
+
cursor: not-allowed;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.control-btn.active {
|
|
175
|
+
background: rgba(59, 130, 246, 0.6);
|
|
176
|
+
border-color: rgba(59, 130, 246, 0.8);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.time-display {
|
|
180
|
+
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
|
|
181
|
+
font-size: 13px;
|
|
182
|
+
font-weight: 500;
|
|
183
|
+
padding: 6px 12px;
|
|
184
|
+
background: rgba(0, 0, 0, 0.3);
|
|
185
|
+
border-radius: 6px;
|
|
186
|
+
letter-spacing: 0.5px;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.zoom-controls {
|
|
190
|
+
display: flex;
|
|
191
|
+
align-items: center;
|
|
192
|
+
gap: 8px;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.zoom-btn {
|
|
196
|
+
width: 32px;
|
|
197
|
+
height: 32px;
|
|
198
|
+
display: flex;
|
|
199
|
+
align-items: center;
|
|
200
|
+
justify-content: center;
|
|
201
|
+
background: rgba(255, 255, 255, 0.1);
|
|
202
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
203
|
+
border-radius: 6px;
|
|
204
|
+
color: inherit;
|
|
205
|
+
font-size: 18px;
|
|
206
|
+
font-weight: 600;
|
|
207
|
+
cursor: pointer;
|
|
208
|
+
transition: all 0.2s ease;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.zoom-btn:hover {
|
|
212
|
+
background: rgba(255, 255, 255, 0.2);
|
|
213
|
+
border-color: rgba(255, 255, 255, 0.3);
|
|
214
|
+
transform: scale(1.05);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.zoom-label {
|
|
218
|
+
font-size: 12px;
|
|
219
|
+
min-width: 48px;
|
|
220
|
+
text-align: center;
|
|
221
|
+
font-weight: 500;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/* === TIMELINE LAYOUT === */
|
|
225
|
+
.timeline-area {
|
|
226
|
+
flex: 1;
|
|
227
|
+
display: flex;
|
|
228
|
+
flex-direction: column;
|
|
229
|
+
position: relative;
|
|
230
|
+
overflow: hidden;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.ruler-row {
|
|
234
|
+
display: flex;
|
|
235
|
+
height: 24px;
|
|
236
|
+
background: var(--timeline-ruler-bg);
|
|
237
|
+
border-bottom: 1px solid var(--timeline-border);
|
|
238
|
+
flex-shrink: 0;
|
|
239
|
+
/* Sticky positioning for native scroll sync */
|
|
240
|
+
position: sticky;
|
|
241
|
+
top: 0;
|
|
242
|
+
z-index: 10;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.ruler-spacer {
|
|
246
|
+
width: var(--timeline-hierarchy-width);
|
|
247
|
+
flex-shrink: 0;
|
|
248
|
+
background: var(--timeline-header-bg);
|
|
249
|
+
border-right: 1px solid var(--timeline-border);
|
|
250
|
+
/* Sticky positioning to stay fixed during horizontal scroll */
|
|
251
|
+
position: sticky;
|
|
252
|
+
left: 0;
|
|
253
|
+
z-index: 11;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.ruler-content {
|
|
257
|
+
flex: 1;
|
|
258
|
+
position: relative;
|
|
259
|
+
overflow: hidden;
|
|
260
|
+
cursor: ew-resize;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.ruler-content ef-timeline-ruler {
|
|
264
|
+
width: 100%;
|
|
265
|
+
height: 100%;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.ruler-playhead-handle {
|
|
269
|
+
position: absolute;
|
|
270
|
+
bottom: -6px;
|
|
271
|
+
transform: translateX(-50%);
|
|
272
|
+
width: 12px;
|
|
273
|
+
height: 12px;
|
|
274
|
+
background: var(--timeline-playhead);
|
|
275
|
+
border-radius: 50%;
|
|
276
|
+
pointer-events: auto;
|
|
277
|
+
cursor: ew-resize;
|
|
278
|
+
z-index: 101;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/* Thumbnail strip row */
|
|
282
|
+
.thumbnail-row {
|
|
283
|
+
display: flex;
|
|
284
|
+
height: 48px;
|
|
285
|
+
background: var(--timeline-bg);
|
|
286
|
+
border-bottom: 1px solid var(--timeline-border);
|
|
287
|
+
flex-shrink: 0;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.thumbnail-spacer {
|
|
291
|
+
width: var(--timeline-hierarchy-width);
|
|
292
|
+
flex-shrink: 0;
|
|
293
|
+
background: var(--timeline-header-bg);
|
|
294
|
+
border-right: 1px solid var(--timeline-border);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.thumbnail-content {
|
|
298
|
+
flex: 1;
|
|
299
|
+
position: relative;
|
|
300
|
+
overflow: hidden;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.thumbnail-strip .thumbnail {
|
|
304
|
+
flex-shrink: 0;
|
|
305
|
+
border-radius: 2px;
|
|
306
|
+
overflow: hidden;
|
|
307
|
+
background: var(--timeline-track-bg);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.tracks-viewport {
|
|
311
|
+
flex: 1;
|
|
312
|
+
display: flex;
|
|
313
|
+
flex-direction: column;
|
|
314
|
+
overflow: hidden;
|
|
315
|
+
position: relative;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.tracks-scroll {
|
|
319
|
+
flex: 1;
|
|
320
|
+
overflow: auto;
|
|
321
|
+
position: relative;
|
|
322
|
+
background: var(--timeline-track-bg);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/* === TRACKS CONTENT uses grid to layer playhead over tracks === */
|
|
326
|
+
.tracks-content {
|
|
327
|
+
position: relative;
|
|
328
|
+
min-height: 100%;
|
|
329
|
+
display: grid;
|
|
330
|
+
grid-template-columns: 1fr;
|
|
331
|
+
grid-template-rows: 1fr;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.tracks-content > * {
|
|
335
|
+
grid-area: 1 / 1;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.tracks-rows-layer {
|
|
339
|
+
display: flex;
|
|
340
|
+
flex-direction: column;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/* === PLAYHEAD (sticky layer that stays visible during vertical scroll) === */
|
|
344
|
+
.playhead-layer {
|
|
345
|
+
position: sticky;
|
|
346
|
+
top: 0;
|
|
347
|
+
height: 100%;
|
|
348
|
+
pointer-events: none;
|
|
349
|
+
/* Below sticky labels (z-index 10-11) but above tracks */
|
|
350
|
+
z-index: 5;
|
|
351
|
+
overflow: hidden;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
.playhead {
|
|
355
|
+
position: absolute;
|
|
356
|
+
top: 0;
|
|
357
|
+
bottom: 0;
|
|
358
|
+
width: 2px;
|
|
359
|
+
background: var(--timeline-playhead);
|
|
360
|
+
pointer-events: none;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.playhead-drag-target {
|
|
364
|
+
position: absolute;
|
|
365
|
+
top: 0;
|
|
366
|
+
bottom: 0;
|
|
367
|
+
left: 50%;
|
|
368
|
+
transform: translateX(-50%);
|
|
369
|
+
width: 16px;
|
|
370
|
+
cursor: ew-resize;
|
|
371
|
+
pointer-events: auto;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/* === FRAME HIGHLIGHT === */
|
|
375
|
+
.frame-highlight {
|
|
376
|
+
position: absolute;
|
|
377
|
+
top: 0;
|
|
378
|
+
bottom: 0;
|
|
379
|
+
background: rgba(59, 130, 246, 0.3);
|
|
380
|
+
border-left: 2px solid rgba(59, 130, 246, 0.7);
|
|
381
|
+
pointer-events: none;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.empty-state {
|
|
385
|
+
display: flex;
|
|
386
|
+
align-items: center;
|
|
387
|
+
justify-content: center;
|
|
388
|
+
height: 100%;
|
|
389
|
+
color: rgba(148, 163, 184, 0.6);
|
|
390
|
+
font-style: italic;
|
|
391
|
+
}
|
|
392
|
+
`];
|
|
393
|
+
}
|
|
394
|
+
get hideSelectors() {
|
|
395
|
+
if (!this.hide) return void 0;
|
|
396
|
+
return this.hide.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
397
|
+
}
|
|
398
|
+
get showSelectors() {
|
|
399
|
+
if (!this.show) return void 0;
|
|
400
|
+
return this.show.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
401
|
+
}
|
|
402
|
+
static {
|
|
403
|
+
this.CONTEXT_UPDATE_INTERVAL_MS = 100;
|
|
404
|
+
}
|
|
405
|
+
#playbackSubscription = null;
|
|
406
|
+
#wheelHandler = null;
|
|
407
|
+
get providedPlaying() {
|
|
408
|
+
return this.isPlaying;
|
|
409
|
+
}
|
|
410
|
+
get providedLoop() {
|
|
411
|
+
return this.isLooping;
|
|
412
|
+
}
|
|
413
|
+
get providedCurrentTime() {
|
|
414
|
+
return this.currentTimeMs;
|
|
415
|
+
}
|
|
416
|
+
get providedDuration() {
|
|
417
|
+
return this.targetTemporal?.durationMs ?? 0;
|
|
418
|
+
}
|
|
419
|
+
get providedTargetTemporal() {
|
|
420
|
+
return this.targetTemporal;
|
|
421
|
+
}
|
|
422
|
+
/** Get timeline state (for external access) */
|
|
423
|
+
get timelineState() {
|
|
424
|
+
return this._timelineState;
|
|
425
|
+
}
|
|
426
|
+
/** Update timeline state when any constituent value changes */
|
|
427
|
+
updateTimelineState() {
|
|
428
|
+
const hierarchyWidth = this.showHierarchy ? _EFTimeline.HIERARCHY_WIDTH : 0;
|
|
429
|
+
const viewportWidth = this.cachedViewportWidth - hierarchyWidth;
|
|
430
|
+
const newState = {
|
|
431
|
+
pixelsPerMs: this.pixelsPerMs,
|
|
432
|
+
currentTimeMs: this.currentTimeMs,
|
|
433
|
+
durationMs: this.durationMs,
|
|
434
|
+
viewportScrollLeft: this.viewportScrollLeft,
|
|
435
|
+
viewportWidth,
|
|
436
|
+
seek: (ms) => this.handleSeek(ms),
|
|
437
|
+
zoomIn: () => this.handleZoomIn(),
|
|
438
|
+
zoomOut: () => this.handleZoomOut()
|
|
439
|
+
};
|
|
440
|
+
const pixelsPerMsChanged = this._timelineState.pixelsPerMs !== newState.pixelsPerMs;
|
|
441
|
+
const currentTimeMsChanged = this._timelineState.currentTimeMs !== newState.currentTimeMs;
|
|
442
|
+
const durationMsChanged = this._timelineState.durationMs !== newState.durationMs;
|
|
443
|
+
const viewportScrollLeftChanged = this._timelineState.viewportScrollLeft !== newState.viewportScrollLeft;
|
|
444
|
+
const viewportWidthChanged = this._timelineState.viewportWidth !== newState.viewportWidth;
|
|
445
|
+
const shouldSkipScrollUpdate = viewportScrollLeftChanged && !pixelsPerMsChanged && !currentTimeMsChanged && !durationMsChanged && !viewportWidthChanged && this.isPlaying;
|
|
446
|
+
if (pixelsPerMsChanged || currentTimeMsChanged || durationMsChanged || viewportWidthChanged || viewportScrollLeftChanged && !shouldSkipScrollUpdate) {
|
|
447
|
+
this._timelineState = newState;
|
|
448
|
+
this.requestUpdate();
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Get the target canvas element.
|
|
453
|
+
* The canvas is the source of truth for selection and highlight state.
|
|
454
|
+
*/
|
|
455
|
+
getCanvas() {
|
|
456
|
+
if (this.targetElement && this.targetElement.selectionContext) return this.targetElement;
|
|
457
|
+
if (this.target) {
|
|
458
|
+
const target = document.getElementById(this.target);
|
|
459
|
+
if (target && target.selectionContext) return target;
|
|
460
|
+
}
|
|
461
|
+
return document.querySelector("ef-canvas");
|
|
462
|
+
}
|
|
463
|
+
getCanvasSelectionContext() {
|
|
464
|
+
if (this.selectionContext) return this.selectionContext;
|
|
465
|
+
return this.getCanvas()?.selectionContext;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Get the currently highlighted element from the canvas.
|
|
469
|
+
*/
|
|
470
|
+
getHighlightedElement() {
|
|
471
|
+
return this.getCanvas()?.highlightedElement ?? null;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Set the highlighted element on the canvas.
|
|
475
|
+
* Called when user hovers a row in the timeline.
|
|
476
|
+
*/
|
|
477
|
+
setHighlightedElement(element) {
|
|
478
|
+
this.getCanvas()?.setHighlightedElement(element);
|
|
479
|
+
}
|
|
480
|
+
get targetTemporal() {
|
|
481
|
+
if (this.controlTarget && this.controlTarget !== "" && this.controlTarget !== "selection") {
|
|
482
|
+
const element = document.getElementById(this.controlTarget);
|
|
483
|
+
if (element && isEFTemporal(element)) return element;
|
|
484
|
+
if (this.isPlaying) console.warn("[EFTimeline] controlTarget set but element not found:", this.controlTarget, "isPlaying:", this.isPlaying);
|
|
485
|
+
}
|
|
486
|
+
const selectionCtx = this.getCanvasSelectionContext();
|
|
487
|
+
if (selectionCtx) {
|
|
488
|
+
const selectedIds = Array.from(selectionCtx.selectedIds);
|
|
489
|
+
if (selectedIds.length > 0 && selectedIds[0]) {
|
|
490
|
+
const element = document.getElementById(selectedIds[0]);
|
|
491
|
+
if (element) {
|
|
492
|
+
const rootTemporal = findRootTemporal(element);
|
|
493
|
+
if (rootTemporal) return rootTemporal;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
if (this.isPlaying && selectedIds.length > 0) console.warn("[EFTimeline] Selection found but no temporal element:", selectedIds[0], "isPlaying:", this.isPlaying);
|
|
497
|
+
}
|
|
498
|
+
if (this.isPlaying) console.warn("[EFTimeline] targetTemporal is null during playback. controlTarget:", this.controlTarget, "target:", this.target, "hasSelectionContext:", !!this.getCanvasSelectionContext());
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
get durationMs() {
|
|
502
|
+
return this.targetTemporal?.durationMs ?? 0;
|
|
503
|
+
}
|
|
504
|
+
/** Content width in pixels (derived from duration and pixelsPerMs) */
|
|
505
|
+
get contentWidthPx() {
|
|
506
|
+
return timeToPx(this.durationMs, this.pixelsPerMs);
|
|
507
|
+
}
|
|
508
|
+
/** Current zoom as percentage (for display) */
|
|
509
|
+
get zoomPercent() {
|
|
510
|
+
return Math.round(pixelsPerMsToZoom(this.pixelsPerMs) * 100);
|
|
511
|
+
}
|
|
512
|
+
/** Derive fps from target temporal (defaults to 30) */
|
|
513
|
+
get fps() {
|
|
514
|
+
const target = this.targetTemporal;
|
|
515
|
+
if (target && "fps" in target) return target.fps ?? 30;
|
|
516
|
+
return 30;
|
|
517
|
+
}
|
|
518
|
+
/** Whether frame markers should be visible at current zoom */
|
|
519
|
+
get showFrameMarkers() {
|
|
520
|
+
return shouldShowFrameMarkers(calculatePixelsPerFrame(calculateFrameIntervalMs(this.fps), this.pixelsPerMs));
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Get the root timegroup ID for localStorage key generation.
|
|
524
|
+
* Returns null if no root timegroup is found or it has no ID.
|
|
525
|
+
*/
|
|
526
|
+
getRootTimegroupId() {
|
|
527
|
+
if (!this.targetTemporal) return null;
|
|
528
|
+
const rootTemporal = findRootTemporal(this.targetTemporal);
|
|
529
|
+
if (rootTemporal instanceof EFTimegroup && rootTemporal.id) return rootTemporal.id;
|
|
530
|
+
return null;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Get localStorage key for timeline state (zoom and scroll).
|
|
534
|
+
*/
|
|
535
|
+
getTimelineStorageKey() {
|
|
536
|
+
const rootId = this.getRootTimegroupId();
|
|
537
|
+
return rootId ? `ef-timeline-${rootId}` : null;
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Save timeline zoom and scroll to localStorage.
|
|
541
|
+
*/
|
|
542
|
+
saveTimelineState() {
|
|
543
|
+
const storageKey = this.getTimelineStorageKey();
|
|
544
|
+
if (!storageKey) return;
|
|
545
|
+
try {
|
|
546
|
+
const state$1 = {
|
|
547
|
+
pixelsPerMs: this.pixelsPerMs,
|
|
548
|
+
viewportScrollLeft: this.viewportScrollLeft
|
|
549
|
+
};
|
|
550
|
+
localStorage.setItem(storageKey, JSON.stringify(state$1));
|
|
551
|
+
} catch (error) {
|
|
552
|
+
console.warn("Failed to save timeline state to localStorage", error);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Restore timeline zoom and scroll from localStorage.
|
|
557
|
+
*/
|
|
558
|
+
restoreTimelineState() {
|
|
559
|
+
const storageKey = this.getTimelineStorageKey();
|
|
560
|
+
if (!storageKey) return;
|
|
561
|
+
try {
|
|
562
|
+
const stored = localStorage.getItem(storageKey);
|
|
563
|
+
if (!stored) return;
|
|
564
|
+
const state$1 = JSON.parse(stored);
|
|
565
|
+
if (typeof state$1.pixelsPerMs === "number" && state$1.pixelsPerMs > 0) this.pixelsPerMs = Math.max(this.minZoom * DEFAULT_PIXELS_PER_MS, Math.min(this.maxZoom * DEFAULT_PIXELS_PER_MS, state$1.pixelsPerMs));
|
|
566
|
+
if (typeof state$1.viewportScrollLeft === "number" && state$1.viewportScrollLeft >= 0) requestAnimationFrame(() => {
|
|
567
|
+
const tracksScroll = this.tracksScrollRef.value;
|
|
568
|
+
if (tracksScroll) {
|
|
569
|
+
tracksScroll.scrollLeft = state$1.viewportScrollLeft;
|
|
570
|
+
this.viewportScrollLeft = state$1.viewportScrollLeft;
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
} catch (error) {
|
|
574
|
+
console.warn("Failed to restore timeline state from localStorage", error);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Debounced save of timeline state to avoid excessive localStorage writes.
|
|
579
|
+
*/
|
|
580
|
+
debouncedSaveTimelineState() {
|
|
581
|
+
if (this.saveZoomScrollDebounceTimer !== null) clearTimeout(this.saveZoomScrollDebounceTimer);
|
|
582
|
+
this.saveZoomScrollDebounceTimer = window.setTimeout(() => {
|
|
583
|
+
this.saveZoomScrollDebounceTimer = null;
|
|
584
|
+
this.saveTimelineState();
|
|
585
|
+
}, 200);
|
|
586
|
+
}
|
|
587
|
+
connectedCallback() {
|
|
588
|
+
super.connectedCallback();
|
|
589
|
+
this.startTimeUpdate();
|
|
590
|
+
this.setupSelectionListener();
|
|
591
|
+
this.setupKeyboardListener();
|
|
592
|
+
this.updateTimelineState();
|
|
593
|
+
this.subscribeToPlaybackController();
|
|
594
|
+
}
|
|
595
|
+
disconnectedCallback() {
|
|
596
|
+
super.disconnectedCallback();
|
|
597
|
+
this.stopTimeUpdate();
|
|
598
|
+
this.removeSelectionListener();
|
|
599
|
+
this.removeScrollListener();
|
|
600
|
+
this.removeKeyboardListener();
|
|
601
|
+
this.targetObserver?.disconnect();
|
|
602
|
+
this.resizeObserver?.disconnect();
|
|
603
|
+
this.unsubscribeFromPlaybackController();
|
|
604
|
+
if (this.saveZoomScrollDebounceTimer !== null) {
|
|
605
|
+
clearTimeout(this.saveZoomScrollDebounceTimer);
|
|
606
|
+
this.saveZoomScrollDebounceTimer = null;
|
|
607
|
+
}
|
|
608
|
+
this.saveTimelineState();
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Setup MutationObserver to watch target element for ANY changes.
|
|
612
|
+
* Re-registers when target changes.
|
|
613
|
+
*/
|
|
614
|
+
setupTargetObserver() {
|
|
615
|
+
this.targetObserver?.disconnect();
|
|
616
|
+
const target = this.targetTemporal;
|
|
617
|
+
if (target && target instanceof Element) {
|
|
618
|
+
this.targetObserver = new MutationObserver(() => this.requestUpdate());
|
|
619
|
+
this.targetObserver.observe(target, {
|
|
620
|
+
childList: true,
|
|
621
|
+
subtree: true,
|
|
622
|
+
attributes: true
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
willUpdate(changedProperties) {
|
|
627
|
+
if (changedProperties.has("target") && this.target && !this.targetController) this.targetController = new TargetController(this);
|
|
628
|
+
this.setupSelectionListener();
|
|
629
|
+
this.updateTimelineState();
|
|
630
|
+
super.willUpdate(changedProperties);
|
|
631
|
+
}
|
|
632
|
+
firstUpdated() {
|
|
633
|
+
this.setupResizeObserver();
|
|
634
|
+
}
|
|
635
|
+
setupResizeObserver() {
|
|
636
|
+
const tracksScroll = this.tracksScrollRef.value;
|
|
637
|
+
if (!tracksScroll) return;
|
|
638
|
+
if (this.resizeObserver) this.resizeObserver.disconnect();
|
|
639
|
+
const initialWidth = tracksScroll.clientWidth;
|
|
640
|
+
if (initialWidth > 0) this.cachedViewportWidth = initialWidth;
|
|
641
|
+
this.resizeObserver = new ResizeObserver((entries) => {
|
|
642
|
+
for (const entry of entries) {
|
|
643
|
+
const width = entry.contentRect.width;
|
|
644
|
+
if (width > 0) {
|
|
645
|
+
this.cachedViewportWidth = width;
|
|
646
|
+
this.updateTimelineState();
|
|
647
|
+
this.requestUpdate();
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
this.resizeObserver.observe(tracksScroll);
|
|
652
|
+
}
|
|
653
|
+
updated(changedProperties) {
|
|
654
|
+
super.updated(changedProperties);
|
|
655
|
+
if (changedProperties.has("targetTemporal") || changedProperties.has("controlTarget")) this.subscribeToPlaybackController();
|
|
656
|
+
if (changedProperties.has("targetTemporal") || changedProperties.has("target")) requestAnimationFrame(() => {
|
|
657
|
+
this.restoreTimelineState();
|
|
658
|
+
});
|
|
659
|
+
if (changedProperties.has("pixelsPerMs") || changedProperties.has("viewportScrollLeft")) this.debouncedSaveTimelineState();
|
|
660
|
+
if (changedProperties.has("targetElement") || changedProperties.has("target")) {
|
|
661
|
+
this.setupTargetObserver();
|
|
662
|
+
this.removeSelectionListener();
|
|
663
|
+
this.selectionChangeHandler = void 0;
|
|
664
|
+
this.setupSelectionListener();
|
|
665
|
+
}
|
|
666
|
+
if (this.tracksScrollRef.value && !this.scrollHandler) this.setupScrollListener();
|
|
667
|
+
else if (!this.tracksScrollRef.value && this.scrollHandler) this.removeScrollListener();
|
|
668
|
+
if (this.tracksScrollRef.value && !this.resizeObserver) {
|
|
669
|
+
this.setupResizeObserver();
|
|
670
|
+
this.updateTimelineState();
|
|
671
|
+
} else if (!this.tracksScrollRef.value && this.resizeObserver) {
|
|
672
|
+
this.resizeObserver.disconnect();
|
|
673
|
+
this.resizeObserver = void 0;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
setupSelectionListener() {
|
|
677
|
+
if (this.selectionChangeHandler) return;
|
|
678
|
+
const selectionCtx = this.getCanvasSelectionContext();
|
|
679
|
+
if (selectionCtx && "addEventListener" in selectionCtx) {
|
|
680
|
+
this.selectionChangeHandler = () => this.requestUpdate();
|
|
681
|
+
selectionCtx.addEventListener("selectionchange", this.selectionChangeHandler);
|
|
682
|
+
this.requestUpdate();
|
|
683
|
+
}
|
|
684
|
+
const canvas = this.getCanvas();
|
|
685
|
+
if (canvas && !this.canvasActiveRootTemporalChangeHandler) {
|
|
686
|
+
this.canvasActiveRootTemporalChangeHandler = () => {
|
|
687
|
+
this.requestUpdate();
|
|
688
|
+
};
|
|
689
|
+
canvas.addEventListener("activeroottemporalchange", this.canvasActiveRootTemporalChangeHandler);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
removeSelectionListener() {
|
|
693
|
+
const selectionCtx = this.getCanvasSelectionContext();
|
|
694
|
+
if (selectionCtx && "removeEventListener" in selectionCtx && this.selectionChangeHandler) {
|
|
695
|
+
selectionCtx.removeEventListener("selectionchange", this.selectionChangeHandler);
|
|
696
|
+
this.selectionChangeHandler = void 0;
|
|
697
|
+
}
|
|
698
|
+
const canvas = document.querySelector("ef-canvas");
|
|
699
|
+
if (canvas && this.canvasActiveRootTemporalChangeHandler) {
|
|
700
|
+
canvas.removeEventListener("activeroottemporalchange", this.canvasActiveRootTemporalChangeHandler);
|
|
701
|
+
this.canvasActiveRootTemporalChangeHandler = void 0;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
setupScrollListener() {
|
|
705
|
+
if (this.tracksScrollRef.value) {
|
|
706
|
+
this.scrollHandler = () => {
|
|
707
|
+
if (this.tracksScrollRef.value) {
|
|
708
|
+
const newScrollLeft = this.tracksScrollRef.value.scrollLeft;
|
|
709
|
+
if (newScrollLeft !== this.viewportScrollLeft) {
|
|
710
|
+
this.viewportScrollLeft = newScrollLeft;
|
|
711
|
+
this.updateTimelineState();
|
|
712
|
+
this.debouncedSaveTimelineState();
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
this.tracksScrollRef.value.addEventListener("scroll", this.scrollHandler, { passive: true });
|
|
717
|
+
this.scrollHandler();
|
|
718
|
+
this.#wheelHandler = (e) => this.handleWheel(e);
|
|
719
|
+
this.tracksScrollRef.value.addEventListener("wheel", this.#wheelHandler, { passive: false });
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
removeScrollListener() {
|
|
723
|
+
if (this.tracksScrollRef.value) {
|
|
724
|
+
if (this.scrollHandler) this.tracksScrollRef.value.removeEventListener("scroll", this.scrollHandler);
|
|
725
|
+
if (this.#wheelHandler) {
|
|
726
|
+
this.tracksScrollRef.value.removeEventListener("wheel", this.#wheelHandler);
|
|
727
|
+
this.#wheelHandler = null;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
setupKeyboardListener() {
|
|
732
|
+
this.keydownHandler = (e) => this.handleKeyDown(e);
|
|
733
|
+
this.addEventListener("keydown", this.keydownHandler);
|
|
734
|
+
}
|
|
735
|
+
removeKeyboardListener() {
|
|
736
|
+
if (this.keydownHandler) this.removeEventListener("keydown", this.keydownHandler);
|
|
737
|
+
}
|
|
738
|
+
static {
|
|
739
|
+
this.PLAYHEAD_MARGIN = 100;
|
|
740
|
+
}
|
|
741
|
+
startTimeUpdate() {
|
|
742
|
+
const update = () => {
|
|
743
|
+
if (this.targetTemporal) {
|
|
744
|
+
if (!this.targetTemporal.captureInProgress) {
|
|
745
|
+
const rawTime = this.targetTemporal.currentTimeMs ?? 0;
|
|
746
|
+
const duration = this.targetTemporal.durationMs ?? 0;
|
|
747
|
+
const newTimeMs = Math.max(0, Math.min(rawTime, duration));
|
|
748
|
+
this.updatePlayheadPositionDirect(newTimeMs);
|
|
749
|
+
const newIsPlaying = this.targetTemporal.playing ?? false;
|
|
750
|
+
const newIsLooping = this.targetTemporal.loop ?? false;
|
|
751
|
+
if (newIsPlaying !== this.isPlaying || newIsLooping !== this.isLooping) {
|
|
752
|
+
if (!this.#playbackSubscription) {
|
|
753
|
+
const wasPlaying = this.isPlaying;
|
|
754
|
+
this.isPlaying = newIsPlaying;
|
|
755
|
+
this.isLooping = newIsLooping;
|
|
756
|
+
if (wasPlaying && !newIsPlaying) this.updateTimelineState();
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
const now = performance.now();
|
|
760
|
+
if ((!newIsPlaying || now - this.lastContextUpdateTime >= _EFTimeline.CONTEXT_UPDATE_INTERVAL_MS) && this.currentTimeMs !== newTimeMs) {
|
|
761
|
+
this.currentTimeMs = newTimeMs;
|
|
762
|
+
this.lastContextUpdateTime = now;
|
|
763
|
+
}
|
|
764
|
+
if (this.isPlaying && !this.isDraggingPlayhead) this.followPlayhead(newTimeMs);
|
|
765
|
+
else this.isFollowingPlayhead = false;
|
|
766
|
+
}
|
|
767
|
+
} else if (this.isPlaying) {
|
|
768
|
+
console.warn("[EFTimeline] Lost targetTemporal during playback. Stopping playback state.", "controlTarget:", this.controlTarget, "target:", this.target);
|
|
769
|
+
this.isPlaying = false;
|
|
770
|
+
this.isLooping = false;
|
|
771
|
+
this.updateTimelineState();
|
|
772
|
+
}
|
|
773
|
+
this.animationFrameId = requestAnimationFrame(update);
|
|
774
|
+
};
|
|
775
|
+
update();
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Update playhead position directly via DOM manipulation.
|
|
779
|
+
* This bypasses the Lit render cycle for smooth 60fps playhead movement.
|
|
780
|
+
*/
|
|
781
|
+
updatePlayheadPositionDirect(timeMs) {
|
|
782
|
+
const hierarchyWidth = this.showHierarchy ? _EFTimeline.HIERARCHY_WIDTH : 0;
|
|
783
|
+
const playheadPx = timeToPx(timeMs, this.pixelsPerMs);
|
|
784
|
+
const playheadLeft = hierarchyWidth + playheadPx;
|
|
785
|
+
const playhead = this.playheadRef.value;
|
|
786
|
+
if (playhead) playhead.style.left = `${playheadLeft - 1}px`;
|
|
787
|
+
const handle = this.playheadHandleRef.value;
|
|
788
|
+
if (handle) handle.style.left = `${playheadPx}px`;
|
|
789
|
+
if (this.showFrameMarkers && this.durationMs > 0) {
|
|
790
|
+
const frameHighlight = this.frameHighlightRef.value;
|
|
791
|
+
if (frameHighlight) {
|
|
792
|
+
const fps = this.fps;
|
|
793
|
+
const frameDurationMs = 1e3 / fps;
|
|
794
|
+
const frameStartMs = quantizeToFrameTimeMs(timeMs, fps);
|
|
795
|
+
const frameEndMs = Math.min(frameStartMs + frameDurationMs, this.durationMs);
|
|
796
|
+
const startPx = timeToPx(frameStartMs, this.pixelsPerMs);
|
|
797
|
+
const widthPx = timeToPx(frameEndMs, this.pixelsPerMs) - startPx;
|
|
798
|
+
if (widthPx > 0 && startPx >= 0) {
|
|
799
|
+
frameHighlight.style.left = `${hierarchyWidth + startPx}px`;
|
|
800
|
+
frameHighlight.style.width = `${widthPx}px`;
|
|
801
|
+
frameHighlight.style.display = "block";
|
|
802
|
+
} else frameHighlight.style.display = "none";
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Smooth playhead following - scrolls to keep playhead at a fixed screen position.
|
|
808
|
+
* This eliminates jitter by scrolling exactly as much as the playhead moves.
|
|
809
|
+
*
|
|
810
|
+
* PERFORMANCE NOTE: We DO update viewportScrollLeft state here, but the context
|
|
811
|
+
* cascade is prevented in updateTimelineState() during playback. This means:
|
|
812
|
+
* - State stays in sync (for non-context consumers)
|
|
813
|
+
* - But context consumers don't re-render during auto-scroll
|
|
814
|
+
* - Components inside the scroll container scroll natively
|
|
815
|
+
*/
|
|
816
|
+
followPlayhead(currentTimeMs) {
|
|
817
|
+
const tracksScroll = this.tracksScrollRef.value;
|
|
818
|
+
if (!tracksScroll) return;
|
|
819
|
+
const hierarchyWidth = this.showHierarchy ? _EFTimeline.HIERARCHY_WIDTH : 0;
|
|
820
|
+
const playheadPx = timeToPx(currentTimeMs, this.pixelsPerMs);
|
|
821
|
+
const viewportWidth = tracksScroll.clientWidth - hierarchyWidth;
|
|
822
|
+
const maxScroll = tracksScroll.scrollWidth - tracksScroll.clientWidth;
|
|
823
|
+
const playheadInViewport = playheadPx - tracksScroll.scrollLeft;
|
|
824
|
+
const rightThreshold = viewportWidth - _EFTimeline.PLAYHEAD_MARGIN;
|
|
825
|
+
const leftThreshold = _EFTimeline.PLAYHEAD_MARGIN;
|
|
826
|
+
let newScrollLeft = tracksScroll.scrollLeft;
|
|
827
|
+
let scrollChanged = false;
|
|
828
|
+
if (this.isFollowingPlayhead) {
|
|
829
|
+
const delta = playheadPx - this.lastPlayheadPx;
|
|
830
|
+
if (delta !== 0) {
|
|
831
|
+
newScrollLeft = Math.max(0, Math.min(maxScroll, tracksScroll.scrollLeft + delta));
|
|
832
|
+
tracksScroll.scrollLeft = newScrollLeft;
|
|
833
|
+
scrollChanged = true;
|
|
834
|
+
}
|
|
835
|
+
} else if (playheadInViewport > rightThreshold) this.isFollowingPlayhead = true;
|
|
836
|
+
else if (playheadInViewport < leftThreshold && tracksScroll.scrollLeft > 0) {
|
|
837
|
+
newScrollLeft = Math.max(0, playheadPx - leftThreshold);
|
|
838
|
+
tracksScroll.scrollLeft = newScrollLeft;
|
|
839
|
+
scrollChanged = true;
|
|
840
|
+
}
|
|
841
|
+
if (scrollChanged && this.viewportScrollLeft !== newScrollLeft) this.viewportScrollLeft = newScrollLeft;
|
|
842
|
+
this.lastPlayheadPx = playheadPx;
|
|
843
|
+
}
|
|
844
|
+
stopTimeUpdate() {
|
|
845
|
+
if (this.animationFrameId) cancelAnimationFrame(this.animationFrameId);
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Subscribe to playback controller events for playing/loop state.
|
|
849
|
+
* This avoids race conditions from polling when targetTemporal changes.
|
|
850
|
+
*/
|
|
851
|
+
subscribeToPlaybackController() {
|
|
852
|
+
this.unsubscribeFromPlaybackController();
|
|
853
|
+
const temporal = this.targetTemporal;
|
|
854
|
+
if (!temporal) return;
|
|
855
|
+
if (!temporal.playbackController) {
|
|
856
|
+
temporal.updateComplete?.then(() => {
|
|
857
|
+
if (temporal === this.targetTemporal && temporal.playbackController) this.#playbackSubscription = createDirectTemporalSubscription(temporal, {
|
|
858
|
+
onPlayingChange: (value) => {
|
|
859
|
+
if (temporal === this.targetTemporal) {
|
|
860
|
+
const wasPlaying = this.isPlaying;
|
|
861
|
+
this.isPlaying = value;
|
|
862
|
+
if (wasPlaying && !value) this.updateTimelineState();
|
|
863
|
+
}
|
|
864
|
+
},
|
|
865
|
+
onLoopChange: (value) => {
|
|
866
|
+
if (temporal === this.targetTemporal) this.isLooping = value;
|
|
867
|
+
},
|
|
868
|
+
onCurrentTimeMsChange: () => {},
|
|
869
|
+
onDurationMsChange: () => {},
|
|
870
|
+
onTargetTemporalChange: () => {}
|
|
871
|
+
});
|
|
872
|
+
});
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
this.#playbackSubscription = createDirectTemporalSubscription(temporal, {
|
|
876
|
+
onPlayingChange: (value) => {
|
|
877
|
+
if (temporal === this.targetTemporal) {
|
|
878
|
+
const wasPlaying = this.isPlaying;
|
|
879
|
+
this.isPlaying = value;
|
|
880
|
+
if (wasPlaying && !value) this.updateTimelineState();
|
|
881
|
+
}
|
|
882
|
+
},
|
|
883
|
+
onLoopChange: (value) => {
|
|
884
|
+
if (temporal === this.targetTemporal) this.isLooping = value;
|
|
885
|
+
},
|
|
886
|
+
onCurrentTimeMsChange: () => {},
|
|
887
|
+
onDurationMsChange: () => {},
|
|
888
|
+
onTargetTemporalChange: () => {}
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Unsubscribe from playback controller events.
|
|
893
|
+
*/
|
|
894
|
+
unsubscribeFromPlaybackController() {
|
|
895
|
+
if (this.#playbackSubscription) {
|
|
896
|
+
this.#playbackSubscription.unsubscribe();
|
|
897
|
+
this.#playbackSubscription = null;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
handlePlay() {
|
|
901
|
+
if (!this.targetTemporal) {
|
|
902
|
+
console.warn("[EFTimeline] Cannot play: targetTemporal is null. controlTarget:", this.controlTarget, "target:", this.target);
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
this.targetTemporal.play();
|
|
906
|
+
}
|
|
907
|
+
handlePause() {
|
|
908
|
+
if (!this.targetTemporal) {
|
|
909
|
+
console.warn("[EFTimeline] Cannot pause: targetTemporal is null. controlTarget:", this.controlTarget, "target:", this.target);
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
this.targetTemporal.pause();
|
|
913
|
+
}
|
|
914
|
+
handleToggleLoop() {
|
|
915
|
+
if (!this.targetTemporal) {
|
|
916
|
+
console.warn("[EFTimeline] Cannot toggle loop: targetTemporal is null. controlTarget:", this.controlTarget, "target:", this.target);
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
this.targetTemporal.loop = !this.targetTemporal.loop;
|
|
920
|
+
this.isLooping = this.targetTemporal.loop;
|
|
921
|
+
}
|
|
922
|
+
handleZoomIn() {
|
|
923
|
+
const currentZoom = pixelsPerMsToZoom(this.pixelsPerMs);
|
|
924
|
+
this.pixelsPerMs = Math.min(this.maxZoom, currentZoom * 1.25) * DEFAULT_PIXELS_PER_MS;
|
|
925
|
+
}
|
|
926
|
+
handleZoomOut() {
|
|
927
|
+
const currentZoom = pixelsPerMsToZoom(this.pixelsPerMs);
|
|
928
|
+
this.pixelsPerMs = Math.max(this.minZoom, currentZoom / 1.25) * DEFAULT_PIXELS_PER_MS;
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Handle wheel events for gestural zoom.
|
|
932
|
+
* Cmd/Ctrl + wheel zooms toward the cursor position.
|
|
933
|
+
* Without modifier, native scroll behavior is preserved.
|
|
934
|
+
*/
|
|
935
|
+
handleWheel(e) {
|
|
936
|
+
if (!(e.metaKey || e.ctrlKey)) return;
|
|
937
|
+
e.preventDefault();
|
|
938
|
+
e.stopPropagation();
|
|
939
|
+
const tracksScroll = this.tracksScrollRef.value;
|
|
940
|
+
if (!tracksScroll) return;
|
|
941
|
+
const rect = tracksScroll.getBoundingClientRect();
|
|
942
|
+
const hierarchyWidth = this.showHierarchy ? _EFTimeline.HIERARCHY_WIDTH : 0;
|
|
943
|
+
const cursorXInViewport = e.clientX - rect.left - hierarchyWidth;
|
|
944
|
+
if (cursorXInViewport < 0) return;
|
|
945
|
+
const timeAtCursor = pxToTime(cursorXInViewport + tracksScroll.scrollLeft, this.pixelsPerMs);
|
|
946
|
+
const zoomFactor = e.deltaY > 0 ? .95 : 1.05;
|
|
947
|
+
const currentZoom = pixelsPerMsToZoom(this.pixelsPerMs);
|
|
948
|
+
const newPixelsPerMs = Math.max(this.minZoom, Math.min(this.maxZoom, currentZoom * zoomFactor)) * DEFAULT_PIXELS_PER_MS;
|
|
949
|
+
const newScrollLeft = timeToPx(timeAtCursor, newPixelsPerMs) - cursorXInViewport;
|
|
950
|
+
this.pixelsPerMs = newPixelsPerMs;
|
|
951
|
+
const maxScroll = Math.max(0, timeToPx(this.durationMs, newPixelsPerMs) - (rect.width - hierarchyWidth));
|
|
952
|
+
tracksScroll.scrollLeft = Math.max(0, Math.min(maxScroll, newScrollLeft));
|
|
953
|
+
this.viewportScrollLeft = tracksScroll.scrollLeft;
|
|
954
|
+
this.updateTimelineState();
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Seek to a specific time, optionally quantizing to frame boundaries.
|
|
958
|
+
* @param timeMs The raw time to seek to
|
|
959
|
+
* @param snapToFrame Whether to quantize to the nearest frame boundary (default: true when frame markers visible)
|
|
960
|
+
*/
|
|
961
|
+
handleSeek(timeMs, snapToFrame = this.showFrameMarkers) {
|
|
962
|
+
if (!this.targetTemporal) {
|
|
963
|
+
console.warn("[EFTimeline] Cannot seek: targetTemporal is null. controlTarget:", this.controlTarget, "target:", this.target);
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
let seekTime = timeMs;
|
|
967
|
+
if (snapToFrame && this.fps > 0) seekTime = quantizeToFrameTimeMs(seekTime, this.fps);
|
|
968
|
+
const clampedTime = Math.max(0, Math.min(seekTime, this.durationMs));
|
|
969
|
+
this.targetTemporal.currentTimeMs = clampedTime;
|
|
970
|
+
this.currentTimeMs = clampedTime;
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Handle keyboard navigation for frame-by-frame or second-by-second movement.
|
|
974
|
+
* - Arrow Left/Right: move by one frame
|
|
975
|
+
* - Shift+Arrow Left/Right: move by one second
|
|
976
|
+
*/
|
|
977
|
+
handleKeyDown(e) {
|
|
978
|
+
if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return;
|
|
979
|
+
const activeElement = document.activeElement;
|
|
980
|
+
if (activeElement?.tagName === "INPUT" || activeElement?.tagName === "TEXTAREA" || activeElement?.isContentEditable) return;
|
|
981
|
+
e.preventDefault();
|
|
982
|
+
const isShiftPressed = e.shiftKey;
|
|
983
|
+
const isRightArrow = e.key === "ArrowRight";
|
|
984
|
+
const fps = this.fps;
|
|
985
|
+
const durationMs = this.durationMs;
|
|
986
|
+
let newTime;
|
|
987
|
+
if (isShiftPressed) {
|
|
988
|
+
const deltaMs = isRightArrow ? 1e3 : -1e3;
|
|
989
|
+
newTime = this.currentTimeMs + deltaMs;
|
|
990
|
+
} else {
|
|
991
|
+
const frameIntervalMs = fps > 0 ? 1e3 / fps : 1e3 / 30;
|
|
992
|
+
const deltaMs = isRightArrow ? frameIntervalMs : -frameIntervalMs;
|
|
993
|
+
newTime = this.currentTimeMs + deltaMs;
|
|
994
|
+
newTime = quantizeToFrameTimeMs(newTime, fps);
|
|
995
|
+
}
|
|
996
|
+
newTime = Math.max(0, Math.min(newTime, durationMs));
|
|
997
|
+
this.handleSeek(newTime);
|
|
998
|
+
}
|
|
999
|
+
handleRulerPointerDown(e) {
|
|
1000
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
1001
|
+
const timeMs = pxToTime(e.clientX - rect.left, this.pixelsPerMs);
|
|
1002
|
+
this.handleSeek(timeMs);
|
|
1003
|
+
this.startPlayheadDrag(e);
|
|
1004
|
+
e.preventDefault();
|
|
1005
|
+
}
|
|
1006
|
+
handlePlayheadPointerDown(e) {
|
|
1007
|
+
e.stopPropagation();
|
|
1008
|
+
this.startPlayheadDrag(e);
|
|
1009
|
+
}
|
|
1010
|
+
static {
|
|
1011
|
+
this.HIERARCHY_WIDTH = 200;
|
|
1012
|
+
}
|
|
1013
|
+
handleTracksPointerDown(e) {
|
|
1014
|
+
if (e.target === e.currentTarget || e.target.classList.contains("tracks-content")) {
|
|
1015
|
+
const target = e.currentTarget;
|
|
1016
|
+
const rect = target.getBoundingClientRect();
|
|
1017
|
+
const scrollLeft = target.scrollLeft;
|
|
1018
|
+
const hierarchyWidth = this.showHierarchy ? _EFTimeline.HIERARCHY_WIDTH : 0;
|
|
1019
|
+
const x = e.clientX - rect.left + scrollLeft - hierarchyWidth;
|
|
1020
|
+
if (x >= 0) {
|
|
1021
|
+
const timeMs = pxToTime(x, this.pixelsPerMs);
|
|
1022
|
+
this.handleSeek(timeMs);
|
|
1023
|
+
this.startPlayheadDrag(e);
|
|
1024
|
+
}
|
|
1025
|
+
e.preventDefault();
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
static {
|
|
1029
|
+
this.EDGE_SCROLL_ZONE = 50;
|
|
1030
|
+
}
|
|
1031
|
+
static {
|
|
1032
|
+
this.EDGE_SCROLL_SPEED = 8;
|
|
1033
|
+
}
|
|
1034
|
+
startPlayheadDrag(e) {
|
|
1035
|
+
this.isDraggingPlayhead = true;
|
|
1036
|
+
const tracksScroll = this.tracksScrollRef.value;
|
|
1037
|
+
if (tracksScroll) this.viewportScrollLeft = tracksScroll.scrollLeft;
|
|
1038
|
+
const hierarchyWidth = this.showHierarchy ? _EFTimeline.HIERARCHY_WIDTH : 0;
|
|
1039
|
+
let lastClientX = e.clientX;
|
|
1040
|
+
let edgeScrollAnimationId = null;
|
|
1041
|
+
const updatePlayheadFromMouse = () => {
|
|
1042
|
+
const tracksScroll$1 = this.tracksScrollRef.value;
|
|
1043
|
+
if (!tracksScroll$1) return;
|
|
1044
|
+
const rect = tracksScroll$1.getBoundingClientRect();
|
|
1045
|
+
const scrollLeft = tracksScroll$1.scrollLeft;
|
|
1046
|
+
const x = lastClientX - rect.left + scrollLeft - hierarchyWidth;
|
|
1047
|
+
const timeMs = pxToTime(Math.max(0, x), this.pixelsPerMs);
|
|
1048
|
+
this.handleSeek(Math.min(timeMs, this.durationMs));
|
|
1049
|
+
};
|
|
1050
|
+
const edgeScrollLoop = () => {
|
|
1051
|
+
if (!this.isDraggingPlayhead) return;
|
|
1052
|
+
const tracksScroll$1 = this.tracksScrollRef.value;
|
|
1053
|
+
if (!tracksScroll$1) return;
|
|
1054
|
+
const rect = tracksScroll$1.getBoundingClientRect();
|
|
1055
|
+
const trackAreaLeft = rect.left + hierarchyWidth;
|
|
1056
|
+
const trackAreaRight = rect.right;
|
|
1057
|
+
trackAreaRight - trackAreaLeft;
|
|
1058
|
+
const distanceFromLeft = lastClientX - trackAreaLeft;
|
|
1059
|
+
const distanceFromRight = trackAreaRight - lastClientX;
|
|
1060
|
+
let scrollDelta = 0;
|
|
1061
|
+
if (distanceFromLeft < _EFTimeline.EDGE_SCROLL_ZONE && distanceFromLeft >= 0) {
|
|
1062
|
+
const intensity = 1 - distanceFromLeft / _EFTimeline.EDGE_SCROLL_ZONE;
|
|
1063
|
+
scrollDelta = -_EFTimeline.EDGE_SCROLL_SPEED * intensity;
|
|
1064
|
+
} else if (distanceFromRight < _EFTimeline.EDGE_SCROLL_ZONE && distanceFromRight >= 0) {
|
|
1065
|
+
const intensity = 1 - distanceFromRight / _EFTimeline.EDGE_SCROLL_ZONE;
|
|
1066
|
+
scrollDelta = _EFTimeline.EDGE_SCROLL_SPEED * intensity;
|
|
1067
|
+
} else if (lastClientX < trackAreaLeft) scrollDelta = -_EFTimeline.EDGE_SCROLL_SPEED;
|
|
1068
|
+
else if (lastClientX > trackAreaRight) scrollDelta = _EFTimeline.EDGE_SCROLL_SPEED;
|
|
1069
|
+
if (scrollDelta !== 0) {
|
|
1070
|
+
const maxScroll = tracksScroll$1.scrollWidth - tracksScroll$1.clientWidth;
|
|
1071
|
+
const newScrollLeft = Math.max(0, Math.min(maxScroll, tracksScroll$1.scrollLeft + scrollDelta));
|
|
1072
|
+
tracksScroll$1.scrollLeft = newScrollLeft;
|
|
1073
|
+
this.viewportScrollLeft = newScrollLeft;
|
|
1074
|
+
updatePlayheadFromMouse();
|
|
1075
|
+
}
|
|
1076
|
+
edgeScrollAnimationId = requestAnimationFrame(edgeScrollLoop);
|
|
1077
|
+
};
|
|
1078
|
+
const onMove = (moveEvent) => {
|
|
1079
|
+
if (!this.isDraggingPlayhead) return;
|
|
1080
|
+
lastClientX = moveEvent.clientX;
|
|
1081
|
+
updatePlayheadFromMouse();
|
|
1082
|
+
};
|
|
1083
|
+
const onUp = () => {
|
|
1084
|
+
this.isDraggingPlayhead = false;
|
|
1085
|
+
if (edgeScrollAnimationId) cancelAnimationFrame(edgeScrollAnimationId);
|
|
1086
|
+
window.removeEventListener("pointermove", onMove);
|
|
1087
|
+
window.removeEventListener("pointerup", onUp);
|
|
1088
|
+
};
|
|
1089
|
+
window.addEventListener("pointermove", onMove);
|
|
1090
|
+
window.addEventListener("pointerup", onUp);
|
|
1091
|
+
edgeScrollAnimationId = requestAnimationFrame(edgeScrollLoop);
|
|
1092
|
+
}
|
|
1093
|
+
formatTime(ms) {
|
|
1094
|
+
const seconds = Math.floor(ms / 1e3);
|
|
1095
|
+
const minutes = Math.floor(seconds / 60);
|
|
1096
|
+
const remainingSeconds = seconds % 60;
|
|
1097
|
+
const frames = Math.floor(ms % 1e3 / (1e3 / 30));
|
|
1098
|
+
return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}:${frames.toString().padStart(2, "0")}`;
|
|
1099
|
+
}
|
|
1100
|
+
renderPlaybackControls() {
|
|
1101
|
+
if (!this.showPlaybackControls) return nothing;
|
|
1102
|
+
return html`
|
|
1103
|
+
<div class="playback-controls">
|
|
1104
|
+
${this.isPlaying ? html`<button class="control-btn" @click=${this.handlePause} title="Pause">⏸</button>` : html`<button class="control-btn" @click=${this.handlePlay} title="Play">▶</button>`}
|
|
1105
|
+
<button class="control-btn ${this.isLooping ? "active" : ""}" @click=${this.handleToggleLoop} title="Loop">🔁</button>
|
|
1106
|
+
</div>
|
|
1107
|
+
`;
|
|
1108
|
+
}
|
|
1109
|
+
renderTimeDisplay() {
|
|
1110
|
+
if (!this.showTimeDisplay) return nothing;
|
|
1111
|
+
return html`<span class="time-display">${this.formatTime(this.currentTimeMs)} / ${this.formatTime(this.durationMs)}</span>`;
|
|
1112
|
+
}
|
|
1113
|
+
renderZoomControls() {
|
|
1114
|
+
if (!this.showZoomControls) return nothing;
|
|
1115
|
+
return html`
|
|
1116
|
+
<div class="zoom-controls">
|
|
1117
|
+
<button class="zoom-btn" @click=${this.handleZoomOut} title="Zoom out">−</button>
|
|
1118
|
+
<span class="zoom-label">${this.zoomPercent}%</span>
|
|
1119
|
+
<button class="zoom-btn" @click=${this.handleZoomIn} title="Zoom in">+</button>
|
|
1120
|
+
</div>
|
|
1121
|
+
`;
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Calculate frame highlight bounds (semantics).
|
|
1125
|
+
* Returns null if frame markers aren't visible or duration is invalid.
|
|
1126
|
+
*/
|
|
1127
|
+
calculateFrameHighlightBounds() {
|
|
1128
|
+
if (!this.showFrameMarkers || this.durationMs <= 0) return null;
|
|
1129
|
+
const fps = this.fps;
|
|
1130
|
+
const frameDurationMs = 1e3 / fps;
|
|
1131
|
+
const frameStartMs = quantizeToFrameTimeMs(this.currentTimeMs, fps);
|
|
1132
|
+
const frameEndMs = Math.min(frameStartMs + frameDurationMs, this.durationMs);
|
|
1133
|
+
const startPx = timeToPx(frameStartMs, this.pixelsPerMs);
|
|
1134
|
+
const widthPx = timeToPx(frameEndMs, this.pixelsPerMs) - startPx;
|
|
1135
|
+
if (widthPx <= 0 || startPx < 0) return null;
|
|
1136
|
+
return {
|
|
1137
|
+
startPx,
|
|
1138
|
+
widthPx
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Render frame highlight (mechanism).
|
|
1143
|
+
* Shows the current frame as a rectangle to indicate frames have duration.
|
|
1144
|
+
* Only rendered when frame markers are visible (zoom level high enough).
|
|
1145
|
+
*/
|
|
1146
|
+
renderFrameHighlight() {
|
|
1147
|
+
if (!this.showFrameMarkers) return nothing;
|
|
1148
|
+
const bounds = this.calculateFrameHighlightBounds();
|
|
1149
|
+
if (!bounds) return nothing;
|
|
1150
|
+
const hierarchyWidth = this.showHierarchy ? _EFTimeline.HIERARCHY_WIDTH : 0;
|
|
1151
|
+
return html`
|
|
1152
|
+
<div
|
|
1153
|
+
${ref(this.frameHighlightRef)}
|
|
1154
|
+
class="frame-highlight"
|
|
1155
|
+
style=${styleMap({
|
|
1156
|
+
left: `${hierarchyWidth + bounds.startPx}px`,
|
|
1157
|
+
width: `${bounds.widthPx}px`
|
|
1158
|
+
})}
|
|
1159
|
+
></div>
|
|
1160
|
+
`;
|
|
1161
|
+
}
|
|
1162
|
+
renderControls() {
|
|
1163
|
+
if (!this.showControls) return nothing;
|
|
1164
|
+
return html`
|
|
1165
|
+
<div class="header">
|
|
1166
|
+
<div class="controls">
|
|
1167
|
+
${this.renderPlaybackControls()}
|
|
1168
|
+
${this.renderTimeDisplay()}
|
|
1169
|
+
</div>
|
|
1170
|
+
${this.renderZoomControls()}
|
|
1171
|
+
</div>
|
|
1172
|
+
`;
|
|
1173
|
+
}
|
|
1174
|
+
handleTrimChange(e) {
|
|
1175
|
+
const { elementId, type, newValueMs } = e.detail;
|
|
1176
|
+
const element = this.targetElement?.querySelector(`#${elementId}`);
|
|
1177
|
+
if (element) if (type === "start") element.trimStartMs = newValueMs;
|
|
1178
|
+
else element.trimEndMs = newValueMs;
|
|
1179
|
+
}
|
|
1180
|
+
/**
|
|
1181
|
+
* Handle row hover events - update canvas highlighted element.
|
|
1182
|
+
*/
|
|
1183
|
+
handleRowHover(e) {
|
|
1184
|
+
this.setHighlightedElement(e.detail.element);
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* Handle row selection events - update selection context.
|
|
1188
|
+
*/
|
|
1189
|
+
handleRowSelect(e) {
|
|
1190
|
+
const selectionCtx = this.getCanvasSelectionContext();
|
|
1191
|
+
if (selectionCtx && e.detail.elementId) selectionCtx.select(e.detail.elementId);
|
|
1192
|
+
}
|
|
1193
|
+
/**
|
|
1194
|
+
* Render timeline rows using flattened hierarchy.
|
|
1195
|
+
* Each row is a unified component with both label and track.
|
|
1196
|
+
*/
|
|
1197
|
+
renderRows(target) {
|
|
1198
|
+
const rows = flattenHierarchy(target).filter((row) => shouldRenderElement(row.element, this.hideSelectors, this.showSelectors));
|
|
1199
|
+
const selectionCtx = this.getCanvasSelectionContext();
|
|
1200
|
+
const selectedIds = new Set(selectionCtx?.selectedIds ?? []);
|
|
1201
|
+
const highlightedElement = this.getHighlightedElement();
|
|
1202
|
+
return html`
|
|
1203
|
+
<div
|
|
1204
|
+
class="tracks-rows"
|
|
1205
|
+
@track-trim-change=${this.handleTrimChange}
|
|
1206
|
+
@row-hover=${this.handleRowHover}
|
|
1207
|
+
@row-select=${this.handleRowSelect}
|
|
1208
|
+
>
|
|
1209
|
+
${repeat(rows, (row) => {
|
|
1210
|
+
const el = row.element;
|
|
1211
|
+
return el && el.id ? el.id : row.element;
|
|
1212
|
+
}, (row) => html`
|
|
1213
|
+
<ef-timeline-row
|
|
1214
|
+
.element=${row.element}
|
|
1215
|
+
depth=${row.depth}
|
|
1216
|
+
pixels-per-ms=${this.pixelsPerMs}
|
|
1217
|
+
?enable-trim=${this.enableTrim}
|
|
1218
|
+
.hideSelectors=${this.hideSelectors}
|
|
1219
|
+
.showSelectors=${this.showSelectors}
|
|
1220
|
+
.highlightedElement=${highlightedElement}
|
|
1221
|
+
.selectedIds=${selectedIds}
|
|
1222
|
+
></ef-timeline-row>
|
|
1223
|
+
`)}
|
|
1224
|
+
</div>
|
|
1225
|
+
`;
|
|
1226
|
+
}
|
|
1227
|
+
render() {
|
|
1228
|
+
const target = this.targetTemporal;
|
|
1229
|
+
if (!target) return html`
|
|
1230
|
+
<div class="timeline-container">
|
|
1231
|
+
${this.renderControls()}
|
|
1232
|
+
<div class="empty-state">No target element selected</div>
|
|
1233
|
+
</div>
|
|
1234
|
+
`;
|
|
1235
|
+
const playheadPx = timeToPx(this.currentTimeMs, this.pixelsPerMs);
|
|
1236
|
+
const hierarchyWidth = this.showHierarchy ? _EFTimeline.HIERARCHY_WIDTH : 0;
|
|
1237
|
+
const playheadLeft = hierarchyWidth + playheadPx;
|
|
1238
|
+
return html`
|
|
1239
|
+
<div class="timeline-container" tabindex="0" ${ref(this.containerRef)}>
|
|
1240
|
+
${this.renderControls()}
|
|
1241
|
+
<div class="timeline-area">
|
|
1242
|
+
<!-- Tracks Viewport - Single scrollable container -->
|
|
1243
|
+
<div class="tracks-viewport">
|
|
1244
|
+
<div
|
|
1245
|
+
class="tracks-scroll"
|
|
1246
|
+
${ref(this.tracksScrollRef)}
|
|
1247
|
+
@pointerdown=${this.handleTracksPointerDown}
|
|
1248
|
+
>
|
|
1249
|
+
<!-- Ruler Row - Inside scroll container with sticky positioning -->
|
|
1250
|
+
${this.showRuler ? html`
|
|
1251
|
+
<div class="ruler-row" style="width: ${this.contentWidthPx + hierarchyWidth}px;">
|
|
1252
|
+
${this.showHierarchy ? html`<div class="ruler-spacer"></div>` : nothing}
|
|
1253
|
+
<div
|
|
1254
|
+
class="ruler-content"
|
|
1255
|
+
@pointerdown=${this.handleRulerPointerDown}
|
|
1256
|
+
>
|
|
1257
|
+
<ef-timeline-ruler
|
|
1258
|
+
duration-ms=${this.durationMs}
|
|
1259
|
+
fps=${this.fps}
|
|
1260
|
+
content-width=${this.contentWidthPx}
|
|
1261
|
+
></ef-timeline-ruler>
|
|
1262
|
+
${this.showPlayhead ? html`
|
|
1263
|
+
<div
|
|
1264
|
+
${ref(this.playheadHandleRef)}
|
|
1265
|
+
class="ruler-playhead-handle"
|
|
1266
|
+
style="left: ${playheadPx}px;"
|
|
1267
|
+
@pointerdown=${this.handlePlayheadPointerDown}
|
|
1268
|
+
></div>
|
|
1269
|
+
` : nothing}
|
|
1270
|
+
</div>
|
|
1271
|
+
</div>
|
|
1272
|
+
` : nothing}
|
|
1273
|
+
<div class="tracks-content" style="min-width: ${this.contentWidthPx + hierarchyWidth}px;">
|
|
1274
|
+
<!-- Track rows layer -->
|
|
1275
|
+
<div class="tracks-rows-layer">
|
|
1276
|
+
${this.renderRows(target)}
|
|
1277
|
+
</div>
|
|
1278
|
+
|
|
1279
|
+
<!-- Playhead layer - sticky to stay visible during vertical scroll -->
|
|
1280
|
+
<div class="playhead-layer">
|
|
1281
|
+
${this.renderFrameHighlight()}
|
|
1282
|
+
${this.showPlayhead ? html`
|
|
1283
|
+
<div ${ref(this.playheadRef)} class="playhead" style="left: ${playheadLeft - 1}px;">
|
|
1284
|
+
<div class="playhead-drag-target" @pointerdown=${this.handlePlayheadPointerDown}></div>
|
|
1285
|
+
</div>
|
|
1286
|
+
` : nothing}
|
|
1287
|
+
</div>
|
|
1288
|
+
</div>
|
|
1289
|
+
</div>
|
|
1290
|
+
</div>
|
|
1291
|
+
</div>
|
|
1292
|
+
</div>
|
|
1293
|
+
`;
|
|
1294
|
+
}
|
|
1295
|
+
};
|
|
1296
|
+
__decorate([property({ type: String })], EFTimeline.prototype, "target", void 0);
|
|
1297
|
+
__decorate([property({
|
|
1298
|
+
type: Number,
|
|
1299
|
+
attribute: "pixels-per-ms"
|
|
1300
|
+
})], EFTimeline.prototype, "pixelsPerMs", void 0);
|
|
1301
|
+
__decorate([property({
|
|
1302
|
+
type: Number,
|
|
1303
|
+
attribute: "min-zoom"
|
|
1304
|
+
})], EFTimeline.prototype, "minZoom", void 0);
|
|
1305
|
+
__decorate([property({
|
|
1306
|
+
type: Number,
|
|
1307
|
+
attribute: "max-zoom"
|
|
1308
|
+
})], EFTimeline.prototype, "maxZoom", void 0);
|
|
1309
|
+
__decorate([property({
|
|
1310
|
+
type: Boolean,
|
|
1311
|
+
attribute: "enable-trim"
|
|
1312
|
+
})], EFTimeline.prototype, "enableTrim", void 0);
|
|
1313
|
+
__decorate([property({
|
|
1314
|
+
type: Boolean,
|
|
1315
|
+
attribute: "show-controls"
|
|
1316
|
+
})], EFTimeline.prototype, "showControls", void 0);
|
|
1317
|
+
__decorate([property({
|
|
1318
|
+
type: Boolean,
|
|
1319
|
+
attribute: "show-ruler"
|
|
1320
|
+
})], EFTimeline.prototype, "showRuler", void 0);
|
|
1321
|
+
__decorate([property({
|
|
1322
|
+
type: Boolean,
|
|
1323
|
+
attribute: "show-hierarchy"
|
|
1324
|
+
})], EFTimeline.prototype, "showHierarchy", void 0);
|
|
1325
|
+
__decorate([property({
|
|
1326
|
+
type: Boolean,
|
|
1327
|
+
attribute: "show-playhead"
|
|
1328
|
+
})], EFTimeline.prototype, "showPlayhead", void 0);
|
|
1329
|
+
__decorate([property({
|
|
1330
|
+
type: Boolean,
|
|
1331
|
+
attribute: "show-playback-controls"
|
|
1332
|
+
})], EFTimeline.prototype, "showPlaybackControls", void 0);
|
|
1333
|
+
__decorate([property({
|
|
1334
|
+
type: Boolean,
|
|
1335
|
+
attribute: "show-zoom-controls"
|
|
1336
|
+
})], EFTimeline.prototype, "showZoomControls", void 0);
|
|
1337
|
+
__decorate([property({
|
|
1338
|
+
type: Boolean,
|
|
1339
|
+
attribute: "show-time-display"
|
|
1340
|
+
})], EFTimeline.prototype, "showTimeDisplay", void 0);
|
|
1341
|
+
__decorate([property({
|
|
1342
|
+
type: String,
|
|
1343
|
+
attribute: "control-target"
|
|
1344
|
+
})], EFTimeline.prototype, "controlTarget", void 0);
|
|
1345
|
+
__decorate([property({ type: String })], EFTimeline.prototype, "hide", void 0);
|
|
1346
|
+
__decorate([property({ type: String })], EFTimeline.prototype, "show", void 0);
|
|
1347
|
+
__decorate([state()], EFTimeline.prototype, "targetElement", void 0);
|
|
1348
|
+
__decorate([state()], EFTimeline.prototype, "currentTimeMs", void 0);
|
|
1349
|
+
__decorate([state()], EFTimeline.prototype, "isPlaying", void 0);
|
|
1350
|
+
__decorate([state()], EFTimeline.prototype, "isLooping", void 0);
|
|
1351
|
+
__decorate([state()], EFTimeline.prototype, "viewportScrollLeft", void 0);
|
|
1352
|
+
__decorate([provide({ context: timelineStateContext }), state()], EFTimeline.prototype, "_timelineState", void 0);
|
|
1353
|
+
__decorate([consume({
|
|
1354
|
+
context: selectionContext,
|
|
1355
|
+
subscribe: true
|
|
1356
|
+
})], EFTimeline.prototype, "selectionContext", void 0);
|
|
1357
|
+
__decorate([provide({ context: playingContext })], EFTimeline.prototype, "providedPlaying", null);
|
|
1358
|
+
__decorate([provide({ context: loopContext })], EFTimeline.prototype, "providedLoop", null);
|
|
1359
|
+
__decorate([provide({ context: currentTimeContext })], EFTimeline.prototype, "providedCurrentTime", null);
|
|
1360
|
+
__decorate([provide({ context: durationContext })], EFTimeline.prototype, "providedDuration", null);
|
|
1361
|
+
__decorate([provide({ context: targetTemporalContext })], EFTimeline.prototype, "providedTargetTemporal", null);
|
|
1362
|
+
__decorate([eventOptions({ passive: false })], EFTimeline.prototype, "handleRulerPointerDown", null);
|
|
1363
|
+
__decorate([eventOptions({ passive: false })], EFTimeline.prototype, "handlePlayheadPointerDown", null);
|
|
1364
|
+
__decorate([eventOptions({ passive: false })], EFTimeline.prototype, "handleTracksPointerDown", null);
|
|
1365
|
+
EFTimeline = _EFTimeline = __decorate([customElement("ef-timeline")], EFTimeline);
|
|
1366
|
+
|
|
1367
|
+
//#endregion
|
|
1368
|
+
export { EFTimeline };
|
|
1369
|
+
//# sourceMappingURL=EFTimeline.js.map
|