initial commit

trunk
aoife cassidy 2025-01-19 00:06:25 +02:00
commit dc52412148
No known key found for this signature in database
GPG Key ID: 7184AC1C9835CE48
11 changed files with 468 additions and 0 deletions

1
.gitignore vendored 100644
View File

@ -0,0 +1 @@
welcome

20
LICENSE 100644
View File

@ -0,0 +1,20 @@
Copyright (c) 2025 nbsp@tilde.town
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

7
README.md 100644
View File

@ -0,0 +1,7 @@
# welcome
a teeny script to teach less experienced users how to use town and the terminal
## license
MIT

5
TODO 100644
View File

@ -0,0 +1,5 @@
- an introduction to the shell
- editing your homepage
- checking your mail
- joining irc
- goodbye & link to town explore and wiki

View File

@ -0,0 +1,34 @@
package app
import (
"git.sr.ht/~rockorager/vaxis"
"git.tilde.town/nbsp/welcome/ui"
)
type Introduction struct{}
func (view *Introduction) Event(state *ui.State, event vaxis.Event) (processed bool) {
if key, ok := event.(vaxis.Key); ok && key.EventType == vaxis.EventPress {
switch key.String() {
case "Ctrl+c", "Ctrl+d":
close(ui.Quit)
case "Enter":
ui.ViewChange <- &Settings{}
}
processed = true
}
win := state.Window()
win.Print(vaxis.Segment{Text: ` welcome to tilde town
welcome to tilde town! we're glad you're here.
this short tutorial will help you get accustomed to the terminal
the text-based user interface that you will do most of your interacting with
town on. if you're here, this means you've successfully connected to town over
ssh good job! you can handle the rest. i believe in you <3
press to continue to the next page. if you know what you're doing, you can
press C-c or C-d at any point to exit to a shell.
`})
return
}

95
app/settings.go 100644
View File

@ -0,0 +1,95 @@
package app
import (
"git.sr.ht/~rockorager/vaxis"
"git.sr.ht/~rockorager/vaxis/widgets/textinput"
"git.tilde.town/nbsp/welcome/ui"
)
type Settings struct {
index int
inputs [3]textinput.Model
}
func (view *Settings) Event(state *ui.State, event vaxis.Event) (processed bool) {
if key, ok := event.(vaxis.Key); ok && key.EventType == vaxis.EventPress {
switch key.String() {
case "Ctrl+c", "Ctrl+d":
close(ui.Quit)
case "Up":
if view.index > 0 {
view.index--
}
case "Down":
if view.index < 3 {
view.index++
}
case "Enter":
if view.index == 3 {
ui.ViewChange <- &Introduction{}
}
if view.index < 3 {
view.index++
}
default:
if view.index != 3 {
view.inputs[view.index].Update(event)
}
}
processed = true
}
enterStyle := vaxis.AttributeMask(vaxis.AttrNone)
if view.index == 3 {
enterStyle = vaxis.AttrReverse
}
win := state.Window()
win.Print(vaxis.Segment{Text: ` initial settings
let's get you set up with some useful settings. you can press or to move
between the inputs. feel free to leave any of them empty! you can always set
them later, or leave them unset.
1. what are your pronouns? this is useful information for folks to be able to
refer to you in the third person. common pronoun sets are he/him, she/her,
and they/them.
>
2. when's your birthday? this will show up in certain places, so people can wish
you a happy birthday :) format is MM/YY.
>
3. what's your timezone? this will change all times you view in town to your
local timezone, so you don't have to convert from utc in your head.
the format for timezones can be found here:
`},
vaxis.Segment{
Text: "https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List",
Style: vaxis.Style{
Foreground: vaxis.IndexColor(4),
// UnderlineColor: vaxis.IndexColor(3),
UnderlineStyle: vaxis.UnderlineSingle,
},
},
vaxis.Segment{Text: `
it should look something like America/New_York.
>
`},
vaxis.Segment{Text: " press ↲ to continue ", Style: vaxis.Style{Attribute: enterStyle}},
)
rows := [3]int{10, 15, 23}
for i, input := range view.inputs {
input.Draw(vaxis.Window{win.Vx, &win, 5, rows[i], 74, 1})
if i == view.index {
win.SetStyle(3, rows[i], vaxis.Style{Attribute: vaxis.AttrReverse})
}
}
state.HideCursor()
return
}

19
go.mod 100644
View File

@ -0,0 +1,19 @@
module git.tilde.town/nbsp/welcome
go 1.23.4
require (
git.sr.ht/~rockorager/vaxis v0.11.0
)
require (
github.com/containerd/console v1.0.3 // indirect
github.com/creack/pty v1.1.18 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mattn/go-sixel v0.0.5 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/soniakeys/quant v1.0.0 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/image v0.9.0 // indirect
golang.org/x/sys v0.10.0 // indirect
)

64
go.sum 100644
View File

