mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-06-02 13:41:01 +00:00
408 lines
12 KiB
TypeScript
408 lines
12 KiB
TypeScript
import { IPublicModelNode } from './../../../types/src/shell/model/node';
|
||
import { Editor, globalContext } from '@alilc/lowcode-editor-core';
|
||
import { isFormEvent } from '@alilc/lowcode-utils';
|
||
import {
|
||
focusing,
|
||
insertChildren,
|
||
TransformStage,
|
||
clipboard,
|
||
ILowCodePluginContext,
|
||
} from '@alilc/lowcode-designer';
|
||
|
||
export function isInLiveEditing() {
|
||
const workSpace = globalContext.get('workSpace');
|
||
if (workSpace.isActive) {
|
||
return Boolean(
|
||
workSpace.window.editor.get('designer')?.project?.simulator?.liveEditing?.editing,
|
||
);
|
||
}
|
||
|
||
if (globalContext.has(Editor)) {
|
||
return Boolean(
|
||
globalContext.get(Editor).get('designer')?.project?.simulator?.liveEditing?.editing,
|
||
);
|
||
}
|
||
}
|
||
|
||
/* istanbul ignore next */
|
||
function getNextForSelect(next: IPublicModelNode | null, head?: any, parent?: IPublicModelNode | null): any {
|
||
if (next) {
|
||
if (!head) {
|
||
return next;
|
||
}
|
||
|
||
let ret;
|
||
if (next.isContainer) {
|
||
const children = next.children;
|
||
if (children && !children.isEmpty) {
|
||
ret = getNextForSelect(children.get(0));
|
||
if (ret) {
|
||
return ret;
|
||
}
|
||
}
|
||
}
|
||
|
||
ret = getNextForSelect(next.nextSibling);
|
||
if (ret) {
|
||
return ret;
|
||
}
|
||
}
|
||
|
||
if (parent) {
|
||
return getNextForSelect(parent.nextSibling, false, parent?.parent);
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/* istanbul ignore next */
|
||
function getPrevForSelect(prev: IPublicModelNode | null, head?: any, parent?: IPublicModelNode | null): any {
|
||
if (prev) {
|
||
let ret;
|
||
if (!head && prev.isContainer) {
|
||
const children = prev.children;
|
||
const lastChild = children && !children.isEmpty ? children.get(children.size - 1) : null;
|
||
|
||
ret = getPrevForSelect(lastChild);
|
||
if (ret) {
|
||
return ret;
|
||
}
|
||
}
|
||
|
||
if (!head) {
|
||
return prev;
|
||
}
|
||
|
||
ret = getPrevForSelect(prev.prevSibling);
|
||
if (ret) {
|
||
return ret;
|
||
}
|
||
}
|
||
|
||
if (parent) {
|
||
return parent;
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
// 注册默认的 setters
|
||
export const builtinHotkey = (ctx: ILowCodePluginContext) => {
|
||
return {
|
||
init() {
|
||
const { hotkey, project, logger } = ctx;
|
||
// hotkey binding
|
||
hotkey.bind(['backspace', 'del'], (e: KeyboardEvent, action) => {
|
||
logger.info(`action ${action} is triggered`);
|
||
|
||
if (isInLiveEditing()) {
|
||
return;
|
||
}
|
||
// TODO: use focus-tracker
|
||
const doc = project.currentDocument;
|
||
if (isFormEvent(e) || !doc) {
|
||
return;
|
||
}
|
||
e.preventDefault();
|
||
|
||
const sel = doc.selection;
|
||
const topItems = sel.getTopNodes();
|
||
// TODO: check can remove
|
||
topItems.forEach((node) => {
|
||
if (node?.canPerformAction('remove')) {
|
||
node && doc.removeNode(node);
|
||
}
|
||
});
|
||
sel.clear();
|
||
});
|
||
|
||
hotkey.bind('escape', (e: KeyboardEvent, action) => {
|
||
logger.info(`action ${action} is triggered`);
|
||
// const currentFocus = focusing.current;
|
||
if (isInLiveEditing()) {
|
||
return;
|
||
}
|
||
const sel = focusing.focusDesigner?.currentDocument?.selection;
|
||
if (isFormEvent(e) || !sel) {
|
||
return;
|
||
}
|
||
e.preventDefault();
|
||
|
||
sel.clear();
|
||
// currentFocus.esc();
|
||
});
|
||
|
||
// command + c copy command + x cut
|
||
hotkey.bind(['command+c', 'ctrl+c', 'command+x', 'ctrl+x'], (e, action) => {
|
||
logger.info(`action ${action} is triggered`);
|
||
if (isInLiveEditing()) {
|
||
return;
|
||
}
|
||
const doc = project.currentDocument;
|
||
if (isFormEvent(e) || !doc) {
|
||
return;
|
||
}
|
||
e.preventDefault();
|
||
|
||
let selected = doc.selection.getTopNodes(true);
|
||
selected = selected.filter((node) => {
|
||
return node?.canPerformAction('copy');
|
||
});
|
||
if (!selected || selected.length < 1) {
|
||
return;
|
||
}
|
||
|
||
const componentsMap = {};
|
||
const componentsTree = selected.map((item) => item?.exportSchema(TransformStage.Clone));
|
||
|
||
// FIXME: clear node.id
|
||
|
||
const data = { type: 'nodeSchema', componentsMap, componentsTree };
|
||
|
||
clipboard.setData(data);
|
||
|
||
const cutMode = action && action.indexOf('x') > 0;
|
||
if (cutMode) {
|
||
selected.forEach((node) => {
|
||
const parentNode = node?.parent;
|
||
parentNode?.select();
|
||
node?.remove();
|
||
});
|
||
}
|
||
});
|
||
|
||
// command + v paste
|
||
hotkey.bind(['command+v', 'ctrl+v'], (e, action) => {
|
||
logger.info(`action ${action} is triggered`);
|
||
if (isInLiveEditing()) {
|
||
return;
|
||
}
|
||
if (isInLiveEditing()) return;
|
||
// TODO
|
||
const designer = focusing.focusDesigner;
|
||
const doc = designer?.currentDocument;
|
||
if (isFormEvent(e) || !designer || !doc) {
|
||
return;
|
||
}
|
||
/* istanbul ignore next */
|
||
clipboard.waitPasteData(e, ({ componentsTree }) => {
|
||
if (componentsTree) {
|
||
const { target, index } = designer.getSuitableInsertion(componentsTree) || {};
|
||
if (!target) {
|
||
return;
|
||
}
|
||
let canAddComponentsTree = componentsTree.filter((i) => {
|
||
return doc.checkNestingUp(target, i);
|
||
});
|
||
if (canAddComponentsTree.length === 0) return;
|
||
const nodes = insertChildren(target, canAddComponentsTree, index);
|
||
if (nodes) {
|
||
doc.selection.selectAll(nodes.map((o) => o.id));
|
||
setTimeout(() => designer.activeTracker.track(nodes[0]), 10);
|
||
}
|
||
}
|
||
});
|
||
});
|
||
|
||
// command + z undo
|
||
hotkey.bind(['command+z', 'ctrl+z'], (e, action) => {
|
||
logger.info(`action ${action} is triggered`);
|
||
if (isInLiveEditing()) {
|
||
return;
|
||
}
|
||
const history = project.currentDocument?.history;
|
||
if (isFormEvent(e) || !history) {
|
||
return;
|
||
}
|
||
|
||
e.preventDefault();
|
||
const selection = project.currentDocument?.selection;
|
||
const curSelected = selection?.selected && Array.from(selection?.selected);
|
||
history.back();
|
||
selection?.selectAll(curSelected);
|
||
});
|
||
|
||
// command + shift + z redo
|
||
hotkey.bind(['command+y', 'ctrl+y', 'command+shift+z'], (e, action) => {
|
||
logger.info(`action ${action} is triggered`);
|
||
if (isInLiveEditing()) {
|
||
return;
|
||
}
|
||
const history = project.currentDocument?.history;
|
||
if (isFormEvent(e) || !history) {
|
||
return;
|
||
}
|
||
e.preventDefault();
|
||
const selection = project.currentDocument?.selection;
|
||
const curSelected = selection?.selected && Array.from(selection?.selected);
|
||
history.forward();
|
||
selection?.selectAll(curSelected);
|
||
});
|
||
|
||
// sibling selection
|
||
hotkey.bind(['left', 'right'], (e, action) => {
|
||
logger.info(`action ${action} is triggered`);
|
||
if (isInLiveEditing()) {
|
||
return;
|
||
}
|
||
const doc = project.currentDocument;
|
||
if (isFormEvent(e) || !doc) {
|
||
return;
|
||
}
|
||
e.preventDefault();
|
||
const selected = doc.selection.getTopNodes(true);
|
||
if (!selected || selected.length < 1) {
|
||
return;
|
||
}
|
||
const firstNode = selected[0];
|
||
const silbing = action === 'left' ? firstNode?.prevSibling : firstNode?.nextSibling;
|
||
silbing?.select();
|
||
});
|
||
|
||
hotkey.bind(['up', 'down'], (e, action) => {
|
||
logger.info(`action ${action} is triggered`);
|
||
if (isInLiveEditing()) {
|
||
return;
|
||
}
|
||
const doc = project.currentDocument;
|
||
if (isFormEvent(e) || !doc) {
|
||
return;
|
||
}
|
||
e.preventDefault();
|
||
const selected = doc.selection.getTopNodes(true);
|
||
if (!selected || selected.length < 1) {
|
||
return;
|
||
}
|
||
const firstNode = selected[0];
|
||
|
||
if (action === 'down') {
|
||
const next = getNextForSelect(firstNode, true, firstNode?.parent);
|
||
next?.select();
|
||
} else if (action === 'up') {
|
||
const prev = getPrevForSelect(firstNode, true, firstNode?.parent);
|
||
prev?.select();
|
||
}
|
||
});
|
||
|
||
hotkey.bind(['option+left', 'option+right'], (e, action) => {
|
||
logger.info(`action ${action} is triggered`);
|
||
if (isInLiveEditing()) {
|
||
return;
|
||
}
|
||
const doc = project.currentDocument;
|
||
if (isFormEvent(e) || !doc) {
|
||
return;
|
||
}
|
||
e.preventDefault();
|
||
const selected = doc.selection.getTopNodes(true);
|
||
if (!selected || selected.length < 1) {
|
||
return;
|
||
}
|
||
// TODO: 此处需要增加判断当前节点是否可被操作移动,原ve里是用 node.canOperating()来判断
|
||
// TODO: 移动逻辑也需要重新梳理,对于移动目标位置的选择,是否可以移入,需要增加判断
|
||
|
||
const firstNode = selected[0];
|
||
const parent = firstNode?.parent;
|
||
if (!parent) return;
|
||
|
||
const isPrev = action && /(left)$/.test(action);
|
||
|
||
const silbing = isPrev ? firstNode.prevSibling : firstNode.nextSibling;
|
||
if (silbing) {
|
||
if (isPrev) {
|
||
parent.insertBefore(firstNode, silbing);
|
||
} else {
|
||
parent.insertAfter(firstNode, silbing);
|
||
}
|
||
firstNode?.select();
|
||
}
|
||
});
|
||
|
||
hotkey.bind(['option+up'], (e, action) => {
|
||
logger.info(`action ${action} is triggered`);
|
||
if (isInLiveEditing()) {
|
||
return;
|
||
}
|
||
const doc = project.currentDocument;
|
||
if (isFormEvent(e) || !doc) {
|
||
return;
|
||
}
|
||
e.preventDefault();
|
||
const selected = doc.selection.getTopNodes(true);
|
||
if (!selected || selected.length < 1) {
|
||
return;
|
||
}
|
||
// TODO: 此处需要增加判断当前节点是否可被操作移动,原ve里是用 node.canOperating()来判断
|
||
// TODO: 移动逻辑也需要重新梳理,对于移动目标位置的选择,是否可以移入,需要增加判断
|
||
|
||
const firstNode = selected[0];
|
||
const parent = firstNode?.parent;
|
||
if (!parent) {
|
||
return;
|
||
}
|
||
|
||
const silbing = firstNode.prevSibling;
|
||
if (silbing) {
|
||
if (silbing.isContainer) {
|
||
const place = silbing.getSuitablePlace(firstNode, null);
|
||
silbing.insertAfter(place, place.ref);
|
||
} else {
|
||
parent.insertBefore(firstNode, silbing);
|
||
}
|
||
firstNode?.select();
|
||
} else {
|
||
const place = parent.getSuitablePlace(firstNode, null); // upwards
|
||
if (place) {
|
||
place.container.insertBefore(firstNode, place.ref);
|
||
firstNode?.select();
|
||
}
|
||
}
|
||
});
|
||
|
||
hotkey.bind(['option+down'], (e, action) => {
|
||
logger.info(`action ${action} is triggered`);
|
||
if (isInLiveEditing()) {
|
||
return;
|
||
}
|
||
const doc = project.getCurrentDocument();
|
||
if (isFormEvent(e) || !doc) {
|
||
return;
|
||
}
|
||
e.preventDefault();
|
||
const selected = doc.selection.getTopNodes(true);
|
||
if (!selected || selected.length < 1) {
|
||
return;
|
||
}
|
||
// TODO: 此处需要增加判断当前节点是否可被操作移动,原 ve 里是用 node.canOperating() 来判断
|
||
// TODO: 移动逻辑也需要重新梳理,对于移动目标位置的选择,是否可以移入,需要增加判断
|
||
|
||
const firstNode = selected[0];
|
||
const parent = firstNode?.parent;
|
||
if (!parent) {
|
||
return;
|
||
}
|
||
|
||
const silbing = firstNode.nextSibling;
|
||
if (silbing) {
|
||
if (silbing.isContainer) {
|
||
// const place = silbing.getSuitablePlace(firstNode, null);
|
||
silbing.insertBefore(firstNode, undefined);
|
||
// place.container.insertBefore(firstNode, place.ref);
|
||
} else {
|
||
parent.insertAfter(firstNode, silbing);
|
||
}
|
||
firstNode?.select();
|
||
} else {
|
||
const place = parent.getSuitablePlace(firstNode, null); // upwards
|
||
if (place) {
|
||
place.container.insertAfter(firstNode, place.ref);
|
||
firstNode?.select();
|
||
}
|
||
}
|
||
});
|
||
},
|
||
};
|
||
};
|
||
|
||
builtinHotkey.pluginName = '___builtin_hotkey___';
|