feat: add custom decoder/encoder for value content
This commit is contained in:
parent
7faca878a3
commit
450e451781
|
@ -740,6 +740,8 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
|
|||
}
|
||||
}
|
||||
|
||||
decoder := Preferences().GetDecoder()
|
||||
|
||||
switch data.KeyType {
|
||||
case "string":
|
||||
var str string
|
||||
|
@ -782,7 +784,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
|
|||
Value: val,
|
||||
})
|
||||
if doConvert {
|
||||
if dv, _, _ := convutil.ConvertTo(val, param.Decode, param.Format); dv != val {
|
||||
if dv, _, _ := convutil.ConvertTo(val, param.Decode, param.Format, decoder); dv != val {
|
||||
items[len(items)-1].DisplayValue = dv
|
||||
}
|
||||
}
|
||||
|
@ -829,7 +831,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
|
|||
Value: strutil.EncodeRedisKey(loadedVal[i+1]),
|
||||
})
|
||||
if doConvert {
|
||||
if dv, _, _ := convutil.ConvertTo(loadedVal[i+1], param.Decode, param.Format); dv != loadedVal[i+1] {
|
||||
if dv, _, _ := convutil.ConvertTo(loadedVal[i+1], param.Decode, param.Format, decoder); dv != loadedVal[i+1] {
|
||||
items[len(items)-1].DisplayValue = dv
|
||||
}
|
||||
}
|
||||
|
@ -854,7 +856,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
|
|||
items[i/2].Key = loadedVal[i]
|
||||
items[i/2].Value = strutil.EncodeRedisKey(loadedVal[i+1])
|
||||
if doConvert {
|
||||
if dv, _, _ := convutil.ConvertTo(loadedVal[i+1], param.Decode, param.Format); dv != loadedVal[i+1] {
|
||||
if dv, _, _ := convutil.ConvertTo(loadedVal[i+1], param.Decode, param.Format, decoder); dv != loadedVal[i+1] {
|
||||
items[i/2].DisplayValue = dv
|
||||
}
|
||||
}
|
||||
|
@ -899,7 +901,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
|
|||
Value: val,
|
||||
})
|
||||
if doConvert {
|
||||
if dv, _, _ := convutil.ConvertTo(val, param.Decode, param.Format); dv != val {
|
||||
if dv, _, _ := convutil.ConvertTo(val, param.Decode, param.Format, decoder); dv != val {
|
||||
items[len(items)-1].DisplayValue = dv
|
||||
}
|
||||
}
|
||||
|
@ -919,7 +921,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
|
|||
for i, val := range loadedKey {
|
||||
items[i].Value = val
|
||||
if doConvert {
|
||||
if dv, _, _ := convutil.ConvertTo(val, param.Decode, param.Format); dv != val {
|
||||
if dv, _, _ := convutil.ConvertTo(val, param.Decode, param.Format, decoder); dv != val {
|
||||
items[i].DisplayValue = dv
|
||||
}
|
||||
}
|
||||
|
@ -967,7 +969,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
|
|||
Score: score,
|
||||
})
|
||||
if doConvert {
|
||||
if dv, _, _ := convutil.ConvertTo(loadedVal[i], param.Decode, param.Format); dv != loadedVal[i] {
|
||||
if dv, _, _ := convutil.ConvertTo(loadedVal[i], param.Decode, param.Format, decoder); dv != loadedVal[i] {
|
||||
items[len(items)-1].DisplayValue = dv
|
||||
}
|
||||
}
|
||||
|
@ -1001,7 +1003,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
|
|||
Value: val,
|
||||
})
|
||||
if doConvert {
|
||||
if dv, _, _ := convutil.ConvertTo(val, param.Decode, param.Format); dv != val {
|
||||
if dv, _, _ := convutil.ConvertTo(val, param.Decode, param.Format, decoder); dv != val {
|
||||
items[len(items)-1].DisplayValue = dv
|
||||
}
|
||||
}
|
||||
|
@ -1062,7 +1064,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
|
|||
if vb, merr := json.Marshal(msg.Values); merr != nil {
|
||||
it.DisplayValue = "{}"
|
||||
} else {
|
||||
it.DisplayValue, _, _ = convutil.ConvertTo(string(vb), types.DECODE_NONE, types.FORMAT_JSON)
|
||||
it.DisplayValue, _, _ = convutil.ConvertTo(string(vb), types.DECODE_NONE, types.FORMAT_JSON, decoder)
|
||||
}
|
||||
if doFilter && !strings.Contains(it.DisplayValue, param.MatchPattern) {
|
||||
continue
|
||||
|
@ -1096,7 +1098,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
|
|||
// blank format indicate auto format
|
||||
func (b *browserService) ConvertValue(value any, decode, format string) (resp types.JSResp) {
|
||||
str := strutil.DecodeRedisKey(value)
|
||||
value, decode, format = convutil.ConvertTo(str, decode, format)
|
||||
value, decode, format = convutil.ConvertTo(str, decode, format, Preferences().GetDecoder())
|
||||
resp.Success = true
|
||||
resp.Data = map[string]any{
|
||||
"value": value,
|
||||
|
@ -1139,7 +1141,7 @@ func (b *browserService) SetKeyValue(param types.SetKeyParam) (resp types.JSResp
|
|||
return
|
||||
} else {
|
||||
var saveStr string
|
||||
if saveStr, err = convutil.SaveAs(str, param.Format, param.Decode); err != nil {
|
||||
if saveStr, err = convutil.SaveAs(str, param.Format, param.Decode, Preferences().GetDecoder()); err != nil {
|
||||
resp.Msg = fmt.Sprintf(`save to type "%s" fail: %s`, param.Format, err.Error())
|
||||
return
|
||||
}
|
||||
|
@ -1250,12 +1252,13 @@ func (b *browserService) SetHashValue(param types.SetHashParam) (resp types.JSRe
|
|||
key := strutil.DecodeRedisKey(param.Key)
|
||||
str := strutil.DecodeRedisKey(param.Value)
|
||||
var saveStr, displayStr string
|
||||
if saveStr, err = convutil.SaveAs(str, param.Format, param.Decode); err != nil {
|
||||
decoder := Preferences().GetDecoder()
|
||||
if saveStr, err = convutil.SaveAs(str, param.Format, param.Decode, decoder); err != nil {
|
||||
resp.Msg = fmt.Sprintf(`save to type "%s" fail: %s`, param.Format, err.Error())
|
||||
return
|
||||
}
|
||||
if len(param.RetDecode) > 0 && len(param.RetFormat) > 0 {
|
||||
displayStr, _, _ = convutil.ConvertTo(saveStr, param.RetDecode, param.RetFormat)
|
||||
displayStr, _, _ = convutil.ConvertTo(saveStr, param.RetDecode, param.RetFormat, decoder)
|
||||
}
|
||||
var updated, added, removed []types.HashEntryItem
|
||||
var replaced []types.HashReplaceItem
|
||||
|
@ -1473,7 +1476,8 @@ func (b *browserService) SetListItem(param types.SetListParam) (resp types.JSRes
|
|||
} else {
|
||||
// replace index value
|
||||
var saveStr string
|
||||
if saveStr, err = convutil.SaveAs(str, param.Format, param.Decode); err != nil {
|
||||
decoder := Preferences().GetDecoder()
|
||||
if saveStr, err = convutil.SaveAs(str, param.Format, param.Decode, decoder); err != nil {
|
||||
resp.Msg = fmt.Sprintf(`save to type "%s" fail: %s`, param.Format, err.Error())
|
||||
return
|
||||
}
|
||||
|
@ -1484,7 +1488,7 @@ func (b *browserService) SetListItem(param types.SetListParam) (resp types.JSRes
|
|||
}
|
||||
var displayStr string
|
||||
if len(param.RetDecode) > 0 && len(param.RetFormat) > 0 {
|
||||
displayStr, _, _ = convutil.ConvertTo(saveStr, param.RetDecode, param.RetFormat)
|
||||
displayStr, _, _ = convutil.ConvertTo(saveStr, param.RetDecode, param.RetFormat, decoder)
|
||||
}
|
||||
replaced = append(replaced, types.ListReplaceItem{
|
||||
Index: param.Index,
|
||||
|
@ -1574,8 +1578,9 @@ func (b *browserService) UpdateSetItem(param types.SetSetParam) (resp types.JSRe
|
|||
|
||||
// insert new value
|
||||
str = strutil.DecodeRedisKey(param.NewValue)
|
||||
decoder := Preferences().GetDecoder()
|
||||
var saveStr string
|
||||
if saveStr, err = convutil.SaveAs(str, param.Format, param.Decode); err != nil {
|
||||
if saveStr, err = convutil.SaveAs(str, param.Format, param.Decode, decoder); err != nil {
|
||||
resp.Msg = fmt.Sprintf(`save to type "%s" fail: %s`, param.Format, err.Error())
|
||||
return
|
||||
}
|
||||
|
@ -1583,7 +1588,7 @@ func (b *browserService) UpdateSetItem(param types.SetSetParam) (resp types.JSRe
|
|||
// add new item
|
||||
var displayStr string
|
||||
if len(param.RetDecode) > 0 && len(param.RetFormat) > 0 {
|
||||
displayStr, _, _ = convutil.ConvertTo(saveStr, param.RetDecode, param.RetFormat)
|
||||
displayStr, _, _ = convutil.ConvertTo(saveStr, param.RetDecode, param.RetFormat, decoder)
|
||||
}
|
||||
added = append(added, types.SetEntryItem{
|
||||
Value: saveStr,
|
||||
|
@ -1620,6 +1625,7 @@ func (b *browserService) UpdateZSetValue(param types.SetZSetParam) (resp types.J
|
|||
var added, updated, removed []types.ZSetEntryItem
|
||||
var replaced []types.ZSetReplaceItem
|
||||
var affect int64
|
||||
decoder := Preferences().GetDecoder()
|
||||
if len(newVal) <= 0 {
|
||||
// no new value, delete value
|
||||
if affect, err = client.ZRem(ctx, key, val).Result(); affect > 0 {
|
||||
|
@ -1630,7 +1636,7 @@ func (b *browserService) UpdateZSetValue(param types.SetZSetParam) (resp types.J
|
|||
}
|
||||
} else {
|
||||
var saveVal string
|
||||
if saveVal, err = convutil.SaveAs(newVal, param.Format, param.Decode); err != nil {
|
||||
if saveVal, err = convutil.SaveAs(newVal, param.Format, param.Decode, decoder); err != nil {
|
||||
resp.Msg = fmt.Sprintf(`save to type "%s" fail: %s`, param.Format, err.Error())
|
||||
return
|
||||
}
|
||||
|
@ -1640,7 +1646,7 @@ func (b *browserService) UpdateZSetValue(param types.SetZSetParam) (resp types.J
|
|||
Score: param.Score,
|
||||
Member: saveVal,
|
||||
}).Result()
|
||||
displayValue, _, _ := convutil.ConvertTo(val, param.RetDecode, param.RetFormat)
|
||||
displayValue, _, _ := convutil.ConvertTo(val, param.RetDecode, param.RetFormat, decoder)
|
||||
if affect > 0 {
|
||||
// add new item
|
||||
added = append(added, types.ZSetEntryItem{
|
||||
|
@ -1668,7 +1674,7 @@ func (b *browserService) UpdateZSetValue(param types.SetZSetParam) (resp types.J
|
|||
Score: param.Score,
|
||||
Member: saveVal,
|
||||
}).Result()
|
||||
displayValue, _, _ := convutil.ConvertTo(saveVal, param.RetDecode, param.RetFormat)
|
||||
displayValue, _, _ := convutil.ConvertTo(saveVal, param.RetDecode, param.RetFormat, decoder)
|
||||
if affect <= 0 {
|
||||
// no new value added, just update exists item
|
||||
removed = append(removed, types.ZSetEntryItem{
|
||||
|
@ -1794,7 +1800,7 @@ func (b *browserService) AddStreamValue(server string, db int, k any, ID string,
|
|||
updateValues[fieldItems[i].(string)] = fieldItems[i+1]
|
||||
}
|
||||
vb, _ := json.Marshal(updateValues)
|
||||
displayValue, _, _ := convutil.ConvertTo(string(vb), types.DECODE_NONE, types.FORMAT_JSON)
|
||||
displayValue, _, _ := convutil.ConvertTo(string(vb), types.DECODE_NONE, types.FORMAT_JSON, Preferences().GetDecoder())
|
||||
|
||||
resp.Success = true
|
||||
resp.Data = struct {
|
||||
|
|
|
@ -13,11 +13,14 @@ import (
|
|||
storage2 "tinyrdm/backend/storage"
|
||||
"tinyrdm/backend/types"
|
||||
"tinyrdm/backend/utils/coll"
|
||||
convutil "tinyrdm/backend/utils/convert"
|
||||
sliceutil "tinyrdm/backend/utils/slice"
|
||||
)
|
||||
|
||||
type preferencesService struct {
|
||||
pref *storage2.PreferencesStorage
|
||||
clientVersion string
|
||||
customDecoder []convutil.CmdConvert
|
||||
}
|
||||
|
||||
var preferences *preferencesService
|
||||
|
@ -182,6 +185,23 @@ func (p *preferencesService) GetScanSize() int {
|
|||
return size
|
||||
}
|
||||
|
||||
func (p *preferencesService) GetDecoder() []convutil.CmdConvert {
|
||||
data := p.pref.GetPreferences()
|
||||
return sliceutil.FilterMap(data.Decoder, func(i int) (convutil.CmdConvert, bool) {
|
||||
//if !data.Decoder[i].Enable {
|
||||
// return convutil.CmdConvert{}, false
|
||||
//}
|
||||
return convutil.CmdConvert{
|
||||
Name: data.Decoder[i].Name,
|
||||
Auto: data.Decoder[i].Auto,
|
||||
DecodePath: data.Decoder[i].DecodePath,
|
||||
DecodeArgs: data.Decoder[i].DecodeArgs,
|
||||
EncodePath: data.Decoder[i].EncodePath,
|
||||
EncodeArgs: data.Decoder[i].EncodeArgs,
|
||||
}, true
|
||||
})
|
||||
}
|
||||
|
||||
type latestRelease struct {
|
||||
Name string `json:"name"`
|
||||
TagName string `json:"tag_name"`
|
||||
|
|
|
@ -7,6 +7,7 @@ type Preferences struct {
|
|||
General PreferencesGeneral `json:"general" yaml:"general"`
|
||||
Editor PreferencesEditor `json:"editor" yaml:"editor"`
|
||||
Cli PreferencesCli `json:"cli" yaml:"cli"`
|
||||
Decoder []PreferencesDecoder `json:"decoder" yaml:"decoder,omitempty"`
|
||||
}
|
||||
|
||||
func NewPreferences() Preferences {
|
||||
|
@ -33,6 +34,7 @@ func NewPreferences() Preferences {
|
|||
FontSize: consts.DEFAULT_FONT_SIZE,
|
||||
CursorStyle: "block",
|
||||
},
|
||||
Decoder: []PreferencesDecoder{},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,3 +74,13 @@ type PreferencesCli struct {
|
|||
FontSize int `json:"fontSize" yaml:"font_size"`
|
||||
CursorStyle string `json:"cursorStyle" yaml:"cursor_style,omitempty"`
|
||||
}
|
||||
|
||||
type PreferencesDecoder struct {
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Enable bool `json:"enable" yaml:"enable"`
|
||||
Auto bool `json:"auto" yaml:"auto"`
|
||||
DecodePath string `json:"decodePath" yaml:"decode_path"`
|
||||
DecodeArgs []string `json:"decodeArgs" yaml:"decode_args,omitempty"`
|
||||
EncodePath string `json:"encodePath" yaml:"encode_path"`
|
||||
EncodeArgs []string `json:"encodeArgs" yaml:"encode_args,omitempty"`
|
||||
}
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
package convutil
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"os/exec"
|
||||
"strings"
|
||||
sliceutil "tinyrdm/backend/utils/slice"
|
||||
)
|
||||
|
||||
type CmdConvert struct {
|
||||
Name string
|
||||
Auto bool
|
||||
DecodePath string
|
||||
DecodeArgs []string
|
||||
EncodePath string
|
||||
EncodeArgs []string
|
||||
}
|
||||
|
||||
const replaceholder = "{VALUE}"
|
||||
|
||||
func (c CmdConvert) Encode(str string) (string, bool) {
|
||||
base64Content := base64.StdEncoding.EncodeToString([]byte(str))
|
||||
var containHolder bool
|
||||
args := sliceutil.Map(c.EncodeArgs, func(i int) string {
|
||||
arg := strings.TrimSpace(c.EncodeArgs[i])
|
||||
if strings.Contains(arg, replaceholder) {
|
||||
arg = strings.ReplaceAll(arg, replaceholder, base64Content)
|
||||
containHolder = true
|
||||
}
|
||||
return arg
|
||||
})
|
||||
if len(args) <= 0 || !containHolder {
|
||||
args = append(args, base64Content)
|
||||
}
|
||||
cmd := exec.Command(c.EncodePath, args...)
|
||||
output, err := cmd.Output()
|
||||
if err != nil || len(output) <= 0 || string(output) == "[RDM-ERROR]" {
|
||||
return str, false
|
||||
}
|
||||
|
||||
outputContent := make([]byte, base64.StdEncoding.DecodedLen(len(output)))
|
||||
n, err := base64.StdEncoding.Decode(outputContent, output)
|
||||
if err != nil {
|
||||
return str, false
|
||||
}
|
||||
return string(outputContent[:n]), true
|
||||
}
|
||||
|
||||
func (c CmdConvert) Decode(str string) (string, bool) {
|
||||
base64Content := base64.StdEncoding.EncodeToString([]byte(str))
|
||||
var containHolder bool
|
||||
args := sliceutil.Map(c.DecodeArgs, func(i int) string {
|
||||
arg := strings.TrimSpace(c.DecodeArgs[i])
|
||||
if strings.Contains(arg, replaceholder) {
|
||||
arg = strings.ReplaceAll(arg, replaceholder, base64Content)
|
||||
containHolder = true
|
||||
}
|
||||
return arg
|
||||
})
|
||||
if len(args) <= 0 || !containHolder {
|
||||
args = append(args, base64Content)
|
||||
}
|
||||
cmd := exec.Command(c.DecodePath, args...)
|
||||
output, err := cmd.Output()
|
||||
if err != nil || len(output) <= 0 || string(output) == "[RDM-ERROR]" {
|
||||
return str, false
|
||||
}
|
||||
|
||||
outputContent := make([]byte, base64.StdEncoding.DecodedLen(len(output)))
|
||||
n, err := base64.StdEncoding.Decode(outputContent, output)
|
||||
if err != nil {
|
||||
return str, false
|
||||
}
|
||||
return string(outputContent[:n]), true
|
||||
}
|
|
@ -29,7 +29,8 @@ var (
|
|||
// ConvertTo convert string to specified type
|
||||
// @param decodeType empty string indicates automatic detection
|
||||
// @param formatType empty string indicates automatic detection
|
||||
func ConvertTo(str, decodeType, formatType string) (value, resultDecode, resultFormat string) {
|
||||
// @param custom decoder if any
|
||||
func ConvertTo(str, decodeType, formatType string, customDecoder []CmdConvert) (value, resultDecode, resultFormat string) {
|
||||
if len(str) <= 0 {
|
||||
// empty content
|
||||
if len(formatType) <= 0 {
|
||||
|
@ -46,13 +47,17 @@ func ConvertTo(str, decodeType, formatType string) (value, resultDecode, resultF
|
|||
}
|
||||
|
||||
// decode first
|
||||
value, resultDecode = decodeWith(str, decodeType)
|
||||
value, resultDecode = decodeWith(str, decodeType, customDecoder)
|
||||
// then format content
|
||||
if len(formatType) <= 0 {
|
||||
value, resultFormat = autoViewAs(value)
|
||||
} else {
|
||||
value, resultFormat = viewAs(value, formatType)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func decodeWith(str, decodeType string) (value, resultDecode string) {
|
||||
func decodeWith(str, decodeType string, customDecoder []CmdConvert) (value, resultDecode string) {
|
||||
if len(decodeType) > 0 {
|
||||
switch decodeType {
|
||||
case types.DECODE_NONE:
|
||||
|
@ -99,17 +104,31 @@ func decodeWith(str, decodeType string) (value, resultDecode string) {
|
|||
} else {
|
||||
value = str
|
||||
}
|
||||
|
||||
default:
|
||||
for _, decoder := range customDecoder {
|
||||
if decoder.Name == decodeType {
|
||||
if decodedStr, ok := decoder.Decode(str); ok {
|
||||
value = decodedStr
|
||||
} else {
|
||||
value = str
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resultDecode = decodeType
|
||||
return
|
||||
}
|
||||
|
||||
return autoDecode(str)
|
||||
value, resultDecode = autoDecode(str, customDecoder)
|
||||
return
|
||||
}
|
||||
|
||||
// attempt try possible decode method
|
||||
// if no decode is possible, it will return the origin string value and "none" decode type
|
||||
func autoDecode(str string) (value, resultDecode string) {
|
||||
func autoDecode(str string, customDecoder []CmdConvert) (value, resultDecode string) {
|
||||
if len(str) > 0 {
|
||||
// pure digit content may incorrect regard as some encoded type, skip decode
|
||||
if match, _ := regexp.MatchString(`^\d+$`, str); !match {
|
||||
|
@ -147,6 +166,16 @@ func autoDecode(str string) (value, resultDecode string) {
|
|||
resultDecode = types.DECODE_MSGPACK
|
||||
return
|
||||
}
|
||||
|
||||
// try decode with custom decoder
|
||||
for _, decoder := range customDecoder {
|
||||
if decoder.Auto {
|
||||
if value, ok = decoder.Decode(str); ok {
|
||||
resultDecode = decoder.Name
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,6 +187,8 @@ func autoDecode(str string) (value, resultDecode string) {
|
|||
func viewAs(str, formatType string) (value, resultFormat string) {
|
||||
if len(formatType) > 0 {
|
||||
switch formatType {
|
||||
default:
|
||||
fallthrough
|
||||
case types.FORMAT_RAW, types.FORMAT_YAML, types.FORMAT_XML:
|
||||
value = str
|
||||
|
||||
|
@ -185,8 +216,7 @@ func viewAs(str, formatType string) (value, resultFormat string) {
|
|||
resultFormat = formatType
|
||||
return
|
||||
}
|
||||
|
||||
return autoViewAs(str)
|
||||
return
|
||||
}
|
||||
|
||||
// attempt automatic convert to possible types
|
||||
|
@ -222,7 +252,7 @@ func autoViewAs(str string) (value, resultFormat string) {
|
|||
return
|
||||
}
|
||||
|
||||
func SaveAs(str, format, decode string) (value string, err error) {
|
||||
func SaveAs(str, format, decode string, customDecoder []CmdConvert) (value string, err error) {
|
||||
value = str
|
||||
switch format {
|
||||
case types.FORMAT_JSON:
|
||||
|
@ -297,6 +327,18 @@ func SaveAs(str, format, decode string) (value string, err error) {
|
|||
err = errors.New("fail to build msgpack")
|
||||
}
|
||||
return
|
||||
|
||||
default:
|
||||
for _, decoder := range customDecoder {
|
||||
if decoder.Name == decode {
|
||||
if encodedStr, ok := decoder.Encode(str); ok {
|
||||
value = encodedStr
|
||||
} else {
|
||||
value = str
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return str, nil
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package convutil
|
|||
|
||||
import (
|
||||
"gopkg.in/yaml.v3"
|
||||
"log"
|
||||
)
|
||||
|
||||
type YamlConvert struct{}
|
||||
|
@ -14,10 +13,5 @@ func (YamlConvert) Encode(str string) (string, bool) {
|
|||
func (YamlConvert) Decode(str string) (string, bool) {
|
||||
var obj map[string]any
|
||||
err := yaml.Unmarshal([]byte(str), &obj)
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
} else {
|
||||
log.Println(obj)
|
||||
}
|
||||
return str, err == nil
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import FlushDbDialog from '@/components/dialogs/FlushDbDialog.vue'
|
|||
import ExportKeyDialog from '@/components/dialogs/ExportKeyDialog.vue'
|
||||
import ImportKeyDialog from '@/components/dialogs/ImportKeyDialog.vue'
|
||||
import { Info } from 'wailsjs/go/services/systemService.js'
|
||||
import DecoderDialog from '@/components/dialogs/DecoderDialog.vue'
|
||||
|
||||
const prefStore = usePreferencesStore()
|
||||
const connectionStore = useConnectionStore()
|
||||
|
@ -79,6 +80,7 @@ watch(
|
|||
<flush-db-dialog />
|
||||
<set-ttl-dialog />
|
||||
<preferences-dialog />
|
||||
<decoder-dialog />
|
||||
<about-dialog />
|
||||
</n-dialog-provider>
|
||||
</n-config-provider>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup>
|
||||
import { computed, h, ref } from 'vue'
|
||||
import { get, map } from 'lodash'
|
||||
import { get, isEmpty } from 'lodash'
|
||||
import { NIcon, NText } from 'naive-ui'
|
||||
import { useRender } from '@/utils/render.js'
|
||||
|
||||
|
@ -10,8 +10,8 @@ const props = defineProps({
|
|||
value: '',
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
value: {},
|
||||
type: Array,
|
||||
value: () => [],
|
||||
},
|
||||
tooltip: {
|
||||
type: String,
|
||||
|
@ -40,15 +40,32 @@ const dropdownOption = computed(() => {
|
|||
type: 'divider',
|
||||
},
|
||||
]
|
||||
return [
|
||||
...options,
|
||||
...map(props.options, (t) => {
|
||||
return {
|
||||
key: t,
|
||||
label: t,
|
||||
if (get(props.options, 0) instanceof Array) {
|
||||
// multiple group
|
||||
for (let i = 0; i < props.options.length; i++) {
|
||||
if (i !== 0 && !isEmpty(props.options[i])) {
|
||||
// add divider
|
||||
options.push({
|
||||
key: 'header-divider' + (i + 1),
|
||||
type: 'divider',
|
||||
})
|
||||
}
|
||||
}),
|
||||
]
|
||||
for (const option of props.options[i]) {
|
||||
options.push({
|
||||
key: option,
|
||||
label: option,
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const option of props.options) {
|
||||
options.push({
|
||||
key: option,
|
||||
label: option,
|
||||
})
|
||||
}
|
||||
}
|
||||
return options
|
||||
})
|
||||
|
||||
const onDropdownSelect = (key) => {
|
||||
|
|
|
@ -35,6 +35,7 @@ const handleSelectFile = async () => {
|
|||
<n-input
|
||||
:disabled="props.disabled"
|
||||
:placeholder="placeholder"
|
||||
:title="props.value"
|
||||
:value="props.value"
|
||||
clearable
|
||||
@clear="onClear"
|
||||
|
|
|
@ -3,7 +3,9 @@ import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
|||
import Code from '@/components/icons/Code.vue'
|
||||
import Conversion from '@/components/icons/Conversion.vue'
|
||||
import DropdownSelector from '@/components/common/DropdownSelector.vue'
|
||||
import { some } from 'lodash'
|
||||
import { isEmpty, map, some } from 'lodash'
|
||||
import { computed } from 'vue'
|
||||
import usePreferencesStore from 'stores/preferences.js'
|
||||
|
||||
const props = defineProps({
|
||||
decode: {
|
||||
|
@ -17,13 +19,35 @@ const props = defineProps({
|
|||
disabled: Boolean,
|
||||
})
|
||||
|
||||
const prefStore = usePreferencesStore()
|
||||
|
||||
const formatTypeOption = computed(() => {
|
||||
return map(formatTypes, (t) => t)
|
||||
})
|
||||
|
||||
const decodeTypeOption = computed(() => {
|
||||
const customTypes = []
|
||||
// has custom decoder
|
||||
if (!isEmpty(prefStore.decoder)) {
|
||||
for (const decoder of prefStore.decoder) {
|
||||
// types[decoder.name] = types[decoder.name] || decoder.name
|
||||
if (!decodeTypes.hasOwnProperty(decoder.name)) {
|
||||
customTypes.push(decoder.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return [map(decodeTypes, (t) => t), customTypes]
|
||||
})
|
||||
|
||||
const emit = defineEmits(['formatChanged', 'update:decode', 'update:format'])
|
||||
const onFormatChanged = (selDecode, selFormat) => {
|
||||
if (!some(decodeTypes, (val) => val === selDecode)) {
|
||||
const [buildin, external] = decodeTypeOption.value
|
||||
if (!some([...buildin, ...external], (val) => val === selDecode)) {
|
||||
selDecode = decodeTypes.NONE
|
||||
}
|
||||
if (!some(formatTypes, (val) => val === selFormat)) {
|
||||
selFormat = formatTypes.RAW
|
||||
// set to auto chose format
|
||||
selFormat = ''
|
||||
}
|
||||
emit('formatChanged', selDecode, selFormat)
|
||||
if (selDecode !== props.decode) {
|
||||
|
@ -41,7 +65,7 @@ const onFormatChanged = (selDecode, selFormat) => {
|
|||
:default="formatTypes.RAW"
|
||||
:disabled="props.disabled"
|
||||
:icon="Code"
|
||||
:options="formatTypes"
|
||||
:options="formatTypeOption"
|
||||
:tooltip="$t('interface.view_as')"
|
||||
:value="props.format"
|
||||
@update:value="(f) => onFormatChanged(props.decode, f)" />
|
||||
|
@ -50,10 +74,10 @@ const onFormatChanged = (selDecode, selFormat) => {
|
|||
:default="decodeTypes.NONE"
|
||||
:disabled="props.disabled"
|
||||
:icon="Conversion"
|
||||
:options="decodeTypes"
|
||||
:options="decodeTypeOption"
|
||||
:tooltip="$t('interface.decode_with')"
|
||||
:value="props.decode"
|
||||
@update:value="(d) => onFormatChanged(d, props.format)" />
|
||||
@update:value="(d) => onFormatChanged(d, '')" />
|
||||
</n-space>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -0,0 +1,206 @@
|
|||
<script setup>
|
||||
import useDialog from 'stores/dialog.js'
|
||||
import { computed, reactive, ref, toRaw, watch } from 'vue'
|
||||
import FileOpenInput from '@/components/common/FileOpenInput.vue'
|
||||
import Delete from '@/components/icons/Delete.vue'
|
||||
import Add from '@/components/icons/Add.vue'
|
||||
import IconButton from '@/components/common/IconButton.vue'
|
||||
import { cloneDeep, get, isEmpty } from 'lodash'
|
||||
import usePreferencesStore from 'stores/preferences.js'
|
||||
import { joinCommand } from '@/utils/decoder_cmd.js'
|
||||
import Help from '@/components/icons/Help.vue'
|
||||
|
||||
const editName = ref('')
|
||||
const decoderForm = reactive({
|
||||
name: '',
|
||||
auto: true,
|
||||
decodePath: '',
|
||||
decodeArgs: [],
|
||||
encodePath: '',
|
||||
encodeArgs: [],
|
||||
})
|
||||
|
||||
const dialogStore = useDialog()
|
||||
const prefStore = usePreferencesStore()
|
||||
|
||||
watch(
|
||||
() => dialogStore.decodeDialogVisible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
const name = get(dialogStore.decodeParam, 'name', '')
|
||||
if (!isEmpty(name)) {
|
||||
editName.value = decoderForm.name = name
|
||||
decoderForm.auto = dialogStore.decodeParam.auto !== false
|
||||
decoderForm.decodePath = get(dialogStore.decodeParam, 'decodePath', '')
|
||||
decoderForm.decodeArgs = get(dialogStore.decodeParam, 'decodeArgs', [])
|
||||
decoderForm.encodePath = get(dialogStore.decodeParam, 'encodePath', '')
|
||||
decoderForm.encodeArgs = get(dialogStore.decodeParam, 'encodeArgs', [])
|
||||
} else {
|
||||
editName.value = ''
|
||||
decoderForm.decodePath = ''
|
||||
decoderForm.encodePath = ''
|
||||
decoderForm.decodeArgs = []
|
||||
decoderForm.encodeArgs = []
|
||||
}
|
||||
} else {
|
||||
editName.value = ''
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
const decodeCmdPreview = computed(() => {
|
||||
return joinCommand(decoderForm.decodePath, decoderForm.decodeArgs, '')
|
||||
})
|
||||
|
||||
const encodeCmdPreview = computed(() => {
|
||||
return joinCommand(decoderForm.encodePath, decoderForm.encodeArgs, '')
|
||||
})
|
||||
|
||||
const onAddOrUpdate = () => {
|
||||
if (isEmpty(editName.value)) {
|
||||
// add decoder
|
||||
prefStore.addCustomDecoder(toRaw(decoderForm))
|
||||
} else {
|
||||
// update decoder
|
||||
const param = cloneDeep(toRaw(decoderForm))
|
||||
param.newName = param.name
|
||||
param.name = editName.value
|
||||
prefStore.updateCustomDecoder(param)
|
||||
}
|
||||
}
|
||||
const onClose = () => {}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-modal
|
||||
v-model:show="dialogStore.decodeDialogVisible"
|
||||
:closable="false"
|
||||
:close-on-esc="false"
|
||||
:mask-closable="false"
|
||||
:negative-button-props="{ focusable: false, size: 'medium' }"
|
||||
:negative-text="$t('common.cancel')"
|
||||
:positive-button-props="{ focusable: false, size: 'medium' }"
|
||||
:positive-text="$t('common.confirm')"
|
||||
:show-icon="false"
|
||||
:title="editName ? $t('dialogue.decoder.edit_name') : $t('dialogue.decoder.name')"
|
||||
preset="dialog"
|
||||
transform-origin="center"
|
||||
@positive-click="onAddOrUpdate"
|
||||
@negative-click="onClose">
|
||||
<n-form :model="decoderForm" :show-require-mark="false" label-align="left" label-placement="top">
|
||||
<n-form-item :label="$t('dialogue.decoder.decoder_name')" required show-require-mark>
|
||||
<n-input v-model:value="decoderForm.name" />
|
||||
</n-form-item>
|
||||
<n-tabs type="line">
|
||||
<!-- decode pane -->
|
||||
<n-tab-pane :tab="$t('dialogue.decoder.decoder')" name="decode">
|
||||
<n-form-item required show-require-mark>
|
||||
<template #label>
|
||||
<n-space :size="5" :wrap-item="false" align="center" justify="center">
|
||||
<span>{{ $t('dialogue.decoder.decode_path') }}</span>
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<n-icon :component="Help" />
|
||||
</template>
|
||||
<div class="text-block" style="max-width: 600px">
|
||||
{{ $t('dialogue.decoder.path_help') }}
|
||||
</div>
|
||||
</n-tooltip>
|
||||
</n-space>
|
||||
</template>
|
||||
<file-open-input
|
||||
v-model:value="decoderForm.decodePath"
|
||||
:placeholder="$t('dialogue.decoder.decode_path')" />
|
||||
</n-form-item>
|
||||
<n-form-item required>
|
||||
<template #label>
|
||||
<n-space :size="5" :wrap-item="false" align="center" justify="center">
|
||||
<span>{{ $t('dialogue.decoder.args') }}</span>
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<n-icon :component="Help" />
|
||||
</template>
|
||||
<div class="text-block" style="max-width: 600px">
|
||||
{{ $t('dialogue.decoder.args_help').replace('[', '{').replace(']', '}') }}
|
||||
</div>
|
||||
</n-tooltip>
|
||||
</n-space>
|
||||
</template>
|
||||
<n-dynamic-input v-model:value="decoderForm.decodeArgs" @create="() => ''">
|
||||
<template #action="{ index, create, remove, move }">
|
||||
<icon-button :icon="Add" size="18" @click="() => create(index)" />
|
||||
<icon-button :icon="Delete" size="18" @click="() => remove(index)" />
|
||||
</template>
|
||||
</n-dynamic-input>
|
||||
</n-form-item>
|
||||
<n-card
|
||||
v-if="decodeCmdPreview"
|
||||
content-class="cmd-line"
|
||||
content-style="padding: 10px;"
|
||||
embedded
|
||||
size="small">
|
||||
{{ decodeCmdPreview }}
|
||||
</n-card>
|
||||
</n-tab-pane>
|
||||
|
||||
<!-- encode pane -->
|
||||
<n-tab-pane :tab="$t('dialogue.decoder.encoder')" name="encode">
|
||||
<n-form-item required show-require-mark>
|
||||
<template #label>
|
||||
<n-space :size="5" :wrap-item="false" align="center" justify="center">
|
||||
<span>{{ $t('dialogue.decoder.encode_path') }}</span>
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<n-icon :component="Help" />
|
||||
</template>
|
||||
<div class="text-block" style="max-width: 600px">
|
||||
{{ $t('dialogue.decoder.path_help') }}
|
||||
</div>
|
||||
</n-tooltip>
|
||||
</n-space>
|
||||
</template>
|
||||
<file-open-input
|
||||
v-model:value="decoderForm.encodePath"
|
||||
:placeholder="$t('dialogue.decoder.encode_path')" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="$t('dialogue.decoder.args')" required>
|
||||
<template #label>
|
||||
<n-space :size="5" :wrap-item="false" align="center" justify="center">
|
||||
<span>{{ $t('dialogue.decoder.args') }}</span>
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<n-icon :component="Help" />
|
||||
</template>
|
||||
<div class="text-block" style="max-width: 600px">
|
||||
{{ $t('dialogue.decoder.args_help').replace('[', '{').replace(']', '}') }}
|
||||
</div>
|
||||
</n-tooltip>
|
||||
</n-space>
|
||||
</template>
|
||||
<n-dynamic-input v-model:value="decoderForm.encodeArgs" @create="() => ''">
|
||||
<template #action="{ index, create, remove, move }">
|
||||
<icon-button :icon="Add" size="18" @click="() => create(index)" />
|
||||
<icon-button :icon="Delete" size="18" @click="() => remove(index)" />
|
||||
</template>
|
||||
</n-dynamic-input>
|
||||
</n-form-item>
|
||||
<n-card
|
||||
v-if="encodeCmdPreview"
|
||||
content-class="cmd-line"
|
||||
content-style="padding: 10px;"
|
||||
embedded
|
||||
size="small">
|
||||
{{ encodeCmdPreview }}
|
||||
</n-card>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
<n-form-item :show-feedback="false">
|
||||
<n-checkbox v-model:checked="decoderForm.auto" :label="$t('dialogue.decoder.auto')" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/styles/content';
|
||||
</style>
|
|
@ -1,11 +1,18 @@
|
|||
<script setup>
|
||||
import { computed, ref, watchEffect } from 'vue'
|
||||
import { computed, h, ref, watchEffect } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import useDialog from 'stores/dialog'
|
||||
import usePreferencesStore from 'stores/preferences.js'
|
||||
import { map, sortBy } from 'lodash'
|
||||
import { find, map, sortBy } from 'lodash'
|
||||
import { typesIconStyle } from '@/consts/support_redis_type.js'
|
||||
import Help from '@/components/icons/Help.vue'
|
||||
import Delete from '@/components/icons/Delete.vue'
|
||||
import IconButton from '@/components/common/IconButton.vue'
|
||||
import { NButton, NEllipsis, NIcon, NSpace, NTooltip } from 'naive-ui'
|
||||
import Edit from '@/components/icons/Edit.vue'
|
||||
import { joinCommand } from '@/utils/decoder_cmd.js'
|
||||
import AddLink from '@/components/icons/AddLink.vue'
|
||||
import Checked from '@/components/icons/Checked.vue'
|
||||
|
||||
const prefStore = usePreferencesStore()
|
||||
|
||||
|
@ -23,6 +30,8 @@ const initPreferences = async () => {
|
|||
prevPreferences.value = {
|
||||
general: prefStore.general,
|
||||
editor: prefStore.editor,
|
||||
cli: prefStore.cli,
|
||||
decoder: prefStore.decoder,
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
|
@ -43,6 +52,99 @@ const keyOptions = computed(() => {
|
|||
return sortBy(opts, (o) => o.value)
|
||||
})
|
||||
|
||||
const decoderList = computed(() => {
|
||||
const decoder = prefStore.decoder || []
|
||||
const list = []
|
||||
for (const d of decoder) {
|
||||
// decode command
|
||||
list.push({
|
||||
name: d.name,
|
||||
auto: d.auto,
|
||||
decodeCmd: joinCommand(d.decodePath, d.decodeArgs),
|
||||
encodeCmd: joinCommand(d.encodePath, d.encodeArgs),
|
||||
})
|
||||
}
|
||||
return list
|
||||
})
|
||||
|
||||
const decoderColumns = computed(() => {
|
||||
return [
|
||||
{
|
||||
key: 'name',
|
||||
title: () => i18n.t('preferences.decoder.decoder_name'),
|
||||
width: 120,
|
||||
align: 'center',
|
||||
titleAlign: 'center',
|
||||
},
|
||||
{
|
||||
key: 'cmd',
|
||||
title: () => i18n.t('preferences.decoder.cmd_preview'),
|
||||
titleAlign: 'center',
|
||||
render: ({ decodeCmd, encodeCmd }, index) => {
|
||||
return h(NSpace, { vertical: true, wrapItem: false, wrap: false, justify: 'center', size: 15 }, () => [
|
||||
h(NEllipsis, {}, { default: () => decodeCmd, tooltip: () => decodeCmd + '\n\n' + encodeCmd }),
|
||||
h(NEllipsis, {}, { default: () => encodeCmd, tooltip: () => decodeCmd + '\n\n' + encodeCmd }),
|
||||
])
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
title: () => i18n.t('preferences.decoder.status'),
|
||||
width: 80,
|
||||
align: 'center',
|
||||
titleAlign: 'center',
|
||||
render: ({ auto }, index) => {
|
||||
if (auto) {
|
||||
return h(
|
||||
NTooltip,
|
||||
{ delay: 0, showArrow: false },
|
||||
{
|
||||
default: () => i18n.t('preferences.decoder.auto_enabled'),
|
||||
trigger: () => h(NIcon, { component: Checked, size: 16 }),
|
||||
},
|
||||
)
|
||||
}
|
||||
return '-'
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'action',
|
||||
title: () => i18n.t('interface.action'),
|
||||
width: 80,
|
||||
align: 'center',
|
||||
titleAlign: 'center',
|
||||
render: ({ name, auto }, index) => {
|
||||
return h(NSpace, { wrapItem: false, wrap: false, justify: 'center', size: 'small' }, () => [
|
||||
h(IconButton, {
|
||||
icon: Delete,
|
||||
tTooltip: 'interface.delete_row',
|
||||
onClick: () => {
|
||||
prefStore.removeCustomDecoder(name)
|
||||
},
|
||||
}),
|
||||
h(IconButton, {
|
||||
icon: Edit,
|
||||
tTooltip: 'interface.edit_row',
|
||||
onClick: () => {
|
||||
const decoders = prefStore.decoder || []
|
||||
const decoder = find(decoders, { name })
|
||||
const { auto, decodePath, decodeArgs, encodePath, encodeArgs } = decoder
|
||||
dialogStore.openDecoderDialog({
|
||||
name,
|
||||
auto,
|
||||
decodePath,
|
||||
decodeArgs,
|
||||
encodePath,
|
||||
encodeArgs,
|
||||
})
|
||||
},
|
||||
}),
|
||||
])
|
||||
},
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
const onSavePreferences = async () => {
|
||||
const success = await prefStore.savePreferences()
|
||||
if (success) {
|
||||
|
@ -68,11 +170,17 @@ const onClose = () => {
|
|||
:show-icon="false"
|
||||
:title="$t('preferences.name')"
|
||||
preset="dialog"
|
||||
style="width: 500px"
|
||||
style="width: 640px"
|
||||
transform-origin="center">
|
||||
<!-- FIXME: set loading will slow down appear animation of dialog in linux -->
|
||||
<!-- <n-spin :show="loading"> -->
|
||||
<n-tabs v-model:value="tab" animated type="line">
|
||||
<n-tabs
|
||||
v-model:value="tab"
|
||||
animated
|
||||
pane-style="min-height: 300px"
|
||||
placement="left"
|
||||
tab-style="justify-content: right; font-weight: 420;"
|
||||
type="line">
|
||||
<n-tab-pane :tab="$t('preferences.general.name')" display-directive="show" name="general">
|
||||
<n-form :disabled="loading" :model="prefStore.general" :show-require-mark="false" label-placement="top">
|
||||
<n-grid :x-gap="10">
|
||||
|
@ -230,6 +338,23 @@ const onClose = () => {
|
|||
</n-grid>
|
||||
</n-form>
|
||||
</n-tab-pane>
|
||||
|
||||
<!-- Custom decoder pane -->
|
||||
<n-tab-pane :tab="$t('preferences.decoder.name')" display-directive="show:lazy" name="decoder">
|
||||
<n-space>
|
||||
<n-button @click="dialogStore.openDecoderDialog()">
|
||||
<template #icon>
|
||||
<n-icon :component="AddLink" size="18" />
|
||||
</template>
|
||||
{{ $t('preferences.decoder.new') }}
|
||||
</n-button>
|
||||
<n-data-table
|
||||
:columns="decoderColumns"
|
||||
:data="decoderList"
|
||||
:single-line="false"
|
||||
max-height="350px" />
|
||||
</n-space>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
<!-- </n-spin> -->
|
||||
|
||||
|
|
|
@ -269,7 +269,7 @@ const exThemeVars = computed(() => {
|
|||
.nav-menu-item {
|
||||
align-items: center;
|
||||
padding: 10px 0 15px;
|
||||
gap: 18px;
|
||||
gap: 20px;
|
||||
--wails-draggable: none;
|
||||
|
||||
.nav-menu-button {
|
||||
|
|
|
@ -62,6 +62,14 @@
|
|||
"cursor_style_block": "Block",
|
||||
"cursor_style_underline": "Underline",
|
||||
"cursor_style_bar": "Bar"
|
||||
},
|
||||
"decoder": {
|
||||
"name": "Custom Decoder",
|
||||
"new": "New Decoder",
|
||||
"decoder_name": "Name",
|
||||
"cmd_preview": "Preview",
|
||||
"status": "Status",
|
||||
"path": "Decoder Execution Path"
|
||||
}
|
||||
},
|
||||
"interface": {
|
||||
|
@ -339,6 +347,20 @@
|
|||
"quick_set": "Quick Settings",
|
||||
"success": "All TTL of keys have been updated"
|
||||
},
|
||||
"decoder": {
|
||||
"name": "New Decoder/Encoder",
|
||||
"edit_name": "Edit Decoder/Encoder",
|
||||
"new": "New",
|
||||
"decoder": "Decoder",
|
||||
"encoder": "Encoder",
|
||||
"decoder_name": "Name",
|
||||
"auto": "Automatic Decoding",
|
||||
"decode_path": "Decoder Execution Path",
|
||||
"encode_path": "Encoder Execution Path",
|
||||
"path_help": "The path of executable file, any cli alias like 'sh/php/python' are also supported.",
|
||||
"args": "Arguments",
|
||||
"args_help": "Use [VALUE] as a placeholder for encoding/decoding content. The content will be appended to the end if no placeholder is provided."
|
||||
},
|
||||
"upgrade": {
|
||||
"title": "New Version Available",
|
||||
"new_version_tip": "A new version({ver}) is available. Download now?",
|
||||
|
|
|
@ -62,6 +62,14 @@
|
|||
"cursor_style_block": "方块",
|
||||
"cursor_style_underline": "下划线",
|
||||
"cursor_style_bar": "竖线"
|
||||
},
|
||||
"decoder": {
|
||||
"name": "自定义解码",
|
||||
"new": "新增自定义解码",
|
||||
"decoder_name": "解码器名称",
|
||||
"cmd_preview": "命令预览",
|
||||
"status": "状态",
|
||||
"auto_enabled": "已加入自动解码"
|
||||
}
|
||||
},
|
||||
"interface": {
|
||||
|
@ -339,6 +347,20 @@
|
|||
"quick_set": "快捷设置",
|
||||
"success": "已全部更新TTL"
|
||||
},
|
||||
"decoder": {
|
||||
"name": "新增解码/编码器",
|
||||
"edit_name": "编辑解码/编码器",
|
||||
"new": "新增",
|
||||
"decoder": "解码器",
|
||||
"encoder": "编码器",
|
||||
"decoder_name": "解码器名称",
|
||||
"auto": "自动解码",
|
||||
"decode_path": "解码器执行路径",
|
||||
"encode_path": "编码器执行路径",
|
||||
"path_help": "执行文件路径,也可以直接填写命令行接口,如sh/php/python",
|
||||
"args": "运行参数",
|
||||
"args_help": "使用[VALUE]代替编码/解码内容占位符,如果不填内容占位则默认放最后"
|
||||
},
|
||||
"upgrade": {
|
||||
"title": "有可用新版本",
|
||||
"new_version_tip": "新版本({ver}),是否立即下载",
|
||||
|
|
|
@ -91,6 +91,16 @@ const useDialogStore = defineStore('dialog', {
|
|||
ttl: 0,
|
||||
},
|
||||
|
||||
decodeDialogVisible: false,
|
||||
decodeParam: {
|
||||
name: '',
|
||||
auto: true,
|
||||
decodePath: '',
|
||||
decodeArgs: [],
|
||||
encodePath: '',
|
||||
encodeArgs: [],
|
||||
},
|
||||
|
||||
preferencesDialogVisible: false,
|
||||
aboutDialogVisible: false,
|
||||
}),
|
||||
|
@ -290,6 +300,36 @@ const useDialogStore = defineStore('dialog', {
|
|||
this.ttlDialogVisible = false
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {boolean} auto
|
||||
* @param {string} decodePath
|
||||
* @param {string[]} decodeArgs
|
||||
* @param {string} encodePath
|
||||
* @param {string[]} encodeArgs
|
||||
*/
|
||||
openDecoderDialog({
|
||||
name = '',
|
||||
auto = true,
|
||||
decodePath = '',
|
||||
decodeArgs = [],
|
||||
encodePath = '',
|
||||
encodeArgs = [],
|
||||
} = {}) {
|
||||
this.decodeDialogVisible = true
|
||||
this.decodeParam.name = name
|
||||
this.decodeParam.auto = auto !== false
|
||||
this.decodeParam.decodePath = decodePath
|
||||
this.decodeParam.decodeArgs = decodeArgs || []
|
||||
this.decodeParam.encodePath = encodePath
|
||||
this.decodeParam.encodeArgs = encodeArgs || []
|
||||
},
|
||||
|
||||
closeDecoderDialog() {
|
||||
this.decodeDialogVisible = false
|
||||
},
|
||||
|
||||
openPreferencesDialog() {
|
||||
this.preferencesDialogVisible = true
|
||||
},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { lang } from '@/langs/index.js'
|
||||
import { cloneDeep, get, isEmpty, join, map, pick, set, split } from 'lodash'
|
||||
import { cloneDeep, findIndex, get, isEmpty, join, map, merge, pick, set, some, split } from 'lodash'
|
||||
import {
|
||||
CheckForUpdate,
|
||||
GetFontList,
|
||||
|
@ -64,6 +64,7 @@ const usePreferencesStore = defineStore('preferences', {
|
|||
fontSize: 14,
|
||||
cursorStyle: 'block',
|
||||
},
|
||||
decoder: [],
|
||||
lastPref: {},
|
||||
fontList: [],
|
||||
}),
|
||||
|
@ -306,7 +307,7 @@ const usePreferencesStore = defineStore('preferences', {
|
|||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async savePreferences() {
|
||||
const pf = pick(this, ['behavior', 'general', 'editor', 'cli'])
|
||||
const pf = pick(this, ['behavior', 'general', 'editor', 'cli', 'decoder'])
|
||||
const { success, msg } = await SetPreferences(pf)
|
||||
return success === true
|
||||
},
|
||||
|
@ -335,6 +336,81 @@ const usePreferencesStore = defineStore('preferences', {
|
|||
return false
|
||||
},
|
||||
|
||||
/**
|
||||
* add a new custom decoder
|
||||
* @param {string} name
|
||||
* @param {boolean} enable
|
||||
* @param {boolean} auto
|
||||
* @param {string} encodePath
|
||||
* @param {string[]} encodeArgs
|
||||
* @param {string} decodePath
|
||||
* @param {string[]} decodeArgs
|
||||
*/
|
||||
addCustomDecoder({ name, enable = true, auto = true, encodePath, encodeArgs, decodePath, decodeArgs }) {
|
||||
if (some(this.decoder, { name })) {
|
||||
return false
|
||||
}
|
||||
this.decoder = this.decoder || []
|
||||
this.decoder.push({ name, enable, auto, encodePath, encodeArgs, decodePath, decodeArgs })
|
||||
return true
|
||||
},
|
||||
|
||||
/**
|
||||
* update an existing custom decoder
|
||||
* @param {string} newName
|
||||
* @param {boolean} enable
|
||||
* @param {boolean} auto
|
||||
* @param {string} name
|
||||
* @param {string} encodePath
|
||||
* @param {string[]} encodeArgs
|
||||
* @param {string} decodePath
|
||||
* @param {string[]} decodeArgs
|
||||
*/
|
||||
updateCustomDecoder({
|
||||
newName,
|
||||
enable = true,
|
||||
auto = true,
|
||||
name,
|
||||
encodePath,
|
||||
encodeArgs,
|
||||
decodePath,
|
||||
decodeArgs,
|
||||
}) {
|
||||
const idx = findIndex(this.decoder, { name })
|
||||
if (idx === -1) {
|
||||
return false
|
||||
}
|
||||
// conflicted
|
||||
if (newName !== name && some(this.decoder, { name: newName })) {
|
||||
return false
|
||||
}
|
||||
|
||||
this.decoder[idx] = merge(this.decoder[idx], {
|
||||
name: newName || name,
|
||||
enable,
|
||||
auto,
|
||||
encodePath,
|
||||
encodeArgs,
|
||||
decodePath,
|
||||
decodeArgs,
|
||||
})
|
||||
return true
|
||||
},
|
||||
|
||||
/**
|
||||
* remove an existing custom decoder
|
||||
* @param {string} name
|
||||
* @return {boolean}
|
||||
*/
|
||||
removeCustomDecoder(name) {
|
||||
const idx = findIndex(this.decoder, { name })
|
||||
if (idx === -1) {
|
||||
return false
|
||||
}
|
||||
this.decoder.splice(idx, 1)
|
||||
return true
|
||||
},
|
||||
|
||||
async checkForUpdate(manual = false) {
|
||||
let msgRef = null
|
||||
if (manual) {
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import { includes, isEmpty, toUpper, trim } from 'lodash'
|
||||
|
||||
/**
|
||||
* join execute path and arguments into a command string
|
||||
* @param {string} path
|
||||
* @param {string[]} args
|
||||
* @param {string} [emptyContent]
|
||||
* @return {string}
|
||||
*/
|
||||
export const joinCommand = (path, args = [], emptyContent = '-') => {
|
||||
let cmd = ''
|
||||
path = trim(path)
|
||||
if (!isEmpty(path)) {
|
||||
let containValuePlaceholder = false
|
||||
cmd = includes(path, ' ') ? `"${path}"` : path
|
||||
for (let part of args) {
|
||||
part = trim(part)
|
||||
if (isEmpty(part)) {
|
||||
continue
|
||||
}
|
||||
if (includes(part, ' ')) {
|
||||
cmd += ' "' + part + '"'
|
||||
} else {
|
||||
if (toUpper(part) === '{VALUE}') {
|
||||
part = '{VALUE}'
|
||||
containValuePlaceholder = true
|
||||
}
|
||||
cmd += ' ' + part
|
||||
}
|
||||
}
|
||||
if (!containValuePlaceholder) {
|
||||
cmd += ' {VALUE}'
|
||||
}
|
||||
}
|
||||
return cmd || emptyContent
|
||||
}
|
Loading…
Reference in New Issue