UNPKG

@checksub_team/peaks_timeline

Version:

JavaScript UI component for displaying audio waveforms

509 lines (414 loc) 15.4 kB
/** * @file * * Defines the {@link SegmentShape} class. * * @module segment-shape */ define([ 'konva', './segment-marker', './utils' ], function( Konva, SegmentMarker, Utils) { 'use strict'; var SEGMENT_WIDTH = 10; var SEGMENT_CORNER_RADIUS = 10; var INDICATOR_RADIUS = 4; // px var INDICATORS_MARGIN_RIGHT = 4; // px var INDICATORS_MARGIN_TOP = 8; // px /** * Creates a waveform segment shape with optional start and end markers. * * @class * @alias SegmentShape * * @param {Segment} segment * @param {Peaks} peaks * @param {SegmentsGroup} group * @param {WaveformOverview|WaveformZoomView} view */ function SegmentShape(segment, peaks, group, view) { this._segment = segment; this._peaks = peaks; this._group = group; this._view = view; // this._waveformShape = null; this._segmentsGroup = null; this._label = null; this._startMarker = null; this._endMarker = null; this._shapeGroup = null; this._rectangle = null; this._indicators = {}; var self = this; this._segmentWidth = this._view.timeToPixels(this._segment.endTime - this._segment.startTime); this._segmentHeight = this._peaks.options.segmentHeight; this._shapeGroup = new Konva.Group({ x: this._view.timeToPixels(this._segment.startTime), y: 0, width: this._segmentWidth, height: this._segmentHeight, draggable: this._segment.editable, clipFunc: function(ctx) { self._drawRect(ctx); } }); var width = this._view.timeToPixels(this._segment.endTime - this._segment.startTime); var fillColor = segment.color + Math.round(segment.opacity * 255).toString(16); this._rectangle = new Konva.Rect({ width: width, height: this._segmentHeight, cornerRadius: this._cornerRadius(), stroke: segment.borderColor || segment.textColor + 'FF', strokeWidth: segment.borderWidth !== undefined && segment.borderWidth !== null ? segment.borderWidth : 2, fillPriority: segment.shouldShowWarning() ? 'linear-gradient' : 'color', fill: fillColor, fillLinearGradientColorStops: [ 0, fillColor, 0.65, segment.warningColor ], fillLinearGradientStartPointX: Math.max(width - 20, width / 2), fillLinearGradientEndPointX: width }); this._shapeGroup.add(this._rectangle); this._shapeGroup.dragBoundFunc(function() { return self._onShapeGroupDrag(this); }); // Set up event handlers to show/hide the segment label text when the user // hovers the mouse over the segment. this._shapeGroup.on('mouseenter', this._onMouseEnter.bind(this)); this._shapeGroup.on('mouseleave', this._onMouseLeave.bind(this)); this._shapeGroup.on('click', this._onClick.bind(this)); this._shapeGroup.on('dragstart', this._onSegmentDragStart.bind(this)); this._shapeGroup.on('dragend', this._onSegmentDragEnd.bind(this)); // Event handlers for markers this._onSegmentHandleDrag = this._onSegmentHandleDrag.bind(this); this._onSegmentHandleDragStart = this._onSegmentHandleDragStart.bind(this); this._onSegmentHandleDragEnd = this._onSegmentHandleDragEnd.bind(this); this._label = this._peaks.options.createSegmentLabel({ x: this._view.timeToPixels(segment.startTime), y: this._segmentY, width: this._view.timeToPixels(this._segment.endTime - this._segment.startTime), height: this._segmentHeight, segment: segment, view: this._view.getName(), group: this._group, fontSize: 12 }); this._createMarkers(); this._indicatorsGroup = new Konva.Group(); this._shapeGroup.add(this._indicatorsGroup); this.createIndicators(); } SegmentShape.prototype._onShapeGroupDrag = function(draggedElement) { const diff = this._view.getPointerPosition().x - this._mouseDownX; this._group.updateSegment( this._segment, this._initialStartPixel + diff, this._initialEndPixel + diff ); this._startMarker.timeUpdated(this._segment.startTime); this._endMarker.timeUpdated(this._segment.endTime); return { x: draggedElement.absolutePosition().x, y: draggedElement.absolutePosition().y }; }; SegmentShape.prototype._cornerRadius = function() { return this._segment.borderRadius !== undefined && this._segment.borderRadius !== null ? this._segment.borderRadius : SEGMENT_CORNER_RADIUS; }; SegmentShape.prototype.update = function() { var startPixel = this._view.timeToPixels(this._segment.startTime); var endPixel = this._view.timeToPixels(this._segment.endTime); var frameOffset = this._view.timeToPixels(this._view.getTimeOffset()); this._shapeGroup.x(startPixel - frameOffset); this._segmentWidth = endPixel - startPixel; this._shapeGroup.width(this._segmentWidth); this._rectangle.width(this._segmentWidth); if (this._segment.shouldShowWarning()) { this._rectangle.fillPriority('linear-gradient'); this._rectangle.fillLinearGradientStartPointX( Math.max(this._segmentWidth - 20, this._segmentWidth / 2) ); this._rectangle.fillLinearGradientEndPointX(this._segmentWidth); } else { this._rectangle.fillPriority('color'); } this._indicatorsGroup.x(this._segmentWidth); var newWidth = Math.floor(this._segmentWidth / 2); if (this._segmentWidth < this._startMarker.getHandleWidth() + this._endMarker.getHandleWidth()) { this._startMarker.setHandleWidth(newWidth); this._endMarker.setHandleWidth(newWidth); } else if (this._startMarker.getHandleWidth() < SEGMENT_WIDTH && newWidth > this._startMarker.getHandleWidth()) { this._startMarker.setHandleWidth(Math.min(newWidth, SEGMENT_WIDTH)); this._endMarker.setHandleWidth(Math.min(newWidth, SEGMENT_WIDTH)); } if (this._startMarker) { this._startMarker.setX(startPixel - frameOffset); this._startMarker.timeUpdated(this._segment.startTime); } if (this._endMarker) { this._endMarker.setX(endPixel - this._endMarker.getHandleWidth() - frameOffset); this._endMarker.timeUpdated(this._segment.endTime); } if (this._label) { this._label.setX(startPixel - frameOffset); this._label.setWidth(endPixel - startPixel); } }; SegmentShape.prototype.getSegmentHeight = function() { return this._segmentHeight; }; SegmentShape.prototype.getText = function() { return this._label; }; SegmentShape.prototype.getSegment = function() { return this._segment; }; SegmentShape.prototype.getSegmentsGroup = function() { return this._segmentsGroup; }; SegmentShape.prototype.getStartMarker = function() { return this._startMarker; }; SegmentShape.prototype.getEndMarker = function() { return this._endMarker; }; SegmentShape.prototype.addToGroup = function(group, segmentsGroup) { if (segmentsGroup) { this._segmentsGroup = segmentsGroup; } group.add(this._shapeGroup); if (this._label) { group.add(this._label); } if (this._startMarker) { this._startMarker.addToGroup(group); } if (this._endMarker) { this._endMarker.addToGroup(group); } }; SegmentShape.prototype._createMarkers = function() { var editable = this._group.isEditingEnabled() && this._segment.editable; var startMarker = this._peaks.options.createSegmentMarker({ peaks: this._peaks, segment: this._segment, draggable: editable, startMarker: true, group: this._group, view: this._view, showSegmentMarkers: this._peaks.options.showSegmentMarkers, segmentHeight: this._segmentHeight, segmentWidth: SEGMENT_WIDTH, y: this._segmentY }); if (startMarker) { this._startMarker = new SegmentMarker({ segment: this._segment, segmentShape: this, draggable: editable, startMarker: true, marker: startMarker, onDrag: this._onSegmentHandleDrag, onDragStart: this._onSegmentHandleDragStart, onDragEnd: this._onSegmentHandleDragEnd, view: this._view }); } var endMarker = this._peaks.options.createSegmentMarker({ peaks: this._peaks, segment: this._segment, draggable: editable, startMarker: false, group: this._group, view: this._view, showSegmentMarkers: this._peaks.options.showSegmentMarkers, segmentHeight: this._segmentHeight, segmentWidth: SEGMENT_WIDTH, y: this._segmentY }); if (endMarker) { this._endMarker = new SegmentMarker({ segment: this._segment, segmentShape: this, draggable: editable, startMarker: false, marker: endMarker, onDrag: this._onSegmentHandleDrag, onDragStart: this._onSegmentHandleDragStart, onDragEnd: this._onSegmentHandleDragEnd, view: this._view }); } }; SegmentShape.prototype.createIndicators = function() { var newIndicatorsColors = this._segment.indicators; var oldIndicators = this._indicators; var newIndicators = {}; if (newIndicatorsColors) { newIndicatorsColors.forEach(function(indicatorColor) { var oldIndicator = oldIndicators[indicatorColor]; if (oldIndicator) { newIndicators[indicatorColor] = oldIndicator; delete oldIndicators[indicatorColor]; } else { newIndicators[indicatorColor] = null; } }); for (var color in oldIndicators) { if (Utils.objectHasProperty(oldIndicators, color)) { oldIndicators[color].destroy(); } } } this._indicators = Object.keys(newIndicators) .sort() .reverse() .reduce(function(objEntries, key) { objEntries[key] = newIndicators[key]; return objEntries; }, {} ); this._createIndicators(); }; SegmentShape.prototype._createIndicators = function() { var currentX = 0; var zIndex = 0; for (var color in this._indicators) { if (Utils.objectHasProperty(this._indicators, color)) { if (!this._indicators[color]) { this._indicators[color] = new Konva.Circle({ radius: INDICATOR_RADIUS, fill: color, strokeEnabled: false }); this._indicatorsGroup.add(this._indicators[color]); } this._indicators[color].x(currentX); this._indicators[color].zIndex(zIndex); currentX += INDICATOR_RADIUS; zIndex += 1; } } this._indicatorsGroup.offsetX(currentX + INDICATORS_MARGIN_RIGHT); this._indicatorsGroup.offsetY(-INDICATORS_MARGIN_TOP); }; SegmentShape.prototype._onMouseEnter = function() { this._view.setCursor('pointer'); this._view.setHoveredElement(this); var fillColor = this._segment.hoverColor + Math.round( this._segment.opacity * 255 ).toString(16); this._rectangle.fill(fillColor); this._rectangle.fillLinearGradientColorStops([ 0, fillColor, 0.65, this._segment.warningColor ]); this._view.drawSourcesLayer(); this._peaks.emit('segments.mouseenter', this._segment); }; SegmentShape.prototype._onMouseLeave = function() { this._view.setCursor('default'); this._view.setHoveredElement(null); var fillColor = this._segment.color + Math.round( this._segment.opacity * 255 ).toString(16); this._rectangle.fill(fillColor); this._rectangle.fillLinearGradientColorStops([ 0, fillColor, 0.65, this._segment.warningColor ]); this._view.drawSourcesLayer(); this._peaks.emit('segments.mouseleave', this._segment); }; SegmentShape.prototype._onClick = function() { this._peaks.emit('segments.click', this._segment); }; SegmentShape.prototype._onSegmentDragStart = function() { this._view.setCursor('grab'); this._mouseDownX = this._view.getPointerPosition().x; this._initialStartTime = this._segment.startTime; this._initialStartPixel = this._view.timeToPixels(this._initialStartTime); this._initialEndTime = this._segment.endTime; this._initialEndPixel = this._view.timeToPixels(this._initialEndTime); this._peaks.emit('segments.dragstart', this._segment); }; SegmentShape.prototype._onSegmentDragEnd = function() { this._view.setCursor('pointer'); this._peaks.emit('segments.dragend', this._segment); }; /** * @param {SegmentMarker} segmentMarker */ SegmentShape.prototype._onSegmentHandleDrag = function() { this._peaks.emit('segments.dragged'); }; /** * @param {SegmentMarker} segmentMarker */ SegmentShape.prototype._onSegmentHandleDragStart = function(segmentMarker) { var startMarker = segmentMarker.isStartMarker(); this._peaks.emit('segments.dragstart', this._segment, startMarker); }; /** * @param {SegmentMarker} segmentMarker */ SegmentShape.prototype._onSegmentHandleDragEnd = function(segmentMarker) { var startMarker = segmentMarker.isStartMarker(); this._peaks.emit('segments.dragend', this._segment, startMarker); }; SegmentShape.prototype.fitToView = function() { if (this._startMarker) { this._startMarker.fitToView(); } if (this._endMarker) { this._endMarker.fitToView(); } }; SegmentShape.prototype.destroy = function() { this._shapeGroup.destroy(); if (this._label) { this._label.destroy(); } if (this._startMarker) { this._startMarker.destroy(); } if (this._endMarker) { this._endMarker.destroy(); } }; SegmentShape.prototype._drawRect = function(ctx) { var cornerRadius = this._cornerRadius(); ctx.beginPath(); ctx.moveTo(cornerRadius + 1.5, 0); ctx.lineTo(this._segmentWidth - cornerRadius - 1.5, 0); ctx.quadraticCurveTo(this._segmentWidth, 0, this._segmentWidth, cornerRadius + 1.5); ctx.lineTo(this._segmentWidth, this._segmentHeight - cornerRadius - 1.5); ctx.quadraticCurveTo( this._segmentWidth, this._segmentHeight, this._segmentWidth - cornerRadius - 1.5, this._segmentHeight ); ctx.lineTo(cornerRadius + 1.5, this._segmentHeight); ctx.quadraticCurveTo( 0, this._segmentHeight, 0, this._segmentHeight - cornerRadius - 1.5 ); ctx.lineTo(0, cornerRadius + 1.5); ctx.quadraticCurveTo(0, 0, cornerRadius + 1.5, 0); ctx.closePath(); }; return SegmentShape; });