@editframe/elements 0.12.0-beta.6 → 0.13.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/elements/EFAudio.d.ts +1 -1
- package/dist/elements/EFCaptions.d.ts +5 -5
- package/dist/elements/EFImage.d.ts +1 -1
- package/dist/elements/EFMedia.browsertest.d.ts +1 -1
- package/dist/elements/EFMedia.d.ts +6 -1
- package/dist/elements/{src/elements/EFMedia.js → EFMedia.js} +3 -3
- package/dist/elements/EFTemporal.browsertest.d.ts +1 -1
- package/dist/elements/EFTemporal.d.ts +145 -2
- package/dist/elements/{src/elements/EFTemporal.js → EFTemporal.js} +3 -0
- package/dist/elements/EFTimegroup.browsertest.d.ts +2 -2
- package/dist/elements/EFTimegroup.d.ts +8 -3
- package/dist/elements/{src/elements/EFTimegroup.js → EFTimegroup.js} +104 -53
- package/dist/elements/EFVideo.d.ts +1 -1
- package/dist/elements/{src/elements/EFVideo.js → EFVideo.js} +10 -2
- package/dist/elements/EFWaveform.d.ts +7 -4
- package/dist/elements/{src/elements/EFWaveform.js → EFWaveform.js} +48 -56
- package/dist/elements/TimegroupController.d.ts +2 -2
- package/dist/elements/util.d.ts +1 -1
- package/dist/gui/ContextMixin.browsertest.d.ts +1 -1
- package/dist/gui/ContextMixin.d.ts +4 -1
- package/dist/{elements/src/gui → gui}/ContextMixin.js +32 -44
- package/dist/gui/EFFilmstrip.d.ts +15 -9
- package/dist/{elements/src/gui → gui}/EFFilmstrip.js +64 -16
- package/dist/gui/EFFocusOverlay.d.ts +17 -0
- package/dist/gui/EFFocusOverlay.js +82 -0
- package/dist/gui/EFPreview.d.ts +3 -1
- package/dist/{elements/src/gui → gui}/EFPreview.js +24 -16
- package/dist/gui/EFScrubber.d.ts +1 -1
- package/dist/gui/EFTimeDisplay.d.ts +1 -1
- package/dist/gui/EFToggleLoop.d.ts +1 -1
- package/dist/gui/EFTogglePlay.d.ts +1 -3
- package/dist/{elements/src/gui → gui}/EFTogglePlay.js +1 -9
- package/dist/gui/EFWorkbench.d.ts +1 -1
- package/dist/{elements/src/gui → gui}/TWMixin.css.js +1 -1
- package/dist/gui/efContext.d.ts +1 -1
- package/dist/index.d.ts +15 -14
- package/dist/{elements/src/index.js → index.js} +2 -0
- package/dist/style.css +769 -19
- package/package.json +5 -4
- package/src/elements/EFAudio.ts +3 -3
- package/src/elements/EFCaptions.browsertest.ts +3 -3
- package/src/elements/EFCaptions.ts +9 -9
- package/src/elements/EFImage.browsertest.ts +2 -2
- package/src/elements/EFImage.ts +4 -4
- package/src/elements/EFMedia.browsertest.ts +6 -6
- package/src/elements/EFMedia.ts +14 -7
- package/src/elements/EFSourceMixin.ts +3 -3
- package/src/elements/EFTemporal.browsertest.ts +1 -1
- package/src/elements/EFTemporal.ts +159 -4
- package/src/elements/EFTimegroup.browsertest.ts +5 -5
- package/src/elements/EFTimegroup.ts +141 -68
- package/src/elements/EFVideo.ts +15 -4
- package/src/elements/EFWaveform.ts +82 -98
- package/src/elements/FetchMixin.ts +2 -2
- package/src/elements/TimegroupController.ts +2 -2
- package/src/elements/durationConverter.ts +1 -1
- package/src/elements/util.ts +1 -1
- package/src/gui/ContextMixin.browsertest.ts +3 -3
- package/src/gui/ContextMixin.ts +45 -52
- package/src/gui/EFFilmstrip.ts +92 -36
- package/src/gui/EFFocusOverlay.ts +79 -0
- package/src/gui/EFPreview.ts +35 -18
- package/src/gui/EFScrubber.ts +3 -3
- package/src/gui/EFTimeDisplay.ts +2 -2
- package/src/gui/EFToggleLoop.ts +4 -4
- package/src/gui/EFTogglePlay.ts +4 -10
- package/src/gui/EFWorkbench.ts +2 -2
- package/src/gui/efContext.ts +1 -1
- package/dist/assets/src/EncodedAsset.js +0 -560
- package/dist/assets/src/MP4File.js +0 -223
- package/dist/assets/src/memoize.js +0 -14
- package/dist/{elements/src/EF_FRAMEGEN.js → EF_FRAMEGEN.js} +0 -0
- package/dist/{elements/src/EF_INTERACTIVE.js → EF_INTERACTIVE.js} +0 -0
- package/dist/{elements/src/EF_RENDERING.js → EF_RENDERING.js} +0 -0
- package/dist/elements/{src/elements/CrossUpdateController.js → CrossUpdateController.js} +0 -0
- package/dist/elements/{src/elements/EFAudio.js → EFAudio.js} +2 -2
- package/dist/elements/{src/elements/EFCaptions.js → EFCaptions.js} +0 -0
- package/dist/elements/{src/elements/EFImage.js → EFImage.js} +0 -0
- package/dist/elements/{src/elements/EFSourceMixin.js → EFSourceMixin.js} +1 -1
- package/dist/elements/{src/elements/FetchMixin.js → FetchMixin.js} +0 -0
- package/dist/elements/{src/elements/TimegroupController.js → TimegroupController.js} +0 -0
- package/dist/elements/{src/elements/durationConverter.js → durationConverter.js} +0 -0
- package/dist/elements/{src/elements/parseTimeToMs.js → parseTimeToMs.js} +0 -0
- package/dist/{elements/src/gui → gui}/EFScrubber.js +0 -0
- package/dist/{elements/src/gui → gui}/EFTimeDisplay.js +0 -0
- package/dist/{elements/src/gui → gui}/EFToggleLoop.js +1 -1
- /package/dist/{elements/src/gui → gui}/EFWorkbench.js +0 -0
- /package/dist/{elements/src/gui → gui}/TWMixin.js +0 -0
- /package/dist/{elements/src/gui → gui}/apiHostContext.js +0 -0
- /package/dist/{elements/src/gui → gui}/efContext.js +0 -0
- /package/dist/{elements/src/gui → gui}/fetchContext.js +0 -0
- /package/dist/{elements/src/gui → gui}/focusContext.js +0 -0
- /package/dist/{elements/src/gui → gui}/focusedElementContext.js +0 -0
- /package/dist/{elements/src/gui → gui}/playingContext.js +0 -0
- /package/dist/{elements/src/msToTimeCode.js → msToTimeCode.js} +0 -0
package/src/gui/ContextMixin.ts
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
import { provide } from "@lit/context";
|
|
1
|
+
import { createContext, provide } from "@lit/context";
|
|
2
2
|
import type { LitElement } from "lit";
|
|
3
3
|
import { property, state } from "lit/decorators.js";
|
|
4
4
|
|
|
5
5
|
import { createRef } from "lit/directives/ref.js";
|
|
6
|
-
import type { EFTimegroup } from "../elements/EFTimegroup.
|
|
7
|
-
import { apiHostContext } from "./apiHostContext.
|
|
8
|
-
import { efContext } from "./efContext.
|
|
9
|
-
import { fetchContext } from "./fetchContext.
|
|
10
|
-
import { type FocusContext, focusContext } from "./focusContext.
|
|
11
|
-
import { focusedElementContext } from "./focusedElementContext.
|
|
12
|
-
import { loopContext, playingContext } from "./playingContext.
|
|
6
|
+
import type { EFTimegroup } from "../elements/EFTimegroup.js";
|
|
7
|
+
import { apiHostContext } from "./apiHostContext.js";
|
|
8
|
+
import { efContext } from "./efContext.js";
|
|
9
|
+
import { fetchContext } from "./fetchContext.js";
|
|
10
|
+
import { type FocusContext, focusContext } from "./focusContext.js";
|
|
11
|
+
import { focusedElementContext } from "./focusedElementContext.js";
|
|
12
|
+
import { loopContext, playingContext } from "./playingContext.js";
|
|
13
|
+
|
|
14
|
+
export const targetTimegroupContext = createContext<EFTimegroup | null>(
|
|
15
|
+
"target-timegroup",
|
|
16
|
+
);
|
|
13
17
|
|
|
14
18
|
export declare class ContextMixinInterface extends LitElement {
|
|
15
19
|
signingURL?: string;
|
|
@@ -55,6 +59,10 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
55
59
|
@provide({ context: efContext })
|
|
56
60
|
efContext = this;
|
|
57
61
|
|
|
62
|
+
@provide({ context: targetTimegroupContext })
|
|
63
|
+
@state()
|
|
64
|
+
targetTimegroup: EFTimegroup | null = null;
|
|
65
|
+
|
|
58
66
|
@provide({ context: fetchContext })
|
|
59
67
|
fetch = async (url: string, init: RequestInit = {}) => {
|
|
60
68
|
init.headers ||= {};
|
|
@@ -106,8 +114,8 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
106
114
|
@property({ type: Boolean, reflect: true })
|
|
107
115
|
loop = false;
|
|
108
116
|
|
|
109
|
-
@state()
|
|
110
|
-
private stageScale = 1;
|
|
117
|
+
// @state()
|
|
118
|
+
// private stageScale = 1;
|
|
111
119
|
|
|
112
120
|
@property({ type: Boolean })
|
|
113
121
|
rendering = false;
|
|
@@ -121,49 +129,32 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
121
129
|
#FPS = 30;
|
|
122
130
|
#MS_PER_FRAME = 1000 / this.#FPS;
|
|
123
131
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
// Determine the appropriate scale factor to make the canvas fit into
|
|
131
|
-
canvasElement.style.width = `${canvasChild.clientWidth}px`;
|
|
132
|
-
canvasElement.style.height = `${canvasChild.clientHeight}px`;
|
|
133
|
-
const stageWidth = stageElement.clientWidth;
|
|
134
|
-
const stageHeight = stageElement.clientHeight;
|
|
135
|
-
const canvasWidth = canvasElement.clientWidth;
|
|
136
|
-
const canvasHeight = canvasElement.clientHeight;
|
|
137
|
-
const stageRatio = stageWidth / stageHeight;
|
|
138
|
-
const canvasRatio = canvasWidth / canvasHeight;
|
|
139
|
-
|
|
140
|
-
if (stageRatio > canvasRatio) {
|
|
141
|
-
const scale = stageHeight / canvasHeight;
|
|
142
|
-
if (this.stageScale !== scale) {
|
|
143
|
-
canvasElement.style.transform = `scale(${scale})`;
|
|
144
|
-
canvasElement.style.transformOrigin = "center left";
|
|
145
|
-
}
|
|
146
|
-
this.stageScale = scale;
|
|
147
|
-
} else {
|
|
148
|
-
const scale = stageWidth / canvasWidth;
|
|
149
|
-
if (this.stageScale !== scale) {
|
|
150
|
-
canvasElement.style.transform = `scale(${scale})`;
|
|
151
|
-
canvasElement.style.transformOrigin = "center left";
|
|
152
|
-
}
|
|
153
|
-
this.stageScale = scale;
|
|
132
|
+
#timegroupObserver = new MutationObserver((mutations) => {
|
|
133
|
+
for (const mutation of mutations) {
|
|
134
|
+
if (mutation.type === "childList") {
|
|
135
|
+
const newTimegroup = this.querySelector("ef-timegroup");
|
|
136
|
+
if (newTimegroup !== this.targetTimegroup) {
|
|
137
|
+
this.targetTimegroup = newTimegroup;
|
|
154
138
|
}
|
|
155
139
|
}
|
|
156
140
|
}
|
|
157
|
-
|
|
158
|
-
requestAnimationFrame(this.setStageScale);
|
|
159
|
-
}
|
|
160
|
-
};
|
|
141
|
+
});
|
|
161
142
|
|
|
162
143
|
connectedCallback(): void {
|
|
163
144
|
super.connectedCallback();
|
|
145
|
+
|
|
146
|
+
// Initialize targetTimegroup
|
|
147
|
+
this.targetTimegroup = this.querySelector("ef-timegroup");
|
|
148
|
+
|
|
149
|
+
this.#timegroupObserver.observe(this, {
|
|
150
|
+
childList: true,
|
|
151
|
+
subtree: true,
|
|
152
|
+
attributes: true,
|
|
153
|
+
});
|
|
154
|
+
|
|
164
155
|
// Preferrably we would use a resizeObserver, but it is difficult to get the first resize
|
|
165
156
|
// timed correctly. So we use requestAnimationFrame as a stop-gap.
|
|
166
|
-
requestAnimationFrame(this.setStageScale);
|
|
157
|
+
// requestAnimationFrame(this.setStageScale);
|
|
167
158
|
if (this.playing) {
|
|
168
159
|
this.startPlayback();
|
|
169
160
|
}
|
|
@@ -171,6 +162,7 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
171
162
|
|
|
172
163
|
disconnectedCallback(): void {
|
|
173
164
|
super.disconnectedCallback();
|
|
165
|
+
this.#timegroupObserver.disconnect();
|
|
174
166
|
this.stopPlayback();
|
|
175
167
|
}
|
|
176
168
|
|
|
@@ -201,10 +193,6 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
201
193
|
super.update(changedProperties);
|
|
202
194
|
}
|
|
203
195
|
|
|
204
|
-
get targetTimegroup() {
|
|
205
|
-
return this.querySelector("ef-timegroup");
|
|
206
|
-
}
|
|
207
|
-
|
|
208
196
|
play() {
|
|
209
197
|
this.playing = true;
|
|
210
198
|
}
|
|
@@ -252,6 +240,14 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
252
240
|
await timegroup.waitForMediaDurations();
|
|
253
241
|
|
|
254
242
|
let currentMs = timegroup.currentTimeMs;
|
|
243
|
+
const fromMs = currentMs;
|
|
244
|
+
const toMs = timegroup.endTimeMs;
|
|
245
|
+
|
|
246
|
+
if (fromMs >= toMs) {
|
|
247
|
+
this.pause();
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
255
251
|
let bufferCount = 0;
|
|
256
252
|
this.#playbackAudioContext = new AudioContext({
|
|
257
253
|
latencyHint: "playback",
|
|
@@ -281,9 +277,6 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
281
277
|
}
|
|
282
278
|
};
|
|
283
279
|
|
|
284
|
-
const fromMs = currentMs;
|
|
285
|
-
const toMs = timegroup.endTimeMs;
|
|
286
|
-
|
|
287
280
|
const queueBufferSource = async () => {
|
|
288
281
|
if (currentMs >= toMs) {
|
|
289
282
|
return false;
|
package/src/gui/EFFilmstrip.ts
CHANGED
|
@@ -17,21 +17,22 @@ import {
|
|
|
17
17
|
import { createRef, ref } from "lit/directives/ref.js";
|
|
18
18
|
import { styleMap } from "lit/directives/style-map.js";
|
|
19
19
|
|
|
20
|
-
import { EFAudio } from "../elements/EFAudio.
|
|
21
|
-
import { EFCaptions, EFCaptionsActiveWord } from "../elements/EFCaptions.
|
|
22
|
-
import { EFImage } from "../elements/EFImage.
|
|
23
|
-
import type { TemporalMixinInterface } from "../elements/EFTemporal.
|
|
24
|
-
import { EFTimegroup } from "../elements/EFTimegroup.
|
|
25
|
-
import { EFVideo } from "../elements/EFVideo.
|
|
26
|
-
import { EFWaveform } from "../elements/EFWaveform.
|
|
27
|
-
import { TimegroupController } from "../elements/TimegroupController.
|
|
28
|
-
import { msToTimeCode } from "../msToTimeCode.
|
|
29
|
-
import
|
|
30
|
-
import type {
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
20
|
+
import { EFAudio } from "../elements/EFAudio.js";
|
|
21
|
+
import { EFCaptions, EFCaptionsActiveWord } from "../elements/EFCaptions.js";
|
|
22
|
+
import { EFImage } from "../elements/EFImage.js";
|
|
23
|
+
import type { TemporalMixinInterface } from "../elements/EFTemporal.js";
|
|
24
|
+
import { EFTimegroup } from "../elements/EFTimegroup.js";
|
|
25
|
+
import { EFVideo } from "../elements/EFVideo.js";
|
|
26
|
+
import { EFWaveform } from "../elements/EFWaveform.js";
|
|
27
|
+
import { TimegroupController } from "../elements/TimegroupController.js";
|
|
28
|
+
import { msToTimeCode } from "../msToTimeCode.js";
|
|
29
|
+
import { targetTimegroupContext } from "./ContextMixin.ts";
|
|
30
|
+
import type { EFPreview } from "./EFPreview.js";
|
|
31
|
+
import type { EFWorkbench } from "./EFWorkbench.js";
|
|
32
|
+
import { TWMixin } from "./TWMixin.js";
|
|
33
|
+
import { type FocusContext, focusContext } from "./focusContext.js";
|
|
34
|
+
import { focusedElementContext } from "./focusedElementContext.js";
|
|
35
|
+
import { loopContext, playingContext } from "./playingContext.js";
|
|
35
36
|
|
|
36
37
|
class ElementFilmstripController implements ReactiveController {
|
|
37
38
|
constructor(
|
|
@@ -336,7 +337,7 @@ class EFHierarchyItem<
|
|
|
336
337
|
</div>`;
|
|
337
338
|
}
|
|
338
339
|
|
|
339
|
-
renderChildren(): Array<TemplateResult<1
|
|
340
|
+
renderChildren(): Array<TemplateResult<1> | typeof nothing> | typeof nothing {
|
|
340
341
|
return renderHierarchyChildren(Array.from(this.element.children));
|
|
341
342
|
}
|
|
342
343
|
}
|
|
@@ -419,8 +420,11 @@ class EFHTMLHierarchyItem extends EFHierarchyItem {
|
|
|
419
420
|
|
|
420
421
|
const renderHierarchyChildren = (
|
|
421
422
|
children: Element[],
|
|
422
|
-
): Array<TemplateResult<1
|
|
423
|
+
): Array<TemplateResult<1> | typeof nothing> => {
|
|
423
424
|
return children.map((child) => {
|
|
425
|
+
if (child instanceof HTMLElement && child.dataset?.efHidden) {
|
|
426
|
+
return nothing;
|
|
427
|
+
}
|
|
424
428
|
if (child instanceof EFTimegroup) {
|
|
425
429
|
return html`<ef-timegroup-hierarchy-item
|
|
426
430
|
.element=${child}
|
|
@@ -467,6 +471,9 @@ const renderFilmstripChildren = (
|
|
|
467
471
|
pixelsPerMs: number,
|
|
468
472
|
): Array<TemplateResult<1> | typeof nothing> => {
|
|
469
473
|
return children.map((child) => {
|
|
474
|
+
if (child instanceof HTMLElement && child.dataset?.efHidden) {
|
|
475
|
+
return nothing;
|
|
476
|
+
}
|
|
470
477
|
if (child instanceof EFTimegroup) {
|
|
471
478
|
return html`<ef-timegroup-filmstrip
|
|
472
479
|
.element=${child}
|
|
@@ -513,6 +520,14 @@ const renderFilmstripChildren = (
|
|
|
513
520
|
|
|
514
521
|
@customElement("ef-filmstrip")
|
|
515
522
|
export class EFFilmstrip extends TWMixin(LitElement) {
|
|
523
|
+
static styles = [
|
|
524
|
+
css`
|
|
525
|
+
:host {
|
|
526
|
+
display: block;
|
|
527
|
+
overflow: hidden;
|
|
528
|
+
}
|
|
529
|
+
`,
|
|
530
|
+
];
|
|
516
531
|
@property({ type: Number })
|
|
517
532
|
pixelsPerMs = 0.04;
|
|
518
533
|
|
|
@@ -535,15 +550,35 @@ export class EFFilmstrip extends TWMixin(LitElement) {
|
|
|
535
550
|
@state()
|
|
536
551
|
currentTimeMs = 0;
|
|
537
552
|
|
|
553
|
+
@property({ type: Boolean, reflect: true, attribute: "auto-scale" })
|
|
554
|
+
autoScale = false;
|
|
555
|
+
|
|
556
|
+
private resizeObserver = new ResizeObserver(() => {
|
|
557
|
+
if (this.autoScale) {
|
|
558
|
+
this.updatePixelsPerMs();
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
|
|
538
562
|
connectedCallback(): void {
|
|
539
563
|
super.connectedCallback();
|
|
540
564
|
this.#bindToTargetTimegroup();
|
|
541
565
|
window.addEventListener("keypress", this.#handleKeyPress);
|
|
566
|
+
|
|
567
|
+
this.resizeObserver.observe(this);
|
|
542
568
|
}
|
|
543
569
|
|
|
544
570
|
disconnectedCallback(): void {
|
|
545
571
|
super.disconnectedCallback();
|
|
546
572
|
window.removeEventListener("keypress", this.#handleKeyPress);
|
|
573
|
+
this.resizeObserver.disconnect();
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
updatePixelsPerMs() {
|
|
577
|
+
const target = this.targetTimegroup;
|
|
578
|
+
const gutter = this.gutterRef.value;
|
|
579
|
+
if (target && gutter && gutter.clientWidth > 0) {
|
|
580
|
+
this.pixelsPerMs = gutter.clientWidth / (target.durationMs || 1);
|
|
581
|
+
}
|
|
547
582
|
}
|
|
548
583
|
|
|
549
584
|
#bindToTargetTimegroup() {
|
|
@@ -645,7 +680,9 @@ export class EFFilmstrip extends TWMixin(LitElement) {
|
|
|
645
680
|
@eventOptions({ passive: false })
|
|
646
681
|
scrollScrub(e: WheelEvent) {
|
|
647
682
|
if (this.targetTimegroup && this.gutter && !this.playing) {
|
|
648
|
-
e.
|
|
683
|
+
if (e.deltaX !== 0) {
|
|
684
|
+
e.preventDefault(); // Prevent default side scroll behavior only
|
|
685
|
+
}
|
|
649
686
|
// Avoid over-scrolling to the left
|
|
650
687
|
if (
|
|
651
688
|
this.gutterRef.value &&
|
|
@@ -694,38 +731,42 @@ export class EFFilmstrip extends TWMixin(LitElement) {
|
|
|
694
731
|
<div
|
|
695
732
|
class="z-20 col-span-2 border-b-slate-600 bg-slate-100 shadow shadow-slate-300"
|
|
696
733
|
>
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
734
|
+
${
|
|
735
|
+
!this.autoScale
|
|
736
|
+
? html`<input
|
|
737
|
+
type="range"
|
|
738
|
+
.value=${this.pixelsPerMs}
|
|
739
|
+
min="0.01"
|
|
740
|
+
max="0.1"
|
|
741
|
+
step="0.001"
|
|
742
|
+
@input=${(e: Event) => {
|
|
743
|
+
const target = e.target as HTMLInputElement;
|
|
744
|
+
this.pixelsPerMs = Number.parseFloat(target.value);
|
|
745
|
+
}}
|
|
746
|
+
/>`
|
|
747
|
+
: nothing
|
|
748
|
+
}
|
|
708
749
|
<code>${msToTimeCode(this.currentTimeMs, true)} </code> /
|
|
709
750
|
<code>${msToTimeCode(target?.durationMs ?? 0, true)}</code>
|
|
710
751
|
<ef-toggle-play class="inline-block mx-2">
|
|
711
752
|
<div slot="pause">
|
|
712
|
-
<button
|
|
753
|
+
<button>⏸️</button>
|
|
713
754
|
</div>
|
|
714
755
|
<div slot="play">
|
|
715
|
-
<button
|
|
756
|
+
<button>▶️</button>
|
|
716
757
|
</div>
|
|
717
758
|
</ef-toggle-play>
|
|
718
|
-
<ef-toggle-loop><button>${this.loop ? "🔁" : html`<span class="opacity-50">🔁</span>`}</button></ef-toggle-loop>
|
|
759
|
+
<ef-toggle-loop><button>${this.loop ? "🔁" : html`<span class="opacity-50 line-through">🔁</span>`}</button></ef-toggle-loop>
|
|
719
760
|
</div>
|
|
720
761
|
<div
|
|
721
|
-
class="z-10 pl-1 pr-1 pt-
|
|
762
|
+
class="z-10 pl-1 pr-1 pt-[8px] shadow shadow-slate-600 overflow-auto"
|
|
722
763
|
${ref(this.hierarchyRef)}
|
|
723
764
|
@scroll=${this.syncHierarchyScroll}
|
|
724
765
|
>
|
|
725
766
|
${renderHierarchyChildren(target ? [target] : [])}
|
|
726
767
|
</div>
|
|
727
768
|
<div
|
|
728
|
-
class="h-full w-full cursor-crosshair overflow-auto bg-slate-200 pt-
|
|
769
|
+
class="flex h-full w-full cursor-crosshair overflow-auto bg-slate-200 pt-[8px]"
|
|
729
770
|
id="gutter"
|
|
730
771
|
${ref(this.gutterRef)}
|
|
731
772
|
@scroll=${this.syncGutterScroll}
|
|
@@ -767,8 +808,23 @@ export class EFFilmstrip extends TWMixin(LitElement) {
|
|
|
767
808
|
return this.closest("ef-workbench, ef-preview") as EFWorkbench | EFPreview;
|
|
768
809
|
}
|
|
769
810
|
|
|
770
|
-
|
|
771
|
-
|
|
811
|
+
@property({ type: String })
|
|
812
|
+
target?: string;
|
|
813
|
+
|
|
814
|
+
@consume({ context: targetTimegroupContext, subscribe: true })
|
|
815
|
+
@state()
|
|
816
|
+
targetTimegroup?: EFTimegroup | null;
|
|
817
|
+
|
|
818
|
+
protected willUpdate(
|
|
819
|
+
changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,
|
|
820
|
+
) {
|
|
821
|
+
if (changedProperties.has("targetTimegroup")) {
|
|
822
|
+
this.#bindToTargetTimegroup();
|
|
823
|
+
}
|
|
824
|
+
if (this.autoScale) {
|
|
825
|
+
this.updatePixelsPerMs();
|
|
826
|
+
}
|
|
827
|
+
super.willUpdate(changedProperties);
|
|
772
828
|
}
|
|
773
829
|
}
|
|
774
830
|
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { consume } from "@lit/context";
|
|
2
|
+
import { LitElement, css, html } from "lit";
|
|
3
|
+
import { customElement } from "lit/decorators.js";
|
|
4
|
+
import { createRef, ref } from "lit/directives/ref.js";
|
|
5
|
+
import { focusedElementContext } from "./focusedElementContext.js";
|
|
6
|
+
|
|
7
|
+
@customElement("ef-focus-overlay")
|
|
8
|
+
export class EFFocusOverlay extends LitElement {
|
|
9
|
+
static styles = css`
|
|
10
|
+
:host {
|
|
11
|
+
display: block;
|
|
12
|
+
position: relative;
|
|
13
|
+
width: 100%;
|
|
14
|
+
height: 100%;
|
|
15
|
+
pointer-events: none;
|
|
16
|
+
}
|
|
17
|
+
.overlay {
|
|
18
|
+
position: fixed;
|
|
19
|
+
outline: 2px solid var(--ef-focus-overlay-color, rgb(59, 130, 246));
|
|
20
|
+
background: var(--ef-focus-overlay-background, rgb(59, 130, 246));
|
|
21
|
+
outline: 2px solid var(--ef-focus-overlay-color, rgb(59, 130, 246));
|
|
22
|
+
mix-blend-mode: multiply;
|
|
23
|
+
opacity: 0.4;
|
|
24
|
+
display: none;
|
|
25
|
+
}
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
@consume({ context: focusedElementContext, subscribe: true })
|
|
29
|
+
focusedElement?: HTMLElement | null;
|
|
30
|
+
|
|
31
|
+
overlay = createRef<HTMLDivElement>();
|
|
32
|
+
|
|
33
|
+
private animationFrame?: number;
|
|
34
|
+
|
|
35
|
+
drawOverlay = () => {
|
|
36
|
+
const overlay = this.overlay.value;
|
|
37
|
+
if (overlay) {
|
|
38
|
+
if (this.focusedElement) {
|
|
39
|
+
overlay.style.display = "block";
|
|
40
|
+
const rect = this.focusedElement.getBoundingClientRect();
|
|
41
|
+
Object.assign(overlay.style, {
|
|
42
|
+
top: `${rect.top}px`,
|
|
43
|
+
left: `${rect.left}px`,
|
|
44
|
+
width: `${rect.width}px`,
|
|
45
|
+
height: `${rect.height}px`,
|
|
46
|
+
});
|
|
47
|
+
this.animationFrame = requestAnimationFrame(this.drawOverlay);
|
|
48
|
+
} else {
|
|
49
|
+
overlay.style.display = "none";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
render() {
|
|
55
|
+
return html`<div ${ref(this.overlay)} class="overlay"></div>`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
connectedCallback(): void {
|
|
59
|
+
super.connectedCallback();
|
|
60
|
+
this.drawOverlay();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
disconnectedCallback(): void {
|
|
64
|
+
super.disconnectedCallback();
|
|
65
|
+
if (this.animationFrame) {
|
|
66
|
+
cancelAnimationFrame(this.animationFrame);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
protected updated(): void {
|
|
71
|
+
this.drawOverlay();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
declare global {
|
|
76
|
+
interface HTMLElementTagNameMap {
|
|
77
|
+
"ef-focus-overlay": EFFocusOverlay;
|
|
78
|
+
}
|
|
79
|
+
}
|
package/src/gui/EFPreview.ts
CHANGED
|
@@ -1,36 +1,53 @@
|
|
|
1
1
|
import { LitElement, css, html } from "lit";
|
|
2
2
|
import { customElement } from "lit/decorators.js";
|
|
3
|
-
import { ref } from "lit/directives/ref.js";
|
|
4
3
|
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { provide } from "@lit/context";
|
|
5
|
+
import { ContextMixin } from "./ContextMixin.js";
|
|
6
|
+
import { TWMixin } from "./TWMixin.js";
|
|
7
|
+
import { focusedElementContext } from "./focusedElementContext.js";
|
|
7
8
|
|
|
8
9
|
@customElement("ef-preview")
|
|
9
10
|
export class EFPreview extends ContextMixin(TWMixin(LitElement)) {
|
|
11
|
+
@provide({ context: focusedElementContext })
|
|
12
|
+
focusedElement?: HTMLElement;
|
|
13
|
+
|
|
14
|
+
constructor() {
|
|
15
|
+
super();
|
|
16
|
+
this.addEventListener("mouseover", (e) => {
|
|
17
|
+
const target = e.target as HTMLElement;
|
|
18
|
+
const timegroup = target.closest("ef-timegroup");
|
|
19
|
+
if (target !== this && timegroup) {
|
|
20
|
+
this.focusedElement = target;
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
this.addEventListener("mouseout", (e) => {
|
|
24
|
+
const relatedTarget = e.relatedTarget as HTMLElement;
|
|
25
|
+
const targetingTimegroup = relatedTarget?.closest("ef-timegroup");
|
|
26
|
+
// Clear focus if:
|
|
27
|
+
// 1. Moving outside the preview entirely, or
|
|
28
|
+
// 2. Moving to the preview itself, or
|
|
29
|
+
// 3. Moving to an element that's not within a timegroup
|
|
30
|
+
if (
|
|
31
|
+
!this.contains(relatedTarget) ||
|
|
32
|
+
relatedTarget === this ||
|
|
33
|
+
!targetingTimegroup
|
|
34
|
+
) {
|
|
35
|
+
this.focusedElement = undefined;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
10
40
|
static styles = [
|
|
11
41
|
css`
|
|
12
42
|
:host {
|
|
13
43
|
display: block;
|
|
14
|
-
|
|
15
|
-
height: 100%;
|
|
44
|
+
cursor: crosshair;
|
|
16
45
|
}
|
|
17
46
|
`,
|
|
18
47
|
];
|
|
19
48
|
|
|
20
49
|
render() {
|
|
21
|
-
return html
|
|
22
|
-
<div
|
|
23
|
-
${ref(this.stageRef)}
|
|
24
|
-
class="relative grid h-full w-full place-content-center place-items-center overflow-hidden"
|
|
25
|
-
>
|
|
26
|
-
<slot
|
|
27
|
-
${ref(this.canvasRef)}
|
|
28
|
-
class="inline-block"
|
|
29
|
-
name="canvas"
|
|
30
|
-
></slot>
|
|
31
|
-
<slot name="controls"></slot>
|
|
32
|
-
</div>
|
|
33
|
-
`;
|
|
50
|
+
return html`<slot></slot>`;
|
|
34
51
|
}
|
|
35
52
|
}
|
|
36
53
|
|
package/src/gui/EFScrubber.ts
CHANGED
|
@@ -3,9 +3,9 @@ import { LitElement, css, html } from "lit";
|
|
|
3
3
|
import { customElement, state } from "lit/decorators.js";
|
|
4
4
|
|
|
5
5
|
import { ref } from "lit/directives/ref.js";
|
|
6
|
-
import type { ContextMixinInterface } from "./ContextMixin.
|
|
7
|
-
import { efContext } from "./efContext.
|
|
8
|
-
import { playingContext } from "./playingContext.
|
|
6
|
+
import type { ContextMixinInterface } from "./ContextMixin.js";
|
|
7
|
+
import { efContext } from "./efContext.js";
|
|
8
|
+
import { playingContext } from "./playingContext.js";
|
|
9
9
|
|
|
10
10
|
@customElement("ef-scrubber")
|
|
11
11
|
export class EFScrubber extends LitElement {
|
package/src/gui/EFTimeDisplay.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { consume } from "@lit/context";
|
|
2
2
|
import { LitElement, css, html } from "lit";
|
|
3
3
|
import { customElement } from "lit/decorators.js";
|
|
4
|
-
import type { ContextMixinInterface } from "./ContextMixin.
|
|
5
|
-
import { efContext } from "./efContext.
|
|
4
|
+
import type { ContextMixinInterface } from "./ContextMixin.js";
|
|
5
|
+
import { efContext } from "./efContext.js";
|
|
6
6
|
|
|
7
7
|
@customElement("ef-time-display")
|
|
8
8
|
export class EFTimeDisplay extends LitElement {
|
package/src/gui/EFToggleLoop.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { css, html, LitElement } from "lit";
|
|
2
|
-
import { customElement } from "lit/decorators.js";
|
|
3
1
|
import { consume } from "@lit/context";
|
|
2
|
+
import { LitElement, css, html } from "lit";
|
|
3
|
+
import { customElement } from "lit/decorators.js";
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
5
|
+
import type { ContextMixinInterface } from "./ContextMixin.js";
|
|
6
|
+
import { efContext } from "./efContext.js";
|
|
7
7
|
|
|
8
8
|
@customElement("ef-toggle-loop")
|
|
9
9
|
export class EFToggleLoop extends LitElement {
|
package/src/gui/EFTogglePlay.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { consume } from "@lit/context";
|
|
2
2
|
import { LitElement, css, html } from "lit";
|
|
3
|
-
import { customElement
|
|
3
|
+
import { customElement } from "lit/decorators.js";
|
|
4
4
|
|
|
5
|
-
import type { ContextMixinInterface } from "./ContextMixin.
|
|
6
|
-
import { efContext } from "./efContext.
|
|
7
|
-
import { playingContext } from "./playingContext.
|
|
5
|
+
import type { ContextMixinInterface } from "./ContextMixin.js";
|
|
6
|
+
import { efContext } from "./efContext.js";
|
|
7
|
+
import { playingContext } from "./playingContext.js";
|
|
8
8
|
|
|
9
9
|
@customElement("ef-toggle-play")
|
|
10
10
|
export class EFTogglePlay extends LitElement {
|
|
@@ -23,12 +23,6 @@ export class EFTogglePlay extends LitElement {
|
|
|
23
23
|
@consume({ context: playingContext, subscribe: true })
|
|
24
24
|
playing = false;
|
|
25
25
|
|
|
26
|
-
@property({ type: String })
|
|
27
|
-
play = '<button class="text-2xl cursor-pointer">▶️</button>';
|
|
28
|
-
|
|
29
|
-
@property({ type: String })
|
|
30
|
-
pause = '<button class="text-2xl cursor-pointer">⏸️</button>';
|
|
31
|
-
|
|
32
26
|
render() {
|
|
33
27
|
return html`
|
|
34
28
|
<div
|
package/src/gui/EFWorkbench.ts
CHANGED
|
@@ -2,8 +2,8 @@ import { LitElement, type PropertyValueMap, css, html } from "lit";
|
|
|
2
2
|
import { customElement, eventOptions } from "lit/decorators.js";
|
|
3
3
|
import { createRef, ref } from "lit/directives/ref.js";
|
|
4
4
|
|
|
5
|
-
import { ContextMixin } from "./ContextMixin.
|
|
6
|
-
import { TWMixin } from "./TWMixin.
|
|
5
|
+
import { ContextMixin } from "./ContextMixin.js";
|
|
6
|
+
import { TWMixin } from "./TWMixin.js";
|
|
7
7
|
|
|
8
8
|
@customElement("ef-workbench")
|
|
9
9
|
export class EFWorkbench extends ContextMixin(TWMixin(LitElement)) {
|
package/src/gui/efContext.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createContext } from "@lit/context";
|
|
2
|
-
import type { ContextMixinInterface } from "./ContextMixin.
|
|
2
|
+
import type { ContextMixinInterface } from "./ContextMixin.js";
|
|
3
3
|
|
|
4
4
|
export const efContext = createContext<ContextMixinInterface | null>(
|
|
5
5
|
Symbol("efContext"),
|