From 5391ac2c64741534c7bc7a3746a0bc1956765703 Mon Sep 17 00:00:00 2001 From: lengzhao <zgq_126@126.com> Date: Sun, 20 Dec 2020 21:09:54 +0800 Subject: [PATCH] dynamic ui --- bin/gui/README.md | 2 +- bin/gui/assets/zh.json | 1 + bin/gui/build.sh | 2 + bin/gui/conf.json | 3 +- bin/gui/conf/config.go | 15 +- bin/gui/dynamic_ui.json | 246 +++++++++++ bin/gui/screens/app.go | 9 +- bin/gui/screens/common.go | 146 +++++++ bin/gui/screens/dynamic_ui.go | 791 ++++++++++++++++++++++++++++++++++ bin/gui/screens/history.go | 66 --- bin/gui/screens/master.go | 1 + bin/gui/screens/task.go | 50 +-- bin/gui/usage.txt | 124 ++++++ 13 files changed, 1342 insertions(+), 114 deletions(-) create mode 100644 bin/gui/dynamic_ui.json create mode 100644 bin/gui/screens/common.go create mode 100644 bin/gui/screens/dynamic_ui.go create mode 100644 bin/gui/usage.txt diff --git a/bin/gui/README.md b/bin/gui/README.md index 5b89f19..c7a6661 100644 --- a/bin/gui/README.md +++ b/bin/gui/README.md @@ -4,7 +4,7 @@ 1. fyne.exe package -os windows -name govm.net -icon ./assets/govm.png 2. mv gui.exe govm.exe -2. tar zcvf govm_windows_$(date +'%Y%m%d_%H%M%S').tar.gz govm.exe assets conf.json +3. tar zcvf govm_windows_$(date +'%Y%m%d_%H%M%S').tar.gz govm.exe assets conf.json ## add local language diff --git a/bin/gui/assets/zh.json b/bin/gui/assets/zh.json index 4e8f122..cf5d6bf 100644 --- a/bin/gui/assets/zh.json +++ b/bin/gui/assets/zh.json @@ -81,6 +81,7 @@ "Reward":"奖励", "Life":"有效期", "Message":"信息", + "Customize":"自定义", "":"", "Number":"数量" } \ No newline at end of file diff --git a/bin/gui/build.sh b/bin/gui/build.sh index 0429c84..5457d3c 100644 --- a/bin/gui/build.sh +++ b/bin/gui/build.sh @@ -8,6 +8,8 @@ mkdir $folder mv govm.exe $folder cp assets $folder -rf cp conf.json $folder -rf +cp dynamic_ui.json $folder -rf +cp usage.txt $folder -rf zip -r govm_windows_wallet_$(date +'%Y%m%d_%H%M%S').zip $folder echo Enter to exit read k diff --git a/bin/gui/conf.json b/bin/gui/conf.json index 58ac96b..e2003bd 100644 --- a/bin/gui/conf.json +++ b/bin/gui/conf.json @@ -11,5 +11,6 @@ "chains": [ "1", "2" - ] + ], + "dynamic_ui_file": "dynamic_ui.json" } diff --git a/bin/gui/conf/config.go b/bin/gui/conf/config.go index 9e2ea65..0c6a125 100644 --- a/bin/gui/conf/config.go +++ b/bin/gui/conf/config.go @@ -17,13 +17,14 @@ const Vserion = "v0.5.6" // Config config type Config struct { - APIServer string `json:"api_server,omitempty"` - WalletFile string `json:"wallet_file,omitempty"` - Langure string `json:"langure,omitempty"` - LangureList []string `json:"langure_list,omitempty"` - CoinUnit string `json:"coin_unit,omitempty"` - DefaultChain string `json:"default_chain,omitempty"` - Chains []string `json:"chains,omitempty"` + APIServer string `json:"api_server,omitempty"` + WalletFile string `json:"wallet_file,omitempty"` + Langure string `json:"langure,omitempty"` + LangureList []string `json:"langure_list,omitempty"` + CoinUnit string `json:"coin_unit,omitempty"` + DefaultChain string `json:"default_chain,omitempty"` + Chains []string `json:"chains,omitempty"` + DynamicUIFile string `json:"dynamic_ui_file,omitempty"` } // Public key of config diff --git a/bin/gui/dynamic_ui.json b/bin/gui/dynamic_ui.json new file mode 100644 index 0000000..7fe304c --- /dev/null +++ b/bin/gui/dynamic_ui.json @@ -0,0 +1,246 @@ +{ + "view_ui": [ + { + "name": "Category", + "app": "cdc09c276df3af32f8f60c33d57710374fe126f482fa50db61271bac0886e348", + "description": "category of flag", + "struct": "tCategory", + "chain": 1, + "show_expiration": true, + "hide": false, + "input": [ + { + "mode": "string", + "title": "key" + } + ], + "view": [ + { + "mode": "map", + "title": "", + "sub": [ + { + "mode": "string", + "title": "ID", + "key": "id" + }, + { + "mode": "string", + "title": "Description", + "key": "desc" + } + ] + } + ] + }, + { + "name": "Flag", + "app": "cdc09c276df3af32f8f60c33d57710374fe126f482fa50db61271bac0886e348", + "description": "flag of address", + "struct": "tUser", + "chain": 1, + "show_key": true, + "show_expiration": true, + "input": [ + { + "mode": "address", + "title": "Address", + "value": "01ccaf415a3a6dc8964bf935a1f40e55654a4243ae99c709" + }, + { + "mode": "string", + "title": "Category", + "value": "discord" + }, + { + "mode": "hash", + "hide": true + } + ], + "view": [ + { + "mode": "map", + "title": "", + "sub": [ + { + "mode": "hex", + "title": "Address", + "key": "addr" + }, + { + "mode": "string", + "title": "Category", + "key": "category" + }, + { + "mode": "string", + "title": "ID", + "key": "id" + }, + { + "mode": "string", + "title": "Desc", + "key": "desc" + } + ] + } + ] + }, + { + "name": "LastAction", + "app": "c11b3b8aa630a7fbfccec9e023c363749e9c60db43d7678f43c96075e5c2ddc0", + "description": "accept one task to new one action", + "struct": "tActionStatus", + "chain": 1, + "show_key": true, + "show_expiration": true, + "input": [ + { + "mode": "address", + "title": "Address" + } + ], + "view": [ + { + "mode": "number", + "title": "Action ID" + } + ] + } + ], + "run_ui": [ + { + "name": "Flag", + "app": "cdc09c276df3af32f8f60c33d57710374fe126f482fa50db61271bac0886e348", + "description": "Tag yourself and let others know you", + "chains": [ + 1 + ], + "energy": 0.1, + "hide": false, + "input": [ + { + "mode": "hex", + "title": "Prefix", + "value": "02", + "hide": true + }, + { + "mode": "map", + "sub": [ + { + "mode": "string", + "title": "Category", + "key": "category", + "value":"discord" + }, + { + "mode": "string", + "title": "ID", + "key": "id", + "value":"" + }, + { + "mode": "string", + "title": "Description", + "key": "desc", + "value": "", + "empty_enable": true + } + ] + } + ] + }, + { + "name": "Category", + "app": "cdc09c276df3af32f8f60c33d57710374fe126f482fa50db61271bac0886e348", + "description": "add new category of flag", + "chains": [ + 1 + ], + "energy": 0.1, + "hide": false, + "input": [ + { + "mode": "hex", + "title": "Prefix", + "value": "00", + "hide": true + }, + { + "mode": "map", + "sub": [ + { + "mode": "string", + "title": "Category", + "key": "id" + }, + { + "mode": "string", + "title": "Desc", + "key": "desc", + "empty_enable": true + } + ] + } + ] + }, + { + "name": "AcceptTask", + "app": "c11b3b8aa630a7fbfccec9e023c363749e9c60db43d7678f43c96075e5c2ddc0", + "description": "accept task", + "chains": [ + 1 + ], + "energy": 0.1, + "hide": false, + "input": [ + { + "mode": "hex", + "title": "Option", + "value": "02", + "hide":true + }, + { + "mode": "uint64", + "title": "TaskID", + "value": "1" + } + ] + }, + { + "name": "CommitAction", + "app": "c11b3b8aa630a7fbfccec9e023c363749e9c60db43d7678f43c96075e5c2ddc0", + "description": "add new category of flag", + "chains": [ + 1 + ], + "energy": 0.1, + "hide": false, + "input": [ + { + "mode": "hex", + "title": "Prefix", + "value": "03", + "hide": true + }, + { + "mode": "map", + "sub": [ + { + "mode": "string", + "title": "ActionID", + "key": "id" + }, + { + "mode": "string", + "title": "Proof", + "key": "msg", + "empty_enable": true + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/bin/gui/screens/app.go b/bin/gui/screens/app.go index 5b74800..2167956 100644 --- a/bin/gui/screens/app.go +++ b/bin/gui/screens/app.go @@ -262,6 +262,11 @@ func makeNewAPPTab(w fyne.Window) fyne.Widget { fd.SetFilter(storage.NewExtensionFileFilter([]string{".go", ".govm"})) fd.Show() }) + var chainID uint64 = 1 + chain := widget.NewSelect(c.Chains, func(s string) { + chainID, _ = strconv.ParseUint(s, 10, 64) + }) + chain.SetSelected(c.DefaultChain) energy := widget.NewEntry() energy.SetText("1") unit := widget.NewLabel(c.CoinUnit) @@ -311,7 +316,7 @@ func makeNewAPPTab(w fyne.Window) fyne.Widget { base := res.GetBaseOfUnit(c.CoinUnit) myWlt := conf.GetWallet() - ts := trans.NewTransaction(1, myWlt.Address, 0) + ts := trans.NewTransaction(chainID, myWlt.Address, 0) ts.Energy = uint64(engF * float64(base)) ts.Ops = trans.OpsNewApp ts.Data = code @@ -332,7 +337,7 @@ func makeNewAPPTab(w fyne.Window) fyne.Widget { result.SetText(fmt.Sprintf("%x", key)) }, } - + form.Append(res.GetLocalString("Chain"), chain) form.Append(res.GetLocalString("Description"), desc) borderLayout := layout.NewBorderLayout(nil, nil, nil, btnOpen) form.Append(res.GetLocalString("Code"), fyne.NewContainerWithLayout(borderLayout, btnOpen, codeEntry)) diff --git a/bin/gui/screens/common.go b/bin/gui/screens/common.go new file mode 100644 index 0000000..2ca7e51 --- /dev/null +++ b/bin/gui/screens/common.go @@ -0,0 +1,146 @@ +package screens + +import ( + "encoding/hex" + "encoding/json" + "io/ioutil" + "log" + "net/http" + "strconv" + + "fyne.io/fyne" + "fyne.io/fyne/layout" + "fyne.io/fyne/widget" + "github.com/lengzhao/wallet/bin/gui/conf" + "github.com/lengzhao/wallet/trans" +) + +//statCoinLock +//statCoinUnlock +//statMiningCount +//statMinerHit +//statMinerReg +//statTransferIn +//statTransferOut +//statMove +//statAPPRun + +const coreName = "ff0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" + +func newFormItemWithUnit(in fyne.CanvasObject, right string) fyne.CanvasObject { + unit := widget.NewLabel(right) + borderLayout := layout.NewBorderLayout(nil, nil, nil, unit) + return fyne.NewContainerWithLayout(borderLayout, unit, in) +} +func newFormItemWithObj(in, right fyne.CanvasObject) fyne.CanvasObject { + borderLayout := layout.NewBorderLayout(nil, nil, nil, right) + return fyne.NewContainerWithLayout(borderLayout, right, in) +} + +func runApp(chain, cost, energy uint64, app, prefix string, param []byte) (string, error) { + myWlt := conf.GetWallet() + trans := trans.NewTransaction(chain, myWlt.Address, cost) + var body []byte + var err error + if prefix != "" { + body, err = hex.DecodeString(prefix) + if err != nil { + return "", err + } + } + if len(param) > 0 { + body = append(body, param...) + } + trans.Energy = energy + err = trans.RunApp(app, body) + if err != nil { + return "", err + } + td := trans.GetSignData() + sign := myWlt.Sign(td) + trans.SetTheSign(sign) + td = trans.Output() + key := trans.Key[:] + + err = postTrans(chain, td) + if err != nil { + // result.SetText(fmt.Sprintf("%s", err)) + log.Println("fail to run app:", err) + return "", err + } + return hex.EncodeToString(key), nil +} + +type dataInfo struct { + AppName string `json:"app_name,omitempty"` + StructName string `json:"struct_name,omitempty"` + IsDBData bool `json:"is_db_data,omitempty"` + Key string `json:"key,omitempty"` + Value string `json:"value,omitempty"` + Life uint64 `json:"life,omitempty"` +} + +func getNextKeyOfDB(chain, appName, structName, preKey string) string { + if appName == "" { + appName = coreName + } + urlStr := conf.Get().APIServer + urlStr += "/api/v1/" + chain + "/data/visit?app_name=" + appName + urlStr += "&is_db_data=true&struct_name=" + structName + urlStr += "&pre_key=" + preKey + resp, err := http.Get(urlStr) + if err != nil { + log.Println("fail to get db.", urlStr, err) + return "" + } + defer resp.Body.Close() + data, err := ioutil.ReadAll(resp.Body) + if resp.StatusCode != http.StatusOK || len(data) == 0 { + log.Println("fail to get db.", urlStr, resp.Status, string(data)) + return "" + } + info := dataInfo{} + json.Unmarshal(data, &info) + return info.Key +} +func getIntOfDB(chain, appName, structName, address string) uint64 { + value, _ := getStringOfDB(chain, appName, structName, address) + if value == "" { + return 0 + } + out, err := strconv.ParseUint(value, 16, 64) + if err != nil { + log.Println("parse error.", value, err) + } + + return out +} + +func getStringOfDB(chain, appName, structName, address string) (string, uint64) { + if appName == "" { + appName = coreName + } + urlStr := conf.Get().APIServer + urlStr += "/api/v1/" + chain + "/data?app_name=" + appName + urlStr += "&is_db_data=true&struct_name=" + structName + urlStr += "&key=" + address + resp, err := http.Get(urlStr) + if err != nil { + log.Println("fail to get db.", urlStr, err) + return "", 0 + } + defer resp.Body.Close() + data, err := ioutil.ReadAll(resp.Body) + if resp.StatusCode != http.StatusOK || len(data) == 0 { + log.Println("fail to get db.", urlStr, resp.Status, string(data)) + return "", 0 + } + info := dataInfo{} + err = json.Unmarshal(data, &info) + if err != nil || info.Value == "" { + log.Println("not value.", urlStr) + return "", 0 + } + // log.Println("success to get:", urlStr, info.Value) + return info.Value, info.Life +} diff --git a/bin/gui/screens/dynamic_ui.go b/bin/gui/screens/dynamic_ui.go new file mode 100644 index 0000000..8088072 --- /dev/null +++ b/bin/gui/screens/dynamic_ui.go @@ -0,0 +1,791 @@ +package screens + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "math/big" + "strconv" + "strings" + "time" + + "fyne.io/fyne" + "fyne.io/fyne/dialog" + "fyne.io/fyne/widget" + "github.com/govm-net/govm/wallet" + "github.com/lengzhao/wallet/bin/gui/conf" + "github.com/lengzhao/wallet/bin/gui/res" + "github.com/lengzhao/wallet/trans" +) + +type dataInMode int +type dataOutMode int + +const ( + diHex = dataInMode(iota + 1) + diAddress + diString + diUint64 + diUint32 + diUint16 + diUint8 + diFloat64 + diHash + diMap +) +const ( + doHex = dataOutMode(iota + 100) + doMap + doList + doString + doNumber +) + +var enableEmptyType = map[dataInMode]bool{ + diHash: true, diMap: true, +} + +func (a *dataInMode) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + switch strings.ToLower(s) { + default: + return fmt.Errorf("unknow input mode:%s", s) + case "hex": + *a = diHex + case "address": + *a = diAddress + case "string": + *a = diString + case "uint64": + *a = diUint64 + case "uint32": + *a = diUint32 + case "uint16": + *a = diUint16 + case "uint8": + *a = diUint8 + case "float": + *a = diFloat64 + case "hash": + *a = diHash + case "map": + *a = diMap + } + return nil +} + +func (a *dataOutMode) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + switch strings.ToLower(s) { + default: + return fmt.Errorf("unknow view mode:%s", s) + case "hex": + *a = doHex + case "string": + *a = doString + case "number": + *a = doNumber + case "map": + *a = doMap + case "list": + *a = doList + } + return nil +} + +type uiInput struct { + Mode dataInMode `json:"mode,omitempty"` + Title string `json:"title,omitempty"` + Key string `json:"key,omitempty"` + Value string `json:"value,omitempty"` + Hide bool `json:"hide,omitempty"` + MultiLine bool `json:"multi_line,omitempty"` + EmptyEnable bool `json:"empty_enable,omitempty"` + Sub []uiInput `json:"sub,omitempty"` + win *widget.Entry +} + +type viewOutput struct { + Mode dataOutMode `json:"mode,omitempty"` + Title string `json:"title,omitempty"` + Length int `json:"length,omitempty"` + Key string `json:"key,omitempty"` + MultiLine bool `json:"multi_line,omitempty"` + Sub []viewOutput `json:"sub,omitempty"` + win *widget.Entry +} + +type dReadUI struct { + Name string `json:"name,omitempty"` + App string `json:"app,omitempty"` + Description string `json:"description,omitempty"` + Struct string `json:"struct,omitempty"` + IsLog bool `json:"is_log,omitempty"` + Chain uint64 `json:"chain,omitempty"` + ShowExpiration bool `json:"show_expiration,omitempty"` + ShowKey bool `json:"show_key,omitempty"` + Hide bool `json:"hide,omitempty"` + Input []uiInput `json:"input,omitempty"` + View []viewOutput `json:"view,omitempty"` + winExpiration *widget.Entry + winKey *widget.Entry +} + +func addInputItem(form *widget.Form, lst []uiInput) { + for i, it := range lst { + if it.Title != "" { + ent := widget.NewEntry() + if it.MultiLine { + ent = widget.NewMultiLineEntry() + } + if it.Value != "" { + ent.SetText(it.Value) + } else if it.Mode == diAddress { + ent.SetText(conf.GetWallet().AddressStr) + } + it.win = ent + lst[i] = it + if it.Hide { + continue + } + form.Append(it.Title, ent) + } + if len(it.Sub) > 0 { + addInputItem(form, it.Sub) + } + } +} + +func addViewItem(form *widget.Form, lst []viewOutput) { + for i, it := range lst { + if it.Title != "" { + ent := widget.NewEntry() + if it.MultiLine { + ent = widget.NewMultiLineEntry() + } + ent.Disable() + form.Append(it.Title, ent) + it.win = ent + lst[i] = it + } + if len(it.Sub) > 0 { + addViewItem(form, it.Sub) + } + } +} +func showViewItemFromMap(lst []viewOutput, data map[string]interface{}) error { + for _, it := range lst { + switch it.Mode { + case doHex: + v := data[it.Key] + var in []byte + if v != nil { + d, _ := json.Marshal(v) + err := json.Unmarshal(d, &in) + if err != nil { + return err + } + } + + if it.win != nil { + it.win.SetText(hex.EncodeToString(in)) + } + if len(it.Sub) > 0 { + err := showViewItem(it.Sub, in) + if err != nil { + return err + } + } + case doMap: + v := data[it.Key] + var in map[string]interface{} + if v != nil { + d, _ := json.Marshal(v) + err := json.Unmarshal(d, &in) + if err != nil { + return err + } + } + + if it.win != nil { + d, _ := json.MarshalIndent(in, "", " ") + it.win.SetText(string(d)) + } + if len(it.Sub) > 0 { + err := showViewItemFromMap(it.Sub, in) + if err != nil { + return err + } + } + case doList: + v := data[it.Key] + var in []interface{} + if v != nil { + d, _ := json.Marshal(v) + err := json.Unmarshal(d, &in) + if err != nil { + return err + } + } + + if it.win != nil { + d, _ := json.MarshalIndent(in, "", " ") + it.win.SetText(string(d)) + } + if len(it.Sub) > 0 { + return fmt.Errorf("not support sub of list") + } + case doString: + v := data[it.Key] + var in string + if v != nil { + d, _ := json.Marshal(v) + err := json.Unmarshal(d, &in) + if err != nil { + return err + } + } + if it.win != nil { + it.win.SetText(in) + } + if len(it.Sub) > 0 { + err := showViewItem(it.Sub, []byte(in)) + if err != nil { + return err + } + } + case doNumber: + v := data[it.Key] + var in int64 + if v != nil { + d, _ := json.Marshal(v) + err := json.Unmarshal(d, &in) + if err != nil { + return err + } + } + if it.win != nil { + it.win.SetText(fmt.Sprintf("%d", in)) + } + if len(it.Sub) > 0 { + return fmt.Errorf("not support") + } + } + } + return nil +} + +func showViewItem(lst []viewOutput, data []byte) error { + for _, it := range lst { + switch it.Mode { + case doHex: + ld := data + if it.Length > 0 { + ld = data[:it.Length] + data = data[it.Length:] + } + if it.win != nil { + it.win.SetText(hex.EncodeToString(ld)) + } + if len(it.Sub) > 0 { + err := showViewItem(it.Sub, ld) + if err != nil { + return err + } + } + case doMap: + ld := data + if it.Length > 0 { + ld = data[:it.Length] + data = data[it.Length:] + } + var info map[string]interface{} + err := json.Unmarshal(ld, &info) + if err != nil { + return err + } + if it.win != nil { + d, _ := json.MarshalIndent(info, "", " ") + it.win.SetText(string(d)) + } + if len(it.Sub) > 0 { + err = showViewItemFromMap(it.Sub, info) + if err != nil { + return err + } + } + case doList: + ld := data + if it.Length > 0 { + ld = data[:it.Length] + data = data[it.Length:] + } + var info []interface{} + err := json.Unmarshal(ld, &info) + if err != nil { + return err + } + if it.win != nil { + d, _ := json.MarshalIndent(info, "", " ") + it.win.SetText(string(d)) + } + if len(it.Sub) > 0 { + return fmt.Errorf("not support") + } + case doString: + ld := data + if it.Length > 0 { + ld = data[:it.Length] + data = data[it.Length:] + } + if it.win != nil { + it.win.SetText(string(ld)) + } + if len(it.Sub) > 0 { + err := showViewItem(it.Sub, ld) + if err != nil { + return err + } + } + case doNumber: + ld := data + if it.Length > 0 { + ld = data[:it.Length] + data = data[it.Length:] + } + if it.win != nil { + var num big.Int + num.SetBytes(ld) + it.win.SetText(num.String()) + } + if len(it.Sub) > 0 { + err := showViewItem(it.Sub, ld) + if err != nil { + return err + } + } + } + } + return nil +} + +func newReadUI(w fyne.Window, in dReadUI) fyne.Widget { + inputForm := &widget.Form{} + viewForm := &widget.Form{} + var lastKey string + chainStr := fmt.Sprintf("%d", in.Chain) + if in.ShowKey { + in.winKey = widget.NewEntry() + viewForm.Append(res.GetLocalString("Key:"), in.winKey) + } + if in.ShowExpiration { + in.winExpiration = widget.NewEntry() + viewForm.Append(res.GetLocalString("Expiration:"), in.winExpiration) + } + if in.Description != "" { + inputForm.Append(res.GetLocalString("Description"), + widget.NewLabel(in.Description)) + } + + addInputItem(inputForm, in.Input) + + empty := widget.NewLabel("") + viewData := func() { + // var localKey []byte + localKey, err := encodeRunInput(in.Input) + if err != nil { + dialog.ShowError(err, w) + return + } + if len(localKey) == 0 { + dialog.ShowError(fmt.Errorf("request key"), w) + return + } + k := hex.EncodeToString(localKey) + data, life := getStringOfDB(chainStr, in.App, in.Struct, k) + if life == 0 { + dialog.ShowError(fmt.Errorf("not found"), w) + return + } + + log.Println("key:", localKey, data, life) + if in.ShowExpiration { + t := time.Unix(int64(life/1000), 0) + in.winExpiration.SetText(t.Local().String()) + } + if in.ShowKey { + in.winKey.SetText(k) + } + + hexData, _ := hex.DecodeString(data) + showViewItem(in.View, hexData) + lastKey = k + } + btnSearch := widget.NewButton(res.GetLocalString("Search"), viewData) + btnNext := widget.NewButton(res.GetLocalString("Next"), func() { + key := getNextKeyOfDB(chainStr, in.App, in.Struct, lastKey) + lastKey = key + if in.ShowKey { + in.winKey.SetText(key) + } + data, life := getStringOfDB(chainStr, in.App, in.Struct, key) + if life == 0 { + dialog.ShowError(fmt.Errorf("not found"), w) + return + } + + log.Println("key:", key, data, life) + if in.ShowExpiration { + t := time.Unix(int64(life/1000), 0) + in.winExpiration.SetText(t.Local().String()) + } + hexData, _ := hex.DecodeString(data) + showViewItem(in.View, hexData) + }) + btns := widget.NewHBox(btnNext, empty, btnSearch) + searchItem := newFormItemWithObj(empty, btns) + + addViewItem(viewForm, in.View) + + return widget.NewVBox(inputForm, searchItem, viewForm) +} + +type appRunUI struct { + Name string `json:"name,omitempty"` + App string `json:"app,omitempty"` + Description string `json:"description,omitempty"` + Chains []uint64 `json:"chains,omitempty"` + Hide bool `json:"hide,omitempty"` + Cost float64 `json:"cost,omitempty"` + Energy float64 `json:"energy,omitempty"` + Input []uiInput `json:"input,omitempty"` +} + +type uiConf struct { + ViewUI []dReadUI `json:"view_ui,omitempty"` + RunUI []appRunUI `json:"run_ui,omitempty"` +} + +func encodeRunInputMap(in []uiInput, param map[string]interface{}) error { + for _, it := range in { + var data string + if it.win != nil { + data = it.win.Text + } + switch it.Mode { + case diHex: + if data == "" { + if it.EmptyEnable || enableEmptyType[it.Mode] { + continue + } + return fmt.Errorf("empty value:%s", it.Title) + } + d, err := hex.DecodeString(data) + if err != nil { + return err + } + param[it.Key] = d + case diAddress: + fallthrough + case diString: + if data == "" { + if it.EmptyEnable || enableEmptyType[it.Mode] { + continue + } + return fmt.Errorf("empty value:%s", it.Title) + } + param[it.Key] = data + case diFloat64: + if data == "" { + if it.EmptyEnable || enableEmptyType[it.Mode] { + continue + } + return fmt.Errorf("empty value:%s", it.Title) + } + number, err := strconv.ParseFloat(data, 64) + if err != nil { + return err + } + param[it.Key] = number + case diUint64: + if data == "" { + if it.EmptyEnable || enableEmptyType[it.Mode] { + continue + } + return fmt.Errorf("empty value:%s", it.Title) + } + number, err := strconv.ParseUint(data, 10, 64) + if err != nil { + return err + } + param[it.Key] = number + case diUint32: + if data == "" { + if it.EmptyEnable || enableEmptyType[it.Mode] { + continue + } + return fmt.Errorf("empty value:%s", it.Title) + } + number, err := strconv.ParseUint(data, 10, 32) + if err != nil { + return err + } + param[it.Key] = number + case diUint16: + if data == "" { + if it.EmptyEnable || enableEmptyType[it.Mode] { + continue + } + return fmt.Errorf("empty value:%s", it.Title) + } + number, err := strconv.ParseUint(data, 10, 16) + if err != nil { + return err + } + param[it.Key] = number + case diUint8: + if data == "" { + if it.EmptyEnable || enableEmptyType[it.Mode] { + continue + } + return fmt.Errorf("empty value:%s", it.Title) + } + number, err := strconv.ParseUint(data, 10, 8) + if err != nil { + return err + } + param[it.Key] = number + case diMap: + info := make(map[string]interface{}) + err := encodeRunInputMap(it.Sub, info) + if err != nil { + return err + } + param[it.Key] = info + default: + return fmt.Errorf("unsupport:%d", it.Mode) + } + } + return nil +} + +func encodeRunInput(in []uiInput) ([]byte, error) { + var out []byte + for _, it := range in { + var lv []byte + var data string + if it.win != nil { + data = it.win.Text + } + switch it.Mode { + case diAddress: + fallthrough + case diHex: + if data == "" { + if it.EmptyEnable || enableEmptyType[it.Mode] { + continue + } + return nil, fmt.Errorf("empty value:%s", it.Title) + } + d, err := hex.DecodeString(data) + if err != nil { + return nil, err + } + lv = d + case diString: + lv = []byte(data) + case diFloat64: + if data == "" { + if it.EmptyEnable || enableEmptyType[it.Mode] { + continue + } + return nil, fmt.Errorf("empty value:%s", it.Title) + } + number, err := strconv.ParseFloat(data, 64) + if err != nil { + return nil, err + } + lv = trans.Encode(number) + case diUint64: + if data == "" { + if it.EmptyEnable || enableEmptyType[it.Mode] { + continue + } + return nil, fmt.Errorf("empty value:%s", it.Title) + } + number, err := strconv.ParseUint(data, 10, 64) + if err != nil { + return nil, err + } + lv = trans.Encode(number) + case diUint32: + if data == "" { + if it.EmptyEnable || enableEmptyType[it.Mode] { + continue + } + return nil, fmt.Errorf("empty value:%s", it.Title) + } + number, err := strconv.ParseUint(data, 10, 32) + if err != nil { + return nil, err + } + lv = trans.Encode(uint32(number)) + case diUint16: + if data == "" { + if it.EmptyEnable || enableEmptyType[it.Mode] { + continue + } + return nil, fmt.Errorf("empty value:%s", it.Title) + } + number, err := strconv.ParseUint(data, 10, 16) + if err != nil { + return nil, err + } + lv = trans.Encode(uint16(number)) + case diUint8: + if data == "" { + if it.EmptyEnable || enableEmptyType[it.Mode] { + continue + } + return nil, fmt.Errorf("empty value:%s", it.Title) + } + number, err := strconv.ParseUint(data, 10, 8) + if err != nil { + return nil, err + } + lv = trans.Encode(uint8(number)) + case diMap: + info := make(map[string]interface{}) + err := encodeRunInputMap(it.Sub, info) + if err != nil { + return nil, err + } + lv, _ = json.Marshal(info) + case diHash: + lv = wallet.GetHash(out) + out = nil + default: + return nil, fmt.Errorf("unsupport:%d", it.Mode) + } + out = append(out, lv...) + } + return out, nil +} + +func newRunUI(w fyne.Window, in appRunUI) fyne.Widget { + infoForm := &widget.Form{} + inputForm := &widget.Form{} + result := widget.NewEntry() + result.Disable() + + // chainStr := fmt.Sprintf("%d", in.Chain) + if len(in.Chains) == 0 { + in.Chains = append(in.Chains, 1) + } + var chainArr []string + for _, c := range in.Chains { + chainArr = append(chainArr, fmt.Sprintf("%d", c)) + } + chainID := in.Chains[0] + chain := widget.NewSelect(chainArr, func(s string) { + chainID, _ = strconv.ParseUint(s, 10, 64) + }) + chain.SetSelected(chainArr[0]) + + amount := widget.NewEntry() + amount.SetText("0") + eng := widget.NewEntry() + eng.SetText("0") + infoForm.Append(res.GetLocalString("Description"), + widget.NewLabel(in.Description)) + infoForm.Append(res.GetLocalString("Chain"), chain) + + if in.Cost > 0 { + amount.SetText(fmt.Sprintf("%f", in.Cost)) + infoForm.Append(res.GetLocalString("Cost"), + newFormItemWithUnit(amount, "govm")) + } + if in.Energy > 0 { + eng.SetText(fmt.Sprintf("%f", in.Energy)) + infoForm.Append(res.GetLocalString("Energy"), + newFormItemWithUnit(eng, "govm")) + } + addInputItem(inputForm, in.Input) + + inputForm.SubmitText = res.GetLocalString("Run") + inputForm.OnSubmit = func() { + result.SetText("") + var cost, energy uint64 + if amount.Text != "" { + costF, err := strconv.ParseFloat(amount.Text, 10) + if err != nil { + dialog.ShowError(fmt.Errorf("error cost"), w) + return + } + amount.SetPlaceHolder(amount.Text) + amount.SetText("") + cost = uint64(costF * 1e9) + } + if eng.Text != "" { + engF, err := strconv.ParseFloat(eng.Text, 10) + if err != nil { + dialog.ShowError(fmt.Errorf("error cost"), w) + return + } + energy = uint64(engF * 1e9) + } + + data, err := encodeRunInput(in.Input) + if err != nil { + dialog.ShowError(err, w) + return + } + log.Printf("run. chain:%d,app:%s,msg:%x\n", chainID, in.App, data) + key, err := runApp(chainID, cost, energy, in.App, "", data) + if err != nil { + dialog.ShowError(err, w) + return + } + result.SetText(key) + } + inputForm.Refresh() + + return widget.NewVBox(infoForm, inputForm, result) +} + +// CustomizeScreen Customize dApp ui +func CustomizeScreen(w fyne.Window) fyne.CanvasObject { + c := conf.Get() + tb := widget.NewTabContainer() + d, _ := ioutil.ReadFile(c.DynamicUIFile) + var info uiConf + err := json.Unmarshal(d, &info) + if err != nil { + // fmt.Println("dReadUI:", err) + tb.Append(widget.NewTabItem("error", widget.NewLabel(fmt.Sprint(err)))) + tb.SelectTabIndex(0) + return tb + } + for _, it := range info.RunUI { + if it.Hide { + continue + } + tb.Append(widget.NewTabItem("R_"+it.Name, newRunUI(w, it))) + } + for _, it := range info.ViewUI { + if it.Hide { + continue + } + tb.Append(widget.NewTabItem("V_"+it.Name, newReadUI(w, it))) + } + tb.SelectTabIndex(0) + return tb +} diff --git a/bin/gui/screens/history.go b/bin/gui/screens/history.go index 0d66568..58fa4d4 100644 --- a/bin/gui/screens/history.go +++ b/bin/gui/screens/history.go @@ -1,12 +1,8 @@ package screens import ( - "encoding/json" "fmt" - "io/ioutil" "log" - "net/http" - "strconv" "time" "fyne.io/fyne" @@ -16,68 +12,6 @@ import ( "github.com/lengzhao/wallet/bin/gui/res" ) -//statCoinLock -//statCoinUnlock -//statMiningCount -//statMinerHit -//statMinerReg -//statTransferIn -//statTransferOut -//statMove -//statAPPRun - -const coreName = "ff0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" - -type dataInfo struct { - AppName string `json:"app_name,omitempty"` - StructName string `json:"struct_name,omitempty"` - IsDBData bool `json:"is_db_data,omitempty"` - Key string `json:"key,omitempty"` - Value string `json:"value,omitempty"` - Life uint64 `json:"life,omitempty"` -} - -func getIntOfDB(chain, appName, structName, address string) uint64 { - value, _ := getStringOfDB(chain, appName, structName, address) - if value == "" { - return 0 - } - out, err := strconv.ParseUint(value, 16, 64) - if err != nil { - log.Println("parse error.", value, err) - } - - return out -} -func getStringOfDB(chain, appName, structName, address string) (string, uint64) { - if appName == "" { - appName = coreName - } - urlStr := conf.Get().APIServer - urlStr += "/api/v1/" + chain + "/data?app_name=" + appName - urlStr += "&is_db_data=true&struct_name=" + structName - urlStr += "&key=" + address - resp, err := http.Get(urlStr) - if err != nil { - log.Println("fail to get db.", urlStr, err) - return "", 0 - } - defer resp.Body.Close() - data, err := ioutil.ReadAll(resp.Body) - if resp.StatusCode != http.StatusOK || len(data) == 0 { - log.Println("fail to get db.", urlStr, resp.Status, string(data)) - return "", 0 - } - info := dataInfo{} - err = json.Unmarshal(data, &info) - if err != nil || info.Value == "" { - log.Println("not value.", urlStr) - return "", 0 - } - // log.Println("success to get:", urlStr, info.Value) - return info.Value, info.Life -} - func makeTransferOutList(w fyne.Window) fyne.Widget { var his = make(map[string]string) var numbers = make(map[string]uint64) diff --git a/bin/gui/screens/master.go b/bin/gui/screens/master.go index 9d7b2bc..2fecf96 100644 --- a/bin/gui/screens/master.go +++ b/bin/gui/screens/master.go @@ -24,6 +24,7 @@ func Master(a fyne.App) fyne.Window { widget.NewTabItemWithIcon(res.GetLocalString("Transaction"), theme.MailSendIcon(), TransactionScreen(w)), widget.NewTabItemWithIcon(res.GetLocalString("dAPP"), theme.ComputerIcon(), AppScreen(w)), widget.NewTabItemWithIcon(res.GetLocalString("Task"), theme.FileTextIcon(), TaskScreen(w)), + widget.NewTabItemWithIcon(res.GetLocalString("Customize"), theme.FileAudioIcon(), CustomizeScreen(w)), widget.NewTabItemWithIcon(res.GetLocalString("Search"), theme.SearchIcon(), SearchScreen(w)), widget.NewTabItemWithIcon(res.GetLocalString("History"), theme.ContentPasteIcon(), HistoryScreen(w)), widget.NewTabItemWithIcon(res.GetLocalString("Setting"), theme.SettingsIcon(), SettingScreen(w))) diff --git a/bin/gui/screens/task.go b/bin/gui/screens/task.go index 600c235..5006b6c 100644 --- a/bin/gui/screens/task.go +++ b/bin/gui/screens/task.go @@ -75,40 +75,6 @@ func statusToString(in int) string { } } -func runApp(chain, cost, energy uint64, app, prefix string, param []byte) (string, error) { - myWlt := conf.GetWallet() - trans := trans.NewTransaction(chain, myWlt.Address, cost) - var body []byte - var err error - if prefix != "" { - body, err = hex.DecodeString(prefix) - if err != nil { - return "", err - } - } - if len(param) > 0 { - body = append(body, param...) - } - trans.Energy = energy - err = trans.RunApp(app, body) - if err != nil { - return "", err - } - td := trans.GetSignData() - sign := myWlt.Sign(td) - trans.SetTheSign(sign) - td = trans.Output() - key := trans.Key[:] - - err = postTrans(chain, td) - if err != nil { - // result.SetText(fmt.Sprintf("%s", err)) - log.Println("fail to run app:", err) - return "", err - } - return hex.EncodeToString(key), nil -} - func makeTaskInfoTab(w fyne.Window) fyne.Widget { c := conf.Get() chain := widget.NewSelect(c.Chains, nil) @@ -390,7 +356,13 @@ func makeActionTab(w fyne.Window) fyne.Widget { clickEvent := func() { result.SetText("") - showForm.Hide() + // showForm.Hide() + actionID.SetText("") + actionUser.SetText("") + actionStatus.SetText("") + actionReward.SetText("") + actionMsg.SetText("") + id, err := strconv.ParseUint(taskIndex.Text, 10, 64) if err != nil || id == 0 { result.SetText("error task id") @@ -520,7 +492,7 @@ func makeActionTab(w fyne.Window) fyne.Widget { } } } - showForm.Show() + // showForm.Show() showForm.Refresh() } NextEvent := func() { @@ -539,7 +511,11 @@ func makeActionTab(w fyne.Window) fyne.Widget { form := &widget.Form{} form.Append(res.GetLocalString("Task ID"), taskIndex) form.Append(res.GetLocalString("Offset"), searchItem) - go clickEvent() + go func() { + num := getIntOfDB(chain.Selected, taskApp, "tApp", "0000") + taskIndex.SetText(fmt.Sprintf("%d", num)) + clickEvent() + }() return widget.NewVBox(form, widget.NewGroup(res.GetLocalString("Action Info"), showForm), result) diff --git a/bin/gui/usage.txt b/bin/gui/usage.txt new file mode 100644 index 0000000..008a0d8 --- /dev/null +++ b/bin/gui/usage.txt @@ -0,0 +1,124 @@ +1. webset: https://govm.net/ +2. default password: govm_pwd@2019 +3. Important: Please back up the private key file wallet.key +4. How to copy content: select content -> right click -> Copy +5. How to create a dApp: + 1. Sample code: https://github.com/lengzhao/dapp + 2. Open the GUI wallet: govm.exe + 3. dApp->New APP->Select chain->Code: Open->Select the app code->Submit + 4. Copy the transaction ID, you can go to "Search" -> Transaction -> Paste that transaction ID -> Submit, you can query the specific information of the transaction + 5. Wait a few minutes until the transaction is packaged +6. How to customize the UI of a dApp + 1. You can edit the custom ui file:dynamic_ui.json + 2. All custom UIs are in the "Customize" page + 1. Tab tabs beginning with V indicate the query UI + 2. The tabs beginning with R represent the UI to run App + 3. Code reference: screens/dynamic_ui.go + 3. Next, we will introduce the configuration of custom UI "dynamic_ui.json" + 4. View UI: view_ui, only query data, no cost + 1. name: the name of the label + 2. app: the name of the dAPP + 3. description: introduction information, will be displayed on the tab page + 4. struct: Each data is stored in the struct of the app, and the name of the struct is required for query + 5. chain: The chain where the data is stored + 6. show_expiration: Whether to display the expiration time of the data + 7. hide: Whether to hide the tab, if set to true, the tab will not be displayed + 8. input: Query parameter information list + 1. mode: data entry mode + 1. hex: The input is a hex string, such as "a101" + 2. address: The input is a address, which will automatically fill in the user's wallet address + 3. string: The input is a string + 4. uint64/uint32/uint16/uint8: The input is a number + 5. float: The input is a float + 6. hash: Hash of the previous input data + 7. map: The input is map[string]interface{}, which can include sub + 2. title: The title of the data, which will be displayed on the left side of the input box + 3. value: default value, can be empty + 4. hide: Whether to hide the input, if it is a fixed input, it can be hidden + 5. multi_line: whether the input box needs multiple lines + 6. empty_enable: whether the input can be empty + 7. sub: the sub-parameter of the loop, used when mode=map, the sample can check the input in run_ui + 9. view: how to display the results of the query + 1. mode: data display mode + 1. hex: convert data to hex string. E.g. address + 2. string: directly output data in string format + 3. number: Data is parsed into digital + 4. map: The data is parsed into map[string]interface{} data, which can carry sub to display specific information + 2. title: The title of the data, which will be displayed on the left side of the input box + 3. length: The length of the data, it is optional, if configured, the specified length data will be intercepted and processed + 4. key: the key of the data, used when the data is in json format + 5. multi_line: Whether to display in multiple lines + 6. sub: cycle sub-parameter, used when mode=map + 3. Run UI: run_ui, execution of dApp need energy + 1. name: The name of the label + 2. app: the name of the dAPP + 3. description: Introduction information, will be displayed on the tab + 4. chains: List of supported chains + 5. hide: Whether to hide the label, if set to true, the label will not be displayed + 6. cost:The cost required by the dAPP, the dAPP is free without setting + 7. energy: The energy for executing the dAPP (to the miner), if not set, the default energy will be used + 8. input: List of parameter information for executing the contract + Specific configuration refer to view_ui->input + +Simplified Chinese, zh: 中文 +1. 官网:https://govm.net/ +2. 默认密码:govm_pwd@2019 +3. 重要:请备份好私钥文件wallet.key +4. 如何复制内容:选择内容->右键->Copy +5. 如何创建dApp: + 1. 样例代码:https://github.com/lengzhao/dapp + 2. 打开GUI钱包:govm.exe + 3. dApp->New APP->Select chain->Code: Open->Select the app code->Submit + 4. 复制交易ID,可以在“查询”->交易->粘贴那个交易ID->Submit,可以查询交易的具体信息 + 5. 等几分钟,直到交易被打包 +6. 如何自定义dApp的UI + 1. 请编辑自定义ui的文件:dynamic_ui.json + 2. 所有的自定义UI在“自定义”页面中 + 1. 以V开头的标签页表示查询UI + 2. 以R开头的标签页表示运行App的UI + 3. 代码参考:screens/dynamic_ui.go + 3. 接下来将介绍自定义UI的配置dynamic_ui.json + 4. 查询UI:view_ui,只查询数据,不需要费用 + 1. name:标签的名字 + 2. app:dAPP的名字 + 3. description:介绍信息,将显示在标签页上 + 4. struct:每个数据都是存储在app的struct中,查询时需要struct的名字 + 5. chain:数据所在的chain + 6. show_expiration:是否显示数据的过期时间 + 7. hide:是否隐藏标签,如果设置为true,该标签将不显示 + 8. input:查询的参数信息列表 + 1. mode:数据的入参模式 + 1. hex:输入的为hex字符串,如“a101” + 2. address:入参为地址,将自动填充用户的钱包地址 + 3. string:输入的就是字符串 + 4. uint64/uint32/uint16/uint8:输入的为数字 + 5. float:输入的为浮点数 + 6. hash:对前面的输入的数据进行哈希计算 + 7. map:输入的是map[string]interface{},需要携带sub + 2. title:数据的标题,将显示在输入框的左侧 + 3. value:默认值,可以为空 + 4. hide:是否隐藏该配置,如果是固定的输入,可以隐藏 + 5. multi_line:输入框是否需要多行 + 6. empty_enable:输入是否可以为空 + 7. sub:循环的子参数,mode=map时使用,样例可以查看run_ui中的input + 9. view:查询得到的结果的显示方式 + 1. mode:数据的显示模式 + 1. hex:将数据转为hex string + 2. string:直接将数据以string格式输出 + 3. number:数据直接解析为数字显示 + 4. map:数据解析为map[string]interface{}数据,可以携带sub进一步显示具体信息 + 2. title:数据的标题,将显示在输入框的左侧 + 3. length:数据的长度,可以不配置,如果配置,将截取指定长度,进行处理 + 4. key:数据的key,当数据为json格式时使用 + 5. multi_line:是否多行显示 + 6. sub:循环的子参数,mode=map时使用 + 3. 执行UI:run_ui,执行dApp需要费用 + 1. name:标签的名字 + 2. app:dAPP的名字 + 3. description:介绍信息,将显示在标签页上 + 4. chains:支持的chain列表 + 5. hide:是否隐藏标签,如果设置为true,该标签将不显示 + 6. cost:合约要求的费用,没有设置表示合约免费 + 7. energy:执行合约的手续费(给矿工的),不设置将使用默认手续费 + 8. input:执行合约的参数信息列表 + 具体的配置参考view_ui->input