tiptap: fix mind toolbar

This commit is contained in:
fantasticit 2022-04-27 16:23:14 +08:00
parent a9eefd3c37
commit 2ad45a880e
12 changed files with 449 additions and 423 deletions

View File

@ -1,4 +1,4 @@
export const Divider = () => {
export const Divider = ({ vertical = false }) => {
return (
<div
style={{
@ -7,6 +7,7 @@ export const Divider = () => {
height: 24,
margin: '0 6px',
backgroundColor: 'var(--semi-color-border)',
transform: `rotate(${vertical ? 90 : 0}deg)`,
}}
></div>
);

View File

@ -1,74 +1,11 @@
import { useCallback } from 'react';
import cls from 'classnames';
import { Space, Button, Popover, Typography } from '@douyinfe/semi-ui';
import { IconAlignCenter, IconDelete } from '@douyinfe/semi-icons';
import { Space, Button } from '@douyinfe/semi-ui';
import { IconDelete } from '@douyinfe/semi-icons';
import { Tooltip } from 'components/tooltip';
import { IconStructure, IconDrawBoard, IconZoomIn, IconZoomOut } from 'components/icons';
import { BubbleMenu } from '../../views/bubble-menu';
import { Mind } from '../../extensions/mind';
import { Divider } from '../../divider';
import { clamp } from '../../utils/clamp';
import { TEMPLATES, THEMES, MAX_ZOOM, MIN_ZOOM, ZOOM_STEP } from './constant';
import styles from './bubble.module.scss';
const { Text } = Typography;
export const MindBubbleMenu = ({ editor }) => {
const { template, theme, zoom, callCenterCount } = editor.getAttributes(Mind.name);
const setZoom = useCallback(
(type: 'minus' | 'plus') => {
return () => {
editor
.chain()
.updateAttributes(Mind.name, {
zoom: clamp(type === 'minus' ? parseInt(zoom) - ZOOM_STEP : parseInt(zoom) + ZOOM_STEP, MIN_ZOOM, MAX_ZOOM),
})
.focus()
.run();
};
},
[editor, zoom]
);
const setCenter = useCallback(() => {
const nextValue = Number.isNaN(callCenterCount) ? 1 : Number(callCenterCount) + 1;
editor
.chain()
.updateAttributes(Mind.name, {
callCenterCount: nextValue,
})
.focus()
.run();
}, [editor, callCenterCount]);
const setTemplate = useCallback(
(template) => {
editor
.chain()
.updateAttributes(Mind.name, {
template,
})
.focus()
.run();
},
[editor]
);
const setTheme = useCallback(
(theme) => {
editor
.chain()
.updateAttributes(Mind.name, {
theme,
})
.focus()
.run();
},
[editor]
);
const deleteNode = useCallback(() => editor.chain().deleteSelection().run(), [editor]);
return (
@ -80,90 +17,6 @@ export const MindBubbleMenu = ({ editor }) => {
tippyOptions={{ maxWidth: 'calc(100vw - 100px)' }}
>
<Space>
<Tooltip content="缩小">
<Button
size="small"
type="tertiary"
theme="borderless"
disabled={+zoom <= MIN_ZOOM}
icon={<IconZoomOut />}
onClick={setZoom('minus')}
/>
</Tooltip>
<Text style={{ width: 20, textAlign: 'center' }}>{zoom}</Text>
<Tooltip content="放大">
<Button
size="small"
type="tertiary"
theme="borderless"
disabled={+zoom >= MAX_ZOOM}
icon={<IconZoomIn />}
onClick={setZoom('plus')}
/>
</Tooltip>
<Tooltip content="居中">
<Button size="small" type="tertiary" theme="borderless" icon={<IconAlignCenter />} onClick={setCenter} />
</Tooltip>
<Divider />
<Popover
zIndex={10000}
spacing={10}
style={{ padding: '0 12px 12px', overflow: 'hidden' }}
content={
<section className={styles.sectionWrap}>
<Text type="secondary"></Text>
<div>
<ul>
{TEMPLATES.map((item) => {
return (
<li
key={item.label}
className={cls(template === item.value && styles.active)}
onClick={() => setTemplate(item.value)}
>
<Text>{item.label}</Text>
</li>
);
})}
</ul>
</div>
</section>
}
>
<Button icon={<IconStructure />} type="tertiary" theme="borderless" size="small" />
</Popover>
<Popover
zIndex={10000}
spacing={10}
style={{ padding: '0 12px 12px', overflow: 'hidden' }}
content={
<section className={styles.sectionWrap}>
<Text type="secondary"></Text>
<div>
<ul>
{THEMES.map((item) => {
return (
<li
key={item.label}
className={cls(theme === item.value && styles.active)}
style={item.style || {}}
onClick={() => setTheme(item.value)}
>
{item.label}
</li>
);
})}
</ul>
</div>
</section>
}
>
<Button icon={<IconDrawBoard />} type="tertiary" theme="borderless" size="small" />
</Popover>
<Divider />
<Tooltip content="删除节点" hideOnClick>
<Button onClick={deleteNode} icon={<IconDelete />} type="tertiary" theme="borderless" size="small" />
</Tooltip>

View File

@ -13,21 +13,15 @@
}
}
.mindHandlerWrap {
.toolbarWrap {
position: absolute;
right: 20px;
bottom: 20px;
z-index: 1000;
padding: 4px 8px;
background-color: var(--semi-color-bg-2);
border: 1px solid var(--node-border-color);
border-radius: var(--border-radius);
top: 50%;
left: 0;
opacity: 0;
box-shadow: var(--box-shadow);
}
&.isActive {
.mindHandlerWrap {
.toolbarWrap {
opacity: 1;
}
}

View File

@ -1,16 +1,15 @@
import { NodeViewWrapper } from '@tiptap/react';
import cls from 'classnames';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Spin, Button, Typography } from '@douyinfe/semi-ui';
import { IconMinus, IconPlus, IconAlignCenter } from '@douyinfe/semi-icons';
import { Spin, Typography } from '@douyinfe/semi-ui';
import deepEqual from 'deep-equal';
import { Resizeable } from 'components/resizeable';
import { Tooltip } from 'components/tooltip';
import { useToggle } from 'hooks/use-toggle';
import { MIN_ZOOM, MAX_ZOOM, ZOOM_STEP } from '../../menus/mind/constant';
import { clamp } from '../../utils/clamp';
import { Mind } from '../../extensions/mind';
import { loadKityMinder } from './kityminder';
import { Toolbar } from './toolbar';
import { MIN_ZOOM, MAX_ZOOM, ZOOM_STEP } from './toolbar/constant';
import styles from './index.module.scss';
const { Text } = Typography;
@ -20,7 +19,7 @@ export const MindWrapper = ({ editor, node, updateAttributes }) => {
const $mind = useRef<any>();
const isMindActive = editor.isActive(Mind.name);
const isEditable = editor.isEditable;
const { data, template, theme, zoom, callCenterCount, width, height } = node.attrs;
const { data, template, theme, zoom, width, height } = node.attrs;
const [loading, toggleLoading] = useToggle(true);
const [error, setError] = useState<Error | null>(null);
@ -55,12 +54,6 @@ export const MindWrapper = ({ editor, node, updateAttributes }) => {
[updateAttributes]
);
const setCenter = useCallback(() => {
const minder = $mind.current;
if (!minder) return;
minder.execCommand('camera');
}, []);
const setZoom = useCallback(
(type: 'minus' | 'plus') => {
return () => {
@ -73,16 +66,45 @@ export const MindWrapper = ({ editor, node, updateAttributes }) => {
MAX_ZOOM
);
minder.execCommand('zoom', nextZoom);
isEditable && updateAttributes({ zoom: nextZoom });
};
},
[editor, zoom]
[editor, zoom, isEditable, updateAttributes]
);
const setCenter = useCallback(() => {
const minder = $mind.current;
if (!minder) return;
minder.execCommand('camera');
}, []);
// 布局
const setTemplate = useCallback(
(template) => {
const minder = $mind.current;
if (!minder) return;
minder.execCommand('template', template);
isEditable && updateAttributes({ template });
},
[updateAttributes, isEditable]
);
// 主题
const setTheme = useCallback(
(theme) => {
const minder = $mind.current;
if (!minder) return;
minder.execCommand('theme', theme);
isEditable && updateAttributes({ theme });
},
[updateAttributes, isEditable]
);
const saveData = useCallback(() => {
const minder = $mind.current;
if (!minder) return;
updateAttributes({ data: minder.exportJson() });
}, [updateAttributes]);
isEditable && updateAttributes({ data: minder.exportJson() });
}, [updateAttributes, isEditable]);
// 加载依赖
useEffect(() => {
@ -113,6 +135,8 @@ export const MindWrapper = ({ editor, node, updateAttributes }) => {
if (!isEditable) {
minder.disable();
} else {
minder.enable();
}
$mind.current = minder;
@ -125,6 +149,7 @@ export const MindWrapper = ({ editor, node, updateAttributes }) => {
return () => {
if ($mind.current) {
$mind.current.off('contentChange', onChange);
$mind.current.destroy();
}
};
}, [loading]);
@ -133,14 +158,33 @@ export const MindWrapper = ({ editor, node, updateAttributes }) => {
useEffect(() => {
const minder = $mind.current;
if (!minder) return;
const currentData = minder.exportJson();
const isEqual = deepEqual(currentData, data);
if (isEqual) return;
// TODO: 也许刷新更好些
minder.importJson(data);
}, [data]);
// 启用/禁用
useEffect(() => {
const minder = $mind.current;
if (!minder) return;
if (!isEditable) {
minder.disable();
} else {
minder.enable();
}
}, [isEditable]);
// 缩放
useEffect(() => {
const minder = $mind.current;
if (!minder) return;
minder.execCommand('zoom', parseInt(zoom));
}, [zoom]);
// 布局
useEffect(() => {
const minder = $mind.current;
@ -155,30 +199,6 @@ export const MindWrapper = ({ editor, node, updateAttributes }) => {
minder.execCommand('theme', theme);
}, [theme]);
// 缩放
useEffect(() => {
const minder = $mind.current;
if (!minder) return;
minder.execCommand('zoom', parseInt(zoom));
}, [zoom]);
// 启用/禁用
useEffect(() => {
const minder = $mind.current;
if (!minder) return;
if (isEditable) {
minder.enable();
} else {
minder.disable();
}
}, [isEditable]);
// 居中
useEffect(() => {
setCenter();
}, [callCenterCount]);
return (
<NodeViewWrapper className={cls(styles.wrap, isMindActive && styles.isActive)}>
{isEditable ? (
@ -188,38 +208,19 @@ export const MindWrapper = ({ editor, node, updateAttributes }) => {
) : (
<div style={{ display: 'inline-block', width, height, maxWidth: '100%' }}>{content}</div>
)}
{!isEditable && (
<div className={styles.mindHandlerWrap}>
<Tooltip content="缩小">
<Button
size="small"
theme="borderless"
type="tertiary"
icon={<IconMinus style={{ fontSize: 14 }} />}
onClick={setZoom('minus')}
/>
</Tooltip>
<Tooltip content="放大">
<Button
size="small"
theme="borderless"
type="tertiary"
icon={<IconPlus style={{ fontSize: 14 }} />}
onClick={setZoom('plus')}
/>
</Tooltip>
<Tooltip content="居中">
<Button
size="small"
theme="borderless"
type="tertiary"
icon={<IconAlignCenter style={{ fontSize: 14 }} />}
onClick={setCenter}
/>
</Tooltip>
</div>
)}
<div className={styles.toolbarWrap}>
<Toolbar
isEditable={isEditable}
template={template}
theme={theme}
zoom={zoom}
setZoomMinus={setZoom('minus')}
setZoomPlus={setZoom('plus')}
setCenter={setCenter}
setTemplate={setTemplate}
setTheme={setTheme}
/>
</div>
</NodeViewWrapper>
);
};

View File

@ -1,7 +1,10 @@
export const loadKityMinder = async (): Promise<any> => {
if (typeof window !== 'undefined') {
if (window.kityminder) {
if (window.kityminder.Editor) return;
if (window.kityminder.Editor) {
console.log('无需重复');
return;
}
}
}

View File

@ -4,10 +4,10 @@ define(function (require, exports, module) {
Minder.registerInitHook(function () {
this.on('beforemousedown', function (e) {
this.focus();
// FIXME如果遇到事件触发问题需要检查这里
if (e.kityEvent.targetShape.__KityClassName === 'Paper') return;
e.preventDefault();
this.focus();
});
this.on('paperrender', function () {
this.focus();

View File

@ -7,57 +7,56 @@
* @copyright: Baidu FEX, 2014
*/
define(function(require, exports, module) {
var kity = require('./kity');
var Minder = require('./minder');
var MinderEvent = require('./event');
define(function (require, exports, module) {
var kity = require('./kity');
var Minder = require('./minder');
var MinderEvent = require('./event');
Minder.registerInitHook(function(options) {
if (options.readOnly) {
this.setDisabled();
Minder.registerInitHook(function (options) {
if (options.readOnly) {
this.setDisabled();
}
});
kity.extendClass(Minder, {
disable: function () {
var me = this;
//禁用命令
me.bkqueryCommandState = me.queryCommandState;
me.bkqueryCommandValue = me.queryCommandValue;
me.queryCommandState = function (type) {
var cmd = this._getCommand(type);
if (cmd && cmd.enableReadOnly) {
return me.bkqueryCommandState.apply(me, arguments);
}
});
kity.extendClass(Minder, {
disable: function() {
var me = this;
//禁用命令
me.bkqueryCommandState = me.queryCommandState;
me.bkqueryCommandValue = me.queryCommandValue;
me.queryCommandState = function(type) {
var cmd = this._getCommand(type);
if (cmd && cmd.enableReadOnly) {
return me.bkqueryCommandState.apply(me, arguments);
}
return -1;
};
me.queryCommandValue = function(type) {
var cmd = this._getCommand(type);
if (cmd && cmd.enableReadOnly) {
return me.bkqueryCommandValue.apply(me, arguments);
}
return null;
};
this.setStatus('readonly');
me._interactChange();
},
enable: function() {
var me = this;
if (me.bkqueryCommandState) {
me.queryCommandState = me.bkqueryCommandState;
delete me.bkqueryCommandState;
}
if (me.bkqueryCommandValue) {
me.queryCommandValue = me.bkqueryCommandValue;
delete me.bkqueryCommandValue;
}
this.setStatus('normal');
me._interactChange();
return -1;
};
me.queryCommandValue = function (type) {
var cmd = this._getCommand(type);
if (cmd && cmd.enableReadOnly) {
return me.bkqueryCommandValue.apply(me, arguments);
}
});
});
return null;
};
this.setStatus('readonly');
me._interactChange();
},
enable: function () {
var me = this;
if (me.bkqueryCommandState) {
me.queryCommandState = me.bkqueryCommandState;
delete me.bkqueryCommandState;
}
if (me.bkqueryCommandValue) {
me.queryCommandValue = me.bkqueryCommandValue;
delete me.bkqueryCommandValue;
}
this.setStatus('normal');
me._interactChange();
},
});
});

View File

@ -222,22 +222,23 @@ define(function (require, exports, module) {
let dx = 0;
let dy = 0;
if (!focusNode || focusNode.type === 'root') {
// 默认居中
const parentNode = km.getPaper().node;
const shapeNode = km.getRoot().rc.container.node;
const { width: pw, height: ph, x: px, y: py } = parentNode.getBoundingClientRect();
const { width: sw, height: sh, x, y } = shapeNode.getBBox();
dx = pw / 2 - x - sw / 2;
dy = ph / 2 - y - sh / 2;
dragger.moveTo(new kity.Point(dx, dy), duration);
} else {
var viewport = km.getPaper().getViewPort();
var offset = focusNode.getRenderContainer().getRenderBox('view');
dx = viewport.center.x - offset.x - offset.width / 2;
dy = viewport.center.y - offset.y;
dragger.move(new kity.Point(dx, dy), duration);
}
// if (!focusNode || focusNode.type === 'root') {
// // 默认居中
// const parentNode = km.getPaper().node;
// const shapeNode = km.getRoot().rc.container.node;
// const { width: pw, height: ph, x: px, y: py } = parentNode.getBoundingClientRect();
// const { width: sw, height: sh, x, y } = shapeNode.getBBox();
// dx = pw / 2 - x - sw / 2;
// dy = ph / 2 - y - sh / 2;
// dragger.moveTo(new kity.Point(dx, dy), duration);
// } else {
focusNode = focusNode || km.getRoot();
var viewport = km.getPaper().getViewPort();
var offset = focusNode.getRenderContainer().getRenderBox('view');
dx = viewport.center.x - offset.x - offset.width / 2;
dy = viewport.center.y - offset.y;
dragger.move(new kity.Point(dx, dy), duration);
// }
this.setContentChanged(false);
},

View File

@ -7,139 +7,137 @@
* @copyright: Baidu FEX, 2014
*/
define(function(require, exports, module) {
var key = require('../tool/key');
define(function (require, exports, module) {
var key = require('../tool/key');
function ReceiverRuntime() {
var fsm = this.fsm;
var minder = this.minder;
var me = this;
function ReceiverRuntime() {
var fsm = this.fsm;
var minder = this.minder;
var me = this;
// 接收事件的 div
var element = document.createElement('div');
element.contentEditable = true;
/**
* @Desc: 增加tabindex属性使得element的contenteditable不管是trur还是false都能有focus和blur事件
* @Editor: Naixor
* @Date: 2015.09.14
*/
element.setAttribute('tabindex', -1);
element.classList.add('receiver');
element.onkeydown = element.onkeypress = element.onkeyup = dispatchKeyEvent;
element.addEventListener('compositionstart', dispatchKeyEvent);
// element.addEventListener('compositionend', dispatchKeyEvent);
this.container.appendChild(element);
// 接收事件的 div
var element = document.createElement('div');
element.contentEditable = true;
/**
* @Desc: 增加tabindex属性使得element的contenteditable不管是trur还是false都能有focus和blur事件
* @Editor: Naixor
* @Date: 2015.09.14
*/
element.setAttribute('tabindex', -1);
element.classList.add('receiver');
element.onkeydown = element.onkeypress = element.onkeyup = dispatchKeyEvent;
element.addEventListener('compositionstart', dispatchKeyEvent);
// element.addEventListener('compositionend', dispatchKeyEvent);
this.container.appendChild(element);
// receiver 对象
var receiver = {
element: element,
selectAll: function() {
// 保证有被选中的
if (!element.innerHTML) element.innerHTML = '&nbsp;';
var range = document.createRange();
var selection = window.getSelection();
range.selectNodeContents(element);
selection.removeAllRanges();
selection.addRange(range);
element.focus();
},
/**
* @Desc: 增加enable和disable方法用于解决热核态的输入法屏蔽问题
* @Editor: Naixor
* @Date: 2015.09.14
*/
enable: function() {
element.setAttribute("contenteditable", true);
},
disable: function() {
element.setAttribute("contenteditable", false);
},
/**
* @Desc: hack FF下div contenteditable的光标丢失BUG
* @Editor: Naixor
* @Date: 2015.10.15
*/
fixFFCaretDisappeared: function() {
element.removeAttribute("contenteditable");
element.setAttribute("contenteditable", "true");
element.blur();
element.focus();
},
/**
* 以此事件代替通过mouse事件来判断receiver丢失焦点的事件
* @editor Naixor
* @Date 2015-12-2
*/
onblur: function (handler) {
element.onblur = handler;
}
};
receiver.selectAll();
// receiver 对象
var receiver = {
element: element,
selectAll: function () {
// 保证有被选中的
if (!element.innerHTML) element.innerHTML = '&nbsp;';
var range = document.createRange();
var selection = window.getSelection();
range.selectNodeContents(element);
selection.removeAllRanges();
selection.addRange(range);
element.focus();
},
/**
* @Desc: 增加enable和disable方法用于解决热核态的输入法屏蔽问题
* @Editor: Naixor
* @Date: 2015.09.14
*/
enable: function () {
element.setAttribute('contenteditable', true);
},
disable: function () {
element.setAttribute('contenteditable', false);
},
/**
* @Desc: hack FF下div contenteditable的光标丢失BUG
* @Editor: Naixor
* @Date: 2015.10.15
*/
fixFFCaretDisappeared: function () {
element.removeAttribute('contenteditable');
element.setAttribute('contenteditable', 'true');
element.blur();
element.focus();
},
/**
* 以此事件代替通过mouse事件来判断receiver丢失焦点的事件
* @editor Naixor
* @Date 2015-12-2
*/
onblur: function (handler) {
element.onblur = handler;
},
};
receiver.selectAll();
minder.on('beforemousedown', receiver.selectAll);
minder.on('receiverfocus', receiver.selectAll);
minder.on('readonly', function() {
// 屏蔽minder的事件接受删除receiver和hotbox
minder.disable();
editor.receiver.element.parentElement.removeChild(editor.receiver.element);
editor.hotbox.$container.removeChild(editor.hotbox.$element);
});
minder.on('beforemousedown', receiver.selectAll);
minder.on('receiverfocus', receiver.selectAll);
minder.on('readonly', function () {
// 屏蔽minder的事件接受删除receiver和hotbox
minder.disable();
editor.receiver.element.parentElement.removeChild(editor.receiver.element);
editor.hotbox.$container.removeChild(editor.hotbox.$element);
});
// 侦听器,接收到的事件会派发给所有侦听器
var listeners = [];
// 侦听器,接收到的事件会派发给所有侦听器
var listeners = [];
// 侦听指定状态下的事件,如果不传 state侦听所有状态
receiver.listen = function(state, listener) {
if (arguments.length == 1) {
listener = state;
state = '*';
}
listener.notifyState = state;
listeners.push(listener);
};
// 侦听指定状态下的事件,如果不传 state侦听所有状态
receiver.listen = function (state, listener) {
if (arguments.length == 1) {
listener = state;
state = '*';
}
listener.notifyState = state;
listeners.push(listener);
};
function dispatchKeyEvent(e) {
e.is = function(keyExpression) {
var subs = keyExpression.split('|');
for (var i = 0; i < subs.length; i++) {
if (key.is(this, subs[i])) return true;
}
return false;
};
var listener, jumpState;
for (var i = 0; i < listeners.length; i++) {
listener = listeners[i];
// 忽略不在侦听状态的侦听器
if (listener.notifyState != '*' && listener.notifyState != fsm.state()) {
continue;
}
/**
*
* 对于所有的侦听器只允许一种处理方式跳转状态
* 如果侦听器确定要跳转则返回要跳转的状态
* 每个事件只允许一个侦听器进行状态跳转
* 跳转动作由侦听器自行完成因为可能需要在跳转时传递 reason返回跳转结果即可
* 比如
*
* ```js
* receiver.listen('normal', function(e) {
* if (isSomeReasonForJumpState(e)) {
* return fsm.jump('newstate', e);
* }
* });
* ```
*/
if (listener.call(null, e)) {
return;
}
}
function dispatchKeyEvent(e) {
e.is = function (keyExpression) {
var subs = keyExpression.split('|');
for (var i = 0; i < subs.length; i++) {
if (key.is(this, subs[i])) return true;
}
return false;
};
var listener, jumpState;
for (var i = 0; i < listeners.length; i++) {
listener = listeners[i];
// 忽略不在侦听状态的侦听器
if (listener.notifyState != '*' && listener.notifyState != fsm.state()) {
continue;
}
this.receiver = receiver;
/**
*
* 对于所有的侦听器只允许一种处理方式跳转状态
* 如果侦听器确定要跳转则返回要跳转的状态
* 每个事件只允许一个侦听器进行状态跳转
* 跳转动作由侦听器自行完成因为可能需要在跳转时传递 reason返回跳转结果即可
* 比如
*
* ```js
* receiver.listen('normal', function(e) {
* if (isSomeReasonForJumpState(e)) {
* return fsm.jump('newstate', e);
* }
* });
* ```
*/
if (listener.call(null, e)) {
return;
}
}
}
return module.exports = ReceiverRuntime;
this.receiver = receiver;
}
return (module.exports = ReceiverRuntime);
});

View File

@ -1,3 +1,22 @@
.wrap {
position: absolute;
top: 50%;
left: 1em;
z-index: 1000;
display: flex;
padding: 4px;
overflow-x: auto;
background-color: var(--semi-color-nav-bg);
border: 1px solid var(--semi-color-border);
border-radius: 3px;
transform: translateY(-50%);
flex-direction: column;
row-gap: 8px;
align-items: center;
box-shadow: var(--box-shadow);
user-select: none;
}
.sectionWrap {
margin-top: 16px;

View File

@ -0,0 +1,157 @@
import cls from 'classnames';
import { Button, Popover, Typography } from '@douyinfe/semi-ui';
import { IconMinus, IconPlus, IconAlignCenter } from '@douyinfe/semi-icons';
import { Tooltip } from 'components/tooltip';
import { IconStructure, IconDrawBoard, IconZoomIn, IconZoomOut } from 'components/icons';
import { Divider } from '../../../divider';
import { TEMPLATES, THEMES, MAX_ZOOM, MIN_ZOOM } from './constant';
import styles from './index.module.scss';
const { Text } = Typography;
interface IProps {
isEditable: boolean;
zoom: number | string;
template: string;
theme: string;
setZoomMinus: () => void;
setZoomPlus: () => void;
setCenter: () => void;
setTemplate: (arg: string) => void;
setTheme: (arg: string) => void;
}
export const Toolbar: React.FC<IProps> = ({
isEditable,
template,
theme,
zoom,
setZoomMinus,
setZoomPlus,
setCenter,
setTemplate,
setTheme,
}) => {
return (
<div className={styles.wrap}>
{isEditable ? (
<>
<Tooltip content="缩小" position="right">
<Button
size="small"
type="tertiary"
theme="borderless"
disabled={+zoom <= MIN_ZOOM}
icon={<IconZoomOut />}
onClick={setZoomMinus}
/>
</Tooltip>
<Text style={{ width: 26, textAlign: 'center' }}>{zoom}</Text>
<Tooltip content="放大" position="right">
<Button
size="small"
type="tertiary"
theme="borderless"
disabled={+zoom >= MAX_ZOOM}
icon={<IconZoomIn />}
onClick={setZoomPlus}
/>
</Tooltip>
<Tooltip content="居中" position="right">
<Button size="small" type="tertiary" theme="borderless" icon={<IconAlignCenter />} onClick={setCenter} />
</Tooltip>
<Popover
position="right"
zIndex={10000}
spacing={10}
style={{ padding: '0 12px 12px', overflow: 'hidden' }}
content={
<section className={styles.sectionWrap}>
<Text type="secondary"></Text>
<div>
<ul>
{TEMPLATES.map((item) => {
return (
<li
key={item.label}
className={cls(template === item.value && styles.active)}
onClick={() => setTemplate(item.value)}
>
<Text>{item.label}</Text>
</li>
);
})}
</ul>
</div>
</section>
}
>
<Button icon={<IconStructure />} type="tertiary" theme="borderless" size="small" />
</Popover>
<Popover
position="right"
zIndex={10000}
spacing={10}
style={{ padding: '0 12px 12px', overflow: 'hidden' }}
content={
<section className={styles.sectionWrap}>
<Text type="secondary"></Text>
<div>
<ul>
{THEMES.map((item) => {
return (
<li
key={item.label}
className={cls(theme === item.value && styles.active)}
style={item.style || {}}
onClick={() => setTheme(item.value)}
>
{item.label}
</li>
);
})}
</ul>
</div>
</section>
}
>
<Button icon={<IconDrawBoard />} type="tertiary" theme="borderless" size="small" />
</Popover>
</>
) : (
<>
<Tooltip content="缩小" position="right">
<Button
size="small"
theme="borderless"
type="tertiary"
icon={<IconMinus style={{ fontSize: 14 }} />}
onClick={setZoomMinus}
/>
</Tooltip>
<Tooltip content="放大" position="right">
<Button
size="small"
theme="borderless"
type="tertiary"
icon={<IconPlus style={{ fontSize: 14 }} />}
onClick={setZoomPlus}
/>
</Tooltip>
<Tooltip content="居中" position="right">
<Button
size="small"
theme="borderless"
type="tertiary"
icon={<IconAlignCenter style={{ fontSize: 14 }} />}
onClick={setCenter}
/>
</Tooltip>
</>
)}
</div>
);
};