@editframe/elements 0.5.0-beta.6 → 0.5.0-beta.8
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/src/EF_FRAMEGEN.mjs +130 -0
- package/dist/elements/src/EF_INTERACTIVE.mjs +4 -0
- package/dist/elements/{elements → src/elements}/EFAudio.mjs +20 -0
- package/dist/elements/{elements → src/elements}/EFCaptions.mjs +3 -0
- package/dist/elements/{elements → src/elements}/EFImage.mjs +15 -3
- package/dist/elements/{elements → src/elements}/EFMedia.mjs +81 -4
- package/dist/elements/{elements → src/elements}/EFTemporal.mjs +29 -1
- package/dist/elements/{elements → src/elements}/EFTimegroup.mjs +124 -0
- package/dist/elements/{elements → src/elements}/EFVideo.mjs +10 -0
- package/dist/elements/{elements → src/elements}/EFWaveform.mjs +41 -24
- package/dist/elements/{elements.mjs → src/elements.mjs} +2 -1
- package/dist/elements/{gui → src/gui}/EFFilmstrip.mjs +3 -2
- package/dist/elements/{gui → src/gui}/EFWorkbench.mjs +51 -63
- package/dist/elements/{gui → src/gui}/TWMixin.css.mjs +1 -1
- package/dist/style.css +3 -0
- package/dist/util/awaitAnimationFrame.mjs +11 -0
- package/docker-compose.yaml +17 -0
- package/package.json +2 -2
- package/src/EF_FRAMEGEN.ts +208 -0
- package/src/EF_INTERACTIVE.ts +2 -0
- package/src/elements/CrossUpdateController.ts +18 -0
- package/src/elements/EFAudio.ts +42 -0
- package/src/elements/EFCaptions.ts +202 -0
- package/src/elements/EFImage.ts +70 -0
- package/src/elements/EFMedia.ts +395 -0
- package/src/elements/EFSourceMixin.ts +57 -0
- package/src/elements/EFTemporal.ts +246 -0
- package/src/elements/EFTimegroup.browsertest.ts +360 -0
- package/src/elements/EFTimegroup.ts +394 -0
- package/src/elements/EFTimeline.ts +13 -0
- package/src/elements/EFVideo.ts +114 -0
- package/src/elements/EFWaveform.ts +407 -0
- package/src/elements/FetchMixin.ts +18 -0
- package/src/elements/TimegroupController.ts +25 -0
- package/src/elements/buildLitFixture.ts +13 -0
- package/src/elements/durationConverter.ts +6 -0
- package/src/elements/parseTimeToMs.ts +10 -0
- package/src/elements/util.ts +24 -0
- package/src/gui/EFFilmstrip.ts +702 -0
- package/src/gui/EFWorkbench.ts +242 -0
- package/src/gui/TWMixin.css +3 -0
- package/src/gui/TWMixin.ts +27 -0
- package/src/util.d.ts +1 -0
- package/dist/elements/elements.css.mjs +0 -1
- /package/dist/elements/{elements → src/elements}/CrossUpdateController.mjs +0 -0
- /package/dist/elements/{elements → src/elements}/EFSourceMixin.mjs +0 -0
- /package/dist/elements/{elements → src/elements}/EFTimeline.mjs +0 -0
- /package/dist/elements/{elements → src/elements}/FetchMixin.mjs +0 -0
- /package/dist/elements/{elements → src/elements}/TimegroupController.mjs +0 -0
- /package/dist/elements/{elements → src/elements}/durationConverter.mjs +0 -0
- /package/dist/elements/{elements → src/elements}/parseTimeToMs.mjs +0 -0
- /package/dist/elements/{elements → src/elements}/util.mjs +0 -0
- /package/dist/elements/{gui → src/gui}/TWMixin.mjs +0 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
import { EFAudio } from "./EFAudio";
|
|
2
|
+
|
|
3
|
+
import { LitElement, html, css } from "lit";
|
|
4
|
+
import { customElement, property } from "lit/decorators.js";
|
|
5
|
+
import { EFVideo } from "./EFVideo";
|
|
6
|
+
import { EFTemporal } from "./EFTemporal";
|
|
7
|
+
import { CrossUpdateController } from "./CrossUpdateController";
|
|
8
|
+
import { TWMixin } from "../gui/TWMixin";
|
|
9
|
+
import { Task } from "@lit/task";
|
|
10
|
+
import * as d3 from "d3";
|
|
11
|
+
import { Ref, createRef, ref } from "lit/directives/ref.js";
|
|
12
|
+
import { EF_INTERACTIVE } from "../EF_INTERACTIVE";
|
|
13
|
+
|
|
14
|
+
@customElement("ef-waveform")
|
|
15
|
+
export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
|
|
16
|
+
static styles = [];
|
|
17
|
+
svgRef: Ref<SVGElement> = createRef();
|
|
18
|
+
createRenderRoot() {
|
|
19
|
+
return this;
|
|
20
|
+
}
|
|
21
|
+
render() {
|
|
22
|
+
return html` <svg ${ref(this.svgRef)} class="h-full w-full" store></svg> `;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@property({
|
|
26
|
+
type: String,
|
|
27
|
+
attribute: "mode",
|
|
28
|
+
})
|
|
29
|
+
mode:
|
|
30
|
+
| "roundBars"
|
|
31
|
+
| "bars"
|
|
32
|
+
| "bricks"
|
|
33
|
+
| "equalizer"
|
|
34
|
+
| "curve"
|
|
35
|
+
| "line"
|
|
36
|
+
| "pixel"
|
|
37
|
+
| "wave" = "bars";
|
|
38
|
+
|
|
39
|
+
@property({ type: String })
|
|
40
|
+
color: string = "currentColor";
|
|
41
|
+
|
|
42
|
+
connectedCallback() {
|
|
43
|
+
super.connectedCallback();
|
|
44
|
+
if (this.target) {
|
|
45
|
+
new CrossUpdateController(this.target, this);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
protected drawBars(svg: SVGElement, frequencyData: Uint8Array) {
|
|
50
|
+
const waveWidth = svg.clientWidth * devicePixelRatio;
|
|
51
|
+
const waveHeight = svg.clientHeight;
|
|
52
|
+
const waveLeft = 0;
|
|
53
|
+
const waveRight = waveWidth;
|
|
54
|
+
|
|
55
|
+
const barX = d3
|
|
56
|
+
.scaleBand()
|
|
57
|
+
.paddingInner(0.5)
|
|
58
|
+
.paddingOuter(0.01)
|
|
59
|
+
.domain(d3.range(frequencyData.length).map((n) => String(n)))
|
|
60
|
+
.rangeRound([waveLeft, waveRight]);
|
|
61
|
+
|
|
62
|
+
const height = d3
|
|
63
|
+
.scaleLinear()
|
|
64
|
+
.domain([0, 255])
|
|
65
|
+
.range([0, waveHeight / 2]);
|
|
66
|
+
|
|
67
|
+
const baseline = waveHeight / 2;
|
|
68
|
+
|
|
69
|
+
const bars = d3.select(svg).selectAll("rect").data(frequencyData);
|
|
70
|
+
const minBarHeight = 2;
|
|
71
|
+
|
|
72
|
+
bars
|
|
73
|
+
.enter()
|
|
74
|
+
.append("rect")
|
|
75
|
+
.merge(bars)
|
|
76
|
+
.attr("x", (_, i) => barX(String(i)))
|
|
77
|
+
.attr("y", (value) => baseline - height(value))
|
|
78
|
+
.attr("width", barX.bandwidth() / 1.2)
|
|
79
|
+
.attr("height", (value) => Math.max(height(value) * 2, minBarHeight));
|
|
80
|
+
|
|
81
|
+
bars.exit().remove();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
protected drawBricks(svg: SVGElement, frequencyData: Uint8Array) {
|
|
85
|
+
const waveWidth = svg.clientWidth * devicePixelRatio;
|
|
86
|
+
const waveHeight = svg.clientHeight * devicePixelRatio;
|
|
87
|
+
const brickWidth = waveWidth / frequencyData.length;
|
|
88
|
+
const brickHeightFactor = waveHeight / 255 / 2;
|
|
89
|
+
const brickPadding = 2;
|
|
90
|
+
const midHeight = waveHeight / 2;
|
|
91
|
+
|
|
92
|
+
d3.select(svg)
|
|
93
|
+
.selectAll("line.brickBaseLine")
|
|
94
|
+
.data([0])
|
|
95
|
+
.join("line")
|
|
96
|
+
.attr("class", "brickBaseLine")
|
|
97
|
+
.attr("x1", 0)
|
|
98
|
+
.attr("x2", waveWidth)
|
|
99
|
+
.attr("y1", midHeight)
|
|
100
|
+
.attr("y2", midHeight)
|
|
101
|
+
.attr("stroke", "currentColor")
|
|
102
|
+
.attr("stroke-width", 4)
|
|
103
|
+
.attr("stroke-dasharray", "2");
|
|
104
|
+
|
|
105
|
+
d3.select(svg)
|
|
106
|
+
.selectAll("rect.brick")
|
|
107
|
+
.data(frequencyData)
|
|
108
|
+
.join("rect")
|
|
109
|
+
.attr("class", "brick")
|
|
110
|
+
.attr("x", (_d, i) => i * brickWidth)
|
|
111
|
+
.attr("y", (d) => midHeight - d * brickHeightFactor)
|
|
112
|
+
.attr("width", brickWidth - brickPadding)
|
|
113
|
+
.attr("height", (d) => d * brickHeightFactor * 2);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
protected drawLine(svg: SVGElement, frequencyData: Uint8Array) {
|
|
117
|
+
const waveWidth = svg.clientWidth * devicePixelRatio;
|
|
118
|
+
const waveHeight = svg.clientHeight * devicePixelRatio;
|
|
119
|
+
|
|
120
|
+
const xScale = d3
|
|
121
|
+
.scaleLinear()
|
|
122
|
+
.domain([0, frequencyData.length - 1])
|
|
123
|
+
.range([0, waveWidth]);
|
|
124
|
+
|
|
125
|
+
const yScale = d3.scaleLinear().domain([0, 255]).range([waveHeight, 0]);
|
|
126
|
+
|
|
127
|
+
const lineGenerator = d3
|
|
128
|
+
.line<Uint8Array[number]>()
|
|
129
|
+
.x((_, i) => xScale(i))
|
|
130
|
+
.y((d) => yScale(d));
|
|
131
|
+
|
|
132
|
+
const pathData = lineGenerator(frequencyData);
|
|
133
|
+
|
|
134
|
+
d3.select(svg)
|
|
135
|
+
.selectAll("path.line")
|
|
136
|
+
.data([frequencyData])
|
|
137
|
+
.join("path")
|
|
138
|
+
.attr("class", "line")
|
|
139
|
+
.attr("d", pathData)
|
|
140
|
+
.attr("fill", "none")
|
|
141
|
+
.attr("stroke", "currentColor")
|
|
142
|
+
.attr("stroke-width", 4);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
protected drawRoundBars(svg: SVGElement, frequencyData: Uint8Array) {
|
|
146
|
+
const waveWidth = svg.clientWidth * devicePixelRatio;
|
|
147
|
+
const waveHeight = svg.clientHeight;
|
|
148
|
+
const waveLeft = 0;
|
|
149
|
+
const waveRight = waveWidth;
|
|
150
|
+
|
|
151
|
+
const barX = d3
|
|
152
|
+
.scaleBand()
|
|
153
|
+
.paddingInner(0.5)
|
|
154
|
+
.paddingOuter(0.01)
|
|
155
|
+
.domain(d3.range(frequencyData.length).map((n) => String(n)))
|
|
156
|
+
.rangeRound([waveLeft, waveRight]);
|
|
157
|
+
|
|
158
|
+
const height = d3
|
|
159
|
+
.scaleLinear()
|
|
160
|
+
.domain([0, 255])
|
|
161
|
+
.range([0, waveHeight / 2]);
|
|
162
|
+
|
|
163
|
+
const baseline = waveHeight / 2;
|
|
164
|
+
|
|
165
|
+
const bars = d3.select(svg).selectAll("rect").data(frequencyData);
|
|
166
|
+
const minBarHeight = 2;
|
|
167
|
+
|
|
168
|
+
bars
|
|
169
|
+
.enter()
|
|
170
|
+
.append("rect")
|
|
171
|
+
.merge(bars)
|
|
172
|
+
.attr("x", (_, i) => barX(String(i)))
|
|
173
|
+
.attr("y", (value) => baseline - height(value))
|
|
174
|
+
.attr("width", barX.bandwidth() / 1.2)
|
|
175
|
+
.attr("height", (value) => Math.max(height(value) * 2, minBarHeight))
|
|
176
|
+
.attr("rx", barX.bandwidth())
|
|
177
|
+
.attr("ry", barX.bandwidth());
|
|
178
|
+
|
|
179
|
+
bars.exit().remove();
|
|
180
|
+
}
|
|
181
|
+
protected drawEqualizer(svg: SVGElement, frequencyData: Uint8Array) {
|
|
182
|
+
const waveWidth = svg.clientWidth * devicePixelRatio;
|
|
183
|
+
const waveHeight = svg.clientHeight * devicePixelRatio;
|
|
184
|
+
const barWidth = waveWidth / frequencyData.length;
|
|
185
|
+
const barPadding = 1;
|
|
186
|
+
const minHeight = 1;
|
|
187
|
+
|
|
188
|
+
const heightScale = d3
|
|
189
|
+
.scaleLinear()
|
|
190
|
+
.domain([0, 255])
|
|
191
|
+
.range([0, waveHeight / 2]);
|
|
192
|
+
|
|
193
|
+
d3.select(svg)
|
|
194
|
+
.selectAll("line.equalizerBaseLine")
|
|
195
|
+
.data([0])
|
|
196
|
+
.join("line")
|
|
197
|
+
.attr("class", "equalizerBaseLine")
|
|
198
|
+
.attr("x1", 0)
|
|
199
|
+
.attr("x2", waveWidth)
|
|
200
|
+
.attr("y1", waveHeight / 2)
|
|
201
|
+
.attr("y2", waveHeight / 2)
|
|
202
|
+
.attr("stroke-width", 2);
|
|
203
|
+
|
|
204
|
+
d3.select(svg)
|
|
205
|
+
.selectAll("rect.equalizerBar")
|
|
206
|
+
.data(frequencyData)
|
|
207
|
+
.join("rect")
|
|
208
|
+
.attr("class", "equalizerBar")
|
|
209
|
+
.attr("x", (d, i) => i * barWidth + barPadding / 2)
|
|
210
|
+
.attr(
|
|
211
|
+
"y",
|
|
212
|
+
(d) => waveHeight / 2 - Math.max(heightScale(d) / 2, minHeight),
|
|
213
|
+
)
|
|
214
|
+
.attr("width", barWidth - barPadding)
|
|
215
|
+
.attr("height", (d) => Math.max(heightScale(d), minHeight));
|
|
216
|
+
|
|
217
|
+
d3.select(svg)
|
|
218
|
+
.selectAll("rect.equalizerBar")
|
|
219
|
+
.transition()
|
|
220
|
+
.duration(100)
|
|
221
|
+
.attr(
|
|
222
|
+
"y",
|
|
223
|
+
(d) => waveHeight / 2 - Math.max(heightScale(d) / 2, minHeight),
|
|
224
|
+
)
|
|
225
|
+
.attr("height", (d) => Math.max(heightScale(d), minHeight));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
protected drawCurve(svg: SVGElement, frequencyData: Uint8Array) {
|
|
229
|
+
const waveWidth = svg.clientWidth * devicePixelRatio;
|
|
230
|
+
const waveHeight = svg.clientHeight * devicePixelRatio;
|
|
231
|
+
|
|
232
|
+
const xScale = d3
|
|
233
|
+
.scaleLinear()
|
|
234
|
+
.domain([0, frequencyData.length])
|
|
235
|
+
.range([0, waveWidth]);
|
|
236
|
+
|
|
237
|
+
const yScale = d3.scaleLinear().domain([0, 255]).range([waveHeight, 0]);
|
|
238
|
+
|
|
239
|
+
const curveGenerator = d3
|
|
240
|
+
.line<Uint8Array[number]>()
|
|
241
|
+
.x((_, i) => xScale(i))
|
|
242
|
+
.y((d) => yScale(d))
|
|
243
|
+
.curve(d3.curveNatural);
|
|
244
|
+
|
|
245
|
+
const pathData = curveGenerator(frequencyData);
|
|
246
|
+
|
|
247
|
+
d3.select(svg)
|
|
248
|
+
.selectAll("path.curve")
|
|
249
|
+
.data([frequencyData])
|
|
250
|
+
.join("path")
|
|
251
|
+
.attr("class", "curve")
|
|
252
|
+
.attr("d", pathData)
|
|
253
|
+
.attr("fill", "none")
|
|
254
|
+
.attr("stroke", "currentColor")
|
|
255
|
+
.attr("stroke-width", 4);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
protected drawPixel(svg: SVGElement, frequencyData: Uint8Array) {
|
|
259
|
+
const waveWidth = svg.clientWidth * devicePixelRatio;
|
|
260
|
+
const waveHeight = svg.clientHeight;
|
|
261
|
+
const baseline = waveHeight / 2;
|
|
262
|
+
|
|
263
|
+
const barX = d3
|
|
264
|
+
.scaleBand()
|
|
265
|
+
.domain(d3.range(frequencyData.length).map(String))
|
|
266
|
+
.rangeRound([0, waveWidth])
|
|
267
|
+
.paddingInner(0.03)
|
|
268
|
+
.paddingOuter(0.02);
|
|
269
|
+
|
|
270
|
+
const height = d3.scaleLinear().domain([0, 255]).range([0, baseline]);
|
|
271
|
+
|
|
272
|
+
const bars = d3.select(svg).selectAll("rect").data(frequencyData);
|
|
273
|
+
|
|
274
|
+
bars
|
|
275
|
+
.enter()
|
|
276
|
+
.append("rect")
|
|
277
|
+
.merge(bars)
|
|
278
|
+
.attr("x", (_, i) => barX(String(i)))
|
|
279
|
+
.attr("y", (value) => baseline - height(value))
|
|
280
|
+
.attr("width", barX.bandwidth())
|
|
281
|
+
.attr("height", (value) => height(value) * 2);
|
|
282
|
+
|
|
283
|
+
bars.exit().remove();
|
|
284
|
+
}
|
|
285
|
+
protected drawWave(svg: SVGElement, frequencyData: Uint8Array) {
|
|
286
|
+
const waveWidth = svg.clientWidth * devicePixelRatio;
|
|
287
|
+
const waveHeight = svg.clientHeight;
|
|
288
|
+
|
|
289
|
+
const barX = d3
|
|
290
|
+
.scaleBand()
|
|
291
|
+
.domain(d3.range(frequencyData.length).map(String))
|
|
292
|
+
.rangeRound([0, waveWidth])
|
|
293
|
+
.paddingInner(0.03)
|
|
294
|
+
.paddingOuter(0.02);
|
|
295
|
+
|
|
296
|
+
const height = d3.scaleLinear().domain([0, 255]).range([0, waveHeight / 2]);
|
|
297
|
+
|
|
298
|
+
d3.select(svg)
|
|
299
|
+
.selectAll("line.baseline")
|
|
300
|
+
.data([0])
|
|
301
|
+
.join("line")
|
|
302
|
+
.attr("class", "baseline")
|
|
303
|
+
.attr("x1", (_, i) => barX(String(i)))
|
|
304
|
+
.attr("x2", waveWidth)
|
|
305
|
+
.attr("y1", waveHeight / 2)
|
|
306
|
+
.attr("y2", waveHeight / 2)
|
|
307
|
+
.attr("stroke", "currentColor")
|
|
308
|
+
.attr("stroke-width", 2);
|
|
309
|
+
|
|
310
|
+
const bars = d3.select(svg).selectAll("rect").data(frequencyData);
|
|
311
|
+
|
|
312
|
+
bars
|
|
313
|
+
.enter()
|
|
314
|
+
.append("rect")
|
|
315
|
+
.merge(bars)
|
|
316
|
+
.attr("x", (_, i) => barX(String(i)))
|
|
317
|
+
.attr("y", (value) => waveHeight / 2 - height(value))
|
|
318
|
+
.attr("width", barX.bandwidth())
|
|
319
|
+
.attr("height", (value) => height(value) * 2)
|
|
320
|
+
|
|
321
|
+
bars.exit().remove();
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
// update(arg) {
|
|
326
|
+
// super.update(arg);
|
|
327
|
+
// }
|
|
328
|
+
|
|
329
|
+
frameTask = new Task(this, {
|
|
330
|
+
autoRun: EF_INTERACTIVE,
|
|
331
|
+
args: () => [this.target.audioBufferTask.status] as const,
|
|
332
|
+
task: async () => {
|
|
333
|
+
await this.target.audioBufferTask.taskComplete;
|
|
334
|
+
},
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
protected async updated() {
|
|
338
|
+
const svg = this.svgRef.value;
|
|
339
|
+
if (!svg) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
if (!this.target.audioBufferTask.value) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
if (this.target.ownCurrentTimeMs > 0) {
|
|
346
|
+
const audioContext = new OfflineAudioContext(2, 48000 / 25, 48000);
|
|
347
|
+
const audioBufferSource = audioContext.createBufferSource();
|
|
348
|
+
audioBufferSource.buffer = this.target.audioBufferTask.value.buffer;
|
|
349
|
+
const analyser = audioContext.createAnalyser();
|
|
350
|
+
analyser.fftSize = 256;
|
|
351
|
+
audioBufferSource.connect(analyser);
|
|
352
|
+
|
|
353
|
+
audioBufferSource.start(
|
|
354
|
+
0,
|
|
355
|
+
Math.max(
|
|
356
|
+
0,
|
|
357
|
+
(this.target.ownCurrentTimeMs -
|
|
358
|
+
this.target.audioBufferTask.value.startOffsetMs) /
|
|
359
|
+
1000,
|
|
360
|
+
),
|
|
361
|
+
48000 / 1000,
|
|
362
|
+
);
|
|
363
|
+
await audioContext.startRendering();
|
|
364
|
+
const frequencyData = new Uint8Array(analyser.frequencyBinCount);
|
|
365
|
+
analyser.getByteFrequencyData(frequencyData);
|
|
366
|
+
const rect = this.getBoundingClientRect();
|
|
367
|
+
|
|
368
|
+
svg.setAttribute("width", (rect.width * devicePixelRatio).toString());
|
|
369
|
+
svg.setAttribute("height", (rect.height * devicePixelRatio).toString());
|
|
370
|
+
|
|
371
|
+
switch (this.mode) {
|
|
372
|
+
case "bars":
|
|
373
|
+
this.drawBars(svg, frequencyData);
|
|
374
|
+
break;
|
|
375
|
+
case "bricks":
|
|
376
|
+
this.drawBricks(svg, frequencyData);
|
|
377
|
+
break;
|
|
378
|
+
case "curve":
|
|
379
|
+
this.drawCurve(svg, frequencyData);
|
|
380
|
+
break;
|
|
381
|
+
case "line":
|
|
382
|
+
this.drawLine(svg, frequencyData);
|
|
383
|
+
break;
|
|
384
|
+
case "pixel":
|
|
385
|
+
this.drawPixel(svg, frequencyData);
|
|
386
|
+
break;
|
|
387
|
+
case "wave":
|
|
388
|
+
this.drawWave(svg, frequencyData);
|
|
389
|
+
break;
|
|
390
|
+
case "roundBars":
|
|
391
|
+
this.drawRoundBars(svg, frequencyData);
|
|
392
|
+
break;
|
|
393
|
+
case "equalizer":
|
|
394
|
+
this.drawEqualizer(svg, frequencyData);
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
get target() {
|
|
401
|
+
const target = document.querySelector(this.getAttribute("target") ?? "");
|
|
402
|
+
if (target instanceof EFAudio || target instanceof EFVideo) {
|
|
403
|
+
return target;
|
|
404
|
+
}
|
|
405
|
+
throw new Error("Invalid target, must be an EFAudio element");
|
|
406
|
+
}
|
|
407
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { consume } from "@lit/context";
|
|
2
|
+
import { LitElement } from "lit";
|
|
3
|
+
import { fetchContext } from "../gui/EFWorkbench";
|
|
4
|
+
import { state } from "lit/decorators/state.js";
|
|
5
|
+
|
|
6
|
+
export declare class FetchMixinInterface {
|
|
7
|
+
fetch: typeof fetch;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function FetchMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
11
|
+
class FetchElement extends superClass {
|
|
12
|
+
@consume({ context: fetchContext, subscribe: true })
|
|
13
|
+
@state()
|
|
14
|
+
fetch = fetch.bind(window);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return FetchElement as Constructor<FetchMixinInterface> & T;
|
|
18
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ReactiveController, LitElement } from "lit";
|
|
2
|
+
import { EFTimegroup } from "./EFTimegroup";
|
|
3
|
+
|
|
4
|
+
export class TimegroupController implements ReactiveController {
|
|
5
|
+
constructor(
|
|
6
|
+
private host: EFTimegroup,
|
|
7
|
+
private child: { currentTimeMs: number; startTimeMs?: number } & LitElement,
|
|
8
|
+
) {
|
|
9
|
+
this.host.addController(this);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
remove() {
|
|
13
|
+
this.host.removeController(this);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
hostDisconnected(): void {
|
|
17
|
+
this.host.removeController(this);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
hostUpdated(): void {
|
|
21
|
+
this.child.requestUpdate();
|
|
22
|
+
this.child.currentTimeMs =
|
|
23
|
+
this.host.currentTimeMs - (this.child.startTimeMs ?? 0);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { render, TemplateResult } from "lit";
|
|
2
|
+
|
|
3
|
+
export const buildLitFixture = <EL extends HTMLElement>(
|
|
4
|
+
templateResult: TemplateResult<1>,
|
|
5
|
+
) => {
|
|
6
|
+
return async (
|
|
7
|
+
{ container }: { container: HTMLDivElement },
|
|
8
|
+
use: (value: EL) => Promise<any>,
|
|
9
|
+
) => {
|
|
10
|
+
render(templateResult, container);
|
|
11
|
+
await use(container.firstElementChild as EL);
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const parseTimeToMs = (time: string) => {
|
|
2
|
+
if (time.endsWith("ms")) {
|
|
3
|
+
return parseFloat(time);
|
|
4
|
+
}
|
|
5
|
+
if (time.endsWith("s")) {
|
|
6
|
+
return parseFloat(time) * 1000;
|
|
7
|
+
} else {
|
|
8
|
+
throw new Error("Time must be in milliseconds or seconds (10s, 10000ms)");
|
|
9
|
+
}
|
|
10
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { EFTimegroup } from "./EFTimegroup";
|
|
2
|
+
|
|
3
|
+
export const getRootTimeGroup = (element: Element): EFTimegroup | null => {
|
|
4
|
+
let bestCandidate: EFTimegroup | null = null;
|
|
5
|
+
|
|
6
|
+
let currentElement: Element | null = element;
|
|
7
|
+
while (currentElement) {
|
|
8
|
+
if (currentElement instanceof EFTimegroup) {
|
|
9
|
+
bestCandidate = currentElement;
|
|
10
|
+
}
|
|
11
|
+
currentElement = currentElement.parentElement;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return bestCandidate;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const getStartTimeMs = (element: Element): number => {
|
|
18
|
+
const nearestTimeGroup = element.closest("ef-timegroup");
|
|
19
|
+
if (!(nearestTimeGroup instanceof EFTimegroup)) {
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return nearestTimeGroup.startTimeMs;
|
|
24
|
+
};
|