lexgui 8.2.0 → 8.2.1
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/build/components/Avatar.d.ts +15 -15
- package/build/components/NodeTree.d.ts +51 -51
- package/build/components/Vector.d.ts +10 -10
- package/build/core/Event.d.ts +6 -6
- package/build/core/Namespace.js +1 -1
- package/build/core/Namespace.js.map +1 -1
- package/build/core/Panel.d.ts +538 -538
- package/build/extensions/AssetView.d.ts +137 -137
- package/build/extensions/AssetView.js +5 -6
- package/build/extensions/AssetView.js.map +1 -1
- package/build/extensions/CodeEditor.d.ts +358 -358
- package/build/extensions/CodeEditor.js +7 -7
- package/build/extensions/CodeEditor.js.map +1 -1
- package/build/extensions/DocMaker.js +1 -0
- package/build/extensions/DocMaker.js.map +1 -1
- package/build/extensions/GraphEditor.js +2754 -2754
- package/build/extensions/Timeline.d.ts +668 -668
- package/build/extensions/Timeline.js +2 -2
- package/build/extensions/Timeline.js.map +1 -1
- package/build/extensions/VideoEditor.d.ts +37 -15
- package/build/extensions/VideoEditor.js +287 -166
- package/build/extensions/VideoEditor.js.map +1 -1
- package/build/index.css.d.ts +3 -3
- package/build/index.d.ts +57 -57
- package/build/lexgui.all.js +327 -185
- package/build/lexgui.all.js.map +1 -1
- package/build/lexgui.all.min.js +1 -1
- package/build/lexgui.all.module.js +327 -185
- package/build/lexgui.all.module.js.map +1 -1
- package/build/lexgui.all.module.min.js +1 -1
- package/build/lexgui.css +213 -220
- package/build/lexgui.js +25 -4
- package/build/lexgui.js.map +1 -1
- package/build/lexgui.min.css +1 -1
- package/build/lexgui.min.js +1 -1
- package/build/lexgui.module.js +25 -4
- package/build/lexgui.module.js.map +1 -1
- package/build/lexgui.module.min.js +1 -1
- package/changelog.md +23 -1
- package/examples/all-components.html +3 -4
- package/examples/code-editor.html +11 -0
- package/examples/dialogs.html +13 -2
- package/package.json +1 -1
|
@@ -22,17 +22,22 @@ class TimeBar {
|
|
|
22
22
|
duration = 1.0;
|
|
23
23
|
canvas;
|
|
24
24
|
ctx;
|
|
25
|
+
options;
|
|
25
26
|
markerWidth = 8;
|
|
26
27
|
markerHeight;
|
|
27
28
|
offset;
|
|
28
29
|
lineWidth;
|
|
29
30
|
lineHeight;
|
|
30
|
-
|
|
31
|
+
linePosition;
|
|
31
32
|
startX;
|
|
32
33
|
endX;
|
|
33
34
|
currentX;
|
|
34
35
|
hovering;
|
|
35
36
|
dragging;
|
|
37
|
+
_onMouseUpListener;
|
|
38
|
+
_onMouseMoveListener;
|
|
39
|
+
_mouseDownCanvasRect = null;
|
|
40
|
+
updateTheme;
|
|
36
41
|
onChangeCurrent;
|
|
37
42
|
onChangeStart;
|
|
38
43
|
onChangeEnd;
|
|
@@ -40,46 +45,65 @@ class TimeBar {
|
|
|
40
45
|
onMouse;
|
|
41
46
|
constructor(area, type, options = {}) {
|
|
42
47
|
this.type = type ?? TimeBar.TIMEBAR_PLAY;
|
|
48
|
+
this.options = options ?? {};
|
|
43
49
|
this.duration = options.duration ?? this.duration;
|
|
44
50
|
// Create canvas
|
|
45
51
|
this.canvas = document.createElement('canvas');
|
|
52
|
+
this.canvas.style.borderRadius = '6px';
|
|
46
53
|
this.canvas.width = area.size[0];
|
|
47
54
|
this.canvas.height = area.size[1];
|
|
48
55
|
area.attach(this.canvas);
|
|
49
56
|
this.ctx = this.canvas.getContext('2d');
|
|
50
57
|
this.markerWidth = options.markerWidth ?? this.markerWidth;
|
|
51
|
-
this.markerHeight = options.markerHeight ??
|
|
52
|
-
|
|
58
|
+
this.markerHeight = (options.markerHeight ?? 0.5) * this.canvas.height;
|
|
59
|
+
const defaultOffset = this.markerWidth * 0.5 + 5;
|
|
60
|
+
if (typeof (options.offset) == 'number') {
|
|
61
|
+
this.offset = new vec2(options.offset, options.offset);
|
|
62
|
+
}
|
|
63
|
+
else if (Array.isArray(options.offset)) {
|
|
64
|
+
this.offset = new vec2(options.offset[0] ?? defaultOffset, options.offset[1] ?? defaultOffset);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
this.offset = new vec2(defaultOffset, defaultOffset);
|
|
68
|
+
}
|
|
53
69
|
// dimensions of line (not canvas)
|
|
54
|
-
this.lineWidth = this.canvas.width - this.offset * 2;
|
|
70
|
+
this.lineWidth = this.canvas.width - this.offset.x * 2;
|
|
55
71
|
this.lineHeight = options.barHeight ?? 5;
|
|
56
|
-
this.
|
|
57
|
-
this.startX = this.
|
|
58
|
-
this.endX = this.
|
|
72
|
+
this.linePosition = new vec2(this.offset.x, this.canvas.height * 0.5 - this.lineHeight * 0.5);
|
|
73
|
+
this.startX = this.linePosition.x;
|
|
74
|
+
this.endX = this.linePosition.x + this.lineWidth;
|
|
59
75
|
this.currentX = this.startX;
|
|
60
76
|
this._draw();
|
|
77
|
+
function updateTheme() {
|
|
78
|
+
TimeBar.BACKGROUND_COLOR = LX.getCSSVariable('secondary');
|
|
79
|
+
TimeBar.COLOR = LX.getCSSVariable('accent');
|
|
80
|
+
TimeBar.ACTIVE_COLOR = LX.getCSSVariable('color-blue-400');
|
|
81
|
+
}
|
|
82
|
+
this.updateTheme = updateTheme.bind(this);
|
|
83
|
+
LX.addSignal('@on_new_color_scheme', this.updateTheme);
|
|
61
84
|
this.updateTheme();
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
});
|
|
85
|
+
// prepare event listeners' functions
|
|
86
|
+
this._onMouseUpListener = this.onMouseUp.bind(this);
|
|
87
|
+
this._onMouseMoveListener = this.onMouseMove.bind(this);
|
|
66
88
|
this.canvas.onmousedown = (e) => this.onMouseDown(e);
|
|
67
|
-
this.canvas.onmousemove = (e) =>
|
|
68
|
-
|
|
89
|
+
this.canvas.onmousemove = (e) => {
|
|
90
|
+
if (this.dragging)
|
|
91
|
+
return; // already handled by _onMouseMoveListener
|
|
92
|
+
this.onMouseMove(e);
|
|
93
|
+
};
|
|
69
94
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
TimeBar.ACTIVE_COLOR = LX.getCSSVariable('color-blue-400');
|
|
95
|
+
unbind() {
|
|
96
|
+
removeEventListener('mousemove', this._onMouseMoveListener);
|
|
97
|
+
removeEventListener('mouseup', this._onMouseUpListener);
|
|
74
98
|
}
|
|
75
99
|
setDuration(duration) {
|
|
76
100
|
this.duration = duration;
|
|
77
101
|
}
|
|
78
102
|
xToTime(x) {
|
|
79
|
-
return ((x - this.offset) / (this.lineWidth)) * this.duration;
|
|
103
|
+
return ((x - this.offset.x) / (this.lineWidth)) * this.duration;
|
|
80
104
|
}
|
|
81
105
|
timeToX(time) {
|
|
82
|
-
return (time / this.duration) * (this.lineWidth) + this.offset;
|
|
106
|
+
return (time / this.duration) * (this.lineWidth) + this.offset.x;
|
|
83
107
|
}
|
|
84
108
|
setCurrentTime(time) {
|
|
85
109
|
this.currentX = this.timeToX(time);
|
|
@@ -124,10 +148,10 @@ class TimeBar {
|
|
|
124
148
|
ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
|
125
149
|
// Draw background timeline
|
|
126
150
|
ctx.fillStyle = TimeBar.COLOR;
|
|
127
|
-
ctx.fillRect(this.
|
|
151
|
+
ctx.fillRect(this.linePosition.x, this.linePosition.y, this.lineWidth, this.lineHeight);
|
|
128
152
|
// Draw background trimed timeline
|
|
129
153
|
ctx.fillStyle = TimeBar.ACTIVE_COLOR;
|
|
130
|
-
ctx.fillRect(this.startX, this.
|
|
154
|
+
ctx.fillRect(this.startX, this.linePosition.y, this.endX - this.startX, this.lineHeight);
|
|
131
155
|
ctx.restore();
|
|
132
156
|
// Min-Max time markers
|
|
133
157
|
this._drawTrimMarker('start', this.startX, { color: null, fillColor: TimeBar.ACTIVE_COLOR || '#5f88c9' });
|
|
@@ -164,9 +188,9 @@ class TimeBar {
|
|
|
164
188
|
ctx.shadowBlur = 0;
|
|
165
189
|
}
|
|
166
190
|
_drawTimeMarker(name, x, options = {}) {
|
|
167
|
-
let y = this.offset;
|
|
191
|
+
let y = this.offset.y;
|
|
168
192
|
const w = options.width ? options.width : (this.dragging == name ? 6 : 4);
|
|
169
|
-
|
|
193
|
+
this.canvas.height - this.offset.y * 2;
|
|
170
194
|
let ctx = this.ctx;
|
|
171
195
|
if (!ctx)
|
|
172
196
|
return;
|
|
@@ -181,15 +205,14 @@ class TimeBar {
|
|
|
181
205
|
ctx.fillStyle = ctx.strokeStyle = 'white';
|
|
182
206
|
ctx.beginPath();
|
|
183
207
|
ctx.moveTo(x, y);
|
|
184
|
-
ctx.lineTo(x, y +
|
|
208
|
+
ctx.lineTo(x, this.linePosition.y + this.lineHeight * 0.5);
|
|
185
209
|
ctx.stroke();
|
|
186
210
|
ctx.closePath();
|
|
187
211
|
ctx.fillStyle = ctx.strokeStyle = options.fillColor || '#111'; // "#FFF";
|
|
188
|
-
y -= this.offset + 8;
|
|
189
212
|
// Current time ball grab
|
|
190
213
|
ctx.fillStyle = options.fillColor || '#e5e5e5';
|
|
191
214
|
ctx.beginPath();
|
|
192
|
-
ctx.roundRect(x - w * 0.5, y
|
|
215
|
+
ctx.roundRect(x - w * 0.5, y - w * 0.5, w, w, 5);
|
|
193
216
|
ctx.fill();
|
|
194
217
|
ctx.shadowBlur = 0;
|
|
195
218
|
}
|
|
@@ -211,13 +234,11 @@ class TimeBar {
|
|
|
211
234
|
const y = e.offsetY;
|
|
212
235
|
// Check if some marker is clicked
|
|
213
236
|
const threshold = this.markerWidth;
|
|
237
|
+
const startDist = Math.abs(this.startX - x);
|
|
238
|
+
const endDist = Math.abs(this.endX - x);
|
|
214
239
|
// grab trim markers only from the bottom
|
|
215
|
-
if (
|
|
216
|
-
this.dragging = 'start';
|
|
217
|
-
canvas.style.cursor = 'grabbing';
|
|
218
|
-
}
|
|
219
|
-
else if (Math.abs(this.endX - x) < threshold && this.position.y < y) {
|
|
220
|
-
this.dragging = 'end';
|
|
240
|
+
if ((startDist < threshold || endDist < threshold) && this.linePosition.y < y) {
|
|
241
|
+
this.dragging = (startDist < endDist || x < this.startX) ? 'start' : 'end';
|
|
221
242
|
canvas.style.cursor = 'grabbing';
|
|
222
243
|
}
|
|
223
244
|
else {
|
|
@@ -234,9 +255,14 @@ class TimeBar {
|
|
|
234
255
|
}
|
|
235
256
|
this.onSetCurrentValue(this.currentX);
|
|
236
257
|
}
|
|
258
|
+
this._mouseDownCanvasRect = canvas.getBoundingClientRect(); // cache this to avoid stalls during mousemove
|
|
259
|
+
window.addEventListener('mousemove', this._onMouseMoveListener);
|
|
260
|
+
window.addEventListener('mouseup', this._onMouseUpListener);
|
|
237
261
|
this._draw();
|
|
238
262
|
}
|
|
239
263
|
onMouseUp(e) {
|
|
264
|
+
window.removeEventListener('mousemove', this._onMouseMoveListener);
|
|
265
|
+
window.removeEventListener('mouseup', this._onMouseUpListener);
|
|
240
266
|
if (this.onMouse) {
|
|
241
267
|
this.onMouse(e);
|
|
242
268
|
}
|
|
@@ -259,17 +285,17 @@ class TimeBar {
|
|
|
259
285
|
e.preventDefault();
|
|
260
286
|
const canvas = this.canvas;
|
|
261
287
|
// Process mouse
|
|
262
|
-
const x = e.target == canvas ? e.offsetX : e.clientX -
|
|
263
|
-
e.target == canvas ? e.offsetY : e.clientY -
|
|
288
|
+
const x = e.target == canvas ? e.offsetX : (e.clientX - this._mouseDownCanvasRect.left);
|
|
289
|
+
e.target == canvas ? e.offsetY : (e.clientY - this._mouseDownCanvasRect.top);
|
|
264
290
|
if (this.dragging) {
|
|
265
291
|
switch (this.dragging) {
|
|
266
292
|
case 'start':
|
|
267
|
-
this.startX = Math.max(this.
|
|
293
|
+
this.startX = Math.max(this.linePosition.x, Math.min(this.endX, x));
|
|
268
294
|
this.currentX = this.startX;
|
|
269
295
|
this.onSetStartValue(this.startX);
|
|
270
296
|
break;
|
|
271
297
|
case 'end':
|
|
272
|
-
this.endX = Math.max(this.startX, Math.min(this.
|
|
298
|
+
this.endX = Math.max(this.startX, Math.min(this.linePosition.x + this.lineWidth, x));
|
|
273
299
|
this.currentX = this.endX;
|
|
274
300
|
this.onSetEndValue(this.endX);
|
|
275
301
|
break;
|
|
@@ -303,15 +329,18 @@ class TimeBar {
|
|
|
303
329
|
resize(size) {
|
|
304
330
|
this.canvas.width = Math.max(0, size[0]);
|
|
305
331
|
this.canvas.height = Math.max(0, size[1]);
|
|
306
|
-
|
|
332
|
+
this.markerHeight = (this.options.markerHeight ?? 0.5) * this.canvas.height;
|
|
333
|
+
let newWidth = size[0] - this.offset.x * 2;
|
|
307
334
|
newWidth = newWidth < 0.00001 ? 0.00001 : newWidth; // actual width of the line = canvas.width - offsetleft - offsetRight
|
|
308
|
-
const startRatio = (this.startX - this.offset) / this.lineWidth;
|
|
309
|
-
const currentRatio = (this.currentX - this.offset) / this.lineWidth;
|
|
310
|
-
const endRatio = (this.endX - this.offset) / this.lineWidth;
|
|
335
|
+
const startRatio = (this.startX - this.offset.x) / this.lineWidth;
|
|
336
|
+
const currentRatio = (this.currentX - this.offset.x) / this.lineWidth;
|
|
337
|
+
const endRatio = (this.endX - this.offset.x) / this.lineWidth;
|
|
311
338
|
this.lineWidth = newWidth;
|
|
312
|
-
this.
|
|
313
|
-
this.
|
|
314
|
-
this.
|
|
339
|
+
this.linePosition.x = this.offset.x;
|
|
340
|
+
this.linePosition.y = this.canvas.height * 0.5 - this.lineHeight * 0.5;
|
|
341
|
+
this.startX = Math.min(Math.max(newWidth * startRatio, 0), newWidth) + this.offset.x;
|
|
342
|
+
this.currentX = Math.min(Math.max(newWidth * currentRatio, 0), newWidth) + this.offset.x;
|
|
343
|
+
this.endX = Math.min(Math.max(newWidth * endRatio, 0), newWidth) + this.offset.x;
|
|
315
344
|
this._draw();
|
|
316
345
|
}
|
|
317
346
|
}
|
|
@@ -332,10 +361,7 @@ class VideoEditor {
|
|
|
332
361
|
playing = false;
|
|
333
362
|
videoReady = false;
|
|
334
363
|
controls = true;
|
|
335
|
-
startTimeString = '0:0';
|
|
336
|
-
endTimeString = '0:0';
|
|
337
364
|
speed = 1.0;
|
|
338
|
-
currentTime = 0.0;
|
|
339
365
|
startTime = 0.0;
|
|
340
366
|
endTime = 0.0;
|
|
341
367
|
requestId;
|
|
@@ -346,27 +372,29 @@ class VideoEditor {
|
|
|
346
372
|
crop = false;
|
|
347
373
|
dragOffsetX = 0.0;
|
|
348
374
|
dragOffsetY = 0.0;
|
|
349
|
-
|
|
350
|
-
timebar;
|
|
375
|
+
timebar = null;
|
|
351
376
|
mainArea;
|
|
352
377
|
cropArea; // HTMLElement with normCoord attribute;
|
|
378
|
+
videoArea;
|
|
353
379
|
controlsArea;
|
|
354
|
-
|
|
355
|
-
controlsPanelRight;
|
|
356
|
-
controlsCurrentPanel;
|
|
380
|
+
controlsComponents;
|
|
357
381
|
onChangeCurrent;
|
|
358
382
|
onChangeStart;
|
|
359
383
|
onChangeEnd;
|
|
360
384
|
onKeyUp;
|
|
361
385
|
onSetTime;
|
|
362
386
|
onVideoLoaded;
|
|
363
|
-
onCropArea;
|
|
364
387
|
onResize;
|
|
388
|
+
onCropArea;
|
|
365
389
|
onChangeSpeed;
|
|
390
|
+
onChangeState;
|
|
391
|
+
onChangeLoop;
|
|
366
392
|
_updateTime = true;
|
|
367
393
|
_onCropMouseUp;
|
|
368
394
|
_onCropMouseMove;
|
|
369
|
-
resize;
|
|
395
|
+
resize = null;
|
|
396
|
+
resizeControls = null;
|
|
397
|
+
resizeVideo = null;
|
|
370
398
|
constructor(area, options = {}) {
|
|
371
399
|
this.options = options ?? {};
|
|
372
400
|
this.speed = options.speed ?? this.speed;
|
|
@@ -409,120 +437,42 @@ class VideoEditor {
|
|
|
409
437
|
videoArea.root.classList.add('lexvideoeditor');
|
|
410
438
|
}
|
|
411
439
|
videoArea.root.style.position = 'relative';
|
|
440
|
+
this.videoArea = videoArea;
|
|
412
441
|
this.controlsArea = controlsArea;
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
this.controlsCurrentPanel.clear();
|
|
423
|
-
this.controlsCurrentPanel.addLabel(this.currentTimeString, { float: 'center' });
|
|
442
|
+
this.controlsComponents = {
|
|
443
|
+
timebar: null,
|
|
444
|
+
playBtn: null,
|
|
445
|
+
speedBtn: null,
|
|
446
|
+
loopBtn: null,
|
|
447
|
+
trimStartText: null,
|
|
448
|
+
trimEndText: null,
|
|
449
|
+
curTimeText: null,
|
|
450
|
+
resetCropBtn: null
|
|
424
451
|
};
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
this.controlsCurrentPanel.refresh();
|
|
428
|
-
const style = getComputedStyle(bottomArea.root);
|
|
429
|
-
let padding = Number(style.getPropertyValue('padding').replace('px', ''));
|
|
430
|
-
this.timebar = new TimeBar(timeBarArea, TimeBar.TIMEBAR_TRIM, { offset: padding });
|
|
431
|
-
// Create controls panel (play/pause button and start time)
|
|
432
|
-
this.controlsPanelLeft = new LX.Panel({ className: 'lexcontrolspanel' });
|
|
433
|
-
this.controlsPanelLeft.refresh = () => {
|
|
434
|
-
this.controlsPanelLeft.clear();
|
|
435
|
-
this.controlsPanelLeft.sameLine();
|
|
436
|
-
let playbtn = this.controlsPanelLeft.addButton(null, 'PlayButton', (v) => {
|
|
437
|
-
this.playing = v;
|
|
438
|
-
if (this.playing) {
|
|
439
|
-
if (this.video.currentTime + 0.000001 >= this.endTime) {
|
|
440
|
-
this.video.currentTime = this.startTime;
|
|
441
|
-
}
|
|
442
|
-
this.video.play();
|
|
443
|
-
}
|
|
444
|
-
else {
|
|
445
|
-
this.video.pause();
|
|
446
|
-
}
|
|
447
|
-
}, { icon: 'Play@solid', swap: 'Pause@solid', hideName: true, title: 'Play', tooltip: true, className: 'justify-center' });
|
|
448
|
-
playbtn.setState(this.playing, true);
|
|
449
|
-
this.controlsPanelLeft.addButton(null, '', (v, e) => {
|
|
450
|
-
const panel = new LX.Panel();
|
|
451
|
-
panel.addRange('Speed', this.speed, (v) => {
|
|
452
|
-
this.speed = v;
|
|
453
|
-
this.video.playbackRate = v;
|
|
454
|
-
if (this.onChangeSpeed) {
|
|
455
|
-
this.onChangeSpeed(v);
|
|
456
|
-
}
|
|
457
|
-
}, { min: 0, max: 2.5, step: 0.01, hideName: true });
|
|
458
|
-
new LX.Popover(e.target, [panel], { align: 'start', side: 'top', sideOffset: 12 });
|
|
459
|
-
}, { icon: 'Timer@solid', title: 'Speed', tooltip: true, className: 'justify-center' });
|
|
460
|
-
this.controlsPanelLeft.addButton(null, 'Loop', (v) => {
|
|
461
|
-
this.loop = v;
|
|
462
|
-
}, { title: 'Loop', tooltip: true, icon: ('Repeat@solid'), className: `justify-center`, selectable: true, selected: this.loop });
|
|
463
|
-
this.controlsPanelLeft.addLabel(this.startTimeString, { width: '100px' });
|
|
464
|
-
this.controlsPanelLeft.endLine();
|
|
465
|
-
let availableWidth = leftArea.root.clientWidth - controlsLeft.root.clientWidth;
|
|
466
|
-
this.timebar.resize([availableWidth, timeBarArea.root.clientHeight]);
|
|
467
|
-
};
|
|
468
|
-
this.controlsPanelLeft.refresh();
|
|
469
|
-
controlsLeft.root.style.minWidth = 'fit-content';
|
|
470
|
-
// controlsLeft.root.classList.add();
|
|
471
|
-
controlsLeft.attach(this.controlsPanelLeft);
|
|
472
|
-
// Create right controls panel (ens time)
|
|
473
|
-
this.controlsPanelRight = new LX.Panel({ className: 'lexcontrolspanel' });
|
|
474
|
-
this.controlsPanelRight.refresh = () => {
|
|
475
|
-
this.controlsPanelRight.clear();
|
|
476
|
-
this.controlsPanelRight.addLabel(this.endTimeString, { width: 100 });
|
|
477
|
-
};
|
|
478
|
-
this.controlsPanelRight.refresh();
|
|
479
|
-
controlsRight.root.style.minWidth = 'fit-content';
|
|
480
|
-
controlsRight.attach(this.controlsPanelRight);
|
|
481
|
-
this.timebar.onChangeCurrent = this._setCurrentTime.bind(this);
|
|
482
|
-
this.timebar.onChangeStart = this._setStartTime.bind(this);
|
|
483
|
-
this.timebar.onChangeEnd = this._setEndTime.bind(this);
|
|
484
|
-
this.resize = () => {
|
|
485
|
-
bottomArea.setSize([this.controlsArea.root.clientWidth, 40]);
|
|
486
|
-
let availableWidth = this.controlsArea.root.clientWidth - controlsLeft.root.clientWidth
|
|
487
|
-
- controlsRight.root.clientWidth;
|
|
488
|
-
this.timebar.resize([availableWidth, timeBarArea.root.clientHeight]);
|
|
452
|
+
this.createControls();
|
|
453
|
+
this.resizeVideo = () => {
|
|
489
454
|
this.moveCropArea(this.cropArea.normCoords.x, this.cropArea.normCoords.y, true);
|
|
490
455
|
this.resizeCropArea(this.cropArea.normCoords.w, this.cropArea.normCoords.h, true);
|
|
491
456
|
if (this.onResize) {
|
|
492
457
|
this.onResize([videoArea.root.clientWidth, videoArea.root.clientHeight]);
|
|
493
458
|
}
|
|
494
459
|
};
|
|
460
|
+
this.resize = () => {
|
|
461
|
+
this.resizeVideo();
|
|
462
|
+
this.resizeControls();
|
|
463
|
+
};
|
|
495
464
|
area.onresize = this.resize.bind(this);
|
|
496
465
|
window.addEventListener('resize', area.onresize);
|
|
497
466
|
this.onKeyUp = (e) => {
|
|
498
467
|
if (this.controls && e.key == ' ') {
|
|
499
468
|
e.preventDefault();
|
|
500
469
|
e.stopPropagation();
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
if (this.video.currentTime + 0.000001 >= this.endTime) {
|
|
504
|
-
this.video.currentTime = this.startTime;
|
|
505
|
-
}
|
|
506
|
-
this.video.play();
|
|
507
|
-
}
|
|
508
|
-
else {
|
|
509
|
-
this.video.pause();
|
|
510
|
-
}
|
|
511
|
-
this.controlsPanelLeft.refresh();
|
|
470
|
+
// do not skip callback
|
|
471
|
+
this.controlsComponents.playBtn?.setState(!this.playing, false);
|
|
512
472
|
}
|
|
513
473
|
};
|
|
514
474
|
window.addEventListener('keyup', this.onKeyUp);
|
|
515
|
-
const parent = controlsArea.parentElement ? controlsArea.parentElement : controlsArea.root.parentElement;
|
|
516
|
-
// Add canvas event listeneres
|
|
517
|
-
parent.addEventListener('mousedown', (e) => {
|
|
518
|
-
// if( this.controls) {
|
|
519
|
-
// this.timebar.onMouseDown(e);
|
|
520
|
-
// }
|
|
521
|
-
});
|
|
522
475
|
this._onCropMouseUp = (event) => {
|
|
523
|
-
// if(this.controls) {
|
|
524
|
-
// this.timebar.onMouseUp(event);
|
|
525
|
-
// }
|
|
526
476
|
event.preventDefault();
|
|
527
477
|
event.stopPropagation();
|
|
528
478
|
if ((this.isDragging || this.isResizing) && this.onCropArea) {
|
|
@@ -534,9 +484,6 @@ class VideoEditor {
|
|
|
534
484
|
document.removeEventListener('mousemove', this._onCropMouseMove); // self destroy. Added during mouseDown on cropArea and handles
|
|
535
485
|
};
|
|
536
486
|
this._onCropMouseMove = (event) => {
|
|
537
|
-
// if(this.controls) {
|
|
538
|
-
// this.timebar.onMouseMove(event);
|
|
539
|
-
// }
|
|
540
487
|
window.getSelection()?.removeAllRanges();
|
|
541
488
|
event.preventDefault();
|
|
542
489
|
event.stopPropagation();
|
|
@@ -597,6 +544,182 @@ class VideoEditor {
|
|
|
597
544
|
this.onChangeStart = null;
|
|
598
545
|
this.onChangeEnd = null;
|
|
599
546
|
}
|
|
547
|
+
createControls(options = null) {
|
|
548
|
+
const controlsArea = this.controlsArea;
|
|
549
|
+
options = options ?? this.options;
|
|
550
|
+
// clear area. Signals are not cleared !!! (not a problem if there are no signals)
|
|
551
|
+
while (controlsArea.root.children.length) {
|
|
552
|
+
controlsArea.root.children[0].remove();
|
|
553
|
+
}
|
|
554
|
+
controlsArea.sections.length = 0;
|
|
555
|
+
// start trimming text
|
|
556
|
+
this.controlsComponents.trimStartText = new LX.TextInput(null, this.timeToString(this.startTime), null, { width: '100px',
|
|
557
|
+
title: 'Trimmed Start Time', disabled: true, inputClass: 'bg-none' });
|
|
558
|
+
this.controlsComponents.trimEndText = new LX.TextInput(null, this.timeToString(this.endTime), null, { width: '100px',
|
|
559
|
+
title: 'Trimmed End Time', disabled: true, inputClass: 'bg-none' });
|
|
560
|
+
this.controlsComponents.curTimeText = new LX.TextInput(null, this.video.currentTime, null, { title: 'Current Time', float: 'center',
|
|
561
|
+
disabled: true, inputClass: 'bg-none' });
|
|
562
|
+
// reset crop area
|
|
563
|
+
this.controlsComponents.resetCropBtn = new LX.Button('ResetCrop', null, (v) => {
|
|
564
|
+
this.moveCropArea(0, 0, true);
|
|
565
|
+
this.resizeCropArea(1, 1, true);
|
|
566
|
+
if (this.onCropArea) {
|
|
567
|
+
this.onCropArea(this.getCroppedArea());
|
|
568
|
+
}
|
|
569
|
+
}, { width: '40px', title: 'Reset Crop Area', icon: 'Crop@solid', hideName: true,
|
|
570
|
+
className: 'justify-center' + (this.crop ? '' : ' hidden') });
|
|
571
|
+
// play button
|
|
572
|
+
this.controlsComponents.playBtn = new LX.Button('Play', '', (v) => {
|
|
573
|
+
this.playing = v;
|
|
574
|
+
if (this.playing) {
|
|
575
|
+
if (this.video.currentTime + 0.000001 >= this.endTime) {
|
|
576
|
+
this.video.currentTime = this.startTime;
|
|
577
|
+
}
|
|
578
|
+
this.video.play();
|
|
579
|
+
}
|
|
580
|
+
else {
|
|
581
|
+
this.video.pause();
|
|
582
|
+
}
|
|
583
|
+
if (this.onChangeState) {
|
|
584
|
+
this.onChangeState(v);
|
|
585
|
+
}
|
|
586
|
+
}, { width: '40px', title: 'Play/Pause', icon: 'Play@solid', swap: 'Pause@solid', hideName: true, className: 'justify-center' });
|
|
587
|
+
this.controlsComponents.playBtn.setState(this.playing, true);
|
|
588
|
+
// speed button
|
|
589
|
+
this.controlsComponents.speedBtn = new LX.Button('Speed', '', (v, e) => {
|
|
590
|
+
const panel = new LX.Panel();
|
|
591
|
+
panel.addRange('Speed', this.speed, (v) => {
|
|
592
|
+
this.speed = v;
|
|
593
|
+
this.video.playbackRate = v;
|
|
594
|
+
if (this.onChangeSpeed) {
|
|
595
|
+
this.onChangeSpeed(v);
|
|
596
|
+
}
|
|
597
|
+
}, { min: 0, max: 2.5, step: 0.01, hideName: true });
|
|
598
|
+
new LX.Popover(e.target, [panel], { align: 'start', side: 'top', sideOffset: 12 });
|
|
599
|
+
}, { width: '40px', title: 'Speed', hideName: true, icon: 'Timer@solid', className: 'justify-center' });
|
|
600
|
+
// loop button
|
|
601
|
+
this.controlsComponents.loopBtn = new LX.Button('', 'Loop', (v) => {
|
|
602
|
+
this.loop = v;
|
|
603
|
+
if (this.onChangeLoop) {
|
|
604
|
+
this.onChangeLoop(v);
|
|
605
|
+
}
|
|
606
|
+
}, { width: '40px', hideName: true, title: 'Loop', icon: 'Repeat@solid', className: `justify-center`, selectable: true,
|
|
607
|
+
selected: this.loop });
|
|
608
|
+
let timeBarArea = null;
|
|
609
|
+
if (typeof (options.controlsLayout) == 'function') {
|
|
610
|
+
timeBarArea = options.controlsLayout;
|
|
611
|
+
}
|
|
612
|
+
else if (options.controlsLayout == 1) {
|
|
613
|
+
timeBarArea = this._createControlsLayout_1();
|
|
614
|
+
}
|
|
615
|
+
else {
|
|
616
|
+
timeBarArea = this._createControlsLayout_0();
|
|
617
|
+
}
|
|
618
|
+
if (this.timebar) {
|
|
619
|
+
this.timebar.unbind();
|
|
620
|
+
}
|
|
621
|
+
this.timebar = this.controlsComponents.timebar = new TimeBar(timeBarArea, TimeBar.TIMEBAR_TRIM, { offset: [12, null] });
|
|
622
|
+
this.timebar.onChangeCurrent = this._setCurrentTime.bind(this);
|
|
623
|
+
this.timebar.onChangeStart = this._setStartTime.bind(this);
|
|
624
|
+
this.timebar.onChangeEnd = this._setEndTime.bind(this);
|
|
625
|
+
let duration = 1;
|
|
626
|
+
if (this.video.duration !== Infinity && !isNaN(this.video.duration)) {
|
|
627
|
+
duration = this.video.duration;
|
|
628
|
+
}
|
|
629
|
+
this.timebar.setDuration(duration);
|
|
630
|
+
this.timebar.setEndTime(this.endTime);
|
|
631
|
+
this.timebar.setStartTime(this.startTime);
|
|
632
|
+
this.timebar.setCurrentTime(this.startTime);
|
|
633
|
+
this.resizeControls();
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Creates the areas where components will be.
|
|
637
|
+
* Attaches all (desired) components of controlsComponents except the timebar
|
|
638
|
+
* @returns {Area} for the timebar
|
|
639
|
+
* Layout:
|
|
640
|
+
* |--------------------------timebar--------------------------|
|
|
641
|
+
* play speed loop resetCrop curTime trimStart / trimEnd
|
|
642
|
+
*/
|
|
643
|
+
_createControlsLayout_1() {
|
|
644
|
+
const controlsArea = this.controlsArea;
|
|
645
|
+
// Create playing timeline area and attach panels
|
|
646
|
+
let [timeBarArea, bottomArea] = controlsArea.split({ type: 'vertical', sizes: ['50%', null], minimizable: false, resize: false });
|
|
647
|
+
bottomArea.root.classList.add('relative');
|
|
648
|
+
let separator = document.createElement('p');
|
|
649
|
+
separator.style.alignContent = 'center';
|
|
650
|
+
separator.innerText = '/';
|
|
651
|
+
let trimDiv = LX.makeContainer(['fit-content', '100%'], 'relative flex flex-row pb-2', null, bottomArea, { float: 'right' });
|
|
652
|
+
trimDiv.appendChild(this.controlsComponents.trimStartText.root);
|
|
653
|
+
trimDiv.appendChild(separator);
|
|
654
|
+
trimDiv.appendChild(this.controlsComponents.trimEndText.root);
|
|
655
|
+
this.controlsComponents.trimStartText.root.querySelector('input').classList.add('text-end');
|
|
656
|
+
this.controlsComponents.trimStartText.root.classList.add('top-0', 'bottom-0');
|
|
657
|
+
this.controlsComponents.trimEndText.root.classList.add('top-0', 'bottom-0');
|
|
658
|
+
// current time
|
|
659
|
+
let curTimeDiv = LX.makeContainer(['100%', '100%'], 'absolute top-0 left-0 flex flex-row justify-center items-center pb-2', null, bottomArea, {});
|
|
660
|
+
curTimeDiv.appendChild(this.controlsComponents.curTimeText.root);
|
|
661
|
+
// Buttons
|
|
662
|
+
const buttonsPanel = bottomArea.addPanel({ className: 'absolute top-0 left-0 flex flex-row pl-4 pr-4 pt-1 pb-2' });
|
|
663
|
+
buttonsPanel.root.classList.remove('pad-md');
|
|
664
|
+
buttonsPanel._attachComponent(this.controlsComponents.playBtn);
|
|
665
|
+
buttonsPanel._attachComponent(this.controlsComponents.speedBtn);
|
|
666
|
+
buttonsPanel._attachComponent(this.controlsComponents.loopBtn);
|
|
667
|
+
buttonsPanel._attachComponent(this.controlsComponents.resetCropBtn);
|
|
668
|
+
this.controlsComponents.playBtn.root.classList.add('pl-0');
|
|
669
|
+
this.controlsComponents.resetCropBtn.root.classList.add('pr-0');
|
|
670
|
+
// timebar
|
|
671
|
+
timeBarArea.root.classList.add('p-4', 'pb-0');
|
|
672
|
+
this.resizeControls = () => {
|
|
673
|
+
const style = getComputedStyle(timeBarArea.root);
|
|
674
|
+
let pleft = parseFloat(style.paddingLeft);
|
|
675
|
+
let pright = parseFloat(style.paddingRight);
|
|
676
|
+
let ptop = parseFloat(style.paddingTop);
|
|
677
|
+
let pbot = parseFloat(style.paddingBottom);
|
|
678
|
+
// assuming timeBarArea will not overflow
|
|
679
|
+
this.timebar.resize([timeBarArea.root.clientWidth - pleft - pright, timeBarArea.root.clientHeight - ptop - pbot]);
|
|
680
|
+
};
|
|
681
|
+
return timeBarArea;
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Creates the areas where components will be.
|
|
685
|
+
* Attaches all (desired) components of controlsComponents except the timebar
|
|
686
|
+
* @returns {Area} for the timebar
|
|
687
|
+
* Layout:
|
|
688
|
+
* curTime
|
|
689
|
+
* play speed loop trimStart |---timebar---| trimend
|
|
690
|
+
*/
|
|
691
|
+
_createControlsLayout_0() {
|
|
692
|
+
const controlsArea = this.controlsArea;
|
|
693
|
+
// Create playing timeline area and attach panels
|
|
694
|
+
let [topArea, bottomArea] = controlsArea.split({ type: 'vertical', sizes: ['50%', null], minimizable: false, resize: false });
|
|
695
|
+
bottomArea.setSize([bottomArea.size[0], 40]);
|
|
696
|
+
let [leftArea, controlsRight] = bottomArea.split({ type: 'horizontal', sizes: ['92%', null], minimizable: false, resize: false });
|
|
697
|
+
let [controlsLeft, timeBarArea] = leftArea.split({ type: 'horizontal', sizes: ['10%', null], minimizable: false, resize: false });
|
|
698
|
+
const controlsCurrentPanel = topArea.addPanel({ className: 'flex' });
|
|
699
|
+
controlsCurrentPanel._attachComponent(this.controlsComponents.curTimeText);
|
|
700
|
+
// Create controls panel (play/pause button and start time)
|
|
701
|
+
controlsLeft.root.classList.add('min-w-fit');
|
|
702
|
+
const controlsPanelLeft = controlsLeft.addPanel({ className: 'lexcontrolspanel p-0 pl-2' });
|
|
703
|
+
controlsPanelLeft.root.classList.remove('pad-md');
|
|
704
|
+
controlsPanelLeft.sameLine();
|
|
705
|
+
controlsPanelLeft._attachComponent(this.controlsComponents.playBtn);
|
|
706
|
+
controlsPanelLeft._attachComponent(this.controlsComponents.speedBtn);
|
|
707
|
+
controlsPanelLeft._attachComponent(this.controlsComponents.loopBtn);
|
|
708
|
+
controlsPanelLeft._attachComponent(this.controlsComponents.trimStartText);
|
|
709
|
+
controlsPanelLeft.endLine();
|
|
710
|
+
// Create right controls panel (end time)
|
|
711
|
+
controlsRight.root.classList.add('min-w-fit');
|
|
712
|
+
const controlsPanelRight = controlsRight.addPanel({ className: 'lexcontrolspanel p-0' });
|
|
713
|
+
controlsPanelRight.root.classList.remove('pad-md');
|
|
714
|
+
controlsPanelRight._attachComponent(this.controlsComponents.trimEndText);
|
|
715
|
+
this.resizeControls = () => {
|
|
716
|
+
bottomArea.setSize([this.controlsArea.root.clientWidth, 40]);
|
|
717
|
+
let availableWidth = this.controlsArea.root.clientWidth - controlsLeft.root.clientWidth
|
|
718
|
+
- controlsRight.root.clientWidth;
|
|
719
|
+
this.timebar.resize([availableWidth, timeBarArea.root.clientHeight]);
|
|
720
|
+
};
|
|
721
|
+
return timeBarArea;
|
|
722
|
+
}
|
|
600
723
|
setCropAreaHandles(flags) {
|
|
601
724
|
// remove existing resizer handles
|
|
602
725
|
const resizers = this.cropArea.getElementsByClassName('resize-handle');
|
|
@@ -725,18 +848,12 @@ class VideoEditor {
|
|
|
725
848
|
this.video.currentTime = this.startTime;
|
|
726
849
|
this.timebar.setCurrentTime(this.video.currentTime);
|
|
727
850
|
};
|
|
728
|
-
this.timebar.startX = this.timebar.position.x;
|
|
729
|
-
this.timebar.endX = this.timebar.position.x + this.timebar.lineWidth;
|
|
730
851
|
this.startTime = 0;
|
|
731
852
|
this.endTime = this.video.duration;
|
|
732
853
|
this.timebar.setDuration(this.endTime);
|
|
733
854
|
this.timebar.setEndTime(this.video.duration);
|
|
734
855
|
this.timebar.setStartTime(this.startTime);
|
|
735
856
|
this.timebar.setCurrentTime(this.startTime);
|
|
736
|
-
// this.timebar.setStartValue( this.timebar.startX);
|
|
737
|
-
// this.timebar.currentX = this._timeToX( this.video.currentTime);
|
|
738
|
-
// this.timebar.setCurrentValue( this.timebar.currentX);
|
|
739
|
-
// this.timebar.update( this.timebar.currentX );
|
|
740
857
|
// only have one update on flight
|
|
741
858
|
if (!this.requestId) {
|
|
742
859
|
this._update();
|
|
@@ -769,7 +886,7 @@ class VideoEditor {
|
|
|
769
886
|
this.video.pause();
|
|
770
887
|
if (!this.loop) {
|
|
771
888
|
this.playing = false;
|
|
772
|
-
this.
|
|
889
|
+
this.controlsComponents.playBtn?.setState(false, true); // skip callback
|
|
773
890
|
}
|
|
774
891
|
else {
|
|
775
892
|
this.video.currentTime = this.startTime;
|
|
@@ -795,8 +912,7 @@ class VideoEditor {
|
|
|
795
912
|
if (this.video.currentTime != t && this._updateTime) {
|
|
796
913
|
this.video.currentTime = t;
|
|
797
914
|
}
|
|
798
|
-
this.
|
|
799
|
-
this.controlsCurrentPanel.refresh();
|
|
915
|
+
this.controlsComponents.curTimeText?.set(this.timeToString(t));
|
|
800
916
|
if (this.onSetTime) {
|
|
801
917
|
this.onSetTime(t);
|
|
802
918
|
}
|
|
@@ -806,8 +922,7 @@ class VideoEditor {
|
|
|
806
922
|
}
|
|
807
923
|
_setStartTime(t) {
|
|
808
924
|
this.startTime = this.video.currentTime = t;
|
|
809
|
-
this.
|
|
810
|
-
this.controlsPanelLeft.refresh();
|
|
925
|
+
this.controlsComponents.trimStartText?.set(this.timeToString(t));
|
|
811
926
|
if (this.onSetTime) {
|
|
812
927
|
this.onSetTime(t);
|
|
813
928
|
}
|
|
@@ -817,8 +932,7 @@ class VideoEditor {
|
|
|
817
932
|
}
|
|
818
933
|
_setEndTime(t) {
|
|
819
934
|
this.endTime = this.video.currentTime = t;
|
|
820
|
-
this.
|
|
821
|
-
this.controlsPanelRight.refresh();
|
|
935
|
+
this.controlsComponents.trimEndText?.set(this.timeToString(t));
|
|
822
936
|
if (this.onSetTime) {
|
|
823
937
|
this.onSetTime(t);
|
|
824
938
|
}
|
|
@@ -839,7 +953,9 @@ class VideoEditor {
|
|
|
839
953
|
return this.cropArea.getBoundingClientRect();
|
|
840
954
|
}
|
|
841
955
|
showCropArea() {
|
|
956
|
+
this.crop = true;
|
|
842
957
|
this.cropArea.classList.remove('hidden');
|
|
958
|
+
this.controlsComponents.resetCropBtn?.root.classList.remove('hidden');
|
|
843
959
|
const nodes = this.cropArea.parentElement?.childNodes ?? [];
|
|
844
960
|
const rect = this.cropArea.getBoundingClientRect();
|
|
845
961
|
for (let i = 0; i < nodes.length; i++) {
|
|
@@ -852,7 +968,9 @@ class VideoEditor {
|
|
|
852
968
|
}
|
|
853
969
|
}
|
|
854
970
|
hideCropArea() {
|
|
971
|
+
this.crop = false;
|
|
855
972
|
this.cropArea.classList.add('hidden');
|
|
973
|
+
this.controlsComponents.resetCropBtn?.root.classList.add('hidden');
|
|
856
974
|
const nodes = this.cropArea.parentElement?.childNodes ?? [];
|
|
857
975
|
for (let i = 0; i < nodes.length; i++) {
|
|
858
976
|
const node = nodes[i];
|
|
@@ -880,8 +998,11 @@ class VideoEditor {
|
|
|
880
998
|
this.stopUpdates();
|
|
881
999
|
this.video.pause();
|
|
882
1000
|
this.playing = false;
|
|
883
|
-
this.
|
|
1001
|
+
this.controlsComponents.playBtn?.setState(false, true); // skip callback
|
|
884
1002
|
this.video.src = '';
|
|
1003
|
+
if (this.timebar) {
|
|
1004
|
+
this.timebar.unbind();
|
|
1005
|
+
}
|
|
885
1006
|
window.removeEventListener('keyup', this.onKeyUp);
|
|
886
1007
|
document.removeEventListener('mouseup', this._onCropMouseUp);
|
|
887
1008
|
document.removeEventListener('mousemove', this._onCropMouseMove);
|