tiny-rdm/frontend/src/components/sidebar/ConnectionTree.vue

281 lines
7.9 KiB
Vue
Raw Normal View History

<script setup>
import useDialogStore from '../../stores/dialog.js'
import { h, nextTick, onMounted, reactive, ref } from 'vue'
import useConnectionStore from '../../stores/connections.js'
import { NIcon, useMessage } from 'naive-ui'
import { ConnectionType } from '../../consts/connection_type.js'
import ToggleFolder from '../icons/ToggleFolder.vue'
import ToggleServer from '../icons/ToggleServer.vue'
import { indexOf } from 'lodash'
import Config from '../icons/Config.vue'
import Delete from '../icons/Delete.vue'
import Unlink from '../icons/Unlink.vue'
import CopyLink from '../icons/CopyLink.vue'
import Connect from '../icons/Connect.vue'
import { useI18n } from 'vue-i18n'
import useTabStore from '../../stores/tab.js'
import Edit from '../icons/Edit.vue'
const i18n = useI18n()
const loadingConnection = ref(false)
const openingConnection = ref(false)
const connectionStore = useConnectionStore()
const tabStore = useTabStore()
const dialogStore = useDialogStore()
const message = useMessage()
const expandedKeys = ref([])
const selectedKeys = ref([])
onMounted(async () => {
try {
loadingConnection.value = true
await nextTick()
await connectionStore.initConnections()
} finally {
loadingConnection.value = false
}
})
const contextMenuParam = reactive({
show: false,
x: 0,
y: 0,
options: null,
currentNode: null,
})
const renderIcon = (icon) => {
return () => {
return h(NIcon, null, {
default: () => h(icon),
})
}
}
const menuOptions = {
[ConnectionType.Group]: ({ opened }) => [
{
key: 'group_reload',
label: i18n.t('edit_conn_group'),
icon: renderIcon(Config),
},
{
key: 'group_delete',
label: i18n.t('remove_conn_group'),
icon: renderIcon(Delete),
},
],
[ConnectionType.Server]: ({ name }) => {
const connected = connectionStore.isConnected(name)
if (connected) {
return [
{
key: 'server_close',
label: i18n.t('disconnect'),
icon: renderIcon(Unlink),
},
{
key: 'server_dup',
label: i18n.t('dup_conn'),
icon: renderIcon(CopyLink),
},
{
key: 'server_config',
label: i18n.t('edit_conn'),
icon: renderIcon(Config),
},
{
type: 'divider',
key: 'd1',
},
{
key: 'server_remove',
label: i18n.t('remove_conn'),
icon: renderIcon(Delete),
},
]
} else {
return [
{
key: 'server_open',
label: i18n.t('open_connection'),
icon: renderIcon(Connect),
},
{
key: 'server_edit',
label: i18n.t('edit_conn'),
icon: renderIcon(Edit),
},
{
type: 'divider',
key: 'd1',
},
{
key: 'server_delete',
label: i18n.t('remove_conn'),
icon: renderIcon(Delete),
},
]
}
},
}
const renderLabel = ({ option }) => {
// switch (option.type) {
// case ConnectionType.Server:
// return h(ConnectionTreeItem, { title: option.label })
// }
return option.label
}
const renderPrefix = ({ option }) => {
switch (option.type) {
case ConnectionType.Group:
const opened = indexOf(expandedKeys.value, option.key) !== -1
return h(
NIcon,
{ size: 20 },
{
default: () => h(ToggleFolder, { modelValue: opened }),
}
)
case ConnectionType.Server:
const connected = connectionStore.isConnected(option.name)
return h(
NIcon,
{ size: 20 },
{
default: () => h(ToggleServer, { modelValue: !!connected }),
}
)
}
}
const onUpdateExpandedKeys = (keys, option, meta) => {
expandedKeys.value = keys
}
const onUpdateSelectedKeys = (keys, option, meta) => {
selectedKeys.value = keys
}
/**
* Open connection
* @param name
* @returns {Promise<void>}
*/
const openConnection = async (name) => {
try {
if (!connectionStore.isConnected(name)) {
openingConnection.value = true
await connectionStore.openConnection(name)
}
tabStore.upsertTab({
server: nam,
})
} catch (e) {
message.error(e.message)
// node.isLeaf = undefined
} finally {
openingConnection.value = false
}
}
const nodeProps = ({ option }) => {
return {
onDblclick: async () => {
if (option.type === ConnectionType.Server) {
openConnection(option.name).then(() => {})
}
},
onContextmenu(e) {
e.preventDefault()
const mop = menuOptions[option.type]
if (mop == null) {
return
}
contextMenuParam.show = false
nextTick().then(() => {
contextMenuParam.options = mop(option)
contextMenuParam.currentNode = option
contextMenuParam.x = e.clientX
contextMenuParam.y = e.clientY
contextMenuParam.show = true
selectedKeys.value = [option.key]
})
},
}
}
const renderContextLabel = (option) => {
return h('div', { class: 'context-menu-item' }, option.label)
}
const handleSelectContextMenu = (key) => {
contextMenuParam.show = false
const { name, db, key: nodeKey, redisKey } = contextMenuParam.currentNode
switch (key) {
case 'server_open':
openConnection(name).then(() => {})
break
case 'server_close':
connectionStore.closeConnection(name)
break
}
console.warn('TODO: handle context menu:' + key)
}
</script>
<template>
<n-tree
:animated="false"
:block-line="true"
:block-node="true"
:cancelable="false"
:data="connectionStore.connections"
:expand-on-click="true"
:expanded-keys="expandedKeys"
:on-update:selected-keys="onUpdateSelectedKeys"
:node-props="nodeProps"
:on-update:expanded-keys="onUpdateExpandedKeys"
:selected-keys="selectedKeys"
:render-label="renderLabel"
:render-prefix="renderPrefix"
class="fill-height"
virtual-scroll
/>
<!-- status display modal -->
<n-modal :show="loadingConnection || openingConnection" transform-origin="center">
<n-card
:bordered="false"
:content-style="{ textAlign: 'center' }"
aria-model="true"
role="dialog"
style="width: 400px"
>
<n-spin>
<template #description>
{{ openingConnection ? $t('opening_connection') : '' }}
</template>
</n-spin>
</n-card>
</n-modal>
<!-- context menu -->
<n-dropdown
:animated="false"
:options="contextMenuParam.options"
:render-label="renderContextLabel"
:show="contextMenuParam.show"
:x="contextMenuParam.x"
:y="contextMenuParam.y"
placement="bottom-start"
trigger="manual"
@clickoutside="contextMenuParam.show = false"
@select="handleSelectContextMenu"
/>
</template>
<style lang="scss" scoped></style>