Add drag and drop to resort connection list
Add filter search connection
This commit is contained in:
parent
d573cab0c0
commit
0f29a3c34f
|
@ -119,6 +119,17 @@ func (c *connectionService) RemoveConnection(name string) (resp types.JSResp) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SaveSortedConnection save sorted connection after drag
|
||||||
|
func (c *connectionService) SaveSortedConnection(sortedConns types.Connections) (resp types.JSResp) {
|
||||||
|
err := c.conns.SaveSortedConnection(sortedConns)
|
||||||
|
if err != nil {
|
||||||
|
resp.Msg = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp.Success = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// CreateGroup create new group
|
// CreateGroup create new group
|
||||||
func (c *connectionService) CreateGroup(name string) (resp types.JSResp) {
|
func (c *connectionService) CreateGroup(name string) (resp types.JSResp) {
|
||||||
err := c.conns.CreateGroup(name)
|
err := c.conns.CreateGroup(name)
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"sync"
|
"sync"
|
||||||
"tinyrdm/backend/types"
|
"tinyrdm/backend/types"
|
||||||
|
sliceutil "tinyrdm/backend/utils/slice"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConnectionsStorage struct {
|
type ConnectionsStorage struct {
|
||||||
|
@ -240,6 +241,47 @@ func (c *ConnectionsStorage) RemoveConnection(name string) error {
|
||||||
return c.saveConnections(conns)
|
return c.saveConnections(conns)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SaveSortedConnection save connection after sort
|
||||||
|
func (c *ConnectionsStorage) SaveSortedConnection(sortedConns types.Connections) error {
|
||||||
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
|
conns := c.GetConnectionsFlat()
|
||||||
|
takeConn := func(name string) (types.Connection, bool) {
|
||||||
|
idx, ok := sliceutil.Find(conns, func(i int) bool {
|
||||||
|
return conns[i].Name == name
|
||||||
|
})
|
||||||
|
if ok {
|
||||||
|
ret := conns[idx]
|
||||||
|
conns = append(conns[:idx], conns[idx+1:]...)
|
||||||
|
return ret, true
|
||||||
|
}
|
||||||
|
return types.Connection{}, false
|
||||||
|
}
|
||||||
|
var replaceConn func(connections types.Connections) types.Connections
|
||||||
|
replaceConn = func(cons types.Connections) types.Connections {
|
||||||
|
var newConns types.Connections
|
||||||
|
for _, conn := range cons {
|
||||||
|
if conn.Type == "group" {
|
||||||
|
newConns = append(newConns, types.Connection{
|
||||||
|
ConnectionConfig: types.ConnectionConfig{
|
||||||
|
Name: conn.Name,
|
||||||
|
},
|
||||||
|
Type: "group",
|
||||||
|
Connections: replaceConn(conn.Connections),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if foundConn, ok := takeConn(conn.Name); ok {
|
||||||
|
newConns = append(newConns, foundConn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newConns
|
||||||
|
}
|
||||||
|
conns = replaceConn(sortedConns)
|
||||||
|
return c.saveConnections(conns)
|
||||||
|
}
|
||||||
|
|
||||||
// CreateGroup create new group
|
// CreateGroup create new group
|
||||||
func (c *ConnectionsStorage) CreateGroup(name string) error {
|
func (c *ConnectionsStorage) CreateGroup(name string) error {
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
|
|
|
@ -4,7 +4,7 @@ type ConnectionCategory int
|
||||||
|
|
||||||
type ConnectionConfig struct {
|
type ConnectionConfig struct {
|
||||||
Name string `json:"name" yaml:"name"`
|
Name string `json:"name" yaml:"name"`
|
||||||
Group string `json:"group" yaml:"-"`
|
Group string `json:"group,omitempty" yaml:"-"`
|
||||||
Addr string `json:"addr,omitempty" yaml:"addr,omitempty"`
|
Addr string `json:"addr,omitempty" yaml:"addr,omitempty"`
|
||||||
Port int `json:"port,omitempty" yaml:"port,omitempty"`
|
Port int `json:"port,omitempty" yaml:"port,omitempty"`
|
||||||
Username string `json:"username,omitempty" yaml:"username,omitempty"`
|
Username string `json:"username,omitempty" yaml:"username,omitempty"`
|
||||||
|
|
|
@ -9,13 +9,11 @@ import Filter from '../icons/Filter.vue'
|
||||||
import ConnectionTree from './ConnectionTree.vue'
|
import ConnectionTree from './ConnectionTree.vue'
|
||||||
import Unlink from '../icons/Unlink.vue'
|
import Unlink from '../icons/Unlink.vue'
|
||||||
import useConnectionStore from '../../stores/connections.js'
|
import useConnectionStore from '../../stores/connections.js'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
const dialogStore = useDialogStore()
|
const dialogStore = useDialogStore()
|
||||||
const connectionStore = useConnectionStore()
|
const connectionStore = useConnectionStore()
|
||||||
|
const filterPattern = ref('')
|
||||||
const onSort = () => {
|
|
||||||
dialogStore.openPreferencesDialog()
|
|
||||||
}
|
|
||||||
|
|
||||||
const onDisconnectAll = () => {
|
const onDisconnectAll = () => {
|
||||||
connectionStore.closeAllConnection()
|
connectionStore.closeAllConnection()
|
||||||
|
@ -24,7 +22,7 @@ const onDisconnectAll = () => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="nav-pane-container flex-box-v">
|
<div class="nav-pane-container flex-box-v">
|
||||||
<connection-tree />
|
<connection-tree :filter-pattern="filterPattern" />
|
||||||
|
|
||||||
<!-- bottom function bar -->
|
<!-- bottom function bar -->
|
||||||
<div class="nav-pane-bottom flex-box-h">
|
<div class="nav-pane-bottom flex-box-h">
|
||||||
|
@ -53,9 +51,7 @@ const onDisconnectAll = () => {
|
||||||
t-tooltip="disconnect_all"
|
t-tooltip="disconnect_all"
|
||||||
@click="onDisconnectAll"
|
@click="onDisconnectAll"
|
||||||
/>
|
/>
|
||||||
<n-divider style="margin: 0 4px; --n-color: #aaa; width: 2px" vertical />
|
<n-input v-model:value="filterPattern" :placeholder="$t('filter')" clearable>
|
||||||
<icon-button :icon="Sort" color="#555" size="20" stroke-width="4" t-tooltip="sort_conn" @click="onSort" />
|
|
||||||
<n-input placeholder="">
|
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<n-icon :component="Filter" color="#aaa" size="20" />
|
<n-icon :component="Filter" color="#aaa" size="20" />
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { NIcon, useDialog, useMessage } from 'naive-ui'
|
||||||
import { ConnectionType } from '../../consts/connection_type.js'
|
import { ConnectionType } from '../../consts/connection_type.js'
|
||||||
import ToggleFolder from '../icons/ToggleFolder.vue'
|
import ToggleFolder from '../icons/ToggleFolder.vue'
|
||||||
import ToggleServer from '../icons/ToggleServer.vue'
|
import ToggleServer from '../icons/ToggleServer.vue'
|
||||||
import { indexOf } from 'lodash'
|
import { debounce, indexOf, throttle } from 'lodash'
|
||||||
import Config from '../icons/Config.vue'
|
import Config from '../icons/Config.vue'
|
||||||
import Delete from '../icons/Delete.vue'
|
import Delete from '../icons/Delete.vue'
|
||||||
import Unlink from '../icons/Unlink.vue'
|
import Unlink from '../icons/Unlink.vue'
|
||||||
|
@ -28,6 +28,12 @@ const message = useMessage()
|
||||||
const expandedKeys = ref([])
|
const expandedKeys = ref([])
|
||||||
const selectedKeys = ref([])
|
const selectedKeys = ref([])
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
filterPattern: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
loadingConnection.value = true
|
loadingConnection.value = true
|
||||||
|
@ -152,11 +158,11 @@ const renderPrefix = ({ option }) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onUpdateExpandedKeys = (keys, option, meta) => {
|
const onUpdateExpandedKeys = (keys, option) => {
|
||||||
expandedKeys.value = keys
|
expandedKeys.value = keys
|
||||||
}
|
}
|
||||||
|
|
||||||
const onUpdateSelectedKeys = (keys, option, meta) => {
|
const onUpdateSelectedKeys = (keys, option) => {
|
||||||
selectedKeys.value = keys
|
selectedKeys.value = keys
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,6 +265,56 @@ const handleSelectContextMenu = (key) => {
|
||||||
}
|
}
|
||||||
console.warn('TODO: handle context menu:' + key)
|
console.warn('TODO: handle context menu:' + key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const findSiblingsAndIndex = (node, nodes) => {
|
||||||
|
if (!nodes) {
|
||||||
|
return [null, null]
|
||||||
|
}
|
||||||
|
for (let i = 0; i < nodes.length; ++i) {
|
||||||
|
const siblingNode = nodes[i]
|
||||||
|
if (siblingNode.key === node.key) {
|
||||||
|
return [nodes, i]
|
||||||
|
}
|
||||||
|
const [siblings, index] = findSiblingsAndIndex(node, siblingNode.children)
|
||||||
|
if (siblings && index !== null) {
|
||||||
|
return [siblings, index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [null, null]
|
||||||
|
}
|
||||||
|
|
||||||
|
// delay save until stop drop after 2 seconds
|
||||||
|
const saveSort = debounce(connectionStore.saveConnectionSort, 2000, { trailing: true })
|
||||||
|
const handleDrop = ({ node, dragNode, dropPosition }) => {
|
||||||
|
const [dragNodeSiblings, dragNodeIndex] = findSiblingsAndIndex(dragNode, connectionStore.connections)
|
||||||
|
if (dragNodeSiblings === null || dragNodeIndex === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dragNodeSiblings.splice(dragNodeIndex, 1)
|
||||||
|
if (dropPosition === 'inside') {
|
||||||
|
if (node.children) {
|
||||||
|
node.children.unshift(dragNode)
|
||||||
|
} else {
|
||||||
|
node.children = [dragNode]
|
||||||
|
}
|
||||||
|
} else if (dropPosition === 'before') {
|
||||||
|
const [nodeSiblings, nodeIndex] = findSiblingsAndIndex(node, connectionStore.connections)
|
||||||
|
if (nodeSiblings === null || nodeIndex === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nodeSiblings.splice(nodeIndex, 0, dragNode)
|
||||||
|
} else if (dropPosition === 'after') {
|
||||||
|
const [nodeSiblings, nodeIndex] = findSiblingsAndIndex(node, connectionStore.connections)
|
||||||
|
if (nodeSiblings === null || nodeIndex === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nodeSiblings.splice(nodeIndex + 1, 0, dragNode)
|
||||||
|
}
|
||||||
|
connectionStore.connections = Array.from(connectionStore.connections)
|
||||||
|
saveSort()
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveDrop = () => {}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -267,15 +323,18 @@ const handleSelectContextMenu = (key) => {
|
||||||
:block-line="true"
|
:block-line="true"
|
||||||
:block-node="true"
|
:block-node="true"
|
||||||
:cancelable="false"
|
:cancelable="false"
|
||||||
|
:draggable="true"
|
||||||
:data="connectionStore.connections"
|
:data="connectionStore.connections"
|
||||||
:expand-on-click="true"
|
:expand-on-click="true"
|
||||||
:expanded-keys="expandedKeys"
|
:expanded-keys="expandedKeys"
|
||||||
:on-update:selected-keys="onUpdateSelectedKeys"
|
@update:selected-keys="onUpdateSelectedKeys"
|
||||||
:node-props="nodeProps"
|
:node-props="nodeProps"
|
||||||
:on-update:expanded-keys="onUpdateExpandedKeys"
|
@update:expanded-keys="onUpdateExpandedKeys"
|
||||||
:selected-keys="selectedKeys"
|
:selected-keys="selectedKeys"
|
||||||
:render-label="renderLabel"
|
:render-label="renderLabel"
|
||||||
:render-prefix="renderPrefix"
|
:render-prefix="renderPrefix"
|
||||||
|
@drop="handleDrop"
|
||||||
|
:pattern="props.filterPattern"
|
||||||
class="fill-height"
|
class="fill-height"
|
||||||
virtual-scroll
|
virtual-scroll
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -142,7 +142,7 @@ const expandKey = (key) => {
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const onUpdateExpanded = (value, option, meta) => {
|
const onUpdateExpanded = (value, option) => {
|
||||||
expandedKeys.value = value
|
expandedKeys.value = value
|
||||||
if (!meta.node) {
|
if (!meta.node) {
|
||||||
return
|
return
|
||||||
|
@ -158,7 +158,7 @@ const onUpdateExpanded = (value, option, meta) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onUpdateSelectedKeys = (keys, option, meta) => {
|
const onUpdateSelectedKeys = (keys, option) => {
|
||||||
selectedKeys.value = keys
|
selectedKeys.value = keys
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -325,10 +325,10 @@ const handleOutsideContextMenu = () => {
|
||||||
:expand-on-click="false"
|
:expand-on-click="false"
|
||||||
:expanded-keys="expandedKeys"
|
:expanded-keys="expandedKeys"
|
||||||
:selected-keys="selectedKeys"
|
:selected-keys="selectedKeys"
|
||||||
:on-update:selected-keys="onUpdateSelectedKeys"
|
@update:selected-keys="onUpdateSelectedKeys"
|
||||||
:node-props="nodeProps"
|
:node-props="nodeProps"
|
||||||
:on-load="onLoadTree"
|
@load="onLoadTree"
|
||||||
:on-update:expanded-keys="onUpdateExpanded"
|
@update:expanded-keys="onUpdateExpanded"
|
||||||
:render-label="renderLabel"
|
:render-label="renderLabel"
|
||||||
:render-prefix="renderPrefix"
|
:render-prefix="renderPrefix"
|
||||||
:render-suffix="renderSuffix"
|
:render-suffix="renderSuffix"
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
"new_group": "Add New Group",
|
"new_group": "Add New Group",
|
||||||
"rename_group": "Rename Group",
|
"rename_group": "Rename Group",
|
||||||
"disconnect_all": "Disconnect all connections",
|
"disconnect_all": "Disconnect all connections",
|
||||||
|
"filter": "Filter",
|
||||||
"sort_conn": "Resort Connections",
|
"sort_conn": "Resort Connections",
|
||||||
"reload_key": "Reload Current Key",
|
"reload_key": "Reload Current Key",
|
||||||
"close_confirm": "Confirm close this tab and connection",
|
"close_confirm": "Confirm close this tab and connection",
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
"new_group": "添加新分组",
|
"new_group": "添加新分组",
|
||||||
"rename_group": "重命名分组",
|
"rename_group": "重命名分组",
|
||||||
"disconnect_all": "断开所有连接",
|
"disconnect_all": "断开所有连接",
|
||||||
|
"filter": "筛选",
|
||||||
"sort_conn": "调整连接顺序",
|
"sort_conn": "调整连接顺序",
|
||||||
"reload_key": "重新载入此键内容",
|
"reload_key": "重新载入此键内容",
|
||||||
"close_confirm": "是否关闭当前连接",
|
"close_confirm": "是否关闭当前连接",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { get, isEmpty, last, remove, size, sortedIndexBy, split, uniq } from 'lodash'
|
import { get, isEmpty, last, map, remove, size, sortedIndexBy, split, uniq } from 'lodash'
|
||||||
import {
|
import {
|
||||||
AddHashField,
|
AddHashField,
|
||||||
AddListItem,
|
AddListItem,
|
||||||
|
@ -17,6 +17,7 @@ import {
|
||||||
RenameGroup,
|
RenameGroup,
|
||||||
RenameKey,
|
RenameKey,
|
||||||
SaveConnection,
|
SaveConnection,
|
||||||
|
SaveSortedConnection,
|
||||||
SetHashValue,
|
SetHashValue,
|
||||||
SetKeyTTL,
|
SetKeyTTL,
|
||||||
SetKeyValue,
|
SetKeyValue,
|
||||||
|
@ -176,7 +177,7 @@ const useConnectionStore = defineStore('connections', {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new connection or update current connection profile
|
* create a new connection or update current connection profile
|
||||||
* @param {string} name set null if create a new connection
|
* @param {string} name set null if create a new connection
|
||||||
* @param {{}} param
|
* @param {{}} param
|
||||||
* @returns {Promise<{success: boolean, [msg]: string}>}
|
* @returns {Promise<{success: boolean, [msg]: string}>}
|
||||||
|
@ -193,7 +194,34 @@ const useConnectionStore = defineStore('connections', {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if connection is connected
|
* save connection
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async saveConnectionSort() {
|
||||||
|
const mapToList = (conns) => {
|
||||||
|
const list = []
|
||||||
|
for (const conn of conns) {
|
||||||
|
if (conn.type === ConnectionType.Group) {
|
||||||
|
const children = mapToList(conn.children)
|
||||||
|
list.push({
|
||||||
|
name: conn.label,
|
||||||
|
type: 'group',
|
||||||
|
connections: children,
|
||||||
|
})
|
||||||
|
} else if (conn.type === ConnectionType.Server) {
|
||||||
|
list.push({
|
||||||
|
name: conn.name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
const s = mapToList(this.connections)
|
||||||
|
SaveSortedConnection(s)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* check if connection is connected
|
||||||
* @param name
|
* @param name
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
|
@ -203,7 +231,7 @@ const useConnectionStore = defineStore('connections', {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open connection
|
* open connection
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
|
@ -241,7 +269,7 @@ const useConnectionStore = defineStore('connections', {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close connection
|
* close connection
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
* @returns {Promise<boolean>}
|
* @returns {Promise<boolean>}
|
||||||
*/
|
*/
|
||||||
|
@ -260,7 +288,7 @@ const useConnectionStore = defineStore('connections', {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close all connection
|
* close all connection
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async closeAllConnection() {
|
async closeAllConnection() {
|
||||||
|
@ -274,7 +302,7 @@ const useConnectionStore = defineStore('connections', {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove connection
|
* remove connection
|
||||||
* @param name
|
* @param name
|
||||||
* @returns {Promise<{success: boolean, [msg]: string}>}
|
* @returns {Promise<{success: boolean, [msg]: string}>}
|
||||||
*/
|
*/
|
||||||
|
@ -290,7 +318,7 @@ const useConnectionStore = defineStore('connections', {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create connection group
|
* create connection group
|
||||||
* @param name
|
* @param name
|
||||||
* @returns {Promise<{success: boolean, [msg]: string}>}
|
* @returns {Promise<{success: boolean, [msg]: string}>}
|
||||||
*/
|
*/
|
||||||
|
@ -304,7 +332,7 @@ const useConnectionStore = defineStore('connections', {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rename connection group
|
* rename connection group
|
||||||
* @param name
|
* @param name
|
||||||
* @param newName
|
* @param newName
|
||||||
* @returns {Promise<{success: boolean, [msg]: string}>}
|
* @returns {Promise<{success: boolean, [msg]: string}>}
|
||||||
|
@ -322,7 +350,7 @@ const useConnectionStore = defineStore('connections', {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove group by name
|
* remove group by name
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
* @param {boolean} [includeConn]
|
* @param {boolean} [includeConn]
|
||||||
* @returns {Promise<{success: boolean, [msg]: string}>}
|
* @returns {Promise<{success: boolean, [msg]: string}>}
|
||||||
|
@ -337,7 +365,7 @@ const useConnectionStore = defineStore('connections', {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open database and load all keys
|
* open database and load all keys
|
||||||
* @param connName
|
* @param connName
|
||||||
* @param db
|
* @param db
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
|
|
Loading…
Reference in New Issue