From bad2d5e8ad043cfafc41954a7932d67630a62888 Mon Sep 17 00:00:00 2001 From: liujuping Date: Mon, 12 Dec 2022 21:52:59 +0800 Subject: [PATCH] feat: update --- .../designer/src/builtin-simulator/host.ts | 3 +- .../designer/src/designer/builtin-hotkey.ts | 361 ----------------- packages/designer/src/designer/clipboard.ts | 2 +- .../designer/src/designer/designer-view.tsx | 2 +- packages/designer/src/designer/index.ts | 5 +- packages/editor-core/src/editor.ts | 10 +- packages/editor-core/src/hotkey.ts | 8 +- packages/editor-view/package.json | 62 --- packages/editor-view/src/base-context.ts | 70 ---- packages/editor-view/src/context.ts | 24 -- packages/editor-view/src/index.tsx | 31 -- packages/editor-window/build.test.json | 6 - packages/editor-window/jest.config.js | 9 - packages/editor-window/package.json | 62 --- packages/editor-window/src/context.ts | 14 - packages/editor-window/src/index.tsx | 32 -- packages/editor-window/tsconfig.json | 9 - packages/engine/src/engine-core.ts | 20 +- .../src/inner-plugins/builtin-hotkey.ts | 371 ++++++++++++++++++ packages/shell/src/common.tsx | 20 +- packages/shell/src/hotkey.ts | 29 +- packages/shell/src/node.ts | 4 + packages/shell/src/selection.ts | 4 +- packages/shell/src/symbols.ts | 3 +- packages/types/src/engine-config.ts | 5 + .../types/src/shell/model/document-model.ts | 6 + packages/types/src/shell/model/node.ts | 8 +- packages/types/src/shell/model/selection.ts | 2 +- packages/workspace/src/base-context.ts | 12 +- .../workspace/src/editor-window/context.ts | 4 + 30 files changed, 482 insertions(+), 716 deletions(-) delete mode 100644 packages/designer/src/designer/builtin-hotkey.ts delete mode 100644 packages/editor-view/package.json delete mode 100644 packages/editor-view/src/base-context.ts delete mode 100644 packages/editor-view/src/context.ts delete mode 100644 packages/editor-view/src/index.tsx delete mode 100644 packages/editor-window/build.test.json delete mode 100644 packages/editor-window/jest.config.js delete mode 100644 packages/editor-window/package.json delete mode 100644 packages/editor-window/src/context.ts delete mode 100644 packages/editor-window/src/index.tsx delete mode 100644 packages/editor-window/tsconfig.json create mode 100644 packages/engine/src/inner-plugins/builtin-hotkey.ts diff --git a/packages/designer/src/builtin-simulator/host.ts b/packages/designer/src/builtin-simulator/host.ts index c1c66e56c..977a9844b 100644 --- a/packages/designer/src/builtin-simulator/host.ts +++ b/packages/designer/src/builtin-simulator/host.ts @@ -64,7 +64,7 @@ import { DragNodeObject, } from '@alilc/lowcode-types'; import { BuiltinSimulatorRenderer } from './renderer'; -import clipboard from '../designer/clipboard'; +import { clipboard } from '../designer/clipboard'; import { LiveEditing } from './live-editing/live-editing'; import { Project } from '../project'; import { Scroller } from '../designer/scroller'; @@ -478,6 +478,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost { - if (isInLiveEditing()) return; - // TODO: use focus-tracker - const doc = focusing.focusDesigner?.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')) { - doc.removeNode(node); - } - }); - sel.clear(); -}); - -hotkey.bind('escape', (e: KeyboardEvent) => { - // 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) => { - if (isInLiveEditing()) return; - const doc = focusing.focusDesigner?.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.export(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.getParent(); - parentNode?.select(); - node.remove(); - }); - } -}); - -// command + v paste -hotkey.bind(['command+v', 'ctrl+v'], (e) => { - if (isInLiveEditing()) return; - 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) => { - if (isInLiveEditing()) return; - const his = focusing.focusDesigner?.currentHistory; - if (isFormEvent(e) || !his) { - return; - } - - e.preventDefault(); - const selection = focusing.focusDesigner?.currentSelection; - const curSelected = Array.from(selection?.selected); - his.back(); - selection?.selectAll(curSelected); -}); - -// command + shift + z redo -hotkey.bind(['command+y', 'ctrl+y', 'command+shift+z'], (e) => { - if (isInLiveEditing()) return; - const his = focusing.focusDesigner?.currentHistory; - if (isFormEvent(e) || !his) { - return; - } - e.preventDefault(); - const selection = focusing.focusDesigner?.currentSelection; - const curSelected = Array.from(selection?.selected); - his.forward(); - selection?.selectAll(curSelected); -}); - -// sibling selection -hotkey.bind(['left', 'right'], (e, action) => { - if (isInLiveEditing()) return; - const designer = focusing.focusDesigner; - const doc = designer?.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) => { - if (isInLiveEditing()) return; - const designer = focusing.focusDesigner; - const doc = designer?.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.getParent()); - next?.select(); - } else if (action === 'up') { - const prev = getPrevForSelect(firstNode, true, firstNode.getParent()); - prev?.select(); - } -}); - -hotkey.bind(['option+left', 'option+right'], (e, action) => { - if (isInLiveEditing()) return; - const designer = focusing.focusDesigner; - const doc = designer?.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.getParent(); - 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) => { - if (isInLiveEditing()) return; - const designer = focusing.focusDesigner; - const doc = designer?.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.getParent(); - if (!parent) { - return; - } - - const silbing = firstNode.prevSibling; - if (silbing) { - if (silbing.isContainer()) { - const place = silbing.getSuitablePlace(firstNode, null); - place.container.insertAfter(firstNode, 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) => { - if (isInLiveEditing()) return; - const designer = focusing.focusDesigner; - const doc = designer?.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.getParent(); - 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(); - } - } -}); diff --git a/packages/designer/src/designer/clipboard.ts b/packages/designer/src/designer/clipboard.ts index dcaa21c0f..03546f11e 100644 --- a/packages/designer/src/designer/clipboard.ts +++ b/packages/designer/src/designer/clipboard.ts @@ -109,4 +109,4 @@ class Clipboard { } } -export default new Clipboard(); +export const clipboard = new Clipboard(); diff --git a/packages/designer/src/designer/designer-view.tsx b/packages/designer/src/designer/designer-view.tsx index 11e1ac11e..af3aeb598 100644 --- a/packages/designer/src/designer/designer-view.tsx +++ b/packages/designer/src/designer/designer-view.tsx @@ -4,7 +4,7 @@ import BuiltinDragGhostComponent from './drag-ghost'; import { Designer, DesignerProps } from './designer'; import { ProjectView } from '../project'; import './designer.less'; -import clipboard from './clipboard'; +import { clipboard } from './clipboard'; export class DesignerView extends Component { - Array.isArray(d) ? setArrayAssets(d) : setAssetsComponent(d, { - exportName: i.toString(), - subName: i.toString(), + const exportName = [preExportName, i.toString()].filter(d => !!d).join('.'); + const subName = [preSubName, i.toString()].filter(d => !!d).join('.'); + Array.isArray(d) ? setArrayAssets(d, exportName, subName) : setAssetsComponent(d, { + exportName, + subName, }); }); } diff --git a/packages/editor-core/src/hotkey.ts b/packages/editor-core/src/hotkey.ts index 884e2a1d1..2bb6f2d97 100644 --- a/packages/editor-core/src/hotkey.ts +++ b/packages/editor-core/src/hotkey.ts @@ -354,7 +354,7 @@ function fireCallback(callback: HotkeyCallback, e: KeyboardEvent, combo?: string } export class Hotkey { - private callBacks: HotkeyCallbacks = {}; + callBacks: HotkeyCallbacks = {}; private directMap: HotkeyDirectMap = {}; @@ -368,6 +368,8 @@ export class Hotkey { private nextExpectedAction: boolean | string = false; + constructor(readonly name: string = 'unknown') {} + mount(window: Window) { const { document } = window; const handleKeyEvent = this.handleKeyEvent.bind(this); @@ -645,5 +647,5 @@ export class Hotkey { } } -export const hotkey = new Hotkey(); -hotkey.mount(window); +// export const hotkey = new Hotkey(); +// hotkey.mount(window); diff --git a/packages/editor-view/package.json b/packages/editor-view/package.json deleted file mode 100644 index 39e086638..000000000 --- a/packages/editor-view/package.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "name": "@alilc/lowcode-editor-view", - "version": "1.0.15", - "description": "Shell Layer for AliLowCodeEngine", - "main": "lib/index.js", - "private": true, - "module": "es/index.js", - "files": [ - "lib", - "es" - ], - "scripts": { - "build": "build-scripts build --skip-demo", - "test": "build-scripts test --config build.test.json", - "test:cov": "build-scripts test --config build.test.json --jest-coverage" - }, - "license": "MIT", - "dependencies": { - "@alilc/lowcode-designer": "1.0.15", - "@alilc/lowcode-editor-core": "1.0.15", - "@alilc/lowcode-editor-skeleton": "1.0.15", - "@alilc/lowcode-types": "1.0.15", - "@alilc/lowcode-utils": "1.0.15", - "classnames": "^2.2.6", - "enzyme": "^3.11.0", - "enzyme-adapter-react-16": "^1.15.5", - "react": "^16", - "react-dom": "^16.7.0", - "zen-logger": "^1.1.0" - }, - "devDependencies": { - "@alib/build-scripts": "^0.1.29", - "@alilc/lowcode-test-mate": "^1.0.1", - "@testing-library/react": "^11.2.2", - "@types/classnames": "^2.2.7", - "@types/jest": "^26.0.16", - "@types/lodash": "^4.14.165", - "@types/medium-editor": "^5.0.3", - "@types/node": "^13.7.1", - "@types/react": "^16", - "@types/react-dom": "^16", - "babel-jest": "^26.5.2", - "build-plugin-component": "^1.0.0", - "build-scripts-config": "^3.0.3", - "jest": "^26.6.3", - "lodash": "^4.17.20", - "moment": "^2.29.1", - "typescript": "^4.0.3" - }, - "publishConfig": { - "access": "public", - "registry": "https://registry.npmjs.org/" - }, - "resolutions": { - "@builder/babel-preset-ice": "1.0.1" - }, - "repository": { - "type": "http", - "url": "https://github.com/alibaba/lowcode-engine/tree/main/packages/shell" - }, - "gitHead": "2669f179e6f899d395ce1942d0fe04f9c5ed48a6" -} diff --git a/packages/editor-view/src/base-context.ts b/packages/editor-view/src/base-context.ts deleted file mode 100644 index bb5d3ecb0..000000000 --- a/packages/editor-view/src/base-context.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { globalContext, Editor, engineConfig, Setters as InnerSetters, EngineOptions } from '@alilc/lowcode-editor-core'; -import { - Designer, - LowCodePluginManager, - TransformStage, -} from '@alilc/lowcode-designer'; -import { - Skeleton as InnerSkeleton, -} from '@alilc/lowcode-editor-skeleton'; -import { - WorkSpace, -} from '@alilc/lowcode-workspace'; - -import { Hotkey, Project, Skeleton, Setters, Material, Event } from '@alilc/lowcode-shell'; -import { getLogger } from '@alilc/lowcode-utils'; - -export class BasicContext { - skeleton; - plugins; - project; - setters; - material; - config; - event; - logger; - hotkey; - - constructor() { - const editor = new Editor(); - // globalContext.register(editor, Editor); - // globalContext.register(editor, 'editor'); - - const innerSkeleton = new InnerSkeleton(editor); - editor.set('skeleton' as any, innerSkeleton); - - const designer = new Designer({ editor }); - editor.set('designer' as any, designer); - - const plugins = new LowCodePluginManager(editor).toProxy(); - editor.set('plugins' as any, plugins); - - const { project: innerProject } = designer; - // const skeletonCabin = getSkeletonCabin(innerSkeleton); - // const { Workbench } = skeletonCabin; - - const hotkey = new Hotkey(); - const project = new Project(innerProject); - const skeleton = new Skeleton(innerSkeleton); - const innerSetters = new InnerSetters(); - const setters = new Setters(innerSetters); - const material = new Material(editor); - const config = engineConfig; - const event = new Event(editor, { prefix: 'common' }); - const logger = getLogger({ level: 'warn', bizName: 'common' }); - // const designerCabin = getDesignerCabin(editor); - const objects = { - TransformStage, - }; - const workSpace = new WorkSpace(); - this.skeleton = skeleton; - this.plugins = plugins; - this.project = project; - this.setters = setters; - this.material = material; - this.config = config; - this.event = event; - this.logger = logger; - this.hotkey = hotkey; - } -} \ No newline at end of file diff --git a/packages/editor-view/src/context.ts b/packages/editor-view/src/context.ts deleted file mode 100644 index f05e2384b..000000000 --- a/packages/editor-view/src/context.ts +++ /dev/null @@ -1,24 +0,0 @@ -// import { LowCodePluginManager } from "@alilc/lowcode-designer"; -// import { Editor, Hotkey } from "@alilc/lowcode-editor-core"; -// import { Material } from "@alilc/lowcode-shell"; - -// import { -// Skeleton as InnerSkeleton, -// SettingsPrimaryPane, -// registerDefaults, -// Workbench, -// } from '@alilc/lowcode-editor-skeleton'; -import { EditorViewOptions } from '@alilc/lowcode-workspace'; -import { BasicContext } from './base-context'; - -export class Context extends BasicContext { - constructor(public editorView: EditorViewOptions) { - super(); - this.init(); - } - - async init() { - await this.editorView?.init(this); - this.plugins.init(); - } -} \ No newline at end of file diff --git a/packages/editor-view/src/index.tsx b/packages/editor-view/src/index.tsx deleted file mode 100644 index bd309273c..000000000 --- a/packages/editor-view/src/index.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { skeletonSymbol } from '@alilc/lowcode-shell'; -import { observer } from '@alilc/lowcode-editor-core'; -import { - Workbench, -} from '@alilc/lowcode-editor-skeleton'; -import { Component } from 'react'; -import { Context } from './context'; - -export * from './base-context'; - -@observer -export class EditorView extends Component { - // 因为 document 数据在不同视图下使用的是同一份,所以这里通过 constructor 传入 - constructor(props: any) { - super(props); - this.ctx = new Context(props.editorView); - } - - ctx: Context; - - render() { - const innerSkeleton = this.ctx.skeleton[skeletonSymbol]; - return ( - - ); - } -} diff --git a/packages/editor-window/build.test.json b/packages/editor-window/build.test.json deleted file mode 100644 index dcdc891e9..000000000 --- a/packages/editor-window/build.test.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "plugins": [ - "build-plugin-component", - "@alilc/lowcode-test-mate/plugin/index.ts" - ] -} diff --git a/packages/editor-window/jest.config.js b/packages/editor-window/jest.config.js deleted file mode 100644 index 0e05687d7..000000000 --- a/packages/editor-window/jest.config.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - moduleFileExtensions: ['ts', 'tsx', 'js', 'json'], - collectCoverage: true, - collectCoverageFrom: [ - 'src/**/*.{ts,tsx}', - '!**/node_modules/**', - '!**/vendor/**', - ], -}; diff --git a/packages/editor-window/package.json b/packages/editor-window/package.json deleted file mode 100644 index d4ebfc7d9..000000000 --- a/packages/editor-window/package.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "name": "@alilc/lowcode-editor-window", - "version": "1.0.15", - "description": "Shell Layer for AliLowCodeEngine", - "main": "lib/index.js", - "private": true, - "module": "es/index.js", - "files": [ - "lib", - "es" - ], - "scripts": { - "build": "build-scripts build --skip-demo", - "test": "build-scripts test --config build.test.json", - "test:cov": "build-scripts test --config build.test.json --jest-coverage" - }, - "license": "MIT", - "dependencies": { - "@alilc/lowcode-designer": "1.0.15", - "@alilc/lowcode-editor-core": "1.0.15", - "@alilc/lowcode-editor-skeleton": "1.0.15", - "@alilc/lowcode-types": "1.0.15", - "@alilc/lowcode-utils": "1.0.15", - "classnames": "^2.2.6", - "enzyme": "^3.11.0", - "enzyme-adapter-react-16": "^1.15.5", - "react": "^16", - "react-dom": "^16.7.0", - "zen-logger": "^1.1.0" - }, - "devDependencies": { - "@alib/build-scripts": "^0.1.29", - "@alilc/lowcode-test-mate": "^1.0.1", - "@testing-library/react": "^11.2.2", - "@types/classnames": "^2.2.7", - "@types/jest": "^26.0.16", - "@types/lodash": "^4.14.165", - "@types/medium-editor": "^5.0.3", - "@types/node": "^13.7.1", - "@types/react": "^16", - "@types/react-dom": "^16", - "babel-jest": "^26.5.2", - "build-plugin-component": "^1.0.0", - "build-scripts-config": "^3.0.3", - "jest": "^26.6.3", - "lodash": "^4.17.20", - "moment": "^2.29.1", - "typescript": "^4.0.3" - }, - "publishConfig": { - "access": "public", - "registry": "https://registry.npmjs.org/" - }, - "resolutions": { - "@builder/babel-preset-ice": "1.0.1" - }, - "repository": { - "type": "http", - "url": "https://github.com/alibaba/lowcode-engine/tree/main/packages/shell" - }, - "gitHead": "2669f179e6f899d395ce1942d0fe04f9c5ed48a6" -} diff --git a/packages/editor-window/src/context.ts b/packages/editor-window/src/context.ts deleted file mode 100644 index 7d5d23f94..000000000 --- a/packages/editor-window/src/context.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { EditorWindow } from '@alilc/lowcode-workspace'; -import { BasicContext } from '@alilc/lowcode-editor-view'; - -export class Context extends BasicContext { - constructor(public editorWindow: EditorWindow) { - super(); - this.init(); - } - - async init() { - await this.editorWindow.resource.init(this); - this.plugins.init(); - } -} \ No newline at end of file diff --git a/packages/editor-window/src/index.tsx b/packages/editor-window/src/index.tsx deleted file mode 100644 index 75e397b58..000000000 --- a/packages/editor-window/src/index.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Component } from 'react'; -import { EditorView } from '@alilc/lowcode-editor-view'; -import { Context } from './context'; -import { observer } from '@alilc/lowcode-editor-core'; - -@observer -export class EditorWindow extends Component<{ - editorWindow: any; -}, any> { - constructor(props: any) { - super(props); - // if (!props.resource) { - // throw new Error('resource is required'); - // } - this.ctx = new Context(props.editorWindow); - } - - ctx; - - render() { - const { resource, editorView } = this.props.editorWindow; - return ( - - ); - } -} \ No newline at end of file diff --git a/packages/editor-window/tsconfig.json b/packages/editor-window/tsconfig.json deleted file mode 100644 index c37b76ecc..000000000 --- a/packages/editor-window/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "lib" - }, - "include": [ - "./src/" - ] -} diff --git a/packages/engine/src/engine-core.ts b/packages/engine/src/engine-core.ts index 38761402e..ab7d92740 100644 --- a/packages/engine/src/engine-core.ts +++ b/packages/engine/src/engine-core.ts @@ -1,8 +1,16 @@ /* eslint-disable no-param-reassign */ import { createElement } from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { globalContext, Editor, engineConfig, Setters as InnerSetters } from '@alilc/lowcode-editor-core'; -import { EngineOptions } from '@alilc/lowcode-types'; +import { + globalContext, + Editor, + engineConfig, + Setters as InnerSetters, + Hotkey as InnerHotkey, +} from '@alilc/lowcode-editor-core'; +import { EngineOptions, + IPublicModelDocumentModel, +} from '@alilc/lowcode-types'; import { Designer, LowCodePluginManager, @@ -37,6 +45,7 @@ import { componentMetaParser } from './inner-plugins/component-meta-parser'; import { setterRegistry } from './inner-plugins/setter-registry'; import { defaultPanelRegistry } from './inner-plugins/default-panel-registry'; import { shellModelFactory } from './modules/shell-model-factory'; +import { builtinHotkey } from './inner-plugins/builtin-hotkey'; export * from './modules/skeleton-types'; export * from './modules/designer-types'; @@ -57,7 +66,8 @@ const designer = new Designer({ editor, shellModelFactory }); editor.set('designer' as any, designer); const { project: innerProject } = designer; -const hotkey = new Hotkey(); +const innerHotkey = new InnerHotkey(); +const hotkey = new Hotkey(innerHotkey); const project = new Project(innerProject); const skeleton = new Skeleton(innerSkeleton); const innerSetters = new InnerSetters(); @@ -67,6 +77,7 @@ const material = new Material(editor); editor.set('project', project); editor.set('setters' as any, setters); editor.set('material', material); +editor.set('innerHotkey', innerHotkey); const config = engineConfig; const event = new Event(editor, { prefix: 'common' }); const logger = getLogger({ level: 'warn', bizName: 'common' }); @@ -158,6 +169,7 @@ export async function init( await plugins.register(componentMetaParser(designer)); await plugins.register(setterRegistry); await plugins.register(defaultPanelRegistry(editor, designer)); + await plugins.register(builtinHotkey); await plugins.init(pluginPreference as any); @@ -189,7 +201,7 @@ export async function destroy() { // remove all documents const { documents } = project; if (Array.isArray(documents) && documents.length > 0) { - documents.forEach(((doc: DocumentModel) => project.removeDocument(doc))); + documents.forEach(((doc: IPublicModelDocumentModel) => project.removeDocument(doc))); } // TODO: delete plugins except for core plugins diff --git a/packages/engine/src/inner-plugins/builtin-hotkey.ts b/packages/engine/src/inner-plugins/builtin-hotkey.ts new file mode 100644 index 000000000..9b8b8526d --- /dev/null +++ b/packages/engine/src/inner-plugins/builtin-hotkey.ts @@ -0,0 +1,371 @@ +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: any, head?: any, parent?: any): any { + if (next) { + if (!head) { + return next; + } + + let ret; + if (next.isContainer()) { + const children = next.getChildren() || []; + 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.getParent()); + } + + return null; +} + +/* istanbul ignore next */ +function getPrevForSelect(prev: any, head?: any, parent?: any): any { + if (prev) { + let ret; + if (!head && prev.isContainer()) { + const children = prev.getChildren() || []; + 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 } = ctx; + // hotkey binding + hotkey.bind(['backspace', 'del'], (e: KeyboardEvent) => { + 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) => { + // 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) => { + 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) => { + 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) => { + 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) => { + 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) => { + 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) => { + 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) => { + 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) => { + 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) => { + 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___'; diff --git a/packages/shell/src/common.tsx b/packages/shell/src/common.tsx index f5f439397..2090c6f91 100644 --- a/packages/shell/src/common.tsx +++ b/packages/shell/src/common.tsx @@ -224,6 +224,11 @@ class Utils implements IPublicCommonUtils { } class EditorCabin { + private readonly [editorSymbol]: Editor; + + constructor(editor: Editor) { + this[editorSymbol] = editor; + } /** * @deprecated */ @@ -259,12 +264,13 @@ class EditorCabin { return innerIntl(data, params); } - // /** - // * @deprecated - // */ - // createSetterContent(setter: any, props: Record): ReactNode { - // return innerCreateSetterContent(setter, props); - // } + /** + * @deprecated + */ + createSetterContent = (setter: any, props: Record): ReactNode => { + const setters = this[editorSymbol].get('setters'); + return setters.createSetterContent(setter, props); + }; /** * @deprecated @@ -326,7 +332,7 @@ export default class Common implements IPublicApiCommon { constructor(editor: Editor, skeleton: InnerSkeleton) { this.__designerCabin = new DesignerCabin(editor); this.__skeletonCabin = new SkeletonCabin(skeleton); - this.__editorCabin = new EditorCabin(); + this.__editorCabin = new EditorCabin(editor); this.__utils = new Utils(); } diff --git a/packages/shell/src/hotkey.ts b/packages/shell/src/hotkey.ts index c2daeaa21..3a11cbc03 100644 --- a/packages/shell/src/hotkey.ts +++ b/packages/shell/src/hotkey.ts @@ -1,12 +1,29 @@ -import { hotkey } from '@alilc/lowcode-editor-core'; +import { globalContext, Hotkey as InnerHotkey } from '@alilc/lowcode-editor-core'; +import { hotkeySymbol } from './symbols'; import { Disposable, HotkeyCallback, IPublicApiHotkey } from '@alilc/lowcode-types'; +const innerHotkeySymbol = Symbol('innerHotkey'); + export default class Hotkey implements IPublicApiHotkey { - constructor(public name: string = 'unknown', public workspaceMode: boolean = false) { + private readonly [innerHotkeySymbol]: InnerHotkey; + get [hotkeySymbol](): InnerHotkey { + if (this.workspaceMode) { + return this[innerHotkeySymbol]; + } + const workSpace = globalContext.get('workSpace'); + if (workSpace.isActive) { + return workSpace.window.innerHotkey; + } + + return this[innerHotkeySymbol]; } - get callbacks() { - return hotkey.callBacks; + constructor(hotkey: InnerHotkey, public name: string = 'unknown', public workspaceMode: boolean = false) { + this[innerHotkeySymbol] = hotkey; + } + + get callbacks(): any { + return this[hotkeySymbol].callBacks; } /** * @deprecated @@ -22,9 +39,9 @@ export default class Hotkey implements IPublicApiHotkey { * @returns */ bind(combos: string[] | string, callback: HotkeyCallback, action?: string): Disposable { - hotkey.bind(combos, callback, action); + this[hotkeySymbol].bind(combos, callback, action); return () => { - hotkey.unbind(combos, callback, action); + this[hotkeySymbol].unbind(combos, callback, action); }; } } \ No newline at end of file diff --git a/packages/shell/src/node.ts b/packages/shell/src/node.ts index 34faf5f57..5591a098f 100644 --- a/packages/shell/src/node.ts +++ b/packages/shell/src/node.ts @@ -527,4 +527,8 @@ export default class Node implements IPublicModelNode { internalToShellNode() { return this; } + + canPerformAction(actionName: string): boolean { + return this[nodeSymbol].canPerformAction(actionName); + } } diff --git a/packages/shell/src/selection.ts b/packages/shell/src/selection.ts index 43d2c3ccc..0dd8ec434 100644 --- a/packages/shell/src/selection.ts +++ b/packages/shell/src/selection.ts @@ -92,7 +92,7 @@ export default class Selection implements IPublicModelSelection { * getTopNodes() will return [A, B], subA will be removed * @returns */ - getTopNodes(): Array { - return this[selectionSymbol].getTopNodes().map((node: InnerNode) => Node.create(node)); + getTopNodes(includeRoot: boolean = false): Array { + return this[selectionSymbol].getTopNodes(includeRoot).map((node: InnerNode) => Node.create(node)); } } diff --git a/packages/shell/src/symbols.ts b/packages/shell/src/symbols.ts index 04ba9d17e..8493d4b82 100644 --- a/packages/shell/src/symbols.ts +++ b/packages/shell/src/symbols.ts @@ -24,4 +24,5 @@ export const simulatorHostSymbol = Symbol('simulatorHost'); export const simulatorRendererSymbol = Symbol('simulatorRenderer'); export const dragObjectSymbol = Symbol('dragObject'); export const locateEventSymbol = Symbol('locateEvent'); -export const designerCabinSymbol = Symbol('designerCabin'); \ No newline at end of file +export const designerCabinSymbol = Symbol('designerCabin'); +export const hotkeySymbol = Symbol('hotkey'); \ No newline at end of file diff --git a/packages/types/src/engine-config.ts b/packages/types/src/engine-config.ts index 5fb2196fd..dcd6cc41b 100644 --- a/packages/types/src/engine-config.ts +++ b/packages/types/src/engine-config.ts @@ -132,6 +132,11 @@ export interface EngineOptions { * 配置指定节点为根组件 */ focusNodeSelector?: (rootNode: Node) => Node; + + /** + * 开启应用级设计模式 + */ + enableWorkspaceMode?: boolean; } export interface IEngineConfig { diff --git a/packages/types/src/shell/model/document-model.ts b/packages/types/src/shell/model/document-model.ts index 2407cdab1..403cb42de 100644 --- a/packages/types/src/shell/model/document-model.ts +++ b/packages/types/src/shell/model/document-model.ts @@ -5,6 +5,8 @@ import { IPublicApiProject } from '../api'; import { PropChangeOptions } from '../index'; import { IPublicModelModalNodesManager } from './modal-nodes-manager'; import { IPublicModelNode } from './node'; +import { IPublicModelSelection } from './selection'; +import { IPublicModelHistory } from './history'; export interface IPublicModelDocumentModel { @@ -16,6 +18,10 @@ export interface IPublicModelDocumentModel { set id(id); + selection: IPublicModelSelection; + + history: IPublicModelHistory; + /** * 获取当前文档所属的 project * @returns diff --git a/packages/types/src/shell/model/node.ts b/packages/types/src/shell/model/node.ts index f0aaf9506..eed86b941 100644 --- a/packages/types/src/shell/model/node.ts +++ b/packages/types/src/shell/model/node.ts @@ -12,7 +12,6 @@ import { IPublicModelNodeChildren } from './node-children'; import { IPublicModelComponentMeta } from './component-meta'; export interface IPublicModelNode { - /** * 节点 id */ @@ -313,4 +312,11 @@ export interface IPublicModelNode { * @returns Boolean */ get isRGLContainer(); + + /** + * 是否可执行某 action + * @param actionName action 名字 + * @returns boolean + */ + canPerformAction(actionName: string): boolean; } diff --git a/packages/types/src/shell/model/selection.ts b/packages/types/src/shell/model/selection.ts index 43a5ee494..c7724bbec 100644 --- a/packages/types/src/shell/model/selection.ts +++ b/packages/types/src/shell/model/selection.ts @@ -61,5 +61,5 @@ export interface IPublicModelSelection { * getTopNodes() will return [A, B], subA will be removed * @returns */ - getTopNodes(): Array; + getTopNodes(includeRoot?: boolean): Array; } diff --git a/packages/workspace/src/base-context.ts b/packages/workspace/src/base-context.ts index f1b4cfa18..ba745fde6 100644 --- a/packages/workspace/src/base-context.ts +++ b/packages/workspace/src/base-context.ts @@ -1,4 +1,4 @@ -import { Editor, engineConfig, Setters as InnerSetters } from '@alilc/lowcode-editor-core'; +import { Editor, engineConfig, Setters as InnerSetters, Hotkey as InnerHotkey } from '@alilc/lowcode-editor-core'; import { Designer, ILowCodePluginContextApiAssembler, @@ -18,6 +18,7 @@ import { getLogger } from '@alilc/lowcode-utils'; import { setterRegistry } from 'engine/src/inner-plugins/setter-registry'; import { componentMetaParser } from 'engine/src/inner-plugins/component-meta-parser'; import defaultPanelRegistry from 'engine/src/inner-plugins/default-panel-registry'; +import { builtinHotkey } from 'engine/src/inner-plugins/builtin-hotkey'; import { EditorWindow } from './editor-window/context'; import { shellModelFactory } from './shell-model-factory'; @@ -37,6 +38,7 @@ export class BasicContext { registerInnerPlugins: any; innerSetters: any; innerSkeleton: any; + innerHotkey: any; constructor(workSpace: WorkSpace, name: string, public editorWindow?: EditorWindow) { const editor = new Editor(name, true); @@ -60,7 +62,8 @@ export class BasicContext { editor.set('designer' as any, designer); const { project: innerProject } = designer; - const hotkey = new Hotkey(name); + const innerHotkey = new InnerHotkey(name); + const hotkey = new Hotkey(innerHotkey, name, true); const innerSetters = new InnerSetters(name); const setters = new Setters(innerSetters, true); const material = new Material(editor, true, name); @@ -72,6 +75,9 @@ export class BasicContext { editor.set('setters', setters); editor.set('project', project); editor.set('material', material); + editor.set('hotkey', hotkey); + editor.set('innerHotkey', innerHotkey); + innerHotkey.mount(window); this.innerSetters = innerSetters; this.innerSkeleton = innerSkeleton; this.skeleton = skeleton; @@ -83,6 +89,7 @@ export class BasicContext { this.event = event; this.logger = logger; this.hotkey = hotkey; + this.innerHotkey = innerHotkey; this.editor = editor; this.designer = designer; const common = new Common(editor, innerSkeleton); @@ -110,6 +117,7 @@ export class BasicContext { await plugins.register(componentMetaParser(designer)); await plugins.register(setterRegistry); await plugins.register(defaultPanelRegistry(editor, designer)); + await plugins.register(builtinHotkey); }; } diff --git a/packages/workspace/src/editor-window/context.ts b/packages/workspace/src/editor-window/context.ts index 0990e6523..84bbd2815 100644 --- a/packages/workspace/src/editor-window/context.ts +++ b/packages/workspace/src/editor-window/context.ts @@ -85,6 +85,10 @@ export class EditorWindow { return this.editorView?.innerSetters; } + get innerHotkey() { + return this.editorView?.innerHotkey; + } + get editor() { return this.editorView?.editor; }