tiptap: add lazy render for flow and mind

This commit is contained in:
fantasticit 2022-06-18 00:13:02 +08:00
parent 86fc6bf843
commit a03312b816
4 changed files with 123 additions and 86 deletions

View File

@ -90,6 +90,7 @@
"react-pdf": "^5.7.2",
"react-query": "^3.39.0",
"react-split-pane": "^0.1.92",
"react-visibility-sensor": "^5.1.1",
"requestidlecallback-polyfill": "^1.0.2",
"resize-observer-polyfill": "^1.5.1",
"scroll-into-view-if-needed": "^2.2.29",

View File

@ -5,6 +5,7 @@ import { IconFlow, IconMindCenter, IconZoomIn, IconZoomOut } from 'components/ic
import { Resizeable } from 'components/resizeable';
import { useToggle } from 'hooks/use-toggle';
import { useCallback, useEffect, useRef, useState } from 'react';
import VisibilitySensor from 'react-visibility-sensor';
import { load, renderXml } from 'thirtypart/diagram';
import { Flow } from 'tiptap/core/extensions/flow';
import { getEditorContainerDOMSize } from 'tiptap/prose-utils';
@ -22,6 +23,7 @@ export const FlowWrapper = ({ editor, node, updateAttributes }) => {
const $graph = useRef(null);
const $container = useRef<HTMLElement>();
const [bgColor, setBgColor] = useState('var(--semi-color-bg-3)');
const [visible, toggleVisible] = useToggle(false);
const [loading, toggleLoading] = useToggle(true);
const [error, setError] = useState(null);
@ -77,6 +79,15 @@ export const FlowWrapper = ({ editor, node, updateAttributes }) => {
[render]
);
const onViewportChange = useCallback(
(visible) => {
if (visible) {
toggleVisible(true);
}
},
[toggleVisible]
);
useEffect(() => {
load()
.catch(setError)
@ -85,40 +96,44 @@ export const FlowWrapper = ({ editor, node, updateAttributes }) => {
return (
<NodeViewWrapper className={cls(styles.wrap, isActive && styles.isActive)}>
<Resizeable isEditable={isEditable} width={width} height={height} maxWidth={maxWidth} onChangeEnd={onResize}>
<div
className={cls(styles.renderWrap, 'render-wrapper')}
style={{ ...INHERIT_SIZE_STYLE, overflow: 'hidden', backgroundColor: bgColor }}
>
{loading && (
<div>
<Spin spinning>
{/* FIXME: semi-design 的问题,不加 div文字会换行! */}
<div></div>
</Spin>
</div>
)}
{error && <Text>{(error && error.message) || '未知错误'}</Text>}
{!loading && !error && <div style={{ maxHeight: '100%' }} ref={setMxgraph}></div>}
</div>
<VisibilitySensor onChange={onViewportChange}>
<Resizeable isEditable={isEditable} width={width} height={height} maxWidth={maxWidth} onChangeEnd={onResize}>
<div
className={cls(styles.renderWrap, 'render-wrapper')}
style={{ ...INHERIT_SIZE_STYLE, overflow: 'hidden', backgroundColor: bgColor }}
>
{loading && (
<div>
<Spin spinning>
{/* FIXME: semi-design 的问题,不加 div文字会换行! */}
<div></div>
</Spin>
</div>
)}
<div className={styles.title}>
<Space>
<span className={styles.icon}>
<IconFlow />
</span>
</Space>
</div>
{error && <Text>{(error && error.message) || '未知错误'}</Text>}
<div className={styles.toolbarWrap}>
<Space spacing={2}>
<Button type="tertiary" theme="borderless" size="small" onClick={center} icon={<IconMindCenter />} />
<Button type="tertiary" theme="borderless" size="small" onClick={zoomOut} icon={<IconZoomOut />} />
<Button type="tertiary" theme="borderless" size="small" onClick={zoomIn} icon={<IconZoomIn />} />
</Space>
</div>
</Resizeable>
{!loading && !error && visible && <div style={{ maxHeight: '100%' }} ref={setMxgraph}></div>}
</div>
<div className={styles.title}>
<Space>
<span className={styles.icon}>
<IconFlow />
</span>
</Space>
</div>
<div className={styles.toolbarWrap}>
<Space spacing={2}>
<Button type="tertiary" theme="borderless" size="small" onClick={center} icon={<IconMindCenter />} />
<Button type="tertiary" theme="borderless" size="small" onClick={zoomOut} icon={<IconZoomOut />} />
<Button type="tertiary" theme="borderless" size="small" onClick={zoomIn} icon={<IconZoomIn />} />
</Space>
</div>
</Resizeable>
</VisibilitySensor>
</NodeViewWrapper>
);
};

View File

@ -7,6 +7,7 @@ import { Tooltip } from 'components/tooltip';
import deepEqual from 'deep-equal';
import { useToggle } from 'hooks/use-toggle';
import { useCallback, useEffect, useRef, useState } from 'react';
import VisibilitySensor from 'react-visibility-sensor';
import { load, renderMind } from 'thirtypart/kityminder';
import { Mind } from 'tiptap/core/extensions/mind';
import { MAX_ZOOM, MIN_ZOOM, ZOOM_STEP } from 'tiptap/core/menus/mind/constant';
@ -24,6 +25,7 @@ export const MindWrapper = ({ editor, node, updateAttributes }) => {
const isActive = editor.isActive(Mind.name);
const { width: maxWidth } = getEditorContainerDOMSize(editor);
const { data, width, height } = node.attrs;
const [visible, toggleVisible] = useToggle(false);
const [loading, toggleLoading] = useToggle(true);
const [error, setError] = useState<Error | null>(null);
@ -76,6 +78,15 @@ export const MindWrapper = ({ editor, node, updateAttributes }) => {
[render]
);
const onViewportChange = useCallback(
(visible) => {
if (visible) {
toggleVisible(true);
}
},
[toggleVisible]
);
useEffect(() => {
load()
.catch(setError)
@ -98,46 +109,59 @@ export const MindWrapper = ({ editor, node, updateAttributes }) => {
return (
<NodeViewWrapper className={cls(styles.wrap, isActive && styles.isActive)}>
<Resizeable isEditable={isEditable} width={width} height={height} maxWidth={maxWidth} onChangeEnd={onResize}>
<div className={cls(styles.renderWrap, 'render-wrapper')} style={{ ...INHERIT_SIZE_STYLE, overflow: 'hidden' }}>
{error && (
<div style={INHERIT_SIZE_STYLE}>
<Text>{error.message || error}</Text>
<VisibilitySensor onChange={onViewportChange}>
<Resizeable isEditable={isEditable} width={width} height={height} maxWidth={maxWidth} onChangeEnd={onResize}>
<div
className={cls(styles.renderWrap, 'render-wrapper')}
style={{ ...INHERIT_SIZE_STYLE, overflow: 'hidden' }}
>
{error && (
<div style={INHERIT_SIZE_STYLE}>
<Text>{error.message || error}</Text>
</div>
)}
{loading && <Spin spinning style={INHERIT_SIZE_STYLE}></Spin>}
{!loading && !error && visible && (
<div style={{ height: '100%', maxHeight: '100%', overflow: 'hidden' }} ref={setMind}></div>
)}
<div className={styles.title}>
<Space>
<span className={styles.icon}>
<IconMind />
</span>
</Space>
</div>
)}
{loading && <Spin spinning style={INHERIT_SIZE_STYLE}></Spin>}
{!loading && !error && (
<div style={{ height: '100%', maxHeight: '100%', overflow: 'hidden' }} ref={setMind}></div>
)}
<div className={styles.title}>
<Space>
<span className={styles.icon}>
<IconMind />
</span>
</Space>
<div className={styles.mindHandlerWrap}>
<Tooltip content="居中">
<Button size="small" theme="borderless" type="tertiary" icon={<IconMindCenter />} onClick={setCenter} />
</Tooltip>
<Tooltip content="缩小">
<Button
size="small"
theme="borderless"
type="tertiary"
icon={<IconZoomOut />}
onClick={setZoom('minus')}
/>
</Tooltip>
<Tooltip content="放大">
<Button
size="small"
theme="borderless"
type="tertiary"
icon={<IconZoomIn />}
onClick={setZoom('plus')}
/>
</Tooltip>
</div>
</div>
<div className={styles.mindHandlerWrap}>
<Tooltip content="居中">
<Button size="small" theme="borderless" type="tertiary" icon={<IconMindCenter />} onClick={setCenter} />
</Tooltip>
<Tooltip content="缩小">
<Button
size="small"
theme="borderless"
type="tertiary"
icon={<IconZoomOut />}
onClick={setZoom('minus')}
/>
</Tooltip>
<Tooltip content="放大">
<Button size="small" theme="borderless" type="tertiary" icon={<IconZoomIn />} onClick={setZoom('plus')} />
</Tooltip>
</div>
</div>
</Resizeable>
</Resizeable>
</VisibilitySensor>
</NodeViewWrapper>
);
};

View File

@ -135,6 +135,7 @@ importers:
react-pdf: ^5.7.2
react-query: ^3.39.0
react-split-pane: ^0.1.92
react-visibility-sensor: ^5.1.1
requestidlecallback-polyfill: ^1.0.2
resize-observer-polyfill: ^1.5.1
scroll-into-view-if-needed: ^2.2.29
@ -228,6 +229,7 @@ importers:
react-pdf: 5.7.2_react-dom@17.0.2+react@17.0.2
react-query: 3.39.0_react-dom@17.0.2+react@17.0.2
react-split-pane: 0.1.92_react-dom@17.0.2+react@17.0.2
react-visibility-sensor: 5.1.1_react-dom@17.0.2+react@17.0.2
requestidlecallback-polyfill: 1.0.2
resize-observer-polyfill: 1.5.1
scroll-into-view-if-needed: 2.2.29
@ -246,7 +248,7 @@ importers:
eslint: 8.14.0
eslint-config-prettier: 8.5.0_eslint@8.14.0
eslint-plugin-import: 2.26.0_eslint@8.14.0
eslint-plugin-prettier: 4.0.0_74ebb802163a9b4fa8f89d76ed02f62a
eslint-plugin-prettier: 4.0.0_740be41c8168d0cc214a306089357ad0
eslint-plugin-react: 7.29.4_eslint@8.14.0
eslint-plugin-react-hooks: 4.5.0_eslint@8.14.0
eslint-plugin-simple-import-sort: 7.0.0_eslint@8.14.0
@ -5603,22 +5605,6 @@ packages:
prettier-linter-helpers: 1.0.0
dev: true
/eslint-plugin-prettier/4.0.0_74ebb802163a9b4fa8f89d76ed02f62a:
resolution: {integrity: sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==}
engines: {node: '>=6.0.0'}
peerDependencies:
eslint: '>=7.28.0'
eslint-config-prettier: '*'
prettier: '>=2.0.0'
peerDependenciesMeta:
eslint-config-prettier:
optional: true
dependencies:
eslint: 8.14.0
eslint-config-prettier: 8.5.0_eslint@8.14.0
prettier-linter-helpers: 1.0.0
dev: true
/eslint-plugin-react-hooks/4.5.0_eslint@8.14.0:
resolution: {integrity: sha512-8k1gRt7D7h03kd+SAAlzXkQwWK22BnK6GKZG+FJA6BAGy22CFvl8kCIXKpVux0cCxMWDQUPqSok0LKaZ0aOcCw==}
engines: {node: '>=10'}
@ -9611,6 +9597,17 @@ packages:
prop-types: 15.8.1
dev: false
/react-visibility-sensor/5.1.1_react-dom@17.0.2+react@17.0.2:
resolution: {integrity: sha512-cTUHqIK+zDYpeK19rzW6zF9YfT4486TIgizZW53wEZ+/GPBbK7cNS0EHyJVyHYacwFEvvHLEKfgJndbemWhB/w==}
peerDependencies:
react: '>=16.0.0'
react-dom: '>=16.0.0'
dependencies:
prop-types: 15.8.1
react: 17.0.2
react-dom: 17.0.2_react@17.0.2
dev: false
/react-window/1.8.6_react-dom@17.0.2+react@17.0.2:
resolution: {integrity: sha512-8VwEEYyjz6DCnGBsd+MgkD0KJ2/OXFULyDtorIiTz+QzwoP94tBoA7CnbtyXMm+cCeAUER5KJcPtWl9cpKbOBg==}
engines: {node: '>8.0.0'}