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