@editframe/elements 0.8.0-beta.2 → 0.8.0-beta.4
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/EFCaptions.d.ts +3 -2
- package/dist/elements/EFTemporal.d.ts +3 -0
- package/dist/elements/EFTimegroup.d.ts +1 -3
- package/dist/elements/durationConverter.d.ts +8 -0
- package/dist/elements/src/elements/EFCaptions.js +10 -7
- package/dist/elements/src/elements/EFMedia.js +6 -6
- package/dist/elements/src/elements/EFTemporal.js +51 -2
- package/dist/elements/src/elements/EFTimegroup.js +22 -39
- package/dist/elements/src/elements/EFWaveform.js +3 -3
- package/dist/elements/src/elements/parseTimeToMs.js +1 -0
- package/dist/elements/src/gui/ContextMixin.js +28 -18
- package/dist/elements/src/gui/EFFilmstrip.js +27 -25
- 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 +4 -4
- package/dist/elements/src/gui/TWMixin.css.js +1 -1
- package/dist/elements/src/gui/efContext.js +7 -0
- package/dist/elements/src/gui/playingContext.js +2 -0
- package/dist/elements/src/index.js +4 -0
- package/dist/gui/ContextMixin.d.ts +8 -11
- package/dist/gui/EFFilmstrip.d.ts +7 -2
- package/dist/gui/EFPreview.d.ts +1 -15
- package/dist/gui/EFToggleLoop.d.ts +13 -0
- package/dist/gui/EFTogglePlay.d.ts +13 -0
- package/dist/gui/EFWorkbench.d.ts +1 -16
- package/dist/gui/efContext.d.ts +5 -0
- package/dist/gui/playingContext.d.ts +3 -0
- package/dist/index.d.ts +2 -0
- package/dist/style.css +7 -5
- package/package.json +2 -2
- package/src/elements/EFCaptions.ts +14 -8
- package/src/elements/EFMedia.ts +9 -6
- package/src/elements/EFTemporal.ts +60 -2
- package/src/elements/EFTimegroup.ts +21 -41
- package/src/elements/EFWaveform.ts +3 -3
- package/src/elements/durationConverter.ts +20 -0
- package/src/elements/parseTimeToMs.ts +1 -0
- package/src/gui/ContextMixin.ts +42 -30
- package/src/gui/EFFilmstrip.ts +27 -28
- package/src/gui/EFToggleLoop.ts +34 -0
- package/src/gui/EFTogglePlay.ts +38 -0
- package/src/gui/EFWorkbench.ts +4 -4
- package/src/gui/efContext.ts +6 -0
- package/src/gui/playingContext.ts +2 -0
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import { TimegroupController } from "./TimegroupController.ts";
|
|
14
14
|
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.ts";
|
|
15
15
|
import { deepGetMediaElements } from "./EFMedia.ts";
|
|
16
|
+
import { durationConverter } from "./durationConverter.ts";
|
|
16
17
|
|
|
17
18
|
const log = debug("ef:elements:EFTimegroup");
|
|
18
19
|
|
|
@@ -37,7 +38,7 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
37
38
|
display: block;
|
|
38
39
|
width: 100%;
|
|
39
40
|
height: 100%;
|
|
40
|
-
position:
|
|
41
|
+
position: absolute;
|
|
41
42
|
top: 0;
|
|
42
43
|
}
|
|
43
44
|
`;
|
|
@@ -53,6 +54,13 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
53
54
|
})
|
|
54
55
|
mode: "fixed" | "sequence" | "contain" = "sequence";
|
|
55
56
|
|
|
57
|
+
@property({
|
|
58
|
+
type: Number,
|
|
59
|
+
converter: durationConverter,
|
|
60
|
+
attribute: "overlap",
|
|
61
|
+
})
|
|
62
|
+
overlapMs = 0;
|
|
63
|
+
|
|
56
64
|
@property({ type: Number })
|
|
57
65
|
set currentTime(time: number) {
|
|
58
66
|
this.#currentTime = Math.max(0, Math.min(time, this.durationMs / 1000));
|
|
@@ -76,25 +84,6 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
76
84
|
this.currentTime = ms / 1000;
|
|
77
85
|
}
|
|
78
86
|
|
|
79
|
-
@property({
|
|
80
|
-
attribute: "crossover",
|
|
81
|
-
converter: {
|
|
82
|
-
fromAttribute: (value: string): number => {
|
|
83
|
-
if (value.endsWith("ms")) {
|
|
84
|
-
return Number.parseFloat(value);
|
|
85
|
-
}
|
|
86
|
-
if (value.endsWith("s")) {
|
|
87
|
-
return Number.parseFloat(value) * 1000;
|
|
88
|
-
}
|
|
89
|
-
throw new Error(
|
|
90
|
-
"`crossover` MUST be in milliseconds or seconds (10s, 10000ms)",
|
|
91
|
-
);
|
|
92
|
-
},
|
|
93
|
-
toAttribute: (value: number) => `${value}ms`,
|
|
94
|
-
},
|
|
95
|
-
})
|
|
96
|
-
crossoverMs = 0;
|
|
97
|
-
|
|
98
87
|
render() {
|
|
99
88
|
return html`<slot></slot> `;
|
|
100
89
|
}
|
|
@@ -134,32 +123,18 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
134
123
|
return `ef-timegroup-${this.id}`;
|
|
135
124
|
}
|
|
136
125
|
|
|
137
|
-
get crossoverStartMs() {
|
|
138
|
-
const parentTimeGroup = this.parentTimegroup;
|
|
139
|
-
if (!parentTimeGroup || !this.previousElementSibling) {
|
|
140
|
-
return 0;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return parentTimeGroup.crossoverMs;
|
|
144
|
-
}
|
|
145
|
-
get crossoverEndMs() {
|
|
146
|
-
const parentTimeGroup = this.parentTimegroup;
|
|
147
|
-
if (!parentTimeGroup || !this.nextElementSibling) {
|
|
148
|
-
return 0;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return parentTimeGroup.crossoverMs;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
126
|
get durationMs() {
|
|
155
127
|
switch (this.mode) {
|
|
156
128
|
case "fixed":
|
|
157
129
|
return super.durationMs;
|
|
158
130
|
case "sequence": {
|
|
159
131
|
let duration = 0;
|
|
160
|
-
|
|
132
|
+
this.childTemporals.forEach((node, index) => {
|
|
133
|
+
if (index > 0) {
|
|
134
|
+
duration -= this.overlapMs;
|
|
135
|
+
}
|
|
161
136
|
duration += node.durationMs;
|
|
162
|
-
}
|
|
137
|
+
});
|
|
163
138
|
return duration;
|
|
164
139
|
}
|
|
165
140
|
case "contain": {
|
|
@@ -207,9 +182,14 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
207
182
|
this.style.display = "";
|
|
208
183
|
|
|
209
184
|
const animations = this.getAnimations({ subtree: true });
|
|
185
|
+
this.style.setProperty("--ef-duration", `${this.durationMs}ms`);
|
|
186
|
+
this.style.setProperty(
|
|
187
|
+
"--ef-transition--duration",
|
|
188
|
+
`${this.parentTimegroup?.overlapMs ?? 0}ms`,
|
|
189
|
+
);
|
|
210
190
|
this.style.setProperty(
|
|
211
|
-
"--ef-
|
|
212
|
-
`${this.durationMs
|
|
191
|
+
"--ef-transition-out-start",
|
|
192
|
+
`${this.durationMs - (this.parentTimegroup?.overlapMs ?? 0)}ms`,
|
|
213
193
|
);
|
|
214
194
|
|
|
215
195
|
for (const animation of animations) {
|
|
@@ -351,7 +351,7 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
|
|
|
351
351
|
if (!this.targetElement.audioBufferTask.value) {
|
|
352
352
|
return;
|
|
353
353
|
}
|
|
354
|
-
if (this.targetElement.
|
|
354
|
+
if (this.targetElement.trimAdjustedOwnCurrentTimeMs > 0) {
|
|
355
355
|
const audioContext = new OfflineAudioContext(2, 48000 / 25, 48000);
|
|
356
356
|
const audioBufferSource = audioContext.createBufferSource();
|
|
357
357
|
audioBufferSource.buffer =
|
|
@@ -364,7 +364,7 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
|
|
|
364
364
|
0,
|
|
365
365
|
Math.max(
|
|
366
366
|
0,
|
|
367
|
-
(this.targetElement.
|
|
367
|
+
(this.targetElement.trimAdjustedOwnCurrentTimeMs -
|
|
368
368
|
this.targetElement.audioBufferTask.value.startOffsetMs) /
|
|
369
369
|
1000,
|
|
370
370
|
),
|
|
@@ -412,6 +412,6 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
|
|
|
412
412
|
if (target instanceof EFAudio || target instanceof EFVideo) {
|
|
413
413
|
return target;
|
|
414
414
|
}
|
|
415
|
-
throw new Error("Invalid target, must be an EFAudio element");
|
|
415
|
+
throw new Error("Invalid target, must be an EFAudio or EFVideo element");
|
|
416
416
|
}
|
|
417
417
|
}
|
|
@@ -4,3 +4,23 @@ export const durationConverter = {
|
|
|
4
4
|
fromAttribute: (value: string): number => parseTimeToMs(value),
|
|
5
5
|
toAttribute: (value: number) => `${value}s`,
|
|
6
6
|
};
|
|
7
|
+
|
|
8
|
+
const positiveDurationConverter = (error: string) => {
|
|
9
|
+
return {
|
|
10
|
+
fromAttribute: (value: string): number => {
|
|
11
|
+
if (value.startsWith("-")) {
|
|
12
|
+
throw new Error(error);
|
|
13
|
+
}
|
|
14
|
+
return parseTimeToMs(value);
|
|
15
|
+
},
|
|
16
|
+
toAttribute: (value: number) => `${value}s`,
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const trimDurationConverter = positiveDurationConverter(
|
|
21
|
+
"Trimstart & trimend must be a positive value in milliseconds or seconds (1s, 1000ms)",
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
export const imageDurationConverter = positiveDurationConverter(
|
|
25
|
+
"Image duration must be a positive value in milliseconds or seconds (1s, 1000ms)",
|
|
26
|
+
);
|
package/src/gui/ContextMixin.ts
CHANGED
|
@@ -5,25 +5,23 @@ import { property, state } from "lit/decorators.js";
|
|
|
5
5
|
import { focusContext, type FocusContext } from "./focusContext.ts";
|
|
6
6
|
import { focusedElementContext } from "./focusedElementContext.ts";
|
|
7
7
|
import { fetchContext } from "./fetchContext.ts";
|
|
8
|
-
import { apiHostContext } from "./apiHostContext.ts";
|
|
9
8
|
import { createRef } from "lit/directives/ref.js";
|
|
10
|
-
import { playingContext } from "./playingContext.ts";
|
|
9
|
+
import { loopContext, playingContext } from "./playingContext.ts";
|
|
11
10
|
import type { EFTimegroup } from "../elements/EFTimegroup.ts";
|
|
11
|
+
import { efContext } from "./efContext.ts";
|
|
12
12
|
|
|
13
|
-
declare class ContextMixinInterface {
|
|
14
|
-
focusContext: FocusContext;
|
|
15
|
-
focusedElement?: HTMLElement;
|
|
16
|
-
fetch: typeof fetch;
|
|
13
|
+
export declare class ContextMixinInterface {
|
|
17
14
|
signingURL?: string;
|
|
18
|
-
apiToken?: string;
|
|
19
|
-
apiHost: string;
|
|
20
|
-
stageScale: number;
|
|
21
15
|
rendering: boolean;
|
|
22
|
-
stageRef: ReturnType<typeof createRef<HTMLDivElement>>;
|
|
23
|
-
canvasRef: ReturnType<typeof createRef<HTMLElement>>;
|
|
24
16
|
playing: boolean;
|
|
25
|
-
|
|
17
|
+
loop: boolean;
|
|
26
18
|
currentTimeMs: number;
|
|
19
|
+
focusedElement?: HTMLElement;
|
|
20
|
+
stageRef: ReturnType<typeof createRef<HTMLDivElement>>;
|
|
21
|
+
canvasRef: ReturnType<typeof createRef<HTMLElement>>;
|
|
22
|
+
targetTimegroup: EFTimegroup | null;
|
|
23
|
+
play(): void;
|
|
24
|
+
pause(): void;
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
type Constructor<T = {}> = new (...args: any[]) => T;
|
|
@@ -36,6 +34,9 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
36
34
|
@state()
|
|
37
35
|
focusedElement?: HTMLElement;
|
|
38
36
|
|
|
37
|
+
@provide({ context: efContext })
|
|
38
|
+
efContext = this;
|
|
39
|
+
|
|
39
40
|
@provide({ context: fetchContext })
|
|
40
41
|
fetch = async (url: string, init: RequestInit = {}) => {
|
|
41
42
|
init.headers ||= {};
|
|
@@ -43,13 +44,6 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
43
44
|
"Content-Type": "application/json",
|
|
44
45
|
});
|
|
45
46
|
|
|
46
|
-
const bearerToken = this.apiToken;
|
|
47
|
-
if (bearerToken) {
|
|
48
|
-
Object.assign(init.headers, {
|
|
49
|
-
Authorization: `Bearer ${bearerToken}`,
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
47
|
if (this.signingURL) {
|
|
54
48
|
if (!this.#URLTokens[url]) {
|
|
55
49
|
this.#URLTokens[url] = fetch(this.signingURL, {
|
|
@@ -77,25 +71,23 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
77
71
|
|
|
78
72
|
#URLTokens: Record<string, Promise<string>> = {};
|
|
79
73
|
|
|
74
|
+
/**
|
|
75
|
+
* A URL that will be used to generated signed tokens for accessing media files from the
|
|
76
|
+
* editframe API. This is used to authenticate media requests per-user.
|
|
77
|
+
*/
|
|
80
78
|
@property({ type: String })
|
|
81
79
|
signingURL?: string;
|
|
82
80
|
|
|
83
|
-
@property({ type: String })
|
|
84
|
-
apiToken?: string;
|
|
85
|
-
|
|
86
|
-
@provide({ context: apiHostContext })
|
|
87
|
-
@property({ type: String })
|
|
88
|
-
apiHost = "";
|
|
89
|
-
|
|
90
81
|
@provide({ context: playingContext })
|
|
91
82
|
@property({ type: Boolean, reflect: true })
|
|
92
83
|
playing = false;
|
|
93
84
|
|
|
85
|
+
@provide({ context: loopContext })
|
|
94
86
|
@property({ type: Boolean, reflect: true })
|
|
95
87
|
loop = false;
|
|
96
88
|
|
|
97
89
|
@state()
|
|
98
|
-
stageScale = 1;
|
|
90
|
+
private stageScale = 1;
|
|
99
91
|
|
|
100
92
|
@property({ type: Boolean })
|
|
101
93
|
rendering = false;
|
|
@@ -110,9 +102,11 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
110
102
|
if (this.isConnected && !this.rendering) {
|
|
111
103
|
const canvasElement = this.canvasRef.value;
|
|
112
104
|
const stageElement = this.stageRef.value;
|
|
113
|
-
|
|
105
|
+
const canvasChild = canvasElement?.assignedElements()[0];
|
|
106
|
+
if (stageElement && canvasElement && canvasChild) {
|
|
114
107
|
// Determine the appropriate scale factor to make the canvas fit into
|
|
115
|
-
|
|
108
|
+
canvasElement.style.width = `${canvasChild.clientWidth}px`;
|
|
109
|
+
canvasElement.style.height = `${canvasChild.clientHeight}px`;
|
|
116
110
|
const stageWidth = stageElement.clientWidth;
|
|
117
111
|
const stageHeight = stageElement.clientHeight;
|
|
118
112
|
const canvasWidth = canvasElement.clientWidth;
|
|
@@ -123,12 +117,14 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
123
117
|
const scale = stageHeight / canvasHeight;
|
|
124
118
|
if (this.stageScale !== scale) {
|
|
125
119
|
canvasElement.style.transform = `scale(${scale})`;
|
|
120
|
+
canvasElement.style.transformOrigin = "top";
|
|
126
121
|
}
|
|
127
122
|
this.stageScale = scale;
|
|
128
123
|
} else {
|
|
129
124
|
const scale = stageWidth / canvasWidth;
|
|
130
125
|
if (this.stageScale !== scale) {
|
|
131
126
|
canvasElement.style.transform = `scale(${scale})`;
|
|
127
|
+
canvasElement.style.transformOrigin = "top";
|
|
132
128
|
}
|
|
133
129
|
this.stageScale = scale;
|
|
134
130
|
}
|
|
@@ -167,6 +163,14 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
167
163
|
return this.querySelector("ef-timegroup");
|
|
168
164
|
}
|
|
169
165
|
|
|
166
|
+
play() {
|
|
167
|
+
this.playing = true;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
pause() {
|
|
171
|
+
this.playing = false;
|
|
172
|
+
}
|
|
173
|
+
|
|
170
174
|
#playbackAudioContext: AudioContext | null = null;
|
|
171
175
|
#playbackAnimationFrameRequest: number | null = null;
|
|
172
176
|
#AUDIO_PLAYBACK_SLICE_MS = 1000;
|
|
@@ -239,7 +243,15 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
239
243
|
source.onended = () => {
|
|
240
244
|
bufferCount--;
|
|
241
245
|
if (endMs >= toMs) {
|
|
242
|
-
this.
|
|
246
|
+
this.pause();
|
|
247
|
+
if (this.loop) {
|
|
248
|
+
this.updateComplete.then(() => {
|
|
249
|
+
this.currentTimeMs = 0;
|
|
250
|
+
this.updateComplete.then(() => {
|
|
251
|
+
this.play();
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
}
|
|
243
255
|
} else {
|
|
244
256
|
fillBuffer();
|
|
245
257
|
}
|
package/src/gui/EFFilmstrip.ts
CHANGED
|
@@ -29,7 +29,7 @@ import { TWMixin } from "./TWMixin.ts";
|
|
|
29
29
|
import { msToTimeCode } from "../msToTimeCode.ts";
|
|
30
30
|
import { focusedElementContext } from "./focusedElementContext.ts";
|
|
31
31
|
import { type FocusContext, focusContext } from "./focusContext.ts";
|
|
32
|
-
import { playingContext } from "./playingContext.ts";
|
|
32
|
+
import { playingContext, loopContext } from "./playingContext.ts";
|
|
33
33
|
import type { EFWorkbench } from "./EFWorkbench.ts";
|
|
34
34
|
import type { EFPreview } from "./EFPreview.ts";
|
|
35
35
|
|
|
@@ -86,18 +86,25 @@ class FilmstripItem extends TWMixin(LitElement) {
|
|
|
86
86
|
@property({ type: Number })
|
|
87
87
|
pixelsPerMs = 0.04;
|
|
88
88
|
|
|
89
|
-
get
|
|
89
|
+
get gutterStyles() {
|
|
90
90
|
return {
|
|
91
91
|
position: "relative",
|
|
92
|
-
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 {
|
|
93
99
|
width: `${this.pixelsPerMs * this.element.durationMs}px`,
|
|
100
|
+
left: `${this.pixelsPerMs * this.element.trimStartMs}px`,
|
|
94
101
|
};
|
|
95
102
|
}
|
|
96
103
|
|
|
97
104
|
render() {
|
|
98
|
-
return html
|
|
105
|
+
return html`<div style=${styleMap(this.gutterStyles)}>
|
|
99
106
|
<div
|
|
100
|
-
class="
|
|
107
|
+
class="bg-slate-300"
|
|
101
108
|
?data-focused=${this.isFocused}
|
|
102
109
|
@mouseenter=${() => {
|
|
103
110
|
if (this.focusContext) {
|
|
@@ -110,7 +117,13 @@ class FilmstripItem extends TWMixin(LitElement) {
|
|
|
110
117
|
}
|
|
111
118
|
}}
|
|
112
119
|
>
|
|
113
|
-
|
|
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>
|
|
114
127
|
</div>
|
|
115
128
|
${this.renderChildren()}
|
|
116
129
|
</div>`;
|
|
@@ -500,6 +513,10 @@ export class EFFilmstrip extends TWMixin(LitElement) {
|
|
|
500
513
|
@state()
|
|
501
514
|
playing?: boolean;
|
|
502
515
|
|
|
516
|
+
@consume({ context: loopContext, subscribe: true })
|
|
517
|
+
@state()
|
|
518
|
+
loop?: boolean;
|
|
519
|
+
|
|
503
520
|
timegroupController?: TimegroupController;
|
|
504
521
|
|
|
505
522
|
@state()
|
|
@@ -532,12 +549,13 @@ export class EFFilmstrip extends TWMixin(LitElement) {
|
|
|
532
549
|
#handleKeyPress = (event: KeyboardEvent) => {
|
|
533
550
|
// On spacebar, toggle playback
|
|
534
551
|
if (event.key === " ") {
|
|
552
|
+
const [target] = event.composedPath();
|
|
535
553
|
// CSS selector to match all interactive elements
|
|
536
554
|
const interactiveSelector =
|
|
537
555
|
"input, textarea, button, select, a, [contenteditable]";
|
|
538
556
|
|
|
539
557
|
// Check if the event target or its ancestor matches an interactive element
|
|
540
|
-
const closestInteractive = (
|
|
558
|
+
const closestInteractive = (target as HTMLElement | null)?.closest(
|
|
541
559
|
interactiveSelector,
|
|
542
560
|
);
|
|
543
561
|
if (closestInteractive) {
|
|
@@ -676,27 +694,8 @@ export class EFFilmstrip extends TWMixin(LitElement) {
|
|
|
676
694
|
/>
|
|
677
695
|
<code>${msToTimeCode(this.currentTimeMs, true)} </code> /
|
|
678
696
|
<code>${msToTimeCode(target?.durationMs ?? 0, true)}</code>
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
? html`<button
|
|
682
|
-
@click=${() => {
|
|
683
|
-
if (this.#contextElement) {
|
|
684
|
-
this.#contextElement.playing = false;
|
|
685
|
-
}
|
|
686
|
-
}}
|
|
687
|
-
>
|
|
688
|
-
⏸️
|
|
689
|
-
</button>`
|
|
690
|
-
: html`<button
|
|
691
|
-
@click=${() => {
|
|
692
|
-
if (this.#contextElement) {
|
|
693
|
-
this.#contextElement.playing = true;
|
|
694
|
-
}
|
|
695
|
-
}}
|
|
696
|
-
>
|
|
697
|
-
▶️
|
|
698
|
-
</button>`
|
|
699
|
-
}
|
|
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>
|
|
700
699
|
</div>
|
|
701
700
|
<div
|
|
702
701
|
class="z-10 pl-1 pr-1 pt-2 shadow shadow-slate-600 overflow-auto"
|
|
@@ -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
|
@@ -78,21 +78,21 @@ export class EFWorkbench extends ContextMixin(TWMixin(LitElement)) {
|
|
|
78
78
|
>
|
|
79
79
|
<div
|
|
80
80
|
${ref(this.stageRef)}
|
|
81
|
-
class="relative grid h-full w-full
|
|
81
|
+
class="relative grid h-full w-full justify-center overflow-hidden"
|
|
82
82
|
@wheel=${this.handleStageWheel}
|
|
83
83
|
>
|
|
84
84
|
<slot
|
|
85
85
|
${ref(this.canvasRef)}
|
|
86
|
-
class="inline-block"
|
|
87
86
|
name="canvas"
|
|
87
|
+
class="inline-block"
|
|
88
88
|
></slot>
|
|
89
89
|
<div
|
|
90
|
-
class="border border-blue-500 bg-blue-200 bg-opacity-20"
|
|
90
|
+
class="border border-blue-500 bg-blue-200 bg-opacity-20 absolute"
|
|
91
91
|
${ref(this.focusOverlay)}
|
|
92
92
|
></div>
|
|
93
93
|
</div>
|
|
94
94
|
|
|
95
|
-
<slot class="overflow" name="timeline"></slot>
|
|
95
|
+
<slot class="overflow inline-block" name="timeline"></slot>
|
|
96
96
|
</div>
|
|
97
97
|
`;
|
|
98
98
|
}
|