@checksub_team/peaks_timeline 2.3.0-alpha.0 → 2.3.0-alpha.2

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.
@@ -63,9 +63,12 @@ define([
63
63
  this._selected = this._source.selected;
64
64
  this._hovered = false;
65
65
  this._isDragged = false;
66
- this._isHandleDragging = false;
66
+ this._isHandleDragged = false;
67
67
  this._destroyed = false;
68
68
 
69
+ // Optional Konva element used as a drag "ghost" preview.
70
+ this._dragGhost = null;
71
+
69
72
  // Performance: track if draw is needed
70
73
  this._drawScheduled = false;
71
74
 
@@ -151,11 +154,54 @@ define([
151
154
  SourceGroup.prototype._onHoverEnd = function() {
152
155
  this._hovered = false;
153
156
  this._manualHover = false;
157
+ this._disableManualHoverTracking();
154
158
  this._view.setHoveredElement(null);
155
159
  this._hideButtons();
156
160
  this._scheduleBatchDraw();
157
161
  };
158
162
 
163
+ SourceGroup.prototype._enableManualHoverTracking = function() {
164
+ if (this._manualHoverTrackingEnabled) {
165
+ return;
166
+ }
167
+
168
+ if (!this._group || this._destroyed) {
169
+ return;
170
+ }
171
+
172
+ var stage = this._group.getStage && this._group.getStage();
173
+
174
+ if (!stage) {
175
+ return;
176
+ }
177
+
178
+ this._manualHoverTrackingEnabled = true;
179
+ this._manualHoverNamespace = '.manualHover.' + this._source.id;
180
+
181
+ this._manualHoverMoveHandler = function() {
182
+ this._manageManualHoverStop();
183
+ }.bind(this);
184
+
185
+ stage.on('mousemove' + this._manualHoverNamespace, this._manualHoverMoveHandler);
186
+ stage.on('touchmove' + this._manualHoverNamespace, this._manualHoverMoveHandler);
187
+ };
188
+
189
+ SourceGroup.prototype._disableManualHoverTracking = function() {
190
+ if (!this._manualHoverTrackingEnabled) {
191
+ return;
192
+ }
193
+
194
+ var stage = this._group && this._group.getStage && this._group.getStage();
195
+
196
+ if (stage && this._manualHoverNamespace) {
197
+ stage.off(this._manualHoverNamespace);
198
+ }
199
+
200
+ this._manualHoverTrackingEnabled = false;
201
+ this._manualHoverMoveHandler = null;
202
+ this._manualHoverNamespace = null;
203
+ };
204
+
159
205
  SourceGroup.prototype._onDragStart = function(element) {
160
206
  this._isDragged = true;
161
207
  this._layer.onSourcesGroupDragStart(element);
@@ -192,7 +238,11 @@ define([
192
238
  };
193
239
 
194
240
  SourceGroup.prototype.isActive = function() {
195
- return this._isDragged || this._isHandleDragging;
241
+ return this._isDragged || this._isHandleDragged;
242
+ };
243
+
244
+ SourceGroup.prototype.isDragged = function() {
245
+ return this._isDragged;
196
246
  };
197
247
 
198
248
  SourceGroup.prototype.addToContent = function(newChild) {
@@ -204,7 +254,7 @@ define([
204
254
  }
205
255
  };
206
256
 
207
- SourceGroup.prototype.prepareDragEnd = function() {
257
+ SourceGroup.prototype._updateHandles = function() {
208
258
  var handleWidth = Math.min(this._peaks.options.sourceHandleWidth, this._width / 2);
209
259
 
210
260
  this._leftHandle.width(handleWidth);
@@ -231,7 +281,7 @@ define([
231
281
  this._layer.batchDraw();
232
282
  }
233
283
  }.bind(this)
234
- );
284
+ , null, false);
235
285
 
236
286
  return {
237
287
  x: draggedElement.absolutePosition().x,
@@ -263,8 +313,6 @@ define([
263
313
  this._currentTimeToPixelsScaleUsed = newTimeToPixelsScale;
264
314
 
265
315
  this._updateMarkers();
266
-
267
- this._rightHandle.x(this._width - this._rightHandle.width());
268
316
  }
269
317
  else {
270
318
  // the zoom was not changed, but the source was resized
@@ -278,6 +326,7 @@ define([
278
326
  }
279
327
  }
280
328
 
329
+ this._updateHandles();
281
330
  this._updateVolumeSlider();
282
331
  this._updateButtons();
283
332
  this._updateLoadingOverlay();
@@ -285,6 +334,13 @@ define([
285
334
  // update unwrap
286
335
  this.updatePreviews();
287
336
  }
337
+
338
+ // Keep the drag ghost in sync automatically while dragging.
339
+ // This lets SourcesLayer avoid manually updating ghosts.
340
+ if (this._isDragged) {
341
+ this.createDragGhost();
342
+ this.updateDragGhost();
343
+ }
288
344
  };
289
345
 
290
346
  SourceGroup.prototype.setWrapping = function(wrap, forceCreate, notify) {
@@ -324,20 +380,14 @@ define([
324
380
  start: this._source.startTime,
325
381
  end: this._source.endTime
326
382
  };
327
- this._isHandleDragging = true;
328
-
383
+ this._isHandleDragged = true;
329
384
  this._hideButtons();
330
385
  };
331
386
 
332
387
  SourceGroup.prototype._onHandleDragEnd = function() {
333
- this._isHandleDragging = false;
388
+ this._isHandleDragged = false;
334
389
  this._showButtons();
335
-
336
- // When resizing via handles, drag events no longer bubble to the parent
337
- // group, so we won't hit the move-drag end path that calls prepareDragEnd.
338
- // Normalize handle geometry here so handles snap to the updated width.
339
- this.update();
340
- this.prepareDragEnd();
390
+ this._layer.processSourceUpdates([this._source]);
341
391
  };
342
392
 
343
393
  SourceGroup.prototype._addHandles = function(forceCreate) {
@@ -356,9 +406,6 @@ define([
356
406
  }
357
407
  });
358
408
 
359
- // Prevent handle drag events from bubbling to the parent group.
360
- // Otherwise the parent SourceGroup dragstart/dragend handlers run and
361
- // the sources-layer creates move-drag ghosts during resize.
362
409
  this._leftHandle.on('dragstart', function(event) {
363
410
  event.cancelBubble = true;
364
411
  self._onHandleDragStart(event);
@@ -392,9 +439,6 @@ define([
392
439
  }
393
440
  });
394
441
 
395
- // Prevent handle drag events from bubbling to the parent group.
396
- // Otherwise the parent SourceGroup dragstart/dragend handlers run and
397
- // the sources-layer creates move-drag ghosts during resize.
398
442
  this._rightHandle.on('dragstart', function(event) {
399
443
  event.cancelBubble = true;
400
444
  self._onHandleDragStart(event);
@@ -467,16 +511,15 @@ define([
467
511
  )
468
512
  );
469
513
 
470
- var actualX = this._group.x() + this._view.getFrameOffset();
471
514
  var x = Math.max(
472
515
  0,
473
- this._view.getFrameOffset() - actualX - 2 * radius
516
+ -(this._group.x() + 2 * radius)
474
517
  );
475
518
  var width = Math.min(
476
519
  this._width - x,
477
520
  this._view.getWidth() + 4 * radius - Math.max(
478
521
  0,
479
- actualX - this._view.getFrameOffset()
522
+ this._group.x()
480
523
  )
481
524
  );
482
525
 
@@ -506,7 +549,7 @@ define([
506
549
  if (this._selected) {
507
550
  backgroundColor = this._source.selectedBackgroundColor;
508
551
  }
509
- else if (this._hovered) {
552
+ else if (this._hovered && this._view.getCurrentMode() !== 'cut') {
510
553
  backgroundColor = this._source.hoverBackgroundColor;
511
554
  }
512
555
  else {
@@ -753,6 +796,7 @@ define([
753
796
 
754
797
  SourceGroup.prototype.startHover = function() {
755
798
  this._manualHover = true;
799
+ this._enableManualHoverTracking();
756
800
  this._group.fire('mouseenter', { evt: new MouseEvent('mouseenter') }, true);
757
801
  };
758
802
 
@@ -762,6 +806,14 @@ define([
762
806
 
763
807
  SourceGroup.prototype.setDragging = function(isDragging) {
764
808
  this._isDragged = isDragging;
809
+
810
+ // Ghost lifecycle is tied to dragging state.
811
+ if (isDragging) {
812
+ this.createDragGhost();
813
+ }
814
+ else {
815
+ this.destroyDragGhost();
816
+ }
765
817
  };
766
818
 
767
819
  SourceGroup.prototype.startDrag = function() {
@@ -948,12 +1000,15 @@ define([
948
1000
  }
949
1001
  }
950
1002
 
1003
+ var self = this;
1004
+
951
1005
  var waveform = new WaveformShape({
952
- layer: this._layer,
953
1006
  view: this._view,
954
- source: this._source,
1007
+ color: this._source.color,
955
1008
  height: preview.group.height(),
956
- url: url
1009
+ waveformDataFunc: function() {
1010
+ return self._createWaveformPointsIterator(url);
1011
+ }
957
1012
  });
958
1013
 
959
1014
  preview.group.add(waveform);
@@ -966,6 +1021,101 @@ define([
966
1021
  this._previewList.push(preview);
967
1022
  };
968
1023
 
1024
+ SourceGroup.prototype._createWaveformPointsIterator = function(url) {
1025
+ var loaded = this._layer.getLoadedData(url + '-scaled');
1026
+ var waveformData = loaded && loaded.data;
1027
+
1028
+ if (!waveformData) {
1029
+ return {
1030
+ next: function() {
1031
+ return { done: true };
1032
+ }
1033
+ };
1034
+ }
1035
+
1036
+ var view = this._view;
1037
+ var source = this._source;
1038
+
1039
+ var groupX = this._group && typeof this._group.x === 'function' ? this._group.x() : 0;
1040
+ var groupWidth = this._width;
1041
+ var viewWidth = view.getWidth();
1042
+
1043
+ var startPixel = 0;
1044
+ var startOffset = 0;
1045
+ var endPixel = Math.min(viewWidth, waveformData.length);
1046
+ var targetSpeed = 1.0;
1047
+
1048
+ if (source) {
1049
+ targetSpeed = source.targetSpeed || 1.0;
1050
+
1051
+ // Determine which part of the source is visible in the view based on
1052
+ // its current on-canvas geometry (supports dragging without relying on
1053
+ // startTime/endTime).
1054
+ var hiddenLeftPixels = Math.max(-groupX, 0);
1055
+ var hiddenRightPixels = Math.max(groupX + groupWidth - viewWidth, 0);
1056
+
1057
+ startPixel = Math.floor(
1058
+ (view.timeToPixels(source.mediaStartTime) + hiddenLeftPixels) * targetSpeed
1059
+ );
1060
+
1061
+ startOffset = view.timeToPixels(source.mediaStartTime);
1062
+
1063
+ endPixel = Math.min(
1064
+ Math.ceil(
1065
+ (view.timeToPixels(source.mediaEndTime) - hiddenRightPixels) * targetSpeed
1066
+ ),
1067
+ waveformData.length
1068
+ );
1069
+ }
1070
+
1071
+ if (startPixel < 0) {
1072
+ startPixel = 0;
1073
+ }
1074
+
1075
+ if (endPixel < startPixel) {
1076
+ endPixel = startPixel;
1077
+ }
1078
+
1079
+ var channels = waveformData.channels;
1080
+
1081
+ var channelData = new Array(channels);
1082
+
1083
+ for (var c = 0; c < channels; c++) {
1084
+ channelData[c] = waveformData.channel(c);
1085
+ }
1086
+
1087
+ var x = startPixel;
1088
+
1089
+ return {
1090
+ next: function() {
1091
+ if (x >= endPixel) {
1092
+ return { done: true };
1093
+ }
1094
+
1095
+ var min = new Array(channels);
1096
+ var max = new Array(channels);
1097
+
1098
+ for (var i = 0; i < channels; i++) {
1099
+ min[i] = channelData[i].min_sample(x);
1100
+ max[i] = channelData[i].max_sample(x);
1101
+ }
1102
+
1103
+ var value = {
1104
+ x: x / targetSpeed - startOffset + 0.5,
1105
+ min: min,
1106
+ max: max
1107
+ };
1108
+
1109
+ x++;
1110
+
1111
+ return {
1112
+ done: false,
1113
+ value: value
1114
+ };
1115
+ }
1116
+ };
1117
+ };
1118
+
969
1119
  SourceGroup.prototype.getAudioPreview = function() {
970
1120
  return this._previewList.filter(function(preview) {
971
1121
  return preview.type === 'audio';
@@ -1271,6 +1421,90 @@ define([
1271
1421
  return this._height;
1272
1422
  };
1273
1423
 
1424
+ /**
1425
+ * Creates the drag ghost preview element if it does not exist.
1426
+ * The ghost shows where the source will be placed when released.
1427
+ */
1428
+ SourceGroup.prototype.createDragGhost = function() {
1429
+ if (this._dragGhost) {
1430
+ return this._dragGhost;
1431
+ }
1432
+
1433
+ var frameOffset = this._view.getFrameOffset();
1434
+ var x = this._view.timeToPixels(this._source.startTime) - frameOffset;
1435
+ var width = this._view.timeToPixels(this._source.endTime - this._source.startTime);
1436
+ var height = this.getCurrentHeight();
1437
+ var y = this.getAbsoluteY();
1438
+
1439
+ this._dragGhost = new Konva.Rect({
1440
+ x: x,
1441
+ y: y,
1442
+ width: width,
1443
+ height: height,
1444
+ fill: this._source.backgroundColor,
1445
+ opacity: 0.4,
1446
+ stroke: this._source.selectedBorderColor,
1447
+ strokeWidth: 2,
1448
+ dash: [8, 4],
1449
+ cornerRadius: 8,
1450
+ listening: false
1451
+ });
1452
+
1453
+ // Add to the main sources layer (not the group) so it stays behind sources.
1454
+ this._layer.add(this._dragGhost);
1455
+ this._dragGhost.moveToBottom();
1456
+
1457
+ // Ensure initial Y snaps to the current line position.
1458
+ this.updateDragGhost();
1459
+
1460
+ return this._dragGhost;
1461
+ };
1462
+
1463
+ /**
1464
+ * Updates the drag ghost preview position and size.
1465
+ *
1466
+ * @param {Object} lineGroupsById Map of lineId -> Konva.Group
1467
+ */
1468
+ SourceGroup.prototype.updateDragGhost = function(lineGroupsById) {
1469
+ if (!this._dragGhost) {
1470
+ return;
1471
+ }
1472
+
1473
+ // Allow callers to omit the lookup; resolve via the owning layer.
1474
+ if (!lineGroupsById
1475
+ && this._layer
1476
+ && typeof this._layer.getLineGroups === 'function') {
1477
+ var lineGroups = this._layer.getLineGroups();
1478
+
1479
+ if (lineGroups && typeof lineGroups.getLineGroupsById === 'function') {
1480
+ lineGroupsById = lineGroups.getLineGroupsById();
1481
+ }
1482
+ }
1483
+
1484
+ var frameOffset = this._view.getFrameOffset();
1485
+ var x = this._view.timeToPixels(this._source.startTime) - frameOffset;
1486
+ var width = this._view.timeToPixels(this._source.endTime - this._source.startTime);
1487
+
1488
+ this._dragGhost.x(x);
1489
+ this._dragGhost.width(width);
1490
+ this._dragGhost.height(this.getCurrentHeight());
1491
+
1492
+ if (lineGroupsById) {
1493
+ var lineGroup = lineGroupsById[this._source.lineId];
1494
+
1495
+ if (lineGroup) {
1496
+ this._dragGhost.y(lineGroup.y());
1497
+ }
1498
+ }
1499
+ };
1500
+
1501
+ SourceGroup.prototype.destroyDragGhost = function() {
1502
+ if (this._dragGhost) {
1503
+ this._dragGhost.destroy();
1504
+ this._dragGhost = null;
1505
+ }
1506
+ };
1507
+
1274
1508
  SourceGroup.prototype.getHeights = function() {
1275
1509
  return {
1276
1510
  unwrapped: this._unwrappedHeight,
@@ -1746,12 +1980,14 @@ define([
1746
1980
  volumeSliderGroup.add(volumeSliderRect);
1747
1981
  volumeSliderGroup.add(volumeSliderLine);
1748
1982
 
1749
- volumeSliderGroup.on('dragstart', function() {
1983
+ volumeSliderGroup.on('dragstart', function(evt) {
1984
+ evt.cancelBubble = true;
1750
1985
  volumeText.visible(true);
1751
1986
  self._peaks.emit('source.startVolumeChange', self._source);
1752
1987
  });
1753
1988
 
1754
- volumeSliderGroup.on('dragmove', function() {
1989
+ volumeSliderGroup.on('dragmove', function(evt) {
1990
+ evt.cancelBubble = true;
1755
1991
  var volume = self._getVolumeFromY(volumeSliderGroup.y());
1756
1992
 
1757
1993
  volumeText.text((volume * 100).toFixed(0) + '%');
@@ -1762,7 +1998,8 @@ define([
1762
1998
  self._scheduleBatchDraw();
1763
1999
  });
1764
2000
 
1765
- volumeSliderGroup.on('dragend', function() {
2001
+ volumeSliderGroup.on('dragend', function(evt) {
2002
+ evt.cancelBubble = true;
1766
2003
  volumeText.visible(false);
1767
2004
  self._peaks.emit('source.endVolumeChange', self._source);
1768
2005
  });
@@ -1782,6 +2019,10 @@ define([
1782
2019
  };
1783
2020
 
1784
2021
  SourceGroup.prototype.destroy = function() {
2022
+ this.destroyDragGhost();
2023
+
2024
+ this._disableManualHoverTracking();
2025
+
1785
2026
  // Cancel any pending idle callbacks to prevent memory leaks
1786
2027
  if (this._pendingIdleCallbacks) {
1787
2028
  this._pendingIdleCallbacks.forEach(function(id) {