feat: The outline tree supports hotkeys and works like canvas (#1226)
This commit is contained in:
parent
564f929b85
commit
20c3b071bd
|
|
@ -1,7 +1,10 @@
|
|||
import CanvasContainer from './src/CanvasContainer.vue'
|
||||
import { useMultiSelect } from './src/composables/useMultiSelect'
|
||||
import { registerHotkeyEvent, removeHotkeyEvent } from './src/keyboard'
|
||||
import metaData from './meta'
|
||||
|
||||
export default {
|
||||
...metaData,
|
||||
entry: CanvasContainer
|
||||
entry: CanvasContainer,
|
||||
api: { useMultiSelect, registerHotkeyEvent, removeHotkeyEvent }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -146,7 +146,15 @@ const handleCopyEvent = (event) => {
|
|||
setClipboardSchema(event, dataToCopy)
|
||||
}
|
||||
|
||||
const eventFiltersMap = new WeakMap()
|
||||
|
||||
const handlerClipboardEvent = (event) => {
|
||||
const eventFilter = eventFiltersMap.get(event.currentTarget)
|
||||
// 如果过滤器返回 false,则阻止处理
|
||||
if (typeof eventFilter === 'function' && !eventFilter(event)) {
|
||||
return
|
||||
}
|
||||
|
||||
switch (event.type) {
|
||||
case 'copy':
|
||||
handleCopyEvent(event)
|
||||
|
|
@ -163,12 +171,18 @@ const handlerClipboardEvent = (event) => {
|
|||
}
|
||||
|
||||
const keyboardHandler = (event) => {
|
||||
const eventFilter = eventFiltersMap.get(event.currentTarget)
|
||||
// 如果过滤器返回 false,则阻止处理
|
||||
if (typeof eventFilter === 'function' && !eventFilter(event)) {
|
||||
return
|
||||
}
|
||||
|
||||
// 处理 Ctrl 或 Command 键
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
handlerCtrl(event)
|
||||
} else {
|
||||
handlerArrow(event.keyCode)
|
||||
}
|
||||
|
||||
handlerArrow(event.keyCode)
|
||||
}
|
||||
|
||||
const removeHotkeyEvent = (dom) => {
|
||||
|
|
@ -176,11 +190,19 @@ const removeHotkeyEvent = (dom) => {
|
|||
dom.removeEventListener('copy', handlerClipboardEvent)
|
||||
dom.removeEventListener('cut', handlerClipboardEvent)
|
||||
dom.removeEventListener('paste', handlerClipboardEvent)
|
||||
|
||||
eventFiltersMap.delete(dom)
|
||||
}
|
||||
|
||||
const registerHotkeyEvent = (dom) => {
|
||||
const registerHotkeyEvent = (dom, options) => {
|
||||
removeHotkeyEvent(dom)
|
||||
|
||||
const { eventFilter } = options || {}
|
||||
|
||||
if (typeof eventFilter === 'function') {
|
||||
eventFiltersMap.set(dom, eventFilter)
|
||||
}
|
||||
|
||||
dom.addEventListener('keydown', keyboardHandler)
|
||||
dom.addEventListener('copy', handlerClipboardEvent)
|
||||
dom.addEventListener('cut', handlerClipboardEvent)
|
||||
|
|
|
|||
|
|
@ -9,14 +9,14 @@
|
|||
'tree-row',
|
||||
'flex-center',
|
||||
{
|
||||
active: active === row.id,
|
||||
active: activesComputed.includes(row.id),
|
||||
dragging: draggingState.hovering?.id === row.id,
|
||||
'border-all': draggingState.hovering?.id === row.id && draggingState.position === 'center',
|
||||
forbid: draggingState.forbidInsert
|
||||
}
|
||||
]"
|
||||
:draggable="draggable ? 'true' : undefined"
|
||||
@click="handleClickRow(row)"
|
||||
@click="handleClickRow($event, row)"
|
||||
@mouseenter="handleMouseEnterRow(row)"
|
||||
@dragstart="handleDragStart($event, row)"
|
||||
@dragover="handleDragOver($event, row)"
|
||||
|
|
@ -60,6 +60,10 @@ const props = defineProps({
|
|||
active: {
|
||||
type: String
|
||||
},
|
||||
actives: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
idKey: {
|
||||
type: String,
|
||||
default: 'id'
|
||||
|
|
@ -82,6 +86,15 @@ const props = defineProps({
|
|||
}
|
||||
})
|
||||
|
||||
/** @type {import('vue').ComputedRef<string[]>} */
|
||||
const activesComputed = computed(() => {
|
||||
if (props.actives.length === 0 && props.active) {
|
||||
return [props.active]
|
||||
}
|
||||
|
||||
return props.actives
|
||||
})
|
||||
|
||||
/**
|
||||
* @typedef {Object} Node
|
||||
* @property {string} id
|
||||
|
|
@ -195,8 +208,8 @@ const rows = computed(() => flattenNodes(normalizedData.value))
|
|||
|
||||
const emit = defineEmits(['click', 'mouseenter', 'drop'])
|
||||
|
||||
const handleClickRow = (row) => {
|
||||
emit('click', row)
|
||||
const handleClickRow = (event, row) => {
|
||||
emit('click', event, row)
|
||||
}
|
||||
|
||||
const handleMouseEnterRow = (row) => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<plugin-panel class="outlinebox plugin-tree" title="大纲树" @close="$emit('close')">
|
||||
<plugin-panel class="outlinebox plugin-tree" title="大纲树" @close="$emit('close')" ref="panelRef" tabindex="0">
|
||||
<template #header>
|
||||
<svg-button
|
||||
class="item icon-sidebar"
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
label-key="componentName"
|
||||
:data="state.pageSchema"
|
||||
:draggable="true"
|
||||
:active="pageState.currentSchema?.id"
|
||||
:actives="selectedIds"
|
||||
:disallow-drop="disallowDrop"
|
||||
class="outline-tree"
|
||||
@click="handleClickRow"
|
||||
|
|
@ -38,10 +38,17 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive, watch, computed, onActivated, onDeactivated, nextTick } from 'vue'
|
||||
import { reactive, watch, computed, onActivated, onDeactivated, onMounted, onBeforeUnmount, nextTick, ref } from 'vue'
|
||||
import { PluginPanel, SvgButton } from '@opentiny/tiny-engine-common'
|
||||
import { constants } from '@opentiny/tiny-engine-utils'
|
||||
import { useCanvas, useMaterial, useLayout, useMessage, useHistory } from '@opentiny/tiny-engine-meta-register'
|
||||
import {
|
||||
useCanvas,
|
||||
useMaterial,
|
||||
useLayout,
|
||||
useMessage,
|
||||
useHistory,
|
||||
getMergeMeta
|
||||
} from '@opentiny/tiny-engine-meta-register'
|
||||
import { extend } from '@opentiny/vue-renderless/common/object'
|
||||
import DraggableTree from './DraggableTree.vue'
|
||||
|
||||
|
|
@ -65,6 +72,10 @@ export default {
|
|||
|
||||
const panelFixed = computed(() => props.fixedPanels?.includes(PLUGIN_NAME.OutlineTree))
|
||||
|
||||
const { useMultiSelect, registerHotkeyEvent, removeHotkeyEvent } = getMergeMeta('engine.canvas.container').api
|
||||
|
||||
const selectedIds = computed(() => useMultiSelect().multiSelectedStates.value.map((state) => state.id))
|
||||
|
||||
const filterSchema = (data) => {
|
||||
const translateChild = (data) => {
|
||||
data.forEach((item) => {
|
||||
|
|
@ -195,9 +206,10 @@ export default {
|
|||
})
|
||||
}
|
||||
|
||||
const handleClickRow = (row) => {
|
||||
const handleClickRow = (event, row) => {
|
||||
const isCtrlKey = event.ctrlKey || event.metaKey
|
||||
const { selectNode } = useCanvas().canvasApi.value
|
||||
selectNode(row.id, 'clickTree')
|
||||
selectNode(row.id, 'clickTree', isCtrlKey)
|
||||
}
|
||||
|
||||
const getIconName = (row) => {
|
||||
|
|
@ -205,8 +217,29 @@ export default {
|
|||
return iconName.toLowerCase()
|
||||
}
|
||||
|
||||
const panelRef = ref(null)
|
||||
|
||||
// 由于copy,paste等事件需要在document上监听才有效果,这里加了一个事件过滤器。当焦点在大纲树上时才触发热键事件
|
||||
const eventFilter = () => {
|
||||
return panelRef.value.$el.contains(document.activeElement)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (panelRef.value) {
|
||||
registerHotkeyEvent(document, { eventFilter })
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (panelRef.value) {
|
||||
removeHotkeyEvent(document)
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
panelFixed,
|
||||
selectedIds,
|
||||
panelRef,
|
||||
eyeOpen,
|
||||
delNode,
|
||||
showNode,
|
||||
|
|
@ -227,6 +260,9 @@ export default {
|
|||
.outlinebox {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
.outline-tree {
|
||||
flex: 1;
|
||||
|
|
|
|||
Loading…
Reference in New Issue