@checksub_team/peaks_timeline 2.2.1 → 2.3.0-alpha.0
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.
- package/package.json +1 -1
- package/peaks.js +650 -137
- package/src/components/invoker.js +218 -2
- package/src/components/line-group.js +6 -6
- package/src/components/line-groups.js +40 -3
- package/src/components/source-group.js +152 -64
- package/src/components/sources-layer.js +397 -41
- package/src/components/waveform-builder.js +27 -9
- package/src/utils.js +90 -0
- package/src/view.js +90 -49
|
@@ -6,12 +6,22 @@
|
|
|
6
6
|
* @module invoker
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
define([
|
|
10
10
|
], function() {
|
|
11
11
|
'use strict';
|
|
12
12
|
|
|
13
|
+
// Use requestAnimationFrame for smoother animations when available
|
|
14
|
+
var raf = (typeof window !== 'undefined' && window.requestAnimationFrame) ||
|
|
15
|
+
function(fn) {
|
|
16
|
+
return setTimeout(fn, 16);
|
|
17
|
+
};
|
|
18
|
+
var caf = (typeof window !== 'undefined' && window.cancelAnimationFrame) ||
|
|
19
|
+
function(id) {
|
|
20
|
+
clearTimeout(id);
|
|
21
|
+
};
|
|
22
|
+
|
|
13
23
|
/**
|
|
14
|
-
* An invoker class for throttling.
|
|
24
|
+
* An invoker class for throttling and performance optimization.
|
|
15
25
|
*
|
|
16
26
|
* @class
|
|
17
27
|
* @alias Invoker
|
|
@@ -19,8 +29,14 @@
|
|
|
19
29
|
|
|
20
30
|
function Invoker() {
|
|
21
31
|
this._throttledFunc = {};
|
|
32
|
+
this._rafThrottled = {};
|
|
33
|
+
this._frameCallbacks = [];
|
|
34
|
+
this._frameScheduled = false;
|
|
22
35
|
}
|
|
23
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Throttle using setInterval - good for consistent timing
|
|
39
|
+
*/
|
|
24
40
|
Invoker.prototype.throttle = function(id, func, wait) {
|
|
25
41
|
var self = this;
|
|
26
42
|
|
|
@@ -50,6 +66,78 @@
|
|
|
50
66
|
}
|
|
51
67
|
};
|
|
52
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Throttle using requestAnimationFrame - ideal for visual updates
|
|
71
|
+
* Ensures function runs at most once per animation frame
|
|
72
|
+
*/
|
|
73
|
+
Invoker.prototype.throttleRAF = function(id, func) {
|
|
74
|
+
var self = this;
|
|
75
|
+
|
|
76
|
+
if (this._rafThrottled[id]) {
|
|
77
|
+
// Update the function but don't schedule again
|
|
78
|
+
this._rafThrottled[id].func = func;
|
|
79
|
+
this._rafThrottled[id].pending = true;
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
this._rafThrottled[id] = {
|
|
84
|
+
func: func,
|
|
85
|
+
pending: true,
|
|
86
|
+
rafId: null
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
function frame() {
|
|
90
|
+
if (self._rafThrottled[id] && self._rafThrottled[id].pending) {
|
|
91
|
+
self._rafThrottled[id].pending = false;
|
|
92
|
+
self._rafThrottled[id].func();
|
|
93
|
+
self._rafThrottled[id].rafId = raf(frame);
|
|
94
|
+
}
|
|
95
|
+
else if (self._rafThrottled[id]) {
|
|
96
|
+
delete self._rafThrottled[id];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
this._rafThrottled[id].rafId = raf(frame);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Cancel a RAF-throttled function
|
|
105
|
+
*/
|
|
106
|
+
Invoker.prototype.cancelThrottleRAF = function(id) {
|
|
107
|
+
if (this._rafThrottled[id]) {
|
|
108
|
+
if (this._rafThrottled[id].rafId) {
|
|
109
|
+
caf(this._rafThrottled[id].rafId);
|
|
110
|
+
}
|
|
111
|
+
delete this._rafThrottled[id];
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Schedule a callback for the next animation frame
|
|
117
|
+
* Multiple callbacks are batched together
|
|
118
|
+
*/
|
|
119
|
+
Invoker.prototype.scheduleFrame = function(callback) {
|
|
120
|
+
var self = this;
|
|
121
|
+
|
|
122
|
+
this._frameCallbacks.push(callback);
|
|
123
|
+
|
|
124
|
+
if (!this._frameScheduled) {
|
|
125
|
+
this._frameScheduled = true;
|
|
126
|
+
raf(function() {
|
|
127
|
+
self._frameScheduled = false;
|
|
128
|
+
var callbacks = self._frameCallbacks;
|
|
129
|
+
|
|
130
|
+
self._frameCallbacks = [];
|
|
131
|
+
for (var i = 0; i < callbacks.length; i++) {
|
|
132
|
+
callbacks[i]();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Creates a debounced function
|
|
140
|
+
*/
|
|
53
141
|
Invoker.prototype.debounce = function(func, wait, immediate) {
|
|
54
142
|
var timeout;
|
|
55
143
|
|
|
@@ -77,5 +165,133 @@
|
|
|
77
165
|
};
|
|
78
166
|
};
|
|
79
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Creates a leading-edge throttled function using RAF
|
|
170
|
+
* Executes immediately, then throttles subsequent calls
|
|
171
|
+
*/
|
|
172
|
+
Invoker.prototype.throttleLeading = function(func) {
|
|
173
|
+
var scheduled = false;
|
|
174
|
+
var savedArgs = null;
|
|
175
|
+
var savedThis = null;
|
|
176
|
+
|
|
177
|
+
return function throttled() {
|
|
178
|
+
if (scheduled) {
|
|
179
|
+
savedArgs = arguments;
|
|
180
|
+
// eslint-disable-next-line consistent-this
|
|
181
|
+
savedThis = this;
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
func.apply(this, arguments);
|
|
186
|
+
scheduled = true;
|
|
187
|
+
|
|
188
|
+
raf(function() {
|
|
189
|
+
scheduled = false;
|
|
190
|
+
if (savedArgs) {
|
|
191
|
+
func.apply(savedThis, savedArgs);
|
|
192
|
+
savedArgs = null;
|
|
193
|
+
savedThis = null;
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Creates a trailing-edge throttled function using RAF
|
|
201
|
+
* Collects calls and executes only the last one per frame
|
|
202
|
+
*/
|
|
203
|
+
Invoker.prototype.throttleTrailing = function(func) {
|
|
204
|
+
var rafId = null;
|
|
205
|
+
var savedArgs = null;
|
|
206
|
+
var savedThis = null;
|
|
207
|
+
|
|
208
|
+
return function throttled() {
|
|
209
|
+
savedArgs = arguments;
|
|
210
|
+
// eslint-disable-next-line consistent-this
|
|
211
|
+
savedThis = this;
|
|
212
|
+
|
|
213
|
+
if (rafId === null) {
|
|
214
|
+
rafId = raf(function() {
|
|
215
|
+
rafId = null;
|
|
216
|
+
func.apply(savedThis, savedArgs);
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Batch multiple function calls into a single execution
|
|
224
|
+
* Useful for accumulating updates before rendering
|
|
225
|
+
*/
|
|
226
|
+
Invoker.prototype.createBatcher = function(processFn, options) {
|
|
227
|
+
options = options || {};
|
|
228
|
+
var maxWait = options.maxWait || 100;
|
|
229
|
+
var items = [];
|
|
230
|
+
var timeout = null;
|
|
231
|
+
var lastFlush = 0;
|
|
232
|
+
|
|
233
|
+
function flush() {
|
|
234
|
+
if (items.length === 0) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
var batch = items;
|
|
238
|
+
|
|
239
|
+
items = [];
|
|
240
|
+
timeout = null;
|
|
241
|
+
lastFlush = Date.now();
|
|
242
|
+
processFn(batch);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
add: function(item) {
|
|
247
|
+
items.push(item);
|
|
248
|
+
|
|
249
|
+
if (timeout === null) {
|
|
250
|
+
var timeSinceLastFlush = Date.now() - lastFlush;
|
|
251
|
+
|
|
252
|
+
if (timeSinceLastFlush >= maxWait) {
|
|
253
|
+
raf(flush);
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
timeout = setTimeout(flush, Math.min(16, maxWait - timeSinceLastFlush));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
flush: flush,
|
|
261
|
+
size: function() {
|
|
262
|
+
return items.length;
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Destroy all pending operations
|
|
269
|
+
*/
|
|
270
|
+
Invoker.prototype.destroy = function() {
|
|
271
|
+
var id;
|
|
272
|
+
|
|
273
|
+
// Clear interval-based throttles
|
|
274
|
+
for (id in this._throttledFunc) {
|
|
275
|
+
if (Object.prototype.hasOwnProperty.call(this._throttledFunc, id)) {
|
|
276
|
+
clearInterval(this._throttledFunc[id].timer);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
this._throttledFunc = {};
|
|
280
|
+
|
|
281
|
+
// Clear RAF-based throttles
|
|
282
|
+
for (id in this._rafThrottled) {
|
|
283
|
+
if (Object.prototype.hasOwnProperty.call(this._rafThrottled, id)) {
|
|
284
|
+
if (this._rafThrottled[id].rafId) {
|
|
285
|
+
caf(this._rafThrottled[id].rafId);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
this._rafThrottled = {};
|
|
290
|
+
|
|
291
|
+
// Clear frame callbacks
|
|
292
|
+
this._frameCallbacks = [];
|
|
293
|
+
this._frameScheduled = false;
|
|
294
|
+
};
|
|
295
|
+
|
|
80
296
|
return Invoker;
|
|
81
297
|
});
|
|
@@ -195,8 +195,12 @@ define([
|
|
|
195
195
|
LineGroup.prototype.addSource = function(source, sourceGroup, sourcesAround) {
|
|
196
196
|
if (sourceGroup) {
|
|
197
197
|
this._sourcesGroup[source.id] = sourceGroup;
|
|
198
|
-
|
|
199
|
-
|
|
198
|
+
// Only move to this group if not currently being dragged
|
|
199
|
+
// (during drag, source stays in drag layer for z-order)
|
|
200
|
+
if (!sourceGroup.isActive()) {
|
|
201
|
+
if (!sourceGroup.getParent() || !sourceGroup.isDescendantOf(this._group)) {
|
|
202
|
+
sourceGroup.moveTo(this._group);
|
|
203
|
+
}
|
|
200
204
|
}
|
|
201
205
|
}
|
|
202
206
|
|
|
@@ -342,10 +346,6 @@ define([
|
|
|
342
346
|
|
|
343
347
|
delete this._sourcesGroup[source.id];
|
|
344
348
|
|
|
345
|
-
if (sourceGroup) {
|
|
346
|
-
sourceGroup.hideButKeepFocus();
|
|
347
|
-
}
|
|
348
|
-
|
|
349
349
|
return sourceGroup;
|
|
350
350
|
};
|
|
351
351
|
|
|
@@ -30,6 +30,7 @@ define([
|
|
|
30
30
|
|
|
31
31
|
this._automaticallyCreatedLineId = null;
|
|
32
32
|
this._automaticLineCreationPosition = null;
|
|
33
|
+
this._automaticLineCreationMouseY = null;
|
|
33
34
|
this._automaticLineCreationTimeout = null;
|
|
34
35
|
|
|
35
36
|
this._segmentsGroups = {};
|
|
@@ -141,7 +142,6 @@ define([
|
|
|
141
142
|
|
|
142
143
|
LineGroups.prototype._onLineHeightChanged = function() {
|
|
143
144
|
this.refreshLineYs();
|
|
144
|
-
this._view.updateTimeline();
|
|
145
145
|
};
|
|
146
146
|
|
|
147
147
|
LineGroups.prototype._onSourcesWrappingChanged = function(sources) {
|
|
@@ -282,10 +282,11 @@ define([
|
|
|
282
282
|
}
|
|
283
283
|
this._automaticLineCreationTimeout = null;
|
|
284
284
|
this._automaticLineCreationPosition = null;
|
|
285
|
+
this._automaticLineCreationMouseY = null;
|
|
285
286
|
this._automaticallyCreatedLineId = null;
|
|
286
287
|
};
|
|
287
288
|
|
|
288
|
-
LineGroups.prototype.manageAutomaticLineCreation = function(newLinePosition, initialPosition, sources) {
|
|
289
|
+
LineGroups.prototype.manageAutomaticLineCreation = function(newLinePosition, initialPosition, sources, mouseY) {
|
|
289
290
|
if (!Utils.isNullOrUndefined(this._automaticallyCreatedLineId)) {
|
|
290
291
|
return;
|
|
291
292
|
}
|
|
@@ -301,6 +302,7 @@ define([
|
|
|
301
302
|
}
|
|
302
303
|
|
|
303
304
|
this._automaticLineCreationPosition = newLinePosition;
|
|
305
|
+
this._automaticLineCreationMouseY = mouseY;
|
|
304
306
|
this._automaticLineCreationTimeout = setTimeout(function() {
|
|
305
307
|
this._automaticLineCreationTimeout = null;
|
|
306
308
|
|
|
@@ -344,9 +346,44 @@ define([
|
|
|
344
346
|
|
|
345
347
|
this._automaticallyCreatedLineId = automaticallyCreatedLineGroup.getId();
|
|
346
348
|
this._moveSourcesToPositionIfPossible(sources, newLinePosition);
|
|
349
|
+
|
|
350
|
+
// After creating and moving to the new line, nudge the vertical scroll so
|
|
351
|
+
// the pointer is closer to the center of that line.
|
|
352
|
+
this._nudgeFrameOffsetYToLineCenter(newLinePosition, this._automaticLineCreationMouseY);
|
|
353
|
+
|
|
354
|
+
// Notify that sources have been moved to a new line (for ghost preview updates)
|
|
355
|
+
this._peaks.emit('sources.delayedLineChange', sources);
|
|
347
356
|
}.bind(this), this._peaks.options.automaticLineCreationDelay);
|
|
348
357
|
};
|
|
349
358
|
|
|
359
|
+
LineGroups.prototype._nudgeFrameOffsetYToLineCenter = function(linePosition, mouseY) {
|
|
360
|
+
if (!this._peaks.options.enableVerticalScrolling) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (Utils.isNullOrUndefined(mouseY)) {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const lineGroup = this._lineGroupsByPosition[linePosition];
|
|
369
|
+
|
|
370
|
+
if (!lineGroup) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const targetCenterY = lineGroup.y() + (lineGroup.lineHeight() / 2);
|
|
375
|
+
const deltaY = targetCenterY - mouseY;
|
|
376
|
+
|
|
377
|
+
if (deltaY === 0) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const maxOffsetY = Math.max(0, this._view.getFullHeight() - this._view.getHeight());
|
|
382
|
+
const nextOffsetY = Utils.clamp(this._view.getFrameOffsetY() + deltaY, 0, maxOffsetY);
|
|
383
|
+
|
|
384
|
+
this._view.updateTimeline(null, nextOffsetY);
|
|
385
|
+
};
|
|
386
|
+
|
|
350
387
|
LineGroups.prototype.manageVerticalPosition = function(sources, startTime, endTime, mouseX, mouseY) {
|
|
351
388
|
if (Utils.isNullOrUndefined(mouseX)) {
|
|
352
389
|
return;
|
|
@@ -361,7 +398,7 @@ define([
|
|
|
361
398
|
}
|
|
362
399
|
|
|
363
400
|
if (linePos[0] !== linePos[1]) {
|
|
364
|
-
this.manageAutomaticLineCreation(linePos[0] + 1, position, sources);
|
|
401
|
+
this.manageAutomaticLineCreation(linePos[0] + 1, position, sources, mouseY);
|
|
365
402
|
}
|
|
366
403
|
else {
|
|
367
404
|
this.stopAutomaticLineCreation();
|