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
35func adminscreen() {
36 initLogging("null", "null", "null")
37 stdout := bufio.NewWriter(os.Stdout)
38 esc := "\x1b"
39 smcup := esc + "[?1049h"
40 rmcup := esc + "[?1049l"
41
42 var avatarColors string
43 getconfig("avatarcolors", &avatarColors)
44 loadLingo()
45
46 type adminfield struct {
47 name string
48 label string
49 text string
50 oneline bool
51 }
52
53 messages := []*adminfield{
54 {
55 name: "servermsg",
56 label: "server",
57 text: string(serverMsg),
58 },
59 {
60 name: "aboutmsg",
61 label: "about",
62 text: string(aboutMsg),
63 },
64 {
65 name: "loginmsg",
66 label: "login",
67 text: string(loginMsg),
68 },
69 {
70 name: "avatarcolors",
71 label: "avatar colors (4 RGBA hex numbers)",
72 text: string(avatarColors),
73 oneline: true,
74 },
75 }
76 for _, l := range []string{"honked", "bonked", "honked back", "qonked", "evented"} {
77 messages = append(messages, &adminfield{
78 name: "lingo-" + strings.ReplaceAll(l, " ", ""),
79 label: "lingo for " + l,
80 text: relingo[l],
81 oneline: true,
82 })
83 }
84 cursel := 0
85
86 hidecursor := func() {
87 stdout.WriteString(esc + "[?25l")
88 }
89 showcursor := func() {
90 stdout.WriteString(esc + "[?12;25h")
91 }
92 movecursor := func(x, y int) {
93 stdout.WriteString(fmt.Sprintf(esc+"[%d;%dH", y, x))
94 }
95 moveleft := func() {
96 stdout.WriteString(esc + "[1D")
97 }
98 clearscreen := func() {
99 stdout.WriteString(esc + "[2J")
100 }
101 //clearline := func() { stdout.WriteString(esc + "[2K") }
102 colorfn := func(code int) func(string) string {
103 return func(s string) string {
104 return fmt.Sprintf(esc+"[%dm"+"%s"+esc+"[0m", code, s)
105 }
106 }
107 reverse := colorfn(7)
108 magenta := colorfn(35)
109 readchar := func() byte {
110 var buf [1]byte
111 os.Stdin.Read(buf[:])
112 c := buf[0]
113 return c
114 }
115
116 savedtio := new(C.struct_termios)
117 C.tcgetattr(1, savedtio)
118 restore := func() {
119 stdout.WriteString(rmcup)
120 showcursor()
121 stdout.Flush()
122 C.tcsetattr(1, C.TCSAFLUSH, savedtio)
123 }
124 defer restore()
125 go func() {
126 sig := make(chan os.Signal)
127 signal.Notify(sig, os.Interrupt)
128 <-sig
129 restore()
130 os.Exit(0)
131 }()
132
133 init := func() {
134 tio := new(C.struct_termios)
135 C.tcgetattr(1, tio)
136 C.clearecho(tio)
137 C.tcsetattr(1, C.TCSADRAIN, tio)
138
139 hidecursor()
140 stdout.WriteString(smcup)
141 clearscreen()
142 movecursor(1, 1)
143 stdout.Flush()
144 }
145
146 editing := false
147
148 linecount := func(s string) int {
149 lines := 1
150 for i := range s {
151 if s[i] == '\n' {
152 lines++
153 }
154 }
155 return lines
156 }
157
158 msglineno := func(idx int) int {
159 off := 1
160 if idx == -1 {
161 return off
162 }
163 for i, m := range messages {
164 off += 1
165 if i == idx {
166 return off
167 }
168 if !m.oneline {
169 off += 1
170 off += linecount(m.text)
171 }
172 }
173 off += 2
174 return off
175 }
176
177 forscreen := func(s string) string {
178 return strings.Replace(s, "\n", "\n ", -1)
179 }
180
181 drawmessage := func(idx int) {
182 line := msglineno(idx)
183 movecursor(4, line)
184 label := messages[idx].label
185 if idx == cursel {
186 label = reverse(label)
187 }
188 label = magenta(label)
189 text := forscreen(messages[idx].text)
190 if messages[idx].oneline {
191 stdout.WriteString(fmt.Sprintf("%s\t %s", label, text))
192 } else {
193 stdout.WriteString(fmt.Sprintf("%s\n %s", label, text))
194 }
195 }
196
197 drawscreen := func() {
198 clearscreen()
199 movecursor(4, msglineno(-1))
200 stdout.WriteString(magenta(serverName + " admin panel"))
201 for i := range messages {
202 if !editing || i != cursel {
203 drawmessage(i)
204 }
205 }
206 movecursor(4, msglineno(len(messages)))
207 dir := "j/k to move - q to quit - enter to edit"
208 if editing {
209 dir = "esc to end"
210 }
211 stdout.WriteString(magenta(dir))
212 if editing {
213 drawmessage(cursel)
214 }
215 stdout.Flush()
216 }
217
218 selectnext := func() {
219 if cursel < len(messages)-1 {
220 movecursor(4, msglineno(cursel))
221 stdout.WriteString(magenta(messages[cursel].label))
222 cursel++
223 movecursor(4, msglineno(cursel))
224 stdout.WriteString(reverse(magenta(messages[cursel].label)))
225 stdout.Flush()
226 }
227 }
228 selectprev := func() {
229 if cursel > 0 {
230 movecursor(4, msglineno(cursel))
231 stdout.WriteString(magenta(messages[cursel].label))
232 cursel--
233 movecursor(4, msglineno(cursel))
234 stdout.WriteString(reverse(magenta(messages[cursel].label)))
235 stdout.Flush()
236 }
237 }
238 editsel := func() {
239 editing = true
240 showcursor()
241 drawscreen()
242 m := messages[cursel]
243 loop:
244 for {
245 c := readchar()
246 switch c {
247 case '\x1b':
248 break loop
249 case '\n':
250 if m.oneline {
251 break loop
252 }
253 m.text += "\n"
254 drawscreen()
255 case 127:
256 if len(m.text) > 0 {
257 last := m.text[len(m.text)-1]
258 m.text = m.text[:len(m.text)-1]
259 if last == '\n' {
260 drawscreen()
261 } else {
262 moveleft()
263 stdout.WriteString(" ")
264 moveleft()
265 }
266 }
267 default:
268 m.text += string(c)
269 stdout.WriteString(string(c))
270 }
271 stdout.Flush()
272 }
273 editing = false
274 setconfig(m.name, m.text)
275 hidecursor()
276 drawscreen()
277 }
278
279 init()
280 drawscreen()
281
282 for {
283 c := readchar()
284 switch c {
285 case 'q':
286 return
287 case 'j':
288 selectnext()
289 case 'k':
290 selectprev()
291 case '\n':
292 editsel()
293 default:
294
295 }
296 }
297}