feat: add quick function icon button inside item of tree view

This commit is contained in:
tiny-craft 2023-07-31 16:57:35 +08:00
parent 2d0e8f5445
commit 0556ff299e
4 changed files with 181 additions and 65 deletions

View File

@ -32,7 +32,7 @@ const hasTooltip = computed(() => {
<template> <template>
<n-tooltip v-if="hasTooltip"> <n-tooltip v-if="hasTooltip">
<template #trigger> <template #trigger>
<n-button :text="!border" :disabled="disabled" @click="emit('click')"> <n-button :text="!border" :disabled="disabled" @click.prevent="emit('click')">
<n-icon :size="props.size" :color="props.color"> <n-icon :size="props.size" :color="props.color">
<component :is="props.icon" :stroke-width="props.strokeWidth" /> <component :is="props.icon" :stroke-width="props.strokeWidth" />
</n-icon> </n-icon>
@ -40,7 +40,7 @@ const hasTooltip = computed(() => {
</template> </template>
{{ props.tTooltip ? $t(props.tTooltip) : props.tooltip }} {{ props.tTooltip ? $t(props.tTooltip) : props.tooltip }}
</n-tooltip> </n-tooltip>
<n-button v-else :text="!border" :disabled="disabled" @click="emit('click')"> <n-button v-else :text="!border" :disabled="disabled" @click.prevent="emit('click')">
<n-icon :size="props.size" :color="props.color"> <n-icon :size="props.size" :color="props.color">
<component :is="props.icon" :stroke-width="props.strokeWidth" /> <component :is="props.icon" :stroke-width="props.strokeWidth" />
</n-icon> </n-icon>

View File

@ -80,8 +80,8 @@ const infoFilter = ref('')
<n-space vertical> <n-space vertical>
<n-card> <n-card>
<template #header> <template #header>
{{ props.server }} <n-space align="center" :wrap-item="false" inline size="small">
<n-space inline size="small"> {{ props.server }}
<n-tooltip v-if="redisVersion"> <n-tooltip v-if="redisVersion">
Redis Version Redis Version
<template #trigger> <template #trigger>

View File

@ -4,7 +4,7 @@ import { ConnectionType } from '../../consts/connection_type.js'
import { NIcon, NSpace, NTag, useDialog, useMessage } from 'naive-ui' import { NIcon, NSpace, NTag, useDialog, useMessage } from 'naive-ui'
import Key from '../icons/Key.vue' import Key from '../icons/Key.vue'
import ToggleDb from '../icons/ToggleDb.vue' import ToggleDb from '../icons/ToggleDb.vue'
import { find, get, indexOf, isEmpty, remove } from 'lodash' import { find, get, includes, indexOf, isEmpty, remove } from 'lodash'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import Refresh from '../icons/Refresh.vue' import Refresh from '../icons/Refresh.vue'
import CopyLink from '../icons/CopyLink.vue' import CopyLink from '../icons/CopyLink.vue'
@ -22,6 +22,7 @@ import Filter from '../icons/Filter.vue'
import Close from '../icons/Close.vue' import Close from '../icons/Close.vue'
import { typesBgColor, typesColor } from '../../consts/support_redis_type.js' import { typesBgColor, typesColor } from '../../consts/support_redis_type.js'
import useTabStore from '../../stores/tab.js' import useTabStore from '../../stores/tab.js'
import IconButton from '../common/IconButton.vue'
const props = defineProps({ const props = defineProps({
server: String, server: String,
@ -51,7 +52,7 @@ const data = computed(() => {
const dbs = get(connectionStore.databases, props.server, []) const dbs = get(connectionStore.databases, props.server, [])
return [ return [
{ {
key: props.server, key: `${props.server}`,
label: props.server, label: props.server,
type: ConnectionType.Server, type: ConnectionType.Server,
children: dbs, children: dbs,
@ -76,7 +77,6 @@ const contextMenuParam = reactive({
x: 0, x: 0,
y: 0, y: 0,
options: null, options: null,
currentNode: null,
}) })
const renderIcon = (icon) => { const renderIcon = (icon) => {
return () => { return () => {
@ -87,7 +87,6 @@ const renderIcon = (icon) => {
} }
const menuOptions = { const menuOptions = {
[ConnectionType.Server]: () => { [ConnectionType.Server]: () => {
console.log('open server context')
return [ return [
{ {
key: 'server_reload', key: 'server_reload',
@ -121,7 +120,7 @@ const menuOptions = {
}, },
{ {
type: 'divider', type: 'divider',
key: 'd2', key: 'd1',
}, },
{ {
key: 'key_remove', key: 'key_remove',
@ -130,7 +129,7 @@ const menuOptions = {
}, },
{ {
type: 'divider', type: 'divider',
key: 'd1', key: 'd2',
}, },
{ {
key: 'db_close', key: 'db_close',
@ -244,7 +243,7 @@ const onUpdateSelectedKeys = (keys, options) => {
for (const node of options) { for (const node of options) {
if (node.type === ConnectionType.RedisValue) { if (node.type === ConnectionType.RedisValue) {
const { key, db, redisKey } = node const { key, db, redisKey } = node
if (indexOf(selectedKeys.value, key) === -1) { if (!includes(selectedKeys.value, key)) {
connectionStore.loadKeyValue(props.server, db, redisKey) connectionStore.loadKeyValue(props.server, db, redisKey)
} }
return return
@ -296,9 +295,57 @@ const renderPrefix = ({ option }) => {
} }
} }
// render tree item label
const renderLabel = ({ option }) => { const renderLabel = ({ option }) => {
switch (option.type) { switch (option.type) {
case ConnectionType.RedisDB: case ConnectionType.RedisDB:
const { name: server, db } = option
let { match: matchPattern, type: typeFilter } = connectionStore.getKeyFilter(server, db)
const items = [`${option.label} (${option.keys || 0})`]
// show filter tag after label
// type filter tag
if (!isEmpty(typeFilter)) {
items.push(
h(
NTag,
{
size: 'small',
closable: true,
bordered: false,
color: {
color: typesBgColor[typeFilter],
textColor: typesColor[typeFilter],
},
onClose: () => {
// remove type filter
connectionStore.setKeyFilter(server, db, matchPattern)
connectionStore.reopenDatabase(server, db)
},
},
{ default: () => typeFilter },
),
)
}
// match pattern tag
if (!isEmpty(matchPattern) && matchPattern !== '*') {
items.push(
h(
NTag,
{
bordered: false,
closable: true,
size: 'small',
onClose: () => {
// remove key match pattern
connectionStore.setKeyFilter(server, db, '*', typeFilter)
connectionStore.reopenDatabase(server, db)
},
},
{ default: () => matchPattern },
),
)
}
return renderIconMenu(items)
case ConnectionType.RedisKey: case ConnectionType.RedisKey:
return `${option.label} (${option.keys || 0})` return `${option.label} (${option.keys || 0})`
// case ConnectionType.RedisValue: // case ConnectionType.RedisValue:
@ -307,55 +354,99 @@ const renderLabel = ({ option }) => {
return option.label return option.label
} }
// render horizontal item
const renderIconMenu = (items) => {
return h(
NSpace,
{
align: 'center',
inline: true,
size: 2,
wrapItem: false,
wrap: false,
style: 'margin-right: 5px',
},
() => items,
)
}
const getDatabaseMenu = (opened) => {
const btns = []
if (opened) {
btns.push(
h(IconButton, {
tTooltip: 'filter_key',
icon: Filter,
onClick: () => handleSelectContextMenu('db_filter'),
}),
h(IconButton, {
tTooltip: 'reload',
icon: Refresh,
onClick: () => handleSelectContextMenu('db_reload'),
}),
h(IconButton, {
tTooltip: 'new_key',
icon: Add,
onClick: () => handleSelectContextMenu('db_newkey'),
}),
h(IconButton, {
tTooltip: 'batch_delete',
icon: Delete,
onClick: () => handleSelectContextMenu('key_remove'),
}),
)
} else {
btns.push(
h(IconButton, {
tTooltip: 'open_db',
icon: Connect,
onClick: () => handleSelectContextMenu('db_open'),
}),
)
}
return btns
}
const getLayerMenu = () => {
return [
h(IconButton, {
tTooltip: 'reload',
icon: Refresh,
onClick: () => handleSelectContextMenu('key_reload'),
}),
h(IconButton, {
tTooltip: 'new_key',
icon: Add,
onClick: () => handleSelectContextMenu('key_newkey'),
}),
h(IconButton, {
tTooltip: 'batch_delete',
icon: Delete,
onClick: () => handleSelectContextMenu('key_remove'),
}),
]
}
const getValueMenu = () => {
return [
h(IconButton, {
tTooltip: 'remove_key',
icon: Delete,
onClick: () => handleSelectContextMenu('value_remove'),
}),
]
}
// render menu function icon
const renderSuffix = ({ option }) => { const renderSuffix = ({ option }) => {
if (option.type === ConnectionType.RedisDB) { if (includes(selectedKeys.value, option.key)) {
const { name: server, db } = option switch (option.type) {
let { match: matchPattern, type: typeFilter } = connectionStore.getKeyFilter(server, db) case ConnectionType.RedisDB:
const filterNodes = [] return renderIconMenu(getDatabaseMenu(option.opened))
// type filter tag case ConnectionType.RedisKey:
if (!isEmpty(typeFilter)) { return renderIconMenu(getLayerMenu())
filterNodes.push( case ConnectionType.RedisValue:
h( return renderIconMenu(getValueMenu())
NTag,
{
size: 'small',
closable: true,
bordered: false,
color: {
color: typesBgColor[typeFilter],
textColor: typesColor[typeFilter],
},
onClose: () => {
// remove type filter
connectionStore.setKeyFilter(server, db, matchPattern)
connectionStore.reopenDatabase(server, db)
},
},
{ default: () => typeFilter },
),
)
}
// match pattern tag
if (!isEmpty(matchPattern) && matchPattern !== '*') {
filterNodes.push(
h(
NTag,
{
bordered: false,
closable: true,
size: 'small',
onClose: () => {
// remove key match pattern
connectionStore.setKeyFilter(server, db, '*', typeFilter)
connectionStore.reopenDatabase(server, db)
},
},
{ default: () => matchPattern },
),
)
}
if (filterNodes.length > 0) {
return h(NSpace, { align: 'center', inline: true, size: 2 }, () => filterNodes)
} }
} }
return null return null
@ -363,7 +454,7 @@ const renderSuffix = ({ option }) => {
const nodeProps = ({ option }) => { const nodeProps = ({ option }) => {
return { return {
onDblclick: async () => { onDblclick: () => {
if (loading.value) { if (loading.value) {
console.warn('TODO: alert to ignore double click when loading') console.warn('TODO: alert to ignore double click when loading')
return return
@ -373,14 +464,12 @@ const nodeProps = ({ option }) => {
}, },
onContextmenu(e) { onContextmenu(e) {
e.preventDefault() e.preventDefault()
const mop = menuOptions[option.type] if (!menuOptions.hasOwnProperty(option.type)) {
if (mop == null) {
return return
} }
contextMenuParam.show = false contextMenuParam.show = false
contextMenuParam.options = menuOptions[option.type](option)
nextTick().then(() => { nextTick().then(() => {
contextMenuParam.options = mop(option)
contextMenuParam.currentNode = option
contextMenuParam.x = e.clientX contextMenuParam.x = e.clientX
contextMenuParam.y = e.clientY contextMenuParam.y = e.clientY
contextMenuParam.show = true contextMenuParam.show = true
@ -419,7 +508,12 @@ const onLoadTree = async (node) => {
const confirmDialog = useConfirmDialog() const confirmDialog = useConfirmDialog()
const handleSelectContextMenu = (key) => { const handleSelectContextMenu = (key) => {
contextMenuParam.show = false contextMenuParam.show = false
const { db, key: nodeKey, redisKey } = contextMenuParam.currentNode const selectedKey = get(selectedKeys.value, 0)
if (selectedKey == null) {
return
}
const node = connectionStore.getNode(selectedKey)
const { db, key: nodeKey, redisKey } = node || {}
switch (key) { switch (key) {
case 'server_reload': case 'server_reload':
connectionStore.openConnection(props.server, true).then(() => { connectionStore.openConnection(props.server, true).then(() => {

View File

@ -797,6 +797,28 @@ const useConnectionStore = defineStore('connections', {
return false return false
}, },
/**
* get tree node by key name
* @param key
* @return {DatabaseItem|null}
*/
getNode(key) {
const matches = key.match(/^(?<server>\w+)(?:\/db(?<db>\d+))?(?:#(?<key>[\w/]+))?$/)
if (matches) {
const { server, db, key } = matches.groups
if (db != null) {
const dbIndex = parseInt(db)
const nodeMap = this._getNodeMap(server, dbIndex)
if (key != null) {
return nodeMap.get(key)
} else {
return this.databases[server][dbIndex]
}
}
}
return null
},
/** /**
* set redis key * set redis key
* @param {string} connName * @param {string} connName