diff --git a/packages/client/package.json b/packages/client/package.json index 8daf2837..36114bcd 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -59,6 +59,7 @@ "copy-to-clipboard": "^3.3.1", "deep-equal": "^2.0.5", "dompurify": "^2.3.5", + "interactjs": "^1.10.11", "katex": "^0.15.2", "kity": "^2.0.4", "lib0": "^0.2.47", @@ -74,7 +75,6 @@ "prosemirror-tables": "^1.1.1", "prosemirror-utils": "^0.9.6", "prosemirror-view": "^1.23.6", - "re-resizable": "^6.9.9", "react": "17.0.2", "react-countdown": "^2.3.2", "react-dom": "17.0.2", diff --git a/packages/client/src/components/resizeable/resizeable.tsx b/packages/client/src/components/resizeable/resizeable.tsx index 5c326158..bf86afdd 100644 --- a/packages/client/src/components/resizeable/resizeable.tsx +++ b/packages/client/src/components/resizeable/resizeable.tsx @@ -1,51 +1,108 @@ -import React, { useCallback, useState } from 'react'; +import React, { useRef, useEffect } from 'react'; import cls from 'classnames'; +import { useClickOutside } from 'hooks/use-click-outside'; +import interact from 'interactjs'; import styles from './style.module.scss'; -import { Resizable } from 're-resizable'; +type ISize = { width: number; height: number }; interface IProps { width: number; height: number; - onChange?: (arg: { width: number; height: number }) => void; - onChangeEnd?: (arg: { width: number; height: number }) => void; + maxWidth?: number; + onChange?: (arg: ISize) => void; + onChangeEnd?: (arg: ISize) => void; className?: string; } const MIN_WIDTH = 50; const MIN_HEIGHT = 50; +function clamp(val: number, min: number, max: number): number { + if (val < min) { + return min; + } + if (val > max) { + return max; + } + return val; +} + export const Resizeable: React.FC = ({ - width: defaultWidth, - height: defaultHeight, + width, + height, + maxWidth, className, onChange, onChangeEnd, children, }) => { - const [width, setWidth] = useState(defaultWidth); - const [height, setHeight] = useState(defaultHeight); + const $container = useRef(null); + const $topLeft = useRef(null); + const $topRight = useRef(null); + const $bottomLeft = useRef(null); + const $bottomRight = useRef(null); - const onResizeStop = useCallback( - (e, direction, ref, d) => { - const nextWidth = width + d.width; - const nextHeight = height + d.height; - setWidth(nextWidth); - setHeight(nextHeight); - onChangeEnd({ width: nextWidth, height: nextHeight }); - }, - [width, height] - ); + useClickOutside($container, { + in: () => $container.current.classList.add(styles.isActive), + out: () => $container.current.classList.remove(styles.isActive), + }); + + useEffect(() => { + interact($container.current).resizable({ + edges: { + top: true, + right: true, + bottom: true, + left: true, + }, + listeners: { + move: function (event) { + let { x, y } = event.target.dataset; + x = (parseFloat(x) || 0) + event.deltaRect.left; + y = (parseFloat(y) || 0) + event.deltaRect.top; + + let { width, height } = event.rect; + width = clamp(width, MIN_WIDTH, maxWidth || Infinity); + height = clamp(height, MIN_HEIGHT, Infinity); + + Object.assign(event.target.style, { + width: `${width}px`, + height: `${height}px`, + }); + Object.assign(event.target.dataset, { x, y }); + onChange && onChange({ width, height }); + }, + end: function (event) { + let { width, height } = event.rect; + width = clamp(width, MIN_WIDTH, maxWidth || Infinity); + height = clamp(height, MIN_HEIGHT, Infinity); + + onChangeEnd && onChangeEnd({ width, height }); + }, + }, + }); + }, [maxWidth]); + + useEffect(() => { + Object.assign($container.current.style, { + width: `${width}px`, + height: `${height}px`, + }); + }, [width, height]); return ( - + + + + {children} - + ); }; diff --git a/packages/client/src/components/resizeable/style.module.scss b/packages/client/src/components/resizeable/style.module.scss index 64232ffa..51cd1e34 100644 --- a/packages/client/src/components/resizeable/style.module.scss +++ b/packages/client/src/components/resizeable/style.module.scss @@ -4,6 +4,43 @@ width: 100px; height: 100px; max-width: 100%; + box-sizing: border-box; + + .resizer { + position: absolute; + z-index: 9999; + width: 10px; + height: 10px; + background: white; + border: 3px solid #4286f4; + border-radius: 50%; + opacity: 0; + box-sizing: border-box; + } + + .resizer.topLeft { + top: -5px; + left: -5px; + cursor: nwse-resize; + } + + .resizer.topRight { + top: -5px; + right: -5px; + cursor: nesw-resize; + } + + .resizer.bottomLeft { + bottom: -5px; + left: -5px; + cursor: nesw-resize; + } + + .resizer.bottomRight { + right: -5px; + bottom: -5px; + cursor: nwse-resize; + } &.isActive { .resizer { diff --git a/packages/client/src/tiptap/extensions/mind.ts b/packages/client/src/tiptap/extensions/mind.ts index 10075ec7..0e37dd46 100644 --- a/packages/client/src/tiptap/extensions/mind.ts +++ b/packages/client/src/tiptap/extensions/mind.ts @@ -10,10 +10,19 @@ const DEFAULT_MIND_DATA = { version: '1.4.43', }; +export interface IMindAttrs { + width?: number; + height?: number; + data?: Record; + template?: string; + theme?: string; + zoom?: number; +} + declare module '@tiptap/core' { interface Commands { mind: { - setMind: (attrs?: unknown) => ReturnType; + setMind: (attrs?: IMindAttrs) => ReturnType; }; } } @@ -28,7 +37,7 @@ export const Mind = Node.create({ addAttributes() { return { width: { - default: '100%', + default: null, parseHTML: getDatasetAttribute('width'), }, height: { @@ -51,10 +60,6 @@ export const Mind = Node.create({ default: 100, parseHTML: getDatasetAttribute('zoom'), }, - callCenterCount: { - default: 0, - parseHTML: (element) => Number(getDatasetAttribute('callcentercount')(element)), - }, }; }, @@ -83,6 +88,9 @@ export const Mind = Node.create({ setMind: (options) => ({ tr, commands, chain, editor }) => { + options = options || {}; + options.data = options.data || DEFAULT_MIND_DATA; + // @ts-ignore if (tr.selection?.node?.type?.name == this.name) { return commands.updateAttributes(this.name, options); @@ -94,7 +102,7 @@ export const Mind = Node.create({ .insertContentAt(pos.before(), [ { type: this.name, - attrs: { data: DEFAULT_MIND_DATA }, + attrs: options, }, ]) .run(); diff --git a/packages/client/src/tiptap/menubar.tsx b/packages/client/src/tiptap/menubar.tsx index 64db8933..c5e317be 100644 --- a/packages/client/src/tiptap/menubar.tsx +++ b/packages/client/src/tiptap/menubar.tsx @@ -34,6 +34,7 @@ import { Search } from './menus/search'; import { Callout } from './menus/callout'; import { Countdonw } from './menus/countdown'; +import { DocumentChildren } from './menus/document-children'; import { DocumentReference } from './menus/document-reference'; import { Image } from './menus/image'; import { Iframe } from './menus/iframe'; @@ -91,6 +92,7 @@ export const MenuBar: React.FC<{ editor: any }> = ({ editor }) => { +