@checksub_team/peaks_timeline
Version:
JavaScript UI component for displaying audio waveforms
511 lines (414 loc) • 14.8 kB
JavaScript
/**
* @file
*
* Defines the {@link ModeLayer} class.
*
* @module mode-layer
*/
define([
'./utils',
'./source',
'./source-group',
'konva'
], function(Utils, Source, SourceGroup, Konva) {
'use strict';
var TIME_X_OFFSET = 20;
var TIME_Y_OFFSET = 10;
var SNAP_DISTANCE_FOR_CUTTING = 10;
/**
* Creates a Konva.Layer that displays additionnal information for alternative modes.
*
* @class
* @alias ModeLayer
*
* @param {Peaks} peaks
* @param {WaveformOverview|WaveformZoomView} view
* @param {String} initialMode
*/
function ModeLayer(peaks, view, playheadLayer, stage, initialMode) {
this._peaks = peaks;
this._view = view;
this._playheadLayer = playheadLayer;
this._stage = stage;
this._selectedElements = {};
this._currentLine = null;
this._layer = new Konva.Layer({
listening: this._mode !== 'default'
});
this._prepareDefaultMode();
this._onMouseClickInDefaultMode = this._onMouseClickInDefaultMode.bind(this);
this._onKeyboardDelete = this._onKeyboardDelete.bind(this);
this._prepareCutMode();
this._onMouseEnterInCutMode = this._onMouseEnterInCutMode.bind(this);
this._onMouseMoveInCutMode = this._onMouseMoveInCutMode.bind(this);
this._onMouseLeaveInCutMode = this._onMouseLeaveInCutMode.bind(this);
this._onMouseClickInCutMode = this._onMouseClickInCutMode.bind(this);
this.setMode(initialMode);
this._peaks.on('sources.destroy', this._onSourcesDestroy.bind(this));
}
ModeLayer.prototype._onSourcesDestroy = function(sources) {
const selectedElementsToDeselect = {};
sources.forEach(function(source) {
if (this._selectedElements[source.id]) {
selectedElementsToDeselect[source.id] = source;
}
}.bind(this));
this._deselectElements(selectedElementsToDeselect, false);
};
/**
* Adds the layer to the given {Konva.Stage}.
*
* @param {Konva.Stage} stage
*/
ModeLayer.prototype.addToStage = function(stage) {
stage.add(this._layer);
};
ModeLayer.prototype.selectElements = function(elements, notify) {
const sources = [];
const segments = [];
const self = this;
this.deselectDifference(elements, false);
elements.forEach(function(element) {
if (!self._selectedElements[element.id]) {
self._selectedElements[element.id] = element;
element.selected = true;
if (element instanceof Source) {
sources.push(element);
}
else {
segments.push(element);
}
}
});
if (sources.length) {
this._peaks.emit('sources.setSelected', sources);
if (notify) {
this._peaks.emit('sources.selected', sources);
}
}
if (segments.length) {
this._peaks.emit('segments.setSelected', segments);
if (notify) {
this._peaks.emit('segments.selected', segments);
}
}
};
ModeLayer.prototype.deselectDifference = function(newElements, notify) {
const selectedElementsToDeselect = Object.assign({}, this._selectedElements);
newElements.forEach(function(element) {
delete selectedElementsToDeselect[element.id];
});
this._deselectElements(selectedElementsToDeselect, notify);
};
ModeLayer.prototype._deselectElements = function(elements, notify) {
const sources = [];
const segments = [];
for (var id in elements) {
if (Utils.objectHasProperty(this._selectedElements, id)) {
const element = this._selectedElements[id];
element.selected = false;
if (element instanceof Source) {
sources.push(element);
}
else {
segments.push(element);
}
delete this._selectedElements[id];
}
}
if (sources.length) {
this._peaks.emit('sources.setSelected', sources);
if (notify) {
this._peaks.emit('sources.deselected', sources);
}
}
if (segments.length) {
this._peaks.emit('segments.setSelected', segments);
if (notify) {
this._peaks.emit('segments.deselected', segments);
}
}
};
ModeLayer.prototype.getSelectedElements = function() {
return this._selectedElements;
};
ModeLayer.prototype._prepareDefaultMode = function() {
this._selectedElement = null;
};
ModeLayer.prototype._prepareCutMode = function() {
this._cutGroup = new Konva.Group({
width: this._view.getWidth(),
height: this._view.getHeight(),
visible: false,
listening: false
});
this._cursorTime = new Konva.Group({
listening: false
});
this._timeLabel = new Konva.Text({
x: 4,
y: 2,
text: '00:00:00',
fontSize: 11,
fontFamily: 'Open Sans',
fill: '#999b9d',
align: 'center',
listening: false
});
this._timeBackground = new Konva.Rect({
width: this._timeLabel.width() + 8,
height: this._timeLabel.height() + 4,
cornerRadius: 3,
fill: '#37373c',
strokeWidth: 0,
listening: false
});
this._cursorTime.add(this._timeBackground);
this._cursorTime.add(this._timeLabel);
this._cuttingLine = new Konva.Line({
points: [0, 0, 0, 10],
stroke: 'red',
strokeWidth: 1,
opacity: 0.8,
listening: false,
visible: false
});
this._cutGroup.add(this._cursorTime);
this._cutGroup.add(this._cuttingLine);
this._layer.add(this._cutGroup);
};
ModeLayer.prototype._onMouseClickInDefaultMode = function() {
const hoveredKonvaElement = this._view.getHoveredElement();
if (hoveredKonvaElement) {
if (hoveredKonvaElement instanceof SourceGroup) {
this.selectElements([hoveredKonvaElement.getSource()], true);
}
else {
this.selectElements([hoveredKonvaElement.getSegment()], true);
}
}
else {
this.deselectDifference([], true);
this._view.drawSourcesLayer(); // Redraw sources layer to remove selection
}
};
ModeLayer.prototype._onKeyboardDelete = function() {
const selectedElements = Object.values(this._selectedElements);
// We allow deletion if there is ONLY 1 element selected
if (selectedElements.length === 1) {
var selectedElement = selectedElements[0];
this.deselectDifference([], true);
if (selectedElement instanceof Source) {
if (selectedElement.deletable) {
this._peaks.destroySource(selectedElement.id, true);
}
}
else {
if (selectedElement.allowDeletion) {
this._peaks.destroySegment(selectedElement.id);
}
}
}
};
ModeLayer.prototype._onMouseEnterInCutMode = function() {
this._cutGroup.visible(true);
this._layer.draw();
};
ModeLayer.prototype._updateCursorTime = function(position) {
var hoveredElement = this._view.getHoveredElement();
if (hoveredElement && hoveredElement instanceof SourceGroup && hoveredElement.isCuttable()) {
this._timeLabel.text(
Utils.formatTime(
this._view.pixelsToTime(position.x + this._view.getFrameOffset()),
false
)
);
this._timeBackground.width(this._timeLabel.width() + 8);
this._cursorTime.x(position.x + TIME_X_OFFSET);
if (position.y > this._view.getHeight()
- this._timeBackground.height() - TIME_Y_OFFSET) {
this._cursorTime.y(position.y - TIME_Y_OFFSET);
}
else {
this._cursorTime.y(position.y + TIME_Y_OFFSET);
}
this._cursorTime.visible(true);
}
else {
this._cursorTime.visible(false);
}
};
ModeLayer.prototype._updateCuttingLine = function(position) {
var hoveredElement = this._view.getHoveredElement();
if (hoveredElement && hoveredElement instanceof SourceGroup && hoveredElement.isCuttable()) {
var minSize = this._view.timeToPixels(hoveredElement.getSource().minSize);
if (hoveredElement.getWidth() >= 2 * minSize) {
var height = hoveredElement.getCurrentHeight();
var y = hoveredElement.getY();
if (position.x < hoveredElement.x() + minSize) {
this._cuttingLine.points([
hoveredElement.x() + minSize,
y,
hoveredElement.x() + minSize,
y + height
]);
}
else if (position.x
> hoveredElement.x() + hoveredElement.getWidth() - minSize) {
this._cuttingLine.points([
hoveredElement.x() + hoveredElement.getWidth() - minSize,
y,
hoveredElement.x() + hoveredElement.getWidth() - minSize,
y + height
]);
}
else {
this._cuttingLine.points([position.x, y, position.x, y + height]);
}
this._cuttingLine.visible(true);
}
else {
this._view.setCursor('not-allowed');
}
}
else {
this._cuttingLine.visible(false);
if (this._view.getCursor('not-allowed')) {
this._view.setCursor('default');
}
}
};
ModeLayer.prototype._onMouseMoveInCutMode = function() {
var mousePosition = this._stage.getPointerPosition();
mousePosition.x = this._view.timeToPixels(this._view.pixelsToTime(mousePosition.x));
var cuttingPosition = mousePosition;
var hoveredElement = this._view.getHoveredElement();
if (hoveredElement && hoveredElement instanceof SourceGroup && hoveredElement.isCuttable()) {
var playheadPositionX =
Math.round(this._view.timeToPixels(this._playheadLayer._time) - this._view.getFrameOffset());
var playheadIsOnHoveredElement =
playheadPositionX >= hoveredElement.x() &&
playheadPositionX <= (hoveredElement.x() + hoveredElement.getWidth());
if (Math.abs(mousePosition.x - playheadPositionX) < SNAP_DISTANCE_FOR_CUTTING && playheadIsOnHoveredElement) {
cuttingPosition.x = playheadPositionX;
}
}
this._updateCursorTime(cuttingPosition);
this._updateCuttingLine(cuttingPosition);
this._layer.draw();
};
ModeLayer.prototype._onMouseLeaveInCutMode = function() {
this._cutGroup.visible(false);
this._layer.draw();
};
ModeLayer.prototype._onMouseClickInCutMode = function() {
var mousePosition = this._stage.getPointerPosition();
mousePosition.x = this._view.timeToPixels(this._view.pixelsToTime(mousePosition.x));
var hoveredElement = this._view.getHoveredElement();
if (hoveredElement && hoveredElement instanceof SourceGroup && hoveredElement.isCuttable()) {
var cuttingPosition = mousePosition;
var playheadPositionX =
Math.round(this._view.timeToPixels(this._playheadLayer._time) - this._view.getFrameOffset());
var playheadIsOnHoveredElement =
playheadPositionX >= hoveredElement.x() &&
playheadPositionX <= (hoveredElement.x() + hoveredElement.getWidth());
if (Math.abs(mousePosition.x - playheadPositionX) < SNAP_DISTANCE_FOR_CUTTING && playheadIsOnHoveredElement) {
cuttingPosition.x = playheadPositionX;
}
var minSize = this._view.timeToPixels(hoveredElement.getSource().minSize);
if (hoveredElement.getWidth() >= 2 * minSize) {
var cuttingPixel;
if (cuttingPosition.x < hoveredElement.x() + minSize) {
cuttingPixel = hoveredElement.x() + minSize;
}
else if (cuttingPosition.x
> hoveredElement.x() + hoveredElement.getWidth() - minSize) {
cuttingPixel = hoveredElement.x()
+ hoveredElement.getWidth() - minSize;
}
else {
cuttingPixel = cuttingPosition.x;
}
// Relative cuttingPixel
cuttingPixel -= hoveredElement.x();
this._cuttingLine.visible(false);
this._peaks.emit(
'source.cut',
hoveredElement.getSource(),
this._view.pixelsToTime(cuttingPixel)
);
this._view.setHoveredElement(null);
this._updateCursorTime(cuttingPosition);
this._updateCuttingLine(cuttingPosition);
this._layer.draw();
}
}
};
ModeLayer.prototype.setMode = function(mode) {
if (this._mode === mode) {
return;
}
var hoveredElement = this._view.getHoveredElement();
// Clean current mode
switch (this._mode) {
case 'cut':
this._stage.off('mouseover', this._onMouseEnterInCutMode);
this._stage.off('mousemove', this._onMouseMoveInCutMode);
this._stage.off('mouseleave', this._onMouseLeaveInCutMode);
this._stage.off('click', this._onMouseClickInCutMode);
this._cutGroup.visible(false);
if (hoveredElement
&& hoveredElement instanceof SourceGroup
&& hoveredElement.isCuttable()) {
hoveredElement.toggleDragging(hoveredElement.getSource().draggable);
hoveredElement.toggleResizing(hoveredElement.getSource().resizable);
}
this._view.toggleMainCursor(false);
break;
case 'default':
this._stage.off('click', this._onMouseClickInDefaultMode);
this._peaks.off('keyboard.delete', this._onKeyboardDelete);
break;
}
this.deselectDifference([], true);
// Set new mode
switch (mode) {
case 'cut':
this._stage.on('mouseover', this._onMouseEnterInCutMode);
this._stage.on('mousemove', this._onMouseMoveInCutMode);
this._stage.on('mouseleave', this._onMouseLeaveInCutMode);
this._stage.on('click', this._onMouseClickInCutMode);
var mousePosition = this._stage.getPointerPosition();
if (mousePosition) {
mousePosition.x = this._view.timeToPixels(this._view.pixelsToTime(mousePosition.x));
if (mousePosition.x > 0 && mousePosition.x < this._view.getWidth()
&& mousePosition.y > 0 && mousePosition.y < this._view.getHeight()) {
this._cutGroup.visible(true);
this._updateCursorTime(mousePosition);
this._updateCuttingLine(mousePosition);
}
}
if (hoveredElement
&& hoveredElement instanceof SourceGroup
&& hoveredElement.isCuttable()) {
hoveredElement.toggleDragging(false);
hoveredElement.toggleResizing(false);
}
this._view.toggleMainCursor(true, 'default');
break;
case 'default':
this._stage.on('click', this._onMouseClickInDefaultMode);
this._peaks.on('keyboard.delete', this._onKeyboardDelete);
break;
default:
return;
}
this._mode = mode;
this._layer.draw();
this._view.drawSourcesLayer();
};
ModeLayer.prototype.getCurrentMode = function() {
return this._mode;
};
return ModeLayer;
});