refactor: browser tree node update logic(new/delete/refresh)

This commit is contained in:
tiny-craft 2023-07-19 18:34:38 +08:00
parent 00e29ef611
commit 2b3acc0e2b
4 changed files with 176 additions and 108 deletions

View File

@ -130,7 +130,7 @@ const dragging = computed(() => {
.resize-divider { .resize-divider {
//height: 100%; //height: 100%;
width: 2px; width: 5px;
border-left-width: 5px; border-left-width: 5px;
background-color: v-bind('themeVars.tabColor'); background-color: v-bind('themeVars.tabColor');
} }

View File

@ -68,6 +68,7 @@ watch(
const { prefix, server, db } = dialogStore.newKeyParam const { prefix, server, db } = dialogStore.newKeyParam
newForm.server = server newForm.server = server
newForm.key = isEmpty(prefix) ? '' : prefix newForm.key = isEmpty(prefix) ? '' : prefix
newForm.db = db
newForm.type = options.value[0].value newForm.type = options.value[0].value
newForm.ttl = -1 newForm.ttl = -1
newForm.value = null newForm.value = null

View File

@ -384,10 +384,13 @@ const onLoadTree = async (node) => {
loading.value = false loading.value = false
} }
break break
case ConnectionType.RedisKey: // case ConnectionType.RedisKey:
// load all children // console.warn('load redis key', node.redisKey)
// node.children = [] // node.keys = sumBy(node.children, 'keys')
break // break
// case ConnectionType.RedisValue:
// node.keys = 1
// break
} }
} }

View File

