mirror of https://github.com/fantasticit/think.git
tiptap: add lazy render for flow and mind
This commit is contained in:
parent
86fc6bf843
commit
a03312b816
|
@ -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",
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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'}
|
||||
|
|
Loading…
Reference in New Issue