feat: add icon function buttons to connection tree items

fix: error cause when close non-connected connection
This commit is contained in:
tiny-craft 2023-08-24 20:26:18 +08:00
parent 9fc0cbd40d
commit 484e6a5f6b
10 changed files with 184 additions and 38 deletions

View File

@ -4,5 +4,6 @@
"singleQuote": true, "singleQuote": true,
"semi": false, "semi": false,
"bracketSameLine": true, "bracketSameLine": true,
"endOfLine": "auto" "endOfLine": "auto",
"htmlWhitespaceSensitivity": "ignore"
} }

View File

@ -65,7 +65,6 @@ const valueComponents = {
[types.STREAM]: ContentValueStream, [types.STREAM]: ContentValueStream,
} }
const dialog = useDialog()
const connectionStore = useConnectionStore() const connectionStore = useConnectionStore()
const tabStore = useTabStore() const tabStore = useTabStore()
const tab = computed(() => const tab = computed(() =>

View File

@ -134,7 +134,6 @@ const infoFilter = ref('')
<n-statistic :value="totalKeys"> <n-statistic :value="totalKeys">
<template #label> <template #label>
{{ $t('total_keys') }} {{ $t('total_keys') }}
<n-icon :component="Help" />
</template> </template>
</n-statistic> </n-statistic>
</n-gi> </n-gi>

View File

@ -1,7 +1,7 @@
<script setup> <script setup>
import { computed, h, nextTick, onMounted, reactive, ref } from 'vue' import { computed, h, nextTick, onMounted, reactive, ref } from 'vue'
import { ConnectionType } from '@/consts/connection_type.js' import { ConnectionType } from '@/consts/connection_type.js'
import { NIcon, NSpace, NTag, useDialog } from 'naive-ui' import { NIcon, NSpace, NTag } from 'naive-ui'
import Key from '@/components/icons/Key.vue' import Key from '@/components/icons/Key.vue'
import ToggleDb from '@/components/icons/ToggleDb.vue' import ToggleDb from '@/components/icons/ToggleDb.vue'
import { find, get, includes, indexOf, isEmpty, remove } from 'lodash' import { find, get, includes, indexOf, isEmpty, remove } from 'lodash'
@ -22,6 +22,7 @@ import Close from '@/components/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 '@/components/common/IconButton.vue' import IconButton from '@/components/common/IconButton.vue'
import { parseHexColor } from '@/utils/rgb.js'
const props = defineProps({ const props = defineProps({
server: String, server: String,
@ -61,13 +62,7 @@ const data = computed(() => {
const backgroundColor = computed(() => { const backgroundColor = computed(() => {
const { markColor: hex = '' } = connectionStore.serverProfile[props.server] || {} const { markColor: hex = '' } = connectionStore.serverProfile[props.server] || {}
if (isEmpty(hex)) { const { r, g, b } = parseHexColor(hex)
return ''
}
const bigint = parseInt(hex.slice(1), 16)
const r = (bigint >> 16) & 255
const g = (bigint >> 8) & 255
const b = bigint & 255
return `rgba(${r}, ${g}, ${b}, 0.2)` return `rgba(${r}, ${g}, ${b}, 0.2)`
}) })
@ -292,7 +287,6 @@ defineExpose({
handleSelectContextMenu, handleSelectContextMenu,
}) })
const dialog = useDialog()
const onUpdateExpanded = (value, option, meta) => { const onUpdateExpanded = (value, option, meta) => {
expandedKeys.value = value expandedKeys.value = value
if (!meta.node) { if (!meta.node) {

View File

@ -2,11 +2,11 @@
import useDialogStore from 'stores/dialog.js' import useDialogStore from 'stores/dialog.js'
import { h, nextTick, reactive, ref } from 'vue' import { h, nextTick, reactive, ref } from 'vue'
import useConnectionStore from 'stores/connections.js' import useConnectionStore from 'stores/connections.js'
import { NIcon, useDialog, useThemeVars } from 'naive-ui' import { NIcon, NSpace, NText, useThemeVars } from 'naive-ui'
import { ConnectionType } from '@/consts/connection_type.js' import { ConnectionType } from '@/consts/connection_type.js'
import ToggleFolder from '@/components/icons/ToggleFolder.vue' import ToggleFolder from '@/components/icons/ToggleFolder.vue'
import ToggleServer from '@/components/icons/ToggleServer.vue' import ToggleServer from '@/components/icons/ToggleServer.vue'
import { debounce, indexOf, isEmpty } from 'lodash' import { debounce, get, includes, indexOf, isEmpty, split } from 'lodash'
import Config from '@/components/icons/Config.vue' import Config from '@/components/icons/Config.vue'
import Delete from '@/components/icons/Delete.vue' import Delete from '@/components/icons/Delete.vue'
import Unlink from '@/components/icons/Unlink.vue' import Unlink from '@/components/icons/Unlink.vue'
@ -15,6 +15,8 @@ import Connect from '@/components/icons/Connect.vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import useTabStore from 'stores/tab.js' import useTabStore from 'stores/tab.js'
import Edit from '@/components/icons/Edit.vue' import Edit from '@/components/icons/Edit.vue'
import { hexGammaCorrection, parseHexColor, toHexColor } from '@/utils/rgb.js'
import IconButton from '@/components/common/IconButton.vue'
const themeVars = useThemeVars() const themeVars = useThemeVars()
const i18n = useI18n() const i18n = useI18n()
@ -116,9 +118,41 @@ const menuOptions = {
} }
const renderLabel = ({ option }) => { const renderLabel = ({ option }) => {
if (option.type === ConnectionType.Server) {
const { markColor = '' } = connectionStore.serverProfile[option.name] || {}
if (!isEmpty(markColor)) {
const rgb = parseHexColor(markColor)
const rgb2 = hexGammaCorrection(rgb, 0.75)
return h(
NText,
{
style: {
color: toHexColor(rgb2),
},
},
() => option.label,
)
}
}
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 renderPrefix = ({ option }) => { const renderPrefix = ({ option }) => {
switch (option.type) { switch (option.type) {
case ConnectionType.Group: case ConnectionType.Group:
@ -142,21 +176,67 @@ const renderPrefix = ({ option }) => {
} }
} }
const getServerMenu = (connected) => {
const btns = []
if (connected) {
btns.push(
h(IconButton, {
tTooltip: 'disconnect',
icon: Unlink,
onClick: () => handleSelectContextMenu('server_close'),
}),
h(IconButton, {
tTooltip: 'edit_conn',
icon: Config,
onClick: () => handleSelectContextMenu('server_edit'),
}),
)
} else {
btns.push(
h(IconButton, {
tTooltip: 'open_connection',
icon: Connect,
onClick: () => handleSelectContextMenu('server_open'),
}),
h(IconButton, {
tTooltip: 'edit_conn',
icon: Config,
onClick: () => handleSelectContextMenu('server_edit'),
}),
h(IconButton, {
tTooltip: 'remove_conn',
icon: Delete,
onClick: () => handleSelectContextMenu('server_remove'),
}),
)
}
return btns
}
const getGroupMenu = () => {
return [
h(IconButton, {
tTooltip: 'edit_conn',
icon: Config,
onClick: () => handleSelectContextMenu('group_rename'),
}),
h(IconButton, {
tTooltip: 'remove_conn',
icon: Delete,
onClick: () => handleSelectContextMenu('group_delete'),
}),
]
}
const renderSuffix = ({ option }) => { const renderSuffix = ({ option }) => {
if (option.type === ConnectionType.Server) { if (includes(selectedKeys.value, option.key)) {
const { markColor = '' } = connectionStore.serverProfile[option.name] || {} switch (option.type) {
if (isEmpty(markColor)) { case ConnectionType.Server:
return '' const connected = connectionStore.isConnected(option.name)
return renderIconMenu(getServerMenu(connected))
case ConnectionType.Group:
return renderIconMenu(getGroupMenu())
} }
return h('div', {
style: {
borderRadius: '50%',
backgroundColor: markColor,
width: '13px',
height: '13px',
border: '2px solid white',
},
})
} }
return null return null
} }
@ -191,7 +271,6 @@ const openConnection = async (name) => {
} }
} }
const dialog = useDialog()
const removeConnection = (name) => { const removeConnection = (name) => {
$dialog.warning(i18n.t('remove_tip', { type: i18n.t('conn_name'), name }), async () => { $dialog.warning(i18n.t('remove_tip', { type: i18n.t('conn_name'), name }), async () => {
connectionStore.deleteConnection(name).then(({ success, msg }) => { connectionStore.deleteConnection(name).then(({ success, msg }) => {
@ -203,7 +282,7 @@ const removeConnection = (name) => {
} }
const removeGroup = async (name) => { const removeGroup = async (name) => {
$dialog.warning(i18n.t('remove_tip', { type: i18n.t('conn_group'), name }), async () => { $dialog.warning(i18n.t('remove_group_tip', { name }), async () => {
connectionStore.deleteGroup(name).then(({ success, msg }) => { connectionStore.deleteGroup(name).then(({ success, msg }) => {
if (!success) { if (!success) {
$message.error(msg) $message.error(msg)
@ -256,7 +335,14 @@ const renderContextLabel = (option) => {
const handleSelectContextMenu = (key) => { const handleSelectContextMenu = (key) => {
contextMenuParam.show = false contextMenuParam.show = false
const { name, label, db, key: nodeKey, redisKey } = contextMenuParam.currentNode const selectedKey = get(selectedKeys.value, 0)
if (selectedKey == null) {
return
}
const [group, name] = split(selectedKey, '/')
if (isEmpty(group) && isEmpty(name)) {
return
}
switch (key) { switch (key) {
case 'server_open': case 'server_open':
openConnection(name).then(() => {}) openConnection(name).then(() => {})
@ -276,13 +362,17 @@ const handleSelectContextMenu = (key) => {
removeConnection(name) removeConnection(name)
break break
case 'server_close': case 'server_close':
connectionStore.closeConnection(name) if (!isEmpty(group)) {
connectionStore.closeConnection(name)
}
break break
case 'group_rename': case 'group_rename':
dialogStore.openRenameGroupDialog(label) if (!isEmpty(group)) {
dialogStore.openRenameGroupDialog(group)
}
break break
case 'group_delete': case 'group_delete':
removeGroup(label) removeGroup(group)
break break
default: default:
console.warn('TODO: handle context menu:' + key) console.warn('TODO: handle context menu:' + key)

View File

@ -18,6 +18,7 @@
"edit_close_confirm": "Please close the relevant connections before editing. Do you want to continue?", "edit_close_confirm": "Please close the relevant connections before editing. Do you want to continue?",
"opening_connection": "Opening Connection...", "opening_connection": "Opening Connection...",
"remove_tip": "{type} \"{name}\" will be deleted", "remove_tip": "{type} \"{name}\" will be deleted",
"remove_group_tip": "Group \"{name}\" and all connections in it will be deleted",
"ttl": "TTL", "ttl": "TTL",
"forever": "Forever", "forever": "Forever",
"rename_key": "Rename Key", "rename_key": "Rename Key",

View File

@ -18,6 +18,7 @@
"edit_close_confirm": "编辑前需要关闭相关连接,是否继续", "edit_close_confirm": "编辑前需要关闭相关连接,是否继续",
"opening_connection": "正在打开连接...", "opening_connection": "正在打开连接...",
"remove_tip": "{type} \"{name}\" 将会被删除", "remove_tip": "{type} \"{name}\" 将会被删除",
"remove_group_tip": "分组 \"{name}\"及其所有连接将会被删除",
"ttl": "TTL", "ttl": "TTL",
"forever": "永久", "forever": "永久",
"rename_key": "重命名键", "rename_key": "重命名键",

View File

@ -124,7 +124,7 @@ const useConnectionStore = defineStore('connections', {
if (conn.type !== 'group') { if (conn.type !== 'group') {
// top level // top level
conns.push({ conns.push({
key: conn.name, key: '/' + conn.name,
label: conn.name, label: conn.name,
name: conn.name, name: conn.name,
type: ConnectionType.Server, type: ConnectionType.Server,
@ -151,7 +151,7 @@ const useConnectionStore = defineStore('connections', {
}) })
} }
conns.push({ conns.push({
key: conn.name, key: conn.name + '/',
label: conn.name, label: conn.name,
type: ConnectionType.Group, type: ConnectionType.Group,
children, children,
@ -346,9 +346,11 @@ const useConnectionStore = defineStore('connections', {
} }
const dbs = this.databases[name] const dbs = this.databases[name]
for (const db of dbs) { if (!isEmpty(dbs)) {
this.removeKeyFilter(name, db.db) for (const db of dbs) {
this._getNodeMap(name, db.db).clear() this.removeKeyFilter(name, db.db)
this._getNodeMap(name, db.db).clear()
}
} }
this.removeKeyFilter(name, -1) this.removeKeyFilter(name, -1)
delete this.databases[name] delete this.databases[name]

View File

@ -76,6 +76,7 @@ const useDialogStore = defineStore('dialog', {
}, },
openNewGroupDialog() { openNewGroupDialog() {
this.editGroup = ''
this.groupDialogVisible = true this.groupDialogVisible = true
}, },
closeNewGroupDialog() { closeNewGroupDialog() {

58
frontend/src/utils/rgb.js Normal file
View File

@ -0,0 +1,58 @@
import { padStart, size, startsWith } from 'lodash'
/**
* @typedef {Object} RGB
* @property {number} r
* @property {number} g
* @property {number} b
*/
/**
* parse hex color to rgb object
* @param hex
* @return {RGB}
*/
export function parseHexColor(hex) {
if (size(hex) < 6) {
return { r: 0, g: 0, b: 0 }
}
if (startsWith(hex, '#')) {
hex = hex.slice(1)
}
const bigint = parseInt(hex, 16)
const r = (bigint >> 16) & 255
const g = (bigint >> 8) & 255
const b = bigint & 255
return { r, g, b }
}
/**
* do gamma correction with an RGB object
* @param {RGB} rgb
* @param {Number} gamma
* @return {RGB}
*/
export function hexGammaCorrection(rgb, gamma) {
if (typeof rgb !== 'object') {
return { r: 0, g: 0, b: 0 }
}
return {
r: Math.max(0, Math.min(255, Math.round(rgb.r * gamma))),
g: Math.max(0, Math.min(255, Math.round(rgb.g * gamma))),
b: Math.max(0, Math.min(255, Math.round(rgb.b * gamma))),
}
}
/**
* RGB object to hex color string
* @param {RGB} rgb
* @return {string}
*/
export function toHexColor(rgb) {
return (
'#' +
padStart(rgb.r.toString(16), 2, '0') +
padStart(rgb.g.toString(16), 2, '0') +
padStart(rgb.b.toString(16), 2, '0')
)
}