mirror of https://github.com/fantasticit/think.git
tiptap: fix mind toolbar
This commit is contained in:
parent
a9eefd3c37
commit
2ad45a880e
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
|
|
@ -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 = ' ';
|
||||
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 = ' ';
|
||||
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);
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
Loading…
Reference in New Issue