tiptap: add center method in mind

pull/29/head
fantasticit 2022-04-26 19:13:37 +08:00
parent 08860dffee
commit 2e6a013203
11 changed files with 750 additions and 3818 deletions

3
packages/client/global.d.ts vendored 100644
View File

@ -0,0 +1,3 @@
interface Window {
kityminder: any;
}

View File

@ -7,14 +7,15 @@ import styles from './style.module.scss';
interface IProps { interface IProps {
width: number; width: number;
height: number; height: number;
onChange: (arg: { width: number; height: number }) => void; onChange?: (arg: { width: number; height: number }) => void;
onChangeEnd?: (arg: { width: number; height: number }) => void;
className?: string; className?: string;
} }
const MIN_WIDTH = 50; const MIN_WIDTH = 50;
const MIN_HEIGHT = 50; const MIN_HEIGHT = 50;
export const Resizeable: React.FC<IProps> = ({ width, height, className, onChange, children }) => { export const Resizeable: React.FC<IProps> = ({ width, height, className, onChange, onChangeEnd, children }) => {
const $container = useRef<HTMLDivElement>(null); const $container = useRef<HTMLDivElement>(null);
const $topLeft = useRef<HTMLDivElement>(null); const $topLeft = useRef<HTMLDivElement>(null);
const $topRight = useRef<HTMLDivElement>(null); const $topRight = useRef<HTMLDivElement>(null);
@ -52,6 +53,10 @@ export const Resizeable: React.FC<IProps> = ({ width, height, className, onChang
Object.assign(event.target.dataset, { x, y }); Object.assign(event.target.dataset, { x, y });
onChange && onChange({ width, height }); onChange && onChange({ width, height });
}, },
end: function (event) {
let { width, height } = event.rect;
onChangeEnd && onChangeEnd({ width, height });
},
}, },
}); });
}, []); }, []);

View File

@ -51,6 +51,10 @@ export const Mind = Node.create({
default: 100, default: 100,
parseHTML: getDatasetAttribute('zoom'), parseHTML: getDatasetAttribute('zoom'),
}, },
callCenterCount: {
default: 0,
parseHTML: (element) => Number(getDatasetAttribute('callcentercount')(element)),
},
}; };
}, },

View File

