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