Compare commits

...

3 Commits

8 changed files with 205 additions and 38 deletions

View File

@ -1,9 +1,8 @@
package convutil package convutil
import ( import (
"bytes"
"encoding/json"
"strings" "strings"
strutil "tinyrdm/backend/utils/string"
) )
type JsonConvert struct{} type JsonConvert struct{}
@ -16,18 +15,11 @@ func (JsonConvert) Decode(str string) (string, bool) {
trimedStr := strings.TrimSpace(str) trimedStr := strings.TrimSpace(str)
if (strings.HasPrefix(trimedStr, "{") && strings.HasSuffix(trimedStr, "}")) || if (strings.HasPrefix(trimedStr, "{") && strings.HasSuffix(trimedStr, "}")) ||
(strings.HasPrefix(trimedStr, "[") && strings.HasSuffix(trimedStr, "]")) { (strings.HasPrefix(trimedStr, "[") && strings.HasSuffix(trimedStr, "]")) {
var out bytes.Buffer return strutil.JSONBeautify(trimedStr, " "), true
if err := json.Indent(&out, []byte(trimedStr), "", " "); err == nil {
return out.String(), true
}
} }
return str, false return str, false
} }
func (JsonConvert) Encode(str string) (string, bool) { func (JsonConvert) Encode(str string) (string, bool) {
var dst bytes.Buffer return strutil.JSONMinify(str), true
if err := json.Compact(&dst, []byte(str)); err != nil {
return str, false
}
return dst.String(), true
} }

View File

