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
|
||||
}
|
||||
|
||||
// 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
|
||||
func (c *connectionService) CreateGroup(name string) (resp types.JSResp) {
|
||||
err := c.conns.CreateGroup(name)
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"gopkg.in/yaml.v3"
|
||||
"sync"
|
||||
"tinyrdm/backend/types"
|
||||
sliceutil "tinyrdm/backend/utils/slice"
|
||||
)
|
||||
|
||||
type ConnectionsStorage struct {
|
||||
|
@ -240,6 +241,47 @@ func (c *ConnectionsStorage) RemoveConnection(name string) error {
|
|||
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
|
||||
func (c *ConnectionsStorage) CreateGroup(name string) error {
|
||||
c.mutex.Lock()
|
||||
|
|
|
@ -4,7 +4,7 @@ type ConnectionCategory int
|
|||
|
||||
type ConnectionConfig struct {
|
||||
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"`
|
||||
Port int `json:"port,omitempty" yaml:"port,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 Unlink from '../icons/Unlink.vue'
|
||||
import useConnectionStore from '../../stores/connections.js'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const dialogStore = useDialogStore()
|
||||
const connectionStore = useConnectionStore()
|
||||
|
||||
const onSort = () => {
|
||||
dialogStore.openPreferencesDialog()
|
||||
}
|
||||
const filterPattern = ref('')
|
||||
|
||||
const onDisconnectAll = () => {
|
||||
connectionStore.closeAllConnection()
|
||||
|
@ -24,7 +22,7 @@ const onDisconnectAll = () => {
|
|||
|
||||
<template>
|
||||
<div class="nav-pane-container flex-box-v">
|
||||
<connection-tree />
|
||||
<connection-tree :filter-pattern="filterPattern" />
|
||||
|
||||
<!-- bottom function bar -->
|
||||
<div class="nav-pane-bottom flex-box-h">
|
||||
|
@ -53,9 +51,7 @@ const onDisconnectAll = () => {
|
|||
t-tooltip="disconnect_all"
|
||||
@click="onDisconnectAll"
|
||||
/>
|
||||
<n-divider style="margin: 0 4px; --n-color: #aaa; width: 2px" vertical />
|
||||
<icon-button :icon="Sort" color="#555" size="20" stroke-width="4" t-tooltip="sort_conn" @click="onSort" />
|
||||
<n-input placeholder="">
|
||||
<n-input v-model:value="filterPattern" :placeholder="$t('filter')" clearable>
|
||||
<template #prefix>
|
||||
<n-icon :component="Filter" color="#aaa" size="20" />
|
||||
</template>
|
||||
|
|
|
@ -6,7 +6,7 @@ import { NIcon, useDialog, 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 { debounce, indexOf, throttle } from 'lodash'
|
||||
import Config from '../icons/Config.vue'
|
||||
import Delete from '../icons/Delete.vue'
|
||||
import Unlink from '../icons/Unlink.vue'
|
||||
|
@ -28,6 +28,12 @@ const message = useMessage()
|
|||
const expandedKeys = ref([])
|
||||
const selectedKeys = ref([])
|
||||
|
||||
const props = defineProps({
|
||||
filterPattern: {
|
||||
type: String,
|
||||
},
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
loadingConnection.value = true
|
||||
|
@ -152,11 +158,11 @@ const renderPrefix = ({ option }) => {
|
|||
}
|
||||
}
|
||||
|
||||
const onUpdateExpandedKeys = (keys, option, meta) => {
|
||||
const onUpdateExpandedKeys = (keys, option) => {
|
||||
expandedKeys.value = keys
|
||||
}
|
||||
|
||||
const onUpdateSelectedKeys = (keys, option, meta) => {
|
||||
const onUpdateSelectedKeys = (keys, option) => {
|
||||
selectedKeys.value = keys
|
||||
}
|
||||
|
||||
|
@ -259,6 +265,56 @@ const handleSelectContextMenu = (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>
|
||||
|
||||
<template>
|
||||
|
@ -267,15 +323,18 @@ const handleSelectContextMenu = (key) => {
|
|||
:block-line="true"
|
||||
:block-node="true"
|
||||
:cancelable="false"
|
||||
:draggable="true"
|
||||
:data="connectionStore.connections"
|
||||
:expand-on-click="true"
|
||||
:expanded-keys="expandedKeys"
|
||||
:on-update:selected-keys="onUpdateSelectedKeys"
|
||||
@update:selected-keys="onUpdateSelectedKeys"
|
||||
:node-props="nodeProps"
|
||||
:on-update:expanded-keys="onUpdateExpandedKeys"
|
||||
@update:expanded-keys="onUpdateExpandedKeys"
|
||||
:selected-keys="selectedKeys"
|
||||
:render-label="renderLabel"
|
||||
:render-prefix="renderPrefix"
|
||||
@drop="handleDrop"
|
||||
:pattern="props.filterPattern"
|
||||
class="fill-height"
|
||||
virtual-scroll
|
||||
/>
|
||||
|
|
|
@ -142,7 +142,7 @@ const expandKey = (key) => {
|
|||
|
||||
const message = useMessage()
|
||||
const dialog = useDialog()
|
||||
const onUpdateExpanded = (value, option, meta) => {
|
||||
const onUpdateExpanded = (value, option) => {
|
||||
expandedKeys.value = value
|
||||
if (!meta.node) {
|
||||
return
|
||||
|
@ -158,7 +158,7 @@ const onUpdateExpanded = (value, option, meta) => {
|
|||
}
|
||||
}
|
||||
|
||||
const onUpdateSelectedKeys = (keys, option, meta) => {
|
||||
const onUpdateSelectedKeys = (keys, option) => {
|
||||
selectedKeys.value = keys
|
||||
}
|
||||
|
||||
|
@ -325,10 +325,10 @@ const handleOutsideContextMenu = () => {
|
|||
:expand-on-click="false"
|
||||
:expanded-keys="expandedKeys"
|
||||
:selected-keys="selectedKeys"
|
||||
:on-update:selected-keys="onUpdateSelectedKeys"
|
||||
@update:selected-keys="onUpdateSelectedKeys"
|
||||
:node-props="nodeProps"
|
||||
:on-load="onLoadTree"
|
||||
:on-update:expanded-keys="onUpdateExpanded"
|
||||
@load="onLoadTree"
|
||||
@update:expanded-keys="onUpdateExpanded"
|
||||
:render-label="renderLabel"
|
||||
:render-prefix="renderPrefix"
|
||||
:render-suffix="renderSuffix"
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
"new_group": "Add New Group",
|
||||
"rename_group": "Rename Group",
|
||||
"disconnect_all": "Disconnect all connections",
|
||||
"filter": "Filter",
|
||||
"sort_conn": "Resort Connections",
|
||||
"reload_key": "Reload Current Key",
|
||||
"close_confirm": "Confirm close this tab and connection",
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
"new_group": "添加新分组",
|
||||
"rename_group": "重命名分组",
|
||||
"disconnect_all": "断开所有连接",
|
||||
"filter": "筛选",
|
||||
"sort_conn": "调整连接顺序",
|
||||
"reload_key": "重新载入此键内容",
|
||||
"close_confirm": "是否关闭当前连接",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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 {
|
||||
AddHashField,
|
||||
AddListItem,
|
||||
|
@ -17,6 +17,7 @@ import {
|
|||
RenameGroup,
|
||||
RenameKey,
|
||||
SaveConnection,
|
||||
SaveSortedConnection,
|
||||
SetHashValue,
|
||||
SetKeyTTL,
|
||||
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 {{}} param
|
||||
* @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
|
||||
* @returns {boolean}
|
||||
*/
|
||||
|
@ -203,7 +231,7 @@ const useConnectionStore = defineStore('connections', {
|
|||
},
|
||||
|
||||
/**
|
||||
* Open connection
|
||||
* open connection
|
||||
* @param {string} name
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
|
@ -241,7 +269,7 @@ const useConnectionStore = defineStore('connections', {
|
|||
},
|
||||
|
||||
/**
|
||||
* Close connection
|
||||
* close connection
|
||||
* @param {string} name
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
|
@ -260,7 +288,7 @@ const useConnectionStore = defineStore('connections', {
|
|||
},
|
||||
|
||||
/**
|
||||
* Close all connection
|
||||
* close all connection
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async closeAllConnection() {
|
||||
|
@ -274,7 +302,7 @@ const useConnectionStore = defineStore('connections', {
|
|||
},
|
||||
|
||||
/**
|
||||
* Remove connection
|
||||
* remove connection
|
||||
* @param name
|
||||
* @returns {Promise<{success: boolean, [msg]: string}>}
|
||||
*/
|
||||
|
@ -290,7 +318,7 @@ const useConnectionStore = defineStore('connections', {
|
|||
},
|
||||
|
||||
/**
|
||||
* Create connection group
|
||||
* create connection group
|
||||
* @param name
|
||||
* @returns {Promise<{success: boolean, [msg]: string}>}
|
||||
*/
|
||||
|
@ -304,7 +332,7 @@ const useConnectionStore = defineStore('connections', {
|
|||
},
|
||||
|
||||
/**
|
||||
* Rename connection group
|
||||
* rename connection group
|
||||
* @param name
|
||||
* @param newName
|
||||
* @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 {boolean} [includeConn]
|
||||
* @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 db
|
||||
* @returns {Promise<void>}
|
||||
|
|
Loading…
Reference in New Issue