summaryrefslogtreecommitdiff
path: root/internal/client/ui
diff options
context:
space:
mode:
Diffstat (limited to 'internal/client/ui')
-rw-r--r--internal/client/ui/auth/auth.go62
-rw-r--r--internal/client/ui/chat/chat.go2
-rw-r--r--internal/client/ui/loadscreen/loadscreen.go208
-rw-r--r--internal/client/ui/ui.go9
4 files changed, 255 insertions, 26 deletions
diff --git a/internal/client/ui/auth/auth.go b/internal/client/ui/auth/auth.go
index 0cabe38..e26e88c 100644
--- a/internal/client/ui/auth/auth.go
+++ b/internal/client/ui/auth/auth.go
@@ -3,6 +3,7 @@ package auth
import (
"crypto/ed25519"
"crypto/x509"
+ "encoding/pem"
"errors"
"fmt"
"log"
@@ -19,6 +20,7 @@ import (
"github.com/kyren223/eko/internal/client/ui"
authfield "github.com/kyren223/eko/internal/client/ui/auth/field"
"github.com/kyren223/eko/internal/client/ui/choicepopup"
+ "github.com/kyren223/eko/internal/client/ui/loadscreen"
"github.com/kyren223/eko/pkg/assert"
)
@@ -70,9 +72,6 @@ ___] | |__] | \| | | \|
)
type Model struct {
- width int
- height int
-
focusIndex int
fields []authfield.Model
@@ -196,15 +195,15 @@ func (m Model) View() string {
Padding(0, (vp.Width-width)/2-1, 1)
result := lipgloss.Place(
- m.width, m.height,
+ ui.Width, ui.Height,
lipgloss.Center, lipgloss.Center,
vp.View(),
)
if m.popup != nil {
popup := m.popup.View()
- x := (m.width - lipgloss.Width(popup)) / 2
- y := (m.height - lipgloss.Height(popup)) / 2
+ x := (ui.Width - lipgloss.Width(popup)) / 2
+ y := (ui.Height - lipgloss.Height(popup)) / 2
result = ui.PlaceOverlay(x, y, popup, result)
}
@@ -213,10 +212,6 @@ func (m Model) View() string {
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
- case tea.WindowSizeMsg:
- m.width, m.height = msg.Width, msg.Height
- return m, nil
-
case tea.KeyMsg:
key := msg.Type
switch key {
@@ -364,6 +359,8 @@ func (m *Model) ButtonPressed(msg tea.Msg) tea.Cmd {
}
func (m *Model) Signup() tea.Cmd {
+ username := m.fields[usernameField].Input.Value()
+ assert.Assert(len(username) != 0, "username must not be empty")
passphrase := m.fields[passphraseField].Input.Value()
confirmation := m.fields[passphraseConfirmField].Input.Value()
@@ -387,7 +384,7 @@ func (m *Model) Signup() tea.Cmd {
assert.NotNil(errors.Unwrap(err), "there should always be an error to unwrap", "err", err)
return nil
}
- file, err := os.OpenFile(privateKeyFilepath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o644)
+ file, err := os.OpenFile(privateKeyFilepath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o600)
if errors.Is(err, os.ErrExist) {
info, e := os.Stat(privateKeyFilepath)
assert.NoError(e, "if file exists it should be fine to stat it")
@@ -408,12 +405,33 @@ func (m *Model) Signup() tea.Cmd {
assert.NotNil(errors.Unwrap(err), "there should always be an error to unwrap", "err", err)
return nil
}
+ defer file.Close()
- // file.Write()
- file.Close()
- os.Remove(privateKeyFilepath)
+ _, privKey, err := ed25519.GenerateKey(nil)
+ if err != nil {
+ m.fields[privateKeyField].Input.Err = errors.New("Failed private key generation")
+ log.Println("ed25519 generate key error:", err)
+ return nil
+ }
+ var pemBlock *pem.Block
+ if hasPassphrase {
+ pemBlock, err = ssh.MarshalPrivateKeyWithPassphrase(privKey, username, []byte(passphrase))
+ } else {
+ pemBlock, err = ssh.MarshalPrivateKey(privKey, username)
+ }
+ if err != nil {
+ m.fields[privateKeyField].Input.Err = errors.New("Failed private key marshaling")
+ log.Println("ssh marshaling error:", err)
+ return nil
+ }
+ err = pem.Encode(file, pemBlock)
+ if err != nil {
+ m.fields[privateKeyField].Input.Err = errors.New("Failed writing to disk")
+ log.Println("pem encoding to file error:", err)
+ return nil
+ }
- return tea.Quit
+ return authenticate(privKey)
}
func (m *Model) signin() tea.Cmd {
@@ -472,9 +490,7 @@ func (m *Model) signin() tea.Cmd {
return nil
}
- _ = privKey
-
- return tea.Quit
+ return authenticate(*privKey)
}
func createPopup(content string, leftChoices, rightChoices []string) *choicepopup.Model {
@@ -507,11 +523,7 @@ func expandPath(path string) string {
return path
}
-func test() {
- // pubKey, privKey, err := ed25519.GenerateKey(nil)
- // sshPrivKey, err := ssh.NewSignerFromSigner(privKey)
- // ssh.MarshalPrivateKey()
- // ssh.MarshalAuthorizedKey()
- // ssh.ParseRawPrivateKey()
- // ssh.ParseRawPrivateKeyWithPassphrase()
+func authenticate(privKey ed25519.PrivateKey) tea.Cmd {
+ return ui.Transition(loadscreen.New())
}
+
diff --git a/internal/client/ui/chat/chat.go b/internal/client/ui/chat/chat.go
index 1fd4537..b87071e 100644
--- a/internal/client/ui/chat/chat.go
+++ b/internal/client/ui/chat/chat.go
@@ -59,7 +59,7 @@ func (m Model) View() string {
) + ""
}
-func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.messagebox.Viewport.Width = msg.Width
diff --git a/internal/client/ui/loadscreen/loadscreen.go b/internal/client/ui/loadscreen/loadscreen.go
new file mode 100644
index 0000000..6639f7d
--- /dev/null
+++ b/internal/client/ui/loadscreen/loadscreen.go
@@ -0,0 +1,208 @@
+package loadscreen
+
+import (
+ "log"
+ "time"
+
+ "github.com/charmbracelet/bubbles/spinner"
+ "github.com/charmbracelet/bubbles/viewport"
+ tea "github.com/charmbracelet/bubbletea"
+ "github.com/charmbracelet/lipgloss"
+
+ "github.com/kyren223/eko/internal/client/ui"
+ "github.com/kyren223/eko/pkg/assert"
+)
+
+var (
+ style = lipgloss.NewStyle().Border(lipgloss.ThickBorder()).Padding(1)
+ _ = spinner.Spinner{
+ Frames: []string{
+ "██▓▓▒▒\n ░░\n ",
+ "▓▓▒▒░░\n██ \n ",
+ "▒▒░░ \n▓▓ \n██ ",
+ "░░ \n▒▒ \n▓▓██ ",
+ " \n░░ \n▒▒▓▓██",
+ " \n ██\n░░▒▒▓▓",
+ " ██\n ▓▓\n ░░▒▒",
+ " ██▓▓\n ▒▒\n ░░",
+ },
+ FPS: time.Second / 8,
+ }
+
+ apple = lipgloss.NewStyle().Foreground(lipgloss.Color("#FF0000")).Render("██")
+ // loadingFrames = circleTrail(5, 5, 0, true, " ", "░░", "▒▒", "▓▓", "██")
+ // loadingFrames = circleTrail(5, 5, 0, true, " ", "░░", "▒▒", "▓▓", "██", " ", " ", " ", apple)
+ // loadingFrames = circleTrail(5, 5, 0, true, " ", "░░", "░░", "▒▒", "▒▒", "▓▓", "▓▓", "██", " ", " ", " ", apple)
+ loadingFrames = func() []string {
+ green := lipgloss.NewStyle().Foreground(lipgloss.Color("#00FF00"))
+ trail := []string{"░░", "░░", "▒▒", "▒▒", "▓▓", "██", " ", " "}
+ for i := range trail {
+ trail[i] = green.Render(trail[i])
+ }
+ trail = append(trail, apple)
+ return circleTrail(5, 5, 0, true, " ", trail...)
+ }()
+ loading = spinner.Spinner{
+ Frames: loadingFrames,
+ FPS: time.Second / time.Duration(len(loadingFrames)),
+ }
+)
+
+func circleTrail(width int, height int, offset int, clockwise bool, bg string, trail ...string) (states []string) {
+ slots := (width+height)*2 - 4
+ slotWidth := lipgloss.Width(bg)
+ slotHeight := lipgloss.Height(bg)
+
+ assert.Assert(slots-len(trail) >= 0, "not enough space to fit trail")
+ assert.AddData("slotWidth", slotWidth)
+ assert.AddData("slotHeight", slotHeight)
+ defer assert.RemoveData("slotWidth")
+ defer assert.RemoveData("slotHeight")
+ for _, elem := range trail {
+ w := lipgloss.Width(elem)
+ h := lipgloss.Height(elem)
+ assert.AddData("elemWidth", w)
+ assert.AddData("elemHeight", h)
+ assert.Assert(slotWidth == w, "bg and all trails must have the same height")
+ assert.Assert(slotHeight == h, "bg and all trails must have the same height")
+ assert.RemoveData("elemWidth")
+ assert.RemoveData("elemHeight")
+ }
+
+ matrix := make([][]string, height)
+ for i := range matrix {
+ matrix[i] = make([]string, width)
+ }
+
+ for i := 0; i < slots; i++ {
+ for y := range matrix {
+ for x := range matrix[y] {
+ matrix[y][x] = bg
+ }
+ }
+
+ x, y := 0, 0
+ for j := 0; j < i+offset; j++ {
+ if clockwise {
+ if y == 0 && x != width-1 {
+ x++
+ } else if x == width-1 && y != height-1 {
+ y++
+ } else if y == height-1 && x != 0 {
+ x--
+ } else if x == 0 && y != 0 {
+ y--
+ }
+ } else {
+ if x == 0 && y != height-1 {
+ y++
+ } else if y == height-1 && x != width-1 {
+ x++
+ } else if x == width-1 && y != 0 {
+ y--
+ } else if y == 0 && x != 0 {
+ x--
+ }
+ }
+ }
+
+ for _, trail := range trail {
+ matrix[y][x] = trail
+ if clockwise {
+ if y == 0 && x != width-1 {
+ x++
+ } else if x == width-1 && y != height-1 {
+ y++
+ } else if y == height-1 && x != 0 {
+ x--
+ } else if x == 0 && y != 0 {
+ y--
+ }
+ } else {
+ if x == 0 && y != height-1 {
+ y++
+ } else if y == height-1 && x != width-1 {
+ x++
+ } else if x == width-1 && y != 0 {
+ y--
+ } else if y == 0 && x != 0 {
+ x--
+ }
+ }
+ }
+
+ var rows []string
+ for _, row := range matrix {
+ rows = append(rows, lipgloss.JoinHorizontal(0, row...))
+ }
+ state := lipgloss.JoinVertical(0, rows...)
+ states = append(states, state)
+ }
+
+ return states
+}
+
+type Model struct {
+ sp spinner.Model
+ content string
+
+ Style lipgloss.Style
+
+ delta time.Time
+ index int
+}
+
+func New() Model {
+ return Model{
+ sp: spinner.New(spinner.WithSpinner(loading)),
+ content: "Update Failed - retrying in 3 sec...",
+ Style: style,
+ }
+}
+
+func (m Model) Init() tea.Cmd {
+ return m.sp.Tick
+}
+
+func (m Model) View() string {
+ vp := viewport.New(40, 5)
+ vp.Style = m.Style
+ vp.SetContent(m.content)
+
+ result := lipgloss.JoinVertical(lipgloss.Center, m.sp.View(), vp.View())
+
+ return lipgloss.Place(
+ ui.Width, ui.Height,
+ lipgloss.Center, lipgloss.Center,
+ result,
+ )
+}
+
+func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+ switch msg := msg.(type) {
+
+ case tea.KeyMsg:
+ key := msg.Type
+ switch key {
+ case tea.KeyCtrlC:
+ return m, tea.Quit
+
+ case tea.KeyCtrlT:
+ return m, m.sp.Tick
+ }
+
+ case spinner.TickMsg:
+ if m.index == 0 {
+ m.delta = time.Now()
+ }
+ delta := time.Since(m.delta)
+ log.Printf("Frame %v Delta %v\n", m.index, delta)
+ m.index++
+ m.delta = time.Now()
+ var cmd tea.Cmd
+ m.sp, cmd = m.sp.Update(msg)
+ return m, cmd
+ }
+
+ return m, nil
+}
diff --git a/internal/client/ui/ui.go b/internal/client/ui/ui.go
index 355fbfb..a208576 100644
--- a/internal/client/ui/ui.go
+++ b/internal/client/ui/ui.go
@@ -15,10 +15,19 @@ import (
"github.com/kyren223/eko/pkg/assert"
)
+var Width int
+var Height int
+
type ModelTransition struct {
Model tea.Model
}
+func Transition(model tea.Model) tea.Cmd {
+ return func() tea.Msg {
+ return ModelTransition{Model: model}
+ }
+}
+
func AddBorderHeader(header string, headerOffset int, style lipgloss.Style, render string) string {
b := style.GetBorderStyle()
body := style.UnsetBorderTop().Render(render)