UNPKG

@checksub_team/peaks_timeline

Version:

JavaScript UI component for displaying audio waveforms

511 lines (414 loc) 14.8 kB
/** * @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; });