@checksub_team/peaks_timeline 2.4.0 → 2.5.0-alpha.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.
@@ -69,12 +69,15 @@ define([
69
69
  // Used to suppress drag lifecycle callbacks when a dragged source is
70
70
  // temporarily stopped/restarted as part of a rebuild.
71
71
  this._suppressDragLifecycleForSourceId = null;
72
+ this._sourceHandleDragSession = null;
73
+ this._segmentDragSession = null;
72
74
 
73
75
  this._peaks.on('handler.sources.add', this._onSourcesAdd.bind(this));
74
76
  this._peaks.on('handler.sources.destroy', this._onSourcesDestroy.bind(this));
75
77
  this._peaks.on('handler.sources.show', this._onSourcesShow.bind(this));
76
78
  this._peaks.on('handler.sources.hide', this._onSourcesHide.bind(this));
77
79
  this._peaks.on('sources.setSelected', this._onSourcesSetSelected.bind(this));
80
+ this._peaks.on('segments.setSelected', this._onSegmentsSetSelected.bind(this));
78
81
  this._peaks.on('model.source.update', this._onSourceUpdate.bind(this));
79
82
  this._peaks.on('data.retrieved', this._onDataRetrieved.bind(this));
80
83
  this._peaks.on('handler.segments.show', this._onSegmentsShow.bind(this));
@@ -96,6 +99,10 @@ define([
96
99
  if (draggedSourceGroup) {
97
100
  draggedSourceGroup.stopDrag();
98
101
  }
102
+
103
+ if (this._segmentDragSession && this._segmentDragSession.draggedNode) {
104
+ this._segmentDragSession.draggedNode.stopDrag();
105
+ }
99
106
  };
100
107
 
101
108
  SourcesLayer.prototype.fitToView = function() {
@@ -114,6 +121,17 @@ define([
114
121
  return this._lineGroups.getSegmentsGroups();
115
122
  };
116
123
 
124
+ /**
125
+ * Returns all segments in the given segment group or displayed line whose start time is at or after the given time.
126
+ *
127
+ * @param {String|Number} lineId
128
+ * @param {Number} time
129
+ * @returns {Array<Segment>}
130
+ */
131
+ SourcesLayer.prototype.getSegmentsOnLineAfter = function(lineId, time) {
132
+ return this._lineGroups.getSegmentsOnLineAfter(lineId, time);
133
+ };
134
+
117
135
  SourcesLayer.prototype.add = function(element) {
118
136
  this._layer.add(element);
119
137
 
@@ -253,13 +271,29 @@ define([
253
271
  };
254
272
 
255
273
  SourcesLayer.prototype._onSourcesSetSelected = function(sources) {
274
+ var sourcesGroups = this._lineGroups.getSourcesGroups();
275
+
256
276
  sources.forEach(function(source) {
257
- const sourceGroup = this._sourcesGroup[source.id];
277
+ const sourceGroup = sourcesGroups[source.id];
258
278
 
259
279
  if (sourceGroup) {
260
280
  sourceGroup.setSelected();
261
281
  }
262
- }.bind(this));
282
+ });
283
+
284
+ this.batchDraw();
285
+ };
286
+
287
+ SourcesLayer.prototype._onSegmentsSetSelected = function(segments) {
288
+ var segmentsGroups = this._lineGroups.getSegmentsGroups();
289
+
290
+ segments.forEach(function(segment) {
291
+ var segmentsGroup = segmentsGroups[segment.line];
292
+
293
+ if (segmentsGroup) {
294
+ segmentsGroup.setSelected(segment);
295
+ }
296
+ });
263
297
 
264
298
  this.batchDraw();
265
299
  };
@@ -441,6 +475,341 @@ define([
441
475
  this._draggedElementsData = null;
442
476
  };
443
477
 
478
+ SourcesLayer.prototype.cleanupAfterSourceHandleDrag = function() {
479
+ this._sourceHandleDragSession = null;
480
+ };
481
+
482
+ SourcesLayer.prototype.onSourceHandleDragStart = function(sourceGroup, leftHandle) {
483
+ var source = sourceGroup.getSource();
484
+
485
+ this._sourceHandleDragSession = {
486
+ source: source,
487
+ leftHandle: leftHandle,
488
+ mouseDownX: this._view.getPointerPosition().x,
489
+ initialTimeOffset: this._view.getTimeOffset(),
490
+ initialStartTime: source.startTime,
491
+ initialEndTime: source.endTime
492
+ };
493
+ };
494
+
495
+ SourcesLayer.prototype.onSourceHandleDrag = function(draggedElement) {
496
+ this._view.updateWithAutoScroll(this._dragSourceHandle.bind(this), null, false);
497
+
498
+ return {
499
+ x: draggedElement.absolutePosition().x,
500
+ y: draggedElement.absolutePosition().y
501
+ };
502
+ };
503
+
504
+ SourcesLayer.prototype._dragSourceHandle = function() {
505
+ var session = this._sourceHandleDragSession;
506
+ var pointer = this._view.getPointerPosition();
507
+ var pointerX;
508
+ var diff;
509
+ var timeOffsetDiff;
510
+ var shouldRedraw;
511
+
512
+ if (!session) {
513
+ return;
514
+ }
515
+
516
+ pointerX = pointer ? pointer.x : session.mouseDownX;
517
+ diff = this._view.pixelsToTime(pointerX - session.mouseDownX);
518
+ timeOffsetDiff = this._view.getTimeOffset() - session.initialTimeOffset;
519
+ shouldRedraw = this.manageSourceMovements(
520
+ [session.source],
521
+ session.leftHandle ? session.initialStartTime + diff + timeOffsetDiff : null,
522
+ session.leftHandle ? null : session.initialEndTime + diff + timeOffsetDiff
523
+ );
524
+
525
+ if (shouldRedraw) {
526
+ this.batchDraw();
527
+ }
528
+ };
529
+
530
+ SourcesLayer.prototype.onSourceHandleDragEnd = function(sourceGroup) {
531
+ this.cleanupAfterSourceHandleDrag();
532
+ this.processSourceUpdates([sourceGroup.getSource()]);
533
+ };
534
+
535
+ SourcesLayer.prototype.isSegmentDragInProgress = function() {
536
+ return Boolean(this._segmentDragSession);
537
+ };
538
+
539
+ SourcesLayer.prototype.cleanupAfterSegmentDrag = function() {
540
+ this._segmentDragSession = null;
541
+ };
542
+
543
+ SourcesLayer.prototype._getSelectedSegmentsForDrag = function(draggedSegment) {
544
+ var selectedElements = this._view.getSelectedElements();
545
+ var selectedSegments;
546
+ var draggedSegmentIsSelected;
547
+
548
+ if (!Array.isArray(selectedElements)) {
549
+ selectedElements = Object.values(selectedElements);
550
+ }
551
+
552
+ selectedSegments = selectedElements.filter(function(element) {
553
+ return !Utils.isNullOrUndefined(element.line) && Utils.isNullOrUndefined(element.lineId);
554
+ });
555
+
556
+ draggedSegmentIsSelected = selectedSegments.some(function(segment) {
557
+ return segment.id === draggedSegment.id;
558
+ });
559
+
560
+ if (!draggedSegmentIsSelected) {
561
+ selectedSegments = [draggedSegment];
562
+ this._view.deselectAll();
563
+ }
564
+
565
+ return selectedSegments;
566
+ };
567
+
568
+ SourcesLayer.prototype._buildSegmentDragGroups = function(segments) {
569
+ var segmentsGroups = this._lineGroups.getSegmentsGroups();
570
+ var groupsByLine = {};
571
+
572
+ segments.forEach(function(segment) {
573
+ var groupContext = groupsByLine[segment.line];
574
+
575
+ if (!groupContext) {
576
+ groupContext = {
577
+ group: segmentsGroups[segment.line],
578
+ segments: [],
579
+ initialPositions: {}
580
+ };
581
+ groupsByLine[segment.line] = groupContext;
582
+ }
583
+
584
+ groupContext.segments.push(segment);
585
+ groupContext.initialPositions[segment.id] = {
586
+ startTime: segment.startTime,
587
+ endTime: segment.endTime
588
+ };
589
+ });
590
+
591
+ return Object.keys(groupsByLine).map(function(lineId) {
592
+ groupsByLine[lineId].segments.sort(function(a, b) {
593
+ return a.startTime - b.startTime;
594
+ });
595
+
596
+ return groupsByLine[lineId];
597
+ });
598
+ };
599
+
600
+ SourcesLayer.prototype.onSegmentDragStart = function(segmentShape) {
601
+ var segment = segmentShape.getSegment();
602
+ var selectedSegments = this._getSelectedSegmentsForDrag(segment);
603
+ var groups = this._buildSegmentDragGroups(selectedSegments);
604
+ var minTimeOffset = -Infinity;
605
+ var maxTimeOffset = Infinity;
606
+ var draggable = true;
607
+ var shouldClampToAdjacentGroups = !(groups.length === 1 && groups[0].segments.length === 1);
608
+
609
+ groups.forEach(function(groupContext) {
610
+ if (shouldClampToAdjacentGroups) {
611
+ var limits = groupContext.group.getSegmentsDragTimeLimits(
612
+ groupContext.segments,
613
+ groupContext.initialPositions
614
+ );
615
+
616
+ minTimeOffset = Math.max(minTimeOffset, limits.minTimeOffset);
617
+ maxTimeOffset = Math.min(maxTimeOffset, limits.maxTimeOffset);
618
+ }
619
+
620
+ draggable = draggable && groupContext.segments.every(function(draggedSegment) {
621
+ return draggedSegment.editable;
622
+ });
623
+ });
624
+
625
+ this._segmentDragSession = {
626
+ kind: 'move',
627
+ draggedNode: segmentShape,
628
+ draggedSegment: segment,
629
+ mouseDownX: this._view.getPointerPosition().x,
630
+ initialTimeOffset: this._view.getTimeOffset(),
631
+ groups: groups,
632
+ minTimeOffset: minTimeOffset,
633
+ maxTimeOffset: maxTimeOffset,
634
+ appliedTimeOffset: 0,
635
+ draggable: draggable
636
+ };
637
+
638
+ this._peaks.emit('segments.dragstart', segment);
639
+ };
640
+
641
+ SourcesLayer.prototype.onSegmentDrag = function(draggedElement) {
642
+ this._view.updateWithAutoScroll(this._dragSegments.bind(this), null, false);
643
+
644
+ return {
645
+ x: draggedElement.absolutePosition().x,
646
+ y: draggedElement.absolutePosition().y
647
+ };
648
+ };
649
+
650
+ SourcesLayer.prototype._dragSegments = function() {
651
+ var session = this._segmentDragSession;
652
+ var pointerPos;
653
+ var mousePos;
654
+ var timeDiff;
655
+ var timeOffsetDiff;
656
+ var requestedTimeOffset;
657
+ var appliedTimeOffset;
658
+ var groupContext;
659
+ var segment;
660
+ var initialPosition;
661
+
662
+ if (!session || session.kind !== 'move' || !session.draggable) {
663
+ return;
664
+ }
665
+
666
+ pointerPos = this._view.getPointerPosition();
667
+ mousePos = Math.min(
668
+ this._view.getWidth() - this._peaks.options.autoScrollThreshold * this._view.getWidth(),
669
+ Math.max(0, pointerPos ? pointerPos.x : session.mouseDownX)
670
+ );
671
+ timeDiff = this._view.pixelsToTime(mousePos - session.mouseDownX);
672
+ timeOffsetDiff = this._view.getTimeOffset() - session.initialTimeOffset;
673
+ requestedTimeOffset = Utils.roundTime(timeDiff + timeOffsetDiff);
674
+ appliedTimeOffset = Utils.clamp(
675
+ requestedTimeOffset,
676
+ session.minTimeOffset,
677
+ session.maxTimeOffset
678
+ );
679
+
680
+ if (appliedTimeOffset === session.appliedTimeOffset) {
681
+ return;
682
+ }
683
+
684
+ if (session.groups.length === 1 && session.groups[0].segments.length === 1) {
685
+ groupContext = session.groups[0];
686
+ segment = groupContext.segments[0];
687
+ initialPosition = groupContext.initialPositions[segment.id];
688
+
689
+ groupContext.group.updateSegment(
690
+ segment,
691
+ this._view.timeToPixels(initialPosition.startTime + appliedTimeOffset),
692
+ this._view.timeToPixels(initialPosition.endTime + appliedTimeOffset),
693
+ false
694
+ );
695
+ }
696
+ else {
697
+ session.groups.forEach(function(groupContext) {
698
+ groupContext.group.dragSegmentsByTimeOffset(
699
+ groupContext.segments,
700
+ groupContext.initialPositions,
701
+ appliedTimeOffset
702
+ );
703
+ });
704
+ }
705
+
706
+ session.appliedTimeOffset = appliedTimeOffset;
707
+ this._view.updateTimelineLength();
708
+ this.batchDraw();
709
+ };
710
+
711
+ SourcesLayer.prototype.onSegmentDragEnd = function(segmentShape) {
712
+ var session = this._segmentDragSession;
713
+
714
+ if (session && session.kind === 'move' && session.appliedTimeOffset !== 0) {
715
+ session.groups.forEach(function(groupContext) {
716
+ groupContext.group.markSegmentsUpdated(groupContext.segments);
717
+ });
718
+ this._view.updateTimelineLength();
719
+ }
720
+
721
+ this.cleanupAfterSegmentDrag();
722
+ this._peaks.emit('segments.dragend', segmentShape.getSegment());
723
+ };
724
+
725
+ SourcesLayer.prototype.onSegmentHandleDragStart = function(segmentMarker) {
726
+ this._segmentDragSession = {
727
+ kind: segmentMarker.isStartMarker() ? 'resize-start' : 'resize-end',
728
+ draggedNode: segmentMarker,
729
+ segment: segmentMarker.getSegment(),
730
+ group: this._lineGroups.getSegmentsGroups()[segmentMarker.getSegment().line],
731
+ mouseDownX: this._view.getPointerPosition().x,
732
+ initialTimeOffset: this._view.getTimeOffset(),
733
+ initialStartTime: segmentMarker.getSegment().startTime,
734
+ initialEndTime: segmentMarker.getSegment().endTime,
735
+ startMarker: segmentMarker.isStartMarker()
736
+ };
737
+
738
+ this._peaks.emit('segments.dragstart', segmentMarker.getSegment(), segmentMarker.isStartMarker());
739
+ };
740
+
741
+ SourcesLayer.prototype.onSegmentHandleDrag = function(segmentMarker) {
742
+ this._view.updateWithAutoScroll(this._dragSegmentHandle.bind(this), null, false);
743
+
744
+ return {
745
+ x: segmentMarker.getAbsolutePosition().x,
746
+ y: segmentMarker.getAbsolutePosition().y
747
+ };
748
+ };
749
+
750
+ SourcesLayer.prototype._dragSegmentHandle = function() {
751
+ var session = this._segmentDragSession;
752
+ var pointerPos;
753
+ var pointerX;
754
+ var timeDiff;
755
+ var timeOffsetDiff;
756
+ var newStartTime;
757
+ var newEndTime;
758
+
759
+ if (!session || (session.kind !== 'resize-start' && session.kind !== 'resize-end')) {
760
+ return;
761
+ }
762
+
763
+ pointerPos = this._view.getPointerPosition();
764
+ pointerX = pointerPos ? pointerPos.x : session.mouseDownX;
765
+ timeDiff = this._view.pixelsToTime(pointerX - session.mouseDownX);
766
+ timeOffsetDiff = this._view.getTimeOffset() - session.initialTimeOffset;
767
+
768
+ if (session.startMarker) {
769
+ newStartTime = Utils.roundTime(session.initialStartTime + timeDiff + timeOffsetDiff);
770
+
771
+ if (session.segment.duration) {
772
+ newStartTime = Math.max(
773
+ newStartTime,
774
+ Utils.roundTime(session.segment.endTime - session.segment.duration)
775
+ );
776
+ }
777
+
778
+ session.group.updateSegment(
779
+ session.segment,
780
+ this._view.timeToPixels(newStartTime),
781
+ null,
782
+ false
783
+ );
784
+ }
785
+ else {
786
+ newEndTime = Utils.roundTime(session.initialEndTime + timeDiff + timeOffsetDiff);
787
+
788
+ if (session.segment.duration) {
789
+ newEndTime = Math.min(
790
+ newEndTime,
791
+ Utils.roundTime(session.segment.startTime + session.segment.duration)
792
+ );
793
+ }
794
+
795
+ session.group.updateSegment(
796
+ session.segment,
797
+ null,
798
+ this._view.timeToPixels(newEndTime),
799
+ false
800
+ );
801
+ }
802
+
803
+ this._view.updateTimelineLength();
804
+ this.batchDraw();
805
+ };
806
+
807
+ SourcesLayer.prototype.onSegmentHandleDragEnd = function(segmentMarker) {
808
+ this._view.updateTimelineLength();
809
+ this.cleanupAfterSegmentDrag();
810
+ this._peaks.emit('segments.dragend', segmentMarker.getSegment(), segmentMarker.isStartMarker());
811
+ };
812
+
444
813
  SourcesLayer.prototype.onSourcesGroupDragStart = function(element) {
445
814
  if (this._suppressDragLifecycleForSourceId
446
815
  && element
package/src/main.js CHANGED
@@ -791,16 +791,48 @@ define([
791
791
  .getSourceGroupById(id);
792
792
  };
793
793
 
794
+ /**
795
+ * Selects a source by its identifier.
796
+ *
797
+ * @param {String} sourceId
798
+ */
794
799
  Peaks.prototype.selectSourceById = function(sourceId) {
795
800
  return this.view
796
801
  .selectSourceById(sourceId);
797
802
  };
798
803
 
804
+ /**
805
+ * Selects all sources on a line whose start time is at or after the given time.
806
+ *
807
+ * @param {String} lineId
808
+ * @param {Number} time
809
+ */
799
810
  Peaks.prototype.selectSourcesOnLineAfter = function(lineId, time) {
800
811
  return this.view
801
812
  .selectSourcesOnLineAfter(lineId, time);
802
813
  };
803
814
 
815
+ /**
816
+ * Selects a segment by its identifier.
817
+ *
818
+ * @param {String} segmentId
819
+ */
820
+ Peaks.prototype.selectSegmentById = function(segmentId) {
821
+ return this.view
822
+ .selectSegmentById(segmentId);
823
+ };
824
+
825
+ /**
826
+ * Selects all segments in the given segment group or displayed line whose start time is at or after the given time.
827
+ *
828
+ * @param {String|Number} lineId
829
+ * @param {Number} time
830
+ */
831
+ Peaks.prototype.selectSegmentsOnLineAfter = function(lineId, time) {
832
+ return this.view
833
+ .selectSegmentsOnLineAfter(lineId, time);
834
+ };
835
+
804
836
  Peaks.prototype.deselectAll = function(notify) {
805
837
  return this.view
806
838
  .deselectAll(notify);
@@ -352,9 +352,23 @@ define([
352
352
  this._peaks.emit('model.segment.updated', this);
353
353
  };
354
354
 
355
- Segment.prototype.setSelected = function(selected) {
356
- this._selected = selected;
357
- this._peaks.emit('model.segment.selected', this);
355
+ /**
356
+ * Updates segment times without emitting a model update event.
357
+ *
358
+ * This is used by controller-owned drag flows that refresh the visible slice
359
+ * explicitly and emit their consolidated updates on drag end.
360
+ *
361
+ * @param {Number} [newStartTime]
362
+ * @param {Number} [newEndTime]
363
+ */
364
+ Segment.prototype.updateTimes = function(newStartTime, newEndTime) {
365
+ if (!Utils.isNullOrUndefined(newStartTime)) {
366
+ this._startTime = Utils.roundTime(newStartTime);
367
+ }
368
+
369
+ if (!Utils.isNullOrUndefined(newEndTime)) {
370
+ this._endTime = Utils.roundTime(newEndTime);
371
+ }
358
372
  };
359
373
 
360
374
  /**
package/src/view.js CHANGED
@@ -525,19 +525,57 @@ define([
525
525
  return this._sourcesLayer.getSourceGroupById(sourceId);
526
526
  };
527
527
 
528
+ /**
529
+ * Selects a source by its identifier.
530
+ *
531
+ * @param {String} sourceId
532
+ */
528
533
  View.prototype.selectSourceById = function(sourceId) {
529
534
  const sourceGroup = this._sourcesLayer.getSourceGroupById(sourceId);
530
535
 
531
536
  if (sourceGroup) {
532
- this._modeLayer.selectElements([sourceGroup.getSource()], false);
537
+ this._modeLayer.selectElements([sourceGroup.getSource()], true);
533
538
  }
534
539
  };
535
540
 
541
+ /**
542
+ * Selects all sources on a line whose start time is at or after the given time.
543
+ *
544
+ * @param {String} lineId
545
+ * @param {Number} time
546
+ */
536
547
  View.prototype.selectSourcesOnLineAfter = function(lineId, time) {
537
548
  const sources = this._sourcesLayer.getSourcesOnLineAfter(lineId, time);
538
549
 
539
- if (sources) {
540
- this._modeLayer.selectElements(sources, false);
550
+ if (sources.length) {
551
+ this._modeLayer.selectElements(sources, true);
552
+ }
553
+ };
554
+
555
+ /**
556
+ * Selects a segment by its identifier.
557
+ *
558
+ * @param {String} segmentId
559
+ */
560
+ View.prototype.selectSegmentById = function(segmentId) {
561
+ const segment = this._peaks.segmentHandler.getSegment(segmentId);
562
+
563
+ if (segment) {
564
+ this._modeLayer.selectElements([segment], true);
565
+ }
566
+ };
567
+
568
+ /**
569
+ * Selects all segments in the given segment group or displayed line whose start time is at or after the given time.
570
+ *
571
+ * @param {String|Number} lineId
572
+ * @param {Number} time
573
+ */
574
+ View.prototype.selectSegmentsOnLineAfter = function(lineId, time) {
575
+ const segments = this._sourcesLayer.getSegmentsOnLineAfter(lineId, time);
576
+
577
+ if (segments.length) {
578
+ this._modeLayer.selectElements(segments, true);
541
579
  }
542
580
  };
543
581