diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..59b4e9e --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +build/bin +node_modules +frontend/dist +.vscode +.idea diff --git a/README.md b/README.md index 8722815..749f8b1 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,10 @@ -# tiny-rdm -Redis Desktop Manager +# README + +## Tiny RDM + +## About + +This is the official Wails Vue template. + +You can configure the project by editing `wails.json`. More information about the project settings can be found +here: https://wails.io/docs/reference/project-config diff --git a/app.go b/app.go new file mode 100644 index 0000000..af53038 --- /dev/null +++ b/app.go @@ -0,0 +1,27 @@ +package main + +import ( + "context" + "fmt" +) + +// App struct +type App struct { + ctx context.Context +} + +// NewApp creates a new App application struct +func NewApp() *App { + return &App{} +} + +// startup is called when the app starts. The context is saved +// so we can call the runtime methods +func (a *App) startup(ctx context.Context) { + a.ctx = ctx +} + +// Greet returns a greeting for the given name +func (a *App) Greet(name string) string { + return fmt.Sprintf("Hello %s, It's show time!", name) +} diff --git a/backend/services/connection_service.go b/backend/services/connection_service.go new file mode 100644 index 0000000..a487507 --- /dev/null +++ b/backend/services/connection_service.go @@ -0,0 +1,861 @@ +package services + +import ( + "context" + "errors" + "fmt" + "github.com/redis/go-redis/v9" + "log" + "strconv" + "strings" + "sync" + "time" + . "tinyrdm/backend/storage" + "tinyrdm/backend/types" + maputil "tinyrdm/backend/utils/map" + redis2 "tinyrdm/backend/utils/redis" +) + +type connectionService struct { + ctx context.Context + conns *ConnectionsStorage + connMap map[string]connectionItem +} + +type connectionItem struct { + rdb *redis.Client + ctx context.Context + cancelFunc context.CancelFunc +} + +type keyItem struct { + Type string `json:"t"` +} + +var connection *connectionService +var onceConnection sync.Once + +func Connection() *connectionService { + if connection == nil { + onceConnection.Do(func() { + connection = &connectionService{ + conns: NewConnections(), + connMap: map[string]connectionItem{}, + } + }) + } + return connection +} + +func (c *connectionService) Start(ctx context.Context) { + c.ctx = ctx +} + +func (c *connectionService) Stop(ctx context.Context) { + for _, item := range c.connMap { + if item.rdb != nil { + item.cancelFunc() + item.rdb.Close() + } + } + c.connMap = map[string]connectionItem{} +} + +func (c *connectionService) TestConnection(host string, port int, username, password string) (resp types.JSResp) { + rdb := redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("%s:%d", host, port), + Username: username, + Password: password, + }) + defer rdb.Close() + if _, err := rdb.Ping(c.ctx).Result(); err != nil && err != redis.Nil { + resp.Msg = err.Error() + } else { + resp.Success = true + } + return +} + +// ListConnection list all saved connection in local profile +func (c *connectionService) ListConnection() (resp types.JSResp) { + resp.Success = true + resp.Data = c.conns.GetConnections() + return +} + +// SaveConnection save connection config to local profile +func (c *connectionService) SaveConnection(param types.Connection, replace bool) (resp types.JSResp) { + if err := c.conns.UpsertConnection(param, replace); err != nil { + resp.Msg = err.Error() + } else { + resp.Success = true + } + return +} + +// OpenConnection open redis server connection +func (c *connectionService) OpenConnection(name string) (resp types.JSResp) { + rdb, ctx, err := c.getRedisClient(name, 0) + if err != nil { + resp.Msg = err.Error() + return + } + + // get total database + config, err := rdb.ConfigGet(ctx, "databases").Result() + if err != nil { + resp.Msg = err.Error() + return + } + totaldb, err := strconv.Atoi(config["database"]) + if err != nil { + totaldb = 16 + } + + // get database info + res, err := rdb.Info(ctx, "keyspace").Result() + if err != nil { + resp.Msg = "list database fail:" + err.Error() + return + } + // Parse all db, response content like below + var dbs []types.ConnectionDB + info := c.parseInfo(res) + for i := 0; i < totaldb; i++ { + dbName := "db" + strconv.Itoa(i) + dbInfoStr := info[dbName] + if len(dbInfoStr) > 0 { + dbInfo := c.parseDBItemInfo(dbInfoStr) + dbs = append(dbs, types.ConnectionDB{ + Name: dbName, + Keys: dbInfo["keys"], + Expires: dbInfo["expires"], + AvgTTL: dbInfo["avg_ttl"], + }) + } else { + dbs = append(dbs, types.ConnectionDB{ + Name: dbName, + }) + } + } + + resp.Success = true + resp.Data = map[string]any{ + "db": dbs, + } + return +} + +// CloseConnection close redis server connection +func (c *connectionService) CloseConnection(name string) (resp types.JSResp) { + item, ok := c.connMap[name] + if ok { + delete(c.connMap, name) + if item.rdb != nil { + item.cancelFunc() + item.rdb.Close() + } + } + resp.Success = true + return +} + +// get redis client from local cache or create a new open +// if db >= 0, also switch to db index +func (c *connectionService) getRedisClient(connName string, db int) (*redis.Client, context.Context, error) { + item, ok := c.connMap[connName] + var rdb *redis.Client + var ctx context.Context + if ok { + rdb, ctx = item.rdb, item.ctx + } else { + connGroups := c.conns.GetConnections() + var selConn *types.Connection + for _, connGroup := range connGroups { + for _, conn := range connGroup.Connections { + if conn.Name == connName { + selConn = &conn + break + } + } + } + if selConn == nil { + return nil, nil, errors.New("no match connection connName") + } + + rdb = redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("%s:%d", selConn.Addr, selConn.Port), + Username: selConn.Username, + Password: selConn.Password, + ReadTimeout: -1, + }) + rdb.AddHook(redis2.NewHook(connName)) + + if _, err := rdb.Ping(c.ctx).Result(); err != nil && err != redis.Nil { + return nil, nil, errors.New("can not connect to redis server:" + err.Error()) + } + var cancelFunc context.CancelFunc + ctx, cancelFunc = context.WithCancel(c.ctx) + c.connMap[connName] = connectionItem{ + rdb: rdb, + ctx: ctx, + cancelFunc: cancelFunc, + } + } + + if db >= 0 { + if err := rdb.Do(ctx, "SELECT", strconv.Itoa(db)).Err(); err != nil { + return nil, nil, err + } + } + return rdb, ctx, nil +} + +// parse command response content which use "redis info" +// # Keyspace\r\ndb0:keys=2,expires=1,avg_ttl=1877111749\r\ndb1:keys=33,expires=0,avg_ttl=0\r\ndb3:keys=17,expires=0,avg_ttl=0\r\ndb5:keys=3,expires=0,avg_ttl=0\r\n +func (c *connectionService) parseInfo(info string) map[string]string { + parsedInfo := map[string]string{} + lines := strings.Split(info, "\r\n") + if len(lines) > 0 { + for _, line := range lines { + if !strings.HasPrefix(line, "#") { + items := strings.SplitN(line, ":", 2) + if len(items) < 2 { + continue + } + parsedInfo[items[0]] = items[1] + } + } + } + return parsedInfo +} + +// parse db item value, content format like below +// keys=2,expires=1,avg_ttl=1877111749 +func (c *connectionService) parseDBItemInfo(info string) map[string]int { + ret := map[string]int{} + items := strings.Split(info, ",") + for _, item := range items { + kv := strings.SplitN(item, "=", 2) + if len(kv) > 1 { + ret[kv[0]], _ = strconv.Atoi(kv[1]) + } + } + return ret +} + +// OpenDatabase open select database, and list all keys +// @param path contain connection name and db name +func (c *connectionService) OpenDatabase(connName string, db int) (resp types.JSResp) { + log.Println("open db:" + strconv.Itoa(db)) + rdb, ctx, err := c.getRedisClient(connName, db) + if err != nil { + resp.Msg = err.Error() + return + } + + //var keys []string + keys := map[string]keyItem{} + var cursor uint64 + for { + var loadedKey []string + loadedKey, cursor, err = rdb.Scan(ctx, cursor, "*", 10000).Result() + if err != nil { + resp.Msg = err.Error() + return + } + //c.updateDBKey(connName, db, loadedKey) + for _, k := range loadedKey { + //t, _ := rdb.Type(ctx, k).Result() + keys[k] = keyItem{Type: "t"} + } + //keys = append(keys, loadedKey...) + // no more loadedKey + if cursor == 0 { + break + } + } + + resp.Success = true + resp.Data = map[string]any{ + "keys": keys, + } + return +} + +// GetKeyValue get value by key +func (c *connectionService) GetKeyValue(connName string, db int, key string) (resp types.JSResp) { + rdb, ctx, err := c.getRedisClient(connName, db) + if err != nil { + resp.Msg = err.Error() + return + } + + var keyType string + var dur time.Duration + keyType, err = rdb.Type(ctx, key).Result() + if err != nil { + resp.Msg = err.Error() + return + } + + var ttl int64 + if dur, err = rdb.TTL(ctx, key).Result(); err != nil { + ttl = -1 + } else { + if dur < 0 { + ttl = -1 + } else { + ttl = int64(dur.Seconds()) + } + } + + var value any + var cursor uint64 + switch strings.ToLower(keyType) { + case "string": + value, err = rdb.Get(ctx, key).Result() + case "list": + value, err = rdb.LRange(ctx, key, 0, -1).Result() + case "hash": + //value, err = rdb.HGetAll(ctx, key).Result() + items := map[string]string{} + for { + var loadedVal []string + loadedVal, cursor, err = rdb.HScan(ctx, key, cursor, "*", 10000).Result() + if err != nil { + resp.Msg = err.Error() + return + } + for i := 0; i < len(loadedVal); i += 2 { + items[loadedVal[i]] = loadedVal[i+1] + } + if cursor == 0 { + break + } + } + value = items + case "set": + //value, err = rdb.SMembers(ctx, key).Result() + items := []string{} + for { + var loadedKey []string + loadedKey, cursor, err = rdb.SScan(ctx, key, cursor, "*", 10000).Result() + if err != nil { + resp.Msg = err.Error() + return + } + items = append(items, loadedKey...) + if cursor == 0 { + break + } + } + value = items + case "zset": + //value, err = rdb.ZRangeWithScores(ctx, key, 0, -1).Result() + var items []types.ZSetItem + for { + var loadedVal []string + loadedVal, cursor, err = rdb.ZScan(ctx, key, cursor, "*", 10000).Result() + if err != nil { + resp.Msg = err.Error() + return + } + var score float64 + for i := 0; i < len(loadedVal); i += 2 { + if score, err = strconv.ParseFloat(loadedVal[i+1], 64); err == nil { + items = append(items, types.ZSetItem{ + Value: loadedVal[i], + Score: score, + }) + } + } + if cursor == 0 { + break + } + } + value = items + } + if err != nil { + resp.Msg = err.Error() + return + } + resp.Success = true + resp.Data = map[string]any{ + "type": keyType, + "ttl": ttl, + "value": value, + } + return +} + +// SetKeyValue set value by key +func (c *connectionService) SetKeyValue(connName string, db int, key, keyType string, value any, ttl int64) (resp types.JSResp) { + rdb, ctx, err := c.getRedisClient(connName, db) + if err != nil { + resp.Msg = err.Error() + return + } + + var expiration time.Duration + if ttl < 0 { + expiration = redis.KeepTTL + } else { + expiration = time.Duration(ttl) * time.Second + } + switch strings.ToLower(keyType) { + case "string": + if str, ok := value.(string); !ok { + resp.Msg = "invalid string value" + return + } else { + _, err = rdb.Set(ctx, key, str, expiration).Result() + } + case "list": + if strs, ok := value.([]any); !ok { + resp.Msg = "invalid list value" + return + } else { + _, err = rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error { + pipe.LPush(ctx, key, strs...) + if expiration > 0 { + pipe.Expire(ctx, key, expiration) + } + return nil + }) + } + case "hash": + if strs, ok := value.([]any); !ok { + resp.Msg = "invalid hash value" + return + } else { + _, err = rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error { + if len(strs) > 1 { + for i := 0; i < len(strs); i += 2 { + pipe.HSetNX(ctx, key, strs[i].(string), strs[i+1]) + } + } else { + pipe.HSet(ctx, key) + } + if expiration > 0 { + pipe.Expire(ctx, key, expiration) + } + return nil + }) + } + case "set": + if strs, ok := value.([]any); !ok || len(strs) <= 0 { + resp.Msg = "invalid set value" + return + } else { + _, err = rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error { + if len(strs) > 1 { + for _, str := range strs { + pipe.SAdd(ctx, key, str.(string)) + } + } else { + pipe.SAdd(ctx, key) + } + if expiration > 0 { + pipe.Expire(ctx, key, expiration) + } + return nil + }) + } + case "zset": + if strs, ok := value.([]any); !ok || len(strs) <= 0 { + resp.Msg = "invalid zset value" + return + } else { + _, err = rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error { + var members []redis.Z + for i := 0; i < len(strs); i += 2 { + members = append(members, redis.Z{ + Score: strs[i].(float64), + Member: strs[i+1], + }) + } + + if len(members) > 0 { + pipe.ZAdd(ctx, key, members...) + } else { + pipe.ZAdd(ctx, key) + } + if expiration > 0 { + pipe.Expire(ctx, key, expiration) + } + return nil + }) + } + } + + if err != nil { + resp.Msg = err.Error() + return + } + resp.Success = true + resp.Data = map[string]any{ + "value": value, + } + return +} + +// SetHashValue set hash field +func (c *connectionService) SetHashValue(connName string, db int, key, field, newField, value string) (resp types.JSResp) { + rdb, ctx, err := c.getRedisClient(connName, db) + if err != nil { + resp.Msg = err.Error() + return + } + + var removedField []string + updatedField := map[string]string{} + if len(field) <= 0 { + // old filed is empty, add new field + _, err = rdb.HSet(ctx, key, newField, value).Result() + updatedField[newField] = value + } else if len(newField) <= 0 { + // new field is empty, delete old field + _, err = rdb.HDel(ctx, key, field, value).Result() + removedField = append(removedField, field) + } else if field == newField { + // replace field + _, err = rdb.HSet(ctx, key, newField, value).Result() + updatedField[newField] = value + } else { + // remove old field and add new field + if _, err = rdb.HDel(ctx, key, field).Result(); err != nil { + resp.Msg = err.Error() + return + } + _, err = rdb.HSet(ctx, key, newField, value).Result() + removedField = append(removedField, field) + updatedField[newField] = value + } + if err != nil { + resp.Msg = err.Error() + return + } + + resp.Success = true + resp.Data = map[string]any{ + "removed": removedField, + "updated": updatedField, + } + return +} + +// AddHashField add or update hash field +func (c *connectionService) AddHashField(connName string, db int, key string, action int, fieldItems []any) (resp types.JSResp) { + rdb, ctx, err := c.getRedisClient(connName, db) + if err != nil { + resp.Msg = err.Error() + return + } + + updated := map[string]any{} + switch action { + case 1: + // ignore duplicated fields + for i := 0; i < len(fieldItems); i += 2 { + _, err = rdb.HSetNX(ctx, key, fieldItems[i].(string), fieldItems[i+1]).Result() + if err == nil { + updated[fieldItems[i].(string)] = fieldItems[i+1] + } + } + default: + // overwrite duplicated fields + _, err = rdb.HSet(ctx, key, fieldItems...).Result() + for i := 0; i < len(fieldItems); i += 2 { + updated[fieldItems[i].(string)] = fieldItems[i+1] + } + } + if err != nil { + resp.Msg = err.Error() + return + } + + resp.Success = true + resp.Data = map[string]any{ + "updated": updated, + } + return +} + +// AddListItem add item to list or remove from it +func (c *connectionService) AddListItem(connName string, db int, key string, action int, items []any) (resp types.JSResp) { + rdb, ctx, err := c.getRedisClient(connName, db) + if err != nil { + resp.Msg = err.Error() + return + } + + var leftPush, rightPush []any + switch action { + case 0: + // push to head + _, err = rdb.LPush(ctx, key, items...).Result() + leftPush = append(leftPush, items...) + default: + // append to tail + _, err = rdb.RPush(ctx, key, items...).Result() + rightPush = append(rightPush, items...) + } + if err != nil { + resp.Msg = err.Error() + return + } + + resp.Success = true + resp.Data = map[string]any{ + "left": leftPush, + "right": rightPush, + } + return +} + +// SetListItem update or remove list item by index +func (c *connectionService) SetListItem(connName string, db int, key string, index int64, value string) (resp types.JSResp) { + rdb, ctx, err := c.getRedisClient(connName, db) + if err != nil { + resp.Msg = err.Error() + return + } + + var removed []int64 + updated := map[int64]string{} + if len(value) <= 0 { + // remove from list + err = rdb.LSet(ctx, key, index, "---VALUE_REMOVED_BY_TINY_RDM---").Err() + if err != nil { + resp.Msg = err.Error() + return + } + + err = rdb.LRem(ctx, key, 1, "---VALUE_REMOVED_BY_TINY_RDM---").Err() + if err != nil { + resp.Msg = err.Error() + return + } + removed = append(removed, index) + } else { + // replace index value + err = rdb.LSet(ctx, key, index, value).Err() + if err != nil { + resp.Msg = err.Error() + return + } + updated[index] = value + } + + resp.Success = true + resp.Data = map[string]any{ + "removed": removed, + "updated": updated, + } + return +} + +// SetSetItem add members to set or remove from set +func (c *connectionService) SetSetItem(connName string, db int, key string, remove bool, members []any) (resp types.JSResp) { + rdb, ctx, err := c.getRedisClient(connName, db) + if err != nil { + resp.Msg = err.Error() + return + } + + if remove { + _, err = rdb.SRem(ctx, key, members...).Result() + } else { + _, err = rdb.SAdd(ctx, key, members...).Result() + } + if err != nil { + resp.Msg = err.Error() + return + } + + resp.Success = true + return +} + +// UpdateSetItem replace member of set +func (c *connectionService) UpdateSetItem(connName string, db int, key, value, newValue string) (resp types.JSResp) { + rdb, ctx, err := c.getRedisClient(connName, db) + if err != nil { + resp.Msg = err.Error() + return + } + + _, _ = rdb.SRem(ctx, key, value).Result() + _, err = rdb.SAdd(ctx, key, newValue).Result() + if err != nil { + resp.Msg = err.Error() + return + } + + resp.Success = true + return +} + +// UpdateZSetValue update value of sorted set member +func (c *connectionService) UpdateZSetValue(connName string, db int, key, value, newValue string, score float64) (resp types.JSResp) { + rdb, ctx, err := c.getRedisClient(connName, db) + if err != nil { + resp.Msg = err.Error() + return + } + + updated := map[string]any{} + var removed []string + if len(newValue) <= 0 { + // blank new value, delete value + _, err = rdb.ZRem(ctx, key, value).Result() + if err == nil { + removed = append(removed, value) + } + } else if newValue == value { + // update score only + _, err = rdb.ZAdd(ctx, key, redis.Z{ + Score: score, + Member: value, + }).Result() + } else { + // remove old value and add new one + _, err = rdb.ZRem(ctx, key, value).Result() + if err == nil { + removed = append(removed, value) + } + + _, err = rdb.ZAdd(ctx, key, redis.Z{ + Score: score, + Member: newValue, + }).Result() + if err == nil { + updated[newValue] = score + } + } + if err != nil { + resp.Msg = err.Error() + return + } + + resp.Success = true + resp.Data = map[string]any{ + "updated": updated, + "removed": removed, + } + return +} + +// AddZSetValue add item to sorted set +func (c *connectionService) AddZSetValue(connName string, db int, key string, action int, valueScore map[string]float64) (resp types.JSResp) { + rdb, ctx, err := c.getRedisClient(connName, db) + if err != nil { + resp.Msg = err.Error() + return + } + + members := maputil.ToSlice(valueScore, func(k string) redis.Z { + return redis.Z{ + Score: valueScore[k], + Member: k, + } + }) + + switch action { + case 1: + // ignore duplicated fields + _, err = rdb.ZAddNX(ctx, key, members...).Result() + default: + // overwrite duplicated fields + _, err = rdb.ZAdd(ctx, key, members...).Result() + } + if err != nil { + resp.Msg = err.Error() + return + } + + resp.Success = true + return +} + +// SetKeyTTL set ttl of key +func (c *connectionService) SetKeyTTL(connName string, db int, key string, ttl int64) (resp types.JSResp) { + rdb, ctx, err := c.getRedisClient(connName, db) + if err != nil { + resp.Msg = err.Error() + return + } + + var expiration time.Duration + if ttl < 0 { + if err = rdb.Persist(ctx, key).Err(); err != nil { + resp.Msg = err.Error() + return + } + } else { + expiration = time.Duration(ttl) * time.Second + if err = rdb.Expire(ctx, key, expiration).Err(); err != nil { + resp.Msg = err.Error() + return + } + } + + resp.Success = true + return +} + +// RemoveKey remove redis key +func (c *connectionService) RemoveKey(connName string, db int, key string) (resp types.JSResp) { + rdb, ctx, err := c.getRedisClient(connName, db) + if err != nil { + resp.Msg = err.Error() + return + } + + rmCount, err := rdb.Del(ctx, key).Result() + if err != nil { + resp.Msg = err.Error() + return + } + + resp.Success = true + resp.Data = map[string]any{ + "effect_count": rmCount, + } + return +} + +// RenameKey rename key +func (c *connectionService) RenameKey(connName string, db int, key, newKey string) (resp types.JSResp) { + rdb, ctx, err := c.getRedisClient(connName, db) + if err != nil { + resp.Msg = err.Error() + return + } + + _, err = rdb.RenameNX(ctx, key, newKey).Result() + if err != nil { + resp.Msg = err.Error() + return + } + + resp.Success = true + return +} + +// update or insert key info to database +//func (c *connectionService) updateDBKey(connName string, db int, keys []string, separator string) { +// dbStruct := map[string]any{} +// for _, key := range keys { +// keyPart := strings.Split(key, separator) +// prefixLen := len(keyPart)-1 +// if prefixLen > 0 { +// for i := 0; i < prefixLen; i++ { +// if dbStruct[keyPart[i]] +// keyPart[i] +// } +// } +// log.Println("key", key) +// } +//} diff --git a/backend/services/storage_service.go b/backend/services/storage_service.go new file mode 100644 index 0000000..5d540f1 --- /dev/null +++ b/backend/services/storage_service.go @@ -0,0 +1,18 @@ +package services + +import "sync" + +type storageService struct { +} + +var storage *storageService +var onceStorage sync.Once + +func Storage() *storageService { + if storage == nil { + onceStorage.Do(func() { + storage = &storageService{} + }) + } + return storage +} diff --git a/backend/storage/connections.go b/backend/storage/connections.go new file mode 100644 index 0000000..b366f84 --- /dev/null +++ b/backend/storage/connections.go @@ -0,0 +1,215 @@ +package storage + +import ( + "errors" + "gopkg.in/yaml.v3" + "sync" + "tinyrdm/backend/types" + sliceutil "tinyrdm/backend/utils/slice" +) + +type ConnectionsStorage struct { + storage *localStorage + mutex sync.Mutex +} + +func NewConnections() *ConnectionsStorage { + return &ConnectionsStorage{ + storage: NewLocalStore("connections.yaml"), + } +} + +func (c *ConnectionsStorage) defaultConnections() []types.ConnectionGroup { + return []types.ConnectionGroup{ + { + GroupName: "", + Connections: []types.Connection{}, + }, + } +} + +func (c *ConnectionsStorage) defaultConnectionItem() types.Connection { + return types.Connection{ + Name: "", + Addr: "127.0.0.1", + Port: 6379, + Username: "", + Password: "", + DefaultFilter: "*", + KeySeparator: ":", + ConnTimeout: 60, + ExecTimeout: 60, + MarkColor: "", + } +} + +func (c *ConnectionsStorage) getConnections() (ret []types.ConnectionGroup) { + b, err := c.storage.Load() + if err != nil { + ret = c.defaultConnections() + return + } + + if err = yaml.Unmarshal(b, &ret); err != nil { + ret = c.defaultConnections() + return + } + if len(ret) <= 0 { + ret = c.defaultConnections() + } + //if !sliceutil.AnyMatch(ret, func(i int) bool { + // return ret[i].GroupName == "" + //}) { + // ret = append(ret, c.defaultConnections()...) + //} + return +} + +// GetConnections get all store connections from local +func (c *ConnectionsStorage) GetConnections() (ret []types.ConnectionGroup) { + c.mutex.Lock() + defer c.mutex.Unlock() + + return c.getConnections() +} + +// GetConnectionsFlat get all store connections from local flat(exclude group level) +func (c *ConnectionsStorage) GetConnectionsFlat() (ret []types.Connection) { + c.mutex.Lock() + defer c.mutex.Unlock() + + conns := c.getConnections() + for _, group := range conns { + for _, conn := range group.Connections { + ret = append(ret, conn) + } + } + return +} + +func (c *ConnectionsStorage) saveConnections(conns []types.ConnectionGroup) error { + b, err := yaml.Marshal(&conns) + if err != nil { + return err + } + if err = c.storage.Store(b); err != nil { + return err + } + return nil +} + +// UpsertConnection update or insert a connection +func (c *ConnectionsStorage) UpsertConnection(param types.Connection, replace bool) error { + c.mutex.Lock() + defer c.mutex.Unlock() + + conns := c.getConnections() + groupIndex := -1 + connIndex := -1 + for i, group := range conns { + for j, conn := range group.Connections { + // check conflict connection name + if conn.Name == param.Name { + if !replace { + return errors.New("duplicated connection name") + } else { + // different group name, should move group + // remove from current group first + if group.GroupName != param.Group { + group.Connections = append(group.Connections[:j], group.Connections[j+1:]...) + + // find new group index + groupIndex, _ = sliceutil.Find(conns, func(i int) bool { + return conns[i].GroupName == param.Group + }) + } else { + groupIndex = i + connIndex = j + } + break + } + } + } + } + + if groupIndex >= 0 { + // group exists + if connIndex >= 0 { + // connection exists + conns[groupIndex].Connections[connIndex] = param + } else { + // new connection + conns[groupIndex].Connections = append(conns[groupIndex].Connections, param) + } + } else { + // new group + group := types.ConnectionGroup{ + GroupName: param.Group, + Connections: []types.Connection{param}, + } + conns = append(conns, group) + } + + return c.saveConnections(conns) +} + +// RemoveConnection remove special connection +func (c *ConnectionsStorage) RemoveConnection(group, name string) error { + c.mutex.Lock() + defer c.mutex.Unlock() + + conns := c.getConnections() + for i, connGroup := range conns { + if connGroup.GroupName == group { + for j, conn := range connGroup.Connections { + if conn.Name == name { + connList := conns[i].Connections + connList = append(connList[:j], connList[j+1:]...) + conns[i].Connections = connList + return c.saveConnections(conns) + } + } + } + } + + return errors.New("no match connection") +} + +// UpsertGroup update or insert a group +// When want to create group only, set group == param.name +func (c *ConnectionsStorage) UpsertGroup(group string, param types.ConnectionGroup) error { + c.mutex.Lock() + defer c.mutex.Unlock() + + conns := c.getConnections() + for i, connGroup := range conns { + if connGroup.GroupName == group { + conns[i].GroupName = param.GroupName + return c.saveConnections(conns) + } + } + + // No match group, create one + connGroup := types.ConnectionGroup{ + GroupName: param.GroupName, + Connections: []types.Connection{}, + } + conns = append(conns, connGroup) + return c.saveConnections(conns) +} + +// RemoveGroup remove special group, include all connections under it +func (c *ConnectionsStorage) RemoveGroup(group string) error { + c.mutex.Lock() + defer c.mutex.Unlock() + + conns := c.getConnections() + for i, connGroup := range conns { + if connGroup.GroupName == group { + conns = append(conns[:i], conns[i+1:]...) + return c.saveConnections(conns) + } + } + + return errors.New("no match group") +} diff --git a/backend/storage/local_storage.go b/backend/storage/local_storage.go new file mode 100644 index 0000000..65c7e09 --- /dev/null +++ b/backend/storage/local_storage.go @@ -0,0 +1,55 @@ +package storage + +import ( + "github.com/vrischmann/userdir" + "os" + "path" +) + +// localStorage provides reading and writing application data to the user's +// configuration directory. +type localStorage struct { + ConfPath string +} + +// NewLocalStore returns a localStore instance. +func NewLocalStore(filename string) *localStorage { + return &localStorage{ + ConfPath: path.Join(userdir.GetConfigHome(), "TinyRDM", filename), + } +} + +// Load reads the given file in the user's configuration directory and returns +// its contents. +func (l *localStorage) Load() ([]byte, error) { + d, err := os.ReadFile(l.ConfPath) + if err != nil { + return nil, err + } + return d, err +} + +// Store writes data to the user's configuration directory at the given +// filename. +func (l *localStorage) Store(data []byte) error { + dir := path.Dir(l.ConfPath) + if err := ensureDirExists(dir); err != nil { + return err + } + if err := os.WriteFile(l.ConfPath, data, 0777); err != nil { + return err + } + return nil +} + +// ensureDirExists checks for the existence of the directory at the given path, +// which is created if it does not exist. +func ensureDirExists(path string) error { + _, err := os.Stat(path) + if os.IsNotExist(err) { + if err = os.Mkdir(path, 0777); err != nil { + return err + } + } + return nil +} diff --git a/backend/storage/preferences.go b/backend/storage/preferences.go new file mode 100644 index 0000000..e2ee5f0 --- /dev/null +++ b/backend/storage/preferences.go @@ -0,0 +1,124 @@ +package storage + +import ( + "fmt" + "gopkg.in/yaml.v3" + "strings" + "sync" +) + +type PreferencesStorage struct { + storage *localStorage + mutex sync.Mutex +} + +func NewPreferences() *PreferencesStorage { + return &PreferencesStorage{ + storage: NewLocalStore("preferences.yaml"), + } +} + +func (p *PreferencesStorage) DefaultPreferences() map[string]any { + return map[string]any{ + "general": map[string]any{ + "language": "en", + "font": "", + "font_size": 14, + "use_proxy": false, + "use_proxy_http": false, + "check_update": true, + }, + "editor": map[string]any{ + "font": "", + "font_size": 14, + }, + } +} + +func (p *PreferencesStorage) getPreferences() (ret map[string]any) { + b, err := p.storage.Load() + if err != nil { + ret = p.DefaultPreferences() + return + } + + if err := yaml.Unmarshal(b, &ret); err != nil { + ret = p.DefaultPreferences() + return + } + return +} + +// GetPreferences Get preferences from local +func (p *PreferencesStorage) GetPreferences() (ret map[string]any) { + p.mutex.Lock() + defer p.mutex.Unlock() + + return p.getPreferences() +} + +func (p *PreferencesStorage) setPreferences(pf map[string]any, key string, value any) error { + keyPath := strings.Split(key, ".") + if len(keyPath) <= 0 { + return fmt.Errorf("invalid key path(%s)", key) + } + var node any = pf + for _, k := range keyPath[:len(keyPath)-1] { + if subNode, ok := node.(map[string]any); ok { + node = subNode[k] + } else { + return fmt.Errorf("invalid key path(%s)", key) + } + } + + if subNode, ok := node.(map[string]any); ok { + subNode[keyPath[len(keyPath)-1]] = value + } + + return nil +} + +func (p *PreferencesStorage) savePreferences(pf map[string]any) error { + b, err := yaml.Marshal(&pf) + if err != nil { + return err + } + + if err = p.storage.Store(b); err != nil { + return err + } + return nil +} + +// SetPreferences assign value to key path, the key path use "." to indicate multiple level +func (p *PreferencesStorage) SetPreferences(key string, value any) error { + p.mutex.Lock() + defer p.mutex.Unlock() + + pf := p.getPreferences() + if err := p.setPreferences(pf, key, value); err != nil { + return err + } + return p.savePreferences(pf) +} + +// SetPreferencesN set multiple key path and value +func (p *PreferencesStorage) SetPreferencesN(values map[string]any) error { + p.mutex.Lock() + defer p.mutex.Unlock() + + pf := p.getPreferences() + for path, v := range values { + if err := p.setPreferences(pf, path, v); err != nil { + return err + } + } + + return p.savePreferences(pf) +} + +func (p *PreferencesStorage) RestoreDefault() map[string]any { + pf := p.DefaultPreferences() + p.savePreferences(pf) + return pf +} diff --git a/backend/types/connection.go b/backend/types/connection.go new file mode 100644 index 0000000..52fb703 --- /dev/null +++ b/backend/types/connection.go @@ -0,0 +1,29 @@ +package types + +type ConnectionCategory int + +type Connection struct { + Group string `json:"group" yaml:"-"` + Name string `json:"name" yaml:"name"` + Addr string `json:"addr" yaml:"addr"` + Port int `json:"port" yaml:"port"` + Username string `json:"username" yaml:"username"` + Password string `json:"password" yaml:"password"` + DefaultFilter string `json:"defaultFilter" yaml:"default_filter"` + KeySeparator string `json:"keySeparator" yaml:"key_separator"` + ConnTimeout int `json:"connTimeout" yaml:"conn_timeout"` + ExecTimeout int `json:"execTimeout" yaml:"exec_timeout"` + MarkColor string `json:"markColor" yaml:"mark_color"` +} + +type ConnectionGroup struct { + GroupName string `json:"groupName" yaml:"group_name"` + Connections []Connection `json:"connections" yaml:"connections"` +} + +type ConnectionDB struct { + Name string `json:"name"` + Keys int `json:"keys"` + Expires int `json:"expires,omitempty"` + AvgTTL int `json:"avgTtl,omitempty"` +} diff --git a/backend/types/js_resp.go b/backend/types/js_resp.go new file mode 100644 index 0000000..c6efb0d --- /dev/null +++ b/backend/types/js_resp.go @@ -0,0 +1,7 @@ +package types + +type JSResp struct { + Success bool `json:"success"` + Msg string `json:"msg"` + Data any `json:"data,omitempty"` +} diff --git a/backend/types/zset.go b/backend/types/zset.go new file mode 100644 index 0000000..f87cd86 --- /dev/null +++ b/backend/types/zset.go @@ -0,0 +1,6 @@ +package types + +type ZSetItem struct { + Value string `json:"value"` + Score float64 `json:"score"` +} diff --git a/backend/utils/coll/queue.go b/backend/utils/coll/queue.go new file mode 100644 index 0000000..39549e8 --- /dev/null +++ b/backend/utils/coll/queue.go @@ -0,0 +1,82 @@ +package coll + +// Queue 队列, 先进先出 +type Queue[T any] []T + +func NewQueue[T any](elems ...T) Queue[T] { + if len(elems) > 0 { + data := make([]T, len(elems)) + copy(data, elems) + return data + } else { + return Queue[T]{} + } +} + +// Push 尾部插入元素 +func (q *Queue[T]) Push(elem T) { + if q == nil { + return + } + *q = append(*q, elem) +} + +// PushN 尾部插入多个元素 +func (q *Queue[T]) PushN(elems ...T) { + if q == nil { + return + } + if len(elems) <= 0 { + return + } + *q = append(*q, elems...) +} + +// Pop 移除并返回头部元素 +func (q *Queue[T]) Pop() (T, bool) { + var elem T + if q == nil || len(*q) <= 0 { + return elem, false + } + elem = (*q)[0] + *q = (*q)[1:] + return elem, true +} + +func (q *Queue[T]) PopN(n int) []T { + if q == nil { + return []T{} + } + + var popElems []T + if n <= 0 { + return []T{} + } + + l := len(*q) + if n >= l { + popElems = *q + *q = []T{} + return *q + } + + popElems = (*q)[:n] + *q = (*q)[n:] + return popElems +} + +// Clear 移除所有元素 +func (q *Queue[T]) Clear() { + if q == nil { + return + } + *q = []T{} +} + +func (q Queue[T]) IsEmpty() bool { + return len(q) <= 0 +} + +func (q Queue[T]) Size() int { + return len(q) +} diff --git a/backend/utils/coll/set.go b/backend/utils/coll/set.go new file mode 100644 index 0000000..c8e2742 --- /dev/null +++ b/backend/utils/coll/set.go @@ -0,0 +1,287 @@ +package coll + +import ( + "fmt" + json "github.com/bytedance/sonic" + "sort" + . "tinyrdm/backend/utils" + "tinyrdm/backend/utils/rand" +) + +type Void struct{} + +// Set 集合, 存放不重复的元素 +type Set[T Hashable] map[T]Void + +// type Set[T Hashable] struct { +// data map[T]Void +// } + +func NewSet[T Hashable](elems ...T) Set[T] { + if len(elems) > 0 { + data := make(Set[T], len(elems)) + for _, e := range elems { + data[e] = Void{} + } + return data + } else { + return Set[T]{} + } +} + +// Add 添加元素 +func (s Set[T]) Add(elem T) bool { + if s == nil { + return false + } + if _, exists := s[elem]; !exists { + s[elem] = Void{} + return true + } + return false +} + +// AddN 添加多个元素 +func (s Set[T]) AddN(elems ...T) int { + if s == nil { + return 0 + } + addCount := 0 + var exists bool + for _, elem := range elems { + if _, exists = s[elem]; !exists { + s[elem] = Void{} + addCount += 1 + } + } + return addCount +} + +// Merge 合并其他集合 +func (s Set[T]) Merge(other Set[T]) int { + return s.AddN(other.ToSlice()...) +} + +// Contains 判断是否存在指定元素 +func (s Set[T]) Contains(elem T) bool { + if s == nil { + return false + } + _, exists := s[elem] + return exists +} + +// ContainAny 判断是否包含任意元素 +func (s Set[T]) ContainAny(elems ...T) bool { + if s == nil { + return false + } + var exists bool + for _, elem := range elems { + if _, exists = s[elem]; exists { + return true + } + } + return false +} + +// Equals 判断两个集合内元素是否一致 +func (s Set[T]) Equals(other Set[T]) bool { + if s.Size() != other.Size() { + return false + } + for elem := range s { + if !other.Contains(elem) { + return false + } + } + return true +} + +// ContainAll 判断是否包含所有元素 +func (s Set[T]) ContainAll(elems ...T) bool { + if s == nil { + return false + } + var exists bool + for _, elem := range elems { + if _, exists = s[elem]; !exists { + return false + } + } + return true +} + +// Remove 移除元素 +func (s Set[T]) Remove(elem T) bool { + if s == nil { + return false + } + if _, exists := s[elem]; exists { + delete(s, elem) + return true + } + return false +} + +// RemoveN 移除多个元素 +func (s Set[T]) RemoveN(elems ...T) int { + if s == nil { + return 0 + } + var exists bool + removeCnt := 0 + for _, elem := range elems { + if _, exists = s[elem]; exists { + delete(s, elem) + removeCnt += 1 + } + } + return removeCnt +} + +// RemoveSub 移除子集 +func (s Set[T]) RemoveSub(subSet Set[T]) int { + if s == nil { + return 0 + } + var exists bool + removeCnt := 0 + for elem := range subSet { + if _, exists = s[elem]; exists { + delete(s, elem) + removeCnt += 1 + } + } + return removeCnt +} + +// Filter 根据条件筛出符合的元素 +func (s Set[T]) Filter(filterFunc func(i T) bool) []T { + ret := []T{} + for v := range s { + if filterFunc(v) { + ret = append(ret, v) + } + } + return ret +} + +// RandomElem 随机抽取一个元素 +// @param remove 随机出来的元素是否同时从集合中移除 +// @return 抽取的元素 +// @return 是否抽取成功 +func (s Set[T]) RandomElem(remove bool) (T, bool) { + size := s.Size() + if size > 0 { + selIdx := rand.Intn(size) + idx := 0 + for elem := range s { + if idx == selIdx { + if remove { + delete(s, elem) + } + return elem, true + } else { + idx++ + } + } + } + + var r T + return r, false +} + +// Size 集合长度 +func (s Set[T]) Size() int { + return len(s) +} + +// IsEmpty 判断是否为空 +func (s Set[T]) IsEmpty() bool { + return len(s) <= 0 +} + +// Clear 清空集合 +func (s Set[T]) Clear() { + for elem := range s { + delete(s, elem) + } +} + +// ToSlice 转为切片 +func (s Set[T]) ToSlice() []T { + size := len(s) + if size <= 0 { + return []T{} + } + + ret := make([]T, 0, size) + for elem := range s { + ret = append(ret, elem) + } + return ret +} + +// ToSortedSlice 转为排序好的切片 +func (s Set[T]) ToSortedSlice(sortFunc func(v1, v2 T) bool) []T { + list := s.ToSlice() + sort.Slice(list, func(i, j int) bool { + return sortFunc(list[i], list[j]) + }) + return list +} + +// Each 遍历检索每个元素 +func (s Set[T]) Each(eachFunc func(T)) { + if len(s) <= 0 { + return + } + for elem := range s { + eachFunc(elem) + } +} + +// Clone 克隆 +func (s Set[T]) Clone() Set[T] { + if s == nil { + return nil + } + + other := NewSet[T]() + for elem := range s { + other[elem] = Void{} + } + return other +} + +func (s Set[T]) String() string { + arr := s.ToSlice() + return fmt.Sprintf("%v", arr) +} + +// MarshalJSON to output non base64 encoded []byte +func (s Set[T]) MarshalJSON() ([]byte, error) { + if s == nil { + return []byte("null"), nil + } + t := s.ToSlice() + return json.Marshal(t) +} + +// UnmarshalJSON to deserialize []byte +func (s *Set[T]) UnmarshalJSON(b []byte) error { + t := []T{} + err := json.Unmarshal(b, &t) + if err != nil { + *s = NewSet[T]() + } else { + *s = NewSet[T](t...) + } + return nil +} + +// GormDataType gorm common data type +func (s Set[T]) GormDataType() string { + return "json" +} diff --git a/backend/utils/coll/stack.go b/backend/utils/coll/stack.go new file mode 100644 index 0000000..7d8259b --- /dev/null +++ b/backend/utils/coll/stack.go @@ -0,0 +1,88 @@ +package coll + +// Stack 栈, 先进后出 +type Stack[T any] []T + +func NewStack[T any](elems ...T) Stack[T] { + if len(elems) > 0 { + data := make([]T, len(elems)) + copy(data, elems) + return data + } else { + return Stack[T]{} + } +} + +// Push 顶部添加一个元素 +func (s *Stack[T]) Push(elem T) { + if s == nil { + panic("queue should not be nil") + } + *s = append(*s, elem) +} + +// PushN 顶部添加一个元素 +func (s *Stack[T]) PushN(elems ...T) { + if s == nil { + panic("queue should not be nil") + } + if len(elems) <= 0 { + return + } + *s = append(*s, elems...) +} + +// Pop 移除并返回顶部元素 +func (s *Stack[T]) Pop() T { + if s == nil { + panic("queue should not be nil") + } + l := len(*s) + popElem := (*s)[l-1] + *s = (*s)[:l-1] + return popElem +} + +// PopN 移除并返回顶部多个元素 +func (s *Stack[T]) PopN(n int) []T { + if s == nil { + panic("queue should not be nil") + } + var popElems []T + if n <= 0 { + return popElems + } + + l := len(*s) + if n >= l { + popElems = *s + *s = []T{} + return *s + } + + popElems = (*s)[l-n:] + *s = (*s)[:l-n] + + // 翻转弹出结果 + pl := len(popElems) + for i := 0; i < pl/2; i++ { + popElems[i], popElems[pl-i-1] = popElems[pl-i-1], popElems[i] + } + return popElems +} + +// Clear 移除所有元素 +func (s *Stack[T]) Clear() { + if s == nil { + panic("queue should not be nil") + } + *s = []T{} +} + +func (s Stack[T]) IsEmpty() bool { + return len(s) <= 0 +} + +func (s Stack[T]) Size() int { + return len(s) +} diff --git a/backend/utils/constraints.go b/backend/utils/constraints.go new file mode 100644 index 0000000..94a3e15 --- /dev/null +++ b/backend/utils/constraints.go @@ -0,0 +1,13 @@ +package utils + +type Hashable interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64 | ~string +} + +type SignedNumber interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~float32 | ~float64 +} + +type UnsignedNumber interface { + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 +} diff --git a/backend/utils/map/map_util.go b/backend/utils/map/map_util.go new file mode 100644 index 0000000..7ca710f --- /dev/null +++ b/backend/utils/map/map_util.go @@ -0,0 +1,289 @@ +package maputil + +import ( + . "tinyrdm/backend/utils" + "tinyrdm/backend/utils/coll" +) + +// Get 获取键值对指定键的值, 如果不存在则返回自定默认值 +func Get[M ~map[K]V, K Hashable, V any](m M, key K, defaultVal V) V { + if m != nil { + if v, exists := m[key]; exists { + return v + } + } + return defaultVal +} + +// ContainsKey 判断指定键是否存在 +func ContainsKey[M ~map[K]V, K Hashable, V any](m M, key K) bool { + if m == nil { + return false + } + _, exists := m[key] + return exists +} + +// MustGet 获取键值对指定键的值, 如果不存在则调用给定的函数进行获取 +func MustGet[M ~map[K]V, K Hashable, V any](m M, key K, getFunc func(K) V) V { + if v, exists := m[key]; exists { + return v + } + if getFunc != nil { + return getFunc(key) + } + var defaultV V + return defaultV +} + +// Keys 获取键值对中所有键 +func Keys[M ~map[K]V, K Hashable, V any](m M) []K { + if len(m) <= 0 { + return []K{} + } + keys := make([]K, len(m)) + index := 0 + for k := range m { + keys[index] = k + index += 1 + } + return keys +} + +// KeySet 获取键值对中所有键集合 +func KeySet[M ~map[K]V, K Hashable, V any](m M) coll.Set[K] { + if len(m) <= 0 { + return coll.NewSet[K]() + } + keySet := coll.NewSet[K]() + for k := range m { + keySet.Add(k) + } + return keySet +} + +// Values 获取键值对中所有值 +func Values[M ~map[K]V, K Hashable, V any](m M) []V { + if len(m) <= 0 { + return []V{} + } + values := make([]V, len(m)) + index := 0 + for _, v := range m { + values[index] = v + index += 1 + } + return values +} + +// ValueSet 获取键值对中所有值集合 +func ValueSet[M ~map[K]V, K Hashable, V Hashable](m M) coll.Set[V] { + if len(m) <= 0 { + return coll.NewSet[V]() + } + valueSet := coll.NewSet[V]() + for _, v := range m { + valueSet.Add(v) + } + return valueSet +} + +// Fill 填充键值对 +func Fill[M ~map[K]V, K Hashable, V any](dest M, src M) M { + for k, v := range src { + dest[k] = v + } + return dest +} + +// Merge 合并键值对, 后续键值对有重复键的元素会覆盖旧元素 +func Merge[M ~map[K]V, K Hashable, V any](mapArr ...M) M { + result := make(M, len(mapArr)) + for _, m := range mapArr { + for k, v := range m { + result[k] = v + } + } + return result +} + +// DeepMerge 深度递归覆盖src值到dst中 +// 将返回新的值 +func DeepMerge[M ~map[K]any, K Hashable](src1, src2 M) M { + out := make(map[K]any, len(src1)) + for k, v := range src1 { + out[k] = v + } + for k, v := range src2 { + if v1, ok := v.(map[K]any); ok { + if bv, ok := out[k]; ok { + if bv1, ok := bv.(map[K]any); ok { + out[k] = DeepMerge(bv1, v1) + continue + } + } + } + out[k] = v + } + return out +} + +// Omit 根据条件省略指定元素 +func Omit[M ~map[K]V, K Hashable, V any](m M, omitFunc func(k K, v V) bool) (M, []K) { + result := M{} + var removedKeys []K + for k, v := range m { + if !omitFunc(k, v) { + result[k] = v + } else { + removedKeys = append(removedKeys, k) + } + } + return result, removedKeys +} + +// OmitKeys 省略指定键的的元素 +func OmitKeys[M ~map[K]V, K Hashable, V any](m M, keys ...K) M { + omitKey := map[K]struct{}{} + for _, k := range keys { + omitKey[k] = struct{}{} + } + + result := M{} + var exists bool + for k, v := range m { + if _, exists = omitKey[k]; !exists { + result[k] = v + } + } + return result +} + +// ContainsAnyKey 是否包含任意键 +func ContainsAnyKey[M ~map[K]V, K Hashable, V any](m M, keys ...K) bool { + var exists bool + for _, key := range keys { + if _, exists = m[key]; exists { + return true + } + } + return false +} + +// ContainsAllKey 是否包含所有键 +func ContainsAllKey[M ~map[K]V, K Hashable, V any](m M, keys ...K) bool { + var exists bool + for _, key := range keys { + if _, exists = m[key]; !exists { + return false + } + } + return true +} + +// AnyMatch 是否任意元素符合条件 +func AnyMatch[M ~map[K]V, K Hashable, V any](m M, matchFunc func(k K, v V) bool) bool { + for k, v := range m { + if matchFunc(k, v) { + return true + } + } + return false +} + +// AllMatch 是否所有元素符合条件 +func AllMatch[M ~map[K]V, K Hashable, V any](m M, matchFunc func(k K, v V) bool) bool { + for k, v := range m { + if !matchFunc(k, v) { + return false + } + } + return true +} + +// Reduce 累计 +func Reduce[M ~map[K]V, K Hashable, V any, R any](m M, init R, reduceFunc func(R, K, V) R) R { + result := init + for k, v := range m { + result = reduceFunc(result, k, v) + } + return result +} + +// ToSlice 键值对转切片 +func ToSlice[M ~map[K]V, K Hashable, V any, R any](m M, mapFunc func(k K) R) []R { + ret := make([]R, 0, len(m)) + for k := range m { + ret = append(ret, mapFunc(k)) + } + return ret +} + +// Filter 筛选出指定条件的所有元素 +func Filter[M ~map[K]V, K Hashable, V any](m M, filterFunc func(k K) bool) M { + ret := make(M, len(m)) + for k, v := range m { + if filterFunc(k) { + ret[k] = v + } + } + return ret +} + +// FilterToSlice 键值对筛选并转切片 +func FilterToSlice[M ~map[K]V, K Hashable, V any, R any](m M, mapFunc func(k K) (R, bool)) []R { + ret := make([]R, 0, len(m)) + for k := range m { + if v, filter := mapFunc(k); filter { + ret = append(ret, v) + } + } + return ret +} + +// FilterKey 筛选出指定条件的所有键 +func FilterKey[M ~map[K]V, K Hashable, V any](m M, filterFunc func(k K) bool) []K { + ret := make([]K, 0, len(m)) + for k := range m { + if filterFunc(k) { + ret = append(ret, k) + } + } + return ret +} + +// Clone 复制键值对 +func Clone[M ~map[K]V, K Hashable, V any](src M) M { + dest := make(M, len(src)) + for k, v := range src { + dest[k] = v + } + return dest +} + +// Reverse 键->值映射翻转为值->键映射(如果重复则覆盖最后的) +func Reverse[M ~map[K]V, K Hashable, V Hashable](src M) map[V]K { + dest := make(map[V]K, len(src)) + for k, v := range src { + dest[v] = k + } + return dest +} + +// ReverseAll 键->值映射翻转为值->键列表映射 +func ReverseAll[M ~map[K]V, K Hashable, V Hashable](src M) map[V][]K { + dest := make(map[V][]K, len(src)) + for k, v := range src { + dest[v] = append(dest[v], k) + } + return dest +} + +// RemoveIf 移除指定条件的键 +func RemoveIf[M ~map[K]V, K Hashable, V any](src M, cond func(key K) bool) { + for k := range src { + if cond(k) { + delete(src, k) + } + } +} diff --git a/backend/utils/rand/random.go b/backend/utils/rand/random.go new file mode 100644 index 0000000..4aba6e8 --- /dev/null +++ b/backend/utils/rand/random.go @@ -0,0 +1,103 @@ +package rand + +import ( + "math/rand" + "strings" + "sync" + "time" +) + +// 随机对象缓存池(解决自带随机函数全局抢锁问题) +var randObjectPool = sync.Pool{ + New: func() interface{} { + return rand.New(rand.NewSource(time.Now().UnixNano())) + }, +} +var lowerChar = []rune("abcdefghijklmnopqrstuvwxyz") // strings.Split("abcdefghijklmnopqrstuvwxyz", "") +var upperChar = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ") // strings.Split("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "") +var numberChar = []rune("0123456789") // strings.Split("0123456789", "") +var numberAndChar = append(lowerChar, numberChar...) + +func init() { + rand.Seed(time.Now().UnixNano()) +} + +func Intn[T ~int](n T) T { + r := randObjectPool.Get() + res := r.(*rand.Rand).Intn(int(n)) + randObjectPool.Put(r) + return T(res) +} + +func IntnCount[T ~int](n T, count int) []T { + res := make([]T, count) + r := randObjectPool.Get() + for i := 0; i < count; i++ { + res[i] = T(r.(*rand.Rand).Intn(int(n))) + } + randObjectPool.Put(r) + return res +} + +// Int31n 生成 max { + min, max = max, min + } + return Intn(max-min) + min +} + +// RangeString 生成随机字符串 +func RangeString(charSet []rune, n int) string { + r := randObjectPool.Get() + + res := strings.Builder{} + size := len(charSet) + for i := 0; i < n; i++ { + res.WriteRune(charSet[r.(*rand.Rand).Intn(size)]) + } + randObjectPool.Put(r) + return res.String() +} + +// LowerString 生成随机指定长度小写字母 +func LowerString(n int) string { + return RangeString(lowerChar, n) +} + +// UpperString 生成随机指定长度大写字母 +func UpperString(n int) string { + return RangeString(upperChar, n) +} + +// NumberString 生成随机指定长度数字字符串 +func NumberString(n int) string { + return RangeString(numberChar, n) +} + +// CharNumberString 生成随机指定长度小写字母和数字 +func CharNumberString(n int) string { + return RangeString(numberAndChar, n) +} + +// Shuffle 执行指定次数打乱 +func Shuffle(n int, swap func(i, j int)) { + r := randObjectPool.Get() + r.(*rand.Rand).Shuffle(n, swap) +} diff --git a/backend/utils/rand/weight_random.go b/backend/utils/rand/weight_random.go new file mode 100644 index 0000000..d48ecce --- /dev/null +++ b/backend/utils/rand/weight_random.go @@ -0,0 +1,102 @@ +package rand + +import ( + "github.com/google/go-cmp/cmp" + "math/rand" + "sync" + "time" +) + +// WeightObject 权重单项 +type WeightObject[T any] struct { + Obj T + Weight int +} + +// WeightRandom 根据权重随机 +type WeightRandom[T any] struct { + WeightObject []WeightObject[T] + totalWeight int + randObj *rand.Rand + lk sync.Mutex +} + +func NewWeightRandom[T any]() *WeightRandom[T] { + return &WeightRandom[T]{ + WeightObject: []WeightObject[T]{}, + totalWeight: 0, + randObj: rand.New(rand.NewSource(time.Now().UnixNano())), + } +} + +func (w *WeightRandom[T]) Add(object T, weight int) { + weightObj := WeightObject[T]{ + Obj: object, + Weight: weight, + } + w.AddObject(weightObj) +} + +// AddObject 添加单个权重对象 +func (w *WeightRandom[T]) AddObject(weightObject WeightObject[T]) { + if weightObject.Weight <= 0 { + return + } + + exists := false + for i, object := range w.WeightObject { + if cmp.Equal(weightObject.Obj, object.Obj) { + // 已经存在, 覆盖权重 + w.subWeight(object.Weight) + w.WeightObject[i].Weight = weightObject.Weight + w.addWeight(weightObject.Weight) + exists = true + break + } + } + if !exists { + // 已经存在, 覆盖权重 + w.WeightObject = append(w.WeightObject, weightObject) + w.addWeight(weightObject.Weight) + } +} + +// AddObjects 添加多个权重对象 +func (w *WeightRandom[T]) AddObjects(object []WeightObject[T]) { + for _, weightObject := range object { + w.AddObject(weightObject) + } +} + +func (w *WeightRandom[T]) addWeight(weight int) { + if w.totalWeight < 0 { + w.totalWeight = 0 + } + w.totalWeight += weight +} + +func (w *WeightRandom[T]) subWeight(weight int) { + if w.totalWeight-weight < 0 { + w.totalWeight = 0 + } else { + w.totalWeight -= weight + } +} + +// Next 通过权重随机到下一个 +func (w *WeightRandom[T]) Next() T { + if w.totalWeight > 0 { + w.lk.Lock() + randomWeight := w.randObj.Intn(w.totalWeight) + w.lk.Unlock() + weightCount := 0 + for _, object := range w.WeightObject { + weightCount += object.Weight + if weightCount > randomWeight { + return object.Obj + } + } + } + var noop T + return noop +} diff --git a/backend/utils/redis/log_hook.go b/backend/utils/redis/log_hook.go new file mode 100644 index 0000000..a599630 --- /dev/null +++ b/backend/utils/redis/log_hook.go @@ -0,0 +1,38 @@ +package redis + +import ( + "context" + "github.com/redis/go-redis/v9" + "log" + "net" +) + +type LogHook struct { + name string +} + +func NewHook(name string) LogHook { + return LogHook{ + name: name, + } +} + +func (LogHook) DialHook(next redis.DialHook) redis.DialHook { + return func(ctx context.Context, network, addr string) (net.Conn, error) { + return next(ctx, network, addr) + } +} +func (LogHook) ProcessHook(next redis.ProcessHook) redis.ProcessHook { + return func(ctx context.Context, cmd redis.Cmder) error { + log.Println(cmd.String()) + return next(ctx, cmd) + } +} +func (LogHook) ProcessPipelineHook(next redis.ProcessPipelineHook) redis.ProcessPipelineHook { + return func(ctx context.Context, cmds []redis.Cmder) error { + for _, cmd := range cmds { + log.Println(cmd.String()) + } + return next(ctx, cmds) + } +} diff --git a/backend/utils/slice/slice_util.go b/backend/utils/slice/slice_util.go new file mode 100644 index 0000000..2863cd7 --- /dev/null +++ b/backend/utils/slice/slice_util.go @@ -0,0 +1,467 @@ +package sliceutil + +import ( + "sort" + "strconv" + "strings" + . "tinyrdm/backend/utils" + "tinyrdm/backend/utils/rand" +) + +// Get 获取指定索引的值, 如果不存在则返回默认值 +func Get[S ~[]T, T any](arr S, index int, defaultVal T) T { + if index < 0 || index >= len(arr) { + return defaultVal + } + return arr[index] +} + +// Remove 删除指定索引的元素 +func Remove[S ~[]T, T any](arr S, index int) S { + return append(arr[:index], arr[index+1:]...) +} + +// RemoveIf 移除指定条件的元素 +func RemoveIf[S ~[]T, T any](arr S, cond func(T) bool) S { + l := len(arr) + if l <= 0 { + return arr + } + for i := l - 1; i >= 0; i-- { + if cond(arr[i]) { + arr = append(arr[:i], arr[i+1:]...) + } + } + return arr +} + +// RemoveRange 删除从[from, to]部分元素 +func RemoveRange[S ~[]T, T any](arr S, from, to int) S { + return append(arr[:from], arr[to:]...) +} + +// Find 查找指定条件的元素第一个出现位置 +func Find[S ~[]T, T any](arr S, matchFunc func(int) bool) (int, bool) { + total := len(arr) + for i := 0; i < total; i++ { + if matchFunc(i) { + return i, true + } + } + return -1, false +} + +// AnyMatch 判断是否有任意元素符合条件 +func AnyMatch[S ~[]T, T any](arr S, matchFunc func(int) bool) bool { + total := len(arr) + if total > 0 { + for i := 0; i < total; i++ { + if matchFunc(i) { + return true + } + } + } + return false +} + +// AllMatch 判断是否所有元素都符合条件 +func AllMatch[S ~[]T, T any](arr S, matchFunc func(int) bool) bool { + total := len(arr) + for i := 0; i < total; i++ { + if !matchFunc(i) { + return false + } + } + return true +} + +// Equals 比较两个切片内容是否完全一致 +func Equals[S ~[]T, T comparable](arr1, arr2 S) bool { + if &arr1 == &arr2 { + return true + } + + len1, len2 := len(arr1), len(arr2) + if len1 != len2 { + return false + } + for i := 0; i < len1; i++ { + if arr1[i] != arr2[i] { + return false + } + } + return true +} + +// Contains 判断数组是否包含指定元素 +func Contains[S ~[]T, T Hashable](arr S, elem T) bool { + return AnyMatch(arr, func(idx int) bool { + return arr[idx] == elem + }) +} + +// ContainsAny 判断数组是否包含任意指定元素 +func ContainsAny[S ~[]T, T Hashable](arr S, elems ...T) bool { + for _, elem := range elems { + if Contains(arr, elem) { + return true + } + } + return false +} + +// ContainsAll 判断数组是否包含所有指定元素 +func ContainsAll[S ~[]T, T Hashable](arr S, elems ...T) bool { + for _, elem := range elems { + if !Contains(arr, elem) { + return false + } + } + return true +} + +// Filter 筛选出符合指定条件的所有元素 +func Filter[S ~[]T, T any](arr S, filterFunc func(int) bool) []T { + total := len(arr) + var result []T + for i := 0; i < total; i++ { + if filterFunc(i) { + result = append(result, arr[i]) + } + } + return result +} + +// Map 数组映射转换 +func Map[S ~[]T, T any, R any](arr S, mappingFunc func(int) R) []R { + total := len(arr) + result := make([]R, total) + for i := 0; i < total; i++ { + result[i] = mappingFunc(i) + } + return result +} + +// FilterMap 数组过滤和映射转换 +func FilterMap[S ~[]T, T any, R any](arr S, mappingFunc func(int) (R, bool)) []R { + total := len(arr) + result := make([]R, 0, total) + var filter bool + var mapItem R + for i := 0; i < total; i++ { + if mapItem, filter = mappingFunc(i); filter { + result = append(result, mapItem) + } + } + return result +} + +// ToMap 数组转键值对 +func ToMap[S ~[]T, T any, K Hashable, V any](arr S, mappingFunc func(int) (K, V)) map[K]V { + total := len(arr) + result := map[K]V{} + for i := 0; i < total; i++ { + key, val := mappingFunc(i) + result[key] = val + } + return result +} + +// Flat 二维数组扁平化 +func Flat[T any](arr [][]T) []T { + total := len(arr) + var result []T + for i := 0; i < total; i++ { + subTotal := len(arr[i]) + for j := 0; j < subTotal; j++ { + result = append(result, arr[i][j]) + } + } + return result +} + +// FlatMap 二维数组扁平化映射 +func FlatMap[T any, R any](arr [][]T, mappingFunc func(int, int) R) []R { + total := len(arr) + var result []R + for i := 0; i < total; i++ { + subTotal := len(arr[i]) + for j := 0; j < subTotal; j++ { + result = append(result, mappingFunc(i, j)) + } + } + return result +} + +func FlatValueMap[T Hashable](arr [][]T) []T { + return FlatMap(arr, func(i, j int) T { + return arr[i][j] + }) +} + +// Reduce 数组累计 +func Reduce[S ~[]T, T any, R any](arr S, init R, reduceFunc func(R, T) R) R { + result := init + for _, item := range arr { + result = reduceFunc(result, item) + } + return result +} + +// Reverse 反转数组(会修改原数组) +func Reverse[S ~[]T, T any](arr S) S { + total := len(arr) + for i := 0; i < total/2; i++ { + arr[i], arr[total-i-1] = arr[total-i-1], arr[i] + } + return arr +} + +// Join 数组拼接转字符串 +func Join[S ~[]T, T any](arr S, sep string, toStringFunc func(int) string) string { + total := len(arr) + if total <= 0 { + return "" + } + if total == 1 { + return toStringFunc(0) + } + + sb := strings.Builder{} + for i := 0; i < total; i++ { + if i != 0 { + sb.WriteString(sep) + } + sb.WriteString(toStringFunc(i)) + } + return sb.String() +} + +// JoinString 字符串数组拼接成字符串 +func JoinString(arr []string, sep string) string { + return Join(arr, sep, func(idx int) string { + return arr[idx] + }) +} + +// JoinInt 整形数组拼接转字符串 +func JoinInt(arr []int, sep string) string { + return Join(arr, sep, func(idx int) string { + return strconv.Itoa(arr[idx]) + }) +} + +// Unique 数组去重 +func Unique[S ~[]T, T Hashable](arr S) S { + result := make(S, 0, len(arr)) + uniKeys := map[T]struct{}{} + var exists bool + for _, item := range arr { + if _, exists = uniKeys[item]; !exists { + uniKeys[item] = struct{}{} + result = append(result, item) + } + } + return result +} + +// UniqueEx 数组去重(任意类型) +// @param toKeyFunc 数组元素转为唯一标识字符串函数, 如转为哈希值等 +func UniqueEx[S ~[]T, T any](arr S, toKeyFunc func(i int) string) S { + result := make(S, 0, len(arr)) + keyArr := Map(arr, toKeyFunc) + uniKeys := map[string]struct{}{} + var exists bool + for i, item := range arr { + if _, exists = uniKeys[keyArr[i]]; !exists { + uniKeys[keyArr[i]] = struct{}{} + result = append(result, item) + } + } + return result +} + +// Sort 顺序排序(会修改原数组) +func Sort[S ~[]T, T Hashable](arr S) S { + sort.Slice(arr, func(i, j int) bool { + return arr[i] <= arr[j] + }) + return arr +} + +// SortDesc 倒序排序(会修改原数组) +func SortDesc[S ~[]T, T Hashable](arr S) S { + sort.Slice(arr, func(i, j int) bool { + return arr[i] > arr[j] + }) + return arr +} + +// Union 返回两个切片共同拥有的元素 +func Union[S ~[]T, T Hashable](arr1 S, arr2 S) S { + hashArr, compArr := arr1, arr2 + if len(arr1) < len(arr2) { + hashArr, compArr = compArr, hashArr + } + hash := map[T]struct{}{} + for _, item := range hashArr { + hash[item] = struct{}{} + } + + uniq := map[T]struct{}{} + ret := make(S, 0, len(compArr)) + exists := false + for _, item := range compArr { + if _, exists = hash[item]; exists { + if _, exists = uniq[item]; !exists { + ret = append(ret, item) + uniq[item] = struct{}{} + } + } + } + return ret +} + +// Exclude 返回不包含的元素 +func Exclude[S ~[]T, T Hashable](arr1 S, arr2 S) S { + diff := make([]T, 0, len(arr1)) + hash := map[T]struct{}{} + for _, item := range arr2 { + hash[item] = struct{}{} + } + + for _, item := range arr1 { + if _, exists := hash[item]; !exists { + diff = append(diff, item) + } + } + return diff +} + +// PadLeft 左边填充指定数量 +func PadLeft[S ~[]T, T any](arr S, val T, count int) S { + prefix := make(S, count) + for i := 0; i < count; i++ { + prefix[i] = val + } + arr = append(prefix, arr...) + return arr +} + +// PadRight 右边填充指定数量 +func PadRight[S ~[]T, T any](arr S, val T, count int) S { + for i := 0; i < count; i++ { + arr = append(arr, val) + } + return arr +} + +// RemoveLeft 移除左侧相同元素 +func RemoveLeft[S ~[]T, T comparable](arr S, val T) S { + for len(arr) > 0 && arr[0] == val { + arr = arr[1:] + } + return arr +} + +// RemoveRight 移除右侧相同元素 +func RemoveRight[S ~[]T, T comparable](arr S, val T) S { + for { + length := len(arr) + if length > 0 && arr[length-1] == val { + arr = arr[:length] + } else { + break + } + } + return arr +} + +// RandomElem 从切片中随机抽一个 +func RandomElem[S ~[]T, T any](arr S) T { + l := len(arr) + if l <= 0 { + var r T + return r + } + return arr[rand.Intn(l)] +} + +// RandomElems 从切片中随机抽多个 +// 如果切片长度为空, 则返回空切片 +func RandomElems[S ~[]T, T any](arr S, count int) []T { + l := len(arr) + ret := make([]T, 0, l) + if l <= 0 { + return ret + } + + idxList := rand.IntnCount(l, count) + for _, idx := range idxList { + ret = append(ret, arr[idx]) + } + return ret +} + +// RandomUniqElems 从切片中随机抽多个不同的元素 +// 如果切片长度为空, 则返回空切片 +// 如果所需数量大于切片唯一元素数量, 则返回整个切片 +func RandomUniqElems[S ~[]T, T Hashable](arr S, count int) []T { + if len(arr) <= 0 { + // 可选列表为空, 返回空切片 + return []T{} + } + // 转换为集合 + uniqList := Unique(arr) + uniqLen := len(uniqList) + if uniqLen <= count { + // 可选集合总数<=所需元素数量, 直接返回整个可选集合 + return uniqList + } + + if count >= uniqLen/2 { + // 所需唯一元素大于可选集合一半, 随机筛掉(uniqLen-count)个元素 + for i := 0; i < uniqLen-count; i++ { + uniqList = Remove(uniqList, rand.Intn(uniqLen-i)) + } + return uniqList + } else { + // 所需唯一元素小于可选集合一半, 随机抽取count个元素 + res := make([]T, count) + var idx int + for i := 0; i < count; i++ { + idx = rand.Intn(uniqLen - i) + res[i] = uniqList[idx] + uniqList = Remove(uniqList, idx) + } + return res + } +} + +// Clone 复制切片 +func Clone[S ~[]T, T any](src S) S { + dest := make(S, len(src)) + copy(dest, src) + return dest +} + +// Count 统计制定条件元素数量 +func Count[S ~[]T, T any](arr S, filter func(int) bool) int { + count := 0 + for i := range arr { + if filter(i) { + count += 1 + } + } + return count +} + +// Group 根据分组函数对数组进行分组汇总 +func Group[S ~[]T, T any, K Hashable, R any](arr S, groupFunc func(int) (K, R)) map[K][]R { + ret := map[K][]R{} + for i := range arr { + key, val := groupFunc(i) + ret[key] = append(ret[key], val) + } + return ret +} diff --git a/build/README.md b/build/README.md new file mode 100644 index 0000000..b0753fe --- /dev/null +++ b/build/README.md @@ -0,0 +1,35 @@ +# Build Directory + +The build directory is used to house all the build files and assets for your application. + +The structure is: + +* bin - Output directory +* darwin - macOS specific files +* windows - Windows specific files + +## Mac + +The `darwin` directory holds files specific to Mac builds. +These may be customised and used as part of the build. To return these files to the default state, simply delete them +and +build with `wails build`. + +The directory contains the following files: + +- `Info.plist` - the main plist file used for Mac builds. It is used when building using `wails build`. +- `Info.dev.plist` - same as the main plist file but used when building using `wails dev`. + +## Windows + +The `windows` directory contains the manifest and rc files used when building with `wails build`. +These may be customised for your application. To return these files to the default state, simply delete them and +build with `wails build`. + +- `icon.ico` - The icon used for the application. This is used when building using `wails build`. If you wish to + use a different icon, simply replace this file with your own. If it is missing, a new `icon.ico` file + will be created using the `appicon.png` file in the build directory. +- `installer/*` - The files used to create the Windows installer. These are used when building using `wails build`. +- `info.json` - Application details used for Windows builds. The data here will be used by the Windows installer, + as well as the application itself (right click the exe -> properties -> details) +- `wails.exe.manifest` - The main application manifest file. diff --git a/build/appicon.png b/build/appicon.png new file mode 100644 index 0000000..63617fe Binary files /dev/null and b/build/appicon.png differ diff --git a/build/darwin/Info.dev.plist b/build/darwin/Info.dev.plist new file mode 100644 index 0000000..02e7358 --- /dev/null +++ b/build/darwin/Info.dev.plist @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + {{.Info.ProductName}} + CFBundleExecutable + {{.Name}} + CFBundleIdentifier + com.wails.{{.Name}} + CFBundleVersion + {{.Info.ProductVersion}} + CFBundleGetInfoString + {{.Info.Comments}} + CFBundleShortVersionString + {{.Info.ProductVersion}} + CFBundleIconFile + iconfile + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + {{.Info.Copyright}} + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + \ No newline at end of file diff --git a/build/darwin/Info.plist b/build/darwin/Info.plist new file mode 100644 index 0000000..e7819a7 --- /dev/null +++ b/build/darwin/Info.plist @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + {{.Info.ProductName}} + CFBundleExecutable + {{.Name}} + CFBundleIdentifier + com.wails.{{.Name}} + CFBundleVersion + {{.Info.ProductVersion}} + CFBundleGetInfoString + {{.Info.Comments}} + CFBundleShortVersionString + {{.Info.ProductVersion}} + CFBundleIconFile + iconfile + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + {{.Info.Copyright}} + + \ No newline at end of file diff --git a/build/windows/icon.ico b/build/windows/icon.ico new file mode 100644 index 0000000..f334798 Binary files /dev/null and b/build/windows/icon.ico differ diff --git a/build/windows/info.json b/build/windows/info.json new file mode 100644 index 0000000..f3db03b --- /dev/null +++ b/build/windows/info.json @@ -0,0 +1,15 @@ +{ + "fixed": { + "file_version": "{{.Info.ProductVersion}}" + }, + "info": { + "0000": { + "ProductVersion": "{{.Info.ProductVersion}}", + "CompanyName": "{{.Info.CompanyName}}", + "FileDescription": "{{.Info.ProductName}}", + "LegalCopyright": "{{.Info.Copyright}}", + "ProductName": "{{.Info.ProductName}}", + "Comments": "{{.Info.Comments}}" + } + } +} diff --git a/build/windows/installer/project.nsi b/build/windows/installer/project.nsi new file mode 100644 index 0000000..7b2148b --- /dev/null +++ b/build/windows/installer/project.nsi @@ -0,0 +1,105 @@ +Unicode true + +#### +## Please note: Template replacements don't work in this file. They are provided with default defines like +## mentioned underneath. +## If the keyword is not defined, "wails_tools.nsh" will populate them with the values from ProjectInfo. +## If they are defined here, "wails_tools.nsh" will not touch them. This allows to use this project.nsi manually +## from outside of Wails for debugging and development of the installer. +## +## For development first make a wails nsis build to populate the "wails_tools.nsh": +## > wails build --target windows/amd64 --nsis +## Then you can call makensis on this file with specifying the path to your binary: +## For a AMD64 only installer: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app.exe +## For a ARM64 only installer: +## > makensis -DARG_WAILS_ARM64_BINARY=..\..\bin\app.exe +## For a installer with both architectures: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe +#### +## The following information is taken from the ProjectInfo file, but they can be overwritten here. +#### +## !define INFO_PROJECTNAME "MyProject" # Default "{{.Name}}" +## !define INFO_COMPANYNAME "MyCompany" # Default "{{.Info.CompanyName}}" +## !define INFO_PRODUCTNAME "MyProduct" # Default "{{.Info.ProductName}}" +## !define INFO_PRODUCTVERSION "1.0.0" # Default "{{.Info.ProductVersion}}" +## !define INFO_COPYRIGHT "Copyright" # Default "{{.Info.Copyright}}" +### +## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe" +## !define UNINST_KEY_NAME "UninstKeyInRegistry" # Default "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +#### +## !define REQUEST_EXECUTION_LEVEL "admin" # Default "admin" see also https://nsis.sourceforge.io/Docs/Chapter4.html +#### +## Include the wails tools +#### +!include "wails_tools.nsh" + +# The version information for this two must consist of 4 parts +VIProductVersion "${INFO_PRODUCTVERSION}.0" +VIFileVersion "${INFO_PRODUCTVERSION}.0" + +VIAddVersionKey "CompanyName" "${INFO_COMPANYNAME}" +VIAddVersionKey "FileDescription" "${INFO_PRODUCTNAME} Installer" +VIAddVersionKey "ProductVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "FileVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "LegalCopyright" "${INFO_COPYRIGHT}" +VIAddVersionKey "ProductName" "${INFO_PRODUCTNAME}" + +!include "MUI.nsh" + +!define MUI_ICON "..\icon.ico" +!define MUI_UNICON "..\icon.ico" +# !define MUI_WELCOMEFINISHPAGE_BITMAP "resources\leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314 +!define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps +!define MUI_ABORTWARNING # This will warn the user if they exit from the installer. + +!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page. +# !insertmacro MUI_PAGE_LICENSE "resources\eula.txt" # Adds a EULA page to the installer +!insertmacro MUI_PAGE_DIRECTORY # In which folder install page. +!insertmacro MUI_PAGE_INSTFILES # Installing page. +!insertmacro MUI_PAGE_FINISH # Finished installation page. + +!insertmacro MUI_UNPAGE_INSTFILES # Uinstalling page + +!insertmacro MUI_LANGUAGE "English" # Set the Language of the installer + +## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1 +#!uninstfinalize 'signtool --file "%1"' +#!finalize 'signtool --file "%1"' + +Name "${INFO_PRODUCTNAME}" +OutFile "..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file. +InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder). +ShowInstDetails show # This will always show the installation details. + +Function .onInit + !insertmacro wails.checkArchitecture +FunctionEnd + +Section + !insertmacro wails.setShellContext + + !insertmacro wails.webview2runtime + + SetOutPath $INSTDIR + + !insertmacro wails.files + + CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + + !insertmacro wails.writeUninstaller +SectionEnd + +Section "uninstall" + !insertmacro wails.setShellContext + + RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath + + RMDir /r $INSTDIR + + Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" + Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk" + + !insertmacro wails.deleteUninstaller +SectionEnd diff --git a/build/windows/installer/wails_tools.nsh b/build/windows/installer/wails_tools.nsh new file mode 100644 index 0000000..467c349 --- /dev/null +++ b/build/windows/installer/wails_tools.nsh @@ -0,0 +1,179 @@ +# DO NOT EDIT - Generated automatically by `wails build` + +!include "x64.nsh" +!include "WinVer.nsh" +!include "FileFunc.nsh" + +!ifndef INFO_PROJECTNAME + !define INFO_PROJECTNAME "{{.Name}}" +!endif +!ifndef INFO_COMPANYNAME + !define INFO_COMPANYNAME "{{.Info.CompanyName}}" +!endif +!ifndef INFO_PRODUCTNAME + !define INFO_PRODUCTNAME "{{.Info.ProductName}}" +!endif +!ifndef INFO_PRODUCTVERSION + !define INFO_PRODUCTVERSION "{{.Info.ProductVersion}}" +!endif +!ifndef INFO_COPYRIGHT + !define INFO_COPYRIGHT "{{.Info.Copyright}}" +!endif +!ifndef PRODUCT_EXECUTABLE + !define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe" +!endif +!ifndef UNINST_KEY_NAME + !define UNINST_KEY_NAME "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +!endif +!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}" + +!ifndef REQUEST_EXECUTION_LEVEL + !define REQUEST_EXECUTION_LEVEL "admin" +!endif + +RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}" + +!ifdef ARG_WAILS_AMD64_BINARY + !define SUPPORTS_AMD64 +!endif + +!ifdef ARG_WAILS_ARM64_BINARY + !define SUPPORTS_ARM64 +!endif + +!ifdef SUPPORTS_AMD64 + !ifdef SUPPORTS_ARM64 + !define ARCH "amd64_arm64" + !else + !define ARCH "amd64" + !endif +!else + !ifdef SUPPORTS_ARM64 + !define ARCH "arm64" + !else + !error "Wails: Undefined ARCH, please provide at least one of ARG_WAILS_AMD64_BINARY or ARG_WAILS_ARM64_BINARY" + !endif +!endif + +!macro wails.checkArchitecture + !ifndef WAILS_WIN10_REQUIRED + !define WAILS_WIN10_REQUIRED "This product is only supported on Windows 10 (Server 2016) and later." + !endif + + !ifndef WAILS_ARCHITECTURE_NOT_SUPPORTED + !define WAILS_ARCHITECTURE_NOT_SUPPORTED "This product can't be installed on the current Windows architecture. Supports: ${ARCH}" + !endif + + ${If} ${AtLeastWin10} + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + Goto ok + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + Goto ok + ${EndIf} + !endif + + IfSilent silentArch notSilentArch + silentArch: + SetErrorLevel 65 + Abort + notSilentArch: + MessageBox MB_OK "${WAILS_ARCHITECTURE_NOT_SUPPORTED}" + Quit + ${else} + IfSilent silentWin notSilentWin + silentWin: + SetErrorLevel 64 + Abort + notSilentWin: + MessageBox MB_OK "${WAILS_WIN10_REQUIRED}" + Quit + ${EndIf} + + ok: +!macroend + +!macro wails.files + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_AMD64_BINARY}" + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_ARM64_BINARY}" + ${EndIf} + !endif +!macroend + +!macro wails.writeUninstaller + WriteUninstaller "$INSTDIR\uninstall.exe" + + SetRegView 64 + WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}" + WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" + WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" + + ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 + IntFmt $0 "0x%08X" $0 + WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0" +!macroend + +!macro wails.deleteUninstaller + Delete "$INSTDIR\uninstall.exe" + + SetRegView 64 + DeleteRegKey HKLM "${UNINST_KEY}" +!macroend + +!macro wails.setShellContext + ${If} ${REQUEST_EXECUTION_LEVEL} == "admin" + SetShellVarContext all + ${else} + SetShellVarContext current + ${EndIf} +!macroend + +# Install webview2 by launching the bootstrapper +# See https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment +!macro wails.webview2runtime + !ifndef WAILS_INSTALL_WEBVIEW_DETAILPRINT + !define WAILS_INSTALL_WEBVIEW_DETAILPRINT "Installing: WebView2 Runtime" + !endif + + SetRegView 64 + # If the admin key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + + ${If} ${REQUEST_EXECUTION_LEVEL} == "user" + # If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + ${EndIf} + + SetDetailsPrint both + DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}" + SetDetailsPrint listonly + + InitPluginsDir + CreateDirectory "$pluginsdir\webview2bootstrapper" + SetOutPath "$pluginsdir\webview2bootstrapper" + File "tmp\MicrosoftEdgeWebview2Setup.exe" + ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install' + + SetDetailsPrint both + ok: +!macroend \ No newline at end of file diff --git a/build/windows/wails.exe.manifest b/build/windows/wails.exe.manifest new file mode 100644 index 0000000..17e1a23 --- /dev/null +++ b/build/windows/wails.exe.manifest @@ -0,0 +1,15 @@ + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + \ No newline at end of file diff --git a/frontend/.prettierrc b/frontend/.prettierrc new file mode 100644 index 0000000..9be60a5 --- /dev/null +++ b/frontend/.prettierrc @@ -0,0 +1,8 @@ +{ + "printWidth": 120, + "tabWidth": 4, + "singleQuote": true, + "semi": false, + "bracketSameLine": false, + "endOfLine": "auto" +} diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..b4719be --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,8 @@ +# Vue 3 + Vite + +This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 ` + + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..c62cbe3 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,3483 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "highlight.js": "^11.8.0", + "lodash": "^4.17.21", + "pinia": "^2.1.3", + "sass": "^1.62.1", + "vue": "^3.2.37", + "vue-i18n": "^9.2.2" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^4.2.3", + "naive-ui": "^2.34.4", + "prettier": "^2.8.8", + "unplugin-auto-import": "^0.16.4", + "unplugin-icons": "^0.16.1", + "unplugin-vue-components": "^0.25.0", + "vite": "^4.3.0" + } + }, + "node_modules/@antfu/install-pkg": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/@antfu/install-pkg/-/install-pkg-0.1.1.tgz", + "integrity": "sha512-LyB/8+bSfa0DFGC06zpCEfs89/XoWZwws5ygEa5D+Xsm3OfI+aXQ86VgVG7Acyef+rSZ5HE7J8rrxzrQeM3PjQ==", + "dev": true, + "dependencies": { + "execa": "^5.1.1", + "find-up": "^5.0.0" + } + }, + "node_modules/@antfu/utils": { + "version": "0.7.4", + "resolved": "https://registry.npmmirror.com/@antfu/utils/-/utils-0.7.4.tgz", + "integrity": "sha512-qe8Nmh9rYI/HIspLSTwtbMFPj6dISG6+dJnOguTlPNXtCvS2uezdxscVBb7/3DrmNbQK49TDqpkSQ1chbRGdpQ==", + "dev": true + }, + "node_modules/@babel/parser": { + "version": "7.22.5", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.22.5.tgz", + "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.22.5", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.22.5.tgz", + "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@css-render/plugin-bem": { + "version": "0.15.12", + "resolved": "https://registry.npmmirror.com/@css-render/plugin-bem/-/plugin-bem-0.15.12.tgz", + "integrity": "sha512-Lq2jSOZn+wYQtsyaFj6QRz2EzAnd3iW5fZeHO1WSXQdVYwvwGX0ZiH3X2JQgtgYLT1yeGtrwrqJdNdMEUD2xTw==", + "dev": true, + "peerDependencies": { + "css-render": "~0.15.12" + } + }, + "node_modules/@css-render/vue3-ssr": { + "version": "0.15.12", + "resolved": "https://registry.npmmirror.com/@css-render/vue3-ssr/-/vue3-ssr-0.15.12.tgz", + "integrity": "sha512-AQLGhhaE0F+rwybRCkKUdzBdTEM/5PZBYy+fSYe1T9z9+yxMuV/k7ZRqa4M69X+EI1W8pa4kc9Iq2VjQkZx4rg==", + "dev": true, + "peerDependencies": { + "vue": "^3.0.11" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmmirror.com/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "dev": true + }, + "node_modules/@esbuild/android-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.17.19.tgz", + "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", + "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.17.19.tgz", + "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", + "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", + "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", + "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", + "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", + "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", + "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", + "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", + "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", + "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", + "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", + "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", + "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", + "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", + "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", + "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", + "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", + "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", + "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", + "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "dev": true + }, + "node_modules/@iconify/utils": { + "version": "2.1.6", + "resolved": "https://registry.npmmirror.com/@iconify/utils/-/utils-2.1.6.tgz", + "integrity": "sha512-WJNcj/mmFQoYok+576EexlCQe/g2tZ8X9jR4QLo++z6DlVqrjwt7FBYetTQ3iyTtrPMFHcAx0JiCqtUz30XG5A==", + "dev": true, + "dependencies": { + "@antfu/install-pkg": "^0.1.1", + "@antfu/utils": "^0.7.4", + "@iconify/types": "^2.0.0", + "debug": "^4.3.4", + "kolorist": "^1.8.0", + "local-pkg": "^0.4.3" + } + }, + "node_modules/@intlify/core-base": { + "version": "9.2.2", + "resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-9.2.2.tgz", + "integrity": "sha512-JjUpQtNfn+joMbrXvpR4hTF8iJQ2sEFzzK3KIESOx+f+uwIjgw20igOyaIdhfsVVBCds8ZM64MoeNSx+PHQMkA==", + "dependencies": { + "@intlify/devtools-if": "9.2.2", + "@intlify/message-compiler": "9.2.2", + "@intlify/shared": "9.2.2", + "@intlify/vue-devtools": "9.2.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@intlify/devtools-if": { + "version": "9.2.2", + "resolved": "https://registry.npmmirror.com/@intlify/devtools-if/-/devtools-if-9.2.2.tgz", + "integrity": "sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg==", + "dependencies": { + "@intlify/shared": "9.2.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@intlify/message-compiler": { + "version": "9.2.2", + "resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-9.2.2.tgz", + "integrity": "sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA==", + "dependencies": { + "@intlify/shared": "9.2.2", + "source-map": "0.6.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@intlify/shared": { + "version": "9.2.2", + "resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-9.2.2.tgz", + "integrity": "sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/@intlify/vue-devtools": { + "version": "9.2.2", + "resolved": "https://registry.npmmirror.com/@intlify/vue-devtools/-/vue-devtools-9.2.2.tgz", + "integrity": "sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg==", + "dependencies": { + "@intlify/core-base": "9.2.2", + "@intlify/shared": "9.2.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@juggle/resize-observer": { + "version": "3.4.0", + "resolved": "https://registry.npmmirror.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", + "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==", + "dev": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.0.2.tgz", + "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@types/estree": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "dev": true + }, + "node_modules/@types/katex": { + "version": "0.14.0", + "resolved": "https://registry.npmmirror.com/@types/katex/-/katex-0.14.0.tgz", + "integrity": "sha512-+2FW2CcT0K3P+JMR8YG846bmDwplKUTsWgT2ENwdQ1UdVfRk3GQrh6Mi4sTopy30gI8Uau5CEqHTDZ6YvWIUPA==", + "dev": true + }, + "node_modules/@types/lodash": { + "version": "4.14.195", + "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.195.tgz", + "integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==", + "dev": true + }, + "node_modules/@types/lodash-es": { + "version": "4.17.7", + "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.7.tgz", + "integrity": "sha512-z0ptr6UI10VlU6l5MYhGwS4mC8DZyYer2mCoyysZtSF7p26zOX8UpbrV0YpNYLGS8K4PUFIyEr62IMFFjveSiQ==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.16", + "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz", + "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@vitejs/plugin-vue": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-4.2.3.tgz", + "integrity": "sha512-R6JDUfiZbJA9cMiguQ7jxALsgiprjBeHL5ikpXfJCH62pPHtI+JdJ5xWj6Ev73yXSlYl86+blXn1kZHQ7uElxw==", + "dev": true, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.3.4", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.3.4.tgz", + "integrity": "sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==", + "dependencies": { + "@babel/parser": "^7.21.3", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.3.4", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz", + "integrity": "sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==", + "dependencies": { + "@vue/compiler-core": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.3.4", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz", + "integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==", + "dependencies": { + "@babel/parser": "^7.20.15", + "@vue/compiler-core": "3.3.4", + "@vue/compiler-dom": "3.3.4", + "@vue/compiler-ssr": "3.3.4", + "@vue/reactivity-transform": "3.3.4", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.0", + "postcss": "^8.1.10", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.3.4", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz", + "integrity": "sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==", + "dependencies": { + "@vue/compiler-dom": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.5.0.tgz", + "integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==" + }, + "node_modules/@vue/reactivity": { + "version": "3.3.4", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.3.4.tgz", + "integrity": "sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==", + "dependencies": { + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/reactivity-transform": { + "version": "3.3.4", + "resolved": "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz", + "integrity": "sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==", + "dependencies": { + "@babel/parser": "^7.20.15", + "@vue/compiler-core": "3.3.4", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.0" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.3.4", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.3.4.tgz", + "integrity": "sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==", + "dependencies": { + "@vue/reactivity": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.3.4", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.3.4.tgz", + "integrity": "sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==", + "dependencies": { + "@vue/runtime-core": "3.3.4", + "@vue/shared": "3.3.4", + "csstype": "^3.1.1" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.3.4", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.3.4.tgz", + "integrity": "sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==", + "dependencies": { + "@vue/compiler-ssr": "3.3.4", + "@vue/shared": "3.3.4" + }, + "peerDependencies": { + "vue": "3.3.4" + } + }, + "node_modules/@vue/shared": { + "version": "3.3.4", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.3.4.tgz", + "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==" + }, + "node_modules/@vueuse/core": { + "version": "9.13.0", + "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-9.13.0.tgz", + "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@types/web-bluetooth": "^0.0.16", + "@vueuse/metadata": "9.13.0", + "@vueuse/shared": "9.13.0", + "vue-demi": "*" + } + }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.5", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.5.tgz", + "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "peer": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "9.13.0", + "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.13.0.tgz", + "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@vueuse/shared": { + "version": "9.13.0", + "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-9.13.0.tgz", + "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "vue-demi": "*" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.5", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.5.tgz", + "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "peer": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-render": { + "version": "0.15.12", + "resolved": "https://registry.npmmirror.com/css-render/-/css-render-0.15.12.tgz", + "integrity": "sha512-eWzS66patiGkTTik+ipO9qNGZ+uNuGyTmnz6/+EJIiFg8+3yZRpnMwgFo8YdXhQRsiePzehnusrxVvugNjXzbw==", + "dev": true, + "dependencies": { + "@emotion/hash": "~0.8.0", + "csstype": "~3.0.5" + } + }, + "node_modules/css-render/node_modules/csstype": { + "version": "3.0.11", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.0.11.tgz", + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==", + "dev": true + }, + "node_modules/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmmirror.com/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + } + }, + "node_modules/date-fns-tz": { + "version": "1.3.8", + "resolved": "https://registry.npmmirror.com/date-fns-tz/-/date-fns-tz-1.3.8.tgz", + "integrity": "sha512-qwNXUFtMHTTU6CFSFjoJ80W8Fzzp24LntbjFFBgL/faqds4e5mo9mftoRLgr3Vi1trISsg4awSpYVsOQCRnapQ==", + "dev": true, + "peerDependencies": { + "date-fns": ">=2.0.0" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/esbuild": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.17.19.tgz", + "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.19", + "@esbuild/android-arm64": "0.17.19", + "@esbuild/android-x64": "0.17.19", + "@esbuild/darwin-arm64": "0.17.19", + "@esbuild/darwin-x64": "0.17.19", + "@esbuild/freebsd-arm64": "0.17.19", + "@esbuild/freebsd-x64": "0.17.19", + "@esbuild/linux-arm": "0.17.19", + "@esbuild/linux-arm64": "0.17.19", + "@esbuild/linux-ia32": "0.17.19", + "@esbuild/linux-loong64": "0.17.19", + "@esbuild/linux-mips64el": "0.17.19", + "@esbuild/linux-ppc64": "0.17.19", + "@esbuild/linux-riscv64": "0.17.19", + "@esbuild/linux-s390x": "0.17.19", + "@esbuild/linux-x64": "0.17.19", + "@esbuild/netbsd-x64": "0.17.19", + "@esbuild/openbsd-x64": "0.17.19", + "@esbuild/sunos-x64": "0.17.19", + "@esbuild/win32-arm64": "0.17.19", + "@esbuild/win32-ia32": "0.17.19", + "@esbuild/win32-x64": "0.17.19" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, + "node_modules/evtd": { + "version": "0.2.4", + "resolved": "https://registry.npmmirror.com/evtd/-/evtd-0.2.4.tgz", + "integrity": "sha512-qaeGN5bx63s/AXgQo8gj6fBkxge+OoLddLniox5qtLAEY5HSnuSlISXVPxnSae1dWblvTh4/HoMIB+mbMsvZzw==", + "dev": true + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/highlight.js": { + "version": "11.8.0", + "resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.8.0.tgz", + "integrity": "sha512-MedQhoqVdr0U6SSnWPzfiadUcDHfN/Wzq25AkXiQv9oiOO/sG0S7XkvpFIqWBl9Yq1UYyYOOVORs5UW2XlPyzg==", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/immutable": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/immutable/-/immutable-4.3.0.tgz", + "integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.12.1", + "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "dev": true + }, + "node_modules/local-pkg": { + "version": "0.4.3", + "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-0.4.3.tgz", + "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "dev": true + }, + "node_modules/magic-string": { + "version": "0.30.0", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.0.tgz", + "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.13" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mlly": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.3.0.tgz", + "integrity": "sha512-HT5mcgIQKkOrZecOjOX3DJorTikWXwsBfpcr/MGBkhfWcjiqvnaL/9ppxvIUXfjT6xt4DVIAsN9fMUz1ev4bIw==", + "dev": true, + "dependencies": { + "acorn": "^8.8.2", + "pathe": "^1.1.0", + "pkg-types": "^1.0.3", + "ufo": "^1.1.2" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/naive-ui": { + "version": "2.34.4", + "resolved": "https://registry.npmmirror.com/naive-ui/-/naive-ui-2.34.4.tgz", + "integrity": "sha512-aPG8PDfhSzIzn/jSC9y3Jb3Pe2wHJ7F0cFV1EWlbImSrZECeUmoc+fIcOSWbizoztkKfaUAeKwYdMl09MKkj1g==", + "dev": true, + "dependencies": { + "@css-render/plugin-bem": "^0.15.10", + "@css-render/vue3-ssr": "^0.15.10", + "@types/katex": "^0.14.0", + "@types/lodash": "^4.14.181", + "@types/lodash-es": "^4.17.6", + "async-validator": "^4.0.7", + "css-render": "^0.15.10", + "date-fns": "^2.28.0", + "date-fns-tz": "^1.3.3", + "evtd": "^0.2.4", + "highlight.js": "^11.5.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "seemly": "^0.3.6", + "treemate": "^0.3.11", + "vdirs": "^0.1.8", + "vooks": "^0.2.12", + "vueuc": "^0.4.51" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/pathe": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-1.1.1.tgz", + "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + } + }, + "node_modules/pinia": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.1.3.tgz", + "integrity": "sha512-XNA/z/ye4P5rU1pieVmh0g/hSuDO98/a5UC8oSP0DNdvt6YtetJNHTrXwpwsQuflkGT34qKxAEcp7lSxXNjf/A==", + "dependencies": { + "@vue/devtools-api": "^6.5.0", + "vue-demi": ">=0.14.5" + }, + "peerDependencies": { + "@vue/composition-api": "^1.4.0", + "typescript": ">=4.4.4", + "vue": "^2.6.14 || ^3.3.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/pinia/node_modules/vue-demi": { + "version": "0.14.5", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.5.tgz", + "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/pkg-types": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.0.3.tgz", + "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", + "dev": true, + "dependencies": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.2.0", + "pathe": "^1.1.0" + } + }, + "node_modules/postcss": { + "version": "8.4.24", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.24.tgz", + "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmmirror.com/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, + "dependencies": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "3.24.0", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-3.24.0.tgz", + "integrity": "sha512-OgraHOIg2YpHQTjl0/ymWfFNBEyPucB7lmhXrQUh38qNOegxLapSPFs9sNr0qKR75awW41D93XafoR2QfhBdUQ==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/sass": { + "version": "1.63.2", + "resolved": "https://registry.npmmirror.com/sass/-/sass-1.63.2.tgz", + "integrity": "sha512-u56TU0AIFqMtauKl/OJ1AeFsXqRHkgO7nCWmHaDwfxDo9GUMSqBA4NEh6GMuh1CYVM7zuROYtZrHzPc2ixK+ww==", + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/scule": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/scule/-/scule-1.0.0.tgz", + "integrity": "sha512-4AsO/FrViE/iDNEPaAQlb77tf0csuq27EsVpy6ett584EcRTp6pTDLoGWVxCD77y5iU5FauOvhsI4o1APwPoSQ==", + "dev": true + }, + "node_modules/seemly": { + "version": "0.3.6", + "resolved": "https://registry.npmmirror.com/seemly/-/seemly-0.3.6.tgz", + "integrity": "sha512-lEV5VB8BUKTo/AfktXJcy+JeXns26ylbMkIUco8CYREsQijuz4mrXres2Q+vMLdwkuLxJdIPQ8IlCIxLYm71Yw==", + "dev": true + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-literal": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/strip-literal/-/strip-literal-1.0.1.tgz", + "integrity": "sha512-QZTsipNpa2Ppr6v1AmJHESqJ3Uz247MUS0OjrnnZjFAvEoWqxuyFuXn2xLgMtRnijJShAa1HL0gtJyUs7u7n3Q==", + "dev": true, + "dependencies": { + "acorn": "^8.8.2" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/treemate": { + "version": "0.3.11", + "resolved": "https://registry.npmmirror.com/treemate/-/treemate-0.3.11.tgz", + "integrity": "sha512-M8RGFoKtZ8dF+iwJfAJTOH/SM4KluKOKRJpjCMhI8bG3qB74zrFoArKZ62ll0Fr3mqkMJiQOmWYkdYgDeITYQg==", + "dev": true + }, + "node_modules/ufo": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.1.2.tgz", + "integrity": "sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ==", + "dev": true + }, + "node_modules/unimport": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/unimport/-/unimport-3.0.7.tgz", + "integrity": "sha512-2dVQUxJEGcrSZ0U4qtwJVODrlfyGcwmIOoHVqbAFFUx7kPoEN5JWr1cZFhLwoAwTmZOvqAm3YIkzv1engIQocg==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.2", + "escape-string-regexp": "^5.0.0", + "fast-glob": "^3.2.12", + "local-pkg": "^0.4.3", + "magic-string": "^0.30.0", + "mlly": "^1.2.1", + "pathe": "^1.1.0", + "pkg-types": "^1.0.3", + "scule": "^1.0.0", + "strip-literal": "^1.0.1", + "unplugin": "^1.3.1" + } + }, + "node_modules/unplugin": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-1.3.1.tgz", + "integrity": "sha512-h4uUTIvFBQRxUKS2Wjys6ivoeofGhxzTe2sRWlooyjHXVttcVfV/JiavNd3d4+jty0SVV0dxGw9AkY9MwiaCEw==", + "dev": true, + "dependencies": { + "acorn": "^8.8.2", + "chokidar": "^3.5.3", + "webpack-sources": "^3.2.3", + "webpack-virtual-modules": "^0.5.0" + } + }, + "node_modules/unplugin-auto-import": { + "version": "0.16.4", + "resolved": "https://registry.npmmirror.com/unplugin-auto-import/-/unplugin-auto-import-0.16.4.tgz", + "integrity": "sha512-xdgBa9NAS3JG8HjkAZHSbGSMlrjKpaWKXGUzaF6RzEtr980RCl1t0Zsu0skUInNYrEQfqaHc7aGWPv41DLTK/w==", + "dev": true, + "dependencies": { + "@antfu/utils": "^0.7.2", + "@rollup/pluginutils": "^5.0.2", + "local-pkg": "^0.4.3", + "magic-string": "^0.30.0", + "minimatch": "^9.0.1", + "unimport": "^3.0.7", + "unplugin": "^1.3.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@nuxt/kit": "^3.2.2", + "@vueuse/core": "*" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + }, + "@vueuse/core": { + "optional": true + } + } + }, + "node_modules/unplugin-icons": { + "version": "0.16.3", + "resolved": "https://registry.npmmirror.com/unplugin-icons/-/unplugin-icons-0.16.3.tgz", + "integrity": "sha512-hivVVr6++WHSj6Iz+rjTa14/ALMYT+PFd2sPtTBKlQR3cdzui1VwM72TzSu94NkDm/KVncvOIiBwoHwUPeL9bg==", + "dev": true, + "dependencies": { + "@antfu/install-pkg": "^0.1.1", + "@antfu/utils": "^0.7.4", + "@iconify/utils": "^2.1.6", + "debug": "^4.3.4", + "kolorist": "^1.8.0", + "local-pkg": "^0.4.3", + "unplugin": "^1.3.1" + }, + "peerDependencies": { + "@svgr/core": ">=7.0.0", + "@vue/compiler-sfc": "^3.0.2 || ^2.7.0", + "vue-template-compiler": "^2.6.12", + "vue-template-es2015-compiler": "^1.9.0" + }, + "peerDependenciesMeta": { + "@svgr/core": { + "optional": true + }, + "@vue/compiler-sfc": { + "optional": true + }, + "vue-template-compiler": { + "optional": true + }, + "vue-template-es2015-compiler": { + "optional": true + } + } + }, + "node_modules/unplugin-vue-components": { + "version": "0.25.1", + "resolved": "https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-0.25.1.tgz", + "integrity": "sha512-kzS2ZHVMaGU2XEO2keYQcMjNZkanDSGDdY96uQT9EPe+wqSZwwgbFfKVJ5ti0+8rGAcKHColwKUvctBhq2LJ3A==", + "dev": true, + "dependencies": { + "@antfu/utils": "^0.7.4", + "@rollup/pluginutils": "^5.0.2", + "chokidar": "^3.5.3", + "debug": "^4.3.4", + "fast-glob": "^3.2.12", + "local-pkg": "^0.4.3", + "magic-string": "^0.30.0", + "minimatch": "^9.0.1", + "resolve": "^1.22.2", + "unplugin": "^1.3.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@babel/parser": "^7.15.8", + "@nuxt/kit": "^3.2.2", + "vue": "2 || 3" + }, + "peerDependenciesMeta": { + "@babel/parser": { + "optional": true + }, + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/vdirs": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/vdirs/-/vdirs-0.1.8.tgz", + "integrity": "sha512-H9V1zGRLQZg9b+GdMk8MXDN2Lva0zx72MPahDKc30v+DtwKjfyOSXWRIX4t2mhDubM1H09gPhWeth/BJWPHGUw==", + "dev": true, + "dependencies": { + "evtd": "^0.2.2" + }, + "peerDependencies": { + "vue": "^3.0.11" + } + }, + "node_modules/vite": { + "version": "4.3.9", + "resolved": "https://registry.npmmirror.com/vite/-/vite-4.3.9.tgz", + "integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==", + "dev": true, + "dependencies": { + "esbuild": "^0.17.5", + "postcss": "^8.4.23", + "rollup": "^3.21.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vooks": { + "version": "0.2.12", + "resolved": "https://registry.npmmirror.com/vooks/-/vooks-0.2.12.tgz", + "integrity": "sha512-iox0I3RZzxtKlcgYaStQYKEzWWGAduMmq+jS7OrNdQo1FgGfPMubGL3uGHOU9n97NIvfFDBGnpSvkWyb/NSn/Q==", + "dev": true, + "dependencies": { + "evtd": "^0.2.2" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/vue": { + "version": "3.3.4", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.3.4.tgz", + "integrity": "sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==", + "dependencies": { + "@vue/compiler-dom": "3.3.4", + "@vue/compiler-sfc": "3.3.4", + "@vue/runtime-dom": "3.3.4", + "@vue/server-renderer": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/vue-i18n": { + "version": "9.2.2", + "resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-9.2.2.tgz", + "integrity": "sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ==", + "dependencies": { + "@intlify/core-base": "9.2.2", + "@intlify/shared": "9.2.2", + "@intlify/vue-devtools": "9.2.2", + "@vue/devtools-api": "^6.2.1" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/vueuc": { + "version": "0.4.51", + "resolved": "https://registry.npmmirror.com/vueuc/-/vueuc-0.4.51.tgz", + "integrity": "sha512-pLiMChM4f+W8czlIClGvGBYo656lc2Y0/mXFSCydcSmnCR1izlKPGMgiYBGjbY9FDkFG8a2HEVz7t0DNzBWbDw==", + "dev": true, + "dependencies": { + "@css-render/vue3-ssr": "^0.15.10", + "@juggle/resize-observer": "^3.3.1", + "css-render": "^0.15.10", + "evtd": "^0.2.4", + "seemly": "^0.3.6", + "vdirs": "^0.1.4", + "vooks": "^0.2.4" + }, + "peerDependencies": { + "vue": "^3.0.11" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.5.0", + "resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.5.0.tgz", + "integrity": "sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==", + "dev": true + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + } + } + }, + "dependencies": { + "@antfu/install-pkg": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/@antfu/install-pkg/-/install-pkg-0.1.1.tgz", + "integrity": "sha512-LyB/8+bSfa0DFGC06zpCEfs89/XoWZwws5ygEa5D+Xsm3OfI+aXQ86VgVG7Acyef+rSZ5HE7J8rrxzrQeM3PjQ==", + "dev": true, + "requires": { + "execa": "^5.1.1", + "find-up": "^5.0.0" + } + }, + "@antfu/utils": { + "version": "0.7.4", + "resolved": "https://registry.npmmirror.com/@antfu/utils/-/utils-0.7.4.tgz", + "integrity": "sha512-qe8Nmh9rYI/HIspLSTwtbMFPj6dISG6+dJnOguTlPNXtCvS2uezdxscVBb7/3DrmNbQK49TDqpkSQ1chbRGdpQ==", + "dev": true + }, + "@babel/parser": { + "version": "7.22.5", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.22.5.tgz", + "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==" + }, + "@babel/runtime": { + "version": "7.22.5", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.22.5.tgz", + "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.11" + } + }, + "@css-render/plugin-bem": { + "version": "0.15.12", + "resolved": "https://registry.npmmirror.com/@css-render/plugin-bem/-/plugin-bem-0.15.12.tgz", + "integrity": "sha512-Lq2jSOZn+wYQtsyaFj6QRz2EzAnd3iW5fZeHO1WSXQdVYwvwGX0ZiH3X2JQgtgYLT1yeGtrwrqJdNdMEUD2xTw==", + "dev": true, + "requires": {} + }, + "@css-render/vue3-ssr": { + "version": "0.15.12", + "resolved": "https://registry.npmmirror.com/@css-render/vue3-ssr/-/vue3-ssr-0.15.12.tgz", + "integrity": "sha512-AQLGhhaE0F+rwybRCkKUdzBdTEM/5PZBYy+fSYe1T9z9+yxMuV/k7ZRqa4M69X+EI1W8pa4kc9Iq2VjQkZx4rg==", + "dev": true, + "requires": {} + }, + "@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmmirror.com/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "dev": true + }, + "@esbuild/android-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.17.19.tgz", + "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", + "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.17.19.tgz", + "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", + "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", + "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", + "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", + "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", + "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", + "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", + "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", + "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", + "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", + "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", + "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", + "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", + "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", + "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", + "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", + "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", + "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", + "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", + "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "dev": true, + "optional": true + }, + "@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "dev": true + }, + "@iconify/utils": { + "version": "2.1.6", + "resolved": "https://registry.npmmirror.com/@iconify/utils/-/utils-2.1.6.tgz", + "integrity": "sha512-WJNcj/mmFQoYok+576EexlCQe/g2tZ8X9jR4QLo++z6DlVqrjwt7FBYetTQ3iyTtrPMFHcAx0JiCqtUz30XG5A==", + "dev": true, + "requires": { + "@antfu/install-pkg": "^0.1.1", + "@antfu/utils": "^0.7.4", + "@iconify/types": "^2.0.0", + "debug": "^4.3.4", + "kolorist": "^1.8.0", + "local-pkg": "^0.4.3" + } + }, + "@intlify/core-base": { + "version": "9.2.2", + "resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-9.2.2.tgz", + "integrity": "sha512-JjUpQtNfn+joMbrXvpR4hTF8iJQ2sEFzzK3KIESOx+f+uwIjgw20igOyaIdhfsVVBCds8ZM64MoeNSx+PHQMkA==", + "requires": { + "@intlify/devtools-if": "9.2.2", + "@intlify/message-compiler": "9.2.2", + "@intlify/shared": "9.2.2", + "@intlify/vue-devtools": "9.2.2" + } + }, + "@intlify/devtools-if": { + "version": "9.2.2", + "resolved": "https://registry.npmmirror.com/@intlify/devtools-if/-/devtools-if-9.2.2.tgz", + "integrity": "sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg==", + "requires": { + "@intlify/shared": "9.2.2" + } + }, + "@intlify/message-compiler": { + "version": "9.2.2", + "resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-9.2.2.tgz", + "integrity": "sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA==", + "requires": { + "@intlify/shared": "9.2.2", + "source-map": "0.6.1" + } + }, + "@intlify/shared": { + "version": "9.2.2", + "resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-9.2.2.tgz", + "integrity": "sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==" + }, + "@intlify/vue-devtools": { + "version": "9.2.2", + "resolved": "https://registry.npmmirror.com/@intlify/vue-devtools/-/vue-devtools-9.2.2.tgz", + "integrity": "sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg==", + "requires": { + "@intlify/core-base": "9.2.2", + "@intlify/shared": "9.2.2" + } + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "@juggle/resize-observer": { + "version": "3.4.0", + "resolved": "https://registry.npmmirror.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", + "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==", + "dev": true + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@rollup/pluginutils": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.0.2.tgz", + "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==", + "dev": true, + "requires": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + } + }, + "@types/estree": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "dev": true + }, + "@types/katex": { + "version": "0.14.0", + "resolved": "https://registry.npmmirror.com/@types/katex/-/katex-0.14.0.tgz", + "integrity": "sha512-+2FW2CcT0K3P+JMR8YG846bmDwplKUTsWgT2ENwdQ1UdVfRk3GQrh6Mi4sTopy30gI8Uau5CEqHTDZ6YvWIUPA==", + "dev": true + }, + "@types/lodash": { + "version": "4.14.195", + "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.195.tgz", + "integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==", + "dev": true + }, + "@types/lodash-es": { + "version": "4.17.7", + "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.7.tgz", + "integrity": "sha512-z0ptr6UI10VlU6l5MYhGwS4mC8DZyYer2mCoyysZtSF7p26zOX8UpbrV0YpNYLGS8K4PUFIyEr62IMFFjveSiQ==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, + "@types/web-bluetooth": { + "version": "0.0.16", + "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz", + "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==", + "dev": true, + "optional": true, + "peer": true + }, + "@vitejs/plugin-vue": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-4.2.3.tgz", + "integrity": "sha512-R6JDUfiZbJA9cMiguQ7jxALsgiprjBeHL5ikpXfJCH62pPHtI+JdJ5xWj6Ev73yXSlYl86+blXn1kZHQ7uElxw==", + "dev": true, + "requires": {} + }, + "@vue/compiler-core": { + "version": "3.3.4", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.3.4.tgz", + "integrity": "sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==", + "requires": { + "@babel/parser": "^7.21.3", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "source-map-js": "^1.0.2" + } + }, + "@vue/compiler-dom": { + "version": "3.3.4", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz", + "integrity": "sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==", + "requires": { + "@vue/compiler-core": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "@vue/compiler-sfc": { + "version": "3.3.4", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz", + "integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==", + "requires": { + "@babel/parser": "^7.20.15", + "@vue/compiler-core": "3.3.4", + "@vue/compiler-dom": "3.3.4", + "@vue/compiler-ssr": "3.3.4", + "@vue/reactivity-transform": "3.3.4", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.0", + "postcss": "^8.1.10", + "source-map-js": "^1.0.2" + } + }, + "@vue/compiler-ssr": { + "version": "3.3.4", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz", + "integrity": "sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==", + "requires": { + "@vue/compiler-dom": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "@vue/devtools-api": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.5.0.tgz", + "integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==" + }, + "@vue/reactivity": { + "version": "3.3.4", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.3.4.tgz", + "integrity": "sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==", + "requires": { + "@vue/shared": "3.3.4" + } + }, + "@vue/reactivity-transform": { + "version": "3.3.4", + "resolved": "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz", + "integrity": "sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==", + "requires": { + "@babel/parser": "^7.20.15", + "@vue/compiler-core": "3.3.4", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.0" + } + }, + "@vue/runtime-core": { + "version": "3.3.4", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.3.4.tgz", + "integrity": "sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==", + "requires": { + "@vue/reactivity": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "@vue/runtime-dom": { + "version": "3.3.4", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.3.4.tgz", + "integrity": "sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==", + "requires": { + "@vue/runtime-core": "3.3.4", + "@vue/shared": "3.3.4", + "csstype": "^3.1.1" + } + }, + "@vue/server-renderer": { + "version": "3.3.4", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.3.4.tgz", + "integrity": "sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==", + "requires": { + "@vue/compiler-ssr": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "@vue/shared": { + "version": "3.3.4", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.3.4.tgz", + "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==" + }, + "@vueuse/core": { + "version": "9.13.0", + "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-9.13.0.tgz", + "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@types/web-bluetooth": "^0.0.16", + "@vueuse/metadata": "9.13.0", + "@vueuse/shared": "9.13.0", + "vue-demi": "*" + }, + "dependencies": { + "vue-demi": { + "version": "0.14.5", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.5.tgz", + "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==", + "dev": true, + "optional": true, + "peer": true, + "requires": {} + } + } + }, + "@vueuse/metadata": { + "version": "9.13.0", + "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.13.0.tgz", + "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==", + "dev": true, + "optional": true, + "peer": true + }, + "@vueuse/shared": { + "version": "9.13.0", + "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-9.13.0.tgz", + "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "vue-demi": "*" + }, + "dependencies": { + "vue-demi": { + "version": "0.14.5", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.5.tgz", + "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==", + "dev": true, + "optional": true, + "peer": true, + "requires": {} + } + } + }, + "acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "css-render": { + "version": "0.15.12", + "resolved": "https://registry.npmmirror.com/css-render/-/css-render-0.15.12.tgz", + "integrity": "sha512-eWzS66patiGkTTik+ipO9qNGZ+uNuGyTmnz6/+EJIiFg8+3yZRpnMwgFo8YdXhQRsiePzehnusrxVvugNjXzbw==", + "dev": true, + "requires": { + "@emotion/hash": "~0.8.0", + "csstype": "~3.0.5" + }, + "dependencies": { + "csstype": { + "version": "3.0.11", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.0.11.tgz", + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==", + "dev": true + } + } + }, + "csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, + "date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmmirror.com/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.21.0" + } + }, + "date-fns-tz": { + "version": "1.3.8", + "resolved": "https://registry.npmmirror.com/date-fns-tz/-/date-fns-tz-1.3.8.tgz", + "integrity": "sha512-qwNXUFtMHTTU6CFSFjoJ80W8Fzzp24LntbjFFBgL/faqds4e5mo9mftoRLgr3Vi1trISsg4awSpYVsOQCRnapQ==", + "dev": true, + "requires": {} + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "esbuild": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.17.19.tgz", + "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.17.19", + "@esbuild/android-arm64": "0.17.19", + "@esbuild/android-x64": "0.17.19", + "@esbuild/darwin-arm64": "0.17.19", + "@esbuild/darwin-x64": "0.17.19", + "@esbuild/freebsd-arm64": "0.17.19", + "@esbuild/freebsd-x64": "0.17.19", + "@esbuild/linux-arm": "0.17.19", + "@esbuild/linux-arm64": "0.17.19", + "@esbuild/linux-ia32": "0.17.19", + "@esbuild/linux-loong64": "0.17.19", + "@esbuild/linux-mips64el": "0.17.19", + "@esbuild/linux-ppc64": "0.17.19", + "@esbuild/linux-riscv64": "0.17.19", + "@esbuild/linux-s390x": "0.17.19", + "@esbuild/linux-x64": "0.17.19", + "@esbuild/netbsd-x64": "0.17.19", + "@esbuild/openbsd-x64": "0.17.19", + "@esbuild/sunos-x64": "0.17.19", + "@esbuild/win32-arm64": "0.17.19", + "@esbuild/win32-ia32": "0.17.19", + "@esbuild/win32-x64": "0.17.19" + } + }, + "escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true + }, + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, + "evtd": { + "version": "0.2.4", + "resolved": "https://registry.npmmirror.com/evtd/-/evtd-0.2.4.tgz", + "integrity": "sha512-qaeGN5bx63s/AXgQo8gj6fBkxge+OoLddLniox5qtLAEY5HSnuSlISXVPxnSae1dWblvTh4/HoMIB+mbMsvZzw==", + "dev": true + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "requires": { + "is-glob": "^4.0.1" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "highlight.js": { + "version": "11.8.0", + "resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.8.0.tgz", + "integrity": "sha512-MedQhoqVdr0U6SSnWPzfiadUcDHfN/Wzq25AkXiQv9oiOO/sG0S7XkvpFIqWBl9Yq1UYyYOOVORs5UW2XlPyzg==" + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "immutable": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/immutable/-/immutable-4.3.0.tgz", + "integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==" + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-core-module": { + "version": "2.12.1", + "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, + "kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "dev": true + }, + "local-pkg": { + "version": "0.4.3", + "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-0.4.3.tgz", + "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", + "dev": true + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "dev": true + }, + "magic-string": { + "version": "0.30.0", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.0.tgz", + "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==", + "requires": { + "@jridgewell/sourcemap-codec": "^1.4.13" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "mlly": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.3.0.tgz", + "integrity": "sha512-HT5mcgIQKkOrZecOjOX3DJorTikWXwsBfpcr/MGBkhfWcjiqvnaL/9ppxvIUXfjT6xt4DVIAsN9fMUz1ev4bIw==", + "dev": true, + "requires": { + "acorn": "^8.8.2", + "pathe": "^1.1.0", + "pkg-types": "^1.0.3", + "ufo": "^1.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "naive-ui": { + "version": "2.34.4", + "resolved": "https://registry.npmmirror.com/naive-ui/-/naive-ui-2.34.4.tgz", + "integrity": "sha512-aPG8PDfhSzIzn/jSC9y3Jb3Pe2wHJ7F0cFV1EWlbImSrZECeUmoc+fIcOSWbizoztkKfaUAeKwYdMl09MKkj1g==", + "dev": true, + "requires": { + "@css-render/plugin-bem": "^0.15.10", + "@css-render/vue3-ssr": "^0.15.10", + "@types/katex": "^0.14.0", + "@types/lodash": "^4.14.181", + "@types/lodash-es": "^4.17.6", + "async-validator": "^4.0.7", + "css-render": "^0.15.10", + "date-fns": "^2.28.0", + "date-fns-tz": "^1.3.3", + "evtd": "^0.2.4", + "highlight.js": "^11.5.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "seemly": "^0.3.6", + "treemate": "^0.3.11", + "vdirs": "^0.1.8", + "vooks": "^0.2.12", + "vueuc": "^0.4.51" + } + }, + "nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==" + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "pathe": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-1.1.1.tgz", + "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + }, + "pinia": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.1.3.tgz", + "integrity": "sha512-XNA/z/ye4P5rU1pieVmh0g/hSuDO98/a5UC8oSP0DNdvt6YtetJNHTrXwpwsQuflkGT34qKxAEcp7lSxXNjf/A==", + "requires": { + "@vue/devtools-api": "^6.5.0", + "vue-demi": ">=0.14.5" + }, + "dependencies": { + "vue-demi": { + "version": "0.14.5", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.5.tgz", + "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==", + "requires": {} + } + } + }, + "pkg-types": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.0.3.tgz", + "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", + "dev": true, + "requires": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.2.0", + "pathe": "^1.1.0" + } + }, + "postcss": { + "version": "8.4.24", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.24.tgz", + "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==", + "requires": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmmirror.com/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "requires": { + "picomatch": "^2.2.1" + } + }, + "regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true + }, + "resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, + "requires": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rollup": { + "version": "3.24.0", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-3.24.0.tgz", + "integrity": "sha512-OgraHOIg2YpHQTjl0/ymWfFNBEyPucB7lmhXrQUh38qNOegxLapSPFs9sNr0qKR75awW41D93XafoR2QfhBdUQ==", + "dev": true, + "requires": { + "fsevents": "~2.3.2" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "sass": { + "version": "1.63.2", + "resolved": "https://registry.npmmirror.com/sass/-/sass-1.63.2.tgz", + "integrity": "sha512-u56TU0AIFqMtauKl/OJ1AeFsXqRHkgO7nCWmHaDwfxDo9GUMSqBA4NEh6GMuh1CYVM7zuROYtZrHzPc2ixK+ww==", + "requires": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + } + }, + "scule": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/scule/-/scule-1.0.0.tgz", + "integrity": "sha512-4AsO/FrViE/iDNEPaAQlb77tf0csuq27EsVpy6ett584EcRTp6pTDLoGWVxCD77y5iU5FauOvhsI4o1APwPoSQ==", + "dev": true + }, + "seemly": { + "version": "0.3.6", + "resolved": "https://registry.npmmirror.com/seemly/-/seemly-0.3.6.tgz", + "integrity": "sha512-lEV5VB8BUKTo/AfktXJcy+JeXns26ylbMkIUco8CYREsQijuz4mrXres2Q+vMLdwkuLxJdIPQ8IlCIxLYm71Yw==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-literal": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/strip-literal/-/strip-literal-1.0.1.tgz", + "integrity": "sha512-QZTsipNpa2Ppr6v1AmJHESqJ3Uz247MUS0OjrnnZjFAvEoWqxuyFuXn2xLgMtRnijJShAa1HL0gtJyUs7u7n3Q==", + "dev": true, + "requires": { + "acorn": "^8.8.2" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } + }, + "treemate": { + "version": "0.3.11", + "resolved": "https://registry.npmmirror.com/treemate/-/treemate-0.3.11.tgz", + "integrity": "sha512-M8RGFoKtZ8dF+iwJfAJTOH/SM4KluKOKRJpjCMhI8bG3qB74zrFoArKZ62ll0Fr3mqkMJiQOmWYkdYgDeITYQg==", + "dev": true + }, + "ufo": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.1.2.tgz", + "integrity": "sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ==", + "dev": true + }, + "unimport": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/unimport/-/unimport-3.0.7.tgz", + "integrity": "sha512-2dVQUxJEGcrSZ0U4qtwJVODrlfyGcwmIOoHVqbAFFUx7kPoEN5JWr1cZFhLwoAwTmZOvqAm3YIkzv1engIQocg==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^5.0.2", + "escape-string-regexp": "^5.0.0", + "fast-glob": "^3.2.12", + "local-pkg": "^0.4.3", + "magic-string": "^0.30.0", + "mlly": "^1.2.1", + "pathe": "^1.1.0", + "pkg-types": "^1.0.3", + "scule": "^1.0.0", + "strip-literal": "^1.0.1", + "unplugin": "^1.3.1" + } + }, + "unplugin": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-1.3.1.tgz", + "integrity": "sha512-h4uUTIvFBQRxUKS2Wjys6ivoeofGhxzTe2sRWlooyjHXVttcVfV/JiavNd3d4+jty0SVV0dxGw9AkY9MwiaCEw==", + "dev": true, + "requires": { + "acorn": "^8.8.2", + "chokidar": "^3.5.3", + "webpack-sources": "^3.2.3", + "webpack-virtual-modules": "^0.5.0" + } + }, + "unplugin-auto-import": { + "version": "0.16.4", + "resolved": "https://registry.npmmirror.com/unplugin-auto-import/-/unplugin-auto-import-0.16.4.tgz", + "integrity": "sha512-xdgBa9NAS3JG8HjkAZHSbGSMlrjKpaWKXGUzaF6RzEtr980RCl1t0Zsu0skUInNYrEQfqaHc7aGWPv41DLTK/w==", + "dev": true, + "requires": { + "@antfu/utils": "^0.7.2", + "@rollup/pluginutils": "^5.0.2", + "local-pkg": "^0.4.3", + "magic-string": "^0.30.0", + "minimatch": "^9.0.1", + "unimport": "^3.0.7", + "unplugin": "^1.3.1" + } + }, + "unplugin-icons": { + "version": "0.16.3", + "resolved": "https://registry.npmmirror.com/unplugin-icons/-/unplugin-icons-0.16.3.tgz", + "integrity": "sha512-hivVVr6++WHSj6Iz+rjTa14/ALMYT+PFd2sPtTBKlQR3cdzui1VwM72TzSu94NkDm/KVncvOIiBwoHwUPeL9bg==", + "dev": true, + "requires": { + "@antfu/install-pkg": "^0.1.1", + "@antfu/utils": "^0.7.4", + "@iconify/utils": "^2.1.6", + "debug": "^4.3.4", + "kolorist": "^1.8.0", + "local-pkg": "^0.4.3", + "unplugin": "^1.3.1" + } + }, + "unplugin-vue-components": { + "version": "0.25.1", + "resolved": "https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-0.25.1.tgz", + "integrity": "sha512-kzS2ZHVMaGU2XEO2keYQcMjNZkanDSGDdY96uQT9EPe+wqSZwwgbFfKVJ5ti0+8rGAcKHColwKUvctBhq2LJ3A==", + "dev": true, + "requires": { + "@antfu/utils": "^0.7.4", + "@rollup/pluginutils": "^5.0.2", + "chokidar": "^3.5.3", + "debug": "^4.3.4", + "fast-glob": "^3.2.12", + "local-pkg": "^0.4.3", + "magic-string": "^0.30.0", + "minimatch": "^9.0.1", + "resolve": "^1.22.2", + "unplugin": "^1.3.1" + } + }, + "vdirs": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/vdirs/-/vdirs-0.1.8.tgz", + "integrity": "sha512-H9V1zGRLQZg9b+GdMk8MXDN2Lva0zx72MPahDKc30v+DtwKjfyOSXWRIX4t2mhDubM1H09gPhWeth/BJWPHGUw==", + "dev": true, + "requires": { + "evtd": "^0.2.2" + } + }, + "vite": { + "version": "4.3.9", + "resolved": "https://registry.npmmirror.com/vite/-/vite-4.3.9.tgz", + "integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==", + "dev": true, + "requires": { + "esbuild": "^0.17.5", + "fsevents": "~2.3.2", + "postcss": "^8.4.23", + "rollup": "^3.21.0" + } + }, + "vooks": { + "version": "0.2.12", + "resolved": "https://registry.npmmirror.com/vooks/-/vooks-0.2.12.tgz", + "integrity": "sha512-iox0I3RZzxtKlcgYaStQYKEzWWGAduMmq+jS7OrNdQo1FgGfPMubGL3uGHOU9n97NIvfFDBGnpSvkWyb/NSn/Q==", + "dev": true, + "requires": { + "evtd": "^0.2.2" + } + }, + "vue": { + "version": "3.3.4", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.3.4.tgz", + "integrity": "sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==", + "requires": { + "@vue/compiler-dom": "3.3.4", + "@vue/compiler-sfc": "3.3.4", + "@vue/runtime-dom": "3.3.4", + "@vue/server-renderer": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "vue-i18n": { + "version": "9.2.2", + "resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-9.2.2.tgz", + "integrity": "sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ==", + "requires": { + "@intlify/core-base": "9.2.2", + "@intlify/shared": "9.2.2", + "@intlify/vue-devtools": "9.2.2", + "@vue/devtools-api": "^6.2.1" + } + }, + "vueuc": { + "version": "0.4.51", + "resolved": "https://registry.npmmirror.com/vueuc/-/vueuc-0.4.51.tgz", + "integrity": "sha512-pLiMChM4f+W8czlIClGvGBYo656lc2Y0/mXFSCydcSmnCR1izlKPGMgiYBGjbY9FDkFG8a2HEVz7t0DNzBWbDw==", + "dev": true, + "requires": { + "@css-render/vue3-ssr": "^0.15.10", + "@juggle/resize-observer": "^3.3.1", + "css-render": "^0.15.10", + "evtd": "^0.2.4", + "seemly": "^0.3.6", + "vdirs": "^0.1.4", + "vooks": "^0.2.4" + } + }, + "webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true + }, + "webpack-virtual-modules": { + "version": "0.5.0", + "resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.5.0.tgz", + "integrity": "sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..acf4120 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,28 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "highlight.js": "^11.8.0", + "lodash": "^4.17.21", + "pinia": "^2.1.3", + "sass": "^1.62.1", + "vue": "^3.2.37", + "vue-i18n": "^9.2.2" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^4.2.3", + "naive-ui": "^2.34.4", + "prettier": "^2.8.8", + "unplugin-auto-import": "^0.16.4", + "unplugin-icons": "^0.16.1", + "unplugin-vue-components": "^0.25.0", + "vite": "^4.3.0" + } +} diff --git a/frontend/package.json.md5 b/frontend/package.json.md5 new file mode 100755 index 0000000..eb075a8 --- /dev/null +++ b/frontend/package.json.md5 @@ -0,0 +1 @@ +da66eb9d13a7ace25f7f75d36c2510f9 \ No newline at end of file diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..42eb5d1 --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,160 @@ + + + + + diff --git a/frontend/src/assets/fonts/OFL.txt b/frontend/src/assets/fonts/OFL.txt new file mode 100644 index 0000000..9cac04c --- /dev/null +++ b/frontend/src/assets/fonts/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2016 The Nunito Project Authors (contact@sansoxygen.com), + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 b/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 new file mode 100644 index 0000000..2f9cc59 Binary files /dev/null and b/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 differ diff --git a/frontend/src/assets/images/logo-universal.png b/frontend/src/assets/images/logo-universal.png new file mode 100644 index 0000000..e9913c1 Binary files /dev/null and b/frontend/src/assets/images/logo-universal.png differ diff --git a/frontend/src/components/ConnectionsTree.vue b/frontend/src/components/ConnectionsTree.vue new file mode 100644 index 0000000..6c79f2d --- /dev/null +++ b/frontend/src/components/ConnectionsTree.vue @@ -0,0 +1,450 @@ + + + + + diff --git a/frontend/src/components/ContentPane.vue b/frontend/src/components/ContentPane.vue new file mode 100644 index 0000000..624800e --- /dev/null +++ b/frontend/src/components/ContentPane.vue @@ -0,0 +1,111 @@ + + + + + diff --git a/frontend/src/components/ContentTab.vue b/frontend/src/components/ContentTab.vue new file mode 100644 index 0000000..762fe1d --- /dev/null +++ b/frontend/src/components/ContentTab.vue @@ -0,0 +1,139 @@ + + + + + diff --git a/frontend/src/components/ContentToolbar.vue b/frontend/src/components/ContentToolbar.vue new file mode 100644 index 0000000..f895994 --- /dev/null +++ b/frontend/src/components/ContentToolbar.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/frontend/src/components/EditableTableColumn.vue b/frontend/src/components/EditableTableColumn.vue new file mode 100644 index 0000000..6ffd3f4 --- /dev/null +++ b/frontend/src/components/EditableTableColumn.vue @@ -0,0 +1,39 @@ + + + + + diff --git a/frontend/src/components/EditableTableRow.vue b/frontend/src/components/EditableTableRow.vue new file mode 100644 index 0000000..098b1dd --- /dev/null +++ b/frontend/src/components/EditableTableRow.vue @@ -0,0 +1,27 @@ + + + diff --git a/frontend/src/components/IconButton.vue b/frontend/src/components/IconButton.vue new file mode 100644 index 0000000..daef80a --- /dev/null +++ b/frontend/src/components/IconButton.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/frontend/src/components/NavigationPane.vue b/frontend/src/components/NavigationPane.vue new file mode 100644 index 0000000..b5fb65c --- /dev/null +++ b/frontend/src/components/NavigationPane.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/frontend/src/components/RedisTypeTag.vue b/frontend/src/components/RedisTypeTag.vue new file mode 100644 index 0000000..0beafcd --- /dev/null +++ b/frontend/src/components/RedisTypeTag.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/frontend/src/components/Toolbar.vue b/frontend/src/components/Toolbar.vue new file mode 100644 index 0000000..d2e1b51 --- /dev/null +++ b/frontend/src/components/Toolbar.vue @@ -0,0 +1,14 @@ + + + + + diff --git a/frontend/src/components/content_value/ContentValueHash.vue b/frontend/src/components/content_value/ContentValueHash.vue new file mode 100644 index 0000000..47908b3 --- /dev/null +++ b/frontend/src/components/content_value/ContentValueHash.vue @@ -0,0 +1,288 @@ + + + + + diff --git a/frontend/src/components/content_value/ContentValueList.vue b/frontend/src/components/content_value/ContentValueList.vue new file mode 100644 index 0000000..2e8f234 --- /dev/null +++ b/frontend/src/components/content_value/ContentValueList.vue @@ -0,0 +1,212 @@ + + + + + diff --git a/frontend/src/components/content_value/ContentValueSet.vue b/frontend/src/components/content_value/ContentValueSet.vue new file mode 100644 index 0000000..a67c971 --- /dev/null +++ b/frontend/src/components/content_value/ContentValueSet.vue @@ -0,0 +1,208 @@ + + + + + diff --git a/frontend/src/components/content_value/ContentValueString.vue b/frontend/src/components/content_value/ContentValueString.vue new file mode 100644 index 0000000..ced2f57 --- /dev/null +++ b/frontend/src/components/content_value/ContentValueString.vue @@ -0,0 +1,219 @@ + + + + + diff --git a/frontend/src/components/content_value/ContentValueZSet.vue b/frontend/src/components/content_value/ContentValueZSet.vue new file mode 100644 index 0000000..1fba3cc --- /dev/null +++ b/frontend/src/components/content_value/ContentValueZSet.vue @@ -0,0 +1,238 @@ + + + + + diff --git a/frontend/src/components/dialogs/AddFieldsDialog.vue b/frontend/src/components/dialogs/AddFieldsDialog.vue new file mode 100644 index 0000000..964b706 --- /dev/null +++ b/frontend/src/components/dialogs/AddFieldsDialog.vue @@ -0,0 +1,203 @@ + + + + + diff --git a/frontend/src/components/dialogs/NewConnDialog.vue b/frontend/src/components/dialogs/NewConnDialog.vue new file mode 100644 index 0000000..50af4fc --- /dev/null +++ b/frontend/src/components/dialogs/NewConnDialog.vue @@ -0,0 +1,252 @@ + + + + + diff --git a/frontend/src/components/dialogs/NewKeyDialog.vue b/frontend/src/components/dialogs/NewKeyDialog.vue new file mode 100644 index 0000000..6f5b0b8 --- /dev/null +++ b/frontend/src/components/dialogs/NewKeyDialog.vue @@ -0,0 +1,147 @@ + + + + + diff --git a/frontend/src/components/dialogs/PreferencesDialog.vue b/frontend/src/components/dialogs/PreferencesDialog.vue new file mode 100644 index 0000000..e80d4d9 --- /dev/null +++ b/frontend/src/components/dialogs/PreferencesDialog.vue @@ -0,0 +1,189 @@ + + + + + diff --git a/frontend/src/components/dialogs/RenameKeyDialog.vue b/frontend/src/components/dialogs/RenameKeyDialog.vue new file mode 100644 index 0000000..7972088 --- /dev/null +++ b/frontend/src/components/dialogs/RenameKeyDialog.vue @@ -0,0 +1,84 @@ + + + + + diff --git a/frontend/src/components/dialogs/SetTtlDialog.vue b/frontend/src/components/dialogs/SetTtlDialog.vue new file mode 100644 index 0000000..b5018aa --- /dev/null +++ b/frontend/src/components/dialogs/SetTtlDialog.vue @@ -0,0 +1,115 @@ + + + + + diff --git a/frontend/src/components/icons/Add.vue b/frontend/src/components/icons/Add.vue new file mode 100644 index 0000000..d602ce9 --- /dev/null +++ b/frontend/src/components/icons/Add.vue @@ -0,0 +1,36 @@ + + + + + diff --git a/frontend/src/components/icons/AddGroup.vue b/frontend/src/components/icons/AddGroup.vue new file mode 100644 index 0000000..bc508e1 --- /dev/null +++ b/frontend/src/components/icons/AddGroup.vue @@ -0,0 +1,24 @@ + + + + + diff --git a/frontend/src/components/icons/AddLink.vue b/frontend/src/components/icons/AddLink.vue new file mode 100644 index 0000000..2484489 --- /dev/null +++ b/frontend/src/components/icons/AddLink.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/frontend/src/components/icons/Close.vue b/frontend/src/components/icons/Close.vue new file mode 100644 index 0000000..bdb136d --- /dev/null +++ b/frontend/src/components/icons/Close.vue @@ -0,0 +1,36 @@ + + + + + diff --git a/frontend/src/components/icons/Config.vue b/frontend/src/components/icons/Config.vue new file mode 100644 index 0000000..1dbd478 --- /dev/null +++ b/frontend/src/components/icons/Config.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/frontend/src/components/icons/Connect.vue b/frontend/src/components/icons/Connect.vue new file mode 100644 index 0000000..9c14991 --- /dev/null +++ b/frontend/src/components/icons/Connect.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/frontend/src/components/icons/Copy.vue b/frontend/src/components/icons/Copy.vue new file mode 100644 index 0000000..fd61445 --- /dev/null +++ b/frontend/src/components/icons/Copy.vue @@ -0,0 +1,39 @@ + + + + + diff --git a/frontend/src/components/icons/CopyLink.vue b/frontend/src/components/icons/CopyLink.vue new file mode 100644 index 0000000..66e04d9 --- /dev/null +++ b/frontend/src/components/icons/CopyLink.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/frontend/src/components/icons/Delete.vue b/frontend/src/components/icons/Delete.vue new file mode 100644 index 0000000..1ad1bdf --- /dev/null +++ b/frontend/src/components/icons/Delete.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/frontend/src/components/icons/Edit.vue b/frontend/src/components/icons/Edit.vue new file mode 100644 index 0000000..fa30d1c --- /dev/null +++ b/frontend/src/components/icons/Edit.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/frontend/src/components/icons/Filter.vue b/frontend/src/components/icons/Filter.vue new file mode 100644 index 0000000..b7e4997 --- /dev/null +++ b/frontend/src/components/icons/Filter.vue @@ -0,0 +1,13 @@ + + + diff --git a/frontend/src/components/icons/Key.vue b/frontend/src/components/icons/Key.vue new file mode 100644 index 0000000..959bd9f --- /dev/null +++ b/frontend/src/components/icons/Key.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/frontend/src/components/icons/Layer.vue b/frontend/src/components/icons/Layer.vue new file mode 100644 index 0000000..9b1bd33 --- /dev/null +++ b/frontend/src/components/icons/Layer.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/frontend/src/components/icons/Refresh.vue b/frontend/src/components/icons/Refresh.vue new file mode 100644 index 0000000..5d9c322 --- /dev/null +++ b/frontend/src/components/icons/Refresh.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/frontend/src/components/icons/Save.vue b/frontend/src/components/icons/Save.vue new file mode 100644 index 0000000..f66cc6e --- /dev/null +++ b/frontend/src/components/icons/Save.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/frontend/src/components/icons/Search.vue b/frontend/src/components/icons/Search.vue new file mode 100644 index 0000000..8091e7f --- /dev/null +++ b/frontend/src/components/icons/Search.vue @@ -0,0 +1,36 @@ + + + + + diff --git a/frontend/src/components/icons/Sort.vue b/frontend/src/components/icons/Sort.vue new file mode 100644 index 0000000..90f46a5 --- /dev/null +++ b/frontend/src/components/icons/Sort.vue @@ -0,0 +1,43 @@ + + + + + diff --git a/frontend/src/components/icons/Timer.vue b/frontend/src/components/icons/Timer.vue new file mode 100644 index 0000000..ed665ab --- /dev/null +++ b/frontend/src/components/icons/Timer.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/frontend/src/components/icons/ToggleDb.vue b/frontend/src/components/icons/ToggleDb.vue new file mode 100644 index 0000000..0dfe48d --- /dev/null +++ b/frontend/src/components/icons/ToggleDb.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/frontend/src/components/icons/ToggleFolder.vue b/frontend/src/components/icons/ToggleFolder.vue new file mode 100644 index 0000000..49bbd0b --- /dev/null +++ b/frontend/src/components/icons/ToggleFolder.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/frontend/src/components/icons/ToggleServer.vue b/frontend/src/components/icons/ToggleServer.vue new file mode 100644 index 0000000..9c6aa00 --- /dev/null +++ b/frontend/src/components/icons/ToggleServer.vue @@ -0,0 +1,125 @@ + + + + + diff --git a/frontend/src/components/icons/Unlink.vue b/frontend/src/components/icons/Unlink.vue new file mode 100644 index 0000000..37de6f5 --- /dev/null +++ b/frontend/src/components/icons/Unlink.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/frontend/src/components/new_value/AddHashValue.vue b/frontend/src/components/new_value/AddHashValue.vue new file mode 100644 index 0000000..ac7134a --- /dev/null +++ b/frontend/src/components/new_value/AddHashValue.vue @@ -0,0 +1,69 @@ + + + + + diff --git a/frontend/src/components/new_value/AddListValue.vue b/frontend/src/components/new_value/AddListValue.vue new file mode 100644 index 0000000..ee81741 --- /dev/null +++ b/frontend/src/components/new_value/AddListValue.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/frontend/src/components/new_value/AddZSetValue.vue b/frontend/src/components/new_value/AddZSetValue.vue new file mode 100644 index 0000000..381e08c --- /dev/null +++ b/frontend/src/components/new_value/AddZSetValue.vue @@ -0,0 +1,77 @@ + + + + + diff --git a/frontend/src/components/new_value/NewHashValue.vue b/frontend/src/components/new_value/NewHashValue.vue new file mode 100644 index 0000000..a21dd64 --- /dev/null +++ b/frontend/src/components/new_value/NewHashValue.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/frontend/src/components/new_value/NewListValue.vue b/frontend/src/components/new_value/NewListValue.vue new file mode 100644 index 0000000..d0657c7 --- /dev/null +++ b/frontend/src/components/new_value/NewListValue.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/frontend/src/components/new_value/NewSetValue.vue b/frontend/src/components/new_value/NewSetValue.vue new file mode 100644 index 0000000..4a32a77 --- /dev/null +++ b/frontend/src/components/new_value/NewSetValue.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/frontend/src/components/new_value/NewStringValue.vue b/frontend/src/components/new_value/NewStringValue.vue new file mode 100644 index 0000000..655fa8e --- /dev/null +++ b/frontend/src/components/new_value/NewStringValue.vue @@ -0,0 +1,21 @@ + + + + + diff --git a/frontend/src/components/new_value/NewZSetValue.vue b/frontend/src/components/new_value/NewZSetValue.vue new file mode 100644 index 0000000..7280d22 --- /dev/null +++ b/frontend/src/components/new_value/NewZSetValue.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/frontend/src/consts/connection_type.js b/frontend/src/consts/connection_type.js new file mode 100644 index 0000000..6385a7b --- /dev/null +++ b/frontend/src/consts/connection_type.js @@ -0,0 +1,7 @@ +export const ConnectionType = { + Group: 0, + Server: 1, + RedisDB: 2, + RedisKey: 3, + RedisValue: 4, +} diff --git a/frontend/src/consts/support_redis_type.js b/frontend/src/consts/support_redis_type.js new file mode 100644 index 0000000..b48439e --- /dev/null +++ b/frontend/src/consts/support_redis_type.js @@ -0,0 +1,13 @@ +export const types = { + STRING: 'STRING', + HASH: 'HASH', + LIST: 'LIST', + SET: 'SET', + ZSET: 'ZSET', +} + +// export const typesName = Object.fromEntries(Object.entries(types).map(([key, value]) => [key, value.name])) + +export const validType = (t) => { + return types.hasOwnProperty(t) +} diff --git a/frontend/src/consts/tree_context_menu.js b/frontend/src/consts/tree_context_menu.js new file mode 100644 index 0000000..8d4a748 --- /dev/null +++ b/frontend/src/consts/tree_context_menu.js @@ -0,0 +1,9 @@ +import { ConnectionType } from './connection_type.js' + +const i18n = useI18n() +export const contextMenuKey = { + [ConnectionType.Server]: { + key: '', + label: '', + }, +} diff --git a/frontend/src/consts/value_view_type.js b/frontend/src/consts/value_view_type.js new file mode 100644 index 0000000..ec22699 --- /dev/null +++ b/frontend/src/consts/value_view_type.js @@ -0,0 +1,6 @@ +export const types = { + PLAIN_TEXT: 'Plain Text', + JSON: 'JSON', + BASE64_TO_TEXT: 'Base64 To Text', + BASE64_TO_JSON: 'Base64 To JSON', +} diff --git a/frontend/src/langs/en.json b/frontend/src/langs/en.json new file mode 100644 index 0000000..4a0e171 --- /dev/null +++ b/frontend/src/langs/en.json @@ -0,0 +1,107 @@ +{ + "lang_name": "English", + "confirm": "Confirm", + "cancel": "Cancel", + "success": "Success", + "warning": "Warning", + "save": "Save", + "new_conn": "Add New Connection", + "new_group": "Add New Group", + "sort_conn": "Resort Connections", + "reload_key": "Reload Current Key", + "ttl": "TTL", + "forever": "Forever", + "rename_key": "Rename Key", + "delete_key": "Delete Key", + "delete_key_tip": "\"{key}\" will be deleted", + "delete_key_succ": "\"{key}\" has been deleted", + "copy_value": "Copy Value", + "edit_value": "Edit Value", + "save_update": "Save Update", + "cancel_update": "Cancel Update", + "add_row": "Add Row", + "edit_row": "Edit Row", + "delete_row": "Delete Row", + "search": "Search", + "view_as": "View As", + "reload": "Reload", + "open_connection": "Open Connection", + "open_db": "Expand Database", + "filter_key": "Filter Keys", + "disconnect": "Disconnect", + "dup_conn": "Duplicate Connection", + "remove_conn": "Delete Connection", + "config_conn": "Edit Connection Config", + "config_conn_group": "Edit Connection Group", + "remove_conn_group": "Delete Connection Group", + "copy_path": "Copy Path", + "remove_path": "Remove Path", + "copy_key": "Copy Key Name", + "remove_key": "Remove Key", + "new_conn_title": "New Connection", + "general": "General", + "advanced": "Advanced", + "editor": "Editor", + "conn_name": "Name", + "conn_addr": "Address", + "conn_usr": "Username", + "conn_pwd": "Password", + "conn_name_tip": "Connection name", + "conn_addr_tip": "Redis server host", + "conn_usr_tip": "(Optional) Redis server username", + "conn_pwd_tip": "(Optional) Redis server authentication password (Redis > 6.0)", + "conn_test": "Test Connection", + "conn_test_succ": "Successful connection to redis-server", + "conn_test_fail": "Fail Connection", + "conn_advn_filter": "Default Filter", + "conn_advn_filter_tip": "Pattern which defines loaded keys from redis server", + "conn_advn_separator": "Key Separator", + "conn_advn_separator_tip": "Separator used for key path item", + "conn_advn_conn_timeout": "Connection Timeout", + "conn_advn_exec_timeout": "Execution Timeout", + "conn_advn_mark_color": "Mark Color", + "new_conn_succ": "Create new connection success!", + "second": "Second(s)", + "new_key_name": "New Key Name", + "new_key": "Add New Key", + "new_field": "Add New Field", + "overwrite_field": "Overwrite Duplicated Field", + "ignore_field": "Ignore Duplicated Field", + "new_item": "Add New Item", + "insert_type": "Insert", + "append_item": "Append", + "prepend_item": "Prepend", + "enter_key": "Enter Key", + "enter_value": "Enter Value", + "enter_field": "Enter Field Name", + "enter_elem": "Enter Element", + "enter_member": "Enter Member", + "enter_score": "Enter Score", + "key": "Key", + "value": "Value", + "field": "Field", + "action": "Action", + "type": "Type", + "score": "Score", + "order_no": "Order", + "preferences": "Preferences", + "language": "Language", + "font": "Font", + "font_size": "Font Size", + "restore_defaults": "Restore Defaults", + "proxy": "Proxy", + "use_system_proxy": "Use system proxy", + "use_system_proxy_http": "Use system proxy only for HTTP(S) request", + "update": "Update", + "auto_check_update": "Automatically check for updates", + "set_ttl": "Set Key TTL", + "persist_key": "Persist Key", + "copy_value_succ": "Value Copied !", + "save_value_succ": "Value Saved !", + "handle_succ": "Handle Success !", + "field_required": "This item should not be blank", + "spec_field_required": "\"{key}\" should not be blank", + "no_connections": "No Connection", + "empty_tab_content": "Select the key from left list to see the details of the key.", + "reload_when_succ": "Reload immediately after success" +} diff --git a/frontend/src/langs/index.js b/frontend/src/langs/index.js new file mode 100644 index 0000000..42ef041 --- /dev/null +++ b/frontend/src/langs/index.js @@ -0,0 +1,7 @@ +import en from './en' +import zh from './zh-cn' + +export const lang = { + en, + zh, +} diff --git a/frontend/src/langs/zh-cn.json b/frontend/src/langs/zh-cn.json new file mode 100644 index 0000000..9426a60 --- /dev/null +++ b/frontend/src/langs/zh-cn.json @@ -0,0 +1,110 @@ +{ + "lang_name": "简体中文", + "confirm": "确认", + "cancel": "取消", + "success": "成功", + "warning": "警告", + "save": "保存", + "new_conn": "添加新连接", + "new_group": "添加新分组", + "sort_conn": "调整连接顺序", + "reload_key": "重新载入此键内容", + "ttl": "TTL", + "forever": "永久", + "rename_key": "重命名键", + "delete_key": "删除键", + "delete_key_tip": "{key} 将会被删除", + "delete_key_succ": "{key} 已被删除", + "copy_value": "复制值", + "edit_value": "修改值", + "save_update": "保存修改", + "cancel_update": "取消修改", + "add_row": "插入行", + "edit_row": "编辑行", + "delete_row": "删除行", + "search": "搜索", + "filter_field": "筛选字段", + "filter_value": "筛选值", + "view_as": "查看方式", + "reload": "重新载入", + "open_connection": "打开连接", + "open_db": "展开数据库", + "filter_key": "过滤键", + "disconnect": "断开连接", + "dup_conn": "复制连接", + "remove_conn": "删除连接", + "config_conn": "编辑连接配置", + "config_conn_group": "编辑连接分组", + "remove_conn_group": "删除连接分组", + "copy_path": "复制路径", + "remove_path": "删除路径", + "copy_key": "复制键名", + "remove_key": "删除键", + "new_conn_title": "新建连接", + "general": "常规配置", + "advanced": "高级配置", + "editor": "编辑器", + "conn_name": "连接名", + "conn_addr": "连接地址", + "conn_usr": "用户名", + "conn_pwd": "密码", + "conn_name_tip": "连接名", + "conn_addr_tip": "Redis服务地址", + "conn_usr_tip": "(可选)Redis服务授权用户名", + "conn_pwd_tip": "(可选)Redis服务授权密码 (Redis > 6.0)", + "conn_test": "测试连接", + "conn_test_succ": "成功连接到Redis服务器", + "conn_test_fail": "连接失败", + "conn_advn_filter": "默认过滤", + "conn_advn_filter_tip": "需要加载的键名表达式", + "conn_advn_separator": "键分隔符", + "conn_advn_separator_tip": "键名路径分隔符", + "conn_advn_conn_timeout": "连接超时", + "conn_advn_exec_timeout": "执行超时", + "conn_advn_mark_color": "标记颜色", + "new_conn_succ": "新建连接成功", + "second": "秒", + "new_key_name": "新键名", + "new_key": "添加新键", + "new_field": "添加新字段", + "overwrite_field": "覆盖同名字段", + "ignore_field": "忽略同名字段", + "new_item": "添加新元素", + "insert_type": "插入类型", + "append_item": "尾部追加", + "prepend_item": "插入头部", + "enter_key": "输入键名", + "enter_value": "输入值", + "enter_field": "输入字段名", + "enter_elem": "输入新元素", + "enter_member": "输入成员", + "enter_score": "输入分值", + "element": "元素", + "key": "键", + "value": "值", + "field": "字段", + "action": "操作", + "type": "类型", + "score": "分值", + "order_no": "序号", + "preferences": "偏好设置", + "language": "语言", + "font": "字体", + "font_size": "字体尺寸", + "restore_defaults": "重置为默认", + "proxy": "代理", + "use_system_proxy": "使用系统代理", + "use_system_proxy_http": "仅在HTTP请求时使用系统代理", + "update": "更新", + "auto_check_update": "自动检查更新", + "set_ttl": "设置键存活时间", + "persist_key": "持久化键", + "copy_succ": "已复制到剪切板", + "save_value_succ": "已保存值", + "handle_succ": "操作成功", + "field_required": "此项不能为空", + "spec_field_required": "{key} 不能为空", + "no_connections": "空空如也", + "empty_tab_content": "可以从左边选择键来查看键的详细内容", + "reload_when_succ": "操作成功后立即重新加载" +} diff --git a/frontend/src/main.js b/frontend/src/main.js new file mode 100644 index 0000000..92f984f --- /dev/null +++ b/frontend/src/main.js @@ -0,0 +1,21 @@ +import { createPinia } from 'pinia' +import { createApp } from 'vue' +import { createI18n } from 'vue-i18n' +import App from './App.vue' +import { lang } from './langs' +import './style.scss' + +const app = createApp(App) +app.use( + createI18n({ + locale: 'en', + fallbackLocale: 'en', + globalInjection: true, + legacy: false, + messages: { + ...lang, + }, + }) +) +app.use(createPinia()) +app.mount('#app') diff --git a/frontend/src/stores/connection.js b/frontend/src/stores/connection.js new file mode 100644 index 0000000..80fa0c0 --- /dev/null +++ b/frontend/src/stores/connection.js @@ -0,0 +1,904 @@ +import { get, isEmpty, last, remove, size, sortedIndexBy, split } from 'lodash' +import { defineStore } from 'pinia' +import { + AddHashField, + AddListItem, + AddZSetValue, + CloseConnection, + GetKeyValue, + ListConnection, + OpenConnection, + OpenDatabase, + RemoveKey, + RenameKey, + SetHashValue, + SetKeyTTL, + SetKeyValue, + SetListItem, + SetSetItem, + UpdateSetItem, + UpdateZSetValue, +} from '../../wailsjs/go/services/connectionService.js' +import { ConnectionType } from '../consts/connection_type.js' +import useTabStore from './tab.js' + +const separator = ':' + +const useConnectionStore = defineStore('connection', { + /** + * @typedef {Object} ConnectionItem + * @property {string} key + * @property {string} label + * @property {string} name - server name, type != ConnectionType.Group only + * @property {number} type + * @property {number} [db] - database index, type == ConnectionType.RedisDB only + * @property {number} keys + * @property {boolean} [connected] - redis server is connected, type == ConnectionType.Server only + * @property {boolean} [opened] - redis db is opened, type == ConnectionType.RedisDB only + * @property {boolean} [expanded] - current node is expanded + */ + + /** + * + * @returns {{connections: ConnectionItem[]}} + */ + state: () => ({ + connections: [], // all connections list + }), + getters: {}, + actions: { + /** + * Load all store connections struct from local profile + * @returns {Promise} + */ + async initConnection() { + if (!isEmpty(this.connections)) { + return + } + const { data = [{ groupName: '', connections: [] }] } = await ListConnection() + for (let i = 0; i < data.length; i++) { + const group = data[i] + // Top level group + if (isEmpty(group.groupName)) { + for (let j = 0; j < group.connections.length; j++) { + const item = group.connections[j] + this.connections.push({ + key: item.name, + label: item.name, + name: item.name, + type: ConnectionType.Server, + // isLeaf: false, + }) + } + } else { + // Custom group + const children = [] + for (let j = 0; j < group.connections.length; j++) { + const item = group.connections[j] + const value = group.groupName + '/' + item.name + children.push({ + key: value, + label: item.name, + name: item.name, + type: ConnectionType.Server, + children: j === group.connections.length - 1 ? undefined : [], + // isLeaf: false, + }) + } + this.connections.push({ + key: group.groupName, + label: group.groupName, + type: ConnectionType.Group, + children, + }) + } + } + console.debug(JSON.stringify(this.connections)) + }, + + /** + * Open connection + * @param {string} connName + * @returns {Promise} + */ + async openConnection(connName) { + const { data, success, msg } = await OpenConnection(connName) + if (!success) { + throw new Error(msg) + } + // append to db node to current connection + const connNode = this.getConnection(connName) + if (connNode == null) { + throw new Error('no such connection') + } + const { db } = data + if (isEmpty(db)) { + throw new Error('no db loaded') + } + const children = [] + for (let i = 0; i < db.length; i++) { + children.push({ + key: `${connName}/${db[i].name}`, + label: db[i].name, + name: connName, + keys: db[i].keys, + db: i, + type: ConnectionType.RedisDB, + // isLeaf: false, + }) + } + connNode.children = children + connNode.connected = true + }, + + /** + * Close connection + * @param {string} connName + * @returns {Promise} + */ + async closeConnection(connName) { + const { success, msg } = await CloseConnection(connName) + if (!success) { + // throw new Error(msg) + return false + } + + // successful close connection, remove all children + const connNode = this.getConnection(connName) + if (connNode == null) { + // throw new Error('no such connection') + return false + } + connNode.children = undefined + connNode.isLeaf = undefined + connNode.connected = false + connNode.expanded = false + return true + }, + + /** + * Get Connection by path name + * @param {string} connName + * @returns + */ + getConnection(connName) { + const conn = this.connections + for (let i = 0; i < conn.length; i++) { + if (conn[i].type === ConnectionType.Server && conn[i].key === connName) { + return conn[i] + } else if (conn[i].type === ConnectionType.Group) { + const children = conn[i].children + for (let j = 0; j < children.length; j++) { + if (children[j].type === ConnectionType.Server && conn[i].key === connName) { + return children[j] + } + } + } + } + return null + }, + + /** + * Open database and load all keys + * @param connName + * @param db + * @returns {Promise} + */ + async openDatabase(connName, db) { + const { data, success, msg } = await OpenDatabase(connName, db) + if (!success) { + throw new Error(msg) + } + const { keys = [] } = data + if (isEmpty(keys)) { + const connNode = this.getConnection(connName) + const { children = [] } = connNode + children[db].children = [] + children[db].opened = true + return + } + + // insert child to children list by order + const sortedInsertChild = (childrenList, item) => { + const insertIdx = sortedIndexBy(childrenList, item, 'key') + childrenList.splice(insertIdx, 0, item) + // childrenList.push(item) + } + // update all node item's children num + const updateChildrenNum = (node) => { + let count = 0 + const totalChildren = size(node.children) + if (totalChildren > 0) { + for (const elem of node.children) { + updateChildrenNum(elem) + count += elem.keys + } + } else { + count += 1 + } + node.keys = count + // node.children = sortBy(node.children, 'label') + } + + const keyStruct = [] + const mark = {} + for (const key in keys) { + const keyPart = split(key, separator) + // const prefixLen = size(keyPart) - 1 + const len = size(keyPart) + let handlePath = '' + let ks = keyStruct + for (let i = 0; i < len; i++) { + handlePath += keyPart[i] + if (i !== len - 1) { + // layer + const treeKey = `${handlePath}@${ConnectionType.RedisKey}` + if (!mark.hasOwnProperty(treeKey)) { + mark[treeKey] = { + key: `${connName}/db${db}/${treeKey}`, + label: keyPart[i], + name: connName, + db, + keys: 0, + redisKey: handlePath, + type: ConnectionType.RedisKey, + children: [], + } + sortedInsertChild(ks, mark[treeKey]) + } + ks = mark[treeKey].children + handlePath += separator + } else { + // key + const treeKey = `${handlePath}@${ConnectionType.RedisValue}` + mark[treeKey] = { + key: `${connName}/db${db}/${treeKey}`, + label: keyPart[i], + name: connName, + db, + keys: 0, + redisKey: handlePath, + type: ConnectionType.RedisValue, + } + sortedInsertChild(ks, mark[treeKey]) + } + } + } + + // append db node to current connection's children + const connNode = this.getConnection(connName) + const { children = [] } = connNode + children[db].children = keyStruct + children[db].opened = true + updateChildrenNum(children[db]) + }, + + /** + * select node + * @param key + * @param name + * @param db + * @param type + * @param redisKey + */ + select({ key, name, db, type, redisKey }) { + if (type === ConnectionType.RedisValue) { + console.log(`[click]key:${key} db: ${db} redis key: ${redisKey}`) + + // async get value for key + this.loadKeyValue(name, db, redisKey).then(() => {}) + } + }, + + /** + * load redis key + * @param server + * @param db + * @param key + */ + async loadKeyValue(server, db, key) { + try { + const { data, success, msg } = await GetKeyValue(server, db, key) + if (success) { + const { type, ttl, value } = data + const tab = useTabStore() + tab.upsertTab({ + server, + db, + type, + ttl, + key, + value, + }) + } else { + console.warn('TODO: handle get key fail') + } + } finally { + } + }, + + /** + * + * @param {string} connName + * @param {number} db + * @param {string} key + * @private + */ + _addKey(connName, db, key) { + const connNode = this.getConnection(connName) + const { children: dbs = [] } = connNode + const dbDetail = get(dbs, db, {}) + + if (dbDetail == null) { + return + } + + const descendantChain = [dbDetail] + + const keyPart = split(key, separator) + let redisKey = '' + const keyLen = size(keyPart) + let added = false + for (let i = 0; i < keyLen; i++) { + redisKey += keyPart[i] + + const node = last(descendantChain) + const nodeList = get(node, 'children', []) + const len = size(nodeList) + const isLastKeyPart = i === keyLen - 1 + for (let j = 0; j < len + 1; j++) { + const treeKey = get(nodeList[j], 'key') + const isLast = j >= len - 1 + const currentKey = `${connName}/db${db}/${redisKey}@${ + isLastKeyPart ? ConnectionType.RedisValue : ConnectionType.RedisKey + }` + if (treeKey > currentKey || isLast) { + // out of search range, add new item + if (isLastKeyPart) { + // key not exists, add new one + const item = { + key: currentKey, + label: keyPart[i], + name: connName, + db, + keys: 1, + redisKey, + type: ConnectionType.RedisValue, + } + if (isLast) { + nodeList.push(item) + } else { + nodeList.splice(j, 0, item) + } + added = true + } else { + // layer not exists, add new one + const item = { + key: currentKey, + label: keyPart[i], + name: connName, + db, + keys: 0, + redisKey, + type: ConnectionType.RedisKey, + children: [], + } + if (isLast) { + nodeList.push(item) + descendantChain.push(last(nodeList)) + } else { + nodeList.splice(j, 0, item) + descendantChain.push(nodeList[j]) + } + redisKey += separator + added = true + } + break + } else if (treeKey === currentKey) { + if (isLastKeyPart) { + // same key exists, do nothing + console.log('TODO: same key exist, do nothing now, should replace value later') + } else { + // same group exists, find into it's children + descendantChain.push(nodeList[j]) + redisKey += separator + } + break + } + } + } + + // update ancestor node's info + if (added) { + const desLen = size(descendantChain) + for (let i = 0; i < desLen; i++) { + const children = get(descendantChain[i], 'children', []) + let keys = 0 + for (const child of children) { + if (child.type === ConnectionType.RedisKey) { + keys += get(child, 'keys', 1) + } else if (child.type === ConnectionType.RedisValue) { + keys += get(child, 'keys', 0) + } + } + descendantChain[i].keys = keys + } + } + }, + + /** + * set redis key + * @param {string} connName + * @param {number} db + * @param {string} key + * @param {number} keyType + * @param {any} value + * @param {number} ttl + * @returns {Promise<{[msg]: string, success: boolean}>} + */ + async setKey(connName, db, key, keyType, value, ttl) { + try { + const { data, success, msg } = await SetKeyValue(connName, db, key, keyType, value, ttl) + if (success) { + // update tree view data + this._addKey(connName, db, key) + return { success } + } else { + return { success, msg } + } + } catch (e) { + return { success: false, msg: e.message } + } + }, + + /** + * update hash field + * when field is set, newField is null, delete field + * when field is null, newField is set, add new field + * when both field and newField are set, and field === newField, update field + * when both field and newField are set, and field !== newField, delete field and add newField + * @param {string} connName + * @param {number} db + * @param {string} key + * @param {string} field + * @param {string} newField + * @param {string} value + * @returns {Promise<{[msg]: string, success: boolean, [updated]: {}}>} + */ + async setHash(connName, db, key, field, newField, value) { + try { + const { data, success, msg } = await SetHashValue(connName, db, key, field, newField || '', value || '') + if (success) { + const { updated = {} } = data + return { success, updated } + } else { + return { success, msg } + } + } catch (e) { + return { success: false, msg: e.message } + } + }, + + /** + * insert or update hash field item + * @param {string} connName + * @param {number} db + * @param {string} key + * @param {number }action 0:ignore duplicated fields 1:overwrite duplicated fields + * @param {string[]} fieldItems field1, value1, filed2, value2... + * @returns {Promise<{[msg]: string, success: boolean, [updated]: {}}>} + */ + async addHashField(connName, db, key, action, fieldItems) { + try { + const { data, success, msg } = await AddHashField(connName, db, key, action, fieldItems) + if (success) { + const { updated = {} } = data + return { success, updated } + } else { + return { success: false, msg } + } + } catch (e) { + return { success: false, msg: e.message } + } + }, + + /** + * remove hash field + * @param {string} connName + * @param {number} db + * @param {string} key + * @param {string} field + * @returns {Promise<{[msg]: {}, success: boolean, [removed]: string[]}>} + */ + async removeHashField(connName, db, key, field) { + try { + const { data, success, msg } = await SetHashValue(connName, db, key, field, '', '') + if (success) { + const { removed = [] } = data + return { success, removed } + } else { + return { success, msg } + } + } catch (e) { + return { success: false, msg: e.message } + } + }, + + /** + * insert list item + * @param {string} connName + * @param {number} db + * @param {string} key + * @param {int} action 0: push to head, 1: push to tail + * @param {string[]}values + * @returns {Promise<*|{msg, success: boolean}>} + */ + async addListItem(connName, db, key, action, values) { + try { + return AddListItem(connName, db, key, action, values) + } catch (e) { + return { success: false, msg: e.message } + } + }, + + /** + * prepend item to head of list + * @param connName + * @param db + * @param key + * @param values + * @returns {Promise<[msg]: string, success: boolean, [item]: []>} + */ + async prependListItem(connName, db, key, values) { + try { + const { data, success, msg } = await AddListItem(connName, db, key, 0, values) + if (success) { + const { left = [] } = data + return { success, item: left } + } else { + return { success: false, msg } + } + } catch (e) { + return { success: false, msg: e.message } + } + }, + + /** + * append item to tail of list + * @param connName + * @param db + * @param key + * @param values + * @returns {Promise<[msg]: string, success: boolean, [item]: any[]>} + */ + async appendListItem(connName, db, key, values) { + try { + const { data, success, msg } = await AddListItem(connName, db, key, 1, values) + if (success) { + const { right = [] } = data + return { success, item: right } + } else { + return { success: false, msg } + } + } catch (e) { + return { success: false, msg: e.message } + } + }, + + /** + * update value of list item by index + * @param {string} connName + * @param {number} db + * @param {string} key + * @param {number} index + * @param {string} value + * @returns {Promise<{[msg]: string, success: boolean, [updated]: {}}>} + */ + async updateListItem(connName, db, key, index, value) { + try { + const { data, success, msg } = await SetListItem(connName, db, key, index, value) + if (success) { + const { updated = {} } = data + return { success, updated } + } else { + return { success, msg } + } + } catch (e) { + return { success: false, msg: e.message } + } + }, + + /** + * remove list item + * @param {string} connName + * @param {number} db + * @param {string} key + * @param {number} index + * @returns {Promise<{[msg]: string, success: boolean, [removed]: string[]}>} + */ + async removeListItem(connName, db, key, index) { + try { + const { data, success, msg } = await SetListItem(connName, db, key, index, '') + if (success) { + const { removed = [] } = data + return { success, removed } + } else { + return { success, msg } + } + } catch (e) { + return { success: false, msg: e.message } + } + }, + + /** + * add item to set + * @param {string} connName + * @param {number} db + * @param {string} key + * @param {string} value + * @returns {Promise<{[msg]: string, success: boolean}>} + */ + async addSetItem(connName, db, key, value) { + try { + const { success, msg } = await SetSetItem(connName, db, key, false, [value]) + if (success) { + return { success } + } else { + return { success, msg } + } + } catch (e) { + return { success: false, msg: e.message } + } + }, + + /** + * update value of set item + * @param {string} connName + * @param {number} db + * @param {string} key + * @param {string} value + * @param {string} newValue + * @returns {Promise<{[msg]: string, success: boolean}>} + */ + async updateSetItem(connName, db, key, value, newValue) { + try { + const { success, msg } = await UpdateSetItem(connName, db, key, value, newValue) + if (success) { + return { success: true } + } else { + return { success, msg } + } + } catch (e) { + return { success: false, msg: e.message } + } + }, + + /** + * remove item from set + * @param connName + * @param db + * @param key + * @param value + * @returns {Promise<{[msg]: string, success: boolean}>} + */ + async removeSetItem(connName, db, key, value) { + try { + const { success, msg } = await SetSetItem(connName, db, key, true, [value]) + if (success) { + return { success } + } else { + return { success, msg } + } + } catch (e) { + return { success: false, msg: e.message } + } + }, + + /** + * add item to sorted set + * @param {string} connName + * @param {number} db + * @param {string} key + * @param {number} action + * @param {Object.} vs value: score + * @returns {Promise<{[msg]: string, success: boolean}>} + */ + async addZSetItem(connName, db, key, action, vs) { + try { + const { success, msg } = await AddZSetValue(connName, db, key, action, vs) + if (success) { + return { success } + } else { + return { success, msg } + } + } catch (e) { + return { success: false, msg: e.message } + } + }, + + /** + * update item of sorted set + * @param {string} connName + * @param {number} db + * @param {string} key + * @param {string} value + * @param {string} newValue + * @param {number} score + * @returns {Promise<{[msg]: string, success: boolean, [updated]: {}, [removed]: []}>} + */ + async updateZSetItem(connName, db, key, value, newValue, score) { + try { + const { data, success, msg } = await UpdateZSetValue(connName, db, key, value, newValue, score) + if (success) { + const { updated, removed } = data + return { success, updated, removed } + } else { + return { success, msg } + } + } catch (e) { + return { success: false, msg: e.message } + } + }, + + /** + * remove item from sorted set + * @param {string} connName + * @param {number} db + * @param key + * @param {string} value + * @returns {Promise<{[msg]: string, success: boolean, [removed]: []}>} + */ + async removeZSetItem(connName, db, key, value) { + try { + const { data, success, msg } = await UpdateZSetValue(connName, db, key, value, '', 0) + if (success) { + const { removed } = data + return { success, removed } + } else { + return { success, msg } + } + } catch (e) { + return { success: false, msg: e.message } + } + }, + + /** + * reset key's ttl + * @param {string} connName + * @param {number} db + * @param {string} key + * @param {number} ttl + * @returns {Promise} + */ + async setTTL(connName, db, key, ttl) { + try { + const { success, msg } = await SetKeyTTL(connName, db, key, ttl) + return success === true + } catch (e) { + return false + } + }, + + /** + * + * @param {string} connName + * @param {number} db + * @param {string} key + * @private + */ + _removeKey(connName, db, key) { + const connNode = this.getConnection(connName) + const { children: dbs = [] } = connNode + const dbDetail = get(dbs, db, {}) + + if (dbDetail == null) { + return + } + + const descendantChain = [dbDetail] + const keyPart = split(key, separator) + let redisKey = '' + const keyLen = size(keyPart) + let deleted = false + let forceBreak = false + for (let i = 0; i < keyLen && !forceBreak; i++) { + redisKey += keyPart[i] + + const node = last(descendantChain) + const nodeList = get(node, 'children', []) + const len = size(nodeList) + const isLastKeyPart = i === keyLen - 1 + for (let j = 0; j < len; j++) { + const treeKey = get(nodeList[j], 'key') + const currentKey = `${connName}/db${db}/${redisKey}@${ + isLastKeyPart ? ConnectionType.RedisValue : ConnectionType.RedisKey + }` + if (treeKey > currentKey) { + // out of search range, target not exists + forceBreak = true + break + } else if (treeKey === currentKey) { + if (isLastKeyPart) { + // find target + nodeList.splice(j, 1) + node.keys -= 1 + deleted = true + forceBreak = true + } else { + // find into it's children + descendantChain.push(nodeList[j]) + redisKey += separator + } + break + } + } + + if (forceBreak) { + break + } + } + // console.log(JSON.stringify(descendantChain)) + + // update ancestor node's info + if (deleted) { + const desLen = size(descendantChain) + for (let i = desLen - 1; i > 0; i--) { + const children = get(descendantChain[i], 'children', []) + const parent = descendantChain[i - 1] + if (isEmpty(children)) { + const parentChildren = get(parent, 'children', []) + const k = get(descendantChain[i], 'key') + remove(parentChildren, (item) => item.key === k) + } + parent.keys -= 1 + } + } + }, + + /** + * remove redis key + * @param {string} connName + * @param {number} db + * @param {string} key + * @returns {Promise} + */ + async removeKey(connName, db, key) { + try { + const { data, success, msg } = await RemoveKey(connName, db, key) + if (success) { + // update tree view data + this._removeKey(connName, db, key) + + // set tab content empty + const tab = useTabStore() + tab.emptyTab(connName) + return true + } + } finally { + } + return false + }, + + /** + * rename key + * @param {string} connName + * @param {number} db + * @param {string} key + * @param {string} newKey + * @returns {Promise<{[msg]: string, success: boolean}>} + */ + async renameKey(connName, db, key, newKey) { + const { success = false, msg } = await RenameKey(connName, db, key, newKey) + if (success) { + // delete old key and add new key struct + this._removeKey(connName, db, key) + this._addKey(connName, db, newKey) + return { success: true } + } else { + return { success: false, msg } + } + }, + }, +}) + +export default useConnectionStore diff --git a/frontend/src/stores/dialog.js b/frontend/src/stores/dialog.js new file mode 100644 index 0000000..73094d4 --- /dev/null +++ b/frontend/src/stores/dialog.js @@ -0,0 +1,115 @@ +import { defineStore } from 'pinia' + +const useDialogStore = defineStore('dialog', { + state: () => ({ + newDialogVisible: false, + + /** + * @property {string} prefix + * @property {string} server + * @property {int} db + */ + newKeyParam: { + prefix: '', + server: '', + db: 0, + }, + newKeyDialogVisible: false, + + addFieldParam: { + server: '', + db: 0, + key: '', + type: null, + }, + addFieldsDialogVisible: false, + + renameKeyParam: { + server: '', + db: 0, + key: '', + }, + renameDialogVisible: false, + + selectTTL: -1, + ttlDialogVisible: false, + + preferencesDialogVisible: false, + }), + actions: { + openNewDialog() { + this.newDialogVisible = true + }, + closeNewDialog() { + this.newDialogVisible = false + }, + + /** + * + * @param {string} server + * @param {number} db + * @param {string} key + */ + openRenameKeyDialog(server, db, key) { + this.renameKeyParam.server = server + this.renameKeyParam.db = db + this.renameKeyParam.key = key + this.renameDialogVisible = true + }, + closeRenameKeyDialog() { + this.renameDialogVisible = false + }, + + /** + * + * @param {string} prefix + * @param {number} server + * @param {string} db + */ + openNewKeyDialog(prefix, server, db) { + this.newKeyParam.prefix = prefix + this.newKeyParam.server = server + this.newKeyParam.db = db + this.newKeyDialogVisible = true + }, + closeNewKeyDialog() { + this.newKeyDialogVisible = false + }, + + /** + * + * @param {string} server + * @param {number} db + * @param {string} key + * @param {string} type + */ + openAddFieldsDialog(server, db, key, type) { + this.addFieldParam.server = server + this.addFieldParam.db = db + this.addFieldParam.key = key + this.addFieldParam.type = type + this.addFieldsDialogVisible = true + }, + closeAddFieldsDialog() { + this.addFieldsDialogVisible = false + }, + + openTTLDialog(ttl) { + this.selectTTL = ttl + this.ttlDialogVisible = true + }, + closeTTLDialog() { + this.selectTTL = -1 + this.ttlDialogVisible = false + }, + + openPreferencesDialog() { + this.preferencesDialogVisible = true + }, + closePreferencesDialog() { + this.preferencesDialogVisible = false + }, + }, +}) + +export default useDialogStore diff --git a/frontend/src/stores/tab.js b/frontend/src/stores/tab.js new file mode 100644 index 0000000..3fb14f5 --- /dev/null +++ b/frontend/src/stores/tab.js @@ -0,0 +1,183 @@ +import { find, findIndex, isEmpty, size } from 'lodash' +import { defineStore } from 'pinia' + +const useTabStore = defineStore('tab', { + /** + * @typedef {Object} TabItem + * @property {string} name connection name + * @property {boolean} blank is blank tab + * @property {string} [title] tab title + * @property {string} [icon] tab title + * @property {string} [type] key type + * @property {Object|Array} [value] key value + * @property {string} [server] server name + * @property {int} [db] database index + * @property {string} [key] current key name + * @property {int} [ttl] ttl of current key + */ + + /** + * + * @returns {{tabList: TabItem[], activatedTab: string, activatedIndex: number}} + */ + state: () => ({ + tabList: [], + activatedTab: '', + activatedIndex: 0, // current activated tab index + }), + getters: { + /** + * get current tab list item + * @returns {TabItem[]} + */ + tabs() { + if (isEmpty(this.tabList)) { + this.newBlankTab() + } + return this.tabList + }, + + /** + * get current activated tab item + * @returns {TabItem|null} + */ + currentTab() { + return this.tabs[this.activatedIndex || 0] + // let current = find(this.tabs, {name: this.activatedTab}) + // if (current == null) { + // current = this.tabs[0] + // } + // return current + }, + + currentTabIndex() { + const len = size(this.tabs) + if (this.activatedIndex < 0 || this.activatedIndex >= len) { + this.activatedIndex = 0 + } + return this.tabs[this.activatedIndex] + }, + }, + actions: { + /** + * create new blank tab to tail + */ + newBlankTab() { + this.tabList.push({ + name: Date.now().toString(), + title: 'new tab', + blank: true, + }) + this.activatedIndex = size(this.tabList) - 1 + }, + + /** + * update or insert a new tab if not exists with the same name + * @param {string} server + * @param {number} db + * @param {number} type + * @param {number} ttl + * @param {string} key + * @param {*} value + */ + upsertTab({ server, db, type, ttl, key, value }) { + let tabIndex = findIndex(this.tabList, { name: server }) + if (tabIndex === -1) { + this.tabList.push({ + name: server, + server, + db, + type, + ttl, + key, + value, + }) + tabIndex = this.tabList.length - 1 + } + const tab = this.tabList[tabIndex] + tab.blank = false + tab.title = `${server}/db${db}` + tab.server = server + tab.db = db + tab.type = type + tab.ttl = ttl + tab.key = key + tab.value = value + this.activatedIndex = tabIndex + // this.activatedTab = tab.name + }, + + /** + * update ttl by tag + * @param {string} server + * @param {number} db + * @param {string} key + * @param {number} ttl + */ + updateTTL({ server, db, key, ttl }) { + let tab = find(this.tabList, { name: server, db, key }) + if (tab == null) { + return + } + tab.ttl = ttl + }, + + /** + * set tab's content to empty + * @param {string} name + */ + emptyTab(name) { + const tab = find(this.tabList, { name }) + if (tab != null) { + tab.key = null + tab.value = null + } + }, + switchTab(tabIndex) { + // const len = size(this.tabList) + // if (tabIndex < 0 || tabIndex >= len) { + // tabIndex = 0 + // } + // this.activatedIndex = tabIndex + // const tabIndex = findIndex(this.tabList, {name}) + // if (tabIndex === -1) { + // return + // } + // this.activatedIndex = tabIndex + }, + removeTab(tabIndex) { + const len = size(this.tabs) + // ignore remove last blank tab + if (len === 1 && this.tabs[0].blank) { + return + } + + if (tabIndex < 0 || tabIndex >= len) { + return + } + this.tabList.splice(tabIndex, 1) + + if (len === 1) { + this.newBlankTab() + } else { + // Update select index if removed index equal current selected + this.activatedIndex -= 1 + if (this.activatedIndex < 0 && this.tabList.length > 0) { + this.activatedIndex = 0 + } + } + }, + removeTabByName(tabName) { + const idx = findIndex(this.tabs, { name: tabName }) + if (idx !== -1) { + this.removeTab(idx) + } + }, + removeAllTab() { + this.tabList = [] + this.newBlankTab() + }, + }, +}) + +export default useTabStore diff --git a/frontend/src/style.scss b/frontend/src/style.scss new file mode 100644 index 0000000..2419964 --- /dev/null +++ b/frontend/src/style.scss @@ -0,0 +1,109 @@ +:root { + --bg-color: #ffffff; + --bg-color-page: #f2f3f5; + --text-color-regular: #606266; + --border-color: #dcdfe6; + --transition-duration-fast: 0.2s; + --transition-function-ease-in-out-bezier: cubic-bezier(0.645, 0.045, 0.355, 1); +} + +html { + background-color: #ffffff; + //text-align: center; + cursor: default; + -webkit-user-select: none; /* Chrome, Safari */ + -moz-user-select: none; /* Firefox */ + user-select: none; +} + +body { + margin: 0; + font-family: "Nunito", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; +} + +@font-face { + font-family: "Nunito"; + font-style: normal; + font-weight: 400; + src: local(""), + url("assets/fonts/nunito-v16-latin-regular.woff2") format("woff2"); +} + +#app { + height: 100vh; + //text-align: center; +} + +.flex-box { + display: flex; +} + +.flex-box-v { + @extend .flex-box; + flex-direction: column; +} + +.flex-box-h { + @extend .flex-box; + flex-direction: row; +} + +.flex-item { + flex: 0 0 auto; +} + +.flex-item-expand { + flex-grow: 1; +} + +.icon-btn { + cursor: pointer; + line-height: 100%; +} + +.ellipsis { + white-space: nowrap; /* 禁止文本换行 */ + overflow: hidden; /* 隐藏超出容器的文本 */ + text-overflow: ellipsis; /* 使用省略号表示被截断的文本 */ +} + +.unit-item { + margin-left: 10px; +} + +.fill-height { + height: 100%; +} + +.content-wrapper { + background-color: var(--bg-color); + height: 100%; + overflow: hidden; + gap: 5px; + padding: 5px; + box-sizing: border-box; + + .tb2 { + gap: 5px; + justify-content: flex-end; + align-items: center; + } + + .value-wrapper { + border-top: var(--border-color) 1px solid; + padding: 5px; + user-select: text; + } +} + +.n-dynamic-input-item { + align-items: center; + gap: 10px; +} + +.context-menu-item { + min-width: 120px; + padding-right: 10px; +} diff --git a/frontend/src/utils/check_string_format.js b/frontend/src/utils/check_string_format.js new file mode 100644 index 0000000..c70f1bb --- /dev/null +++ b/frontend/src/utils/check_string_format.js @@ -0,0 +1,25 @@ +import { endsWith, isEmpty, size, startsWith } from 'lodash' + +export const IsRedisKey = (str, separator) => { + if (isEmpty(separator)) { + separator = ':' + } +} + +/** + * check string is json + * @param str + * @returns {boolean} + * @constructor + */ +export const IsJson = (str) => { + if (size(str) >= 2) { + if (startsWith(str, '{') && endsWith(str, '}')) { + return true + } + if (startsWith(str, '[') && endsWith(str, ']')) { + return true + } + } + return false +} diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 0000000..9c141c8 --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,24 @@ +import vue from '@vitejs/plugin-vue' +import AutoImport from 'unplugin-auto-import/vite' +import Icons from 'unplugin-icons/vite' +import { NaiveUiResolver } from 'unplugin-vue-components/resolvers' +import Components from 'unplugin-vue-components/vite' +import { defineConfig } from 'vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + vue(), + AutoImport({ + imports: [ + { + 'naive-ui': ['useDialog', 'useMessage', 'useNotification', 'useLoadingBar'], + }, + ], + }), + Components({ + resolvers: [NaiveUiResolver()], + }), + Icons(), + ], +}) diff --git a/frontend/wailsjs/go/main/App.d.ts b/frontend/wailsjs/go/main/App.d.ts new file mode 100755 index 0000000..74062c5 --- /dev/null +++ b/frontend/wailsjs/go/main/App.d.ts @@ -0,0 +1,4 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export function Greet(arg1: string): Promise diff --git a/frontend/wailsjs/go/main/App.js b/frontend/wailsjs/go/main/App.js new file mode 100755 index 0000000..128f6e5 --- /dev/null +++ b/frontend/wailsjs/go/main/App.js @@ -0,0 +1,7 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export function Greet(arg1) { + return window['go']['main']['App']['Greet'](arg1) +} diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts new file mode 100755 index 0000000..9fbb0e9 --- /dev/null +++ b/frontend/wailsjs/go/models.ts @@ -0,0 +1,51 @@ +export namespace types { + export class Connection { + group: string + name: string + addr: string + port: number + username: string + password: string + defaultFilter: string + keySeparator: string + connTimeout: number + execTimeout: number + markColor: string + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source) + this.group = source['group'] + this.name = source['name'] + this.addr = source['addr'] + this.port = source['port'] + this.username = source['username'] + this.password = source['password'] + this.defaultFilter = source['defaultFilter'] + this.keySeparator = source['keySeparator'] + this.connTimeout = source['connTimeout'] + this.execTimeout = source['execTimeout'] + this.markColor = source['markColor'] + } + + static createFrom(source: any = {}) { + return new Connection(source) + } + } + + export class JSResp { + success: boolean + msg: string + data?: any + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source) + this.success = source['success'] + this.msg = source['msg'] + this.data = source['data'] + } + + static createFrom(source: any = {}) { + return new JSResp(source) + } + } +} diff --git a/frontend/wailsjs/go/services/connectionService.d.ts b/frontend/wailsjs/go/services/connectionService.d.ts new file mode 100755 index 0000000..10af116 --- /dev/null +++ b/frontend/wailsjs/go/services/connectionService.d.ts @@ -0,0 +1,98 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT +import { context, types } from '../models' + +export function AddHashField( + arg1: string, + arg2: number, + arg3: string, + arg4: number, + arg5: Array +): Promise + +export function AddListItem( + arg1: string, + arg2: number, + arg3: string, + arg4: number, + arg5: Array +): Promise + +export function AddZSetValue( + arg1: string, + arg2: number, + arg3: string, + arg4: number, + arg5: { + [key: string]: number + } +): Promise + +export function CloseConnection(arg1: string): Promise + +export function GetKeyValue(arg1: string, arg2: number, arg3: string): Promise + +export function ListConnection(): Promise + +export function OpenConnection(arg1: string): Promise + +export function OpenDatabase(arg1: string, arg2: number): Promise + +export function RemoveKey(arg1: string, arg2: number, arg3: string): Promise + +export function RenameKey(arg1: string, arg2: number, arg3: string, arg4: string): Promise + +export function SaveConnection(arg1: types.Connection, arg2: boolean): Promise + +export function SetHashValue( + arg1: string, + arg2: number, + arg3: string, + arg4: string, + arg5: string, + arg6: string +): Promise + +export function SetKeyTTL(arg1: string, arg2: number, arg3: string, arg4: number): Promise + +export function SetKeyValue( + arg1: string, + arg2: number, + arg3: string, + arg4: string, + arg5: any, + arg6: number +): Promise + +export function SetListItem(arg1: string, arg2: number, arg3: string, arg4: number, arg5: string): Promise + +export function SetSetItem( + arg1: string, + arg2: number, + arg3: string, + arg4: boolean, + arg5: Array +): Promise + +export function Start(arg1: context.Context): Promise + +export function Stop(arg1: context.Context): Promise + +export function TestConnection(arg1: string, arg2: number, arg3: string, arg4: string): Promise + +export function UpdateSetItem( + arg1: string, + arg2: number, + arg3: string, + arg4: string, + arg5: string +): Promise + +export function UpdateZSetValue( + arg1: string, + arg2: number, + arg3: string, + arg4: string, + arg5: string, + arg6: number +): Promise diff --git a/frontend/wailsjs/go/services/connectionService.js b/frontend/wailsjs/go/services/connectionService.js new file mode 100755 index 0000000..076905d --- /dev/null +++ b/frontend/wailsjs/go/services/connectionService.js @@ -0,0 +1,87 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export function AddHashField(arg1, arg2, arg3, arg4, arg5) { + return window['go']['services']['connectionService']['AddHashField'](arg1, arg2, arg3, arg4, arg5) +} + +export function AddListItem(arg1, arg2, arg3, arg4, arg5) { + return window['go']['services']['connectionService']['AddListItem'](arg1, arg2, arg3, arg4, arg5) +} + +export function AddZSetValue(arg1, arg2, arg3, arg4, arg5) { + return window['go']['services']['connectionService']['AddZSetValue'](arg1, arg2, arg3, arg4, arg5) +} + +export function CloseConnection(arg1) { + return window['go']['services']['connectionService']['CloseConnection'](arg1) +} + +export function GetKeyValue(arg1, arg2, arg3) { + return window['go']['services']['connectionService']['GetKeyValue'](arg1, arg2, arg3) +} + +export function ListConnection() { + return window['go']['services']['connectionService']['ListConnection']() +} + +export function OpenConnection(arg1) { + return window['go']['services']['connectionService']['OpenConnection'](arg1) +} + +export function OpenDatabase(arg1, arg2) { + return window['go']['services']['connectionService']['OpenDatabase'](arg1, arg2) +} + +export function RemoveKey(arg1, arg2, arg3) { + return window['go']['services']['connectionService']['RemoveKey'](arg1, arg2, arg3) +} + +export function RenameKey(arg1, arg2, arg3, arg4) { + return window['go']['services']['connectionService']['RenameKey'](arg1, arg2, arg3, arg4) +} + +export function SaveConnection(arg1, arg2) { + return window['go']['services']['connectionService']['SaveConnection'](arg1, arg2) +} + +export function SetHashValue(arg1, arg2, arg3, arg4, arg5, arg6) { + return window['go']['services']['connectionService']['SetHashValue'](arg1, arg2, arg3, arg4, arg5, arg6) +} + +export function SetKeyTTL(arg1, arg2, arg3, arg4) { + return window['go']['services']['connectionService']['SetKeyTTL'](arg1, arg2, arg3, arg4) +} + +export function SetKeyValue(arg1, arg2, arg3, arg4, arg5, arg6) { + return window['go']['services']['connectionService']['SetKeyValue'](arg1, arg2, arg3, arg4, arg5, arg6) +} + +export function SetListItem(arg1, arg2, arg3, arg4, arg5) { + return window['go']['services']['connectionService']['SetListItem'](arg1, arg2, arg3, arg4, arg5) +} + +export function SetSetItem(arg1, arg2, arg3, arg4, arg5) { + return window['go']['services']['connectionService']['SetSetItem'](arg1, arg2, arg3, arg4, arg5) +} + +export function Start(arg1) { + return window['go']['services']['connectionService']['Start'](arg1) +} + +export function Stop(arg1) { + return window['go']['services']['connectionService']['Stop'](arg1) +} + +export function TestConnection(arg1, arg2, arg3, arg4) { + return window['go']['services']['connectionService']['TestConnection'](arg1, arg2, arg3, arg4) +} + +export function UpdateSetItem(arg1, arg2, arg3, arg4, arg5) { + return window['go']['services']['connectionService']['UpdateSetItem'](arg1, arg2, arg3, arg4, arg5) +} + +export function UpdateZSetValue(arg1, arg2, arg3, arg4, arg5, arg6) { + return window['go']['services']['connectionService']['UpdateZSetValue'](arg1, arg2, arg3, arg4, arg5, arg6) +} diff --git a/frontend/wailsjs/go/storage/PreferencesStorage.d.ts b/frontend/wailsjs/go/storage/PreferencesStorage.d.ts new file mode 100755 index 0000000..688eb64 --- /dev/null +++ b/frontend/wailsjs/go/storage/PreferencesStorage.d.ts @@ -0,0 +1,12 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export function DefaultPreferences(): Promise<{ [key: string]: any }> + +export function GetPreferences(): Promise<{ [key: string]: any }> + +export function RestoreDefault(): Promise<{ [key: string]: any }> + +export function SetPreferences(arg1: string, arg2: any): Promise + +export function SetPreferencesN(arg1: { [key: string]: any }): Promise diff --git a/frontend/wailsjs/go/storage/PreferencesStorage.js b/frontend/wailsjs/go/storage/PreferencesStorage.js new file mode 100755 index 0000000..1ed1c9a --- /dev/null +++ b/frontend/wailsjs/go/storage/PreferencesStorage.js @@ -0,0 +1,23 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export function DefaultPreferences() { + return window['go']['storage']['PreferencesStorage']['DefaultPreferences']() +} + +export function GetPreferences() { + return window['go']['storage']['PreferencesStorage']['GetPreferences']() +} + +export function RestoreDefault() { + return window['go']['storage']['PreferencesStorage']['RestoreDefault']() +} + +export function SetPreferences(arg1, arg2) { + return window['go']['storage']['PreferencesStorage']['SetPreferences'](arg1, arg2) +} + +export function SetPreferencesN(arg1) { + return window['go']['storage']['PreferencesStorage']['SetPreferencesN'](arg1) +} diff --git a/frontend/wailsjs/runtime/package.json b/frontend/wailsjs/runtime/package.json new file mode 100644 index 0000000..1e7c8a5 --- /dev/null +++ b/frontend/wailsjs/runtime/package.json @@ -0,0 +1,24 @@ +{ + "name": "@wailsapp/runtime", + "version": "2.0.0", + "description": "Wails Javascript runtime library", + "main": "runtime.js", + "types": "runtime.d.ts", + "scripts": { + }, + "repository": { + "type": "git", + "url": "git+https://github.com/wailsapp/wails.git" + }, + "keywords": [ + "Wails", + "Javascript", + "Go" + ], + "author": "Lea Anthony ", + "license": "MIT", + "bugs": { + "url": "https://github.com/wailsapp/wails/issues" + }, + "homepage": "https://github.com/wailsapp/wails#readme" +} diff --git a/frontend/wailsjs/runtime/runtime.d.ts b/frontend/wailsjs/runtime/runtime.d.ts new file mode 100644 index 0000000..2da13f9 --- /dev/null +++ b/frontend/wailsjs/runtime/runtime.d.ts @@ -0,0 +1,235 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +export interface Position { + x: number + y: number +} + +export interface Size { + w: number + h: number +} + +export interface Screen { + isCurrent: boolean + isPrimary: boolean + width: number + height: number +} + +// Environment information such as platform, buildtype, ... +export interface EnvironmentInfo { + buildType: string + platform: string + arch: string +} + +// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit) +// emits the given event. Optional data may be passed with the event. +// This will trigger any event listeners. +export function EventsEmit(eventName: string, ...data: any): void + +// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name. +export function EventsOn(eventName: string, callback: (...data: any) => void): () => void + +// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple) +// sets up a listener for the given event name, but will only trigger a given number times. +export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void + +// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce) +// sets up a listener for the given event name, but will only trigger once. +export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void + +// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff) +// unregisters the listener for the given event name. +export function EventsOff(eventName: string, ...additionalEventNames: string[]): void + +// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall) +// unregisters all listeners. +export function EventsOffAll(): void + +// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint) +// logs the given message as a raw message +export function LogPrint(message: string): void + +// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace) +// logs the given message at the `trace` log level. +export function LogTrace(message: string): void + +// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug) +// logs the given message at the `debug` log level. +export function LogDebug(message: string): void + +// [LogError](https://wails.io/docs/reference/runtime/log#logerror) +// logs the given message at the `error` log level. +export function LogError(message: string): void + +// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal) +// logs the given message at the `fatal` log level. +// The application will quit after calling this method. +export function LogFatal(message: string): void + +// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo) +// logs the given message at the `info` log level. +export function LogInfo(message: string): void + +// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning) +// logs the given message at the `warning` log level. +export function LogWarning(message: string): void + +// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload) +// Forces a reload by the main application as well as connected browsers. +export function WindowReload(): void + +// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp) +// Reloads the application frontend. +export function WindowReloadApp(): void + +// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop) +// Sets the window AlwaysOnTop or not on top. +export function WindowSetAlwaysOnTop(b: boolean): void + +// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme) +// *Windows only* +// Sets window theme to system default (dark/light). +export function WindowSetSystemDefaultTheme(): void + +// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme) +// *Windows only* +// Sets window to light theme. +export function WindowSetLightTheme(): void + +// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme) +// *Windows only* +// Sets window to dark theme. +export function WindowSetDarkTheme(): void + +// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter) +// Centers the window on the monitor the window is currently on. +export function WindowCenter(): void + +// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle) +// Sets the text in the window title bar. +export function WindowSetTitle(title: string): void + +// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen) +// Makes the window full screen. +export function WindowFullscreen(): void + +// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen) +// Restores the previous window dimensions and position prior to full screen. +export function WindowUnfullscreen(): void + +// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen) +// Returns the state of the window, i.e. whether the window is in full screen mode or not. +export function WindowIsFullscreen(): Promise + +// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize) +// Sets the width and height of the window. +export function WindowSetSize(width: number, height: number): Promise + +// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize) +// Gets the width and height of the window. +export function WindowGetSize(): Promise + +// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize) +// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions. +// Setting a size of 0,0 will disable this constraint. +export function WindowSetMaxSize(width: number, height: number): void + +// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize) +// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions. +// Setting a size of 0,0 will disable this constraint. +export function WindowSetMinSize(width: number, height: number): void + +// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition) +// Sets the window position relative to the monitor the window is currently on. +export function WindowSetPosition(x: number, y: number): void + +// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition) +// Gets the window position relative to the monitor the window is currently on. +export function WindowGetPosition(): Promise + +// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide) +// Hides the window. +export function WindowHide(): void + +// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow) +// Shows the window, if it is currently hidden. +export function WindowShow(): void + +// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise) +// Maximises the window to fill the screen. +export function WindowMaximise(): void + +// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise) +// Toggles between Maximised and UnMaximised. +export function WindowToggleMaximise(): void + +// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise) +// Restores the window to the dimensions and position prior to maximising. +export function WindowUnmaximise(): void + +// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised) +// Returns the state of the window, i.e. whether the window is maximised or not. +export function WindowIsMaximised(): Promise + +// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise) +// Minimises the window. +export function WindowMinimise(): void + +// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise) +// Restores the window to the dimensions and position prior to minimising. +export function WindowUnminimise(): void + +// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised) +// Returns the state of the window, i.e. whether the window is minimised or not. +export function WindowIsMinimised(): Promise + +// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal) +// Returns the state of the window, i.e. whether the window is normal or not. +export function WindowIsNormal(): Promise + +// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour) +// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels. +export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void + +// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall) +// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system. +export function ScreenGetAll(): Promise + +// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl) +// Opens the given URL in the system browser. +export function BrowserOpenURL(url: string): void + +// [Environment](https://wails.io/docs/reference/runtime/intro#environment) +// Returns information about the environment +export function Environment(): Promise + +// [Quit](https://wails.io/docs/reference/runtime/intro#quit) +// Quits the application. +export function Quit(): void + +// [Hide](https://wails.io/docs/reference/runtime/intro#hide) +// Hides the application. +export function Hide(): void + +// [Show](https://wails.io/docs/reference/runtime/intro#show) +// Shows the application. +export function Show(): void + +// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext) +// Returns the current text stored on clipboard +export function ClipboardGetText(): Promise + +// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext) +// Sets a text on the clipboard +export function ClipboardSetText(text: string): Promise diff --git a/frontend/wailsjs/runtime/runtime.js b/frontend/wailsjs/runtime/runtime.js new file mode 100644 index 0000000..b2b327a --- /dev/null +++ b/frontend/wailsjs/runtime/runtime.js @@ -0,0 +1,202 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +export function LogPrint(message) { + window.runtime.LogPrint(message) +} + +export function LogTrace(message) { + window.runtime.LogTrace(message) +} + +export function LogDebug(message) { + window.runtime.LogDebug(message) +} + +export function LogInfo(message) { + window.runtime.LogInfo(message) +} + +export function LogWarning(message) { + window.runtime.LogWarning(message) +} + +export function LogError(message) { + window.runtime.LogError(message) +} + +export function LogFatal(message) { + window.runtime.LogFatal(message) +} + +export function EventsOnMultiple(eventName, callback, maxCallbacks) { + return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks) +} + +export function EventsOn(eventName, callback) { + return EventsOnMultiple(eventName, callback, -1) +} + +export function EventsOff(eventName, ...additionalEventNames) { + return window.runtime.EventsOff(eventName, ...additionalEventNames) +} + +export function EventsOnce(eventName, callback) { + return EventsOnMultiple(eventName, callback, 1) +} + +export function EventsEmit(eventName) { + let args = [eventName].slice.call(arguments) + return window.runtime.EventsEmit.apply(null, args) +} + +export function WindowReload() { + window.runtime.WindowReload() +} + +export function WindowReloadApp() { + window.runtime.WindowReloadApp() +} + +export function WindowSetAlwaysOnTop(b) { + window.runtime.WindowSetAlwaysOnTop(b) +} + +export function WindowSetSystemDefaultTheme() { + window.runtime.WindowSetSystemDefaultTheme() +} + +export function WindowSetLightTheme() { + window.runtime.WindowSetLightTheme() +} + +export function WindowSetDarkTheme() { + window.runtime.WindowSetDarkTheme() +} + +export function WindowCenter() { + window.runtime.WindowCenter() +} + +export function WindowSetTitle(title) { + window.runtime.WindowSetTitle(title) +} + +export function WindowFullscreen() { + window.runtime.WindowFullscreen() +} + +export function WindowUnfullscreen() { + window.runtime.WindowUnfullscreen() +} + +export function WindowIsFullscreen() { + return window.runtime.WindowIsFullscreen() +} + +export function WindowGetSize() { + return window.runtime.WindowGetSize() +} + +export function WindowSetSize(width, height) { + window.runtime.WindowSetSize(width, height) +} + +export function WindowSetMaxSize(width, height) { + window.runtime.WindowSetMaxSize(width, height) +} + +export function WindowSetMinSize(width, height) { + window.runtime.WindowSetMinSize(width, height) +} + +export function WindowSetPosition(x, y) { + window.runtime.WindowSetPosition(x, y) +} + +export function WindowGetPosition() { + return window.runtime.WindowGetPosition() +} + +export function WindowHide() { + window.runtime.WindowHide() +} + +export function WindowShow() { + window.runtime.WindowShow() +} + +export function WindowMaximise() { + window.runtime.WindowMaximise() +} + +export function WindowToggleMaximise() { + window.runtime.WindowToggleMaximise() +} + +export function WindowUnmaximise() { + window.runtime.WindowUnmaximise() +} + +export function WindowIsMaximised() { + return window.runtime.WindowIsMaximised() +} + +export function WindowMinimise() { + window.runtime.WindowMinimise() +} + +export function WindowUnminimise() { + window.runtime.WindowUnminimise() +} + +export function WindowSetBackgroundColour(R, G, B, A) { + window.runtime.WindowSetBackgroundColour(R, G, B, A) +} + +export function ScreenGetAll() { + return window.runtime.ScreenGetAll() +} + +export function WindowIsMinimised() { + return window.runtime.WindowIsMinimised() +} + +export function WindowIsNormal() { + return window.runtime.WindowIsNormal() +} + +export function BrowserOpenURL(url) { + window.runtime.BrowserOpenURL(url) +} + +export function Environment() { + return window.runtime.Environment() +} + +export function Quit() { + window.runtime.Quit() +} + +export function Hide() { + window.runtime.Hide() +} + +export function Show() { + window.runtime.Show() +} + +export function ClipboardGetText() { + return window.runtime.ClipboardGetText() +} + +export function ClipboardSetText(text) { + return window.runtime.ClipboardSetText(text) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..8ef2ca9 --- /dev/null +++ b/go.mod @@ -0,0 +1,48 @@ +module tinyrdm + +go 1.20 + +require ( + github.com/bytedance/sonic v1.9.1 + github.com/google/go-cmp v0.5.9 + github.com/redis/go-redis/v9 v9.0.5 + github.com/vrischmann/userdir v0.0.0-20151206171402-20f291cebd68 + github.com/wailsapp/wails/v2 v2.5.1 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/bep/debounce v1.2.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect + github.com/klauspost/cpuid/v2 v2.2.5 // indirect + github.com/labstack/echo/v4 v4.10.2 // indirect + github.com/labstack/gommon v0.4.0 // indirect + github.com/leaanthony/go-ansi-parser v1.6.0 // indirect + github.com/leaanthony/gosod v1.0.3 // indirect + github.com/leaanthony/slicer v1.6.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/samber/lo v1.38.1 // indirect + github.com/stretchr/testify v1.8.3 // indirect + github.com/tkrajina/go-reflector v0.5.6 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect +) + +// replace github.com/wailsapp/wails/v2 v2.5.1 => /Users/lykin/go/pkg/mod diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1214053 --- /dev/null +++ b/go.sum @@ -0,0 +1,122 @@ +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= +github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao= +github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= +github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck= +github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= +github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M= +github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k= +github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= +github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= +github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc= +github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA= +github.com/leaanthony/go-ansi-parser v1.6.0 h1:T8TuMhFB6TUMIUm0oRrSbgJudTFw9csT3ZK09w0t4Pg= +github.com/leaanthony/go-ansi-parser v1.6.0/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/gosod v1.0.3 h1:Fnt+/B6NjQOVuCWOKYRREZnjGyvg+mEhd1nkkA04aTQ= +github.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4= +github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY= +github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js= +github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8= +github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.0.5 h1:CuQcn5HIEeK7BgElubPP8CGtE0KakrnbBSTLjathl5o= +github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= +github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQSepKdE= +github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/vrischmann/userdir v0.0.0-20151206171402-20f291cebd68 h1:Ah2/69Z24rwD6OByyOdpJDmttftz0FTF8Q4QZ/SF1E4= +github.com/vrischmann/userdir v0.0.0-20151206171402-20f291cebd68/go.mod h1:EqKqAeKddSL9XSGnfXd/7iLncccKhR16HBKVva7ENw8= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +github.com/wailsapp/wails/v2 v2.5.1 h1:mfG+2kWqQXYOwdgI43HEILjOZDXbk5woPYI3jP2b+js= +github.com/wailsapp/wails/v2 v2.5.1/go.mod h1:jbOZbcr/zm79PxXxAjP8UoVlDd9wLW3uDs+isIthDfs= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= +golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/main.go b/main.go new file mode 100644 index 0000000..2fbc2d2 --- /dev/null +++ b/main.go @@ -0,0 +1,59 @@ +package main + +import ( + "context" + "embed" + "github.com/wailsapp/wails/v2/pkg/options/mac" + "tinyrdm/backend/services" + "tinyrdm/backend/storage" + + "github.com/wailsapp/wails/v2" + "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" +) + +//go:embed all:frontend/dist +var assets embed.FS + +func main() { + // Create an instance of the app structure + app := NewApp() + preferences := storage.NewPreferences() + //connections := storage.NewConnections() + connSvc := services.Connection() + + // Create application with options + err := wails.Run(&options.App{ + Title: "Tiny RDM", + Width: 1024, + Height: 768, + MinWidth: 1024, + MinHeight: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, + BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, + OnStartup: func(ctx context.Context) { + app.startup(ctx) + connSvc.Start(ctx) + }, + OnShutdown: func(ctx context.Context) { + connSvc.Stop(ctx) + }, + Bind: []interface{}{ + app, + preferences, + //connections, + connSvc, + }, + Mac: &mac.Options{ + //TitleBar: mac.TitleBarHiddenInset(), + //WebviewIsTransparent: true, + //WindowIsTranslucent: true, + }, + }) + + if err != nil { + println("Error:", err.Error()) + } +} diff --git a/wails.json b/wails.json new file mode 100644 index 0000000..04266cc --- /dev/null +++ b/wails.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://wails.io/schemas/config.v2.json", + "name": "Tiny RDM", + "outputfilename": "tiny-rdm", + "frontend:install": "npm install", + "frontend:build": "npm run build", + "frontend:dev:watcher": "npm run dev", + "frontend:dev:serverUrl": "auto", + "author": { + "name": "Lykin", + "email": "huangliu3060@qq.com" + } +}