tiny-rdm/frontend/src/stores/tab.js

826 lines
27 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { assign, find, findIndex, get, includes, indexOf, isEmpty, pullAt, remove, set, size } from 'lodash'
import { defineStore } from 'pinia'
import { TabItem } from '@/objects/tabItem.js'
import useBrowserStore from 'stores/browser.js'
import { i18nGlobal } from '@/utils/i18n.js'
const useTabStore = defineStore('tab', {
/**
* @typedef {Object} ListEntryItem
* @property {string|number[]} v value
* @property {string} [dv] display value
*/
/**
* @typedef {Object} ListReplaceItem
* @property {number} index
* @property {string|number[]} v value
* @property {string} [dv] display value
*/
/**
* @typedef {Object} HashEntryItem
* @property {string} k field name
* @property {string|number[]} v value
* @property {string} [dv] display value
*/
/**
* @typedef {Object} HashReplaceItem
* @property {string|number[]} k field name
* @property {string|number[]} nk new field name
* @property {string|number[]} v value
* @property {string} [dv] display value
*/
/**
* @typedef {Object} SetEntryItem
* @property {string|number[]} v value
* @property {string} [dv] display value
*/
/**
* @typedef {Object} ZSetEntryItem
* @property {number} s score
* @property {string|number[]} v value
* @property {string} [dv] display value
*/
/**
* @typedef {Object} ZSetReplaceItem
* @property {number} s score
* @property {string|number[]} v value
* @property {string|number[]} nv new value
* @property {string} [dv] display value
*/
/**
* @typedef {Object} StreamEntryItem
* @property {string} id
* @property {Object.<string, *>} v value
* @property {string} [dv] display value
*/
/**
* @typedef {Object} TabState
* @property {string} nav
* @property {number} asideWidth
* @property {TabItem[]} tabList
* @property {number} activatedIndex
*/
/**
*
* @returns {TabState}
*/
state: () => ({
nav: 'server',
asideWidth: 300,
tabList: [],
activatedIndex: 0, // current activated tab index
}),
getters: {
/**
* get current tab list item
* @returns {TabItem[]}
*/
tabs() {
// if (isEmpty(this.tabList)) {
// this.newBlankTab()
// }
return this.tabList
},
/**
* get current activated tab item
* @returns {TabItem|null}
*/
currentTab() {
return get(this.tabs, this.activatedIndex)
},
currentTabName() {
return get(this.tabs, [this.activatedIndex, 'name'])
},
currentCheckedKeys() {
const tab = this.currentTab
return get(tab, 'checkedKeys', [])
},
},
actions: {
/**
*
* @param idx
* @param {boolean} [switchNav]
* @param {string} [subTab]
* @private
*/
_setActivatedIndex(idx, switchNav, subTab) {
this.activatedIndex = idx
if (switchNav === true) {
this.nav = idx >= 0 ? 'browser' : 'server'
if (!isEmpty(subTab)) {
set(this.tabList, [idx, 'subTab'], subTab)
}
} else {
if (idx < 0) {
this.nav = 'server'
}
}
},
openBlank(server) {
this.upsertTab({ server, clearValue: true })
},
/**
*
* @param {string} tabName
*/
closeTab(tabName) {
$dialog.warning(i18nGlobal.t('dialogue.close_confirm', { name: tabName }), () => {
const browserStore = useBrowserStore()
browserStore.closeConnection(tabName)
})
},
/**
* update or insert a new tab if not exists with the same name
* @param {string} subTab
* @param {string} server
* @param {number} [db]
* @param {number} [type]
* @param {number} [ttl]
* @param {string} [key]
* @param {string} [keyCode]
* @param {number} [size]
* @param {number} [length]
* @param {string} [matchPattern]
* @param {boolean} [clearValue]
* @param {string} format
* @param {string} decode
* @param {*} [value]
*/
upsertTab({
subTab,
server,
db,
type,
ttl,
key,
keyCode,
size,
length,
matchPattern = '',
clearValue,
format = '',
decode = '',
}) {
let tabIndex = findIndex(this.tabList, { name: server })
if (tabIndex === -1) {
const tabItem = new TabItem({
name: server,
title: server,
subTab,
server,
db,
type,
ttl,
key,
keyCode,
size,
length,
matchPattern,
value: undefined,
format,
decode,
})
this.tabList.push(tabItem)
tabIndex = this.tabList.length - 1
this._setActivatedIndex(tabIndex, true, subTab)
} else {
const tab = this.tabList[tabIndex]
tab.blank = false
tab.subTab = subTab
// tab.title = db !== undefined ? `${server}/db${db}` : `${server}`
tab.title = server
tab.server = server
tab.db = db == null ? tab.db : db
tab.type = type
tab.ttl = ttl
tab.key = key
tab.keyCode = keyCode
tab.size = size
tab.length = length
tab.matchPattern = matchPattern
tab.format = format
tab.decode = decode
if (clearValue === true) {
tab.value = undefined
}
}
},
/**
* keep update value in tab
* @param {string} server
* @param {number} db
* @param {string} key
* @param {*} [value]
* @param {string} [format]
* @param {string] [decode]
* @param {string} [matchPattern]
* @param {boolean} [reset]
* @param {boolean} [end] keep end status if not set
* @param {number} [size]
* @param {number} [length]
*/
updateValue({ server, db, key, value, format, decode, matchPattern, reset, end, size = -1, length = -1 }) {
const tabData = find(this.tabList, { name: server, db, key })
if (tabData == null) {
return
}
tabData.format = format || tabData.format
tabData.decode = decode || tabData.decode
tabData.matchPattern = matchPattern || ''
if (size >= 0) {
tabData.size = size
}
if (length >= 0) {
tabData.length = length
}
if (typeof end === 'boolean') {
tabData.end = end
}
if (!!!reset && typeof value === 'object') {
if (value instanceof Array) {
tabData.value = tabData.value || []
// direct deconstruction leads to 'Maximum call stack size exceeded'
// tabData.value.push(...value)
for (let i = 0; i < value.length; i++) {
tabData.value.push(value[i])
}
} else {
tabData.value = assign(value, tabData.value || {})
}
} else {
tabData.value = value
}
},
/**
* insert entries
* @param {string} server
* @param {number} db
* @param {string|number[]} key
* @param {string} type
* @param {ListEntryItem[]|HashEntryItem[]|SetEntryItem[]|ZSetEntryItem[]|StreamEntryItem[]} entries
* @param {boolean} [prepend] for list only
*/
insertValueEntries({ server, db, key, type, entries, prepend }) {
const tab = find(this.tabList, { name: server, db, key })
if (tab == null) {
return
}
switch (type.toLowerCase()) {
case 'list': // {v:string, dv:[string]}[]
tab.value = tab.value || []
if (prepend === true) {
tab.value = [...entries, ...tab.value]
} else {
tab.value.push(...entries)
}
tab.length += size(entries)
break
case 'hash': // {k:string, v:string, dv:[string]}[]
case 'set': // {v: string, s: number}[]
case 'zset': // {v: string, s: number}[]
tab.value = tab.value || []
tab.value.push(...entries)
tab.length += size(entries)
break
case 'stream': // {id: string, v: {}}[]
tab.value = tab.value || []
tab.value = [...entries, ...tab.value]
tab.length += size(entries)
break
}
},
/**
* update entries' value
* @param {string} server
* @param {number} db
* @param {string|number[]} key
* @param {string} type
* @param {ListEntryItem[]|HashEntryItem[]|SetEntryItem[]|ZSetEntryItem[]|StreamEntryItem[]} entries
*/
updateValueEntries({ server, db, key, type, entries }) {
const tab = find(this.tabList, { name: server, db, key })
if (tab == null) {
return
}
switch (type.toLowerCase()) {
case 'hash': // {k:string, v:string, dv:string}[]
tab.value = tab.value || []
for (const entry of entries) {
let updated = false
for (const val of tab.value) {
if (val.k === entry.k) {
val.v = entry.v
val.dv = entry.dv
updated = true
break
}
}
if (!updated) {
// no match element, append
tab.value.push(entry)
tab.length += 1
}
}
break
case 'zset': // {s:number, v:string, dv:string}[]
tab.value = tab.value || []
for (const entry of entries) {
let updated = false
for (const val of tab.value) {
if (val.v === entry.v) {
val.s = entry.s
val.dv = entry.dv
updated = true
break
}
}
if (!updated) {
// no match element, append
tab.value.push(entry)
tab.length += 1
}
}
break
}
},
/**
* replace entry item key or field in value(modify the index key)
* @param {string} server
* @param {number} db
* @param {string|number[]} key
* @param {string} type
* @param {ListReplaceItem[]|HashReplaceItem[]|ZSetReplaceItem[]} entries
* @param {number[]} [index] indexes for replacement, can improve search efficiency if configured
*/
replaceValueEntries({ server, db, key, type, entries, index }) {
const tab = find(this.tabList, { name: server, db, key })
if (tab == null) {
return
}
switch (type.toLowerCase()) {
case 'list': // ListReplaceItem[]
tab.value = tab.value || []
for (const entry of entries) {
if (size(tab.value) > entry.index) {
tab.value[entry.index] = {
v: entry.v,
dv: entry.dv,
}
} else {
// out of range, append
tab.value.push(entry)
tab.length += 1
}
}
break
case 'hash': // HashReplaceItem[]
tab.value = tab.value || []
for (const idx of index) {
const entry = get(tab.value, idx)
if (entry != null) {
/** @type HashReplaceItem[] **/
const replaceEntry = remove(entries, (e) => e.k === entry.k)
if (!isEmpty(replaceEntry)) {
entry.k = replaceEntry[0].nk
entry.v = replaceEntry[0].v
entry.dv = replaceEntry[0].dv
}
}
}
// the left entries do not included in index list, try to retrieve the whole list
for (const entry of entries) {
let updated = false
for (const val of tab.value) {
if (val.k === entry.k) {
val.k = entry.nk
val.v = entry.v
val.dv = entry.dv
updated = true
break
}
}
if (!updated) {
// no match element, append
tab.value.push({
k: entry.nk,
v: entry.v,
dv: entry.dv,
})
tab.length += 1
}
}
break
case 'zset': // ZSetReplaceItem[]
tab.value = tab.value || []
for (const idx of index) {
const entry = get(tab.value, idx)
if (entry != null) {
/** @type ZSetReplaceItem[] **/
const replaceEntry = remove(entries, ({ v }) => v === entry.k)
if (!isEmpty(replaceEntry)) {
entry.s = replaceEntry[0].s
entry.v = replaceEntry[0].nv
entry.dv = replaceEntry[0].dv
}
}
}
// the left entries do not included in index list, try to retrieve the whole list
for (const entry of entries) {
let updated = false
for (const val of tab.value) {
if (val.v === entry.v) {
val.s = entry.s
val.v = entry.nv
val.dv = entry.dv
updated = true
break
}
}
if (!updated) {
// no match element, append
tab.value.push({
s: entry.s,
v: entry.nv,
dv: entry.dv,
})
tab.length += 1
}
}
break
}
},
/**
* remove value entries
* @param {string} server
* @param {number} db
* @param {string} key
* @param {string} type
* @param {string[] | number[]} entries
*/
removeValueEntries({ server, db, key, type, entries }) {
const tab = find(this.tabList, { name: server, db, key })
if (tab == null) {
return
}
switch (type.toLowerCase()) {
case 'list': // string[] | number[]
tab.value = tab.value || []
if (typeof entries[0] === 'number') {
// remove by index, sort by desc first
entries.sort((a, b) => b - a)
const removed = pullAt(tab.value, ...entries)
tab.length -= size(removed)
} else {
// append or prepend items
for (const elem of entries) {
if (!isEmpty(remove(tab.value, elem))) {
tab.length -= 1
}
}
}
break
case 'hash': // string[]
tab.value = tab.value || {}
for (const k of entries) {
for (let i = 0; i < tab.value.length; i++) {
if (tab.value[i].k === k) {
tab.value.splice(i, 1)
tab.length -= 1
break
}
}
}
break
case 'set': // string[]
case 'zset': // string[]
tab.value = tab.value || []
for (const v of entries) {
for (let i = 0; i < tab.value.length; i++) {
if (tab.value[i].v === v) {
tab.value.splice(i, 1)
tab.length -= 1
break
}
}
}
break
case 'stream': // string[]
tab.value = tab.value || []
for (const id of entries) {
for (let i = 0; i < tab.value.length; i++) {
if (tab.value[i].id === id) {
tab.value.splice(i, 1)
tab.length -= 1
break
}
}
}
break
}
},
/**
* update loading status of content in tab
* @param {string} server
* @param {number} db
* @param {boolean} loading
*/
updateLoading({ server, db, loading }) {
const tab = find(this.tabList, { name: server, db })
if (tab == null) {
return
}
tab.loading = loading
},
/**
* update ttl in tab
* @param {string} server
* @param {number} db
* @param {string} key
* @param {number} ttl
*/
updateTTL({ server, db, key, ttl }) {
let tab = find(this.tabList, { name: server, db, key })
if (tab == null) {
return
}
tab.ttl = ttl
},
/**
* set tab's content to empty
* @param {string} name
*/
emptyTab(name) {
const tab = find(this.tabList, { name })
if (tab != null) {
tab.key = null
tab.value = null
}
},
switchTab(tabIndex) {
// const len = size(this.tabList)
// if (tabIndex < 0 || tabIndex >= len) {
// tabIndex = 0
// }
// this.activatedIndex = tabIndex
// const tabIndex = findIndex(this.tabList, {name})
// if (tabIndex === -1) {
// return
// }
// this.activatedIndex = tabIndex
},
switchSubTab(name) {
const tab = this.currentTab
if (tab == null) {
return
}
tab.subTab = name
},
/**
*
* @param {number} tabIndex
* @returns {*|null}
*/
removeTab(tabIndex) {
const len = size(this.tabs)
// ignore remove last blank tab
if (len === 1 && this.tabs[0].blank) {
return null
}
if (tabIndex < 0 || tabIndex >= len) {
return null
}
const removed = this.tabList.splice(tabIndex, 1)
// update select index if removed index equal current selected
this.activatedIndex -= 1
if (this.activatedIndex < 0) {
if (this.tabList.length > 0) {
this._setActivatedIndex(0, false)
} else {
this._setActivatedIndex(-1, false)
}
} else {
this._setActivatedIndex(this.activatedIndex, false)
}
return size(removed) > 0 ? removed[0] : null
},
/**
*
* @param {string} tabName
*/
removeTabByName(tabName) {
const idx = findIndex(this.tabs, { name: tabName })
if (idx !== -1) {
this.removeTab(idx)
}
},
/**
*
*/
removeAllTab() {
this.tabList = []
this._setActivatedIndex(-1, false)
},
/**
* set expanded keys for server
* @param {string} server
* @param {string|string[]} keys
*/
setExpandedKeys(server, keys = []) {
/** @type TabItem**/
let tab = find(this.tabList, { name: server })
if (tab != null) {
if (typeof keys === 'string') {
keys = [keys]
}
if (isEmpty(keys)) {
tab.expandedKeys = []
} else {
tab.expandedKeys = keys
}
}
},
/**
*
* @param {string} server
* @param {string|string[]} keys
*/
addExpandedKey(server, keys) {
/** @type TabItem**/
let tab = find(this.tabList, { name: server })
if (tab != null) {
if (typeof keys === 'string') {
keys = [keys]
}
for (const k of keys) {
if (!includes(tab.expandedKeys, k)) {
tab.expandedKeys.push(k)
}
}
}
},
/**
*
* @param {string} server
* @param {string} key
*/
toggleExpandKey(server, key) {
/** @type TabItem**/
let tab = find(this.tabList, { name: server })
if (tab != null) {
const idx = indexOf(tab.expandedKeys, key)
if (idx === -1) {
tab.expandedKeys.push(key)
} else {
tab.expandedKeys.splice(idx, 1)
}
}
},
/**
*
* @param {string} server
* @param {string} key
*/
removeExpandedKey(server, key) {
/** @type TabItem**/
let tab = find(this.tabList, { name: server })
if (tab != null) {
remove(tab.expandedKeys, (v) => v === key)
}
},
/**
* set selected keys for server
* @param {string} server
* @param {string|string[]} [keys]
*/
setSelectedKeys(server, keys = null) {
/** @type TabItem**/
let tab = find(this.tabList, { name: server })
if (tab != null) {
if (keys == null) {
// select nothing
tab.selectedKeys = []
tab.activatedKey = null
} else if (typeof keys === 'string') {
tab.selectedKeys = [keys]
} else {
tab.selectedKeys = keys
}
}
},
/**
* get checked keys
* @param server
* @returns {CheckedKey[]}
*/
getCheckedKeys(server) {
/** @type TabItem**/
let tab = find(this.tabList, { name: server })
if (tab != null) {
return tab.checkedKeys || []
}
return []
},
/**
* set checked keys for server
* @param {string} server
* @param {CheckedKey[]} [keys]
*/
setCheckedKeys(server, keys = null) {
/** @type TabItem**/
let tab = find(this.tabList, { name: server })
if (tab != null) {
if (isEmpty(keys)) {
// select nothing
tab.checkedKeys = []
} else {
tab.checkedKeys = keys
}
}
},
/**
* get activated key
* @param {string} server
* @return {string|null}
*/
getActivatedKey(server) {
let tab = find(this.tabList, { name: server })
return get(tab, 'activatedKey')
},
/**
* set activated key and return current activatedKey
* @param {string} server
* @param {string} key
* @return {boolean}
*/
setActivatedKey(server, key) {
/** @type TabItem**/
let tab = find(this.tabList, { name: server })
if (tab != null) {
if (!isEmpty(key) && key !== tab.activatedKey) {
tab.activatedKey = key
return true
}
}
return false
},
},
})
export default useTabStore