From 046215898e06ffda271a88bd55bdbf859c007b40 Mon Sep 17 00:00:00 2001 From: Paulo Gustavo Veiga Date: Wed, 16 Nov 2022 21:12:33 -0800 Subject: [PATCH] Add delay render support. --- .../editor/cypress/e2e/relationship.cy.js | 6 +++ .../editor/src/classes/model/editor/index.ts | 6 +-- packages/editor/src/components/index.tsx | 10 +++-- .../playground/map-render/js/viewmode.tsx | 5 ++- .../playground/map-render/samples/huge2.wxml | 11 +++++ packages/mindplot/src/components/Designer.ts | 30 +++++++------ .../src/components/MindplotWebComponent.ts | 20 ++++++--- .../src/components/PersistenceManager.ts | 9 ++-- packages/mindplot/src/components/Workspace.ts | 45 +++++++++++++++++++ .../web2d/src/components/peer/svg/TextPeer.js | 7 +-- 10 files changed, 110 insertions(+), 39 deletions(-) create mode 100644 packages/editor/test/playground/map-render/samples/huge2.wxml diff --git a/packages/editor/cypress/e2e/relationship.cy.js b/packages/editor/cypress/e2e/relationship.cy.js index ec25e895..a6a161b2 100644 --- a/packages/editor/cypress/e2e/relationship.cy.js +++ b/packages/editor/cypress/e2e/relationship.cy.js @@ -11,10 +11,16 @@ context('Relationship Topics', () => { cy.contains('Try it Now!').first().click(); cy.get('[test-id="11-15-relationship"]').first().click({ force: true }); + cy.get('[test-id="11-15-relationship"]').should('exist'); + cy.matchImageSnapshot('addRelationship'); }); it('Delete Relationship', () => { + cy.contains('Features').first().click(); + cy.get(`[aria-label="Add Relationship"]`).first().click(); + cy.contains('Try it Now!').first().click(); + cy.get('[test-id="11-15-relationship"]').click({ force: true }); cy.get('body').type('{backspace}'); diff --git a/packages/editor/src/classes/model/editor/index.ts b/packages/editor/src/classes/model/editor/index.ts index bd455b4d..a95b6699 100644 --- a/packages/editor/src/classes/model/editor/index.ts +++ b/packages/editor/src/classes/model/editor/index.ts @@ -32,7 +32,7 @@ class Editor { } isMapLoadded(): boolean { - return this.component?.getDesigner()?.getMindmap() != null; + return this.component.isLoaded(); } save(minor: boolean): void { @@ -57,9 +57,9 @@ class Editor { mapId: string, persistenceManager: PersistenceManager, widgetManager: WidgetManager, - ): void { + ): Promise { this.component.buildDesigner(persistenceManager, widgetManager); - this.component.loadMap(mapId); + return this.component.loadMap(mapId); } registerEvents(canvasUpdate: (timestamp: number) => void, capability: Capability): void { diff --git a/packages/editor/src/components/index.tsx b/packages/editor/src/components/index.tsx index 79799f72..8dadf9ac 100644 --- a/packages/editor/src/components/index.tsx +++ b/packages/editor/src/components/index.tsx @@ -79,10 +79,12 @@ const Editor = ({ const capability = new Capability(options.mode, mapInfo.isLocked()); const mindplotRef = useCallback((component: MindplotWebComponent) => { - // Initialized model ... const model = new Model(component); - model.loadMindmap(mapInfo.getId(), persistenceManager, widgetManager); - model.registerEvents(setCanvasUpdate, capability); + + // Force refresh after map load ... + model.loadMindmap(mapInfo.getId(), persistenceManager, widgetManager).then(() => { + setCanvasUpdate(Date.now()); + }); setModel(model); }, []); @@ -137,7 +139,7 @@ const Editor = ({ message={mapInfo.isLocked() ? mapInfo.getLockedMessage() : ''} /> - {!model && ( + {!model?.isMapLoadded() && ( console.log('action called:', action)} onLoad={initialization} - />); + />, +); diff --git a/packages/editor/test/playground/map-render/samples/huge2.wxml b/packages/editor/test/playground/map-render/samples/huge2.wxml new file mode 100644 index 00000000..15943d2b --- /dev/null +++ b/packages/editor/test/playground/map-render/samples/huge2.wxml @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/packages/mindplot/src/components/Designer.ts b/packages/mindplot/src/components/Designer.ts index abd765c2..6d602c91 100644 --- a/packages/mindplot/src/components/Designer.ts +++ b/packages/mindplot/src/components/Designer.ts @@ -577,14 +577,12 @@ class Designer extends Events { return { zoom: model.getZoom() }; } - /** - * @param {mindplot.Mindmap} mindmap - * @throws will throw an error if mindmapModel is null or undefined - */ - loadMap(mindmap: Mindmap): void { + loadMap(mindmap: Mindmap): Promise { $assert(mindmap, 'mindmapModel can not be null'); this._mindmap = mindmap; + this._workspace.enableQueueRender(true); + // Init layout manager ... const size = { width: 25, height: 25 }; const layoutManager = new LayoutManager(mindmap.getCentralTopic().getId(), size); @@ -601,23 +599,29 @@ class Designer extends Events { // Building node graph ... const branches = mindmap.getBranches(); + + const nodesGraph: Topic[] = []; branches.forEach((branch) => { const nodeGraph = this.nodeModelToTopic(branch); - nodeGraph.setBranchVisibility(true); + nodesGraph.push(nodeGraph); }); - // Connect relationships ... - const relationships = mindmap.getRelationships(); - relationships.forEach((relationship) => this._relationshipModelToRelationship(relationship)); - // Place the focus on the Central Topic const centralTopic = this.getModel().getCentralTopic(); this.goToNode(centralTopic); - // Finally, sort the map ... - EventBus.instance.fireEvent('forceLayout'); + return this._workspace.enableQueueRender(false).then(() => { + // Connect relationships ... + const relationships = mindmap.getRelationships(); + relationships.forEach((relationship) => this._relationshipModelToRelationship(relationship)); - this.fireEvent('loadSuccess'); + // Render nodes ... + nodesGraph.forEach((topic) => topic.setVisibility(true)); + + // Finally, sort the map ... + EventBus.instance.fireEvent('forceLayout'); + this.fireEvent('loadSuccess'); + }); } getMindmap(): Mindmap { diff --git a/packages/mindplot/src/components/MindplotWebComponent.ts b/packages/mindplot/src/components/MindplotWebComponent.ts index d1f2347e..27b74041 100644 --- a/packages/mindplot/src/components/MindplotWebComponent.ts +++ b/packages/mindplot/src/components/MindplotWebComponent.ts @@ -3,7 +3,6 @@ import buildDesigner from './DesignerBuilder'; import DesignerOptionsBuilder from './DesignerOptionsBuilder'; import EditorRenderMode from './EditorRenderMode'; import LocalStorageManager from './LocalStorageManager'; -import Mindmap from './model/Mindmap'; import PersistenceManager from './PersistenceManager'; import WidgetManager from './WidgetManager'; import mindplotStyles from './styles/mindplot-styles'; @@ -27,12 +26,12 @@ export type MindplotWebComponentInterface = { class MindplotWebComponent extends HTMLElement { private _shadowRoot: ShadowRoot; - private _mindmap: Mindmap; - private _designer: Designer; private saveRequired: boolean; + private _isLoaded: boolean; + constructor() { super(); this._shadowRoot = this.attachShadow({ mode: 'open' }); @@ -77,6 +76,16 @@ class MindplotWebComponent extends HTMLElement { }); this.registerShortcuts(); + + this._designer.addEvent('loadSuccess', (): void => { + this._isLoaded = true; + }); + + return this._designer; + } + + isLoaded(): boolean { + return this._isLoaded; } private registerShortcuts() { @@ -100,10 +109,9 @@ class MindplotWebComponent extends HTMLElement { * Load map in designer throught persistence manager instance * @param id the map id to be loaded. */ - async loadMap(id: string): Promise { + loadMap(id: string): Promise { const instance = PersistenceManager.getInstance(); - this._mindmap = await instance.load(id); - this._designer.loadMap(this._mindmap); + return instance.load(id).then((mindmap) => this._designer.loadMap(mindmap)); } /** diff --git a/packages/mindplot/src/components/PersistenceManager.ts b/packages/mindplot/src/components/PersistenceManager.ts index 711c92bd..ead8e31b 100644 --- a/packages/mindplot/src/components/PersistenceManager.ts +++ b/packages/mindplot/src/components/PersistenceManager.ts @@ -61,12 +61,11 @@ abstract class PersistenceManager { return result; } - load(mapId: string): Promise { + async load(mapId: string): Promise { $assert(mapId, 'mapId can not be null'); - return this.loadMapDom(mapId).then((document) => { - console.log(`Loading map with is ${mapId}}`); - return PersistenceManager.loadFromDom(mapId, document); - }); + // eslint-disable-next-line arrow-body-style + const document = await this.loadMapDom(mapId); + return PersistenceManager.loadFromDom(mapId, document); } triggerError(error: PersistenceError) { diff --git a/packages/mindplot/src/components/Workspace.ts b/packages/mindplot/src/components/Workspace.ts index 99697400..8187f042 100644 --- a/packages/mindplot/src/components/Workspace.ts +++ b/packages/mindplot/src/components/Workspace.ts @@ -35,6 +35,10 @@ class Workspace { private _visibleAreaSize: SizeType; + private _renderQueue: Element2D[]; + + private queueRenderEnabled: boolean; + constructor(screenManager: ScreenManager, zoom: number, isReadOnly: boolean) { // Create a suitable container ... $assert(screenManager, 'Div container can not be null'); @@ -66,6 +70,8 @@ class Workspace { }); this.setZoom(zoom, true); + + this._renderQueue = []; } private _adjustWorkspace(): void { @@ -97,6 +103,14 @@ class Workspace { } append(shape: Element2D): void { + if (this.queueRenderEnabled) { + this._renderQueue.push(shape); + } else { + this.appendInternal(shape); + } + } + + private appendInternal(shape: Element2D): void { if ($defined(shape.addToWorkspace)) { shape.addToWorkspace(this); } else { @@ -104,6 +118,37 @@ class Workspace { } } + enableQueueRender(value: boolean): Promise { + this.queueRenderEnabled = value; + + let result = Promise.resolve(); + if (!value) { + result = this.processRenderQueue(this._renderQueue.reverse(), 300); + } + return result; + } + + private processRenderQueue(renderQueue: Element2D[], batch: number): Promise { + function delay(t: number) { + return new Promise((resolve) => setTimeout(resolve, t)); + } + + let result: Promise; + if (renderQueue.length > 0) { + result = new Promise((resolve: (queue: Element2D[]) => void) => { + for (let i = 0; i < batch && renderQueue.length > 0; i++) { + const elem = renderQueue.pop(); + this.appendInternal(elem); + } + + resolve(renderQueue); + }).then((queue) => delay(30).then(() => this.processRenderQueue(queue, batch))); + } else { + result = Promise.resolve(); + } + return result; + } + removeChild(shape: Element2D): void { // Element is a node, not a web2d element? if ($defined(shape.removeFromWorkspace)) { diff --git a/packages/web2d/src/components/peer/svg/TextPeer.js b/packages/web2d/src/components/peer/svg/TextPeer.js index 62575f83..eb8fd8f1 100644 --- a/packages/web2d/src/components/peer/svg/TextPeer.js +++ b/packages/web2d/src/components/peer/svg/TextPeer.js @@ -159,12 +159,7 @@ class TextPeer extends ElementPeer { } getWidth() { - let computedWidth = this._native.getBBox().width; - if (computedWidth === 0) { - const bbox = this._native.getBBox(); - computedWidth = bbox.width; - } - + const computedWidth = this._native.getBBox().width; let width = parseInt(computedWidth, 10); width += this._font.getWidthMargin(); return width;