admin.go (view raw)
1//
2// Copyright (c) 2019 Ted Unangst <tedu@tedunangst.com>
3//
4// Permission to use, copy, modify, and distribute this software for any
5// purpose with or without fee is hereby granted, provided that the above
6// copyright notice and this permission notice appear in all copies.
7//
8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
16package main
17
18/*
19#include <termios.h>
20void
21clearecho(struct termios *tio)
22{
23 tio->c_lflag = tio->c_lflag & ~(ECHO|ICANON);
24}
25*/
26import "C"
27import (
28 "bufio"
29 "fmt"
30 "os"
31 "os/signal"
32 "strings"
33
34 "humungus.tedunangst.com/r/webs/log"
35)
36
37func adminscreen() {
38 log.Init(log.Options{Progname: "honk", Alllogname: "null"})
39 stdout := bufio.NewWriter(os.Stdout)
40 esc := "\x1b"
41 smcup := esc + "[?1049h"
42 rmcup := esc + "[?1049l"
43
44 var avatarColors string
45 getconfig("avatarcolors", &avatarColors)
46 loadLingo()
47
48 type adminfield struct {
49 name string
50 label string
51 text string
52 oneline bool
53 }
54
55 messages := []*adminfield{
56 {
57 name: "servermsg",
58 label: "server",
59 text: string(serverMsg),
60 },
61 {
62 name: "aboutmsg",
63 label: "about",
64 text: string(aboutMsg),
65 },
66 {
67 name: "loginmsg",
68 label: "login",
69 text: string(loginMsg),
70 },
71 {
72 name: "avatarcolors",
73 label: "avatar colors (4 RGBA hex numbers)",
74 text: string(avatarColors),
75 oneline: true,
76 },
77 }
78 for _, l := range []string{"honked", "bonked", "honked back", "qonked", "evented"} {
79 messages = append(messages, &adminfield{
80 name: "lingo-" + strings.ReplaceAll(l, " ", ""),
81 label: "lingo for " + l,
82 text: relingo[l],
83 oneline: true,
84 })
85 }
86 cursel := 0
87
88 hidecursor := func() {
89 stdout.WriteString(esc + "[?25l")
90 }
91 showcursor := func() {
92 stdout.WriteString(esc + "[?12;25h")
93 }
94 movecursor := func(x, y int) {
95 stdout.WriteString(fmt.Sprintf(esc+"[%d;%dH", y, x))
96 }
97 moveleft := func() {
98 stdout.WriteString(esc + "[1D")
99 }
100 clearscreen := func() {
101 stdout.WriteString(esc + "[2J")
102 }
103 //clearline := func() { stdout.WriteString(esc + "[2K") }
104 colorfn := func(code int) func(string) string {
105 return func(s string) string {
106 return fmt.Sprintf(esc+"[%dm"+"%s"+esc+"[0m", code, s)
107 }
108 }
109 reverse := colorfn(7)
110 magenta := colorfn(35)
111 readchar := func() byte {
112 var buf [1]byte
113 os.Stdin.Read(buf[:])
114 c := buf[0]
115 return c
116 }
117
118 savedtio := new(C.struct_termios)
119 C.tcgetattr(1, savedtio)
120 restore := func() {
121 stdout.WriteString(rmcup)
122 showcursor()
123 stdout.Flush()
124 C.tcsetattr(1, C.TCSAFLUSH, savedtio)
125 }
126 defer restore()
127 go func() {
128 sig := make(chan os.Signal, 1)
129 signal.Notify(sig, os.Interrupt)
130 <-sig
131 restore()
132 os.Exit(0)
133 }()
134
135 init := func() {
136 tio := new(C.struct_termios)
137 C.tcgetattr(1, tio)
138 C.clearecho(tio)
139 C.tcsetattr(1, C.TCSADRAIN, tio)
140
141 hidecursor()
142 stdout.WriteString(smcup)
143 clearscreen()
144 movecursor(1, 1)
145 stdout.Flush()
146 }
147
148 editing := false
149
150 linecount := func(s string) int {
151 lines := 1
152 for i := range s {
153 if s[i] == '\n' {
154 lines++
155 }
156 }
157 return lines
158 }
159
160 msglineno := func(idx int) int {
161 off := 1
162 if idx == -1 {
163 return off
164 }
165 for i, m := range messages {
166 off += 1
167 if i == idx {
168 return off
169 }
170 if !m.oneline {
171 off += 1
172 off += linecount(m.text)
173 }
174 }
175 off += 2
176 return off
177 }
178
179 forscreen := func(s string) string {
180 return strings.Replace(s, "\n", "\n ", -1)
181 }
182
183 drawmessage := func(idx int) {
184 line := msglineno(idx)
185 movecursor(4, line)
186 label := messages[idx].label
187 if idx == cursel {
188 label = reverse(label)
189 }
190 label = magenta(label)
191 text := forscreen(messages[idx].text)
192 if messages[idx].oneline {
193 stdout.WriteString(fmt.Sprintf("%s\t %s", label, text))
194 } else {
195 stdout.WriteString(fmt.Sprintf("%s\n %s", label, text))
196 }
197 }
198
199 drawscreen := func() {
200 clearscreen()
201 movecursor(4, msglineno(-1))
202 stdout.WriteString(magenta(serverName + " admin panel"))
203 for i := range messages {
204 if !editing || i != cursel {
205 drawmessage(i)
206 }
207 }
208 movecursor(4, msglineno(len(messages)))
209 dir := "j/k to move - q to quit - enter to edit"
210 if editing {
211 dir = "esc to end"
212 }
213 stdout.WriteString(magenta(dir))
214 if editing {
215 drawmessage(cursel)
216 }
217 stdout.Flush()
218 }
219
220 selectnext := func() {
221 if cursel < len(messages)-1 {
222 movecursor(4, msglineno(cursel))
223 stdout.WriteString(magenta(messages[cursel].label))
224 cursel++
225 movecursor(4, msglineno(cursel))
226 stdout.WriteString(reverse(magenta(messages[cursel].label)))
227 stdout.Flush()
228 }
229 }
230 selectprev := func() {
231 if cursel > 0 {
232 movecursor(4, msglineno(cursel))
233 stdout.WriteString(magenta(messages[cursel].label))
234 cursel--
235 movecursor(4, msglineno(cursel))
236 stdout.WriteString(reverse(magenta(messages[cursel].label)))
237 stdout.Flush()
238 }
239 }
240 editsel := func() {
241 editing = true
242 showcursor()
243 drawscreen()
244 m := messages[cursel]
245 loop:
246 for {
247 c := readchar()
248 switch c {
249 case '\x1b':
250 break loop
251 case '\n':
252 if m.oneline {
253 break loop
254 }
255 m.text += "\n"
256 drawscreen()
257 case 127:
258 if len(m.text) > 0 {
259 last := m.text[len(m.text)-1]
260 m.text = m.text[:len(m.text)-1]
261 if last == '\n' {
262 drawscreen()
263 } else {
264 moveleft()
265 stdout.WriteString(" ")
266 moveleft()
267 }
268 }
269 default:
270 m.text += string(c)
271 stdout.WriteString(string(c))
272 }
273 stdout.Flush()
274 }
275 editing = false
276 setconfig(m.name, m.text)
277 hidecursor()
278 drawscreen()
279 }
280
281 init()
282 drawscreen()
283
284 for {
285 c := readchar()
286 switch c {
287 case 'q':
288 return
289 case 'j':
290 selectnext()
291 case 'k':
292 selectprev()
293 case '\n':
294 editsel()
295 default:
296
297 }
298 }
299}