From 7757de4173d2d31367c828e1f88864d3beec40f2 Mon Sep 17 00:00:00 2001 From: Ken Hibino Date: Sun, 22 May 2022 20:33:50 -0700 Subject: [PATCH] (cli): Add modal --- tools/asynq/cmd/dash/dash.go | 1 + tools/asynq/cmd/dash/draw.go | 72 +++++++++++++++++++++++++++ tools/asynq/cmd/dash/draw_test.go | 33 ++++++++++++ tools/asynq/cmd/dash/screen_drawer.go | 6 +++ 4 files changed, 112 insertions(+) create mode 100644 tools/asynq/cmd/dash/draw_test.go diff --git a/tools/asynq/cmd/dash/dash.go b/tools/asynq/cmd/dash/dash.go index c6913e8..bf6d47a 100644 --- a/tools/asynq/cmd/dash/dash.go +++ b/tools/asynq/cmd/dash/dash.go @@ -40,6 +40,7 @@ type State struct { selectedQueue *asynq.QueueInfo // queue shown on queue details view selectedGroup *asynq.GroupInfo + selectedTask *asynq.TaskInfo pageNum int // pagination page number diff --git a/tools/asynq/cmd/dash/draw.go b/tools/asynq/cmd/dash/draw.go index ded6c5d..8df1123 100644 --- a/tools/asynq/cmd/dash/draw.go +++ b/tools/asynq/cmd/dash/draw.go @@ -49,6 +49,7 @@ func (dd *dashDrawer) draw(state *State) { drawTaskStateBreakdown(d, baseStyle, state) d.NL() drawTaskTable(d, state) + drawTaskModal(d, state) case viewTypeServers: d.Println("=== Servers ===", baseStyle.Bold(true)) d.NL() @@ -439,3 +440,74 @@ func drawTaskStateBreakdown(d *ScreenDrawer, style tcell.Style, state *State) { } d.NL() } + +func drawTaskModal(d *ScreenDrawer, state *State) { + contents := []*rowContent{ + {" === Task Summary ===", baseStyle.Bold(true)}, + {"", baseStyle}, + {" ID: xxxxx", baseStyle}, + {" Type: xxxxx", baseStyle}, + {" State: xxxx", baseStyle}, + } + withModal(d, contents) +} + +type rowContent struct { + s string // should not include newline + style tcell.Style +} + +func withModal(d *ScreenDrawer, contents []*rowContent) { + modalStyle := baseStyle //.Background(tcell.ColorDarkMagenta) + w, h := d.Screen().Size() + var ( + modalWidth = int(math.Floor(float64(w) * 0.6)) + modalHeight = int(math.Floor(float64(h) * 0.6)) + rowOffset = int(math.Floor(float64(h) * 0.2)) // 20% from the top + colOffset = int(math.Floor(float64(w) * 0.2)) // 20% from the left + ) + if modalHeight < 3 { + return // no content can be shown + } + d.Goto(colOffset, rowOffset) + d.Print(string(tcell.RuneULCorner), modalStyle) + d.Print(strings.Repeat(string(tcell.RuneHLine), modalWidth-2), modalStyle) + d.Print(string(tcell.RuneURCorner), modalStyle) + d.NL() + for i := 1; i < modalHeight-1; i++ { + d.Goto(colOffset, rowOffset+i) + d.Print(string(tcell.RuneVLine), modalStyle) + cnt := &rowContent{strings.Repeat(" ", modalWidth-2), baseStyle} + if i <= len(contents) { + cnt = contents[i-1] + cnt.s = adjustWidth(cnt.s, modalWidth-2) + } + d.Print(truncate(cnt.s, modalWidth-2), cnt.style) + d.Print(string(tcell.RuneVLine), modalStyle) + d.NL() + } + d.Goto(colOffset, rowOffset+modalHeight-1) + d.Print(string(tcell.RuneLLCorner), modalStyle) + d.Print(strings.Repeat(string(tcell.RuneHLine), modalWidth-2), modalStyle) + d.Print(string(tcell.RuneLRCorner), modalStyle) + d.NL() +} + +func adjustWidth(s string, width int) string { + sw := runewidth.StringWidth(s) + if sw > width { + return truncate(s, width) + } + var b strings.Builder + b.WriteString(s) + b.WriteString(strings.Repeat(" ", width-sw)) + return b.String() +} + +// truncates s if s exceeds max length. +func truncate(s string, max int) string { + if runewidth.StringWidth(s) <= max { + return s + } + return string([]rune(s)[:max-1]) + "…" +} diff --git a/tools/asynq/cmd/dash/draw_test.go b/tools/asynq/cmd/dash/draw_test.go new file mode 100644 index 0000000..a7a42eb --- /dev/null +++ b/tools/asynq/cmd/dash/draw_test.go @@ -0,0 +1,33 @@ +// Copyright 2022 Kentaro Hibino. All rights reserved. +// Use of this source code is governed by a MIT license +// that can be found in the LICENSE file. + +package dash + +import "testing" + +func TestTruncate(t *testing.T) { + tests := []struct { + s string + max int + want string + }{ + { + s: "hello world!", + max: 15, + want: "hello world!", + }, + { + s: "hello world!", + max: 6, + want: "hello…", + }, + } + + for _, tc := range tests { + got := truncate(tc.s, tc.max) + if tc.want != got { + t.Errorf("truncate(%q, %d) = %q, want %q", tc.s, tc.max, got, tc.want) + } + } +} diff --git a/tools/asynq/cmd/dash/screen_drawer.go b/tools/asynq/cmd/dash/screen_drawer.go index afb37df..dfe2eb4 100644 --- a/tools/asynq/cmd/dash/screen_drawer.go +++ b/tools/asynq/cmd/dash/screen_drawer.go @@ -57,6 +57,12 @@ func (d *ScreenDrawer) Screen() tcell.Screen { return d.l.s } +// Goto moves the screendrawer to the specified cell. +func (d *ScreenDrawer) Goto(x, y int) { + d.l.row = y + d.l.col = x +} + // Go to the bottom of the screen. func (d *ScreenDrawer) GoToBottom() { _, h := d.Screen().Size()