@ -1,8 +1,7 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useRouter } from 'next/router';
import cls from 'classnames'; import cls from 'classnames';
import { Space, Button, List, Popover, Typography, RadioGroup, Radio } from '@douyinfe/semi-ui'; import { Space, Button, Popover, Typography } from '@douyinfe/semi-ui';
import { IconEdit, IconDelete } from '@douyinfe/semi-icons'; import { IconAlignCenter, IconDelete } from '@douyinfe/semi-icons';
import { Tooltip } from 'components/tooltip'; import { Tooltip } from 'components/tooltip';
import { IconStructure, IconDrawBoard, IconZoomIn, IconZoomOut } from 'components/icons'; import { IconStructure, IconDrawBoard, IconZoomIn, IconZoomOut } from 'components/icons';
import { BubbleMenu } from '../../views/bubble-menu'; import { BubbleMenu } from '../../views/bubble-menu';
@ -15,7 +14,7 @@ import styles from './bubble.module.scss';
const { Text } = Typography; const { Text } = Typography;
export const MindBubbleMenu = ({ editor }) => { export const MindBubbleMenu = ({ editor }) => {
const { template, theme, zoom } = editor.getAttributes(Mind.name); const { template, theme, zoom, callCenterCount } = editor.getAttributes(Mind.name);
const setZoom = useCallback( const setZoom = useCallback(
(type: 'minus' | 'plus') => { (type: 'minus' | 'plus') => {
@ -32,6 +31,18 @@ export const MindBubbleMenu = ({ editor }) => {
[editor, zoom] [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( const setTemplate = useCallback(
(template) => { (template) => {
editor editor
@ -91,6 +102,11 @@ export const MindBubbleMenu = ({ editor }) => {
/> />
</Tooltip> </Tooltip>
<Tooltip content="居中">
<Button size="small" type="tertiary" theme="borderless" icon={<IconAlignCenter />} onClick={setCenter} />
</Tooltip>
<Divider />
<Popover <Popover
zIndex={10000} zIndex={10000}
spacing={10} spacing={10}
@ -103,6 +119,7 @@ export const MindBubbleMenu = ({ editor }) => {
{TEMPLATES.map((item) => { {TEMPLATES.map((item) => {
return ( return (
<li <li
key={item.label}
className={cls(template === item.value && styles.active)} className={cls(template === item.value && styles.active)}
onClick={() => setTemplate(item.value)} onClick={() => setTemplate(item.value)}
> >
@ -130,6 +147,7 @@ export const MindBubbleMenu = ({ editor }) => {
{THEMES.map((item) => { {THEMES.map((item) => {
return ( return (
<li <li
key={item.label}
className={cls(theme === item.value && styles.active)} className={cls(theme === item.value && styles.active)}
style={item.style || {}} style={item.style || {}}
onClick={() => setTheme(item.value)} onClick={() => setTheme(item.value)}

View File

@ -1,10 +1,11 @@
import { NodeViewWrapper } from '@tiptap/react'; import { NodeViewWrapper } from '@tiptap/react';
import cls from 'classnames'; import cls from 'classnames';
import { useCallback, useEffect, useRef } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Spin, Button } from '@douyinfe/semi-ui'; import { Spin, Button, Typography } from '@douyinfe/semi-ui';
import { IconMinus, IconPlus } from '@douyinfe/semi-icons'; import { IconMinus, IconPlus, IconAlignCenter } from '@douyinfe/semi-icons';
import { Resizeable } from 'components/resizeable';
import deepEqual from 'deep-equal'; import deepEqual from 'deep-equal';
import { Resizeable } from 'components/resizeable';
import { Tooltip } from 'components/tooltip';
import { useToggle } from 'hooks/use-toggle'; import { useToggle } from 'hooks/use-toggle';
import { MIN_ZOOM, MAX_ZOOM, ZOOM_STEP } from '../../menus/mind/constant'; import { MIN_ZOOM, MAX_ZOOM, ZOOM_STEP } from '../../menus/mind/constant';
import { clamp } from '../../utils/clamp'; import { clamp } from '../../utils/clamp';
@ -12,21 +13,54 @@ import { Mind } from '../../extensions/mind';
import { loadKityMinder } from './kityminder'; import { loadKityMinder } from './kityminder';
import styles from './index.module.scss'; import styles from './index.module.scss';
const { Text } = Typography;
export const MindWrapper = ({ editor, node, updateAttributes }) => { export const MindWrapper = ({ editor, node, updateAttributes }) => {
const $container = useRef(); const $container = useRef();
const $mind = useRef<any>(); const $mind = useRef<any>();
const isMindActive = editor.isActive(Mind.name); const isMindActive = editor.isActive(Mind.name);
const isEditable = editor.isEditable; const isEditable = editor.isEditable;
const { data, template, theme, zoom, width, height = 100 } = node.attrs; const { data, template, theme, zoom, callCenterCount, width, height } = node.attrs;
const [loading, toggleLoading] = useToggle(true); const [loading, toggleLoading] = useToggle(true);
const [error, setError] = useState<Error | null>(null);
const content = useMemo(() => {
if (error) {
return (
<div style={{ width: '100%', height: '100%' }}>
<Text>{error.message || error}</Text>
</div>
);
}
if (loading) {
return <Spin spinning={loading} style={{ width: '100%', height: '100%' }}></Spin>;
}
return (
<div
ref={$container}
className={cls(styles.renderWrap, 'render-wrapper')}
tabIndex={0}
style={{ width: '100%', height: '100%' }}
></div>
);
}, [loading, error]);
const onResize = useCallback( const onResize = useCallback(
(size) => { (size) => {
updateAttributes({ width: size.width, height: size.height }); updateAttributes({ width: size.width, height: size.height });
setCenter();
}, },
[updateAttributes] [updateAttributes]
); );
const setCenter = useCallback(() => {
const minder = $mind.current;
if (!minder) return;
minder.execCommand('camera');
}, []);
const setZoom = useCallback( const setZoom = useCallback(
(type: 'minus' | 'plus') => { (type: 'minus' | 'plus') => {
return () => { return () => {
@ -50,30 +84,46 @@ export const MindWrapper = ({ editor, node, updateAttributes }) => {
updateAttributes({ data: minder.exportJson() }); updateAttributes({ data: minder.exportJson() });
}, [updateAttributes]); }, [updateAttributes]);
// 初始化 // 加载依赖
useEffect(() => { useEffect(() => {
loadKityMinder()
.then(() => {
toggleLoading(false);
})
.catch((e) => {
setError(e);
});
}, []);
// 初始化渲染
useEffect(() => {
if (loading || !$container.current) return;
const onChange = () => { const onChange = () => {
saveData(); saveData();
}; };
loadKityMinder().then((Editor) => {
toggleLoading(false);
try { try {
const minder = new Editor($container.current).minder; const Editor = window.kityminder.Editor;
minder.importJson(data); const minder = new Editor($container.current).minder;
$mind.current = minder; minder.importJson(data);
minder.on('contentChange', onChange); minder.execCommand('template', template);
} catch (e) { minder.execCommand('theme', theme);
// minder.execCommand('zoom', parseInt(zoom));
}
}); $mind.current = minder;
minder.on('contentChange', onChange);
toggleLoading(false);
} catch (e) {
setError(e);
}
return () => { return () => {
if ($mind.current) { if ($mind.current) {
$mind.current.off('contentChange', onChange); $mind.current.off('contentChange', onChange);
} }
}; };
}, [toggleLoading]); }, [loading]);
// 数据同步渲染 // 数据同步渲染
useEffect(() => { useEffect(() => {
@ -82,7 +132,9 @@ export const MindWrapper = ({ editor, node, updateAttributes }) => {
const currentData = minder.exportJson(); const currentData = minder.exportJson();
const isEqual = deepEqual(currentData, data); const isEqual = deepEqual(currentData, data);
if (isEqual) return; if (isEqual) return;
minder.importData(data);
// TODO: 也许刷新更好些
minder.importJson(data);
}, [data]); }, [data]);
// 布局 // 布局
@ -118,21 +170,15 @@ export const MindWrapper = ({ editor, node, updateAttributes }) => {
} }
}, [isEditable]); }, [isEditable]);
const content = loading ? ( // 居中
<Spin spinning={loading} style={{ width: '100%', height: '100%' }}></Spin> useEffect(() => {
) : ( setCenter();
<div }, [callCenterCount]);
ref={$container}
className={cls(styles.renderWrap, 'render-wrapper')}
tabIndex={0}
style={{ width: '100%', height: '100%' }}
></div>
);
return ( return (
<NodeViewWrapper className={cls(styles.wrap, isMindActive && styles.isActive)}> <NodeViewWrapper className={cls(styles.wrap, isMindActive && styles.isActive)}>
{isEditable ? ( {isEditable ? (
<Resizeable width={width} height={height} onChange={onResize}> <Resizeable width={width} height={height} onChangeEnd={onResize}>
{content} {content}
</Resizeable> </Resizeable>
) : ( ) : (
@ -141,20 +187,33 @@ export const MindWrapper = ({ editor, node, updateAttributes }) => {
{!isEditable && ( {!isEditable && (
<div className={styles.mindHandlerWrap}> <div className={styles.mindHandlerWrap}>
<Button <Tooltip content="缩小">
size="small" <Button
theme="borderless" size="small"
type="tertiary" theme="borderless"
icon={<IconMinus style={{ fontSize: 14 }} />} type="tertiary"
onClick={setZoom('minus')} icon={<IconMinus style={{ fontSize: 14 }} />}
/> onClick={setZoom('minus')}
<Button />
size="small" </Tooltip>
theme="borderless" <Tooltip content="放大">
type="tertiary" <Button
icon={<IconPlus style={{ fontSize: 14 }} />} size="small"
onClick={setZoom('plus')} 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>
)} )}
</NodeViewWrapper> </NodeViewWrapper>

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,11 @@
export const loadKityMinder = async (): Promise<any> => { export const loadKityMinder = async (): Promise<any> => {
if (typeof window !== 'undefined') {
if (window.kityminder) {
if (window.kityminder.Editor) return;
}
}
await import('kity'); await import('kity');
await import('./kity-core/kityminder'); await import('./kity-core/kityminder');
await import('./kity-editor/expose-editor'); await import('./kity-editor/expose-editor');
const Editor = (window as any).kityminder.Editor;
return Editor;
}; };

View File

@ -1,151 +1,149 @@
define(function(require, exports, module) { define(function (require, exports, module) {
var kity = require('./kity'); var kity = require('./kity');
var utils = require('./utils'); var utils = require('./utils');
var Minder = require('./minder'); var Minder = require('./minder');
/* 已注册的模块 */ /* 已注册的模块 */
var _modules = {}; var _modules = {};
exports.register = function(name, module) { exports.register = function (name, module) {
_modules[name] = module; _modules[name] = module;
}; };
/* 模块初始化 */ /* 模块初始化 */
Minder.registerInitHook(function() { Minder.registerInitHook(function () {
this._initModules(); this._initModules();
}); });
// 模块声明周期维护 // 模块声明周期维护
kity.extendClass(Minder, { kity.extendClass(Minder, {
_initModules: function() { _initModules: function () {
var modulesPool = _modules; var modulesPool = _modules;
var modulesToLoad = this._options.modules || utils.keys(modulesPool); var modulesToLoad = this._options.modules || utils.keys(modulesPool);
this._commands = {}; this._commands = {};
this._query = {}; this._query = {};
this._modules = {}; this._modules = {};
this._rendererClasses = {}; this._rendererClasses = {};
var i, name, type, module, moduleDeals, var i, name, type, module, moduleDeals, dealCommands, dealEvents, dealRenderers;
dealCommands, dealEvents, dealRenderers;
var me = this; var me = this;
for (i = 0; i < modulesToLoad.length; i++) { for (i = 0; i < modulesToLoad.length; i++) {
name = modulesToLoad[i]; name = modulesToLoad[i];
if (!modulesPool[name]) continue; if (!modulesPool[name]) continue;
// 执行模块初始化,抛出后续处理对象 // 执行模块初始化,抛出后续处理对象
if (typeof(modulesPool[name]) == 'function') { if (typeof modulesPool[name] == 'function') {
moduleDeals = modulesPool[name].call(me); moduleDeals = modulesPool[name].call(me);
} else { } else {
moduleDeals = modulesPool[name]; moduleDeals = modulesPool[name];
}
this._modules[name] = moduleDeals;
if (!moduleDeals) continue;
if (moduleDeals.defaultOptions) {
me.setDefaultOptions(moduleDeals.defaultOptions);
}
if (moduleDeals.init) {
moduleDeals.init.call(me, this._options);
}
/**
* @Desc: 判断是否支持原生clipboard事件如果支持则对pager添加其监听
* @Editor: Naixor
* @Date: 2015.9.20
*/
/**
* 由于当前脑图解构问题clipboard暂时全权交由玩不托管
* @Editor: Naixor
* @Date: 2015.9.24
*/
// if (name === 'ClipboardModule' && this.supportClipboardEvent && !kity.Browser.gecko) {
// var on = function () {
// var clipBoardReceiver = this.clipBoardReceiver || document;
// if (document.addEventListener) {
// clipBoardReceiver.addEventListener.apply(this, arguments);
// } else {
// arguments[0] = 'on' + arguments[0];
// clipBoardReceiver.attachEvent.apply(this, arguments);
// }
// }
// for (var command in moduleDeals.clipBoardEvents) {
// on(command, moduleDeals.clipBoardEvents[command]);
// }
// };
// command加入命令池子
dealCommands = moduleDeals.commands;
for (name in dealCommands) {
this._commands[name.toLowerCase()] = new dealCommands[name]();
}
// 绑定事件
dealEvents = moduleDeals.events;
if (dealEvents) {
for (type in dealEvents) {
me.on(type, dealEvents[type]);
}
}
// 渲染器
dealRenderers = moduleDeals.renderers;
if (dealRenderers) {
for (type in dealRenderers) {
this._rendererClasses[type] = this._rendererClasses[type] || [];
if (utils.isArray(dealRenderers[type])) {
this._rendererClasses[type] = this._rendererClasses[type].concat(dealRenderers[type]);
} else {
this._rendererClasses[type].push(dealRenderers[type]);
}
}
}
//添加模块的快捷键
if (moduleDeals.commandShortcutKeys) {
this.addCommandShortcutKeys(moduleDeals.commandShortcutKeys);
}
}
},
_garbage: function() {
this.clearSelect();
while (this._root.getChildren().length) {
this._root.removeChild(0);
}
},
destroy: function() {
var modules = this._modules;
this._resetEvents();
this._garbage();
for (var key in modules) {
if (!modules[key].destroy) continue;
modules[key].destroy.call(this);
}
},
reset: function() {
var modules = this._modules;
this._garbage();
for (var key in modules) {
if (!modules[key].reset) continue;
modules[key].reset.call(this);
}
} }
}); this._modules[name] = moduleDeals;
});
if (!moduleDeals) continue;
if (moduleDeals.defaultOptions) {
me.setDefaultOptions(moduleDeals.defaultOptions);
}
if (moduleDeals.init) {
moduleDeals.init.call(me, this._options);
}
/**
* @Desc: 判断是否支持原生clipboard事件如果支持则对pager添加其监听
* @Editor: Naixor
* @Date: 2015.9.20
*/
/**
* 由于当前脑图解构问题clipboard暂时全权交由玩不托管
* @Editor: Naixor
* @Date: 2015.9.24
*/
// if (name === 'ClipboardModule' && this.supportClipboardEvent && !kity.Browser.gecko) {
// var on = function () {
// var clipBoardReceiver = this.clipBoardReceiver || document;
// if (document.addEventListener) {
// clipBoardReceiver.addEventListener.apply(this, arguments);
// } else {
// arguments[0] = 'on' + arguments[0];
// clipBoardReceiver.attachEvent.apply(this, arguments);
// }
// }
// for (var command in moduleDeals.clipBoardEvents) {
// on(command, moduleDeals.clipBoardEvents[command]);
// }
// };
// command加入命令池子
dealCommands = moduleDeals.commands;
for (name in dealCommands) {
this._commands[name.toLowerCase()] = new dealCommands[name]();
}
// 绑定事件
dealEvents = moduleDeals.events;
if (dealEvents) {
for (type in dealEvents) {
me.on(type, dealEvents[type]);
}
}
// 渲染器
dealRenderers = moduleDeals.renderers;
if (dealRenderers) {
for (type in dealRenderers) {
this._rendererClasses[type] = this._rendererClasses[type] || [];
if (utils.isArray(dealRenderers[type])) {
this._rendererClasses[type] = this._rendererClasses[type].concat(dealRenderers[type]);
} else {
this._rendererClasses[type].push(dealRenderers[type]);
}
}
}
//添加模块的快捷键
if (moduleDeals.commandShortcutKeys) {
this.addCommandShortcutKeys(moduleDeals.commandShortcutKeys);
}
}
},
_garbage: function () {
// this.clearSelect();
while (this._root.getChildren().length) {
this._root.removeChild(0);
}
},
destroy: function () {
var modules = this._modules;
this._resetEvents();
this._garbage();
for (var key in modules) {
if (!modules[key].destroy) continue;
modules[key].destroy.call(this);
}
},
reset: function () {
var modules = this._modules;
this._garbage();
for (var key in modules) {
if (!modules[key].reset) continue;
modules[key].reset.call(this);
}
},
});
});

View File

@ -1,92 +1,101 @@
define(function(require, exports, module) { define(function (require, exports, module) {
var kity = require('./kity'); var kity = require('./kity');
var utils = require('./utils'); var utils = require('./utils');
var Minder = require('./minder'); var Minder = require('./minder');
var Command = require('./command'); var Command = require('./command');
var MinderNode = require('./node'); var MinderNode = require('./node');
var Module = require('./module'); var Module = require('./module');
var _templates = {}; var _templates = {};
function register(name, supports) { function register(name, supports) {
_templates[name] = supports; _templates[name] = supports;
} }
exports.register = register; exports.register = register;
utils.extend(Minder, { utils.extend(Minder, {
getTemplateList: function() { getTemplateList: function () {
return _templates; return _templates;
} },
}); });
kity.extendClass(Minder, (function() { kity.extendClass(
var originGetTheme = Minder.prototype.getTheme; Minder,
return { (function () {
useTemplate: function(name, duration) { var originGetTheme = Minder.prototype.getTheme;
this.setTemplate(name); return {
this.refresh(duration || 800); useTemplate: function (name, duration) {
}, this.setTemplate(name);
this.refresh(duration || 500);
},
getTemplate: function() { getTemplate: function () {
return this._template || 'default'; return this._template || 'default';
}, },
setTemplate: function(name) { setTemplate: function (name) {
this._template = name || null; this._template = name || null;
}, },
getTemplateSupport: function(method) { getTemplateSupport: function (method) {
var supports = _templates[this.getTemplate()]; var supports = _templates[this.getTemplate()];
return supports && supports[method]; return supports && supports[method];
}, },
getTheme: function(node) { getTheme: function (node) {
var support = this.getTemplateSupport('getTheme') || originGetTheme; var support = this.getTemplateSupport('getTheme') || originGetTheme;
return support.call(this, node); return support.call(this, node);
} },
}; };
})()); })()
);
kity.extendClass(
MinderNode,
(function () {
var originGetLayout = MinderNode.prototype.getLayout;
var originGetConnect = MinderNode.prototype.getConnect;
return {
getLayout: function () {
var support = this.getMinder().getTemplateSupport('getLayout') || originGetLayout;
return support.call(this, this);
},
kity.extendClass(MinderNode, (function() { getConnect: function () {
var originGetLayout = MinderNode.prototype.getLayout; var support = this.getMinder().getTemplateSupport('getConnect') || originGetConnect;
var originGetConnect = MinderNode.prototype.getConnect; return support.call(this, this);
return { },
getLayout: function() { };
var support = this.getMinder().getTemplateSupport('getLayout') || originGetLayout; })()
return support.call(this, this); );
}, let timer = null;
getConnect: function() { Module.register('TemplateModule', {
var support = this.getMinder().getTemplateSupport('getConnect') || originGetConnect; /**
return support.call(this, this); * @command Template
} * @description 设置当前脑图的模板
}; * @param {string} name 模板名称
})()); * 允许使用的模板可以使用 `kityminder.Minder.getTemplateList()` 查询
* @state
* 0: 始终可用
* @return 返回当前的模板名称
*/
commands: {
template: kity.createClass('TemplateCommand', {
base: Command,
Module.register('TemplateModule', { execute: function (minder, name) {
/** minder.useTemplate(name);
* @command Template clearTimeout(timer);
* @description 设置当前脑图的模板 timer = setTimeout(() => {
* @param {string} name 模板名称 minder.execCommand('camera');
* 允许使用的模板可以使用 `kityminder.Minder.getTemplateList()` 查询 }, 550);
* @state },
* 0: 始终可用
* @return 返回当前的模板名称
*/
commands: {
'template': kity.createClass('TemplateCommand', {
base: Command,
execute: function(minder, name) { queryValue: function (minder) {
minder.useTemplate(name); return minder.getTemplate() || 'default';
minder.execCommand('camera'); },
}, }),
},
queryValue: function(minder) { });
return minder.getTemplate() || 'default'; });
}
})
}
});
});

View File

@ -1,397 +1,403 @@
define(function(require, exports, module) { define(function (require, exports, module) {
var kity = require('../core/kity'); var kity = require('../core/kity');
var utils = require('../core/utils'); var utils = require('../core/utils');
var Minder = require('../core/minder'); var Minder = require('../core/minder');
var MinderNode = require('../core/node'); var MinderNode = require('../core/node');
var Command = require('../core/command'); var Command = require('../core/command');
var Module = require('../core/module'); var Module = require('../core/module');
var Renderer = require('../core/render'); var Renderer = require('../core/render');
var ViewDragger = kity.createClass('ViewDragger', { var ViewDragger = kity.createClass('ViewDragger', {
constructor: function(minder) { constructor: function (minder) {
this._minder = minder; this._minder = minder;
this._enabled = false; this._enabled = false;
this._bind(); this._bind();
var me = this; var me = this;
this._minder.getViewDragger = function() { this._minder.getViewDragger = function () {
return me; return me;
}; };
this.setEnabled(false); this.setEnabled(false);
}, },
isEnabled: function() { isEnabled: function () {
return this._enabled; return this._enabled;
}, },
setEnabled: function(value) { setEnabled: function (value) {
var paper = this._minder.getPaper(); var paper = this._minder.getPaper();
paper.setStyle('cursor', value ? 'pointer' : 'default'); paper.setStyle('cursor', value ? 'pointer' : 'default');
paper.setStyle('cursor', value ? '-webkit-grab' : 'default'); paper.setStyle('cursor', value ? '-webkit-grab' : 'default');
this._enabled = value; this._enabled = value;
}, },
timeline: function() { timeline: function () {
return this._moveTimeline; return this._moveTimeline;
}, },
move: function(offset, duration) { move: function (offset, duration) {
var minder = this._minder; var minder = this._minder;
var targetPosition = this.getMovement().offset(offset); var targetPosition = this.getMovement().offset(offset);
this.moveTo(targetPosition, duration); this.moveTo(targetPosition, duration);
}, },
moveTo: function(position, duration) { moveTo: function (position, duration) {
if (duration) {
var dragger = this;
if (duration) { if (this._moveTimeline) this._moveTimeline.stop();
var dragger = this;
if (this._moveTimeline) this._moveTimeline.stop(); this._moveTimeline = this._minder
.getRenderContainer()
.animate(
new kity.Animator(this.getMovement(), position, function (target, value) {
dragger.moveTo(value);
}),
duration,
'easeOutCubic'
)
.timeline();
this._moveTimeline = this._minder.getRenderContainer().animate(new kity.Animator( this._moveTimeline.on('finish', function () {
this.getMovement(), dragger._moveTimeline = null;
position, });
function(target, value) {
dragger.moveTo(value);
}
), duration, 'easeOutCubic').timeline();
this._moveTimeline.on('finish', function() { return this;
dragger._moveTimeline = null; }
});
return this; this._minder.getRenderContainer().setTranslate(position.round());
} this._minder.fire('viewchange');
},
this._minder.getRenderContainer().setTranslate(position.round()); getMovement: function () {
this._minder.fire('viewchange'); var translate = this._minder.getRenderContainer().transform.translate;
}, return translate ? translate[0] : new kity.Point();
},
getMovement: function() { getView: function () {
var translate = this._minder.getRenderContainer().transform.translate; var minder = this._minder;
return translate ? translate[0] : new kity.Point(); var c = minder._lastClientSize || {
}, width: minder.getRenderTarget().clientWidth,
height: minder.getRenderTarget().clientHeight,
};
var m = this.getMovement();
var box = new kity.Box(0, 0, c.width, c.height);
var viewMatrix = minder.getPaper().getViewPortMatrix();
return viewMatrix.inverse().translate(-m.x, -m.y).transformBox(box);
},
getView: function() { _bind: function () {
var minder = this._minder; var dragger = this,
var c = minder._lastClientSize || { isTempDrag = false,
width: minder.getRenderTarget().clientWidth, lastPosition = null,
height: minder.getRenderTarget().clientHeight currentPosition = null;
};
var m = this.getMovement();
var box = new kity.Box(0, 0, c.width, c.height);
var viewMatrix = minder.getPaper().getViewPortMatrix();
return viewMatrix.inverse().translate(-m.x, -m.y).transformBox(box);
},
_bind: function() { function dragEnd(e) {
var dragger = this, if (!lastPosition) return;
isTempDrag = false,
lastPosition = null,
currentPosition = null;
function dragEnd(e) { lastPosition = null;
if (!lastPosition) return;
e.stopPropagation();
lastPosition = null; // 临时拖动需要还原状态
if (isTempDrag) {
e.stopPropagation(); dragger.setEnabled(false);
isTempDrag = false;
// 临时拖动需要还原状态 if (dragger._minder.getStatus() == 'hand') dragger._minder.rollbackStatus();
if (isTempDrag) {
dragger.setEnabled(false);
isTempDrag = false;
if (dragger._minder.getStatus() == 'hand')
dragger._minder.rollbackStatus();
}
var paper = dragger._minder.getPaper();
paper.setStyle('cursor', dragger._minder.getStatus() == 'hand' ? '-webkit-grab' : 'default');
dragger._minder.fire('viewchanged');
}
this._minder.on('normal.mousedown normal.touchstart ' +
'inputready.mousedown inputready.touchstart ' +
'readonly.mousedown readonly.touchstart',
function(e) {
if (e.originEvent.button == 2) {
e.originEvent.preventDefault(); // 阻止中键拉动
}
// 点击未选中的根节点临时开启
if (e.getTargetNode() == this.getRoot() || e.originEvent.button == 2 || e.originEvent.altKey) {
lastPosition = e.getPosition('view');
isTempDrag = true;
}
})
.on('normal.mousemove normal.touchmove ' +
'readonly.mousemove readonly.touchmove ' +
'inputready.mousemove inputready.touchmove',
function(e) {
if (e.type == 'touchmove') {
e.preventDefault(); // 阻止浏览器的后退事件
}
if (!isTempDrag) return;
var offset = kity.Vector.fromPoints(lastPosition, e.getPosition('view'));
if (offset.length() > 10) {
this.setStatus('hand', true);
var paper = dragger._minder.getPaper();
paper.setStyle('cursor', '-webkit-grabbing');
}
})
.on('hand.beforemousedown hand.beforetouchstart', function(e) {
// 已经被用户打开拖放模式
if (dragger.isEnabled()) {
lastPosition = e.getPosition('view');
e.stopPropagation();
var paper = dragger._minder.getPaper();
paper.setStyle('cursor', '-webkit-grabbing');
}
})
.on('hand.beforemousemove hand.beforetouchmove', function(e) {
if (lastPosition) {
currentPosition = e.getPosition('view');
// 当前偏移加上历史偏移
var offset = kity.Vector.fromPoints(lastPosition, currentPosition);
dragger.move(offset);
e.stopPropagation();
e.preventDefault();
e.originEvent.preventDefault();
lastPosition = currentPosition;
}
})
.on('mouseup touchend', dragEnd);
window.addEventListener('mouseup', dragEnd);
this._minder.on('contextmenu', function(e) {
e.preventDefault();
});
} }
}); var paper = dragger._minder.getPaper();
paper.setStyle('cursor', dragger._minder.getStatus() == 'hand' ? '-webkit-grab' : 'default');
Module.register('View', function() { dragger._minder.fire('viewchanged');
}
var km = this; this._minder
.on(
/** 'normal.mousedown normal.touchstart ' +
* @command Hand 'inputready.mousedown inputready.touchstart ' +
* @description 切换抓手状态抓手状态下鼠标拖动将拖动视野而不是创建选区 'readonly.mousedown readonly.touchstart',
* @state function (e) {
* 0: 当前不是抓手状态 if (e.originEvent.button == 2) {
* 1: 当前是抓手状态 e.originEvent.preventDefault(); // 阻止中键拉动
*/
var ToggleHandCommand = kity.createClass('ToggleHandCommand', {
base: Command,
execute: function(minder) {
if (minder.getStatus() != 'hand') {
minder.setStatus('hand', true);
} else {
minder.rollbackStatus();
}
this.setContentChanged(false);
},
queryState: function(minder) {
return minder.getStatus() == 'hand' ? 1 : 0;
},
enableReadOnly: true
});
/**
* @command Camera
* @description 设置当前视野的中心位置到某个节点上
* @param {kityminder.MinderNode} focusNode 要定位的节点
* @param {number} duration 设置视野移动的动画时长单位 ms设置为 0 不使用动画
* @state
* 0: 始终可用
*/
var CameraCommand = kity.createClass('CameraCommand', {
base: Command,
execute: function(km, focusNode) {
focusNode = focusNode || km.getRoot();
var viewport = km.getPaper().getViewPort();
var offset = focusNode.getRenderContainer().getRenderBox('view');
var dx = viewport.center.x - offset.x - offset.width / 2,
dy = viewport.center.y - offset.y;
var dragger = km._viewDragger;
var duration = km.getOption('viewAnimationDuration');
dragger.move(new kity.Point(dx, dy), duration);
this.setContentChanged(false);
},
enableReadOnly: true
});
/**
* @command Move
* @description 指定方向移动当前视野
* @param {string} dir 移动方向
* 取值为 'left'视野向左移动一半
* 取值为 'right'视野向右移动一半
* 取值为 'up'视野向上移动一半
* 取值为 'down'视野向下移动一半
* @param {number} duration 视野移动的动画时长单位 ms设置为 0 不使用动画
* @state
* 0: 始终可用
*/
var MoveCommand = kity.createClass('MoveCommand', {
base: Command,
execute: function(km, dir) {
var dragger = km._viewDragger;
var size = km._lastClientSize;
var duration = km.getOption('viewAnimationDuration');
switch (dir) {
case 'up':
dragger.move(new kity.Point(0, size.height / 2), duration);
break;
case 'down':
dragger.move(new kity.Point(0, -size.height / 2), duration);
break;
case 'left':
dragger.move(new kity.Point(size.width / 2, 0), duration);
break;
case 'right':
dragger.move(new kity.Point(-size.width / 2, 0), duration);
break;
}
},
enableReadOnly: true
});
return {
init: function() {
this._viewDragger = new ViewDragger(this);
},
commands: {
'hand': ToggleHandCommand,
'camera': CameraCommand,
'move': MoveCommand
},
events: {
statuschange: function(e) {
this._viewDragger.setEnabled(e.currentStatus == 'hand');
},
mousewheel: function(e) {
var dx, dy;
e = e.originEvent;
if (e.ctrlKey || e.shiftKey) return;
if ('wheelDeltaX' in e) {
dx = e.wheelDeltaX || 0;
dy = e.wheelDeltaY || 0;
} else {
dx = 0;
dy = e.wheelDelta;
}
this._viewDragger.move({
x: dx / 2.5,
y: dy / 2.5
});
var me = this;
clearTimeout(this._mousewheeltimer);
this._mousewheeltimer = setTimeout(function() {
me.fire('viewchanged');
}, 100);
e.preventDefault();
},
'normal.dblclick readonly.dblclick': function(e) {
if (e.kityEvent.targetShape instanceof kity.Paper) {
this.execCommand('camera', this.getRoot(), 800);
}
},
'paperrender finishInitHook': function() {
if (!this.getRenderTarget()) {
return;
}
this.execCommand('camera', null, 0);
this._lastClientSize = {
width: this.getRenderTarget().clientWidth,
height: this.getRenderTarget().clientHeight
};
},
resize: function(e) {
var a = {
width: this.getRenderTarget().clientWidth,
height: this.getRenderTarget().clientHeight
},
b = this._lastClientSize;
this._viewDragger.move(
new kity.Point((a.width - b.width) / 2 | 0, (a.height - b.height) / 2 | 0));
this._lastClientSize = a;
},
'selectionchange layoutallfinish': function(e) {
var selected = this.getSelectedNode();
var minder = this;
/*
* Added by zhangbobell 2015.9.9
* windows 10 edge 浏览器在全部动画停止后优先级图标不显示 text
* 因此再次触发一次 render 事件让浏览器重绘
* */
if (kity.Browser.edge) {
this.fire('paperrender');
}
if (!selected) return;
var dragger = this._viewDragger;
var timeline = dragger.timeline();
/*
* Added by zhangbobell 2015.09.25
* 如果之前有动画那么就先暂时返回等之前动画结束之后再次执行本函数
* 以防止 view 动画变动了位置导致本函数执行的时候位置计算不对
*
* fixed bug : 初始化的时候中心节点位置不固定有的时候在左上角有的时候在中心
* */
if (timeline){
timeline.on('finish', function() {
minder.fire('selectionchange');
});
return;
}
var view = dragger.getView();
var focus = selected.getLayoutBox();
var space = 50;
var dx = 0, dy = 0;
if (focus.right > view.right) {
dx += view.right - focus.right - space;
}
else if (focus.left < view.left) {
dx += view.left - focus.left + space;
}
if (focus.bottom > view.bottom) {
dy += view.bottom - focus.bottom - space;
}
if (focus.top < view.top) {
dy += view.top - focus.top + space;
}
if (dx || dy) dragger.move(new kity.Point(dx, dy), 100);
}
} }
}; // 点击未选中的根节点临时开启
if (e.getTargetNode() == this.getRoot() || e.originEvent.button == 2 || e.originEvent.altKey) {
lastPosition = e.getPosition('view');
isTempDrag = true;
}
}
)
.on(
'normal.mousemove normal.touchmove ' +
'readonly.mousemove readonly.touchmove ' +
'inputready.mousemove inputready.touchmove',
function (e) {
if (e.type == 'touchmove') {
e.preventDefault(); // 阻止浏览器的后退事件
}
if (!isTempDrag) return;
var offset = kity.Vector.fromPoints(lastPosition, e.getPosition('view'));
if (offset.length() > 10) {
this.setStatus('hand', true);
var paper = dragger._minder.getPaper();
paper.setStyle('cursor', '-webkit-grabbing');
}
}
)
.on('hand.beforemousedown hand.beforetouchstart', function (e) {
// 已经被用户打开拖放模式
if (dragger.isEnabled()) {
lastPosition = e.getPosition('view');
e.stopPropagation();
var paper = dragger._minder.getPaper();
paper.setStyle('cursor', '-webkit-grabbing');
}
})
.on('hand.beforemousemove hand.beforetouchmove', function (e) {
if (lastPosition) {
currentPosition = e.getPosition('view');
// 当前偏移加上历史偏移
var offset = kity.Vector.fromPoints(lastPosition, currentPosition);
dragger.move(offset);
e.stopPropagation();
e.preventDefault();
e.originEvent.preventDefault();
lastPosition = currentPosition;
}
})
.on('mouseup touchend', dragEnd);
window.addEventListener('mouseup', dragEnd);
this._minder.on('contextmenu', function (e) {
e.preventDefault();
});
},
});
Module.register('View', function () {
var km = this;
/**
* @command Hand
* @description 切换抓手状态抓手状态下鼠标拖动将拖动视野而不是创建选区
* @state
* 0: 当前不是抓手状态
* 1: 当前是抓手状态
*/
var ToggleHandCommand = kity.createClass('ToggleHandCommand', {
base: Command,
execute: function (minder) {
if (minder.getStatus() != 'hand') {
minder.setStatus('hand', true);
} else {
minder.rollbackStatus();
}
this.setContentChanged(false);
},
queryState: function (minder) {
return minder.getStatus() == 'hand' ? 1 : 0;
},
enableReadOnly: true,
}); });
});
/**
* @command Camera
* @description 设置当前视野的中心位置到某个节点上
* @param {kityminder.MinderNode} focusNode 要定位的节点
* @param {number} duration 设置视野移动的动画时长单位 ms设置为 0 不使用动画
* @state
* 0: 始终可用
*/
var CameraCommand = kity.createClass('CameraCommand', {
base: Command,
execute: function (km, focusNode) {
const dragger = km._viewDragger;
const duration = km.getOption('viewAnimationDuration');
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);
}
this.setContentChanged(false);
},
enableReadOnly: true,
});
/**
* @command Move
* @description 指定方向移动当前视野
* @param {string} dir 移动方向
* 取值为 'left'视野向左移动一半
* 取值为 'right'视野向右移动一半
* 取值为 'up'视野向上移动一半
* 取值为 'down'视野向下移动一半
* @param {number} duration 视野移动的动画时长单位 ms设置为 0 不使用动画
* @state
* 0: 始终可用
*/
var MoveCommand = kity.createClass('MoveCommand', {
base: Command,
execute: function (km, dir) {
var dragger = km._viewDragger;
var size = km._lastClientSize;
var duration = km.getOption('viewAnimationDuration');
switch (dir) {
case 'up':
dragger.move(new kity.Point(0, size.height / 2), duration);
break;
case 'down':
dragger.move(new kity.Point(0, -size.height / 2), duration);
break;
case 'left':
dragger.move(new kity.Point(size.width / 2, 0), duration);
break;
case 'right':
dragger.move(new kity.Point(-size.width / 2, 0), duration);
break;
}
},
enableReadOnly: true,
});
return {
init: function () {
this._viewDragger = new ViewDragger(this);
},
commands: {
hand: ToggleHandCommand,
camera: CameraCommand,
move: MoveCommand,
},
events: {
'statuschange': function (e) {
this._viewDragger.setEnabled(e.currentStatus == 'hand');
},
'mousewheel': function (e) {
var dx, dy;
e = e.originEvent;
if (e.ctrlKey || e.shiftKey) return;
if ('wheelDeltaX' in e) {
dx = e.wheelDeltaX || 0;
dy = e.wheelDeltaY || 0;
} else {
dx = 0;
dy = e.wheelDelta;
}
this._viewDragger.move({
x: dx / 2.5,
y: dy / 2.5,
});
var me = this;
clearTimeout(this._mousewheeltimer);
this._mousewheeltimer = setTimeout(function () {
me.fire('viewchanged');
}, 100);
e.preventDefault();
},
'normal.dblclick readonly.dblclick': function (e) {
if (e.kityEvent.targetShape instanceof kity.Paper) {
this.execCommand('camera', this.getRoot(), 800);
}
},
'paperrender finishInitHook': function () {
if (!this.getRenderTarget()) {
return;
}
this.execCommand('camera', null, 0);
this._lastClientSize = {
width: this.getRenderTarget().clientWidth,
height: this.getRenderTarget().clientHeight,
};
},
'resize': function (e) {
var a = {
width: this.getRenderTarget().clientWidth,
height: this.getRenderTarget().clientHeight,
},
b = this._lastClientSize;
this._viewDragger.move(new kity.Point(((a.width - b.width) / 2) | 0, ((a.height - b.height) / 2) | 0));
this._lastClientSize = a;
},
'selectionchange layoutallfinish': function (e) {
var selected = this.getSelectedNode();
var minder = this;
/*
* Added by zhangbobell 2015.9.9
* windows 10 edge 浏览器在全部动画停止后优先级图标不显示 text
* 因此再次触发一次 render 事件让浏览器重绘
* */
if (kity.Browser.edge) {
this.fire('paperrender');
}
if (!selected) return;
var dragger = this._viewDragger;
var timeline = dragger.timeline();
/*
* Added by zhangbobell 2015.09.25
* 如果之前有动画那么就先暂时返回等之前动画结束之后再次执行本函数
* 以防止 view 动画变动了位置导致本函数执行的时候位置计算不对
*
* fixed bug : 初始化的时候中心节点位置不固定有的时候在左上角有的时候在中心
* */
if (timeline) {
timeline.on('finish', function () {
minder.fire('selectionchange');
});
return;
}
var view = dragger.getView();
var focus = selected.getLayoutBox();
var space = 50;
var dx = 0,
dy = 0;
if (focus.right > view.right) {
dx += view.right - focus.right - space;
} else if (focus.left < view.left) {
dx += view.left - focus.left + space;
}
if (focus.bottom > view.bottom) {
dy += view.bottom - focus.bottom - space;
}
if (focus.top < view.top) {
dy += view.top - focus.top + space;
}
if (dx || dy) dragger.move(new kity.Point(dx, dy), 100);
},
},
};
});
});

View File

@ -30,6 +30,6 @@
"event/*": ["event/*"] "event/*": ["event/*"]
} }
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], "include": ["next-env.d.ts", "global.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"] "exclude": ["node_modules"]
} }