@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.
@@ -39,12 +39,17 @@ define([
39
39
  this._allowEditing = allowEditing;
40
40
  this._sourcesGroup = {};
41
41
  this._layer = new Konva.Layer();
42
- // Separate layer for dragged elements - always renders on top of sources layer
43
- this._dragLayer = new Konva.Layer();
44
42
  this._dataRetriever = new DataRetriever(peaks);
45
43
  this._lineGroups = new LineGroups(peaks, view, this);
46
44
  this._lineGroups.addToLayer(this);
47
45
 
46
+ // Drag overlay container.
47
+ // Use a Group inside the main sources layer to avoid adding an extra Stage layer.
48
+ this._dragGroup = new Konva.Group({
49
+ listening: false
50
+ });
51
+ this._layer.add(this._dragGroup);
52
+
48
53
  this._loadedData = {};
49
54
 
50
55
  // Create invoker for performance optimizations
@@ -76,7 +81,7 @@ define([
76
81
 
77
82
  SourcesLayer.prototype._onSourcesDelayedLineChanged = function() {
78
83
  // Update dragged source groups when sources change line after a delay (e.g., after automatic line creation)
79
- if (this._dragGhosts && this._draggedElements && this._draggedElements.length > 0) {
84
+ if (this._draggedElements && this._draggedElements.length > 0) {
80
85
  this._dragSourcesGroup();
81
86
  }
82
87
  };
@@ -107,6 +112,11 @@ define([
107
112
 
108
113
  SourcesLayer.prototype.add = function(element) {
109
114
  this._layer.add(element);
115
+
116
+ // Keep drag group on top even if other callers add elements later.
117
+ if (this._dragGroup) {
118
+ this._dragGroup.moveToTop();
119
+ }
110
120
  };
111
121
 
112
122
  /**
@@ -117,7 +127,6 @@ define([
117
127
 
118
128
  SourcesLayer.prototype.addToStage = function(stage) {
119
129
  stage.add(this._layer);
120
- stage.add(this._dragLayer);
121
130
  };
122
131
 
123
132
  SourcesLayer.prototype.enableEditing = function(enable) {
@@ -146,22 +155,22 @@ define([
146
155
  const frameEndTime = this._view.pixelsToTime(frameOffset + width);
147
156
  var redraw = false;
148
157
  var isSourceGroupHovered = false;
149
- var isSourceGroupActive = false;
158
+ var isSourceGroupDragged = false;
150
159
 
151
160
  if (sourceGroup) {
152
161
  isSourceGroupHovered = sourceGroup.isHovered();
153
- isSourceGroupActive = sourceGroup.isActive();
162
+ isSourceGroupDragged = sourceGroup.isDragged();
154
163
  this._destroySourceGroup(source);
155
164
  redraw = true;
156
165
  }
157
166
 
158
- if (source.isVisible(frameStartTime, frameEndTime) || isSourceGroupActive) {
167
+ if (source.isVisible(frameStartTime, frameEndTime) || isSourceGroupDragged) {
159
168
  const newSourceGroup = this._addSourceGroup(source);
160
169
 
161
170
  if (isSourceGroupHovered) {
162
171
  newSourceGroup.startHover();
163
172
  }
164
- if (isSourceGroupActive) {
173
+ if (isSourceGroupDragged) {
165
174
  newSourceGroup.startDrag();
166
175
  }
167
176
  redraw = true;
@@ -375,10 +384,8 @@ define([
375
384
  draggable: true
376
385
  });
377
386
 
378
- // Store initial source positions and create ghost previews
379
387
  var self = this;
380
388
 
381
- this._dragGhosts = [];
382
389
  this._initialSourcePositions = {};
383
390
 
384
391
  this._draggedElements.forEach(function(source) {
@@ -399,38 +406,22 @@ define([
399
406
  // Get absolute Y position before moving (relative to line group)
400
407
  var absoluteY = sourceGroup.getAbsoluteY();
401
408
 
402
- // Move source to the drag layer so it draws above ALL other sources/segments
403
- sourceGroup.moveTo(self._dragLayer);
409
+ // Move source to the drag group so it draws above ALL other sources/segments
410
+ // without introducing an additional Konva.Layer on the Stage.
411
+ sourceGroup.moveTo(self._dragGroup);
404
412
  // Restore the Y position (now relative to drag layer, which is at y=0)
405
413
  sourceGroup.y(absoluteY);
406
-
407
- // Create ghost preview
408
- var ghost = self._createDragGhost(sourceGroup);
409
-
410
- self._dragGhosts.push({
411
- ghost: ghost,
412
- sourceId: source.id
413
- });
414
414
  }
415
415
  });
416
416
  };
417
417
 
418
418
  SourcesLayer.prototype.onSourcesGroupDragEnd = function() {
419
- // Clean up ghost previews
420
- if (this._dragGhosts) {
421
- this._dragGhosts.forEach(function(item) {
422
- if (item.ghost) {
423
- item.ghost.destroy();
424
- }
425
- });
426
- this._dragGhosts = null;
427
- }
419
+ var self = this;
420
+
428
421
  this._initialSourcePositions = null;
429
422
  this._dragOffsetX = undefined;
430
423
  this._dragOffsetY = undefined;
431
424
 
432
- var self = this;
433
-
434
425
  const updatedSources = this._draggedElements.map(
435
426
  function(source) {
436
427
  const sourceGroup = self._sourcesGroup[source.id];
@@ -438,7 +429,6 @@ define([
438
429
  if (sourceGroup) {
439
430
  // Clear dragging state before moving back to line group
440
431
  sourceGroup.setDragging(false);
441
- sourceGroup.prepareDragEnd();
442
432
  // Move source back to its line group (it was moved to layer during drag)
443
433
  self._lineGroups.addSource(source, sourceGroup);
444
434
  // Reset Y position to 0 relative to parent line group
@@ -453,19 +443,15 @@ define([
453
443
  this._draggedElementId = null;
454
444
 
455
445
  this.refresh();
456
- this._view.batchDrawSourcesLayer();
457
- this._view.updateTimelineLength();
458
-
459
- this._peaks.emit('sources.updated', updatedSources);
446
+ this.processSourceUpdates(updatedSources);
460
447
  };
461
448
 
462
449
  SourcesLayer.prototype.onSourcesGroupDrag = function(draggedElement) {
463
450
  var pointerPos = this._view.getPointerPosition();
464
451
 
465
- this._view.updateWithAutoScroll(this._dragSourcesGroup.bind(this));
452
+ this._view.updateWithAutoScroll(this._dragSourcesGroup.bind(this), null, true);
466
453
 
467
454
  // Return position that follows the mouse cursor exactly
468
- // The ghost preview shows where it will actually be placed
469
455
  var clickedSourceGroup = this._sourcesGroup[this._draggedElementId];
470
456
 
471
457
  if (clickedSourceGroup) {
@@ -563,14 +549,18 @@ define([
563
549
  mousePosY
564
550
  );
565
551
 
566
- this._updateDragGhosts();
567
-
568
552
  if (shouldRedraw) {
569
553
  this.batchDraw();
570
- this._dragLayer.batchDraw();
571
554
  }
572
555
  };
573
556
 
557
+ SourcesLayer.prototype.processSourceUpdates = function(updatedSources) {
558
+ this._view.batchDrawSourcesLayer();
559
+ this._view.updateTimelineLength();
560
+
561
+ this._peaks.emit('sources.updated', updatedSources);
562
+ };
563
+
574
564
  SourcesLayer.prototype.findSources = function(startTime, endTime) {
575
565
  var sources = this._peaks.sourceHandler.find(startTime, endTime);
576
566
  var lineIds = this._lineGroups.getVisibleLines();
@@ -582,78 +572,6 @@ define([
582
572
  );
583
573
  };
584
574
 
585
- /**
586
- * Creates a ghost preview element for a source being dragged.
587
- * The ghost shows where the source will be placed when released.
588
- *
589
- * @private
590
- * @param {SourceGroup} sourceGroup The source group to create a ghost for
591
- * @returns {Konva.Rect} The ghost preview element
592
- */
593
- SourcesLayer.prototype._createDragGhost = function(sourceGroup) {
594
- var source = sourceGroup.getSource();
595
- var frameOffset = this._view.getFrameOffset();
596
- var x = this._view.timeToPixels(source.startTime) - frameOffset;
597
- var width = this._view.timeToPixels(source.endTime - source.startTime);
598
- var height = sourceGroup.getCurrentHeight();
599
- var y = sourceGroup.getAbsoluteY();
600
-
601
- var ghost = new Konva.Rect({
602
- x: x,
603
- y: y,
604
- width: width,
605
- height: height,
606
- fill: source.backgroundColor,
607
- opacity: 0.4,
608
- stroke: source.selectedBorderColor,
609
- strokeWidth: 2,
610
- dash: [8, 4],
611
- cornerRadius: 8,
612
- listening: false
613
- });
614
-
615
- this._layer.add(ghost);
616
- ghost.moveToBottom();
617
-
618
- return ghost;
619
- };
620
-
621
- /**
622
- * Updates the positions of all drag ghost previews.
623
- *
624
- * @private
625
- */
626
- SourcesLayer.prototype._updateDragGhosts = function() {
627
- if (!this._dragGhosts) {
628
- return;
629
- }
630
-
631
- var self = this;
632
- var frameOffset = this._view.getFrameOffset();
633
- var lineGroupsById = this._lineGroups.getLineGroupsById();
634
-
635
- this._dragGhosts.forEach(function(item) {
636
- var sourceGroup = self._sourcesGroup[item.sourceId];
637
-
638
- if (!sourceGroup || !item.ghost) {
639
- return;
640
- }
641
-
642
- // Use current source times (updated during drag)
643
- var sourceData = sourceGroup.getSource();
644
- var x = self._view.timeToPixels(sourceData.startTime) - frameOffset;
645
-
646
- item.ghost.x(x);
647
-
648
- // Update Y position based on line
649
- var lineGroup = lineGroupsById[sourceData.lineId];
650
-
651
- if (lineGroup) {
652
- item.ghost.y(lineGroup.y());
653
- }
654
- });
655
- };
656
-
657
575
  /**
658
576
  * Updates source times during drag using initial positions.
659
577
  *
@@ -745,7 +663,7 @@ define([
745
663
  this.refresh();
746
664
  };
747
665
 
748
- // TODO: This assumes that no sources between the start and the end are unselected
666
+ // WARNING: This assumes that no sources between the start and the end are unselected
749
667
  SourcesLayer.prototype.manageSourceMovements = function(sources, newStartTime, newEndTime, orderable, mouseX,
750
668
  mouseY
751
669
  ) {
@@ -815,17 +733,9 @@ define([
815
733
  // Get Y position from line group before moving
816
734
  var absoluteY = sourceGroup.getAbsoluteY();
817
735
 
818
- // Move to drag layer
819
- sourceGroup.moveTo(this._dragLayer);
736
+ // Move to drag group
737
+ sourceGroup.moveTo(this._dragGroup);
820
738
  sourceGroup.y(absoluteY);
821
-
822
- // Create ghost preview for this source
823
- var ghost = this._createDragGhost(sourceGroup);
824
-
825
- this._dragGhosts.push({
826
- ghost: ghost,
827
- sourceId: source.id
828
- });
829
739
  }
830
740
  }
831
741
 
@@ -849,7 +759,10 @@ define([
849
759
  if (Utils.objectHasProperty(this._sourcesGroup, sourceId)) {
850
760
  var sourceGroup = this._sourcesGroup[sourceId];
851
761
 
852
- if (!sourceGroup.isActive()) {
762
+ if (sourceGroup.isActive()) {
763
+ sourceGroup.update();
764
+ }
765
+ else {
853
766
  var source = this._sourcesGroup[sourceId].getSource();
854
767
 
855
768
  if (!this._isSourceVisible(source, startTime, endTime)) {
@@ -51,64 +51,102 @@ define(['../utils', 'konva'], function(Utils, Konva) {
51
51
 
52
52
  function WaveformShape(options) {
53
53
  const shape = new Konva.Shape({
54
- fill: options.source.color
54
+ fill: options.color,
55
+ listening: false
55
56
  });
56
57
 
57
58
  Object.assign(this, shape);
58
59
 
59
- this._layer = options.layer;
60
60
  this._view = options.view;
61
- this._source = options.source;
62
61
  this._height = options.height;
63
- this._url = options.url + '-scaled';
62
+ this._waveformDataFunc = options.waveformDataFunc;
64
63
 
65
- this.sceneFunc(this._sceneFunc);
64
+ this.sceneFunc(function(context) {
65
+ var waveformPoints = this._waveformDataFunc ? this._waveformDataFunc() : null;
66
66
 
67
- this.hitFunc(this._waveformShapeHitFunc);
67
+ this._sceneFunc(context, waveformPoints);
68
+ });
68
69
  }
69
70
 
70
71
  WaveformShape.prototype = Object.create(Konva.Shape.prototype);
71
72
 
72
- WaveformShape.prototype._sceneFunc = function(context) {
73
- var width = this._view.getWidth();
74
- var waveformData = this._layer.getLoadedData(this._url).data;
73
+ WaveformShape.prototype._sceneFunc = function(context, waveformPoints) {
74
+ if (!waveformPoints) {
75
+ return;
76
+ }
75
77
 
76
- var startPixel = 0, startOffset = 0, endPixel = width, targetSpeed = 1.0;
78
+ var xPoints = [];
79
+ var minByChannel = [];
80
+ var maxByChannel = [];
81
+ var channels = 0;
77
82
 
78
- if (this._source) {
79
- targetSpeed = this._source.targetSpeed || 1.0;
83
+ this._forEachWaveformPoint(waveformPoints, function(point) {
84
+ if (!point || !point.min || !point.max) {
85
+ return;
86
+ }
80
87
 
81
- startPixel = Math.floor(
82
- (this._view.timeToPixels(this._source.mediaStartTime) + Math.max(
83
- this._view.getFrameOffset() - this._view.timeToPixels(this._source.startTime),
84
- 0
85
- )) * targetSpeed
86
- );
88
+ if (channels === 0) {
89
+ channels = point.min.length;
90
+ for (var c = 0; c < channels; c++) {
91
+ minByChannel[c] = [];
92
+ maxByChannel[c] = [];
93
+ }
94
+ }
87
95
 
88
- startOffset = this._view.timeToPixels(this._source.mediaStartTime);
89
-
90
- endPixel = Math.min(
91
- Math.ceil(
92
- (this._view.timeToPixels(this._source.mediaEndTime) - Math.max(
93
- this._view.timeToPixels(this._source.endTime)
94
- - this._view.getFrameOffset()
95
- - this._view.getWidth(),
96
- 0
97
- )) * targetSpeed
98
- ),
99
- waveformData.length
100
- );
96
+ xPoints.push(point.x);
97
+
98
+ for (var i = 0; i < channels; i++) {
99
+ minByChannel[i].push(point.min[i]);
100
+ maxByChannel[i].push(point.max[i]);
101
+ }
102
+ });
103
+
104
+ if (channels === 0 || xPoints.length === 0) {
105
+ return;
106
+ }
107
+
108
+ this._drawWaveformFromPoints(context, xPoints, minByChannel, maxByChannel, channels, this._height);
109
+ };
110
+
111
+ WaveformShape.prototype._forEachWaveformPoint = function(waveformPoints, callback) {
112
+ if (!waveformPoints) {
113
+ return;
114
+ }
115
+
116
+ // Support iterators ({ next() }) without requiring Symbol.iterator.
117
+ if (typeof waveformPoints.next === 'function') {
118
+ while (true) {
119
+ var result = waveformPoints.next();
120
+
121
+ if (!result || result.done) {
122
+ break;
123
+ }
124
+ callback(result.value);
125
+ }
126
+ return;
127
+ }
128
+
129
+ // Support ES6 iterables if available.
130
+ if (typeof Symbol !== 'undefined' && waveformPoints[Symbol.iterator]) {
131
+ var iterator = waveformPoints[Symbol.iterator]();
132
+
133
+ while (true) {
134
+ var iterResult = iterator.next();
135
+
136
+ if (iterResult.done) {
137
+ break;
138
+ }
139
+ callback(iterResult.value);
140
+ }
141
+ return;
101
142
  }
102
143
 
103
- this._drawWaveform(
104
- context,
105
- waveformData,
106
- startPixel,
107
- startOffset,
108
- endPixel,
109
- targetSpeed,
110
- this._height
111
- );
144
+ // Fallback for arrays.
145
+ if (Array.isArray(waveformPoints)) {
146
+ for (var i = 0; i < waveformPoints.length; i++) {
147
+ callback(waveformPoints[i]);
148
+ }
149
+ }
112
150
  };
113
151
 
114
152
  /**
@@ -126,10 +164,14 @@ define(['../utils', 'konva'], function(Utils, Konva) {
126
164
  * @param {Number} height The height of the waveform area, in pixels.
127
165
  */
128
166
 
129
- WaveformShape.prototype._drawWaveform = function(context, waveformData,
130
- startPixel, startOffset, endPixel, targetSpeed, height) {
131
- var channels = waveformData.channels;
132
-
167
+ WaveformShape.prototype._drawWaveformFromPoints = function(
168
+ context,
169
+ xPoints,
170
+ minByChannel,
171
+ maxByChannel,
172
+ channels,
173
+ height
174
+ ) {
133
175
  var waveformTop = 0;
134
176
  var waveformHeight = Math.floor(height / channels);
135
177
 
@@ -138,81 +180,35 @@ define(['../utils', 'konva'], function(Utils, Konva) {
138
180
  waveformHeight = height - (channels - 1) * waveformHeight;
139
181
  }
140
182
 
141
- this._drawChannel(
183
+ this._drawChannelFromPoints(
142
184
  context,
143
- waveformData.channel(i),
144
- startPixel,
145
- startOffset,
146
- endPixel,
185
+ xPoints,
186
+ minByChannel[i],
187
+ maxByChannel[i],
147
188
  waveformTop,
148
- waveformHeight,
149
- targetSpeed
189
+ waveformHeight
150
190
  );
151
191
 
152
192
  waveformTop += waveformHeight;
153
193
  }
154
194
  };
155
195
 
156
- WaveformShape.prototype._drawChannel = function(context, channel,
157
- startPixel, startOffset, endPixel, top, height, targetSpeed) {
158
- var x, val;
159
-
196
+ WaveformShape.prototype._drawChannelFromPoints = function(context, xPoints, minValues, maxValues, top, height) {
160
197
  var amplitudeScale = this._view.getAmplitudeScale();
161
198
 
162
199
  context.beginPath();
163
200
 
164
- for (x = startPixel; x < endPixel; x++) {
165
- val = channel.min_sample(x);
166
-
167
- context.lineTo(x / targetSpeed - startOffset + 0.5, top + scaleY(val, height, amplitudeScale) + 0.5);
201
+ for (var i = 0; i < xPoints.length; i++) {
202
+ context.lineTo(xPoints[i], top + scaleY(minValues[i], height, amplitudeScale) + 0.5);
168
203
  }
169
204
 
170
- for (x = endPixel - 1; x >= startPixel; x--) {
171
- val = channel.max_sample(x);
172
-
173
- context.lineTo(x / targetSpeed - startOffset + 0.5, top + scaleY(val, height, amplitudeScale) + 0.5);
205
+ for (var j = xPoints.length - 1; j >= 0; j--) {
206
+ context.lineTo(xPoints[j], top + scaleY(maxValues[j], height, amplitudeScale) + 0.5);
174
207
  }
175
208
 
176
209
  context.closePath();
177
-
178
210
  context.fillShape(this);
179
211
  };
180
212
 
181
- WaveformShape.prototype._waveformShapeHitFunc = function(context) {
182
- if (!this._source) {
183
- return;
184
- }
185
-
186
- var frameOffset = this._view.getFrameOffset();
187
- var viewWidth = this._view.getWidth();
188
-
189
- var startPixels = this._view.timeToPixels(this._source.startTime);
190
- var endPixels = this._view.timeToPixels(this._source.endTime);
191
-
192
- var offsetY = 10;
193
- var hitRectHeight = this._height;
194
-
195
- if (hitRectHeight < 0) {
196
- hitRectHeight = 0;
197
- }
198
-
199
- var hitRectLeft = startPixels - frameOffset;
200
- var hitRectWidth = endPixels - startPixels;
201
-
202
- if (hitRectLeft < 0) {
203
- hitRectWidth -= -hitRectLeft;
204
- hitRectLeft = 0;
205
- }
206
-
207
- if (hitRectLeft + hitRectWidth > viewWidth) {
208
- hitRectWidth -= hitRectLeft + hitRectWidth - viewWidth;
209
- }
210
-
211
- context.beginPath();
212
- context.rect(hitRectLeft, offsetY, hitRectWidth, hitRectHeight);
213
- context.closePath();
214
- context.fillStrokeShape(this);
215
- };
216
-
217
213
  return WaveformShape;
218
214
  });