Refactor server and database save structure, support multiple server detail tab
This commit is contained in:
parent
79784fd109
commit
dadde8d090
|
@ -1,21 +1,25 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import ContentPane from './components/ContentPane.vue'
|
import ContentPane from './components/content/ContentPane.vue'
|
||||||
import NavigationPane from './components/NavigationPane.vue'
|
import DatabasePane from './components/sidebar/DatabasePane.vue'
|
||||||
import { computed, nextTick, onMounted, provide, reactive, ref } from 'vue'
|
import { computed, nextTick, onMounted, provide, reactive, ref } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { GetPreferences } from '../wailsjs/go/storage/PreferencesStorage.js'
|
import { GetPreferences } from '../wailsjs/go/storage/PreferencesStorage.js'
|
||||||
import { get } from 'lodash'
|
import { get } from 'lodash'
|
||||||
import { useThemeVars } from 'naive-ui'
|
import { useThemeVars } from 'naive-ui'
|
||||||
import NavMenu from './components/NavMenu.vue'
|
import NavMenu from './components/NavMenu.vue'
|
||||||
|
import ConnectionPane from './components/sidebar/ConnectionPane.vue'
|
||||||
|
import ContentServerPane from './components/content/ContentServerPane.vue'
|
||||||
|
import useTabStore from './stores/tab.js'
|
||||||
|
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
|
|
||||||
const data = reactive({
|
const data = reactive({
|
||||||
asideWith: 300,
|
navMenuWidth: 60,
|
||||||
hoverResize: false,
|
hoverResize: false,
|
||||||
resizing: false,
|
resizing: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const tabStore = useTabStore()
|
||||||
const preferences = ref({})
|
const preferences = ref({})
|
||||||
provide('preferences', preferences)
|
provide('preferences', preferences)
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
|
@ -34,7 +38,7 @@ const getFontSize = computed(() => {
|
||||||
|
|
||||||
const handleResize = (evt) => {
|
const handleResize = (evt) => {
|
||||||
if (data.resizing) {
|
if (data.resizing) {
|
||||||
data.asideWith = Math.max(evt.clientX, 300)
|
tabStore.asideWidth = Math.max(evt.clientX - data.navMenuWidth, 300)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +56,7 @@ const startResize = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const asideWidthVal = computed(() => {
|
const asideWidthVal = computed(() => {
|
||||||
return data.asideWith + 'px'
|
return tabStore.asideWidth + 'px'
|
||||||
})
|
})
|
||||||
|
|
||||||
const dragging = computed(() => {
|
const dragging = computed(() => {
|
||||||
|
@ -62,22 +66,51 @@ const dragging = computed(() => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- app content-->
|
<!-- app content-->
|
||||||
<div id="app-container" :class="{ dragging: dragging }" class="flex-box-h">
|
<div id="app-container" :class="{ dragging }" class="flex-box-h">
|
||||||
<nav-menu />
|
<nav-menu v-model:value="tabStore.nav" :width="data.navMenuWidth" />
|
||||||
<div id="app-side" :style="{ width: asideWidthVal }" class="flex-box-h flex-item">
|
<!-- structure page-->
|
||||||
<navigation-pane class="flex-item-expand"></navigation-pane>
|
<div v-show="tabStore.nav === 'structure'" class="flex-box-h flex-item-expand">
|
||||||
<div
|
<div id="app-side" :style="{ width: asideWidthVal }" class="flex-box-h flex-item">
|
||||||
:class="{
|
<database-pane
|
||||||
'resize-divider-hover': data.hoverResize,
|
v-for="t in tabStore.tabs"
|
||||||
'resize-divider-drag': data.resizing,
|
v-show="get(tabStore.currentTab, 'name') === t.name"
|
||||||
}"
|
:key="t.name"
|
||||||
class="resize-divider"
|
class="flex-item-expand"
|
||||||
@mousedown="startResize"
|
/>
|
||||||
@mouseout="data.hoverResize = false"
|
<div
|
||||||
@mouseover="data.hoverResize = true"
|
:class="{
|
||||||
></div>
|
'resize-divider-hover': data.hoverResize,
|
||||||
|
'resize-divider-drag': data.resizing,
|
||||||
|
}"
|
||||||
|
class="resize-divider"
|
||||||
|
@mousedown="startResize"
|
||||||
|
@mouseout="data.hoverResize = false"
|
||||||
|
@mouseover="data.hoverResize = true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<content-pane class="flex-item-expand" />
|
||||||
</div>
|
</div>
|
||||||
<content-pane class="flex-item-expand" />
|
|
||||||
|
<!-- server list page -->
|
||||||
|
<div v-show="tabStore.nav === 'server'" class="flex-box-h flex-item-expand">
|
||||||
|
<div id="app-side" :style="{ width: asideWidthVal }" class="flex-box-h flex-item">
|
||||||
|
<connection-pane class="flex-item-expand" />
|
||||||
|
<div
|
||||||
|
:class="{
|
||||||
|
'resize-divider-hover': data.hoverResize,
|
||||||
|
'resize-divider-drag': data.resizing,
|
||||||
|
}"
|
||||||
|
class="resize-divider"
|
||||||
|
@mousedown="startResize"
|
||||||
|
@mouseout="data.hoverResize = false"
|
||||||
|
@mouseover="data.hoverResize = true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<content-server-pane class="flex-item-expand" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- log page -->
|
||||||
|
<div v-show="tabStore.nav === 'log'">display log</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,11 @@ import Delete from './icons/Delete.vue'
|
||||||
import Edit from './icons/Edit.vue'
|
import Edit from './icons/Edit.vue'
|
||||||
import Refresh from './icons/Refresh.vue'
|
import Refresh from './icons/Refresh.vue'
|
||||||
import Timer from './icons/Timer.vue'
|
import Timer from './icons/Timer.vue'
|
||||||
import RedisTypeTag from './RedisTypeTag.vue'
|
import RedisTypeTag from './common/RedisTypeTag.vue'
|
||||||
import useConnectionStore from '../stores/connection.js'
|
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useMessage } from 'naive-ui'
|
import { useMessage } from 'naive-ui'
|
||||||
import IconButton from './IconButton.vue'
|
import IconButton from './common/IconButton.vue'
|
||||||
|
import useConnectionStore from '../stores/connections.js'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
server: String,
|
server: String,
|
||||||
|
|
|
@ -1,23 +1,38 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, h, ref } from 'vue'
|
import { computed, h } from 'vue'
|
||||||
import { NIcon, useThemeVars } from 'naive-ui'
|
import { NIcon, useThemeVars } from 'naive-ui'
|
||||||
import ToggleDb from './icons/ToggleDb.vue'
|
import ToggleDb from './icons/ToggleDb.vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import ToggleServer from './icons/ToggleServer.vue'
|
import ToggleServer from './icons/ToggleServer.vue'
|
||||||
import IconButton from './IconButton.vue'
|
import IconButton from './common/IconButton.vue'
|
||||||
import Config from './icons/Config.vue'
|
import Config from './icons/Config.vue'
|
||||||
import useDialogStore from '../stores/dialog.js'
|
import useDialogStore from '../stores/dialog.js'
|
||||||
import Github from './icons/Github.vue'
|
import Github from './icons/Github.vue'
|
||||||
import { BrowserOpenURL } from '../../wailsjs/runtime/runtime.js'
|
import { BrowserOpenURL } from '../../wailsjs/runtime/runtime.js'
|
||||||
|
import Log from './icons/Log.vue'
|
||||||
|
import useConnectionStore from '../stores/connections.js'
|
||||||
|
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
|
|
||||||
const iconSize = 26
|
const props = defineProps({
|
||||||
const selectedMenu = ref('server')
|
value: {
|
||||||
|
type: String,
|
||||||
|
default: 'server',
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: Number,
|
||||||
|
default: 60,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:value'])
|
||||||
|
|
||||||
|
const iconSize = computed(() => Math.floor(props.width * 0.4))
|
||||||
const renderIcon = (icon) => {
|
const renderIcon = (icon) => {
|
||||||
return () => h(NIcon, null, { default: () => h(icon) })
|
return () => h(NIcon, null, { default: () => h(icon) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const connectionStore = useConnectionStore()
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const menuOptions = computed(() => {
|
const menuOptions = computed(() => {
|
||||||
return [
|
return [
|
||||||
|
@ -25,13 +40,18 @@ const menuOptions = computed(() => {
|
||||||
label: i18n.t('structure'),
|
label: i18n.t('structure'),
|
||||||
key: 'structure',
|
key: 'structure',
|
||||||
icon: renderIcon(ToggleDb),
|
icon: renderIcon(ToggleDb),
|
||||||
show: true,
|
show: connectionStore.anyConnectionOpened,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: i18n.t('server'),
|
label: i18n.t('server'),
|
||||||
key: 'server',
|
key: 'server',
|
||||||
icon: renderIcon(ToggleServer),
|
icon: renderIcon(ToggleServer),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: i18n.t('log'),
|
||||||
|
key: 'log',
|
||||||
|
icon: renderIcon(Log),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -74,12 +94,19 @@ const openGithub = () => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div id="app-nav-menu" class="flex-box-v">
|
<div
|
||||||
|
id="app-nav-menu"
|
||||||
|
:style="{
|
||||||
|
width: props.width + 'px',
|
||||||
|
}"
|
||||||
|
class="flex-box-v"
|
||||||
|
>
|
||||||
<n-menu
|
<n-menu
|
||||||
v-model:value="selectedMenu"
|
:collapsed-width="props.width"
|
||||||
|
:value="props.value"
|
||||||
:collapsed="true"
|
:collapsed="true"
|
||||||
:collapsed-icon-size="iconSize"
|
:collapsed-icon-size="iconSize"
|
||||||
:collapsed-width="60"
|
@update:value="(val) => emit('update:value', val)"
|
||||||
:options="menuOptions"
|
:options="menuOptions"
|
||||||
></n-menu>
|
></n-menu>
|
||||||
<div class="flex-item-expand"></div>
|
<div class="flex-item-expand"></div>
|
||||||
|
@ -101,7 +128,7 @@ const openGithub = () => {
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
#app-nav-menu {
|
#app-nav-menu {
|
||||||
width: 60px;
|
//width: 60px;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
border-right: var(--border-color) solid 1px;
|
border-right: var(--border-color) solid 1px;
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import IconButton from './IconButton.vue'
|
import IconButton from './IconButton.vue'
|
||||||
import Delete from './icons/Delete.vue'
|
import Delete from '../icons/Delete.vue'
|
||||||
import Edit from './icons/Edit.vue'
|
import Edit from '../icons/Edit.vue'
|
||||||
import Close from './icons/Close.vue'
|
import Close from '../icons/Close.vue'
|
||||||
import Save from './icons/Save.vue'
|
import Save from '../icons/Save.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
bindKey: String,
|
bindKey: String,
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { types, validType } from '../consts/support_redis_type.js'
|
import { types, validType } from '../../consts/support_redis_type.js'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
type: {
|
type: {
|
|
@ -1,13 +1,16 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { types } from '../consts/support_redis_type.js'
|
import { types } from '../../consts/support_redis_type.js'
|
||||||
import ContentValueHash from './content_value/ContentValueHash.vue'
|
import ContentValueHash from '../content_value/ContentValueHash.vue'
|
||||||
import ContentValueList from './content_value/ContentValueList.vue'
|
import ContentValueList from '../content_value/ContentValueList.vue'
|
||||||
import ContentValueString from './content_value/ContentValueString.vue'
|
import ContentValueString from '../content_value/ContentValueString.vue'
|
||||||
import ContentValueSet from './content_value/ContentValueSet.vue'
|
import ContentValueSet from '../content_value/ContentValueSet.vue'
|
||||||
import ContentValueZset from './content_value/ContentValueZset.vue'
|
import ContentValueZset from '../content_value/ContentValueZSet.vue'
|
||||||
import { isEmpty, map, toUpper } from 'lodash'
|
import { isEmpty, map, toUpper } from 'lodash'
|
||||||
import useTabStore from '../stores/tab.js'
|
import useTabStore from '../../stores/tab.js'
|
||||||
|
import { useDialog } from 'naive-ui'
|
||||||
|
import useConnectionStore from '../../stores/connections.js'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
const valueComponents = {
|
const valueComponents = {
|
||||||
[types.STRING]: ContentValueString,
|
[types.STRING]: ContentValueString,
|
||||||
|
@ -17,6 +20,8 @@ const valueComponents = {
|
||||||
[types.ZSET]: ContentValueZset,
|
[types.ZSET]: ContentValueZset,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dialog = useDialog()
|
||||||
|
const connectionStore = useConnectionStore()
|
||||||
const tabStore = useTabStore()
|
const tabStore = useTabStore()
|
||||||
const tab = computed(() =>
|
const tab = computed(() =>
|
||||||
map(tabStore.tabs, (item) => ({
|
map(tabStore.tabs, (item) => ({
|
||||||
|
@ -52,9 +57,24 @@ const onAddTab = () => {
|
||||||
tabStore.newBlankTab()
|
tabStore.newBlankTab()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const i18n = useI18n()
|
||||||
const onCloseTab = (tabIndex) => {
|
const onCloseTab = (tabIndex) => {
|
||||||
tabStore.removeTab(tabIndex)
|
dialog.warning({
|
||||||
console.log('TODO: close connection also')
|
title: i18n.t('close_confirm_title'),
|
||||||
|
content: i18n.t('close_confirm'),
|
||||||
|
positiveText: i18n.t('confirm'),
|
||||||
|
negativeText: i18n.t('cancel'),
|
||||||
|
closable: false,
|
||||||
|
closeOnEsc: false,
|
||||||
|
maskClosable: false,
|
||||||
|
transformOrigin: 'center',
|
||||||
|
onPositiveClick: () => {
|
||||||
|
const removedTab = tabStore.removeTab(tabIndex)
|
||||||
|
if (removedTab != null) {
|
||||||
|
connectionStore.closeConnection(removedTab.name)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -63,8 +83,7 @@ const onCloseTab = (tabIndex) => {
|
||||||
<!-- <content-tab :model-value="tab"></content-tab>-->
|
<!-- <content-tab :model-value="tab"></content-tab>-->
|
||||||
<n-tabs
|
<n-tabs
|
||||||
v-model:value="tabStore.activatedIndex"
|
v-model:value="tabStore.activatedIndex"
|
||||||
:closable="tab.length > 1"
|
:closable="true"
|
||||||
addable
|
|
||||||
size="small"
|
size="small"
|
||||||
type="card"
|
type="card"
|
||||||
@add="onAddTab"
|
@add="onAddTab"
|
||||||
|
@ -75,10 +94,10 @@ const onCloseTab = (tabIndex) => {
|
||||||
<n-ellipsis style="max-width: 150px">{{ t.label }}</n-ellipsis>
|
<n-ellipsis style="max-width: 150px">{{ t.label }}</n-ellipsis>
|
||||||
</n-tab>
|
</n-tab>
|
||||||
</n-tabs>
|
</n-tabs>
|
||||||
<!-- add loading status -->
|
<!-- TODO: add loading status -->
|
||||||
<component
|
<component
|
||||||
:is="valueComponents[tabContent.type]"
|
|
||||||
v-if="tabContent != null && !isEmpty(tabContent.keyPath)"
|
v-if="tabContent != null && !isEmpty(tabContent.keyPath)"
|
||||||
|
:is="valueComponents[tabContent.type]"
|
||||||
:db="tabContent.db"
|
:db="tabContent.db"
|
||||||
:key-path="tabContent.keyPath"
|
:key-path="tabContent.keyPath"
|
||||||
:name="tabContent.name"
|
:name="tabContent.name"
|
||||||
|
@ -92,20 +111,5 @@ const onCloseTab = (tabIndex) => {
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.content-container {
|
@import 'content';
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
background-color: var(--bg-color);
|
|
||||||
padding-top: 2px;
|
|
||||||
padding-bottom: 5px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-content {
|
|
||||||
height: 100%;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-content {
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
|
@ -0,0 +1,43 @@
|
||||||
|
<script setup>
|
||||||
|
import useDialog from '../../stores/dialog.js'
|
||||||
|
import AddLink from '../icons/AddLink.vue'
|
||||||
|
|
||||||
|
const dialogStore = useDialog()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="content-container flex-box-v">
|
||||||
|
<!-- TODO: replace icon to app icon -->
|
||||||
|
<n-empty :description="$t('empty_server_content')">
|
||||||
|
<template #extra>
|
||||||
|
<n-button @click="dialogStore.openNewDialog()">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon :component="AddLink" size="18" />
|
||||||
|
</template>
|
||||||
|
{{ $t('new_conn') }}
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
</n-empty>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import 'content';
|
||||||
|
|
||||||
|
.content-container {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-preset-item {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
margin-right: 2px;
|
||||||
|
border: white 3px solid;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&_selected,
|
||||||
|
&:hover {
|
||||||
|
border-color: #cdd0d6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,9 +1,8 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch } from 'vue'
|
import { ref } from 'vue'
|
||||||
import useConnectionStore from '../stores/connection.js'
|
import { ConnectionType } from '../../consts/connection_type.js'
|
||||||
import { throttle } from 'lodash'
|
import Close from '../icons/Close.vue'
|
||||||
import { ConnectionType } from '../consts/connection_type.js'
|
import useConnectionStore from '../../stores/connections.js'
|
||||||
import Close from './icons/Close.vue'
|
|
||||||
|
|
||||||
const emit = defineEmits(['switchTab', 'closeTab', 'update:modelValue'])
|
const emit = defineEmits(['switchTab', 'closeTab', 'update:modelValue'])
|
||||||
|
|
||||||
|
@ -31,7 +30,7 @@ const onCurrentSelectChange = ({ type, group = '', server = '', db = 0, key = ''
|
||||||
// load and update content value
|
// load and update content value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
watch(() => connectionStore.currentSelect, throttle(onCurrentSelectChange, 1000))
|
// watch(() => databaseStore.currentSelect, throttle(onCurrentSelectChange, 1000))
|
||||||
|
|
||||||
const items = ref(props.modelValue)
|
const items = ref(props.modelValue)
|
||||||
const selIndex = ref(props.selectedIndex)
|
const selIndex = ref(props.selectedIndex)
|
|
@ -0,0 +1,16 @@
|
||||||
|
.content-container {
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
padding-top: 2px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-content {
|
||||||
|
height: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content {
|
||||||
|
}
|
|
@ -5,9 +5,9 @@ import ContentToolbar from '../ContentToolbar.vue'
|
||||||
import AddLink from '../icons/AddLink.vue'
|
import AddLink from '../icons/AddLink.vue'
|
||||||
import { NButton, NCode, NIcon, NInput, useMessage } from 'naive-ui'
|
import { NButton, NCode, NIcon, NInput, useMessage } from 'naive-ui'
|
||||||
import { types, types as redisTypes } from '../../consts/support_redis_type.js'
|
import { types, types as redisTypes } from '../../consts/support_redis_type.js'
|
||||||
import EditableTableColumn from '../EditableTableColumn.vue'
|
import EditableTableColumn from '../common/EditableTableColumn.vue'
|
||||||
import useConnectionStore from '../../stores/connection.js'
|
|
||||||
import useDialogStore from '../../stores/dialog.js'
|
import useDialogStore from '../../stores/dialog.js'
|
||||||
|
import useConnectionStore from '../../stores/connections.js'
|
||||||
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,9 @@ import AddLink from '../icons/AddLink.vue'
|
||||||
import { NButton, NCode, NIcon, NInput, useMessage } from 'naive-ui'
|
import { NButton, NCode, NIcon, NInput, useMessage } from 'naive-ui'
|
||||||
import { size } from 'lodash'
|
import { size } from 'lodash'
|
||||||
import { types, types as redisTypes } from '../../consts/support_redis_type.js'
|
import { types, types as redisTypes } from '../../consts/support_redis_type.js'
|
||||||
import EditableTableColumn from '../EditableTableColumn.vue'
|
import EditableTableColumn from '../common/EditableTableColumn.vue'
|
||||||
import useConnectionStore from '../../stores/connection.js'
|
|
||||||
import useDialogStore from '../../stores/dialog.js'
|
import useDialogStore from '../../stores/dialog.js'
|
||||||
|
import useConnectionStore from '../../stores/connections.js'
|
||||||
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,10 @@ import ContentToolbar from '../ContentToolbar.vue'
|
||||||
import AddLink from '../icons/AddLink.vue'
|
import AddLink from '../icons/AddLink.vue'
|
||||||
import { NButton, NCode, NIcon, NInput, useMessage } from 'naive-ui'
|
import { NButton, NCode, NIcon, NInput, useMessage } from 'naive-ui'
|
||||||
import { size } from 'lodash'
|
import { size } from 'lodash'
|
||||||
import useConnectionStore from '../../stores/connection.js'
|
|
||||||
import useDialogStore from '../../stores/dialog.js'
|
import useDialogStore from '../../stores/dialog.js'
|
||||||
import { types, types as redisTypes } from '../../consts/support_redis_type.js'
|
import { types, types as redisTypes } from '../../consts/support_redis_type.js'
|
||||||
import EditableTableColumn from '../EditableTableColumn.vue'
|
import EditableTableColumn from '../common/EditableTableColumn.vue'
|
||||||
|
import useConnectionStore from '../../stores/connections.js'
|
||||||
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|
|
@ -9,10 +9,10 @@ import { types } from '../../consts/value_view_type.js'
|
||||||
import Close from '../icons/Close.vue'
|
import Close from '../icons/Close.vue'
|
||||||
import Edit from '../icons/Edit.vue'
|
import Edit from '../icons/Edit.vue'
|
||||||
import { IsJson } from '../../utils/check_string_format.js'
|
import { IsJson } from '../../utils/check_string_format.js'
|
||||||
import useConnectionStore from '../../stores/connection.js'
|
|
||||||
import { types as redisTypes } from '../../consts/support_redis_type.js'
|
import { types as redisTypes } from '../../consts/support_redis_type.js'
|
||||||
import { ClipboardSetText } from '../../../wailsjs/runtime/runtime.js'
|
import { ClipboardSetText } from '../../../wailsjs/runtime/runtime.js'
|
||||||
import { toLower } from 'lodash'
|
import { toLower } from 'lodash'
|
||||||
|
import useConnectionStore from '../../stores/connections.js'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
name: String,
|
name: String,
|
||||||
|
|
|
@ -5,10 +5,10 @@ import ContentToolbar from '../ContentToolbar.vue'
|
||||||
import AddLink from '../icons/AddLink.vue'
|
import AddLink from '../icons/AddLink.vue'
|
||||||
import { NButton, NCode, NIcon, NInput, NInputNumber, useMessage } from 'naive-ui'
|
import { NButton, NCode, NIcon, NInput, NInputNumber, useMessage } from 'naive-ui'
|
||||||
import { types, types as redisTypes } from '../../consts/support_redis_type.js'
|
import { types, types as redisTypes } from '../../consts/support_redis_type.js'
|
||||||
import EditableTableColumn from '../EditableTableColumn.vue'
|
import EditableTableColumn from '../common/EditableTableColumn.vue'
|
||||||
import useConnectionStore from '../../stores/connection.js'
|
|
||||||
import { isEmpty } from 'lodash'
|
import { isEmpty } from 'lodash'
|
||||||
import useDialogStore from '../../stores/dialog.js'
|
import useDialogStore from '../../stores/dialog.js'
|
||||||
|
import useConnectionStore from '../../stores/connections.js'
|
||||||
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|
|
@ -4,12 +4,12 @@ import { types } from '../../consts/support_redis_type'
|
||||||
import useDialog from '../../stores/dialog'
|
import useDialog from '../../stores/dialog'
|
||||||
import NewStringValue from '../new_value/NewStringValue.vue'
|
import NewStringValue from '../new_value/NewStringValue.vue'
|
||||||
import NewSetValue from '../new_value/NewSetValue.vue'
|
import NewSetValue from '../new_value/NewSetValue.vue'
|
||||||
import useConnectionStore from '../../stores/connection.js'
|
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useMessage } from 'naive-ui'
|
import { useMessage } from 'naive-ui'
|
||||||
import AddListValue from '../new_value/AddListValue.vue'
|
import AddListValue from '../new_value/AddListValue.vue'
|
||||||
import AddHashValue from '../new_value/AddHashValue.vue'
|
import AddHashValue from '../new_value/AddHashValue.vue'
|
||||||
import AddZSetValue from '../new_value/AddZSetValue.vue'
|
import AddZSetValue from '../new_value/AddZSetValue.vue'
|
||||||
|
import useConnectionStore from '../../stores/connections.js'
|
||||||
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const newForm = reactive({
|
const newForm = reactive({
|
||||||
|
|
|
@ -8,8 +8,8 @@ import NewHashValue from '../new_value/NewHashValue.vue'
|
||||||
import NewListValue from '../new_value/NewListValue.vue'
|
import NewListValue from '../new_value/NewListValue.vue'
|
||||||
import NewZSetValue from '../new_value/NewZSetValue.vue'
|
import NewZSetValue from '../new_value/NewZSetValue.vue'
|
||||||
import NewSetValue from '../new_value/NewSetValue.vue'
|
import NewSetValue from '../new_value/NewSetValue.vue'
|
||||||
import useConnectionStore from '../../stores/connection.js'
|
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import useConnectionStore from '../../stores/connections.js'
|
||||||
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const newForm = reactive({
|
const newForm = reactive({
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { reactive, watch } from 'vue'
|
import { reactive, watch } from 'vue'
|
||||||
import useDialog from '../../stores/dialog'
|
import useDialog from '../../stores/dialog'
|
||||||
import useConnectionStore from '../../stores/connection.js'
|
|
||||||
import { useMessage } from 'naive-ui'
|
import { useMessage } from 'naive-ui'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import useConnectionStore from '../../stores/connections.js'
|
||||||
|
|
||||||
const renameForm = reactive({
|
const renameForm = reactive({
|
||||||
server: '',
|
server: '',
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
import { reactive, ref, watch } from 'vue'
|
import { reactive, ref, watch } from 'vue'
|
||||||
import useDialog from '../../stores/dialog'
|
import useDialog from '../../stores/dialog'
|
||||||
import useTabStore from '../../stores/tab.js'
|
import useTabStore from '../../stores/tab.js'
|
||||||
import useConnectionStore from '../../stores/connection.js'
|
|
||||||
import { useMessage } from 'naive-ui'
|
import { useMessage } from 'naive-ui'
|
||||||
|
import useConnectionStore from '../../stores/connections.js'
|
||||||
|
|
||||||
const ttlForm = reactive({
|
const ttlForm = reactive({
|
||||||
ttl: -1,
|
ttl: -1,
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
strokeWidth: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 3,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<svg fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect
|
||||||
|
:stroke-width="strokeWidth"
|
||||||
|
fill="none"
|
||||||
|
height="34"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
width="28"
|
||||||
|
x="13"
|
||||||
|
y="10"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
:stroke-width="strokeWidth"
|
||||||
|
d="M35 10V4H8C7.44772 4 7 4.44772 7 5V38H13"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
:stroke-width="strokeWidth"
|
||||||
|
d="M21 22H33"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
:stroke-width="strokeWidth"
|
||||||
|
d="M21 30H33"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
|
@ -3,7 +3,7 @@ import { ref } from 'vue'
|
||||||
import { flatMap, reject } from 'lodash'
|
import { flatMap, reject } from 'lodash'
|
||||||
import Add from '../icons/Add.vue'
|
import Add from '../icons/Add.vue'
|
||||||
import Delete from '../icons/Delete.vue'
|
import Delete from '../icons/Delete.vue'
|
||||||
import IconButton from '../IconButton.vue'
|
import IconButton from '../common/IconButton.vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { ref } from 'vue'
|
||||||
import { compact } from 'lodash'
|
import { compact } from 'lodash'
|
||||||
import Add from '../icons/Add.vue'
|
import Add from '../icons/Add.vue'
|
||||||
import Delete from '../icons/Delete.vue'
|
import Delete from '../icons/Delete.vue'
|
||||||
import IconButton from '../IconButton.vue'
|
import IconButton from '../common/IconButton.vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { ref } from 'vue'
|
||||||
import { isEmpty, reject } from 'lodash'
|
import { isEmpty, reject } from 'lodash'
|
||||||
import Add from '../icons/Add.vue'
|
import Add from '../icons/Add.vue'
|
||||||
import Delete from '../icons/Delete.vue'
|
import Delete from '../icons/Delete.vue'
|
||||||
import IconButton from '../IconButton.vue'
|
import IconButton from '../common/IconButton.vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { ref } from 'vue'
|
||||||
import { flatMap, reject } from 'lodash'
|
import { flatMap, reject } from 'lodash'
|
||||||
import Add from '../icons/Add.vue'
|
import Add from '../icons/Add.vue'
|
||||||
import Delete from '../icons/Delete.vue'
|
import Delete from '../icons/Delete.vue'
|
||||||
import IconButton from '../IconButton.vue'
|
import IconButton from '../common/IconButton.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
value: Array,
|
value: Array,
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { ref } from 'vue'
|
||||||
import { compact } from 'lodash'
|
import { compact } from 'lodash'
|
||||||
import Add from '../icons/Add.vue'
|
import Add from '../icons/Add.vue'
|
||||||
import Delete from '../icons/Delete.vue'
|
import Delete from '../icons/Delete.vue'
|
||||||
import IconButton from '../IconButton.vue'
|
import IconButton from '../common/IconButton.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
value: Array,
|
value: Array,
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { ref } from 'vue'
|
||||||
import { compact, uniq } from 'lodash'
|
import { compact, uniq } from 'lodash'
|
||||||
import Add from '../icons/Add.vue'
|
import Add from '../icons/Add.vue'
|
||||||
import Delete from '../icons/Delete.vue'
|
import Delete from '../icons/Delete.vue'
|
||||||
import IconButton from '../IconButton.vue'
|
import IconButton from '../common/IconButton.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
value: Array,
|
value: Array,
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { ref } from 'vue'
|
||||||
import { flatMap, isEmpty, reject } from 'lodash'
|
import { flatMap, isEmpty, reject } from 'lodash'
|
||||||
import Add from '../icons/Add.vue'
|
import Add from '../icons/Add.vue'
|
||||||
import Delete from '../icons/Delete.vue'
|
import Delete from '../icons/Delete.vue'
|
||||||
import IconButton from '../IconButton.vue'
|
import IconButton from '../common/IconButton.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
value: Array,
|
value: Array,
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import useDialogStore from '../stores/dialog.js'
|
import useDialogStore from '../../stores/dialog.js'
|
||||||
import { NIcon } from 'naive-ui'
|
import { NIcon } from 'naive-ui'
|
||||||
import AddGroup from './icons/AddGroup.vue'
|
import AddGroup from '../icons/AddGroup.vue'
|
||||||
import AddLink from './icons/AddLink.vue'
|
import AddLink from '../icons/AddLink.vue'
|
||||||
import Sort from './icons/Sort.vue'
|
import Sort from '../icons/Sort.vue'
|
||||||
import ConnectionsTree from './ConnectionsTree.vue'
|
import IconButton from '../common/IconButton.vue'
|
||||||
import IconButton from './IconButton.vue'
|
import Filter from '../icons/Filter.vue'
|
||||||
import Filter from './icons/Filter.vue'
|
import ConnectionTree from './ConnectionTree.vue'
|
||||||
|
|
||||||
const dialogStore = useDialogStore()
|
const dialogStore = useDialogStore()
|
||||||
|
|
||||||
|
@ -16,8 +16,8 @@ const onSort = () => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="true" class="nav-pane-container flex-box-v">
|
<div class="nav-pane-container flex-box-v">
|
||||||
<ConnectionsTree />
|
<connection-tree />
|
||||||
|
|
||||||
<!-- bottom function bar -->
|
<!-- bottom function bar -->
|
||||||
<div class="nav-pane-bottom flex-box-h">
|
<div class="nav-pane-bottom flex-box-h">
|
|
@ -0,0 +1,271 @@
|
||||||
|
<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 Refresh from '../icons/Refresh.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([])
|
||||||
|
|
||||||
|
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]: ({ connected }) => {
|
||||||
|
if (connected) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'server_reload',
|
||||||
|
label: i18n.t('reload'),
|
||||||
|
icon: renderIcon(Refresh),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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 = (value, option, meta) => {
|
||||||
|
expandedKeys.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open connection
|
||||||
|
* @param name
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
const openConnection = async (name) => {
|
||||||
|
try {
|
||||||
|
openingConnection.value = true
|
||||||
|
await connectionStore.openConnection(name)
|
||||||
|
tabStore.upsertTab({
|
||||||
|
server: name,
|
||||||
|
})
|
||||||
|
} 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
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(() => {})
|
||||||
|
}
|
||||||
|
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"
|
||||||
|
:node-props="nodeProps"
|
||||||
|
:on-update:expanded-keys="onUpdateExpandedKeys"
|
||||||
|
: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>
|
|
@ -0,0 +1,26 @@
|
||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
title: String,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="db-tree-item flex-box-v">
|
||||||
|
<div class="tree-item-title">{{ title }}</div>
|
||||||
|
<div class="tree-item-addr">localhost:3306</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.db-tree-item {
|
||||||
|
padding: 0 5px;
|
||||||
|
|
||||||
|
.tree-item-title {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-item-addr {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,44 @@
|
||||||
|
<script setup>
|
||||||
|
import { NIcon } from 'naive-ui'
|
||||||
|
import AddGroup from '../icons/AddGroup.vue'
|
||||||
|
import AddLink from '../icons/AddLink.vue'
|
||||||
|
import DatabaseTree from './DatabaseTree.vue'
|
||||||
|
import IconButton from '../common/IconButton.vue'
|
||||||
|
import Filter from '../icons/Filter.vue'
|
||||||
|
import useTabStore from '../../stores/tab.js'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { get } from 'lodash'
|
||||||
|
|
||||||
|
const tabStore = useTabStore()
|
||||||
|
const currentName = computed(() => get(tabStore.currentTab, 'name', ''))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="nav-pane-container flex-box-v">
|
||||||
|
<database-tree :server="currentName" />
|
||||||
|
|
||||||
|
<!-- bottom function bar -->
|
||||||
|
<div class="nav-pane-bottom flex-box-h">
|
||||||
|
<icon-button :icon="AddLink" color="#555" size="20" stroke-width="4" t-tooltip="new_conn" />
|
||||||
|
<icon-button :icon="AddGroup" color="#555" size="20" stroke-width="4" t-tooltip="new_group" />
|
||||||
|
<n-input placeholder="">
|
||||||
|
<template #prefix>
|
||||||
|
<n-icon :component="Filter" color="#aaa" size="20" />
|
||||||
|
</template>
|
||||||
|
</n-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.nav-pane-container {
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
|
||||||
|
.nav-pane-bottom {
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 3px 3px 5px 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,38 +1,37 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { h, nextTick, onMounted, reactive, ref } from 'vue'
|
import { h, nextTick, onMounted, reactive, ref } from 'vue'
|
||||||
import { ConnectionType } from '../consts/connection_type.js'
|
import { ConnectionType } from '../../consts/connection_type.js'
|
||||||
import useConnection from '../stores/connection.js'
|
|
||||||
import { NIcon, useDialog, useMessage } from 'naive-ui'
|
import { NIcon, useDialog, useMessage } from 'naive-ui'
|
||||||
import ToggleFolder from './icons/ToggleFolder.vue'
|
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 ToggleServer from './icons/ToggleServer.vue'
|
|
||||||
import { indexOf, remove, startsWith } from 'lodash'
|
import { indexOf, remove, startsWith } 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 Config from './icons/Config.vue'
|
import CopyLink from '../icons/CopyLink.vue'
|
||||||
import CopyLink from './icons/CopyLink.vue'
|
import Add from '../icons/Add.vue'
|
||||||
import Unlink from './icons/Unlink.vue'
|
import Layer from '../icons/Layer.vue'
|
||||||
import Add from './icons/Add.vue'
|
import Delete from '../icons/Delete.vue'
|
||||||
import Layer from './icons/Layer.vue'
|
import Connect from '../icons/Connect.vue'
|
||||||
import Delete from './icons/Delete.vue'
|
import useDialogStore from '../../stores/dialog.js'
|
||||||
import Connect from './icons/Connect.vue'
|
import { ClipboardSetText } from '../../../wailsjs/runtime/runtime.js'
|
||||||
import useDialogStore from '../stores/dialog.js'
|
import useTabStore from '../../stores/tab.js'
|
||||||
import { ClipboardSetText } from '../../wailsjs/runtime/runtime.js'
|
import useConnectionStore from '../../stores/connections.js'
|
||||||
import useTabStore from '../stores/tab.js'
|
|
||||||
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const loadingConnections = ref(false)
|
const loadingConnections = ref(false)
|
||||||
const expandedKeys = ref([])
|
const expandedKeys = ref([])
|
||||||
const connectionStore = useConnection()
|
const connectionStore = useConnectionStore()
|
||||||
const tabStore = useTabStore()
|
const tabStore = useTabStore()
|
||||||
const dialogStore = useDialogStore()
|
const dialogStore = useDialogStore()
|
||||||
|
|
||||||
const showContextMenu = ref(false)
|
const contextMenuParam = reactive({
|
||||||
const contextPos = reactive({ x: 0, y: 0 })
|
show: false,
|
||||||
const contextMenuOptions = ref(null)
|
x: 0,
|
||||||
const currentContextNode = ref(null)
|
y: 0,
|
||||||
|
options: null,
|
||||||
|
currentNode: null,
|
||||||
|
})
|
||||||
const renderIcon = (icon) => {
|
const renderIcon = (icon) => {
|
||||||
return () => {
|
return () => {
|
||||||
return h(NIcon, null, {
|
return h(NIcon, null, {
|
||||||
|
@ -41,61 +40,6 @@ const renderIcon = (icon) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const menuOptions = {
|
const menuOptions = {
|
||||||
[ConnectionType.Group]: ({ opened }) => [
|
|
||||||
{
|
|
||||||
key: 'group_reload',
|
|
||||||
label: i18n.t('config_conn_group'),
|
|
||||||
icon: renderIcon(Config),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'group_delete',
|
|
||||||
label: i18n.t('remove_conn_group'),
|
|
||||||
icon: renderIcon(Delete),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[ConnectionType.Server]: ({ connected }) => {
|
|
||||||
if (connected) {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
key: 'server_reload',
|
|
||||||
label: i18n.t('reload'),
|
|
||||||
icon: renderIcon(Refresh),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'server_disconnect',
|
|
||||||
label: i18n.t('disconnect'),
|
|
||||||
icon: renderIcon(Unlink),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'server_dup',
|
|
||||||
label: i18n.t('dup_conn'),
|
|
||||||
icon: renderIcon(CopyLink),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'server_config',
|
|
||||||
label: i18n.t('config_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),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[ConnectionType.RedisDB]: ({ opened }) => {
|
[ConnectionType.RedisDB]: ({ opened }) => {
|
||||||
if (opened) {
|
if (opened) {
|
||||||
return [
|
return [
|
||||||
|
@ -177,12 +121,16 @@ onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
// TODO: Show loading list status
|
// TODO: Show loading list status
|
||||||
loadingConnections.value = true
|
loadingConnections.value = true
|
||||||
nextTick(connectionStore.initConnection)
|
// nextTick(connectionStore.initConnection)
|
||||||
} finally {
|
} finally {
|
||||||
loadingConnections.value = false
|
loadingConnections.value = false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
server: Strig,
|
||||||
|
})
|
||||||
|
|
||||||
const expandKey = (key) => {
|
const expandKey = (key) => {
|
||||||
const idx = indexOf(expandedKeys.value, key)
|
const idx = indexOf(expandedKeys.value, key)
|
||||||
if (idx === -1) {
|
if (idx === -1) {
|
||||||
|
@ -224,22 +172,6 @@ const onUpdateExpanded = (value, option, meta) => {
|
||||||
|
|
||||||
const renderPrefix = ({ option }) => {
|
const renderPrefix = ({ option }) => {
|
||||||
switch (option.type) {
|
switch (option.type) {
|
||||||
case ConnectionType.Group:
|
|
||||||
return h(
|
|
||||||
NIcon,
|
|
||||||
{ size: 20 },
|
|
||||||
{
|
|
||||||
default: () => h(ToggleFolder, { modelValue: option.expanded === true }),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
case ConnectionType.Server:
|
|
||||||
return h(
|
|
||||||
NIcon,
|
|
||||||
{ size: 20 },
|
|
||||||
{
|
|
||||||
default: () => h(ToggleServer, { modelValue: option.connected === true }),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
case ConnectionType.RedisDB:
|
case ConnectionType.RedisDB:
|
||||||
return h(
|
return h(
|
||||||
NIcon,
|
NIcon,
|
||||||
|
@ -302,6 +234,7 @@ const nodeProps = ({ option }) => {
|
||||||
|
|
||||||
case ConnectionType.RedisDB:
|
case ConnectionType.RedisDB:
|
||||||
option.isLeaf = false
|
option.isLeaf = false
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// default handle is expand current node
|
// default handle is expand current node
|
||||||
|
@ -313,13 +246,13 @@ const nodeProps = ({ option }) => {
|
||||||
if (mop == null) {
|
if (mop == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
showContextMenu.value = false
|
contextMenuParam.show = false
|
||||||
nextTick().then(() => {
|
nextTick().then(() => {
|
||||||
contextMenuOptions.value = mop(option)
|
contextMenuParam.options = mop(option)
|
||||||
currentContextNode.value = option
|
contextMenuParam.currentNode = option
|
||||||
contextPos.x = e.clientX
|
contextMenuParam.x = e.clientX
|
||||||
contextPos.y = e.clientY
|
contextMenuParam.y = e.clientY
|
||||||
showContextMenu.value = true
|
contextMenuParam.show = true
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
// onMouseover() {
|
// onMouseover() {
|
||||||
|
@ -330,17 +263,6 @@ const nodeProps = ({ option }) => {
|
||||||
|
|
||||||
const onLoadTree = async (node) => {
|
const onLoadTree = async (node) => {
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
case ConnectionType.Server:
|
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
await connectionStore.openConnection(node.name)
|
|
||||||
} catch (e) {
|
|
||||||
message.error(e.message)
|
|
||||||
node.isLeaf = undefined
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case ConnectionType.RedisDB:
|
case ConnectionType.RedisDB:
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
|
@ -356,8 +278,8 @@ const onLoadTree = async (node) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSelectContextMenu = (key) => {
|
const handleSelectContextMenu = (key) => {
|
||||||
showContextMenu.value = false
|
contextMenuParam.show = false
|
||||||
const { name, db, key: nodeKey, redisKey } = currentContextNode.value
|
const { name, db, key: nodeKey, redisKey } = contextMenuParam.currentNode
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'server_disconnect':
|
case 'server_disconnect':
|
||||||
connectionStore.closeConnection(nodeKey).then((success) => {
|
connectionStore.closeConnection(nodeKey).then((success) => {
|
||||||
|
@ -412,7 +334,7 @@ const handleSelectContextMenu = (key) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleOutsideContextMenu = () => {
|
const handleOutsideContextMenu = () => {
|
||||||
showContextMenu.value = false
|
contextMenuParam.show = false
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -420,7 +342,9 @@ const handleOutsideContextMenu = () => {
|
||||||
<n-tree
|
<n-tree
|
||||||
:block-line="true"
|
:block-line="true"
|
||||||
:block-node="true"
|
:block-node="true"
|
||||||
:data="connectionStore.connections"
|
:animated="false"
|
||||||
|
:cancelable="false"
|
||||||
|
:data="connectionStore.databases[props.server] || []"
|
||||||
:expand-on-click="false"
|
:expand-on-click="false"
|
||||||
:expanded-keys="expandedKeys"
|
:expanded-keys="expandedKeys"
|
||||||
:node-props="nodeProps"
|
:node-props="nodeProps"
|
||||||
|
@ -429,17 +353,16 @@ const handleOutsideContextMenu = () => {
|
||||||
:render-label="renderLabel"
|
:render-label="renderLabel"
|
||||||
:render-prefix="renderPrefix"
|
:render-prefix="renderPrefix"
|
||||||
:render-suffix="renderSuffix"
|
:render-suffix="renderSuffix"
|
||||||
block-line
|
|
||||||
class="fill-height"
|
class="fill-height"
|
||||||
virtual-scroll
|
virtual-scroll
|
||||||
/>
|
/>
|
||||||
<n-dropdown
|
<n-dropdown
|
||||||
:animated="false"
|
:animated="false"
|
||||||
:options="contextMenuOptions"
|
:options="contextMenuParam.options"
|
||||||
:render-label="renderContextLabel"
|
:render-label="renderContextLabel"
|
||||||
:show="showContextMenu"
|
:show="contextMenuParam.show"
|
||||||
:x="contextPos.x"
|
:x="contextMenuParam.x"
|
||||||
:y="contextPos.y"
|
:y="contextMenuParam.y"
|
||||||
placement="bottom-start"
|
placement="bottom-start"
|
||||||
trigger="manual"
|
trigger="manual"
|
||||||
@clickoutside="handleOutsideContextMenu"
|
@clickoutside="handleOutsideContextMenu"
|
|
@ -9,6 +9,9 @@
|
||||||
"new_group": "Add New Group",
|
"new_group": "Add New Group",
|
||||||
"sort_conn": "Resort Connections",
|
"sort_conn": "Resort Connections",
|
||||||
"reload_key": "Reload Current Key",
|
"reload_key": "Reload Current Key",
|
||||||
|
"close_confirm_title": "Confirm Close",
|
||||||
|
"close_confirm": "Confirm close this tab and connection",
|
||||||
|
"opening_connection": "Opening Connection...",
|
||||||
"ttl": "TTL",
|
"ttl": "TTL",
|
||||||
"forever": "Forever",
|
"forever": "Forever",
|
||||||
"rename_key": "Rename Key",
|
"rename_key": "Rename Key",
|
||||||
|
@ -31,8 +34,8 @@
|
||||||
"disconnect": "Disconnect",
|
"disconnect": "Disconnect",
|
||||||
"dup_conn": "Duplicate Connection",
|
"dup_conn": "Duplicate Connection",
|
||||||
"remove_conn": "Delete Connection",
|
"remove_conn": "Delete Connection",
|
||||||
"config_conn": "Edit Connection Config",
|
"edit_conn": "Edit Connection Config",
|
||||||
"config_conn_group": "Edit Connection Group",
|
"edit_conn_group": "Edit Connection Group",
|
||||||
"remove_conn_group": "Delete Connection Group",
|
"remove_conn_group": "Delete Connection Group",
|
||||||
"copy_path": "Copy Path",
|
"copy_path": "Copy Path",
|
||||||
"remove_path": "Remove Path",
|
"remove_path": "Remove Path",
|
||||||
|
@ -102,10 +105,12 @@
|
||||||
"field_required": "This item should not be blank",
|
"field_required": "This item should not be blank",
|
||||||
"spec_field_required": "\"{key}\" should not be blank",
|
"spec_field_required": "\"{key}\" should not be blank",
|
||||||
"no_connections": "No Connection",
|
"no_connections": "No Connection",
|
||||||
"empty_tab_content": "Select the key from left list to see the details of the key.",
|
"empty_tab_content": "Select the key from left list to see detail.",
|
||||||
|
"empty_server_content": "Connect server from left list",
|
||||||
"reload_when_succ": "Reload immediately after success",
|
"reload_when_succ": "Reload immediately after success",
|
||||||
"server": "Server",
|
"server": "Server",
|
||||||
"structure": "Structure",
|
"structure": "Structure",
|
||||||
|
"log": "Log",
|
||||||
"about": "About",
|
"about": "About",
|
||||||
"check_update": "Check for Updates..."
|
"check_update": "Check for Updates..."
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,9 @@
|
||||||
"new_group": "添加新分组",
|
"new_group": "添加新分组",
|
||||||
"sort_conn": "调整连接顺序",
|
"sort_conn": "调整连接顺序",
|
||||||
"reload_key": "重新载入此键内容",
|
"reload_key": "重新载入此键内容",
|
||||||
|
"close_confirm_title": "关闭确认",
|
||||||
|
"close_confirm": "是否关闭当前连接",
|
||||||
|
"opening_connection": "正在打开连接...",
|
||||||
"ttl": "TTL",
|
"ttl": "TTL",
|
||||||
"forever": "永久",
|
"forever": "永久",
|
||||||
"rename_key": "重命名键",
|
"rename_key": "重命名键",
|
||||||
|
@ -33,8 +36,8 @@
|
||||||
"disconnect": "断开连接",
|
"disconnect": "断开连接",
|
||||||
"dup_conn": "复制连接",
|
"dup_conn": "复制连接",
|
||||||
"remove_conn": "删除连接",
|
"remove_conn": "删除连接",
|
||||||
"config_conn": "编辑连接配置",
|
"edit_conn": "编辑连接配置",
|
||||||
"config_conn_group": "编辑连接分组",
|
"edit_conn_group": "编辑连接分组",
|
||||||
"remove_conn_group": "删除连接分组",
|
"remove_conn_group": "删除连接分组",
|
||||||
"copy_path": "复制路径",
|
"copy_path": "复制路径",
|
||||||
"remove_path": "删除路径",
|
"remove_path": "删除路径",
|
||||||
|
@ -106,9 +109,11 @@
|
||||||
"spec_field_required": "{key} 不能为空",
|
"spec_field_required": "{key} 不能为空",
|
||||||
"no_connections": "空空如也",
|
"no_connections": "空空如也",
|
||||||
"empty_tab_content": "可以从左边选择键来查看键的详细内容",
|
"empty_tab_content": "可以从左边选择键来查看键的详细内容",
|
||||||
|
"empty_server_content": "可以从左边选择并打开连接",
|
||||||
"reload_when_succ": "操作成功后立即重新加载",
|
"reload_when_succ": "操作成功后立即重新加载",
|
||||||
"server": "服务器",
|
"server": "服务器",
|
||||||
"structure": "结构",
|
"structure": "结构",
|
||||||
|
"log": "日志",
|
||||||
"about": "关于",
|
"about": "关于",
|
||||||
"check_update": "检查更新..."
|
"check_update": "检查更新..."
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,921 @@
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { get, isEmpty, last, remove, size, sortedIndexBy, split } from 'lodash'
|
||||||
|
import {
|
||||||
|
AddHashField,
|
||||||
|
AddListItem,
|
||||||
|
AddZSetValue,
|
||||||
|
CloseConnection,
|
||||||
|
GetKeyValue,
|
||||||
|
ListConnection,
|
||||||
|
OpenConnection,
|
||||||
|
OpenDatabase,
|
||||||
|
RemoveKey,
|
||||||
|
RenameKey,
|
||||||
|
SetHashValue,
|
||||||
|
SetKeyTTL,
|
||||||
|
SetKeyValue,
|
||||||
|
SetListItem,
|
||||||
|
SetSetItem,
|
||||||
|
UpdateSetItem,
|
||||||
|
UpdateZSetValue,
|
||||||
|
} from '../../wailsjs/go/services/connectionService.js'
|
||||||
|
import { ConnectionType } from '../consts/connection_type.js'
|
||||||
|
import useTabStore from './tab.js'
|
||||||
|
|
||||||
|
const separator = ':'
|
||||||
|
|
||||||
|
const useConnectionStore = defineStore('connections', {
|
||||||
|
/**
|
||||||
|
* @typedef {Object} ConnectionItem
|
||||||
|
* @property {string} key
|
||||||
|
* @property {string} label display label
|
||||||
|
* @property {string} name database name
|
||||||
|
* @property {number} type
|
||||||
|
* @property {ConnectionItem[]} children
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} DatabaseItem
|
||||||
|
* @property {string} key
|
||||||
|
* @property {string} label
|
||||||
|
* @property {string} name - server name, type != ConnectionType.Group only
|
||||||
|
* @property {number} type
|
||||||
|
* @property {number} [db] - database index, type == ConnectionType.RedisDB only
|
||||||
|
* @property {number} keys
|
||||||
|
* @property {boolean} [opened] - redis db is opened, type == ConnectionType.RedisDB only
|
||||||
|
* @property {boolean} [expanded] - current node is expanded
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns {{databases: Object<string, DatabaseItem[]>, connections: ConnectionItem[]}}
|
||||||
|
*/
|
||||||
|
state: () => ({
|
||||||
|
connections: [], // all connections
|
||||||
|
databases: {}, // all databases in opened connections group by name
|
||||||
|
}),
|
||||||
|
getters: {
|
||||||
|
anyConnectionOpened() {
|
||||||
|
return !isEmpty(this.databases)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
/**
|
||||||
|
* Load all store connections struct from local profile
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async initConnections() {
|
||||||
|
if (!isEmpty(this.connections)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const conns = []
|
||||||
|
const { data = [{ groupName: '', connections: [] }] } = await ListConnection()
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
const group = data[i]
|
||||||
|
// Top level group
|
||||||
|
if (isEmpty(group.groupName)) {
|
||||||
|
const len = size(group.connections)
|
||||||
|
for (let j = 0; j < len; j++) {
|
||||||
|
const item = group.connections[j]
|
||||||
|
conns.push({
|
||||||
|
key: item.name,
|
||||||
|
label: item.name,
|
||||||
|
name: item.name,
|
||||||
|
type: ConnectionType.Server,
|
||||||
|
// isLeaf: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Custom group
|
||||||
|
const children = []
|
||||||
|
const len = size(group.connections)
|
||||||
|
for (let j = 0; j < len; j++) {
|
||||||
|
const item = group.connections[j]
|
||||||
|
const value = group.groupName + '/' + item.name
|
||||||
|
children.push({
|
||||||
|
key: value,
|
||||||
|
label: item.name,
|
||||||
|
name: item.name,
|
||||||
|
type: ConnectionType.Server,
|
||||||
|
children: j === len - 1 ? undefined : [],
|
||||||
|
// isLeaf: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
conns.push({
|
||||||
|
key: group.groupName,
|
||||||
|
label: group.groupName,
|
||||||
|
type: ConnectionType.Group,
|
||||||
|
children,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.connections = conns
|
||||||
|
console.debug(JSON.stringify(this.connections))
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get database server by name
|
||||||
|
* @param name
|
||||||
|
* @returns {ConnectionItem|null}
|
||||||
|
*/
|
||||||
|
getConnection(name) {
|
||||||
|
const conns = this.connections
|
||||||
|
for (let i = 0; i < conns.length; i++) {
|
||||||
|
if (conns[i].type === ConnectionType.Server && conns[i].key === name) {
|
||||||
|
return conns[i]
|
||||||
|
} else if (conns[i].type === ConnectionType.Group) {
|
||||||
|
const children = conns[i].children
|
||||||
|
for (let j = 0; j < children.length; j++) {
|
||||||
|
if (children[j].type === ConnectionType.Server && conns[i].key === name) {
|
||||||
|
return children[j]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if connection is connected
|
||||||
|
* @param name
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
isConnected(name) {
|
||||||
|
let dbs = get(this.databases, name, [])
|
||||||
|
return !isEmpty(dbs)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open connection
|
||||||
|
* @param {string} name
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async openConnection(name) {
|
||||||
|
if (this.isConnected(name)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data, success, msg } = await OpenConnection(name)
|
||||||
|
if (!success) {
|
||||||
|
throw new Error(msg)
|
||||||
|
}
|
||||||
|
// append to db node to current connection
|
||||||
|
// const connNode = this.getConnection(name)
|
||||||
|
// if (connNode == null) {
|
||||||
|
// throw new Error('no such connection')
|
||||||
|
// }
|
||||||
|
const { db } = data
|
||||||
|
if (isEmpty(db)) {
|
||||||
|
throw new Error('no db loaded')
|
||||||
|
}
|
||||||
|
const dbs = []
|
||||||
|
for (let i = 0; i < db.length; i++) {
|
||||||
|
dbs.push({
|
||||||
|
key: `${name}/${db[i].name}`,
|
||||||
|
label: db[i].name,
|
||||||
|
name: name,
|
||||||
|
keys: db[i].keys,
|
||||||
|
db: i,
|
||||||
|
type: ConnectionType.RedisDB,
|
||||||
|
isLeaf: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.databases[name] = dbs
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close connection
|
||||||
|
* @param {string} name
|
||||||
|
* @returns {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
async closeConnection(name) {
|
||||||
|
const { success, msg } = await CloseConnection(name)
|
||||||
|
if (!success) {
|
||||||
|
// throw new Error(msg)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
delete this.databases[name]
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open database and load all keys
|
||||||
|
* @param connName
|
||||||
|
* @param db
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async openDatabase(connName, db) {
|
||||||
|
const { data, success, msg } = await OpenDatabase(connName, db)
|
||||||
|
if (!success) {
|
||||||
|
throw new Error(msg)
|
||||||
|
}
|
||||||
|
const { keys = [] } = data
|
||||||
|
if (isEmpty(keys)) {
|
||||||
|
const dbs = this.databases[connName]
|
||||||
|
dbs[db].children = []
|
||||||
|
dbs[db].opened = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert child to children list by order
|
||||||
|
const sortedInsertChild = (childrenList, item) => {
|
||||||
|
const insertIdx = sortedIndexBy(childrenList, item, 'key')
|
||||||
|
childrenList.splice(insertIdx, 0, item)
|
||||||
|
// childrenList.push(item)
|
||||||
|
}
|
||||||
|
// update all node item's children num
|
||||||
|
const updateChildrenNum = (node) => {
|
||||||
|
let count = 0
|
||||||
|
const totalChildren = size(node.children)
|
||||||
|
if (totalChildren > 0) {
|
||||||
|
for (const elem of node.children) {
|
||||||
|
updateChildrenNum(elem)
|
||||||
|
count += elem.keys
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
node.keys = count
|
||||||
|
// node.children = sortBy(node.children, 'label')
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyStruct = []
|
||||||
|
const mark = {}
|
||||||
|
for (const key in keys) {
|
||||||
|
const keyPart = split(key, separator)
|
||||||
|
// const prefixLen = size(keyPart) - 1
|
||||||
|
const len = size(keyPart)
|
||||||
|
let handlePath = ''
|
||||||
|
let ks = keyStruct
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
handlePath += keyPart[i]
|
||||||
|
if (i !== len - 1) {
|
||||||
|
// layer
|
||||||
|
const treeKey = `${handlePath}@${ConnectionType.RedisKey}`
|
||||||
|
if (!mark.hasOwnProperty(treeKey)) {
|
||||||
|
mark[treeKey] = {
|
||||||
|
key: `${connName}/db${db}/${treeKey}`,
|
||||||
|
label: keyPart[i],
|
||||||
|
name: connName,
|
||||||
|
db,
|
||||||
|
keys: 0,
|
||||||
|
redisKey: handlePath,
|
||||||
|
type: ConnectionType.RedisKey,
|
||||||
|
children: [],
|
||||||
|
}
|
||||||
|
sortedInsertChild(ks, mark[treeKey])
|
||||||
|
}
|
||||||
|
ks = mark[treeKey].children
|
||||||
|
handlePath += separator
|
||||||
|
} else {
|
||||||
|
// key
|
||||||
|
const treeKey = `${handlePath}@${ConnectionType.RedisValue}`
|
||||||
|
mark[treeKey] = {
|
||||||
|
key: `${connName}/db${db}/${treeKey}`,
|
||||||
|
label: keyPart[i],
|
||||||
|
name: connName,
|
||||||
|
db,
|
||||||
|
keys: 0,
|
||||||
|
redisKey: handlePath,
|
||||||
|
type: ConnectionType.RedisValue,
|
||||||
|
}
|
||||||
|
sortedInsertChild(ks, mark[treeKey])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// append db node to current connection's children
|
||||||
|
const dbs = this.databases[connName]
|
||||||
|
dbs[db].children = keyStruct
|
||||||
|
dbs[db].opened = true
|
||||||
|
updateChildrenNum(dbs[db])
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* select node
|
||||||
|
* @param key
|
||||||
|
* @param name
|
||||||
|
* @param db
|
||||||
|
* @param type
|
||||||
|
* @param redisKey
|
||||||
|
*/
|
||||||
|
select({ key, name, db, type, redisKey }) {
|
||||||
|
if (type === ConnectionType.RedisValue) {
|
||||||
|
console.log(`[click]key:${key} db: ${db} redis key: ${redisKey}`)
|
||||||
|
|
||||||
|
// async get value for key
|
||||||
|
this.loadKeyValue(name, db, redisKey).then(() => {})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* load redis key
|
||||||
|
* @param server
|
||||||
|
* @param db
|
||||||
|
* @param key
|
||||||
|
*/
|
||||||
|
async loadKeyValue(server, db, key) {
|
||||||
|
try {
|
||||||
|
const { data, success, msg } = await GetKeyValue(server, db, key)
|
||||||
|
if (success) {
|
||||||
|
const { type, ttl, value } = data
|
||||||
|
const tab = useTabStore()
|
||||||
|
tab.upsertTab({
|
||||||
|
server,
|
||||||
|
db,
|
||||||
|
type,
|
||||||
|
ttl,
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.warn('TODO: handle get key fail')
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} connName
|
||||||
|
* @param {number} db
|
||||||
|
* @param {string} key
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_addKey(connName, db, key) {
|
||||||
|
const dbs = this.databases[connName]
|
||||||
|
const dbDetail = get(dbs, db, {})
|
||||||
|
|
||||||
|
if (dbDetail == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const descendantChain = [dbDetail]
|
||||||
|
|
||||||
|
const keyPart = split(key, separator)
|
||||||
|
let redisKey = ''
|
||||||
|
const keyLen = size(keyPart)
|
||||||
|
let added = false
|
||||||
|
for (let i = 0; i < keyLen; i++) {
|
||||||
|
redisKey += keyPart[i]
|
||||||
|
|
||||||
|
const node = last(descendantChain)
|
||||||
|
const nodeList = get(node, 'children', [])
|
||||||
|
const len = size(nodeList)
|
||||||
|
const isLastKeyPart = i === keyLen - 1
|
||||||
|
for (let j = 0; j < len + 1; j++) {
|
||||||
|
const treeKey = get(nodeList[j], 'key')
|
||||||
|
const isLast = j >= len - 1
|
||||||
|
const currentKey = `${connName}/db${db}/${redisKey}@${
|
||||||
|
isLastKeyPart ? ConnectionType.RedisValue : ConnectionType.RedisKey
|
||||||
|
}`
|
||||||
|
if (treeKey > currentKey || isLast) {
|
||||||
|
// out of search range, add new item
|
||||||
|
if (isLastKeyPart) {
|
||||||
|
// key not exists, add new one
|
||||||
|
const item = {
|
||||||
|
key: currentKey,
|
||||||
|
label: keyPart[i],
|
||||||
|
name: connName,
|
||||||
|
db,
|
||||||
|
keys: 1,
|
||||||
|
redisKey,
|
||||||
|
type: ConnectionType.RedisValue,
|
||||||
|
}
|
||||||
|
if (isLast) {
|
||||||
|
nodeList.push(item)
|
||||||
|
} else {
|
||||||
|
nodeList.splice(j, 0, item)
|
||||||
|
}
|
||||||
|
added = true
|
||||||
|
} else {
|
||||||
|
// layer not exists, add new one
|
||||||
|
const item = {
|
||||||
|
key: currentKey,
|
||||||
|
label: keyPart[i],
|
||||||
|
name: connName,
|
||||||
|
db,
|
||||||
|
keys: 0,
|
||||||
|
redisKey,
|
||||||
|
type: ConnectionType.RedisKey,
|
||||||
|
children: [],
|
||||||
|
}
|
||||||
|
if (isLast) {
|
||||||
|
nodeList.push(item)
|
||||||
|
descendantChain.push(last(nodeList))
|
||||||
|
} else {
|
||||||
|
nodeList.splice(j, 0, item)
|
||||||
|
descendantChain.push(nodeList[j])
|
||||||
|
}
|
||||||
|
redisKey += separator
|
||||||
|
added = true
|
||||||
|
}
|
||||||
|
break
|
||||||
|
} else if (treeKey === currentKey) {
|
||||||
|
if (isLastKeyPart) {
|
||||||
|
// same key exists, do nothing
|
||||||
|
console.log('TODO: same key exist, do nothing now, should replace value later')
|
||||||
|
} else {
|
||||||
|
// same group exists, find into it's children
|
||||||
|
descendantChain.push(nodeList[j])
|
||||||
|
redisKey += separator
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update ancestor node's info
|
||||||
|
if (added) {
|
||||||
|
const desLen = size(descendantChain)
|
||||||
|
for (let i = 0; i < desLen; i++) {
|
||||||
|
const children = get(descendantChain[i], 'children', [])
|
||||||
|
let keys = 0
|
||||||
|
for (const child of children) {
|
||||||
|
if (child.type === ConnectionType.RedisKey) {
|
||||||
|
keys += get(child, 'keys', 1)
|
||||||
|
} else if (child.type === ConnectionType.RedisValue) {
|
||||||
|
keys += get(child, 'keys', 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
descendantChain[i].keys = keys
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set redis key
|
||||||
|
* @param {string} connName
|
||||||
|
* @param {number} db
|
||||||
|
* @param {string} key
|
||||||
|
* @param {number} keyType
|
||||||
|
* @param {any} value
|
||||||
|
* @param {number} ttl
|
||||||
|
* @returns {Promise<{[msg]: string, success: boolean}>}
|
||||||
|
*/
|
||||||
|
async setKey(connName, db, key, keyType, value, ttl) {
|
||||||
|
try {
|
||||||
|
const { data, success, msg } = await SetKeyValue(connName, db, key, keyType, value, ttl)
|
||||||
|
if (success) {
|
||||||
|
// update tree view data
|
||||||
|
this._addKey(connName, db, key)
|
||||||
|
return { success }
|
||||||
|
} else {
|
||||||
|
return { success, msg }
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return { success: false, msg: e.message }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* update hash field
|
||||||
|
* when field is set, newField is null, delete field
|
||||||
|
* when field is null, newField is set, add new field
|
||||||
|
* when both field and newField are set, and field === newField, update field
|
||||||
|
* when both field and newField are set, and field !== newField, delete field and add newField
|
||||||
|
* @param {string} connName
|
||||||
|
* @param {number} db
|
||||||
|
* @param {string} key
|
||||||
|
* @param {string} field
|
||||||
|
* @param {string} newField
|
||||||
|
* @param {string} value
|
||||||
|
* @returns {Promise<{[msg]: string, success: boolean, [updated]: {}}>}
|
||||||
|
*/
|
||||||
|
async setHash(connName, db, key, field, newField, value) {
|
||||||
|
try {
|
||||||
|
const { data, success, msg } = await SetHashValue(connName, db, key, field, newField || '', value || '')
|
||||||
|
if (success) {
|
||||||
|
const { updated = {} } = data
|
||||||
|
return { success, updated }
|
||||||
|
} else {
|
||||||
|
return { success, msg }
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return { success: false, msg: e.message }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* insert or update hash field item
|
||||||
|
* @param {string} connName
|
||||||
|
* @param {number} db
|
||||||
|
* @param {string} key
|
||||||
|
* @param {number }action 0:ignore duplicated fields 1:overwrite duplicated fields
|
||||||
|
* @param {string[]} fieldItems field1, value1, filed2, value2...
|
||||||
|
* @returns {Promise<{[msg]: string, success: boolean, [updated]: {}}>}
|
||||||
|
*/
|
||||||
|
async addHashField(connName, db, key, action, fieldItems) {
|
||||||
|
try {
|
||||||
|
const { data, success, msg } = await AddHashField(connName, db, key, action, fieldItems)
|
||||||
|
if (success) {
|
||||||
|
const { updated = {} } = data
|
||||||
|
return { success, updated }
|
||||||
|
} else {
|
||||||
|
return { success: false, msg }
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return { success: false, msg: e.message }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* remove hash field
|
||||||
|
* @param {string} connName
|
||||||
|
* @param {number} db
|
||||||
|
* @param {string} key
|
||||||
|
* @param {string} field
|
||||||
|
* @returns {Promise<{[msg]: {}, success: boolean, [removed]: string[]}>}
|
||||||
|
*/
|
||||||
|
async removeHashField(connName, db, key, field) {
|
||||||
|
try {
|
||||||
|
const { data, success, msg } = await SetHashValue(connName, db, key, field, '', '')
|
||||||
|
if (success) {
|
||||||
|
const { removed = [] } = data
|
||||||
|
return { success, removed }
|
||||||
|
} else {
|
||||||
|
return { success, msg }
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return { success: false, msg: e.message }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* insert list item
|
||||||
|
* @param {string} connName
|
||||||
|
* @param {number} db
|
||||||
|
* @param {string} key
|
||||||
|
* @param {int} action 0: push to head, 1: push to tail
|
||||||
|
* @param {string[]}values
|
||||||
|
* @returns {Promise<*|{msg, success: boolean}>}
|
||||||
|
*/
|
||||||
|
async addListItem(connName, db, key, action, values) {
|
||||||
|
try {
|
||||||
|
return AddListItem(connName, db, key, action, values)
|
||||||
|
} catch (e) {
|
||||||
|
return { success: false, msg: e.message }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* prepend item to head of list
|
||||||
|
* @param connName
|
||||||
|
* @param db
|
||||||
|
* @param key
|
||||||
|
* @param values
|
||||||
|
* @returns {Promise<[msg]: string, success: boolean, [item]: []>}
|
||||||
|
*/
|
||||||
|
async prependListItem(connName, db, key, values) {
|
||||||
|
try {
|
||||||
|
const { data, success, msg } = await AddListItem(connName, db, key, 0, values)
|
||||||
|
if (success) {
|
||||||
|
const { left = [] } = data
|
||||||
|
return { success, item: left }
|
||||||
|
} else {
|
||||||
|
return { success: false, msg }
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return { success: false, msg: e.message }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* append item to tail of list
|
||||||
|
* @param connName
|
||||||
|
* @param db
|
||||||
|
* @param key
|
||||||
|
* @param values
|
||||||
|
* @returns {Promise<[msg]: string, success: boolean, [item]: any[]>}
|
||||||
|
*/
|
||||||
|
async appendListItem(connName, db, key, values) {
|
||||||
|
try {
|
||||||
|
const { data, success, msg } = await AddListItem(connName, db, key, 1, values)
|
||||||
|
if (success) {
|
||||||
|
const { right = [] } = data
|
||||||
|
return { success, item: right }
|
||||||
|
} else {
|
||||||
|
return { success: false, msg }
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return { success: false, msg: e.message }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* update value of list item by index
|
||||||
|
* @param {string} connName
|
||||||
|
* @param {number} db
|
||||||
|
* @param {string} key
|
||||||
|
* @param {number} index
|
||||||
|
* @param {string} value
|
||||||
|
* @returns {Promise<{[msg]: string, success: boolean, [updated]: {}}>}
|
||||||
|
*/
|
||||||
|
async updateListItem(connName, db, key, index, value) {
|
||||||
|
try {
|
||||||
|
const { data, success, msg } = await SetListItem(connName, db, key, index, value)
|
||||||
|
if (success) {
|
||||||
|
const { updated = {} } = data
|
||||||
|
return { success, updated }
|
||||||
|
} else {
|
||||||
|
return { success, msg }
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return { success: false, msg: e.message }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* remove list item
|
||||||
|
* @param {string} connName
|
||||||
|
* @param {number} db
|
||||||
|
* @param {string} key
|
||||||
|
* @param {number} index
|
||||||
|
* @returns {Promise<{[msg]: string, success: boolean, [removed]: string[]}>}
|
||||||
|
*/
|
||||||
|
async removeListItem(connName, db, key, index) {
|
||||||
|
try {
|
||||||
|
const { data, success, msg } = await SetListItem(connName, db, key, index, '')
|
||||||
|
if (success) {
|
||||||
|
const { removed = [] } = data
|
||||||
|
return { success, removed }
|
||||||
|
} else {
|
||||||
|
return { success, msg }
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return { success: false, msg: e.message }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add item to set
|
||||||
|
* @param {string} connName
|
||||||
|
* @param {number} db
|
||||||
|
* @param {string} key
|
||||||
|
* @param {string} value
|
||||||
|
* @returns {Promise<{[msg]: string, success: boolean}>}
|
||||||
|
*/
|
||||||
|
async addSetItem(connName, db, key, value) {
|
||||||
|
try {
|
||||||
|
const { success, msg } = await SetSetItem(connName, db, key, false, [value])
|
||||||
|
if (success) {
|
||||||
|
return { success }
|
||||||
|
} else {
|
||||||
|
return { success, msg }
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return { success: false, msg: e.message }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* update value of set item
|
||||||
|
* @param {string} connName
|
||||||
|
* @param {number} db
|
||||||
|
* @param {string} key
|
||||||
|
* @param {string} value
|
||||||
|
* @param {string} newValue
|
||||||
|
* @returns {Promise<{[msg]: string, success: boolean}>}
|
||||||
|
*/
|
||||||
|
async updateSetItem(connName, db, key, value, newValue) {
|
||||||
|
try {
|
||||||
|
const { success, msg } = await UpdateSetItem(connName, db, key, value, newValue)
|
||||||
|
if (success) {
|
||||||
|
return { success: true }
|
||||||
|
} else {
|
||||||
|
return { success, msg }
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return { success: false, msg: e.message }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* remove item from set
|
||||||
|
* @param connName
|
||||||
|
* @param db
|
||||||
|
* @param key
|
||||||
|
* @param value
|
||||||
|
* @returns {Promise<{[msg]: string, success: boolean}>}
|
||||||
|
*/
|
||||||
|
async removeSetItem(connName, db, key, value) {
|
||||||
|
try {
|
||||||
|
const { success, msg } = await SetSetItem(connName, db, key, true, [value])
|
||||||
|
if (success) {
|
||||||
|
return { success }
|
||||||
|
} else {
|
||||||
|
return { success, msg }
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return { success: false, msg: e.message }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add item to sorted set
|
||||||
|
* @param {string} connName
|
||||||
|
* @param {number} db
|
||||||
|
* @param {string} key
|
||||||
|
* @param {number} action
|
||||||
|
* @param {Object.<string, number>} vs value: score
|
||||||
|
* @returns {Promise<{[msg]: string, success: boolean}>}
|
||||||
|
*/
|
||||||
|
async addZSetItem(connName, db, key, action, vs) {
|
||||||
|
try {
|
||||||
|
const { success, msg } = await AddZSetValue(connName, db, key, action, vs)
|
||||||
|
if (success) {
|
||||||
|
return { success }
|
||||||
|
} else {
|
||||||
|
return { success, msg }
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return { success: false, msg: e.message }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* update item of sorted set
|
||||||
|
* @param {string} connName
|
||||||
|
* @param {number} db
|
||||||
|
* @param {string} key
|
||||||
|
* @param {string} value
|
||||||
|
* @param {string} newValue
|
||||||
|
* @param {number} score
|
||||||
|
* @returns {Promise<{[msg]: string, success: boolean, [updated]: {}, [removed]: []}>}
|
||||||
|
*/
|
||||||
|
async updateZSetItem(connName, db, key, value, newValue, score) {
|
||||||
|
try {
|
||||||
|
const { data, success, msg } = await UpdateZSetValue(connName, db, key, value, newValue, score)
|
||||||
|
if (success) {
|
||||||
|
const { updated, removed } = data
|
||||||
|
return { success, updated, removed }
|
||||||
|
} else {
|
||||||
|
return { success, msg }
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return { success: false, msg: e.message }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* remove item from sorted set
|
||||||
|
* @param {string} connName
|
||||||
|
* @param {number} db
|
||||||
|
* @param key
|
||||||
|
* @param {string} value
|
||||||
|
* @returns {Promise<{[msg]: string, success: boolean, [removed]: []}>}
|
||||||
|
*/
|
||||||
|
async removeZSetItem(connName, db, key, value) {
|
||||||
|
try {
|
||||||
|
const { data, success, msg } = await UpdateZSetValue(connName, db, key, value, '', 0)
|
||||||
|
if (success) {
|
||||||
|
const { removed } = data
|
||||||
|
return { success, removed }
|
||||||
|
} else {
|
||||||
|
return { success, msg }
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return { success: false, msg: e.message }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* reset key's ttl
|
||||||
|
* @param {string} connName
|
||||||
|
* @param {number} db
|
||||||
|
* @param {string} key
|
||||||
|
* @param {number} ttl
|
||||||
|
* @returns {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
async setTTL(connName, db, key, ttl) {
|
||||||
|
try {
|
||||||
|
const { success, msg } = await SetKeyTTL(connName, db, key, ttl)
|
||||||
|
return success === true
|
||||||
|
} catch (e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} connName
|
||||||
|
* @param {number} db
|
||||||
|
* @param {string} key
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_removeKey(connName, db, key) {
|
||||||
|
const dbs = this.databases[connName]
|
||||||
|
const dbDetail = get(dbs, db, {})
|
||||||
|
|
||||||
|
if (dbDetail == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const descendantChain = [dbDetail]
|
||||||
|
const keyPart = split(key, separator)
|
||||||
|
let redisKey = ''
|
||||||
|
const keyLen = size(keyPart)
|
||||||
|
let deleted = false
|
||||||
|
let forceBreak = false
|
||||||
|
for (let i = 0; i < keyLen && !forceBreak; i++) {
|
||||||
|
redisKey += keyPart[i]
|
||||||
|
|
||||||
|
const node = last(descendantChain)
|
||||||
|
const nodeList = get(node, 'children', [])
|
||||||
|
const len = size(nodeList)
|
||||||
|
const isLastKeyPart = i === keyLen - 1
|
||||||
|
for (let j = 0; j < len; j++) {
|
||||||
|
const treeKey = get(nodeList[j], 'key')
|
||||||
|
const currentKey = `${connName}/db${db}/${redisKey}@${
|
||||||
|
isLastKeyPart ? ConnectionType.RedisValue : ConnectionType.RedisKey
|
||||||
|
}`
|
||||||
|
if (treeKey > currentKey) {
|
||||||
|
// out of search range, target not exists
|
||||||
|
forceBreak = true
|
||||||
|
break
|
||||||
|
} else if (treeKey === currentKey) {
|
||||||
|
if (isLastKeyPart) {
|
||||||
|
// find target
|
||||||
|
nodeList.splice(j, 1)
|
||||||
|
node.keys -= 1
|
||||||
|
deleted = true
|
||||||
|
forceBreak = true
|
||||||
|
} else {
|
||||||
|
// find into it's children
|
||||||
|
descendantChain.push(nodeList[j])
|
||||||
|
redisKey += separator
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (forceBreak) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// console.log(JSON.stringify(descendantChain))
|
||||||
|
|
||||||
|
// update ancestor node's info
|
||||||
|
if (deleted) {
|
||||||
|
const desLen = size(descendantChain)
|
||||||
|
for (let i = desLen - 1; i > 0; i--) {
|
||||||
|
const children = get(descendantChain[i], 'children', [])
|
||||||
|
const parent = descendantChain[i - 1]
|
||||||
|
if (isEmpty(children)) {
|
||||||
|
const parentChildren = get(parent, 'children', [])
|
||||||
|
const k = get(descendantChain[i], 'key')
|
||||||
|
remove(parentChildren, (item) => item.key === k)
|
||||||
|
}
|
||||||
|
parent.keys -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* remove redis key
|
||||||
|
* @param {string} connName
|
||||||
|
* @param {number} db
|
||||||
|
* @param {string} key
|
||||||
|
* @returns {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
async removeKey(connName, db, key) {
|
||||||
|
try {
|
||||||
|
const { data, success, msg } = await RemoveKey(connName, db, key)
|
||||||
|
if (success) {
|
||||||
|
// update tree view data
|
||||||
|
this._removeKey(connName, db, key)
|
||||||
|
|
||||||
|
// set tab content empty
|
||||||
|
const tab = useTabStore()
|
||||||
|
tab.emptyTab(connName)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* rename key
|
||||||
|
* @param {string} connName
|
||||||
|
* @param {number} db
|
||||||
|
* @param {string} key
|
||||||
|
* @param {string} newKey
|
||||||
|
* @returns {Promise<{[msg]: string, success: boolean}>}
|
||||||
|
*/
|
||||||
|
async renameKey(connName, db, key, newKey) {
|
||||||
|
const { success = false, msg } = await RenameKey(connName, db, key, newKey)
|
||||||
|
if (success) {
|
||||||
|
// delete old key and add new key struct
|
||||||
|
this._removeKey(connName, db, key)
|
||||||
|
this._addKey(connName, db, newKey)
|
||||||
|
return { success: true }
|
||||||
|
} else {
|
||||||
|
return { success: false, msg }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default useConnectionStore
|
|
@ -6,7 +6,6 @@ import {
|
||||||
AddZSetValue,
|
AddZSetValue,
|
||||||
CloseConnection,
|
CloseConnection,
|
||||||
GetKeyValue,
|
GetKeyValue,
|
||||||
ListConnection,
|
|
||||||
OpenConnection,
|
OpenConnection,
|
||||||
OpenDatabase,
|
OpenDatabase,
|
||||||
RemoveKey,
|
RemoveKey,
|
||||||
|
@ -21,12 +20,13 @@ import {
|
||||||
} from '../../wailsjs/go/services/connectionService.js'
|
} from '../../wailsjs/go/services/connectionService.js'
|
||||||
import { ConnectionType } from '../consts/connection_type.js'
|
import { ConnectionType } from '../consts/connection_type.js'
|
||||||
import useTabStore from './tab.js'
|
import useTabStore from './tab.js'
|
||||||
|
import useConnectionStore from './connections.js'
|
||||||
|
|
||||||
const separator = ':'
|
const separator = ':'
|
||||||
|
|
||||||
const useConnectionStore = defineStore('connection', {
|
const useDatabaseStore = defineStore('database', {
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} ConnectionItem
|
* @typedef {Object} DatabaseItem
|
||||||
* @property {string} key
|
* @property {string} 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
|
||||||
|
@ -40,62 +40,14 @@ const useConnectionStore = defineStore('connection', {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @returns {{connections: ConnectionItem[]}}
|
* @returns {{connections: DatabaseItem[]}}
|
||||||
*/
|
*/
|
||||||
state: () => ({
|
state: () => ({
|
||||||
connections: [], // all connections list
|
connections: [], // all connections list
|
||||||
|
databases: {}, // all database group by opened connections
|
||||||
}),
|
}),
|
||||||
getters: {},
|
getters: {},
|
||||||
actions: {
|
actions: {
|
||||||
/**
|
|
||||||
* Load all store connections struct from local profile
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
async initConnection() {
|
|
||||||
if (!isEmpty(this.connections)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const { data = [{ groupName: '', connections: [] }] } = await ListConnection()
|
|
||||||
for (let i = 0; i < data.length; i++) {
|
|
||||||
const group = data[i]
|
|
||||||
// Top level group
|
|
||||||
if (isEmpty(group.groupName)) {
|
|
||||||
for (let j = 0; j < group.connections.length; j++) {
|
|
||||||
const item = group.connections[j]
|
|
||||||
this.connections.push({
|
|
||||||
key: item.name,
|
|
||||||
label: item.name,
|
|
||||||
name: item.name,
|
|
||||||
type: ConnectionType.Server,
|
|
||||||
// isLeaf: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Custom group
|
|
||||||
const children = []
|
|
||||||
for (let j = 0; j < group.connections.length; j++) {
|
|
||||||
const item = group.connections[j]
|
|
||||||
const value = group.groupName + '/' + item.name
|
|
||||||
children.push({
|
|
||||||
key: value,
|
|
||||||
label: item.name,
|
|
||||||
name: item.name,
|
|
||||||
type: ConnectionType.Server,
|
|
||||||
children: j === group.connections.length - 1 ? undefined : [],
|
|
||||||
// isLeaf: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
this.connections.push({
|
|
||||||
key: group.groupName,
|
|
||||||
label: group.groupName,
|
|
||||||
type: ConnectionType.Group,
|
|
||||||
children,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.debug(JSON.stringify(this.connections))
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open connection
|
* Open connection
|
||||||
* @param {string} connName
|
* @param {string} connName
|
||||||
|
@ -107,7 +59,8 @@ const useConnectionStore = defineStore('connection', {
|
||||||
throw new Error(msg)
|
throw new Error(msg)
|
||||||
}
|
}
|
||||||
// append to db node to current connection
|
// append to db node to current connection
|
||||||
const connNode = this.getConnection(connName)
|
const connStore = useConnectionStore()
|
||||||
|
const connNode = connStore.getConnection(connName)
|
||||||
if (connNode == null) {
|
if (connNode == null) {
|
||||||
throw new Error('no such connection')
|
throw new Error('no such connection')
|
||||||
}
|
}
|
||||||
|
@ -901,4 +854,4 @@ const useConnectionStore = defineStore('connection', {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export default useConnectionStore
|
export default useDatabaseStore
|
|
@ -1,4 +1,4 @@
|
||||||
import { find, findIndex, isEmpty, size } from 'lodash'
|
import { find, findIndex, size } from 'lodash'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
const useTabStore = defineStore('tab', {
|
const useTabStore = defineStore('tab', {
|
||||||
|
@ -21,6 +21,8 @@ const useTabStore = defineStore('tab', {
|
||||||
* @returns {{tabList: TabItem[], activatedTab: string, activatedIndex: number}}
|
* @returns {{tabList: TabItem[], activatedTab: string, activatedIndex: number}}
|
||||||
*/
|
*/
|
||||||
state: () => ({
|
state: () => ({
|
||||||
|
nav: 'server',
|
||||||
|
asideWidth: 300,
|
||||||
tabList: [],
|
tabList: [],
|
||||||
activatedTab: '',
|
activatedTab: '',
|
||||||
activatedIndex: 0, // current activated tab index
|
activatedIndex: 0, // current activated tab index
|
||||||
|
@ -31,9 +33,9 @@ const useTabStore = defineStore('tab', {
|
||||||
* @returns {TabItem[]}
|
* @returns {TabItem[]}
|
||||||
*/
|
*/
|
||||||
tabs() {
|
tabs() {
|
||||||
if (isEmpty(this.tabList)) {
|
// if (isEmpty(this.tabList)) {
|
||||||
this.newBlankTab()
|
// this.newBlankTab()
|
||||||
}
|
// }
|
||||||
return this.tabList
|
return this.tabList
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -53,7 +55,7 @@ const useTabStore = defineStore('tab', {
|
||||||
currentTabIndex() {
|
currentTabIndex() {
|
||||||
const len = size(this.tabs)
|
const len = size(this.tabs)
|
||||||
if (this.activatedIndex < 0 || this.activatedIndex >= len) {
|
if (this.activatedIndex < 0 || this.activatedIndex >= len) {
|
||||||
this.activatedIndex = 0
|
this._setActivatedIndex(0)
|
||||||
}
|
}
|
||||||
return this.tabs[this.activatedIndex]
|
return this.tabs[this.activatedIndex]
|
||||||
},
|
},
|
||||||
|
@ -68,17 +70,22 @@ const useTabStore = defineStore('tab', {
|
||||||
title: 'new tab',
|
title: 'new tab',
|
||||||
blank: true,
|
blank: true,
|
||||||
})
|
})
|
||||||
this.activatedIndex = size(this.tabList) - 1
|
this._setActivatedIndex(size(this.tabList) - 1)
|
||||||
|
},
|
||||||
|
|
||||||
|
_setActivatedIndex(idx) {
|
||||||
|
this.activatedIndex = idx
|
||||||
|
this.nav = idx >= 0 ? 'structure' : 'server'
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* update or insert a new tab if not exists with the same name
|
* update or insert a new tab if not exists with the same name
|
||||||
* @param {string} server
|
* @param {string} server
|
||||||
* @param {number} db
|
* @param {number} [db]
|
||||||
* @param {number} type
|
* @param {number} [type]
|
||||||
* @param {number} ttl
|
* @param {number} [ttl]
|
||||||
* @param {string} key
|
* @param {string} [key]
|
||||||
* @param {*} value
|
* @param {*} [value]
|
||||||
*/
|
*/
|
||||||
upsertTab({ server, db, type, ttl, key, value }) {
|
upsertTab({ server, db, type, ttl, key, value }) {
|
||||||
let tabIndex = findIndex(this.tabList, { name: server })
|
let tabIndex = findIndex(this.tabList, { name: server })
|
||||||
|
@ -90,20 +97,21 @@ const useTabStore = defineStore('tab', {
|
||||||
type,
|
type,
|
||||||
ttl,
|
ttl,
|
||||||
key,
|
key,
|
||||||
value,
|
valu,
|
||||||
})
|
})
|
||||||
tabIndex = this.tabList.length - 1
|
tabIndex = this.tabList.length - 1
|
||||||
}
|
}
|
||||||
const tab = this.tabList[tabIndex]
|
const tab = this.tabList[tabIndex]
|
||||||
tab.blank = false
|
tab.blank = false
|
||||||
tab.title = `${server}/db${db}`
|
// tab.title = db !== undefined ? `${server}/db${db}` : `${server}`
|
||||||
|
tab.title = server
|
||||||
tab.server = server
|
tab.server = server
|
||||||
tab.db = db
|
tab.db = db
|
||||||
tab.type = type
|
tab.type = type
|
||||||
tab.ttl = ttl
|
tab.ttl = ttl
|
||||||
tab.key = key
|
tab.key = key
|
||||||
tab.value = value
|
tab.value = value
|
||||||
this.activatedIndex = tabIndex
|
this._setActivatedIndex(tabIndex)
|
||||||
// this.activatedTab = tab.name
|
// this.activatedTab = tab.name
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -149,23 +157,27 @@ const useTabStore = defineStore('tab', {
|
||||||
const len = size(this.tabs)
|
const len = size(this.tabs)
|
||||||
// ignore remove last blank tab
|
// ignore remove last blank tab
|
||||||
if (len === 1 && this.tabs[0].blank) {
|
if (len === 1 && this.tabs[0].blank) {
|
||||||
return
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tabIndex < 0 || tabIndex >= len) {
|
if (tabIndex < 0 || tabIndex >= len) {
|
||||||
return
|
return null
|
||||||
}
|
}
|
||||||
this.tabList.splice(tabIndex, 1)
|
const removed = this.tabList.splice(tabIndex, 1)
|
||||||
|
|
||||||
if (len === 1) {
|
// update select index if removed index equal current selected
|
||||||
this.newBlankTab()
|
this.activatedIndex -= 1
|
||||||
} else {
|
if (this.activatedIndex < 0) {
|
||||||
// Update select index if removed index equal current selected
|
if (this.tabList.length > 0) {
|
||||||
this.activatedIndex -= 1
|
this._setActivatedIndex(0)
|
||||||
if (this.activatedIndex < 0 && this.tabList.length > 0) {
|
} else {
|
||||||
this.activatedIndex = 0
|
this._setActivatedIndex(-1)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this._setActivatedIndex(this.activatedIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return size(removed) > 0 ? removed[0] : null
|
||||||
},
|
},
|
||||||
removeTabByName(tabName) {
|
removeTabByName(tabName) {
|
||||||
const idx = findIndex(this.tabs, { name: tabName })
|
const idx = findIndex(this.tabs, { name: tabName })
|
||||||
|
|
|
@ -104,6 +104,6 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
.context-menu-item {
|
.context-menu-item {
|
||||||
min-width: 120px;
|
min-width: 100px;
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue