@checksub_team/peaks_timeline 1.4.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,509 @@
1
+ /**
2
+ * @file
3
+ *
4
+ * Defines the {@link SourcesLayer} class.
5
+ *
6
+ * @module sources-layer
7
+ */
8
+
9
+ define([
10
+ './source-group',
11
+ './segments-group',
12
+ './lines',
13
+ './data-retriever',
14
+ './utils',
15
+ 'konva'
16
+ ], function(
17
+ SourceGroup,
18
+ SegmentsGroup,
19
+ Lines,
20
+ DataRetriever,
21
+ Utils,
22
+ Konva) {
23
+ 'use strict';
24
+
25
+ /**
26
+ * Creates a Konva.Layer that displays sources on the timeline.
27
+ *
28
+ * @class
29
+ * @alias SourcesLayer
30
+ *
31
+ * @param {Peaks} peaks
32
+ * @param {WaveformOverview|WaveformZoomView} view
33
+ * @param {Boolean} allowEditing
34
+ */
35
+
36
+ function SourcesLayer(peaks, view, allowEditing) {
37
+ this._peaks = peaks;
38
+ this._view = view;
39
+ this._allowEditing = allowEditing;
40
+ this._sourcesGroup = {};
41
+ this._layer = new Konva.Layer();
42
+ this._dataRetriever = new DataRetriever(peaks);
43
+ this._lines = new Lines(peaks, view, this);
44
+ this._lines.addToLayer(this);
45
+
46
+ this._segmentsGroup = new SegmentsGroup(peaks, view, true);
47
+
48
+ this._loadedData = {};
49
+
50
+ this._peaks.on('sources.add', this._onSourcesAdd.bind(this));
51
+ this._peaks.on('sources.destroy', this._onSourcesDestroy.bind(this));
52
+ this._peaks.on('sources.show', this._onSourcesShow.bind(this));
53
+ this._peaks.on('sources.hide', this._onSourcesHide.bind(this));
54
+ this._peaks.on('source.update', this._onSourceUpdate.bind(this));
55
+ this._peaks.on('data.retrieved', this._onDataRetrieved.bind(this));
56
+ this._peaks.on('sources.refresh', this._onSourcesRefresh.bind(this));
57
+ this._peaks.on('segments.show', this._onSegmentsShow.bind(this));
58
+ }
59
+
60
+ SourcesLayer.prototype.getLoadedData = function(id) {
61
+ return this._loadedData[id];
62
+ };
63
+
64
+ SourcesLayer.prototype.setLoadedData = function(id, data) {
65
+ this._loadedData[id] = data;
66
+ };
67
+
68
+ SourcesLayer.prototype.getSegmentsGroup = function() {
69
+ return this._segmentsGroup;
70
+ };
71
+
72
+ SourcesLayer.prototype.add = function(element) {
73
+ this._layer.add(element);
74
+ };
75
+
76
+ /**
77
+ * Adds the layer to the given {Konva.Stage}.
78
+ *
79
+ * @param {Konva.Stage} stage
80
+ */
81
+
82
+ SourcesLayer.prototype.addToStage = function(stage) {
83
+ stage.add(this._layer);
84
+ };
85
+
86
+ SourcesLayer.prototype.enableEditing = function(enable) {
87
+ this._allowEditing = enable;
88
+ };
89
+
90
+ SourcesLayer.prototype.isEditingEnabled = function() {
91
+ return this._allowEditing;
92
+ };
93
+
94
+ SourcesLayer.prototype._onSourceUpdate = function(source) {
95
+ var redraw = false;
96
+ var sourceGroup = this._sourcesGroup[source.id];
97
+ var frameOffset = this._view.getFrameOffset();
98
+ var width = this._view.getWidth();
99
+ var frameStartTime = this._view.pixelsToTime(frameOffset);
100
+ var frameEndTime = this._view.pixelsToTime(frameOffset + width);
101
+
102
+ if (sourceGroup) {
103
+ this._removeSource(source);
104
+ redraw = true;
105
+ }
106
+
107
+ if (source.isVisible(frameStartTime, frameEndTime)) {
108
+ this._addSourceGroup(source);
109
+ redraw = true;
110
+ }
111
+
112
+ if (redraw) {
113
+ this.updateSources(frameStartTime, frameEndTime);
114
+ }
115
+ };
116
+
117
+ SourcesLayer.prototype._onSourcesShow = function(sources) {
118
+ var self = this;
119
+
120
+ sources.forEach(function(source) {
121
+ self._sourcesGroup[source.id].unwrap(true);
122
+ });
123
+
124
+ this._layer.draw();
125
+ };
126
+
127
+ SourcesLayer.prototype._onSourcesAdd = function(sources) {
128
+ var self = this;
129
+
130
+ var frameOffset = self._view.getFrameOffset();
131
+ var width = self._view.getWidth();
132
+
133
+ var frameStartTime = self._view.pixelsToTime(frameOffset);
134
+ var frameEndTime = self._view.pixelsToTime(frameOffset + width);
135
+
136
+ sources.forEach(function(source) {
137
+ self._addSourceGroup(source);
138
+ });
139
+
140
+ this._view.updateTimelineLength();
141
+
142
+ this.updateSources(frameStartTime, frameEndTime);
143
+ };
144
+
145
+ SourcesLayer.prototype._onSourcesDestroy = function(sources) {
146
+ var self = this;
147
+
148
+ sources.forEach(function(source) {
149
+ self._removeSource(source, true);
150
+ });
151
+
152
+ this._view.updateTimelineLength();
153
+
154
+ this._layer.draw();
155
+ };
156
+
157
+ SourcesLayer.prototype._onSourcesShow = function(sources) {
158
+ var self = this;
159
+
160
+ sources.forEach(function(source) {
161
+ self._sourcesGroup[source.id].unwrap(true);
162
+ });
163
+
164
+ this._layer.draw();
165
+ };
166
+
167
+ SourcesLayer.prototype._onSourcesHide = function(sources) {
168
+ var self = this;
169
+
170
+ sources.forEach(function(source) {
171
+ self._sourcesGroup[source.id].wrap(true);
172
+ });
173
+
174
+ this._layer.draw();
175
+ };
176
+
177
+ SourcesLayer.prototype._onDataRetrieved = function(data, source, url) {
178
+ if (data) {
179
+ var type = data.type.split('/')[0];
180
+ var sourceGroup = this._sourcesGroup[source.id];
181
+
182
+ if (sourceGroup) {
183
+ switch (type) {
184
+ case 'image':
185
+ sourceGroup.addImagePreview(data.content, url, true);
186
+ break;
187
+ case 'video':
188
+ sourceGroup.addVideoPreview(data.content, url, true);
189
+ break;
190
+ case 'binary':
191
+ case 'text':
192
+ case 'application':
193
+ case 'other':
194
+ case 'audio':
195
+ sourceGroup.addAudioPreview(type, data.content, url, true);
196
+ break;
197
+ default:
198
+ // Type not handled
199
+ }
200
+ }
201
+ }
202
+ };
203
+
204
+ SourcesLayer.prototype._onSourcesRefresh = function() {
205
+ this._layer.draw();
206
+ };
207
+
208
+ SourcesLayer.prototype._onSegmentsShow = function(position) {
209
+ this._lines.addSegments(this._segmentsGroup, position);
210
+ this._view.updateTimelineLength();
211
+ this._layer.draw();
212
+ };
213
+
214
+ /**
215
+ * Creates the Konva UI objects for a given source.
216
+ *
217
+ * @private
218
+ * @param {Source} source
219
+ * @returns {Kanva.Group}
220
+ */
221
+
222
+ SourcesLayer.prototype._createSourceGroup = function(source) {
223
+ return new SourceGroup(source, this._peaks, this, this._view);
224
+ };
225
+
226
+ /**
227
+ * Adds a Konva UI object to the layer for a given source.
228
+ *
229
+ * @private
230
+ * @param {Source} source
231
+ * @returns {Konva.Group}
232
+ */
233
+
234
+ SourcesLayer.prototype._addSourceGroup = function(source) {
235
+ var sourceGroup = this._createSourceGroup(source);
236
+
237
+ this._sourcesGroup[source.id] = sourceGroup;
238
+ this._lines.addSourceGroup(sourceGroup, source.position);
239
+
240
+ // After creating and referencing the new group, we can start data retrieval
241
+ this._dataRetriever.retrieveData(source);
242
+
243
+ return sourceGroup;
244
+ };
245
+
246
+ /**
247
+ * Updates the positions of all displayed sources in the view.
248
+ *
249
+ * @param {Number} startTime The start of the visible range in the view,
250
+ * in seconds.
251
+ * @param {Number} endTime The end of the visible range in the view,
252
+ * in seconds.
253
+ */
254
+
255
+ SourcesLayer.prototype.updateSources = function(startTime, endTime) {
256
+ // Update segments
257
+ this._lines.updateSegments(startTime, endTime);
258
+
259
+ // Update sources in visible time range.
260
+ var sources = this.findSources(startTime, endTime);
261
+
262
+ // Should implement virtualization on Y
263
+
264
+ var count = sources.length;
265
+
266
+ sources.forEach(this._updateSource.bind(this));
267
+ this._lines.setOffsetY(this._view.getFrameOffsetY());
268
+
269
+ count += this._removeInvisibleSources(startTime, endTime);
270
+
271
+ if (count > 0) {
272
+ this._layer.draw();
273
+ }
274
+ };
275
+
276
+ SourcesLayer.prototype.findSources = function(startTime, endTime) {
277
+ var sources = this._peaks.sources.find(startTime, endTime);
278
+ var positions = this._lines.getVisibleLines();
279
+
280
+ return sources.filter(
281
+ function(source) {
282
+ return positions[source.position];
283
+ }
284
+ );
285
+ };
286
+
287
+ SourcesLayer.prototype.updateSource = function(source, newStartX, newEndX, newY) {
288
+ var newXs = {
289
+ startX: newStartX,
290
+ endX: newEndX,
291
+ updateWidth: false
292
+ };
293
+
294
+ this.manageVerticalPosition(source, newY);
295
+
296
+ newXs = this.manageSourceOrder(source, newStartX, newEndX);
297
+
298
+ newXs = this.manageCollision(source, newXs.startX, newXs.endX);
299
+
300
+ source.updateTimes(
301
+ newXs.startX !== null ? this._view.pixelsToTime(newXs.startX) : null,
302
+ newXs.endX !== null ? this._view.pixelsToTime(newXs.endX) : null
303
+ );
304
+
305
+ if (newXs) {
306
+ this._updateSource(
307
+ source,
308
+ newXs.updateWidth
309
+ );
310
+
311
+ this.draw();
312
+ }
313
+ };
314
+
315
+ SourcesLayer.prototype.manageVerticalPosition = function(source, newY) {
316
+ return this._lines.manageVerticalPosition(source, newY);
317
+ };
318
+
319
+ SourcesLayer.prototype.manageSourceOrder = function(source, newStartX, newEndX) {
320
+ return this._lines.manageSourceOrder(source, newStartX, newEndX);
321
+ };
322
+
323
+ SourcesLayer.prototype.manageCollision = function(source, newStartX, newEndX) {
324
+ return this._lines.manageCollision(source, newStartX, newEndX);
325
+ };
326
+
327
+ /**
328
+ * @private
329
+ */
330
+
331
+ SourcesLayer.prototype._updateSource = function(source) {
332
+ var sourceGroup = this._findOrAddSourceGroup(source);
333
+
334
+ sourceGroup.update();
335
+ };
336
+
337
+ SourcesLayer.prototype._findOrAddSourceGroup = function(source) {
338
+ var sourceGroup = this._sourcesGroup[source.id];
339
+
340
+ if (!sourceGroup) {
341
+ sourceGroup = this._addSourceGroup(source);
342
+ }
343
+
344
+ return sourceGroup;
345
+ };
346
+
347
+ /**
348
+ * Removes any sources that are not visible, i.e., are not within and do not
349
+ * overlap the given time range.
350
+ *
351
+ * @private
352
+ * @param {Number} startTime The start of the visible time range, in seconds.
353
+ * @param {Number} endTime The end of the visible time range, in seconds.
354
+ * @returns {Number} The number of sources removed.
355
+ */
356
+
357
+ SourcesLayer.prototype._removeInvisibleSources = function(startTime, endTime) {
358
+ var count = 0;
359
+
360
+ for (var sourceId in this._sourcesGroup) {
361
+ if (Utils.objectHasProperty(this._sourcesGroup, sourceId)) {
362
+ var source = this._sourcesGroup[sourceId].getSource();
363
+
364
+ if (!this._isSourceVisible(source, startTime, endTime)
365
+ && !this._sourcesGroup[sourceId].isDragged()) {
366
+ this._removeSource(source);
367
+ count++;
368
+ }
369
+ }
370
+ }
371
+
372
+ return count;
373
+ };
374
+
375
+ SourcesLayer.prototype._isSourceVisible = function(source, startTime, endTime) {
376
+ return source.isVisible(startTime, endTime) && this._lines.isLineVisible(source.position);
377
+ };
378
+
379
+ /**
380
+ * Get all visible sources.
381
+ *
382
+ * @private
383
+ * @returns {Array<Source>} The visible sources.
384
+ */
385
+
386
+ SourcesLayer.prototype._getVisibleSources = function() {
387
+ var frameOffset = this._view.getFrameOffset();
388
+ var width = this._view.getWidth();
389
+ var frameStartTime = this._view.pixelsToTime(frameOffset);
390
+ var frameEndTime = this._view.pixelsToTime(frameOffset + width);
391
+
392
+ var visibleSources = [];
393
+
394
+ for (var sourceId in this._sourcesGroup) {
395
+ if (Utils.objectHasProperty(this._sourcesGroup, sourceId)) {
396
+ var source = this._sourcesGroup[sourceId].data;
397
+
398
+ if (source.isVisible(frameStartTime, frameEndTime)) {
399
+ visibleSources.push(source);
400
+ }
401
+ }
402
+ }
403
+
404
+ return visibleSources;
405
+ };
406
+
407
+ SourcesLayer.prototype._getOverlappedSources = function() {
408
+ var self = this;
409
+ var sources = this._getVisibleSources();
410
+
411
+ return sources.reduce(function(result, source) {
412
+ sources.forEach(function(source2) {
413
+ if (self._sourcesGroupOverlapped(source, source2)) {
414
+ if (!result.includes(source2.id)) {
415
+ result.push(source2);
416
+ }
417
+ }
418
+ });
419
+
420
+ return result;
421
+ }, []);
422
+ };
423
+
424
+ /**
425
+ * Removes the given source from the view.
426
+ *
427
+ * @param {Source} source
428
+ */
429
+
430
+ SourcesLayer.prototype._removeSource = function(source, isPermanent) {
431
+ var sourceGroup = this._sourcesGroup[source.id];
432
+
433
+ if (sourceGroup) {
434
+ delete this._sourcesGroup[source.id];
435
+ this._lines.removeSourceGroup(source, isPermanent);
436
+ sourceGroup.destroy();
437
+ }
438
+
439
+ if (isPermanent) {
440
+ this._peaks.emit('source.destroyed', source);
441
+ }
442
+ };
443
+
444
+ /**
445
+ * Toggles visibility of the sources layer.
446
+ *
447
+ * @param {Boolean} visible
448
+ */
449
+
450
+ SourcesLayer.prototype.setVisible = function(visible) {
451
+ this._layer.setVisible(visible);
452
+ };
453
+
454
+ SourcesLayer.prototype.draw = function() {
455
+ this._layer.draw();
456
+ };
457
+
458
+ SourcesLayer.prototype.listening = function(bool) {
459
+ this._layer.listening(bool);
460
+ };
461
+
462
+ SourcesLayer.prototype.stopDrag = function() {
463
+ this._layer.stopDrag();
464
+ };
465
+
466
+ SourcesLayer.prototype._sourcesOverlapped = function(source1, source2) {
467
+ var endsLater = (source1.startTime < source2.startTime)
468
+ && (source1.endTime > source2.startTime);
469
+ var startsEarlier = (source1.startTime > source2.startTime)
470
+ && (source1.startTime < source2.endTime);
471
+
472
+ return endsLater || startsEarlier;
473
+ };
474
+
475
+ SourcesLayer.prototype.rescale = function() {
476
+ this._lines.rescale();
477
+ this._layer.draw();
478
+ };
479
+
480
+ SourcesLayer.prototype.destroy = function() {
481
+ this._peaks.off('sources.add', this._onSourcesAdd);
482
+ this._peaks.off('sources.destroy', this._onSourcesDestroy);
483
+ this._peaks.off('sources.show', this._onSourcesShow);
484
+ this._peaks.off('sources.hide', this._onSourcesHide);
485
+ this._peaks.off('source.wrappingChanged', this._onSourceWrappingChanged);
486
+ this._peaks.off('data.retrieved', this._onDataRetrieved);
487
+ this._peaks.off('sources.refresh', this._onSourcesRefresh);
488
+ this._peaks.off('segments.show', this._onSegmentsShow);
489
+ };
490
+
491
+ SourcesLayer.prototype.getHeight = function() {
492
+ return this._layer.getHeight();
493
+ };
494
+
495
+ SourcesLayer.prototype.getLength = function() {
496
+ return this._lines.linesLength();
497
+ };
498
+
499
+ /**
500
+ * Object for storing data and UI of a source.
501
+ *
502
+ * @typedef {Object} CompleteSource
503
+ * @global
504
+ * @property {Source} data
505
+ * @property {Konva.Group} ui
506
+ */
507
+
508
+ return SourcesLayer;
509
+ });