console.go raw
1 package gui
2
3 import (
4 "encoding/json"
5 "fmt"
6 "regexp"
7 "sort"
8 "strconv"
9 "strings"
10
11 "github.com/atotto/clipboard"
12 "golang.org/x/exp/shiny/materialdesign/icons"
13
14 l "github.com/p9c/p9/pkg/gel/gio/layout"
15 ctl2 "github.com/p9c/p9/cmd/ctl"
16
17 icons2 "golang.org/x/exp/shiny/materialdesign/icons"
18
19 "github.com/p9c/p9/pkg/gel"
20 )
21
22 type Console struct {
23 *gel.Window
24 output []l.Widget
25 outputList *gel.List
26 editor *gel.Editor
27 clearClickable *gel.Clickable
28 clearButton *gel.IconButton
29 copyClickable *gel.Clickable
30 copyButton *gel.IconButton
31 pasteClickable *gel.Clickable
32 pasteButton *gel.IconButton
33 submitFunc func(txt string)
34 clickables []*gel.Clickable
35 }
36
37 var findSpaceRegexp = regexp.MustCompile(`\s+`)
38
39 func (wg *WalletGUI) ConsolePage() *Console {
40 D.Ln("running ConsolePage")
41 c := &Console{
42 Window: wg.Window,
43 editor: wg.Editor().SingleLine().Submit(true),
44 clearClickable: wg.Clickable(),
45 copyClickable: wg.Clickable(),
46 pasteClickable: wg.Clickable(),
47 outputList: wg.List().ScrollToEnd(),
48 }
49 c.submitFunc = func(txt string) {
50 go func() {
51 D.Ln("submit", txt)
52 c.output = append(
53 c.output,
54 func(gtx l.Context) l.Dimensions {
55 return wg.VFlex().
56 Rigid(wg.Inset(0.25, gel.EmptySpace(0, 0)).Fn).
57 Rigid(
58 wg.Flex().
59 Flexed(
60 1,
61 wg.Body2(txt).Color("DocText").Font("bariol bold").Fn,
62 ).
63 Fn,
64 ).Fn(gtx)
65 },
66 )
67 c.editor.SetText("")
68 split := strings.Split(txt, " ")
69 method, args := split[0], split[1:]
70 var params []interface{}
71 var e error
72 var result []byte
73 var o string
74 var errString, prev string
75 for i := range args {
76 params = append(params, args[i])
77 }
78 if method == "clear" || method == "cls" {
79 // clear the list of display widgets
80 c.output = c.output[:0]
81 // free up the pool widgets used in the current output
82 for i := range c.clickables {
83 wg.WidgetPool.FreeClickable(c.clickables[i])
84 }
85 c.clickables = c.clickables[:0]
86 return
87 }
88 if method == "help" {
89 if len(args) == 0 {
90 D.Ln("rpc called help")
91 var result1, result2 []byte
92 if result1, e = ctl2.Call(wg.cx.Config, false, method, params...); E.Chk(e) {
93 }
94 r1 := string(result1)
95 if r1, e = strconv.Unquote(r1); E.Chk(e) {
96 }
97 o = r1 + "\n"
98 if result2, e = ctl2.Call(wg.cx.Config, true, method, params...); E.Chk(e) {
99 }
100 r2 := string(result2)
101 if r2, e = strconv.Unquote(r2); E.Chk(e) {
102 }
103 o += r2 + "\n"
104 splitted := strings.Split(o, "\n")
105 sort.Strings(splitted)
106 var dedup []string
107 for i := range splitted {
108 if i > 0 {
109 if splitted[i] != prev {
110 dedup = append(dedup, splitted[i])
111 }
112 }
113 prev = splitted[i]
114 }
115 o = strings.Join(dedup, "\n")
116 if errString != "" {
117 o += "BTCJSONError:\n"
118 o += errString
119 }
120 splitResult := strings.Split(o, "\n")
121 const maxPerWidget = 6
122 for i := 0; i < len(splitResult)-maxPerWidget; i += maxPerWidget {
123 sri := strings.Join(splitResult[i:i+maxPerWidget], "\n")
124 c.output = append(
125 c.output,
126 func(gtx l.Context) l.Dimensions {
127 return wg.Flex().
128 Flexed(
129 1,
130 wg.Caption(sri).
131 Color("DocText").
132 Font("bariol regular").
133 MaxLines(maxPerWidget).Fn,
134 ).
135 Fn(gtx)
136 },
137 )
138 }
139 return
140 } else {
141 var out string
142 var isErr bool
143 if result, e = ctl2.Call(wg.cx.Config, false, method, params...); E.Chk(e) {
144 isErr = true
145 out = e.Error()
146 I.Ln(out)
147 // if out, e = strconv.Unquote(); E.Chk(e) {
148 // }
149 } else {
150 if out, e = strconv.Unquote(string(result)); E.Chk(e) {
151 }
152 }
153 strings.ReplaceAll(out, "\t", " ")
154 D.Ln(out)
155 splitResult := strings.Split(out, "\n")
156 outputColor := "DocText"
157 if isErr {
158 outputColor = "Danger"
159 }
160 for i := range splitResult {
161 sri := splitResult[i]
162 c.output = append(
163 c.output,
164 func(gtx l.Context) l.Dimensions {
165 return c.Theme.Flex().AlignStart().
166 Rigid(
167 wg.Body2(sri).
168 Color(outputColor).
169 Font("go regular").MaxLines(4).
170 Fn,
171 ).
172 Fn(gtx)
173 },
174 )
175 }
176 return
177 }
178 } else {
179 D.Ln("method", method, "args", args)
180 if result, e = ctl2.Call(wg.cx.Config, false, method, params...); E.Chk(e) {
181 var errR string
182 if result, e = ctl2.Call(wg.cx.Config, true, method, params...); E.Chk(e) {
183 if e != nil {
184 errR = e.Error()
185 }
186 c.output = append(
187 c.output, c.Theme.Flex().AlignStart().
188 Rigid(wg.Body2(errR).Color("Danger").Fn).Fn,
189 )
190 return
191 }
192 if e != nil {
193 errR = e.Error()
194 }
195 c.output = append(
196 c.output, c.Theme.Flex().AlignStart().
197 Rigid(
198 wg.Body2(errR).Color("Danger").Fn,
199 ).Fn,
200 )
201 }
202 c.output = append(c.output, wg.console.JSONWidget("DocText", result)...)
203 }
204 c.outputList.JumpToEnd()
205 }()
206 }
207 clearClickableFn := func() {
208 c.editor.SetText("")
209 c.editor.Focus()
210 }
211 copyClickableFn := func() {
212 go func() {
213 if e := clipboard.WriteAll(c.editor.Text()); E.Chk(e) {
214 }
215 }()
216 c.editor.Focus()
217 }
218 pasteClickableFn := func() {
219 // // col := c.editor.Caret.Col
220 // go func() {
221 // txt := c.editor.Text()
222 // var e error
223 // var cb string
224 // if cb, e = clipboard.ReadAll(); E.Chk(e) {
225 // }
226 // cb = findSpaceRegexp.ReplaceAllString(cb, " ")
227 // txt = txt[:col] + cb + txt[col:]
228 // c.editor.SetText(txt)
229 // c.editor.Move(col + len(cb))
230 // }()
231 }
232 c.clearButton = wg.IconButton(c.clearClickable.SetClick(clearClickableFn)).
233 Icon(
234 wg.Icon().
235 Color("DocText").
236 Src(&icons2.ContentBackspace),
237 ).
238 Background("").
239 ButtonInset(0.25)
240 c.copyButton = wg.IconButton(c.copyClickable.SetClick(copyClickableFn)).
241 Icon(
242 wg.Icon().
243 Color("DocText").
244 Src(&icons2.ContentContentCopy),
245 ).
246 Background("").
247 ButtonInset(0.25)
248 c.pasteButton = wg.IconButton(c.pasteClickable.SetClick(pasteClickableFn)).
249 Icon(
250 wg.Icon().
251 Color("DocText").
252 Src(&icons2.ContentContentPaste),
253 ).
254 Background("").
255 ButtonInset(0.25)
256 c.output = append(
257 c.output, func(gtx l.Context) l.Dimensions {
258 return c.Theme.Flex().AlignStart().Rigid(c.H6("Welcome to the Parallelcoin RPC console").Color("DocText").Fn).Fn(gtx)
259 }, func(gtx l.Context) l.Dimensions {
260 return c.Theme.Flex().AlignStart().Rigid(c.Caption("Type 'help' to get available commands and 'clear' or 'cls' to clear the screen").Color("DocText").Fn).Fn(gtx)
261 },
262 )
263 return c
264 }
265
266 func (c *Console) Fn(gtx l.Context) l.Dimensions {
267 le := func(gtx l.Context, index int) l.Dimensions {
268 if index >= len(c.output) || index < 0 {
269 return l.Dimensions{}
270 } else {
271 return c.output[index](gtx)
272 }
273 }
274 fn := c.Theme.VFlex().
275 Flexed(
276 0.1,
277 c.Fill(
278 "PanelBg", l.Center, c.TextSize.V, 0, func(gtx l.Context) l.Dimensions {
279 return c.Inset(
280 0.25,
281 c.outputList.
282 ScrollToEnd().
283 End().
284 Background("PanelBg").
285 Color("DocBg").
286 Active("Primary").
287 Vertical().
288 Length(len(c.output)).
289 ListElement(le).
290 Fn,
291 ).
292 Fn(gtx)
293 },
294 ).Fn,
295 ).
296 Rigid(
297 c.Fill(
298 "DocBg", l.Center, c.TextSize.V, 0, c.Inset(
299 0.25,
300 c.Theme.Flex().
301 Flexed(
302 1,
303 c.TextInput(c.editor.SetSubmit(c.submitFunc), "enter an rpc command").
304 Color("DocText").
305 Fn,
306 ).
307 Rigid(c.copyButton.Fn).
308 Rigid(c.pasteButton.Fn).
309 Rigid(c.clearButton.Fn).
310 Fn,
311 ).Fn,
312 ).Fn,
313 ).
314 Fn
315 return fn(gtx)
316 }
317
318 type JSONElement struct {
319 key string
320 value interface{}
321 }
322
323 type JSONElements []JSONElement
324
325 func (je JSONElements) Len() int {
326 return len(je)
327 }
328
329 func (je JSONElements) Less(i, j int) bool {
330 return je[i].key < je[j].key
331 }
332
333 func (je JSONElements) Swap(i, j int) {
334 je[i], je[j] = je[j], je[i]
335 }
336
337 func GetJSONElements(in map[string]interface{}) (je JSONElements) {
338 for i := range in {
339 je = append(
340 je, JSONElement{
341 key: i,
342 value: in[i],
343 },
344 )
345 }
346 sort.Sort(je)
347 return
348 }
349
350 func (c *Console) getIndent(n int, size float32, widget l.Widget) (out l.Widget) {
351 o := c.Theme.Flex()
352 for i := 0; i < n; i++ {
353 o.Rigid(c.Inset(size/2, gel.EmptySpace(0, 0)).Fn)
354 }
355 o.Rigid(widget)
356 out = o.Fn
357 return
358 }
359
360 func (c *Console) JSONWidget(color string, j []byte) (out []l.Widget) {
361 var ifc interface{}
362 var e error
363 if e = json.Unmarshal(j, &ifc); E.Chk(e) {
364 }
365 return c.jsonWidget(color, 0, "", ifc)
366 }
367
368 func (c *Console) jsonWidget(color string, depth int, key string, in interface{}) (out []l.Widget) {
369 switch in.(type) {
370 case []interface{}:
371 if key != "" {
372 out = append(
373 out, c.getIndent(
374 depth, 1,
375 func(gtx l.Context) l.Dimensions {
376 return c.Body2(key).Font("bariol bold").Color(color).Fn(gtx)
377 },
378 ),
379 )
380 }
381 D.Ln("got type []interface{}")
382 res := in.([]interface{})
383 if len(res) == 0 {
384 out = append(
385 out, c.getIndent(
386 depth+1, 1,
387 func(gtx l.Context) l.Dimensions {
388 return c.Body2("[]").Color(color).Fn(gtx)
389 },
390 ),
391 )
392 } else {
393 for i := range res {
394 // D.S(res[i])
395 out = append(out, c.jsonWidget(color, depth+1, fmt.Sprint(i), res[i])...)
396 }
397 }
398 case map[string]interface{}:
399 if key != "" {
400 out = append(
401 out, c.getIndent(
402 depth, 1,
403 func(gtx l.Context) l.Dimensions {
404 return c.Body2(key).Font("bariol bold").Color(color).Fn(gtx)
405 },
406 ),
407 )
408 }
409 D.Ln("got type map[string]interface{}")
410 res := in.(map[string]interface{})
411 je := GetJSONElements(res)
412 // D.S(je)
413 if len(res) == 0 {
414 out = append(
415 out, c.getIndent(
416 depth+1, 1,
417 func(gtx l.Context) l.Dimensions {
418 return c.Body2("{}").Color(color).Fn(gtx)
419 },
420 ),
421 )
422 } else {
423 for i := range je {
424 D.S(je[i])
425 out = append(out, c.jsonWidget(color, depth+1, je[i].key, je[i].value)...)
426 }
427 }
428 case JSONElement:
429 res := in.(JSONElement)
430 key = res.key
431 switch res.value.(type) {
432 case string:
433 D.Ln("got type string")
434 res := res.value.(string)
435 clk := c.Theme.WidgetPool.GetClickable()
436 out = append(
437 out,
438 c.jsonElement(
439 key, color, depth, func(gtx l.Context) l.Dimensions {
440 return c.Theme.Flex().
441 Rigid(c.Body2("\"" + res + "\"").Color(color).Fn).
442 Rigid(c.Inset(0.25, gel.EmptySpace(0, 0)).Fn).
443 Rigid(
444 c.IconButton(clk).
445 Background("").
446 ButtonInset(0).
447 Color(color).
448 Icon(c.Icon().Color("DocBg").Scale(1).Src(&icons.ContentContentCopy)).
449 SetClick(
450 func() {
451 go func() {
452 if e := clipboard.WriteAll(res); E.Chk(e) {
453 }
454 }()
455 },
456 ).Fn,
457 ).Fn(gtx)
458 },
459 ),
460 )
461 case float64:
462 D.Ln("got type float64")
463 res := res.value.(float64)
464 clk := c.Theme.WidgetPool.GetClickable()
465 out = append(
466 out,
467 c.jsonElement(
468 key, color, depth, func(gtx l.Context) l.Dimensions {
469 return c.Theme.Flex().
470 Rigid(c.Body2(fmt.Sprint(res)).Color(color).Fn).
471 Rigid(c.Inset(0.25, gel.EmptySpace(0, 0)).Fn).
472 Rigid(
473 c.IconButton(clk).
474 Background("").
475 ButtonInset(0).
476 Color(color).
477 Icon(c.Icon().Color("DocBg").Scale(1).Src(&icons.ContentContentCopy)).
478 SetClick(
479 func() {
480 go func() {
481 if e := clipboard.WriteAll(fmt.Sprint(res)); E.Chk(e) {
482 }
483 }()
484 },
485 ).Fn,
486 ).Fn(gtx)
487 // return c.th.ButtonLayout(clk).Embed(c.th.Body2().Color(color).Fn).Fn(gtx)
488 },
489 ),
490 )
491 case bool:
492 D.Ln("got type bool")
493 res := res.value.(bool)
494 out = append(
495 out,
496 c.jsonElement(
497 key, color, depth, func(gtx l.Context) l.Dimensions {
498 return c.Body2(fmt.Sprint(res)).Color(color).Fn(gtx)
499 },
500 ),
501 )
502 }
503 case string:
504 D.Ln("got type string")
505 res := in.(string)
506 clk := c.Theme.WidgetPool.GetClickable()
507 out = append(
508 out,
509 c.jsonElement(
510 key, color, depth, func(gtx l.Context) l.Dimensions {
511 return c.Theme.Flex().
512 Rigid(c.Body2("\"" + res + "\"").Color(color).Fn).
513 Rigid(c.Inset(0.25, gel.EmptySpace(0, 0)).Fn).
514 Rigid(
515 c.IconButton(clk).
516 Background("").
517 ButtonInset(0).
518 Color(color).
519 Icon(c.Icon().Color("DocBg").Scale(1).Src(&icons.ContentContentCopy)).
520 SetClick(
521 func() {
522 go func() {
523 if e := clipboard.WriteAll(res); E.Chk(e) {
524 }
525 }()
526 },
527 ).Fn,
528 ).Fn(gtx)
529 },
530 ),
531 )
532 case float64:
533 D.Ln("got type float64")
534 res := in.(float64)
535 clk := c.Theme.WidgetPool.GetClickable()
536 out = append(
537 out,
538 c.jsonElement(
539 key, color, depth, func(gtx l.Context) l.Dimensions {
540 return c.Theme.Flex().
541 Rigid(c.Body2(fmt.Sprint(res)).Color(color).Fn).
542 Rigid(c.Inset(0.25, gel.EmptySpace(0, 0)).Fn).
543 Rigid(
544 c.IconButton(clk).
545 Background("").
546 ButtonInset(0).
547 Color(color).
548 Icon(c.Icon().Color("DocBg").Scale(1).Src(&icons.ContentContentCopy)).
549 SetClick(
550 func() {
551 go func() {
552 if e := clipboard.WriteAll(fmt.Sprint(res)); E.Chk(e) {
553 }
554 }()
555 },
556 ).Fn,
557 ).Fn(gtx)
558 // return c.th.ButtonLayout(clk).Embed(c.th.Body2(fmt.Sprint(res)).Color(color).Fn).Fn(gtx)
559 },
560 ),
561 )
562 case bool:
563 D.Ln("got type bool")
564 res := in.(bool)
565 out = append(
566 out,
567 c.jsonElement(
568 key, color, depth, func(gtx l.Context) l.Dimensions {
569 return c.Body2(fmt.Sprint(res)).Color(color).Fn(gtx)
570 },
571 ),
572 )
573 default:
574 D.S(in)
575 }
576 return
577 }
578
579 func (c *Console) jsonElement(key, color string, depth int, w l.Widget) l.Widget {
580 return func(gtx l.Context) l.Dimensions {
581 return c.Theme.Flex().
582 Rigid(
583 c.getIndent(
584 depth, 1,
585 c.Body2(key).Font("bariol bold").Color(color).Fn,
586 ),
587 ).
588 Rigid(c.Inset(0.125, gel.EmptySpace(0, 0)).Fn).
589 Rigid(w).
590 Fn(gtx)
591 }
592 }
593