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
@@ -12,7 +12,7 @@ const g$2 = globalThis;
12
12
  let LX = g$2.LX;
13
13
  if (!LX) {
14
14
  LX = {
15
- version: '8.2',
15
+ version: '8.2.1',
16
16
  ready: false,
17
17
  extensions: [], // Store extensions used
18
18
  extraCommandbarEntries: [], // User specific entries for command bar
@@ -7615,14 +7615,14 @@ class Branch {
7615
7615
  if (options.id) {
7616
7616
  root.id = options.id;
7617
7617
  }
7618
- root.className = LX.mergeClass('lexbranch w-full rounded-lg my-0 mx-auto', options.className);
7618
+ root.className = LX.mergeClass('lexbranch bg-secondary/50 dark:bg-card text-secondary-foreground dark:text-card-foreground w-full rounded-lg my-0 mx-auto', options.className);
7619
7619
  var that = this;
7620
7620
  this.closed = options.closed ?? false;
7621
7621
  this.root = root;
7622
7622
  this.components = [];
7623
7623
  this.panel = null;
7624
7624
  // Create element
7625
- const title = LX.makeElement('div', 'lexbranchtitle flex cursor-pointer select-none pad-lg bg-card text-card-foreground text-lg', '', root);
7625
+ const title = LX.makeElement('div', 'lexbranchtitle flex cursor-pointer select-none pad-lg text-lg', '', root);
7626
7626
  if (options.icon) {
7627
7627
  const branchIcon = LX.makeIcon(options.icon, { iconClass: 'mr-2' });
7628
7628
  title.appendChild(branchIcon);
@@ -7630,7 +7630,7 @@ class Branch {
7630
7630
  title.innerHTML += name || 'Branch';
7631
7631
  const collapseIcon = LX.makeIcon('Right', { iconClass: 'switch-branch-button', svgClass: 'sm' });
7632
7632
  title.appendChild(collapseIcon);
7633
- var branchContent = LX.makeElement('div', 'lexbranchcontent pad-xs bg-card', '', root);
7633
+ var branchContent = LX.makeElement('div', 'lexbranchcontent pad-xs', '', root);
7634
7634
  branchContent.id = name.replace(/\s/g, '');
7635
7635
  this.content = branchContent;
7636
7636
  this._addBranchSeparator();
@@ -13439,6 +13439,27 @@ LX.addSignal = function (name, obj, callback) {
13439
13439
  }
13440
13440
  LX.signals[name].push(obj);
13441
13441
  };
13442
+ /**
13443
+ * @method removeSignal
13444
+ * @param {String} name
13445
+ * @param {Object} targetObj
13446
+ */
13447
+ LX.removeSignal = function (name, targetObj) {
13448
+ const data = LX.signals[name];
13449
+ if (!data) {
13450
+ return;
13451
+ }
13452
+ if (!targetObj) {
13453
+ delete LX.signals[name];
13454
+ return;
13455
+ }
13456
+ for (let i = 0; i < data.length; ++i) {
13457
+ if (data[i] == targetObj) {
13458
+ data.splice(i, 1);
13459
+ break;
13460
+ }
13461
+ }
13462
+ };
13442
13463
  /**
13443
13464
  * @method emitSignal
13444
13465
  * @param {String} name
@@ -14445,9 +14466,8 @@ class AssetView {
14445
14466
  _subscribeTreeEvents(tree) {
14446
14467
  // If some of these events we don't have to call "resolve" since the AV itself
14447
14468
  // will update the data and refresh when necessary
14448
- tree.on("select", (event, resolve) => {
14449
- if (event.items.length > 1) // Do nothing if multiple selection
14450
- {
14469
+ tree.on('select', (event, resolve) => {
14470
+ if (event.items.length > 1) { // Do nothing if multiple selection
14451
14471
  return;
14452
14472
  }
14453
14473
  const node = event.items[0];
@@ -14471,7 +14491,7 @@ class AssetView {
14471
14491
  this.selectedItem = node;
14472
14492
  }
14473
14493
  });
14474
- tree.on("beforeMove", (event, resolve) => {
14494
+ tree.on('beforeMove', (event, resolve) => {
14475
14495
  const onBeforeNodeDragged = this._callbacks['beforeNodeDragged'];
14476
14496
  const onNodeDragged = this._callbacks['nodeDragged'];
14477
14497
  const node = event.items[0];
@@ -14513,11 +14533,11 @@ class AssetView {
14513
14533
  av_resolve();
14514
14534
  }
14515
14535
  });
14516
- tree.on("beforeDelete", (event, resolve) => {
14536
+ tree.on('beforeDelete', (event, resolve) => {
14517
14537
  const node = event.items[0];
14518
14538
  this._requestDeleteItem(node);
14519
14539
  });
14520
- tree.on("beforeRename", (event, resolve) => {
14540
+ tree.on('beforeRename', (event, resolve) => {
14521
14541
  const node = event.items[0];
14522
14542
  this._requestRenameItem(node, event.newName, true);
14523
14543
  });
@@ -16006,11 +16026,11 @@ class CodeEditor {
16006
16026
  rename: false,
16007
16027
  skipDefaultIcon: true
16008
16028
  });
16009
- this.explorer.on("dblClick", (event) => {
16029
+ this.explorer.on('dblClick', (event) => {
16010
16030
  const node = event.items[0];
16011
16031
  this.loadTab(node.id);
16012
16032
  });
16013
- this.explorer.on("delete", (event) => {
16033
+ this.explorer.on('delete', (event) => {
16014
16034
  const node = event.items[0];
16015
16035
  this.closeTab(node.id);
16016
16036
  });
@@ -16826,12 +16846,12 @@ class CodeEditor {
16826
16846
  this.codeArea.root.style.height = `calc(100% - ${this._fullVerticalOffset}px)`;
16827
16847
  // Process lines on finish computing final sizes
16828
16848
  this.processLines();
16849
+ this._preparedAt = performance.now();
16850
+ if (this.onReady) {
16851
+ this.onReady(this);
16852
+ }
16853
+ console.log(`[LX.CodeEditor] Ready! (font size: ${this.fontSize}px)`);
16829
16854
  }, 50);
16830
- if (this.onReady) {
16831
- this.onReady(this);
16832
- }
16833
- this._preparedAt = performance.now();
16834
- console.log(`[LX.CodeEditor] Ready! (font size: ${this.fontSize}px)`);
16835
16855
  }
16836
16856
  // Clear signals
16837
16857
  clear() {
@@ -20825,6 +20845,7 @@ class DocMaker {
20825
20845
  console.log('Copied!');
20826
20846
  }
20827
20847
  }
20848
+ LX.DocMaker = DocMaker;
20828
20849
 
20829
20850
  // GraphEditor.ts @jxarco
20830
20851
  if (!LX) {
@@ -24144,7 +24165,7 @@ class Timeline {
24144
24165
  treeTracks = this.generateSelectedItemsTreeData();
24145
24166
  }
24146
24167
  this.trackTreesComponent = p.addTree(null, treeTracks, { filter: false, rename: false, draggable: false });
24147
- this.trackTreesComponent.on("select", (event, resolve) => {
24168
+ this.trackTreesComponent.on('select', (event, resolve) => {
24148
24169
  const node = event.items[0];
24149
24170
  if (!event.domEvent.shiftKey) {
24150
24171
  this.deselectAllTracks(false); // no need to update left panel
@@ -24154,7 +24175,7 @@ class Timeline {
24154
24175
  this.setTrackSelection(node.trackData.trackIdx, flag, false, false); // do callback, do not update left panel
24155
24176
  }
24156
24177
  });
24157
- this.trackTreesComponent.on("visibleChanged", (event, resolve) => {
24178
+ this.trackTreesComponent.on('visibleChanged', (event, resolve) => {
24158
24179
  const node = event.items[0];
24159
24180
  if (node.trackData) {
24160
24181
  this.setTrackState(node.trackData.trackIdx, node.visible, false, false); // do not update left panel
@@ -27771,17 +27792,22 @@ class TimeBar {
27771
27792
  duration = 1.0;
27772
27793
  canvas;
27773
27794
  ctx;
27795
+ options;
27774
27796
  markerWidth = 8;
27775
27797
  markerHeight;
27776
27798
  offset;
27777
27799
  lineWidth;
27778
27800
  lineHeight;
27779
- position;
27801
+ linePosition;
27780
27802
  startX;
27781
27803
  endX;
27782
27804
  currentX;
27783
27805
  hovering;
27784
27806
  dragging;
27807
+ _onMouseUpListener;
27808
+ _onMouseMoveListener;
27809
+ _mouseDownCanvasRect = null;
27810
+ updateTheme;
27785
27811
  onChangeCurrent;
27786
27812
  onChangeStart;
27787
27813
  onChangeEnd;
@@ -27789,46 +27815,65 @@ class TimeBar {
27789
27815
  onMouse;
27790
27816
  constructor(area, type, options = {}) {
27791
27817
  this.type = type ?? TimeBar.TIMEBAR_PLAY;
27818
+ this.options = options ?? {};
27792
27819
  this.duration = options.duration ?? this.duration;
27793
27820
  // Create canvas
27794
27821
  this.canvas = document.createElement('canvas');
27822
+ this.canvas.style.borderRadius = '6px';
27795
27823
  this.canvas.width = area.size[0];
27796
27824
  this.canvas.height = area.size[1];
27797
27825
  area.attach(this.canvas);
27798
27826
  this.ctx = this.canvas.getContext('2d');
27799
27827
  this.markerWidth = options.markerWidth ?? this.markerWidth;
27800
- this.markerHeight = options.markerHeight ?? (this.canvas.height * 0.5);
27801
- this.offset = options.offset || (this.markerWidth * 0.5 + 5);
27828
+ this.markerHeight = (options.markerHeight ?? 0.5) * this.canvas.height;
27829
+ const defaultOffset = this.markerWidth * 0.5 + 5;
27830
+ if (typeof (options.offset) == 'number') {
27831
+ this.offset = new vec2(options.offset, options.offset);
27832
+ }
27833
+ else if (Array.isArray(options.offset)) {
27834
+ this.offset = new vec2(options.offset[0] ?? defaultOffset, options.offset[1] ?? defaultOffset);
27835
+ }
27836
+ else {
27837
+ this.offset = new vec2(defaultOffset, defaultOffset);
27838
+ }
27802
27839
  // dimensions of line (not canvas)
27803
- this.lineWidth = this.canvas.width - this.offset * 2;
27840
+ this.lineWidth = this.canvas.width - this.offset.x * 2;
27804
27841
  this.lineHeight = options.barHeight ?? 5;
27805
- this.position = new vec2(this.offset, this.canvas.height * 0.5 - this.lineHeight * 0.5);
27806
- this.startX = this.position.x;
27807
- this.endX = this.position.x + this.lineWidth;
27842
+ this.linePosition = new vec2(this.offset.x, this.canvas.height * 0.5 - this.lineHeight * 0.5);
27843
+ this.startX = this.linePosition.x;
27844
+ this.endX = this.linePosition.x + this.lineWidth;
27808
27845
  this.currentX = this.startX;
27809
27846
  this._draw();
27847
+ function updateTheme() {
27848
+ TimeBar.BACKGROUND_COLOR = LX.getCSSVariable('secondary');
27849
+ TimeBar.COLOR = LX.getCSSVariable('accent');
27850
+ TimeBar.ACTIVE_COLOR = LX.getCSSVariable('color-blue-400');
27851
+ }
27852
+ this.updateTheme = updateTheme.bind(this);
27853
+ LX.addSignal('@on_new_color_scheme', this.updateTheme);
27810
27854
  this.updateTheme();
27811
- LX.addSignal('@on_new_color_scheme', () => {
27812
- // Retrieve again the color using LX.getCSSVariable, which checks the applied theme
27813
- this.updateTheme();
27814
- });
27855
+ // prepare event listeners' functions
27856
+ this._onMouseUpListener = this.onMouseUp.bind(this);
27857
+ this._onMouseMoveListener = this.onMouseMove.bind(this);
27815
27858
  this.canvas.onmousedown = (e) => this.onMouseDown(e);
27816
- this.canvas.onmousemove = (e) => this.onMouseMove(e);
27817
- this.canvas.onmouseup = (e) => this.onMouseUp(e);
27859
+ this.canvas.onmousemove = (e) => {
27860
+ if (this.dragging)
27861
+ return; // already handled by _onMouseMoveListener
27862
+ this.onMouseMove(e);
27863
+ };
27818
27864
  }
27819
- updateTheme() {
27820
- TimeBar.BACKGROUND_COLOR = LX.getCSSVariable('secondary');
27821
- TimeBar.COLOR = LX.getCSSVariable('accent');
27822
- TimeBar.ACTIVE_COLOR = LX.getCSSVariable('color-blue-400');
27865
+ unbind() {
27866
+ removeEventListener('mousemove', this._onMouseMoveListener);
27867
+ removeEventListener('mouseup', this._onMouseUpListener);
27823
27868
  }
27824
27869
  setDuration(duration) {
27825
27870
  this.duration = duration;
27826
27871
  }
27827
27872
  xToTime(x) {
27828
- return ((x - this.offset) / (this.lineWidth)) * this.duration;
27873
+ return ((x - this.offset.x) / (this.lineWidth)) * this.duration;
27829
27874
  }
27830
27875
  timeToX(time) {
27831
- return (time / this.duration) * (this.lineWidth) + this.offset;
27876
+ return (time / this.duration) * (this.lineWidth) + this.offset.x;
27832
27877
  }
27833
27878
  setCurrentTime(time) {
27834
27879
  this.currentX = this.timeToX(time);
@@ -27873,10 +27918,10 @@ class TimeBar {
27873
27918
  ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
27874
27919
  // Draw background timeline
27875
27920
  ctx.fillStyle = TimeBar.COLOR;
27876
- ctx.fillRect(this.position.x, this.position.y, this.lineWidth, this.lineHeight);
27921
+ ctx.fillRect(this.linePosition.x, this.linePosition.y, this.lineWidth, this.lineHeight);
27877
27922
  // Draw background trimed timeline
27878
27923
  ctx.fillStyle = TimeBar.ACTIVE_COLOR;
27879
- ctx.fillRect(this.startX, this.position.y, this.endX - this.startX, this.lineHeight);
27924
+ ctx.fillRect(this.startX, this.linePosition.y, this.endX - this.startX, this.lineHeight);
27880
27925
  ctx.restore();
27881
27926
  // Min-Max time markers
27882
27927
  this._drawTrimMarker('start', this.startX, { color: null, fillColor: TimeBar.ACTIVE_COLOR || '#5f88c9' });
@@ -27913,9 +27958,9 @@ class TimeBar {
27913
27958
  ctx.shadowBlur = 0;
27914
27959
  }
27915
27960
  _drawTimeMarker(name, x, options = {}) {
27916
- let y = this.offset;
27961
+ let y = this.offset.y;
27917
27962
  const w = options.width ? options.width : (this.dragging == name ? 6 : 4);
27918
- const h = this.canvas.height - this.offset * 2;
27963
+ this.canvas.height - this.offset.y * 2;
27919
27964
  let ctx = this.ctx;
27920
27965
  if (!ctx)
27921
27966
  return;
@@ -27930,15 +27975,14 @@ class TimeBar {
27930
27975
  ctx.fillStyle = ctx.strokeStyle = 'white';
27931
27976
  ctx.beginPath();
27932
27977
  ctx.moveTo(x, y);
27933
- ctx.lineTo(x, y + h * 0.5);
27978
+ ctx.lineTo(x, this.linePosition.y + this.lineHeight * 0.5);
27934
27979
  ctx.stroke();
27935
27980
  ctx.closePath();
27936
27981
  ctx.fillStyle = ctx.strokeStyle = options.fillColor || '#111'; // "#FFF";
27937
- y -= this.offset + 8;
27938
27982
  // Current time ball grab
27939
27983
  ctx.fillStyle = options.fillColor || '#e5e5e5';
27940
27984
  ctx.beginPath();
27941
- ctx.roundRect(x - w * 0.5, y + this.offset, w, w, 5);
27985
+ ctx.roundRect(x - w * 0.5, y - w * 0.5, w, w, 5);
27942
27986
  ctx.fill();
27943
27987
  ctx.shadowBlur = 0;
27944
27988
  }
@@ -27960,13 +28004,11 @@ class TimeBar {
27960
28004
  const y = e.offsetY;
27961
28005
  // Check if some marker is clicked
27962
28006
  const threshold = this.markerWidth;
28007
+ const startDist = Math.abs(this.startX - x);
28008
+ const endDist = Math.abs(this.endX - x);
27963
28009
  // grab trim markers only from the bottom
27964
- if (Math.abs(this.startX - x) < threshold && this.position.y < y) {
27965
- this.dragging = 'start';
27966
- canvas.style.cursor = 'grabbing';
27967
- }
27968
- else if (Math.abs(this.endX - x) < threshold && this.position.y < y) {
27969
- this.dragging = 'end';
28010
+ if ((startDist < threshold || endDist < threshold) && this.linePosition.y < y) {
28011
+ this.dragging = (startDist < endDist || x < this.startX) ? 'start' : 'end';
27970
28012
  canvas.style.cursor = 'grabbing';
27971
28013
  }
27972
28014
  else {
@@ -27983,9 +28025,14 @@ class TimeBar {
27983
28025
  }
27984
28026
  this.onSetCurrentValue(this.currentX);
27985
28027
  }
28028
+ this._mouseDownCanvasRect = canvas.getBoundingClientRect(); // cache this to avoid stalls during mousemove
28029
+ window.addEventListener('mousemove', this._onMouseMoveListener);
28030
+ window.addEventListener('mouseup', this._onMouseUpListener);
27986
28031
  this._draw();
27987
28032
  }
27988
28033
  onMouseUp(e) {
28034
+ window.removeEventListener('mousemove', this._onMouseMoveListener);
28035
+ window.removeEventListener('mouseup', this._onMouseUpListener);
27989
28036
  if (this.onMouse) {
27990
28037
  this.onMouse(e);
27991
28038
  }
@@ -28008,17 +28055,17 @@ class TimeBar {
28008
28055
  e.preventDefault();
28009
28056
  const canvas = this.canvas;
28010
28057
  // Process mouse
28011
- const x = e.target == canvas ? e.offsetX : e.clientX - canvas.offsetLeft;
28012
- e.target == canvas ? e.offsetY : e.clientY - canvas.offsetTop;
28058
+ const x = e.target == canvas ? e.offsetX : (e.clientX - this._mouseDownCanvasRect.left);
28059
+ e.target == canvas ? e.offsetY : (e.clientY - this._mouseDownCanvasRect.top);
28013
28060
  if (this.dragging) {
28014
28061
  switch (this.dragging) {
28015
28062
  case 'start':
28016
- this.startX = Math.max(this.position.x, Math.min(this.endX, x));
28063
+ this.startX = Math.max(this.linePosition.x, Math.min(this.endX, x));
28017
28064
  this.currentX = this.startX;
28018
28065
  this.onSetStartValue(this.startX);
28019
28066
  break;
28020
28067
  case 'end':
28021
- this.endX = Math.max(this.startX, Math.min(this.position.x + this.lineWidth, x));
28068
+ this.endX = Math.max(this.startX, Math.min(this.linePosition.x + this.lineWidth, x));
28022
28069
  this.currentX = this.endX;
28023
28070
  this.onSetEndValue(this.endX);
28024
28071
  break;
@@ -28052,15 +28099,18 @@ class TimeBar {
28052
28099
  resize(size) {
28053
28100
  this.canvas.width = Math.max(0, size[0]);
28054
28101
  this.canvas.height = Math.max(0, size[1]);
28055
- let newWidth = size[0] - this.offset * 2;
28102
+ this.markerHeight = (this.options.markerHeight ?? 0.5) * this.canvas.height;
28103
+ let newWidth = size[0] - this.offset.x * 2;
28056
28104
  newWidth = newWidth < 0.00001 ? 0.00001 : newWidth; // actual width of the line = canvas.width - offsetleft - offsetRight
28057
- const startRatio = (this.startX - this.offset) / this.lineWidth;
28058
- const currentRatio = (this.currentX - this.offset) / this.lineWidth;
28059
- const endRatio = (this.endX - this.offset) / this.lineWidth;
28105
+ const startRatio = (this.startX - this.offset.x) / this.lineWidth;
28106
+ const currentRatio = (this.currentX - this.offset.x) / this.lineWidth;
28107
+ const endRatio = (this.endX - this.offset.x) / this.lineWidth;
28060
28108
  this.lineWidth = newWidth;
28061
- this.startX = Math.min(Math.max(newWidth * startRatio, 0), newWidth) + this.offset;
28062
- this.currentX = Math.min(Math.max(newWidth * currentRatio, 0), newWidth) + this.offset;
28063
- this.endX = Math.min(Math.max(newWidth * endRatio, 0), newWidth) + this.offset;
28109
+ this.linePosition.x = this.offset.x;
28110
+ this.linePosition.y = this.canvas.height * 0.5 - this.lineHeight * 0.5;
28111
+ this.startX = Math.min(Math.max(newWidth * startRatio, 0), newWidth) + this.offset.x;
28112
+ this.currentX = Math.min(Math.max(newWidth * currentRatio, 0), newWidth) + this.offset.x;
28113
+ this.endX = Math.min(Math.max(newWidth * endRatio, 0), newWidth) + this.offset.x;
28064
28114
  this._draw();
28065
28115
  }
28066
28116
  }
@@ -28081,10 +28131,7 @@ class VideoEditor {
28081
28131
  playing = false;
28082
28132
  videoReady = false;
28083
28133
  controls = true;
28084
- startTimeString = '0:0';
28085
- endTimeString = '0:0';
28086
28134
  speed = 1.0;
28087
- currentTime = 0.0;
28088
28135
  startTime = 0.0;
28089
28136
  endTime = 0.0;
28090
28137
  requestId;
@@ -28095,27 +28142,29 @@ class VideoEditor {
28095
28142
  crop = false;
28096
28143
  dragOffsetX = 0.0;
28097
28144
  dragOffsetY = 0.0;
28098
- currentTimeString = '';
28099
- timebar;
28145
+ timebar = null;
28100
28146
  mainArea;
28101
28147
  cropArea; // HTMLElement with normCoord attribute;
28148
+ videoArea;
28102
28149
  controlsArea;
28103
- controlsPanelLeft;
28104
- controlsPanelRight;
28105
- controlsCurrentPanel;
28150
+ controlsComponents;
28106
28151
  onChangeCurrent;
28107
28152
  onChangeStart;
28108
28153
  onChangeEnd;
28109
28154
  onKeyUp;
28110
28155
  onSetTime;
28111
28156
  onVideoLoaded;
28112
- onCropArea;
28113
28157
  onResize;
28158
+ onCropArea;
28114
28159
  onChangeSpeed;
28160
+ onChangeState;
28161
+ onChangeLoop;
28115
28162
  _updateTime = true;
28116
28163
  _onCropMouseUp;
28117
28164
  _onCropMouseMove;
28118
- resize;
28165
+ resize = null;
28166
+ resizeControls = null;
28167
+ resizeVideo = null;
28119
28168
  constructor(area, options = {}) {
28120
28169
  this.options = options ?? {};
28121
28170
  this.speed = options.speed ?? this.speed;
@@ -28158,120 +28207,42 @@ class VideoEditor {
28158
28207
  videoArea.root.classList.add('lexvideoeditor');
28159
28208
  }
28160
28209
  videoArea.root.style.position = 'relative';
28210
+ this.videoArea = videoArea;
28161
28211
  this.controlsArea = controlsArea;
28162
- // Create playing timeline area and attach panels
28163
- let [topArea, bottomArea] = controlsArea.split({ type: 'vertical', sizes: ['50%', null], minimizable: false, resize: false });
28164
- bottomArea.setSize([bottomArea.size[0], 40]);
28165
- let [leftArea, controlsRight] = bottomArea.split({ type: 'horizontal', sizes: ['92%', null], minimizable: false, resize: false });
28166
- let [controlsLeft, timeBarArea] = leftArea.split({ type: 'horizontal', sizes: ['10%', null], minimizable: false, resize: false });
28167
- topArea.root.classList.add('lexbar');
28168
- bottomArea.root.classList.add('lexbar');
28169
- this.controlsCurrentPanel = new LX.Panel({ className: 'lexcontrolspanel lextime' });
28170
- this.controlsCurrentPanel.refresh = () => {
28171
- this.controlsCurrentPanel.clear();
28172
- this.controlsCurrentPanel.addLabel(this.currentTimeString, { float: 'center' });
28173
- };
28174
- topArea.root.classList.add('lexflexarea');
28175
- topArea.attach(this.controlsCurrentPanel);
28176
- this.controlsCurrentPanel.refresh();
28177
- const style = getComputedStyle(bottomArea.root);
28178
- let padding = Number(style.getPropertyValue('padding').replace('px', ''));
28179
- this.timebar = new TimeBar(timeBarArea, TimeBar.TIMEBAR_TRIM, { offset: padding });
28180
- // Create controls panel (play/pause button and start time)
28181
- this.controlsPanelLeft = new LX.Panel({ className: 'lexcontrolspanel' });
28182
- this.controlsPanelLeft.refresh = () => {
28183
- this.controlsPanelLeft.clear();
28184
- this.controlsPanelLeft.sameLine();
28185
- let playbtn = this.controlsPanelLeft.addButton(null, 'PlayButton', (v) => {
28186
- this.playing = v;
28187
- if (this.playing) {
28188
- if (this.video.currentTime + 0.000001 >= this.endTime) {
28189
- this.video.currentTime = this.startTime;
28190
- }
28191
- this.video.play();
28192
- }
28193
- else {
28194
- this.video.pause();
28195
- }
28196
- }, { icon: 'Play@solid', swap: 'Pause@solid', hideName: true, title: 'Play', tooltip: true, className: 'justify-center' });
28197
- playbtn.setState(this.playing, true);
28198
- this.controlsPanelLeft.addButton(null, '', (v, e) => {
28199
- const panel = new LX.Panel();
28200
- panel.addRange('Speed', this.speed, (v) => {
28201
- this.speed = v;
28202
- this.video.playbackRate = v;
28203
- if (this.onChangeSpeed) {
28204
- this.onChangeSpeed(v);
28205
- }
28206
- }, { min: 0, max: 2.5, step: 0.01, hideName: true });
28207
- new LX.Popover(e.target, [panel], { align: 'start', side: 'top', sideOffset: 12 });
28208
- }, { icon: 'Timer@solid', title: 'Speed', tooltip: true, className: 'justify-center' });
28209
- this.controlsPanelLeft.addButton(null, 'Loop', (v) => {
28210
- this.loop = v;
28211
- }, { title: 'Loop', tooltip: true, icon: ('Repeat@solid'), className: `justify-center`, selectable: true, selected: this.loop });
28212
- this.controlsPanelLeft.addLabel(this.startTimeString, { width: '100px' });
28213
- this.controlsPanelLeft.endLine();
28214
- let availableWidth = leftArea.root.clientWidth - controlsLeft.root.clientWidth;
28215
- this.timebar.resize([availableWidth, timeBarArea.root.clientHeight]);
28216
- };
28217
- this.controlsPanelLeft.refresh();
28218
- controlsLeft.root.style.minWidth = 'fit-content';
28219
- // controlsLeft.root.classList.add();
28220
- controlsLeft.attach(this.controlsPanelLeft);
28221
- // Create right controls panel (ens time)
28222
- this.controlsPanelRight = new LX.Panel({ className: 'lexcontrolspanel' });
28223
- this.controlsPanelRight.refresh = () => {
28224
- this.controlsPanelRight.clear();
28225
- this.controlsPanelRight.addLabel(this.endTimeString, { width: 100 });
28212
+ this.controlsComponents = {
28213
+ timebar: null,
28214
+ playBtn: null,
28215
+ speedBtn: null,
28216
+ loopBtn: null,
28217
+ trimStartText: null,
28218
+ trimEndText: null,
28219
+ curTimeText: null,
28220
+ resetCropBtn: null
28226
28221
  };
28227
- this.controlsPanelRight.refresh();
28228
- controlsRight.root.style.minWidth = 'fit-content';
28229
- controlsRight.attach(this.controlsPanelRight);
28230
- this.timebar.onChangeCurrent = this._setCurrentTime.bind(this);
28231
- this.timebar.onChangeStart = this._setStartTime.bind(this);
28232
- this.timebar.onChangeEnd = this._setEndTime.bind(this);
28233
- this.resize = () => {
28234
- bottomArea.setSize([this.controlsArea.root.clientWidth, 40]);
28235
- let availableWidth = this.controlsArea.root.clientWidth - controlsLeft.root.clientWidth
28236
- - controlsRight.root.clientWidth;
28237
- this.timebar.resize([availableWidth, timeBarArea.root.clientHeight]);
28222
+ this.createControls();
28223
+ this.resizeVideo = () => {
28238
28224
  this.moveCropArea(this.cropArea.normCoords.x, this.cropArea.normCoords.y, true);
28239
28225
  this.resizeCropArea(this.cropArea.normCoords.w, this.cropArea.normCoords.h, true);
28240
28226
  if (this.onResize) {
28241
28227
  this.onResize([videoArea.root.clientWidth, videoArea.root.clientHeight]);
28242
28228
  }
28243
28229
  };
28230
+ this.resize = () => {
28231
+ this.resizeVideo();
28232
+ this.resizeControls();
28233
+ };
28244
28234
  area.onresize = this.resize.bind(this);
28245
28235
  window.addEventListener('resize', area.onresize);
28246
28236
  this.onKeyUp = (e) => {
28247
28237
  if (this.controls && e.key == ' ') {
28248
28238
  e.preventDefault();
28249
28239
  e.stopPropagation();
28250
- this.playing = !this.playing;
28251
- if (this.playing) {
28252
- if (this.video.currentTime + 0.000001 >= this.endTime) {
28253
- this.video.currentTime = this.startTime;
28254
- }
28255
- this.video.play();
28256
- }
28257
- else {
28258
- this.video.pause();
28259
- }
28260
- this.controlsPanelLeft.refresh();
28240
+ // do not skip callback
28241
+ this.controlsComponents.playBtn?.setState(!this.playing, false);
28261
28242
  }
28262
28243
  };
28263
28244
  window.addEventListener('keyup', this.onKeyUp);
28264
- const parent = controlsArea.parentElement ? controlsArea.parentElement : controlsArea.root.parentElement;
28265
- // Add canvas event listeneres
28266
- parent.addEventListener('mousedown', (e) => {
28267
- // if( this.controls) {
28268
- // this.timebar.onMouseDown(e);
28269
- // }
28270
- });
28271
28245
  this._onCropMouseUp = (event) => {
28272
- // if(this.controls) {
28273
- // this.timebar.onMouseUp(event);
28274
- // }
28275
28246
  event.preventDefault();
28276
28247
  event.stopPropagation();
28277
28248
  if ((this.isDragging || this.isResizing) && this.onCropArea) {
@@ -28283,9 +28254,6 @@ class VideoEditor {
28283
28254
  document.removeEventListener('mousemove', this._onCropMouseMove); // self destroy. Added during mouseDown on cropArea and handles
28284
28255
  };
28285
28256
  this._onCropMouseMove = (event) => {
28286
- // if(this.controls) {
28287
- // this.timebar.onMouseMove(event);
28288
- // }
28289
28257
  window.getSelection()?.removeAllRanges();
28290
28258
  event.preventDefault();
28291
28259
  event.stopPropagation();
@@ -28346,6 +28314,182 @@ class VideoEditor {
28346
28314
  this.onChangeStart = null;
28347
28315
  this.onChangeEnd = null;
28348
28316
  }
28317
+ createControls(options = null) {
28318
+ const controlsArea = this.controlsArea;
28319
+ options = options ?? this.options;
28320
+ // clear area. Signals are not cleared !!! (not a problem if there are no signals)
28321
+ while (controlsArea.root.children.length) {
28322
+ controlsArea.root.children[0].remove();
28323
+ }
28324
+ controlsArea.sections.length = 0;
28325
+ // start trimming text
28326
+ this.controlsComponents.trimStartText = new LX.TextInput(null, this.timeToString(this.startTime), null, { width: '100px',
28327
+ title: 'Trimmed Start Time', disabled: true, inputClass: 'bg-none' });
28328
+ this.controlsComponents.trimEndText = new LX.TextInput(null, this.timeToString(this.endTime), null, { width: '100px',
28329
+ title: 'Trimmed End Time', disabled: true, inputClass: 'bg-none' });
28330
+ this.controlsComponents.curTimeText = new LX.TextInput(null, this.video.currentTime, null, { title: 'Current Time', float: 'center',
28331
+ disabled: true, inputClass: 'bg-none' });
28332
+ // reset crop area
28333
+ this.controlsComponents.resetCropBtn = new LX.Button('ResetCrop', null, (v) => {
28334
+ this.moveCropArea(0, 0, true);
28335
+ this.resizeCropArea(1, 1, true);
28336
+ if (this.onCropArea) {
28337
+ this.onCropArea(this.getCroppedArea());
28338
+ }
28339
+ }, { width: '40px', title: 'Reset Crop Area', icon: 'Crop@solid', hideName: true,
28340
+ className: 'justify-center' + (this.crop ? '' : ' hidden') });
28341
+ // play button
28342
+ this.controlsComponents.playBtn = new LX.Button('Play', '', (v) => {
28343
+ this.playing = v;
28344
+ if (this.playing) {
28345
+ if (this.video.currentTime + 0.000001 >= this.endTime) {
28346
+ this.video.currentTime = this.startTime;
28347
+ }
28348
+ this.video.play();
28349
+ }
28350
+ else {
28351
+ this.video.pause();
28352
+ }
28353
+ if (this.onChangeState) {
28354
+ this.onChangeState(v);
28355
+ }
28356
+ }, { width: '40px', title: 'Play/Pause', icon: 'Play@solid', swap: 'Pause@solid', hideName: true, className: 'justify-center' });
28357
+ this.controlsComponents.playBtn.setState(this.playing, true);
28358
+ // speed button
28359
+ this.controlsComponents.speedBtn = new LX.Button('Speed', '', (v, e) => {
28360
+ const panel = new LX.Panel();
28361
+ panel.addRange('Speed', this.speed, (v) => {
28362
+ this.speed = v;
28363
+ this.video.playbackRate = v;
28364
+ if (this.onChangeSpeed) {
28365
+ this.onChangeSpeed(v);
28366
+ }
28367
+ }, { min: 0, max: 2.5, step: 0.01, hideName: true });
28368
+ new LX.Popover(e.target, [panel], { align: 'start', side: 'top', sideOffset: 12 });
28369
+ }, { width: '40px', title: 'Speed', hideName: true, icon: 'Timer@solid', className: 'justify-center' });
28370
+ // loop button
28371
+ this.controlsComponents.loopBtn = new LX.Button('', 'Loop', (v) => {
28372
+ this.loop = v;
28373
+ if (this.onChangeLoop) {
28374
+ this.onChangeLoop(v);
28375
+ }
28376
+ }, { width: '40px', hideName: true, title: 'Loop', icon: 'Repeat@solid', className: `justify-center`, selectable: true,
28377
+ selected: this.loop });
28378
+ let timeBarArea = null;
28379
+ if (typeof (options.controlsLayout) == 'function') {
28380
+ timeBarArea = options.controlsLayout;
28381
+ }
28382
+ else if (options.controlsLayout == 1) {
28383
+ timeBarArea = this._createControlsLayout_1();
28384
+ }
28385
+ else {
28386
+ timeBarArea = this._createControlsLayout_0();
28387
+ }
28388
+ if (this.timebar) {
28389
+ this.timebar.unbind();
28390
+ }
28391
+ this.timebar = this.controlsComponents.timebar = new TimeBar(timeBarArea, TimeBar.TIMEBAR_TRIM, { offset: [12, null] });
28392
+ this.timebar.onChangeCurrent = this._setCurrentTime.bind(this);
28393
+ this.timebar.onChangeStart = this._setStartTime.bind(this);
28394
+ this.timebar.onChangeEnd = this._setEndTime.bind(this);
28395
+ let duration = 1;
28396
+ if (this.video.duration !== Infinity && !isNaN(this.video.duration)) {
28397
+ duration = this.video.duration;
28398
+ }
28399
+ this.timebar.setDuration(duration);
28400
+ this.timebar.setEndTime(this.endTime);
28401
+ this.timebar.setStartTime(this.startTime);
28402
+ this.timebar.setCurrentTime(this.startTime);
28403
+ this.resizeControls();
28404
+ }
28405
+ /**
28406
+ * Creates the areas where components will be.
28407
+ * Attaches all (desired) components of controlsComponents except the timebar
28408
+ * @returns {Area} for the timebar
28409
+ * Layout:
28410
+ * |--------------------------timebar--------------------------|
28411
+ * play speed loop resetCrop curTime trimStart / trimEnd
28412
+ */
28413
+ _createControlsLayout_1() {
28414
+ const controlsArea = this.controlsArea;
28415
+ // Create playing timeline area and attach panels
28416
+ let [timeBarArea, bottomArea] = controlsArea.split({ type: 'vertical', sizes: ['50%', null], minimizable: false, resize: false });
28417
+ bottomArea.root.classList.add('relative');
28418
+ let separator = document.createElement('p');
28419
+ separator.style.alignContent = 'center';
28420
+ separator.innerText = '/';
28421
+ let trimDiv = LX.makeContainer(['fit-content', '100%'], 'relative flex flex-row pb-2', null, bottomArea, { float: 'right' });
28422
+ trimDiv.appendChild(this.controlsComponents.trimStartText.root);
28423
+ trimDiv.appendChild(separator);
28424
+ trimDiv.appendChild(this.controlsComponents.trimEndText.root);
28425
+ this.controlsComponents.trimStartText.root.querySelector('input').classList.add('text-end');
28426
+ this.controlsComponents.trimStartText.root.classList.add('top-0', 'bottom-0');
28427
+ this.controlsComponents.trimEndText.root.classList.add('top-0', 'bottom-0');
28428
+ // current time
28429
+ let curTimeDiv = LX.makeContainer(['100%', '100%'], 'absolute top-0 left-0 flex flex-row justify-center items-center pb-2', null, bottomArea, {});
28430
+ curTimeDiv.appendChild(this.controlsComponents.curTimeText.root);
28431
+ // Buttons
28432
+ const buttonsPanel = bottomArea.addPanel({ className: 'absolute top-0 left-0 flex flex-row pl-4 pr-4 pt-1 pb-2' });
28433
+ buttonsPanel.root.classList.remove('pad-md');
28434
+ buttonsPanel._attachComponent(this.controlsComponents.playBtn);
28435
+ buttonsPanel._attachComponent(this.controlsComponents.speedBtn);
28436
+ buttonsPanel._attachComponent(this.controlsComponents.loopBtn);
28437
+ buttonsPanel._attachComponent(this.controlsComponents.resetCropBtn);
28438
+ this.controlsComponents.playBtn.root.classList.add('pl-0');
28439
+ this.controlsComponents.resetCropBtn.root.classList.add('pr-0');
28440
+ // timebar
28441
+ timeBarArea.root.classList.add('p-4', 'pb-0');
28442
+ this.resizeControls = () => {
28443
+ const style = getComputedStyle(timeBarArea.root);
28444
+ let pleft = parseFloat(style.paddingLeft);
28445
+ let pright = parseFloat(style.paddingRight);
28446
+ let ptop = parseFloat(style.paddingTop);
28447
+ let pbot = parseFloat(style.paddingBottom);
28448
+ // assuming timeBarArea will not overflow
28449
+ this.timebar.resize([timeBarArea.root.clientWidth - pleft - pright, timeBarArea.root.clientHeight - ptop - pbot]);
28450
+ };
28451
+ return timeBarArea;
28452
+ }
28453
+ /**
28454
+ * Creates the areas where components will be.
28455
+ * Attaches all (desired) components of controlsComponents except the timebar
28456
+ * @returns {Area} for the timebar
28457
+ * Layout:
28458
+ * curTime
28459
+ * play speed loop trimStart |---timebar---| trimend
28460
+ */
28461
+ _createControlsLayout_0() {
28462
+ const controlsArea = this.controlsArea;
28463
+ // Create playing timeline area and attach panels
28464
+ let [topArea, bottomArea] = controlsArea.split({ type: 'vertical', sizes: ['50%', null], minimizable: false, resize: false });
28465
+ bottomArea.setSize([bottomArea.size[0], 40]);
28466
+ let [leftArea, controlsRight] = bottomArea.split({ type: 'horizontal', sizes: ['92%', null], minimizable: false, resize: false });
28467
+ let [controlsLeft, timeBarArea] = leftArea.split({ type: 'horizontal', sizes: ['10%', null], minimizable: false, resize: false });
28468
+ const controlsCurrentPanel = topArea.addPanel({ className: 'flex' });
28469
+ controlsCurrentPanel._attachComponent(this.controlsComponents.curTimeText);
28470
+ // Create controls panel (play/pause button and start time)
28471
+ controlsLeft.root.classList.add('min-w-fit');
28472
+ const controlsPanelLeft = controlsLeft.addPanel({ className: 'lexcontrolspanel p-0 pl-2' });
28473
+ controlsPanelLeft.root.classList.remove('pad-md');
28474
+ controlsPanelLeft.sameLine();
28475
+ controlsPanelLeft._attachComponent(this.controlsComponents.playBtn);
28476
+ controlsPanelLeft._attachComponent(this.controlsComponents.speedBtn);
28477
+ controlsPanelLeft._attachComponent(this.controlsComponents.loopBtn);
28478
+ controlsPanelLeft._attachComponent(this.controlsComponents.trimStartText);
28479
+ controlsPanelLeft.endLine();
28480
+ // Create right controls panel (end time)
28481
+ controlsRight.root.classList.add('min-w-fit');
28482
+ const controlsPanelRight = controlsRight.addPanel({ className: 'lexcontrolspanel p-0' });
28483
+ controlsPanelRight.root.classList.remove('pad-md');
28484
+ controlsPanelRight._attachComponent(this.controlsComponents.trimEndText);
28485
+ this.resizeControls = () => {
28486
+ bottomArea.setSize([this.controlsArea.root.clientWidth, 40]);
28487
+ let availableWidth = this.controlsArea.root.clientWidth - controlsLeft.root.clientWidth
28488
+ - controlsRight.root.clientWidth;
28489
+ this.timebar.resize([availableWidth, timeBarArea.root.clientHeight]);
28490
+ };
28491
+ return timeBarArea;
28492
+ }
28349
28493
  setCropAreaHandles(flags) {
28350
28494
  // remove existing resizer handles
28351
28495
  const resizers = this.cropArea.getElementsByClassName('resize-handle');
@@ -28474,18 +28618,12 @@ class VideoEditor {
28474
28618
  this.video.currentTime = this.startTime;
28475
28619
  this.timebar.setCurrentTime(this.video.currentTime);
28476
28620
  };
28477
- this.timebar.startX = this.timebar.position.x;
28478
- this.timebar.endX = this.timebar.position.x + this.timebar.lineWidth;
28479
28621
  this.startTime = 0;
28480
28622
  this.endTime = this.video.duration;
28481
28623
  this.timebar.setDuration(this.endTime);
28482
28624
  this.timebar.setEndTime(this.video.duration);
28483
28625
  this.timebar.setStartTime(this.startTime);
28484
28626
  this.timebar.setCurrentTime(this.startTime);
28485
- // this.timebar.setStartValue( this.timebar.startX);
28486
- // this.timebar.currentX = this._timeToX( this.video.currentTime);
28487
- // this.timebar.setCurrentValue( this.timebar.currentX);
28488
- // this.timebar.update( this.timebar.currentX );
28489
28627
  // only have one update on flight
28490
28628
  if (!this.requestId) {
28491
28629
  this._update();
@@ -28518,7 +28656,7 @@ class VideoEditor {
28518
28656
  this.video.pause();
28519
28657
  if (!this.loop) {
28520
28658
  this.playing = false;
28521
- this.controlsPanelLeft.refresh();
28659
+ this.controlsComponents.playBtn?.setState(false, true); // skip callback
28522
28660
  }
28523
28661
  else {
28524
28662
  this.video.currentTime = this.startTime;
@@ -28544,8 +28682,7 @@ class VideoEditor {
28544
28682
  if (this.video.currentTime != t && this._updateTime) {
28545
28683
  this.video.currentTime = t;
28546
28684
  }
28547
- this.currentTimeString = this.timeToString(t);
28548
- this.controlsCurrentPanel.refresh();
28685
+ this.controlsComponents.curTimeText?.set(this.timeToString(t));
28549
28686
  if (this.onSetTime) {
28550
28687
  this.onSetTime(t);
28551
28688
  }
@@ -28555,8 +28692,7 @@ class VideoEditor {
28555
28692
  }
28556
28693
  _setStartTime(t) {
28557
28694
  this.startTime = this.video.currentTime = t;
28558
- this.startTimeString = this.timeToString(t);
28559
- this.controlsPanelLeft.refresh();
28695
+ this.controlsComponents.trimStartText?.set(this.timeToString(t));
28560
28696
  if (this.onSetTime) {
28561
28697
  this.onSetTime(t);
28562
28698
  }
@@ -28566,8 +28702,7 @@ class VideoEditor {
28566
28702
  }
28567
28703
  _setEndTime(t) {
28568
28704
  this.endTime = this.video.currentTime = t;
28569
- this.endTimeString = this.timeToString(t);
28570
- this.controlsPanelRight.refresh();
28705
+ this.controlsComponents.trimEndText?.set(this.timeToString(t));
28571
28706
  if (this.onSetTime) {
28572
28707
  this.onSetTime(t);
28573
28708
  }
@@ -28588,7 +28723,9 @@ class VideoEditor {
28588
28723
  return this.cropArea.getBoundingClientRect();
28589
28724
  }
28590
28725
  showCropArea() {
28726
+ this.crop = true;
28591
28727
  this.cropArea.classList.remove('hidden');
28728
+ this.controlsComponents.resetCropBtn?.root.classList.remove('hidden');
28592
28729
  const nodes = this.cropArea.parentElement?.childNodes ?? [];
28593
28730
  const rect = this.cropArea.getBoundingClientRect();
28594
28731
  for (let i = 0; i < nodes.length; i++) {
@@ -28601,7 +28738,9 @@ class VideoEditor {
28601
28738
  }
28602
28739
  }
28603
28740
  hideCropArea() {
28741
+ this.crop = false;
28604
28742
  this.cropArea.classList.add('hidden');
28743
+ this.controlsComponents.resetCropBtn?.root.classList.add('hidden');
28605
28744
  const nodes = this.cropArea.parentElement?.childNodes ?? [];
28606
28745
  for (let i = 0; i < nodes.length; i++) {
28607
28746
  const node = nodes[i];
@@ -28629,8 +28768,11 @@ class VideoEditor {
28629
28768
  this.stopUpdates();
28630
28769
  this.video.pause();
28631
28770
  this.playing = false;
28632
- this.controlsPanelLeft.refresh();
28771
+ this.controlsComponents.playBtn?.setState(false, true); // skip callback
28633
28772
  this.video.src = '';
28773
+ if (this.timebar) {
28774
+ this.timebar.unbind();
28775
+ }
28634
28776
  window.removeEventListener('keyup', this.onKeyUp);
28635
28777
  document.removeEventListener('mouseup', this._onCropMouseUp);
28636
28778
  document.removeEventListener('mousemove', this._onCropMouseMove);