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 Key from '@/components/icons/Key.vue'
|
||||||
import Binary from '@/components/icons/Binary.vue'
|
import Binary from '@/components/icons/Binary.vue'
|
||||||
import Database from '@/components/icons/Database.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 { useI18n } from 'vue-i18n'
|
||||||
import Refresh from '@/components/icons/Refresh.vue'
|
import Refresh from '@/components/icons/Refresh.vue'
|
||||||
import CopyLink from '@/components/icons/CopyLink.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) => {
|
const handleSelectContextMenu = (action) => {
|
||||||
contextMenuParam.show = false
|
contextMenuParam.show = false
|
||||||
const selectedKey = get(selectedKeys.value, 0)
|
const selectedKey = get(selectedKeys.value, 0)
|
||||||
|
@ -471,20 +598,28 @@ const renderSuffix = ({ option }) => {
|
||||||
return null
|
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 }) => {
|
const nodeProps = ({ option }) => {
|
||||||
return {
|
return {
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
if (option.type === ConnectionType.RedisValue) {
|
updateKeyDetail(option)
|
||||||
if (tabStore.setActivatedKey(props.server, option.key)) {
|
|
||||||
const { db, redisKey, redisKeyCode } = option
|
|
||||||
browserStore.loadKeySummary({
|
|
||||||
server: props.server,
|
|
||||||
db,
|
|
||||||
key: redisKeyCode || redisKey,
|
|
||||||
clearValue: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onDblclick: () => {
|
onDblclick: () => {
|
||||||
if (props.loading) {
|
if (props.loading) {
|
||||||
|
@ -604,6 +739,11 @@ defineExpose({
|
||||||
check-strategy="child"
|
check-strategy="child"
|
||||||
class="fill-height"
|
class="fill-height"
|
||||||
virtual-scroll
|
virtual-scroll
|
||||||
|
:keyboard="false"
|
||||||
|
@keydown.up="handleKeyUp"
|
||||||
|
@keydown.down="handleKeyDown"
|
||||||
|
@keydown.left="handleKeyLeft"
|
||||||
|
@keydown.right="handleKeyRight"
|
||||||
@keydown.delete="handleSelectContextMenu('value_remove')"
|
@keydown.delete="handleSelectContextMenu('value_remove')"
|
||||||
@update:selected-keys="onUpdateSelectedKeys"
|
@update:selected-keys="onUpdateSelectedKeys"
|
||||||
@update:expanded-keys="onUpdateExpanded"
|
@update:expanded-keys="onUpdateExpanded"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { defineStore } from 'pinia'
|
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 {
|
import {
|
||||||
AddHashField,
|
AddHashField,
|
||||||
AddListItem,
|
AddListItem,
|
||||||
|
@ -773,6 +773,37 @@ const useBrowserStore = defineStore('browser', {
|
||||||
return serverInst.nodeMap.get(keyPart)
|
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
|
* set redis key
|
||||||
* @param {string} server
|
* @param {string} server
|
||||||
|
|
Loading…
Reference in New Issue