UNPKG

@checksub_team/peaks_timeline

Version:

JavaScript UI component for displaying audio waveforms

518 lines (421 loc) 14.6 kB
/** * @file * * Defines the {@link LineIndicator} class. * * @module line-indicator */ define([ 'konva', './svgs', './invoker', '../utils' ], function( Konva, SVGs, Invoker, Utils) { 'use strict'; /** * Creates a Konva.Stage that displays a representation of each line. * * @class * @alias LineIndicator * * @param {Peaks} peaks * @param {WaveformOverview|WaveformZoomView} view */ function LineIndicator(peaks, view, container) { this._peaks = peaks; this._view = view; this._container = container; this._invoker = new Invoker(); this._width = this._peaks.options.lineIndicatorWidth; this._height = this._view.getHeight(); this._sizes = { font: this._peaks.options.lineIndicatorFontSize, icon: { default: this._peaks.options.lineIndicatorDefaultIconSize, volume: this._peaks.options.lineIndicatorVolumeIconSize, noVolume: this._peaks.options.lineIndicatorNoVolumeIconSize, visibility: this._peaks.options.lineIndicatorVisibilityIconSize, noVisibility: this._peaks.options.lineIndicatorNoVisibilityIconSize } }; this._yPadding = 30; this._topPadding = this._peaks.options.lineIndicatorPadding.top; this._bottomPadding = this._peaks.options.lineIndicatorPadding.bottom; this._types = ['default'].concat(Object.keys(SVGs)); this._stage = new Konva.Stage({ container: container, width: this._width, height: this._height }); this._layer = new Konva.Layer(); this._stage.add(this._layer); // Throttled draws to coalesce multiple updates per frame. this._throttledBatchDraw = this._invoker.throttleTrailing( this._layer.batchDraw.bind(this._layer) ); this._indicators = {}; this._separatingLine = new Konva.Line({ points: [this._width, 0, this._width, this._height], stroke: 'gray', strokeWidth: 1, listening: false }); this._layer.add(this._separatingLine); this.batchDraw(); this._isDragging = false; this._dragLineId = null; this._dragContainerRect = null; this._onWindowMove = this._onWindowMove.bind(this); this._onWindowUp = this._onWindowUp.bind(this); } LineIndicator.prototype.fitToView = function() { this._height = this._view.getHeight(); this._stage.height(this._height); this._separatingLine.points([this._width, 0, this._width, this._height]); this.refreshIndicators(); }; LineIndicator.prototype._createIndicator = function(lineGroup, type, text, subText, defaultStyle = {}) { const indicator = new Konva.Group(); let indicatorHeight = 0; var self = this; var textGroup, iconGroup, subTextGroup; var textNode, iconNode, subTextNode; if (text) { [textGroup, textNode] = this._createIndicatorText(text, 'line-indicator-text', defaultStyle); indicator.add(textGroup); indicatorHeight += textGroup.getAttr('trueHeight') + this._topPadding; } [iconGroup, iconNode] = this._createIndicatorIcon(type, 'line-indicator-icon', defaultStyle); iconGroup.y(indicatorHeight); indicator.add(iconGroup); indicatorHeight += iconGroup.getAttr('trueHeight'); if (subText) { indicatorHeight += this._bottomPadding; [subTextGroup, subTextNode] = this._createIndicatorText(subText, 'line-indicator-subText', defaultStyle); subTextGroup.y(indicatorHeight); indicator.add(subTextGroup); indicatorHeight += subTextGroup.getAttr('trueHeight'); } if (textGroup) { textGroup.on('mouseenter', function() { textNode.fill(textNode.getAttr('selectedColor')); if (iconNode) { iconNode.fill(iconNode.getAttr('selectedColor')); } self.batchDraw(); self._stage.container().style.cursor = 'pointer'; }); textGroup.on('mouseout', function() { textNode.fill(textNode.getAttr('defaultColor')); if (iconNode) { iconNode.fill(iconNode.getAttr('defaultColor')); } self.batchDraw(); if (!self._isDragging) { self._stage.container().style.cursor = 'default'; } }); textGroup.on('click', function(e) { self._peaks.emit('lineIndicator.text.click', self._indicators[lineGroup.getId()], e.evt); }); } if (iconGroup) { iconGroup.on('mouseenter', function() { iconNode.fill(iconNode.getAttr('selectedColor')); if (textNode) { textNode.fill(textNode.getAttr('selectedColor')); } self.batchDraw(); self._stage.container().style.cursor = 'pointer'; }); iconGroup.on('mouseout', function() { iconNode.fill(iconNode.getAttr('defaultColor')); if (textNode) { textNode.fill(textNode.getAttr('defaultColor')); } self.batchDraw(); if (!self._isDragging) { self._stage.container().style.cursor = 'default'; } }); iconGroup.on('click', function(e) { self._peaks.emit('lineIndicator.icon.click', self._indicators[lineGroup.getId()], e.evt); }); iconGroup.on('mousedown touchstart', function() { self._dragLineId = lineGroup.getId(); self._dragContainerRect = self._stage.getContainer().getBoundingClientRect(); window.addEventListener('mousemove', self._onWindowMove, false); window.addEventListener('touchmove', self._onWindowMove, false); window.addEventListener('mouseup', self._onWindowUp, false); window.addEventListener('touchend', self._onWindowUp, false); window.addEventListener('blur', self._onWindowUp, false); }); } if (subTextGroup) { subTextGroup.on('mouseenter', function() { subTextNode.fill(subTextNode.getAttr('selectedColor')); self.batchDraw(); self._stage.container().style.cursor = 'pointer'; }); subTextGroup.on('mouseout', function() { subTextNode.fill(subTextNode.getAttr('defaultColor')); self.batchDraw(); if (!self._isDragging) { self._stage.container().style.cursor = 'default'; } }); subTextGroup.on('click', function(e) { self._peaks.emit('lineIndicator.subText.click', self._indicators[lineGroup.getId()], e.evt); }); } indicator.setAttr('trueHeight', indicatorHeight); indicator.y(lineGroup.y() + (lineGroup.lineHeight() - indicatorHeight) / 2); return indicator; }; LineIndicator.prototype._createIndicatorText = function(text, role, defaultStyle = {}) { defaultStyle = defaultStyle[role] || {}; const textGroup = new Konva.Group(); const textNode = new Konva.Text({ text: text, fontSize: this._sizes.font, fontFamily: this._peaks.options.lineIndicatorFont, fill: defaultStyle.fill || this._peaks.options.lineIndicatorTextColor, align: 'center', width: this._width, listening: false, name: role }); textNode.setAttr('defaultColor', this._peaks.options.lineIndicatorTextColor); textNode.setAttr('selectedColor', this._peaks.options.lineIndicatorSelectedTextColor); var textRect = new Konva.Rect({ x: 0, y: 0, width: this._width, height: this._sizes.font, fill: 'transparent', listening: true }); textGroup.add(textNode); textGroup.add(textRect); textRect.moveToTop(); textGroup.setAttr('trueHeight', this._sizes.font); return [textGroup, textNode]; }; LineIndicator.prototype._createIndicatorIcon = function(type, role, defaultStyle = {}) { defaultStyle = defaultStyle[role] || {}; type = this._types.includes(type) ? type : 'default'; const iconGroup = new Konva.Group(); var iconHeight = this._sizes.icon[type]; var iconNode; if (type === 'default') { iconNode = new Konva.Circle({ x: this._width / 2, y: this._sizes.icon.default / 2, radius: this._sizes.icon.default / 2, fill: defaultStyle.fill || this._peaks.options.lineIndicatorIconColor, strokeWidth: 0, listening: false, name: role }); } else { iconNode = new Konva.Path({ x: (this._width - this._sizes.icon[type]) / 2, y: 0, data: SVGs[type].path, fill: defaultStyle.fill || this._peaks.options.lineIndicatorIconColor, scale: { x: (this._sizes.icon[type]) / SVGs[type].width, y: (this._sizes.icon[type]) / SVGs[type].height }, listening: false, name: role }); } iconNode.setAttr('defaultColor', this._peaks.options.lineIndicatorIconColor); iconNode.setAttr('selectedColor', this._peaks.options.lineIndicatorSelectedIconColor); var iconRect = new Konva.Rect({ x: 0, y: 0, width: this._width, height: iconHeight, fill: 'transparent', listening: true }); iconGroup.add(iconNode); iconGroup.add(iconRect); iconRect.moveToTop(); iconGroup.setAttr('trueHeight', iconHeight); return [iconGroup, iconNode]; }; LineIndicator.prototype.addIndicator = function(lineGroup) { var line = lineGroup.getLine(); if (!this._indicators[lineGroup.id]) { const indicator = this._createIndicator( lineGroup, line.indicatorType, line.indicatorText, line.indicatorSubText ); this._layer.add(indicator); this._indicators[line.id] = { lineGroup: lineGroup, indicator: indicator, type: line.indicatorType, text: line.indicatorText, subText: line.indicatorSubText }; } }; LineIndicator.prototype.updateIndicator = function(line) { const indicatorData = this._indicators[line.id]; if (!indicatorData) { this._peaks.logger('peaks.line-indicator.update(): line indicator not found: ' + line.id); return; } if (indicatorData.type === line.indicatorType && indicatorData.text === line.indicatorText && indicatorData.subText === line.indicatorSubText) { return; } const styleData = this.removeIndicator(line.id, true); var indicator = this._createIndicator( indicatorData.lineGroup, line.indicatorType, line.indicatorText, line.indicatorSubText, styleData ); this._layer.add(indicator); indicatorData.indicator = indicator; indicatorData.type = line.indicatorType; indicatorData.text = line.indicatorText; indicatorData.subText = line.indicatorSubText; this.batchDraw(); }; LineIndicator.prototype._getStyleData = function(konvaItem) { if (!konvaItem) { return {}; } var styleData = {}; const name = konvaItem.name(); if (name) { styleData[name] = { fill: konvaItem.fill() }; } if (typeof konvaItem.getChildren === 'function') { const children = konvaItem.getChildren(); if (children && children.length > 0) { children.forEach(function(child) { styleData = Object.assign(styleData, this._getStyleData(child)); }.bind(this)); } } return styleData; }; LineIndicator.prototype.removeIndicator = function(lineId, keepInList) { var styleData = {}; if (this._indicators[lineId]) { const indicator = this._indicators[lineId].indicator; if (!keepInList) { delete this._indicators[lineId]; } else { this._indicators[lineId].indicator = null; styleData = this._getStyleData(indicator); } if (indicator) { indicator.destroy(); } } return styleData; }; LineIndicator.prototype.refreshIndicator = function(lineId) { var anyChange = false; if (this._indicators[lineId]) { var y = this._indicators[lineId].lineGroup.y(); var isVisible = y + this._indicators[lineId].lineGroup.lineHeight() + this._yPadding > 0 && y - this._yPadding < this._height; const hasNoIndicator = !this._indicators[lineId].indicator; if (isVisible) { if (hasNoIndicator) { this._indicators[lineId].indicator = this._createIndicator( this._indicators[lineId].lineGroup, this._indicators[lineId].type, this._indicators[lineId].text, this._indicators[lineId].subText ); this._layer.add(this._indicators[lineId].indicator); anyChange = true; } else { const newY = y + this._indicators[lineId].lineGroup.lineHeight() / 2 - this._indicators[lineId].indicator.getAttr('trueHeight') / 2; if (this._indicators[lineId].indicator.y() !== newY) { this._indicators[lineId].indicator.y( newY ); anyChange = true; } } } else { this.removeIndicator(lineId, true); if (!hasNoIndicator) { anyChange = true; } } } return anyChange; }; LineIndicator.prototype.refreshIndicators = function() { var anyChange = false; for (var lineId in this._indicators) { if (Utils.objectHasProperty(this._indicators, lineId)) { anyChange = this.refreshIndicator(lineId) || anyChange; } } if (anyChange) { this.batchDraw(); } return anyChange; }; LineIndicator.prototype.batchDraw = function() { this._throttledBatchDraw(); }; LineIndicator.prototype._onWindowMove = function(event) { if (!this._dragContainerRect) { return; } if (!this._isDragging) { this._stage.container().style.cursor = 'grabbing'; } var clientY; if (event.type === 'touchmove') { clientY = Math.floor(event.changedTouches[0].clientY); } else { clientY = event.clientY; } const relY = clientY - this._dragContainerRect.top; this._peaks.emit('lineIndicator.drag', this._dragLineId, relY); }; LineIndicator.prototype._onWindowUp = function() { window.removeEventListener('mousemove', this._onWindowMove, false); window.removeEventListener('touchmove', this._onWindowMove, false); window.removeEventListener('mouseup', this._onWindowUp, false); window.removeEventListener('touchend', this._onWindowUp, false); window.removeEventListener('blur', this._onWindowUp, false); this._isDragging = false; this._dragLineId = null; this._dragContainerRect = null; this._stage.container().style.cursor = 'pointer'; }; return LineIndicator; });