@editframe/elements 0.11.0-beta.1 → 0.11.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 +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 +100 -3
- 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 +20 -6
- package/dist/elements/src/gui/EFFilmstrip.js +28 -8
- package/dist/elements/src/gui/EFTogglePlay.js +38 -6
- 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/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 +133 -6
- package/src/elements/EFTimegroup.browsertest.ts +38 -1
- package/src/elements/EFTimegroup.ts +27 -6
- 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 +23 -7
- package/src/gui/EFFilmstrip.ts +37 -17
- package/src/gui/EFTogglePlay.ts +38 -7
|
@@ -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,112 @@ 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
|
+
this._trimStartMs = value;
|
|
191
|
+
this.setAttribute(
|
|
192
|
+
"trimstart",
|
|
193
|
+
durationConverter.toAttribute(value / 1000),
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
set trimstart(value: string | undefined) {
|
|
197
|
+
if (value !== undefined) {
|
|
198
|
+
this.setAttribute("trimstart", value);
|
|
199
|
+
} else {
|
|
200
|
+
this.removeAttribute("trimstart");
|
|
201
|
+
}
|
|
202
|
+
}
|
|
170
203
|
|
|
204
|
+
private _trimEndMs = 0;
|
|
171
205
|
@property({
|
|
172
206
|
type: Number,
|
|
173
207
|
attribute: "trimend",
|
|
174
208
|
converter: durationConverter,
|
|
175
209
|
})
|
|
176
|
-
private _trimEndMs = 0;
|
|
177
210
|
public get trimEndMs(): number {
|
|
178
211
|
return this._trimEndMs;
|
|
179
212
|
}
|
|
213
|
+
public set trimEndMs(value: number) {
|
|
214
|
+
this._trimEndMs = value;
|
|
215
|
+
this.setAttribute("trimend", durationConverter.toAttribute(value / 1000));
|
|
216
|
+
}
|
|
217
|
+
set trimend(value: string | undefined) {
|
|
218
|
+
if (value !== undefined) {
|
|
219
|
+
this.setAttribute("trimend", value);
|
|
220
|
+
} else {
|
|
221
|
+
this.removeAttribute("trimend");
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private _sourceInMs: number | undefined;
|
|
226
|
+
@property({
|
|
227
|
+
type: Number,
|
|
228
|
+
attribute: "sourcein",
|
|
229
|
+
converter: durationConverter,
|
|
230
|
+
reflect: true,
|
|
231
|
+
})
|
|
232
|
+
get sourceInMs(): number | undefined {
|
|
233
|
+
return this._sourceInMs;
|
|
234
|
+
}
|
|
235
|
+
set sourceInMs(value: number | undefined) {
|
|
236
|
+
this._sourceInMs = value;
|
|
237
|
+
value !== undefined
|
|
238
|
+
? this.setAttribute(
|
|
239
|
+
"sourcein",
|
|
240
|
+
durationConverter.toAttribute(value / 1000),
|
|
241
|
+
)
|
|
242
|
+
: this.removeAttribute("sourcein");
|
|
243
|
+
}
|
|
244
|
+
set sourcein(value: string | undefined) {
|
|
245
|
+
if (value !== undefined) {
|
|
246
|
+
this.setAttribute("sourcein", value);
|
|
247
|
+
} else {
|
|
248
|
+
this.removeAttribute("sourcein");
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private _sourceOutMs: number | undefined;
|
|
253
|
+
@property({
|
|
254
|
+
type: Number,
|
|
255
|
+
attribute: "sourceout",
|
|
256
|
+
converter: durationConverter,
|
|
257
|
+
reflect: true,
|
|
258
|
+
})
|
|
259
|
+
get sourceOutMs(): number | undefined {
|
|
260
|
+
return this._sourceOutMs;
|
|
261
|
+
}
|
|
262
|
+
set sourceOutMs(value: number | undefined) {
|
|
263
|
+
this._sourceOutMs = value;
|
|
264
|
+
value !== undefined
|
|
265
|
+
? this.setAttribute(
|
|
266
|
+
"sourceout",
|
|
267
|
+
durationConverter.toAttribute(value / 1000),
|
|
268
|
+
)
|
|
269
|
+
: this.removeAttribute("sourceout");
|
|
270
|
+
}
|
|
271
|
+
set sourceout(value: string | undefined) {
|
|
272
|
+
if (value !== undefined) {
|
|
273
|
+
this.setAttribute("sourceout", value);
|
|
274
|
+
} else {
|
|
275
|
+
this.removeAttribute("sourceout");
|
|
276
|
+
}
|
|
277
|
+
}
|
|
180
278
|
|
|
181
279
|
@property({
|
|
182
280
|
type: Number,
|
|
@@ -207,6 +305,20 @@ export const EFTemporal = <T extends Constructor<LitElement>>(
|
|
|
207
305
|
// Defining this as a getter to a private property allows us to
|
|
208
306
|
// override it classes that include this mixin.
|
|
209
307
|
get durationMs() {
|
|
308
|
+
if (this.sourceInMs) {
|
|
309
|
+
return (
|
|
310
|
+
this._durationMs ||
|
|
311
|
+
this.parentTimegroup?.durationMs ||
|
|
312
|
+
0 - this.sourceInMs
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
if (this.sourceOutMs) {
|
|
316
|
+
return (
|
|
317
|
+
this._durationMs ||
|
|
318
|
+
this.parentTimegroup?.durationMs ||
|
|
319
|
+
0 - this.sourceOutMs
|
|
320
|
+
);
|
|
321
|
+
}
|
|
210
322
|
return this._durationMs || this.parentTimegroup?.durationMs || 0;
|
|
211
323
|
}
|
|
212
324
|
|
|
@@ -297,6 +409,21 @@ export const EFTemporal = <T extends Constructor<LitElement>>(
|
|
|
297
409
|
*/
|
|
298
410
|
get trimAdjustedOwnCurrentTimeMs() {
|
|
299
411
|
if (this.rootTimegroup) {
|
|
412
|
+
if (this.sourceInMs && this.sourceOutMs) {
|
|
413
|
+
return Math.min(
|
|
414
|
+
Math.max(
|
|
415
|
+
0,
|
|
416
|
+
this.rootTimegroup.currentTimeMs -
|
|
417
|
+
this.startTimeMs +
|
|
418
|
+
this.trimStartMs +
|
|
419
|
+
this.sourceInMs,
|
|
420
|
+
),
|
|
421
|
+
this.durationMs +
|
|
422
|
+
Math.abs(this.startOffsetMs) +
|
|
423
|
+
this.trimStartMs +
|
|
424
|
+
this.sourceInMs,
|
|
425
|
+
);
|
|
426
|
+
}
|
|
300
427
|
return Math.min(
|
|
301
428
|
Math.max(
|
|
302
429
|
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
|
+
});
|