mirror of https://github.com/fantasticit/think.git
tiptap: improve mind operations
parent
a7d3ff0005
commit
c1a4e4b35e
|
@ -4,7 +4,7 @@ export const Divider = ({ vertical = false }) => {
|
|||
style={{
|
||||
display: 'inline-block',
|
||||
width: 1,
|
||||
height: 24,
|
||||
height: 18,
|
||||
margin: '0 6px',
|
||||
backgroundColor: 'var(--semi-color-border)',
|
||||
transform: `rotate(${vertical ? 90 : 0}deg)`,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
.toolbar {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
padding: 8px;
|
||||
padding: 4px 8px;
|
||||
overflow-x: auto;
|
||||
color: #fff;
|
||||
background-color: var(--semi-color-nav-bg);
|
||||
|
|
|
@ -85,11 +85,11 @@ export const MindWrapper = ({ editor, node, updateAttributes }) => {
|
|||
direction: window.MindElixir.SIDE,
|
||||
data: JSON.parse(JSON.stringify(data)),
|
||||
editable: editor.isEditable,
|
||||
draggable: editor.isEditable,
|
||||
contextMenu: editor.isEditable,
|
||||
toolBar: true,
|
||||
keypress: editor.isEditable,
|
||||
nodeMenu: true,
|
||||
nodeMenu: editor.isEditable,
|
||||
toolBar: true,
|
||||
draggable: false, // 需要修复
|
||||
locale: 'zh_CN',
|
||||
});
|
||||
mind.shouldPreventDefault = () => editor.isActive('mind');
|
||||
|
@ -103,7 +103,7 @@ export const MindWrapper = ({ editor, node, updateAttributes }) => {
|
|||
|
||||
return () => {
|
||||
if (mind) {
|
||||
mind.bus.removeListener('operation', onChange);
|
||||
mind.destroy();
|
||||
}
|
||||
};
|
||||
}, [loading, editor, updateAttributes]);
|
||||
|
|
|
@ -112,6 +112,11 @@ export const createLink = function (from, to, isInitPaint, obj) {
|
|||
if (!isInitPaint) {
|
||||
this.showLinkController(p2x, p2y, p3x, p3y, newLinkObj, fromData, toData);
|
||||
}
|
||||
|
||||
this.bus.fire('operation', {
|
||||
name: 'createLink',
|
||||
linkObj: newLinkObj,
|
||||
});
|
||||
};
|
||||
|
||||
export const removeLink = function (linkSvg) {
|
||||
|
@ -129,6 +134,10 @@ export const removeLink = function (linkSvg) {
|
|||
delete this.linkData[id];
|
||||
link.remove();
|
||||
link = null;
|
||||
|
||||
this.bus.fire('operation', {
|
||||
name: 'removeLink',
|
||||
});
|
||||
};
|
||||
export const selectLink = function (targetElement) {
|
||||
this.currentLink = targetElement;
|
||||
|
@ -222,6 +231,11 @@ export const showLinkController = function (p2x, p2y, p3x, p3y, linkObj, fromDat
|
|||
this.line1.setAttribute('y2', p2y);
|
||||
linkObj.delta1.x = p2x - fromData.cx;
|
||||
linkObj.delta1.y = p2y - fromData.cy;
|
||||
|
||||
this.bus.fire('operation', {
|
||||
name: 'updateLink',
|
||||
linkObj,
|
||||
});
|
||||
});
|
||||
|
||||
this.helper2.init(this.map, (deltaX, deltaY) => {
|
||||
|
@ -246,5 +260,10 @@ export const showLinkController = function (p2x, p2y, p3x, p3y, linkObj, fromDat
|
|||
this.line2.setAttribute('y2', p4y);
|
||||
linkObj.delta2.x = p3x - toData.cx;
|
||||
linkObj.delta2.y = p3y - toData.cy;
|
||||
|
||||
this.bus.fire('operation', {
|
||||
name: 'updateLink',
|
||||
linkObj,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -20,80 +20,4 @@ const cn = {
|
|||
export default {
|
||||
cn,
|
||||
zh_CN: cn,
|
||||
zh_TW: {
|
||||
addChild: '插入子節點',
|
||||
addParent: '插入父節點',
|
||||
addSibling: '插入同級節點',
|
||||
removeNode: '刪除節點',
|
||||
focus: '專注',
|
||||
cancelFocus: '取消專注',
|
||||
moveUp: '上移',
|
||||
moveDown: '下移',
|
||||
link: '連接',
|
||||
clickTips: '請點擊目標節點',
|
||||
|
||||
font: '文字',
|
||||
background: '背景',
|
||||
tag: '標簽',
|
||||
icon: '圖標',
|
||||
tagsSeparate: '多個標簽半角逗號分隔',
|
||||
iconsSeparate: '多個圖標半角逗號分隔',
|
||||
},
|
||||
en: {
|
||||
addChild: 'Add child',
|
||||
addParent: 'Add parent',
|
||||
addSibling: 'Add sibling',
|
||||
removeNode: 'Remove node',
|
||||
focus: 'Focus Mode',
|
||||
cancelFocus: 'Cancel Focus Mode',
|
||||
moveUp: 'Move up',
|
||||
moveDown: 'Move down',
|
||||
link: 'Link',
|
||||
clickTips: 'Please click the target node',
|
||||
|
||||
font: 'Font',
|
||||
background: 'Background',
|
||||
tag: 'Tag',
|
||||
icon: 'Icon',
|
||||
tagsSeparate: 'Separate tags by comma',
|
||||
iconsSeparate: 'Separate icons by comma',
|
||||
},
|
||||
ja: {
|
||||
addChild: '子ノードを追加する',
|
||||
addParent: '親ノードを追加します',
|
||||
addSibling: '兄弟ノードを追加する',
|
||||
removeNode: 'ノードを削除',
|
||||
focus: '集中',
|
||||
cancelFocus: '集中解除',
|
||||
moveUp: '上へ移動',
|
||||
moveDown: '下へ移動',
|
||||
link: 'コネクト',
|
||||
clickTips: 'ターゲットノードをクリックしてください',
|
||||
|
||||
font: 'フォント',
|
||||
background: 'バックグラウンド',
|
||||
tag: 'タグ',
|
||||
icon: 'アイコン',
|
||||
tagsSeparate: '複数タグはカンマ区切り',
|
||||
iconsSeparate: '複数アイコンはカンマ区切り',
|
||||
},
|
||||
pt: {
|
||||
addChild: 'Adicionar item filho',
|
||||
addParent: 'Adicionar item pai',
|
||||
addSibling: 'Adicionar item irmao',
|
||||
removeNode: 'Remover item',
|
||||
focus: 'Modo Foco',
|
||||
cancelFocus: 'Cancelar Modo Foco',
|
||||
moveUp: 'Mover para cima',
|
||||
moveDown: 'Mover para baixo',
|
||||
link: 'Link',
|
||||
clickTips: 'Favor clicar no item alvo',
|
||||
|
||||
font: 'Fonte',
|
||||
background: 'Cor de fundo',
|
||||
tag: 'Tag',
|
||||
icon: 'Icone',
|
||||
tagsSeparate: 'Separe tags por virgula',
|
||||
iconsSeparate: 'Separe icones por virgula',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -48,6 +48,7 @@ import {
|
|||
updateNodeStyle,
|
||||
updateNodeTags,
|
||||
updateNodeIcons,
|
||||
updateNodeHyperLink,
|
||||
processPrimaryNode,
|
||||
setNodeTopic,
|
||||
moveNodeBefore,
|
||||
|
@ -294,6 +295,7 @@ MindElixir.prototype = {
|
|||
updateNodeStyle,
|
||||
updateNodeTags,
|
||||
updateNodeIcons,
|
||||
updateNodeHyperLink,
|
||||
processPrimaryNode,
|
||||
setNodeTopic,
|
||||
|
||||
|
@ -400,6 +402,11 @@ MindElixir.prototype = {
|
|||
this.linkDiv();
|
||||
if (!this.overflowHidden) initMouseEvent(this);
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
this.bus.destroy();
|
||||
this.mindElixirBox.removeChild(this.container);
|
||||
},
|
||||
};
|
||||
|
||||
MindElixir.LEFT = LEFT;
|
||||
|
|
|
@ -28,6 +28,7 @@ export const selectNode = function (targetElement, isNewNode, clickEvent) {
|
|||
targetElement.classList.add('selected');
|
||||
this.currentNode = targetElement;
|
||||
if (isNewNode) {
|
||||
console.log('selectNewNode 1');
|
||||
this.bus.fire('selectNewNode', targetElement.nodeObj, clickEvent);
|
||||
} else {
|
||||
this.bus.fire('selectNode', targetElement.nodeObj, clickEvent);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { dragMoveHelper } from './utils/index';
|
||||
|
||||
export default function (mind) {
|
||||
mind.map.addEventListener('click', (e) => {
|
||||
const onClick = (e) => {
|
||||
// if (dragMoveHelper.afterMoving) return
|
||||
// e.preventDefault() // can cause a tag don't work
|
||||
if (e.target.nodeName === 'EPD') {
|
||||
|
@ -17,36 +18,52 @@ export default function (mind) {
|
|||
mind.unselectNode();
|
||||
mind.hideLinkController();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
mind.map.addEventListener('dblclick', (e) => {
|
||||
const onDbClick = (e) => {
|
||||
e.preventDefault();
|
||||
if (!mind.editable) return;
|
||||
if (e.target.parentElement.nodeName === 'T' || e.target.parentElement.nodeName === 'ROOT') {
|
||||
mind.beginEdit(e.target);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* drag and move
|
||||
*/
|
||||
mind.map.addEventListener('mousemove', (e) => {
|
||||
const onMouseMove = (e) => {
|
||||
// click trigger mousemove in windows chrome
|
||||
// the 'true' is a string
|
||||
if (e.target.contentEditable !== 'true') {
|
||||
dragMoveHelper.onMove(e, mind.container);
|
||||
}
|
||||
});
|
||||
mind.map.addEventListener('mousedown', (e) => {
|
||||
};
|
||||
|
||||
const onMouseDown = (e) => {
|
||||
if (e.target.contentEditable !== 'true') {
|
||||
dragMoveHelper.afterMoving = false;
|
||||
dragMoveHelper.mousedown = true;
|
||||
}
|
||||
});
|
||||
mind.map.addEventListener('mouseleave', (e) => {
|
||||
};
|
||||
|
||||
const onMouseLeave = (e) => {
|
||||
dragMoveHelper.clear();
|
||||
});
|
||||
mind.map.addEventListener('mouseup', (e) => {
|
||||
};
|
||||
|
||||
const onMouseUp = (e) => {
|
||||
dragMoveHelper.clear();
|
||||
};
|
||||
|
||||
mind.map.addEventListener('click', onClick);
|
||||
mind.map.addEventListener('dblclick', onDbClick);
|
||||
mind.map.addEventListener('mousemove', onMouseMove);
|
||||
mind.map.addEventListener('mousedown', onMouseDown);
|
||||
mind.map.addEventListener('mouseleave', onMouseLeave);
|
||||
mind.map.addEventListener('mouseup', onMouseUp);
|
||||
|
||||
mind.bus.addListener('destroy', () => {
|
||||
mind.map.removeEventListener('click', onClick);
|
||||
mind.map.removeEventListener('dblclick', onDbClick);
|
||||
mind.map.removeEventListener('mousemove', onMouseMove);
|
||||
mind.map.removeEventListener('mousedown', onMouseDown);
|
||||
mind.map.removeEventListener('mouseleave', onMouseLeave);
|
||||
mind.map.removeEventListener('mouseup', onMouseUp);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -73,17 +73,24 @@ export const updateNodeIcons = function (object, icons) {
|
|||
};
|
||||
|
||||
export const updateNodeHyperLink = function (object, hyperLink) {
|
||||
if (!hyperLink) return;
|
||||
const oldVal = object.hyperLink;
|
||||
object.hyperLink = hyperLink;
|
||||
const nodeEle = findEle(object.id);
|
||||
shapeTpc(nodeEle, object);
|
||||
this.linkDiv();
|
||||
this.bus.fire('operation', {
|
||||
name: 'editHyperLink',
|
||||
obj: object,
|
||||
origin: oldVal,
|
||||
});
|
||||
|
||||
if (!hyperLink) {
|
||||
const linkEle = nodeEle.querySelector('a.hyper-link') as HTMLElement;
|
||||
if (linkEle) {
|
||||
linkEle.parentNode.removeChild(linkEle);
|
||||
}
|
||||
} else {
|
||||
const oldVal = object.hyperLink;
|
||||
object.hyperLink = hyperLink;
|
||||
shapeTpc(nodeEle, object);
|
||||
this.linkDiv();
|
||||
this.bus.fire('operation', {
|
||||
name: 'editHyperLink',
|
||||
obj: object,
|
||||
origin: oldVal,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const updateNodeSvgChart = function () {
|
||||
|
@ -404,6 +411,7 @@ export const removeNode = function (el) {
|
|||
if (t.tagName === 'ROOT') {
|
||||
return;
|
||||
}
|
||||
this.bus.fire('removeNode', nodeObj);
|
||||
if (childrenLength === 0) {
|
||||
// remove epd when children length === 0
|
||||
const parentT = t.parentNode.parentNode.previousSibling;
|
||||
|
|
|
@ -82,6 +82,7 @@ export default function (mind) {
|
|||
};
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (mind.isInnerEditing) return; // 脑图内部操作
|
||||
if (!mind.editable) return;
|
||||
|
||||
if (mind.shouldPreventDefault && mind.shouldPreventDefault()) {
|
||||
|
|
|
@ -11,18 +11,13 @@ export default function (mind, option?) {
|
|||
const add_child = createLi('cm-add_child', 'zijiedian');
|
||||
const add_sibling = createLi('cm-add_sibling', 'tongjijiedian-');
|
||||
const remove_child = createLi('cm-remove_child', 'shanchu2');
|
||||
// let focus = createLi('cm-fucus', i18n[locale].focus, '')
|
||||
// let unfocus = createLi('cm-unfucus', i18n[locale].cancelFocus, '')
|
||||
const up = createLi('cm-up', 'rising');
|
||||
const down = createLi('cm-down', 'falling');
|
||||
const edit = createLi('cm-edit', 'edit');
|
||||
// let link = createLi('cm-down', i18n[locale].link, '')
|
||||
|
||||
const menuUl = document.createElement('ul');
|
||||
menuUl.className = 'menu-list';
|
||||
// if (!option || option.link) {
|
||||
// menuUl.appendChild(link)
|
||||
// }
|
||||
|
||||
if (option && option.extend) {
|
||||
for (let i = 0; i < option.extend.length; i++) {
|
||||
const item = option.extend[i];
|
||||
|
@ -37,10 +32,6 @@ export default function (mind, option?) {
|
|||
menuContainer.appendChild(add_child);
|
||||
menuContainer.appendChild(add_sibling);
|
||||
menuContainer.appendChild(remove_child);
|
||||
// if (!option || option.focus) {
|
||||
// menuContainer.appendChild(focus)
|
||||
// menuContainer.appendChild(unfocus)
|
||||
// }
|
||||
menuContainer.appendChild(up);
|
||||
menuContainer.appendChild(down);
|
||||
menuContainer.appendChild(edit);
|
||||
|
@ -48,39 +39,6 @@ export default function (mind, option?) {
|
|||
|
||||
mind.container.append(menuContainer);
|
||||
let isRoot = true;
|
||||
// mind.container.onclick = function (e) {
|
||||
// e.preventDefault()
|
||||
// // console.log(e.pageY, e.screenY, e.clientY)
|
||||
// let target = e.target
|
||||
// if (target.tagName === 'TPC') {
|
||||
// if (target.parentElement.tagName === 'ROOT') {
|
||||
// isRoot = true
|
||||
// } else {
|
||||
// isRoot = false
|
||||
// }
|
||||
// // if (isRoot) {
|
||||
// // focus.className = 'disabled'
|
||||
// // up.className = 'disabled'
|
||||
// // down.className = 'disabled'
|
||||
// // add_sibling.className = 'disabled'
|
||||
// // remove_child.className = 'disabled'
|
||||
// // } else {
|
||||
// // focus.className = ''
|
||||
// // up.className = ''
|
||||
// // down.className = ''
|
||||
// // add_sibling.className = ''
|
||||
// // remove_child.className = ''
|
||||
// // }
|
||||
// mind.selectNode(target)
|
||||
// menuContainer.hidden = false
|
||||
// let height = menuUl.offsetHeight
|
||||
// let width = menuUl.offsetWidth
|
||||
// let rect = target.getBoundingClientRect()
|
||||
// // menuUl.style.top = rect.top - 10 - height + 'px'
|
||||
// // menuUl.style.left = rect.left - (width - rect.width) / 2 + 'px'
|
||||
// // menuUl.style.left = e.clientX + 'px'
|
||||
// }
|
||||
// }
|
||||
|
||||
mind.bus.addListener('unselectNode', function () {
|
||||
menuContainer.hidden = true;
|
||||
|
@ -108,15 +66,6 @@ export default function (mind, option?) {
|
|||
if (isRoot) return;
|
||||
mind.removeNode();
|
||||
};
|
||||
// focus.onclick = e => {
|
||||
// if (isRoot) return
|
||||
// mind.focusNode(mind.currentNode)
|
||||
// menuContainer.hidden = true
|
||||
// }
|
||||
// unfocus.onclick = e => {
|
||||
// mind.cancelFocus()
|
||||
// menuContainer.hidden = true
|
||||
// }
|
||||
up.onclick = (e) => {
|
||||
if (isRoot) return;
|
||||
mind.moveUpNode();
|
||||
|
@ -128,24 +77,4 @@ export default function (mind, option?) {
|
|||
edit.onclick = (e) => {
|
||||
mind.beginEdit();
|
||||
};
|
||||
// link.onclick = e => {
|
||||
// let from = mind.currentNode
|
||||
// mind.map.addEventListener(
|
||||
// 'click',
|
||||
// e => {
|
||||
// e.preventDefault()
|
||||
// if (
|
||||
// e.target.parentElement.nodeName === 'T' ||
|
||||
// e.target.parentElement.nodeName === 'ROOT'
|
||||
// ) {
|
||||
// mind.createLink(from, mind.currentNode)
|
||||
// } else {
|
||||
// console.log('取消连接')
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// once: true,
|
||||
// }
|
||||
// )
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { dragMoveHelper, throttle } from '../utils/index';
|
||||
import { findEle as E, Topic, Group } from '../utils/dom';
|
||||
// https://html.spec.whatwg.org/multipage/dnd.html#drag-and-drop-processing-model
|
||||
|
||||
const $d = document;
|
||||
|
||||
const insertPreview = function (el, insertLocation) {
|
||||
if (!insertLocation) {
|
||||
clearPreview(el);
|
||||
|
@ -39,13 +39,13 @@ export default function (mind) {
|
|||
let meet: Element;
|
||||
const threshold = 12;
|
||||
|
||||
mind.map.addEventListener('dragstart', function (e) {
|
||||
const onDragStart = function (e) {
|
||||
dragged = e.target;
|
||||
(dragged.parentNode.parentNode as Group).style.opacity = '0.5';
|
||||
dragMoveHelper.clear();
|
||||
});
|
||||
};
|
||||
|
||||
mind.map.addEventListener('dragend', async function (e: DragEvent) {
|
||||
const onDragEnd = async function (e: DragEvent) {
|
||||
e.preventDefault();
|
||||
(e.target as HTMLElement).style.opacity = '';
|
||||
clearPreview(meet);
|
||||
|
@ -65,38 +65,43 @@ export default function (mind) {
|
|||
}
|
||||
(dragged.parentNode.parentNode as Group).style.opacity = '1';
|
||||
dragged = null;
|
||||
});
|
||||
};
|
||||
|
||||
mind.map.addEventListener(
|
||||
'dragover',
|
||||
throttle(function (e: DragEvent) {
|
||||
// console.log('drag', e)
|
||||
clearPreview(meet);
|
||||
// minus threshold infer that postion of the cursor is above topic
|
||||
const topMeet = $d.elementFromPoint(e.clientX, e.clientY - threshold);
|
||||
if (canPreview(topMeet, dragged)) {
|
||||
meet = topMeet;
|
||||
const y = topMeet.getBoundingClientRect().y;
|
||||
if (e.clientY > y + topMeet.clientHeight) {
|
||||
insertLocation = 'after';
|
||||
} else if (e.clientY > y + topMeet.clientHeight / 2) {
|
||||
const onDragOver = throttle(function (e: DragEvent) {
|
||||
clearPreview(meet);
|
||||
const topMeet = $d.elementFromPoint(e.clientX, e.clientY - threshold);
|
||||
if (canPreview(topMeet, dragged)) {
|
||||
meet = topMeet;
|
||||
const y = topMeet.getBoundingClientRect().y;
|
||||
if (e.clientY > y + topMeet.clientHeight) {
|
||||
insertLocation = 'after';
|
||||
} else if (e.clientY > y + topMeet.clientHeight / 2) {
|
||||
insertLocation = 'in';
|
||||
}
|
||||
} else {
|
||||
const bottomMeet = $d.elementFromPoint(e.clientX, e.clientY + threshold);
|
||||
if (canPreview(bottomMeet, dragged)) {
|
||||
meet = bottomMeet;
|
||||
const y = bottomMeet.getBoundingClientRect().y;
|
||||
if (e.clientY < y) {
|
||||
insertLocation = 'before';
|
||||
} else if (e.clientY < y + bottomMeet.clientHeight / 2) {
|
||||
insertLocation = 'in';
|
||||
}
|
||||
} else {
|
||||
const bottomMeet = $d.elementFromPoint(e.clientX, e.clientY + threshold);
|
||||
if (canPreview(bottomMeet, dragged)) {
|
||||
meet = bottomMeet;
|
||||
const y = bottomMeet.getBoundingClientRect().y;
|
||||
if (e.clientY < y) {
|
||||
insertLocation = 'before';
|
||||
} else if (e.clientY < y + bottomMeet.clientHeight / 2) {
|
||||
insertLocation = 'in';
|
||||
}
|
||||
} else {
|
||||
insertLocation = meet = null;
|
||||
}
|
||||
insertLocation = meet = null;
|
||||
}
|
||||
if (meet) insertPreview(meet, insertLocation);
|
||||
}, 200)
|
||||
);
|
||||
}
|
||||
if (meet) insertPreview(meet, insertLocation);
|
||||
}, 200);
|
||||
|
||||
mind.map.addEventListener('dragstart', onDragStart);
|
||||
mind.map.addEventListener('dragend', onDragEnd);
|
||||
mind.map.addEventListener('dragover', onDragOver);
|
||||
|
||||
mind.bus.addListener('destroy', () => {
|
||||
mind.map.removeEventListener('dragstart', onDragStart);
|
||||
mind.map.removeEventListener('dragend', onDragEnd);
|
||||
mind.map.removeEventListener('dragover', onDragOver);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,11 +1,93 @@
|
|||
import tippy from 'tippy.js';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Button, Tooltip, Space } from '@douyinfe/semi-ui';
|
||||
import { IconBold, IconFont, IconMark } from '@douyinfe/semi-icons';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Button, Tooltip, Space, Dropdown, Form } from '@douyinfe/semi-ui';
|
||||
import { IconBold, IconFont, IconMark, IconLink } from '@douyinfe/semi-icons';
|
||||
import { ColorPicker } from 'tiptap/menus/_components/color-picker';
|
||||
import { findEle } from '../utils/dom';
|
||||
import { FormApi } from '@douyinfe/semi-ui/lib/es/form';
|
||||
|
||||
export const Link = ({ mind, link, setLink }) => {
|
||||
const $form = useRef<FormApi>();
|
||||
const $input = useRef<HTMLInputElement>();
|
||||
const [initialState, setInitialState] = useState({ link });
|
||||
|
||||
const handleOk = useCallback(() => {
|
||||
$form.current.validate().then((values) => {
|
||||
setLink(values.link);
|
||||
});
|
||||
}, [setLink]);
|
||||
|
||||
useEffect(() => {
|
||||
setInitialState({ link });
|
||||
}, [link]);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
stopPropagation
|
||||
zIndex={10000}
|
||||
trigger="click"
|
||||
position={'bottomLeft'}
|
||||
render={
|
||||
<div style={{ padding: 12 }}>
|
||||
<Form
|
||||
initValues={initialState}
|
||||
getFormApi={(formApi) => ($form.current = formApi)}
|
||||
labelPosition="left"
|
||||
onSubmit={handleOk}
|
||||
layout="horizontal"
|
||||
>
|
||||
<Form.Input
|
||||
autofocus
|
||||
label="链接"
|
||||
field="link"
|
||||
placeholder="请输入外链地址"
|
||||
onFocus={() => (mind.isInnerEditing = true)}
|
||||
onBlur={() => (mind.isInnerEditing = false)}
|
||||
/>
|
||||
<Button type="primary" htmlType="submit">
|
||||
保存
|
||||
</Button>
|
||||
</Form>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<span style={{ display: 'inline-block' }}>
|
||||
<Tooltip content="设置链接" zIndex={10000}>
|
||||
<Button type="tertiary" theme="borderless" size="small" icon={<IconLink style={{ fontSize: '0.85em' }} />} />
|
||||
</Tooltip>
|
||||
</span>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
||||
const Toolbar = ({ mind, toggleBold, setFontColor, setBackgroundColor, setLink }) => {
|
||||
const [textColor, setTextColor] = useState('');
|
||||
const [bgColor, setBgColor] = useState('');
|
||||
const [hyperLink, setHyperLink] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const listener = function (nodeObj, clickEvent) {
|
||||
if (!clickEvent) return;
|
||||
|
||||
if (nodeObj.style) {
|
||||
setTextColor(nodeObj.style.color);
|
||||
setBgColor(nodeObj.style.background);
|
||||
} else {
|
||||
setTextColor('');
|
||||
setBgColor('');
|
||||
}
|
||||
|
||||
setHyperLink(nodeObj.hyperLink);
|
||||
};
|
||||
|
||||
mind.bus.addListener('selectNode', listener);
|
||||
|
||||
return () => {
|
||||
mind.bus.removeListener('selectNode', listener);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const Toolbar = ({ toggleBold, setFontColor, setBackgroundColor }) => {
|
||||
return (
|
||||
<Space spacing={4}>
|
||||
<Tooltip content="加粗" zIndex={10000}>
|
||||
|
@ -24,7 +106,29 @@ const Toolbar = ({ toggleBold, setFontColor, setBackgroundColor }) => {
|
|||
}}
|
||||
>
|
||||
<Tooltip content="文本色" zIndex={10000}>
|
||||
<Button type="tertiary" theme="borderless" size="small" icon={<IconFont style={{ fontSize: '0.85em' }} />} />
|
||||
<Button
|
||||
type="tertiary"
|
||||
theme="borderless"
|
||||
size="small"
|
||||
icon={
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<IconFont style={{ fontSize: '0.85em' }} />
|
||||
<span
|
||||
style={{
|
||||
width: 12,
|
||||
height: 2,
|
||||
backgroundColor: textColor,
|
||||
}}
|
||||
></span>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
</ColorPicker>
|
||||
|
||||
|
@ -37,6 +141,8 @@ const Toolbar = ({ toggleBold, setFontColor, setBackgroundColor }) => {
|
|||
<Button type="tertiary" theme="borderless" size="small" icon={<IconMark />} />
|
||||
</Tooltip>
|
||||
</ColorPicker>
|
||||
|
||||
<Link mind={mind} link={hyperLink} setLink={setLink} />
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
|
@ -81,8 +187,19 @@ export default function (mind) {
|
|||
mind.updateNodeIcons(mind.currentNode.nodeObj, newIcons);
|
||||
}
|
||||
|
||||
function setLink(link: string) {
|
||||
if (!mind.currentNode) return;
|
||||
mind.updateNodeHyperLink(mind.currentNode.nodeObj, link);
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<Toolbar toggleBold={toggleBold} setFontColor={setFontColor} setBackgroundColor={setBackgroundColor} />,
|
||||
<Toolbar
|
||||
mind={mind}
|
||||
toggleBold={toggleBold}
|
||||
setFontColor={setFontColor}
|
||||
setBackgroundColor={setBackgroundColor}
|
||||
setLink={setLink}
|
||||
/>,
|
||||
menuContainer
|
||||
);
|
||||
|
||||
|
@ -94,20 +211,27 @@ export default function (mind) {
|
|||
trigger: 'manual',
|
||||
placement: 'top',
|
||||
hideOnClick: 'toggle',
|
||||
offset: [-42, 40],
|
||||
offset: [-45, 45],
|
||||
appendTo: mind.container,
|
||||
}) as any;
|
||||
|
||||
mind.bus.addListener('selectNode', function (nodeObj, clickEvent) {
|
||||
if (!clickEvent) return;
|
||||
const onSelectNode = function (nodeObj) {
|
||||
const element = findEle(nodeObj.id, mind) as HTMLElement;
|
||||
toolbarInstance.setProps({
|
||||
getReferenceClientRect: () => element.getBoundingClientRect(),
|
||||
});
|
||||
toolbarInstance.show();
|
||||
});
|
||||
};
|
||||
|
||||
mind.bus.addListener('selectNode', onSelectNode);
|
||||
mind.bus.addListener('selectNewNode', onSelectNode);
|
||||
mind.bus.addListener('unselectNode', function () {
|
||||
toolbarInstance.hide();
|
||||
});
|
||||
mind.bus.addListener('removeNode', function (nodeObj) {
|
||||
const isRemoveCurrentNode = mind.currentNode && mind.currentNode.nodeObj.id === nodeObj.id;
|
||||
if (isRemoveCurrentNode) {
|
||||
toolbarInstance.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -9,46 +9,11 @@ import {
|
|||
IconZoomOut,
|
||||
IconZoomIn,
|
||||
} from 'components/icons';
|
||||
|
||||
function Direction({ mind }) {
|
||||
return (
|
||||
<Space spacing={4}>
|
||||
<Button
|
||||
type="tertiary"
|
||||
theme="borderless"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
mind.initLeft();
|
||||
}}
|
||||
icon={<IconMindLeft style={{ fontSize: '0.85em' }} />}
|
||||
/>
|
||||
|
||||
<Button
|
||||
type="tertiary"
|
||||
theme="borderless"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
mind.initRight();
|
||||
}}
|
||||
icon={<IconMindRight style={{ fontSize: '0.85em' }} />}
|
||||
/>
|
||||
|
||||
<Button
|
||||
type="tertiary"
|
||||
theme="borderless"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
mind.initSide();
|
||||
}}
|
||||
icon={<IconMindSide style={{ fontSize: '0.85em' }} />}
|
||||
/>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
import { Divider } from 'tiptap/divider';
|
||||
|
||||
function Operation({ mind }) {
|
||||
return (
|
||||
<Space spacing={4}>
|
||||
<Space spacing={2}>
|
||||
<Button
|
||||
type="tertiary"
|
||||
theme="borderless"
|
||||
|
@ -90,22 +55,45 @@ function Operation({ mind }) {
|
|||
}}
|
||||
icon={<IconZoomIn style={{ fontSize: '0.85em' }} />}
|
||||
/>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Button
|
||||
type="tertiary"
|
||||
theme="borderless"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
mind.initLeft();
|
||||
}}
|
||||
icon={<IconMindLeft style={{ fontSize: '0.85em' }} />}
|
||||
/>
|
||||
|
||||
<Button
|
||||
type="tertiary"
|
||||
theme="borderless"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
mind.initRight();
|
||||
}}
|
||||
icon={<IconMindRight style={{ fontSize: '0.85em' }} />}
|
||||
/>
|
||||
|
||||
<Button
|
||||
type="tertiary"
|
||||
theme="borderless"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
mind.initSide();
|
||||
}}
|
||||
icon={<IconMindSide style={{ fontSize: '0.85em' }} />}
|
||||
/>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
export default function (mind) {
|
||||
{
|
||||
const div = document.createElement('div');
|
||||
div.className = 'toolbar lt';
|
||||
ReactDOM.render(<Direction mind={mind} />, div);
|
||||
mind.container.append(div);
|
||||
}
|
||||
|
||||
{
|
||||
const div = document.createElement('div');
|
||||
div.className = 'toolbar rb';
|
||||
ReactDOM.render(<Operation mind={mind} />, div);
|
||||
mind.container.append(div);
|
||||
}
|
||||
const div = document.createElement('div');
|
||||
div.className = 'toolbar rb';
|
||||
ReactDOM.render(<Operation mind={mind} />, div);
|
||||
mind.container.append(div);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { LEFT, RIGHT, SIDE } from '../const';
|
||||
import { NodeObj } from '../index';
|
||||
import { encodeHTML } from '../utils/index';
|
||||
export type Top = HTMLElement;
|
||||
|
||||
export type Top = HTMLElement;
|
||||
export type Group = HTMLElement;
|
||||
|
||||
export interface Topic extends HTMLElement {
|
||||
|
|
|
@ -1,103 +0,0 @@
|
|||
// WIP
|
||||
export function pieChart(data) {
|
||||
const cx = 45 // circle center
|
||||
const cy = 45
|
||||
const r = 35 // radius
|
||||
const lx = 105 // description position
|
||||
const ly = 15
|
||||
const svgns = 'http://www.w3.org/2000/svg'
|
||||
const colors = [
|
||||
'#004c6d',
|
||||
'#346888',
|
||||
'#5886a5',
|
||||
'#7aa6c2',
|
||||
'#9dc6e0',
|
||||
'#c1e7ff',
|
||||
]
|
||||
// create <svg /> with specific width and height
|
||||
const chart = document.createElementNS(svgns, 'svg:svg')
|
||||
chart.setAttribute('width', 160)
|
||||
chart.setAttribute('height', 90)
|
||||
// chart.setAttribute("viewBox", "0 0 " + width + " " + height);
|
||||
chart.setAttribute('style', 'display:block;')
|
||||
|
||||
let total = 0
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
total += data[i].value
|
||||
}
|
||||
|
||||
const angles = []
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
angles[i] = (data[i].value / total) * Math.PI * 2
|
||||
}
|
||||
|
||||
let starttangle = 0
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const endangle = starttangle + angles[i]
|
||||
|
||||
const x1 = cx + r * Math.sin(starttangle)
|
||||
const y1 = cy - r * Math.cos(starttangle)
|
||||
const x2 = cx + r * Math.sin(endangle)
|
||||
const y2 = cy - r * Math.cos(endangle)
|
||||
|
||||
const path = document.createElementNS(svgns, 'path')
|
||||
|
||||
const d = `M ${cx},${cy} L ${x1},${y1} A ${r},${r} 0 0 1 ${x2},${y2} Z`
|
||||
|
||||
path.setAttribute('d', d)
|
||||
path.setAttribute('fill', colors[i])
|
||||
chart.appendChild(path)
|
||||
|
||||
starttangle = endangle
|
||||
|
||||
// description
|
||||
const icon = document.createElementNS(svgns, 'rect')
|
||||
icon.setAttribute('x', lx)
|
||||
icon.setAttribute('y', ly + 15 * i)
|
||||
icon.setAttribute('width', 10)
|
||||
icon.setAttribute('height', 10)
|
||||
icon.setAttribute('fill', colors[i])
|
||||
chart.appendChild(icon)
|
||||
|
||||
const label = document.createElementNS(svgns, 'text')
|
||||
label.setAttribute('x', lx + 50)
|
||||
label.setAttribute('y', ly + 15 * i + 10)
|
||||
label.setAttribute('font-family', 'sans-serif')
|
||||
label.setAttribute('font-size', '14')
|
||||
label.appendChild(document.createTextNode(data[i].label))
|
||||
chart.appendChild(label)
|
||||
}
|
||||
return chart
|
||||
}
|
||||
|
||||
// svg style
|
||||
// background-color: #fff;
|
||||
// padding: 5px;
|
||||
// border-radius: 5px;
|
||||
// border: 1px solid #ccc;
|
||||
|
||||
// example
|
||||
if (nodeObj.pie) {
|
||||
const pieContainer = $d.createElement('div')
|
||||
pieContainer.className = 'pie'
|
||||
const pieData = [
|
||||
{
|
||||
value: 12,
|
||||
label: 'a',
|
||||
},
|
||||
{
|
||||
value: 15,
|
||||
label: 'b',
|
||||
},
|
||||
{
|
||||
value: 42,
|
||||
label: 'c',
|
||||
},
|
||||
{
|
||||
value: 33,
|
||||
label: 'd',
|
||||
},
|
||||
]
|
||||
pieContainer.appendChild(pieChart(pieData))
|
||||
tpc.appendChild(pieContainer)
|
||||
}
|
|
@ -30,4 +30,8 @@ Bus.prototype = {
|
|||
}
|
||||
}
|
||||
},
|
||||
destroy: function () {
|
||||
this.fire('destroy');
|
||||
this.handlers = {};
|
||||
},
|
||||
};
|
||||
|
|
|
@ -5,7 +5,7 @@ export const createMainPath = function (d: string) {
|
|||
path.setAttribute('d', d);
|
||||
path.setAttribute('stroke', '#4f83fd');
|
||||
path.setAttribute('fill', 'none');
|
||||
path.setAttribute('stroke-width', '2');
|
||||
path.setAttribute('stroke-width', '3');
|
||||
return path;
|
||||
};
|
||||
|
||||
|
@ -21,7 +21,7 @@ export const createLine = function (x1: number, y1: number, x2: number, y2: numb
|
|||
line.setAttribute('y1', y1);
|
||||
line.setAttribute('x2', x2);
|
||||
line.setAttribute('y2', y2);
|
||||
line.setAttribute('stroke', '#f00');
|
||||
line.setAttribute('stroke', '#6ec4c4');
|
||||
line.setAttribute('fill', 'none');
|
||||
line.setAttribute('stroke-width', '2');
|
||||
return line;
|
||||
|
@ -33,7 +33,7 @@ export const createPath = function (d: string) {
|
|||
path.setAttribute('stroke', '#6e80db');
|
||||
path.setAttribute('fill', 'none');
|
||||
path.setAttribute('stroke-linecap', 'square');
|
||||
path.setAttribute('stroke-width', '1');
|
||||
path.setAttribute('stroke-width', '2');
|
||||
path.setAttribute('transform', 'translate(0.5,-0.5)');
|
||||
// adding translate(0.5,-0.5) can fix render error on windows, but i still dunno why
|
||||
return path;
|
||||
|
@ -47,12 +47,12 @@ export const createSvgGroup = function (d: string, arrowd: string): CustomSvg {
|
|||
const path = $d.createElementNS(svgNS, 'path');
|
||||
const arrow = $d.createElementNS(svgNS, 'path');
|
||||
arrow.setAttribute('d', arrowd);
|
||||
arrow.setAttribute('stroke', 'rgb(235, 95, 82)');
|
||||
arrow.setAttribute('stroke', '#6ec4c4');
|
||||
arrow.setAttribute('fill', 'none');
|
||||
arrow.setAttribute('stroke-linecap', 'cap');
|
||||
arrow.setAttribute('stroke-width', '2');
|
||||
path.setAttribute('d', d);
|
||||
path.setAttribute('stroke', 'rgb(235, 95, 82)');
|
||||
path.setAttribute('stroke', '#6ec4c4');
|
||||
path.setAttribute('fill', 'none');
|
||||
path.setAttribute('stroke-linecap', 'cap');
|
||||
path.setAttribute('stroke-width', '2');
|
||||
|
|
Loading…
Reference in New Issue