@ -0,0 +1,64 @@
git.sr.ht/~rockorager/vaxis v0.11.0 h1:FDEaYCVlC1JVooqrVfMh4Y2GtZBpbkrf/LBAaiLnUbw=
git.sr.ht/~rockorager/vaxis v0.11.0/go.mod h1:h94aKek3frIV1hJbdXjqnBqaLkbWXvV+UxAsQHg9bns=
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sixel v0.0.5 h1:55w2FR5ncuhKhXrM5ly1eiqMQfZsnAHIpYNGZX03Cv8=
github.com/mattn/go-sixel v0.0.5/go.mod h1:h2Sss+DiUEHy0pUqcIB6PFXo5Cy8sTQEFr3a9/5ZLNw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/soniakeys/quant v1.0.0 h1:N1um9ktjbkZVcywBVAAYpZYSHxEfJGzshHCxx/DaI0Y=
github.com/soniakeys/quant v1.0.0/go.mod h1:HI1k023QuVbD4H8i9YdfZP2munIHU4QpjsImz6Y6zds=
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/image v0.9.0 h1:QrzfX26snvCM20hIhBwuHI/ThTg18b/+kcKdXHvnR+g=
golang.org/x/image v0.9.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

26
main.go 100644
View File

@ -0,0 +1,26 @@
package main
import (
"git.tilde.town/nbsp/welcome/app"
"git.tilde.town/nbsp/welcome/ui"
)
func main() {
state, err := ui.New(&app.Introduction{})
if err != nil {
panic(err)
}
defer state.Close()
loop:
for {
select {
case event := <-ui.Events:
state.HandleEvent(event)
case newState := <-ui.ViewChange:
state.HandleViewChange(newState)
case <-ui.Quit:
break loop
}
}
}

105
ui/list.go 100644
View File

@ -0,0 +1,105 @@
/**
* vaxis/widget/list, edited to support wraparounds and number seeking
* soon to support confirmations, once i get around to that
*/
package ui
import (
"fmt"
"strconv"
"git.sr.ht/~rockorager/vaxis"
)
type List struct {
index int
items []string
offset int
}
func NewList(items []string, index int) List {
return List{
items: items,
index: index,
}
}
func (m *List) Draw(win vaxis.Window) {
_, height := win.Size()
if m.index >= m.offset+height {
m.offset = m.index - height + 1
} else if m.index < m.offset {
m.offset = m.index
}
defaultStyle := vaxis.Style{}
selectedStyle := vaxis.Style{Attribute: vaxis.AttrReverse}
index := m.index - m.offset
for i, subject := range m.items[m.offset:] {
var style vaxis.Style
if i == index {
style = selectedStyle
} else {
style = defaultStyle
}
win.Println(i, vaxis.Segment{Text: fmt.Sprintf(" %-"+strconv.Itoa(win.Width-2)+"s", subject), Style: style})
}
}
func (m *List) Down() {
if m.index == len(m.items)-1 {
m.index = 0
} else {
m.index++
}
}
func (m *List) Up() {
if m.index == 0 {
m.index = len(m.items) - 1
} else {
m.index--
}
}
func (m *List) Home() {
m.index = 0
}
func (m *List) End() {
m.index = len(m.items) - 1
}
func (m *List) PageDown(win vaxis.Window) {
_, height := win.Size()
m.index = min(len(m.items)-1, m.index+height)
}
func (m *List) PageUp(win vaxis.Window) {
_, height := win.Size()
m.index = max(0, m.index-height)
}
func (m *List) Items() []string {
return m.items
}
func (m *List) SetItems(items []string) {
m.items = items
m.index = min(len(items)-1, m.index)
}
func (m *List) SetItem(index int, item string) {
m.items[index] = item
}
// Returns the index of the currently selected item.
func (m *List) Index() int {
return m.index
}
func (m *List) SetIndex(index int) {
m.index = index
}

92
ui/ui.go 100644
View File

@ -0,0 +1,92 @@
package ui
import (
"git.sr.ht/~rockorager/vaxis"
)
type Context struct {
Window vaxis.Window
x, y int
}
type View interface {
Event(state *State, event vaxis.Event) bool
}
var Events = make(chan vaxis.Event)
var Quit = make(chan struct{})
var ViewChange = make(chan View, 1)
type State struct {
content View
vx *vaxis.Vaxis
}
func New(view View) (state State, err error) {
vx, err := vaxis.New(vaxis.Options{
DisableMouse: true,
CSIuBitMask: vaxis.CSIuDisambiguate | vaxis.CSIuReportEvents | vaxis.CSIuAlternateKeys | vaxis.CSIuAllKeys | vaxis.CSIuAssociatedText,
})
if err != nil {
return
}
state = State{
content: view,
vx: vx,
}
state.vx.SetTitle("welcome to tilde.town")
go func() {
for event := range state.vx.Events() {
state.vx.Window().Clear()
Events <- event
}
close(Events)
}()
return
}
func (state *State) HandleEvent(event vaxis.Event) {
state.content.Event(state, event)
state.vx.Render()
}
func (state *State) HandleViewChange(view View) {
state.vx.Window().Clear()
state.content = view
state.content.Event(state, vaxis.Redraw{})
state.vx.Render()
}
func (state *State) Close() {
state.vx.Close()
}
func (state *State) PostEvent() func(ev vaxis.Event) {
return state.vx.PostEvent
}
func (state *State) Events() chan vaxis.Event {
return state.vx.Events()
}
func (state *State) Render() {
state.vx.Render()
}
func (state *State) HideCursor() {
state.vx.HideCursor()
}
func (state *State) Window() vaxis.Window {
return state.vx.Window()
}
func (state *State) Suspend() {
state.vx.Suspend()
}
func (state *State) Resume() {
state.vx.Resume()
}