@editframe/elements 0.37.3-beta → 0.38.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.js +17 -14
- package/dist/EF_FRAMEGEN.js.map +1 -1
- package/dist/EF_RENDERING.js.map +1 -1
- package/dist/canvas/EFCanvas.d.ts +9 -2
- package/dist/canvas/EFCanvas.js +14 -4
- package/dist/canvas/EFCanvas.js.map +1 -1
- package/dist/canvas/EFCanvasItem.d.ts +2 -2
- package/dist/canvas/overlays/SelectionOverlay.d.ts +10 -2
- package/dist/canvas/overlays/SelectionOverlay.js +5 -12
- package/dist/canvas/overlays/SelectionOverlay.js.map +1 -1
- package/dist/canvas/overlays/overlayState.js.map +1 -1
- package/dist/canvas/selection/SelectionController.js.map +1 -1
- package/dist/elements/EFAudio.d.ts +1 -11
- package/dist/elements/EFAudio.js +2 -10
- package/dist/elements/EFAudio.js.map +1 -1
- package/dist/elements/EFCaptions.d.ts +5 -9
- package/dist/elements/EFCaptions.js +34 -11
- package/dist/elements/EFCaptions.js.map +1 -1
- package/dist/elements/EFImage.d.ts +10 -8
- package/dist/elements/EFImage.js +117 -32
- package/dist/elements/EFImage.js.map +1 -1
- package/dist/elements/EFMedia/AssetMediaEngine.js +2 -2
- package/dist/elements/EFMedia/AssetMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/BaseMediaEngine.js +15 -92
- package/dist/elements/EFMedia/BaseMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/BufferedSeekingInput.js +10 -11
- package/dist/elements/EFMedia/BufferedSeekingInput.js.map +1 -1
- package/dist/elements/EFMedia/{AssetIdMediaEngine.js → FileMediaEngine.js} +44 -24
- package/dist/elements/EFMedia/FileMediaEngine.js.map +1 -0
- package/dist/elements/EFMedia/JitMediaEngine.js +14 -13
- package/dist/elements/EFMedia/JitMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js +3 -3
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js.map +1 -1
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +12 -7
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js.map +1 -1
- package/dist/elements/EFMedia/shared/timeoutUtils.js +44 -0
- package/dist/elements/EFMedia/shared/timeoutUtils.js.map +1 -0
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js +1 -1
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +4 -4
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js.map +1 -1
- package/dist/elements/EFMedia.d.ts +14 -8
- package/dist/elements/EFMedia.js +52 -19
- package/dist/elements/EFMedia.js.map +1 -1
- package/dist/elements/EFPanZoom.d.ts +2 -2
- package/dist/elements/EFPanZoom.js +1 -1
- package/dist/elements/EFPanZoom.js.map +1 -1
- package/dist/elements/EFSourceMixin.js +16 -8
- package/dist/elements/EFSourceMixin.js.map +1 -1
- package/dist/elements/EFSurface.d.ts +5 -8
- package/dist/elements/EFSurface.js +4 -43
- package/dist/elements/EFSurface.js.map +1 -1
- package/dist/elements/EFTemporal.d.ts +33 -8
- package/dist/elements/EFTemporal.js +92 -40
- package/dist/elements/EFTemporal.js.map +1 -1
- package/dist/elements/EFText.d.ts +3 -0
- package/dist/elements/EFText.js +54 -21
- package/dist/elements/EFText.js.map +1 -1
- package/dist/elements/EFTextSegment.js +8 -4
- package/dist/elements/EFTextSegment.js.map +1 -1
- package/dist/elements/EFTimegroup.d.ts +26 -43
- package/dist/elements/EFTimegroup.js +295 -314
- package/dist/elements/EFTimegroup.js.map +1 -1
- package/dist/elements/EFVideo.d.ts +44 -42
- package/dist/elements/EFVideo.js +259 -172
- package/dist/elements/EFVideo.js.map +1 -1
- package/dist/elements/EFWaveform.d.ts +3 -8
- package/dist/elements/EFWaveform.js +18 -13
- package/dist/elements/EFWaveform.js.map +1 -1
- package/dist/elements/ElementPositionInfo.js.map +1 -1
- package/dist/elements/FetchMixin.js.map +1 -1
- package/dist/elements/TargetController.d.ts +0 -3
- package/dist/elements/TargetController.js +12 -35
- package/dist/elements/TargetController.js.map +1 -1
- package/dist/elements/TimegroupController.js.map +1 -1
- package/dist/elements/cloneFactoryRegistry.d.ts +14 -0
- package/dist/elements/cloneFactoryRegistry.js +15 -0
- package/dist/elements/cloneFactoryRegistry.js.map +1 -0
- package/dist/elements/renderTemporalAudio.js +8 -6
- package/dist/elements/renderTemporalAudio.js.map +1 -1
- package/dist/elements/setupTemporalHierarchy.js +62 -0
- package/dist/elements/setupTemporalHierarchy.js.map +1 -0
- package/dist/elements/updateAnimations.js +62 -87
- package/dist/elements/updateAnimations.js.map +1 -1
- package/dist/getRenderInfo.d.ts +3 -2
- package/dist/getRenderInfo.js +20 -4
- package/dist/getRenderInfo.js.map +1 -1
- package/dist/gui/ContextMixin.js +68 -12
- package/dist/gui/ContextMixin.js.map +1 -1
- package/dist/gui/Controllable.js +1 -1
- package/dist/gui/Controllable.js.map +1 -1
- package/dist/gui/EFActiveRootTemporal.d.ts +2 -2
- package/dist/gui/EFActiveRootTemporal.js.map +1 -1
- package/dist/gui/EFControls.d.ts +2 -2
- package/dist/gui/EFControls.js +2 -2
- package/dist/gui/EFControls.js.map +1 -1
- package/dist/gui/EFDial.d.ts +2 -2
- package/dist/gui/EFDial.js +12 -9
- package/dist/gui/EFDial.js.map +1 -1
- package/dist/gui/EFFilmstrip.d.ts +2 -0
- package/dist/gui/EFFilmstrip.js +18 -10
- package/dist/gui/EFFilmstrip.js.map +1 -1
- package/dist/gui/EFFitScale.d.ts +28 -4
- package/dist/gui/EFFitScale.js +88 -26
- package/dist/gui/EFFitScale.js.map +1 -1
- package/dist/gui/EFFocusOverlay.d.ts +2 -2
- package/dist/gui/EFFocusOverlay.js +3 -3
- package/dist/gui/EFFocusOverlay.js.map +1 -1
- package/dist/gui/EFOverlayItem.d.ts +2 -2
- package/dist/gui/EFOverlayLayer.d.ts +2 -2
- package/dist/gui/EFPause.d.ts +2 -2
- package/dist/gui/EFPause.js +1 -1
- package/dist/gui/EFPlay.d.ts +2 -2
- package/dist/gui/EFPlay.js +1 -1
- package/dist/gui/EFPreview.js +1 -1
- package/dist/gui/EFResizableBox.d.ts +2 -2
- package/dist/gui/EFResizableBox.js +5 -5
- package/dist/gui/EFResizableBox.js.map +1 -1
- package/dist/gui/EFScrubber.d.ts +2 -2
- package/dist/gui/EFScrubber.js +8 -13
- package/dist/gui/EFScrubber.js.map +1 -1
- package/dist/gui/EFTimeDisplay.d.ts +6 -2
- package/dist/gui/EFTimeDisplay.js +25 -7
- package/dist/gui/EFTimeDisplay.js.map +1 -1
- package/dist/gui/EFTimelineRuler.d.ts +2 -2
- package/dist/gui/EFTimelineRuler.js +3 -3
- package/dist/gui/EFTimelineRuler.js.map +1 -1
- package/dist/gui/EFToggleLoop.d.ts +2 -2
- package/dist/gui/EFToggleLoop.js +1 -1
- package/dist/gui/EFTogglePlay.d.ts +2 -2
- package/dist/gui/EFTogglePlay.js +1 -1
- package/dist/gui/EFTransformHandles.d.ts +2 -2
- package/dist/gui/EFTransformHandles.js +6 -6
- package/dist/gui/EFTransformHandles.js.map +1 -1
- package/dist/gui/EFWorkbench.d.ts +40 -36
- package/dist/gui/EFWorkbench.js +436 -822
- package/dist/gui/EFWorkbench.js.map +1 -1
- package/dist/gui/FitScaleHelpers.js.map +1 -1
- package/dist/gui/PlaybackController.d.ts +3 -8
- package/dist/gui/PlaybackController.js +59 -56
- 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/TargetOrContextMixin.js +43 -6
- package/dist/gui/TargetOrContextMixin.js.map +1 -1
- package/dist/gui/ef-theme.css +136 -0
- package/dist/gui/hierarchy/EFHierarchy.d.ts +2 -2
- package/dist/gui/hierarchy/EFHierarchy.js +14 -24
- package/dist/gui/hierarchy/EFHierarchy.js.map +1 -1
- package/dist/gui/hierarchy/EFHierarchyItem.d.ts +3 -3
- package/dist/gui/hierarchy/EFHierarchyItem.js +22 -10
- package/dist/gui/hierarchy/EFHierarchyItem.js.map +1 -1
- package/dist/gui/icons.js.map +1 -1
- package/dist/gui/previewSettingsContext.d.ts +18 -0
- package/dist/gui/previewSettingsContext.js.map +1 -1
- package/dist/gui/theme.js +34 -0
- package/dist/gui/theme.js.map +1 -0
- package/dist/gui/timeline/EFTimeline.d.ts +2 -2
- package/dist/gui/timeline/EFTimeline.js +70 -52
- package/dist/gui/timeline/EFTimeline.js.map +1 -1
- package/dist/gui/timeline/EFTimelineRow.d.ts +5 -3
- package/dist/gui/timeline/EFTimelineRow.js +55 -32
- package/dist/gui/timeline/EFTimelineRow.js.map +1 -1
- package/dist/gui/timeline/TrimHandles.d.ts +23 -9
- package/dist/gui/timeline/TrimHandles.js +224 -51
- package/dist/gui/timeline/TrimHandles.js.map +1 -1
- package/dist/gui/timeline/flattenHierarchy.js.map +1 -1
- package/dist/gui/timeline/timelineEditingContext.d.ts +34 -0
- package/dist/gui/timeline/timelineEditingContext.js +24 -0
- package/dist/gui/timeline/timelineEditingContext.js.map +1 -0
- package/dist/gui/timeline/timelineStateContext.js.map +1 -1
- package/dist/gui/timeline/tracks/AudioTrack.js +1 -1
- package/dist/gui/timeline/tracks/AudioTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/CaptionsTrack.d.ts +2 -3
- package/dist/gui/timeline/tracks/CaptionsTrack.js +17 -75
- package/dist/gui/timeline/tracks/CaptionsTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/EFThumbnailStrip.d.ts +52 -0
- package/dist/gui/timeline/tracks/EFThumbnailStrip.js +596 -0
- package/dist/gui/timeline/tracks/EFThumbnailStrip.js.map +1 -0
- package/dist/gui/timeline/tracks/HTMLTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/ImageTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TextTrack.d.ts +3 -2
- package/dist/gui/timeline/tracks/TextTrack.js +17 -43
- package/dist/gui/timeline/tracks/TextTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TimegroupTrack.d.ts +3 -4
- package/dist/gui/timeline/tracks/TimegroupTrack.js +33 -23
- package/dist/gui/timeline/tracks/TimegroupTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TrackItem.d.ts +7 -9
- package/dist/gui/timeline/tracks/TrackItem.js +18 -17
- package/dist/gui/timeline/tracks/TrackItem.js.map +1 -1
- package/dist/gui/timeline/tracks/VideoTrack.d.ts +3 -3
- package/dist/gui/timeline/tracks/VideoTrack.js +11 -14
- package/dist/gui/timeline/tracks/VideoTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/WaveformTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/renderTrackChildren.js.map +1 -1
- package/dist/gui/timeline/tracks/waveformUtils.js +1 -1
- package/dist/gui/timeline/tracks/waveformUtils.js.map +1 -1
- package/dist/gui/tree/EFTree.d.ts +2 -2
- package/dist/gui/tree/EFTree.js +8 -14
- package/dist/gui/tree/EFTree.js.map +1 -1
- package/dist/gui/tree/EFTreeItem.d.ts +2 -2
- package/dist/gui/tree/EFTreeItem.js +3 -3
- package/dist/gui/tree/EFTreeItem.js.map +1 -1
- package/dist/gui/tree/treeContext.js.map +1 -1
- package/dist/index.d.ts +10 -8
- package/dist/index.js +6 -5
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +2 -2
- package/dist/node.js +2 -2
- package/dist/preview/AdaptiveResolutionTracker.js +3 -3
- package/dist/preview/AdaptiveResolutionTracker.js.map +1 -1
- package/dist/preview/FrameController.d.ts +2 -17
- package/dist/preview/FrameController.js +40 -63
- package/dist/preview/FrameController.js.map +1 -1
- package/dist/preview/QualityUpgradeScheduler.d.ts +76 -0
- package/dist/preview/QualityUpgradeScheduler.js +158 -0
- package/dist/preview/QualityUpgradeScheduler.js.map +1 -0
- package/dist/preview/RenderContext.d.ts +119 -1
- package/dist/preview/RenderContext.js +21 -3
- package/dist/preview/RenderContext.js.map +1 -1
- package/dist/preview/RenderProfiler.js.map +1 -1
- package/dist/preview/RenderStats.js +85 -0
- package/dist/preview/RenderStats.js.map +1 -0
- package/dist/preview/encoding/canvasEncoder.js +2 -52
- package/dist/preview/encoding/canvasEncoder.js.map +1 -1
- package/dist/preview/encoding/mainThreadEncoder.js.map +1 -1
- package/dist/preview/encoding/workerEncoder.js.map +1 -1
- package/dist/preview/logger.js.map +1 -1
- package/dist/preview/previewSettings.d.ts +34 -0
- package/dist/preview/previewSettings.js +29 -17
- package/dist/preview/previewSettings.js.map +1 -1
- package/dist/preview/previewTypes.js +4 -4
- package/dist/preview/previewTypes.js.map +1 -1
- package/dist/preview/renderElementToCanvas.d.ts +44 -0
- package/dist/preview/renderElementToCanvas.js +72 -0
- package/dist/preview/renderElementToCanvas.js.map +1 -0
- package/dist/preview/renderTimegroupToCanvas.d.ts +134 -32
- package/dist/preview/renderTimegroupToCanvas.js +321 -146
- package/dist/preview/renderTimegroupToCanvas.js.map +1 -1
- package/dist/preview/renderTimegroupToCanvas.types.d.ts +51 -0
- package/dist/preview/renderTimegroupToVideo.d.ts +20 -35
- package/dist/preview/renderTimegroupToVideo.js +94 -106
- package/dist/preview/renderTimegroupToVideo.js.map +1 -1
- package/dist/preview/renderTimegroupToVideo.types.d.ts +42 -0
- package/dist/preview/renderVideoToVideo.js +286 -0
- package/dist/preview/renderVideoToVideo.js.map +1 -0
- package/dist/preview/renderers.d.ts +56 -0
- package/dist/preview/renderers.js +13 -1
- package/dist/preview/renderers.js.map +1 -1
- package/dist/preview/rendering/ScaleConfig.js +74 -0
- package/dist/preview/rendering/ScaleConfig.js.map +1 -0
- package/dist/preview/rendering/inlineImages.d.ts +13 -0
- package/dist/preview/rendering/inlineImages.js +7 -44
- package/dist/preview/rendering/inlineImages.js.map +1 -1
- package/dist/preview/rendering/loadImage.d.ts +8 -0
- package/dist/preview/rendering/loadImage.js +22 -0
- package/dist/preview/rendering/loadImage.js.map +1 -0
- package/dist/preview/rendering/renderToImageNative.js +3 -3
- package/dist/preview/rendering/renderToImageNative.js.map +1 -1
- package/dist/preview/rendering/serializeTimelineDirect.js +224 -68
- package/dist/preview/rendering/serializeTimelineDirect.js.map +1 -1
- package/dist/preview/statsTrackingStrategy.js +1 -101
- package/dist/preview/statsTrackingStrategy.js.map +1 -1
- package/dist/preview/workers/WorkerPool.js +0 -1
- package/dist/preview/workers/WorkerPool.js.map +1 -1
- package/dist/preview/workers/encoderWorkerInline.js +21 -54
- package/dist/preview/workers/encoderWorkerInline.js.map +1 -1
- package/dist/render/EFRenderAPI.d.ts +2 -1
- package/dist/render/EFRenderAPI.js +12 -36
- package/dist/render/EFRenderAPI.js.map +1 -1
- package/dist/render/getRenderData.js +4 -4
- package/dist/render/getRenderData.js.map +1 -1
- package/dist/style.css +114 -163
- package/dist/transcoding/cache/RequestDeduplicator.js +1 -0
- package/dist/transcoding/cache/RequestDeduplicator.js.map +1 -1
- package/dist/transcoding/types/index.d.ts +1 -1
- package/dist/transcoding/utils/UrlGenerator.js +10 -3
- package/dist/transcoding/utils/UrlGenerator.js.map +1 -1
- package/dist/utils/LRUCache.js +1 -0
- package/dist/utils/LRUCache.js.map +1 -1
- package/dist/utils/frameTime.js +23 -1
- package/dist/utils/frameTime.js.map +1 -1
- package/package.json +45 -8
- package/scripts/build-css.js +8 -1
- package/test/setup.ts +0 -1
- package/test/useAssetMSW.ts +50 -0
- package/test/visualRegressionUtils.ts +23 -9
- package/tsdown.config.ts +6 -1
- package/dist/_virtual/rolldown_runtime.js +0 -27
- package/dist/elements/EFMedia/AssetIdMediaEngine.js.map +0 -1
- package/dist/elements/EFThumbnailStrip.d.ts +0 -167
- package/dist/elements/EFThumbnailStrip.js +0 -731
- package/dist/elements/EFThumbnailStrip.js.map +0 -1
- package/dist/elements/SessionThumbnailCache.js +0 -154
- package/dist/elements/SessionThumbnailCache.js.map +0 -1
- package/dist/node_modules/react/cjs/react-jsx-runtime.development.js +0 -688
- package/dist/node_modules/react/cjs/react-jsx-runtime.development.js.map +0 -1
- package/dist/node_modules/react/cjs/react.development.js +0 -1521
- package/dist/node_modules/react/cjs/react.development.js.map +0 -1
- package/dist/node_modules/react/index.js +0 -13
- package/dist/node_modules/react/index.js.map +0 -1
- package/dist/node_modules/react/jsx-runtime.js +0 -13
- package/dist/node_modules/react/jsx-runtime.js.map +0 -1
- package/dist/preview/encoding/types.d.ts +0 -1
- package/dist/preview/renderTimegroupPreview.js +0 -686
- package/dist/preview/renderTimegroupPreview.js.map +0 -1
- package/dist/preview/rendering/renderToImage.d.ts +0 -2
- package/dist/preview/rendering/renderToImage.js +0 -95
- package/dist/preview/rendering/renderToImage.js.map +0 -1
- package/dist/preview/rendering/renderToImageForeignObject.js +0 -163
- package/dist/preview/rendering/renderToImageForeignObject.js.map +0 -1
- package/dist/preview/rendering/renderToImageNative.d.ts +0 -1
- package/dist/preview/rendering/svgSerializer.js +0 -43
- package/dist/preview/rendering/svgSerializer.js.map +0 -1
- package/dist/preview/rendering/types.d.ts +0 -2
- package/dist/preview/thumbnailCacheSettings.js +0 -52
- package/dist/preview/thumbnailCacheSettings.js.map +0 -1
- package/dist/sandbox/PlaybackControls.d.ts +0 -1
- package/dist/sandbox/PlaybackControls.js +0 -10
- package/dist/sandbox/PlaybackControls.js.map +0 -1
- package/dist/sandbox/ScenarioRunner.d.ts +0 -1
- package/dist/sandbox/ScenarioRunner.js +0 -1
- package/dist/sandbox/defineSandbox.d.ts +0 -1
- package/dist/sandbox/index.d.ts +0 -3
- package/dist/sandbox/index.js +0 -2
- package/test/EFVideo.framegen.browsertest.ts +0 -80
- package/test/thumbnail-performance-test.html +0 -116
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContextMixin.js","names":["#getTokenCacheKey","#parseTokenExpiration","#apiHost","#targetTemporal","#onControllerUpdate","#controllerSubscribed","#targetTemporalProvider","#loop","#playingProvider","#loopProvider","#currentTimeMsProvider","#signingURL","#timegroupObserver"],"sources":["../../src/gui/ContextMixin.ts"],"sourcesContent":["import { ContextProvider, consume, createContext, provide } from \"@lit/context\";\nimport type { LitElement } from \"lit\";\nimport { property, state } from \"lit/decorators.js\";\nimport { EF_RENDERING } from \"../EF_RENDERING.ts\";\nimport {\n isEFTemporal,\n type TemporalMixinInterface,\n} from \"../elements/EFTemporal.js\";\nimport { globalURLTokenDeduplicator } from \"../transcoding/cache/URLTokenDeduplicator.js\";\nimport { currentTimeContext } from \"./currentTimeContext.js\";\nimport { durationContext } from \"./durationContext.js\";\nimport {\n type EFConfiguration,\n efConfigurationContext,\n} from \"./EFConfiguration.ts\";\nimport { efContext } from \"./efContext.js\";\nimport { fetchContext } from \"./fetchContext.js\";\nimport { type FocusContext, focusContext } from \"./focusContext.js\";\nimport { focusedElementContext } from \"./focusedElementContext.js\";\nimport { loopContext, playingContext } from \"./playingContext.js\";\n\nexport const targetTemporalContext =\n createContext<TemporalMixinInterface | null>(Symbol(\"target-temporal\"));\n\nexport declare class ContextMixinInterface extends LitElement {\n signingURL?: string;\n apiHost?: string;\n rendering: boolean;\n playing: boolean;\n loop: boolean;\n currentTimeMs: number;\n focusedElement?: HTMLElement;\n targetTemporal: TemporalMixinInterface | null;\n play(): Promise<void>;\n pause(): void;\n}\n\nconst contextMixinSymbol = Symbol(\"contextMixin\");\n\nexport function isContextMixin(value: any): value is ContextMixinInterface {\n return (\n typeof value === \"object\" &&\n value !== null &&\n contextMixinSymbol in value.constructor\n );\n}\n\ntype Constructor<T = {}> = new (...args: any[]) => T;\nexport function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {\n class ContextElement extends superClass {\n static [contextMixinSymbol] = true;\n\n @consume({ context: efConfigurationContext, subscribe: true })\n efConfiguration: EFConfiguration | null = null;\n\n @provide({ context: focusContext })\n focusContext = this as FocusContext;\n\n @provide({ context: focusedElementContext })\n @state()\n focusedElement?: HTMLElement;\n\n #playingProvider!: ContextProvider<typeof playingContext>;\n #loopProvider!: ContextProvider<typeof loopContext>;\n #currentTimeMsProvider!: ContextProvider<typeof currentTimeContext>;\n #targetTemporalProvider!: ContextProvider<typeof targetTemporalContext>;\n\n #loop = false;\n\n #apiHost?: string;\n @property({ type: String, attribute: \"api-host\" })\n get apiHost() {\n return this.#apiHost ?? this.efConfiguration?.apiHost ?? \"\";\n }\n\n set apiHost(value: string) {\n this.#apiHost = value;\n }\n\n @provide({ context: efContext })\n efContext = this;\n\n #targetTemporal: TemporalMixinInterface | null = null;\n\n @state()\n get targetTemporal(): TemporalMixinInterface | null {\n return this.#targetTemporal;\n }\n #controllerSubscribed = false;\n\n /**\n * Find the first root temporal element (recursively searches through children)\n * Supports ef-timegroup, ef-video, ef-audio, and any other temporal elements\n * even when they're wrapped in non-temporal elements like divs\n */\n private findRootTemporal(): TemporalMixinInterface | null {\n const findRecursive = (\n element: Element,\n ): TemporalMixinInterface | null => {\n if (isEFTemporal(element)) {\n return element as TemporalMixinInterface & HTMLElement;\n }\n\n for (const child of element.children) {\n const found = findRecursive(child);\n if (found) return found;\n }\n\n return null;\n };\n\n for (const child of this.children) {\n const found = findRecursive(child);\n if (found) return found;\n }\n\n return null;\n }\n\n set targetTemporal(value: TemporalMixinInterface | null) {\n if (this.#targetTemporal === value) return;\n\n // Unsubscribe from old controller updates\n if (this.#targetTemporal?.playbackController) {\n this.#targetTemporal.playbackController.removeListener(\n this.#onControllerUpdate,\n );\n this.#controllerSubscribed = false;\n }\n\n this.#targetTemporal = value;\n this.#targetTemporalProvider?.setValue(value);\n\n // Sync all provided contexts\n this.requestUpdate(\"targetTemporal\");\n this.requestUpdate(\"playing\");\n this.requestUpdate(\"loop\");\n this.requestUpdate(\"currentTimeMs\");\n\n // If the new targetTemporal has a playbackController, apply stored loop value immediately\n if (value?.playbackController && this.#loop) {\n value.playbackController.setLoop(this.#loop);\n }\n\n // If the new targetTemporal doesn't have a playbackController yet,\n // wait for it to complete its updates (it might be initializing)\n if (value && !value.playbackController) {\n // Wait for the temporal element to initialize\n (value as any).updateComplete?.then(() => {\n if (value === this.#targetTemporal && !this.#controllerSubscribed) {\n this.requestUpdate();\n }\n });\n }\n }\n\n #onControllerUpdate = (\n event: import(\"./PlaybackController.js\").PlaybackControllerUpdateEvent,\n ) => {\n switch (event.property) {\n case \"playing\":\n this.#playingProvider.setValue(event.value as boolean);\n break;\n case \"loop\":\n this.#loopProvider.setValue(event.value as boolean);\n break;\n case \"currentTimeMs\":\n this.#currentTimeMsProvider.setValue(event.value as number);\n break;\n }\n };\n\n // Add reactive properties that depend on the targetTemporal\n @provide({ context: durationContext })\n @property({ type: Number })\n durationMs = 0;\n\n @property({ type: Number })\n endTimeMs = 0;\n\n @provide({ context: fetchContext })\n fetch = async (url: string, init: RequestInit = {}) => {\n init.headers ||= {};\n Object.assign(init.headers, {\n \"Content-Type\": \"application/json\",\n });\n\n // Check if this is a local @ef-* endpoint that doesn't need authentication\n // These endpoints are handled by the Vite plugin locally and don't require signing\n const isLocalEndpoint = url.startsWith(\"/@ef-\");\n\n if (!EF_RENDERING() && this.signingURL && !isLocalEndpoint) {\n const { cacheKey, signingPayload } = this.#getTokenCacheKey(url);\n\n // Use global token deduplicator to share tokens across all context providers\n const urlToken = await globalURLTokenDeduplicator.getToken(\n cacheKey,\n async () => {\n try {\n const response = await fetch(this.signingURL, {\n method: \"POST\",\n body: JSON.stringify(signingPayload),\n });\n\n if (response.ok) {\n const tokenData = await response.json();\n return tokenData.token;\n }\n throw new Error(\n `Failed to sign URL: ${url}. SigningURL: ${this.signingURL} ${response.status} ${response.statusText}`,\n );\n } catch (error) {\n console.error(\"ContextMixin urlToken fetch error\", url, error);\n throw error;\n }\n },\n (token: string) => this.#parseTokenExpiration(token),\n );\n\n Object.assign(init.headers, {\n authorization: `Bearer ${urlToken}`,\n });\n } else {\n init.credentials = \"include\";\n }\n\n try {\n const fetchPromise = fetch(url, init);\n // Wrap the promise to catch rejections and log the URL\n // Return the promise chain so errors are logged but still propagate\n return fetchPromise.catch((error) => {\n // For AbortErrors, re-throw directly without modification\n // DOMException properties like 'name' are read-only\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n \n console.error(\n \"ContextMixin fetch error\",\n url,\n error,\n window.location.href,\n );\n // Create a new error with the URL in the message, preserving the original error type\n const ErrorConstructor =\n error instanceof Error ? error.constructor : Error;\n const enhancedError = new (ErrorConstructor as typeof Error)(\n `Failed to fetch: ${url}. Original error: ${error instanceof Error ? error.message : String(error)}`,\n );\n // Preserve the original error's properties (except for DOMException which has read-only properties)\n if (error instanceof Error && !(error instanceof DOMException)) {\n enhancedError.name = error.name;\n enhancedError.stack = error.stack;\n // Copy any additional properties from the original error\n Object.assign(enhancedError, error);\n }\n throw enhancedError;\n });\n } catch (error) {\n console.error(\n \"ContextMixin fetch error (synchronous)\",\n url,\n error,\n window.location.href,\n );\n throw error;\n }\n };\n\n // Note: URL token caching is now handled globally via URLTokenDeduplicator\n // Keeping these for any potential backwards compatibility, but they're no longer used\n\n /**\n * Generate a cache key for URL token based on signing strategy\n *\n * Uses unified prefix + parameter matching approach:\n * - For transcode URLs: signs base \"/api/v1/transcode\" + params like {url: \"source.mp4\"}\n * - For regular URLs: signs full URL with empty params {}\n * - All validation uses prefix matching + exhaustive parameter matching\n * - Multiple transcode segments with same source share one token (reduces round-trips)\n */\n #getTokenCacheKey(url: string): {\n cacheKey: string;\n signingPayload: { url: string; params?: Record<string, string> };\n } {\n try {\n const urlObj = new URL(url);\n\n // Check if this is a transcode URL pattern\n if (urlObj.pathname.includes(\"/api/v1/transcode/\")) {\n const urlParam = urlObj.searchParams.get(\"url\");\n if (urlParam) {\n // For transcode URLs, sign the base path + url parameter\n const basePath = `${urlObj.origin}/api/v1/transcode`;\n const cacheKey = `${basePath}?url=${urlParam}`;\n return {\n cacheKey,\n signingPayload: { url: basePath, params: { url: urlParam } },\n };\n }\n }\n\n // For non-transcode URLs, use full URL (existing behavior)\n return {\n cacheKey: url,\n signingPayload: { url },\n };\n } catch {\n // If URL parsing fails, fall back to full URL\n return {\n cacheKey: url,\n signingPayload: { url },\n };\n }\n }\n\n /**\n * Parse JWT token to extract safe expiration time (with buffer)\n * @param token JWT token string\n * @returns Safe expiration timestamp in milliseconds (actual expiry minus buffer), or 0 if parsing fails\n */\n #parseTokenExpiration(token: string): number {\n try {\n // JWT has 3 parts separated by dots: header.payload.signature\n const parts = token.split(\".\");\n if (parts.length !== 3) return 0;\n\n // Decode the payload (second part)\n const payload = parts[1];\n if (!payload) return 0;\n\n const decoded = atob(payload.replace(/-/g, \"+\").replace(/_/g, \"/\"));\n const parsed = JSON.parse(decoded);\n\n // Extract timestamps (in seconds)\n const exp = parsed.exp;\n const iat = parsed.iat;\n if (!exp) return 0;\n\n // Calculate token lifetime and buffer\n const lifetimeSeconds = iat ? exp - iat : 3600; // Default to 1 hour if no iat\n const tenPercentBufferMs = lifetimeSeconds * 0.1 * 1000; // 10% of lifetime in ms\n const fiveMinutesMs = 5 * 60 * 1000; // 5 minutes in ms\n\n // Use whichever buffer is smaller (more conservative)\n const bufferMs = Math.min(fiveMinutesMs, tenPercentBufferMs);\n\n // Return expiration time minus buffer\n return exp * 1000 - bufferMs;\n } catch {\n return 0;\n }\n }\n\n #signingURL?: string;\n /**\n * A URL that will be used to generated signed tokens for accessing media files from the\n * editframe API. This is used to authenticate media requests per-user.\n */\n @property({ type: String, attribute: \"signing-url\" })\n get signingURL() {\n return this.#signingURL ?? this.efConfiguration?.signingURL ?? \"\";\n }\n set signingURL(value: string) {\n this.#signingURL = value;\n }\n\n @property({ type: Boolean, reflect: true })\n get playing(): boolean {\n return this.targetTemporal?.playbackController?.playing ?? false;\n }\n set playing(value: boolean) {\n if (this.targetTemporal?.playbackController) {\n this.targetTemporal.playbackController.setPlaying(value);\n }\n }\n\n @property({ type: Boolean, reflect: true, attribute: \"loop\" })\n get loop(): boolean {\n return this.targetTemporal?.playbackController?.loop ?? this.#loop;\n }\n set loop(value: boolean) {\n const oldValue = this.#loop;\n this.#loop = value;\n if (this.targetTemporal?.playbackController) {\n this.targetTemporal.playbackController.setLoop(value);\n }\n this.requestUpdate(\"loop\", oldValue);\n }\n\n @property({ type: Boolean })\n rendering = false;\n\n @property({ type: Number })\n get currentTimeMs(): number {\n return (\n this.targetTemporal?.playbackController?.currentTimeMs ?? Number.NaN\n );\n }\n set currentTimeMs(value: number) {\n if (this.targetTemporal?.playbackController) {\n this.targetTemporal.playbackController.setCurrentTimeMs(value);\n }\n }\n\n #timegroupObserver = new MutationObserver((mutations) => {\n let shouldUpdate = false;\n\n for (const mutation of mutations) {\n if (mutation.type === \"childList\") {\n const newTemporal = this.findRootTemporal();\n if (newTemporal !== this.targetTemporal) {\n this.targetTemporal = newTemporal;\n shouldUpdate = true;\n } else if (\n mutation.target instanceof Element &&\n isEFTemporal(mutation.target)\n ) {\n // Handle childList changes within existing temporal elements\n shouldUpdate = true;\n }\n } else if (mutation.type === \"attributes\") {\n // Watch for attribute changes that might affect duration\n const durationAffectingAttributes = [\n \"duration\",\n \"mode\",\n \"trimstart\",\n \"trimend\",\n \"sourcein\",\n \"sourceout\",\n ];\n\n if (\n durationAffectingAttributes.includes(\n mutation.attributeName || \"\",\n ) ||\n (mutation.target instanceof Element &&\n isEFTemporal(mutation.target))\n ) {\n shouldUpdate = true;\n }\n }\n }\n\n if (shouldUpdate) {\n // Trigger an update to ensure reactive properties recalculate\n // Use a microtask to ensure DOM updates are complete\n queueMicrotask(() => {\n // Recalculate duration and endTime when temporal element changes\n this.updateDurationProperties();\n this.requestUpdate();\n // Also ensure the targetTemporal updates its computed properties\n if (this.targetTemporal) {\n (this.targetTemporal as any).requestUpdate();\n }\n });\n }\n });\n\n /**\n * Update duration properties when temporal element changes\n */\n updateDurationProperties(): void {\n const newDuration = this.targetTemporal?.durationMs ?? 0;\n const newEndTime = this.targetTemporal?.endTimeMs ?? 0;\n\n if (this.durationMs !== newDuration) {\n this.durationMs = newDuration;\n }\n\n if (this.endTimeMs !== newEndTime) {\n this.endTimeMs = newEndTime;\n }\n }\n\n connectedCallback(): void {\n super.connectedCallback();\n\n // Create manual context providers for playback state\n this.#playingProvider = new ContextProvider(this, {\n context: playingContext,\n initialValue: this.playing,\n });\n this.#loopProvider = new ContextProvider(this, {\n context: loopContext,\n initialValue: this.loop,\n });\n this.#currentTimeMsProvider = new ContextProvider(this, {\n context: currentTimeContext,\n initialValue: this.currentTimeMs,\n });\n this.#targetTemporalProvider = new ContextProvider(this, {\n context: targetTemporalContext,\n initialValue: this.targetTemporal,\n });\n\n // Initialize targetTemporal to first root temporal element\n this.targetTemporal = this.findRootTemporal();\n // Initialize duration properties\n this.updateDurationProperties();\n\n this.#timegroupObserver.observe(this, {\n childList: true,\n subtree: true,\n attributes: true,\n });\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback();\n this.#timegroupObserver.disconnect();\n\n // Unsubscribe from controller\n if (this.#targetTemporal?.playbackController) {\n this.#targetTemporal.playbackController.removeListener(\n this.#onControllerUpdate,\n );\n this.#controllerSubscribed = false;\n }\n\n this.pause();\n }\n\n updated(changedProperties: Map<string | number | symbol, unknown>) {\n super.updated?.(changedProperties);\n\n // Subscribe to controller when it becomes available\n if (\n !this.#controllerSubscribed &&\n this.#targetTemporal?.playbackController\n ) {\n this.#targetTemporal.playbackController.addListener(\n this.#onControllerUpdate,\n );\n this.#controllerSubscribed = true;\n\n // Apply stored loop value when playbackController becomes available\n if (this.#loop) {\n this.#targetTemporal.playbackController.setLoop(this.#loop);\n }\n\n // Trigger initial sync of context providers\n this.#playingProvider.setValue(this.playing);\n this.#loopProvider.setValue(this.loop);\n this.#currentTimeMsProvider.setValue(this.currentTimeMs);\n }\n }\n\n async play() {\n // If targetTemporal is not set, try to find it now\n // This handles cases where the DOM may not have been fully ready during connectedCallback\n if (!this.targetTemporal) {\n // Wait for any temporal custom elements to be defined\n const potentialTemporalTags = Array.from(this.children)\n .map((el) => el.tagName.toLowerCase())\n .filter((tag) => tag.startsWith(\"ef-\"));\n\n await Promise.all(\n potentialTemporalTags.map((tag) =>\n customElements.whenDefined(tag).catch(() => {}),\n ),\n );\n\n const foundTemporal = this.findRootTemporal();\n if (foundTemporal) {\n this.targetTemporal = foundTemporal;\n // Wait for it to initialize\n await (foundTemporal as any).updateComplete;\n } else {\n console.warn(\"No temporal element found to play\");\n return;\n }\n }\n\n // If playbackController doesn't exist yet, wait for it\n if (!this.targetTemporal.playbackController) {\n await (this.targetTemporal as any).updateComplete;\n // After waiting, check again\n if (!this.targetTemporal.playbackController) {\n console.warn(\"PlaybackController not available for temporal element\");\n return;\n }\n }\n\n this.targetTemporal.playbackController.play();\n }\n\n pause() {\n if (this.targetTemporal?.playbackController) {\n this.targetTemporal.playbackController.pause();\n }\n }\n }\n\n return ContextElement as Constructor<ContextMixinInterface> & T;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ContextMixin.js","names":["#getTokenCacheKey","#parseTokenExpiration","#isCrossOrigin","#isEditframeDomain","#apiHost","#targetTemporal","#subscribedController","#controllerSubscribed","#onControllerUpdate","#targetTemporalProvider","#loop","#playingProvider","#loopProvider","#currentTimeMsProvider","#signingURL","#collectUndefinedEFTags","#retryTemporalDiscovery","#timegroupObserver"],"sources":["../../src/gui/ContextMixin.ts"],"sourcesContent":["import { ContextProvider, consume, createContext, provide } from \"@lit/context\";\nimport type { LitElement } from \"lit\";\nimport { property, state } from \"lit/decorators.js\";\nimport { EF_RENDERING } from \"../EF_RENDERING.ts\";\nimport {\n isEFTemporal,\n type TemporalMixinInterface,\n} from \"../elements/EFTemporal.js\";\nimport { globalURLTokenDeduplicator } from \"../transcoding/cache/URLTokenDeduplicator.js\";\nimport { currentTimeContext } from \"./currentTimeContext.js\";\nimport { durationContext } from \"./durationContext.js\";\nimport {\n type EFConfiguration,\n efConfigurationContext,\n} from \"./EFConfiguration.ts\";\nimport { efContext } from \"./efContext.js\";\nimport { fetchContext } from \"./fetchContext.js\";\nimport { type FocusContext, focusContext } from \"./focusContext.js\";\nimport { focusedElementContext } from \"./focusedElementContext.js\";\nimport { loopContext, playingContext } from \"./playingContext.js\";\n\nexport const targetTemporalContext =\n createContext<TemporalMixinInterface | null>(Symbol(\"target-temporal\"));\n\nexport declare class ContextMixinInterface extends LitElement {\n signingURL?: string;\n apiHost?: string;\n rendering: boolean;\n playing: boolean;\n loop: boolean;\n currentTimeMs: number;\n focusedElement?: HTMLElement;\n targetTemporal: TemporalMixinInterface | null;\n play(): Promise<void>;\n pause(): void;\n}\n\nconst contextMixinSymbol = Symbol(\"contextMixin\");\n\nexport function isContextMixin(value: any): value is ContextMixinInterface {\n return (\n typeof value === \"object\" &&\n value !== null &&\n contextMixinSymbol in value.constructor\n );\n}\n\ntype Constructor<T = {}> = new (...args: any[]) => T;\nexport function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {\n class ContextElement extends superClass {\n static [contextMixinSymbol] = true;\n\n @consume({ context: efConfigurationContext, subscribe: true })\n efConfiguration: EFConfiguration | null = null;\n\n @provide({ context: focusContext })\n focusContext = this as FocusContext;\n\n @provide({ context: focusedElementContext })\n @state()\n focusedElement?: HTMLElement;\n\n #playingProvider!: ContextProvider<typeof playingContext>;\n #loopProvider!: ContextProvider<typeof loopContext>;\n #currentTimeMsProvider!: ContextProvider<typeof currentTimeContext>;\n #targetTemporalProvider!: ContextProvider<typeof targetTemporalContext>;\n\n #loop = false;\n\n #apiHost?: string;\n @property({ type: String, attribute: \"api-host\" })\n get apiHost() {\n return this.#apiHost ?? this.efConfiguration?.apiHost ?? \"\";\n }\n\n set apiHost(value: string) {\n this.#apiHost = value;\n }\n\n @provide({ context: efContext })\n efContext = this;\n\n #targetTemporal: TemporalMixinInterface | null = null;\n\n @state()\n get targetTemporal(): TemporalMixinInterface | null {\n return this.#targetTemporal;\n }\n #controllerSubscribed = false;\n\n /**\n * Find the first root temporal element (recursively searches through children)\n * Supports ef-timegroup, ef-video, ef-audio, and any other temporal elements\n * even when they're wrapped in non-temporal elements like divs\n */\n private findRootTemporal(): TemporalMixinInterface | null {\n const findRecursive = (\n element: Element,\n ): TemporalMixinInterface | null => {\n if (isEFTemporal(element)) {\n return element as TemporalMixinInterface & HTMLElement;\n }\n\n for (const child of element.children) {\n const found = findRecursive(child);\n if (found) return found;\n }\n\n return null;\n };\n\n for (const child of this.children) {\n const found = findRecursive(child);\n if (found) return found;\n }\n\n return null;\n }\n\n #subscribedController: any = null;\n\n set targetTemporal(value: TemporalMixinInterface | null) {\n if (\n this.#targetTemporal === value &&\n value?.playbackController === this.#subscribedController &&\n this.#controllerSubscribed\n )\n return;\n\n // Unsubscribe from old controller updates\n if (this.#subscribedController) {\n this.#subscribedController.removeListener(this.#onControllerUpdate);\n this.#controllerSubscribed = false;\n this.#subscribedController = null;\n }\n\n this.#targetTemporal = value;\n this.#targetTemporalProvider?.setValue(value);\n\n // Sync all provided contexts\n this.requestUpdate(\"targetTemporal\");\n this.requestUpdate(\"playing\");\n this.requestUpdate(\"loop\");\n this.requestUpdate(\"currentTimeMs\");\n\n // If the new targetTemporal has a playbackController, apply stored loop value immediately\n if (value?.playbackController && this.#loop) {\n value.playbackController.setLoop(this.#loop);\n }\n\n // If the new targetTemporal doesn't have a playbackController yet,\n // wait for it to complete its updates (it might be initializing)\n if (value && !value.playbackController) {\n // Wait for the temporal element to initialize\n (value as any).updateComplete?.then(() => {\n if (value === this.#targetTemporal && !this.#controllerSubscribed) {\n this.requestUpdate();\n }\n });\n }\n }\n\n #onControllerUpdate = (\n event: import(\"./PlaybackController.js\").PlaybackControllerUpdateEvent,\n ) => {\n switch (event.property) {\n case \"playing\":\n this.#playingProvider.setValue(event.value as boolean);\n break;\n case \"loop\":\n this.#loopProvider.setValue(event.value as boolean);\n break;\n case \"currentTimeMs\":\n this.#currentTimeMsProvider.setValue(event.value as number);\n break;\n }\n };\n\n // Add reactive properties that depend on the targetTemporal\n @provide({ context: durationContext })\n @property({ type: Number })\n durationMs = 0;\n\n @property({ type: Number })\n endTimeMs = 0;\n\n @provide({ context: fetchContext })\n fetch = async (url: string, init: RequestInit = {}) => {\n if (init.body) {\n init.headers ||= {};\n Object.assign(init.headers, {\n \"Content-Type\": \"application/json\",\n });\n }\n\n // Check if this is a local @ef-* endpoint that doesn't need authentication\n // These endpoints are handled by the Vite plugin locally and don't require signing\n const isLocalEndpoint = url.startsWith(\"/@ef-\");\n\n if (!EF_RENDERING() && this.signingURL && !isLocalEndpoint) {\n const { cacheKey, signingPayload } = this.#getTokenCacheKey(url);\n\n // Use global token deduplicator to share tokens across all context providers\n const urlToken = await globalURLTokenDeduplicator.getToken(\n cacheKey,\n async () => {\n try {\n const response = await fetch(this.signingURL, {\n method: \"POST\",\n body: JSON.stringify(signingPayload),\n });\n\n if (response.ok) {\n const tokenData = await response.json();\n return tokenData.token;\n }\n throw new Error(\n `Failed to sign URL: ${url}. SigningURL: ${this.signingURL} ${response.status} ${response.statusText}`,\n );\n } catch (error) {\n console.error(\"ContextMixin urlToken fetch error\", url, error);\n throw error;\n }\n },\n (token: string) => this.#parseTokenExpiration(token),\n );\n\n init.headers ||= {};\n Object.assign(init.headers, {\n authorization: `Bearer ${urlToken}`,\n });\n } else {\n // Only include credentials for same-origin requests where session cookies\n // are relevant. For cross-origin requests without a signing URL, credentials\n // cause CORS failures when the server responds with Access-Control-Allow-Origin: *\n if (!this.#isCrossOrigin(url)) {\n init.credentials = \"include\";\n }\n\n if (this.#isEditframeDomain(url)) {\n console.warn(\n `[Editframe] Request to ${new URL(url).hostname} has no signing URL configured. ` +\n `Ensure <ef-configuration signing-url=\"...\"> is an ancestor of your <ef-preview> or <ef-workbench>.`,\n );\n }\n }\n\n try {\n const fetchPromise = fetch(url, init);\n // Wrap the promise to catch rejections and log the URL\n // Return the promise chain so errors are logged but still propagate\n return fetchPromise.catch((error) => {\n // For AbortErrors, re-throw directly without modification\n // DOMException properties like 'name' are read-only\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n\n console.error(\n \"ContextMixin fetch error\",\n url,\n error,\n window.location.href,\n );\n // Create a new error with the URL in the message, preserving the original error type\n const ErrorConstructor =\n error instanceof Error ? error.constructor : Error;\n const enhancedError = new (ErrorConstructor as typeof Error)(\n `Failed to fetch: ${url}. Original error: ${error instanceof Error ? error.message : String(error)}`,\n );\n // Preserve the original error's properties (except for DOMException which has read-only properties)\n if (error instanceof Error && !(error instanceof DOMException)) {\n enhancedError.name = error.name;\n enhancedError.stack = error.stack;\n // Copy any additional properties from the original error\n Object.assign(enhancedError, error);\n }\n throw enhancedError;\n });\n } catch (error) {\n console.error(\n \"ContextMixin fetch error (synchronous)\",\n url,\n error,\n window.location.href,\n );\n throw error;\n }\n };\n\n // Note: URL token caching is now handled globally via URLTokenDeduplicator\n // Keeping these for any potential backwards compatibility, but they're no longer used\n\n /**\n * Generate a cache key for URL token based on signing strategy\n *\n * Uses unified prefix + parameter matching approach:\n * - For transcode URLs: signs base \"/api/v1/transcode\" + params like {url: \"source.mp4\"}\n * - For regular URLs: signs full URL with empty params {}\n * - All validation uses prefix matching + exhaustive parameter matching\n * - Multiple transcode segments with same source share one token (reduces round-trips)\n */\n #isCrossOrigin(url: string): boolean {\n try {\n const targetUrl = new URL(url, window.location.origin);\n return targetUrl.origin !== window.location.origin;\n } catch {\n return false;\n }\n }\n\n #isEditframeDomain(url: string): boolean {\n try {\n const hostname = new URL(url).hostname;\n return (\n hostname === \"editframe.dev\" ||\n hostname === \"editframe.com\" ||\n hostname.endsWith(\".editframe.dev\") ||\n hostname.endsWith(\".editframe.com\")\n );\n } catch {\n return false;\n }\n }\n\n #getTokenCacheKey(url: string): {\n cacheKey: string;\n signingPayload: { url: string; params?: Record<string, string> };\n } {\n try {\n const urlObj = new URL(url);\n\n // Check if this is a transcode URL pattern\n if (urlObj.pathname.includes(\"/api/v1/transcode/\")) {\n const urlParam = urlObj.searchParams.get(\"url\");\n if (urlParam) {\n // For transcode URLs, sign the base path + url parameter\n const basePath = `${urlObj.origin}/api/v1/transcode`;\n const cacheKey = `${basePath}?url=${urlParam}`;\n return {\n cacheKey,\n signingPayload: { url: basePath, params: { url: urlParam } },\n };\n }\n }\n\n // For non-transcode URLs, use full URL (existing behavior)\n return {\n cacheKey: url,\n signingPayload: { url },\n };\n } catch {\n // If URL parsing fails, fall back to full URL\n return {\n cacheKey: url,\n signingPayload: { url },\n };\n }\n }\n\n /**\n * Parse JWT token to extract safe expiration time (with buffer)\n * @param token JWT token string\n * @returns Safe expiration timestamp in milliseconds (actual expiry minus buffer), or 0 if parsing fails\n */\n #parseTokenExpiration(token: string): number {\n try {\n // JWT has 3 parts separated by dots: header.payload.signature\n const parts = token.split(\".\");\n if (parts.length !== 3) return 0;\n\n // Decode the payload (second part)\n const payload = parts[1];\n if (!payload) return 0;\n\n const decoded = atob(payload.replace(/-/g, \"+\").replace(/_/g, \"/\"));\n const parsed = JSON.parse(decoded);\n\n // Extract timestamps (in seconds)\n const exp = parsed.exp;\n const iat = parsed.iat;\n if (!exp) return 0;\n\n // Calculate token lifetime and buffer\n const lifetimeSeconds = iat ? exp - iat : 3600; // Default to 1 hour if no iat\n const tenPercentBufferMs = lifetimeSeconds * 0.1 * 1000; // 10% of lifetime in ms\n const fiveMinutesMs = 5 * 60 * 1000; // 5 minutes in ms\n\n // Use whichever buffer is smaller (more conservative)\n const bufferMs = Math.min(fiveMinutesMs, tenPercentBufferMs);\n\n // Return expiration time minus buffer\n return exp * 1000 - bufferMs;\n } catch {\n return 0;\n }\n }\n\n #signingURL?: string;\n /**\n * A URL that will be used to generated signed tokens for accessing media files from the\n * editframe API. This is used to authenticate media requests per-user.\n */\n @property({ type: String, attribute: \"signing-url\" })\n get signingURL() {\n return this.#signingURL ?? this.efConfiguration?.signingURL ?? \"\";\n }\n set signingURL(value: string) {\n this.#signingURL = value;\n }\n\n @property({ type: Boolean, reflect: true })\n get playing(): boolean {\n return this.targetTemporal?.playbackController?.playing ?? false;\n }\n set playing(value: boolean) {\n if (this.targetTemporal?.playbackController) {\n this.targetTemporal.playbackController.setPlaying(value);\n }\n }\n\n @property({ type: Boolean, reflect: true, attribute: \"loop\" })\n get loop(): boolean {\n return this.targetTemporal?.playbackController?.loop ?? this.#loop;\n }\n set loop(value: boolean) {\n const oldValue = this.#loop;\n this.#loop = value;\n if (this.targetTemporal?.playbackController) {\n this.targetTemporal.playbackController.setLoop(value);\n }\n this.requestUpdate(\"loop\", oldValue);\n }\n\n @property({ type: Boolean })\n rendering = false;\n\n @property({ type: Number })\n get currentTimeMs(): number {\n return (\n this.targetTemporal?.playbackController?.currentTimeMs ?? Number.NaN\n );\n }\n set currentTimeMs(value: number) {\n if (this.targetTemporal?.playbackController) {\n this.targetTemporal.playbackController.setCurrentTimeMs(value);\n }\n }\n\n #timegroupObserver = new MutationObserver((mutations) => {\n let shouldUpdate = false;\n const undefinedEFTags = new Set<string>();\n\n for (const mutation of mutations) {\n if (mutation.type === \"childList\") {\n const newTemporal = this.findRootTemporal();\n if (newTemporal !== this.targetTemporal) {\n this.targetTemporal = newTemporal;\n shouldUpdate = true;\n } else if (\n mutation.target instanceof Element &&\n isEFTemporal(mutation.target)\n ) {\n // Handle childList changes within existing temporal elements\n shouldUpdate = true;\n }\n\n // Collect ef-* tags from added nodes that haven't upgraded yet.\n // When React hydrates or TimelineRoot renders, the custom element\n // may be inserted before its class is defined, so isEFTemporal()\n // returns false. We need to retry after the element upgrades.\n if (!this.targetTemporal) {\n for (const node of mutation.addedNodes) {\n if (node instanceof Element) {\n this.#collectUndefinedEFTags(node, undefinedEFTags);\n }\n }\n }\n } else if (mutation.type === \"attributes\") {\n // Watch for attribute changes that might affect duration\n const durationAffectingAttributes = [\n \"duration\",\n \"mode\",\n \"trimstart\",\n \"trimend\",\n \"sourcein\",\n \"sourceout\",\n ];\n\n if (\n durationAffectingAttributes.includes(\n mutation.attributeName || \"\",\n ) ||\n (mutation.target instanceof Element &&\n isEFTemporal(mutation.target))\n ) {\n shouldUpdate = true;\n }\n }\n }\n\n if (undefinedEFTags.size > 0) {\n this.#retryTemporalDiscovery(undefinedEFTags);\n }\n\n if (shouldUpdate) {\n // Trigger an update to ensure reactive properties recalculate\n // Use a microtask to ensure DOM updates are complete\n queueMicrotask(() => {\n // Recalculate duration and endTime when temporal element changes\n this.updateDurationProperties();\n this.requestUpdate();\n // Also ensure the targetTemporal updates its computed properties\n if (this.targetTemporal) {\n (this.targetTemporal as any).requestUpdate();\n }\n });\n }\n });\n\n /**\n * Recursively collect ef-* tag names from an element tree that\n * have not yet been registered as custom elements.\n */\n #collectUndefinedEFTags(el: Element, tags: Set<string>): void {\n const tag = el.tagName.toLowerCase();\n if (tag.startsWith(\"ef-\") && !customElements.get(tag)) {\n tags.add(tag);\n }\n for (const child of el.children) {\n this.#collectUndefinedEFTags(child, tags);\n }\n }\n\n /**\n * Wait for unregistered ef-* custom elements to upgrade, then\n * retry findRootTemporal(). Mirrors the whenDefined pattern in play().\n */\n async #retryTemporalDiscovery(tags: Set<string>): Promise<void> {\n await Promise.all(\n [...tags].map((tag) => customElements.whenDefined(tag).catch(() => {})),\n );\n\n if (this.targetTemporal) return; // already found by another path\n\n const found = this.findRootTemporal();\n if (found) {\n this.targetTemporal = found;\n await (found as any).updateComplete;\n this.updateDurationProperties();\n this.requestUpdate();\n }\n }\n\n /**\n * Update duration properties when temporal element changes\n */\n updateDurationProperties(): void {\n const newDuration = this.targetTemporal?.durationMs ?? 0;\n const newEndTime = this.targetTemporal?.endTimeMs ?? 0;\n\n if (this.durationMs !== newDuration) {\n this.durationMs = newDuration;\n }\n\n if (this.endTimeMs !== newEndTime) {\n this.endTimeMs = newEndTime;\n }\n }\n\n connectedCallback(): void {\n super.connectedCallback();\n\n // Create manual context providers for playback state\n this.#playingProvider = new ContextProvider(this, {\n context: playingContext,\n initialValue: this.playing,\n });\n this.#loopProvider = new ContextProvider(this, {\n context: loopContext,\n initialValue: this.loop,\n });\n this.#currentTimeMsProvider = new ContextProvider(this, {\n context: currentTimeContext,\n initialValue: this.currentTimeMs,\n });\n this.#targetTemporalProvider = new ContextProvider(this, {\n context: targetTemporalContext,\n initialValue: this.targetTemporal,\n });\n\n // Initialize targetTemporal to first root temporal element\n this.targetTemporal = this.findRootTemporal();\n // Initialize duration properties\n this.updateDurationProperties();\n\n this.#timegroupObserver.observe(this, {\n childList: true,\n subtree: true,\n attributes: true,\n });\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback();\n this.#timegroupObserver.disconnect();\n\n // Unsubscribe from controller\n if (this.#subscribedController) {\n this.#subscribedController.removeListener(this.#onControllerUpdate);\n this.#controllerSubscribed = false;\n this.#subscribedController = null;\n }\n\n this.pause();\n }\n\n updated(changedProperties: Map<string | number | symbol, unknown>) {\n super.updated?.(changedProperties);\n\n // Subscribe to controller when it becomes available or changes\n const currentController = this.#targetTemporal?.playbackController;\n if (\n currentController &&\n (!this.#controllerSubscribed ||\n this.#subscribedController !== currentController)\n ) {\n // Unsubscribe from old controller if it changed\n if (\n this.#subscribedController &&\n this.#subscribedController !== currentController\n ) {\n this.#subscribedController.removeListener(this.#onControllerUpdate);\n }\n currentController.addListener(this.#onControllerUpdate);\n this.#controllerSubscribed = true;\n this.#subscribedController = currentController;\n\n // Apply stored loop value when playbackController becomes available\n if (this.#loop) {\n currentController.setLoop(this.#loop);\n }\n\n // Trigger initial sync of context providers\n this.#playingProvider.setValue(this.playing);\n this.#loopProvider.setValue(this.loop);\n this.#currentTimeMsProvider.setValue(this.currentTimeMs);\n }\n }\n\n async play() {\n // If targetTemporal is not set, try to find it now\n // This handles cases where the DOM may not have been fully ready during connectedCallback\n if (!this.targetTemporal) {\n // Wait for any temporal custom elements to be defined\n const potentialTemporalTags = Array.from(this.children)\n .map((el) => el.tagName.toLowerCase())\n .filter((tag) => tag.startsWith(\"ef-\"));\n\n await Promise.all(\n potentialTemporalTags.map((tag) =>\n customElements.whenDefined(tag).catch(() => {}),\n ),\n );\n\n const foundTemporal = this.findRootTemporal();\n if (foundTemporal) {\n this.targetTemporal = foundTemporal;\n // Wait for it to initialize\n await (foundTemporal as any).updateComplete;\n } else {\n console.warn(\"No temporal element found to play\");\n return;\n }\n }\n\n // If playbackController doesn't exist yet, wait for it\n if (!this.targetTemporal.playbackController) {\n await (this.targetTemporal as any).updateComplete;\n // After waiting, check again\n if (!this.targetTemporal.playbackController) {\n console.warn(\"PlaybackController not available for temporal element\");\n return;\n }\n }\n\n this.targetTemporal.playbackController.play();\n }\n\n pause() {\n if (this.targetTemporal?.playbackController) {\n this.targetTemporal.playbackController.pause();\n }\n }\n }\n\n return ContextElement as Constructor<ContextMixinInterface> & T;\n}\n"],"mappings":"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"}
|
package/dist/gui/Controllable.js
CHANGED
|
@@ -10,7 +10,7 @@ function isControllable(value) {
|
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* Determines the type of controllable target for subscription purposes.
|
|
13
|
-
*
|
|
13
|
+
*
|
|
14
14
|
* - "context-provider": Target is a ContextMixin (like EFPreview) that provides contexts
|
|
15
15
|
* - "direct-temporal": Target is a root temporal element with its own playbackController
|
|
16
16
|
* - "none": Target is not controllable (null, undefined, or nested temporal)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Controllable.js","names":["durationPollInterval: ReturnType<typeof setInterval> | null"],"sources":["../../src/gui/Controllable.ts"],"sourcesContent":["import type { LitElement } from \"lit\";\n\nimport {\n isEFTemporal,\n type TemporalMixinInterface,\n} from \"../elements/EFTemporal.js\";\nimport { type ContextMixinInterface, isContextMixin } from \"./ContextMixin.js\";\nimport type { PlaybackControllerUpdateEvent } from \"./PlaybackController.js\";\n\nexport declare class ControllableInterface extends LitElement {\n playing: boolean;\n loop: boolean;\n currentTimeMs: number;\n durationMs: number;\n play(): void | Promise<void>;\n pause(): void;\n}\n\nexport function isControllable(value: any): value is ControllableInterface {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n if (isContextMixin(value)) {\n return true;\n }\n\n if (isEFTemporal(value)) {\n const temporal = value as TemporalMixinInterface;\n return temporal.playbackController !== undefined;\n }\n\n return false;\n}\n\nexport type ControllableElement =\n | ContextMixinInterface\n | (TemporalMixinInterface & {\n playbackController: NonNullable<\n TemporalMixinInterface[\"playbackController\"]\n >;\n });\n\n// ============================================================================\n// Core Concept: Controllable Target Type\n// ============================================================================\n// A controllable target is either a context-providing wrapper (EFPreview)\n// OR a direct temporal element with its own playback controller.\n// This enumeration makes the mental model explicit.\n// ============================================================================\n\nexport type ControllableTargetType
|
|
1
|
+
{"version":3,"file":"Controllable.js","names":["durationPollInterval: ReturnType<typeof setInterval> | null"],"sources":["../../src/gui/Controllable.ts"],"sourcesContent":["import type { LitElement } from \"lit\";\n\nimport {\n isEFTemporal,\n type TemporalMixinInterface,\n} from \"../elements/EFTemporal.js\";\nimport { type ContextMixinInterface, isContextMixin } from \"./ContextMixin.js\";\nimport type { PlaybackControllerUpdateEvent } from \"./PlaybackController.js\";\n\nexport declare class ControllableInterface extends LitElement {\n playing: boolean;\n loop: boolean;\n currentTimeMs: number;\n durationMs: number;\n play(): void | Promise<void>;\n pause(): void;\n}\n\nexport function isControllable(value: any): value is ControllableInterface {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n if (isContextMixin(value)) {\n return true;\n }\n\n if (isEFTemporal(value)) {\n const temporal = value as TemporalMixinInterface;\n return temporal.playbackController !== undefined;\n }\n\n return false;\n}\n\nexport type ControllableElement =\n | ContextMixinInterface\n | (TemporalMixinInterface & {\n playbackController: NonNullable<\n TemporalMixinInterface[\"playbackController\"]\n >;\n });\n\n// ============================================================================\n// Core Concept: Controllable Target Type\n// ============================================================================\n// A controllable target is either a context-providing wrapper (EFPreview)\n// OR a direct temporal element with its own playback controller.\n// This enumeration makes the mental model explicit.\n// ============================================================================\n\nexport type ControllableTargetType =\n | \"context-provider\"\n | \"direct-temporal\"\n | \"none\";\n\n/**\n * Determines the type of controllable target for subscription purposes.\n *\n * - \"context-provider\": Target is a ContextMixin (like EFPreview) that provides contexts\n * - \"direct-temporal\": Target is a root temporal element with its own playbackController\n * - \"none\": Target is not controllable (null, undefined, or nested temporal)\n */\nexport function determineTargetType(target: unknown): ControllableTargetType {\n if (!target) return \"none\";\n\n if (isContextMixin(target)) {\n return \"context-provider\";\n }\n\n if (isEFTemporal(target)) {\n const temporal = target as TemporalMixinInterface;\n // Only root temporal elements have playbackController\n // Nested elements delegate to their root\n if (temporal.playbackController) {\n return \"direct-temporal\";\n }\n }\n\n return \"none\";\n}\n\n// ============================================================================\n// Subscription Interface\n// ============================================================================\n// Abstracts the mechanism of subscribing to playback state updates.\n// Different target types use different mechanisms (context vs direct listener).\n// ============================================================================\n\nexport interface SubscriptionCallbacks {\n onPlayingChange(value: boolean): void;\n onLoopChange(value: boolean): void;\n onCurrentTimeMsChange(value: number): void;\n onDurationMsChange(value: number): void;\n onTargetTemporalChange(value: TemporalMixinInterface | null): void;\n onFocusedElementChange?(value: HTMLElement | undefined): void;\n}\n\nexport interface ControllableSubscription {\n unsubscribe(): void;\n}\n\n/**\n * Creates a subscription to a direct temporal element's playback controller.\n * Used when EFControls targets a temporal element directly (not wrapped in EFPreview).\n */\nexport function createDirectTemporalSubscription(\n target: TemporalMixinInterface & HTMLElement,\n callbacks: SubscriptionCallbacks,\n): ControllableSubscription {\n const controller = target.playbackController!;\n\n // Initial sync - propagate current state immediately\n callbacks.onPlayingChange(controller.playing);\n callbacks.onLoopChange(controller.loop);\n callbacks.onCurrentTimeMsChange(controller.currentTimeMs);\n callbacks.onDurationMsChange(target.durationMs);\n callbacks.onTargetTemporalChange(target);\n\n // Subscribe to playback controller updates\n const listener = (event: PlaybackControllerUpdateEvent) => {\n switch (event.property) {\n case \"playing\":\n callbacks.onPlayingChange(event.value as boolean);\n break;\n case \"loop\":\n callbacks.onLoopChange(event.value as boolean);\n break;\n case \"currentTimeMs\":\n callbacks.onCurrentTimeMsChange(event.value as number);\n break;\n }\n };\n controller.addListener(listener);\n\n // Watch for duration changes via MutationObserver on duration-affecting attributes\n const durationObserver = new MutationObserver(() => {\n callbacks.onDurationMsChange(target.durationMs);\n });\n durationObserver.observe(target, {\n attributes: true,\n attributeFilter: [\n \"duration\",\n \"trimstart\",\n \"trimend\",\n \"sourcein\",\n \"sourceout\",\n ],\n subtree: true,\n });\n\n // For media elements (ef-video, ef-audio), also watch for intrinsic duration changes\n // The intrinsicDurationMs comes from mediaEngineTask which loads asynchronously\n let lastKnownDuration = target.durationMs;\n let durationPollInterval: ReturnType<typeof setInterval> | null = null;\n\n // If duration is currently 0, poll until it becomes available\n // This handles the case where media hasn't loaded yet\n if (lastKnownDuration === 0) {\n durationPollInterval = setInterval(() => {\n const currentDuration = target.durationMs;\n if (currentDuration !== lastKnownDuration) {\n lastKnownDuration = currentDuration;\n callbacks.onDurationMsChange(currentDuration);\n // Once we have a non-zero duration, stop polling\n if (currentDuration > 0 && durationPollInterval) {\n clearInterval(durationPollInterval);\n durationPollInterval = null;\n }\n }\n }, 100); // Check every 100ms\n }\n\n return {\n unsubscribe: () => {\n controller.removeListener(listener);\n durationObserver.disconnect();\n if (durationPollInterval) {\n clearInterval(durationPollInterval);\n }\n },\n };\n}\n"],"mappings":";;;;AAkBA,SAAgB,eAAe,OAA4C;AACzE,KAAI,CAAC,SAAS,OAAO,UAAU,SAC7B,QAAO;AAGT,KAAI,eAAe,MAAM,CACvB,QAAO;AAGT,KAAI,aAAa,MAAM,CAErB,QADiB,MACD,uBAAuB;AAGzC,QAAO;;;;;;;;;AA+BT,SAAgB,oBAAoB,QAAyC;AAC3E,KAAI,CAAC,OAAQ,QAAO;AAEpB,KAAI,eAAe,OAAO,CACxB,QAAO;AAGT,KAAI,aAAa,OAAO,EAItB;MAHiB,OAGJ,mBACX,QAAO;;AAIX,QAAO;;;;;;AA2BT,SAAgB,iCACd,QACA,WAC0B;CAC1B,MAAM,aAAa,OAAO;AAG1B,WAAU,gBAAgB,WAAW,QAAQ;AAC7C,WAAU,aAAa,WAAW,KAAK;AACvC,WAAU,sBAAsB,WAAW,cAAc;AACzD,WAAU,mBAAmB,OAAO,WAAW;AAC/C,WAAU,uBAAuB,OAAO;CAGxC,MAAM,YAAY,UAAyC;AACzD,UAAQ,MAAM,UAAd;GACE,KAAK;AACH,cAAU,gBAAgB,MAAM,MAAiB;AACjD;GACF,KAAK;AACH,cAAU,aAAa,MAAM,MAAiB;AAC9C;GACF,KAAK;AACH,cAAU,sBAAsB,MAAM,MAAgB;AACtD;;;AAGN,YAAW,YAAY,SAAS;CAGhC,MAAM,mBAAmB,IAAI,uBAAuB;AAClD,YAAU,mBAAmB,OAAO,WAAW;GAC/C;AACF,kBAAiB,QAAQ,QAAQ;EAC/B,YAAY;EACZ,iBAAiB;GACf;GACA;GACA;GACA;GACA;GACD;EACD,SAAS;EACV,CAAC;CAIF,IAAI,oBAAoB,OAAO;CAC/B,IAAIA,uBAA8D;AAIlE,KAAI,sBAAsB,EACxB,wBAAuB,kBAAkB;EACvC,MAAM,kBAAkB,OAAO;AAC/B,MAAI,oBAAoB,mBAAmB;AACzC,uBAAoB;AACpB,aAAU,mBAAmB,gBAAgB;AAE7C,OAAI,kBAAkB,KAAK,sBAAsB;AAC/C,kBAAc,qBAAqB;AACnC,2BAAuB;;;IAG1B,IAAI;AAGT,QAAO,EACL,mBAAmB;AACjB,aAAW,eAAe,SAAS;AACnC,mBAAiB,YAAY;AAC7B,MAAI,qBACF,eAAc,qBAAqB;IAGxC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as lit21 from "lit";
|
|
2
2
|
import { LitElement } from "lit";
|
|
3
3
|
import * as lit_html20 from "lit-html";
|
|
4
4
|
|
|
@@ -14,7 +14,7 @@ import * as lit_html20 from "lit-html";
|
|
|
14
14
|
* ```
|
|
15
15
|
*/
|
|
16
16
|
declare class EFActiveRootTemporal extends LitElement {
|
|
17
|
-
static styles:
|
|
17
|
+
static styles: lit21.CSSResult;
|
|
18
18
|
/**
|
|
19
19
|
* Canvas element ID or selector to bind to.
|
|
20
20
|
* If not specified, will search for the nearest ef-canvas ancestor.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EFActiveRootTemporal.js","names":["EFActiveRootTemporal"],"sources":["../../src/gui/EFActiveRootTemporal.ts"],"sourcesContent":["import { css, html, LitElement } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport type { EFCanvas } from \"../canvas/EFCanvas.js\";\nimport type { TemporalMixinInterface } from \"../elements/EFTemporal.js\";\n\n/**\n * Displays the ID of the active root temporal element from a canvas.\n * Automatically updates when selection changes.\n
|
|
1
|
+
{"version":3,"file":"EFActiveRootTemporal.js","names":["EFActiveRootTemporal"],"sources":["../../src/gui/EFActiveRootTemporal.ts"],"sourcesContent":["import { css, html, LitElement } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport type { EFCanvas } from \"../canvas/EFCanvas.js\";\nimport type { TemporalMixinInterface } from \"../elements/EFTemporal.js\";\n\n/**\n * Displays the ID of the active root temporal element from a canvas.\n * Automatically updates when selection changes.\n *\n * @example\n * ```html\n * <ef-active-root-temporal canvas=\"canvas\"></ef-active-root-temporal>\n * ```\n */\n@customElement(\"ef-active-root-temporal\")\nexport class EFActiveRootTemporal extends LitElement {\n static styles = css`\n :host {\n display: inline-block;\n }\n `;\n\n /**\n * Canvas element ID or selector to bind to.\n * If not specified, will search for the nearest ef-canvas ancestor.\n */\n @property({ type: String })\n canvas = \"\";\n\n @state()\n private activeRootTemporal: (TemporalMixinInterface & HTMLElement) | null =\n null;\n\n private canvasElement: EFCanvas | null = null;\n private activeroottemporalchangeHandler?: () => void;\n\n connectedCallback(): void {\n super.connectedCallback();\n this.findCanvas();\n this.setupListener();\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback();\n this.removeListener();\n }\n\n protected updated(\n changedProperties: Map<string | number | symbol, unknown>,\n ): void {\n if (changedProperties.has(\"canvas\")) {\n this.findCanvas();\n this.setupListener();\n }\n }\n\n /**\n * Find the canvas element to bind to.\n */\n private findCanvas(): void {\n // If canvas attribute is set, use it\n if (this.canvas) {\n const byId = document.getElementById(this.canvas);\n if (byId && byId.tagName === \"EF-CANVAS\") {\n this.canvasElement = byId as EFCanvas;\n return;\n }\n\n // Try as selector\n try {\n const bySelector = document.querySelector(\n this.canvas,\n ) as EFCanvas | null;\n if (bySelector && bySelector.tagName === \"EF-CANVAS\") {\n this.canvasElement = bySelector;\n return;\n }\n } catch {\n // Invalid selector, ignore\n }\n }\n\n // Fall back to nearest ancestor\n const ancestor = this.closest(\"ef-canvas\") as EFCanvas | null;\n if (ancestor) {\n this.canvasElement = ancestor;\n return;\n }\n\n this.canvasElement = null;\n }\n\n /**\n * Setup listener for activeroottemporalchange events.\n */\n private setupListener(): void {\n this.removeListener();\n\n if (!this.canvasElement) {\n this.activeRootTemporal = null;\n return;\n }\n\n // Get initial value\n const canvasEl = this.canvasElement as any;\n this.activeRootTemporal = canvasEl.activeRootTemporal || null;\n\n // Listen for changes\n this.activeroottemporalchangeHandler = () => {\n const canvasEl = this.canvasElement as any;\n this.activeRootTemporal = canvasEl.activeRootTemporal || null;\n };\n\n this.canvasElement.addEventListener(\n \"activeroottemporalchange\",\n this.activeroottemporalchangeHandler,\n );\n }\n\n /**\n * Remove event listener.\n */\n private removeListener(): void {\n if (this.canvasElement && this.activeroottemporalchangeHandler) {\n this.canvasElement.removeEventListener(\n \"activeroottemporalchange\",\n this.activeroottemporalchangeHandler,\n );\n this.activeroottemporalchangeHandler = undefined;\n }\n }\n\n render() {\n const displayText =\n this.activeRootTemporal?.id || this.textContent || \"None\";\n return html`<span>${displayText}</span>`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-active-root-temporal\": EFActiveRootTemporal;\n }\n}\n"],"mappings":";;;;;AAeO,iCAAMA,+BAA6B,WAAW;;;gBAY1C;4BAIP;uBAEuC;;;gBAjBzB,GAAG;;;;;;CAoBnB,oBAA0B;AACxB,QAAM,mBAAmB;AACzB,OAAK,YAAY;AACjB,OAAK,eAAe;;CAGtB,uBAA6B;AAC3B,QAAM,sBAAsB;AAC5B,OAAK,gBAAgB;;CAGvB,AAAU,QACR,mBACM;AACN,MAAI,kBAAkB,IAAI,SAAS,EAAE;AACnC,QAAK,YAAY;AACjB,QAAK,eAAe;;;;;;CAOxB,AAAQ,aAAmB;AAEzB,MAAI,KAAK,QAAQ;GACf,MAAM,OAAO,SAAS,eAAe,KAAK,OAAO;AACjD,OAAI,QAAQ,KAAK,YAAY,aAAa;AACxC,SAAK,gBAAgB;AACrB;;AAIF,OAAI;IACF,MAAM,aAAa,SAAS,cAC1B,KAAK,OACN;AACD,QAAI,cAAc,WAAW,YAAY,aAAa;AACpD,UAAK,gBAAgB;AACrB;;WAEI;;EAMV,MAAM,WAAW,KAAK,QAAQ,YAAY;AAC1C,MAAI,UAAU;AACZ,QAAK,gBAAgB;AACrB;;AAGF,OAAK,gBAAgB;;;;;CAMvB,AAAQ,gBAAsB;AAC5B,OAAK,gBAAgB;AAErB,MAAI,CAAC,KAAK,eAAe;AACvB,QAAK,qBAAqB;AAC1B;;AAKF,OAAK,qBADY,KAAK,cACa,sBAAsB;AAGzD,OAAK,wCAAwC;AAE3C,QAAK,qBADY,KAAK,cACa,sBAAsB;;AAG3D,OAAK,cAAc,iBACjB,4BACA,KAAK,gCACN;;;;;CAMH,AAAQ,iBAAuB;AAC7B,MAAI,KAAK,iBAAiB,KAAK,iCAAiC;AAC9D,QAAK,cAAc,oBACjB,4BACA,KAAK,gCACN;AACD,QAAK,kCAAkC;;;CAI3C,SAAS;AAGP,SAAO,IAAI,SADT,KAAK,oBAAoB,MAAM,KAAK,eAAe,OACrB;;;YA7GjC,SAAS,EAAE,MAAM,QAAQ,CAAC;YAG1B,OAAO;mCAfT,cAAc,0BAA0B"}
|
package/dist/gui/EFControls.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TemporalMixinInterface } from "../elements/EFTemporal.js";
|
|
2
2
|
import { ControllableInterface } from "./Controllable.js";
|
|
3
3
|
import { FocusContext } from "./focusContext.js";
|
|
4
|
-
import * as
|
|
4
|
+
import * as lit23 from "lit";
|
|
5
5
|
import { LitElement, PropertyValueMap } from "lit";
|
|
6
6
|
|
|
7
7
|
//#region src/gui/EFControls.d.ts
|
|
@@ -26,7 +26,7 @@ import { LitElement, PropertyValueMap } from "lit";
|
|
|
26
26
|
*/
|
|
27
27
|
declare class EFControls extends LitElement {
|
|
28
28
|
#private;
|
|
29
|
-
static styles:
|
|
29
|
+
static styles: lit23.CSSResult;
|
|
30
30
|
createRenderRoot(): this;
|
|
31
31
|
/**
|
|
32
32
|
* The ID of the ef-preview element to control
|
package/dist/gui/EFControls.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { efContext } from "./efContext.js";
|
|
1
2
|
import { currentTimeContext } from "./currentTimeContext.js";
|
|
2
3
|
import { durationContext } from "./durationContext.js";
|
|
3
4
|
import { loopContext, playingContext } from "./playingContext.js";
|
|
4
5
|
import { __decorate } from "../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
|
|
5
6
|
import { isEFTemporal } from "../elements/EFTemporal.js";
|
|
6
|
-
import { efContext } from "./efContext.js";
|
|
7
7
|
import { focusContext } from "./focusContext.js";
|
|
8
8
|
import { focusedElementContext } from "./focusedElementContext.js";
|
|
9
9
|
import { targetTemporalContext } from "./ContextMixin.js";
|
|
@@ -94,7 +94,7 @@ let EFControls = class EFControls$1 extends LitElement {
|
|
|
94
94
|
* Wait for a temporal element to initialize its playbackController.
|
|
95
95
|
* This handles the case where we target a temporal element before it has
|
|
96
96
|
* completed initialization and created its playbackController.
|
|
97
|
-
*
|
|
97
|
+
*
|
|
98
98
|
* Note: playbackController is created in EFTemporal's connectedCallback via
|
|
99
99
|
* `this.updateComplete.then(() => this.didBecomeRoot())`, so we need to wait
|
|
100
100
|
* past updateComplete for the .then() callback to run.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EFControls.js","names":["EFControls","#subscribeAbortController","#contextUnsubscribeMap","#directTemporalSubscription","#subscribeToContextProvider","#subscribeToDirectTemporal","#waitForTemporalToInitialize","#unsubscribe","#subscribe","#resubscribe"],"sources":["../../src/gui/EFControls.ts"],"sourcesContent":["import { type Context, createContext, provide } from \"@lit/context\";\nimport { css, LitElement, type PropertyValueMap } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { attachContextRoot } from \"../attachContextRoot.js\";\nimport { isEFTemporal, type TemporalMixinInterface } from \"../elements/EFTemporal.js\";\nimport { TargetController } from \"../elements/TargetController.js\";\nimport { targetTemporalContext } from \"./ContextMixin.js\";\nimport {\n type ControllableInterface,\n type ControllableSubscription,\n createDirectTemporalSubscription,\n determineTargetType,\n} from \"./Controllable.js\";\nimport { currentTimeContext } from \"./currentTimeContext.js\";\nimport { durationContext } from \"./durationContext.js\";\nimport { efContext } from \"./efContext.js\";\nimport { type FocusContext, focusContext } from \"./focusContext.js\";\nimport { focusedElementContext } from \"./focusedElementContext.js\";\nimport { loopContext, playingContext } from \"./playingContext.js\";\n\nattachContextRoot();\n\nclass ContextRequestEvent extends Event {\n context: Context<any, any>;\n contextTarget: Element;\n callback: (proxy: EFControls, value: any) => void;\n subscribe: boolean;\n\n constructor(\n context: Context<any, any>,\n contextTarget: Element,\n callback: (proxy: EFControls, value: any) => void,\n subscribe: boolean,\n ) {\n super(\"context-request\", { bubbles: true, composed: true });\n this.context = context;\n this.contextTarget = contextTarget;\n this.callback = callback;\n this.subscribe = subscribe ?? false;\n }\n}\n\nconst proxiedContexts = [\n [\n // biome-ignore lint/suspicious/noAssignInExpressions: Intentional assignment in callback\n (proxy: EFControls, value: boolean) => (proxy.playing = value),\n playingContext,\n ],\n [\n // biome-ignore lint/suspicious/noAssignInExpressions: Intentional assignment in callback\n (proxy: EFControls, value: boolean) => (proxy.loop = value),\n loopContext,\n ],\n [\n // biome-ignore lint/suspicious/noAssignInExpressions: Intentional assignment in callback\n (proxy: EFControls, value: number) => (proxy.currentTimeMs = value),\n currentTimeContext,\n ],\n [\n // biome-ignore lint/suspicious/noAssignInExpressions: Intentional assignment in callback\n (proxy: EFControls, value: number) => (proxy.durationMs = value),\n durationContext,\n ],\n [\n (proxy: EFControls, value: TemporalMixinInterface) =>\n // biome-ignore lint/suspicious/noAssignInExpressions: Intentional assignment in callback\n (proxy.targetTemporal = value),\n targetTemporalContext,\n ],\n [\n // biome-ignore lint/suspicious/noAssignInExpressions: Intentional assignment in callback\n (proxy: EFControls, value: HTMLElement) => (proxy.focusedElement = value),\n focusedElementContext,\n ],\n [\n (proxy: EFControls, value: HTMLElement) =>\n // biome-ignore lint/suspicious/noAssignInExpressions: Intentional assignment in callback\n (proxy.focusContext.focusedElement = value),\n focusContext,\n ],\n] as const;\n\nexport const testContext = createContext<string | null>(\"test\");\n\n/**\n * EFControls provides a way to control an ef-preview element that is not a direct ancestor.\n * It bridges the contexts from a target preview element to its children controls.\n *\n * Usage:\n * ```html\n * <ef-preview id=\"my-preview\">...</ef-preview>\n *\n * <ef-controls target=\"my-preview\">\n * <ef-toggle-play>\n * <button slot=\"play\">Play</button>\n * <button slot=\"pause\">Pause</button>\n * </ef-toggle-play>\n * <ef-scrubber></ef-scrubber>\n * <ef-time-display></ef-time-display>\n * </ef-controls>\n * ```\n */\n@customElement(\"ef-controls\")\nexport class EFControls extends LitElement {\n static styles = css`\n :host {\n display: block;\n }\n `;\n\n createRenderRoot() {\n return this;\n }\n\n /**\n * The ID of the ef-preview element to control\n */\n @property({ type: String })\n target = \"\";\n\n /**\n * The target element (set by TargetController)\n */\n @provide({ context: efContext })\n @state()\n targetElement: ControllableInterface | null = null;\n\n @provide({ context: playingContext })\n @state()\n playing = false;\n\n @provide({ context: loopContext })\n @state()\n loop = false;\n\n @provide({ context: currentTimeContext })\n @state()\n currentTimeMs = 0;\n\n @provide({ context: durationContext })\n @state()\n durationMs = 0;\n\n @provide({ context: targetTemporalContext })\n @state()\n targetTemporal: TemporalMixinInterface | null = null;\n\n @provide({ context: focusedElementContext })\n @state()\n focusedElement?: HTMLElement;\n\n @provide({ context: focusContext })\n focusContext = this as FocusContext;\n\n // @ts-expect-error controller is intentionally not referenced directly\n // biome-ignore lint/correctness/noUnusedPrivateClassMembers: Used for side effects\n #targetController = new TargetController(this);\n\n // Subscription tracking for different target types\n #contextUnsubscribeMap = new Map<Context<any, any>, () => void>();\n #directTemporalSubscription: ControllableSubscription | null = null;\n\n #unsubscribe() {\n // Abort any pending async subscription attempts\n this.#subscribeAbortController?.abort();\n this.#subscribeAbortController = null;\n\n // Unsubscribe from context-provider subscriptions\n for (const unsubscribe of this.#contextUnsubscribeMap.values()) {\n unsubscribe();\n }\n this.#contextUnsubscribeMap.clear();\n\n // Unsubscribe from direct-temporal subscription\n if (this.#directTemporalSubscription) {\n this.#directTemporalSubscription.unsubscribe();\n this.#directTemporalSubscription = null;\n }\n\n // Note: We don't reset state here because:\n // 1. During resubscribe, the new subscription will immediately provide values\n // 2. During disconnect, the element is being removed anyway\n // Resetting state would cause race conditions with context consumers\n }\n\n #subscribeAbortController: AbortController | null = null;\n\n #subscribe() {\n if (!this.targetElement) return;\n\n // Cancel any pending async subscription\n this.#subscribeAbortController?.abort();\n this.#subscribeAbortController = new AbortController();\n\n const targetType = determineTargetType(this.targetElement);\n\n switch (targetType) {\n case \"context-provider\":\n this.#subscribeToContextProvider();\n break;\n case \"direct-temporal\":\n this.#subscribeToDirectTemporal();\n break;\n case \"none\":\n // Target might be a temporal that hasn't initialized yet\n // Try to wait for it to become controllable\n this.#waitForTemporalToInitialize();\n break;\n }\n }\n\n /**\n * Wait for a temporal element to initialize its playbackController.\n * This handles the case where we target a temporal element before it has\n * completed initialization and created its playbackController.\n * \n * Note: playbackController is created in EFTemporal's connectedCallback via\n * `this.updateComplete.then(() => this.didBecomeRoot())`, so we need to wait\n * past updateComplete for the .then() callback to run.\n */\n async #waitForTemporalToInitialize() {\n if (!this.targetElement || !isEFTemporal(this.targetElement)) return;\n\n const temporal = this.targetElement as TemporalMixinInterface & HTMLElement;\n const signal = this.#subscribeAbortController?.signal;\n\n // Wait for the element to finish its update cycle\n await temporal.updateComplete;\n if (signal?.aborted) return;\n\n // playbackController is created in a .then() callback after updateComplete,\n // so we need to wait for that microtask to complete\n if (!temporal.playbackController) {\n await new Promise((resolve) => requestAnimationFrame(resolve));\n if (signal?.aborted) return;\n await temporal.updateComplete;\n if (signal?.aborted) return;\n }\n\n // Check again if it now has a playbackController\n if (temporal.playbackController) {\n this.#subscribeToDirectTemporal();\n }\n }\n\n /**\n * Subscribe to a context-providing target (like EFPreview).\n * Uses Lit Context dispatch mechanism.\n */\n #subscribeToContextProvider() {\n if (!this.targetElement) return;\n\n for (const [callback, context] of proxiedContexts) {\n const event = new ContextRequestEvent(\n context,\n this,\n (value, unsubscribe) => {\n callback(this, value as never);\n this.#contextUnsubscribeMap.set(context, unsubscribe);\n },\n true,\n );\n this.targetElement.dispatchEvent(event);\n }\n }\n\n /**\n * Subscribe to a direct temporal element's playback controller.\n * Used when targeting ef-timegroup, ef-video, ef-audio directly without ef-preview wrapper.\n */\n async #subscribeToDirectTemporal() {\n if (!this.targetElement || !isEFTemporal(this.targetElement)) return;\n\n const temporal = this.targetElement as TemporalMixinInterface & HTMLElement;\n const signal = this.#subscribeAbortController?.signal;\n\n // Wait for the temporal element to complete initialization\n // This ensures playbackController exists\n await temporal.updateComplete;\n if (signal?.aborted) return;\n\n // If playbackController still doesn't exist, the element might need another update cycle\n if (!temporal.playbackController) {\n // Wait one more frame for didBecomeRoot to create the controller\n await new Promise((resolve) => requestAnimationFrame(resolve));\n if (signal?.aborted) return;\n await temporal.updateComplete;\n if (signal?.aborted) return;\n }\n\n if (!temporal.playbackController) {\n // Still no controller - this element is likely a nested temporal\n return;\n }\n\n this.#directTemporalSubscription = createDirectTemporalSubscription(\n temporal,\n {\n onPlayingChange: (value) => {\n this.playing = value;\n },\n onLoopChange: (value) => {\n this.loop = value;\n },\n onCurrentTimeMsChange: (value) => {\n this.currentTimeMs = value;\n },\n onDurationMsChange: (value) => {\n this.durationMs = value;\n },\n onTargetTemporalChange: (value) => {\n this.targetTemporal = value;\n },\n onFocusedElementChange: (value) => {\n this.focusedElement = value;\n },\n },\n );\n }\n\n #resubscribe() {\n this.#unsubscribe();\n this.#subscribe();\n }\n updated(changedProperties: PropertyValueMap<this>) {\n super.updated(changedProperties);\n if (changedProperties.has(\"targetElement\")) {\n this.#resubscribe();\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.#unsubscribe();\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-controls\": EFControls;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAoBA,mBAAmB;AAEnB,IAAM,sBAAN,cAAkC,MAAM;CAMtC,YACE,SACA,eACA,UACA,WACA;AACA,QAAM,mBAAmB;GAAE,SAAS;GAAM,UAAU;GAAM,CAAC;AAC3D,OAAK,UAAU;AACf,OAAK,gBAAgB;AACrB,OAAK,WAAW;AAChB,OAAK,YAAY,aAAa;;;AAIlC,MAAM,kBAAkB;CACtB,EAEG,OAAmB,UAAoB,MAAM,UAAU,OACxD,eACD;CACD,EAEG,OAAmB,UAAoB,MAAM,OAAO,OACrD,YACD;CACD,EAEG,OAAmB,UAAmB,MAAM,gBAAgB,OAC7D,mBACD;CACD,EAEG,OAAmB,UAAmB,MAAM,aAAa,OAC1D,gBACD;CACD,EACG,OAAmB,UAEjB,MAAM,iBAAiB,OAC1B,sBACD;CACD,EAEG,OAAmB,UAAwB,MAAM,iBAAiB,OACnE,sBACD;CACD,EACG,OAAmB,UAEjB,MAAM,aAAa,iBAAiB,OACvC,aACD;CACF;AAED,MAAa,cAAc,cAA6B,OAAO;AAqBxD,uBAAMA,qBAAmB,WAAW;;;gBAehC;uBAOqC;iBAIpC;cAIH;uBAIS;oBAIH;wBAImC;sBAOjC;;;gBAhDC,GAAG;;;;;;CAMnB,mBAAmB;AACjB,SAAO;;CA6CT,oBAAoB,IAAI,iBAAiB,KAAK;CAG9C,yCAAyB,IAAI,KAAoC;CACjE,8BAA+D;CAE/D,eAAe;AAEb,QAAKC,0BAA2B,OAAO;AACvC,QAAKA,2BAA4B;AAGjC,OAAK,MAAM,eAAe,MAAKC,sBAAuB,QAAQ,CAC5D,cAAa;AAEf,QAAKA,sBAAuB,OAAO;AAGnC,MAAI,MAAKC,4BAA6B;AACpC,SAAKA,2BAA4B,aAAa;AAC9C,SAAKA,6BAA8B;;;CASvC,4BAAoD;CAEpD,aAAa;AACX,MAAI,CAAC,KAAK,cAAe;AAGzB,QAAKF,0BAA2B,OAAO;AACvC,QAAKA,2BAA4B,IAAI,iBAAiB;AAItD,UAFmB,oBAAoB,KAAK,cAAc,EAE1D;GACE,KAAK;AACH,UAAKG,4BAA6B;AAClC;GACF,KAAK;AACH,UAAKC,2BAA4B;AACjC;GACF,KAAK;AAGH,UAAKC,6BAA8B;AACnC;;;;;;;;;;;;CAaN,OAAMA,8BAA+B;AACnC,MAAI,CAAC,KAAK,iBAAiB,CAAC,aAAa,KAAK,cAAc,CAAE;EAE9D,MAAM,WAAW,KAAK;EACtB,MAAM,SAAS,MAAKL,0BAA2B;AAG/C,QAAM,SAAS;AACf,MAAI,QAAQ,QAAS;AAIrB,MAAI,CAAC,SAAS,oBAAoB;AAChC,SAAM,IAAI,SAAS,YAAY,sBAAsB,QAAQ,CAAC;AAC9D,OAAI,QAAQ,QAAS;AACrB,SAAM,SAAS;AACf,OAAI,QAAQ,QAAS;;AAIvB,MAAI,SAAS,mBACX,OAAKI,2BAA4B;;;;;;CAQrC,8BAA8B;AAC5B,MAAI,CAAC,KAAK,cAAe;AAEzB,OAAK,MAAM,CAAC,UAAU,YAAY,iBAAiB;GACjD,MAAM,QAAQ,IAAI,oBAChB,SACA,OACC,OAAO,gBAAgB;AACtB,aAAS,MAAM,MAAe;AAC9B,UAAKH,sBAAuB,IAAI,SAAS,YAAY;MAEvD,KACD;AACD,QAAK,cAAc,cAAc,MAAM;;;;;;;CAQ3C,OAAMG,4BAA6B;AACjC,MAAI,CAAC,KAAK,iBAAiB,CAAC,aAAa,KAAK,cAAc,CAAE;EAE9D,MAAM,WAAW,KAAK;EACtB,MAAM,SAAS,MAAKJ,0BAA2B;AAI/C,QAAM,SAAS;AACf,MAAI,QAAQ,QAAS;AAGrB,MAAI,CAAC,SAAS,oBAAoB;AAEhC,SAAM,IAAI,SAAS,YAAY,sBAAsB,QAAQ,CAAC;AAC9D,OAAI,QAAQ,QAAS;AACrB,SAAM,SAAS;AACf,OAAI,QAAQ,QAAS;;AAGvB,MAAI,CAAC,SAAS,mBAEZ;AAGF,QAAKE,6BAA8B,iCACjC,UACA;GACE,kBAAkB,UAAU;AAC1B,SAAK,UAAU;;GAEjB,eAAe,UAAU;AACvB,SAAK,OAAO;;GAEd,wBAAwB,UAAU;AAChC,SAAK,gBAAgB;;GAEvB,qBAAqB,UAAU;AAC7B,SAAK,aAAa;;GAEpB,yBAAyB,UAAU;AACjC,SAAK,iBAAiB;;GAExB,yBAAyB,UAAU;AACjC,SAAK,iBAAiB;;GAEzB,CACF;;CAGH,eAAe;AACb,QAAKI,aAAc;AACnB,QAAKC,WAAY;;CAEnB,QAAQ,mBAA2C;AACjD,QAAM,QAAQ,kBAAkB;AAChC,MAAI,kBAAkB,IAAI,gBAAgB,CACxC,OAAKC,aAAc;;CAIvB,uBAAuB;AACrB,QAAM,sBAAsB;AAC5B,QAAKF,aAAc;;;YAxNpB,SAAS,EAAE,MAAM,QAAQ,CAAC;YAM1B,QAAQ,EAAE,SAAS,WAAW,CAAC,EAC/B,OAAO;YAGP,QAAQ,EAAE,SAAS,gBAAgB,CAAC,EACpC,OAAO;YAGP,QAAQ,EAAE,SAAS,aAAa,CAAC,EACjC,OAAO;YAGP,QAAQ,EAAE,SAAS,oBAAoB,CAAC,EACxC,OAAO;YAGP,QAAQ,EAAE,SAAS,iBAAiB,CAAC,EACrC,OAAO;YAGP,QAAQ,EAAE,SAAS,uBAAuB,CAAC,EAC3C,OAAO;YAGP,QAAQ,EAAE,SAAS,uBAAuB,CAAC,EAC3C,OAAO;YAGP,QAAQ,EAAE,SAAS,cAAc,CAAC;yBAjDpC,cAAc,cAAc"}
|
|
1
|
+
{"version":3,"file":"EFControls.js","names":["EFControls","#subscribeAbortController","#contextUnsubscribeMap","#directTemporalSubscription","#subscribeToContextProvider","#subscribeToDirectTemporal","#waitForTemporalToInitialize","#unsubscribe","#subscribe","#resubscribe"],"sources":["../../src/gui/EFControls.ts"],"sourcesContent":["import { type Context, createContext, provide } from \"@lit/context\";\nimport { css, LitElement, type PropertyValueMap } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { attachContextRoot } from \"../attachContextRoot.js\";\nimport {\n isEFTemporal,\n type TemporalMixinInterface,\n} from \"../elements/EFTemporal.js\";\nimport { TargetController } from \"../elements/TargetController.js\";\nimport { targetTemporalContext } from \"./ContextMixin.js\";\nimport {\n type ControllableInterface,\n type ControllableSubscription,\n createDirectTemporalSubscription,\n determineTargetType,\n} from \"./Controllable.js\";\nimport { currentTimeContext } from \"./currentTimeContext.js\";\nimport { durationContext } from \"./durationContext.js\";\nimport { efContext } from \"./efContext.js\";\nimport { type FocusContext, focusContext } from \"./focusContext.js\";\nimport { focusedElementContext } from \"./focusedElementContext.js\";\nimport { loopContext, playingContext } from \"./playingContext.js\";\n\nattachContextRoot();\n\nclass ContextRequestEvent extends Event {\n context: Context<any, any>;\n contextTarget: Element;\n callback: (proxy: EFControls, value: any) => void;\n subscribe: boolean;\n\n constructor(\n context: Context<any, any>,\n contextTarget: Element,\n callback: (proxy: EFControls, value: any) => void,\n subscribe: boolean,\n ) {\n super(\"context-request\", { bubbles: true, composed: true });\n this.context = context;\n this.contextTarget = contextTarget;\n this.callback = callback;\n this.subscribe = subscribe ?? false;\n }\n}\n\nconst proxiedContexts = [\n [\n // biome-ignore lint/suspicious/noAssignInExpressions: Intentional assignment in callback\n (proxy: EFControls, value: boolean) => (proxy.playing = value),\n playingContext,\n ],\n [\n // biome-ignore lint/suspicious/noAssignInExpressions: Intentional assignment in callback\n (proxy: EFControls, value: boolean) => (proxy.loop = value),\n loopContext,\n ],\n [\n // biome-ignore lint/suspicious/noAssignInExpressions: Intentional assignment in callback\n (proxy: EFControls, value: number) => (proxy.currentTimeMs = value),\n currentTimeContext,\n ],\n [\n // biome-ignore lint/suspicious/noAssignInExpressions: Intentional assignment in callback\n (proxy: EFControls, value: number) => (proxy.durationMs = value),\n durationContext,\n ],\n [\n (proxy: EFControls, value: TemporalMixinInterface) =>\n // biome-ignore lint/suspicious/noAssignInExpressions: Intentional assignment in callback\n (proxy.targetTemporal = value),\n targetTemporalContext,\n ],\n [\n // biome-ignore lint/suspicious/noAssignInExpressions: Intentional assignment in callback\n (proxy: EFControls, value: HTMLElement) => (proxy.focusedElement = value),\n focusedElementContext,\n ],\n [\n (proxy: EFControls, value: HTMLElement) =>\n // biome-ignore lint/suspicious/noAssignInExpressions: Intentional assignment in callback\n (proxy.focusContext.focusedElement = value),\n focusContext,\n ],\n] as const;\n\nexport const testContext = createContext<string | null>(\"test\");\n\n/**\n * EFControls provides a way to control an ef-preview element that is not a direct ancestor.\n * It bridges the contexts from a target preview element to its children controls.\n *\n * Usage:\n * ```html\n * <ef-preview id=\"my-preview\">...</ef-preview>\n *\n * <ef-controls target=\"my-preview\">\n * <ef-toggle-play>\n * <button slot=\"play\">Play</button>\n * <button slot=\"pause\">Pause</button>\n * </ef-toggle-play>\n * <ef-scrubber></ef-scrubber>\n * <ef-time-display></ef-time-display>\n * </ef-controls>\n * ```\n */\n@customElement(\"ef-controls\")\nexport class EFControls extends LitElement {\n static styles = css`\n :host {\n display: block;\n }\n `;\n\n createRenderRoot() {\n return this;\n }\n\n /**\n * The ID of the ef-preview element to control\n */\n @property({ type: String })\n target = \"\";\n\n /**\n * The target element (set by TargetController)\n */\n @provide({ context: efContext })\n @state()\n targetElement: ControllableInterface | null = null;\n\n @provide({ context: playingContext })\n @state()\n playing = false;\n\n @provide({ context: loopContext })\n @state()\n loop = false;\n\n @provide({ context: currentTimeContext })\n @state()\n currentTimeMs = 0;\n\n @provide({ context: durationContext })\n @state()\n durationMs = 0;\n\n @provide({ context: targetTemporalContext })\n @state()\n targetTemporal: TemporalMixinInterface | null = null;\n\n @provide({ context: focusedElementContext })\n @state()\n focusedElement?: HTMLElement;\n\n @provide({ context: focusContext })\n focusContext = this as FocusContext;\n\n // @ts-expect-error controller is intentionally not referenced directly\n // biome-ignore lint/correctness/noUnusedPrivateClassMembers: Used for side effects\n #targetController = new TargetController(this);\n\n // Subscription tracking for different target types\n #contextUnsubscribeMap = new Map<Context<any, any>, () => void>();\n #directTemporalSubscription: ControllableSubscription | null = null;\n\n #unsubscribe() {\n // Abort any pending async subscription attempts\n this.#subscribeAbortController?.abort();\n this.#subscribeAbortController = null;\n\n // Unsubscribe from context-provider subscriptions\n for (const unsubscribe of this.#contextUnsubscribeMap.values()) {\n unsubscribe();\n }\n this.#contextUnsubscribeMap.clear();\n\n // Unsubscribe from direct-temporal subscription\n if (this.#directTemporalSubscription) {\n this.#directTemporalSubscription.unsubscribe();\n this.#directTemporalSubscription = null;\n }\n\n // Note: We don't reset state here because:\n // 1. During resubscribe, the new subscription will immediately provide values\n // 2. During disconnect, the element is being removed anyway\n // Resetting state would cause race conditions with context consumers\n }\n\n #subscribeAbortController: AbortController | null = null;\n\n #subscribe() {\n if (!this.targetElement) return;\n\n // Cancel any pending async subscription\n this.#subscribeAbortController?.abort();\n this.#subscribeAbortController = new AbortController();\n\n const targetType = determineTargetType(this.targetElement);\n\n switch (targetType) {\n case \"context-provider\":\n this.#subscribeToContextProvider();\n break;\n case \"direct-temporal\":\n this.#subscribeToDirectTemporal();\n break;\n case \"none\":\n // Target might be a temporal that hasn't initialized yet\n // Try to wait for it to become controllable\n this.#waitForTemporalToInitialize();\n break;\n }\n }\n\n /**\n * Wait for a temporal element to initialize its playbackController.\n * This handles the case where we target a temporal element before it has\n * completed initialization and created its playbackController.\n *\n * Note: playbackController is created in EFTemporal's connectedCallback via\n * `this.updateComplete.then(() => this.didBecomeRoot())`, so we need to wait\n * past updateComplete for the .then() callback to run.\n */\n async #waitForTemporalToInitialize() {\n if (!this.targetElement || !isEFTemporal(this.targetElement)) return;\n\n const temporal = this.targetElement as TemporalMixinInterface & HTMLElement;\n const signal = this.#subscribeAbortController?.signal;\n\n // Wait for the element to finish its update cycle\n await temporal.updateComplete;\n if (signal?.aborted) return;\n\n // playbackController is created in a .then() callback after updateComplete,\n // so we need to wait for that microtask to complete\n if (!temporal.playbackController) {\n await new Promise((resolve) => requestAnimationFrame(resolve));\n if (signal?.aborted) return;\n await temporal.updateComplete;\n if (signal?.aborted) return;\n }\n\n // Check again if it now has a playbackController\n if (temporal.playbackController) {\n this.#subscribeToDirectTemporal();\n }\n }\n\n /**\n * Subscribe to a context-providing target (like EFPreview).\n * Uses Lit Context dispatch mechanism.\n */\n #subscribeToContextProvider() {\n if (!this.targetElement) return;\n\n for (const [callback, context] of proxiedContexts) {\n const event = new ContextRequestEvent(\n context,\n this,\n (value, unsubscribe) => {\n callback(this, value as never);\n this.#contextUnsubscribeMap.set(context, unsubscribe);\n },\n true,\n );\n this.targetElement.dispatchEvent(event);\n }\n }\n\n /**\n * Subscribe to a direct temporal element's playback controller.\n * Used when targeting ef-timegroup, ef-video, ef-audio directly without ef-preview wrapper.\n */\n async #subscribeToDirectTemporal() {\n if (!this.targetElement || !isEFTemporal(this.targetElement)) return;\n\n const temporal = this.targetElement as TemporalMixinInterface & HTMLElement;\n const signal = this.#subscribeAbortController?.signal;\n\n // Wait for the temporal element to complete initialization\n // This ensures playbackController exists\n await temporal.updateComplete;\n if (signal?.aborted) return;\n\n // If playbackController still doesn't exist, the element might need another update cycle\n if (!temporal.playbackController) {\n // Wait one more frame for didBecomeRoot to create the controller\n await new Promise((resolve) => requestAnimationFrame(resolve));\n if (signal?.aborted) return;\n await temporal.updateComplete;\n if (signal?.aborted) return;\n }\n\n if (!temporal.playbackController) {\n // Still no controller - this element is likely a nested temporal\n return;\n }\n\n this.#directTemporalSubscription = createDirectTemporalSubscription(\n temporal,\n {\n onPlayingChange: (value) => {\n this.playing = value;\n },\n onLoopChange: (value) => {\n this.loop = value;\n },\n onCurrentTimeMsChange: (value) => {\n this.currentTimeMs = value;\n },\n onDurationMsChange: (value) => {\n this.durationMs = value;\n },\n onTargetTemporalChange: (value) => {\n this.targetTemporal = value;\n },\n onFocusedElementChange: (value) => {\n this.focusedElement = value;\n },\n },\n );\n }\n\n #resubscribe() {\n this.#unsubscribe();\n this.#subscribe();\n }\n updated(changedProperties: PropertyValueMap<this>) {\n super.updated(changedProperties);\n if (changedProperties.has(\"targetElement\")) {\n this.#resubscribe();\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.#unsubscribe();\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-controls\": EFControls;\n }\n}\n"],"mappings":"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"}
|
package/dist/gui/EFDial.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as lit22 from "lit";
|
|
2
2
|
import { LitElement } from "lit";
|
|
3
3
|
import * as lit_html21 from "lit-html";
|
|
4
4
|
|
|
@@ -13,7 +13,7 @@ declare class EFDial extends LitElement {
|
|
|
13
13
|
private isDragging;
|
|
14
14
|
private dragStartAngle;
|
|
15
15
|
private dragStartValue;
|
|
16
|
-
static styles:
|
|
16
|
+
static styles: lit22.CSSResult;
|
|
17
17
|
private getAngleFromPoint;
|
|
18
18
|
private handlePointerDown;
|
|
19
19
|
private handlePointerMove;
|
package/dist/gui/EFDial.js
CHANGED
|
@@ -29,26 +29,28 @@ let EFDial = class EFDial$1 extends LitElement {
|
|
|
29
29
|
display: inline-block;
|
|
30
30
|
width: 200px; /* Default size, can be overridden by CSS */
|
|
31
31
|
height: 200px; /* Default size, can be overridden by CSS */
|
|
32
|
+
--dial-stroke: var(--ef-color-border);
|
|
33
|
+
--dial-tick: var(--ef-color-border-subtle);
|
|
32
34
|
}
|
|
33
35
|
.dial-container {
|
|
34
36
|
position: relative;
|
|
35
37
|
width: 100%;
|
|
36
38
|
height: 100%;
|
|
37
39
|
border-radius: 50%;
|
|
38
|
-
background-color:
|
|
39
|
-
border: 2px solid
|
|
40
|
+
background-color: var(--ef-color-bg-panel);
|
|
41
|
+
border: 2px solid var(--ef-color-border);
|
|
40
42
|
}
|
|
41
43
|
.handle {
|
|
42
44
|
position: absolute;
|
|
43
45
|
width: 16px;
|
|
44
46
|
height: 16px;
|
|
45
47
|
border-radius: 50%;
|
|
46
|
-
border: 2px solid
|
|
47
|
-
background-color:
|
|
48
|
+
border: 2px solid var(--ef-color-primary);
|
|
49
|
+
background-color: var(--ef-color-bg-elevated);
|
|
48
50
|
cursor: grab;
|
|
49
51
|
}
|
|
50
52
|
.handle.dragging {
|
|
51
|
-
background-color:
|
|
53
|
+
background-color: var(--ef-color-primary);
|
|
52
54
|
cursor: grabbing;
|
|
53
55
|
}
|
|
54
56
|
.center-text {
|
|
@@ -56,12 +58,13 @@ let EFDial = class EFDial$1 extends LitElement {
|
|
|
56
58
|
top: 50%;
|
|
57
59
|
left: 50%;
|
|
58
60
|
transform: translate(-50%, -50%);
|
|
59
|
-
background-color:
|
|
60
|
-
border: 1px solid
|
|
61
|
+
background-color: var(--ef-color-bg-elevated);
|
|
62
|
+
border: 1px solid var(--ef-color-border);
|
|
61
63
|
padding: 2px 4px;
|
|
62
64
|
border-radius: 4px;
|
|
63
65
|
font-family: monospace;
|
|
64
66
|
font-size: 12px;
|
|
67
|
+
color: var(--ef-color-text);
|
|
65
68
|
}
|
|
66
69
|
`;
|
|
67
70
|
}
|
|
@@ -119,7 +122,7 @@ let EFDial = class EFDial$1 extends LitElement {
|
|
|
119
122
|
cy=${center}
|
|
120
123
|
r=${radius}
|
|
121
124
|
fill="none"
|
|
122
|
-
stroke="
|
|
125
|
+
stroke="var(--dial-stroke)"
|
|
123
126
|
stroke-width="2"
|
|
124
127
|
stroke-dasharray="4 4"
|
|
125
128
|
/>
|
|
@@ -130,7 +133,7 @@ let EFDial = class EFDial$1 extends LitElement {
|
|
|
130
133
|
270
|
|
131
134
|
].map((deg) => {
|
|
132
135
|
const angle = deg * Math.PI / 180;
|
|
133
|
-
return html`<line x1=${center + Math.cos(angle) * (radius - 8)} y1=${center + Math.sin(angle) * (radius - 8)} x2=${center + Math.cos(angle) * (radius + 8)} y2=${center + Math.sin(angle) * (radius + 8)} stroke="
|
|
136
|
+
return html`<line x1=${center + Math.cos(angle) * (radius - 8)} y1=${center + Math.sin(angle) * (radius - 8)} x2=${center + Math.cos(angle) * (radius + 8)} y2=${center + Math.sin(angle) * (radius + 8)} stroke="var(--dial-tick)" stroke-width="2" />`;
|
|
134
137
|
})}
|
|
135
138
|
</svg>
|
|
136
139
|
<div class="handle ${this.isDragging ? "dragging" : ""}" style=${styleMap(handleStyles)}></div>
|
package/dist/gui/EFDial.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EFDial.js","names":["EFDial"],"sources":["../../src/gui/EFDial.ts"],"sourcesContent":["import { css, html, LitElement } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { styleMap } from \"lit/directives/style-map.js\";\n\nexport interface DialChangeDetail {\n value: number;\n}\n\n@customElement(\"ef-dial\")\nexport class EFDial extends LitElement {\n @property({ type: Number })\n set value(newValue: number) {\n // Normalize to 0-360 range\n newValue = newValue % 360;\n if (newValue < 0) {\n newValue += 360;\n }\n // Limit to 6 significant digits\n newValue = Number.parseFloat(newValue.toPrecision(6));\n\n const oldValue = this._value;\n this._value = newValue;\n this.requestUpdate(\"value\", oldValue);\n }\n\n get value() {\n return this._value;\n }\n\n private _value = 0;\n\n @state()\n private isDragging = false;\n\n private dragStartAngle = 0;\n private dragStartValue = 0;\n\n static styles = css`\n :host {\n display: inline-block;\n width: 200px; /* Default size, can be overridden by CSS */\n height: 200px; /* Default size, can be overridden by CSS */\n }\n .dial-container {\n position: relative;\n width: 100%;\n height: 100%;\n border-radius: 50%;\n background-color:
|
|
1
|
+
{"version":3,"file":"EFDial.js","names":["EFDial"],"sources":["../../src/gui/EFDial.ts"],"sourcesContent":["import { css, html, LitElement } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { styleMap } from \"lit/directives/style-map.js\";\n\nexport interface DialChangeDetail {\n value: number;\n}\n\n@customElement(\"ef-dial\")\nexport class EFDial extends LitElement {\n @property({ type: Number })\n set value(newValue: number) {\n // Normalize to 0-360 range\n newValue = newValue % 360;\n if (newValue < 0) {\n newValue += 360;\n }\n // Limit to 6 significant digits\n newValue = Number.parseFloat(newValue.toPrecision(6));\n\n const oldValue = this._value;\n this._value = newValue;\n this.requestUpdate(\"value\", oldValue);\n }\n\n get value() {\n return this._value;\n }\n\n private _value = 0;\n\n @state()\n private isDragging = false;\n\n private dragStartAngle = 0;\n private dragStartValue = 0;\n\n static styles = css`\n :host {\n display: inline-block;\n width: 200px; /* Default size, can be overridden by CSS */\n height: 200px; /* Default size, can be overridden by CSS */\n --dial-stroke: var(--ef-color-border);\n --dial-tick: var(--ef-color-border-subtle);\n }\n .dial-container {\n position: relative;\n width: 100%;\n height: 100%;\n border-radius: 50%;\n background-color: var(--ef-color-bg-panel);\n border: 2px solid var(--ef-color-border);\n }\n .handle {\n position: absolute;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n border: 2px solid var(--ef-color-primary);\n background-color: var(--ef-color-bg-elevated);\n cursor: grab;\n }\n .handle.dragging {\n background-color: var(--ef-color-primary);\n cursor: grabbing;\n }\n .center-text {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background-color: var(--ef-color-bg-elevated);\n border: 1px solid var(--ef-color-border);\n padding: 2px 4px;\n border-radius: 4px;\n font-family: monospace;\n font-size: 12px;\n color: var(--ef-color-text);\n }\n `;\n\n private getAngleFromPoint(clientX: number, clientY: number, rect: DOMRect) {\n const center = this.clientWidth / 2;\n const x = clientX - rect.left - center;\n const y = clientY - rect.top - center;\n return Math.atan2(y, x);\n }\n\n private handlePointerDown(e: PointerEvent) {\n e.preventDefault();\n this.isDragging = true;\n const rect = this.getBoundingClientRect();\n this.dragStartAngle = this.getAngleFromPoint(e.clientX, e.clientY, rect);\n this.dragStartValue = this.value;\n this.setPointerCapture(e.pointerId);\n this.addEventListener(\"pointermove\", this.handlePointerMove, {\n passive: false,\n });\n this.addEventListener(\"pointerup\", this.handlePointerUp, {\n passive: false,\n });\n }\n\n private handlePointerMove(e: PointerEvent) {\n if (!this.isDragging) return;\n\n e.preventDefault();\n const rect = this.getBoundingClientRect();\n const currentAngle = this.getAngleFromPoint(e.clientX, e.clientY, rect);\n const angleDelta = currentAngle - this.dragStartAngle;\n\n let newValue = this.dragStartValue + (angleDelta * 180) / Math.PI;\n\n if (e.shiftKey) {\n newValue = Math.round(newValue / 15) * 15;\n }\n\n // Normalize to 0-360 range\n newValue = newValue % 360;\n if (newValue < 0) {\n newValue += 360;\n }\n\n // Limit to 6 significant digits\n newValue = Number.parseFloat(newValue.toPrecision(6));\n\n this.value = newValue;\n this.dispatchEvent(\n new CustomEvent<DialChangeDetail>(\"change\", {\n detail: { value: this.value },\n }),\n );\n }\n\n private handlePointerUp(e: PointerEvent) {\n e.preventDefault();\n this.isDragging = false;\n this.releasePointerCapture(e.pointerId);\n this.removeEventListener(\"pointermove\", this.handlePointerMove);\n this.removeEventListener(\"pointerup\", this.handlePointerUp);\n }\n\n render() {\n const center = this.clientWidth / 2;\n const radius = center - 20;\n const handleAngle = (this.value * Math.PI) / 180;\n const handleX = center + Math.cos(handleAngle) * radius;\n const handleY = center + Math.sin(handleAngle) * radius;\n\n const handleStyles = {\n left: `${handleX - 8}px`,\n top: `${handleY - 8}px`,\n };\n\n return html`\n <div class=\"dial-container\" @pointerdown=${this.handlePointerDown}>\n <svg class=\"absolute inset-0 w-full h-full\">\n <circle\n cx=${center}\n cy=${center}\n r=${radius}\n fill=\"none\"\n stroke=\"var(--dial-stroke)\"\n stroke-width=\"2\"\n stroke-dasharray=\"4 4\"\n />\n ${[0, 90, 180, 270].map((deg) => {\n const angle = (deg * Math.PI) / 180;\n const x1 = center + Math.cos(angle) * (radius - 8);\n const y1 = center + Math.sin(angle) * (radius - 8);\n const x2 = center + Math.cos(angle) * (radius + 8);\n const y2 = center + Math.sin(angle) * (radius + 8);\n return html`<line x1=${x1} y1=${y1} x2=${x2} y2=${y2} stroke=\"var(--dial-tick)\" stroke-width=\"2\" />`;\n })}\n </svg>\n <div class=\"handle ${this.isDragging ? \"dragging\" : \"\"}\" style=${styleMap(handleStyles)}></div>\n <div class=\"center-text\">${this.value.toFixed(0)}°</div>\n </div>\n `;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { TemporalMixinInterface } from "../elements/EFTemporal.js";
|
|
2
2
|
import "./timeline/EFTimeline.js";
|
|
3
|
+
import * as lit10 from "lit";
|
|
3
4
|
import { LitElement } from "lit";
|
|
4
5
|
import * as lit_html10 from "lit-html";
|
|
5
6
|
import * as lit_html_directives_ref0 from "lit-html/directives/ref";
|
|
@@ -8,6 +9,7 @@ import * as lit_html_directives_ref0 from "lit-html/directives/ref";
|
|
|
8
9
|
declare const EFFilmstrip_base: typeof LitElement;
|
|
9
10
|
declare class EFFilmstrip extends EFFilmstrip_base {
|
|
10
11
|
#private;
|
|
12
|
+
static styles: lit10.CSSResult[];
|
|
11
13
|
target: string;
|
|
12
14
|
pixelsPerMs: number;
|
|
13
15
|
hidePlayhead: boolean;
|
package/dist/gui/EFFilmstrip.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import { TWMixin } from "./TWMixin2.js";
|
|
1
2
|
import { __decorate } from "../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
|
|
2
3
|
import { isEFTemporal } from "../elements/EFTemporal.js";
|
|
3
4
|
import { targetTemporalContext } from "./ContextMixin.js";
|
|
4
|
-
import { TWMixin } from "./TWMixin2.js";
|
|
5
5
|
import { TargetController } from "../elements/TargetController.js";
|
|
6
6
|
import "./timeline/EFTimeline.js";
|
|
7
7
|
import { consume } from "@lit/context";
|
|
8
|
-
import { LitElement, html } from "lit";
|
|
8
|
+
import { LitElement, css, html } from "lit";
|
|
9
9
|
import { customElement, property, state } from "lit/decorators.js";
|
|
10
10
|
import { createRef, ref } from "lit/directives/ref.js";
|
|
11
11
|
|
|
@@ -22,6 +22,15 @@ let EFFilmstrip = class EFFilmstrip$1 extends TWMixin(LitElement) {
|
|
|
22
22
|
this.targetElement = null;
|
|
23
23
|
this.timelineRef = createRef();
|
|
24
24
|
}
|
|
25
|
+
static {
|
|
26
|
+
this.styles = [css`
|
|
27
|
+
:host {
|
|
28
|
+
display: block;
|
|
29
|
+
width: 100%;
|
|
30
|
+
height: 100%;
|
|
31
|
+
}
|
|
32
|
+
`];
|
|
33
|
+
}
|
|
25
34
|
#targetController;
|
|
26
35
|
#lastTargetTemporal;
|
|
27
36
|
get targetTemporal() {
|
|
@@ -51,7 +60,11 @@ let EFFilmstrip = class EFFilmstrip$1 extends TWMixin(LitElement) {
|
|
|
51
60
|
if (this.target && !this.#targetController) this.#targetController = new TargetController(this);
|
|
52
61
|
}
|
|
53
62
|
const currentTargetTemporal = this.targetTemporal;
|
|
54
|
-
if (this.#lastTargetTemporal !== currentTargetTemporal)
|
|
63
|
+
if (this.#lastTargetTemporal !== currentTargetTemporal) {
|
|
64
|
+
this.#lastTargetTemporal = currentTargetTemporal;
|
|
65
|
+
const timeline = this.timelineRef.value;
|
|
66
|
+
if (timeline) timeline.requestUpdate();
|
|
67
|
+
}
|
|
55
68
|
super.willUpdate(changedProperties);
|
|
56
69
|
}
|
|
57
70
|
render() {
|
|
@@ -62,13 +75,8 @@ let EFFilmstrip = class EFFilmstrip$1 extends TWMixin(LitElement) {
|
|
|
62
75
|
target=${targetId}
|
|
63
76
|
control-target=${targetId}
|
|
64
77
|
pixels-per-ms=${this.pixelsPerMs}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
?show-ruler=${false}
|
|
68
|
-
?show-hierarchy=${true}
|
|
69
|
-
?show-playback-controls=${false}
|
|
70
|
-
?show-zoom-controls=${false}
|
|
71
|
-
?show-time-display=${false}
|
|
78
|
+
.showPlayhead=${!this.hidePlayhead}
|
|
79
|
+
.showControls=${true}
|
|
72
80
|
hide=${this.hide}
|
|
73
81
|
show=${this.show}
|
|
74
82
|
></ef-timeline>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EFFilmstrip.js","names":["EFFilmstrip","#targetController","#lastTargetTemporal"],"sources":["../../src/gui/EFFilmstrip.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { html, LitElement
|
|
1
|
+
{"version":3,"file":"EFFilmstrip.js","names":["EFFilmstrip","#targetController","#lastTargetTemporal"],"sources":["../../src/gui/EFFilmstrip.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { css, html, LitElement } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { createRef, ref } from \"lit/directives/ref.js\";\n\nimport {\n isEFTemporal,\n type TemporalMixinInterface,\n} from \"../elements/EFTemporal.js\";\nimport { TargetController } from \"../elements/TargetController.js\";\nimport { targetTemporalContext } from \"./ContextMixin.ts\";\nimport { TWMixin } from \"./TWMixin.js\";\nimport \"./timeline/EFTimeline.js\";\n\n@customElement(\"ef-filmstrip\")\nexport class EFFilmstrip extends TWMixin(LitElement) {\n static styles = [\n css`\n :host {\n display: block;\n width: 100%;\n height: 100%;\n }\n `,\n ];\n\n @property({ type: String })\n target = \"\";\n\n @property({ type: Number, attribute: \"pixels-per-ms\" })\n pixelsPerMs = 0.04;\n\n @property({ type: Boolean, attribute: \"hide-playhead\" })\n hidePlayhead = false;\n\n @property({ type: Boolean, attribute: \"disable-internal-scroll\" })\n disableInternalScroll = false;\n\n @property({ type: String })\n hide = \"\";\n\n @property({ type: String })\n show = \"\";\n\n @state()\n targetElement: Element | null = null;\n\n #targetController?: TargetController;\n #lastTargetTemporal?: TemporalMixinInterface | null;\n\n @consume({ context: targetTemporalContext, subscribe: true })\n @state()\n private _contextProvidedTemporal?: TemporalMixinInterface | null;\n\n get targetTemporal(): TemporalMixinInterface | null {\n const fromTarget =\n this.targetElement && isEFTemporal(this.targetElement)\n ? (this.targetElement as TemporalMixinInterface & HTMLElement)\n : null;\n const fromContext = this._contextProvidedTemporal;\n\n if (fromTarget && fromContext && fromTarget !== fromContext) {\n console.warn(\n \"EFFilmstrip: Both target attribute and parent context found. Using target attribute.\",\n { target: this.target, fromTarget, fromContext },\n );\n }\n\n return fromTarget ?? fromContext ?? null;\n }\n\n get hideSelectors(): string[] | undefined {\n if (!this.hide) return undefined;\n return this.hide\n .split(\",\")\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n }\n\n get showSelectors(): string[] | undefined {\n if (!this.show) return undefined;\n return this.show\n .split(\",\")\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n }\n\n timelineRef = createRef<HTMLElement>();\n\n connectedCallback(): void {\n super.connectedCallback();\n if (this.target) {\n this.#targetController = new TargetController(this);\n }\n }\n\n protected willUpdate(\n changedProperties: Map<string | number | symbol, unknown>,\n ) {\n if (changedProperties.has(\"target\")) {\n if (this.target && !this.#targetController) {\n this.#targetController = new TargetController(this);\n }\n }\n\n const currentTargetTemporal = this.targetTemporal;\n if (this.#lastTargetTemporal !== currentTargetTemporal) {\n this.#lastTargetTemporal = currentTargetTemporal;\n\n // The inner ef-timeline lives in our shadow root and can't resolve\n // targets from the document registry. Force it to re-render when\n // our own target resolution succeeds or changes.\n const timeline = this.timelineRef.value as any;\n if (timeline) {\n timeline.requestUpdate();\n }\n }\n\n super.willUpdate(changedProperties);\n }\n\n render() {\n const targetId = this.targetTemporal\n ? (this.targetTemporal as unknown as HTMLElement).id || this.target\n : this.target;\n\n return html`\n <ef-timeline\n ${ref(this.timelineRef)}\n target=${targetId}\n control-target=${targetId}\n pixels-per-ms=${this.pixelsPerMs}\n .showPlayhead=${!this.hidePlayhead}\n .showControls=${true}\n hide=${this.hide}\n show=${this.show}\n ></ef-timeline>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-filmstrip\": EFFilmstrip;\n }\n}\n"],"mappings":";;;;;;;;;;;;AAeO,wBAAMA,sBAAoB,QAAQ,WAAW,CAAC;;;gBAY1C;qBAGK;sBAGC;+BAGS;cAGjB;cAGA;uBAGyB;qBA0ClB,WAAwB;;;gBAvEtB,CACd,GAAG;;;;;;MAOJ;;CAuBD;CACA;CAMA,IAAI,iBAAgD;EAClD,MAAM,aACJ,KAAK,iBAAiB,aAAa,KAAK,cAAc,GACjD,KAAK,gBACN;EACN,MAAM,cAAc,KAAK;AAEzB,MAAI,cAAc,eAAe,eAAe,YAC9C,SAAQ,KACN,wFACA;GAAE,QAAQ,KAAK;GAAQ;GAAY;GAAa,CACjD;AAGH,SAAO,cAAc,eAAe;;CAGtC,IAAI,gBAAsC;AACxC,MAAI,CAAC,KAAK,KAAM,QAAO;AACvB,SAAO,KAAK,KACT,MAAM,IAAI,CACV,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,QAAQ,MAAM,EAAE,SAAS,EAAE;;CAGhC,IAAI,gBAAsC;AACxC,MAAI,CAAC,KAAK,KAAM,QAAO;AACvB,SAAO,KAAK,KACT,MAAM,IAAI,CACV,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,QAAQ,MAAM,EAAE,SAAS,EAAE;;CAKhC,oBAA0B;AACxB,QAAM,mBAAmB;AACzB,MAAI,KAAK,OACP,OAAKC,mBAAoB,IAAI,iBAAiB,KAAK;;CAIvD,AAAU,WACR,mBACA;AACA,MAAI,kBAAkB,IAAI,SAAS,EACjC;OAAI,KAAK,UAAU,CAAC,MAAKA,iBACvB,OAAKA,mBAAoB,IAAI,iBAAiB,KAAK;;EAIvD,MAAM,wBAAwB,KAAK;AACnC,MAAI,MAAKC,uBAAwB,uBAAuB;AACtD,SAAKA,qBAAsB;GAK3B,MAAM,WAAW,KAAK,YAAY;AAClC,OAAI,SACF,UAAS,eAAe;;AAI5B,QAAM,WAAW,kBAAkB;;CAGrC,SAAS;EACP,MAAM,WAAW,KAAK,iBACjB,KAAK,eAA0C,MAAM,KAAK,SAC3D,KAAK;AAET,SAAO,IAAI;;UAEL,IAAI,KAAK,YAAY,CAAC;iBACf,SAAS;yBACD,SAAS;wBACV,KAAK,YAAY;wBACjB,CAAC,KAAK,aAAa;wBACnB,KAAK;eACd,KAAK,KAAK;eACV,KAAK,KAAK;;;;;YA7GtB,SAAS,EAAE,MAAM,QAAQ,CAAC;YAG1B,SAAS;CAAE,MAAM;CAAQ,WAAW;CAAiB,CAAC;YAGtD,SAAS;CAAE,MAAM;CAAS,WAAW;CAAiB,CAAC;YAGvD,SAAS;CAAE,MAAM;CAAS,WAAW;CAA2B,CAAC;YAGjE,SAAS,EAAE,MAAM,QAAQ,CAAC;YAG1B,SAAS,EAAE,MAAM,QAAQ,CAAC;YAG1B,OAAO;YAMP,QAAQ;CAAE,SAAS;CAAuB,WAAW;CAAM,CAAC,EAC5D,OAAO;0BArCT,cAAc,eAAe"}
|