@ -2,9 +2,9 @@ package convutil
import ( import (
"bytes" "bytes"
"encoding/json"
"strconv" "strconv"
"strings" "strings"
strutil "tinyrdm/backend/utils/string"
"unicode" "unicode"
"unicode/utf16" "unicode/utf16"
"unicode/utf8" "unicode/utf8"
@ -20,22 +20,16 @@ func (UnicodeJsonConvert) Decode(str string) (string, bool) {
trimedStr := strings.TrimSpace(str) trimedStr := strings.TrimSpace(str)
if (strings.HasPrefix(trimedStr, "{") && strings.HasSuffix(trimedStr, "}")) || if (strings.HasPrefix(trimedStr, "{") && strings.HasSuffix(trimedStr, "}")) ||
(strings.HasPrefix(trimedStr, "[") && strings.HasSuffix(trimedStr, "]")) { (strings.HasPrefix(trimedStr, "[") && strings.HasSuffix(trimedStr, "]")) {
var out bytes.Buffer resultStr := strutil.JSONBeautify(trimedStr, " ")
if err := json.Indent(&out, []byte(trimedStr), "", " "); err == nil { if quoteStr, ok := UnquoteUnicodeJson([]byte(resultStr)); ok {
if quoteStr, ok := UnquoteUnicodeJson(out.Bytes()); ok { return string(quoteStr), true
return string(quoteStr), true
}
} }
} }
return str, false return str, false
} }
func (UnicodeJsonConvert) Encode(str string) (string, bool) { func (UnicodeJsonConvert) Encode(str string) (string, bool) {
var dst bytes.Buffer return strutil.JSONMinify(str), true
if err := json.Compact(&dst, []byte(str)); err != nil {
return str, false
}
return dst.String(), true
} }
func UnquoteUnicodeJson(s []byte) ([]byte, bool) { func UnquoteUnicodeJson(s []byte) ([]byte, bool) {

View File

@ -0,0 +1,158 @@
package strutil
import (
"strings"
"unicode"
)
// Convert from https://github.com/ObuchiYuki/SwiftJSONFormatter
// ArrayIterator defines the iterator for an array
type ArrayIterator[T any] struct {
array []T
head int
}
// NewArrayIterator initializes a new ArrayIterator with the given array
func NewArrayIterator[T any](array []T) *ArrayIterator[T] {
return &ArrayIterator[T]{
array: array,
head: -1,
}
}
// HasNext returns true if there are more elements to iterate over
func (it *ArrayIterator[T]) HasNext() bool {
return it.head+1 < len(it.array)
}
// PeekNext returns the next element without advancing the iterator
func (it *ArrayIterator[T]) PeekNext() *T {
if it.head+1 < len(it.array) {
return &it.array[it.head+1]
}
return nil
}
// Next returns the next element and advances the iterator
func (it *ArrayIterator[T]) Next() *T {
defer func() {
it.head++
}()
return it.PeekNext()
}
// JSONBeautify formats a JSON string with indentation
func JSONBeautify(value string, indent string) string {
if len(indent) <= 0 {
indent = " "
}
return format(value, indent, "\n", " ")
}
// JSONMinify formats a JSON string by removing all unnecessary whitespace
func JSONMinify(value string) string {
return format(value, "", "", "")
}
// format applies the specified formatting to a JSON string
func format(value string, indent string, newLine string, separator string) string {
var formatted strings.Builder
chars := NewArrayIterator([]rune(value))
indentLevel := 0
for chars.HasNext() {
if char := chars.Next(); char != nil {
switch *char {
case '{', '[':
formatted.WriteRune(*char)
consumeWhitespaces(chars)
peeked := chars.PeekNext()
if peeked != nil && (*peeked == '}' || *peeked == ']') {
chars.Next()
formatted.WriteRune(*peeked)
} else {
indentLevel++
formatted.WriteString(newLine)
formatted.WriteString(strings.Repeat(indent, indentLevel))
}
case '}', ']':
indentLevel--
formatted.WriteString(newLine)
formatted.WriteString(strings.Repeat(indent, max(0, indentLevel)))
formatted.WriteRune(*char)
case '"':
str := consumeString(chars)
//str = convertUnicodeString(str)
formatted.WriteString(str)
case ',':
consumeWhitespaces(chars)
formatted.WriteRune(',')
peeked := chars.PeekNext()
if peeked != nil && *peeked != '}' && *peeked != ']' {
formatted.WriteString(newLine)
formatted.WriteString(strings.Repeat(indent, max(0, indentLevel)))
}
case ':':
formatted.WriteString(":" + separator)
default:
if !unicode.IsSpace(*char) {
formatted.WriteRune(*char)
}
}
}
}
return formatted.String()
}
// consumeWhitespaces advances the iterator past any whitespace characters
func consumeWhitespaces(iter *ArrayIterator[rune]) {
for iter.HasNext() {
if peeked := iter.PeekNext(); peeked != nil && unicode.IsSpace(*peeked) {
iter.Next()
} else {
break
}
}
}
// consumeString consumes a JSON string value from the iterator
func consumeString(iter *ArrayIterator[rune]) string {
var sb strings.Builder
sb.WriteRune('"')
escaping := false
for iter.HasNext() {
if char := iter.Next(); char != nil {
if *char == '\n' {
return sb.String() // Unterminated string
}
sb.WriteRune(*char)
if escaping {
escaping = false
} else {
if *char == '\\' {
escaping = true
}
if *char == '"' {
break
}
}
}
}
return sb.String()
}
func convertUnicodeString(str string) string {
// TODO: quote UTF-16 characters
//if len(str) > 2 {
// if unqStr, err := strconv.Unquote(str); err == nil {
// return strconv.Quote(unqStr)
// }
//}
return str
}

View File

@ -110,12 +110,27 @@ onMounted(async () => {
const maximised = await WindowIsMaximised() const maximised = await WindowIsMaximised()
onToggleMaximize(maximised) onToggleMaximize(maximised)
}) })
const onKeyShortcut = (e) => {
switch (e.key) {
case 'w':
if (e.metaKey) {
// close current tab
const tabStore = useTabStore()
const currentTab = tabStore.currentTab
if (currentTab != null) {
tabStore.closeTab(currentTab.name)
}
}
break
}
}
</script> </script>
<template> <template>
<!-- app content--> <!-- app content-->
<n-spin :show="props.loading" :style="spinStyle" :theme-overrides="{ opacitySpinning: 0 }"> <n-spin :show="props.loading" :style="spinStyle" :theme-overrides="{ opacitySpinning: 0 }">
<div id="app-content-wrapper" :style="wrapperStyle" class="flex-box-v"> <div id="app-content-wrapper" :style="wrapperStyle" class="flex-box-v" tabindex="0" @keydown="onKeyShortcut">
<!-- title bar --> <!-- title bar -->
<div <div
id="app-toolbar" id="app-toolbar"

View File

@ -2,32 +2,24 @@
import Server from '@/components/icons/Server.vue' import Server from '@/components/icons/Server.vue'
import useTabStore from 'stores/tab.js' import useTabStore from 'stores/tab.js'
import { computed } from 'vue' import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { get, map } from 'lodash' import { get, map } from 'lodash'
import { useThemeVars } from 'naive-ui' import { useThemeVars } from 'naive-ui'
import useConnectionStore from 'stores/connections.js' import useConnectionStore from 'stores/connections.js'
import { extraTheme } from '@/utils/extra_theme.js' import { extraTheme } from '@/utils/extra_theme.js'
import usePreferencesStore from 'stores/preferences.js' import usePreferencesStore from 'stores/preferences.js'
import useBrowserStore from 'stores/browser.js'
/** /**
* Value content tab on head * Value content tab on head
*/ */
const themeVars = useThemeVars() const themeVars = useThemeVars()
const i18n = useI18n()
const tabStore = useTabStore() const tabStore = useTabStore()
const connectionStore = useConnectionStore() const connectionStore = useConnectionStore()
const browserStore = useBrowserStore()
const prefStore = usePreferencesStore() const prefStore = usePreferencesStore()
const onCloseTab = (tabIndex) => { const onCloseTab = (tabIndex) => {
const tab = get(tabStore.tabs, tabIndex) const tab = get(tabStore.tabs, tabIndex)
if (tab != null) { tabStore.closeTab(tab.name)
$dialog.warning(i18n.t('dialogue.close_confirm', { name: tab.name }), () => {
browserStore.closeConnection(tab.name)
})
}
} }
const tabMarkColor = computed(() => { const tabMarkColor = computed(() => {

View File

@ -80,9 +80,11 @@ const refreshInfo = async (force) => {
} }
if (!isEmpty(props.server) && browserStore.isConnected(props.server)) { if (!isEmpty(props.server) && browserStore.isConnected(props.server)) {
try { try {
const info = await browserStore.getServerInfo(props.server) const info = await browserStore.getServerInfo(props.server, true)
serverInfo.value = info if (!isEmpty(info)) {
_updateChart(info) serverInfo.value = info
_updateChart(info)
}
} finally { } finally {
pageState.loading = false pageState.loading = false
pageState.autoLoading = false pageState.autoLoading = false

View File

@ -343,10 +343,11 @@ const useBrowserStore = defineStore('browser', {
/** /**
* *
* @param server * @param {string} server
* @param {boolean} mute
* @returns {Promise<{}>} * @returns {Promise<{}>}
*/ */
async getServerInfo(server) { async getServerInfo(server, mute) {
try { try {
const { success, data, msg } = await ServerInfo(server) const { success, data, msg } = await ServerInfo(server)
if (success) { if (success) {
@ -356,7 +357,7 @@ const useBrowserStore = defineStore('browser', {
serverInst.stats = data serverInst.stats = data
} }
return data return data
} else if (!isEmpty(msg)) { } else if (!isEmpty(msg) && mute !== true) {
$message.warning(msg) $message.warning(msg)
} }
} finally { } finally {

View File

@ -1,6 +1,8 @@
import { assign, find, findIndex, get, includes, indexOf, isEmpty, pullAt, remove, set, size } from 'lodash' import { assign, find, findIndex, get, includes, indexOf, isEmpty, pullAt, remove, set, size } from 'lodash'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { TabItem } from '@/objects/tabItem.js' import { TabItem } from '@/objects/tabItem.js'
import useBrowserStore from 'stores/browser.js'
import { i18nGlobal } from '@/utils/i18n.js'
const useTabStore = defineStore('tab', { const useTabStore = defineStore('tab', {
/** /**
@ -132,6 +134,17 @@ const useTabStore = defineStore('tab', {
this.upsertTab({ server, clearValue: true }) 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 * update or insert a new tab if not exists with the same name
* @param {string} subTab * @param {string} subTab