@checksub_team/peaks_timeline
Version:
JavaScript UI component for displaying audio waveforms
509 lines (414 loc) • 15.4 kB
JavaScript
/**
* @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 = {};
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: segment.borderWidth !== undefined && segment.borderWidth !== null ?
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._label = this._peaks.options.createSegmentLabel({
x: this._view.timeToPixels(segment.startTime),
y: this._segmentY,
width: this._view.timeToPixels(this._segment.endTime - this._segment.startTime),
height: this._segmentHeight,
segment: segment,
view: this._view.getName(),
group: this._group,
fontSize: 12
});
this._createMarkers();
this._indicatorsGroup = new Konva.Group();
this._shapeGroup.add(this._indicatorsGroup);
this.createIndicators();
}
SegmentShape.prototype._onShapeGroupDrag = function(draggedElement) {
const diff = this._view.getPointerPosition().x - this._mouseDownX;
this._group.updateSegment(
this._segment,
this._initialStartPixel + diff, this._initialEndPixel + diff
);
this._startMarker.timeUpdated(this._segment.startTime);
this._endMarker.timeUpdated(this._segment.endTime);
return {
x: draggedElement.absolutePosition().x,
y: draggedElement.absolutePosition().y
};
};
SegmentShape.prototype._cornerRadius = function() {
return this._segment.borderRadius !== undefined && this._segment.borderRadius !== null ?
this._segment.borderRadius :
SEGMENT_CORNER_RADIUS;
};
SegmentShape.prototype.update = function() {
var startPixel = this._view.timeToPixels(this._segment.startTime);
var endPixel = this._view.timeToPixels(this._segment.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(this._segment.startTime);
}
if (this._endMarker) {
this._endMarker.setX(endPixel - this._endMarker.getHandleWidth() - frameOffset);
this._endMarker.timeUpdated(this._segment.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.addToGroup = function(group, segmentsGroup) {
if (segmentsGroup) {
this._segmentsGroup = segmentsGroup;
}
group.add(this._shapeGroup);
if (this._label) {
group.add(this._label);
}
if (this._startMarker) {
this._startMarker.addToGroup(group);
}
if (this._endMarker) {
this._endMarker.addToGroup(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);
var fillColor = this._segment.hoverColor + Math.round(
this._segment.opacity * 255
).toString(16);
this._rectangle.fill(fillColor);
this._rectangle.fillLinearGradientColorStops([
0, fillColor,
0.65, this._segment.warningColor
]);
this._view.drawSourcesLayer();
this._peaks.emit('segments.mouseenter', this._segment);
};
SegmentShape.prototype._onMouseLeave = function() {
this._view.setCursor('default');
this._view.setHoveredElement(null);
var fillColor = this._segment.color + Math.round(
this._segment.opacity * 255
).toString(16);
this._rectangle.fill(fillColor);
this._rectangle.fillLinearGradientColorStops([
0, fillColor,
0.65, this._segment.warningColor
]);
this._view.drawSourcesLayer();
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._mouseDownX = this._view.getPointerPosition().x;
this._initialStartTime = this._segment.startTime;
this._initialStartPixel = this._view.timeToPixels(this._initialStartTime);
this._initialEndTime = this._segment.endTime;
this._initialEndPixel = this._view.timeToPixels(this._initialEndTime);
this._peaks.emit('segments.dragstart', this._segment);
};
SegmentShape.prototype._onSegmentDragEnd = function() {
this._view.setCursor('pointer');
this._peaks.emit('segments.dragend', this._segment);
};
/**
* @param {SegmentMarker} segmentMarker
*/
SegmentShape.prototype._onSegmentHandleDrag = function() {
this._peaks.emit('segments.dragged');
};
/**
* @param {SegmentMarker} segmentMarker
*/
SegmentShape.prototype._onSegmentHandleDragStart = function(segmentMarker) {
var startMarker = segmentMarker.isStartMarker();
this._peaks.emit('segments.dragstart', this._segment, startMarker);
};
/**
* @param {SegmentMarker} segmentMarker
*/
SegmentShape.prototype._onSegmentHandleDragEnd = function(segmentMarker) {
var startMarker = segmentMarker.isStartMarker();
this._peaks.emit('segments.dragend', this._segment, startMarker);
};
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;
});