@editframe/elements 0.45.2 → 0.45.3
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/DelayedLoadingState.js.map +1 -1
- package/dist/EF_FRAMEGEN.js.map +1 -1
- package/dist/EF_RENDERING.js.map +1 -1
- package/dist/canvas/EFCanvas.js +3 -3
- package/dist/canvas/EFCanvas.js.map +1 -1
- package/dist/canvas/EFCanvasItem.js.map +1 -1
- package/dist/canvas/api/CanvasAPI.js.map +1 -1
- package/dist/canvas/getElementBounds.js.map +1 -1
- package/dist/canvas/overlays/SelectionOverlay.js.map +1 -1
- package/dist/canvas/overlays/overlayState.js.map +1 -1
- package/dist/canvas/selection/SelectionController.js +25 -23
- package/dist/canvas/selection/SelectionController.js.map +1 -1
- package/dist/canvas/selection/SelectionModel.js.map +1 -1
- package/dist/canvas/selection/selectionContext.js.map +1 -1
- package/dist/elements/ContainerInfo.js.map +1 -1
- package/dist/elements/CrossUpdateController.js.map +1 -1
- package/dist/elements/EFAudio.js.map +1 -1
- package/dist/elements/EFCaptions.js.map +1 -1
- package/dist/elements/EFImage.js +1 -1
- package/dist/elements/EFImage.js.map +1 -1
- package/dist/elements/EFMedia/BufferedSeekingInput.js.map +1 -1
- package/dist/elements/EFMedia/CachedFetcher.js.map +1 -1
- package/dist/elements/EFMedia/MediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/SegmentIndex.js.map +1 -1
- package/dist/elements/EFMedia/SegmentTransport.js.map +1 -1
- package/dist/elements/EFMedia/TimingModel.js.map +1 -1
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js.map +1 -1
- package/dist/elements/EFMedia/shared/GlobalInputCache.js.map +1 -1
- package/dist/elements/EFMedia/shared/PrecisionUtils.js.map +1 -1
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js.map +1 -1
- package/dist/elements/EFMedia.js.map +1 -1
- package/dist/elements/EFPanZoom.js +9 -8
- package/dist/elements/EFPanZoom.js.map +1 -1
- package/dist/elements/EFSourceMixin.js.map +1 -1
- package/dist/elements/EFSurface.js.map +1 -1
- package/dist/elements/EFTemporal.js.map +1 -1
- package/dist/elements/EFText.d.ts +4 -4
- package/dist/elements/EFText.js.map +1 -1
- package/dist/elements/EFTextSegment.d.ts +4 -4
- package/dist/elements/EFTimegroup.js +7 -8
- package/dist/elements/EFTimegroup.js.map +1 -1
- package/dist/elements/EFVideo.d.ts +4 -4
- package/dist/elements/EFVideo.js.map +1 -1
- package/dist/elements/EFWaveform.d.ts +4 -4
- 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/SampleBuffer.js.map +1 -1
- package/dist/elements/TargetController.js.map +1 -1
- package/dist/elements/TimegroupController.js.map +1 -1
- package/dist/elements/cloneFactoryRegistry.js.map +1 -1
- package/dist/elements/durationConverter.js.map +1 -1
- package/dist/elements/easingUtils.js.map +1 -1
- package/dist/elements/renderTemporalAudio.js.map +1 -1
- package/dist/elements/setupTemporalHierarchy.js.map +1 -1
- package/dist/elements/updateAnimations.js +1 -1
- package/dist/elements/updateAnimations.js.map +1 -1
- package/dist/getRenderInfo.js.map +1 -1
- package/dist/gui/ContextMixin.js.map +1 -1
- package/dist/gui/Controllable.js.map +1 -1
- package/dist/gui/EFActiveRootTemporal.d.ts +4 -4
- package/dist/gui/EFActiveRootTemporal.js.map +1 -1
- package/dist/gui/EFConfiguration.d.ts +4 -4
- package/dist/gui/EFControls.js.map +1 -1
- package/dist/gui/EFDial.d.ts +4 -4
- package/dist/gui/EFFilmstrip.d.ts +4 -4
- package/dist/gui/EFFilmstrip.js.map +1 -1
- package/dist/gui/EFFitScale.js.map +1 -1
- package/dist/gui/EFOverlayItem.d.ts +4 -4
- package/dist/gui/EFOverlayItem.js.map +1 -1
- package/dist/gui/EFOverlayLayer.d.ts +4 -4
- package/dist/gui/EFOverlayLayer.js.map +1 -1
- package/dist/gui/EFPause.d.ts +4 -4
- package/dist/gui/EFPlay.d.ts +4 -4
- package/dist/gui/EFPreview.d.ts +4 -4
- package/dist/gui/EFPreview.js.map +1 -1
- package/dist/gui/EFResizableBox.js.map +1 -1
- package/dist/gui/EFScrubber.d.ts +4 -4
- package/dist/gui/EFScrubber.js.map +1 -1
- package/dist/gui/EFTimeDisplay.d.ts +4 -4
- package/dist/gui/EFTimeDisplay.js.map +1 -1
- package/dist/gui/EFTimelineRuler.d.ts +4 -4
- package/dist/gui/EFTimelineRuler.js.map +1 -1
- package/dist/gui/EFToggleLoop.d.ts +4 -4
- package/dist/gui/EFTogglePlay.d.ts +4 -4
- package/dist/gui/EFTogglePlay.js.map +1 -1
- package/dist/gui/EFTransformHandles.js.map +1 -1
- package/dist/gui/EFWorkbench.d.ts +4 -4
- package/dist/gui/EFWorkbench.js.map +1 -1
- package/dist/gui/FitScaleHelpers.js.map +1 -1
- package/dist/gui/PlaybackController.js.map +1 -1
- package/dist/gui/TWMixin2.js.map +1 -1
- package/dist/gui/TargetOrContextMixin.js.map +1 -1
- package/dist/gui/currentTimeContext.js.map +1 -1
- package/dist/gui/efContext.js.map +1 -1
- package/dist/gui/fetchContext.js.map +1 -1
- package/dist/gui/hierarchy/EFHierarchy.d.ts +4 -4
- package/dist/gui/hierarchy/EFHierarchy.js.map +1 -1
- package/dist/gui/hierarchy/EFHierarchyItem.d.ts +2 -2
- package/dist/gui/hierarchy/EFHierarchyItem.js.map +1 -1
- package/dist/gui/hierarchy/hierarchyContext.js.map +1 -1
- package/dist/gui/panZoomTransformContext.js.map +1 -1
- package/dist/gui/previewSettingsContext.js.map +1 -1
- package/dist/gui/theme.js.map +1 -1
- package/dist/gui/timeline/EFTimeline.d.ts +2 -2
- package/dist/gui/timeline/EFTimeline.js +0 -1
- package/dist/gui/timeline/EFTimeline.js.map +1 -1
- package/dist/gui/timeline/EFTimelineRow.js.map +1 -1
- package/dist/gui/timeline/TrimHandles.d.ts +4 -4
- package/dist/gui/timeline/TrimHandles.js.map +1 -1
- package/dist/gui/timeline/flattenHierarchy.js.map +1 -1
- package/dist/gui/timeline/timelineStateContext.js.map +1 -1
- package/dist/gui/timeline/tracks/AudioTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/CaptionsTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/EFThumbnailStrip.js.map +1 -1
- package/dist/gui/timeline/tracks/ImageTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TextTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TimegroupTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TrackItem.js.map +1 -1
- package/dist/gui/timeline/tracks/VideoTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/renderTrackChildren.js.map +1 -1
- package/dist/gui/timeline/tracks/waveformUtils.js.map +1 -1
- package/dist/gui/transformCalculations.js.map +1 -1
- package/dist/gui/transformUtils.js.map +1 -1
- package/dist/gui/tree/EFTree.d.ts +4 -4
- package/dist/gui/tree/EFTree.js.map +1 -1
- package/dist/gui/tree/EFTreeItem.d.ts +4 -4
- package/dist/gui/tree/EFTreeItem.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/otel/BridgeSpanExporter.js.map +1 -1
- package/dist/otel/setupBrowserTracing.js.map +1 -1
- package/dist/otel/tracingHelpers.js.map +1 -1
- package/dist/preview/AdaptiveResolutionTracker.js.map +1 -1
- package/dist/preview/FrameController.js.map +1 -1
- package/dist/preview/QualityUpgradeScheduler.js.map +1 -1
- package/dist/preview/RenderContext.js.map +1 -1
- package/dist/preview/RenderProfiler.js.map +1 -1
- package/dist/preview/RenderStats.js.map +1 -1
- package/dist/preview/encoding/canvasEncoder.js.map +1 -1
- package/dist/preview/encoding/mainThreadEncoder.js +1 -1
- package/dist/preview/encoding/mainThreadEncoder.js.map +1 -1
- package/dist/preview/previewSettings.js.map +1 -1
- package/dist/preview/previewTypes.js.map +1 -1
- package/dist/preview/renderElementToCanvas.js.map +1 -1
- package/dist/preview/renderTimegroupToCanvas.js +2 -44
- package/dist/preview/renderTimegroupToCanvas.js.map +1 -1
- package/dist/preview/renderTimegroupToVideo.js +2 -2
- package/dist/preview/renderTimegroupToVideo.js.map +1 -1
- package/dist/preview/renderVideoToVideo.js +2 -2
- package/dist/preview/renderVideoToVideo.js.map +1 -1
- package/dist/preview/renderers.js.map +1 -1
- package/dist/preview/rendering/ScaleConfig.js.map +1 -1
- package/dist/preview/rendering/loadImage.js.map +1 -1
- package/dist/preview/rendering/renderToImageNative.js.map +1 -1
- package/dist/preview/rendering/serializeTimelineDirect.js +1 -1
- package/dist/preview/rendering/serializeTimelineDirect.js.map +1 -1
- package/dist/preview/statsTrackingStrategy.js.map +1 -1
- package/dist/preview/workers/WorkerPool.js.map +1 -1
- package/dist/render/EFRenderAPI.js.map +1 -1
- package/dist/transcoding/cache/RequestDeduplicator.js.map +1 -1
- package/dist/utils/LRUCache.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlaybackController.js","names":["#FPS","#host","#playingProvider","#playing","#loopProvider","#loop","#currentTimeMsProvider","#durationMsProvider","#currentTime","#processingPendingSeek","#pendingSeekTime","#seekInProgress","#runSeek","#seekAbortController","#notifyListeners","#hasConnected","#removed","#initializeTime","#selfRenderSuspended","#selfRenderAbortController","#selfRenderPromise","#selfRenderDirty","#startSelfRender","#listeners","#pendingAudioContext","#playbackAudioContext","rawTimeMs: number","#playbackWrapTimeSeconds","#loopingPlayback","#MS_PER_FRAME","#updatePlaybackTime","#playbackAnimationFrameRequest","#syncPlayheadToAudioContext","#AUDIO_PLAYBACK_SLICE_MS"],"sources":["../../src/gui/PlaybackController.ts"],"sourcesContent":["import { ContextProvider } from \"@lit/context\";\nimport type { ReactiveController, ReactiveControllerHost } from \"lit\";\nimport { currentTimeContext } from \"./currentTimeContext.js\";\nimport { durationContext } from \"./durationContext.js\";\nimport { loopContext, playingContext } from \"./playingContext.js\";\nimport {\n updateAnimations,\n type AnimatableElement,\n} from \"../elements/updateAnimations.js\";\nimport type {\n RenderFrameOptions,\n FrameRenderable,\n} from \"../preview/FrameController.js\";\n\ninterface PlaybackHost extends HTMLElement, ReactiveControllerHost {\n currentTimeMs: number;\n durationMs: number;\n endTimeMs: number;\n /** Centralized frame controller (present on EFTimegroup) */\n frameController?: {\n renderFrame(timeMs: number, options?: RenderFrameOptions): Promise<void>;\n abort(): void;\n };\n renderAudio?(fromMs: number, toMs: number): Promise<AudioBuffer>;\n waitForMediaDurations?(signal?: AbortSignal): Promise<void>;\n saveTimeToLocalStorage?(time: number): void;\n loadTimeFromLocalStorage?(): number | undefined;\n requestUpdate(property?: string): void;\n updateComplete: Promise<boolean>;\n playing: boolean;\n loop: boolean;\n play(): void;\n pause(): void;\n playbackController?: PlaybackController;\n parentTimegroup?: any;\n rootTimegroup?: any;\n}\n\nexport type PlaybackControllerUpdateEvent = {\n property: \"playing\" | \"loop\" | \"currentTimeMs\";\n value: boolean | number;\n};\n\n/**\n * Manages playback state and audio-driven timing for root temporal elements\n *\n * Created automatically when a temporal element becomes a root (no parent timegroup)\n * Provides playback contexts (playing, loop, currentTimeMs, durationMs) to descendants\n * Handles:\n * - Audio-driven playback with Web Audio API\n * - Seek and frame rendering throttling\n * - Time state management with pending seek handling\n * - Playback loop behavior\n *\n * Works with any temporal element (timegroups or standalone media) via PlaybackHost interface\n */\nexport class PlaybackController implements ReactiveController {\n #host: PlaybackHost;\n #playing = false;\n #loop = false;\n #listeners = new Set<(event: PlaybackControllerUpdateEvent) => void>();\n #playingProvider: ContextProvider<typeof playingContext>;\n #loopProvider: ContextProvider<typeof loopContext>;\n #currentTimeMsProvider: ContextProvider<typeof currentTimeContext>;\n #durationMsProvider: ContextProvider<typeof durationContext>;\n\n #FPS = 30;\n #MS_PER_FRAME = 1000 / this.#FPS;\n #playbackAudioContext: AudioContext | null = null;\n #playbackAnimationFrameRequest: number | null = null;\n #pendingAudioContext: AudioContext | null = null;\n #AUDIO_PLAYBACK_SLICE_MS = ((47 * 1024) / 48000) * 1000;\n\n #currentTime: number | undefined = undefined;\n #seekInProgress = false;\n #pendingSeekTime: number | undefined;\n #processingPendingSeek = false;\n #loopingPlayback = false; // Track if we're in a looping playback session\n #playbackWrapTimeSeconds = 0; // The AudioContext time when we wrapped\n\n #seekAbortController: AbortController | null = null;\n #hasConnected = false;\n\n constructor(host: PlaybackHost) {\n this.#host = host;\n host.addController(this);\n\n this.#playingProvider = new ContextProvider(host, {\n context: playingContext,\n initialValue: this.#playing,\n });\n this.#loopProvider = new ContextProvider(host, {\n context: loopContext,\n initialValue: this.#loop,\n });\n this.#currentTimeMsProvider = new ContextProvider(host, {\n context: currentTimeContext,\n initialValue: host.currentTimeMs,\n });\n this.#durationMsProvider = new ContextProvider(host, {\n context: durationContext,\n initialValue: host.durationMs,\n });\n }\n\n get currentTime(): number {\n const rawTime = this.#currentTime ?? 0;\n // Quantize to frame boundaries based on host's fps\n const fps = (this.#host as any).fps ?? 30;\n if (!fps || fps <= 0) return rawTime;\n const frameDurationS = 1 / fps;\n const quantizedTime = Math.round(rawTime / frameDurationS) * frameDurationS;\n // Clamp to valid range after quantization to prevent exceeding duration\n const durationS = this.#host.durationMs / 1000;\n return Math.max(0, Math.min(quantizedTime, durationS));\n }\n\n set currentTime(time: number) {\n time = Math.max(0, Math.min(this.#host.durationMs / 1000, time));\n if (Number.isNaN(time)) {\n return;\n }\n if (time === this.#currentTime && !this.#processingPendingSeek) {\n return;\n }\n if (this.#pendingSeekTime === time) {\n return;\n }\n\n if (this.#seekInProgress) {\n this.#pendingSeekTime = time;\n this.#currentTime = time;\n return;\n }\n\n this.#currentTime = time;\n this.#seekInProgress = true;\n\n this.#runSeek(time).finally(async () => {\n // CRITICAL: Coordinate animations after seek completes\n // This ensures animations are positioned correctly, not playing naturally\n const { updateAnimations } =\n await import(\"../elements/updateAnimations.js\");\n updateAnimations(this.#host as any);\n\n if (\n this.#pendingSeekTime !== undefined &&\n this.#pendingSeekTime !== time\n ) {\n const pendingTime = this.#pendingSeekTime;\n this.#pendingSeekTime = undefined;\n this.#processingPendingSeek = true;\n try {\n this.currentTime = pendingTime;\n } finally {\n this.#processingPendingSeek = false;\n }\n } else {\n this.#pendingSeekTime = undefined;\n }\n });\n }\n\n async #runSeek(targetTime: number): Promise<number | undefined> {\n // Abort any in-flight seek\n this.#seekAbortController?.abort();\n this.#seekAbortController = new AbortController();\n const signal = this.#seekAbortController.signal;\n\n try {\n signal.throwIfAborted();\n\n await this.#host.waitForMediaDurations?.(signal);\n signal.throwIfAborted();\n\n const newTime = Math.max(\n 0,\n Math.min(targetTime, this.#host.durationMs / 1000),\n );\n this.#currentTime = newTime;\n this.#host.requestUpdate(\"currentTime\");\n this.#currentTimeMsProvider.setValue(this.currentTimeMs);\n this.#notifyListeners({\n property: \"currentTimeMs\",\n value: this.currentTimeMs,\n });\n\n signal.throwIfAborted();\n\n await this.runThrottledFrameTask();\n signal.throwIfAborted();\n\n // Save to localStorage for persistence (only if not restoring to avoid loops)\n const isRestoring =\n (this.#host as any).isRestoringFromLocalStorage?.() ?? false;\n if (!isRestoring) {\n this.#host.saveTimeToLocalStorage?.(newTime);\n } else {\n (this.#host as any).setRestoringFromLocalStorage?.(false);\n }\n this.#seekInProgress = false;\n return newTime;\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n // Expected - don't log\n return undefined;\n }\n throw error;\n }\n }\n\n get playing(): boolean {\n return this.#playing;\n }\n\n setPlaying(value: boolean): void {\n if (this.#playing === value) return;\n this.#playing = value;\n this.#playingProvider.setValue(value);\n this.#host.requestUpdate(\"playing\");\n this.#notifyListeners({ property: \"playing\", value });\n\n if (value) {\n this.startPlayback();\n } else {\n this.stopPlayback();\n }\n }\n\n get loop(): boolean {\n return this.#loop;\n }\n\n setLoop(value: boolean): void {\n if (this.#loop === value) return;\n this.#loop = value;\n this.#loopProvider.setValue(value);\n this.#host.requestUpdate(\"loop\");\n this.#notifyListeners({ property: \"loop\", value });\n }\n\n get currentTimeMs(): number {\n return this.currentTime * 1000;\n }\n\n setCurrentTimeMs(value: number): void {\n this.currentTime = value / 1000;\n }\n\n // Update time during playback without triggering a seek\n // Used by #syncPlayheadToAudioContext to avoid frame drops\n #updatePlaybackTime(timeMs: number): void {\n // Clamp to valid range to prevent time exceeding duration\n const durationMs = this.#host.durationMs;\n const clampedTimeMs = Math.max(0, Math.min(timeMs, durationMs));\n const timeSec = clampedTimeMs / 1000;\n if (this.#currentTime === timeSec) {\n return;\n }\n this.#currentTime = timeSec;\n this.#host.requestUpdate(\"currentTime\");\n this.#currentTimeMsProvider.setValue(clampedTimeMs);\n this.#notifyListeners({\n property: \"currentTimeMs\",\n value: clampedTimeMs,\n });\n // Trigger frame rendering without the async seek mechanism\n this.runThrottledFrameTask();\n }\n\n play(): void {\n this.setPlaying(true);\n }\n\n pause(): void {\n this.setPlaying(false);\n }\n\n #removed = false;\n\n hostConnected(): void {\n const isReconnect = this.#hasConnected;\n this.#hasConnected = true;\n // Defer all operations to avoid blocking during initialization\n // This prevents deadlocks when many timegroups are initializing simultaneously\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n // Check if this controller was removed before the RAF callback executed.\n // This happens when wrapWithWorkbench moves the element, causing disconnect/reconnect.\n if (this.#removed || this.#host.playbackController !== this) {\n return;\n }\n\n if (this.#playing && isReconnect) {\n this.startPlayback();\n } else if (!this.#playing) {\n this.#initializeTime();\n }\n });\n });\n }\n\n async #initializeTime(): Promise<void> {\n try {\n const waitPromise = this.#host.waitForMediaDurations?.();\n if (waitPromise) {\n await waitPromise;\n }\n } catch (err) {\n const isAbortError =\n (err instanceof DOMException && err.name === \"AbortError\") ||\n (err instanceof Error &&\n (err.name === \"AbortError\" ||\n err.message.includes(\"signal is aborted\") ||\n err.message.includes(\"The user aborted a request\")));\n if (!isAbortError) {\n console.error(\"Error in PlaybackController hostConnected:\", err);\n }\n return;\n }\n\n if (this.#removed || this.#host.playbackController !== this) {\n return;\n }\n\n const maybeLoadedTime = this.#host.loadTimeFromLocalStorage?.();\n if (maybeLoadedTime !== undefined) {\n (this.#host as any).setRestoringFromLocalStorage?.(true);\n this.currentTime = maybeLoadedTime;\n } else if (this.#currentTime === undefined) {\n this.currentTime = 0;\n }\n }\n\n hostDisconnected(): void {\n this.pause();\n }\n\n hostUpdated(): void {\n this.#durationMsProvider.setValue(this.#host.durationMs);\n this.#currentTimeMsProvider.setValue(this.currentTimeMs);\n }\n\n #selfRenderAbortController?: AbortController;\n #selfRenderPromise?: Promise<void>;\n #selfRenderDirty = false;\n #selfRenderSuspended = false;\n\n suspendSelfRender(): void {\n this.#selfRenderSuspended = true;\n this.#selfRenderAbortController?.abort();\n this.#selfRenderAbortController = undefined;\n }\n\n resumeSelfRender(): void {\n this.#selfRenderSuspended = false;\n }\n\n /**\n * Run frame rendering via FrameController, or directly on the host if it\n * implements FrameRenderable (standalone media element without a Timegroup).\n */\n async runThrottledFrameTask(): Promise<void> {\n const timeMs = this.currentTimeMs;\n\n if (this.#host.frameController) {\n try {\n await this.#host.frameController.renderFrame(timeMs, {\n onAnimationsUpdate: (root: Element) => {\n updateAnimations(root as unknown as AnimatableElement);\n },\n });\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\")\n return;\n console.error(\"FrameController error:\", error);\n }\n return;\n }\n\n // Standalone FrameRenderable host (e.g. bare ef-video without a Timegroup)\n const host = this.#host as unknown as Partial<FrameRenderable>;\n if (!host.prepareFrame || !host.renderFrame) return;\n\n if (this.#selfRenderSuspended) return;\n\n // If a render is in-flight, mark dirty so we re-render after it\n // completes (source mapping may have changed due to trim drag).\n if (this.#selfRenderPromise) {\n this.#selfRenderDirty = true;\n return this.#selfRenderPromise;\n }\n\n return this.#startSelfRender(host, timeMs);\n }\n\n #startSelfRender(\n host: Partial<FrameRenderable>,\n timeMs: number,\n ): Promise<void> {\n this.#selfRenderAbortController?.abort();\n this.#selfRenderAbortController = new AbortController();\n const signal = this.#selfRenderAbortController.signal;\n this.#selfRenderDirty = false;\n\n this.#selfRenderPromise = (async () => {\n try {\n await host.prepareFrame!(timeMs, signal);\n signal.throwIfAborted();\n host.renderFrame!(timeMs);\n updateAnimations(this.#host as unknown as AnimatableElement);\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\")\n return;\n if ((error as any)?.name === \"AbortError\") return;\n console.error(\"Standalone frame render error:\", error);\n } finally {\n this.#selfRenderPromise = undefined;\n // Re-render if source mapping changed while we were rendering\n if (this.#selfRenderDirty && !this.#selfRenderSuspended) {\n this.#startSelfRender(host, this.currentTimeMs);\n }\n }\n })();\n\n return this.#selfRenderPromise;\n }\n\n addListener(listener: (event: PlaybackControllerUpdateEvent) => void): void {\n this.#listeners.add(listener);\n }\n\n removeListener(\n listener: (event: PlaybackControllerUpdateEvent) => void,\n ): void {\n this.#listeners.delete(listener);\n }\n\n #notifyListeners(event: PlaybackControllerUpdateEvent): void {\n for (const listener of this.#listeners) {\n listener(event);\n }\n }\n\n remove(): void {\n this.#removed = true; // Mark as removed to abort any pending RAF callbacks\n this.stopPlayback();\n this.#listeners.clear();\n this.#host.removeController(this);\n }\n\n setPendingAudioContext(context: AudioContext): void {\n this.#pendingAudioContext = context;\n }\n\n #syncPlayheadToAudioContext(startMs: number) {\n const audioContextTime = this.#playbackAudioContext?.currentTime ?? 0;\n const endMs = this.#host.endTimeMs;\n\n // Calculate raw time based on audio context\n let rawTimeMs: number;\n if (\n this.#playbackWrapTimeSeconds > 0 &&\n audioContextTime >= this.#playbackWrapTimeSeconds\n ) {\n // After wrap: time since wrap, wrapped to duration\n const timeSinceWrap =\n (audioContextTime - this.#playbackWrapTimeSeconds) * 1000;\n rawTimeMs = timeSinceWrap % endMs;\n } else {\n // Before wrap or no wrap: normal calculation\n rawTimeMs = startMs + audioContextTime * 1000;\n\n // If looping and we've reached the end, wrap around\n if (this.#loopingPlayback && rawTimeMs >= endMs) {\n rawTimeMs = rawTimeMs % endMs;\n }\n }\n\n const nextTimeMs =\n Math.round(rawTimeMs / this.#MS_PER_FRAME) * this.#MS_PER_FRAME;\n\n // During playback, update time directly without triggering seek\n // This avoids frame drops at the loop boundary\n this.#updatePlaybackTime(nextTimeMs);\n\n // Only check for end if we haven't already handled looping\n if (!this.#loopingPlayback && nextTimeMs >= endMs) {\n this.maybeLoopPlayback();\n return;\n }\n\n this.#playbackAnimationFrameRequest = requestAnimationFrame(() => {\n this.#syncPlayheadToAudioContext(startMs);\n });\n }\n\n private async maybeLoopPlayback() {\n if (this.#loop) {\n // Loop enabled: reset to beginning and restart playback\n // We restart the audio system directly without changing #playing state\n // to keep the play button in sync\n this.setCurrentTimeMs(0);\n // Restart in next frame without awaiting to minimize gap\n requestAnimationFrame(() => {\n this.startPlayback();\n });\n } else {\n // No loop: reset to beginning and stop\n // This ensures play button works when clicked again\n this.setCurrentTimeMs(0);\n this.pause();\n }\n }\n\n private async stopPlayback() {\n if (this.#playbackAnimationFrameRequest) {\n cancelAnimationFrame(this.#playbackAnimationFrameRequest);\n this.#playbackAnimationFrameRequest = null;\n }\n if (this.#playbackAudioContext) {\n if (this.#playbackAudioContext.state !== \"closed\") {\n await this.#playbackAudioContext.close();\n }\n }\n this.#playbackAudioContext = null;\n this.#pendingAudioContext = null;\n }\n\n private async startPlayback() {\n // Guard against starting playback on a removed controller\n if (this.#removed) {\n return;\n }\n\n await this.stopPlayback();\n const host = this.#host;\n if (!host) {\n return;\n }\n\n if (host.waitForMediaDurations) {\n await host.waitForMediaDurations();\n }\n\n // Check again after async - controller could have been removed\n if (this.#removed) {\n return;\n }\n\n const currentMs = this.currentTimeMs;\n const fromMs = currentMs;\n const toMs = host.endTimeMs;\n\n if (fromMs >= toMs) {\n this.pause();\n return;\n }\n\n let bufferCount = 0;\n // Check for pre-resumed AudioContext from synchronous user interaction\n if (this.#pendingAudioContext) {\n this.#playbackAudioContext = this.#pendingAudioContext;\n this.#pendingAudioContext = null;\n } else {\n this.#playbackAudioContext = new AudioContext({\n latencyHint: \"playback\",\n });\n }\n this.#loopingPlayback = this.#loop; // Remember if we're in a looping session\n this.#playbackWrapTimeSeconds = 0; // Reset wrap time\n\n if (this.#playbackAnimationFrameRequest) {\n cancelAnimationFrame(this.#playbackAnimationFrameRequest);\n }\n this.#syncPlayheadToAudioContext(currentMs);\n const playbackContext = this.#playbackAudioContext;\n\n // Check if context is suspended (fallback for newly-created contexts)\n if (playbackContext.state === \"suspended\") {\n // Attempt to resume (may not work on mobile if user interaction context is lost)\n try {\n await playbackContext.resume();\n // Check state again after resume attempt\n if (playbackContext.state === \"suspended\") {\n console.warn(\n \"AudioContext is suspended and resume() failed. \" +\n \"On mobile devices, AudioContext.resume() must be called synchronously within a user interaction handler. \" +\n \"Media playback will not work until user has interacted with page.\",\n );\n this.setPlaying(false);\n return;\n }\n } catch (error) {\n console.warn(\n \"Failed to resume AudioContext:\",\n error,\n \"On mobile devices, AudioContext.resume() must be called synchronously within a user interaction handler.\",\n );\n this.setPlaying(false);\n return;\n }\n }\n await playbackContext.suspend();\n\n // Track the logical media time (what position in the media we're rendering)\n // vs the AudioContext schedule time (when to play it)\n let logicalTimeMs = currentMs;\n let audioContextTimeMs = 0; // Tracks the schedule position in the AudioContext timeline\n let hasWrapped = false;\n\n const fillBuffer = async () => {\n if (bufferCount > 2) {\n return;\n }\n const canFillBuffer = await queueBufferSource();\n if (canFillBuffer) {\n fillBuffer().catch(() => {});\n }\n };\n\n const queueBufferSource = async () => {\n // Check if we've already wrapped and aren't looping anymore\n if (hasWrapped && !this.#loopingPlayback) {\n return false;\n }\n\n const startMs = logicalTimeMs;\n const endMs = Math.min(\n logicalTimeMs + this.#AUDIO_PLAYBACK_SLICE_MS,\n toMs,\n );\n\n // Will this slice reach the end?\n const willReachEnd = endMs >= toMs;\n\n if (!host.renderAudio) {\n return false;\n }\n\n const audioBuffer = await host.renderAudio(startMs, endMs);\n bufferCount++;\n const source = playbackContext.createBufferSource();\n source.buffer = audioBuffer;\n source.connect(playbackContext.destination);\n // Schedule this buffer to play at the current audioContextTime position\n source.start(audioContextTimeMs / 1000);\n\n const sliceDurationMs = endMs - startMs;\n\n source.onended = () => {\n bufferCount--;\n\n if (willReachEnd) {\n if (!this.#loopingPlayback) {\n // Not looping, end playback\n this.maybeLoopPlayback();\n } else {\n // Looping: continue filling buffer after wrap\n fillBuffer().catch(() => {});\n }\n } else {\n // Continue filling buffer\n fillBuffer().catch(() => {});\n }\n };\n\n // Advance the AudioContext schedule time\n audioContextTimeMs += sliceDurationMs;\n\n // If this buffer reaches the end and we're looping, immediately queue the wraparound\n if (willReachEnd && this.#loopingPlayback) {\n // Mark that we've wrapped\n hasWrapped = true;\n // Store when we wrapped (relative to when playback started, which is time 0 in AudioContext)\n // This is the duration from start to end\n this.#playbackWrapTimeSeconds = (toMs - fromMs) / 1000;\n // Reset logical time to beginning\n logicalTimeMs = 0;\n // Continue buffering will happen in fillBuffer() call below\n } else {\n // Normal advance\n logicalTimeMs = endMs;\n }\n\n return true;\n };\n\n try {\n await fillBuffer();\n await playbackContext.resume();\n } catch (error) {\n // Ignore errors if AudioContext is closed or during test cleanup\n if (\n error instanceof Error &&\n (error.name === \"InvalidStateError\" || error.message.includes(\"closed\"))\n ) {\n return;\n }\n throw error;\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PlaybackController.js","names":["#FPS","#host","#playingProvider","#playing","#loopProvider","#loop","#currentTimeMsProvider","#durationMsProvider","#currentTime","#processingPendingSeek","#pendingSeekTime","#seekInProgress","#runSeek","#seekAbortController","#notifyListeners","#hasConnected","#removed","#initializeTime","#selfRenderSuspended","#selfRenderAbortController","#selfRenderPromise","#selfRenderDirty","#startSelfRender","#listeners","#pendingAudioContext","#playbackAudioContext","rawTimeMs: number","#playbackWrapTimeSeconds","#loopingPlayback","#MS_PER_FRAME","#updatePlaybackTime","#playbackAnimationFrameRequest","#syncPlayheadToAudioContext","#AUDIO_PLAYBACK_SLICE_MS"],"sources":["../../src/gui/PlaybackController.ts"],"sourcesContent":["import { ContextProvider } from \"@lit/context\";\nimport type { ReactiveController, ReactiveControllerHost } from \"lit\";\nimport { currentTimeContext } from \"./currentTimeContext.js\";\nimport { durationContext } from \"./durationContext.js\";\nimport { loopContext, playingContext } from \"./playingContext.js\";\nimport { updateAnimations, type AnimatableElement } from \"../elements/updateAnimations.js\";\nimport type { RenderFrameOptions, FrameRenderable } from \"../preview/FrameController.js\";\n\ninterface PlaybackHost extends HTMLElement, ReactiveControllerHost {\n currentTimeMs: number;\n durationMs: number;\n endTimeMs: number;\n /** Centralized frame controller (present on EFTimegroup) */\n frameController?: {\n renderFrame(timeMs: number, options?: RenderFrameOptions): Promise<void>;\n abort(): void;\n };\n renderAudio?(fromMs: number, toMs: number): Promise<AudioBuffer>;\n waitForMediaDurations?(signal?: AbortSignal): Promise<void>;\n saveTimeToLocalStorage?(time: number): void;\n loadTimeFromLocalStorage?(): number | undefined;\n requestUpdate(property?: string): void;\n updateComplete: Promise<boolean>;\n playing: boolean;\n loop: boolean;\n play(): void;\n pause(): void;\n playbackController?: PlaybackController;\n parentTimegroup?: any;\n rootTimegroup?: any;\n}\n\nexport type PlaybackControllerUpdateEvent = {\n property: \"playing\" | \"loop\" | \"currentTimeMs\";\n value: boolean | number;\n};\n\n/**\n * Manages playback state and audio-driven timing for root temporal elements\n *\n * Created automatically when a temporal element becomes a root (no parent timegroup)\n * Provides playback contexts (playing, loop, currentTimeMs, durationMs) to descendants\n * Handles:\n * - Audio-driven playback with Web Audio API\n * - Seek and frame rendering throttling\n * - Time state management with pending seek handling\n * - Playback loop behavior\n *\n * Works with any temporal element (timegroups or standalone media) via PlaybackHost interface\n */\nexport class PlaybackController implements ReactiveController {\n #host: PlaybackHost;\n #playing = false;\n #loop = false;\n #listeners = new Set<(event: PlaybackControllerUpdateEvent) => void>();\n #playingProvider: ContextProvider<typeof playingContext>;\n #loopProvider: ContextProvider<typeof loopContext>;\n #currentTimeMsProvider: ContextProvider<typeof currentTimeContext>;\n #durationMsProvider: ContextProvider<typeof durationContext>;\n\n #FPS = 30;\n #MS_PER_FRAME = 1000 / this.#FPS;\n #playbackAudioContext: AudioContext | null = null;\n #playbackAnimationFrameRequest: number | null = null;\n #pendingAudioContext: AudioContext | null = null;\n #AUDIO_PLAYBACK_SLICE_MS = ((47 * 1024) / 48000) * 1000;\n\n #currentTime: number | undefined = undefined;\n #seekInProgress = false;\n #pendingSeekTime: number | undefined;\n #processingPendingSeek = false;\n #loopingPlayback = false; // Track if we're in a looping playback session\n #playbackWrapTimeSeconds = 0; // The AudioContext time when we wrapped\n\n #seekAbortController: AbortController | null = null;\n #hasConnected = false;\n\n constructor(host: PlaybackHost) {\n this.#host = host;\n host.addController(this);\n\n this.#playingProvider = new ContextProvider(host, {\n context: playingContext,\n initialValue: this.#playing,\n });\n this.#loopProvider = new ContextProvider(host, {\n context: loopContext,\n initialValue: this.#loop,\n });\n this.#currentTimeMsProvider = new ContextProvider(host, {\n context: currentTimeContext,\n initialValue: host.currentTimeMs,\n });\n this.#durationMsProvider = new ContextProvider(host, {\n context: durationContext,\n initialValue: host.durationMs,\n });\n }\n\n get currentTime(): number {\n const rawTime = this.#currentTime ?? 0;\n // Quantize to frame boundaries based on host's fps\n const fps = (this.#host as any).fps ?? 30;\n if (!fps || fps <= 0) return rawTime;\n const frameDurationS = 1 / fps;\n const quantizedTime = Math.round(rawTime / frameDurationS) * frameDurationS;\n // Clamp to valid range after quantization to prevent exceeding duration\n const durationS = this.#host.durationMs / 1000;\n return Math.max(0, Math.min(quantizedTime, durationS));\n }\n\n set currentTime(time: number) {\n time = Math.max(0, Math.min(this.#host.durationMs / 1000, time));\n if (Number.isNaN(time)) {\n return;\n }\n if (time === this.#currentTime && !this.#processingPendingSeek) {\n return;\n }\n if (this.#pendingSeekTime === time) {\n return;\n }\n\n if (this.#seekInProgress) {\n this.#pendingSeekTime = time;\n this.#currentTime = time;\n return;\n }\n\n this.#currentTime = time;\n this.#seekInProgress = true;\n\n this.#runSeek(time).finally(async () => {\n // CRITICAL: Coordinate animations after seek completes\n // This ensures animations are positioned correctly, not playing naturally\n const { updateAnimations } = await import(\"../elements/updateAnimations.js\");\n updateAnimations(this.#host as any);\n\n if (this.#pendingSeekTime !== undefined && this.#pendingSeekTime !== time) {\n const pendingTime = this.#pendingSeekTime;\n this.#pendingSeekTime = undefined;\n this.#processingPendingSeek = true;\n try {\n this.currentTime = pendingTime;\n } finally {\n this.#processingPendingSeek = false;\n }\n } else {\n this.#pendingSeekTime = undefined;\n }\n });\n }\n\n async #runSeek(targetTime: number): Promise<number | undefined> {\n // Abort any in-flight seek\n this.#seekAbortController?.abort();\n this.#seekAbortController = new AbortController();\n const signal = this.#seekAbortController.signal;\n\n try {\n signal.throwIfAborted();\n\n await this.#host.waitForMediaDurations?.(signal);\n signal.throwIfAborted();\n\n const newTime = Math.max(0, Math.min(targetTime, this.#host.durationMs / 1000));\n this.#currentTime = newTime;\n this.#host.requestUpdate(\"currentTime\");\n this.#currentTimeMsProvider.setValue(this.currentTimeMs);\n this.#notifyListeners({\n property: \"currentTimeMs\",\n value: this.currentTimeMs,\n });\n\n signal.throwIfAborted();\n\n await this.runThrottledFrameTask();\n signal.throwIfAborted();\n\n // Save to localStorage for persistence (only if not restoring to avoid loops)\n const isRestoring = (this.#host as any).isRestoringFromLocalStorage?.() ?? false;\n if (!isRestoring) {\n this.#host.saveTimeToLocalStorage?.(newTime);\n } else {\n (this.#host as any).setRestoringFromLocalStorage?.(false);\n }\n this.#seekInProgress = false;\n return newTime;\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n // Expected - don't log\n return undefined;\n }\n throw error;\n }\n }\n\n get playing(): boolean {\n return this.#playing;\n }\n\n setPlaying(value: boolean): void {\n if (this.#playing === value) return;\n this.#playing = value;\n this.#playingProvider.setValue(value);\n this.#host.requestUpdate(\"playing\");\n this.#notifyListeners({ property: \"playing\", value });\n\n if (value) {\n this.startPlayback();\n } else {\n this.stopPlayback();\n }\n }\n\n get loop(): boolean {\n return this.#loop;\n }\n\n setLoop(value: boolean): void {\n if (this.#loop === value) return;\n this.#loop = value;\n this.#loopProvider.setValue(value);\n this.#host.requestUpdate(\"loop\");\n this.#notifyListeners({ property: \"loop\", value });\n }\n\n get currentTimeMs(): number {\n return this.currentTime * 1000;\n }\n\n setCurrentTimeMs(value: number): void {\n this.currentTime = value / 1000;\n }\n\n // Update time during playback without triggering a seek\n // Used by #syncPlayheadToAudioContext to avoid frame drops\n #updatePlaybackTime(timeMs: number): void {\n // Clamp to valid range to prevent time exceeding duration\n const durationMs = this.#host.durationMs;\n const clampedTimeMs = Math.max(0, Math.min(timeMs, durationMs));\n const timeSec = clampedTimeMs / 1000;\n if (this.#currentTime === timeSec) {\n return;\n }\n this.#currentTime = timeSec;\n this.#host.requestUpdate(\"currentTime\");\n this.#currentTimeMsProvider.setValue(clampedTimeMs);\n this.#notifyListeners({\n property: \"currentTimeMs\",\n value: clampedTimeMs,\n });\n // Trigger frame rendering without the async seek mechanism\n this.runThrottledFrameTask();\n }\n\n play(): void {\n this.setPlaying(true);\n }\n\n pause(): void {\n this.setPlaying(false);\n }\n\n #removed = false;\n\n hostConnected(): void {\n const isReconnect = this.#hasConnected;\n this.#hasConnected = true;\n // Defer all operations to avoid blocking during initialization\n // This prevents deadlocks when many timegroups are initializing simultaneously\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n // Check if this controller was removed before the RAF callback executed.\n // This happens when wrapWithWorkbench moves the element, causing disconnect/reconnect.\n if (this.#removed || this.#host.playbackController !== this) {\n return;\n }\n\n if (this.#playing && isReconnect) {\n this.startPlayback();\n } else if (!this.#playing) {\n this.#initializeTime();\n }\n });\n });\n }\n\n async #initializeTime(): Promise<void> {\n try {\n const waitPromise = this.#host.waitForMediaDurations?.();\n if (waitPromise) {\n await waitPromise;\n }\n } catch (err) {\n const isAbortError =\n (err instanceof DOMException && err.name === \"AbortError\") ||\n (err instanceof Error &&\n (err.name === \"AbortError\" ||\n err.message.includes(\"signal is aborted\") ||\n err.message.includes(\"The user aborted a request\")));\n if (!isAbortError) {\n console.error(\"Error in PlaybackController hostConnected:\", err);\n }\n return;\n }\n\n if (this.#removed || this.#host.playbackController !== this) {\n return;\n }\n\n const maybeLoadedTime = this.#host.loadTimeFromLocalStorage?.();\n if (maybeLoadedTime !== undefined) {\n (this.#host as any).setRestoringFromLocalStorage?.(true);\n this.currentTime = maybeLoadedTime;\n } else if (this.#currentTime === undefined) {\n this.currentTime = 0;\n }\n }\n\n hostDisconnected(): void {\n this.pause();\n }\n\n hostUpdated(): void {\n this.#durationMsProvider.setValue(this.#host.durationMs);\n this.#currentTimeMsProvider.setValue(this.currentTimeMs);\n }\n\n #selfRenderAbortController?: AbortController;\n #selfRenderPromise?: Promise<void>;\n #selfRenderDirty = false;\n #selfRenderSuspended = false;\n\n suspendSelfRender(): void {\n this.#selfRenderSuspended = true;\n this.#selfRenderAbortController?.abort();\n this.#selfRenderAbortController = undefined;\n }\n\n resumeSelfRender(): void {\n this.#selfRenderSuspended = false;\n }\n\n /**\n * Run frame rendering via FrameController, or directly on the host if it\n * implements FrameRenderable (standalone media element without a Timegroup).\n */\n async runThrottledFrameTask(): Promise<void> {\n const timeMs = this.currentTimeMs;\n\n if (this.#host.frameController) {\n try {\n await this.#host.frameController.renderFrame(timeMs, {\n onAnimationsUpdate: (root: Element) => {\n updateAnimations(root as unknown as AnimatableElement);\n },\n });\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") return;\n console.error(\"FrameController error:\", error);\n }\n return;\n }\n\n // Standalone FrameRenderable host (e.g. bare ef-video without a Timegroup)\n const host = this.#host as unknown as Partial<FrameRenderable>;\n if (!host.prepareFrame || !host.renderFrame) return;\n\n if (this.#selfRenderSuspended) return;\n\n // If a render is in-flight, mark dirty so we re-render after it\n // completes (source mapping may have changed due to trim drag).\n if (this.#selfRenderPromise) {\n this.#selfRenderDirty = true;\n return this.#selfRenderPromise;\n }\n\n return this.#startSelfRender(host, timeMs);\n }\n\n #startSelfRender(host: Partial<FrameRenderable>, timeMs: number): Promise<void> {\n this.#selfRenderAbortController?.abort();\n this.#selfRenderAbortController = new AbortController();\n const signal = this.#selfRenderAbortController.signal;\n this.#selfRenderDirty = false;\n\n this.#selfRenderPromise = (async () => {\n try {\n await host.prepareFrame!(timeMs, signal);\n signal.throwIfAborted();\n host.renderFrame!(timeMs);\n updateAnimations(this.#host as unknown as AnimatableElement);\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") return;\n if ((error as any)?.name === \"AbortError\") return;\n console.error(\"Standalone frame render error:\", error);\n } finally {\n this.#selfRenderPromise = undefined;\n // Re-render if source mapping changed while we were rendering\n if (this.#selfRenderDirty && !this.#selfRenderSuspended) {\n this.#startSelfRender(host, this.currentTimeMs);\n }\n }\n })();\n\n return this.#selfRenderPromise;\n }\n\n addListener(listener: (event: PlaybackControllerUpdateEvent) => void): void {\n this.#listeners.add(listener);\n }\n\n removeListener(listener: (event: PlaybackControllerUpdateEvent) => void): void {\n this.#listeners.delete(listener);\n }\n\n #notifyListeners(event: PlaybackControllerUpdateEvent): void {\n for (const listener of this.#listeners) {\n listener(event);\n }\n }\n\n remove(): void {\n this.#removed = true; // Mark as removed to abort any pending RAF callbacks\n this.stopPlayback();\n this.#listeners.clear();\n this.#host.removeController(this);\n }\n\n setPendingAudioContext(context: AudioContext): void {\n this.#pendingAudioContext = context;\n }\n\n #syncPlayheadToAudioContext(startMs: number) {\n const audioContextTime = this.#playbackAudioContext?.currentTime ?? 0;\n const endMs = this.#host.endTimeMs;\n\n // Calculate raw time based on audio context\n let rawTimeMs: number;\n if (this.#playbackWrapTimeSeconds > 0 && audioContextTime >= this.#playbackWrapTimeSeconds) {\n // After wrap: time since wrap, wrapped to duration\n const timeSinceWrap = (audioContextTime - this.#playbackWrapTimeSeconds) * 1000;\n rawTimeMs = timeSinceWrap % endMs;\n } else {\n // Before wrap or no wrap: normal calculation\n rawTimeMs = startMs + audioContextTime * 1000;\n\n // If looping and we've reached the end, wrap around\n if (this.#loopingPlayback && rawTimeMs >= endMs) {\n rawTimeMs = rawTimeMs % endMs;\n }\n }\n\n const nextTimeMs = Math.round(rawTimeMs / this.#MS_PER_FRAME) * this.#MS_PER_FRAME;\n\n // During playback, update time directly without triggering seek\n // This avoids frame drops at the loop boundary\n this.#updatePlaybackTime(nextTimeMs);\n\n // Only check for end if we haven't already handled looping\n if (!this.#loopingPlayback && nextTimeMs >= endMs) {\n this.maybeLoopPlayback();\n return;\n }\n\n this.#playbackAnimationFrameRequest = requestAnimationFrame(() => {\n this.#syncPlayheadToAudioContext(startMs);\n });\n }\n\n private async maybeLoopPlayback() {\n if (this.#loop) {\n // Loop enabled: reset to beginning and restart playback\n // We restart the audio system directly without changing #playing state\n // to keep the play button in sync\n this.setCurrentTimeMs(0);\n // Restart in next frame without awaiting to minimize gap\n requestAnimationFrame(() => {\n this.startPlayback();\n });\n } else {\n // No loop: reset to beginning and stop\n // This ensures play button works when clicked again\n this.setCurrentTimeMs(0);\n this.pause();\n }\n }\n\n private async stopPlayback() {\n if (this.#playbackAnimationFrameRequest) {\n cancelAnimationFrame(this.#playbackAnimationFrameRequest);\n this.#playbackAnimationFrameRequest = null;\n }\n if (this.#playbackAudioContext) {\n if (this.#playbackAudioContext.state !== \"closed\") {\n await this.#playbackAudioContext.close();\n }\n }\n this.#playbackAudioContext = null;\n this.#pendingAudioContext = null;\n }\n\n private async startPlayback() {\n // Guard against starting playback on a removed controller\n if (this.#removed) {\n return;\n }\n\n await this.stopPlayback();\n const host = this.#host;\n if (!host) {\n return;\n }\n\n if (host.waitForMediaDurations) {\n await host.waitForMediaDurations();\n }\n\n // Check again after async - controller could have been removed\n if (this.#removed) {\n return;\n }\n\n const currentMs = this.currentTimeMs;\n const fromMs = currentMs;\n const toMs = host.endTimeMs;\n\n if (fromMs >= toMs) {\n this.pause();\n return;\n }\n\n let bufferCount = 0;\n // Check for pre-resumed AudioContext from synchronous user interaction\n if (this.#pendingAudioContext) {\n this.#playbackAudioContext = this.#pendingAudioContext;\n this.#pendingAudioContext = null;\n } else {\n this.#playbackAudioContext = new AudioContext({\n latencyHint: \"playback\",\n });\n }\n this.#loopingPlayback = this.#loop; // Remember if we're in a looping session\n this.#playbackWrapTimeSeconds = 0; // Reset wrap time\n\n if (this.#playbackAnimationFrameRequest) {\n cancelAnimationFrame(this.#playbackAnimationFrameRequest);\n }\n this.#syncPlayheadToAudioContext(currentMs);\n const playbackContext = this.#playbackAudioContext;\n\n // Check if context is suspended (fallback for newly-created contexts)\n if (playbackContext.state === \"suspended\") {\n // Attempt to resume (may not work on mobile if user interaction context is lost)\n try {\n await playbackContext.resume();\n // Check state again after resume attempt\n if (playbackContext.state === \"suspended\") {\n console.warn(\n \"AudioContext is suspended and resume() failed. \" +\n \"On mobile devices, AudioContext.resume() must be called synchronously within a user interaction handler. \" +\n \"Media playback will not work until user has interacted with page.\",\n );\n this.setPlaying(false);\n return;\n }\n } catch (error) {\n console.warn(\n \"Failed to resume AudioContext:\",\n error,\n \"On mobile devices, AudioContext.resume() must be called synchronously within a user interaction handler.\",\n );\n this.setPlaying(false);\n return;\n }\n }\n await playbackContext.suspend();\n\n // Track the logical media time (what position in the media we're rendering)\n // vs the AudioContext schedule time (when to play it)\n let logicalTimeMs = currentMs;\n let audioContextTimeMs = 0; // Tracks the schedule position in the AudioContext timeline\n let hasWrapped = false;\n\n const fillBuffer = async () => {\n if (bufferCount > 2) {\n return;\n }\n const canFillBuffer = await queueBufferSource();\n if (canFillBuffer) {\n fillBuffer().catch(() => {});\n }\n };\n\n const queueBufferSource = async () => {\n // Check if we've already wrapped and aren't looping anymore\n if (hasWrapped && !this.#loopingPlayback) {\n return false;\n }\n\n const startMs = logicalTimeMs;\n const endMs = Math.min(logicalTimeMs + this.#AUDIO_PLAYBACK_SLICE_MS, toMs);\n\n // Will this slice reach the end?\n const willReachEnd = endMs >= toMs;\n\n if (!host.renderAudio) {\n return false;\n }\n\n const audioBuffer = await host.renderAudio(startMs, endMs);\n bufferCount++;\n const source = playbackContext.createBufferSource();\n source.buffer = audioBuffer;\n source.connect(playbackContext.destination);\n // Schedule this buffer to play at the current audioContextTime position\n source.start(audioContextTimeMs / 1000);\n\n const sliceDurationMs = endMs - startMs;\n\n source.onended = () => {\n bufferCount--;\n\n if (willReachEnd) {\n if (!this.#loopingPlayback) {\n // Not looping, end playback\n this.maybeLoopPlayback();\n } else {\n // Looping: continue filling buffer after wrap\n fillBuffer().catch(() => {});\n }\n } else {\n // Continue filling buffer\n fillBuffer().catch(() => {});\n }\n };\n\n // Advance the AudioContext schedule time\n audioContextTimeMs += sliceDurationMs;\n\n // If this buffer reaches the end and we're looping, immediately queue the wraparound\n if (willReachEnd && this.#loopingPlayback) {\n // Mark that we've wrapped\n hasWrapped = true;\n // Store when we wrapped (relative to when playback started, which is time 0 in AudioContext)\n // This is the duration from start to end\n this.#playbackWrapTimeSeconds = (toMs - fromMs) / 1000;\n // Reset logical time to beginning\n logicalTimeMs = 0;\n // Continue buffering will happen in fillBuffer() call below\n } else {\n // Normal advance\n logicalTimeMs = endMs;\n }\n\n return true;\n };\n\n try {\n await fillBuffer();\n await playbackContext.resume();\n } catch (error) {\n // Ignore errors if AudioContext is closed or during test cleanup\n if (\n error instanceof Error &&\n (error.name === \"InvalidStateError\" || error.message.includes(\"closed\"))\n ) {\n return;\n }\n throw error;\n }\n }\n}\n"],"mappings":"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"}
|
package/dist/gui/TWMixin2.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TWMixin2.js","names":["twSheet: CSSStyleSheet | null","twStyle","constructorStylesheets: CSSStyleSheet[]"],"sources":["../../src/gui/TWMixin.ts"],"sourcesContent":["import type { CSSResult, LitElement } from \"lit\";\n// @ts-expect-error cannot figure out how to declare this module as a string\nimport twStyle from \"./TWMixin.css?inline\";\n\nlet twSheet: CSSStyleSheet | null = null;\nif (typeof window !== \"undefined\" && typeof CSSStyleSheet !== \"undefined\") {\n try {\n twSheet = new CSSStyleSheet();\n if (typeof twSheet.replaceSync === \"function\") {\n twSheet.replaceSync(twStyle);\n }\n } catch (_error) {\n // CSSStyleSheet or replaceSync not supported in this environment\n twSheet = null;\n }\n}\nexport function TWMixin<T extends new (...args: any[]) => LitElement>(Base: T) {\n class TWElement extends Base {\n createRenderRoot() {\n const renderRoot = super.createRenderRoot();\n if (!(renderRoot instanceof ShadowRoot)) {\n throw new Error(\
|
|
1
|
+
{"version":3,"file":"TWMixin2.js","names":["twSheet: CSSStyleSheet | null","twStyle","constructorStylesheets: CSSStyleSheet[]"],"sources":["../../src/gui/TWMixin.ts"],"sourcesContent":["import type { CSSResult, LitElement } from \"lit\";\n// @ts-expect-error cannot figure out how to declare this module as a string\nimport twStyle from \"./TWMixin.css?inline\";\n\nlet twSheet: CSSStyleSheet | null = null;\nif (typeof window !== \"undefined\" && typeof CSSStyleSheet !== \"undefined\") {\n try {\n twSheet = new CSSStyleSheet();\n if (typeof twSheet.replaceSync === \"function\") {\n twSheet.replaceSync(twStyle);\n }\n } catch (_error) {\n // CSSStyleSheet or replaceSync not supported in this environment\n twSheet = null;\n }\n}\nexport function TWMixin<T extends new (...args: any[]) => LitElement>(Base: T) {\n class TWElement extends Base {\n createRenderRoot() {\n const renderRoot = super.createRenderRoot();\n if (!(renderRoot instanceof ShadowRoot)) {\n throw new Error(\"TWMixin can only be applied to elements with shadow roots\");\n }\n if (!twSheet) {\n throw new Error(\n \"twSheet not found. Probable cause: CSSStyleSheet not supported in this environment\",\n );\n }\n\n const constructorStylesheets: CSSStyleSheet[] = [];\n const constructorStyles = ((\"styles\" in this.constructor && this.constructor.styles) || []) as\n | CSSResult\n | CSSResult[];\n\n if (Array.isArray(constructorStyles)) {\n for (const item of constructorStyles) {\n if (item.styleSheet) {\n constructorStylesheets.push(item.styleSheet);\n }\n }\n } else if (constructorStyles.styleSheet) {\n constructorStylesheets.push(constructorStyles.styleSheet);\n }\n\n if (renderRoot?.adoptedStyleSheets) {\n renderRoot.adoptedStyleSheets = [\n twSheet,\n ...renderRoot.adoptedStyleSheets,\n ...constructorStylesheets,\n ];\n } else {\n renderRoot.adoptedStyleSheets = [twSheet, ...constructorStylesheets];\n }\n return renderRoot;\n }\n }\n\n return TWElement as T;\n}\n"],"mappings":";;;AAIA,IAAIA,UAAgC;AACpC,IAAI,OAAO,WAAW,eAAe,OAAO,kBAAkB,YAC5D,KAAI;AACF,WAAU,IAAI,eAAe;AAC7B,KAAI,OAAO,QAAQ,gBAAgB,WACjC,SAAQ,YAAYC,gBAAQ;SAEvB,QAAQ;AAEf,WAAU;;AAGd,SAAgB,QAAsD,MAAS;CAC7E,MAAM,kBAAkB,KAAK;EAC3B,mBAAmB;GACjB,MAAM,aAAa,MAAM,kBAAkB;AAC3C,OAAI,EAAE,sBAAsB,YAC1B,OAAM,IAAI,MAAM,4DAA4D;AAE9E,OAAI,CAAC,QACH,OAAM,IAAI,MACR,qFACD;GAGH,MAAMC,yBAA0C,EAAE;GAClD,MAAM,oBAAsB,YAAY,KAAK,eAAe,KAAK,YAAY,UAAW,EAAE;AAI1F,OAAI,MAAM,QAAQ,kBAAkB,EAClC;SAAK,MAAM,QAAQ,kBACjB,KAAI,KAAK,WACP,wBAAuB,KAAK,KAAK,WAAW;cAGvC,kBAAkB,WAC3B,wBAAuB,KAAK,kBAAkB,WAAW;AAG3D,OAAI,YAAY,mBACd,YAAW,qBAAqB;IAC9B;IACA,GAAG,WAAW;IACd,GAAG;IACJ;OAED,YAAW,qBAAqB,CAAC,SAAS,GAAG,uBAAuB;AAEtE,UAAO;;;AAIX,QAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TargetOrContextMixin.js","names":["#targetController","#contextRequestHandler","#contextUnsubscribe","#additionalContextUnsubscribes","#directTemporalSubscription","#unsubscribeAll","#tryDirectTemporalSubscription","additionalContexts: Array<[Context<any, any>, string]>","#subscribeToTargetContext"],"sources":["../../src/gui/TargetOrContextMixin.ts"],"sourcesContent":["import { type Context, consume } from \"@lit/context\";\nimport type { LitElement } from \"lit\";\nimport { property, state } from \"lit/decorators.js\";\nimport {\n isEFTemporal,\n type TemporalMixinInterface,\n} from \"../elements/EFTemporal.js\";\nimport { TargetController } from \"../elements/TargetController.js\";\nimport {\n type ControllableInterface,\n type ControllableSubscription,\n isControllable,\n determineTargetType,\n createDirectTemporalSubscription,\n} from \"./Controllable.js\";\nimport { currentTimeContext } from \"./currentTimeContext.js\";\nimport { durationContext } from \"./durationContext.js\";\nimport { loopContext, playingContext } from \"./playingContext.js\";\n\ntype Constructor<T = {}> = new (...args: any[]) => T;\n\nclass ContextRequestEvent extends Event {\n context: Context<any, any>;\n contextTarget: Element;\n callback: (value: any, unsubscribe: () => void) => void;\n subscribe: boolean;\n\n constructor(\n context: Context<any, any>,\n contextTarget: Element,\n callback: (value: any, unsubscribe: () => void) => 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\nexport function TargetOrContextMixin<T extends Constructor<LitElement>>(\n superClass: T,\n contextToProxy: Context<any, any>,\n) {\n class TargetOrContextClass extends superClass {\n @property({ type: String })\n target = \"\";\n\n @state()\n targetElement: ControllableInterface | null = null;\n\n // @ts-expect-error contextToProxy is generic but provides ControllableInterface at runtime\n @consume({ context: contextToProxy, subscribe: true })\n contextFromParent: ControllableInterface | null = null;\n\n #targetController?: TargetController;\n #contextUnsubscribe?: () => void;\n #contextRequestHandler?: (event: Event) => void;\n #additionalContextUnsubscribes = new Map<Context<any, any>, () => void>();\n #directTemporalSubscription?: ControllableSubscription;\n\n get effectiveContext(): ControllableInterface | null {\n return this.targetElement ?? this.contextFromParent;\n }\n\n connectedCallback() {\n super.connectedCallback();\n if (this.target) {\n this.#targetController = new TargetController(\n this as any as LitElement & {\n targetElement: Element | null;\n target: string;\n },\n );\n }\n\n // Intercept context-request events and redirect them to targetElement\n this.#contextRequestHandler = (event: Event) => {\n if (this.targetElement && event.type === \"context-request\") {\n event.stopPropagation();\n this.targetElement.dispatchEvent(\n new (event.constructor as any)(event.type, event),\n );\n }\n };\n this.addEventListener(\n \"context-request\",\n this.#contextRequestHandler,\n true,\n );\n }\n\n #unsubscribeAll() {\n this.#contextUnsubscribe?.();\n this.#contextUnsubscribe = undefined;\n for (const unsubscribe of this.#additionalContextUnsubscribes.values()) {\n unsubscribe();\n }\n this.#additionalContextUnsubscribes.clear();\n this.#directTemporalSubscription?.unsubscribe();\n this.#directTemporalSubscription = undefined;\n }\n\n #tryDirectTemporalSubscription(): boolean {\n if (!this.targetElement) return false;\n\n const targetType = determineTargetType(this.targetElement);\n if (targetType !== \"direct-temporal\") return false;\n\n this.#directTemporalSubscription = createDirectTemporalSubscription(\n this.targetElement as unknown as TemporalMixinInterface & HTMLElement,\n {\n onPlayingChange: (value) => {\n (this as any).playing = value;\n },\n onLoopChange: (value) => {\n if (\"loop\" in this) (this as any).loop = value;\n },\n onCurrentTimeMsChange: (value) => {\n if (\"currentTimeMs\" in this) (this as any).currentTimeMs = value;\n },\n onDurationMsChange: (value) => {\n if (\"durationMs\" in this) (this as any).durationMs = value;\n },\n onTargetTemporalChange: () => {},\n },\n );\n return true;\n }\n\n #subscribeToTargetContext() {\n if (!this.targetElement) return;\n\n this.#unsubscribeAll();\n\n if (this.#tryDirectTemporalSubscription()) return;\n\n // Temporal target without PlaybackController yet — wait for initialization\n if (isEFTemporal(this.targetElement)) {\n const target = this.targetElement as unknown as TemporalMixinInterface &\n HTMLElement;\n target.updateComplete.then(() => {\n if ((this.targetElement as unknown) !== target) return;\n if (!this.#tryDirectTemporalSubscription()) {\n // Still not ready — one more cycle (PlaybackController created in updateComplete.then)\n target.updateComplete.then(() => {\n if ((this.targetElement as unknown) !== target) return;\n this.#tryDirectTemporalSubscription();\n });\n }\n });\n return;\n }\n\n // Context-provider path (EFPreview, etc.)\n const event = new ContextRequestEvent(\n contextToProxy,\n this,\n (value, unsubscribe) => {\n (this as any).contextFromParent = value;\n this.#contextUnsubscribe = unsubscribe;\n },\n true,\n );\n this.targetElement.dispatchEvent(event);\n\n const additionalContexts: Array<[Context<any, any>, string]> = [\n [playingContext, \"playing\"],\n [loopContext, \"loop\"],\n [currentTimeContext, \"currentTimeMs\"],\n [durationContext, \"durationMs\"],\n ];\n\n for (const [context, propertyName] of additionalContexts) {\n const contextEvent = new ContextRequestEvent(\n context,\n this,\n (value, unsubscribe) => {\n if (propertyName in this) {\n (this as any)[propertyName] = value;\n }\n this.#additionalContextUnsubscribes.set(context, unsubscribe);\n },\n true,\n );\n this.targetElement.dispatchEvent(contextEvent);\n }\n }\n\n updated(changedProperties: Map<string | number | symbol, unknown>): void {\n super.updated?.(changedProperties);\n\n if (changedProperties.has(\"targetElement\") && this.targetElement) {\n if (\n isEFTemporal(this.targetElement) &&\n !isControllable(this.targetElement)\n ) {\n console.warn(\n \"Control element is targeting a non-root temporal element without playbackController. \" +\n \"Controls can only target root temporal elements (not nested within a timegroup). \" +\n \"Target element:\",\n this.targetElement,\n );\n }\n this.#subscribeToTargetContext();\n }\n\n if (changedProperties.has(\"target\")) {\n if (this.target && !this.#targetController) {\n this.#targetController = new TargetController(\n this as any as LitElement & {\n targetElement: Element | null;\n target: string;\n },\n );\n }\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.#unsubscribeAll();\n if (this.#contextRequestHandler) {\n this.removeEventListener(\n \"context-request\",\n this.#contextRequestHandler,\n true,\n );\n }\n }\n }\n\n return TargetOrContextClass as Constructor<{\n target: string;\n targetElement: ControllableInterface | null;\n effectiveContext: ControllableInterface | null;\n }> &\n T;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TargetOrContextMixin.js","names":["#targetController","#contextRequestHandler","#contextUnsubscribe","#additionalContextUnsubscribes","#directTemporalSubscription","#unsubscribeAll","#tryDirectTemporalSubscription","additionalContexts: Array<[Context<any, any>, string]>","#subscribeToTargetContext"],"sources":["../../src/gui/TargetOrContextMixin.ts"],"sourcesContent":["import { type Context, consume } from \"@lit/context\";\nimport type { LitElement } from \"lit\";\nimport { property, state } from \"lit/decorators.js\";\nimport { isEFTemporal, type TemporalMixinInterface } from \"../elements/EFTemporal.js\";\nimport { TargetController } from \"../elements/TargetController.js\";\nimport {\n type ControllableInterface,\n type ControllableSubscription,\n isControllable,\n determineTargetType,\n createDirectTemporalSubscription,\n} from \"./Controllable.js\";\nimport { currentTimeContext } from \"./currentTimeContext.js\";\nimport { durationContext } from \"./durationContext.js\";\nimport { loopContext, playingContext } from \"./playingContext.js\";\n\ntype Constructor<T = {}> = new (...args: any[]) => T;\n\nclass ContextRequestEvent extends Event {\n context: Context<any, any>;\n contextTarget: Element;\n callback: (value: any, unsubscribe: () => void) => void;\n subscribe: boolean;\n\n constructor(\n context: Context<any, any>,\n contextTarget: Element,\n callback: (value: any, unsubscribe: () => void) => 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\nexport function TargetOrContextMixin<T extends Constructor<LitElement>>(\n superClass: T,\n contextToProxy: Context<any, any>,\n) {\n class TargetOrContextClass extends superClass {\n @property({ type: String })\n target = \"\";\n\n @state()\n targetElement: ControllableInterface | null = null;\n\n // @ts-expect-error contextToProxy is generic but provides ControllableInterface at runtime\n @consume({ context: contextToProxy, subscribe: true })\n contextFromParent: ControllableInterface | null = null;\n\n #targetController?: TargetController;\n #contextUnsubscribe?: () => void;\n #contextRequestHandler?: (event: Event) => void;\n #additionalContextUnsubscribes = new Map<Context<any, any>, () => void>();\n #directTemporalSubscription?: ControllableSubscription;\n\n get effectiveContext(): ControllableInterface | null {\n return this.targetElement ?? this.contextFromParent;\n }\n\n connectedCallback() {\n super.connectedCallback();\n if (this.target) {\n this.#targetController = new TargetController(\n this as any as LitElement & {\n targetElement: Element | null;\n target: string;\n },\n );\n }\n\n // Intercept context-request events and redirect them to targetElement\n this.#contextRequestHandler = (event: Event) => {\n if (this.targetElement && event.type === \"context-request\") {\n event.stopPropagation();\n this.targetElement.dispatchEvent(new (event.constructor as any)(event.type, event));\n }\n };\n this.addEventListener(\"context-request\", this.#contextRequestHandler, true);\n }\n\n #unsubscribeAll() {\n this.#contextUnsubscribe?.();\n this.#contextUnsubscribe = undefined;\n for (const unsubscribe of this.#additionalContextUnsubscribes.values()) {\n unsubscribe();\n }\n this.#additionalContextUnsubscribes.clear();\n this.#directTemporalSubscription?.unsubscribe();\n this.#directTemporalSubscription = undefined;\n }\n\n #tryDirectTemporalSubscription(): boolean {\n if (!this.targetElement) return false;\n\n const targetType = determineTargetType(this.targetElement);\n if (targetType !== \"direct-temporal\") return false;\n\n this.#directTemporalSubscription = createDirectTemporalSubscription(\n this.targetElement as unknown as TemporalMixinInterface & HTMLElement,\n {\n onPlayingChange: (value) => {\n (this as any).playing = value;\n },\n onLoopChange: (value) => {\n if (\"loop\" in this) (this as any).loop = value;\n },\n onCurrentTimeMsChange: (value) => {\n if (\"currentTimeMs\" in this) (this as any).currentTimeMs = value;\n },\n onDurationMsChange: (value) => {\n if (\"durationMs\" in this) (this as any).durationMs = value;\n },\n onTargetTemporalChange: () => {},\n },\n );\n return true;\n }\n\n #subscribeToTargetContext() {\n if (!this.targetElement) return;\n\n this.#unsubscribeAll();\n\n if (this.#tryDirectTemporalSubscription()) return;\n\n // Temporal target without PlaybackController yet — wait for initialization\n if (isEFTemporal(this.targetElement)) {\n const target = this.targetElement as unknown as TemporalMixinInterface & HTMLElement;\n target.updateComplete.then(() => {\n if ((this.targetElement as unknown) !== target) return;\n if (!this.#tryDirectTemporalSubscription()) {\n // Still not ready — one more cycle (PlaybackController created in updateComplete.then)\n target.updateComplete.then(() => {\n if ((this.targetElement as unknown) !== target) return;\n this.#tryDirectTemporalSubscription();\n });\n }\n });\n return;\n }\n\n // Context-provider path (EFPreview, etc.)\n const event = new ContextRequestEvent(\n contextToProxy,\n this,\n (value, unsubscribe) => {\n (this as any).contextFromParent = value;\n this.#contextUnsubscribe = unsubscribe;\n },\n true,\n );\n this.targetElement.dispatchEvent(event);\n\n const additionalContexts: Array<[Context<any, any>, string]> = [\n [playingContext, \"playing\"],\n [loopContext, \"loop\"],\n [currentTimeContext, \"currentTimeMs\"],\n [durationContext, \"durationMs\"],\n ];\n\n for (const [context, propertyName] of additionalContexts) {\n const contextEvent = new ContextRequestEvent(\n context,\n this,\n (value, unsubscribe) => {\n if (propertyName in this) {\n (this as any)[propertyName] = value;\n }\n this.#additionalContextUnsubscribes.set(context, unsubscribe);\n },\n true,\n );\n this.targetElement.dispatchEvent(contextEvent);\n }\n }\n\n updated(changedProperties: Map<string | number | symbol, unknown>): void {\n super.updated?.(changedProperties);\n\n if (changedProperties.has(\"targetElement\") && this.targetElement) {\n if (isEFTemporal(this.targetElement) && !isControllable(this.targetElement)) {\n console.warn(\n \"Control element is targeting a non-root temporal element without playbackController. \" +\n \"Controls can only target root temporal elements (not nested within a timegroup). \" +\n \"Target element:\",\n this.targetElement,\n );\n }\n this.#subscribeToTargetContext();\n }\n\n if (changedProperties.has(\"target\")) {\n if (this.target && !this.#targetController) {\n this.#targetController = new TargetController(\n this as any as LitElement & {\n targetElement: Element | null;\n target: string;\n },\n );\n }\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.#unsubscribeAll();\n if (this.#contextRequestHandler) {\n this.removeEventListener(\"context-request\", this.#contextRequestHandler, true);\n }\n }\n }\n\n return TargetOrContextClass as Constructor<{\n target: string;\n targetElement: ControllableInterface | null;\n effectiveContext: ControllableInterface | null;\n }> &\n T;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"currentTimeContext.js","names":[],"sources":["../../src/gui/currentTimeContext.ts"],"sourcesContent":["import { createContext } from \"@lit/context\";\n\nexport const currentTimeContext = createContext<number>(
|
|
1
|
+
{"version":3,"file":"currentTimeContext.js","names":[],"sources":["../../src/gui/currentTimeContext.ts"],"sourcesContent":["import { createContext } from \"@lit/context\";\n\nexport const currentTimeContext = createContext<number>(Symbol(\"currentTimeMs\"));\n"],"mappings":";;;AAEA,MAAa,qBAAqB,cAAsB,OAAO,gBAAgB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"efContext.js","names":[],"sources":["../../src/gui/efContext.ts"],"sourcesContent":["import { createContext } from \"@lit/context\";\nimport type { ControllableInterface } from \"./Controllable.js\";\n\nexport const efContext = createContext<ControllableInterface | null>(
|
|
1
|
+
{"version":3,"file":"efContext.js","names":[],"sources":["../../src/gui/efContext.ts"],"sourcesContent":["import { createContext } from \"@lit/context\";\nimport type { ControllableInterface } from \"./Controllable.js\";\n\nexport const efContext = createContext<ControllableInterface | null>(Symbol(\"efContext\"));\n"],"mappings":";;;AAGA,MAAa,YAAY,cAA4C,OAAO,YAAY,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetchContext.js","names":[],"sources":["../../src/gui/fetchContext.ts"],"sourcesContent":["import { createContext } from \"@lit/context\";\n\nexport const fetchContext = createContext
|
|
1
|
+
{"version":3,"file":"fetchContext.js","names":[],"sources":["../../src/gui/fetchContext.ts"],"sourcesContent":["import { createContext } from \"@lit/context\";\n\nexport const fetchContext = createContext<(url: string, init?: RequestInit) => Promise<Response>>(\n Symbol(\"fetchContext\"),\n);\n"],"mappings":";;;AAEA,MAAa,eAAe,cAC1B,OAAO,eAAe,CACvB"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { SelectionContext } from "../../canvas/selection/selectionContext.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as lit11 from "lit";
|
|
3
3
|
import { LitElement, PropertyValues } from "lit";
|
|
4
|
-
import * as
|
|
4
|
+
import * as lit_html11 from "lit-html";
|
|
5
5
|
|
|
6
6
|
//#region src/gui/hierarchy/EFHierarchy.d.ts
|
|
7
7
|
declare const EFHierarchy_base: typeof LitElement;
|
|
8
8
|
declare class EFHierarchy extends EFHierarchy_base {
|
|
9
|
-
static styles:
|
|
9
|
+
static styles: lit11.CSSResult[];
|
|
10
10
|
target: string;
|
|
11
11
|
header: string;
|
|
12
12
|
showHeader: boolean;
|
|
@@ -53,7 +53,7 @@ declare class EFHierarchy extends EFHierarchy_base {
|
|
|
53
53
|
private autoSelectFirstRootTimegroup;
|
|
54
54
|
private setupSelectionListener;
|
|
55
55
|
private removeSelectionListener;
|
|
56
|
-
render():
|
|
56
|
+
render(): lit_html11.TemplateResult<1>;
|
|
57
57
|
}
|
|
58
58
|
declare global {
|
|
59
59
|
interface HTMLElementTagNameMap {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EFHierarchy.js","names":["EFHierarchy","selectedId: string | null"],"sources":["../../../src/gui/hierarchy/EFHierarchy.ts"],"sourcesContent":["import { consume, provide } from \"@lit/context\";\nimport { css, html, LitElement, nothing, type PropertyValues } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\n\nimport { isEFTemporal } from \"../../elements/EFTemporal.js\";\nimport { selectionContext } from \"../../canvas/selection/selectionContext.js\";\nimport { TargetController } from \"../../elements/TargetController.js\";\nimport { TWMixin } from \"../TWMixin.js\";\nimport {\n type HierarchyActions,\n type HierarchyContext,\n type HierarchyState,\n hierarchyContext,\n} from \"./hierarchyContext.js\";\nimport { renderHierarchyChildren } from \"./EFHierarchyItem.js\";\nimport type { EFCanvas } from \"../../canvas/EFCanvas.js\";\n\n@customElement(\"ef-hierarchy\")\nexport class EFHierarchy extends TWMixin(LitElement) {\n static styles = [\n css`\n :host {\n display: block;\n overflow: auto;\n height: 100%;\n font-size: 12px;\n \n /* Component tokens (reference globals from ef-theme.css) */\n --hierarchy-bg: var(--ef-color-bg);\n --hierarchy-border: var(--ef-color-border);\n --hierarchy-text: var(--ef-color-text);\n --hierarchy-hover-bg: var(--ef-color-hover);\n --hierarchy-selected-bg: var(--ef-color-selected);\n --hierarchy-ancestor-selected-bg: var(--ef-color-selected-subtle);\n --hierarchy-focused-bg: var(--ef-color-focused);\n --hierarchy-drop-indicator: var(--ef-color-primary);\n }\n \n .hierarchy-container {\n background: var(--hierarchy-bg);\n color: var(--hierarchy-text);\n padding: 4px 0;\n }\n \n .header {\n padding: 8px 12px;\n font-weight: 600;\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: var(--ef-color-text-muted);\n border-bottom: 1px solid var(--hierarchy-border);\n margin-bottom: 4px;\n }\n \n .empty {\n padding: 16px;\n text-align: center;\n color: var(--ef-color-text-subtle);\n font-style: italic;\n }\n `,\n ];\n\n @property({ type: String })\n target = \"\";\n\n @property({ type: String })\n header = \"\";\n\n @property({ type: Boolean, attribute: \"show-header\" })\n showHeader = false;\n\n @property({ type: Array, attribute: false })\n hideSelectors?: string[];\n\n @property({ type: Array, attribute: false })\n showSelectors?: string[];\n\n @state()\n targetElement: Element | null = null;\n\n private targetController?: TargetController;\n\n @consume({ context: selectionContext, subscribe: true })\n canvasSelectionContext?: import(\"../../canvas/selection/selectionContext.js\").SelectionContext;\n\n /**\n * Get the target canvas element.\n * The canvas is the source of truth for selection and highlight state.\n */\n private getCanvas(): EFCanvas | null {\n // First try TargetController (for registered elements like EFCanvas)\n if (this.targetElement && (this.targetElement as any).selectionContext) {\n return this.targetElement as EFCanvas;\n }\n // Fall back to direct lookup for any element with matching ID\n if (this.target) {\n const target = document.getElementById(this.target);\n if (target && (target as any).selectionContext) {\n return target as EFCanvas;\n }\n }\n return null;\n }\n\n /**\n * Get canvas selection context from the target canvas element.\n * Used when hierarchy is a sibling of canvas (can't access via Lit context).\n */\n private getCanvasSelectionContext():\n | import(\"../../canvas/selection/selectionContext.js\").SelectionContext\n | undefined {\n // First try Lit context (works when hierarchy is inside canvas)\n if (this.canvasSelectionContext) {\n return this.canvasSelectionContext;\n }\n // Use target canvas\n const canvas = this.getCanvas();\n return canvas?.selectionContext;\n }\n\n /**\n * Get the currently highlighted element from the canvas.\n */\n getHighlightedElement(): HTMLElement | null {\n const canvas = this.getCanvas();\n return canvas?.highlightedElement ?? null;\n }\n\n /**\n * Set the highlighted element on the canvas.\n * Called when user hovers an item in the hierarchy.\n */\n setHighlightedElement(element: HTMLElement | null): void {\n const canvas = this.getCanvas();\n canvas?.setHighlightedElement(element);\n }\n\n @state()\n private hierarchyState: HierarchyState = {\n selectedElementId: null,\n expandedIds: new Set(),\n draggedElementId: null,\n dropTargetId: null,\n dropPosition: null,\n };\n\n private selectionChangeHandler?: (event: CustomEvent) => void;\n\n private hierarchyActions: HierarchyActions = {\n select: (elementId: string | null) => {\n const selectionCtx = this.getCanvasSelectionContext();\n if (selectionCtx) {\n if (elementId) {\n // Ensure element is registered with canvas before selecting\n const element = document.getElementById(elementId);\n if (element) {\n // Find the canvas element\n const canvas = element.closest(\"ef-canvas\") as any;\n if (canvas && canvas.tryRegisterElement) {\n // Try to register if not already registered\n canvas.tryRegisterElement(element);\n }\n }\n\n // Select the element directly by its ID\n selectionCtx.select(elementId);\n } else {\n selectionCtx.clear();\n }\n } else {\n this.hierarchyState = {\n ...this.hierarchyState,\n selectedElementId: elementId,\n };\n }\n this.dispatchEvent(\n new CustomEvent(\"hierarchy-select\", {\n detail: { elementId },\n bubbles: true,\n composed: true,\n }),\n );\n },\n\n toggleExpanded: (elementId: string) => {\n const newExpanded = new Set(this.hierarchyState.expandedIds);\n if (newExpanded.has(elementId)) {\n newExpanded.delete(elementId);\n } else {\n newExpanded.add(elementId);\n }\n this.hierarchyState = {\n ...this.hierarchyState,\n expandedIds: newExpanded,\n };\n },\n\n setExpanded: (elementId: string, expanded: boolean) => {\n const newExpanded = new Set(this.hierarchyState.expandedIds);\n if (expanded) {\n newExpanded.add(elementId);\n } else {\n newExpanded.delete(elementId);\n }\n this.hierarchyState = {\n ...this.hierarchyState,\n expandedIds: newExpanded,\n };\n },\n\n startDrag: (elementId: string) => {\n this.hierarchyState = {\n ...this.hierarchyState,\n draggedElementId: elementId,\n };\n },\n\n updateDropTarget: (\n targetId: string | null,\n position: \"before\" | \"after\" | \"inside\" | null,\n ) => {\n if (targetId === this.hierarchyState.draggedElementId) {\n return;\n }\n this.hierarchyState = {\n ...this.hierarchyState,\n dropTargetId: targetId,\n dropPosition: position,\n };\n },\n\n endDrag: () => {\n this.hierarchyState = {\n ...this.hierarchyState,\n draggedElementId: null,\n dropTargetId: null,\n dropPosition: null,\n };\n },\n\n reorder: (\n sourceId: string,\n targetId: string,\n position: \"before\" | \"after\" | \"inside\",\n ) => {\n this.dispatchEvent(\n new CustomEvent(\"hierarchy-reorder\", {\n detail: { sourceId, targetId, position },\n bubbles: true,\n composed: true,\n }),\n );\n },\n };\n\n @provide({ context: hierarchyContext })\n @state()\n // @ts-ignore\n private providedContext: HierarchyContext = {\n state: this.hierarchyState,\n actions: this.hierarchyActions,\n getCanvasSelectionContext: () => this.getCanvasSelectionContext(),\n getHighlightedElement: () => this.getHighlightedElement(),\n setHighlightedElement: (el) => this.setHighlightedElement(el),\n };\n\n private getTargetElement(): Element | null {\n // First try TargetController (for registered elements like EFCanvas)\n if (this.targetElement) {\n return this.targetElement;\n }\n // Fall back to direct lookup for any element with matching ID\n if (this.target) {\n return document.getElementById(this.target);\n }\n return null;\n }\n\n private getRootElements(): Element[] {\n const target = this.getTargetElement();\n if (!target) {\n return [];\n }\n\n if (isEFTemporal(target)) {\n return [target];\n }\n\n return Array.from(target.children);\n }\n\n private initializeExpandedState(): void {\n const roots = this.getRootElements();\n const newExpanded = new Set<string>();\n\n const addExpandedIds = (element: Element) => {\n if (element.id) {\n newExpanded.add(element.id);\n }\n for (const child of Array.from(element.children)) {\n if (child.children.length > 0) {\n addExpandedIds(child);\n }\n }\n };\n\n for (const root of roots) {\n addExpandedIds(root);\n }\n\n this.hierarchyState = {\n ...this.hierarchyState,\n expandedIds: newExpanded,\n };\n }\n\n select(elementId: string | null): void {\n this.hierarchyActions.select(elementId);\n }\n\n getSelectedElementId(): string | null {\n const selectionCtx = this.getCanvasSelectionContext();\n if (selectionCtx) {\n const selectedIds = Array.from(selectionCtx.selectedIds);\n if (selectedIds.length === 0) {\n return null;\n }\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n return selectedIds[0]!;\n }\n return this.hierarchyState.selectedElementId;\n }\n\n protected willUpdate(changedProperties: PropertyValues): void {\n // Set up TargetController if target changes\n if (changedProperties.has(\"target\") && !this.targetController) {\n this.targetController = new TargetController(this as any);\n }\n\n // Retry setting up selection listener if not yet connected\n this.setupSelectionListener();\n\n // Check for selection changes from canvas (via context or direct access)\n const selectionCtx = this.getCanvasSelectionContext();\n if (selectionCtx) {\n const selectedIds = Array.from(selectionCtx.selectedIds);\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const selectedId: string | null =\n selectedIds.length === 0 ? null : selectedIds[0]!;\n\n if (this.hierarchyState.selectedElementId !== selectedId) {\n this.hierarchyState = {\n ...this.hierarchyState,\n selectedElementId: selectedId,\n };\n }\n }\n\n // Always update provided context to ensure children have fresh getters\n this.providedContext = {\n state: this.hierarchyState,\n actions: this.hierarchyActions,\n getCanvasSelectionContext: () => this.getCanvasSelectionContext(),\n getHighlightedElement: () => this.getHighlightedElement(),\n setHighlightedElement: (el) => this.setHighlightedElement(el),\n };\n\n super.willUpdate(changedProperties);\n }\n\n connectedCallback(): void {\n super.connectedCallback();\n this.initializeExpandedState();\n this.setupSelectionListener();\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback();\n this.removeSelectionListener();\n }\n\n protected updated(changedProperties: PropertyValues): void {\n super.updated(changedProperties);\n\n // Re-initialize when target changes\n if (\n changedProperties.has(\"targetElement\") ||\n changedProperties.has(\"target\")\n ) {\n this.initializeExpandedState();\n this.removeSelectionListener();\n this.selectionChangeHandler = undefined;\n this.setupSelectionListener();\n\n // Auto-select first root timegroup if nothing is selected\n this.autoSelectFirstRootTimegroup();\n }\n }\n\n /**\n * Auto-select the first root timegroup if nothing is currently selected\n */\n private autoSelectFirstRootTimegroup(): void {\n // Only auto-select if nothing is currently selected\n const currentSelection = this.getSelectedElementId();\n if (currentSelection) return;\n\n const roots = this.getRootElements();\n for (const root of roots) {\n // Select the first root that is a timegroup (ef-timegroup)\n if (root.tagName.toLowerCase() === \"ef-timegroup\" && root.id) {\n this.hierarchyActions.select(root.id);\n return;\n }\n }\n\n // Fallback: select first root with an ID\n for (const root of roots) {\n if (root.id) {\n this.hierarchyActions.select(root.id);\n return;\n }\n }\n }\n\n private setupSelectionListener(): void {\n // Don't set up if already set up\n if (this.selectionChangeHandler) {\n return;\n }\n\n const selectionCtx = this.getCanvasSelectionContext();\n if (selectionCtx && \"addEventListener\" in selectionCtx) {\n this.selectionChangeHandler = () => {\n this.requestUpdate(); // Trigger re-render to update hierarchy items\n };\n (selectionCtx as any).addEventListener(\n \"selectionchange\",\n this.selectionChangeHandler,\n );\n }\n }\n\n private removeSelectionListener(): void {\n const selectionCtx = this.getCanvasSelectionContext();\n if (\n selectionCtx &&\n \"removeEventListener\" in selectionCtx &&\n this.selectionChangeHandler\n ) {\n (selectionCtx as any).removeEventListener(\n \"selectionchange\",\n this.selectionChangeHandler,\n );\n this.selectionChangeHandler = undefined;\n }\n }\n\n render() {\n const roots = this.getRootElements();\n\n return html`\n <div class=\"hierarchy-container\" part=\"list\">\n ${this.showHeader ? html`<div class=\"header\" part=\"header\">${this.header}</div>` : nothing}\n ${\n roots.length > 0\n ? renderHierarchyChildren(\n roots,\n this.hideSelectors,\n this.showSelectors,\n true,\n )\n : html`<div class=\"empty\">No elements</div>`\n }\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-hierarchy\": EFHierarchy;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EFHierarchy.js","names":["EFHierarchy","selectedId: string | null"],"sources":["../../../src/gui/hierarchy/EFHierarchy.ts"],"sourcesContent":["import { consume, provide } from \"@lit/context\";\nimport { css, html, LitElement, nothing, type PropertyValues } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\n\nimport { isEFTemporal } from \"../../elements/EFTemporal.js\";\nimport { selectionContext } from \"../../canvas/selection/selectionContext.js\";\nimport { TargetController } from \"../../elements/TargetController.js\";\nimport { TWMixin } from \"../TWMixin.js\";\nimport {\n type HierarchyActions,\n type HierarchyContext,\n type HierarchyState,\n hierarchyContext,\n} from \"./hierarchyContext.js\";\nimport { renderHierarchyChildren } from \"./EFHierarchyItem.js\";\nimport type { EFCanvas } from \"../../canvas/EFCanvas.js\";\n\n@customElement(\"ef-hierarchy\")\nexport class EFHierarchy extends TWMixin(LitElement) {\n static styles = [\n css`\n :host {\n display: block;\n overflow: auto;\n height: 100%;\n font-size: 12px;\n \n /* Component tokens (reference globals from ef-theme.css) */\n --hierarchy-bg: var(--ef-color-bg);\n --hierarchy-border: var(--ef-color-border);\n --hierarchy-text: var(--ef-color-text);\n --hierarchy-hover-bg: var(--ef-color-hover);\n --hierarchy-selected-bg: var(--ef-color-selected);\n --hierarchy-ancestor-selected-bg: var(--ef-color-selected-subtle);\n --hierarchy-focused-bg: var(--ef-color-focused);\n --hierarchy-drop-indicator: var(--ef-color-primary);\n }\n \n .hierarchy-container {\n background: var(--hierarchy-bg);\n color: var(--hierarchy-text);\n padding: 4px 0;\n }\n \n .header {\n padding: 8px 12px;\n font-weight: 600;\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: var(--ef-color-text-muted);\n border-bottom: 1px solid var(--hierarchy-border);\n margin-bottom: 4px;\n }\n \n .empty {\n padding: 16px;\n text-align: center;\n color: var(--ef-color-text-subtle);\n font-style: italic;\n }\n `,\n ];\n\n @property({ type: String })\n target = \"\";\n\n @property({ type: String })\n header = \"\";\n\n @property({ type: Boolean, attribute: \"show-header\" })\n showHeader = false;\n\n @property({ type: Array, attribute: false })\n hideSelectors?: string[];\n\n @property({ type: Array, attribute: false })\n showSelectors?: string[];\n\n @state()\n targetElement: Element | null = null;\n\n private targetController?: TargetController;\n\n @consume({ context: selectionContext, subscribe: true })\n canvasSelectionContext?: import(\"../../canvas/selection/selectionContext.js\").SelectionContext;\n\n /**\n * Get the target canvas element.\n * The canvas is the source of truth for selection and highlight state.\n */\n private getCanvas(): EFCanvas | null {\n // First try TargetController (for registered elements like EFCanvas)\n if (this.targetElement && (this.targetElement as any).selectionContext) {\n return this.targetElement as EFCanvas;\n }\n // Fall back to direct lookup for any element with matching ID\n if (this.target) {\n const target = document.getElementById(this.target);\n if (target && (target as any).selectionContext) {\n return target as EFCanvas;\n }\n }\n return null;\n }\n\n /**\n * Get canvas selection context from the target canvas element.\n * Used when hierarchy is a sibling of canvas (can't access via Lit context).\n */\n private getCanvasSelectionContext():\n | import(\"../../canvas/selection/selectionContext.js\").SelectionContext\n | undefined {\n // First try Lit context (works when hierarchy is inside canvas)\n if (this.canvasSelectionContext) {\n return this.canvasSelectionContext;\n }\n // Use target canvas\n const canvas = this.getCanvas();\n return canvas?.selectionContext;\n }\n\n /**\n * Get the currently highlighted element from the canvas.\n */\n getHighlightedElement(): HTMLElement | null {\n const canvas = this.getCanvas();\n return canvas?.highlightedElement ?? null;\n }\n\n /**\n * Set the highlighted element on the canvas.\n * Called when user hovers an item in the hierarchy.\n */\n setHighlightedElement(element: HTMLElement | null): void {\n const canvas = this.getCanvas();\n canvas?.setHighlightedElement(element);\n }\n\n @state()\n private hierarchyState: HierarchyState = {\n selectedElementId: null,\n expandedIds: new Set(),\n draggedElementId: null,\n dropTargetId: null,\n dropPosition: null,\n };\n\n private selectionChangeHandler?: (event: CustomEvent) => void;\n\n private hierarchyActions: HierarchyActions = {\n select: (elementId: string | null) => {\n const selectionCtx = this.getCanvasSelectionContext();\n if (selectionCtx) {\n if (elementId) {\n // Ensure element is registered with canvas before selecting\n const element = document.getElementById(elementId);\n if (element) {\n // Find the canvas element\n const canvas = element.closest(\"ef-canvas\") as any;\n if (canvas && canvas.tryRegisterElement) {\n // Try to register if not already registered\n canvas.tryRegisterElement(element);\n }\n }\n\n // Select the element directly by its ID\n selectionCtx.select(elementId);\n } else {\n selectionCtx.clear();\n }\n } else {\n this.hierarchyState = {\n ...this.hierarchyState,\n selectedElementId: elementId,\n };\n }\n this.dispatchEvent(\n new CustomEvent(\"hierarchy-select\", {\n detail: { elementId },\n bubbles: true,\n composed: true,\n }),\n );\n },\n\n toggleExpanded: (elementId: string) => {\n const newExpanded = new Set(this.hierarchyState.expandedIds);\n if (newExpanded.has(elementId)) {\n newExpanded.delete(elementId);\n } else {\n newExpanded.add(elementId);\n }\n this.hierarchyState = {\n ...this.hierarchyState,\n expandedIds: newExpanded,\n };\n },\n\n setExpanded: (elementId: string, expanded: boolean) => {\n const newExpanded = new Set(this.hierarchyState.expandedIds);\n if (expanded) {\n newExpanded.add(elementId);\n } else {\n newExpanded.delete(elementId);\n }\n this.hierarchyState = {\n ...this.hierarchyState,\n expandedIds: newExpanded,\n };\n },\n\n startDrag: (elementId: string) => {\n this.hierarchyState = {\n ...this.hierarchyState,\n draggedElementId: elementId,\n };\n },\n\n updateDropTarget: (targetId: string | null, position: \"before\" | \"after\" | \"inside\" | null) => {\n if (targetId === this.hierarchyState.draggedElementId) {\n return;\n }\n this.hierarchyState = {\n ...this.hierarchyState,\n dropTargetId: targetId,\n dropPosition: position,\n };\n },\n\n endDrag: () => {\n this.hierarchyState = {\n ...this.hierarchyState,\n draggedElementId: null,\n dropTargetId: null,\n dropPosition: null,\n };\n },\n\n reorder: (sourceId: string, targetId: string, position: \"before\" | \"after\" | \"inside\") => {\n this.dispatchEvent(\n new CustomEvent(\"hierarchy-reorder\", {\n detail: { sourceId, targetId, position },\n bubbles: true,\n composed: true,\n }),\n );\n },\n };\n\n @provide({ context: hierarchyContext })\n @state()\n // @ts-ignore\n private providedContext: HierarchyContext = {\n state: this.hierarchyState,\n actions: this.hierarchyActions,\n getCanvasSelectionContext: () => this.getCanvasSelectionContext(),\n getHighlightedElement: () => this.getHighlightedElement(),\n setHighlightedElement: (el) => this.setHighlightedElement(el),\n };\n\n private getTargetElement(): Element | null {\n // First try TargetController (for registered elements like EFCanvas)\n if (this.targetElement) {\n return this.targetElement;\n }\n // Fall back to direct lookup for any element with matching ID\n if (this.target) {\n return document.getElementById(this.target);\n }\n return null;\n }\n\n private getRootElements(): Element[] {\n const target = this.getTargetElement();\n if (!target) {\n return [];\n }\n\n if (isEFTemporal(target)) {\n return [target];\n }\n\n return Array.from(target.children);\n }\n\n private initializeExpandedState(): void {\n const roots = this.getRootElements();\n const newExpanded = new Set<string>();\n\n const addExpandedIds = (element: Element) => {\n if (element.id) {\n newExpanded.add(element.id);\n }\n for (const child of Array.from(element.children)) {\n if (child.children.length > 0) {\n addExpandedIds(child);\n }\n }\n };\n\n for (const root of roots) {\n addExpandedIds(root);\n }\n\n this.hierarchyState = {\n ...this.hierarchyState,\n expandedIds: newExpanded,\n };\n }\n\n select(elementId: string | null): void {\n this.hierarchyActions.select(elementId);\n }\n\n getSelectedElementId(): string | null {\n const selectionCtx = this.getCanvasSelectionContext();\n if (selectionCtx) {\n const selectedIds = Array.from(selectionCtx.selectedIds);\n if (selectedIds.length === 0) {\n return null;\n }\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n return selectedIds[0]!;\n }\n return this.hierarchyState.selectedElementId;\n }\n\n protected willUpdate(changedProperties: PropertyValues): void {\n // Set up TargetController if target changes\n if (changedProperties.has(\"target\") && !this.targetController) {\n this.targetController = new TargetController(this as any);\n }\n\n // Retry setting up selection listener if not yet connected\n this.setupSelectionListener();\n\n // Check for selection changes from canvas (via context or direct access)\n const selectionCtx = this.getCanvasSelectionContext();\n if (selectionCtx) {\n const selectedIds = Array.from(selectionCtx.selectedIds);\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const selectedId: string | null = selectedIds.length === 0 ? null : selectedIds[0]!;\n\n if (this.hierarchyState.selectedElementId !== selectedId) {\n this.hierarchyState = {\n ...this.hierarchyState,\n selectedElementId: selectedId,\n };\n }\n }\n\n // Always update provided context to ensure children have fresh getters\n this.providedContext = {\n state: this.hierarchyState,\n actions: this.hierarchyActions,\n getCanvasSelectionContext: () => this.getCanvasSelectionContext(),\n getHighlightedElement: () => this.getHighlightedElement(),\n setHighlightedElement: (el) => this.setHighlightedElement(el),\n };\n\n super.willUpdate(changedProperties);\n }\n\n connectedCallback(): void {\n super.connectedCallback();\n this.initializeExpandedState();\n this.setupSelectionListener();\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback();\n this.removeSelectionListener();\n }\n\n protected updated(changedProperties: PropertyValues): void {\n super.updated(changedProperties);\n\n // Re-initialize when target changes\n if (changedProperties.has(\"targetElement\") || changedProperties.has(\"target\")) {\n this.initializeExpandedState();\n this.removeSelectionListener();\n this.selectionChangeHandler = undefined;\n this.setupSelectionListener();\n\n // Auto-select first root timegroup if nothing is selected\n this.autoSelectFirstRootTimegroup();\n }\n }\n\n /**\n * Auto-select the first root timegroup if nothing is currently selected\n */\n private autoSelectFirstRootTimegroup(): void {\n // Only auto-select if nothing is currently selected\n const currentSelection = this.getSelectedElementId();\n if (currentSelection) return;\n\n const roots = this.getRootElements();\n for (const root of roots) {\n // Select the first root that is a timegroup (ef-timegroup)\n if (root.tagName.toLowerCase() === \"ef-timegroup\" && root.id) {\n this.hierarchyActions.select(root.id);\n return;\n }\n }\n\n // Fallback: select first root with an ID\n for (const root of roots) {\n if (root.id) {\n this.hierarchyActions.select(root.id);\n return;\n }\n }\n }\n\n private setupSelectionListener(): void {\n // Don't set up if already set up\n if (this.selectionChangeHandler) {\n return;\n }\n\n const selectionCtx = this.getCanvasSelectionContext();\n if (selectionCtx && \"addEventListener\" in selectionCtx) {\n this.selectionChangeHandler = () => {\n this.requestUpdate(); // Trigger re-render to update hierarchy items\n };\n (selectionCtx as any).addEventListener(\"selectionchange\", this.selectionChangeHandler);\n }\n }\n\n private removeSelectionListener(): void {\n const selectionCtx = this.getCanvasSelectionContext();\n if (selectionCtx && \"removeEventListener\" in selectionCtx && this.selectionChangeHandler) {\n (selectionCtx as any).removeEventListener(\"selectionchange\", this.selectionChangeHandler);\n this.selectionChangeHandler = undefined;\n }\n }\n\n render() {\n const roots = this.getRootElements();\n\n return html`\n <div class=\"hierarchy-container\" part=\"list\">\n ${this.showHeader ? html`<div class=\"header\" part=\"header\">${this.header}</div>` : nothing}\n ${\n roots.length > 0\n ? renderHierarchyChildren(roots, this.hideSelectors, this.showSelectors, true)\n : html`<div class=\"empty\">No elements</div>`\n }\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-hierarchy\": EFHierarchy;\n }\n}\n"],"mappings":"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"}
|
|
@@ -4,13 +4,13 @@ import { EFVideo } from "../../elements/EFVideo.js";
|
|
|
4
4
|
import { EFTimegroup } from "../../elements/EFTimegroup.js";
|
|
5
5
|
import { EFImage } from "../../elements/EFImage.js";
|
|
6
6
|
import { HierarchyContext } from "./hierarchyContext.js";
|
|
7
|
-
import * as
|
|
7
|
+
import * as lit12 from "lit";
|
|
8
8
|
import { LitElement, PropertyValues, TemplateResult, nothing } from "lit";
|
|
9
9
|
|
|
10
10
|
//#region src/gui/hierarchy/EFHierarchyItem.d.ts
|
|
11
11
|
declare const EFHierarchyItem_base: typeof LitElement;
|
|
12
12
|
declare class EFHierarchyItem<ElementType extends HTMLElement = HTMLElement> extends EFHierarchyItem_base {
|
|
13
|
-
static styles:
|
|
13
|
+
static styles: lit12.CSSResult[];
|
|
14
14
|
hierarchyContext?: HierarchyContext;
|
|
15
15
|
canvasSelectionContext?: SelectionContext;
|
|
16
16
|
element: ElementType;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EFHierarchyItem.js","names":["EFHierarchyItem","position: \"before\" | \"after\" | \"inside\"","EFTimegroupHierarchyItem","EFAudioHierarchyItem","EFVideoHierarchyItem","EFCaptionsHierarchyItem","EFCaptionsActiveWordHierarchyItem","EFTextHierarchyItem","EFTextSegmentHierarchyItem","EFWaveformHierarchyItem","EFImageHierarchyItem","EFHTMLHierarchyItem"],"sources":["../../../src/gui/hierarchy/EFHierarchyItem.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport {\n css,\n html,\n LitElement,\n nothing,\n type PropertyValues,\n type TemplateResult,\n} from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { styleMap } from \"lit/directives/style-map.js\";\n\nimport { EFAudio } from \"../../elements/EFAudio.js\";\nimport { EFCaptions, EFCaptionsActiveWord } from \"../../elements/EFCaptions.js\";\nimport { EFImage } from \"../../elements/EFImage.js\";\nimport { EFText } from \"../../elements/EFText.js\";\nimport { EFTextSegment } from \"../../elements/EFTextSegment.js\";\nimport { EFTimegroup } from \"../../elements/EFTimegroup.js\";\nimport { EFVideo } from \"../../elements/EFVideo.js\";\nimport { EFWaveform } from \"../../elements/EFWaveform.js\";\nimport { selectionContext } from \"../../canvas/selection/selectionContext.js\";\n\nimport { TWMixin } from \"../TWMixin.js\";\nimport { phosphorIcon, ICONS } from \"../icons.js\";\nimport { type HierarchyContext, hierarchyContext } from \"./hierarchyContext.js\";\nimport { getElementTypeColor } from \"../theme.js\";\n\nconst DEFAULT_HIDDEN_TAGS = new Set([\n \"SPAN\",\n \"STYLE\",\n \"SCRIPT\",\n \"LINK\",\n \"META\",\n \"SLOT\",\n \"TEMPLATE\",\n \"EF-WORKBENCH\",\n \"EF-FILMSTRIP\",\n \"EF-CONTROLS\",\n \"EF-SCRUBBER\",\n \"EF-TIMELINE-RULER\",\n \"EF-TRIM-HANDLES\",\n \"EF-TEXT-SEGMENT\",\n]);\n\nexport const shouldRenderElement = (\n element: Element,\n hideSelectors?: string[],\n showSelectors?: string[],\n): boolean => {\n if (element instanceof HTMLElement && element.dataset?.efHidden) {\n return false;\n }\n\n // Skip default hidden tags (but allow them if explicitly shown)\n if (DEFAULT_HIDDEN_TAGS.has(element.tagName)) {\n // Still check show selectors - if explicitly shown, allow it\n if (showSelectors && showSelectors.length > 0) {\n return showSelectors.some((selector) => {\n try {\n return element.matches(selector);\n } catch {\n return false;\n }\n });\n }\n return false;\n }\n\n if (showSelectors && showSelectors.length > 0) {\n return showSelectors.some((selector) => {\n try {\n return element.matches(selector);\n } catch {\n return false;\n }\n });\n }\n\n if (hideSelectors && hideSelectors.length > 0) {\n return !hideSelectors.some((selector) => {\n try {\n return element.matches(selector);\n } catch {\n return false;\n }\n });\n }\n\n return true;\n};\n\nexport function renderHierarchyChildren(\n children: Element[],\n hideSelectors?: string[],\n showSelectors?: string[],\n skipRootFiltering = false,\n temporalOnly = false,\n): Array<TemplateResult<1> | typeof nothing> {\n return children.flatMap((child) => {\n if (\n !skipRootFiltering &&\n !shouldRenderElement(child, hideSelectors, showSelectors)\n ) {\n return nothing;\n }\n\n if (child instanceof EFTimegroup) {\n return html`<ef-timegroup-hierarchy-item\n .element=${child}\n .hideSelectors=${hideSelectors}\n .showSelectors=${showSelectors}\n .temporalOnly=${temporalOnly}\n ></ef-timegroup-hierarchy-item>`;\n }\n if (child instanceof EFImage) {\n return html`<ef-image-hierarchy-item\n .element=${child}\n .hideSelectors=${hideSelectors}\n .showSelectors=${showSelectors}\n .temporalOnly=${temporalOnly}\n ></ef-image-hierarchy-item>`;\n }\n if (child instanceof EFAudio) {\n return html`<ef-audio-hierarchy-item\n .element=${child}\n .hideSelectors=${hideSelectors}\n .showSelectors=${showSelectors}\n .temporalOnly=${temporalOnly}\n ></ef-audio-hierarchy-item>`;\n }\n if (child instanceof EFVideo) {\n return html`<ef-video-hierarchy-item\n .element=${child}\n .hideSelectors=${hideSelectors}\n .showSelectors=${showSelectors}\n .temporalOnly=${temporalOnly}\n ></ef-video-hierarchy-item>`;\n }\n if (child instanceof EFCaptions) {\n return html`<ef-captions-hierarchy-item\n .element=${child}\n .hideSelectors=${hideSelectors}\n .showSelectors=${showSelectors}\n .temporalOnly=${temporalOnly}\n ></ef-captions-hierarchy-item>`;\n }\n if (child instanceof EFCaptionsActiveWord) {\n return html`<ef-captions-active-word-hierarchy-item\n .element=${child}\n .hideSelectors=${hideSelectors}\n .showSelectors=${showSelectors}\n .temporalOnly=${temporalOnly}\n ></ef-captions-active-word-hierarchy-item>`;\n }\n if (child instanceof EFText) {\n return html`<ef-text-hierarchy-item\n .element=${child}\n .hideSelectors=${hideSelectors}\n .showSelectors=${showSelectors}\n .temporalOnly=${temporalOnly}\n ></ef-text-hierarchy-item>`;\n }\n // Skip text segments - they're shown within the parent text element\n if (child instanceof EFTextSegment) {\n return nothing;\n }\n if (child instanceof EFWaveform) {\n return html`<ef-waveform-hierarchy-item\n .element=${child}\n .hideSelectors=${hideSelectors}\n .showSelectors=${showSelectors}\n .temporalOnly=${temporalOnly}\n ></ef-waveform-hierarchy-item>`;\n }\n\n // Skip non-temporal HTML elements when temporalOnly is true\n if (temporalOnly) {\n return nothing;\n }\n\n // Handle all other HTML elements (plain DOM nodes, custom elements, etc.)\n if (child instanceof HTMLElement) {\n return html`<ef-html-hierarchy-item\n .element=${child}\n .hideSelectors=${hideSelectors}\n .showSelectors=${showSelectors}\n ></ef-html-hierarchy-item>`;\n }\n\n // Skip non-HTML elements\n return nothing;\n });\n}\n\n@customElement(\"ef-hierarchy-item\")\nexport class EFHierarchyItem<\n ElementType extends HTMLElement = HTMLElement,\n> extends TWMixin(LitElement) {\n static styles = [\n css`\n :host {\n display: block;\n }\n .item-row {\n display: flex;\n align-items: center;\n height: var(--hierarchy-item-height, 1.5rem);\n padding-left: var(--hierarchy-item-padding-left, 0.5rem);\n padding-right: var(--hierarchy-item-padding-right, 0.5rem);\n padding-top: var(--hierarchy-item-padding-top, 0);\n padding-bottom: var(--hierarchy-item-padding-bottom, 0);\n font-size: var(--hierarchy-item-font-size, 0.75rem);\n font-family: system-ui, -apple-system, sans-serif;\n cursor: pointer;\n user-select: none;\n border-left: 3px solid transparent;\n transition: background-color 0.1s ease;\n }\n .item-row:hover {\n background: var(--ef-color-hover);\n }\n .item-row[data-selected] {\n background: var(--ef-color-selected);\n border-left-color: var(--ef-color-primary);\n }\n .item-row[data-ancestor-selected] {\n background: var(--ef-color-selected-subtle);\n }\n .item-row[data-focused] {\n background: var(--ef-color-focused);\n border-left-color: var(--ef-color-primary);\n }\n .item-row[data-dragging] {\n opacity: 0.5;\n }\n .expand-icon {\n width: var(--hierarchy-expand-icon-size, 1rem);\n height: var(--hierarchy-expand-icon-size, 1rem);\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n flex-shrink: 0;\n }\n .expand-icon svg {\n width: 0.75rem;\n height: 0.75rem;\n transition: transform 0.15s ease;\n }\n .expand-icon[data-expanded] svg {\n transform: rotate(90deg);\n }\n .icon {\n margin-right: var(--hierarchy-icon-gap, 0.25rem);\n flex-shrink: 0;\n }\n .label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n flex: 1;\n }\n .children {\n padding-left: var(--hierarchy-indent, 0.75rem);\n }\n .children[data-collapsed] {\n display: none;\n }\n .drop-indicator {\n height: 2px;\n background: var(--hierarchy-drop-indicator, #3b82f6);\n margin-left: var(--hierarchy-indent, 0.75rem);\n }\n .drop-inside {\n outline: 2px solid var(--hierarchy-drop-indicator, #3b82f6);\n outline-offset: -2px;\n }\n `,\n ];\n\n @consume({ context: hierarchyContext, subscribe: true })\n hierarchyContext?: HierarchyContext;\n\n @consume({ context: selectionContext, subscribe: true })\n canvasSelectionContext?: import(\"../../canvas/selection/selectionContext.js\").SelectionContext;\n\n @property({ type: Object, attribute: false })\n element: ElementType = new EFTimegroup() as unknown as ElementType;\n\n @property({ type: Array, attribute: false })\n hideSelectors?: string[];\n\n @property({ type: Array, attribute: false })\n showSelectors?: string[];\n\n @property({ type: Boolean, attribute: false })\n temporalOnly = false;\n\n @state()\n private localExpanded = true;\n\n private selectionChangeHandler?: (event: CustomEvent) => void;\n\n get elementId(): string {\n return this.element?.id || \"\";\n }\n\n get icon(): TemplateResult<1> | string {\n return phosphorIcon(ICONS.code);\n }\n\n get typeColor(): string {\n return getElementTypeColor(\"timegroup\", this);\n }\n\n get isFocused(): boolean {\n const highlightedElement = this.hierarchyContext?.getHighlightedElement?.();\n return this.element && highlightedElement === this.element;\n }\n\n get isSelected(): boolean {\n // Try to get selection context from hierarchy parent (which can access canvas)\n const selectionCtx =\n this.canvasSelectionContext ||\n this.hierarchyContext?.getCanvasSelectionContext?.();\n\n if (selectionCtx && this.elementId) {\n // Check if this element's ID is in the selected IDs\n return selectionCtx.selectedIds.has(this.elementId);\n }\n // Fall back to hierarchy's own selection state\n if (!this.hierarchyContext) return false;\n return this.hierarchyContext.state.selectedElementId === this.elementId;\n }\n\n get isAncestorSelected(): boolean {\n // Check if this element contains any selected element\n const selectionCtx =\n this.canvasSelectionContext ||\n this.hierarchyContext?.getCanvasSelectionContext?.();\n\n if (selectionCtx && this.element) {\n for (const selectedId of selectionCtx.selectedIds) {\n const selectedElement = document.getElementById(selectedId);\n if (\n selectedElement &&\n this.element.contains(selectedElement) &&\n selectedElement !== this.element\n ) {\n return true;\n }\n }\n }\n return false;\n }\n\n get isExpanded(): boolean {\n if (!this.hierarchyContext || !this.elementId) return this.localExpanded;\n return this.hierarchyContext.state.expandedIds.has(this.elementId);\n }\n\n get isDragging(): boolean {\n if (!this.hierarchyContext) return false;\n return this.hierarchyContext.state.draggedElementId === this.elementId;\n }\n\n get isDropTarget(): boolean {\n if (!this.hierarchyContext) return false;\n return this.hierarchyContext.state.dropTargetId === this.elementId;\n }\n\n get dropPosition(): \"before\" | \"after\" | \"inside\" | null {\n if (!this.isDropTarget || !this.hierarchyContext) return null;\n return this.hierarchyContext.state.dropPosition;\n }\n\n get hasChildren(): boolean {\n return this.element.children.length > 0;\n }\n\n displayLabel(): TemplateResult<1> | string | typeof nothing {\n return this.elementId || \"(unnamed)\";\n }\n\n private handleClick(e: Event): void {\n e.stopPropagation();\n if (this.hierarchyContext && this.elementId) {\n this.hierarchyContext.actions.select(this.elementId);\n }\n // Also set highlight on click for visual feedback\n this.hierarchyContext?.setHighlightedElement?.(this.element);\n }\n\n private handleExpandClick(e: Event): void {\n e.stopPropagation();\n if (this.hierarchyContext && this.elementId) {\n this.hierarchyContext.actions.toggleExpanded(this.elementId);\n } else {\n this.localExpanded = !this.localExpanded;\n }\n }\n\n private handleDragStart(e: DragEvent): void {\n if (!this.hierarchyContext || !this.elementId) return;\n e.dataTransfer?.setData(\"text/plain\", this.elementId);\n this.hierarchyContext.actions.startDrag(this.elementId);\n }\n\n private handleDragEnd(): void {\n if (this.hierarchyContext) {\n this.hierarchyContext.actions.endDrag();\n }\n }\n\n private handleDragOver(e: DragEvent): void {\n e.preventDefault();\n if (!this.hierarchyContext || !this.elementId) return;\n\n const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();\n const y = e.clientY - rect.top;\n const height = rect.height;\n\n let position: \"before\" | \"after\" | \"inside\";\n if (y < height * 0.25) {\n position = \"before\";\n } else if (y > height * 0.75) {\n position = \"after\";\n } else {\n position = \"inside\";\n }\n\n this.hierarchyContext.actions.updateDropTarget(this.elementId, position);\n }\n\n private handleDragLeave(): void {\n if (this.hierarchyContext && this.isDropTarget) {\n this.hierarchyContext.actions.updateDropTarget(null, null);\n }\n }\n\n private handleDrop(e: DragEvent): void {\n e.preventDefault();\n if (!this.hierarchyContext || !this.elementId) return;\n\n const sourceId = e.dataTransfer?.getData(\"text/plain\");\n if (sourceId && this.dropPosition) {\n this.hierarchyContext.actions.reorder(\n sourceId,\n this.elementId,\n this.dropPosition,\n );\n }\n this.hierarchyContext.actions.endDrag();\n }\n\n private handleMouseEnter(): void {\n // Update canvas highlight (source of truth)\n this.hierarchyContext?.setHighlightedElement?.(this.element);\n }\n\n private handleMouseLeave(): void {\n // Clear canvas highlight (source of truth)\n const currentHighlight = this.hierarchyContext?.getHighlightedElement?.();\n if (currentHighlight === this.element) {\n this.hierarchyContext?.setHighlightedElement?.(null);\n }\n }\n\n connectedCallback(): void {\n super.connectedCallback();\n this.setupSelectionListener();\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback();\n this.removeSelectionListener();\n }\n\n protected willUpdate(changedProperties: PropertyValues): void {\n // Set up listener if context becomes available or context changed\n if (\n !this.selectionChangeHandler ||\n changedProperties.has(\"hierarchyContext\")\n ) {\n // Remove old listener if context changed\n if (\n changedProperties.has(\"hierarchyContext\") &&\n this.selectionChangeHandler\n ) {\n this.removeSelectionListener();\n this.selectionChangeHandler = undefined;\n }\n this.setupSelectionListener();\n }\n }\n\n private setupSelectionListener(): void {\n // Don't set up if already set up\n if (this.selectionChangeHandler) {\n return;\n }\n\n const selectionCtx =\n this.canvasSelectionContext ||\n this.hierarchyContext?.getCanvasSelectionContext?.();\n if (selectionCtx && \"addEventListener\" in selectionCtx) {\n this.selectionChangeHandler = () => {\n this.requestUpdate(); // Trigger re-render to update selected state\n };\n (selectionCtx as any).addEventListener(\n \"selectionchange\",\n this.selectionChangeHandler,\n );\n }\n }\n\n private removeSelectionListener(): void {\n const selectionCtx =\n this.canvasSelectionContext ||\n this.hierarchyContext?.getCanvasSelectionContext?.();\n if (\n selectionCtx &&\n \"removeEventListener\" in selectionCtx &&\n this.selectionChangeHandler\n ) {\n (selectionCtx as any).removeEventListener(\n \"selectionchange\",\n this.selectionChangeHandler,\n );\n this.selectionChangeHandler = undefined;\n }\n }\n\n render() {\n const expanded = this.isExpanded;\n\n return html`\n ${this.dropPosition === \"before\" ? html`<div class=\"drop-indicator\"></div>` : nothing}\n <div\n class=\"item-row ${this.dropPosition === \"inside\" ? \"drop-inside\" : \"\"}\"\n part=\"row\"\n style=${styleMap({ borderLeftColor: this.typeColor })}\n ?data-focused=${this.isFocused}\n ?data-selected=${this.isSelected}\n ?data-ancestor-selected=${this.isAncestorSelected}\n ?data-dragging=${this.isDragging}\n draggable=\"true\"\n @click=${this.handleClick}\n @dragstart=${this.handleDragStart}\n @dragend=${this.handleDragEnd}\n @dragover=${this.handleDragOver}\n @dragleave=${this.handleDragLeave}\n @drop=${this.handleDrop}\n @mouseenter=${this.handleMouseEnter}\n @mouseleave=${this.handleMouseLeave}\n >\n ${\n this.hasChildren\n ? html`\n <span\n class=\"expand-icon\"\n ?data-expanded=${expanded}\n @click=${this.handleExpandClick}\n >\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M8 5v14l11-7z\" />\n </svg>\n </span>\n `\n : html`<span class=\"expand-icon\"></span>`\n }\n <span class=\"icon\" part=\"icon\">${this.icon}</span>\n <span class=\"label\" part=\"label\">${this.displayLabel()}</span>\n </div>\n ${\n this.hasChildren\n ? html`\n <div class=\"children\" ?data-collapsed=${!expanded}>\n ${this.renderChildren()}\n </div>\n `\n : nothing\n }\n ${this.dropPosition === \"after\" ? html`<div class=\"drop-indicator\"></div>` : nothing}\n `;\n }\n\n renderChildren(): Array<TemplateResult<1> | typeof nothing> | typeof nothing {\n return renderHierarchyChildren(\n Array.from(this.element.children),\n this.hideSelectors,\n this.showSelectors,\n false,\n this.temporalOnly,\n );\n }\n}\n\n/**\n * Generate a friendly label for an element based on its type and siblings\n */\nfunction getFriendlyLabel(element: HTMLElement, typeLabel: string): string {\n // If element has a meaningful ID (not auto-generated), use it\n const id = element.id || \"\";\n if (id && !id.includes(\"-\") && !id.match(/^\\d+$/)) {\n return id;\n }\n\n // Count siblings of same type to generate \"Video 1\", \"Video 2\", etc.\n const parent = element.parentElement;\n if (parent) {\n const tagName = element.tagName;\n const siblings = Array.from(parent.children).filter(\n (child) => child.tagName === tagName,\n );\n const index = siblings.indexOf(element) + 1;\n\n // If there's only one of this type, don't add number\n if (siblings.length === 1) {\n return typeLabel;\n }\n return `${typeLabel} ${index}`;\n }\n\n return typeLabel;\n}\n\n@customElement(\"ef-timegroup-hierarchy-item\")\nexport class EFTimegroupHierarchyItem extends EFHierarchyItem<EFTimegroup> {\n get icon() {\n return phosphorIcon(ICONS.filmSlate);\n }\n\n get typeColor(): string {\n return getElementTypeColor(\"timegroup\", this);\n }\n\n displayLabel(): string | TemplateResult<1> | typeof nothing {\n const label = getFriendlyLabel(this.element, \"Composition\");\n const mode = this.element.mode || \"fixed\";\n const modeLabels: Record<string, string> = {\n fixed: \"Fixed\",\n sequence: \"Sequence\",\n contain: \"Container\",\n fit: \"Fit\",\n };\n const modeLabel = modeLabels[mode] || mode;\n return html`${label} <span style=\"opacity: 0.5; font-size: 0.65rem;\">${modeLabel}</span>`;\n }\n}\n\n@customElement(\"ef-audio-hierarchy-item\")\nexport class EFAudioHierarchyItem extends EFHierarchyItem<EFAudio> {\n get icon() {\n return phosphorIcon(ICONS.speakerHigh);\n }\n\n get typeColor(): string {\n return \"rgb(34, 197, 94)\"; // Green for audio\n }\n\n displayLabel() {\n return getFriendlyLabel(this.element, \"Audio\");\n }\n}\n\n@customElement(\"ef-video-hierarchy-item\")\nexport class EFVideoHierarchyItem extends EFHierarchyItem<EFVideo> {\n get icon() {\n return phosphorIcon(ICONS.filmStrip);\n }\n\n get typeColor(): string {\n return \"rgb(59, 130, 246)\"; // Blue for video\n }\n\n displayLabel() {\n return getFriendlyLabel(this.element, \"Video\");\n }\n}\n\n@customElement(\"ef-captions-hierarchy-item\")\nexport class EFCaptionsHierarchyItem extends EFHierarchyItem {\n get icon() {\n return phosphorIcon(ICONS.subtitles);\n }\n\n get typeColor(): string {\n return \"rgb(34, 197, 94)\"; // Green\n }\n\n displayLabel() {\n return getFriendlyLabel(this.element as HTMLElement, \"Captions\");\n }\n}\n\n@customElement(\"ef-captions-active-word-hierarchy-item\")\nexport class EFCaptionsActiveWordHierarchyItem extends EFHierarchyItem {\n get icon() {\n return phosphorIcon(ICONS.microphone);\n }\n\n get typeColor(): string {\n return \"rgb(34, 197, 94)\"; // Green\n }\n\n displayLabel() {\n return \"Active Word\";\n }\n}\n\n@customElement(\"ef-text-hierarchy-item\")\nexport class EFTextHierarchyItem extends EFHierarchyItem {\n get icon() {\n return phosphorIcon(ICONS.textT);\n }\n\n get typeColor(): string {\n return \"rgb(249, 115, 22)\"; // Orange for text\n }\n\n get hasChildren(): boolean {\n return false; // Text segments are internal, not shown as children\n }\n\n displayLabel() {\n return getFriendlyLabel(this.element as HTMLElement, \"Text\");\n }\n\n renderChildren(): typeof nothing {\n return nothing;\n }\n}\n\n@customElement(\"ef-text-segment-hierarchy-item\")\nexport class EFTextSegmentHierarchyItem extends EFHierarchyItem {\n get icon() {\n return phosphorIcon(ICONS.textT);\n }\n\n get typeColor(): string {\n return \"rgb(249, 115, 22)\"; // Orange\n }\n\n displayLabel() {\n return \"Segment\";\n }\n}\n\n@customElement(\"ef-waveform-hierarchy-item\")\nexport class EFWaveformHierarchyItem extends EFHierarchyItem {\n get icon() {\n return phosphorIcon(ICONS.waveform);\n }\n\n get typeColor(): string {\n return \"rgb(34, 197, 94)\"; // Green\n }\n\n renderChildren(): typeof nothing {\n return nothing;\n }\n}\n\n@customElement(\"ef-image-hierarchy-item\")\nexport class EFImageHierarchyItem extends EFHierarchyItem<EFImage> {\n get icon() {\n return phosphorIcon(ICONS.image);\n }\n\n get typeColor(): string {\n return \"rgb(168, 85, 247)\"; // Purple for images\n }\n\n displayLabel() {\n return getFriendlyLabel(this.element, \"Image\");\n }\n}\n\n@customElement(\"ef-html-hierarchy-item\")\nexport class EFHTMLHierarchyItem extends EFHierarchyItem {\n get icon() {\n return html`<code>${`<${this.element.tagName.toLowerCase()}>`}</code>`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-hierarchy-item\": EFHierarchyItem;\n \"ef-timegroup-hierarchy-item\": EFTimegroupHierarchyItem;\n \"ef-audio-hierarchy-item\": EFAudioHierarchyItem;\n \"ef-video-hierarchy-item\": EFVideoHierarchyItem;\n \"ef-captions-hierarchy-item\": EFCaptionsHierarchyItem;\n \"ef-captions-active-word-hierarchy-item\": EFCaptionsActiveWordHierarchyItem;\n \"ef-text-hierarchy-item\": EFTextHierarchyItem;\n \"ef-text-segment-hierarchy-item\": EFTextSegmentHierarchyItem;\n \"ef-waveform-hierarchy-item\": EFWaveformHierarchyItem;\n \"ef-image-hierarchy-item\": EFImageHierarchyItem;\n \"ef-html-hierarchy-item\": EFHTMLHierarchyItem;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EFHierarchyItem.js","names":["EFHierarchyItem","position: \"before\" | \"after\" | \"inside\"","EFTimegroupHierarchyItem","EFAudioHierarchyItem","EFVideoHierarchyItem","EFCaptionsHierarchyItem","EFCaptionsActiveWordHierarchyItem","EFTextHierarchyItem","EFTextSegmentHierarchyItem","EFWaveformHierarchyItem","EFImageHierarchyItem","EFHTMLHierarchyItem"],"sources":["../../../src/gui/hierarchy/EFHierarchyItem.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { css, html, LitElement, nothing, type PropertyValues, type TemplateResult } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { styleMap } from \"lit/directives/style-map.js\";\n\nimport { EFAudio } from \"../../elements/EFAudio.js\";\nimport { EFCaptions, EFCaptionsActiveWord } from \"../../elements/EFCaptions.js\";\nimport { EFImage } from \"../../elements/EFImage.js\";\nimport { EFText } from \"../../elements/EFText.js\";\nimport { EFTextSegment } from \"../../elements/EFTextSegment.js\";\nimport { EFTimegroup } from \"../../elements/EFTimegroup.js\";\nimport { EFVideo } from \"../../elements/EFVideo.js\";\nimport { EFWaveform } from \"../../elements/EFWaveform.js\";\nimport { selectionContext } from \"../../canvas/selection/selectionContext.js\";\n\nimport { TWMixin } from \"../TWMixin.js\";\nimport { phosphorIcon, ICONS } from \"../icons.js\";\nimport { type HierarchyContext, hierarchyContext } from \"./hierarchyContext.js\";\nimport { getElementTypeColor } from \"../theme.js\";\n\nconst DEFAULT_HIDDEN_TAGS = new Set([\n \"SPAN\",\n \"STYLE\",\n \"SCRIPT\",\n \"LINK\",\n \"META\",\n \"SLOT\",\n \"TEMPLATE\",\n \"EF-WORKBENCH\",\n \"EF-FILMSTRIP\",\n \"EF-CONTROLS\",\n \"EF-SCRUBBER\",\n \"EF-TIMELINE-RULER\",\n \"EF-TRIM-HANDLES\",\n \"EF-TEXT-SEGMENT\",\n]);\n\nexport const shouldRenderElement = (\n element: Element,\n hideSelectors?: string[],\n showSelectors?: string[],\n): boolean => {\n if (element instanceof HTMLElement && element.dataset?.efHidden) {\n return false;\n }\n\n // Skip default hidden tags (but allow them if explicitly shown)\n if (DEFAULT_HIDDEN_TAGS.has(element.tagName)) {\n // Still check show selectors - if explicitly shown, allow it\n if (showSelectors && showSelectors.length > 0) {\n return showSelectors.some((selector) => {\n try {\n return element.matches(selector);\n } catch {\n return false;\n }\n });\n }\n return false;\n }\n\n if (showSelectors && showSelectors.length > 0) {\n return showSelectors.some((selector) => {\n try {\n return element.matches(selector);\n } catch {\n return false;\n }\n });\n }\n\n if (hideSelectors && hideSelectors.length > 0) {\n return !hideSelectors.some((selector) => {\n try {\n return element.matches(selector);\n } catch {\n return false;\n }\n });\n }\n\n return true;\n};\n\nexport function renderHierarchyChildren(\n children: Element[],\n hideSelectors?: string[],\n showSelectors?: string[],\n skipRootFiltering = false,\n temporalOnly = false,\n): Array<TemplateResult<1> | typeof nothing> {\n return children.flatMap((child) => {\n if (!skipRootFiltering && !shouldRenderElement(child, hideSelectors, showSelectors)) {\n return nothing;\n }\n\n if (child instanceof EFTimegroup) {\n return html`<ef-timegroup-hierarchy-item\n .element=${child}\n .hideSelectors=${hideSelectors}\n .showSelectors=${showSelectors}\n .temporalOnly=${temporalOnly}\n ></ef-timegroup-hierarchy-item>`;\n }\n if (child instanceof EFImage) {\n return html`<ef-image-hierarchy-item\n .element=${child}\n .hideSelectors=${hideSelectors}\n .showSelectors=${showSelectors}\n .temporalOnly=${temporalOnly}\n ></ef-image-hierarchy-item>`;\n }\n if (child instanceof EFAudio) {\n return html`<ef-audio-hierarchy-item\n .element=${child}\n .hideSelectors=${hideSelectors}\n .showSelectors=${showSelectors}\n .temporalOnly=${temporalOnly}\n ></ef-audio-hierarchy-item>`;\n }\n if (child instanceof EFVideo) {\n return html`<ef-video-hierarchy-item\n .element=${child}\n .hideSelectors=${hideSelectors}\n .showSelectors=${showSelectors}\n .temporalOnly=${temporalOnly}\n ></ef-video-hierarchy-item>`;\n }\n if (child instanceof EFCaptions) {\n return html`<ef-captions-hierarchy-item\n .element=${child}\n .hideSelectors=${hideSelectors}\n .showSelectors=${showSelectors}\n .temporalOnly=${temporalOnly}\n ></ef-captions-hierarchy-item>`;\n }\n if (child instanceof EFCaptionsActiveWord) {\n return html`<ef-captions-active-word-hierarchy-item\n .element=${child}\n .hideSelectors=${hideSelectors}\n .showSelectors=${showSelectors}\n .temporalOnly=${temporalOnly}\n ></ef-captions-active-word-hierarchy-item>`;\n }\n if (child instanceof EFText) {\n return html`<ef-text-hierarchy-item\n .element=${child}\n .hideSelectors=${hideSelectors}\n .showSelectors=${showSelectors}\n .temporalOnly=${temporalOnly}\n ></ef-text-hierarchy-item>`;\n }\n // Skip text segments - they're shown within the parent text element\n if (child instanceof EFTextSegment) {\n return nothing;\n }\n if (child instanceof EFWaveform) {\n return html`<ef-waveform-hierarchy-item\n .element=${child}\n .hideSelectors=${hideSelectors}\n .showSelectors=${showSelectors}\n .temporalOnly=${temporalOnly}\n ></ef-waveform-hierarchy-item>`;\n }\n\n // Skip non-temporal HTML elements when temporalOnly is true\n if (temporalOnly) {\n return nothing;\n }\n\n // Handle all other HTML elements (plain DOM nodes, custom elements, etc.)\n if (child instanceof HTMLElement) {\n return html`<ef-html-hierarchy-item\n .element=${child}\n .hideSelectors=${hideSelectors}\n .showSelectors=${showSelectors}\n ></ef-html-hierarchy-item>`;\n }\n\n // Skip non-HTML elements\n return nothing;\n });\n}\n\n@customElement(\"ef-hierarchy-item\")\nexport class EFHierarchyItem<ElementType extends HTMLElement = HTMLElement> extends TWMixin(\n LitElement,\n) {\n static styles = [\n css`\n :host {\n display: block;\n }\n .item-row {\n display: flex;\n align-items: center;\n height: var(--hierarchy-item-height, 1.5rem);\n padding-left: var(--hierarchy-item-padding-left, 0.5rem);\n padding-right: var(--hierarchy-item-padding-right, 0.5rem);\n padding-top: var(--hierarchy-item-padding-top, 0);\n padding-bottom: var(--hierarchy-item-padding-bottom, 0);\n font-size: var(--hierarchy-item-font-size, 0.75rem);\n font-family: system-ui, -apple-system, sans-serif;\n cursor: pointer;\n user-select: none;\n border-left: 3px solid transparent;\n transition: background-color 0.1s ease;\n }\n .item-row:hover {\n background: var(--ef-color-hover);\n }\n .item-row[data-selected] {\n background: var(--ef-color-selected);\n border-left-color: var(--ef-color-primary);\n }\n .item-row[data-ancestor-selected] {\n background: var(--ef-color-selected-subtle);\n }\n .item-row[data-focused] {\n background: var(--ef-color-focused);\n border-left-color: var(--ef-color-primary);\n }\n .item-row[data-dragging] {\n opacity: 0.5;\n }\n .expand-icon {\n width: var(--hierarchy-expand-icon-size, 1rem);\n height: var(--hierarchy-expand-icon-size, 1rem);\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n flex-shrink: 0;\n }\n .expand-icon svg {\n width: 0.75rem;\n height: 0.75rem;\n transition: transform 0.15s ease;\n }\n .expand-icon[data-expanded] svg {\n transform: rotate(90deg);\n }\n .icon {\n margin-right: var(--hierarchy-icon-gap, 0.25rem);\n flex-shrink: 0;\n }\n .label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n flex: 1;\n }\n .children {\n padding-left: var(--hierarchy-indent, 0.75rem);\n }\n .children[data-collapsed] {\n display: none;\n }\n .drop-indicator {\n height: 2px;\n background: var(--hierarchy-drop-indicator, #3b82f6);\n margin-left: var(--hierarchy-indent, 0.75rem);\n }\n .drop-inside {\n outline: 2px solid var(--hierarchy-drop-indicator, #3b82f6);\n outline-offset: -2px;\n }\n `,\n ];\n\n @consume({ context: hierarchyContext, subscribe: true })\n hierarchyContext?: HierarchyContext;\n\n @consume({ context: selectionContext, subscribe: true })\n canvasSelectionContext?: import(\"../../canvas/selection/selectionContext.js\").SelectionContext;\n\n @property({ type: Object, attribute: false })\n element: ElementType = new EFTimegroup() as unknown as ElementType;\n\n @property({ type: Array, attribute: false })\n hideSelectors?: string[];\n\n @property({ type: Array, attribute: false })\n showSelectors?: string[];\n\n @property({ type: Boolean, attribute: false })\n temporalOnly = false;\n\n @state()\n private localExpanded = true;\n\n private selectionChangeHandler?: (event: CustomEvent) => void;\n\n get elementId(): string {\n return this.element?.id || \"\";\n }\n\n get icon(): TemplateResult<1> | string {\n return phosphorIcon(ICONS.code);\n }\n\n get typeColor(): string {\n return getElementTypeColor(\"timegroup\", this);\n }\n\n get isFocused(): boolean {\n const highlightedElement = this.hierarchyContext?.getHighlightedElement?.();\n return this.element && highlightedElement === this.element;\n }\n\n get isSelected(): boolean {\n // Try to get selection context from hierarchy parent (which can access canvas)\n const selectionCtx =\n this.canvasSelectionContext || this.hierarchyContext?.getCanvasSelectionContext?.();\n\n if (selectionCtx && this.elementId) {\n // Check if this element's ID is in the selected IDs\n return selectionCtx.selectedIds.has(this.elementId);\n }\n // Fall back to hierarchy's own selection state\n if (!this.hierarchyContext) return false;\n return this.hierarchyContext.state.selectedElementId === this.elementId;\n }\n\n get isAncestorSelected(): boolean {\n // Check if this element contains any selected element\n const selectionCtx =\n this.canvasSelectionContext || this.hierarchyContext?.getCanvasSelectionContext?.();\n\n if (selectionCtx && this.element) {\n for (const selectedId of selectionCtx.selectedIds) {\n const selectedElement = document.getElementById(selectedId);\n if (\n selectedElement &&\n this.element.contains(selectedElement) &&\n selectedElement !== this.element\n ) {\n return true;\n }\n }\n }\n return false;\n }\n\n get isExpanded(): boolean {\n if (!this.hierarchyContext || !this.elementId) return this.localExpanded;\n return this.hierarchyContext.state.expandedIds.has(this.elementId);\n }\n\n get isDragging(): boolean {\n if (!this.hierarchyContext) return false;\n return this.hierarchyContext.state.draggedElementId === this.elementId;\n }\n\n get isDropTarget(): boolean {\n if (!this.hierarchyContext) return false;\n return this.hierarchyContext.state.dropTargetId === this.elementId;\n }\n\n get dropPosition(): \"before\" | \"after\" | \"inside\" | null {\n if (!this.isDropTarget || !this.hierarchyContext) return null;\n return this.hierarchyContext.state.dropPosition;\n }\n\n get hasChildren(): boolean {\n return this.element.children.length > 0;\n }\n\n displayLabel(): TemplateResult<1> | string | typeof nothing {\n return this.elementId || \"(unnamed)\";\n }\n\n private handleClick(e: Event): void {\n e.stopPropagation();\n if (this.hierarchyContext && this.elementId) {\n this.hierarchyContext.actions.select(this.elementId);\n }\n // Also set highlight on click for visual feedback\n this.hierarchyContext?.setHighlightedElement?.(this.element);\n }\n\n private handleExpandClick(e: Event): void {\n e.stopPropagation();\n if (this.hierarchyContext && this.elementId) {\n this.hierarchyContext.actions.toggleExpanded(this.elementId);\n } else {\n this.localExpanded = !this.localExpanded;\n }\n }\n\n private handleDragStart(e: DragEvent): void {\n if (!this.hierarchyContext || !this.elementId) return;\n e.dataTransfer?.setData(\"text/plain\", this.elementId);\n this.hierarchyContext.actions.startDrag(this.elementId);\n }\n\n private handleDragEnd(): void {\n if (this.hierarchyContext) {\n this.hierarchyContext.actions.endDrag();\n }\n }\n\n private handleDragOver(e: DragEvent): void {\n e.preventDefault();\n if (!this.hierarchyContext || !this.elementId) return;\n\n const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();\n const y = e.clientY - rect.top;\n const height = rect.height;\n\n let position: \"before\" | \"after\" | \"inside\";\n if (y < height * 0.25) {\n position = \"before\";\n } else if (y > height * 0.75) {\n position = \"after\";\n } else {\n position = \"inside\";\n }\n\n this.hierarchyContext.actions.updateDropTarget(this.elementId, position);\n }\n\n private handleDragLeave(): void {\n if (this.hierarchyContext && this.isDropTarget) {\n this.hierarchyContext.actions.updateDropTarget(null, null);\n }\n }\n\n private handleDrop(e: DragEvent): void {\n e.preventDefault();\n if (!this.hierarchyContext || !this.elementId) return;\n\n const sourceId = e.dataTransfer?.getData(\"text/plain\");\n if (sourceId && this.dropPosition) {\n this.hierarchyContext.actions.reorder(sourceId, this.elementId, this.dropPosition);\n }\n this.hierarchyContext.actions.endDrag();\n }\n\n private handleMouseEnter(): void {\n // Update canvas highlight (source of truth)\n this.hierarchyContext?.setHighlightedElement?.(this.element);\n }\n\n private handleMouseLeave(): void {\n // Clear canvas highlight (source of truth)\n const currentHighlight = this.hierarchyContext?.getHighlightedElement?.();\n if (currentHighlight === this.element) {\n this.hierarchyContext?.setHighlightedElement?.(null);\n }\n }\n\n connectedCallback(): void {\n super.connectedCallback();\n this.setupSelectionListener();\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback();\n this.removeSelectionListener();\n }\n\n protected willUpdate(changedProperties: PropertyValues): void {\n // Set up listener if context becomes available or context changed\n if (!this.selectionChangeHandler || changedProperties.has(\"hierarchyContext\")) {\n // Remove old listener if context changed\n if (changedProperties.has(\"hierarchyContext\") && this.selectionChangeHandler) {\n this.removeSelectionListener();\n this.selectionChangeHandler = undefined;\n }\n this.setupSelectionListener();\n }\n }\n\n private setupSelectionListener(): void {\n // Don't set up if already set up\n if (this.selectionChangeHandler) {\n return;\n }\n\n const selectionCtx =\n this.canvasSelectionContext || this.hierarchyContext?.getCanvasSelectionContext?.();\n if (selectionCtx && \"addEventListener\" in selectionCtx) {\n this.selectionChangeHandler = () => {\n this.requestUpdate(); // Trigger re-render to update selected state\n };\n (selectionCtx as any).addEventListener(\"selectionchange\", this.selectionChangeHandler);\n }\n }\n\n private removeSelectionListener(): void {\n const selectionCtx =\n this.canvasSelectionContext || this.hierarchyContext?.getCanvasSelectionContext?.();\n if (selectionCtx && \"removeEventListener\" in selectionCtx && this.selectionChangeHandler) {\n (selectionCtx as any).removeEventListener(\"selectionchange\", this.selectionChangeHandler);\n this.selectionChangeHandler = undefined;\n }\n }\n\n render() {\n const expanded = this.isExpanded;\n\n return html`\n ${this.dropPosition === \"before\" ? html`<div class=\"drop-indicator\"></div>` : nothing}\n <div\n class=\"item-row ${this.dropPosition === \"inside\" ? \"drop-inside\" : \"\"}\"\n part=\"row\"\n style=${styleMap({ borderLeftColor: this.typeColor })}\n ?data-focused=${this.isFocused}\n ?data-selected=${this.isSelected}\n ?data-ancestor-selected=${this.isAncestorSelected}\n ?data-dragging=${this.isDragging}\n draggable=\"true\"\n @click=${this.handleClick}\n @dragstart=${this.handleDragStart}\n @dragend=${this.handleDragEnd}\n @dragover=${this.handleDragOver}\n @dragleave=${this.handleDragLeave}\n @drop=${this.handleDrop}\n @mouseenter=${this.handleMouseEnter}\n @mouseleave=${this.handleMouseLeave}\n >\n ${\n this.hasChildren\n ? html`\n <span\n class=\"expand-icon\"\n ?data-expanded=${expanded}\n @click=${this.handleExpandClick}\n >\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M8 5v14l11-7z\" />\n </svg>\n </span>\n `\n : html`<span class=\"expand-icon\"></span>`\n }\n <span class=\"icon\" part=\"icon\">${this.icon}</span>\n <span class=\"label\" part=\"label\">${this.displayLabel()}</span>\n </div>\n ${\n this.hasChildren\n ? html`\n <div class=\"children\" ?data-collapsed=${!expanded}>\n ${this.renderChildren()}\n </div>\n `\n : nothing\n }\n ${this.dropPosition === \"after\" ? html`<div class=\"drop-indicator\"></div>` : nothing}\n `;\n }\n\n renderChildren(): Array<TemplateResult<1> | typeof nothing> | typeof nothing {\n return renderHierarchyChildren(\n Array.from(this.element.children),\n this.hideSelectors,\n this.showSelectors,\n false,\n this.temporalOnly,\n );\n }\n}\n\n/**\n * Generate a friendly label for an element based on its type and siblings\n */\nfunction getFriendlyLabel(element: HTMLElement, typeLabel: string): string {\n // If element has a meaningful ID (not auto-generated), use it\n const id = element.id || \"\";\n if (id && !id.includes(\"-\") && !id.match(/^\\d+$/)) {\n return id;\n }\n\n // Count siblings of same type to generate \"Video 1\", \"Video 2\", etc.\n const parent = element.parentElement;\n if (parent) {\n const tagName = element.tagName;\n const siblings = Array.from(parent.children).filter((child) => child.tagName === tagName);\n const index = siblings.indexOf(element) + 1;\n\n // If there's only one of this type, don't add number\n if (siblings.length === 1) {\n return typeLabel;\n }\n return `${typeLabel} ${index}`;\n }\n\n return typeLabel;\n}\n\n@customElement(\"ef-timegroup-hierarchy-item\")\nexport class EFTimegroupHierarchyItem extends EFHierarchyItem<EFTimegroup> {\n get icon() {\n return phosphorIcon(ICONS.filmSlate);\n }\n\n get typeColor(): string {\n return getElementTypeColor(\"timegroup\", this);\n }\n\n displayLabel(): string | TemplateResult<1> | typeof nothing {\n const label = getFriendlyLabel(this.element, \"Composition\");\n const mode = this.element.mode || \"fixed\";\n const modeLabels: Record<string, string> = {\n fixed: \"Fixed\",\n sequence: \"Sequence\",\n contain: \"Container\",\n fit: \"Fit\",\n };\n const modeLabel = modeLabels[mode] || mode;\n return html`${label} <span style=\"opacity: 0.5; font-size: 0.65rem;\">${modeLabel}</span>`;\n }\n}\n\n@customElement(\"ef-audio-hierarchy-item\")\nexport class EFAudioHierarchyItem extends EFHierarchyItem<EFAudio> {\n get icon() {\n return phosphorIcon(ICONS.speakerHigh);\n }\n\n get typeColor(): string {\n return \"rgb(34, 197, 94)\"; // Green for audio\n }\n\n displayLabel() {\n return getFriendlyLabel(this.element, \"Audio\");\n }\n}\n\n@customElement(\"ef-video-hierarchy-item\")\nexport class EFVideoHierarchyItem extends EFHierarchyItem<EFVideo> {\n get icon() {\n return phosphorIcon(ICONS.filmStrip);\n }\n\n get typeColor(): string {\n return \"rgb(59, 130, 246)\"; // Blue for video\n }\n\n displayLabel() {\n return getFriendlyLabel(this.element, \"Video\");\n }\n}\n\n@customElement(\"ef-captions-hierarchy-item\")\nexport class EFCaptionsHierarchyItem extends EFHierarchyItem {\n get icon() {\n return phosphorIcon(ICONS.subtitles);\n }\n\n get typeColor(): string {\n return \"rgb(34, 197, 94)\"; // Green\n }\n\n displayLabel() {\n return getFriendlyLabel(this.element as HTMLElement, \"Captions\");\n }\n}\n\n@customElement(\"ef-captions-active-word-hierarchy-item\")\nexport class EFCaptionsActiveWordHierarchyItem extends EFHierarchyItem {\n get icon() {\n return phosphorIcon(ICONS.microphone);\n }\n\n get typeColor(): string {\n return \"rgb(34, 197, 94)\"; // Green\n }\n\n displayLabel() {\n return \"Active Word\";\n }\n}\n\n@customElement(\"ef-text-hierarchy-item\")\nexport class EFTextHierarchyItem extends EFHierarchyItem {\n get icon() {\n return phosphorIcon(ICONS.textT);\n }\n\n get typeColor(): string {\n return \"rgb(249, 115, 22)\"; // Orange for text\n }\n\n get hasChildren(): boolean {\n return false; // Text segments are internal, not shown as children\n }\n\n displayLabel() {\n return getFriendlyLabel(this.element as HTMLElement, \"Text\");\n }\n\n renderChildren(): typeof nothing {\n return nothing;\n }\n}\n\n@customElement(\"ef-text-segment-hierarchy-item\")\nexport class EFTextSegmentHierarchyItem extends EFHierarchyItem {\n get icon() {\n return phosphorIcon(ICONS.textT);\n }\n\n get typeColor(): string {\n return \"rgb(249, 115, 22)\"; // Orange\n }\n\n displayLabel() {\n return \"Segment\";\n }\n}\n\n@customElement(\"ef-waveform-hierarchy-item\")\nexport class EFWaveformHierarchyItem extends EFHierarchyItem {\n get icon() {\n return phosphorIcon(ICONS.waveform);\n }\n\n get typeColor(): string {\n return \"rgb(34, 197, 94)\"; // Green\n }\n\n renderChildren(): typeof nothing {\n return nothing;\n }\n}\n\n@customElement(\"ef-image-hierarchy-item\")\nexport class EFImageHierarchyItem extends EFHierarchyItem<EFImage> {\n get icon() {\n return phosphorIcon(ICONS.image);\n }\n\n get typeColor(): string {\n return \"rgb(168, 85, 247)\"; // Purple for images\n }\n\n displayLabel() {\n return getFriendlyLabel(this.element, \"Image\");\n }\n}\n\n@customElement(\"ef-html-hierarchy-item\")\nexport class EFHTMLHierarchyItem extends EFHierarchyItem {\n get icon() {\n return html`<code>${`<${this.element.tagName.toLowerCase()}>`}</code>`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-hierarchy-item\": EFHierarchyItem;\n \"ef-timegroup-hierarchy-item\": EFTimegroupHierarchyItem;\n \"ef-audio-hierarchy-item\": EFAudioHierarchyItem;\n \"ef-video-hierarchy-item\": EFVideoHierarchyItem;\n \"ef-captions-hierarchy-item\": EFCaptionsHierarchyItem;\n \"ef-captions-active-word-hierarchy-item\": EFCaptionsActiveWordHierarchyItem;\n \"ef-text-hierarchy-item\": EFTextHierarchyItem;\n \"ef-text-segment-hierarchy-item\": EFTextSegmentHierarchyItem;\n \"ef-waveform-hierarchy-item\": EFWaveformHierarchyItem;\n \"ef-image-hierarchy-item\": EFImageHierarchyItem;\n \"ef-html-hierarchy-item\": EFHTMLHierarchyItem;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hierarchyContext.js","names":[],"sources":["../../../src/gui/hierarchy/hierarchyContext.ts"],"sourcesContent":["import { createContext } from \"@lit/context\";\n\nexport interface HierarchyState {\n selectedElementId: string | null;\n expandedIds: Set<string>;\n draggedElementId: string | null;\n dropTargetId: string | null;\n dropPosition: \"before\" | \"after\" | \"inside\" | null;\n}\n\nexport interface HierarchyActions {\n select(elementId: string | null): void;\n toggleExpanded(elementId: string): void;\n setExpanded(elementId: string, expanded: boolean): void;\n startDrag(elementId: string): void;\n updateDropTarget(
|
|
1
|
+
{"version":3,"file":"hierarchyContext.js","names":[],"sources":["../../../src/gui/hierarchy/hierarchyContext.ts"],"sourcesContent":["import { createContext } from \"@lit/context\";\n\nexport interface HierarchyState {\n selectedElementId: string | null;\n expandedIds: Set<string>;\n draggedElementId: string | null;\n dropTargetId: string | null;\n dropPosition: \"before\" | \"after\" | \"inside\" | null;\n}\n\nexport interface HierarchyActions {\n select(elementId: string | null): void;\n toggleExpanded(elementId: string): void;\n setExpanded(elementId: string, expanded: boolean): void;\n startDrag(elementId: string): void;\n updateDropTarget(targetId: string | null, position: \"before\" | \"after\" | \"inside\" | null): void;\n endDrag(): void;\n reorder(sourceId: string, targetId: string, position: \"before\" | \"after\" | \"inside\"): void;\n}\n\nexport interface HierarchyContext {\n state: HierarchyState;\n actions: HierarchyActions;\n getCanvasSelectionContext?: () =>\n | import(\"../../canvas/selection/selectionContext.js\").SelectionContext\n | undefined;\n /**\n * Get the currently highlighted element from the canvas.\n */\n getHighlightedElement?: () => HTMLElement | null;\n /**\n * Set the highlighted element on the canvas.\n */\n setHighlightedElement?: (element: HTMLElement | null) => void;\n}\n\nexport const hierarchyContext = createContext<HierarchyContext>(Symbol(\"hierarchyContext\"));\n"],"mappings":";;;AAoCA,MAAa,mBAAmB,cAAgC,OAAO,mBAAmB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"panZoomTransformContext.js","names":[],"sources":["../../src/gui/panZoomTransformContext.ts"],"sourcesContent":["import { createContext } from \"@lit/context\";\nimport type { PanZoomTransform } from \"../elements/EFPanZoom.js\";\n\n/**\n * Lit context for PanZoom transform.\n * Provided by EFPanZoom component, consumed by overlay components.\n */\nexport const panZoomTransformContext = createContext
|
|
1
|
+
{"version":3,"file":"panZoomTransformContext.js","names":[],"sources":["../../src/gui/panZoomTransformContext.ts"],"sourcesContent":["import { createContext } from \"@lit/context\";\nimport type { PanZoomTransform } from \"../elements/EFPanZoom.js\";\n\n/**\n * Lit context for PanZoom transform.\n * Provided by EFPanZoom component, consumed by overlay components.\n */\nexport const panZoomTransformContext = createContext<PanZoomTransform | undefined>(\n Symbol(\"panZoomTransform\"),\n);\n"],"mappings":";;;;;;;AAOA,MAAa,0BAA0B,cACrC,OAAO,mBAAmB,CAC3B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"previewSettingsContext.js","names":[],"sources":["../../src/gui/previewSettingsContext.ts"],"sourcesContent":["import { createContext } from \"@lit/context\";\nimport type {\n PreviewPresentationMode,\n PreviewResolutionScale,\n RenderMode,\n} from \"../preview/previewSettings.js\";\n\n/**\n * Settings for the preview workbench.\n * Provided by EFWorkbench via context, consumable by any descendant.\n */\nexport interface PreviewSettings {\n presentationMode: PreviewPresentationMode;\n renderMode: RenderMode;\n resolutionScale: PreviewResolutionScale;\n showStats: boolean;\n showThumbnailTimestamps: boolean;\n}\n\n/**\n * Context for propagating preview settings through the component tree.\n * Provided by EFWorkbench, consumable by any descendant.\n */\nexport const previewSettingsContext = createContext<PreviewSettings>(
|
|
1
|
+
{"version":3,"file":"previewSettingsContext.js","names":[],"sources":["../../src/gui/previewSettingsContext.ts"],"sourcesContent":["import { createContext } from \"@lit/context\";\nimport type {\n PreviewPresentationMode,\n PreviewResolutionScale,\n RenderMode,\n} from \"../preview/previewSettings.js\";\n\n/**\n * Settings for the preview workbench.\n * Provided by EFWorkbench via context, consumable by any descendant.\n */\nexport interface PreviewSettings {\n presentationMode: PreviewPresentationMode;\n renderMode: RenderMode;\n resolutionScale: PreviewResolutionScale;\n showStats: boolean;\n showThumbnailTimestamps: boolean;\n}\n\n/**\n * Context for propagating preview settings through the component tree.\n * Provided by EFWorkbench, consumable by any descendant.\n */\nexport const previewSettingsContext = createContext<PreviewSettings>(Symbol(\"preview-settings\"));\n"],"mappings":";;;;;;;AAuBA,MAAa,yBAAyB,cAA+B,OAAO,mBAAmB,CAAC"}
|