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 {
|
switch data.KeyType {
|
||||||
case "string":
|
case "string":
|
||||||
var str string
|
var str string
|
||||||
|
@ -782,7 +784,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
|
||||||
Value: val,
|
Value: val,
|
||||||
})
|
})
|
||||||
if doConvert {
|
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
|
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]),
|
Value: strutil.EncodeRedisKey(loadedVal[i+1]),
|
||||||
})
|
})
|
||||||
if doConvert {
|
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
|
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].Key = loadedVal[i]
|
||||||
items[i/2].Value = strutil.EncodeRedisKey(loadedVal[i+1])
|
items[i/2].Value = strutil.EncodeRedisKey(loadedVal[i+1])
|
||||||
if doConvert {
|
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
|
items[i/2].DisplayValue = dv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -899,7 +901,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
|
||||||
Value: val,
|
Value: val,
|
||||||
})
|
})
|
||||||
if doConvert {
|
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
|
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 {
|
for i, val := range loadedKey {
|
||||||
items[i].Value = val
|
items[i].Value = val
|
||||||
if doConvert {
|
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
|
items[i].DisplayValue = dv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -967,7 +969,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
|
||||||
Score: score,
|
Score: score,
|
||||||
})
|
})
|
||||||
if doConvert {
|
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
|
items[len(items)-1].DisplayValue = dv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1001,7 +1003,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
|
||||||
Value: val,
|
Value: val,
|
||||||
})
|
})
|
||||||
if doConvert {
|
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
|
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 {
|
if vb, merr := json.Marshal(msg.Values); merr != nil {
|
||||||
it.DisplayValue = "{}"
|
it.DisplayValue = "{}"
|
||||||
} else {
|
} 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) {
|
if doFilter && !strings.Contains(it.DisplayValue, param.MatchPattern) {
|
||||||
continue
|
continue
|
||||||
|
@ -1096,7 +1098,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
|
||||||
// blank format indicate auto format
|
// blank format indicate auto format
|
||||||
func (b *browserService) ConvertValue(value any, decode, format string) (resp types.JSResp) {
|
func (b *browserService) ConvertValue(value any, decode, format string) (resp types.JSResp) {
|
||||||
str := strutil.DecodeRedisKey(value)
|
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.Success = true
|
||||||
resp.Data = map[string]any{
|
resp.Data = map[string]any{
|
||||||
"value": value,
|
"value": value,
|
||||||
|
@ -1139,7 +1141,7 @@ func (b *browserService) SetKeyValue(param types.SetKeyParam) (resp types.JSResp
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
var saveStr string
|
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())
|
resp.Msg = fmt.Sprintf(`save to type "%s" fail: %s`, param.Format, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1250,12 +1252,13 @@ func (b *browserService) SetHashValue(param types.SetHashParam) (resp types.JSRe
|
||||||
key := strutil.DecodeRedisKey(param.Key)
|
key := strutil.DecodeRedisKey(param.Key)
|
||||||
str := strutil.DecodeRedisKey(param.Value)
|
str := strutil.DecodeRedisKey(param.Value)
|
||||||
var saveStr, displayStr string
|
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())
|
resp.Msg = fmt.Sprintf(`save to type "%s" fail: %s`, param.Format, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(param.RetDecode) > 0 && len(param.RetFormat) > 0 {
|
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 updated, added, removed []types.HashEntryItem
|
||||||
var replaced []types.HashReplaceItem
|
var replaced []types.HashReplaceItem
|
||||||
|
@ -1473,7 +1476,8 @@ func (b *browserService) SetListItem(param types.SetListParam) (resp types.JSRes
|
||||||
} else {
|
} else {
|
||||||
// replace index value
|
// replace index value
|
||||||
var saveStr string
|
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())
|
resp.Msg = fmt.Sprintf(`save to type "%s" fail: %s`, param.Format, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1484,7 +1488,7 @@ func (b *browserService) SetListItem(param types.SetListParam) (resp types.JSRes
|
||||||
}
|
}
|
||||||
var displayStr string
|
var displayStr string
|
||||||
if len(param.RetDecode) > 0 && len(param.RetFormat) > 0 {
|
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{
|
replaced = append(replaced, types.ListReplaceItem{
|
||||||
Index: param.Index,
|
Index: param.Index,
|
||||||
|
@ -1574,8 +1578,9 @@ func (b *browserService) UpdateSetItem(param types.SetSetParam) (resp types.JSRe
|
||||||
|
|
||||||
// insert new value
|
// insert new value
|
||||||
str = strutil.DecodeRedisKey(param.NewValue)
|
str = strutil.DecodeRedisKey(param.NewValue)
|
||||||
|
decoder := Preferences().GetDecoder()
|
||||||
var saveStr string
|
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())
|
resp.Msg = fmt.Sprintf(`save to type "%s" fail: %s`, param.Format, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1583,7 +1588,7 @@ func (b *browserService) UpdateSetItem(param types.SetSetParam) (resp types.JSRe
|
||||||
// add new item
|
// add new item
|
||||||
var displayStr string
|
var displayStr string
|
||||||
if len(param.RetDecode) > 0 && len(param.RetFormat) > 0 {
|
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{
|
added = append(added, types.SetEntryItem{
|
||||||
Value: saveStr,
|
Value: saveStr,
|
||||||
|
@ -1620,6 +1625,7 @@ func (b *browserService) UpdateZSetValue(param types.SetZSetParam) (resp types.J
|
||||||
var added, updated, removed []types.ZSetEntryItem
|
var added, updated, removed []types.ZSetEntryItem
|
||||||
var replaced []types.ZSetReplaceItem
|
var replaced []types.ZSetReplaceItem
|
||||||
var affect int64
|
var affect int64
|
||||||
|
decoder := Preferences().GetDecoder()
|
||||||
if len(newVal) <= 0 {
|
if len(newVal) <= 0 {
|
||||||
// no new value, delete value
|
// no new value, delete value
|
||||||
if affect, err = client.ZRem(ctx, key, val).Result(); affect > 0 {
|
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 {
|
} else {
|
||||||
var saveVal string
|
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())
|
resp.Msg = fmt.Sprintf(`save to type "%s" fail: %s`, param.Format, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1640,7 +1646,7 @@ func (b *browserService) UpdateZSetValue(param types.SetZSetParam) (resp types.J
|
||||||
Score: param.Score,
|
Score: param.Score,
|
||||||
Member: saveVal,
|
Member: saveVal,
|
||||||
}).Result()
|
}).Result()
|
||||||
displayValue, _, _ := convutil.ConvertTo(val, param.RetDecode, param.RetFormat)
|
displayValue, _, _ := convutil.ConvertTo(val, param.RetDecode, param.RetFormat, decoder)
|
||||||
if affect > 0 {
|
if affect > 0 {
|
||||||
// add new item
|
// add new item
|
||||||
added = append(added, types.ZSetEntryItem{
|
added = append(added, types.ZSetEntryItem{
|
||||||
|
@ -1668,7 +1674,7 @@ func (b *browserService) UpdateZSetValue(param types.SetZSetParam) (resp types.J
|
||||||
Score: param.Score,
|
Score: param.Score,
|
||||||
Member: saveVal,
|
Member: saveVal,
|
||||||
}).Result()
|
}).Result()
|
||||||
displayValue, _, _ := convutil.ConvertTo(saveVal, param.RetDecode, param.RetFormat)
|
displayValue, _, _ := convutil.ConvertTo(saveVal, param.RetDecode, param.RetFormat, decoder)
|
||||||
if affect <= 0 {
|
if affect <= 0 {
|
||||||
// no new value added, just update exists item
|
// no new value added, just update exists item
|
||||||
removed = append(removed, types.ZSetEntryItem{
|
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]
|
updateValues[fieldItems[i].(string)] = fieldItems[i+1]
|
||||||
}
|
}
|
||||||
vb, _ := json.Marshal(updateValues)
|
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.Success = true
|
||||||
resp.Data = struct {
|
resp.Data = struct {
|
||||||
|
|
|
@ -13,11 +13,14 @@ import (
|
||||||
storage2 "tinyrdm/backend/storage"
|
storage2 "tinyrdm/backend/storage"
|
||||||
"tinyrdm/backend/types"
|
"tinyrdm/backend/types"
|
||||||
"tinyrdm/backend/utils/coll"
|
"tinyrdm/backend/utils/coll"
|
||||||
|
convutil "tinyrdm/backend/utils/convert"
|
||||||
|
sliceutil "tinyrdm/backend/utils/slice"
|
||||||
)
|
)
|
||||||
|
|
||||||
type preferencesService struct {
|
type preferencesService struct {
|
||||||
pref *storage2.PreferencesStorage
|
pref *storage2.PreferencesStorage
|
||||||
clientVersion string
|
clientVersion string
|
||||||
|
customDecoder []convutil.CmdConvert
|
||||||
}
|
}
|
||||||
|
|
||||||
var preferences *preferencesService
|
var preferences *preferencesService
|
||||||
|
@ -182,6 +185,23 @@ func (p *preferencesService) GetScanSize() int {
|
||||||
return size
|
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 {
|
type latestRelease struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
TagName string `json:"tag_name"`
|
TagName string `json:"tag_name"`
|
||||||
|
|
|
@ -7,6 +7,7 @@ type Preferences struct {
|
||||||
General PreferencesGeneral `json:"general" yaml:"general"`
|
General PreferencesGeneral `json:"general" yaml:"general"`
|
||||||
Editor PreferencesEditor `json:"editor" yaml:"editor"`
|
Editor PreferencesEditor `json:"editor" yaml:"editor"`
|
||||||
Cli PreferencesCli `json:"cli" yaml:"cli"`
|
Cli PreferencesCli `json:"cli" yaml:"cli"`
|
||||||
|
Decoder []PreferencesDecoder `json:"decoder" yaml:"decoder,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPreferences() Preferences {
|
func NewPreferences() Preferences {
|
||||||
|
@ -33,6 +34,7 @@ func NewPreferences() Preferences {
|
||||||
FontSize: consts.DEFAULT_FONT_SIZE,
|
FontSize: consts.DEFAULT_FONT_SIZE,
|
||||||
CursorStyle: "block",
|
CursorStyle: "block",
|
||||||
},
|
},
|
||||||
|
Decoder: []PreferencesDecoder{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,3 +74,13 @@ type PreferencesCli struct {
|
||||||
FontSize int `json:"fontSize" yaml:"font_size"`
|
FontSize int `json:"fontSize" yaml:"font_size"`
|
||||||
CursorStyle string `json:"cursorStyle" yaml:"cursor_style,omitempty"`
|
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
|
// ConvertTo convert string to specified type
|
||||||
// @param decodeType empty string indicates automatic detection
|
// @param decodeType empty string indicates automatic detection
|
||||||
// @param formatType 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 {
|
if len(str) <= 0 {
|
||||||
// empty content
|
// empty content
|
||||||
if len(formatType) <= 0 {
|
if len(formatType) <= 0 {
|
||||||
|
@ -46,13 +47,17 @@ func ConvertTo(str, decodeType, formatType string) (value, resultDecode, resultF
|
||||||
}
|
}
|
||||||
|
|
||||||
// decode first
|
// decode first
|
||||||
value, resultDecode = decodeWith(str, decodeType)
|
value, resultDecode = decodeWith(str, decodeType, customDecoder)
|
||||||
// then format content
|
// then format content
|
||||||
|
if len(formatType) <= 0 {
|
||||||
|
value, resultFormat = autoViewAs(value)
|
||||||
|
} else {
|
||||||
value, resultFormat = viewAs(value, formatType)
|
value, resultFormat = viewAs(value, formatType)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeWith(str, decodeType string) (value, resultDecode string) {
|
func decodeWith(str, decodeType string, customDecoder []CmdConvert) (value, resultDecode string) {
|
||||||
if len(decodeType) > 0 {
|
if len(decodeType) > 0 {
|
||||||
switch decodeType {
|
switch decodeType {
|
||||||
case types.DECODE_NONE:
|
case types.DECODE_NONE:
|
||||||
|
@ -99,17 +104,31 @@ func decodeWith(str, decodeType string) (value, resultDecode string) {
|
||||||
} else {
|
} else {
|
||||||
value = str
|
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
|
resultDecode = decodeType
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return autoDecode(str)
|
value, resultDecode = autoDecode(str, customDecoder)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// attempt try possible decode method
|
// attempt try possible decode method
|
||||||
// if no decode is possible, it will return the origin string value and "none" decode type
|
// 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 {
|
if len(str) > 0 {
|
||||||
// pure digit content may incorrect regard as some encoded type, skip decode
|
// pure digit content may incorrect regard as some encoded type, skip decode
|
||||||
if match, _ := regexp.MatchString(`^\d+$`, str); !match {
|
if match, _ := regexp.MatchString(`^\d+$`, str); !match {
|
||||||
|
@ -147,6 +166,16 @@ func autoDecode(str string) (value, resultDecode string) {
|
||||||
resultDecode = types.DECODE_MSGPACK
|
resultDecode = types.DECODE_MSGPACK
|
||||||
return
|
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) {
|
func viewAs(str, formatType string) (value, resultFormat string) {
|
||||||
if len(formatType) > 0 {
|
if len(formatType) > 0 {
|
||||||
switch formatType {
|
switch formatType {
|
||||||
|
default:
|
||||||
|
fallthrough
|
||||||
case types.FORMAT_RAW, types.FORMAT_YAML, types.FORMAT_XML:
|
case types.FORMAT_RAW, types.FORMAT_YAML, types.FORMAT_XML:
|
||||||
value = str
|
value = str
|
||||||
|
|
||||||
|
@ -185,8 +216,7 @@ func viewAs(str, formatType string) (value, resultFormat string) {
|
||||||
resultFormat = formatType
|
resultFormat = formatType
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
return
|
||||||
return autoViewAs(str)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// attempt automatic convert to possible types
|
// attempt automatic convert to possible types
|
||||||
|
@ -222,7 +252,7 @@ func autoViewAs(str string) (value, resultFormat string) {
|
||||||
return
|
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
|
value = str
|
||||||
switch format {
|
switch format {
|
||||||
case types.FORMAT_JSON:
|
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")
|
err = errors.New("fail to build msgpack")
|
||||||
}
|
}
|
||||||
return
|
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
|
return str, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package convutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type YamlConvert struct{}
|
type YamlConvert struct{}
|
||||||
|
@ -14,10 +13,5 @@ func (YamlConvert) Encode(str string) (string, bool) {
|
||||||
func (YamlConvert) Decode(str string) (string, bool) {
|
func (YamlConvert) Decode(str string) (string, bool) {
|
||||||
var obj map[string]any
|
var obj map[string]any
|
||||||
err := yaml.Unmarshal([]byte(str), &obj)
|
err := yaml.Unmarshal([]byte(str), &obj)
|
||||||
if err != nil {
|
|
||||||
log.Println(err.Error())
|
|
||||||
} else {
|
|
||||||
log.Println(obj)
|
|
||||||
}
|
|
||||||
return str, err == nil
|
return str, err == nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import FlushDbDialog from '@/components/dialogs/FlushDbDialog.vue'
|
||||||
import ExportKeyDialog from '@/components/dialogs/ExportKeyDialog.vue'
|
import ExportKeyDialog from '@/components/dialogs/ExportKeyDialog.vue'
|
||||||
import ImportKeyDialog from '@/components/dialogs/ImportKeyDialog.vue'
|
import ImportKeyDialog from '@/components/dialogs/ImportKeyDialog.vue'
|
||||||
import { Info } from 'wailsjs/go/services/systemService.js'
|
import { Info } from 'wailsjs/go/services/systemService.js'
|
||||||
|
import DecoderDialog from '@/components/dialogs/DecoderDialog.vue'
|
||||||
|
|
||||||
const prefStore = usePreferencesStore()
|
const prefStore = usePreferencesStore()
|
||||||
const connectionStore = useConnectionStore()
|
const connectionStore = useConnectionStore()
|
||||||
|
@ -79,6 +80,7 @@ watch(
|
||||||
<flush-db-dialog />
|
<flush-db-dialog />
|
||||||
<set-ttl-dialog />
|
<set-ttl-dialog />
|
||||||
<preferences-dialog />
|
<preferences-dialog />
|
||||||
|
<decoder-dialog />
|
||||||
<about-dialog />
|
<about-dialog />
|
||||||
</n-dialog-provider>
|
</n-dialog-provider>
|
||||||
</n-config-provider>
|
</n-config-provider>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, h, ref } from 'vue'
|
import { computed, h, ref } from 'vue'
|
||||||
import { get, map } from 'lodash'
|
import { get, isEmpty } from 'lodash'
|
||||||
import { NIcon, NText } from 'naive-ui'
|
import { NIcon, NText } from 'naive-ui'
|
||||||
import { useRender } from '@/utils/render.js'
|
import { useRender } from '@/utils/render.js'
|
||||||
|
|
||||||
|
@ -10,8 +10,8 @@ const props = defineProps({
|
||||||
value: '',
|
value: '',
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
type: Object,
|
type: Array,
|
||||||
value: {},
|
value: () => [],
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -40,15 +40,32 @@ const dropdownOption = computed(() => {
|
||||||
type: 'divider',
|
type: 'divider',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
return [
|
if (get(props.options, 0) instanceof Array) {
|
||||||
...options,
|
// multiple group
|
||||||
...map(props.options, (t) => {
|
for (let i = 0; i < props.options.length; i++) {
|
||||||
return {
|
if (i !== 0 && !isEmpty(props.options[i])) {
|
||||||
key: t,
|
// add divider
|
||||||
label: t,
|
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) => {
|
const onDropdownSelect = (key) => {
|
||||||
|
|
|
@ -35,6 +35,7 @@ const handleSelectFile = async () => {
|
||||||
<n-input
|
<n-input
|
||||||
:disabled="props.disabled"
|
:disabled="props.disabled"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
|
:title="props.value"
|
||||||
:value="props.value"
|
:value="props.value"
|
||||||
clearable
|
clearable
|
||||||
@clear="onClear"
|
@clear="onClear"
|
||||||
|
|
|
@ -3,7 +3,9 @@ import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
||||||
import Code from '@/components/icons/Code.vue'
|
import Code from '@/components/icons/Code.vue'
|
||||||
import Conversion from '@/components/icons/Conversion.vue'
|
import Conversion from '@/components/icons/Conversion.vue'
|
||||||
import DropdownSelector from '@/components/common/DropdownSelector.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({
|
const props = defineProps({
|
||||||
decode: {
|
decode: {
|
||||||
|
@ -17,13 +19,35 @@ const props = defineProps({
|
||||||
disabled: Boolean,
|
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 emit = defineEmits(['formatChanged', 'update:decode', 'update:format'])
|
||||||
const onFormatChanged = (selDecode, selFormat) => {
|
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
|
selDecode = decodeTypes.NONE
|
||||||
}
|
}
|
||||||
if (!some(formatTypes, (val) => val === selFormat)) {
|
if (!some(formatTypes, (val) => val === selFormat)) {
|
||||||
selFormat = formatTypes.RAW
|
// set to auto chose format
|
||||||
|
selFormat = ''
|
||||||
}
|
}
|
||||||
emit('formatChanged', selDecode, selFormat)
|
emit('formatChanged', selDecode, selFormat)
|
||||||
if (selDecode !== props.decode) {
|
if (selDecode !== props.decode) {
|
||||||
|
@ -41,7 +65,7 @@ const onFormatChanged = (selDecode, selFormat) => {
|
||||||
:default="formatTypes.RAW"
|
:default="formatTypes.RAW"
|
||||||
:disabled="props.disabled"
|
:disabled="props.disabled"
|
||||||
:icon="Code"
|
:icon="Code"
|
||||||
:options="formatTypes"
|
:options="formatTypeOption"
|
||||||
:tooltip="$t('interface.view_as')"
|
:tooltip="$t('interface.view_as')"
|
||||||
:value="props.format"
|
:value="props.format"
|
||||||
@update:value="(f) => onFormatChanged(props.decode, f)" />
|
@update:value="(f) => onFormatChanged(props.decode, f)" />
|
||||||
|
@ -50,10 +74,10 @@ const onFormatChanged = (selDecode, selFormat) => {
|
||||||
:default="decodeTypes.NONE"
|
:default="decodeTypes.NONE"
|
||||||
:disabled="props.disabled"
|
:disabled="props.disabled"
|
||||||
:icon="Conversion"
|
:icon="Conversion"
|
||||||
:options="decodeTypes"
|
:options="decodeTypeOption"
|
||||||
:tooltip="$t('interface.decode_with')"
|
:tooltip="$t('interface.decode_with')"
|
||||||
:value="props.decode"
|
:value="props.decode"
|
||||||
@update:value="(d) => onFormatChanged(d, props.format)" />
|
@update:value="(d) => onFormatChanged(d, '')" />
|
||||||
</n-space>
|
</n-space>
|
||||||
</template>
|
</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>
|
<script setup>
|
||||||
import { computed, ref, watchEffect } from 'vue'
|
import { computed, h, ref, watchEffect } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import useDialog from 'stores/dialog'
|
import useDialog from 'stores/dialog'
|
||||||
import usePreferencesStore from 'stores/preferences.js'
|
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 { typesIconStyle } from '@/consts/support_redis_type.js'
|
||||||
import Help from '@/components/icons/Help.vue'
|
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()
|
const prefStore = usePreferencesStore()
|
||||||
|
|
||||||
|
@ -23,6 +30,8 @@ const initPreferences = async () => {
|
||||||
prevPreferences.value = {
|
prevPreferences.value = {
|
||||||
general: prefStore.general,
|
general: prefStore.general,
|
||||||
editor: prefStore.editor,
|
editor: prefStore.editor,
|
||||||
|
cli: prefStore.cli,
|
||||||
|
decoder: prefStore.decoder,
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
|
@ -43,6 +52,99 @@ const keyOptions = computed(() => {
|
||||||
return sortBy(opts, (o) => o.value)
|
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 onSavePreferences = async () => {
|
||||||
const success = await prefStore.savePreferences()
|
const success = await prefStore.savePreferences()
|
||||||
if (success) {
|
if (success) {
|
||||||
|
@ -68,11 +170,17 @@ const onClose = () => {
|
||||||
:show-icon="false"
|
:show-icon="false"
|
||||||
:title="$t('preferences.name')"
|
:title="$t('preferences.name')"
|
||||||
preset="dialog"
|
preset="dialog"
|
||||||
style="width: 500px"
|
style="width: 640px"
|
||||||
transform-origin="center">
|
transform-origin="center">
|
||||||
<!-- FIXME: set loading will slow down appear animation of dialog in linux -->
|
<!-- FIXME: set loading will slow down appear animation of dialog in linux -->
|
||||||
<!-- <n-spin :show="loading"> -->
|
<!-- <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-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-form :disabled="loading" :model="prefStore.general" :show-require-mark="false" label-placement="top">
|
||||||
<n-grid :x-gap="10">
|
<n-grid :x-gap="10">
|
||||||
|
@ -230,6 +338,23 @@ const onClose = () => {
|
||||||
</n-grid>
|
</n-grid>
|
||||||
</n-form>
|
</n-form>
|
||||||
</n-tab-pane>
|
</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-tabs>
|
||||||
<!-- </n-spin> -->
|
<!-- </n-spin> -->
|
||||||
|
|
||||||
|
|
|
@ -269,7 +269,7 @@ const exThemeVars = computed(() => {
|
||||||
.nav-menu-item {
|
.nav-menu-item {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 10px 0 15px;
|
padding: 10px 0 15px;
|
||||||
gap: 18px;
|
gap: 20px;
|
||||||
--wails-draggable: none;
|
--wails-draggable: none;
|
||||||
|
|
||||||
.nav-menu-button {
|
.nav-menu-button {
|
||||||
|
|
|
@ -62,6 +62,14 @@
|
||||||
"cursor_style_block": "Block",
|
"cursor_style_block": "Block",
|
||||||
"cursor_style_underline": "Underline",
|
"cursor_style_underline": "Underline",
|
||||||
"cursor_style_bar": "Bar"
|
"cursor_style_bar": "Bar"
|
||||||
|
},
|
||||||
|
"decoder": {
|
||||||
|
"name": "Custom Decoder",
|
||||||
|
"new": "New Decoder",
|
||||||
|
"decoder_name": "Name",
|
||||||
|
"cmd_preview": "Preview",
|
||||||
|
"status": "Status",
|
||||||
|
"path": "Decoder Execution Path"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"interface": {
|
"interface": {
|
||||||
|
@ -339,6 +347,20 @@
|
||||||
"quick_set": "Quick Settings",
|
"quick_set": "Quick Settings",
|
||||||
"success": "All TTL of keys have been updated"
|
"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": {
|
"upgrade": {
|
||||||
"title": "New Version Available",
|
"title": "New Version Available",
|
||||||
"new_version_tip": "A new version({ver}) is available. Download now?",
|
"new_version_tip": "A new version({ver}) is available. Download now?",
|
||||||
|
|
|
@ -62,6 +62,14 @@
|
||||||
"cursor_style_block": "方块",
|
"cursor_style_block": "方块",
|
||||||
"cursor_style_underline": "下划线",
|
"cursor_style_underline": "下划线",
|
||||||
"cursor_style_bar": "竖线"
|
"cursor_style_bar": "竖线"
|
||||||
|
},
|
||||||
|
"decoder": {
|
||||||
|
"name": "自定义解码",
|
||||||
|
"new": "新增自定义解码",
|
||||||
|
"decoder_name": "解码器名称",
|
||||||
|
"cmd_preview": "命令预览",
|
||||||
|
"status": "状态",
|
||||||
|
"auto_enabled": "已加入自动解码"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"interface": {
|
"interface": {
|
||||||
|
@ -339,6 +347,20 @@
|
||||||
"quick_set": "快捷设置",
|
"quick_set": "快捷设置",
|
||||||
"success": "已全部更新TTL"
|
"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": {
|
"upgrade": {
|
||||||
"title": "有可用新版本",
|
"title": "有可用新版本",
|
||||||
"new_version_tip": "新版本({ver}),是否立即下载",
|
"new_version_tip": "新版本({ver}),是否立即下载",
|
||||||
|
|
|
@ -91,6 +91,16 @@ const useDialogStore = defineStore('dialog', {
|
||||||
ttl: 0,
|
ttl: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
decodeDialogVisible: false,
|
||||||
|
decodeParam: {
|
||||||
|
name: '',
|
||||||
|
auto: true,
|
||||||
|
decodePath: '',
|
||||||
|
decodeArgs: [],
|
||||||
|
encodePath: '',
|
||||||
|
encodeArgs: [],
|
||||||
|
},
|
||||||
|
|
||||||
preferencesDialogVisible: false,
|
preferencesDialogVisible: false,
|
||||||
aboutDialogVisible: false,
|
aboutDialogVisible: false,
|
||||||
}),
|
}),
|
||||||
|
@ -290,6 +300,36 @@ const useDialogStore = defineStore('dialog', {
|
||||||
this.ttlDialogVisible = false
|
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() {
|
openPreferencesDialog() {
|
||||||
this.preferencesDialogVisible = true
|
this.preferencesDialogVisible = true
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { lang } from '@/langs/index.js'
|
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 {
|
import {
|
||||||
CheckForUpdate,
|
CheckForUpdate,
|
||||||
GetFontList,
|
GetFontList,
|
||||||
|
@ -64,6 +64,7 @@ const usePreferencesStore = defineStore('preferences', {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
cursorStyle: 'block',
|
cursorStyle: 'block',
|
||||||
},
|
},
|
||||||
|
decoder: [],
|
||||||
lastPref: {},
|
lastPref: {},
|
||||||
fontList: [],
|
fontList: [],
|
||||||
}),
|
}),
|
||||||
|
@ -306,7 +307,7 @@ const usePreferencesStore = defineStore('preferences', {
|
||||||
* @returns {Promise<boolean>}
|
* @returns {Promise<boolean>}
|
||||||
*/
|
*/
|
||||||
async savePreferences() {
|
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)
|
const { success, msg } = await SetPreferences(pf)
|
||||||
return success === true
|
return success === true
|
||||||
},
|
},
|
||||||
|
@ -335,6 +336,81 @@ const usePreferencesStore = defineStore('preferences', {
|
||||||
return false
|
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) {
|
async checkForUpdate(manual = false) {
|
||||||
let msgRef = null
|
let msgRef = null
|
||||||
if (manual) {
|
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