UNPKG

@checksub_team/peaks_timeline

Version:

JavaScript UI component for displaying audio waveforms

545 lines (444 loc) 15.7 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 = {}; this._selected = this._segment.selected; this._hovered = false; 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: !Utils.isNullOrUndefined(segment.borderWidth) ? 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._createLabel(); this._createMarkers(); this._indicatorsGroup = new Konva.Group(); this._shapeGroup.add(this._indicatorsGroup); this.createIndicators(); this._applyVisualState(); } SegmentShape.prototype._onShapeGroupDrag = function(draggedElement) { return this._view._sourcesLayer.onSegmentDrag(draggedElement); }; SegmentShape.prototype._cornerRadius = function() { return !Utils.isNullOrUndefined(this._segment.borderRadius) ? this._segment.borderRadius : SEGMENT_CORNER_RADIUS; }; SegmentShape.prototype._createLabel = function() { this._label = this._peaks.options.createSegmentLabel({ x: this._view.timeToPixels(this._segment.startTime), y: this._segmentY, width: this._view.timeToPixels(this._segment.endTime - this._segment.startTime), height: this._segmentHeight, segment: this._segment, view: this._view.getName(), group: this._group, fontSize: 12 }); }; SegmentShape.prototype._getFillColor = function() { var color = this._segment.color; if (this._selected) { color = this._segment.selectedColor; } else if (this._hovered) { color = this._segment.hoverColor; } return color + Math.round(this._segment.opacity * 255).toString(16); }; SegmentShape.prototype._getStrokeColor = function() { if (this._selected) { return this._segment.selectedBorderColor; } return this._segment.borderColor || this._segment.textColor + 'FF'; }; SegmentShape.prototype._getLabelTextColor = function() { return this._selected ? this._segment.selectedTextColor : this._segment.textColor; }; SegmentShape.prototype._getHandleTextColor = function() { return this._selected ? this._segment.selectedHandleTextColor : this._segment.handleTextColor; }; SegmentShape.prototype._applyVisualState = function() { var fillColor = this._getFillColor(); var handleTextColor = this._getHandleTextColor(); this._rectangle.fill(fillColor); this._rectangle.stroke(this._getStrokeColor()); this._rectangle.fillLinearGradientColorStops([ 0, fillColor, 0.65, this._segment.warningColor ]); if (this._label && this._label.fill) { this._label.fill(this._getLabelTextColor()); } if (this._startMarker) { this._startMarker.setTextColor(handleTextColor); } if (this._endMarker) { this._endMarker.setTextColor(handleTextColor); } }; SegmentShape.prototype.update = function() { this._applyDisplayTimes(this._segment.startTime, this._segment.endTime); }; SegmentShape.prototype.setSelected = function() { this._selected = this._segment.selected; this._applyVisualState(); }; SegmentShape.prototype._applyDisplayTimes = function(startTime, endTime) { var startPixel = this._view.timeToPixels(startTime); var endPixel = this._view.timeToPixels(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(startTime); } if (this._endMarker) { this._endMarker.setX(endPixel - this._endMarker.getHandleWidth() - frameOffset); this._endMarker.timeUpdated(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.stopDrag = function() { this._shapeGroup.stopDrag(); }; SegmentShape.prototype.moveTo = function(group, segmentsGroup) { if (segmentsGroup) { this._segmentsGroup = segmentsGroup; } this._shapeGroup.moveTo(group); if (this._label) { this._label.moveTo(group); } if (this._startMarker) { this._startMarker.moveTo(group); } if (this._endMarker) { this._endMarker.moveTo(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); this._hovered = true; this._applyVisualState(); this._view.batchDrawSourcesLayer(); this._peaks.emit('segments.mouseenter', this._segment); }; SegmentShape.prototype._onMouseLeave = function() { this._view.setCursor('default'); this._view.setHoveredElement(null); this._hovered = false; this._applyVisualState(); this._view.batchDrawSourcesLayer(); 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._view._sourcesLayer.onSegmentDragStart(this); }; SegmentShape.prototype._onSegmentDragEnd = function() { this._view.setCursor('pointer'); this._view._sourcesLayer.onSegmentDragEnd(this); }; /** * @param {SegmentMarker} segmentMarker */ SegmentShape.prototype._onSegmentHandleDrag = function() { this._peaks.emit('segments.dragged'); }; /** * @param {SegmentMarker} segmentMarker */ SegmentShape.prototype._onSegmentHandleDragStart = function(segmentMarker) { this._view._sourcesLayer.onSegmentHandleDragStart(segmentMarker); }; /** * @param {SegmentMarker} segmentMarker */ SegmentShape.prototype._onSegmentHandleDragEnd = function(segmentMarker) { this._view._sourcesLayer.onSegmentHandleDragEnd(segmentMarker); }; 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; });