@editframe/elements 0.30.2-beta.0 → 0.31.0-beta.1
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 +129 -56
- package/dist/elements/EFThumbnailStrip.js +605 -359
- package/dist/elements/EFThumbnailStrip.js.map +1 -1
- package/dist/elements/EFTimegroup.d.ts +233 -25
- package/dist/elements/EFTimegroup.js +865 -144
- 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 +154 -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 +171 -28
- 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 +7 -1
- 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/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 +178 -0
- 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 +833 -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/preview/workers/encoderWorkerInline.js +103 -0
- package/dist/preview/workers/encoderWorkerInline.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 +71 -67
- 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 @@
|
|
|
1
|
+
{"version":3,"file":"CaptionsTrack.js","names":["measurementCanvas: HTMLCanvasElement | null","measurementContext: CanvasRenderingContext2D | null","wordWidths: Array<{ textWidth: number; timeWidth: number; startPx: number; endPx: number }>","host: EFCaptionsTrack","EFCaptionsTrack","EFCaptionsActiveWordTrack","EFCaptionsSegmentTrack","EFCaptionsBeforeWordTrack","EFCaptionsAfterWordTrack"],"sources":["../../../../src/gui/timeline/tracks/CaptionsTrack.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { css, html, nothing, type TemplateResult } from \"lit\";\nimport { customElement } from \"lit/decorators.js\";\nimport { styleMap } from \"lit/directives/style-map.js\";\nimport type { ReactiveController } from \"lit\";\nimport {\n type Caption,\n EFCaptions,\n type WordSegment,\n} from \"../../../elements/EFCaptions.js\";\nimport { phosphorIcon, ICONS } from \"../../icons.js\";\nimport { currentTimeContext } from \"../../currentTimeContext.js\";\n// TrackItem must be pre-loaded before this module is imported\n// See preloadTracks.ts for the initialization sequence\nimport { TrackItem } from \"./TrackItem.js\";\n\n// Shared canvas context for text measurement (avoids creating new canvas each time)\nlet measurementCanvas: HTMLCanvasElement | null = null;\nlet measurementContext: CanvasRenderingContext2D | null = null;\n// Cache for text measurements: key is \"text:fontSize:fontWeight\"\nconst textMeasurementCache = new Map<string, number>();\nconst MAX_CACHE_SIZE = 500;\n\n/**\n * Measure text width accurately using canvas.\n * Matches the actual font used in word elements (font-weight: 500).\n * Results are cached to avoid repeated measurements of the same text.\n */\nfunction measureTextWidth(text: string, fontSize: number, fontWeight: number = 500): number {\n // Check cache first\n const cacheKey = `${text}:${fontSize}:${fontWeight}`;\n const cached = textMeasurementCache.get(cacheKey);\n if (cached !== undefined) {\n return cached;\n }\n \n // Initialize shared canvas context if needed\n if (!measurementCanvas || !measurementContext) {\n measurementCanvas = document.createElement(\"canvas\");\n measurementContext = measurementCanvas.getContext(\"2d\");\n }\n \n if (!measurementContext) {\n return text.length * fontSize * 0.6; // Fallback estimate\n }\n \n // Match the actual font used in word elements\n const fontFamily = getComputedStyle(document.body).fontFamily || \"system-ui, sans-serif\";\n measurementContext.font = `${fontWeight} ${fontSize}px ${fontFamily}`;\n const width = measurementContext.measureText(text).width;\n \n // Cache the result (with size limit to prevent memory leaks)\n if (textMeasurementCache.size >= MAX_CACHE_SIZE) {\n // Clear oldest entries (simple strategy: clear half the cache)\n const keysToDelete = Array.from(textMeasurementCache.keys()).slice(0, MAX_CACHE_SIZE / 2);\n for (const key of keysToDelete) {\n textMeasurementCache.delete(key);\n }\n }\n textMeasurementCache.set(cacheKey, width);\n \n return width;\n}\n\n/**\n * Check if words can fit individually within a segment when positioned by time\n * \n * Strategy: Allow overlaps as long as all words can be rendered within the container.\n * Only use compact mode when words are so cramped they can't be displayed at all.\n */\nfunction canWordsFitIndividually(\n words: WordSegment[],\n segmentStart: number,\n segmentWidthPx: number,\n pixelsPerMs: number,\n): { fits: boolean; reason?: string } {\n if (words.length === 0) {\n return { fits: false, reason: \"no words\" };\n }\n \n // Measure total text width of all words (as if rendered sequentially)\n let totalTextWidth = 0;\n const wordWidths: Array<{ textWidth: number; timeWidth: number; startPx: number; endPx: number }> = [];\n \n for (const word of words) {\n if (!word) continue;\n \n // Measure actual text width (with padding: 2px left + 2px right = 4px total)\n const textWidth = measureTextWidth(word.text.trim(), 9, 500) + 4;\n \n // Calculate time-based position and width\n const startPx = pixelsPerMs * (word.start - segmentStart) * 1000;\n const endPx = pixelsPerMs * (word.end - segmentStart) * 1000;\n const timeWidth = endPx - startPx;\n \n wordWidths.push({ textWidth, timeWidth, startPx, endPx });\n totalTextWidth += textWidth;\n }\n \n // Key insight: If total text width fits in segment, we can render words individually\n // even if they overlap based on their time positions\n // Use 90% threshold to account for some spacing/overlap\n if (totalTextWidth <= segmentWidthPx * 0.9) {\n // All words can fit - use positioned mode (overlaps are okay)\n return { fits: true };\n }\n \n // If total text doesn't fit, check if individual words are too narrow to be readable\n // If any word's time-based width is less than 30% of its text width, it's unreadable\n for (const { textWidth, timeWidth } of wordWidths) {\n if (timeWidth < textWidth * 0.3) {\n return { fits: false, reason: `word too narrow (${timeWidth.toFixed(1)}px < ${(textWidth * 0.3).toFixed(1)}px)` };\n }\n }\n \n // If words are readable individually but total text is too wide,\n // check if they can still fit with overlaps\n // Find the maximum right edge of all words\n const maxEndPx = Math.max(...wordWidths.map(w => w.endPx));\n \n // If the rightmost word fits within the segment, allow overlaps\n if (maxEndPx <= segmentWidthPx * 1.1) {\n return { fits: true };\n }\n \n // Words don't fit - use compact mode\n return { fits: false, reason: `words exceed segment (total text: ${totalTextWidth.toFixed(1)}px, segment: ${segmentWidthPx.toFixed(1)}px)` };\n}\n\n/**\n * Controller to ensure captions track updates reactively during playback.\n * \n * Performance optimization: Only requests updates when the visual state actually\n * needs to change (active word/segment changed), not on every frame.\n */\nclass CaptionsTimeController implements ReactiveController {\n private animationFrameId?: number;\n private lastTimeMs = -1;\n private lastActiveWordIndex = -1;\n private lastActiveSegmentIndex = -1;\n // Minimum time change to trigger update when no word change (for segment boundaries)\n private static readonly MIN_TIME_CHANGE_MS = 100;\n \n constructor(private host: EFCaptionsTrack) {\n this.host.addController(this);\n }\n \n hostConnected(): void {\n this.startTimeUpdate();\n }\n \n hostDisconnected(): void {\n this.stopTimeUpdate();\n }\n \n private startTimeUpdate(): void {\n const update = () => {\n // Read current time from root timegroup\n const captions = this.host.element as EFCaptions;\n const rootTimegroup = captions.rootTimegroup;\n const currentTimeMs = rootTimegroup?.currentTimeMs || 0;\n const captionsData = captions?.unifiedCaptionsDataTask?.value;\n \n // Check if we actually need to update\n let shouldUpdate = false;\n \n if (captionsData) {\n const captionsLocalTimeMs = currentTimeMs - captions.startTimeMs;\n const captionsLocalTimeSec = captionsLocalTimeMs / 1000;\n \n // Find current active word and segment indices\n const activeWordIndex = captionsData.word_segments.findIndex(\n (word) => captionsLocalTimeSec >= word.start && captionsLocalTimeSec < word.end\n );\n const activeSegmentIndex = captionsData.segments.findIndex(\n (seg) => captionsLocalTimeSec >= seg.start && captionsLocalTimeSec < seg.end\n );\n \n // Update if active word or segment changed\n if (activeWordIndex !== this.lastActiveWordIndex) {\n this.lastActiveWordIndex = activeWordIndex;\n shouldUpdate = true;\n }\n if (activeSegmentIndex !== this.lastActiveSegmentIndex) {\n this.lastActiveSegmentIndex = activeSegmentIndex;\n shouldUpdate = true;\n }\n }\n \n // Also update if time changed significantly (for visual feedback during seek)\n const timeDelta = Math.abs(currentTimeMs - this.lastTimeMs);\n if (timeDelta >= CaptionsTimeController.MIN_TIME_CHANGE_MS) {\n shouldUpdate = true;\n }\n \n if (shouldUpdate) {\n this.lastTimeMs = currentTimeMs;\n this.host.requestUpdate();\n }\n \n this.animationFrameId = requestAnimationFrame(update);\n };\n update();\n }\n \n private stopTimeUpdate(): void {\n if (this.animationFrameId) {\n cancelAnimationFrame(this.animationFrameId);\n this.animationFrameId = undefined;\n }\n }\n}\n\n@customElement(\"ef-captions-track\")\nexport class EFCaptionsTrack extends TrackItem {\n static styles = [\n ...TrackItem.styles,\n css`\n .segment-block {\n position: absolute;\n border-radius: 3px;\n transition: box-shadow 0.15s ease, z-index 0.15s ease;\n cursor: pointer;\n overflow: visible;\n }\n \n .segment-block:hover {\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);\n z-index: 5;\n }\n \n .word-element {\n position: absolute;\n font-size: 9px;\n line-height: 1.2;\n white-space: nowrap;\n font-weight: 500;\n top: 50%;\n transform: translateY(-50%);\n padding: 2px 4px;\n border-radius: 2px;\n transition: all 0.1s ease;\n background: rgba(30, 41, 59, 0.9);\n color: rgb(226, 232, 240);\n z-index: 1;\n }\n \n .word-element.active {\n background: rgb(74, 222, 128);\n color: rgb(20, 30, 20);\n font-weight: 700;\n font-size: 10px;\n z-index: 10;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n }\n \n .word-element.future {\n background: rgba(51, 65, 85, 0.8);\n color: rgb(226, 232, 240);\n z-index: 5;\n }\n \n .segment-block.active .word-element:not(.active):not(.future) {\n color: rgb(203, 213, 225);\n background: rgba(30, 41, 59, 0.9);\n }\n \n /* Compact text mode - when words are too small to position individually */\n .segment-block.compact-text {\n display: flex;\n align-items: center;\n padding: 0 4px;\n overflow: hidden;\n /* Keep position: absolute from .segment-block for correct time-based positioning */\n }\n \n /* Allow overflow on hover for compact text */\n .segment-block.compact-text:hover {\n overflow: visible;\n z-index: 100;\n /* Expand to fit content on hover */\n width: max-content !important;\n min-width: max-content;\n background: rgba(34, 60, 40, 0.95) !important;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);\n }\n \n .segment-text-compact {\n font-size: 10px;\n line-height: 1.2;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: rgb(226, 232, 240);\n width: 100%;\n }\n \n /* On hover, show full text */\n .segment-block.compact-text:hover .segment-text-compact {\n overflow: visible;\n text-overflow: clip;\n }\n \n .segment-block.compact-text.active .segment-text-compact {\n color: rgb(203, 213, 225);\n font-weight: 500;\n }\n \n .segment-duration-indicator {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n height: 2px;\n background: currentColor;\n opacity: 0.2;\n border-radius: 0 0 3px 3px;\n }\n \n .segment-block.active .segment-duration-indicator {\n opacity: 0.4;\n height: 2px;\n }\n \n .word-marker {\n position: absolute;\n bottom: 0;\n width: 1px;\n height: 30%;\n background: rgba(255, 255, 255, 0.3);\n pointer-events: none;\n }\n \n .word-marker.active {\n background: rgba(255, 255, 255, 0.8);\n height: 50%;\n width: 2px;\n }\n `,\n ];\n\n @consume({ context: currentTimeContext, subscribe: true })\n contextCurrentTimeMs = 0;\n \n // Controller ensures real-time updates during playback\n // The controller manages its own lifecycle via ReactiveController interface\n private _timeController = new CaptionsTimeController(this);\n \n private lastPixelsPerMs = 0;\n \n protected updated(changedProperties: Map<string | number | symbol, unknown>): void {\n super.updated(changedProperties);\n \n // Re-render when pixelsPerMs changes (zoom level changes)\n if (changedProperties.has(\"pixelsPerMs\")) {\n const currentPixelsPerMs = this.pixelsPerMs;\n if (currentPixelsPerMs !== this.lastPixelsPerMs) {\n this.lastPixelsPerMs = currentPixelsPerMs;\n // Force update to recalculate layout mode\n this.requestUpdate();\n }\n }\n }\n\n render() {\n const captions = this.element as EFCaptions;\n const captionsData = captions.unifiedCaptionsDataTask.value;\n\n return html`<div style=${styleMap(this.gutterStyles)}>\n <div\n class=\"relative\"\n style=\"background-color: var(--filmstrip-bg);\"\n ?data-focused=${this.isFocused}\n @mouseenter=${() => {\n if (this.focusContext) {\n this.focusContext.focusedElement = this.element;\n }\n }}\n @mouseleave=${() => {\n if (this.focusContext) {\n this.focusContext.focusedElement = null;\n }\n }}\n >\n <div\n ?data-focused=${this.isFocused}\n class=\"trim-container relative mb-0 block text-nowrap border text-sm overflow-visible\"\n style=${styleMap({\n ...this.trimPortionStyles,\n height: \"var(--timeline-track-height, 22px)\",\n backgroundColor: this.isFocused\n ? \"var(--filmstrip-item-focused)\"\n : \"var(--filmstrip-item-bg)\",\n borderColor: \"var(--filmstrip-border)\",\n borderLeftColor: this.getElementTypeColor(),\n borderLeftWidth: \"3px\",\n minHeight: \"22px\",\n })}\n >\n ${this.renderCaptionsData(captionsData)}\n ${\n this.enableTrim\n ? html`<ef-trim-handles\n element-id=${(this.element as HTMLElement).id || \"\"}\n pixels-per-ms=${this.pixelsPerMs}\n trim-start-ms=${this.element.trimStartMs ?? 0}\n trim-end-ms=${this.element.trimEndMs ?? 0}\n intrinsic-duration-ms=${this.element.intrinsicDurationMs ?? this.element.durationMs}\n @trim-change=${this.handleTrimChange}\n ></ef-trim-handles>`\n : nothing\n }\n </div>\n </div>\n ${this.renderChildren()}\n </div>`;\n }\n \n\n renderCaptionsData(captionsData: Caption | null | undefined) {\n if (!captionsData) {\n return html``;\n }\n\n const captions = this.element as EFCaptions;\n const rootTimegroup = captions.rootTimegroup;\n // Use context current time for reactivity, fallback to rootTimegroup\n const currentTimeMs = this.contextCurrentTimeMs || rootTimegroup?.currentTimeMs || 0;\n const captionsLocalTimeMs = currentTimeMs - captions.startTimeMs;\n const captionsLocalTimeSec = captionsLocalTimeMs / 1000;\n\n // Get element type color for captions\n const captionColor = \"rgb(34, 197, 94)\"; // Green for captions\n \n // Find active word for highlighting\n const activeWord = captionsData.word_segments.find(\n (word) =>\n captionsLocalTimeSec >= word.start &&\n captionsLocalTimeSec < word.end\n );\n \n // Render word markers for visual density indication (subtle)\n const wordMarkers = captionsData.word_segments.map((word) => {\n const wordStartPx = this.pixelsPerMs * word.start * 1000;\n const wordWidth = this.pixelsPerMs * (word.end - word.start) * 1000;\n const isActive = word === activeWord;\n \n // Only show markers if they're wide enough to be visible\n if (wordWidth < 1.5) return nothing;\n \n return html`<div\n class=\"word-marker ${isActive ? \"active\" : \"\"}\"\n style=${styleMap({\n left: `${wordStartPx}px`,\n })}\n ></div>`;\n });\n\n // Render semantic segment blocks with words positioned by their actual timing\n const segmentElements = captionsData.segments.map((segment) => {\n const isActiveSegment =\n captionsLocalTimeSec >= segment.start &&\n captionsLocalTimeSec < segment.end;\n \n const segmentStartPx = this.pixelsPerMs * segment.start * 1000;\n const segmentWidth = this.pixelsPerMs * (segment.end - segment.start) * 1000;\n const segmentDuration = (segment.end - segment.start) * 1000;\n \n // Get words in this segment, sorted by start time\n const wordsInSegment = captionsData.word_segments\n .filter(\n (word) =>\n word.start >= segment.start && word.end <= segment.end\n )\n .sort((a, b) => a.start - b.start);\n \n // Calculate visual density based on word count\n const density = Math.min(wordsInSegment.length / 10, 1);\n \n // Use actual measurement to determine if words can fit individually\n // Allow overlaps - only use compact mode when words can't be rendered at all\n const measurementResult = canWordsFitIndividually(\n wordsInSegment,\n segment.start,\n segmentWidth,\n this.pixelsPerMs,\n );\n \n const useCompactText = !measurementResult.fits;\n let avgSpacing = 0;\n \n // Calculate average spacing for font scaling (only if using positioned mode)\n if (!useCompactText && wordsInSegment.length > 1) {\n let totalSpacing = 0;\n let spacingCount = 0;\n \n for (let i = 0; i < wordsInSegment.length - 1; i++) {\n const word1 = wordsInSegment[i];\n const word2 = wordsInSegment[i + 1];\n if (!word1 || !word2) continue;\n \n const word1EndPx = this.pixelsPerMs * (word1.end - segment.start) * 1000;\n const word2StartPx = this.pixelsPerMs * (word2.start - segment.start) * 1000;\n const spacing = word2StartPx - word1EndPx;\n \n if (spacing > 0) {\n totalSpacing += spacing;\n spacingCount++;\n }\n }\n \n avgSpacing = spacingCount > 0 ? totalSpacing / spacingCount : 0;\n }\n \n // Calculate optimal font size for positioned words (if not using compact mode)\n const MIN_READABLE_FONT_SIZE = 6; // Minimum readable font size in pixels\n const baseFontSize = 9;\n const activeFontSize = 10;\n let scaledFontSize = baseFontSize;\n let scaledActiveFontSize = activeFontSize;\n \n if (!useCompactText && wordsInSegment.length > 1 && avgSpacing < 8) {\n // Scale down font size proportionally, but don't go below minimum\n const scaleFactor = Math.max(MIN_READABLE_FONT_SIZE / baseFontSize, avgSpacing / 8);\n scaledFontSize = Math.max(MIN_READABLE_FONT_SIZE, baseFontSize * scaleFactor);\n scaledActiveFontSize = Math.max(MIN_READABLE_FONT_SIZE, activeFontSize * scaleFactor);\n }\n \n // Render words positioned by their actual timing within the segment\n const renderWords = () => {\n if (useCompactText) {\n // Compact mode: show text that can overflow on hover\n return html`\n <span class=\"segment-text-compact\">${segment.text}</span>\n `;\n }\n \n // Positioned mode: render words at their time positions\n return wordsInSegment.map((word) => {\n // Position relative to segment start\n const wordOffsetFromSegmentStart = (word.start - segment.start) * 1000;\n const wordLeftPx = this.pixelsPerMs * wordOffsetFromSegmentStart;\n const wordWidthPx = this.pixelsPerMs * (word.end - word.start) * 1000;\n const isActive = word === activeWord;\n \n // Determine if word is in the future (after active word)\n const isFuture = activeWord && word.start > activeWord.end;\n \n return html`\n <span\n class=\"word-element ${isActive ? \"active\" : \"\"} ${isFuture ? \"future\" : \"\"}\"\n style=${styleMap({\n left: `${wordLeftPx}px`,\n minWidth: `${Math.max(wordWidthPx, 8)}px`,\n fontSize: isActive ? `${scaledActiveFontSize}px` : `${scaledFontSize}px`,\n top: \"50%\",\n })}\n title=\"Word: '${word.text}' (${word.start.toFixed(2)}s - ${word.end.toFixed(2)}s)\"\n >\n ${word.text.trim()}\n </span>\n `;\n });\n };\n \n return html`<div\n class=\"segment-block ${isActiveSegment ? \"active\" : \"\"} ${useCompactText ? \"compact-text\" : \"\"}\"\n style=${styleMap({\n left: `${segmentStartPx}px`,\n width: `${Math.max(segmentWidth, 4)}px`,\n height: \"100%\",\n top: \"0px\",\n backgroundColor: isActiveSegment\n ? `rgba(34, 197, 94, ${0.3 + density * 0.2})`\n : `rgba(34, 197, 94, ${0.1 + density * 0.1})`,\n borderColor: isActiveSegment\n ? captionColor\n : `rgba(34, 197, 94, 0.4)`,\n minWidth: segmentWidth < 20 ? \"20px\" : \"auto\",\n })}\n title=${useCompactText \n ? `Caption: '${segment.text}'\\nDuration: ${this.formatDuration(segmentDuration)}\\nTime: ${segment.start.toFixed(2)}s - ${segment.end.toFixed(2)}s`\n : `Caption: '${segment.text}'\\nDuration: ${this.formatDuration(segmentDuration)}\\nTime: ${segment.start.toFixed(2)}s - ${segment.end.toFixed(2)}s\\nWords: ${wordsInSegment.length}`}\n @click=${(e: MouseEvent) => {\n e.stopPropagation();\n // Affordance: Click to seek to segment start\n if (rootTimegroup) {\n const absoluteStartTime = captions.startTimeMs + segment.start * 1000;\n rootTimegroup.currentTimeMs = absoluteStartTime;\n }\n }}\n >\n ${renderWords()}\n ${!useCompactText ? html`<div class=\"segment-duration-indicator\"></div>` : nothing}\n </div>`;\n });\n\n return html`\n ${wordMarkers}\n ${segmentElements}\n `;\n }\n\n renderChildren(): Array<TemplateResult<1> | typeof nothing> | typeof nothing {\n // Don't render child tracks - captions are consolidated into a single track\n // Child elements (active-word, segment, before-word, after-word) are handled\n // inline within the main captions track visualization\n return nothing;\n }\n}\n\n@customElement(\"ef-captions-active-word-track\")\nexport class EFCaptionsActiveWordTrack extends TrackItem {\n get captionsTrackStyles() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n return {\n position: \"relative\",\n left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,\n width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`,\n };\n }\n\n render() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;\n\n if (!captionsData) {\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"border h-[1.1rem] mb-[1px] text-xs\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${phosphorIcon(ICONS.microphone)} Active Word\n </div>\n </div>`;\n }\n\n const rootTimegroup = parentCaptions.rootTimegroup;\n const currentTimeMs = rootTimegroup?.currentTimeMs || 0;\n const captionsLocalTimeMs = currentTimeMs - parentCaptions.startTimeMs;\n const captionsLocalTimeSec = captionsLocalTimeMs / 1000;\n\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"relative border h-[1.1rem] mb-[1px] w-full\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${captionsData.word_segments.map((word) => {\n const isCurrentlyActive =\n captionsLocalTimeSec >= word.start &&\n captionsLocalTimeSec < word.end;\n\n return html`<div\n class=\"absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? \"font-bold z-[5]\" : \"\"}\"\n style=${styleMap({\n left: `${this.pixelsPerMs * word.start * 1000}px`,\n width: `${this.pixelsPerMs * (word.end - word.start) * 1000}px`,\n height: \"100%\",\n top: \"0px\",\n backgroundColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-bg)\"\n : \"var(--filmstrip-item-bg)\",\n borderColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-border)\"\n : \"var(--filmstrip-border)\",\n })}\n title=\"Word: '${word.text}' (${word.start}s - ${word.end}s)\"\n >\n ${isCurrentlyActive ? html`<span class=\"px-0.5 text-[8px] font-bold whitespace-nowrap\" style=\"background-color: var(--filmstrip-caption-bg);\">${word.text.trim()}</span>` : \"\"}\n </div>`;\n })}\n </div>\n </div>`;\n }\n}\n\n@customElement(\"ef-captions-segment-track\")\nexport class EFCaptionsSegmentTrack extends TrackItem {\n get captionsTrackStyles() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n return {\n position: \"relative\",\n left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,\n width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`,\n };\n }\n\n render() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;\n\n if (!captionsData) {\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"border h-[1.1rem] mb-[1px] text-xs\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${phosphorIcon(ICONS.textT)} Segment\n </div>\n </div>`;\n }\n\n const rootTimegroup = parentCaptions.rootTimegroup;\n const currentTimeMs = rootTimegroup?.currentTimeMs || 0;\n const captionsLocalTimeMs = currentTimeMs - parentCaptions.startTimeMs;\n const captionsLocalTimeSec = captionsLocalTimeMs / 1000;\n\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"relative border h-[1.1rem] mb-[1px] w-full\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${captionsData.segments.map((segment) => {\n const isCurrentlyActive =\n captionsLocalTimeSec >= segment.start &&\n captionsLocalTimeSec < segment.end;\n\n return html`<div\n class=\"absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? \"font-bold z-[5]\" : \"\"}\"\n style=${styleMap({\n left: `${this.pixelsPerMs * segment.start * 1000}px`,\n width: `${this.pixelsPerMs * (segment.end - segment.start) * 1000}px`,\n height: \"100%\",\n top: \"0px\",\n backgroundColor: isCurrentlyActive\n ? \"var(--filmstrip-segment-bg)\"\n : \"var(--filmstrip-item-bg)\",\n borderColor: isCurrentlyActive\n ? \"var(--filmstrip-segment-border)\"\n : \"var(--filmstrip-border)\",\n })}\n title=\"Segment: '${segment.text}' (${segment.start}s - ${segment.end}s)\"\n >\n ${isCurrentlyActive ? html`<span class=\"px-0.5 text-[8px] font-bold whitespace-nowrap\" style=\"background-color: var(--filmstrip-segment-bg);\">${segment.text}</span>` : \"\"}\n </div>`;\n })}\n </div>\n </div>`;\n }\n}\n\n@customElement(\"ef-captions-before-word-track\")\nexport class EFCaptionsBeforeWordTrack extends TrackItem {\n get captionsTrackStyles() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n return {\n position: \"relative\",\n left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,\n width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`,\n };\n }\n\n render() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;\n\n if (!captionsData) {\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"border h-[1.1rem] mb-[1px] text-xs\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${phosphorIcon(ICONS.arrowLeft)} Before\n </div>\n </div>`;\n }\n\n const rootTimegroup = parentCaptions.rootTimegroup;\n const currentTimeMs = rootTimegroup?.currentTimeMs || 0;\n const captionsLocalTimeMs = currentTimeMs - parentCaptions.startTimeMs;\n const captionsLocalTimeSec = captionsLocalTimeMs / 1000;\n\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"relative border h-[1.1rem] mb-[1px] w-full\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${captionsData.word_segments.map((word) => {\n const isCurrentlyActive =\n captionsLocalTimeSec >= word.start &&\n captionsLocalTimeSec < word.end;\n\n return html`<div\n class=\"absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? \"font-bold z-[5]\" : \"\"}\"\n style=${styleMap({\n left: `${this.pixelsPerMs * word.start * 1000}px`,\n width: `${this.pixelsPerMs * (word.end - word.start) * 1000}px`,\n height: \"100%\",\n top: \"0px\",\n backgroundColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-bg)\"\n : \"var(--filmstrip-waveform-bg)\",\n borderColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-border)\"\n : \"var(--filmstrip-waveform-border)\",\n })}\n title=\"Word: '${word.text}' (${word.start}s - ${word.end}s)\"\n >\n </div>`;\n })}\n </div>\n </div>`;\n }\n}\n\n@customElement(\"ef-captions-after-word-track\")\nexport class EFCaptionsAfterWordTrack extends TrackItem {\n get captionsTrackStyles() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n return {\n position: \"relative\",\n left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,\n width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`,\n };\n }\n\n render() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;\n\n if (!captionsData) {\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"border h-[1.1rem] mb-[1px] text-xs\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${phosphorIcon(ICONS.arrowRight)} After\n </div>\n </div>`;\n }\n\n const rootTimegroup = parentCaptions.rootTimegroup;\n const currentTimeMs = rootTimegroup?.currentTimeMs || 0;\n const captionsLocalTimeMs = currentTimeMs - parentCaptions.startTimeMs;\n const captionsLocalTimeSec = captionsLocalTimeMs / 1000;\n\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"relative border h-[1.1rem] mb-[1px] w-full\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${captionsData.word_segments.map((word) => {\n const isCurrentlyActive =\n captionsLocalTimeSec >= word.start &&\n captionsLocalTimeSec < word.end;\n\n return html`<div\n class=\"absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? \"font-bold z-[5]\" : \"\"}\"\n style=${styleMap({\n left: `${this.pixelsPerMs * word.start * 1000}px`,\n width: `${this.pixelsPerMs * (word.end - word.start) * 1000}px`,\n height: \"100%\",\n top: \"0px\",\n backgroundColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-bg)\"\n : \"var(--filmstrip-waveform-bg)\",\n borderColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-border)\"\n : \"var(--filmstrip-waveform-border)\",\n })}\n title=\"Word: '${word.text}' (${word.start}s - ${word.end}s)\"\n >\n </div>`;\n })}\n </div>\n </div>`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-captions-track\": EFCaptionsTrack;\n \"ef-captions-active-word-track\": EFCaptionsActiveWordTrack;\n \"ef-captions-segment-track\": EFCaptionsSegmentTrack;\n \"ef-captions-before-word-track\": EFCaptionsBeforeWordTrack;\n \"ef-captions-after-word-track\": EFCaptionsAfterWordTrack;\n }\n}\n\n"],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { __decorate } from "../../../_virtual/_@oxc-project_runtime@0.94.0/helpers/decorate.js";
|
|
2
|
+
import { TrackItem } from "./TrackItem.js";
|
|
3
|
+
import { renderTrackChildren } from "./renderTrackChildren.js";
|
|
4
|
+
import { html } from "lit";
|
|
5
|
+
import { customElement } from "lit/decorators.js";
|
|
6
|
+
|
|
7
|
+
//#region src/gui/timeline/tracks/HTMLTrack.ts
|
|
8
|
+
let EFHTMLTrack = class EFHTMLTrack$1 extends TrackItem {
|
|
9
|
+
contents() {
|
|
10
|
+
return html`
|
|
11
|
+
<span>${this.element.tagName}</span>
|
|
12
|
+
${renderTrackChildren(Array.from(this.element.children || []), this.pixelsPerMs, this.hideSelectors, this.showSelectors, false, this.enableTrim)}
|
|
13
|
+
`;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
EFHTMLTrack = __decorate([customElement("ef-html-track")], EFHTMLTrack);
|
|
17
|
+
|
|
18
|
+
//#endregion
|
|
19
|
+
//# sourceMappingURL=HTMLTrack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HTMLTrack.js","names":["EFHTMLTrack"],"sources":["../../../../src/gui/timeline/tracks/HTMLTrack.ts"],"sourcesContent":["import { html } from \"lit\";\nimport { customElement } from \"lit/decorators.js\";\n// TrackItem must be pre-loaded before this module is imported\n// See preloadTracks.ts for the initialization sequence\nimport { TrackItem } from \"./TrackItem.js\";\nimport { renderTrackChildren } from \"./renderTrackChildren.js\";\n\n@customElement(\"ef-html-track\")\nexport class EFHTMLTrack extends TrackItem {\n contents() {\n return html`\n <span>${this.element.tagName}</span>\n ${renderTrackChildren(\n Array.from(this.element.children || []),\n this.pixelsPerMs,\n this.hideSelectors,\n this.showSelectors,\n false,\n this.enableTrim,\n )}\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-html-track\": EFHTMLTrack;\n }\n}\n\n"],"mappings":";;;;;;;AAQO,wBAAMA,sBAAoB,UAAU;CACzC,WAAW;AACT,SAAO,IAAI;cACD,KAAK,QAAQ,QAAQ;QAC3B,oBACA,MAAM,KAAK,KAAK,QAAQ,YAAY,EAAE,CAAC,EACvC,KAAK,aACL,KAAK,eACL,KAAK,eACL,OACA,KAAK,WACN,CAAC;;;;0BAZP,cAAc,gBAAgB"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { __decorate } from "../../../_virtual/_@oxc-project_runtime@0.94.0/helpers/decorate.js";
|
|
2
|
+
import { EFImage } from "../../../elements/EFImage.js";
|
|
3
|
+
import { TrackItem } from "./TrackItem.js";
|
|
4
|
+
import { html, nothing } from "lit";
|
|
5
|
+
import { customElement } from "lit/decorators.js";
|
|
6
|
+
import { styleMap } from "lit/directives/style-map.js";
|
|
7
|
+
|
|
8
|
+
//#region src/gui/timeline/tracks/ImageTrack.ts
|
|
9
|
+
let EFImageTrack = class EFImageTrack$1 extends TrackItem {
|
|
10
|
+
contents() {
|
|
11
|
+
const image = this.element;
|
|
12
|
+
if (!(image instanceof EFImage)) return nothing;
|
|
13
|
+
const src = image.getAttribute("src") || image.src;
|
|
14
|
+
const trackWidth = (image.durationMs ?? 0) * this.pixelsPerMs;
|
|
15
|
+
const thumbnailHeight = 18;
|
|
16
|
+
const thumbnailWidth = Math.min(32, trackWidth);
|
|
17
|
+
if (!src || thumbnailWidth < 8) return nothing;
|
|
18
|
+
const thumbnailCount = Math.max(1, Math.floor((trackWidth - 8) / (thumbnailWidth + 2)));
|
|
19
|
+
return html`
|
|
20
|
+
<div style=${styleMap({
|
|
21
|
+
position: "absolute",
|
|
22
|
+
left: "4px",
|
|
23
|
+
right: "4px",
|
|
24
|
+
top: "2px",
|
|
25
|
+
bottom: "2px",
|
|
26
|
+
display: "flex",
|
|
27
|
+
alignItems: "center",
|
|
28
|
+
gap: "2px",
|
|
29
|
+
overflow: "hidden"
|
|
30
|
+
})}>
|
|
31
|
+
${Array.from({ length: thumbnailCount }, () => html`
|
|
32
|
+
<img
|
|
33
|
+
src="${src}"
|
|
34
|
+
alt=""
|
|
35
|
+
style=${styleMap({
|
|
36
|
+
height: `${thumbnailHeight}px`,
|
|
37
|
+
width: `${thumbnailWidth}px`,
|
|
38
|
+
objectFit: "cover",
|
|
39
|
+
borderRadius: "2px",
|
|
40
|
+
opacity: "0.75",
|
|
41
|
+
pointerEvents: "none",
|
|
42
|
+
flexShrink: "0"
|
|
43
|
+
})}
|
|
44
|
+
/>
|
|
45
|
+
`)}
|
|
46
|
+
</div>
|
|
47
|
+
`;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
EFImageTrack = __decorate([customElement("ef-image-track")], EFImageTrack);
|
|
51
|
+
|
|
52
|
+
//#endregion
|
|
53
|
+
//# sourceMappingURL=ImageTrack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageTrack.js","names":["EFImageTrack"],"sources":["../../../../src/gui/timeline/tracks/ImageTrack.ts"],"sourcesContent":["import { html, nothing } from \"lit\";\nimport { customElement } from \"lit/decorators.js\";\nimport { styleMap } from \"lit/directives/style-map.js\";\nimport { EFImage } from \"../../../elements/EFImage.js\";\n// TrackItem must be pre-loaded before this module is imported\n// See preloadTracks.ts for the initialization sequence\nimport { TrackItem } from \"./TrackItem.js\";\n\n@customElement(\"ef-image-track\")\nexport class EFImageTrack extends TrackItem {\n contents() {\n const image = this.element as EFImage;\n if (!(image instanceof EFImage)) {\n return nothing;\n }\n\n // Try to get image src for thumbnail preview\n const src = image.getAttribute(\"src\") || (image as any).src;\n \n // Calculate track dimensions - show repeating thumbnails to fill the track\n const durationMs = image.durationMs ?? 0;\n const trackWidth = durationMs * this.pixelsPerMs;\n const thumbnailHeight = 18;\n const thumbnailWidth = Math.min(32, trackWidth);\n\n if (!src || thumbnailWidth < 8) {\n return nothing;\n }\n\n // Calculate how many thumbnails can fit\n const thumbnailCount = Math.max(1, Math.floor((trackWidth - 8) / (thumbnailWidth + 2)));\n\n return html`\n <div style=${styleMap({\n position: \"absolute\",\n left: \"4px\",\n right: \"4px\",\n top: \"2px\",\n bottom: \"2px\",\n display: \"flex\",\n alignItems: \"center\",\n gap: \"2px\",\n overflow: \"hidden\",\n })}>\n ${Array.from({ length: thumbnailCount }, () => html`\n <img\n src=\"${src}\"\n alt=\"\"\n style=${styleMap({\n height: `${thumbnailHeight}px`,\n width: `${thumbnailWidth}px`,\n objectFit: \"cover\",\n borderRadius: \"2px\",\n opacity: \"0.75\",\n pointerEvents: \"none\",\n flexShrink: \"0\",\n })}\n />\n `)}\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-image-track\": EFImageTrack;\n }\n}\n\n"],"mappings":";;;;;;;;AASO,yBAAMA,uBAAqB,UAAU;CAC1C,WAAW;EACT,MAAM,QAAQ,KAAK;AACnB,MAAI,EAAE,iBAAiB,SACrB,QAAO;EAIT,MAAM,MAAM,MAAM,aAAa,MAAM,IAAK,MAAc;EAIxD,MAAM,cADa,MAAM,cAAc,KACP,KAAK;EACrC,MAAM,kBAAkB;EACxB,MAAM,iBAAiB,KAAK,IAAI,IAAI,WAAW;AAE/C,MAAI,CAAC,OAAO,iBAAiB,EAC3B,QAAO;EAIT,MAAM,iBAAiB,KAAK,IAAI,GAAG,KAAK,OAAO,aAAa,MAAM,iBAAiB,GAAG,CAAC;AAEvF,SAAO,IAAI;mBACI,SAAS;GACpB,UAAU;GACV,MAAM;GACN,OAAO;GACP,KAAK;GACL,QAAQ;GACR,SAAS;GACT,YAAY;GACZ,KAAK;GACL,UAAU;GACX,CAAC,CAAC;UACC,MAAM,KAAK,EAAE,QAAQ,gBAAgB,QAAQ,IAAI;;mBAExC,IAAI;;oBAEH,SAAS;GACf,QAAQ,GAAG,gBAAgB;GAC3B,OAAO,GAAG,eAAe;GACzB,WAAW;GACX,cAAc;GACd,SAAS;GACT,eAAe;GACf,YAAY;GACb,CAAC,CAAC;;UAEL,CAAC;;;;;2BAlDV,cAAc,iBAAiB"}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { __decorate } from "../../../_virtual/_@oxc-project_runtime@0.94.0/helpers/decorate.js";
|
|
2
|
+
import { TrackItem } from "./TrackItem.js";
|
|
3
|
+
import { renderTrackChildren } from "./renderTrackChildren.js";
|
|
4
|
+
import { css, html, nothing } from "lit";
|
|
5
|
+
import { customElement } from "lit/decorators.js";
|
|
6
|
+
import { styleMap } from "lit/directives/style-map.js";
|
|
7
|
+
|
|
8
|
+
//#region src/gui/timeline/tracks/TextTrack.ts
|
|
9
|
+
/**
|
|
10
|
+
* Controller to ensure text track updates reactively during playback.
|
|
11
|
+
*/
|
|
12
|
+
var TextTimeController = class TextTimeController {
|
|
13
|
+
#animationFrameId;
|
|
14
|
+
#lastTimeMs = -1;
|
|
15
|
+
static #MIN_TIME_CHANGE_MS = 50;
|
|
16
|
+
constructor(host) {
|
|
17
|
+
this.host = host;
|
|
18
|
+
this.host.addController(this);
|
|
19
|
+
}
|
|
20
|
+
hostConnected() {
|
|
21
|
+
this.#startTimeUpdate();
|
|
22
|
+
}
|
|
23
|
+
hostDisconnected() {
|
|
24
|
+
this.#stopTimeUpdate();
|
|
25
|
+
}
|
|
26
|
+
#startTimeUpdate() {
|
|
27
|
+
const update = () => {
|
|
28
|
+
const currentTimeMs = (this.host.element?.rootTimegroup)?.currentTimeMs || 0;
|
|
29
|
+
if (Math.abs(currentTimeMs - this.#lastTimeMs) >= TextTimeController.#MIN_TIME_CHANGE_MS) {
|
|
30
|
+
this.#lastTimeMs = currentTimeMs;
|
|
31
|
+
this.host.requestUpdate();
|
|
32
|
+
}
|
|
33
|
+
this.#animationFrameId = requestAnimationFrame(update);
|
|
34
|
+
};
|
|
35
|
+
update();
|
|
36
|
+
}
|
|
37
|
+
#stopTimeUpdate() {
|
|
38
|
+
if (this.#animationFrameId) {
|
|
39
|
+
cancelAnimationFrame(this.#animationFrameId);
|
|
40
|
+
this.#animationFrameId = void 0;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
let EFTextTrack = class EFTextTrack$1 extends TrackItem {
|
|
45
|
+
#timeController = new TextTimeController(this);
|
|
46
|
+
static {
|
|
47
|
+
this.styles = [...TrackItem.styles, css`
|
|
48
|
+
.text-segment-block {
|
|
49
|
+
position: absolute;
|
|
50
|
+
height: calc(100% - 4px);
|
|
51
|
+
top: 2px;
|
|
52
|
+
display: flex;
|
|
53
|
+
align-items: center;
|
|
54
|
+
padding: 0 4px;
|
|
55
|
+
border-radius: 2px;
|
|
56
|
+
overflow: hidden;
|
|
57
|
+
background: rgba(249, 115, 22, 0.15);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.text-segment-block.active {
|
|
61
|
+
background: rgba(249, 115, 22, 0.35);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.text-segment-block:hover {
|
|
65
|
+
z-index: 100;
|
|
66
|
+
overflow: visible;
|
|
67
|
+
width: max-content !important;
|
|
68
|
+
min-width: max-content;
|
|
69
|
+
background: rgba(80, 50, 30, 0.95);
|
|
70
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.segment-text {
|
|
74
|
+
font-size: 10px;
|
|
75
|
+
white-space: nowrap;
|
|
76
|
+
overflow: hidden;
|
|
77
|
+
text-overflow: ellipsis;
|
|
78
|
+
color: rgba(255, 255, 255, 0.85);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.text-segment-block:hover .segment-text {
|
|
82
|
+
overflow: visible;
|
|
83
|
+
text-overflow: clip;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.text-segment-block.active .segment-text {
|
|
87
|
+
font-weight: 500;
|
|
88
|
+
color: white;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Compact mode - single block for full text */
|
|
92
|
+
.text-compact-block {
|
|
93
|
+
position: absolute;
|
|
94
|
+
left: 4px;
|
|
95
|
+
right: 4px;
|
|
96
|
+
top: 2px;
|
|
97
|
+
bottom: 2px;
|
|
98
|
+
display: flex;
|
|
99
|
+
align-items: center;
|
|
100
|
+
padding: 0 4px;
|
|
101
|
+
overflow: hidden;
|
|
102
|
+
border-radius: 2px;
|
|
103
|
+
background: rgba(249, 115, 22, 0.1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.text-compact-block:hover {
|
|
107
|
+
overflow: visible;
|
|
108
|
+
z-index: 100;
|
|
109
|
+
width: max-content;
|
|
110
|
+
background: rgba(80, 50, 30, 0.95);
|
|
111
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
|
|
112
|
+
}
|
|
113
|
+
`];
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get the text content - from segments, direct text nodes, or element textContent
|
|
117
|
+
*/
|
|
118
|
+
#getTextContent(segments) {
|
|
119
|
+
const text = this.element;
|
|
120
|
+
if (segments.length > 0) return segments.map((s) => s.segmentText).join(" ");
|
|
121
|
+
const directText = Array.from(text.childNodes).filter((node) => node.nodeType === Node.TEXT_NODE).map((node) => node.textContent?.trim()).filter(Boolean).join(" ");
|
|
122
|
+
if (directText) return directText;
|
|
123
|
+
return text.textContent?.trim() || "";
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Check if segments can fit individually based on track width
|
|
127
|
+
*/
|
|
128
|
+
#canShowSegmentsIndividually(segments, trackWidthPx) {
|
|
129
|
+
if (segments.length === 0) return false;
|
|
130
|
+
return trackWidthPx >= segments.length * 20;
|
|
131
|
+
}
|
|
132
|
+
render() {
|
|
133
|
+
const text = this.element;
|
|
134
|
+
const segments = Array.from(text.querySelectorAll("ef-text-segment"));
|
|
135
|
+
const textContent = this.#getTextContent(segments);
|
|
136
|
+
const durationMs = text.durationMs ?? 0;
|
|
137
|
+
const trackWidthPx = durationMs * this.pixelsPerMs;
|
|
138
|
+
const canShowSegments = this.#canShowSegmentsIndividually(segments, trackWidthPx);
|
|
139
|
+
return html`<div style=${styleMap(this.gutterStyles)}>
|
|
140
|
+
<div
|
|
141
|
+
class="relative"
|
|
142
|
+
style="background-color: var(--filmstrip-bg);"
|
|
143
|
+
?data-focused=${this.isFocused}
|
|
144
|
+
@mouseenter=${() => {
|
|
145
|
+
if (this.focusContext) this.focusContext.focusedElement = this.element;
|
|
146
|
+
}}
|
|
147
|
+
@mouseleave=${() => {
|
|
148
|
+
if (this.focusContext) this.focusContext.focusedElement = null;
|
|
149
|
+
}}
|
|
150
|
+
>
|
|
151
|
+
<div
|
|
152
|
+
?data-focused=${this.isFocused}
|
|
153
|
+
class="relative mb-[1px] block h-[1.1rem] text-nowrap border text-sm overflow-visible"
|
|
154
|
+
style=${styleMap({
|
|
155
|
+
...this.trimPortionStyles,
|
|
156
|
+
backgroundColor: this.isFocused ? "var(--filmstrip-item-focused)" : "var(--filmstrip-item-bg)",
|
|
157
|
+
borderColor: "var(--filmstrip-border)",
|
|
158
|
+
borderLeftColor: this.getElementTypeColor(),
|
|
159
|
+
borderLeftWidth: "3px"
|
|
160
|
+
})}
|
|
161
|
+
>
|
|
162
|
+
${segments.length > 0 && canShowSegments ? this.#renderSegments(segments, durationMs) : this.#renderCompactText(textContent)}
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
${this.renderChildren()}
|
|
166
|
+
</div>`;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Render segments as positioned blocks (like captions)
|
|
170
|
+
*/
|
|
171
|
+
#renderSegments(segments, durationMs) {
|
|
172
|
+
const text = this.element;
|
|
173
|
+
const textLocalTimeMs = (text.rootTimegroup?.currentTimeMs || 0) - text.startTimeMs;
|
|
174
|
+
return segments.map((segment, index) => {
|
|
175
|
+
const staggerOffset = segment.staggerOffsetMs ?? 0;
|
|
176
|
+
const isActive = textLocalTimeMs >= staggerOffset;
|
|
177
|
+
const segmentWidthMs = (segments[index + 1]?.staggerOffsetMs ?? durationMs) - staggerOffset;
|
|
178
|
+
const segmentWidthPx = Math.max(this.pixelsPerMs * segmentWidthMs, 18);
|
|
179
|
+
return html`<div
|
|
180
|
+
class="text-segment-block ${isActive ? "active" : ""}"
|
|
181
|
+
style=${styleMap({
|
|
182
|
+
left: `${this.pixelsPerMs * staggerOffset}px`,
|
|
183
|
+
width: `${segmentWidthPx}px`
|
|
184
|
+
})}
|
|
185
|
+
title="${segment.segmentText}"
|
|
186
|
+
>
|
|
187
|
+
<span class="segment-text">${segment.segmentText}</span>
|
|
188
|
+
</div>`;
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Render compact text (no segments or not enough space)
|
|
193
|
+
*/
|
|
194
|
+
#renderCompactText(textContent) {
|
|
195
|
+
if (!textContent) return nothing;
|
|
196
|
+
return html`
|
|
197
|
+
<div class="text-compact-block">
|
|
198
|
+
<span class="segment-text">${textContent}</span>
|
|
199
|
+
</div>
|
|
200
|
+
`;
|
|
201
|
+
}
|
|
202
|
+
renderChildren() {
|
|
203
|
+
const nonSegmentChildren = Array.from(this.element.children).filter((child) => child.tagName?.toUpperCase() !== "EF-TEXT-SEGMENT");
|
|
204
|
+
if (nonSegmentChildren.length === 0) return nothing;
|
|
205
|
+
return renderTrackChildren(nonSegmentChildren, this.pixelsPerMs, this.hideSelectors, this.showSelectors, false, this.enableTrim);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
EFTextTrack = __decorate([customElement("ef-text-track")], EFTextTrack);
|
|
209
|
+
let EFTextSegmentTrack = class EFTextSegmentTrack$1 extends TrackItem {
|
|
210
|
+
get textTrackStyles() {
|
|
211
|
+
const parentText = this.element.closest("ef-text");
|
|
212
|
+
return {
|
|
213
|
+
position: "relative",
|
|
214
|
+
left: `${this.pixelsPerMs * (parentText?.startTimeWithinParentMs || 0)}px`,
|
|
215
|
+
width: `${this.pixelsPerMs * (parentText?.durationMs || 0)}px`
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
render() {
|
|
219
|
+
const segment = this.element;
|
|
220
|
+
const parentText = segment.closest("ef-text");
|
|
221
|
+
if (!parentText) return html`<div style=${styleMap(this.textTrackStyles)}>
|
|
222
|
+
<div class="border h-[1.1rem] mb-[1px] text-xs" style="background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);">
|
|
223
|
+
</div>
|
|
224
|
+
</div>`;
|
|
225
|
+
const textLocalTimeMs = (parentText.rootTimegroup?.currentTimeMs || 0) - parentText.startTimeMs;
|
|
226
|
+
const isCurrentlyActive = textLocalTimeMs >= segment.segmentStartMs && textLocalTimeMs < segment.segmentEndMs;
|
|
227
|
+
return html`<div style=${styleMap(this.textTrackStyles)}>
|
|
228
|
+
<div class="relative border h-[1.1rem] mb-[1px] w-full" style="background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);">
|
|
229
|
+
<div
|
|
230
|
+
class="absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? "font-bold z-[5]" : ""}"
|
|
231
|
+
style=${styleMap({
|
|
232
|
+
left: `${this.pixelsPerMs * segment.segmentStartMs}px`,
|
|
233
|
+
width: `${this.pixelsPerMs * (segment.segmentEndMs - segment.segmentStartMs)}px`,
|
|
234
|
+
height: "100%",
|
|
235
|
+
top: "0px",
|
|
236
|
+
backgroundColor: isCurrentlyActive ? "var(--filmstrip-caption-bg)" : "var(--filmstrip-item-bg)",
|
|
237
|
+
borderColor: isCurrentlyActive ? "var(--filmstrip-caption-border)" : "var(--filmstrip-border)"
|
|
238
|
+
})}
|
|
239
|
+
title="Segment: '${segment.segmentText}' (${segment.segmentStartMs}ms - ${segment.segmentEndMs}ms)"
|
|
240
|
+
>
|
|
241
|
+
${isCurrentlyActive ? html`<span class="px-0.5 text-[8px] font-bold whitespace-nowrap" style="background-color: var(--filmstrip-caption-bg);">${segment.segmentText}</span>` : ""}
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
</div>`;
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
EFTextSegmentTrack = __decorate([customElement("ef-text-segment-track")], EFTextSegmentTrack);
|
|
248
|
+
|
|
249
|
+
//#endregion
|
|
250
|
+
//# sourceMappingURL=TextTrack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TextTrack.js","names":["#MIN_TIME_CHANGE_MS","host: EFTextTrack","#startTimeUpdate","#stopTimeUpdate","#lastTimeMs","#animationFrameId","EFTextTrack","#getTextContent","#canShowSegmentsIndividually","#renderSegments","#renderCompactText","EFTextSegmentTrack"],"sources":["../../../../src/gui/timeline/tracks/TextTrack.ts"],"sourcesContent":["import { css, html, nothing, type TemplateResult } from \"lit\";\nimport { customElement } from \"lit/decorators.js\";\nimport { styleMap } from \"lit/directives/style-map.js\";\nimport type { ReactiveController } from \"lit\";\nimport { EFText } from \"../../../elements/EFText.js\";\nimport { EFTextSegment } from \"../../../elements/EFTextSegment.js\";\n// TrackItem must be pre-loaded before this module is imported\n// See preloadTracks.ts for the initialization sequence\nimport { TrackItem } from \"./TrackItem.js\";\nimport { renderTrackChildren } from \"./renderTrackChildren.js\";\n\n/**\n * Controller to ensure text track updates reactively during playback.\n */\nclass TextTimeController implements ReactiveController {\n #animationFrameId?: number;\n #lastTimeMs = -1;\n static readonly #MIN_TIME_CHANGE_MS = 50;\n \n constructor(private host: EFTextTrack) {\n this.host.addController(this);\n }\n \n hostConnected(): void {\n this.#startTimeUpdate();\n }\n \n hostDisconnected(): void {\n this.#stopTimeUpdate();\n }\n \n #startTimeUpdate(): void {\n const update = () => {\n const text = this.host.element as EFText;\n const rootTimegroup = text?.rootTimegroup;\n const currentTimeMs = rootTimegroup?.currentTimeMs || 0;\n \n // Update if time changed significantly\n const timeDelta = Math.abs(currentTimeMs - this.#lastTimeMs);\n if (timeDelta >= TextTimeController.#MIN_TIME_CHANGE_MS) {\n this.#lastTimeMs = currentTimeMs;\n this.host.requestUpdate();\n }\n \n this.#animationFrameId = requestAnimationFrame(update);\n };\n update();\n }\n \n #stopTimeUpdate(): void {\n if (this.#animationFrameId) {\n cancelAnimationFrame(this.#animationFrameId);\n this.#animationFrameId = undefined;\n }\n }\n}\n\n@customElement(\"ef-text-track\")\nexport class EFTextTrack extends TrackItem {\n // Controller ensures real-time updates during playback\n #timeController = new TextTimeController(this);\n static styles = [\n ...TrackItem.styles,\n css`\n .text-segment-block {\n position: absolute;\n height: calc(100% - 4px);\n top: 2px;\n display: flex;\n align-items: center;\n padding: 0 4px;\n border-radius: 2px;\n overflow: hidden;\n background: rgba(249, 115, 22, 0.15);\n }\n \n .text-segment-block.active {\n background: rgba(249, 115, 22, 0.35);\n }\n \n .text-segment-block:hover {\n z-index: 100;\n overflow: visible;\n width: max-content !important;\n min-width: max-content;\n background: rgba(80, 50, 30, 0.95);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);\n }\n \n .segment-text {\n font-size: 10px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: rgba(255, 255, 255, 0.85);\n }\n \n .text-segment-block:hover .segment-text {\n overflow: visible;\n text-overflow: clip;\n }\n \n .text-segment-block.active .segment-text {\n font-weight: 500;\n color: white;\n }\n \n /* Compact mode - single block for full text */\n .text-compact-block {\n position: absolute;\n left: 4px;\n right: 4px;\n top: 2px;\n bottom: 2px;\n display: flex;\n align-items: center;\n padding: 0 4px;\n overflow: hidden;\n border-radius: 2px;\n background: rgba(249, 115, 22, 0.1);\n }\n \n .text-compact-block:hover {\n overflow: visible;\n z-index: 100;\n width: max-content;\n background: rgba(80, 50, 30, 0.95);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);\n }\n `,\n ];\n\n /**\n * Get the text content - from segments, direct text nodes, or element textContent\n */\n #getTextContent(segments: EFTextSegment[]): string {\n const text = this.element as EFText;\n \n // If there are segments, use their text\n if (segments.length > 0) {\n return segments.map(s => s.segmentText).join(\" \");\n }\n \n // Try direct text nodes (excluding templates and other elements)\n const directText = Array.from(text.childNodes)\n .filter(node => node.nodeType === Node.TEXT_NODE)\n .map(node => node.textContent?.trim())\n .filter(Boolean)\n .join(\" \");\n \n if (directText) return directText;\n \n // Ultimate fallback: use the element's full text content\n // (excluding script/style content but including nested text)\n const fullText = text.textContent?.trim() || \"\";\n return fullText;\n }\n\n /**\n * Check if segments can fit individually based on track width\n */\n #canShowSegmentsIndividually(segments: EFTextSegment[], trackWidthPx: number): boolean {\n if (segments.length === 0) return false;\n // Need at least 20px per segment\n return trackWidthPx >= segments.length * 20;\n }\n\n render() {\n const text = this.element as EFText;\n const segments = Array.from(text.querySelectorAll(\"ef-text-segment\")) as EFTextSegment[];\n const textContent = this.#getTextContent(segments);\n const durationMs = text.durationMs ?? 0;\n const trackWidthPx = durationMs * this.pixelsPerMs;\n const canShowSegments = this.#canShowSegmentsIndividually(segments, trackWidthPx);\n\n return html`<div style=${styleMap(this.gutterStyles)}>\n <div\n class=\"relative\"\n style=\"background-color: var(--filmstrip-bg);\"\n ?data-focused=${this.isFocused}\n @mouseenter=${() => {\n if (this.focusContext) {\n this.focusContext.focusedElement = this.element;\n }\n }}\n @mouseleave=${() => {\n if (this.focusContext) {\n this.focusContext.focusedElement = null;\n }\n }}\n >\n <div\n ?data-focused=${this.isFocused}\n class=\"relative mb-[1px] block h-[1.1rem] text-nowrap border text-sm overflow-visible\"\n style=${styleMap({\n ...this.trimPortionStyles,\n backgroundColor: this.isFocused\n ? \"var(--filmstrip-item-focused)\"\n : \"var(--filmstrip-item-bg)\",\n borderColor: \"var(--filmstrip-border)\",\n borderLeftColor: this.getElementTypeColor(),\n borderLeftWidth: \"3px\",\n })}\n >\n ${segments.length > 0 && canShowSegments\n ? this.#renderSegments(segments, durationMs)\n : this.#renderCompactText(textContent)}\n </div>\n </div>\n ${this.renderChildren()}\n </div>`;\n }\n\n /**\n * Render segments as positioned blocks (like captions)\n */\n #renderSegments(segments: EFTextSegment[], durationMs: number) {\n const text = this.element as EFText;\n const rootTimegroup = text.rootTimegroup;\n const currentTimeMs = rootTimegroup?.currentTimeMs || 0;\n const textLocalTimeMs = currentTimeMs - text.startTimeMs;\n\n return segments.map((segment, index) => {\n const staggerOffset = segment.staggerOffsetMs ?? 0;\n // Segment becomes active at its stagger offset\n const isActive = textLocalTimeMs >= staggerOffset;\n \n // Calculate segment width - distribute evenly or use stagger spacing\n const nextSegment = segments[index + 1];\n const nextStagger = nextSegment?.staggerOffsetMs ?? durationMs;\n const segmentWidthMs = nextStagger - staggerOffset;\n const segmentWidthPx = Math.max(this.pixelsPerMs * segmentWidthMs, 18);\n\n return html`<div\n class=\"text-segment-block ${isActive ? \"active\" : \"\"}\"\n style=${styleMap({\n left: `${this.pixelsPerMs * staggerOffset}px`,\n width: `${segmentWidthPx}px`,\n })}\n title=\"${segment.segmentText}\"\n >\n <span class=\"segment-text\">${segment.segmentText}</span>\n </div>`;\n });\n }\n\n /**\n * Render compact text (no segments or not enough space)\n */\n #renderCompactText(textContent: string) {\n if (!textContent) return nothing;\n \n return html`\n <div class=\"text-compact-block\">\n <span class=\"segment-text\">${textContent}</span>\n </div>\n `;\n }\n\n renderChildren(): Array<TemplateResult<1> | typeof nothing> | typeof nothing {\n const nonSegmentChildren = Array.from(this.element.children).filter(\n (child) => child.tagName?.toUpperCase() !== \"EF-TEXT-SEGMENT\"\n );\n \n if (nonSegmentChildren.length === 0) {\n return nothing;\n }\n \n return renderTrackChildren(\n nonSegmentChildren,\n this.pixelsPerMs,\n this.hideSelectors,\n this.showSelectors,\n false,\n this.enableTrim,\n );\n }\n}\n\n@customElement(\"ef-text-segment-track\")\nexport class EFTextSegmentTrack extends TrackItem {\n get textTrackStyles() {\n const parentText = this.element.closest(\"ef-text\") as EFText;\n return {\n position: \"relative\",\n left: `${this.pixelsPerMs * (parentText?.startTimeWithinParentMs || 0)}px`,\n width: `${this.pixelsPerMs * (parentText?.durationMs || 0)}px`,\n };\n }\n\n render() {\n const segment = this.element as EFTextSegment;\n const parentText = segment.closest(\"ef-text\") as EFText;\n\n if (!parentText) {\n return html`<div style=${styleMap(this.textTrackStyles)}>\n <div class=\"border h-[1.1rem] mb-[1px] text-xs\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n </div>\n </div>`;\n }\n\n const rootTimegroup = parentText.rootTimegroup;\n const currentTimeMs = rootTimegroup?.currentTimeMs || 0;\n const textLocalTimeMs = currentTimeMs - parentText.startTimeMs;\n\n const isCurrentlyActive =\n textLocalTimeMs >= segment.segmentStartMs &&\n textLocalTimeMs < segment.segmentEndMs;\n\n return html`<div style=${styleMap(this.textTrackStyles)}>\n <div class=\"relative border h-[1.1rem] mb-[1px] w-full\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n <div\n class=\"absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? \"font-bold z-[5]\" : \"\"}\"\n style=${styleMap({\n left: `${this.pixelsPerMs * segment.segmentStartMs}px`,\n width: `${this.pixelsPerMs * (segment.segmentEndMs - segment.segmentStartMs)}px`,\n height: \"100%\",\n top: \"0px\",\n backgroundColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-bg)\"\n : \"var(--filmstrip-item-bg)\",\n borderColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-border)\"\n : \"var(--filmstrip-border)\",\n })}\n title=\"Segment: '${segment.segmentText}' (${segment.segmentStartMs}ms - ${segment.segmentEndMs}ms)\"\n >\n ${isCurrentlyActive ? html`<span class=\"px-0.5 text-[8px] font-bold whitespace-nowrap\" style=\"background-color: var(--filmstrip-caption-bg);\">${segment.segmentText}</span>` : \"\"}\n </div>\n </div>\n </div>`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-text-track\": EFTextTrack;\n \"ef-text-segment-track\": EFTextSegmentTrack;\n }\n}\n\n"],"mappings":"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"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { __decorate } from "../../../_virtual/_@oxc-project_runtime@0.94.0/helpers/decorate.js";
|
|
2
|
+
import { TrackItem } from "./TrackItem.js";
|
|
3
|
+
import "../../../elements/EFThumbnailStrip.js";
|
|
4
|
+
import { renderTrackChildren } from "./renderTrackChildren.js";
|
|
5
|
+
import { css, html, nothing } from "lit";
|
|
6
|
+
import { customElement, property } from "lit/decorators.js";
|
|
7
|
+
import { styleMap } from "lit/directives/style-map.js";
|
|
8
|
+
|
|
9
|
+
//#region src/gui/timeline/tracks/TimegroupTrack.ts
|
|
10
|
+
/**
|
|
11
|
+
* Check if a timegroup is a root timegroup (has no parent timegroup)
|
|
12
|
+
* Uses the timegroup's own isRootTimegroup property for reliability
|
|
13
|
+
*/
|
|
14
|
+
function isRootTimegroup(element) {
|
|
15
|
+
if (!element) return false;
|
|
16
|
+
const elem = element;
|
|
17
|
+
if (typeof elem.isRootTimegroup === "boolean") return elem.isRootTimegroup;
|
|
18
|
+
if (elem.parentTimegroup !== void 0) return !elem.parentTimegroup;
|
|
19
|
+
let parent = element.parentElement;
|
|
20
|
+
while (parent) {
|
|
21
|
+
if (parent.tagName.toLowerCase() === "ef-timegroup") return false;
|
|
22
|
+
parent = parent.parentElement;
|
|
23
|
+
}
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
/** Height for root timegroup filmstrip row */
|
|
27
|
+
const FILMSTRIP_ROW_HEIGHT = 48;
|
|
28
|
+
let EFTimegroupTrack = class EFTimegroupTrack$1 extends TrackItem {
|
|
29
|
+
constructor(..._args) {
|
|
30
|
+
super(..._args);
|
|
31
|
+
this.skipChildren = false;
|
|
32
|
+
this.showFilmstrip = false;
|
|
33
|
+
}
|
|
34
|
+
static {
|
|
35
|
+
this.styles = [...TrackItem.styles, css`
|
|
36
|
+
.trim-container {
|
|
37
|
+
background: linear-gradient(
|
|
38
|
+
135deg,
|
|
39
|
+
rgba(148, 163, 184, 0.1) 0%,
|
|
40
|
+
rgba(148, 163, 184, 0.05) 100%
|
|
41
|
+
) !important;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
:host(:hover) .trim-container {
|
|
45
|
+
background: linear-gradient(
|
|
46
|
+
135deg,
|
|
47
|
+
rgba(148, 163, 184, 0.15) 0%,
|
|
48
|
+
rgba(148, 163, 184, 0.08) 100%
|
|
49
|
+
) !important;
|
|
50
|
+
}
|
|
51
|
+
`];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Check if this track should show a filmstrip
|
|
55
|
+
*/
|
|
56
|
+
get shouldShowFilmstrip() {
|
|
57
|
+
const skipChildren = this.skipChildren;
|
|
58
|
+
const showFilmstrip = this.showFilmstrip;
|
|
59
|
+
const hasId = !!this.element?.id;
|
|
60
|
+
const isRoot = isRootTimegroup(this.element);
|
|
61
|
+
return skipChildren && showFilmstrip && hasId && isRoot;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Override trimPortionStyles to use taller height for filmstrip rows
|
|
65
|
+
*/
|
|
66
|
+
get trimPortionStyles() {
|
|
67
|
+
const baseStyles = super.trimPortionStyles;
|
|
68
|
+
if (this.shouldShowFilmstrip) {
|
|
69
|
+
const durationMs = this.element.durationMs ?? 0;
|
|
70
|
+
const calculatedWidth = this.pixelsPerMs * durationMs;
|
|
71
|
+
const minWidth = Math.max(calculatedWidth, 500);
|
|
72
|
+
return {
|
|
73
|
+
...baseStyles,
|
|
74
|
+
height: `${FILMSTRIP_ROW_HEIGHT}px`,
|
|
75
|
+
width: `${minWidth}px`,
|
|
76
|
+
minWidth: `${minWidth}px`
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return baseStyles;
|
|
80
|
+
}
|
|
81
|
+
contents() {
|
|
82
|
+
if (this.shouldShowFilmstrip) {
|
|
83
|
+
const durationMs = this.element.durationMs ?? 0;
|
|
84
|
+
const elementId = this.element.id;
|
|
85
|
+
if (!elementId) return nothing;
|
|
86
|
+
return html`
|
|
87
|
+
<ef-thumbnail-strip
|
|
88
|
+
target="${elementId}"
|
|
89
|
+
start-time-ms="0"
|
|
90
|
+
${durationMs > 0 ? html`end-time-ms="${durationMs}"` : nothing}
|
|
91
|
+
pixels-per-ms="${this.pixelsPerMs}"
|
|
92
|
+
></ef-thumbnail-strip>
|
|
93
|
+
`;
|
|
94
|
+
}
|
|
95
|
+
if (this.skipChildren) return nothing;
|
|
96
|
+
return html`${renderTrackChildren(Array.from(this.element.children || []), this.pixelsPerMs, this.hideSelectors, this.showSelectors, false, this.enableTrim)}`;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Override render to use taller height for filmstrip rows
|
|
100
|
+
*/
|
|
101
|
+
render() {
|
|
102
|
+
const trackHeight = this.shouldShowFilmstrip ? `${FILMSTRIP_ROW_HEIGHT}px` : "var(--timeline-track-height, 22px)";
|
|
103
|
+
return html`<div style=${styleMap(this.gutterStyles)}>
|
|
104
|
+
<div
|
|
105
|
+
style="background-color: var(--filmstrip-bg);"
|
|
106
|
+
?data-focused=${this.isFocused}
|
|
107
|
+
@mouseenter=${() => {
|
|
108
|
+
if (this.focusContext) this.focusContext.focusedElement = this.element;
|
|
109
|
+
}}
|
|
110
|
+
@mouseleave=${() => {
|
|
111
|
+
if (this.focusContext) this.focusContext.focusedElement = null;
|
|
112
|
+
}}
|
|
113
|
+
>
|
|
114
|
+
<div
|
|
115
|
+
?data-focused=${this.isFocused}
|
|
116
|
+
class="trim-container relative mb-0 block text-nowrap border text-sm"
|
|
117
|
+
style=${styleMap({
|
|
118
|
+
...this.trimPortionStyles,
|
|
119
|
+
height: trackHeight,
|
|
120
|
+
backgroundColor: this.isFocused ? "var(--filmstrip-item-focused)" : "var(--filmstrip-item-bg)",
|
|
121
|
+
borderColor: this.shouldShowFilmstrip ? "transparent" : "var(--filmstrip-border)"
|
|
122
|
+
})}
|
|
123
|
+
>
|
|
124
|
+
${this.animations()}
|
|
125
|
+
${this.contents()}
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
${this.renderChildren()}
|
|
129
|
+
</div>`;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
__decorate([property({
|
|
133
|
+
type: Boolean,
|
|
134
|
+
attribute: "skip-children"
|
|
135
|
+
})], EFTimegroupTrack.prototype, "skipChildren", void 0);
|
|
136
|
+
__decorate([property({
|
|
137
|
+
type: Boolean,
|
|
138
|
+
attribute: "show-filmstrip"
|
|
139
|
+
})], EFTimegroupTrack.prototype, "showFilmstrip", void 0);
|
|
140
|
+
EFTimegroupTrack = __decorate([customElement("ef-timegroup-track")], EFTimegroupTrack);
|
|
141
|
+
|
|
142
|
+
//#endregion
|
|
143
|
+
//# sourceMappingURL=TimegroupTrack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TimegroupTrack.js","names":["EFTimegroupTrack"],"sources":["../../../../src/gui/timeline/tracks/TimegroupTrack.ts"],"sourcesContent":["import { css, html, nothing } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { styleMap } from \"lit/directives/style-map.js\";\n// TrackItem must be pre-loaded before this module is imported\n// See preloadTracks.ts for the initialization sequence\nimport { TrackItem } from \"./TrackItem.js\";\nimport { renderTrackChildren } from \"./renderTrackChildren.js\";\nimport { EFTimegroup } from \"../../../elements/EFTimegroup.js\";\nimport \"../../../elements/EFThumbnailStrip.js\";\n\n/**\n * Check if a timegroup is a root timegroup (has no parent timegroup)\n * Uses the timegroup's own isRootTimegroup property for reliability\n */\nfunction isRootTimegroup(element: Element | null | undefined): boolean {\n // Handle null/undefined\n if (!element) {\n return false;\n }\n \n // Check if element has the isRootTimegroup property (most reliable)\n // EFTimegroup instances have this property that checks parentTimegroup\n const elem = element as any;\n if (typeof elem.isRootTimegroup === 'boolean') {\n return elem.isRootTimegroup;\n }\n \n // Alternative: check parentTimegroup property directly (EFTimegroup has this)\n if (elem.parentTimegroup !== undefined) {\n return !elem.parentTimegroup; // Root if no parent timegroup\n }\n \n // Fallback: check DOM parent tree (less reliable after DOM moves)\n let parent = element.parentElement;\n while (parent) {\n if (parent.tagName.toLowerCase() === \"ef-timegroup\") {\n return false;\n }\n parent = parent.parentElement;\n }\n return true;\n}\n\n/** Height for root timegroup filmstrip row */\nconst FILMSTRIP_ROW_HEIGHT = 48;\n\n@customElement(\"ef-timegroup-track\")\nexport class EFTimegroupTrack extends TrackItem {\n static styles = [\n ...TrackItem.styles,\n css`\n .trim-container {\n background: linear-gradient(\n 135deg,\n rgba(148, 163, 184, 0.1) 0%,\n rgba(148, 163, 184, 0.05) 100%\n ) !important;\n }\n \n :host(:hover) .trim-container {\n background: linear-gradient(\n 135deg,\n rgba(148, 163, 184, 0.15) 0%,\n rgba(148, 163, 184, 0.08) 100%\n ) !important;\n }\n `,\n ];\n\n /**\n * When true, children are not rendered (used in unified row architecture\n * where children get their own rows).\n */\n @property({ type: Boolean, attribute: \"skip-children\" })\n skipChildren = false;\n\n /**\n * When true, show filmstrip thumbnails for root timegroups\n * TODO: Re-enable when thumbnail strip performance is improved\n */\n @property({ type: Boolean, attribute: \"show-filmstrip\" })\n showFilmstrip = false;\n\n /**\n * Check if this track should show a filmstrip\n */\n private get shouldShowFilmstrip(): boolean {\n const skipChildren = this.skipChildren;\n const showFilmstrip = this.showFilmstrip;\n const hasId = !!this.element?.id;\n const isRoot = isRootTimegroup(this.element);\n \n return skipChildren && showFilmstrip && hasId && isRoot;\n }\n\n /**\n * Override trimPortionStyles to use taller height for filmstrip rows\n */\n override get trimPortionStyles() {\n const baseStyles = super.trimPortionStyles;\n if (this.shouldShowFilmstrip) {\n // Ensure minimum width for filmstrip tracks so thumbnail strip can render\n // even when duration is 0 (e.g., sequence mode timegroups before children load)\n const durationMs = this.element.durationMs ?? 0;\n const calculatedWidth = this.pixelsPerMs * durationMs;\n const minWidth = Math.max(calculatedWidth, 500); // Minimum 500px for thumbnail strip visibility\n \n return {\n ...baseStyles,\n height: `${FILMSTRIP_ROW_HEIGHT}px`,\n width: `${minWidth}px`,\n minWidth: `${minWidth}px`, // Ensure minimum width is enforced\n };\n }\n return baseStyles;\n }\n\n contents() {\n // Show filmstrip only for ROOT timegroups (no parent timegroup)\n const shouldShow = this.shouldShowFilmstrip;\n \n if (shouldShow) {\n // Don't set end-time-ms if duration is 0 - let thumbnail strip use target's duration directly\n // This allows the thumbnail strip to watch for duration changes and update automatically\n const durationMs = this.element.durationMs ?? 0;\n const elementId = this.element.id;\n \n if (!elementId) {\n return nothing;\n }\n \n return html`\n <ef-thumbnail-strip\n target=\"${elementId}\"\n start-time-ms=\"0\"\n ${durationMs > 0 ? html`end-time-ms=\"${durationMs}\"` : nothing}\n pixels-per-ms=\"${this.pixelsPerMs}\"\n ></ef-thumbnail-strip>\n `;\n }\n\n // Mode info is now shown in the label, track is empty for non-root timegroups\n if (this.skipChildren) {\n return nothing;\n }\n // Wrap children in a fragment for consistent return type\n return html`${renderTrackChildren(\n Array.from(this.element.children || []),\n this.pixelsPerMs,\n this.hideSelectors,\n this.showSelectors,\n false,\n this.enableTrim,\n )}`;\n }\n\n /**\n * Override render to use taller height for filmstrip rows\n */\n override render() {\n // Use custom height for filmstrip, standard height otherwise\n const trackHeight = this.shouldShowFilmstrip \n ? `${FILMSTRIP_ROW_HEIGHT}px` \n : \"var(--timeline-track-height, 22px)\";\n\n return html`<div style=${styleMap(this.gutterStyles)}>\n <div\n style=\"background-color: var(--filmstrip-bg);\"\n ?data-focused=${this.isFocused}\n @mouseenter=${() => {\n if (this.focusContext) {\n this.focusContext.focusedElement = this.element;\n }\n }}\n @mouseleave=${() => {\n if (this.focusContext) {\n this.focusContext.focusedElement = null;\n }\n }}\n >\n <div\n ?data-focused=${this.isFocused}\n class=\"trim-container relative mb-0 block text-nowrap border text-sm\"\n style=${styleMap({\n ...this.trimPortionStyles,\n height: trackHeight,\n backgroundColor: this.isFocused\n ? \"var(--filmstrip-item-focused)\"\n : \"var(--filmstrip-item-bg)\",\n borderColor: this.shouldShowFilmstrip ? \"transparent\" : \"var(--filmstrip-border)\",\n })}\n >\n ${this.animations()}\n ${this.contents()}\n </div>\n </div>\n ${this.renderChildren()}\n </div>`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-timegroup-track\": EFTimegroupTrack;\n }\n}\n\n"],"mappings":";;;;;;;;;;;;;AAcA,SAAS,gBAAgB,SAA8C;AAErE,KAAI,CAAC,QACH,QAAO;CAKT,MAAM,OAAO;AACb,KAAI,OAAO,KAAK,oBAAoB,UAClC,QAAO,KAAK;AAId,KAAI,KAAK,oBAAoB,OAC3B,QAAO,CAAC,KAAK;CAIf,IAAI,SAAS,QAAQ;AACrB,QAAO,QAAQ;AACb,MAAI,OAAO,QAAQ,aAAa,KAAK,eACnC,QAAO;AAET,WAAS,OAAO;;AAElB,QAAO;;;AAIT,MAAM,uBAAuB;AAGtB,6BAAMA,2BAAyB,UAAU;;;sBA2B/B;uBAOC;;;gBAjCA,CACd,GAAG,UAAU,QACb,GAAG;;;;;;;;;;;;;;;;MAiBJ;;;;;CAmBD,IAAY,sBAA+B;EACzC,MAAM,eAAe,KAAK;EAC1B,MAAM,gBAAgB,KAAK;EAC3B,MAAM,QAAQ,CAAC,CAAC,KAAK,SAAS;EAC9B,MAAM,SAAS,gBAAgB,KAAK,QAAQ;AAE5C,SAAO,gBAAgB,iBAAiB,SAAS;;;;;CAMnD,IAAa,oBAAoB;EAC/B,MAAM,aAAa,MAAM;AACzB,MAAI,KAAK,qBAAqB;GAG5B,MAAM,aAAa,KAAK,QAAQ,cAAc;GAC9C,MAAM,kBAAkB,KAAK,cAAc;GAC3C,MAAM,WAAW,KAAK,IAAI,iBAAiB,IAAI;AAE/C,UAAO;IACL,GAAG;IACH,QAAQ,GAAG,qBAAqB;IAChC,OAAO,GAAG,SAAS;IACnB,UAAU,GAAG,SAAS;IACvB;;AAEH,SAAO;;CAGT,WAAW;AAIT,MAFmB,KAAK,qBAER;GAGd,MAAM,aAAa,KAAK,QAAQ,cAAc;GAC9C,MAAM,YAAY,KAAK,QAAQ;AAE/B,OAAI,CAAC,UACH,QAAO;AAGT,UAAO,IAAI;;oBAEG,UAAU;;YAElB,aAAa,IAAI,IAAI,gBAAgB,WAAW,KAAK,QAAQ;2BAC9C,KAAK,YAAY;;;;AAMxC,MAAI,KAAK,aACP,QAAO;AAGT,SAAO,IAAI,GAAG,oBACZ,MAAM,KAAK,KAAK,QAAQ,YAAY,EAAE,CAAC,EACvC,KAAK,aACL,KAAK,eACL,KAAK,eACL,OACA,KAAK,WACN;;;;;CAMH,AAAS,SAAS;EAEhB,MAAM,cAAc,KAAK,sBACrB,GAAG,qBAAqB,MACxB;AAEJ,SAAO,IAAI,cAAc,SAAS,KAAK,aAAa,CAAC;;;wBAGjC,KAAK,UAAU;4BACX;AAClB,OAAI,KAAK,aACP,MAAK,aAAa,iBAAiB,KAAK;IAE1C;4BACkB;AAClB,OAAI,KAAK,aACP,MAAK,aAAa,iBAAiB;IAErC;;;0BAGgB,KAAK,UAAU;;kBAEvB,SAAS;GACf,GAAG,KAAK;GACR,QAAQ;GACR,iBAAiB,KAAK,YAClB,kCACA;GACJ,aAAa,KAAK,sBAAsB,gBAAgB;GACzD,CAAC,CAAC;;YAED,KAAK,YAAY,CAAC;YAClB,KAAK,UAAU,CAAC;;;QAGpB,KAAK,gBAAgB,CAAC;;;;YA3H3B,SAAS;CAAE,MAAM;CAAS,WAAW;CAAiB,CAAC;YAOvD,SAAS;CAAE,MAAM;CAAS,WAAW;CAAkB,CAAC;+BAlC1D,cAAc,qBAAqB"}
|