feat(editor): 优化快捷键操作

This commit is contained in:
roymondchen 2023-06-12 17:28:56 +08:00
parent 567b054b32
commit 06d289aff3
5 changed files with 279 additions and 147 deletions

View File

@ -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);

View File

@ -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>('services');
const editorService = services?.editorService;
const keybindingService = services?.keybindingService;
const tree = ref<InstanceType<typeof TMagicTree>>();
const menu = ref<InstanceType<typeof LayerMenu>>();
@ -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) {
// ctrlctrl
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);
});
//

View File

@ -1,5 +1,5 @@
<template>
<div class="m-editor-workspace" tabindex="-1" ref="workspace">
<div class="m-editor-workspace" tabindex="-1">
<Breadcrumb></Breadcrumb>
<slot name="stage">
@ -16,10 +16,7 @@
</template>
<script lang="ts" setup>
import { computed, inject, onMounted, onUnmounted, ref } from 'vue';
import KeyController from 'keycon';
import { isPage } from '@tmagic/utils';
import { computed, inject } from 'vue';
import type { MenuButton, MenuComponent, Services } from '@editor/type';
@ -36,132 +33,6 @@ defineProps<{
}>();
const services = inject<Services>('services');
const workspace = ref<HTMLDivElement>();
const nodes = computed(() => services?.editorService.get('nodes'));
const page = computed(() => services?.editorService.get('page'));
const mouseenterHandler = () => {
workspace.value?.focus();
};
const mouseleaveHandler = () => {
workspace.value?.blur();
};
let keycon: KeyController;
onMounted(() => {
workspace.value?.addEventListener('mouseenter', mouseenterHandler);
workspace.value?.addEventListener('mouseleave', mouseleaveHandler);
keycon = new KeyController(workspace.value);
const isMac = /mac os x/.test(navigator.userAgent.toLowerCase());
const ctrl = isMac ? 'meta' : 'ctrl';
keycon
.keyup('delete', (e) => {
e.inputEvent.preventDefault();
if (!nodes.value || isPage(nodes.value[0])) return;
services?.editorService.remove(nodes.value);
})
.keyup('backspace', (e) => {
e.inputEvent.preventDefault();
if (!nodes.value || isPage(nodes.value[0])) return;
services?.editorService.remove(nodes.value);
})
.keydown([ctrl, 'c'], (e) => {
e.inputEvent.preventDefault();
nodes.value && services?.editorService.copy(nodes.value);
})
.keydown([ctrl, 'v'], (e) => {
e.inputEvent.preventDefault();
nodes.value && services?.editorService.paste({ offsetX: 10, offsetY: 10 });
})
.keydown([ctrl, 'x'], (e) => {
e.inputEvent.preventDefault();
if (!nodes.value || isPage(nodes.value[0])) return;
services?.editorService.copy(nodes.value);
services?.editorService.remove(nodes.value);
})
.keydown([ctrl, 'z'], (e) => {
e.inputEvent.preventDefault();
services?.editorService.undo();
})
.keydown([ctrl, 'shift', 'z'], (e) => {
e.inputEvent.preventDefault();
services?.editorService.redo();
})
.keydown('up', (e) => {
e.inputEvent.preventDefault();
services?.editorService.move(0, -1);
})
.keydown('down', (e) => {
e.inputEvent.preventDefault();
services?.editorService.move(0, 1);
})
.keydown('left', (e) => {
e.inputEvent.preventDefault();
services?.editorService.move(-1, 0);
})
.keydown('right', (e) => {
e.inputEvent.preventDefault();
services?.editorService.move(1, 0);
})
.keydown([ctrl, 'up'], (e) => {
e.inputEvent.preventDefault();
services?.editorService.move(0, -10);
})
.keydown([ctrl, 'down'], (e) => {
e.inputEvent.preventDefault();
services?.editorService.move(0, 10);
})
.keydown([ctrl, 'left'], (e) => {
e.inputEvent.preventDefault();
services?.editorService.move(-10, 0);
})
.keydown([ctrl, 'right'], (e) => {
e.inputEvent.preventDefault();
services?.editorService.move(10, 0);
})
.keydown('tab', (e) => {
e.inputEvent.preventDefault();
services?.editorService.selectNextNode();
})
.keydown([ctrl, 'tab'], (e) => {
e.inputEvent.preventDefault();
services?.editorService.selectNextPage();
})
.keydown([ctrl, '='], (e) => {
e.inputEvent.preventDefault();
services?.uiService.zoom(0.1);
})
.keydown([ctrl, 'numpadplus'], (e) => {
e.inputEvent.preventDefault();
services?.uiService.zoom(0.1);
})
.keydown([ctrl, '-'], (e) => {
e.inputEvent.preventDefault();
services?.uiService.zoom(-0.1);
})
.keydown([ctrl, 'numpad-'], (e) => {
e.inputEvent.preventDefault();
services?.uiService.zoom(-0.1);
})
.keydown([ctrl, '0'], async (e) => {
e.inputEvent.preventDefault();
services?.uiService.set('zoom', await services.uiService.calcZoom());
})
.keydown([ctrl, '1'], (e) => {
e.inputEvent.preventDefault();
services?.uiService.set('zoom', 1);
});
});
onUnmounted(() => {
workspace.value?.removeEventListener('mouseenter', mouseenterHandler);
workspace.value?.removeEventListener('mouseleave', mouseleaveHandler);
keycon.destroy();
});
</script>

View File

@ -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();

View File

@ -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 {