@checksub_team/peaks_timeline
Version:
JavaScript UI component for displaying audio waveforms
518 lines (421 loc) • 14.6 kB
JavaScript
/**
* @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;
});