all repos — honk @ f01d31783a075c0e9cfeebb8a65d237f743d5ea1

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>
 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}