Skip to content

Commit

Permalink
perf: support keyboard navigation in key tree view (#238)
Browse files Browse the repository at this point in the history
  • Loading branch information
tiny-craft committed May 14, 2024
1 parent e2264b3 commit 455a911
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 13 deletions.
164 changes: 152 additions & 12 deletions frontend/src/components/sidebar/BrowserTree.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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"
Expand Down
33 changes: 32 additions & 1 deletion frontend/src/stores/browser.js
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 455a911

Please sign in to comment.