@ -1,5 +1,5 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { endsWith, findIndex, get, isEmpty, size, split, toUpper, uniq } from 'lodash' import { endsWith, get, isEmpty, join, remove, size, slice, split, sumBy, toUpper, uniq } from 'lodash'
import { import {
AddHashField, AddHashField,
AddListItem, AddListItem,
@ -45,15 +45,17 @@ const useConnectionStore = defineStore('connections', {
/** /**
* @typedef {Object} DatabaseItem * @typedef {Object} DatabaseItem
* @property {string} key * @property {string} key - tree node unique key
* @property {string} label * @property {string} label
* @property {string} name - server name, type != ConnectionType.Group only * @property {string} [name] - server name, type != ConnectionType.Group only
* @property {number} type * @property {number} type
* @property {number} [db] - database index, type == ConnectionType.RedisDB only * @property {number} [db] - database index, type == ConnectionType.RedisDB only
* @property {number} keys * @property {string} [redisKey] - redis key, type == ConnectionType.RedisKey || type == ConnectionType.RedisValue only
* @property {number} [keys] - children key count
* @property {boolean} [isLeaf] * @property {boolean} [isLeaf]
* @property {boolean} [opened] - redis db is opened, type == ConnectionType.RedisDB only * @property {boolean} [opened] - redis db is opened, type == ConnectionType.RedisDB only
* @property {boolean} [expanded] - current node is expanded * @property {boolean} [expanded] - current node is expanded
* @property {DatabaseItem[]} [children]
*/ */
/** /**
@ -453,7 +455,7 @@ const useConnectionStore = defineStore('connections', {
// append db node to current connection's children // append db node to current connection's children
this._addKeyNodes(connName, db, keys) this._addKeyNodes(connName, db, keys)
this._tidyNodeChildren(dbs[db]) this._tidyNode(connName, db)
}, },
/** /**
@ -577,40 +579,9 @@ const useConnectionStore = defineStore('connections', {
} }
// remove current keys below prefix // remove current keys below prefix
this._deleteKeyNodes(connName, db, prefix) this._deleteKeyNode(connName, db, prefix, true)
this._addKeyNodes(connName, db, keys) this._addKeyNodes(connName, db, keys)
this._tidyNodeChildren(this.databases[connName][db]) this._tidyNode(connName, db, prefix)
},
/**
* remove key with prefix
* @param {string} connName
* @param {number} db
* @param {string} prefix
* @returns {boolean}
* @private
*/
_deleteKeyNodes(connName, db, prefix) {
const separator = this._getSeparator(connName)
const dbs = this.databases[connName]
let node = dbs[db]
const prefixPart = split(prefix, separator)
const partLen = size(prefixPart)
for (let i = 0; i < partLen; i++) {
let idx = findIndex(node.children, { label: prefixPart[i] })
if (idx === -1) {
node = null
break
}
if (i === partLen - 1) {
// remove last part from parent
node.children.splice(idx, 1)
return true
} else {
node = node.children[idx]
}
}
return false
}, },
/** /**
@ -627,6 +598,21 @@ const useConnectionStore = defineStore('connections', {
return keySeparator return keySeparator
}, },
/**
* get node map
* @param connName
* @param db
* @returns {Map<string, DatabaseItem>}
* @private
*/
_getNodeMap(connName, db) {
if (this.nodeMap[`${connName}#${db}`] == null) {
this.nodeMap[`${connName}#${db}`] = new Map()
}
// construct a tree node list, the format of item key likes 'server/db#type/key'
return this.nodeMap[`${connName}#${db}`]
},
/** /**
* remove keys in db * remove keys in db
* @param {string} connName * @param {string} connName
@ -635,34 +621,32 @@ const useConnectionStore = defineStore('connections', {
* @private * @private
*/ */
_addKeyNodes(connName, db, keys) { _addKeyNodes(connName, db, keys) {
if (isEmpty(keys)) {
return
}
const separator = this._getSeparator(connName) const separator = this._getSeparator(connName)
const dbs = this.databases[connName] const dbs = this.databases[connName]
if (dbs[db].children == null) { if (dbs[db].children == null) {
dbs[db].children = [] dbs[db].children = []
} }
if (this.nodeMap[`${connName}#${db}`] == null) { const nodeMap = this._getNodeMap(connName, db)
this.nodeMap[`${connName}#${db}`] = new Map()
}
// construct a tree node list, the format of item key likes 'server/db#type/key'
const nodeMap = this.nodeMap[`${connName}#${db}`]
const rootChildren = dbs[db].children const rootChildren = dbs[db].children
for (const key of keys) { for (const key of keys) {
const keyPart = split(key, separator) const keyParts = split(key, separator)
// const prefixLen = size(keyPart) - 1 const len = size(keyParts)
const len = size(keyPart)
const lastIdx = len - 1 const lastIdx = len - 1
let handlePath = '' let handlePath = ''
let children = rootChildren let children = rootChildren
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
handlePath += keyPart[i] handlePath += keyParts[i]
if (i !== lastIdx) { if (i !== lastIdx) {
// layer // layer
const nodeKey = `#${ConnectionType.RedisKey}/${handlePath}` const nodeKey = `${ConnectionType.RedisKey}/${handlePath}`
let selectedNode = nodeMap.get(nodeKey) let selectedNode = nodeMap.get(nodeKey)
if (selectedNode == null) { if (selectedNode == null) {
selectedNode = { selectedNode = {
key: `${connName}/db${db}${nodeKey}`, key: `${connName}/db${db}#${nodeKey}`,
label: keyPart[i], label: keyParts[i],
db, db,
keys: 0, keys: 0,
redisKey: handlePath, redisKey: handlePath,
@ -677,11 +661,11 @@ const useConnectionStore = defineStore('connections', {
handlePath += separator handlePath += separator
} else { } else {
// key // key
const nodeKey = `#${ConnectionType.RedisValue}/${handlePath}` const nodeKey = `${ConnectionType.RedisValue}/${handlePath}`
const replaceKey = nodeMap.has(nodeKey) const replaceKey = nodeMap.has(nodeKey)
const selectedNode = { const selectedNode = {
key: `${connName}/db${db}${nodeKey}`, key: `${connName}/db${db}#${nodeKey}`,
label: keyPart[i], label: keyParts[i],
db, db,
keys: 0, keys: 0,
redisKey: handlePath, redisKey: handlePath,
@ -712,23 +696,78 @@ const useConnectionStore = defineStore('connections', {
}, },
/** /**
* sort all node item's children and calculate keys count * tidy node by key
* @param node * @param {string} connName
* @param {number} db
* @param {string} [key]
* @param {boolean} [skipResort]
* @private * @private
*/ */
_tidyNodeChildren(node) { _tidyNode(connName, db, key, skipResort) {
const nodeMap = this._getNodeMap(connName, db)
const separator = this._getSeparator(connName)
const keyParts = split(key, separator)
const totalParts = size(keyParts)
const parentKey = slice(keyParts, 0, totalParts - 1)
const dbNode = get(this.databases, [connName, db], {})
const isDBRoot = isEmpty(parentKey)
let node
if (isDBRoot) {
// use db root node
node = dbNode
} else {
node = nodeMap.get(`${ConnectionType.RedisKey}/${join(parentKey, separator)}`)
}
if (node == null) {
return false
}
const keyCountUpdated = this._tidyNodeChildren(node, skipResort)
if (keyCountUpdated) {
// update key count of parent and above
if (!isDBRoot) {
let i = totalParts - 1
for (; i > 0; i--) {
const parentKey = join(slice(keyParts, 0, i), separator)
const parentNode = nodeMap.get(`${ConnectionType.RedisKey}/${parentKey}`)
if (parentNode == null) {
break
}
parentNode.keys = sumBy(parentNode.children, 'keys')
}
}
// update key count of db
dbNode.keys = sumBy(dbNode.children, 'keys')
}
return true
},
/**
* sort all node item's children and calculate keys count
* @param node
* @param {boolean} skipSort skip sorting children
* @returns {boolean} return whether key count changed
* @private
*/
_tidyNodeChildren(node, skipSort) {
let count = 0 let count = 0
if (!isEmpty(node.children)) { if (!isEmpty(node.children)) {
if (skipSort !== true) {
this._sortNodes(node.children) this._sortNodes(node.children)
}
for (const elem of node.children) { for (const elem of node.children) {
this._tidyNodeChildren(elem) this._tidyNodeChildren(elem, skipSort)
count += elem.keys count += elem.keys
} }
} else { } else {
count += 1 count += 1
} }
if (node.keys !== count) {
node.keys = count node.keys = count
return true
}
return false
}, },
/** /**
@ -743,13 +782,11 @@ const useConnectionStore = defineStore('connections', {
*/ */
async setKey(connName, db, key, keyType, value, ttl) { async setKey(connName, db, key, keyType, value, ttl) {
try { try {
console.log(connName, db, key, keyType, value, ttl)
const { data, success, msg } = await SetKeyValue(connName, db, key, keyType, value, ttl) const { data, success, msg } = await SetKeyValue(connName, db, key, keyType, value, ttl)
if (success) { if (success) {
// update tree view data // update tree view data
// this._addKey(connName, db, key)
this._addKeyNodes(connName, db, [key]) this._addKeyNodes(connName, db, [key])
this._tidyNodeChildren(this.databases[connName][db]) this._tidyNode(connName, db, key)
return { success } return { success }
} else { } else {
return { success, msg } return { success, msg }
@ -1092,61 +1129,86 @@ const useConnectionStore = defineStore('connections', {
* @param {string} connName * @param {string} connName
* @param {number} db * @param {number} db
* @param {string} key * @param {string} key
* @param {boolean} [isLayer]
* @private * @private
*/ */
_deleteKeyNode(connName, db, key) { _deleteKeyNode(connName, db, key, isLayer) {
const dbs = this.databases[connName] const dbRoot = get(this.databases, [connName, db], {})
const dbDetail = get(dbs, db, {})
const separator = this._getSeparator(connName) const separator = this._getSeparator(connName)
if (dbDetail == null) { if (dbRoot == null) {
return return false
} }
const nodeMap = this.nodeMap[`${connName}#${db}`] const nodeMap = this._getNodeMap(connName, db)
if (nodeMap == null) { const keyParts = split(key, separator)
return const totalParts = size(keyParts)
if (isLayer === true) {
this._deleteChildrenKeyNodes(nodeMap, key)
} }
const idx = key.lastIndexOf(separator) // remove from parent in tree node
let parentNode = null const parentKey = slice(keyParts, 0, totalParts - 1)
let parentKey = '' let parentNode
if (idx === -1) { if (isEmpty(parentKey)) {
// root parentNode = dbRoot
parentNode = dbDetail
} else { } else {
parentKey = key.substring(0, idx) parentNode = nodeMap.get(`${ConnectionType.RedisKey}/${join(parentKey, separator)}`)
parentNode = nodeMap.get(`#${ConnectionType.RedisKey}/${parentKey}`)
} }
if (parentNode == null || parentNode.children == null) { // not found parent node
return if (parentNode == null) {
return false
} }
remove(parentNode.children, {
type: isLayer ? ConnectionType.RedisKey : ConnectionType.RedisValue,
redisKey: key,
})
// remove children // check and remove empty layer node
const delIdx = findIndex(parentNode.children, { redisKey: key }) let i = totalParts - 1
if (delIdx !== -1) { for (; i >= 0; i--) {
const childKeys = parentNode.children[delIdx].keys || 1 const anceKey = join(slice(keyParts, 0, i), separator)
parentNode.children.splice(delIdx, 1) if (i > 0) {
parentNode.keys = Math.max(parentNode.keys - childKeys, 0) const anceNode = nodeMap.get(`${ConnectionType.RedisKey}/${anceKey}`)
} if (isEmpty(anceNode.children)) {
nodeMap.delete(`${ConnectionType.RedisKey}/${anceKey}`)
// also remove parent node if no more children
while (isEmpty(parentNode.children)) {
const idx = parentKey.lastIndexOf(separator)
if (idx !== -1) {
parentKey = parentKey.substring(0, idx)
parentNode = nodeMap.get(`#${ConnectionType.RedisKey}/${parentKey}`)
if (parentNode != null) {
parentNode.keys = (parentNode.keys || 1) - 1
parentNode.children = []
}
} else { } else {
// reach root, remove from db // remove last empty layer node from parent
const delIdx = findIndex(dbDetail.children, { redisKey: parentKey }) if (i !== totalParts - 1) {
dbDetail.keys = (dbDetail.keys || 1) - 1 const redisKey = slice(keyParts, 0, i + 1)
dbDetail.children.splice(delIdx, 1) remove(anceNode.children, { type: ConnectionType.RedisKey, redisKey })
}
break break
} }
} else {
// last one, remove from db node
remove(dbRoot.children, { type: ConnectionType.RedisKey, redisKey: keyParts[0] })
}
}
return true
},
/**
* delete node and all it's children from nodeMap
* @param nodeMap
* @param key
* @private
*/
_deleteChildrenKeyNodes(nodeMap, key) {
const mapKey = `${ConnectionType.RedisKey}/${key}`
const node = nodeMap.get(mapKey)
for (const child of node.children || []) {
if (child.type === ConnectionType.RedisValue) {
if (!nodeMap.delete(`${ConnectionType.RedisValue}/${child.redisKey}`)) {
console.warn('delete:', `${ConnectionType.RedisValue}/${child.redisKey}`)
}
} else if (child.type === ConnectionType.RedisKey) {
this._deleteChildrenKeyNodes(nodeMap, child.redisKey)
}
}
if (!nodeMap.delete(mapKey)) {
console.warn('delete map key', mapKey)
} }
}, },
@ -1163,6 +1225,7 @@ const useConnectionStore = defineStore('connections', {
if (success) { if (success) {
// update tree view data // update tree view data
this._deleteKeyNode(connName, db, key) this._deleteKeyNode(connName, db, key)
this._tidyNode(connName, db, key, true)
// set tab content empty // set tab content empty
const tab = useTabStore() const tab = useTabStore()
@ -1202,7 +1265,8 @@ const useConnectionStore = defineStore('connections', {
if (endsWith(prefix, separator)) { if (endsWith(prefix, separator)) {
prefix = prefix.substring(0, prefix.length - 1) prefix = prefix.substring(0, prefix.length - 1)
} }
await this._deleteKeyNode(connName, db, prefix) this._deleteKeyNode(connName, db, prefix, true)
this._tidyNode(connName, db, prefix, true)
return true return true
} }
} finally { } finally {