@editframe/elements 0.7.0-beta.9 → 0.8.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/EF_FRAMEGEN.d.ts +43 -0
- package/dist/EF_INTERACTIVE.d.ts +1 -0
- package/dist/assets/dist/EncodedAsset.js +560 -0
- package/dist/assets/dist/MP4File.js +170 -0
- package/dist/assets/dist/memoize.js +14 -0
- package/dist/elements/CrossUpdateController.d.ts +8 -0
- package/dist/elements/EFAudio.d.ts +9 -0
- package/dist/elements/EFCaptions.d.ts +38 -0
- package/dist/elements/EFImage.d.ts +13 -0
- package/dist/elements/EFMedia.d.ts +63 -0
- package/dist/elements/EFSourceMixin.d.ts +11 -0
- package/dist/elements/EFTemporal.d.ts +40 -0
- package/dist/elements/EFTimegroup.browsertest.d.ts +11 -0
- package/dist/elements/EFTimegroup.d.ts +36 -0
- package/dist/elements/EFVideo.d.ts +13 -0
- package/dist/elements/EFWaveform.d.ts +29 -0
- package/dist/elements/FetchMixin.d.ts +7 -0
- package/dist/elements/TimegroupController.d.ts +13 -0
- package/dist/elements/durationConverter.d.ts +12 -0
- package/dist/elements/parseTimeToMs.d.ts +1 -0
- package/{src/EF_FRAMEGEN.ts → dist/elements/src/EF_FRAMEGEN.js} +35 -115
- package/dist/elements/src/EF_INTERACTIVE.js +7 -0
- package/dist/elements/src/elements/CrossUpdateController.js +16 -0
- package/dist/elements/src/elements/EFAudio.js +54 -0
- package/dist/elements/src/elements/EFCaptions.js +169 -0
- package/dist/elements/src/elements/EFImage.js +80 -0
- package/dist/elements/src/elements/EFMedia.js +356 -0
- package/dist/elements/src/elements/EFSourceMixin.js +55 -0
- package/dist/elements/src/elements/EFTemporal.js +283 -0
- package/dist/elements/src/elements/EFTimegroup.js +338 -0
- package/dist/elements/src/elements/EFVideo.js +110 -0
- package/dist/elements/src/elements/EFWaveform.js +226 -0
- package/dist/elements/src/elements/FetchMixin.js +28 -0
- package/dist/elements/src/elements/TimegroupController.js +20 -0
- package/dist/elements/src/elements/durationConverter.js +8 -0
- package/dist/elements/src/elements/parseTimeToMs.js +13 -0
- package/dist/elements/src/elements/util.js +11 -0
- package/dist/elements/src/gui/ContextMixin.js +246 -0
- package/dist/elements/src/gui/EFFilmstrip.js +731 -0
- package/dist/elements/src/gui/EFPreview.js +45 -0
- package/dist/elements/src/gui/EFToggleLoop.js +39 -0
- package/dist/elements/src/gui/EFTogglePlay.js +43 -0
- package/dist/elements/src/gui/EFWorkbench.js +128 -0
- package/dist/elements/src/gui/TWMixin.css.js +4 -0
- package/dist/elements/src/gui/TWMixin.js +36 -0
- package/dist/elements/src/gui/apiHostContext.js +5 -0
- package/dist/elements/src/gui/efContext.js +7 -0
- package/dist/elements/src/gui/fetchContext.js +5 -0
- package/dist/elements/src/gui/focusContext.js +5 -0
- package/dist/elements/src/gui/focusedElementContext.js +7 -0
- package/dist/elements/src/gui/playingContext.js +7 -0
- package/dist/elements/src/index.js +31 -0
- package/dist/elements/src/msToTimeCode.js +15 -0
- package/dist/elements/util.d.ts +3 -0
- package/dist/gui/ContextMixin.d.ts +19 -0
- package/dist/gui/EFFilmstrip.d.ts +148 -0
- package/dist/gui/EFPreview.d.ts +12 -0
- package/dist/gui/EFToggleLoop.d.ts +12 -0
- package/dist/gui/EFTogglePlay.d.ts +12 -0
- package/dist/gui/EFWorkbench.d.ts +18 -0
- package/dist/gui/TWMixin.d.ts +2 -0
- package/dist/gui/apiHostContext.d.ts +3 -0
- package/dist/gui/efContext.d.ts +4 -0
- package/dist/gui/fetchContext.d.ts +3 -0
- package/dist/gui/focusContext.d.ts +6 -0
- package/dist/gui/focusedElementContext.d.ts +3 -0
- package/dist/gui/playingContext.d.ts +6 -0
- package/dist/index.d.ts +12 -0
- package/dist/msToTimeCode.d.ts +1 -0
- package/dist/style.css +802 -0
- package/package.json +7 -10
- package/src/elements/EFAudio.ts +1 -1
- package/src/elements/EFCaptions.ts +23 -17
- package/src/elements/EFImage.ts +3 -3
- package/src/elements/EFMedia.ts +48 -17
- package/src/elements/EFSourceMixin.ts +1 -1
- package/src/elements/EFTemporal.ts +101 -6
- package/src/elements/EFTimegroup.browsertest.ts +3 -3
- package/src/elements/EFTimegroup.ts +30 -47
- package/src/elements/EFVideo.ts +2 -2
- package/src/elements/EFWaveform.ts +9 -9
- package/src/elements/FetchMixin.ts +5 -3
- package/src/elements/TimegroupController.ts +1 -1
- package/src/elements/durationConverter.ts +21 -1
- package/src/elements/parseTimeToMs.ts +1 -0
- package/src/elements/util.ts +1 -1
- package/src/gui/ContextMixin.ts +268 -0
- package/src/gui/EFFilmstrip.ts +61 -171
- package/src/gui/EFPreview.ts +39 -0
- package/src/gui/EFToggleLoop.ts +34 -0
- package/src/gui/EFTogglePlay.ts +38 -0
- package/src/gui/EFWorkbench.ts +11 -109
- package/src/gui/TWMixin.ts +10 -3
- package/src/gui/apiHostContext.ts +3 -0
- package/src/gui/efContext.ts +6 -0
- package/src/gui/fetchContext.ts +5 -0
- package/src/gui/focusContext.ts +7 -0
- package/src/gui/focusedElementContext.ts +5 -0
- package/src/gui/playingContext.ts +5 -0
- package/CHANGELOG.md +0 -7
- package/postcss.config.cjs +0 -12
- package/src/EF_INTERACTIVE.ts +0 -2
- package/src/elements.css +0 -22
- package/src/index.ts +0 -33
- package/tailwind.config.ts +0 -10
- package/tsconfig.json +0 -4
- package/vite.config.ts +0 -8
package/src/gui/EFFilmstrip.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { EFTimegroup } from "../elements/EFTimegroup";
|
|
2
1
|
import {
|
|
3
2
|
LitElement,
|
|
4
3
|
html,
|
|
@@ -14,19 +13,25 @@ import {
|
|
|
14
13
|
eventOptions,
|
|
15
14
|
state,
|
|
16
15
|
} from "lit/decorators.js";
|
|
16
|
+
import { consume } from "@lit/context";
|
|
17
17
|
import { styleMap } from "lit/directives/style-map.js";
|
|
18
18
|
import { ref, createRef } from "lit/directives/ref.js";
|
|
19
|
-
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import { TWMixin } from "./TWMixin";
|
|
29
|
-
import { msToTimeCode } from "
|
|
19
|
+
|
|
20
|
+
import { EFImage } from "../elements/EFImage.ts";
|
|
21
|
+
import { EFAudio } from "../elements/EFAudio.ts";
|
|
22
|
+
import { EFVideo } from "../elements/EFVideo.ts";
|
|
23
|
+
import { EFCaptions, EFCaptionsActiveWord } from "../elements/EFCaptions.ts";
|
|
24
|
+
import { EFWaveform } from "../elements/EFWaveform.ts";
|
|
25
|
+
import { EFTimegroup } from "../elements/EFTimegroup.ts";
|
|
26
|
+
import type { TemporalMixinInterface } from "../elements/EFTemporal.ts";
|
|
27
|
+
import { TimegroupController } from "../elements/TimegroupController.ts";
|
|
28
|
+
import { TWMixin } from "./TWMixin.ts";
|
|
29
|
+
import { msToTimeCode } from "../msToTimeCode.ts";
|
|
30
|
+
import { focusedElementContext } from "./focusedElementContext.ts";
|
|
31
|
+
import { type FocusContext, focusContext } from "./focusContext.ts";
|
|
32
|
+
import { playingContext, loopContext } from "./playingContext.ts";
|
|
33
|
+
import type { EFWorkbench } from "./EFWorkbench.ts";
|
|
34
|
+
import type { EFPreview } from "./EFPreview.ts";
|
|
30
35
|
|
|
31
36
|
class ElementFilmstripController implements ReactiveController {
|
|
32
37
|
constructor(
|
|
@@ -68,31 +73,38 @@ class FilmstripItem extends TWMixin(LitElement) {
|
|
|
68
73
|
@consume({ context: focusContext, subscribe: true })
|
|
69
74
|
focusContext?: FocusContext;
|
|
70
75
|
|
|
71
|
-
@consume({ context:
|
|
76
|
+
@consume({ context: focusedElementContext, subscribe: true })
|
|
72
77
|
focusedElement?: HTMLElement | null;
|
|
73
78
|
|
|
74
79
|
get isFocused() {
|
|
75
80
|
return this.element && this.focusContext?.focusedElement === this.element;
|
|
76
81
|
}
|
|
77
82
|
|
|
78
|
-
@property({ type:
|
|
83
|
+
@property({ type: Object, attribute: false })
|
|
79
84
|
element: TemporalMixinInterface & LitElement = new EFTimegroup();
|
|
80
85
|
|
|
81
86
|
@property({ type: Number })
|
|
82
87
|
pixelsPerMs = 0.04;
|
|
83
88
|
|
|
84
|
-
get
|
|
89
|
+
get gutterStyles() {
|
|
85
90
|
return {
|
|
86
91
|
position: "relative",
|
|
87
|
-
left: `${this.pixelsPerMs * this.element.startTimeWithinParentMs}px`,
|
|
92
|
+
left: `${this.pixelsPerMs * (this.element.startTimeWithinParentMs - this.element.trimStartMs)}px`,
|
|
93
|
+
width: `${this.pixelsPerMs * (this.element.durationMs + this.element.trimStartMs + this.element.trimEndMs)}px`,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
get trimPortionStyles() {
|
|
98
|
+
return {
|
|
88
99
|
width: `${this.pixelsPerMs * this.element.durationMs}px`,
|
|
100
|
+
left: `${this.pixelsPerMs * this.element.trimStartMs}px`,
|
|
89
101
|
};
|
|
90
102
|
}
|
|
91
103
|
|
|
92
104
|
render() {
|
|
93
|
-
return html
|
|
105
|
+
return html`<div style=${styleMap(this.gutterStyles)}>
|
|
94
106
|
<div
|
|
95
|
-
class="
|
|
107
|
+
class="bg-slate-300"
|
|
96
108
|
?data-focused=${this.isFocused}
|
|
97
109
|
@mouseenter=${() => {
|
|
98
110
|
if (this.focusContext) {
|
|
@@ -105,7 +117,13 @@ class FilmstripItem extends TWMixin(LitElement) {
|
|
|
105
117
|
}
|
|
106
118
|
}}
|
|
107
119
|
>
|
|
108
|
-
|
|
120
|
+
<div
|
|
121
|
+
?data-focused=${this.isFocused}
|
|
122
|
+
class="border-outset relative mb-[1px] block h-[1.1rem] text-nowrap border border-slate-500 bg-blue-200 text-sm data-[focused]:bg-slate-400"
|
|
123
|
+
style=${styleMap(this.trimPortionStyles)}
|
|
124
|
+
>
|
|
125
|
+
${this.animations()}
|
|
126
|
+
</div>
|
|
109
127
|
</div>
|
|
110
128
|
${this.renderChildren()}
|
|
111
129
|
</div>`;
|
|
@@ -254,14 +272,14 @@ export class EFHTMLFilmstrip extends FilmstripItem {
|
|
|
254
272
|
class EFHierarchyItem<
|
|
255
273
|
ElementType extends HTMLElement = HTMLElement,
|
|
256
274
|
> extends TWMixin(LitElement) {
|
|
257
|
-
@property({ type:
|
|
275
|
+
@property({ type: Object, attribute: false })
|
|
258
276
|
// @ts-expect-error This could be initialzed with any HTMLElement
|
|
259
277
|
element: ElementType = new EFTimegroup();
|
|
260
278
|
|
|
261
279
|
@consume({ context: focusContext })
|
|
262
280
|
focusContext?: FocusContext;
|
|
263
281
|
|
|
264
|
-
@consume({ context:
|
|
282
|
+
@consume({ context: focusedElementContext, subscribe: true })
|
|
265
283
|
focusedElement?: HTMLElement | null;
|
|
266
284
|
|
|
267
285
|
get icon(): TemplateResult<1> | string {
|
|
@@ -485,23 +503,25 @@ export class EFFilmstrip extends TWMixin(LitElement) {
|
|
|
485
503
|
@property({ type: Number })
|
|
486
504
|
pixelsPerMs = 0.04;
|
|
487
505
|
|
|
488
|
-
@property({ type: Number })
|
|
489
|
-
currentTimeMs = 0;
|
|
490
|
-
|
|
491
|
-
@property({ type: String, attribute: "target", reflect: true })
|
|
492
|
-
targetSelector = "";
|
|
493
|
-
|
|
494
506
|
@state()
|
|
495
507
|
scrubbing = false;
|
|
496
508
|
|
|
497
509
|
@state()
|
|
498
|
-
|
|
510
|
+
timelineScrolltop = 0;
|
|
499
511
|
|
|
512
|
+
@consume({ context: playingContext, subscribe: true })
|
|
500
513
|
@state()
|
|
501
|
-
|
|
514
|
+
playing?: boolean;
|
|
515
|
+
|
|
516
|
+
@consume({ context: loopContext, subscribe: true })
|
|
517
|
+
@state()
|
|
518
|
+
loop?: boolean;
|
|
502
519
|
|
|
503
520
|
timegroupController?: TimegroupController;
|
|
504
521
|
|
|
522
|
+
@state()
|
|
523
|
+
currentTimeMs = 0;
|
|
524
|
+
|
|
505
525
|
connectedCallback(): void {
|
|
506
526
|
super.connectedCallback();
|
|
507
527
|
this.#bindToTargetTimegroup();
|
|
@@ -529,19 +549,22 @@ export class EFFilmstrip extends TWMixin(LitElement) {
|
|
|
529
549
|
#handleKeyPress = (event: KeyboardEvent) => {
|
|
530
550
|
// On spacebar, toggle playback
|
|
531
551
|
if (event.key === " ") {
|
|
552
|
+
const [target] = event.composedPath();
|
|
532
553
|
// CSS selector to match all interactive elements
|
|
533
554
|
const interactiveSelector =
|
|
534
555
|
"input, textarea, button, select, a, [contenteditable]";
|
|
535
556
|
|
|
536
557
|
// Check if the event target or its ancestor matches an interactive element
|
|
537
|
-
const closestInteractive = (
|
|
558
|
+
const closestInteractive = (target as HTMLElement | null)?.closest(
|
|
538
559
|
interactiveSelector,
|
|
539
560
|
);
|
|
540
561
|
if (closestInteractive) {
|
|
541
562
|
return;
|
|
542
563
|
}
|
|
543
564
|
event.preventDefault();
|
|
544
|
-
this
|
|
565
|
+
if (this.#contextElement) {
|
|
566
|
+
this.#contextElement.playing = !this.#contextElement.playing;
|
|
567
|
+
}
|
|
545
568
|
}
|
|
546
569
|
};
|
|
547
570
|
|
|
@@ -561,108 +584,6 @@ export class EFFilmstrip extends TWMixin(LitElement) {
|
|
|
561
584
|
}
|
|
562
585
|
}
|
|
563
586
|
|
|
564
|
-
#lastTick?: DOMHighResTimeStamp;
|
|
565
|
-
|
|
566
|
-
#playbackAudioContext: AudioContext | null = null;
|
|
567
|
-
#playbackAnimationFrameRequest: number | null = null;
|
|
568
|
-
#AUDIO_PLAYBACK_SLICE_MS = 1000;
|
|
569
|
-
|
|
570
|
-
#syncPlayheadToAudioContext(target: EFTimegroup, startMs: number) {
|
|
571
|
-
target.currentTimeMs =
|
|
572
|
-
startMs + (this.#playbackAudioContext?.currentTime ?? 0) * 1000;
|
|
573
|
-
this.#playbackAnimationFrameRequest = requestAnimationFrame(() => {
|
|
574
|
-
this.#syncPlayheadToAudioContext(target, startMs);
|
|
575
|
-
});
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
async #stopPlayback() {
|
|
579
|
-
if (this.#playbackAudioContext) {
|
|
580
|
-
if (this.#playbackAudioContext.state !== "closed") {
|
|
581
|
-
await this.#playbackAudioContext.close();
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
if (this.#playbackAnimationFrameRequest) {
|
|
585
|
-
cancelAnimationFrame(this.#playbackAnimationFrameRequest);
|
|
586
|
-
}
|
|
587
|
-
this.#playbackAudioContext = null;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
async #startPlayback() {
|
|
591
|
-
await this.#stopPlayback();
|
|
592
|
-
const timegroup = this.targetTimegroup;
|
|
593
|
-
if (!timegroup) {
|
|
594
|
-
return;
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
let currentMs = timegroup.currentTimeMs;
|
|
598
|
-
let bufferCount = 0;
|
|
599
|
-
this.#playbackAudioContext = new AudioContext({
|
|
600
|
-
latencyHint: "playback",
|
|
601
|
-
});
|
|
602
|
-
if (this.#playbackAnimationFrameRequest) {
|
|
603
|
-
cancelAnimationFrame(this.#playbackAnimationFrameRequest);
|
|
604
|
-
}
|
|
605
|
-
this.#syncPlayheadToAudioContext(timegroup, currentMs);
|
|
606
|
-
const playbackContext = this.#playbackAudioContext;
|
|
607
|
-
await playbackContext.suspend();
|
|
608
|
-
|
|
609
|
-
const fillBuffer = async () => {
|
|
610
|
-
if (bufferCount > 1) {
|
|
611
|
-
return;
|
|
612
|
-
}
|
|
613
|
-
const canFillBuffer = await queueBufferSource();
|
|
614
|
-
if (canFillBuffer) {
|
|
615
|
-
fillBuffer();
|
|
616
|
-
}
|
|
617
|
-
};
|
|
618
|
-
|
|
619
|
-
const fromMs = currentMs;
|
|
620
|
-
const toMs = timegroup.endTimeMs;
|
|
621
|
-
|
|
622
|
-
const queueBufferSource = async () => {
|
|
623
|
-
if (currentMs >= toMs) {
|
|
624
|
-
return false;
|
|
625
|
-
}
|
|
626
|
-
const startMs = currentMs;
|
|
627
|
-
const endMs = currentMs + this.#AUDIO_PLAYBACK_SLICE_MS;
|
|
628
|
-
currentMs += this.#AUDIO_PLAYBACK_SLICE_MS;
|
|
629
|
-
const audioBuffer = await timegroup.renderAudio(startMs, endMs);
|
|
630
|
-
bufferCount++;
|
|
631
|
-
const source = playbackContext.createBufferSource();
|
|
632
|
-
source.buffer = audioBuffer;
|
|
633
|
-
source.connect(playbackContext.destination);
|
|
634
|
-
source.start((startMs - fromMs) / 1000);
|
|
635
|
-
source.onended = () => {
|
|
636
|
-
bufferCount--;
|
|
637
|
-
if (endMs >= toMs) {
|
|
638
|
-
this.playing = false;
|
|
639
|
-
} else {
|
|
640
|
-
fillBuffer();
|
|
641
|
-
}
|
|
642
|
-
};
|
|
643
|
-
return true;
|
|
644
|
-
};
|
|
645
|
-
|
|
646
|
-
await fillBuffer();
|
|
647
|
-
await playbackContext.resume();
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
advancePlayhead = (tick?: DOMHighResTimeStamp) => {
|
|
651
|
-
if (this.#lastTick && tick && this.targetTimegroup) {
|
|
652
|
-
this.targetTimegroup.currentTimeMs += tick - this.#lastTick;
|
|
653
|
-
if (
|
|
654
|
-
this.targetTimegroup.currentTimeMs >= this.targetTimegroup.durationMs
|
|
655
|
-
) {
|
|
656
|
-
this.playing = false;
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
this.#lastTick = tick;
|
|
660
|
-
|
|
661
|
-
if (this.playing) {
|
|
662
|
-
requestAnimationFrame(this.advancePlayhead);
|
|
663
|
-
}
|
|
664
|
-
};
|
|
665
|
-
|
|
666
587
|
@eventOptions({ capture: false })
|
|
667
588
|
scrub(e: MouseEvent) {
|
|
668
589
|
if (this.playing) {
|
|
@@ -773,23 +694,8 @@ export class EFFilmstrip extends TWMixin(LitElement) {
|
|
|
773
694
|
/>
|
|
774
695
|
<code>${msToTimeCode(this.currentTimeMs, true)} </code> /
|
|
775
696
|
<code>${msToTimeCode(target?.durationMs ?? 0, true)}</code>
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
? html`<button
|
|
779
|
-
@click=${() => {
|
|
780
|
-
this.playing = false;
|
|
781
|
-
}}
|
|
782
|
-
>
|
|
783
|
-
⏸️
|
|
784
|
-
</button>`
|
|
785
|
-
: html`<button
|
|
786
|
-
@click=${() => {
|
|
787
|
-
this.playing = true;
|
|
788
|
-
}}
|
|
789
|
-
>
|
|
790
|
-
▶️
|
|
791
|
-
</button>`
|
|
792
|
-
}
|
|
697
|
+
<ef-toggle-play><button>${this.playing ? "⏸️" : "▶️"}</button></ef-toggle-play>
|
|
698
|
+
<ef-toggle-loop><button>${this.loop ? "🔁" : html`<span class="opacity-50">🔁</span>`}</button></ef-toggle-loop>
|
|
793
699
|
</div>
|
|
794
700
|
<div
|
|
795
701
|
class="z-10 pl-1 pr-1 pt-2 shadow shadow-slate-600 overflow-auto"
|
|
@@ -826,17 +732,6 @@ export class EFFilmstrip extends TWMixin(LitElement) {
|
|
|
826
732
|
</div>`;
|
|
827
733
|
}
|
|
828
734
|
|
|
829
|
-
update(changedProperties: Map<string | number | symbol, unknown>) {
|
|
830
|
-
if (changedProperties.has("playing")) {
|
|
831
|
-
if (this.playing) {
|
|
832
|
-
this.#startPlayback();
|
|
833
|
-
} else {
|
|
834
|
-
this.#stopPlayback();
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
super.update(changedProperties);
|
|
838
|
-
}
|
|
839
|
-
|
|
840
735
|
updated(changes: PropertyValueMap<any> | Map<PropertyKey, unknown>) {
|
|
841
736
|
if (!this.targetTimegroup) {
|
|
842
737
|
return;
|
|
@@ -846,19 +741,14 @@ export class EFFilmstrip extends TWMixin(LitElement) {
|
|
|
846
741
|
this.targetTimegroup.currentTimeMs = this.currentTimeMs;
|
|
847
742
|
}
|
|
848
743
|
}
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
get #contextElement(): EFWorkbench | EFPreview | null {
|
|
747
|
+
return this.closest("ef-workbench, ef-preview") as EFWorkbench | EFPreview;
|
|
852
748
|
}
|
|
853
749
|
|
|
854
750
|
get targetTimegroup() {
|
|
855
|
-
|
|
856
|
-
const target = document.getElementById(this.getAttribute("target") ?? "");
|
|
857
|
-
if (target instanceof EFTimegroup) {
|
|
858
|
-
return target;
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
return undefined;
|
|
751
|
+
return this.#contextElement?.targetTimegroup;
|
|
862
752
|
}
|
|
863
753
|
}
|
|
864
754
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { LitElement, html, css } from "lit";
|
|
2
|
+
import { customElement } from "lit/decorators.js";
|
|
3
|
+
import { ref } from "lit/directives/ref.js";
|
|
4
|
+
|
|
5
|
+
import { TWMixin } from "./TWMixin.ts";
|
|
6
|
+
import { ContextMixin } from "./ContextMixin.ts";
|
|
7
|
+
|
|
8
|
+
@customElement("ef-preview")
|
|
9
|
+
export class EFPreview extends ContextMixin(TWMixin(LitElement)) {
|
|
10
|
+
static styles = [
|
|
11
|
+
css`
|
|
12
|
+
:host {
|
|
13
|
+
display: block;
|
|
14
|
+
width: 100%;
|
|
15
|
+
height: 100%;
|
|
16
|
+
}
|
|
17
|
+
`,
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
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
|
+
></slot>
|
|
30
|
+
</div>
|
|
31
|
+
`;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
declare global {
|
|
36
|
+
interface HTMLElementTagNameMap {
|
|
37
|
+
"ef-preview": EFPreview;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { css, html, LitElement } from "lit";
|
|
2
|
+
import { customElement } from "lit/decorators.js";
|
|
3
|
+
import { consume } from "@lit/context";
|
|
4
|
+
|
|
5
|
+
import { efContext } from "./efContext.ts";
|
|
6
|
+
import type { ContextMixinInterface } from "./ContextMixin.ts";
|
|
7
|
+
|
|
8
|
+
@customElement("ef-toggle-loop")
|
|
9
|
+
export class EFToggleLoop extends LitElement {
|
|
10
|
+
static styles = [
|
|
11
|
+
css`
|
|
12
|
+
:host {}
|
|
13
|
+
`,
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
@consume({ context: efContext })
|
|
17
|
+
context?: ContextMixinInterface | null;
|
|
18
|
+
|
|
19
|
+
render() {
|
|
20
|
+
return html`
|
|
21
|
+
<slot @click=${() => {
|
|
22
|
+
if (this.context) {
|
|
23
|
+
this.context.loop = !this.context.loop;
|
|
24
|
+
}
|
|
25
|
+
}}></slot>
|
|
26
|
+
`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
declare global {
|
|
31
|
+
interface HTMLElementTagNameMap {
|
|
32
|
+
"ef-toggle-loop": EFToggleLoop;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { css, html, LitElement } from "lit";
|
|
2
|
+
import { customElement } from "lit/decorators.js";
|
|
3
|
+
import { consume } from "@lit/context";
|
|
4
|
+
|
|
5
|
+
import { efContext } from "./efContext.ts";
|
|
6
|
+
import type { ContextMixinInterface } from "./ContextMixin.ts";
|
|
7
|
+
|
|
8
|
+
@customElement("ef-toggle-play")
|
|
9
|
+
export class EFTogglePlay extends LitElement {
|
|
10
|
+
static styles = [
|
|
11
|
+
css`
|
|
12
|
+
:host {}
|
|
13
|
+
`,
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
@consume({ context: efContext })
|
|
17
|
+
context?: ContextMixinInterface | null;
|
|
18
|
+
|
|
19
|
+
render() {
|
|
20
|
+
return html`
|
|
21
|
+
<slot @click=${() => {
|
|
22
|
+
if (this.context) {
|
|
23
|
+
if (this.context.playing) {
|
|
24
|
+
this.context.pause();
|
|
25
|
+
} else {
|
|
26
|
+
this.context.play();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}}></slot>
|
|
30
|
+
`;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
declare global {
|
|
35
|
+
interface HTMLElementTagNameMap {
|
|
36
|
+
"ef-toggle-play": EFTogglePlay;
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/gui/EFWorkbench.ts
CHANGED
|
@@ -1,34 +1,15 @@
|
|
|
1
|
-
import { createContext, provide } from "@lit/context";
|
|
2
1
|
import { LitElement, html, css, type PropertyValueMap } from "lit";
|
|
3
2
|
import { TaskStatus } from "@lit/task";
|
|
4
|
-
import {
|
|
5
|
-
customElement,
|
|
6
|
-
eventOptions,
|
|
7
|
-
property,
|
|
8
|
-
state,
|
|
9
|
-
} from "lit/decorators.js";
|
|
3
|
+
import { customElement, eventOptions } from "lit/decorators.js";
|
|
10
4
|
import { ref, createRef } from "lit/directives/ref.js";
|
|
11
5
|
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
export interface FocusContext {
|
|
18
|
-
focusedElement: HTMLElement | null;
|
|
19
|
-
}
|
|
20
|
-
export const focusContext = createContext<FocusContext>(Symbol("focusContext"));
|
|
21
|
-
|
|
22
|
-
export const focusedElement = createContext<HTMLElement | undefined>(
|
|
23
|
-
Symbol("focusedElement"),
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
export const fetchContext = createContext<typeof fetch>(Symbol("fetchContext"));
|
|
27
|
-
|
|
28
|
-
export const apiHostContext = createContext<string>(Symbol("apiHostContext"));
|
|
6
|
+
import { deepGetTemporalElements } from "../elements/EFTemporal.ts";
|
|
7
|
+
import { TWMixin } from "./TWMixin.ts";
|
|
8
|
+
import { shallowGetTimegroups } from "../elements/EFTimegroup.ts";
|
|
9
|
+
import { ContextMixin } from "./ContextMixin.ts";
|
|
29
10
|
|
|
30
11
|
@customElement("ef-workbench")
|
|
31
|
-
export class EFWorkbench extends TWMixin(LitElement) {
|
|
12
|
+
export class EFWorkbench extends ContextMixin(TWMixin(LitElement)) {
|
|
32
13
|
static styles = [
|
|
33
14
|
css`
|
|
34
15
|
:host {
|
|
@@ -38,51 +19,6 @@ export class EFWorkbench extends TWMixin(LitElement) {
|
|
|
38
19
|
}
|
|
39
20
|
`,
|
|
40
21
|
];
|
|
41
|
-
stageRef = createRef<HTMLDivElement>();
|
|
42
|
-
canvasRef = createRef<HTMLSlotElement>();
|
|
43
|
-
|
|
44
|
-
@state()
|
|
45
|
-
stageScale = 1;
|
|
46
|
-
|
|
47
|
-
setStageScale = () => {
|
|
48
|
-
if (this.isConnected && !this.rendering) {
|
|
49
|
-
const canvasElement = this.canvasRef.value;
|
|
50
|
-
const stageElement = this.stageRef.value;
|
|
51
|
-
if (stageElement && canvasElement) {
|
|
52
|
-
// Determine the appropriate scale factor to make the canvas fit into
|
|
53
|
-
// it's parent element.
|
|
54
|
-
const stageWidth = stageElement.clientWidth;
|
|
55
|
-
const stageHeight = stageElement.clientHeight;
|
|
56
|
-
const canvasWidth = canvasElement.clientWidth;
|
|
57
|
-
const canvasHeight = canvasElement.clientHeight;
|
|
58
|
-
const stageRatio = stageWidth / stageHeight;
|
|
59
|
-
const canvasRatio = canvasWidth / canvasHeight;
|
|
60
|
-
if (stageRatio > canvasRatio) {
|
|
61
|
-
const scale = stageHeight / canvasHeight;
|
|
62
|
-
if (this.stageScale !== scale) {
|
|
63
|
-
canvasElement.style.transform = `scale(${scale})`;
|
|
64
|
-
}
|
|
65
|
-
this.stageScale = scale;
|
|
66
|
-
} else {
|
|
67
|
-
const scale = stageWidth / canvasWidth;
|
|
68
|
-
if (this.stageScale !== scale) {
|
|
69
|
-
canvasElement.style.transform = `scale(${scale})`;
|
|
70
|
-
}
|
|
71
|
-
this.stageScale = scale;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
if (this.isConnected) {
|
|
76
|
-
requestAnimationFrame(this.setStageScale);
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
connectedCallback(): void {
|
|
81
|
-
super.connectedCallback();
|
|
82
|
-
// Preferrably we would use a resizeObserver, but it is difficult to get the first resize
|
|
83
|
-
// timed correctl. So we use requestAnimationFrame as a stop-gap.
|
|
84
|
-
requestAnimationFrame(this.setStageScale);
|
|
85
|
-
}
|
|
86
22
|
|
|
87
23
|
disconnectedCallback(): void {
|
|
88
24
|
super.disconnectedCallback();
|
|
@@ -93,40 +29,6 @@ export class EFWorkbench extends TWMixin(LitElement) {
|
|
|
93
29
|
event.preventDefault();
|
|
94
30
|
}
|
|
95
31
|
|
|
96
|
-
@provide({ context: focusContext })
|
|
97
|
-
focusContext = this as FocusContext;
|
|
98
|
-
|
|
99
|
-
@provide({ context: focusedElement })
|
|
100
|
-
@state()
|
|
101
|
-
focusedElement?: HTMLElement;
|
|
102
|
-
|
|
103
|
-
@provide({ context: fetchContext })
|
|
104
|
-
fetch = (path: URL | RequestInfo, init: RequestInit = {}) => {
|
|
105
|
-
init.headers ||= {};
|
|
106
|
-
Object.assign(init.headers, {
|
|
107
|
-
"Content-Type": "application/json",
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
const bearerToken = this.apiToken;
|
|
111
|
-
if (bearerToken) {
|
|
112
|
-
Object.assign(init.headers, {
|
|
113
|
-
Authorization: `Bearer ${bearerToken}`,
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return fetch(path, init);
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
@property({ type: String })
|
|
121
|
-
apiToken?: string;
|
|
122
|
-
|
|
123
|
-
@provide({ context: apiHostContext })
|
|
124
|
-
@property({ type: String })
|
|
125
|
-
apiHost = "";
|
|
126
|
-
|
|
127
|
-
@property({ type: Boolean })
|
|
128
|
-
rendering = false;
|
|
129
|
-
|
|
130
32
|
focusOverlay = createRef<HTMLDivElement>();
|
|
131
33
|
|
|
132
34
|
update(
|
|
@@ -176,21 +78,21 @@ export class EFWorkbench extends TWMixin(LitElement) {
|
|
|
176
78
|
>
|
|
177
79
|
<div
|
|
178
80
|
${ref(this.stageRef)}
|
|
179
|
-
class="relative grid h-full w-full
|
|
81
|
+
class="relative grid h-full w-full justify-center overflow-hidden"
|
|
180
82
|
@wheel=${this.handleStageWheel}
|
|
181
83
|
>
|
|
182
84
|
<slot
|
|
183
85
|
${ref(this.canvasRef)}
|
|
184
|
-
class="inline-block"
|
|
185
86
|
name="canvas"
|
|
87
|
+
class="inline-block"
|
|
186
88
|
></slot>
|
|
187
89
|
<div
|
|
188
|
-
class="border border-blue-500 bg-blue-200 bg-opacity-20"
|
|
90
|
+
class="border border-blue-500 bg-blue-200 bg-opacity-20 absolute"
|
|
189
91
|
${ref(this.focusOverlay)}
|
|
190
92
|
></div>
|
|
191
93
|
</div>
|
|
192
94
|
|
|
193
|
-
<slot class="overflow" name="timeline"></slot>
|
|
95
|
+
<slot class="overflow inline-block" name="timeline"></slot>
|
|
194
96
|
</div>
|
|
195
97
|
`;
|
|
196
98
|
}
|
|
@@ -215,7 +117,7 @@ export class EFWorkbench extends TWMixin(LitElement) {
|
|
|
215
117
|
|
|
216
118
|
for (let i = 0; i < frameCount; i++) {
|
|
217
119
|
firstGroup.currentTimeMs = i * stepDurationMs;
|
|
218
|
-
await
|
|
120
|
+
await new Promise<void>(queueMicrotask);
|
|
219
121
|
const busyTasks = temporals
|
|
220
122
|
.filter((temporal) => temporal.frameTask.status < TaskStatus.COMPLETE)
|
|
221
123
|
.map((temporal) => temporal.frameTask);
|
package/src/gui/TWMixin.ts
CHANGED
|
@@ -2,8 +2,11 @@ import type { LitElement } from "lit";
|
|
|
2
2
|
// @ts-expect-error cannot figure out how to declare this module as a string
|
|
3
3
|
import twStyle from "./TWMixin.css?inline";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
let twSheet: CSSStyleSheet | null = null;
|
|
6
|
+
if (typeof window !== "undefined") {
|
|
7
|
+
twSheet = new CSSStyleSheet();
|
|
8
|
+
twSheet.replaceSync(twStyle);
|
|
9
|
+
}
|
|
7
10
|
export function TWMixin<T extends new (...args: any[]) => LitElement>(Base: T) {
|
|
8
11
|
class TWElement extends Base {
|
|
9
12
|
createRenderRoot() {
|
|
@@ -13,7 +16,11 @@ export function TWMixin<T extends new (...args: any[]) => LitElement>(Base: T) {
|
|
|
13
16
|
"TWMixin can only be applied to elements with shadow roots",
|
|
14
17
|
);
|
|
15
18
|
}
|
|
16
|
-
|
|
19
|
+
if (!twSheet) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
"twSheet not found. Probable cause: CSSStyleSheet not supported in this environment",
|
|
22
|
+
);
|
|
23
|
+
}
|
|
17
24
|
if (renderRoot?.adoptedStyleSheets) {
|
|
18
25
|
renderRoot.adoptedStyleSheets = [
|
|
19
26
|
twSheet,
|