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.
Files changed (43) hide show
  1. package/build/components/Avatar.d.ts +15 -15
  2. package/build/components/NodeTree.d.ts +51 -51
  3. package/build/components/Vector.d.ts +10 -10
  4. package/build/core/Event.d.ts +6 -6
  5. package/build/core/Namespace.js +1 -1
  6. package/build/core/Namespace.js.map +1 -1
  7. package/build/core/Panel.d.ts +538 -538
  8. package/build/extensions/AssetView.d.ts +137 -137
  9. package/build/extensions/AssetView.js +5 -6
  10. package/build/extensions/AssetView.js.map +1 -1
  11. package/build/extensions/CodeEditor.d.ts +358 -358
  12. package/build/extensions/CodeEditor.js +7 -7
  13. package/build/extensions/CodeEditor.js.map +1 -1
  14. package/build/extensions/DocMaker.js +1 -0
  15. package/build/extensions/DocMaker.js.map +1 -1
  16. package/build/extensions/GraphEditor.js +2754 -2754
  17. package/build/extensions/Timeline.d.ts +668 -668
  18. package/build/extensions/Timeline.js +2 -2
  19. package/build/extensions/Timeline.js.map +1 -1
  20. package/build/extensions/VideoEditor.d.ts +37 -15
  21. package/build/extensions/VideoEditor.js +287 -166
  22. package/build/extensions/VideoEditor.js.map +1 -1
  23. package/build/index.css.d.ts +3 -3
  24. package/build/index.d.ts +57 -57
  25. package/build/lexgui.all.js +327 -185
  26. package/build/lexgui.all.js.map +1 -1
  27. package/build/lexgui.all.min.js +1 -1
  28. package/build/lexgui.all.module.js +327 -185
  29. package/build/lexgui.all.module.js.map +1 -1
  30. package/build/lexgui.all.module.min.js +1 -1
  31. package/build/lexgui.css +213 -220
  32. package/build/lexgui.js +25 -4
  33. package/build/lexgui.js.map +1 -1
  34. package/build/lexgui.min.css +1 -1
  35. package/build/lexgui.min.js +1 -1
  36. package/build/lexgui.module.js +25 -4
  37. package/build/lexgui.module.js.map +1 -1
  38. package/build/lexgui.module.min.js +1 -1
  39. package/changelog.md +23 -1
  40. package/examples/all-components.html +3 -4
  41. package/examples/code-editor.html +11 -0
  42. package/examples/dialogs.html +13 -2
  43. 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
- position;
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 ?? (this.canvas.height * 0.5);
52
- this.offset = options.offset || (this.markerWidth * 0.5 + 5);
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.position = new vec2(this.offset, this.canvas.height * 0.5 - this.lineHeight * 0.5);
57
- this.startX = this.position.x;
58
- this.endX = this.position.x + this.lineWidth;
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
- LX.addSignal('@on_new_color_scheme', () => {
63
- // Retrieve again the color using LX.getCSSVariable, which checks the applied theme
64
- this.updateTheme();
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) => this.onMouseMove(e);
68
- this.canvas.onmouseup = (e) => this.onMouseUp(e);
89
+ this.canvas.onmousemove = (e) => {
90
+ if (this.dragging)
91
+ return; // already handled by _onMouseMoveListener
92
+ this.onMouseMove(e);
93
+ };
69
94
  }
70
- updateTheme() {
71
- TimeBar.BACKGROUND_COLOR = LX.getCSSVariable('secondary');
72
- TimeBar.COLOR = LX.getCSSVariable('accent');
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.position.x, this.position.y, this.lineWidth, this.lineHeight);
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.position.y, this.endX - this.startX, this.lineHeight);
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
- const h = this.canvas.height - this.offset * 2;
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 + h * 0.5);
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 + this.offset, w, w, 5);
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 (Math.abs(this.startX - x) < threshold && this.position.y < y) {
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 - canvas.offsetLeft;
263
- e.target == canvas ? e.offsetY : e.clientY - canvas.offsetTop;
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.position.x, Math.min(this.endX, x));
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.position.x + this.lineWidth, x));
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
- let newWidth = size[0] - this.offset * 2;
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.startX = Math.min(Math.max(newWidth * startRatio, 0), newWidth) + this.offset;
313
- this.currentX = Math.min(Math.max(newWidth * currentRatio, 0), newWidth) + this.offset;
314
- this.endX = Math.min(Math.max(newWidth * endRatio, 0), newWidth) + this.offset;
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
- currentTimeString = '';
350
- timebar;
375
+ timebar = null;
351
376
  mainArea;
352
377
  cropArea; // HTMLElement with normCoord attribute;
378
+ videoArea;
353
379
  controlsArea;
354
- controlsPanelLeft;
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
- // Create playing timeline area and attach panels
414
- let [topArea, bottomArea] = controlsArea.split({ type: 'vertical', sizes: ['50%', null], minimizable: false, resize: false });
415
- bottomArea.setSize([bottomArea.size[0], 40]);
416
- let [leftArea, controlsRight] = bottomArea.split({ type: 'horizontal', sizes: ['92%', null], minimizable: false, resize: false });
417
- let [controlsLeft, timeBarArea] = leftArea.split({ type: 'horizontal', sizes: ['10%', null], minimizable: false, resize: false });
418
- topArea.root.classList.add('lexbar');
419
- bottomArea.root.classList.add('lexbar');
420
- this.controlsCurrentPanel = new LX.Panel({ className: 'lexcontrolspanel lextime' });
421
- this.controlsCurrentPanel.refresh = () => {
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
- topArea.root.classList.add('lexflexarea');
426
- topArea.attach(this.controlsCurrentPanel);
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
- this.playing = !this.playing;
502
- if (this.playing) {
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.controlsPanelLeft.refresh();
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.currentTimeString = this.timeToString(t);
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.startTimeString = this.timeToString(t);
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.endTimeString = this.timeToString(t);
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.controlsPanelLeft.refresh();
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);