all repos — honk @ 4dc9e4fad7242707df1eae5cc05a53c4406d7996

my fork of honk

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}