perf: support keyboard navigation in key tree view (#238)
This commit is contained in:
parent
e2264b33b0
commit
455a911154
|
@ -5,7 +5,7 @@ import { NIcon, NSpace, NText, useThemeVars } from 'naive-ui'
|
|||
import Key from '@/components/icons/Key.vue'
|
||||
import Binary from '@/components/icons/Binary.vue'
|
||||
import Database from '@/components/icons/Database.vue'
|
||||
import { filter, find, get, includes, isEmpty, map, size, toUpper } from 'lodash'
|
||||
import { filter, find, first, get, includes, isEmpty, last, map, size, toUpper } from 'lodash'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import Refresh from '@/components/icons/Refresh.vue'
|
||||
import CopyLink from '@/components/icons/CopyLink.vue'
|
||||
|
@ -153,6 +153,133 @@ const menuOptions = {
|
|||
],
|
||||
}
|
||||
|
||||
const handleKeyUp = () => {
|
||||
const selectedKey = get(selectedKeys.value, 0)
|
||||
if (selectedKey == null) {
|
||||
return
|
||||
}
|
||||
let node = browserStore.getNode(selectedKey)
|
||||
if (node == null) {
|
||||
return
|
||||
}
|
||||
|
||||
let parentNode = browserStore.getParentNode(selectedKey)
|
||||
if (parentNode == null) {
|
||||
return
|
||||
}
|
||||
const nodeIndex = parentNode.children.indexOf(node)
|
||||
if (nodeIndex <= 0) {
|
||||
if (parentNode.type === ConnectionType.RedisKey || parentNode.type === ConnectionType.RedisValue) {
|
||||
onUpdateSelectedKeys([parentNode.key])
|
||||
updateKeyDetail(parentNode)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// try select pre node
|
||||
let preNode = parentNode.children[nodeIndex - 1]
|
||||
while (preNode.expanded && !isEmpty(preNode.children)) {
|
||||
preNode = last(preNode.children)
|
||||
}
|
||||
onUpdateSelectedKeys([preNode.key])
|
||||
updateKeyDetail(preNode)
|
||||
}
|
||||
|
||||
const handleKeyDown = () => {
|
||||
const selectedKey = get(selectedKeys.value, 0)
|
||||
if (selectedKey == null) {
|
||||
return
|
||||
}
|
||||
let node = browserStore.getNode(selectedKey)
|
||||
if (node == null) {
|
||||
return
|
||||
}
|
||||
// try select first child if expanded
|
||||
if (node.expanded && !isEmpty(node.children)) {
|
||||
const childNode = get(node.children, 0)
|
||||
onUpdateSelectedKeys([childNode.key])
|
||||
updateKeyDetail(childNode)
|
||||
return
|
||||
}
|
||||
|
||||
let travelCount = 0
|
||||
let childKey = selectedKey
|
||||
do {
|
||||
if (travelCount++ > 20) {
|
||||
return
|
||||
}
|
||||
// find out parent node
|
||||
const parentNode = browserStore.getParentNode(childKey)
|
||||
if (parentNode == null) {
|
||||
return
|
||||
}
|
||||
const nodeIndex = parentNode.children.indexOf(node)
|
||||
if (nodeIndex < 0 || nodeIndex >= parentNode.children.length - 1) {
|
||||
// last child, try select parent's neighbor node
|
||||
childKey = parentNode.key
|
||||
node = parentNode
|
||||
} else {
|
||||
// select next node
|
||||
const childNode = parentNode.children[nodeIndex + 1]
|
||||
onUpdateSelectedKeys([childNode.key])
|
||||
updateKeyDetail(childNode)
|
||||
return
|
||||
}
|
||||
} while (true)
|
||||
}
|
||||
|
||||
const handleKeyLeft = () => {
|
||||
const selectedKey = get(selectedKeys.value, 0)
|
||||
if (selectedKey == null) {
|
||||
return
|
||||
}
|
||||
let node = browserStore.getNode(selectedKey)
|
||||
if (node == null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (node.type === ConnectionType.RedisKey) {
|
||||
if (node.expanded) {
|
||||
// try collapse
|
||||
onUpdateExpanded([node.key], null, { node, action: 'collapse' })
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// try select parent node
|
||||
let parentNode = browserStore.getParentNode(selectedKey)
|
||||
if (parentNode == null || parentNode.type !== ConnectionType.RedisKey) {
|
||||
return
|
||||
}
|
||||
onUpdateSelectedKeys([parentNode.key])
|
||||
updateKeyDetail(parentNode)
|
||||
}
|
||||
|
||||
const handleKeyRight = () => {
|
||||
const selectedKey = get(selectedKeys.value, 0)
|
||||
if (selectedKey == null) {
|
||||
return
|
||||
}
|
||||
let node = browserStore.getNode(selectedKey)
|
||||
if (node == null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (node.type === ConnectionType.RedisKey) {
|
||||
if (!node.expanded) {
|
||||
// try expand
|
||||
onUpdateExpanded([node.key], null, { node, action: 'expand' })
|
||||
} else if (!isEmpty(node.children)) {
|
||||
// try select first child
|
||||
const childNode = first(node.children)
|
||||
onUpdateSelectedKeys([childNode.key])
|
||||
updateKeyDetail(childNode)
|
||||
}
|
||||
} else if (node.type === ConnectionType.RedisValue) {
|
||||
handleKeyDown()
|
||||
}
|
||||
}
|
||||
|
||||
const handleSelectContextMenu = (action) => {
|
||||
contextMenuParam.show = false
|
||||
const selectedKey = get(selectedKeys.value, 0)
|
||||
|
@ -471,20 +598,28 @@ const renderSuffix = ({ option }) => {
|
|||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {RedisNodeItem} node
|
||||
*/
|
||||
const updateKeyDetail = (node) => {
|
||||
if (node.type === ConnectionType.RedisValue) {
|
||||
if (tabStore.setActivatedKey(props.server, node.key)) {
|
||||
const { db, redisKey, redisKeyCode } = node
|
||||
browserStore.loadKeySummary({
|
||||
server: props.server,
|
||||
db,
|
||||
key: redisKeyCode || redisKey,
|
||||
clearValue: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const nodeProps = ({ option }) => {
|
||||
return {
|
||||
onClick: () => {
|
||||
if (option.type === ConnectionType.RedisValue) {
|
||||
if (tabStore.setActivatedKey(props.server, option.key)) {
|
||||
const { db, redisKey, redisKeyCode } = option
|
||||
browserStore.loadKeySummary({
|
||||
server: props.server,
|
||||
db,
|
||||
key: redisKeyCode || redisKey,
|
||||
clearValue: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
updateKeyDetail(option)
|
||||
},
|
||||
onDblclick: () => {
|
||||
if (props.loading) {
|
||||
|
@ -604,6 +739,11 @@ defineExpose({
|
|||
check-strategy="child"
|
||||
class="fill-height"
|
||||
virtual-scroll
|
||||
:keyboard="false"
|
||||
@keydown.up="handleKeyUp"
|
||||
@keydown.down="handleKeyDown"
|
||||
@keydown.left="handleKeyLeft"
|
||||
@keydown.right="handleKeyRight"
|
||||
@keydown.delete="handleSelectContextMenu('value_remove')"
|
||||
@update:selected-keys="onUpdateSelectedKeys"
|
||||
@update:expanded-keys="onUpdateExpanded"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { endsWith, get, isEmpty, map, now, size } from 'lodash'
|
||||
import { endsWith, get, isEmpty, join, map, now, size, slice, split } from 'lodash'
|
||||
import {
|
||||
AddHashField,
|
||||
AddListItem,
|
||||
|
@ -773,6 +773,37 @@ const useBrowserStore = defineStore('browser', {
|
|||
return serverInst.nodeMap.get(keyPart)
|
||||
},
|
||||
|
||||
/**
|
||||
* get parent tree node by key name
|
||||
* @param key
|
||||
* @return {RedisNodeItem|null}
|
||||
*/
|
||||
getParentNode(key) {
|
||||
const i = key.indexOf('#')
|
||||
if (i < 0) {
|
||||
return null
|
||||
}
|
||||
const [server, db] = split(key.substring(0, i), '/')
|
||||
if (isEmpty(server) || isEmpty(db)) {
|
||||
return null
|
||||
}
|
||||
/** @type {RedisServerState} **/
|
||||
const serverInst = this.servers[server]
|
||||
if (serverInst == null) {
|
||||
return null
|
||||
}
|
||||
const separator = this.getSeparator(server)
|
||||
const keyPart = key.substring(i)
|
||||
const keyStartIdx = keyPart.indexOf('/')
|
||||
const redisKey = keyPart.substring(keyStartIdx + 1)
|
||||
const redisKeyParts = split(redisKey, separator)
|
||||
const parentKey = slice(redisKeyParts, 0, size(redisKeyParts) - 1)
|
||||
if (isEmpty(parentKey)) {
|
||||
return serverInst.getRoot()
|
||||
}
|
||||
return serverInst.nodeMap.get(`${ConnectionType.RedisKey}/${join(parentKey, separator)}`)
|
||||
},
|
||||
|
||||
/**
|
||||
* set redis key
|
||||
* @param {string} server
|
||||
|
|
Loading…
Reference in New Issue