2021-06-26 18:08:35 +00:00
|
|
|
package main
|
|
|
|
|
2021-07-03 00:46:58 +00:00
|
|
|
import (
|
2021-07-07 01:40:12 +00:00
|
|
|
"context"
|
2021-07-03 01:44:45 +00:00
|
|
|
"fmt"
|
2021-07-03 00:46:58 +00:00
|
|
|
"log"
|
2021-07-07 01:40:12 +00:00
|
|
|
"os"
|
2021-07-29 01:06:49 +00:00
|
|
|
"runtime/debug"
|
2021-07-07 01:40:12 +00:00
|
|
|
"sync"
|
2021-07-03 00:46:58 +00:00
|
|
|
|
2021-07-07 01:40:12 +00:00
|
|
|
"git.yetaga.in/alazyreader/library/book"
|
2021-07-03 00:46:58 +00:00
|
|
|
"git.yetaga.in/alazyreader/library/config"
|
2021-07-07 01:40:12 +00:00
|
|
|
"git.yetaga.in/alazyreader/library/database"
|
2021-07-03 17:30:08 +00:00
|
|
|
"git.yetaga.in/alazyreader/library/ui"
|
2021-07-03 00:46:58 +00:00
|
|
|
"github.com/gdamore/tcell"
|
|
|
|
"github.com/kelseyhightower/envconfig"
|
|
|
|
)
|
|
|
|
|
2021-07-07 01:40:12 +00:00
|
|
|
// State holds the UI state keys=>value map and manages access to the map with a mutex
|
|
|
|
type State struct {
|
|
|
|
m sync.Mutex
|
|
|
|
stateMap map[string]interface{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// key, present
|
|
|
|
func (s *State) Get(key string) interface{} {
|
|
|
|
s.m.Lock()
|
|
|
|
defer s.m.Unlock()
|
|
|
|
if s.stateMap == nil {
|
|
|
|
s.stateMap = make(map[string]interface{})
|
|
|
|
}
|
|
|
|
k, ok := s.stateMap[key]
|
|
|
|
if !ok {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return k
|
|
|
|
}
|
|
|
|
|
|
|
|
// key, value
|
|
|
|
func (s *State) Set(key string, value interface{}) {
|
|
|
|
s.m.Lock()
|
|
|
|
defer s.m.Unlock()
|
|
|
|
if s.stateMap == nil {
|
|
|
|
s.stateMap = make(map[string]interface{})
|
|
|
|
}
|
|
|
|
s.stateMap[key] = value
|
|
|
|
}
|
|
|
|
|
2021-07-29 01:36:45 +00:00
|
|
|
// UI states
|
2021-07-04 18:49:36 +00:00
|
|
|
const (
|
|
|
|
IN_MENU = iota
|
|
|
|
IN_BOOK
|
2021-07-29 01:36:45 +00:00
|
|
|
IN_IMPORT
|
2021-07-04 18:49:36 +00:00
|
|
|
)
|
|
|
|
|
2021-06-26 18:08:35 +00:00
|
|
|
func main() {
|
2021-07-03 00:46:58 +00:00
|
|
|
var c config.Config
|
|
|
|
err := envconfig.Process("library", &c)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalln(err)
|
|
|
|
}
|
2021-07-07 01:40:12 +00:00
|
|
|
|
|
|
|
// create state
|
|
|
|
state := State{}
|
|
|
|
|
|
|
|
// set up DB connection
|
|
|
|
if c.DBUser == "" || c.DBPass == "" || c.DBHost == "" || c.DBPort == "" || c.DBName == "" {
|
|
|
|
log.Fatalf("vars: %+v", c)
|
|
|
|
}
|
|
|
|
lib, err := database.NewMySQLConnection(c.DBUser, c.DBPass, c.DBHost, c.DBPort, c.DBName)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalln(err)
|
|
|
|
}
|
|
|
|
err = lib.PrepareDatabase(context.Background())
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalln(err)
|
|
|
|
}
|
|
|
|
_, _, err = lib.RunMigrations(context.Background())
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalln(err)
|
|
|
|
}
|
|
|
|
books, err := lib.GetAllBooks(context.Background())
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalln(err)
|
|
|
|
}
|
|
|
|
state.Set("library", books)
|
|
|
|
|
2021-07-03 00:46:58 +00:00
|
|
|
screen, err := tcell.NewScreen()
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalln(err)
|
|
|
|
}
|
|
|
|
err = screen.Init()
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalln(err)
|
|
|
|
}
|
2021-07-29 01:06:49 +00:00
|
|
|
// cleanup our screen and log if we panic and crash out somewhere
|
|
|
|
defer func() {
|
|
|
|
if r := recover(); r != nil {
|
|
|
|
if screen != nil {
|
|
|
|
screen.Fini()
|
|
|
|
}
|
|
|
|
fmt.Println("fatal panic;", r)
|
|
|
|
if c.Debug {
|
|
|
|
fmt.Println("stacktrace: \n" + string(debug.Stack()))
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}()
|
2021-07-03 14:58:45 +00:00
|
|
|
|
2021-07-29 01:36:45 +00:00
|
|
|
// book list and options menu (left column)
|
2021-07-07 01:40:12 +00:00
|
|
|
l := ui.NewList(Titles(state.Get("library").([]book.Book)), 0)
|
2021-07-03 17:30:08 +00:00
|
|
|
menu := ui.NewBox(
|
2021-07-03 14:58:45 +00:00
|
|
|
"library",
|
2021-07-07 01:40:12 +00:00
|
|
|
[]string{"(n)ew", "(i)mport", "(q)uit"},
|
2021-07-03 17:30:08 +00:00
|
|
|
ui.Contents{{
|
|
|
|
Offsets: ui.Offsets{Top: 1, Left: 2, Bottom: -2, Right: -2},
|
|
|
|
Container: l,
|
2021-07-03 14:58:45 +00:00
|
|
|
}},
|
2021-07-13 22:32:01 +00:00
|
|
|
ui.StyleActive,
|
|
|
|
false,
|
2021-07-03 14:58:45 +00:00
|
|
|
)
|
2021-07-07 01:40:12 +00:00
|
|
|
book := ui.NewBookDetails(&book.Book{
|
|
|
|
Title: "test title",
|
|
|
|
})
|
2021-07-29 01:36:45 +00:00
|
|
|
|
|
|
|
// book display (right column)
|
2021-07-03 17:30:08 +00:00
|
|
|
activeBook := ui.NewBox(
|
|
|
|
"book",
|
2021-07-07 01:40:12 +00:00
|
|
|
nil,
|
2021-07-04 18:49:36 +00:00
|
|
|
ui.Contents{{
|
2021-07-07 01:40:12 +00:00
|
|
|
Offsets: ui.Offsets{Top: 1, Left: 2, Bottom: 0, Right: 0},
|
2021-07-04 18:49:36 +00:00
|
|
|
Container: book,
|
|
|
|
}},
|
2021-07-13 22:32:01 +00:00
|
|
|
ui.StyleInactive,
|
|
|
|
false,
|
2021-07-03 17:30:08 +00:00
|
|
|
)
|
|
|
|
|
2021-07-29 01:36:45 +00:00
|
|
|
// pop-up
|
|
|
|
popup := ui.NewBox("import", nil, nil, ui.StyleActive, false)
|
|
|
|
popup.SetVisible(false)
|
|
|
|
|
2021-07-03 17:30:08 +00:00
|
|
|
container := ui.NewContainer(
|
2021-07-04 18:49:36 +00:00
|
|
|
ui.Contents{
|
|
|
|
{Container: menu, Offsets: ui.Offsets{Percent: 1}},
|
|
|
|
{Container: activeBook, Offsets: ui.Offsets{Percent: 2}},
|
|
|
|
},
|
|
|
|
ui.LayoutHorizontalPercent,
|
2021-07-03 17:30:08 +00:00
|
|
|
)
|
2021-07-03 14:58:45 +00:00
|
|
|
|
|
|
|
// init
|
2021-07-03 00:46:58 +00:00
|
|
|
screen.Clear()
|
2021-07-03 01:44:45 +00:00
|
|
|
w, h := screen.Size()
|
2021-07-03 17:30:08 +00:00
|
|
|
container.SetSize(0, 0, h, w)
|
|
|
|
container.Draw(screen)
|
2021-07-03 14:58:45 +00:00
|
|
|
screen.Sync()
|
|
|
|
|
2021-07-07 01:40:12 +00:00
|
|
|
// init UI state
|
|
|
|
state.Set("ui_state", IN_MENU)
|
2021-07-13 22:32:01 +00:00
|
|
|
screen.PostEvent(NewEventLoadBook(l.SelectedID()))
|
2021-07-04 18:49:36 +00:00
|
|
|
|
2021-07-03 14:58:45 +00:00
|
|
|
// UI loop
|
2021-07-03 00:46:58 +00:00
|
|
|
for {
|
|
|
|
e := screen.PollEvent()
|
|
|
|
switch v := e.(type) {
|
|
|
|
case *tcell.EventError:
|
2021-07-07 01:40:12 +00:00
|
|
|
fmt.Fprintf(os.Stderr, "%v", v)
|
2021-07-03 00:46:58 +00:00
|
|
|
screen.Beep()
|
2021-07-03 14:58:45 +00:00
|
|
|
case *tcell.EventKey: // input handling
|
2021-07-07 01:40:12 +00:00
|
|
|
curr := state.Get("ui_state").(int)
|
|
|
|
if curr == IN_MENU {
|
2021-07-04 18:49:36 +00:00
|
|
|
if v.Key() == tcell.KeyUp && l.Selected() > 0 {
|
|
|
|
l.SetSelected(l.Selected() - 1)
|
2021-07-13 22:32:01 +00:00
|
|
|
screen.PostEvent(NewEventLoadBook(l.SelectedID()))
|
2021-07-04 18:49:36 +00:00
|
|
|
}
|
|
|
|
if v.Key() == tcell.KeyDown && l.Selected() < len(l.ListMembers())-1 {
|
|
|
|
l.SetSelected(l.Selected() + 1)
|
2021-07-13 22:32:01 +00:00
|
|
|
screen.PostEvent(NewEventLoadBook(l.SelectedID()))
|
2021-07-04 18:49:36 +00:00
|
|
|
}
|
2021-07-13 22:32:01 +00:00
|
|
|
if v.Key() == tcell.KeyEnter {
|
2021-07-29 01:36:45 +00:00
|
|
|
screen.PostEvent(NewEventEnterBook())
|
|
|
|
}
|
|
|
|
if v.Rune() == 'i' {
|
|
|
|
screen.PostEvent(NewEventOpenImport())
|
|
|
|
}
|
|
|
|
if v.Rune() == 'q' {
|
|
|
|
screen.PostEvent(NewEventQuit())
|
2021-07-04 18:49:36 +00:00
|
|
|
}
|
2021-07-07 01:40:12 +00:00
|
|
|
} else if curr == IN_BOOK {
|
2021-07-13 22:32:01 +00:00
|
|
|
if v.Key() == tcell.KeyEsc {
|
2021-07-29 01:36:45 +00:00
|
|
|
screen.PostEvent(NewEventExitBook())
|
|
|
|
}
|
|
|
|
} else if curr == IN_IMPORT {
|
|
|
|
if v.Key() == tcell.KeyEsc {
|
|
|
|
screen.PostEvent(NewEventCloseImport())
|
2021-07-04 18:49:36 +00:00
|
|
|
}
|
2021-07-03 00:46:58 +00:00
|
|
|
}
|
2021-07-03 14:58:45 +00:00
|
|
|
case *tcell.EventResize: // screen redraw
|
2021-07-03 01:44:45 +00:00
|
|
|
w, h := screen.Size()
|
2021-07-03 17:30:08 +00:00
|
|
|
container.SetSize(0, 0, h, w)
|
2021-07-07 01:40:12 +00:00
|
|
|
case *EventBookUpdate:
|
|
|
|
// TK
|
2021-07-29 01:36:45 +00:00
|
|
|
case *EventEnterBook:
|
|
|
|
activeBook.SetStyle(ui.StyleActive)
|
|
|
|
menu.SetStyle(ui.StyleInactive)
|
|
|
|
state.Set("ui_state", IN_BOOK)
|
|
|
|
case *EventExitBook:
|
|
|
|
state.Set("ui_state", IN_MENU)
|
|
|
|
activeBook.SetStyle(ui.StyleInactive)
|
|
|
|
menu.SetStyle(ui.StyleActive)
|
2021-07-07 01:40:12 +00:00
|
|
|
case *EventLoadBook:
|
|
|
|
book.SetBook(GetBookByID(v.ID, books))
|
2021-07-29 01:36:45 +00:00
|
|
|
case *EventOpenImport:
|
|
|
|
state.Set("ui_state", IN_IMPORT)
|
|
|
|
menu.SetStyle(ui.StyleInactive)
|
|
|
|
popup.SetVisible(true)
|
|
|
|
popup.SetSize(6, 3, 10, 80)
|
|
|
|
case *EventCloseImport:
|
|
|
|
state.Set("ui_state", IN_MENU)
|
|
|
|
menu.SetStyle(ui.StyleActive)
|
|
|
|
popup.SetVisible(false)
|
|
|
|
case *EventQuit:
|
|
|
|
screen.Fini()
|
|
|
|
fmt.Printf("Thank you for playing Wing Commander!\n\n")
|
|
|
|
return
|
2021-07-03 01:44:45 +00:00
|
|
|
case *tcell.EventInterrupt:
|
|
|
|
case *tcell.EventMouse:
|
2021-07-03 00:46:58 +00:00
|
|
|
case *tcell.EventTime:
|
2021-07-03 01:44:45 +00:00
|
|
|
default:
|
2021-07-03 00:46:58 +00:00
|
|
|
}
|
2021-07-29 01:36:45 +00:00
|
|
|
// repaint
|
|
|
|
screen.Clear()
|
|
|
|
container.Draw(screen)
|
|
|
|
popup.Draw(screen)
|
|
|
|
screen.Show()
|
2021-07-03 01:44:45 +00:00
|
|
|
}
|
2021-06-26 18:08:35 +00:00
|
|
|
}
|
2021-07-07 01:40:12 +00:00
|
|
|
|
|
|
|
func Titles(lb []book.Book) []ui.ListKeyValue {
|
|
|
|
r := []ui.ListKeyValue{}
|
|
|
|
for i := range lb {
|
|
|
|
r = append(r, ui.ListKeyValue{
|
|
|
|
Key: lb[i].ID,
|
|
|
|
Value: lb[i].Title,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetBookByID(id int, lb []book.Book) *book.Book {
|
|
|
|
for i := range lb {
|
|
|
|
if lb[i].ID == id {
|
|
|
|
return &lb[i]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return &book.Book{}
|
|
|
|
}
|