@editframe/elements 0.11.0-beta.1 → 0.11.0-beta.14
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 +8 -15
- package/dist/elements/EFMedia.d.ts +2 -2
- package/dist/elements/EFTemporal.browsertest.d.ts +10 -0
- package/dist/elements/EFTemporal.d.ts +10 -2
- package/dist/elements/EFTimegroup.browsertest.d.ts +4 -0
- package/dist/elements/EFTimegroup.d.ts +12 -1
- package/dist/elements/EFWaveform.d.ts +3 -3
- package/dist/elements/durationConverter.d.ts +4 -0
- package/dist/elements/src/EF_FRAMEGEN.js +24 -26
- package/dist/elements/src/elements/EFImage.js +3 -7
- package/dist/elements/src/elements/EFMedia.js +27 -9
- package/dist/elements/src/elements/EFTemporal.js +106 -13
- package/dist/elements/src/elements/EFTimegroup.js +26 -5
- package/dist/elements/src/elements/EFVideo.js +7 -7
- package/dist/elements/src/elements/EFWaveform.js +14 -8
- package/dist/elements/src/gui/ContextMixin.js +22 -6
- package/dist/elements/src/gui/EFFilmstrip.js +28 -8
- package/dist/elements/src/gui/EFTogglePlay.js +38 -6
- package/dist/elements/src/gui/EFWorkbench.js +1 -24
- package/dist/elements/src/gui/TWMixin.css.js +1 -1
- package/dist/gui/ContextMixin.d.ts +1 -0
- package/dist/gui/EFFilmstrip.d.ts +4 -4
- package/dist/gui/EFTogglePlay.d.ts +4 -0
- package/dist/gui/EFWorkbench.d.ts +0 -1
- package/dist/style.css +15 -4
- package/package.json +2 -2
- package/src/elements/EFImage.ts +4 -8
- package/src/elements/EFMedia.browsertest.ts +231 -2
- package/src/elements/EFMedia.ts +48 -9
- package/src/elements/EFTemporal.browsertest.ts +79 -0
- package/src/elements/EFTemporal.ts +139 -6
- package/src/elements/EFTimegroup.browsertest.ts +38 -1
- package/src/elements/EFTimegroup.ts +28 -5
- package/src/elements/EFVideo.ts +7 -8
- package/src/elements/EFWaveform.ts +14 -9
- package/src/elements/durationConverter.ts +4 -0
- package/src/gui/ContextMixin.browsertest.ts +28 -2
- package/src/gui/ContextMixin.ts +25 -7
- package/src/gui/EFFilmstrip.ts +37 -17
- package/src/gui/EFTogglePlay.ts +38 -7
- package/src/gui/EFWorkbench.ts +3 -36
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { describe, expect, test, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import { v4 } from "uuid";
|
|
3
1
|
import { customElement } from "lit/decorators.js";
|
|
2
|
+
import { v4 } from "uuid";
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
4
4
|
import { EFMedia } from "./EFMedia.ts";
|
|
5
5
|
import "../gui/EFWorkbench.ts";
|
|
6
6
|
import "../gui/EFPreview.ts";
|
|
7
|
+
import { createTestFragmentIndex } from "TEST/createTestFragmentIndex.ts";
|
|
8
|
+
import { useMockWorker } from "TEST/useMockWorker.ts";
|
|
9
|
+
import { http, HttpResponse } from "msw";
|
|
7
10
|
|
|
8
11
|
@customElement("test-media")
|
|
9
12
|
class TestMedia extends EFMedia {}
|
|
@@ -15,6 +18,8 @@ declare global {
|
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
describe("EFMedia", () => {
|
|
21
|
+
const worker = useMockWorker();
|
|
22
|
+
|
|
18
23
|
test("should be defined", () => {
|
|
19
24
|
const element = document.createElement("test-media");
|
|
20
25
|
expect(element.tagName).toBe("TEST-MEDIA");
|
|
@@ -107,4 +112,228 @@ describe("EFMedia", () => {
|
|
|
107
112
|
);
|
|
108
113
|
});
|
|
109
114
|
});
|
|
115
|
+
|
|
116
|
+
describe("calculating duration", () => {
|
|
117
|
+
test("Computes duration from track fragment index", async () => {
|
|
118
|
+
// Mock the request for the track fragment index, responds with a 10 second duration
|
|
119
|
+
worker.use(
|
|
120
|
+
http.get("/@ef-track-fragment-index//assets/10s-bars.mp4", () => {
|
|
121
|
+
return HttpResponse.json(
|
|
122
|
+
createTestFragmentIndex({
|
|
123
|
+
audio: { duration: 10_000 },
|
|
124
|
+
video: { duration: 10_000 },
|
|
125
|
+
}),
|
|
126
|
+
);
|
|
127
|
+
}),
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const element = document.createElement("test-media");
|
|
131
|
+
element.src = "/assets/10s-bars.mp4";
|
|
132
|
+
|
|
133
|
+
const preview = document.createElement("ef-preview");
|
|
134
|
+
preview.appendChild(element);
|
|
135
|
+
document.body.appendChild(preview);
|
|
136
|
+
|
|
137
|
+
// Await the next tick to ensure the element has a chance to load the track fragment
|
|
138
|
+
await Promise.resolve();
|
|
139
|
+
|
|
140
|
+
await element.trackFragmentIndexLoader.taskComplete;
|
|
141
|
+
|
|
142
|
+
expect(element.durationMs).toBe(10_000);
|
|
143
|
+
});
|
|
144
|
+
test("Computes duration from track fragment index sourcein", async () => {
|
|
145
|
+
// Mock the request for the track fragment index, responds with a 10 second duration
|
|
146
|
+
worker.use(
|
|
147
|
+
http.get("/@ef-track-fragment-index//assets/10s-bars.mp4", () => {
|
|
148
|
+
return HttpResponse.json(
|
|
149
|
+
createTestFragmentIndex({
|
|
150
|
+
audio: { duration: 10_000 },
|
|
151
|
+
video: { duration: 10_000 },
|
|
152
|
+
}),
|
|
153
|
+
);
|
|
154
|
+
}),
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const timegroup = document.createElement("ef-timegroup");
|
|
158
|
+
timegroup.mode = "sequence";
|
|
159
|
+
const element = document.createElement("test-media");
|
|
160
|
+
element.src = "/assets/10s-bars.mp4";
|
|
161
|
+
element.sourcein = "1s";
|
|
162
|
+
|
|
163
|
+
const preview = document.createElement("ef-preview");
|
|
164
|
+
timegroup.appendChild(element);
|
|
165
|
+
preview.appendChild(timegroup);
|
|
166
|
+
document.body.appendChild(preview);
|
|
167
|
+
|
|
168
|
+
// Await the next tick to ensure the element has a chance to load the track fragment
|
|
169
|
+
await Promise.resolve();
|
|
170
|
+
|
|
171
|
+
await element.trackFragmentIndexLoader.taskComplete;
|
|
172
|
+
|
|
173
|
+
expect(element.durationMs).toBe(9_000);
|
|
174
|
+
expect(timegroup.durationMs).toBe(9_000);
|
|
175
|
+
});
|
|
176
|
+
test("Computes duration from track fragment index sourcein", async () => {
|
|
177
|
+
// Mock the request for the track fragment index, responds with a 10 second duration
|
|
178
|
+
worker.use(
|
|
179
|
+
http.get("/@ef-track-fragment-index//assets/10s-bars.mp4", () => {
|
|
180
|
+
return HttpResponse.json(
|
|
181
|
+
createTestFragmentIndex({
|
|
182
|
+
audio: { duration: 10_000 },
|
|
183
|
+
video: { duration: 10_000 },
|
|
184
|
+
}),
|
|
185
|
+
);
|
|
186
|
+
}),
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const timegroup = document.createElement("ef-timegroup");
|
|
190
|
+
timegroup.mode = "sequence";
|
|
191
|
+
const element = document.createElement("test-media");
|
|
192
|
+
element.src = "/assets/10s-bars.mp4";
|
|
193
|
+
element.sourcein = "6s";
|
|
194
|
+
|
|
195
|
+
const preview = document.createElement("ef-preview");
|
|
196
|
+
timegroup.appendChild(element);
|
|
197
|
+
preview.appendChild(timegroup);
|
|
198
|
+
document.body.appendChild(preview);
|
|
199
|
+
|
|
200
|
+
// Await the next tick to ensure the element has a chance to load the track fragment
|
|
201
|
+
await Promise.resolve();
|
|
202
|
+
|
|
203
|
+
await element.trackFragmentIndexLoader.taskComplete;
|
|
204
|
+
|
|
205
|
+
expect(element.durationMs).toBe(4_000);
|
|
206
|
+
expect(timegroup.durationMs).toBe(4_000);
|
|
207
|
+
});
|
|
208
|
+
test("Computes duration from track fragment index sourceout", async () => {
|
|
209
|
+
// Mock the request for the track fragment index, responds with a 10 second duration
|
|
210
|
+
worker.use(
|
|
211
|
+
http.get("/@ef-track-fragment-index//assets/10s-bars.mp4", () => {
|
|
212
|
+
return HttpResponse.json(
|
|
213
|
+
createTestFragmentIndex({
|
|
214
|
+
audio: { duration: 10_000 },
|
|
215
|
+
video: { duration: 10_000 },
|
|
216
|
+
}),
|
|
217
|
+
);
|
|
218
|
+
}),
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
const timegroup = document.createElement("ef-timegroup");
|
|
222
|
+
timegroup.mode = "sequence";
|
|
223
|
+
const element = document.createElement("test-media");
|
|
224
|
+
element.src = "/assets/10s-bars.mp4";
|
|
225
|
+
element.sourceout = "6s";
|
|
226
|
+
|
|
227
|
+
const preview = document.createElement("ef-preview");
|
|
228
|
+
timegroup.appendChild(element);
|
|
229
|
+
preview.appendChild(timegroup);
|
|
230
|
+
document.body.appendChild(preview);
|
|
231
|
+
|
|
232
|
+
// Await the next tick to ensure the element has a chance to load the track fragment
|
|
233
|
+
await Promise.resolve();
|
|
234
|
+
|
|
235
|
+
await element.trackFragmentIndexLoader.taskComplete;
|
|
236
|
+
|
|
237
|
+
expect(element.durationMs).toBe(4_000);
|
|
238
|
+
expect(timegroup.durationMs).toBe(4_000);
|
|
239
|
+
});
|
|
240
|
+
test("Computes duration from track fragment index sourceout", async () => {
|
|
241
|
+
// Mock the request for the track fragment index, responds with a 10 second duration
|
|
242
|
+
worker.use(
|
|
243
|
+
http.get("/@ef-track-fragment-index//assets/10s-bars.mp4", () => {
|
|
244
|
+
return HttpResponse.json(
|
|
245
|
+
createTestFragmentIndex({
|
|
246
|
+
audio: { duration: 10_000 },
|
|
247
|
+
video: { duration: 10_000 },
|
|
248
|
+
}),
|
|
249
|
+
);
|
|
250
|
+
}),
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
const timegroup = document.createElement("ef-timegroup");
|
|
254
|
+
timegroup.mode = "sequence";
|
|
255
|
+
const element = document.createElement("test-media");
|
|
256
|
+
element.src = "/assets/10s-bars.mp4";
|
|
257
|
+
element.sourceout = "5s";
|
|
258
|
+
|
|
259
|
+
const preview = document.createElement("ef-preview");
|
|
260
|
+
timegroup.appendChild(element);
|
|
261
|
+
preview.appendChild(timegroup);
|
|
262
|
+
document.body.appendChild(preview);
|
|
263
|
+
|
|
264
|
+
// Await the next tick to ensure the element has a chance to load the track fragment
|
|
265
|
+
await Promise.resolve();
|
|
266
|
+
|
|
267
|
+
await element.trackFragmentIndexLoader.taskComplete;
|
|
268
|
+
|
|
269
|
+
expect(element.durationMs).toBe(5_000);
|
|
270
|
+
expect(timegroup.durationMs).toBe(5_000);
|
|
271
|
+
});
|
|
272
|
+
test("Computes duration from track fragment index sourceout and sourcein", async () => {
|
|
273
|
+
// Mock the request for the track fragment index, responds with a 10 second duration
|
|
274
|
+
worker.use(
|
|
275
|
+
http.get("/@ef-track-fragment-index//assets/10s-bars.mp4", () => {
|
|
276
|
+
return HttpResponse.json(
|
|
277
|
+
createTestFragmentIndex({
|
|
278
|
+
audio: { duration: 10_000 },
|
|
279
|
+
video: { duration: 10_000 },
|
|
280
|
+
}),
|
|
281
|
+
);
|
|
282
|
+
}),
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
const timegroup = document.createElement("ef-timegroup");
|
|
286
|
+
timegroup.mode = "sequence";
|
|
287
|
+
const element = document.createElement("test-media");
|
|
288
|
+
element.src = "/assets/10s-bars.mp4";
|
|
289
|
+
element.sourcein = "1s";
|
|
290
|
+
element.sourceout = "5s";
|
|
291
|
+
|
|
292
|
+
const preview = document.createElement("ef-preview");
|
|
293
|
+
timegroup.appendChild(element);
|
|
294
|
+
preview.appendChild(timegroup);
|
|
295
|
+
document.body.appendChild(preview);
|
|
296
|
+
|
|
297
|
+
// Await the next tick to ensure the element has a chance to load the track fragment
|
|
298
|
+
await Promise.resolve();
|
|
299
|
+
|
|
300
|
+
await element.trackFragmentIndexLoader.taskComplete;
|
|
301
|
+
|
|
302
|
+
expect(element.durationMs).toBe(4_000);
|
|
303
|
+
expect(timegroup.durationMs).toBe(4_000);
|
|
304
|
+
});
|
|
305
|
+
test("Computes duration from track fragment index sourceout and sourcein", async () => {
|
|
306
|
+
// Mock the request for the track fragment index, responds with a 10 second duration
|
|
307
|
+
worker.use(
|
|
308
|
+
http.get("/@ef-track-fragment-index//assets/10s-bars.mp4", () => {
|
|
309
|
+
return HttpResponse.json(
|
|
310
|
+
createTestFragmentIndex({
|
|
311
|
+
audio: { duration: 10_000 },
|
|
312
|
+
video: { duration: 10_000 },
|
|
313
|
+
}),
|
|
314
|
+
);
|
|
315
|
+
}),
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
const timegroup = document.createElement("ef-timegroup");
|
|
319
|
+
timegroup.mode = "sequence";
|
|
320
|
+
const element = document.createElement("test-media");
|
|
321
|
+
element.src = "/assets/10s-bars.mp4";
|
|
322
|
+
element.sourcein = "9s";
|
|
323
|
+
element.sourceout = "10s";
|
|
324
|
+
|
|
325
|
+
const preview = document.createElement("ef-preview");
|
|
326
|
+
timegroup.appendChild(element);
|
|
327
|
+
preview.appendChild(timegroup);
|
|
328
|
+
document.body.appendChild(preview);
|
|
329
|
+
|
|
330
|
+
// Await the next tick to ensure the element has a chance to load the track fragment
|
|
331
|
+
await Promise.resolve();
|
|
332
|
+
|
|
333
|
+
await element.trackFragmentIndexLoader.taskComplete;
|
|
334
|
+
|
|
335
|
+
expect(element.durationMs).toBe(1_000);
|
|
336
|
+
expect(timegroup.durationMs).toBe(1_000);
|
|
337
|
+
});
|
|
338
|
+
});
|
|
110
339
|
});
|
package/src/elements/EFMedia.ts
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
+
import { consume } from "@lit/context";
|
|
2
|
+
import { Task } from "@lit/task";
|
|
3
|
+
import { deepArrayEquals } from "@lit/task/deep-equals.js";
|
|
4
|
+
import debug from "debug";
|
|
1
5
|
import { LitElement, type PropertyValueMap, css } from "lit";
|
|
2
6
|
import { property, state } from "lit/decorators.js";
|
|
3
|
-
import { deepArrayEquals } from "@lit/task/deep-equals.js";
|
|
4
|
-
import { Task } from "@lit/task";
|
|
5
7
|
import type * as MP4Box from "mp4box";
|
|
6
|
-
import { consume } from "@lit/context";
|
|
7
|
-
import debug from "debug";
|
|
8
8
|
|
|
9
9
|
import type { TrackFragmentIndex, TrackSegment } from "@editframe/assets";
|
|
10
10
|
|
|
11
|
-
import { MP4File } from "@editframe/assets/MP4File.js";
|
|
12
11
|
import { VideoAsset } from "@editframe/assets/EncodedAsset.js";
|
|
12
|
+
import { MP4File } from "@editframe/assets/MP4File.js";
|
|
13
|
+
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.ts";
|
|
14
|
+
import { EF_RENDERING } from "../EF_RENDERING.ts";
|
|
15
|
+
import { apiHostContext } from "../gui/apiHostContext.ts";
|
|
16
|
+
import { EFSourceMixin } from "./EFSourceMixin.ts";
|
|
13
17
|
import { EFTemporal } from "./EFTemporal.ts";
|
|
14
18
|
import { FetchMixin } from "./FetchMixin.ts";
|
|
15
|
-
import { EFSourceMixin } from "./EFSourceMixin.ts";
|
|
16
19
|
import { getStartTimeMs } from "./util.ts";
|
|
17
|
-
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.ts";
|
|
18
|
-
import { apiHostContext } from "../gui/apiHostContext.ts";
|
|
19
|
-
import { EF_RENDERING } from "../EF_RENDERING.ts";
|
|
20
20
|
|
|
21
21
|
const log = debug("ef:elements:EFMedia");
|
|
22
22
|
|
|
@@ -321,6 +321,38 @@ export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
321
321
|
if (durations.length === 0) {
|
|
322
322
|
return 0;
|
|
323
323
|
}
|
|
324
|
+
if (
|
|
325
|
+
this.sourceInMs &&
|
|
326
|
+
this.sourceOutMs &&
|
|
327
|
+
this.sourceOutMs > this.sourceInMs
|
|
328
|
+
) {
|
|
329
|
+
return Math.max(this.sourceOutMs - this.sourceInMs);
|
|
330
|
+
}
|
|
331
|
+
if (this.sourceInMs) {
|
|
332
|
+
return (
|
|
333
|
+
Math.max(...durations) -
|
|
334
|
+
this.trimStartMs -
|
|
335
|
+
this.trimEndMs -
|
|
336
|
+
this.sourceInMs
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
if (this.sourceOutMs) {
|
|
340
|
+
return (
|
|
341
|
+
Math.max(...durations) -
|
|
342
|
+
this.trimStartMs -
|
|
343
|
+
this.trimEndMs -
|
|
344
|
+
this.sourceOutMs
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
if (this.sourceInMs && this.sourceOutMs) {
|
|
348
|
+
return (
|
|
349
|
+
Math.max(...durations) -
|
|
350
|
+
this.trimStartMs -
|
|
351
|
+
this.trimEndMs -
|
|
352
|
+
this.sourceOutMs -
|
|
353
|
+
this.sourceInMs
|
|
354
|
+
);
|
|
355
|
+
}
|
|
324
356
|
return Math.max(...durations) - this.trimStartMs - this.trimEndMs;
|
|
325
357
|
}
|
|
326
358
|
|
|
@@ -362,6 +394,12 @@ export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
362
394
|
|
|
363
395
|
async fetchAudioSpanningTime(fromMs: number, toMs: number) {
|
|
364
396
|
// Adjust range for track's own time
|
|
397
|
+
if (this.sourceInMs) {
|
|
398
|
+
fromMs -= this.startTimeMs - this.trimStartMs - this.sourceInMs;
|
|
399
|
+
}
|
|
400
|
+
if (this.sourceOutMs) {
|
|
401
|
+
toMs -= this.startTimeMs - this.trimStartMs - this.sourceOutMs;
|
|
402
|
+
}
|
|
365
403
|
fromMs -= this.startTimeMs - this.trimStartMs;
|
|
366
404
|
toMs -= this.startTimeMs - this.trimStartMs;
|
|
367
405
|
|
|
@@ -371,6 +409,7 @@ export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
371
409
|
log("No audio track found");
|
|
372
410
|
return;
|
|
373
411
|
}
|
|
412
|
+
|
|
374
413
|
const audioTrackIndex = this.trackFragmentIndexLoader.value?.[audioTrackId];
|
|
375
414
|
if (!audioTrackIndex) {
|
|
376
415
|
log("No audio track found");
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { LitElement } from "lit";
|
|
2
|
+
import { customElement } from "lit/decorators/custom-element.js";
|
|
3
|
+
import { describe, expect, test } from "vitest";
|
|
4
|
+
import { EFTemporal } from "./EFTemporal.ts";
|
|
5
|
+
|
|
6
|
+
@customElement("test-temporal")
|
|
7
|
+
class TestTemporal extends EFTemporal(LitElement) {}
|
|
8
|
+
|
|
9
|
+
declare global {
|
|
10
|
+
interface HTMLElementTagNameMap {
|
|
11
|
+
"test-temporal": TestTemporal;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe("sourcein and sourceout", () => {
|
|
16
|
+
test("sourcein and sourceout are parsed correctly", () => {
|
|
17
|
+
const element = document.createElement("test-temporal");
|
|
18
|
+
element.setAttribute("sourcein", "1s");
|
|
19
|
+
element.setAttribute("sourceout", "5s");
|
|
20
|
+
expect(element.sourceInMs).toBe(1_000);
|
|
21
|
+
expect(element.sourceOutMs).toBe(5_000);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("sourcein and sourceout can be set directly on the element", () => {
|
|
25
|
+
const element = document.createElement("test-temporal");
|
|
26
|
+
element.sourcein = "1s";
|
|
27
|
+
element.sourceout = "5s";
|
|
28
|
+
expect(element.sourceInMs).toBe(1_000);
|
|
29
|
+
expect(element.sourceOutMs).toBe(5_000);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("sourcein and sourceout are reflected correctly", () => {
|
|
33
|
+
const element = document.createElement("test-temporal");
|
|
34
|
+
element.sourceInMs = 1_000;
|
|
35
|
+
element.sourceOutMs = 5_000;
|
|
36
|
+
expect(element.getAttribute("sourcein")).toBe("1s");
|
|
37
|
+
expect(element.getAttribute("sourceout")).toBe("5s");
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("trimstart and trimend", () => {
|
|
42
|
+
test("trimstart and trimend attributes are parsed correctly", () => {
|
|
43
|
+
const element = document.createElement("test-temporal");
|
|
44
|
+
element.setAttribute("trimstart", "1s");
|
|
45
|
+
element.setAttribute("trimend", "5s");
|
|
46
|
+
expect(element.trimStartMs).toBe(1_000);
|
|
47
|
+
expect(element.trimEndMs).toBe(5_000);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("trimstart and trimend properties are reflected correctly", () => {
|
|
51
|
+
const element = document.createElement("test-temporal");
|
|
52
|
+
element.trimStartMs = 1_000;
|
|
53
|
+
element.trimEndMs = 5_000;
|
|
54
|
+
expect(element.getAttribute("trimstart")).toBe("1s");
|
|
55
|
+
expect(element.getAttribute("trimend")).toBe("5s");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("trimstart and trimend can be set directly on the element", () => {
|
|
59
|
+
const element = document.createElement("test-temporal");
|
|
60
|
+
element.trimstart = "1s";
|
|
61
|
+
element.trimend = "5s";
|
|
62
|
+
expect(element.trimStartMs).toBe(1_000);
|
|
63
|
+
expect(element.trimEndMs).toBe(5_000);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe("duration", () => {
|
|
68
|
+
test("duration is parsed correctly", () => {
|
|
69
|
+
const element = document.createElement("test-temporal");
|
|
70
|
+
element.setAttribute("duration", "10s");
|
|
71
|
+
expect(element.durationMs).toBe(10_000);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("duration can be set directly on the element", () => {
|
|
75
|
+
const element = document.createElement("test-temporal");
|
|
76
|
+
element.duration = "10s";
|
|
77
|
+
expect(element.durationMs).toBe(10_000);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type { LitElement, ReactiveController } from "lit";
|
|
2
1
|
import { consume, createContext } from "@lit/context";
|
|
2
|
+
import type { LitElement, ReactiveController } from "lit";
|
|
3
3
|
import { property, state } from "lit/decorators.js";
|
|
4
4
|
import type { EFTimegroup } from "./EFTimegroup.ts";
|
|
5
5
|
|
|
6
|
-
import { durationConverter } from "./durationConverter.ts";
|
|
7
6
|
import { Task } from "@lit/task";
|
|
8
7
|
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.ts";
|
|
8
|
+
import { durationConverter } from "./durationConverter.ts";
|
|
9
9
|
|
|
10
10
|
export const timegroupContext = createContext<EFTimegroup>(
|
|
11
11
|
Symbol("timeGroupContext"),
|
|
@@ -15,6 +15,19 @@ export declare class TemporalMixinInterface {
|
|
|
15
15
|
get hasOwnDuration(): boolean;
|
|
16
16
|
get trimStartMs(): number;
|
|
17
17
|
get trimEndMs(): number;
|
|
18
|
+
set trimStartMs(value: number);
|
|
19
|
+
set trimEndMs(value: number);
|
|
20
|
+
set trimstart(value: string);
|
|
21
|
+
set trimend(value: string);
|
|
22
|
+
|
|
23
|
+
get sourceInMs(): number;
|
|
24
|
+
get sourceOutMs(): number;
|
|
25
|
+
|
|
26
|
+
set sourceInMs(value: number);
|
|
27
|
+
set sourceOutMs(value: number);
|
|
28
|
+
set sourcein(value: string);
|
|
29
|
+
set sourceout(value: string);
|
|
30
|
+
|
|
18
31
|
get durationMs(): number;
|
|
19
32
|
get startTimeMs(): number;
|
|
20
33
|
get startTimeWithinParentMs(): number;
|
|
@@ -24,8 +37,6 @@ export declare class TemporalMixinInterface {
|
|
|
24
37
|
|
|
25
38
|
set duration(value: string);
|
|
26
39
|
get duration(): string;
|
|
27
|
-
set trimstart(value: string);
|
|
28
|
-
set trimend(value: string);
|
|
29
40
|
|
|
30
41
|
parentTimegroup?: EFTimegroup;
|
|
31
42
|
rootTimegroup?: EFTimegroup;
|
|
@@ -158,25 +169,118 @@ export const EFTemporal = <T extends Constructor<LitElement>>(
|
|
|
158
169
|
})
|
|
159
170
|
private _durationMs?: number;
|
|
160
171
|
|
|
172
|
+
set duration(value: string | undefined) {
|
|
173
|
+
if (value !== undefined) {
|
|
174
|
+
this.setAttribute("duration", value);
|
|
175
|
+
} else {
|
|
176
|
+
this.removeAttribute("duration");
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private _trimStartMs = 0;
|
|
161
181
|
@property({
|
|
162
182
|
type: Number,
|
|
163
183
|
attribute: "trimstart",
|
|
164
184
|
converter: durationConverter,
|
|
165
185
|
})
|
|
166
|
-
private _trimStartMs = 0;
|
|
167
186
|
public get trimStartMs(): number {
|
|
168
187
|
return this._trimStartMs;
|
|
169
188
|
}
|
|
189
|
+
public set trimStartMs(value: number) {
|
|
190
|
+
if (this._trimStartMs === value) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
this._trimStartMs = value;
|
|
194
|
+
this.setAttribute(
|
|
195
|
+
"trimstart",
|
|
196
|
+
durationConverter.toAttribute(value / 1000),
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
set trimstart(value: string | undefined) {
|
|
200
|
+
if (value !== undefined) {
|
|
201
|
+
this.setAttribute("trimstart", value);
|
|
202
|
+
} else {
|
|
203
|
+
this.removeAttribute("trimstart");
|
|
204
|
+
}
|
|
205
|
+
}
|
|
170
206
|
|
|
207
|
+
private _trimEndMs = 0;
|
|
171
208
|
@property({
|
|
172
209
|
type: Number,
|
|
173
210
|
attribute: "trimend",
|
|
174
211
|
converter: durationConverter,
|
|
175
212
|
})
|
|
176
|
-
private _trimEndMs = 0;
|
|
177
213
|
public get trimEndMs(): number {
|
|
178
214
|
return this._trimEndMs;
|
|
179
215
|
}
|
|
216
|
+
public set trimEndMs(value: number) {
|
|
217
|
+
if (this._trimEndMs === value) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
this._trimEndMs = value;
|
|
221
|
+
this.setAttribute("trimend", durationConverter.toAttribute(value / 1000));
|
|
222
|
+
}
|
|
223
|
+
set trimend(value: string | undefined) {
|
|
224
|
+
if (value !== undefined) {
|
|
225
|
+
this.setAttribute("trimend", value);
|
|
226
|
+
} else {
|
|
227
|
+
this.removeAttribute("trimend");
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private _sourceInMs: number | undefined;
|
|
232
|
+
@property({
|
|
233
|
+
type: Number,
|
|
234
|
+
attribute: "sourcein",
|
|
235
|
+
converter: durationConverter,
|
|
236
|
+
reflect: true,
|
|
237
|
+
})
|
|
238
|
+
get sourceInMs(): number | undefined {
|
|
239
|
+
return this._sourceInMs;
|
|
240
|
+
}
|
|
241
|
+
set sourceInMs(value: number | undefined) {
|
|
242
|
+
this._sourceInMs = value;
|
|
243
|
+
value !== undefined
|
|
244
|
+
? this.setAttribute(
|
|
245
|
+
"sourcein",
|
|
246
|
+
durationConverter.toAttribute(value / 1000),
|
|
247
|
+
)
|
|
248
|
+
: this.removeAttribute("sourcein");
|
|
249
|
+
}
|
|
250
|
+
set sourcein(value: string | undefined) {
|
|
251
|
+
if (value !== undefined) {
|
|
252
|
+
this.setAttribute("sourcein", value);
|
|
253
|
+
} else {
|
|
254
|
+
this.removeAttribute("sourcein");
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private _sourceOutMs: number | undefined;
|
|
259
|
+
@property({
|
|
260
|
+
type: Number,
|
|
261
|
+
attribute: "sourceout",
|
|
262
|
+
converter: durationConverter,
|
|
263
|
+
reflect: true,
|
|
264
|
+
})
|
|
265
|
+
get sourceOutMs(): number | undefined {
|
|
266
|
+
return this._sourceOutMs;
|
|
267
|
+
}
|
|
268
|
+
set sourceOutMs(value: number | undefined) {
|
|
269
|
+
this._sourceOutMs = value;
|
|
270
|
+
value !== undefined
|
|
271
|
+
? this.setAttribute(
|
|
272
|
+
"sourceout",
|
|
273
|
+
durationConverter.toAttribute(value / 1000),
|
|
274
|
+
)
|
|
275
|
+
: this.removeAttribute("sourceout");
|
|
276
|
+
}
|
|
277
|
+
set sourceout(value: string | undefined) {
|
|
278
|
+
if (value !== undefined) {
|
|
279
|
+
this.setAttribute("sourceout", value);
|
|
280
|
+
} else {
|
|
281
|
+
this.removeAttribute("sourceout");
|
|
282
|
+
}
|
|
283
|
+
}
|
|
180
284
|
|
|
181
285
|
@property({
|
|
182
286
|
type: Number,
|
|
@@ -207,6 +311,20 @@ export const EFTemporal = <T extends Constructor<LitElement>>(
|
|
|
207
311
|
// Defining this as a getter to a private property allows us to
|
|
208
312
|
// override it classes that include this mixin.
|
|
209
313
|
get durationMs() {
|
|
314
|
+
if (this.sourceInMs) {
|
|
315
|
+
return (
|
|
316
|
+
this._durationMs ||
|
|
317
|
+
this.parentTimegroup?.durationMs ||
|
|
318
|
+
0 - this.sourceInMs
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
if (this.sourceOutMs) {
|
|
322
|
+
return (
|
|
323
|
+
this._durationMs ||
|
|
324
|
+
this.parentTimegroup?.durationMs ||
|
|
325
|
+
0 - this.sourceOutMs
|
|
326
|
+
);
|
|
327
|
+
}
|
|
210
328
|
return this._durationMs || this.parentTimegroup?.durationMs || 0;
|
|
211
329
|
}
|
|
212
330
|
|
|
@@ -297,6 +415,21 @@ export const EFTemporal = <T extends Constructor<LitElement>>(
|
|
|
297
415
|
*/
|
|
298
416
|
get trimAdjustedOwnCurrentTimeMs() {
|
|
299
417
|
if (this.rootTimegroup) {
|
|
418
|
+
if (this.sourceInMs && this.sourceOutMs) {
|
|
419
|
+
return Math.min(
|
|
420
|
+
Math.max(
|
|
421
|
+
0,
|
|
422
|
+
this.rootTimegroup.currentTimeMs -
|
|
423
|
+
this.startTimeMs +
|
|
424
|
+
this.trimStartMs +
|
|
425
|
+
this.sourceInMs,
|
|
426
|
+
),
|
|
427
|
+
this.durationMs +
|
|
428
|
+
Math.abs(this.startOffsetMs) +
|
|
429
|
+
this.trimStartMs +
|
|
430
|
+
this.sourceInMs,
|
|
431
|
+
);
|
|
432
|
+
}
|
|
300
433
|
return Math.min(
|
|
301
434
|
Math.max(
|
|
302
435
|
0,
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import { describe, test, assert, beforeEach } from "vitest";
|
|
2
1
|
import {
|
|
3
2
|
LitElement,
|
|
4
3
|
type TemplateResult,
|
|
5
4
|
html,
|
|
6
5
|
render as litRender,
|
|
7
6
|
} from "lit";
|
|
7
|
+
import { assert, beforeEach, describe, test } from "vitest";
|
|
8
8
|
import { EFTimegroup } from "./EFTimegroup.ts";
|
|
9
9
|
import "./EFTimegroup.ts";
|
|
10
10
|
import { customElement } from "lit/decorators/custom-element.js";
|
|
11
|
+
import { ContextMixin } from "../gui/ContextMixin.ts";
|
|
11
12
|
import { EFTemporal } from "./EFTemporal.ts";
|
|
13
|
+
// Need workbench to make workbench wrapping occurs
|
|
14
|
+
import "../gui/EFWorkbench.ts";
|
|
12
15
|
|
|
13
16
|
beforeEach(() => {
|
|
14
17
|
for (let i = 0; i < localStorage.length; i++) {
|
|
@@ -21,6 +24,9 @@ beforeEach(() => {
|
|
|
21
24
|
}
|
|
22
25
|
});
|
|
23
26
|
|
|
27
|
+
@customElement("test-context")
|
|
28
|
+
class TestContext extends ContextMixin(LitElement) {}
|
|
29
|
+
|
|
24
30
|
@customElement("test-temporal")
|
|
25
31
|
class TestTemporal extends EFTemporal(LitElement) {
|
|
26
32
|
get hasOwnDuration(): boolean {
|
|
@@ -31,6 +37,7 @@ class TestTemporal extends EFTemporal(LitElement) {
|
|
|
31
37
|
declare global {
|
|
32
38
|
interface HTMLElementTagNameMap {
|
|
33
39
|
"test-temporal": TestTemporal;
|
|
40
|
+
"test-context": TestContext;
|
|
34
41
|
}
|
|
35
42
|
}
|
|
36
43
|
|
|
@@ -331,3 +338,33 @@ describe("setting currentTime", () => {
|
|
|
331
338
|
assert.equal(b.ownCurrentTimeMs, 2_500);
|
|
332
339
|
});
|
|
333
340
|
});
|
|
341
|
+
|
|
342
|
+
describe("shouldWrapWithWorkbench", () => {
|
|
343
|
+
test.skip("should not wrap if EF_INTERACTIVE is false", () => {
|
|
344
|
+
// TODO: need a way to define EF_INTERACTIVE in a test
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
test("should wrap if root-most timegroup", () => {
|
|
348
|
+
const root = document.createElement("ef-timegroup");
|
|
349
|
+
const child = document.createElement("ef-timegroup");
|
|
350
|
+
root.appendChild(child);
|
|
351
|
+
assert.isTrue(child.shouldWrapWithWorkbench());
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
test("should not wrap if contained within a preview context", () => {
|
|
355
|
+
const timegorup = document.createElement("ef-timegroup");
|
|
356
|
+
const context = document.createElement("test-context");
|
|
357
|
+
context.append(timegorup);
|
|
358
|
+
assert.isFalse(timegorup.shouldWrapWithWorkbench());
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
describe("DOM nodes", () => {
|
|
363
|
+
test("can have mode and duration set as attributes", () => {
|
|
364
|
+
const timegroup = document.createElement("ef-timegroup");
|
|
365
|
+
timegroup.setAttribute("mode", "fixed");
|
|
366
|
+
timegroup.setAttribute("duration", "10s");
|
|
367
|
+
assert.equal(timegroup.mode, "fixed");
|
|
368
|
+
assert.equal(timegroup.durationMs, 10_000);
|
|
369
|
+
});
|
|
370
|
+
});
|