test: add test case for viewport with svg renderer (#5441)

* test: add test case for viewport with svg renderer

* chore: rename file

* chore: update test case

* chore: keep the comment

* feat: add createGraphCavas, and use it in main.ts

* chore: move viewport.spec.ts to unit

* test: use real graph instance instead of mock

* chore: remove unused code

* chore: fix ci
This commit is contained in:
hustcc 2024-02-19 20:54:12 +08:00 committed by GitHub
parent 0f21e8d6fa
commit a4346ddc2b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 277 additions and 301 deletions

View File

@ -1,21 +1,7 @@
import type { G6Spec } from '../../../src';
import { DataController } from '../../../src/runtime/data';
import { ElementController } from '../../../src/runtime/element';
import type { RuntimeContext } from '../../../src/runtime/types';
import { Graph } from '../../mock';
import { createGraph } from '../../mock';
import type { AnimationTestCase } from '../types';
const createContext = (canvas: any, options: G6Spec): RuntimeContext => {
const model = new DataController();
model.setData(options.data || {});
return {
canvas,
graph: new Graph() as any,
options,
model,
};
};
export const controllerElementPosition: AnimationTestCase = async (context) => {
const { canvas } = context;
@ -51,15 +37,14 @@ export const controllerElementPosition: AnimationTestCase = async (context) => {
edge: { style: {} },
};
const elementContext = createContext(canvas, options);
const graph = createGraph(options, canvas);
const r = await graph.draw();
await r?.finished;
const elementController = new ElementController(elementContext);
// @ts-expect-error context is private.
const element = graph.context.element!;
const renderResult = await elementController.render(elementContext);
await renderResult?.finished;
const result = elementController.updateNodeLikePosition(
const result = element.updateNodeLikePosition(
{
'node-1': [250, 100],
'node-2': [175, 200],

View File

@ -1,21 +1,7 @@
import type { G6Spec } from '../../../src';
import { DataController } from '../../../src/runtime/data';
import { ElementController } from '../../../src/runtime/element';
import type { RuntimeContext } from '../../../src/runtime/types';
import { Graph } from '../../mock';
import { createGraph } from '../../mock';
import type { AnimationTestCase } from '../types';
const createContext = (canvas: any, options: G6Spec): RuntimeContext => {
const model = new DataController();
model.setData(options.data || {});
return {
canvas,
graph: new Graph() as any,
options,
model,
};
};
export const controllerElementState: AnimationTestCase = async (context) => {
const { canvas } = context;
@ -71,15 +57,11 @@ export const controllerElementState: AnimationTestCase = async (context) => {
},
};
const elementContext = createContext(canvas, options);
const graph = createGraph(options, canvas);
const r = await graph.draw();
await r?.finished;
const elementController = new ElementController(elementContext);
const renderResult = await elementController.render(elementContext);
await renderResult?.finished;
elementContext.model.updateData({
graph.updateData({
nodes: [
{ id: 'node-1', style: { states: [] } },
{ id: 'node-2', style: { states: ['active'] } },
@ -91,8 +73,7 @@ export const controllerElementState: AnimationTestCase = async (context) => {
],
});
const result = await elementController.render(elementContext);
const result = await graph.draw();
return result;
};

View File

@ -1,21 +1,7 @@
import type { G6Spec } from '../../../src';
import { DataController } from '../../../src/runtime/data';
import { ElementController } from '../../../src/runtime/element';
import type { RuntimeContext } from '../../../src/runtime/types';
import { Graph } from '../../mock';
import { createGraph } from '../../mock';
import type { AnimationTestCase } from '../types';
const createContext = (canvas: any, options: G6Spec): RuntimeContext => {
const model = new DataController();
model.setData(options.data || {});
return {
canvas,
graph: new Graph() as any,
options,
model,
};
};
export const controllerElement: AnimationTestCase = async (context) => {
const { canvas } = context;
@ -44,26 +30,21 @@ export const controllerElement: AnimationTestCase = async (context) => {
},
};
const elementContext = createContext(canvas, options);
const graph = createGraph(options, canvas);
const r = await graph.draw();
await r?.finished;
const elementController = new ElementController(elementContext);
const renderResult = await elementController.render(elementContext);
await renderResult?.finished;
elementContext.model.addNodeData([
graph.addNodeData([
{ id: 'node-4', style: { x: 50, y: 200, stroke: 'orange' } },
{ id: 'node-5', style: { x: 75, y: 150, stroke: 'purple' } },
{ id: 'node-6', style: { x: 200, y: 100, stroke: 'cyan' } },
]);
elementContext.model.removeNodeData(['node-1']);
graph.removeNodeData(['node-1']);
elementContext.model.updateNodeData([{ id: 'node-2', style: { x: 200, y: 200, stroke: 'green' } }]);
const result = await elementController.render(elementContext);
graph.updateNodeData([{ id: 'node-2', style: { x: 200, y: 200, stroke: 'green' } }]);
const result = await graph.draw();
return result;
};

View File

@ -1,21 +1,7 @@
import type { G6Spec } from '../../../src';
import { DataController } from '../../../src/runtime/data';
import { ElementController } from '../../../src/runtime/element';
import type { RuntimeContext } from '../../../src/runtime/types';
import { Graph } from '../../mock';
import { createGraph } from '../../mock';
import type { StaticTestCase } from '../types';
const createContext = (canvas: any, options: G6Spec): RuntimeContext => {
const model = new DataController();
model.setData(options.data || {});
return {
canvas,
graph: new Graph() as any,
options,
model,
};
};
export const controllerElementPosition: StaticTestCase = async (context) => {
const { canvas, animation } = context;
@ -55,15 +41,10 @@ export const controllerElementPosition: StaticTestCase = async (context) => {
},
};
const elementContext = createContext(canvas, options);
const graph = createGraph(options, canvas);
await graph.render();
const elementController = new ElementController(elementContext);
const result = await elementController.render(elementContext);
await result?.finished;
elementController.updateNodeLikePosition(
await graph.translateElementTo(
{
'node-1': [250, 100],
'node-2': [175, 200],
@ -74,6 +55,4 @@ export const controllerElementPosition: StaticTestCase = async (context) => {
},
animation,
);
await elementController.render(elementContext);
};

View File

@ -1,22 +1,8 @@
import type { G6Spec } from '../../../src';
import { DataController } from '../../../src/runtime/data';
import { ElementController } from '../../../src/runtime/element';
import type { RuntimeContext } from '../../../src/runtime/types';
import { idOf } from '../../../src/utils/id';
import { Graph } from '../../mock';
import { createGraph } from '../../mock';
import type { StaticTestCase } from '../types';
const createContext = (canvas: any, options: G6Spec): RuntimeContext => {
const model = new DataController();
model.setData(options.data || {});
return {
canvas,
graph: new Graph() as any,
options,
model,
};
};
export const controllerElementVisibility: StaticTestCase = async (context) => {
const { canvas, animation, toMatchSVGSnapshot, env } = context;
@ -48,29 +34,18 @@ export const controllerElementVisibility: StaticTestCase = async (context) => {
},
};
const elementContext = createContext(canvas, options);
const elementController = new ElementController(elementContext);
const result = await elementController.render(elementContext);
await result?.finished;
const graph = createGraph(options, canvas);
await graph.render();
const hide = () =>
elementController.setElementsVisibility(
['node-3', idOf(options.data!.edges![1]), idOf(options.data!.edges![2])],
'hidden',
);
graph.setElementVisibility(['node-3', idOf(options.data!.edges![1]), idOf(options.data!.edges![2])], 'hidden');
const show = () =>
elementController.setElementsVisibility(
['node-3', idOf(options.data!.edges![1]), idOf(options.data!.edges![2])],
'visible',
);
graph.setElementVisibility(['node-3', idOf(options.data!.edges![1]), idOf(options.data!.edges![2])], 'visible');
if (env === 'test') {
await hide()?.finished;
await hide();
await toMatchSVGSnapshot?.('hidden');
await show()?.finished;
await show();
}
controllerElementVisibility.form = [

View File

@ -1,21 +1,7 @@
import type { G6Spec } from '../../../src';
import { DataController } from '../../../src/runtime/data';
import { ElementController } from '../../../src/runtime/element';
import type { RuntimeContext } from '../../../src/runtime/types';
import { Graph } from '../../mock';
import { createGraph } from '../../mock';
import type { StaticTestCase } from '../types';
const createContext = (canvas: any, options: G6Spec): RuntimeContext => {
const model = new DataController();
model.setData(options.data || {});
return {
canvas,
graph: new Graph() as any,
options,
model,
};
};
export const controllerElementZIndex: StaticTestCase = async (context) => {
const { canvas, animation, toMatchSVGSnapshot, env } = context;
@ -38,17 +24,12 @@ export const controllerElementZIndex: StaticTestCase = async (context) => {
},
};
const elementContext = createContext(canvas, options);
const graph = createGraph(options, canvas);
await graph.render();
const elementController = new ElementController(elementContext);
const result = await elementController.render(elementContext);
await result?.finished;
const front = () => elementController.setElementZIndex('node-2', 'front');
const back = () => elementController.setElementZIndex('node-2', 'back');
const to = (zIndex: number) => elementController.setElementZIndex('node-2', zIndex);
const front = () => graph.setElementZIndex('node-2', 'front');
const back = () => graph.setElementZIndex('node-2', 'back');
const to = (zIndex: number) => graph.setElementZIndex('node-2', zIndex);
if (env === 'test') {
front();

View File

@ -1,21 +1,7 @@
import type { G6Spec } from '../../../src';
import { DataController } from '../../../src/runtime/data';
import { ElementController } from '../../../src/runtime/element';
import type { RuntimeContext } from '../../../src/runtime/types';
import { Graph } from '../../mock';
import { createGraph } from '../../mock';
import type { StaticTestCase } from '../types';
const createContext = (canvas: any, options: G6Spec): RuntimeContext => {
const model = new DataController();
model.setData(options.data || {});
return {
canvas,
graph: new Graph() as any,
options,
model,
};
};
export const controllerElement: StaticTestCase = async (context) => {
const { canvas, animation } = context;
@ -46,11 +32,7 @@ export const controllerElement: StaticTestCase = async (context) => {
},
};
const elementContext = createContext(canvas, options);
const graph = createGraph(options, canvas);
const elementController = new ElementController(elementContext);
const result = await elementController.render(elementContext);
await result?.finished;
await graph.render();
};

View File

@ -1,6 +1,6 @@
import '../../src/preset';
import * as animationCases from '../demo/animation';
import { createNodeGCanvas } from './utils/create-node-g-canvas';
import { createGraphCanvas } from '../mock/create';
import { getCases } from './utils/get-cases';
import { sleep } from './utils/sleep';
import './utils/use-snapshot-matchers';
@ -10,7 +10,7 @@ describe('static', () => {
for (const [name, testCase] of cases) {
it(`[animation]: ${name}`, async () => {
const canvas = createNodeGCanvas();
const canvas = createGraphCanvas();
try {
const { times = [], preprocess, postprocess } = testCase;

View File

@ -1,6 +1,6 @@
import '../../src/preset';
import * as staticCases from '../demo/static/common';
import { createNodeGCanvas } from './utils/create-node-g-canvas';
import { createGraphCanvas } from '../mock/create';
import { getCases } from './utils/get-cases';
import { sleep } from './utils/sleep';
import './utils/use-snapshot-matchers';
@ -10,7 +10,7 @@ describe('static', () => {
for (const [name, testCase] of cases) {
it(`[static]: ${name}`, async () => {
const canvas = createNodeGCanvas();
const canvas = createGraphCanvas();
try {
const { preprocess, postprocess } = testCase;

View File

@ -1,29 +0,0 @@
import { resetEntityCounter } from '@antv/g';
import { Plugin as DragAndDropPlugin } from '@antv/g-plugin-dragndrop';
import { Renderer as SVGRenderer } from '@antv/g-svg';
import { Canvas } from '../../../src/runtime/canvas';
import { OffscreenCanvasContext } from './offscreen-canvas-context';
export function createNodeGCanvas(dom?: HTMLDivElement, width = 500, height = 500) {
const container = dom || document.createElement('div');
container.style.width = `${width}px`;
container.style.height = `${height}px`;
resetEntityCounter();
const offscreenNodeCanvas = {
getContext: () => context,
} as unknown as HTMLCanvasElement;
const context = new OffscreenCanvasContext(offscreenNodeCanvas);
const renderer = new SVGRenderer();
renderer.registerPlugin(new DragAndDropPlugin({ dragstartDistanceThreshold: 10 }));
return new Canvas({
container,
width,
height,
renderer: () => new SVGRenderer(),
// @ts-expect-error offscreenCanvas is not in the type definition
document: container.ownerDocument,
offscreenCanvas: offscreenNodeCanvas,
});
}

View File

@ -1,11 +1,8 @@
import { Renderer as CanvasRenderer } from '@antv/g-canvas';
import { Renderer as SVGRenderer } from '@antv/g-svg';
import { Renderer as WebGLRenderer } from '@antv/g-webgl';
import '../src/preset';
import { Canvas } from '../src/runtime/canvas';
import * as animations from './demo/animation';
import * as statics from './demo/static';
import type { TestCase } from './demo/types';
import { createGraphCanvas } from './mock';
const CASES = {
statics,
@ -54,32 +51,14 @@ function loadCasesList(select: HTMLSelectElement) {
});
}
function onchange(testCase: TestCase, rendererName: string, animation: boolean) {
const renderer = getRenderer(rendererName);
const canvas = new Canvas({
width: 500,
height: 500,
container: document.getElementById('container')!,
renderer,
});
function onchange(testCase: TestCase, renderer: string, animation: boolean) {
const canvas = createGraphCanvas(document.getElementById('container'), 500, 500, renderer);
return canvas.init().then(async () => {
await testCase({ canvas, animation, env: 'dev' });
});
}
function getRenderer(rendererName: string) {
switch (rendererName) {
case 'webgl':
return () => new WebGLRenderer();
case 'svg':
return () => new SVGRenderer();
case 'canvas':
return () => new CanvasRenderer();
default:
return undefined;
}
}
function initialize() {
document.getElementById('container')?.remove();
const container = document.createElement('div');

View File

@ -0,0 +1,76 @@
import type { IRenderer } from '@antv/g';
import { resetEntityCounter } from '@antv/g';
import { Renderer as CanvasRenderer } from '@antv/g-canvas';
import { Plugin as DragAndDropPlugin } from '@antv/g-plugin-dragndrop';
import { Renderer as SVGRenderer } from '@antv/g-svg';
import { Renderer as WebGLRenderer } from '@antv/g-webgl';
import type { G6Spec } from '../../src';
import { Graph } from '../../src';
import { Canvas } from '../../src/runtime/canvas';
import { OffscreenCanvasContext } from './offscreen-canvas-context';
/**
* Create a graph with the given options, and use mock Canvas.
* @param options - options
* @param graphCanvas - canvas
* @returns Graph instance
*/
export function createGraph(options: G6Spec, graphCanvas?: Canvas) {
const { width, height } = options;
const canvas = graphCanvas || createGraphCanvas(undefined, width, height);
return new Graph({
...options,
container: canvas, // Use mock Canvas.
});
}
function getRenderer(renderer: string) {
switch (renderer) {
case 'webgl':
return new WebGLRenderer();
case 'svg':
return new SVGRenderer();
case 'canvas':
return new CanvasRenderer();
default:
return new SVGRenderer();
}
}
/**
* Create graph canvas with config.
* @param dom - dom
* @param width - width
* @param height - height
* @param renderer - render
* @returns instance
*/
export function createGraphCanvas(
dom?: null | HTMLElement,
width: number = 500,
height: number = 500,
renderer: string = 'svg',
) {
const container = dom || document.createElement('div');
container.style.width = `${width}px`;
container.style.height = `${height}px`;
resetEntityCounter();
const offscreenNodeCanvas = {
getContext: () => context,
} as unknown as HTMLCanvasElement;
const context = new OffscreenCanvasContext(offscreenNodeCanvas);
const instance = getRenderer(renderer) as any as IRenderer;
instance.registerPlugin(new DragAndDropPlugin({ dragstartDistanceThreshold: 10 }));
return new Canvas({
container,
width,
height,
renderer: () => instance,
// @ts-expect-error document offscreenCanvas is not in the type definition
document: container.ownerDocument,
offscreenCanvas: offscreenNodeCanvas,
});
}

View File

@ -1,3 +0,0 @@
import EventEmitter from '@antv/event-emitter';
export class Graph extends EventEmitter {}

View File

@ -1 +1 @@
export * from './graph';
export { createGraph, createGraphCanvas } from './create';

View File

@ -1,35 +1,10 @@
import { omit } from '@antv/util';
import type { G6Spec } from '../../../src';
import * as BUILT_IN_PALETTES from '../../../src/palettes';
import '../../../src/preset';
import { DataController } from '../../../src/runtime/data';
import { ElementController } from '../../../src/runtime/element';
import type { RuntimeContext } from '../../../src/runtime/types';
import { light as LIGHT_THEME } from '../../../src/themes';
import { idOf } from '../../../src/utils/id';
import { Graph } from '../../mock';
class Canvas {
init() {
return Promise.resolve();
}
children: unknown[] = [];
appendChild(node: unknown) {
this.children.push(node);
return node;
}
}
const createContext = (options: G6Spec): RuntimeContext => {
const model = new DataController();
model.setData(options.data || {});
return {
canvas: new Canvas() as any,
graph: new Graph() as any,
options,
model,
};
};
import { createGraph } from '../../mock';
describe('ElementController', () => {
it('static', async () => {
@ -83,17 +58,16 @@ describe('ElementController', () => {
palette: 'blues',
},
};
const graph = createGraph(options);
const context = createContext(options);
await graph.render();
const elementController = new ElementController(context);
// @ts-expect-error context is private.
const elementController = graph.context.element!;
const edge1Id = idOf(options.data!.edges![0]);
const edge2Id = idOf(options.data!.edges![1]);
// @ts-expect-error computeStyle is private
elementController.computeStyle();
expect(elementController.getDataStyle('node', 'node-1')).toEqual(options.data!.nodes![0].style || {});
// 没有属性 / no style
expect(elementController.getDataStyle('node', 'node-2')).toEqual({});
@ -181,13 +155,14 @@ describe('ElementController', () => {
color: BUILT_IN_PALETTES.spectral[2],
});
expect(elementController.getElementComputedStyle('edge', edge1Id)).toEqual({
expect(omit(elementController.getElementComputedStyle('edge', edge1Id), ['sourceNode', 'targetNode'])).toEqual({
...LIGHT_THEME.edge?.style,
sourcePoint: [0, 0, 0],
targetPoint: [0, 0, 0],
color: BUILT_IN_PALETTES.oranges.at(-1),
});
expect(elementController.getElementComputedStyle('edge', edge2Id)).toEqual({
expect(omit(elementController.getElementComputedStyle('edge', edge2Id), ['sourceNode', 'targetNode'])).toEqual({
...LIGHT_THEME.edge?.style,
...LIGHT_THEME.edge?.state?.active,
...LIGHT_THEME.edge?.state?.selected,
@ -202,14 +177,13 @@ describe('ElementController', () => {
color: BUILT_IN_PALETTES.oranges.at(-2),
});
expect(elementController.getElementComputedStyle('combo', 'combo-1')).toEqual({
const comboStyle = elementController.getElementComputedStyle('combo', 'combo-1');
expect(omit(comboStyle, ['children'])).toEqual({
...LIGHT_THEME.combo?.style,
color: BUILT_IN_PALETTES.blues[0],
children: {
// 值为 undefined 是因为在非运行时环境 / The value is undefined because it is not in the runtime environment
'node-3': undefined,
},
});
expect(Object.keys(comboStyle.children)).toEqual(['node-3']);
});
it('mock runtime', async () => {
@ -228,11 +202,12 @@ describe('ElementController', () => {
},
};
const context = createContext(options);
const graph = createGraph(options);
const elementController = new ElementController(context);
await graph.render();
await elementController.render(context);
// @ts-expect-error context is private.
const elementController = graph.context.element!;
expect(elementController.getNodes().length).toBe(3);
expect(elementController.getEdges().length).toBe(2);

View File

@ -0,0 +1,89 @@
import { Graph } from '../../../src';
import data from '../../dataset/cluster.json';
import { createGraph } from '../../mock/create';
const options = {
width: 500,
height: 500,
data,
theme: 'light',
node: {
style: {
width: 20,
height: 20,
},
state: {
active: { fill: '#dbedd0' },
},
},
edge: {
style: {},
state: {
active: { stroke: 'pink', lineWidth: 3 },
},
},
layout: {
type: 'd3force',
preventOverlap: true,
nodeSize: 20,
animation: true,
},
};
describe('ViewportController', () => {
let graph: Graph;
beforeAll(async () => {
graph = createGraph(options);
await graph.render();
});
it('viewport center', () => {
expect(graph.getViewportCenter()).toEqual(graph.getPosition());
const [x, y] = graph.getViewportCenter();
expect(x).toBeCloseTo(250);
expect(y).toBeCloseTo(250);
});
it('viewport zoom', async () => {
expect(graph.getZoom()).toBe(1);
await graph.zoomBy(0.5);
expect(graph.getZoom()).toBe(0.5);
await graph.zoomBy(4);
expect(graph.getZoom()).toBe(2);
await graph.zoomTo(1);
expect(graph.getZoom()).toBe(1);
graph.setZoomRange([0.1, 10]);
expect(graph.getZoomRange()).toEqual([0.1, 10]);
});
it('viewport translate', async () => {
await graph.translateBy([100, 100]);
let [x, y] = graph.getPosition();
expect(x).toBeCloseTo(350);
expect(y).toBeCloseTo(350);
await graph.translateTo([200, 200]);
[x, y] = graph.getPosition();
expect(x).toBeCloseTo(450);
expect(y).toBeCloseTo(450);
});
it('viewport rotate', async () => {
await graph.rotateBy(Math.PI / 4);
expect(graph.getRotation()).toBe(Math.PI / 4);
await graph.rotateBy(Math.PI / 2);
expect(graph.getRotation()).toBe((Math.PI * 3) / 4);
await graph.rotateTo(Math.PI / 2);
expect(graph.getRotation()).toBe(Math.PI / 2);
});
afterAll(() => {
graph.destroy();
});
});

View File

@ -0,0 +1,15 @@
import { delay } from '../../../src/utils/delay';
describe('delay', () => {
it('should delay for the specified time', async () => {
const startTime = Date.now();
const delayTime = 200; // milliseconds
await delay(delayTime);
const endTime = Date.now();
const elapsedTime = endTime - startTime;
expect(elapsedTime).toBeGreaterThanOrEqual(delayTime);
});
});

View File

@ -557,6 +557,9 @@ export class ElementController {
renderContext,
);
// todo: 不应该返回动画相关的信息,如果确实外部需要,那么应该提供方法获取这类 context 信息。
// animation 不是一个需要给开发者强制暴露的信息,因此不应该在这里返回。
// updateNodeLikePosition 也是同理,这些都会影响到 Graph API 的封装。
return this.postRender(taskId, () => {
this.emit(GraphEvent.AFTER_RENDER);
});

View File

@ -348,10 +348,12 @@ export class Graph extends EventEmitter {
* <zh/>
*
* <en/> Draw elements
* @returns <zh/> | <en/> draw result
*/
public async draw() {
await this.prepare();
await this.context.element?.render(this.context);
// todo: 和 element.draw 一样,不应该返回任何动画相关的信息。
return await this.context.element?.render(this.context);
}
public async layout(): Promise<void> {
@ -369,11 +371,12 @@ export class Graph extends EventEmitter {
}
public destroy(): void {
const { layout, element, model, canvas } = this.context;
const { layout, element, model, canvas, viewport } = this.context;
layout?.destroy();
element?.destroy();
model.destroy();
canvas?.destroy();
viewport?.destroy();
this.options = {};
// @ts-expect-error force delete
delete this.context;
@ -439,6 +442,10 @@ export class Graph extends EventEmitter {
return this.context.viewport!.rotate({ mode: 'absolute', value: angle, origin }, effectTiming);
}
public getRotation(): number {
return this.context.viewport!.getRotation();
}
public translateBy(
offset: Point,
origin?: Point,

View File

@ -7,6 +7,7 @@ import type {
ViewportAnimationEffectTiming,
ZoomOptions,
} from '../types';
import { delay } from '../utils/delay';
import type { RuntimeContext } from './types';
export class ViewportController {
@ -89,11 +90,14 @@ export class ViewportController {
this.context.graph.emit(GraphEvent.BEFORE_VIEWPORT_ANIMATION, options);
return new Promise<void>((resolve) => {
/**
* todo: gotoLandmark onfinish
*/
const onfinish = () => {
this.context.graph.emit(GraphEvent.AFTER_VIEWPORT_ANIMATION, options);
resolve();
};
resolveWhenTimeout(onfinish, effectTiming.duration);
delay(effectTiming.duration).then(onfinish);
this.camera.gotoLandmark(
this.createLandmark(
@ -129,7 +133,7 @@ export class ViewportController {
this.context.graph.emit(GraphEvent.AFTER_VIEWPORT_ANIMATION, options);
resolve();
};
resolveWhenTimeout(onfinish, effectTiming.duration);
delay(effectTiming.duration).then(onfinish);
this.camera.gotoLandmark(
this.createLandmark({ roll: mode === 'relative' ? camera.getRoll() + angle : angle }),
@ -163,7 +167,7 @@ export class ViewportController {
this.context.graph.emit(GraphEvent.AFTER_VIEWPORT_ANIMATION, options);
resolve();
};
resolveWhenTimeout(onfinish, effectTiming.duration);
delay(effectTiming.duration).then(onfinish);
this.camera.gotoLandmark(this.createLandmark({ zoom: targetRatio }), { ...effectTiming, onfinish });
});
@ -185,18 +189,3 @@ export class ViewportController {
this.cancelAnimation();
}
}
/**
* <zh/> resolve
*
* <en/> Execute resolve after a period of time
* @param resolve - <zh/> resolve | <en/> resolve function
* @param timeout - <zh/> | <en/> delay time
* @description
* <zh/> gotoLandmark onfinish
*
* <en/> There is a problem with gotoLandmark, which may not trigger onfinish with a certain probability, so a timeout needs to be set
*/
function resolveWhenTimeout(resolve: () => void, timeout: number = 500) {
setTimeout(resolve, timeout);
}

View File

@ -0,0 +1,11 @@
/**
* <zh/>
* <en/> delay a period of time
* @param timeout - <zh/> | <en/> delay time
* @returns - <zh/> Promise<void> | <en/> Promise<void>
*/
export function delay(timeout: number = 500) {
return new Promise((resolve) => {
setTimeout(resolve, timeout);
});
}