@editframe/elements 0.26.4-beta.0 → 0.30.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/elements/EFSourceMixin.js +1 -1
- package/dist/elements/EFSourceMixin.js.map +1 -1
- package/dist/elements/EFSurface.d.ts +4 -4
- package/dist/elements/EFText.d.ts +52 -0
- package/dist/elements/EFText.js +319 -0
- package/dist/elements/EFText.js.map +1 -0
- package/dist/elements/EFTextSegment.d.ts +30 -0
- package/dist/elements/EFTextSegment.js +94 -0
- package/dist/elements/EFTextSegment.js.map +1 -0
- package/dist/elements/EFThumbnailStrip.d.ts +4 -4
- package/dist/elements/EFWaveform.d.ts +4 -4
- package/dist/elements/FetchMixin.js +22 -7
- package/dist/elements/FetchMixin.js.map +1 -1
- package/dist/elements/easingUtils.js +62 -0
- package/dist/elements/easingUtils.js.map +1 -0
- package/dist/elements/updateAnimations.js +57 -10
- package/dist/elements/updateAnimations.js.map +1 -1
- package/dist/gui/ContextMixin.js +11 -2
- package/dist/gui/ContextMixin.js.map +1 -1
- package/dist/gui/EFConfiguration.d.ts +4 -4
- package/dist/gui/EFControls.d.ts +2 -2
- package/dist/gui/EFDial.d.ts +4 -4
- package/dist/gui/EFDial.js +4 -2
- package/dist/gui/EFDial.js.map +1 -1
- package/dist/gui/EFFilmstrip.d.ts +32 -6
- package/dist/gui/EFFilmstrip.js +314 -50
- package/dist/gui/EFFilmstrip.js.map +1 -1
- package/dist/gui/EFFitScale.js +39 -15
- package/dist/gui/EFFitScale.js.map +1 -1
- package/dist/gui/EFFocusOverlay.d.ts +4 -4
- package/dist/gui/EFPause.d.ts +4 -4
- package/dist/gui/EFPlay.d.ts +4 -4
- package/dist/gui/EFPreview.d.ts +4 -4
- package/dist/gui/EFPreview.js +2 -2
- package/dist/gui/EFPreview.js.map +1 -1
- package/dist/gui/EFResizableBox.d.ts +4 -4
- package/dist/gui/EFResizableBox.js +6 -3
- package/dist/gui/EFResizableBox.js.map +1 -1
- package/dist/gui/EFScrubber.d.ts +8 -5
- package/dist/gui/EFScrubber.js +64 -12
- package/dist/gui/EFScrubber.js.map +1 -1
- package/dist/gui/EFTimeDisplay.d.ts +4 -4
- package/dist/gui/EFToggleLoop.d.ts +4 -4
- package/dist/gui/EFTogglePlay.d.ts +4 -4
- package/dist/gui/EFWorkbench.d.ts +4 -4
- package/dist/gui/EFWorkbench.js +16 -3
- package/dist/gui/EFWorkbench.js.map +1 -1
- package/dist/gui/TWMixin.js +1 -1
- package/dist/gui/TWMixin.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/style.css +7 -120
- package/package.json +3 -3
- package/scripts/build-css.js +2 -6
- package/test/constants.ts +8 -0
- package/test/recordReplayProxyPlugin.js +76 -10
- package/test/setup.ts +32 -0
- package/test/useMSW.ts +3 -0
- package/test/useTranscodeMSW.ts +191 -0
- package/tsdown.config.ts +6 -4
- package/types.json +1 -1
package/dist/gui/EFFilmstrip.js
CHANGED
|
@@ -12,6 +12,8 @@ import { EFImage } from "../elements/EFImage.js";
|
|
|
12
12
|
import { EFAudio } from "../elements/EFAudio.js";
|
|
13
13
|
import { EFVideo } from "../elements/EFVideo.js";
|
|
14
14
|
import { EFCaptions, EFCaptionsActiveWord } from "../elements/EFCaptions.js";
|
|
15
|
+
import { EFText } from "../elements/EFText.js";
|
|
16
|
+
import { EFTextSegment } from "../elements/EFTextSegment.js";
|
|
15
17
|
import { EFWaveform } from "../elements/EFWaveform.js";
|
|
16
18
|
import { msToTimeCode } from "../msToTimeCode.js";
|
|
17
19
|
import { consume } from "@lit/context";
|
|
@@ -75,7 +77,7 @@ var FilmstripItem = class extends TWMixin(LitElement) {
|
|
|
75
77
|
render() {
|
|
76
78
|
return html`<div style=${styleMap(this.gutterStyles)}>
|
|
77
79
|
<div
|
|
78
|
-
|
|
80
|
+
style="background-color: var(--filmstrip-bg);"
|
|
79
81
|
?data-focused=${this.isFocused}
|
|
80
82
|
@mouseenter=${() => {
|
|
81
83
|
if (this.focusContext) this.focusContext.focusedElement = this.element;
|
|
@@ -86,8 +88,12 @@ var FilmstripItem = class extends TWMixin(LitElement) {
|
|
|
86
88
|
>
|
|
87
89
|
<div
|
|
88
90
|
?data-focused=${this.isFocused}
|
|
89
|
-
class="border-outset relative mb-[1px] block h-[1.1rem] text-nowrap border
|
|
90
|
-
style=${styleMap(
|
|
91
|
+
class="border-outset relative mb-[1px] block h-[1.1rem] text-nowrap border text-sm"
|
|
92
|
+
style=${styleMap({
|
|
93
|
+
...this.trimPortionStyles,
|
|
94
|
+
backgroundColor: this.isFocused ? "var(--filmstrip-item-focused)" : "var(--filmstrip-item-bg)",
|
|
95
|
+
borderColor: "var(--filmstrip-border)"
|
|
96
|
+
})}
|
|
91
97
|
>
|
|
92
98
|
${this.animations()}
|
|
93
99
|
</div>
|
|
@@ -113,18 +119,22 @@ var FilmstripItem = class extends TWMixin(LitElement) {
|
|
|
113
119
|
const properties = new Set(Object.keys(firstKeyframe));
|
|
114
120
|
for (const key of CommonEffectKeys) properties.delete(key);
|
|
115
121
|
return html`<div
|
|
116
|
-
class="relative h-[5px]
|
|
122
|
+
class="relative h-[5px] opacity-50"
|
|
117
123
|
label="animation"
|
|
118
124
|
style=${styleMap({
|
|
119
125
|
left: `${this.pixelsPerMs * start}px`,
|
|
120
|
-
width: `${this.pixelsPerMs * Number(duration)}px
|
|
126
|
+
width: `${this.pixelsPerMs * Number(duration)}px`,
|
|
127
|
+
backgroundColor: "var(--filmstrip-animation-bg)"
|
|
121
128
|
})}
|
|
122
129
|
>
|
|
123
130
|
<!-- <div class="text-nowrap">${Array.from(properties).join(" ")}</div> -->
|
|
124
131
|
${effect.getKeyframes().map((keyframe) => {
|
|
125
132
|
return html`<div
|
|
126
|
-
class="absolute top-0 h-full w-1
|
|
127
|
-
style=${styleMap({
|
|
133
|
+
class="absolute top-0 h-full w-1"
|
|
134
|
+
style=${styleMap({
|
|
135
|
+
left: `${this.pixelsPerMs * keyframe.computedOffset * Number(duration)}px`,
|
|
136
|
+
backgroundColor: "var(--filmstrip-keyframe-bg)"
|
|
137
|
+
})}
|
|
128
138
|
></div>`;
|
|
129
139
|
})}
|
|
130
140
|
</div>`;
|
|
@@ -176,7 +186,8 @@ let EFCaptionsFilmstrip = class EFCaptionsFilmstrip$1 extends FilmstripItem {
|
|
|
176
186
|
const captionsData = this.element.unifiedCaptionsDataTask.value;
|
|
177
187
|
return html`<div style=${styleMap(this.gutterStyles)}>
|
|
178
188
|
<div
|
|
179
|
-
class="
|
|
189
|
+
class="relative"
|
|
190
|
+
style="background-color: var(--filmstrip-bg);"
|
|
180
191
|
?data-focused=${this.isFocused}
|
|
181
192
|
@mouseenter=${() => {
|
|
182
193
|
if (this.focusContext) this.focusContext.focusedElement = this.element;
|
|
@@ -187,8 +198,12 @@ let EFCaptionsFilmstrip = class EFCaptionsFilmstrip$1 extends FilmstripItem {
|
|
|
187
198
|
>
|
|
188
199
|
<div
|
|
189
200
|
?data-focused=${this.isFocused}
|
|
190
|
-
class="border-outset relative mb-[1px] block h-[1.1rem] text-nowrap border
|
|
191
|
-
style=${styleMap(
|
|
201
|
+
class="border-outset relative mb-[1px] block h-[1.1rem] text-nowrap border text-sm overflow-hidden"
|
|
202
|
+
style=${styleMap({
|
|
203
|
+
...this.trimPortionStyles,
|
|
204
|
+
backgroundColor: this.isFocused ? "var(--filmstrip-item-focused)" : "var(--filmstrip-item-bg)",
|
|
205
|
+
borderColor: "var(--filmstrip-border)"
|
|
206
|
+
})}
|
|
192
207
|
>
|
|
193
208
|
📝 ${this.renderCaptionsData(captionsData)}
|
|
194
209
|
</div>
|
|
@@ -203,12 +218,14 @@ let EFCaptionsFilmstrip = class EFCaptionsFilmstrip$1 extends FilmstripItem {
|
|
|
203
218
|
return html`${captionsData.segments.map((segment) => {
|
|
204
219
|
const isActive = captionsLocalTimeSec >= segment.start && captionsLocalTimeSec < segment.end;
|
|
205
220
|
return html`<div
|
|
206
|
-
class="absolute border
|
|
221
|
+
class="absolute border text-xs overflow-hidden flex items-center ${isActive ? "font-bold z-[5]" : ""}"
|
|
207
222
|
style=${styleMap({
|
|
208
223
|
left: `${this.pixelsPerMs * segment.start * 1e3}px`,
|
|
209
224
|
width: `${this.pixelsPerMs * (segment.end - segment.start) * 1e3}px`,
|
|
210
225
|
height: "100%",
|
|
211
|
-
top: "0px"
|
|
226
|
+
top: "0px",
|
|
227
|
+
backgroundColor: isActive ? "var(--filmstrip-segment-bg)" : "var(--filmstrip-item-bg)",
|
|
228
|
+
borderColor: isActive ? "var(--filmstrip-segment-border)" : "var(--filmstrip-border)"
|
|
212
229
|
})}
|
|
213
230
|
title="Segment: '${segment.text}' (${segment.start}s - ${segment.end}s)"
|
|
214
231
|
>
|
|
@@ -234,26 +251,28 @@ let EFCaptionsActiveWordFilmstrip = class EFCaptionsActiveWordFilmstrip$1 extend
|
|
|
234
251
|
const parentCaptions = this.element.closest("ef-captions");
|
|
235
252
|
const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;
|
|
236
253
|
if (!captionsData) return html`<div style=${styleMap(this.captionsTrackStyles)}>
|
|
237
|
-
<div class="
|
|
254
|
+
<div class="border h-[1.1rem] mb-[1px] text-xs" style="background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);">
|
|
238
255
|
🗣️ Active Word
|
|
239
256
|
</div>
|
|
240
257
|
</div>`;
|
|
241
258
|
const captionsLocalTimeSec = ((parentCaptions.rootTimegroup?.currentTimeMs || 0) - parentCaptions.startTimeMs) / 1e3;
|
|
242
259
|
return html`<div style=${styleMap(this.captionsTrackStyles)}>
|
|
243
|
-
<div class="
|
|
260
|
+
<div class="relative border h-[1.1rem] mb-[1px] w-full" style="background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);">
|
|
244
261
|
${captionsData.word_segments.map((word) => {
|
|
245
262
|
const isCurrentlyActive = captionsLocalTimeSec >= word.start && captionsLocalTimeSec < word.end;
|
|
246
263
|
return html`<div
|
|
247
|
-
class="absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? "
|
|
264
|
+
class="absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? "font-bold z-[5]" : ""}"
|
|
248
265
|
style=${styleMap({
|
|
249
266
|
left: `${this.pixelsPerMs * word.start * 1e3}px`,
|
|
250
267
|
width: `${this.pixelsPerMs * (word.end - word.start) * 1e3}px`,
|
|
251
268
|
height: "100%",
|
|
252
|
-
top: "0px"
|
|
269
|
+
top: "0px",
|
|
270
|
+
backgroundColor: isCurrentlyActive ? "var(--filmstrip-caption-bg)" : "var(--filmstrip-item-bg)",
|
|
271
|
+
borderColor: isCurrentlyActive ? "var(--filmstrip-caption-border)" : "var(--filmstrip-border)"
|
|
253
272
|
})}
|
|
254
273
|
title="Word: '${word.text}' (${word.start}s - ${word.end}s)"
|
|
255
274
|
>
|
|
256
|
-
${isCurrentlyActive ? html`<span class="px-0.5 text-[8px] font-bold whitespace-nowrap
|
|
275
|
+
${isCurrentlyActive ? html`<span class="px-0.5 text-[8px] font-bold whitespace-nowrap" style="background-color: var(--filmstrip-caption-bg);">${word.text.trim()}</span>` : ""}
|
|
257
276
|
</div>`;
|
|
258
277
|
})}
|
|
259
278
|
</div>
|
|
@@ -274,26 +293,28 @@ let EFCaptionsSegmentFilmstrip = class EFCaptionsSegmentFilmstrip$1 extends Film
|
|
|
274
293
|
const parentCaptions = this.element.closest("ef-captions");
|
|
275
294
|
const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;
|
|
276
295
|
if (!captionsData) return html`<div style=${styleMap(this.captionsTrackStyles)}>
|
|
277
|
-
<div class="
|
|
296
|
+
<div class="border h-[1.1rem] mb-[1px] text-xs" style="background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);">
|
|
278
297
|
📄 Segment
|
|
279
298
|
</div>
|
|
280
299
|
</div>`;
|
|
281
300
|
const captionsLocalTimeSec = ((parentCaptions.rootTimegroup?.currentTimeMs || 0) - parentCaptions.startTimeMs) / 1e3;
|
|
282
301
|
return html`<div style=${styleMap(this.captionsTrackStyles)}>
|
|
283
|
-
<div class="
|
|
302
|
+
<div class="relative border h-[1.1rem] mb-[1px] w-full" style="background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);">
|
|
284
303
|
${captionsData.segments.map((segment) => {
|
|
285
304
|
const isCurrentlyActive = captionsLocalTimeSec >= segment.start && captionsLocalTimeSec < segment.end;
|
|
286
305
|
return html`<div
|
|
287
|
-
class="absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? "
|
|
306
|
+
class="absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? "font-bold z-[5]" : ""}"
|
|
288
307
|
style=${styleMap({
|
|
289
308
|
left: `${this.pixelsPerMs * segment.start * 1e3}px`,
|
|
290
309
|
width: `${this.pixelsPerMs * (segment.end - segment.start) * 1e3}px`,
|
|
291
310
|
height: "100%",
|
|
292
|
-
top: "0px"
|
|
311
|
+
top: "0px",
|
|
312
|
+
backgroundColor: isCurrentlyActive ? "var(--filmstrip-segment-bg)" : "var(--filmstrip-item-bg)",
|
|
313
|
+
borderColor: isCurrentlyActive ? "var(--filmstrip-segment-border)" : "var(--filmstrip-border)"
|
|
293
314
|
})}
|
|
294
315
|
title="Segment: '${segment.text}' (${segment.start}s - ${segment.end}s)"
|
|
295
316
|
>
|
|
296
|
-
${isCurrentlyActive ? html`<span class="px-0.5 text-[8px] font-bold whitespace-nowrap
|
|
317
|
+
${isCurrentlyActive ? html`<span class="px-0.5 text-[8px] font-bold whitespace-nowrap" style="background-color: var(--filmstrip-segment-bg);">${segment.text}</span>` : ""}
|
|
297
318
|
</div>`;
|
|
298
319
|
})}
|
|
299
320
|
</div>
|
|
@@ -314,21 +335,24 @@ let EFCaptionsBeforeWordFilmstrip = class EFCaptionsBeforeWordFilmstrip$1 extend
|
|
|
314
335
|
const parentCaptions = this.element.closest("ef-captions");
|
|
315
336
|
const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;
|
|
316
337
|
if (!captionsData) return html`<div style=${styleMap(this.captionsTrackStyles)}>
|
|
317
|
-
<div class="
|
|
338
|
+
<div class="border h-[1.1rem] mb-[1px] text-xs" style="background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);">
|
|
318
339
|
⬅️ Before
|
|
319
340
|
</div>
|
|
320
341
|
</div>`;
|
|
321
342
|
const captionsLocalTimeSec = ((parentCaptions.rootTimegroup?.currentTimeMs || 0) - parentCaptions.startTimeMs) / 1e3;
|
|
322
343
|
return html`<div style=${styleMap(this.captionsTrackStyles)}>
|
|
323
|
-
<div class="
|
|
344
|
+
<div class="relative border h-[1.1rem] mb-[1px] w-full" style="background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);">
|
|
324
345
|
${captionsData.word_segments.map((word) => {
|
|
346
|
+
const isCurrentlyActive = captionsLocalTimeSec >= word.start && captionsLocalTimeSec < word.end;
|
|
325
347
|
return html`<div
|
|
326
|
-
class="absolute border text-xs overflow-visible flex items-center ${
|
|
348
|
+
class="absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? "font-bold z-[5]" : ""}"
|
|
327
349
|
style=${styleMap({
|
|
328
350
|
left: `${this.pixelsPerMs * word.start * 1e3}px`,
|
|
329
351
|
width: `${this.pixelsPerMs * (word.end - word.start) * 1e3}px`,
|
|
330
352
|
height: "100%",
|
|
331
|
-
top: "0px"
|
|
353
|
+
top: "0px",
|
|
354
|
+
backgroundColor: isCurrentlyActive ? "var(--filmstrip-caption-bg)" : "var(--filmstrip-waveform-bg)",
|
|
355
|
+
borderColor: isCurrentlyActive ? "var(--filmstrip-caption-border)" : "var(--filmstrip-waveform-border)"
|
|
332
356
|
})}
|
|
333
357
|
title="Word: '${word.text}' (${word.start}s - ${word.end}s)"
|
|
334
358
|
>
|
|
@@ -353,21 +377,24 @@ let EFCaptionsAfterWordFilmstrip = class EFCaptionsAfterWordFilmstrip$1 extends
|
|
|
353
377
|
const parentCaptions = this.element.closest("ef-captions");
|
|
354
378
|
const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;
|
|
355
379
|
if (!captionsData) return html`<div style=${styleMap(this.captionsTrackStyles)}>
|
|
356
|
-
<div class="
|
|
380
|
+
<div class="border h-[1.1rem] mb-[1px] text-xs" style="background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);">
|
|
357
381
|
➡️ After
|
|
358
382
|
</div>
|
|
359
383
|
</div>`;
|
|
360
384
|
const captionsLocalTimeSec = ((parentCaptions.rootTimegroup?.currentTimeMs || 0) - parentCaptions.startTimeMs) / 1e3;
|
|
361
385
|
return html`<div style=${styleMap(this.captionsTrackStyles)}>
|
|
362
|
-
<div class="
|
|
386
|
+
<div class="relative border h-[1.1rem] mb-[1px] w-full" style="background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);">
|
|
363
387
|
${captionsData.word_segments.map((word) => {
|
|
388
|
+
const isCurrentlyActive = captionsLocalTimeSec >= word.start && captionsLocalTimeSec < word.end;
|
|
364
389
|
return html`<div
|
|
365
|
-
class="absolute border text-xs overflow-visible flex items-center ${
|
|
390
|
+
class="absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? "font-bold z-[5]" : ""}"
|
|
366
391
|
style=${styleMap({
|
|
367
392
|
left: `${this.pixelsPerMs * word.start * 1e3}px`,
|
|
368
393
|
width: `${this.pixelsPerMs * (word.end - word.start) * 1e3}px`,
|
|
369
394
|
height: "100%",
|
|
370
|
-
top: "0px"
|
|
395
|
+
top: "0px",
|
|
396
|
+
backgroundColor: isCurrentlyActive ? "var(--filmstrip-caption-bg)" : "var(--filmstrip-waveform-bg)",
|
|
397
|
+
borderColor: isCurrentlyActive ? "var(--filmstrip-caption-border)" : "var(--filmstrip-waveform-border)"
|
|
371
398
|
})}
|
|
372
399
|
title="Word: '${word.text}' (${word.start}s - ${word.end}s)"
|
|
373
400
|
>
|
|
@@ -388,6 +415,104 @@ let EFWaveformFilmstrip = class EFWaveformFilmstrip$1 extends FilmstripItem {
|
|
|
388
415
|
}
|
|
389
416
|
};
|
|
390
417
|
EFWaveformFilmstrip = __decorate([customElement("ef-waveform-filmstrip")], EFWaveformFilmstrip);
|
|
418
|
+
let EFTextFilmstrip = class EFTextFilmstrip$1 extends FilmstripItem {
|
|
419
|
+
render() {
|
|
420
|
+
const text = this.element;
|
|
421
|
+
const segments = Array.from(text.querySelectorAll("ef-text-segment"));
|
|
422
|
+
return html`<div style=${styleMap(this.gutterStyles)}>
|
|
423
|
+
<div
|
|
424
|
+
class="relative"
|
|
425
|
+
style="background-color: var(--filmstrip-bg);"
|
|
426
|
+
?data-focused=${this.isFocused}
|
|
427
|
+
@mouseenter=${() => {
|
|
428
|
+
if (this.focusContext) this.focusContext.focusedElement = this.element;
|
|
429
|
+
}}
|
|
430
|
+
@mouseleave=${() => {
|
|
431
|
+
if (this.focusContext) this.focusContext.focusedElement = null;
|
|
432
|
+
}}
|
|
433
|
+
>
|
|
434
|
+
<div
|
|
435
|
+
?data-focused=${this.isFocused}
|
|
436
|
+
class="border-outset relative mb-[1px] block h-[1.1rem] text-nowrap border text-sm overflow-hidden"
|
|
437
|
+
style=${styleMap({
|
|
438
|
+
...this.trimPortionStyles,
|
|
439
|
+
backgroundColor: this.isFocused ? "var(--filmstrip-item-focused)" : "var(--filmstrip-item-bg)",
|
|
440
|
+
borderColor: "var(--filmstrip-border)"
|
|
441
|
+
})}
|
|
442
|
+
>
|
|
443
|
+
📄 ${this.renderTextSegments(segments)}
|
|
444
|
+
</div>
|
|
445
|
+
</div>
|
|
446
|
+
${this.renderChildren()}
|
|
447
|
+
</div>`;
|
|
448
|
+
}
|
|
449
|
+
renderTextSegments(segments) {
|
|
450
|
+
if (segments.length === 0) return html``;
|
|
451
|
+
const text = this.element;
|
|
452
|
+
const textLocalTimeMs = (text.rootTimegroup?.currentTimeMs || 0) - text.startTimeMs;
|
|
453
|
+
return segments.map((segment) => {
|
|
454
|
+
const isActive = textLocalTimeMs >= segment.segmentStartMs && textLocalTimeMs < segment.segmentEndMs;
|
|
455
|
+
return html`<div
|
|
456
|
+
class="absolute border text-xs overflow-hidden flex items-center ${isActive ? "font-bold z-[5]" : ""}"
|
|
457
|
+
style=${styleMap({
|
|
458
|
+
left: `${this.pixelsPerMs * segment.segmentStartMs}px`,
|
|
459
|
+
width: `${this.pixelsPerMs * (segment.segmentEndMs - segment.segmentStartMs)}px`,
|
|
460
|
+
height: "100%",
|
|
461
|
+
top: "0px",
|
|
462
|
+
backgroundColor: isActive ? "var(--filmstrip-segment-bg)" : "var(--filmstrip-item-bg)",
|
|
463
|
+
borderColor: isActive ? "var(--filmstrip-segment-border)" : "var(--filmstrip-border)"
|
|
464
|
+
})}
|
|
465
|
+
title="Segment: '${segment.segmentText}' (${segment.segmentStartMs}ms - ${segment.segmentEndMs}ms)"
|
|
466
|
+
>
|
|
467
|
+
<span class="px-0.5 text-[8px] ${isActive ? "font-bold" : ""}">${segment.segmentText}</span>
|
|
468
|
+
</div>`;
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
renderChildren() {
|
|
472
|
+
return renderFilmstripChildren(Array.from(this.element.children), this.pixelsPerMs, this.hideSelectors, this.showSelectors);
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
EFTextFilmstrip = __decorate([customElement("ef-text-filmstrip")], EFTextFilmstrip);
|
|
476
|
+
let EFTextSegmentFilmstrip = class EFTextSegmentFilmstrip$1 extends FilmstripItem {
|
|
477
|
+
get textTrackStyles() {
|
|
478
|
+
const parentText = this.element.closest("ef-text");
|
|
479
|
+
return {
|
|
480
|
+
position: "relative",
|
|
481
|
+
left: `${this.pixelsPerMs * (parentText?.startTimeWithinParentMs || 0)}px`,
|
|
482
|
+
width: `${this.pixelsPerMs * (parentText?.durationMs || 0)}px`
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
render() {
|
|
486
|
+
const segment = this.element;
|
|
487
|
+
const parentText = segment.closest("ef-text");
|
|
488
|
+
if (!parentText) return html`<div style=${styleMap(this.textTrackStyles)}>
|
|
489
|
+
<div class="border h-[1.1rem] mb-[1px] text-xs" style="background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);">
|
|
490
|
+
📄 Text Segment
|
|
491
|
+
</div>
|
|
492
|
+
</div>`;
|
|
493
|
+
const textLocalTimeMs = (parentText.rootTimegroup?.currentTimeMs || 0) - parentText.startTimeMs;
|
|
494
|
+
const isCurrentlyActive = textLocalTimeMs >= segment.segmentStartMs && textLocalTimeMs < segment.segmentEndMs;
|
|
495
|
+
return html`<div style=${styleMap(this.textTrackStyles)}>
|
|
496
|
+
<div class="relative border h-[1.1rem] mb-[1px] w-full" style="background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);">
|
|
497
|
+
<div
|
|
498
|
+
class="absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? "font-bold z-[5]" : ""}"
|
|
499
|
+
style=${styleMap({
|
|
500
|
+
left: `${this.pixelsPerMs * segment.segmentStartMs}px`,
|
|
501
|
+
width: `${this.pixelsPerMs * (segment.segmentEndMs - segment.segmentStartMs)}px`,
|
|
502
|
+
height: "100%",
|
|
503
|
+
top: "0px",
|
|
504
|
+
backgroundColor: isCurrentlyActive ? "var(--filmstrip-caption-bg)" : "var(--filmstrip-item-bg)",
|
|
505
|
+
borderColor: isCurrentlyActive ? "var(--filmstrip-caption-border)" : "var(--filmstrip-border)"
|
|
506
|
+
})}
|
|
507
|
+
title="Segment: '${segment.segmentText}' (${segment.segmentStartMs}ms - ${segment.segmentEndMs}ms)"
|
|
508
|
+
>
|
|
509
|
+
${isCurrentlyActive ? html`<span class="px-0.5 text-[8px] font-bold whitespace-nowrap" style="background-color: var(--filmstrip-caption-bg);">${segment.segmentText}</span>` : ""}
|
|
510
|
+
</div>
|
|
511
|
+
</div>
|
|
512
|
+
</div>`;
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
EFTextSegmentFilmstrip = __decorate([customElement("ef-text-segment-filmstrip")], EFTextSegmentFilmstrip);
|
|
391
516
|
let EFImageFilmstrip = class EFImageFilmstrip$1 extends FilmstripItem {
|
|
392
517
|
contents() {
|
|
393
518
|
return html` 🖼️ `;
|
|
@@ -431,9 +556,11 @@ let EFHierarchyItem = class EFHierarchyItem$1 extends TWMixin(LitElement) {
|
|
|
431
556
|
return html`
|
|
432
557
|
<div>
|
|
433
558
|
<div
|
|
434
|
-
class="peer
|
|
435
|
-
|
|
436
|
-
|
|
559
|
+
class="peer flex h-[1.1rem] items-center overflow-hidden text-nowrap border pl-2 text-xs font-mono"
|
|
560
|
+
style=${styleMap({
|
|
561
|
+
backgroundColor: this.isFocused ? "var(--filmstrip-timegroup-focused)" : "var(--filmstrip-timegroup-bg)",
|
|
562
|
+
borderColor: "var(--filmstrip-border)"
|
|
563
|
+
})}
|
|
437
564
|
?data-focused=${this.isFocused}
|
|
438
565
|
@mouseenter=${() => {
|
|
439
566
|
if (this.focusContext) this.focusContext.focusedElement = this.element;
|
|
@@ -445,7 +572,8 @@ let EFHierarchyItem = class EFHierarchyItem$1 extends TWMixin(LitElement) {
|
|
|
445
572
|
${this.icon} ${this.displayLabel()}
|
|
446
573
|
</div>
|
|
447
574
|
<div
|
|
448
|
-
class="p-[1px] pb-0 pl-2 pr-0 peer-hover
|
|
575
|
+
class="p-[1px] pb-0 pl-2 pr-0 peer-hover-container peer-data-container"
|
|
576
|
+
style=${styleMap({ backgroundColor: this.isFocused ? "var(--filmstrip-bg)" : "transparent" })}
|
|
449
577
|
>
|
|
450
578
|
${this.renderChildren()}
|
|
451
579
|
</div>
|
|
@@ -512,6 +640,18 @@ let EFCaptionsActiveWordHierarchyItem = class EFCaptionsActiveWordHierarchyItem$
|
|
|
512
640
|
}
|
|
513
641
|
};
|
|
514
642
|
EFCaptionsActiveWordHierarchyItem = __decorate([customElement("ef-captions-active-word-hierarchy-item")], EFCaptionsActiveWordHierarchyItem);
|
|
643
|
+
let EFTextHierarchyItem = class EFTextHierarchyItem$1 extends EFHierarchyItem {
|
|
644
|
+
get icon() {
|
|
645
|
+
return "📄 Text";
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
EFTextHierarchyItem = __decorate([customElement("ef-text-hierarchy-item")], EFTextHierarchyItem);
|
|
649
|
+
let EFTextSegmentHierarchyItem = class EFTextSegmentHierarchyItem$1 extends EFHierarchyItem {
|
|
650
|
+
get icon() {
|
|
651
|
+
return "📄 Segment";
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
EFTextSegmentHierarchyItem = __decorate([customElement("ef-text-segment-hierarchy-item")], EFTextSegmentHierarchyItem);
|
|
515
655
|
let EFWaveformHierarchyItem = class EFWaveformHierarchyItem$1 extends EFHierarchyItem {
|
|
516
656
|
get icon() {
|
|
517
657
|
return "🌊";
|
|
@@ -587,6 +727,16 @@ const renderHierarchyChildren = (children, hideSelectors, showSelectors, skipRoo
|
|
|
587
727
|
.hideSelectors=${hideSelectors}
|
|
588
728
|
.showSelectors=${showSelectors}
|
|
589
729
|
></ef-captions-active-word-hierarchy-item>`;
|
|
730
|
+
if (child instanceof EFText) return html`<ef-text-hierarchy-item
|
|
731
|
+
.element=${child}
|
|
732
|
+
.hideSelectors=${hideSelectors}
|
|
733
|
+
.showSelectors=${showSelectors}
|
|
734
|
+
></ef-text-hierarchy-item>`;
|
|
735
|
+
if (child instanceof EFTextSegment) return html`<ef-text-segment-hierarchy-item
|
|
736
|
+
.element=${child}
|
|
737
|
+
.hideSelectors=${hideSelectors}
|
|
738
|
+
.showSelectors=${showSelectors}
|
|
739
|
+
></ef-text-segment-hierarchy-item>`;
|
|
590
740
|
if (child instanceof EFWaveform) return html`<ef-waveform-hierarchy-item
|
|
591
741
|
.element=${child}
|
|
592
742
|
.hideSelectors=${hideSelectors}
|
|
@@ -639,6 +789,18 @@ const renderFilmstripChildren = (children, pixelsPerMs, hideSelectors, showSelec
|
|
|
639
789
|
.hideSelectors=${hideSelectors}
|
|
640
790
|
.showSelectors=${showSelectors}
|
|
641
791
|
></ef-captions-active-word-filmstrip>`;
|
|
792
|
+
if (child instanceof EFText) return html`<ef-text-filmstrip
|
|
793
|
+
.element=${child}
|
|
794
|
+
.pixelsPerMs=${pixelsPerMs}
|
|
795
|
+
.hideSelectors=${hideSelectors}
|
|
796
|
+
.showSelectors=${showSelectors}
|
|
797
|
+
></ef-text-filmstrip>`;
|
|
798
|
+
if (child instanceof EFTextSegment) return html`<ef-text-segment-filmstrip
|
|
799
|
+
.element=${child}
|
|
800
|
+
.pixelsPerMs=${pixelsPerMs}
|
|
801
|
+
.hideSelectors=${hideSelectors}
|
|
802
|
+
.showSelectors=${showSelectors}
|
|
803
|
+
></ef-text-segment-filmstrip>`;
|
|
642
804
|
if (child.tagName === "EF-CAPTIONS-SEGMENT") return html`<ef-captions-segment-filmstrip
|
|
643
805
|
.element=${child}
|
|
644
806
|
.pixelsPerMs=${pixelsPerMs}
|
|
@@ -678,6 +840,7 @@ let EFFilmstrip = class EFFilmstrip$1 extends TWMixin(LitElement) {
|
|
|
678
840
|
this.hide = "";
|
|
679
841
|
this.show = "";
|
|
680
842
|
this.scrubbing = false;
|
|
843
|
+
this.capturedPointerId = null;
|
|
681
844
|
this.timelineScrolltop = 0;
|
|
682
845
|
this.currentTimeMs = 0;
|
|
683
846
|
this.autoScale = false;
|
|
@@ -697,6 +860,48 @@ let EFFilmstrip = class EFFilmstrip$1 extends TWMixin(LitElement) {
|
|
|
697
860
|
overflow: hidden;
|
|
698
861
|
width: 100%;
|
|
699
862
|
height: 100%;
|
|
863
|
+
|
|
864
|
+
/* Light mode colors */
|
|
865
|
+
--filmstrip-bg: rgb(203 213 225); /* slate-300 */
|
|
866
|
+
--filmstrip-border: rgb(100 116 139); /* slate-500 */
|
|
867
|
+
--filmstrip-item-bg: rgb(191 219 254); /* blue-200 */
|
|
868
|
+
--filmstrip-item-focused: rgb(148 163 184); /* slate-400 */
|
|
869
|
+
--filmstrip-animation-bg: rgb(59 130 246); /* blue-500 */
|
|
870
|
+
--filmstrip-keyframe-bg: rgb(239 68 68); /* red-500 */
|
|
871
|
+
--filmstrip-caption-bg: rgb(254 240 138); /* yellow-200 */
|
|
872
|
+
--filmstrip-caption-border: rgb(234 179 8); /* yellow-500 */
|
|
873
|
+
--filmstrip-segment-bg: rgb(187 247 208); /* green-200 */
|
|
874
|
+
--filmstrip-segment-border: rgb(34 197 94); /* green-500 */
|
|
875
|
+
--filmstrip-waveform-bg: rgb(250 245 255); /* purple-50 */
|
|
876
|
+
--filmstrip-waveform-border: rgb(233 213 255); /* purple-200 */
|
|
877
|
+
--filmstrip-timegroup-bg: rgb(226 232 240); /* slate-200 */
|
|
878
|
+
--filmstrip-timegroup-hover: rgb(148 163 184); /* slate-400 */
|
|
879
|
+
--filmstrip-timegroup-focused: rgb(148 163 184); /* slate-400 */
|
|
880
|
+
--filmstrip-gutter-bg: rgb(241 245 249); /* slate-100 */
|
|
881
|
+
--filmstrip-timeline-bg: rgb(226 232 240); /* slate-200 */
|
|
882
|
+
--filmstrip-playhead: rgb(185 28 28); /* red-700 */
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
:host(.dark), :host-context(.dark) {
|
|
886
|
+
/* Dark mode colors */
|
|
887
|
+
--filmstrip-bg: rgb(71 85 105); /* slate-600 */
|
|
888
|
+
--filmstrip-border: rgb(148 163 184); /* slate-400 */
|
|
889
|
+
--filmstrip-item-bg: rgb(30 64 175); /* blue-800 */
|
|
890
|
+
--filmstrip-item-focused: rgb(100 116 139); /* slate-500 */
|
|
891
|
+
--filmstrip-animation-bg: rgb(96 165 250); /* blue-400 */
|
|
892
|
+
--filmstrip-keyframe-bg: rgb(248 113 113); /* red-400 */
|
|
893
|
+
--filmstrip-caption-bg: rgb(133 77 14); /* yellow-800 */
|
|
894
|
+
--filmstrip-caption-border: rgb(250 204 21); /* yellow-400 */
|
|
895
|
+
--filmstrip-segment-bg: rgb(22 101 52); /* green-800 */
|
|
896
|
+
--filmstrip-segment-border: rgb(74 222 128); /* green-400 */
|
|
897
|
+
--filmstrip-waveform-bg: rgb(88 28 135); /* purple-900 */
|
|
898
|
+
--filmstrip-waveform-border: rgb(126 34 206); /* purple-700 */
|
|
899
|
+
--filmstrip-timegroup-bg: rgb(51 65 85); /* slate-700 */
|
|
900
|
+
--filmstrip-timegroup-hover: rgb(100 116 139); /* slate-500 */
|
|
901
|
+
--filmstrip-timegroup-focused: rgb(100 116 139); /* slate-500 */
|
|
902
|
+
--filmstrip-gutter-bg: rgb(30 41 59); /* slate-800 */
|
|
903
|
+
--filmstrip-timeline-bg: rgb(51 65 85); /* slate-700 */
|
|
904
|
+
--filmstrip-playhead: rgb(239 68 68); /* red-500 */
|
|
700
905
|
}
|
|
701
906
|
`];
|
|
702
907
|
}
|
|
@@ -756,17 +961,61 @@ let EFFilmstrip = class EFFilmstrip$1 extends TWMixin(LitElement) {
|
|
|
756
961
|
scrub(e) {
|
|
757
962
|
if (this.playing) return;
|
|
758
963
|
if (!this.scrubbing) return;
|
|
964
|
+
if (this.capturedPointerId !== null && e.pointerId !== this.capturedPointerId) return;
|
|
965
|
+
e.preventDefault();
|
|
966
|
+
e.stopPropagation();
|
|
759
967
|
this.applyScrub(e);
|
|
760
968
|
}
|
|
761
969
|
startScrub(e) {
|
|
762
970
|
e.preventDefault();
|
|
971
|
+
e.stopPropagation();
|
|
763
972
|
this.scrubbing = true;
|
|
973
|
+
this.capturedPointerId = e.pointerId;
|
|
974
|
+
const target = e.currentTarget;
|
|
975
|
+
if (target) try {
|
|
976
|
+
target.setPointerCapture(e.pointerId);
|
|
977
|
+
} catch (err) {
|
|
978
|
+
console.warn("Failed to set pointer capture:", err);
|
|
979
|
+
}
|
|
764
980
|
queueMicrotask(() => {
|
|
765
981
|
this.applyScrub(e);
|
|
766
982
|
});
|
|
767
|
-
|
|
768
|
-
this.
|
|
769
|
-
|
|
983
|
+
const handlePointerUp = (upEvent) => {
|
|
984
|
+
if (upEvent.pointerId === this.capturedPointerId) {
|
|
985
|
+
upEvent.preventDefault();
|
|
986
|
+
upEvent.stopPropagation();
|
|
987
|
+
if (target) try {
|
|
988
|
+
target.releasePointerCapture(upEvent.pointerId);
|
|
989
|
+
} catch (_err) {}
|
|
990
|
+
this.capturedPointerId = null;
|
|
991
|
+
this.scrubbing = false;
|
|
992
|
+
removeEventListener("pointerup", handlePointerUp);
|
|
993
|
+
}
|
|
994
|
+
};
|
|
995
|
+
const handlePointerCancel = (cancelEvent) => {
|
|
996
|
+
if (cancelEvent.pointerId === this.capturedPointerId) {
|
|
997
|
+
if (target) try {
|
|
998
|
+
target.releasePointerCapture(cancelEvent.pointerId);
|
|
999
|
+
} catch (_err) {}
|
|
1000
|
+
this.capturedPointerId = null;
|
|
1001
|
+
this.scrubbing = false;
|
|
1002
|
+
removeEventListener("pointercancel", handlePointerCancel);
|
|
1003
|
+
}
|
|
1004
|
+
};
|
|
1005
|
+
addEventListener("pointerup", handlePointerUp, {
|
|
1006
|
+
once: true,
|
|
1007
|
+
passive: false
|
|
1008
|
+
});
|
|
1009
|
+
addEventListener("pointercancel", handlePointerCancel, {
|
|
1010
|
+
once: true,
|
|
1011
|
+
passive: false
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
handleContextMenu(e) {
|
|
1015
|
+
if (this.scrubbing) {
|
|
1016
|
+
e.preventDefault();
|
|
1017
|
+
e.stopPropagation();
|
|
1018
|
+
}
|
|
770
1019
|
}
|
|
771
1020
|
applyScrub(e) {
|
|
772
1021
|
const gutter = this.shadowRoot?.querySelector("#gutter");
|
|
@@ -800,14 +1049,16 @@ let EFFilmstrip = class EFFilmstrip$1 extends TWMixin(LitElement) {
|
|
|
800
1049
|
render() {
|
|
801
1050
|
const target = this.targetTemporal;
|
|
802
1051
|
return html` <div
|
|
803
|
-
class="grid h-full
|
|
1052
|
+
class="grid h-full"
|
|
804
1053
|
style=${styleMap({
|
|
805
1054
|
gridTemplateColumns: "200px 1fr",
|
|
806
|
-
gridTemplateRows: "1.5rem 1fr"
|
|
1055
|
+
gridTemplateRows: "1.5rem 1fr",
|
|
1056
|
+
backgroundColor: "var(--filmstrip-gutter-bg)"
|
|
807
1057
|
})}
|
|
808
1058
|
>
|
|
809
1059
|
<div
|
|
810
|
-
class="z-20 col-span-2
|
|
1060
|
+
class="z-20 col-span-2 shadow"
|
|
1061
|
+
style="background-color: var(--filmstrip-gutter-bg); border-bottom-color: var(--filmstrip-border);"
|
|
811
1062
|
>
|
|
812
1063
|
${!this.autoScale ? html`<input
|
|
813
1064
|
type="range"
|
|
@@ -833,30 +1084,33 @@ let EFFilmstrip = class EFFilmstrip$1 extends TWMixin(LitElement) {
|
|
|
833
1084
|
<ef-toggle-loop><button>${this.loop ? "🔁" : html`<span class="opacity-50 line-through">🔁</span>`}</button></ef-toggle-loop>
|
|
834
1085
|
</div>
|
|
835
1086
|
<div
|
|
836
|
-
class="z-10 pl-1 pr-1 pt-[8px] shadow
|
|
1087
|
+
class="z-10 pl-1 pr-1 pt-[8px] shadow overflow-auto"
|
|
837
1088
|
${ref(this.hierarchyRef)}
|
|
838
1089
|
@scroll=${this.syncHierarchyScroll}
|
|
839
1090
|
>
|
|
840
1091
|
${renderHierarchyChildren(target ? [target] : [], this.hideSelectors, this.showSelectors, true)}
|
|
841
1092
|
</div>
|
|
842
1093
|
<div
|
|
843
|
-
class="flex h-full w-full cursor-crosshair overflow-auto
|
|
1094
|
+
class="flex h-full w-full cursor-crosshair overflow-auto pt-[8px] touch-pan-x"
|
|
1095
|
+
style="background-color: var(--filmstrip-timeline-bg);"
|
|
844
1096
|
id="gutter"
|
|
845
1097
|
${ref(this.gutterRef)}
|
|
846
1098
|
@scroll=${this.syncGutterScroll}
|
|
847
1099
|
@wheel=${this.scrollScrub}
|
|
848
1100
|
>
|
|
849
1101
|
<div
|
|
850
|
-
class="relative h-full w-full"
|
|
851
|
-
style="width: ${this.pixelsPerMs * (target?.durationMs ?? 0)}px;"
|
|
852
|
-
@
|
|
853
|
-
@
|
|
1102
|
+
class="relative h-full w-full touch-none"
|
|
1103
|
+
style="width: ${this.pixelsPerMs * (target?.durationMs ?? 0)}px; touch-action: none; user-select: none;"
|
|
1104
|
+
@pointermove=${this.scrub}
|
|
1105
|
+
@pointerdown=${this.startScrub}
|
|
1106
|
+
@contextmenu=${this.handleContextMenu}
|
|
854
1107
|
>
|
|
855
1108
|
<div
|
|
856
|
-
class="border-red pointer-events-none absolute z-[20] h-full w-[2px] border-r-2
|
|
1109
|
+
class="border-red pointer-events-none absolute z-[20] h-full w-[2px] border-r-2"
|
|
857
1110
|
style=${styleMap({
|
|
858
1111
|
left: `${this.pixelsPerMs * this.currentTimeMs}px`,
|
|
859
|
-
top: `${this.timelineScrolltop}px
|
|
1112
|
+
top: `${this.timelineScrolltop}px`,
|
|
1113
|
+
borderColor: "var(--filmstrip-playhead)"
|
|
860
1114
|
})}
|
|
861
1115
|
${ref(this.playheadRef)}
|
|
862
1116
|
></div>
|
|
@@ -921,8 +1175,18 @@ __decorate([property({
|
|
|
921
1175
|
})], EFFilmstrip.prototype, "autoScale", void 0);
|
|
922
1176
|
__decorate([eventOptions({ passive: false })], EFFilmstrip.prototype, "syncGutterScroll", null);
|
|
923
1177
|
__decorate([eventOptions({ passive: false })], EFFilmstrip.prototype, "syncHierarchyScroll", null);
|
|
924
|
-
__decorate([eventOptions({
|
|
925
|
-
|
|
1178
|
+
__decorate([eventOptions({
|
|
1179
|
+
capture: false,
|
|
1180
|
+
passive: false
|
|
1181
|
+
})], EFFilmstrip.prototype, "scrub", null);
|
|
1182
|
+
__decorate([eventOptions({
|
|
1183
|
+
capture: false,
|
|
1184
|
+
passive: false
|
|
1185
|
+
})], EFFilmstrip.prototype, "startScrub", null);
|
|
1186
|
+
__decorate([eventOptions({
|
|
1187
|
+
passive: false,
|
|
1188
|
+
capture: false
|
|
1189
|
+
})], EFFilmstrip.prototype, "handleContextMenu", null);
|
|
926
1190
|
__decorate([eventOptions({ passive: false })], EFFilmstrip.prototype, "scrollScrub", null);
|
|
927
1191
|
__decorate([property({ type: String })], EFFilmstrip.prototype, "target", void 0);
|
|
928
1192
|
__decorate([state()], EFFilmstrip.prototype, "targetElement", void 0);
|