From 06d289aff33221615def85aca6f2954200bb5eb7 Mon Sep 17 00:00:00 2001 From: roymondchen Date: Mon, 12 Jun 2023 17:28:56 +0800 Subject: [PATCH] =?UTF-8?q?feat(editor):=20=E4=BC=98=E5=8C=96=E5=BF=AB?= =?UTF-8?q?=E6=8D=B7=E9=94=AE=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/editor/src/Editor.vue | 2 + .../editor/src/layouts/sidebar/LayerPanel.vue | 45 +++- .../src/layouts/workspace/Workspace.vue | 135 +--------- packages/editor/src/services/keybinding.ts | 240 ++++++++++++++++++ packages/editor/src/type.ts | 4 +- 5 files changed, 279 insertions(+), 147 deletions(-) create mode 100644 packages/editor/src/services/keybinding.ts diff --git a/packages/editor/src/Editor.vue b/packages/editor/src/Editor.vue index 0fd9bc03..a6384464 100644 --- a/packages/editor/src/Editor.vue +++ b/packages/editor/src/Editor.vue @@ -96,6 +96,7 @@ import depService from './services/dep'; import editorService from './services/editor'; import eventsService from './services/events'; import historyService from './services/history'; +import keybindingService from './services/keybinding'; import propsService from './services/props'; import storageService from './services/storage'; import uiService from './services/ui'; @@ -130,6 +131,7 @@ export default defineComponent({ codeBlockService, depService, dataSourceService, + keybindingService, }; initServiceEvents(props, emit, services); diff --git a/packages/editor/src/layouts/sidebar/LayerPanel.vue b/packages/editor/src/layouts/sidebar/LayerPanel.vue index 2722dc91..14150ce4 100644 --- a/packages/editor/src/layouts/sidebar/LayerPanel.vue +++ b/packages/editor/src/layouts/sidebar/LayerPanel.vue @@ -10,6 +10,7 @@ ref="tree" node-key="id" empty-text="页面空荡荡的" + tabindex="-1" draggable :default-expanded-keys="expandedKeys" :default-checked-keys="checkedKeys" @@ -75,6 +76,7 @@ defineProps<{ const throttleTime = 150; const services = inject('services'); const editorService = services?.editorService; +const keybindingService = services?.keybindingService; const tree = ref>(); const menu = ref>(); @@ -246,27 +248,40 @@ const windowBlurHandler = () => { isCtrlKeyDown.value = false; }; +const globalKeyupHandler = () => { + if (document.activeElement !== tree.value?.$el) { + isCtrlKeyDown.value = false; + } +}; + let keycon: KeyController; onMounted(() => { editorService?.on('remove', editorServiceRemoveHandler); - keycon = new KeyController(); - const isMac = /mac os x/.test(navigator.userAgent.toLowerCase()); - const ctrl = isMac ? 'meta' : 'ctrl'; + if (tree.value?.$el) { + // 如果是在树上按下ctrl,那么在树外松开ctrl时,也要触发松开事件 + keybindingService?.on('keyup', globalKeyupHandler); - keycon - .keydown((e) => { - if (e.key !== ctrl) { + keycon = new KeyController(tree.value.$el); + const isMac = /mac os x/.test(navigator.userAgent.toLowerCase()); + const ctrl = isMac ? 'meta' : 'ctrl'; + + keycon + .keydown((e) => { + if (e.key !== ctrl) { + isCtrlKeyDown.value = false; + } + }) + .keydown(ctrl, () => { + isCtrlKeyDown.value = true; + }) + .keyup(ctrl, () => { isCtrlKeyDown.value = false; - } - }) - .keydown(ctrl, () => { - isCtrlKeyDown.value = true; - }) - .keyup(ctrl, () => { - isCtrlKeyDown.value = false; - }); + }); + + tree.value.$el.addEventListener('blur', windowBlurHandler); + } globalThis.addEventListener('blur', windowBlurHandler); }); @@ -275,7 +290,9 @@ onUnmounted(() => { keycon.destroy(); editorService?.off('remove', editorServiceRemoveHandler); + keybindingService?.off('keyup', globalKeyupHandler); globalThis.removeEventListener('blur', windowBlurHandler); + tree.value?.$el.removeEventListener('blur', windowBlurHandler); }); // 鼠标在组件树移动触发高亮 diff --git a/packages/editor/src/layouts/workspace/Workspace.vue b/packages/editor/src/layouts/workspace/Workspace.vue index 14766687..89905b78 100644 --- a/packages/editor/src/layouts/workspace/Workspace.vue +++ b/packages/editor/src/layouts/workspace/Workspace.vue @@ -1,5 +1,5 @@ diff --git a/packages/editor/src/services/keybinding.ts b/packages/editor/src/services/keybinding.ts new file mode 100644 index 00000000..af239f66 --- /dev/null +++ b/packages/editor/src/services/keybinding.ts @@ -0,0 +1,240 @@ +import KeyController from 'keycon'; + +import { isPage } from '@tmagic/utils'; + +import BaseService from './BaseService'; +import editorService from './editor'; +import uiService from './ui'; + +class Keybinding extends BaseService { + private keycon = new KeyController(); + + private ctrlKey = /mac os x/.test(navigator.userAgent.toLowerCase()) ? 'meta' : 'ctrl'; + + constructor() { + super(); + + this.keycon + .keyup((e) => { + this.emit('keyup', e.inputEvent); + }) + .keyup('delete', (e) => { + if (this.isDisabledKeyEvent(e.inputEvent.target)) { + return; + } + + e.inputEvent.preventDefault(); + + this.removeNode(); + }) + .keyup('backspace', (e) => { + if (this.isDisabledKeyEvent(e.inputEvent.target)) { + return; + } + + e.inputEvent.preventDefault(); + + this.removeNode(); + }) + .keydown([this.ctrlKey, 'c'], (e) => { + if (this.isDisabledKeyEvent(e.inputEvent.target)) { + return; + } + + e.inputEvent.preventDefault(); + + const nodes = editorService.get('nodes'); + nodes && editorService.copy(nodes); + }) + .keydown([this.ctrlKey, 'v'], (e) => { + if (this.isDisabledKeyEvent(e.inputEvent.target)) { + return; + } + + e.inputEvent.preventDefault(); + + const nodes = editorService.get('nodes'); + nodes && editorService.paste({ offsetX: 10, offsetY: 10 }); + }) + .keydown([this.ctrlKey, 'x'], (e) => { + if (this.isDisabledKeyEvent(e.inputEvent.target)) { + return; + } + + e.inputEvent.preventDefault(); + if (this.isDisabledKeyEvent(e.inputEvent.target)) { + return; + } + + const nodes = editorService.get('nodes'); + + if (!nodes || isPage(nodes[0])) return; + editorService.copy(nodes); + editorService.remove(nodes); + }) + .keydown([this.ctrlKey, 'z'], (e) => { + if (this.isDisabledKeyEvent(e.inputEvent.target)) { + return; + } + + e.inputEvent.preventDefault(); + editorService.undo(); + }) + .keydown([this.ctrlKey, 'shift', 'z'], (e) => { + if (this.isDisabledKeyEvent(e.inputEvent.target)) { + return; + } + + e.inputEvent.preventDefault(); + editorService.redo(); + }) + .keydown('up', (e) => { + if (this.isDisabledKeyEvent(e.inputEvent.target)) { + return; + } + + e.inputEvent.preventDefault(); + editorService.move(0, -1); + }) + .keydown('down', (e) => { + if (this.isDisabledKeyEvent(e.inputEvent.target)) { + return; + } + + e.inputEvent.preventDefault(); + editorService.move(0, 1); + }) + .keydown('left', (e) => { + if (this.isDisabledKeyEvent(e.inputEvent.target)) { + return; + } + + e.inputEvent.preventDefault(); + editorService.move(-1, 0); + }) + .keydown('right', (e) => { + if (this.isDisabledKeyEvent(e.inputEvent.target)) { + return; + } + + e.inputEvent.preventDefault(); + editorService.move(1, 0); + }) + .keydown([this.ctrlKey, 'up'], (e) => { + if (this.isDisabledKeyEvent(e.inputEvent.target)) { + return; + } + + e.inputEvent.preventDefault(); + editorService.move(0, -10); + }) + .keydown([this.ctrlKey, 'down'], (e) => { + if (this.isDisabledKeyEvent(e.inputEvent.target)) { + return; + } + + e.inputEvent.preventDefault(); + editorService.move(0, 10); + }) + .keydown([this.ctrlKey, 'left'], (e) => { + if (this.isDisabledKeyEvent(e.inputEvent.target)) { + return; + } + + e.inputEvent.preventDefault(); + editorService.move(-10, 0); + }) + .keydown([this.ctrlKey, 'right'], (e) => { + e.inputEvent.preventDefault(); + editorService.move(10, 0); + }) + .keydown('tab', (e) => { + if (this.isDisabledKeyEvent(e.inputEvent.target)) { + return; + } + + e.inputEvent.preventDefault(); + editorService.selectNextNode(); + }) + .keydown([this.ctrlKey, 'tab'], (e) => { + if (this.isDisabledKeyEvent(e.inputEvent.target)) { + return; + } + + e.inputEvent.preventDefault(); + editorService.selectNextPage(); + }) + .keydown([this.ctrlKey, '='], (e) => { + if (this.isDisabledKeyEvent(e.inputEvent.target)) { + return; + } + + e.inputEvent.preventDefault(); + uiService.zoom(0.1); + }) + .keydown([this.ctrlKey, 'numpadplus'], (e) => { + if (this.isDisabledKeyEvent(e.inputEvent.target)) { + return; + } + + e.inputEvent.preventDefault(); + uiService.zoom(0.1); + }) + .keydown([this.ctrlKey, '-'], (e) => { + if (this.isDisabledKeyEvent(e.inputEvent.target)) { + return; + } + + e.inputEvent.preventDefault(); + uiService.zoom(-0.1); + }) + .keydown([this.ctrlKey, 'numpad-'], (e) => { + if (this.isDisabledKeyEvent(e.inputEvent.target)) { + return; + } + + e.inputEvent.preventDefault(); + uiService.zoom(-0.1); + }) + .keydown([this.ctrlKey, '0'], async (e) => { + if (this.isDisabledKeyEvent(e.inputEvent.target)) { + return; + } + + e.inputEvent.preventDefault(); + uiService.set('zoom', await uiService.calcZoom()); + }) + .keydown([this.ctrlKey, '1'], (e) => { + if (this.isDisabledKeyEvent(e.inputEvent.target)) { + return; + } + + e.inputEvent.preventDefault(); + uiService.set('zoom', 1); + }); + } + + public destroy() { + this.keycon.destroy(); + } + + private isDisabledKeyEvent(node: EventTarget | null) { + const el = node as HTMLElement | null; + + if (!el) return false; + + // 当前是在输入框中,禁止响应画布快捷键 + return el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.isContentEditable; + } + + private removeNode() { + const nodes = editorService.get('nodes'); + + if (!nodes || isPage(nodes[0])) return; + editorService.remove(nodes); + } +} + +export type KeybindingService = Keybinding; + +export default new Keybinding(); diff --git a/packages/editor/src/type.ts b/packages/editor/src/type.ts index 25b32c20..7770d425 100644 --- a/packages/editor/src/type.ts +++ b/packages/editor/src/type.ts @@ -30,11 +30,12 @@ import type { import type { CodeBlockService } from './services/codeBlock'; import type { ComponentListService } from './services/componentList'; -import { DataSourceService } from './services/dataSource'; +import type { DataSourceService } from './services/dataSource'; import type { DepService } from './services/dep'; import type { EditorService } from './services/editor'; import type { EventsService } from './services/events'; import type { HistoryService } from './services/history'; +import type { KeybindingService } from './services/keybinding'; import type { PropsService } from './services/props'; import type { StorageService } from './services/storage'; import type { UiService } from './services/ui'; @@ -58,6 +59,7 @@ export interface Services { codeBlockService: CodeBlockService; depService: DepService; dataSourceService: DataSourceService; + keybindingService: KeybindingService; } export interface StageOptions {