220 lines
6.7 KiB
JavaScript
Generated
220 lines
6.7 KiB
JavaScript
Generated
/*
|
|
* Licensed to the Apache Software Foundation (ASF) under one
|
|
* or more contributor license agreements. See the NOTICE file
|
|
* distributed with this work for additional information
|
|
* regarding copyright ownership. The ASF licenses this file
|
|
* to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
|
|
import * as timeline from './timeline';
|
|
|
|
function waitTime(time) {
|
|
return new Promise(resolve => {
|
|
setTimeout(() => {
|
|
resolve();
|
|
}, time);
|
|
});
|
|
};
|
|
|
|
export class ActionPlayback {
|
|
|
|
constructor() {
|
|
this._timer = 0;
|
|
this._current = 0;
|
|
|
|
this._ops = [];
|
|
this._currentOpIndex = 0;
|
|
|
|
this._isLastOpMousewheel = false;
|
|
}
|
|
|
|
getContext() {
|
|
return {
|
|
elapsedTime: this._elapsedTime,
|
|
currentOpIndex: this._currentOpIndex,
|
|
isLastOpMouseWheel: this._isLastOpMousewheel
|
|
}
|
|
}
|
|
|
|
_reset() {
|
|
this._currentOpIndex = 0;
|
|
this._current = Date.now();
|
|
this._elapsedTime = 0;
|
|
this._isLastOpMousewheel = false;
|
|
}
|
|
|
|
_restoreContext(ctx) {
|
|
this._elapsedTime = ctx.elapsedTime;
|
|
this._currentOpIndex = ctx.currentOpIndex;
|
|
this._isLastOpMousewheel = ctx.isLastOpMouseWheel;
|
|
}
|
|
|
|
async runAction(action, ctxToRestore) {
|
|
this.stop();
|
|
|
|
if (!action.ops.length) {
|
|
return;
|
|
}
|
|
|
|
this._ops = action.ops.slice().sort((a, b) => {
|
|
return a.time - b.time;
|
|
});
|
|
let firstOp = this._ops[0];
|
|
this._ops.forEach(op => {
|
|
op.time -= firstOp.time;
|
|
});
|
|
|
|
this._reset();
|
|
|
|
if (ctxToRestore) {
|
|
this._restoreContext(ctxToRestore);
|
|
// Usually restore context happens when page is reloaded after mouseup.
|
|
// In this case the _currentOpIndex is not increased yet.
|
|
this._currentOpIndex++;
|
|
}
|
|
|
|
let self = this;
|
|
|
|
async function takeScreenshot() {
|
|
// Pause timeline when doing screenshot to avoid screenshot needs taking a while.
|
|
timeline.pause();
|
|
await __VRT_ACTION_SCREENSHOT__(action);
|
|
timeline.resume();
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
async function tick() {
|
|
let current = Date.now();
|
|
let dTime = current - self._current;
|
|
self._elapsedTime += dTime;
|
|
self._current = current;
|
|
|
|
try {
|
|
// Execute all if there are multiple ops in one frame.
|
|
do {
|
|
const executed = await self._update(takeScreenshot);
|
|
if (!executed) {
|
|
break;
|
|
}
|
|
} while (true);
|
|
}
|
|
catch (e) {
|
|
// Stop running and throw error.
|
|
reject(e);
|
|
return;
|
|
}
|
|
|
|
if (self._currentOpIndex >= self._ops.length) {
|
|
// Finished
|
|
resolve();
|
|
}
|
|
else {
|
|
self._timer = setTimeout(tick, 0);
|
|
}
|
|
}
|
|
tick();
|
|
});
|
|
}
|
|
|
|
|
|
stop() {
|
|
if (this._timer) {
|
|
clearTimeout(this._timer);
|
|
this._timer = 0;
|
|
}
|
|
}
|
|
|
|
async _update(takeScreenshot) {
|
|
let op = this._ops[this._currentOpIndex];
|
|
|
|
if (!op || (op.time > this._elapsedTime)) {
|
|
// Not yet.
|
|
return;
|
|
}
|
|
|
|
let screenshotTaken = false;
|
|
switch (op.type) {
|
|
case 'mousedown':
|
|
// Pause timeline to avoid frame not sync.
|
|
timeline.pause();
|
|
await __VRT_MOUSE_MOVE__(op.x, op.y);
|
|
await __VRT_MOUSE_DOWN__();
|
|
timeline.resume();
|
|
break;
|
|
case 'mouseup':
|
|
timeline.pause();
|
|
await __VRT_MOUSE_MOVE__(op.x, op.y);
|
|
await __VRT_MOUSE_UP__();
|
|
if (window.__VRT_RELOAD_TRIGGERED__) {
|
|
return;
|
|
}
|
|
timeline.resume();
|
|
break;
|
|
case 'mousemove':
|
|
timeline.pause();
|
|
await __VRT_MOUSE_MOVE__(op.x, op.y);
|
|
timeline.resume();
|
|
break;
|
|
case 'mousewheel':
|
|
let element = document.elementFromPoint(op.x, op.y);
|
|
// Here dispatch mousewheel event because echarts used it.
|
|
// TODO Consider upgrade?
|
|
let event = new WheelEvent('mousewheel', {
|
|
// PENDING
|
|
// Needs inverse delta?
|
|
deltaY: op.deltaY,
|
|
clientX: op.x, clientY: op.y,
|
|
// Needs bubble to parent container
|
|
bubbles: true
|
|
});
|
|
element.dispatchEvent(event);
|
|
this._isLastOpMousewheel = true;
|
|
break;
|
|
case 'screenshot':
|
|
await takeScreenshot();
|
|
screenshotTaken = true;
|
|
break;
|
|
case 'valuechange':
|
|
const selector = document.querySelector(op.selector);
|
|
selector.value = op.value;
|
|
// changing value via js won't trigger `change` event, so trigger it manually
|
|
selector.dispatchEvent(new Event('change'));
|
|
break;
|
|
}
|
|
|
|
this._currentOpIndex++;
|
|
|
|
// If next op is an auto screenshot
|
|
let nextOp = this._ops[this._currentOpIndex];
|
|
if (nextOp && nextOp.type === 'screenshot-auto') {
|
|
let delay = nextOp.delay == null ? 400 : nextOp.delay;
|
|
// TODO Configuration time
|
|
await waitTime(delay);
|
|
await takeScreenshot();
|
|
screenshotTaken = true;
|
|
this._currentOpIndex++;
|
|
}
|
|
|
|
if (this._isLastOpMousewheel && op.type !== 'mousewheel') {
|
|
// Only take screenshot after mousewheel finished
|
|
if (!screenshotTaken) {
|
|
await takeScreenshot();
|
|
}
|
|
this._isLastOpMousewheel = false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|