mirror of
https://github.com/Kyren223/eko.git
synced 2026-04-28 23:53:55 +00:00
359 lines
9.6 KiB
Go
359 lines
9.6 KiB
Go
// Eko: A terminal-native social media platform
|
|
// Copyright (C) 2025 Kyren223
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published
|
|
// by the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package frequencyupdate
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
|
|
"github.com/charmbracelet/bubbles/textinput"
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/charmbracelet/lipgloss"
|
|
"github.com/kyren223/eko/internal/client/gateway"
|
|
"github.com/kyren223/eko/internal/client/ui/colors"
|
|
"github.com/kyren223/eko/internal/client/ui/core/state"
|
|
"github.com/kyren223/eko/internal/client/ui/field"
|
|
"github.com/kyren223/eko/internal/client/ui/layouts/flex"
|
|
"github.com/kyren223/eko/internal/packet"
|
|
"github.com/kyren223/eko/pkg/assert"
|
|
"github.com/kyren223/eko/pkg/snowflake"
|
|
)
|
|
|
|
var (
|
|
width = 48
|
|
|
|
padding = 4
|
|
|
|
headerStyle = func() lipgloss.Style { return lipgloss.NewStyle().Foreground(colors.Turquoise) }
|
|
|
|
underlineStyle = func(s string, width int, color lipgloss.Color) string {
|
|
underline := strings.Repeat("━", width)
|
|
underline = lipgloss.NewStyle().Background(colors.Background).Foreground(color).
|
|
Render(underline + " ")
|
|
return lipgloss.NewStyle().Background(colors.Background).
|
|
Render(lipgloss.JoinVertical(lipgloss.Left, s, underline))
|
|
}
|
|
|
|
colorHeader = func() string { return headerStyle().Bold(true).Render(" Color # ") }
|
|
|
|
blurredUpdate = func() string {
|
|
return lipgloss.NewStyle().Padding(0, 1).
|
|
Background(colors.Gray).Foreground(colors.White).
|
|
Render("Update Frequency")
|
|
}
|
|
focusedUpdate = func() string {
|
|
return lipgloss.NewStyle().Padding(0, 1).
|
|
Background(colors.Blue).Foreground(colors.White).
|
|
Render("Update Frequency")
|
|
}
|
|
|
|
leftpad = 1
|
|
)
|
|
|
|
const (
|
|
MaxHexDigits = 6
|
|
)
|
|
|
|
const (
|
|
NameField = iota
|
|
ReadWriteField
|
|
ReadOnlyField
|
|
NoAccessField
|
|
ColorField
|
|
UpdateField
|
|
FieldCount
|
|
)
|
|
|
|
type Model struct {
|
|
name field.Model
|
|
precomputedStyle lipgloss.Style
|
|
lastColor lipgloss.Color
|
|
update string
|
|
color textinput.Model
|
|
perms int
|
|
nameWidth int
|
|
selected int
|
|
network snowflake.ID
|
|
frequency snowflake.ID
|
|
}
|
|
|
|
func New(network snowflake.ID, frequencyIndex int) Model {
|
|
blurredTextStyle := lipgloss.NewStyle().
|
|
Background(colors.Background).Foreground(colors.White)
|
|
focusedTextStyle := blurredTextStyle.Foreground(colors.Focus)
|
|
|
|
fieldBlurredStyle := lipgloss.NewStyle().
|
|
PaddingLeft(1).
|
|
Border(lipgloss.RoundedBorder()).
|
|
BorderForeground(colors.DarkCyan).
|
|
BorderBackground(colors.Background).
|
|
Background(colors.Background)
|
|
fieldFocusedStyle := fieldBlurredStyle.
|
|
Border(lipgloss.ThickBorder()).
|
|
BorderForeground(colors.Focus)
|
|
|
|
frequency := state.State.Frequencies[network][frequencyIndex]
|
|
|
|
name := field.New(width)
|
|
name.Header = "Frequency Name"
|
|
name.HeaderStyle = headerStyle()
|
|
name.FocusedStyle = fieldFocusedStyle
|
|
name.BlurredStyle = fieldBlurredStyle
|
|
name.FocusedTextStyle = focusedTextStyle
|
|
name.BlurredTextStyle = blurredTextStyle
|
|
name.ErrorStyle = lipgloss.NewStyle().Background(colors.Background).Foreground(colors.Error)
|
|
name.Input.CharLimit = packet.MaxFrequencyName
|
|
name.Focus()
|
|
name.Input.Validate = func(s string) error {
|
|
if strings.TrimSpace(s) == "" {
|
|
return errors.New("cannot be empty")
|
|
}
|
|
return nil
|
|
}
|
|
name.Input.SetValue(frequency.Name)
|
|
|
|
nameWidth := lipgloss.Width(name.View())
|
|
|
|
color := textinput.New()
|
|
color.PlaceholderStyle = blurredTextStyle.Foreground(colors.Gray)
|
|
color.TextStyle = blurredTextStyle
|
|
color.Cursor.Style = blurredTextStyle
|
|
color.Cursor.TextStyle = blurredTextStyle
|
|
color.Prompt = ""
|
|
color.CharLimit = MaxHexDigits
|
|
color.Placeholder = "000000"
|
|
color.Validate = func(s string) error {
|
|
if len(s) != MaxHexDigits {
|
|
return errors.New("err")
|
|
}
|
|
return nil
|
|
}
|
|
color.SetValue(frequency.HexColor[1:])
|
|
|
|
return Model{
|
|
name: name,
|
|
color: color,
|
|
lastColor: lipgloss.Color("#" + color.Value()),
|
|
perms: int(frequency.Perms),
|
|
update: blurredUpdate(),
|
|
network: network,
|
|
frequency: frequency.ID,
|
|
|
|
nameWidth: nameWidth,
|
|
precomputedStyle: lipgloss.NewStyle().PaddingRight(padding).
|
|
Background(colors.Background).Foreground(colors.White).MarginBackground(colors.Background),
|
|
}
|
|
}
|
|
|
|
func (m Model) Init() tea.Cmd {
|
|
return nil
|
|
}
|
|
|
|
func (m Model) View() string {
|
|
name := m.name.View()
|
|
|
|
colorStyle := lipgloss.NewStyle().Background(colors.Background).SetString("■\n ")
|
|
color := colors.Gray
|
|
if m.color.Err != nil {
|
|
color = colors.Error
|
|
} else if m.selected == ColorField {
|
|
color = colors.Focus
|
|
}
|
|
c := lipgloss.NewStyle().Width(MaxHexDigits + 1).Background(colors.Background).Render(m.color.View())
|
|
colorInput := underlineStyle(c, MaxHexDigits, color)
|
|
colorInput = lipgloss.NewStyle().Render(colorInput)
|
|
colorIndicator := colorStyle.Foreground(m.lastColor).String()
|
|
colorText := lipgloss.JoinHorizontal(lipgloss.Top, colorHeader(), colorInput, colorIndicator)
|
|
colorText = m.precomputedStyle.Width(m.nameWidth).Render(colorText)
|
|
|
|
readWrite := "[ ] Read & Write"
|
|
if m.perms == packet.PermReadWrite {
|
|
readWrite = "[x] Read & Write"
|
|
}
|
|
if m.selected == ReadWriteField {
|
|
readWrite = m.precomputedStyle.Foreground(colors.Focus).Render(readWrite)
|
|
} else {
|
|
readWrite = m.precomputedStyle.Render(readWrite)
|
|
}
|
|
|
|
readOnly := "[ ] Read Only"
|
|
if m.perms == packet.PermRead {
|
|
readOnly = "[x] Read Only"
|
|
}
|
|
if m.selected == ReadOnlyField {
|
|
readOnly = m.precomputedStyle.Foreground(colors.Focus).Render(readOnly)
|
|
} else {
|
|
readOnly = m.precomputedStyle.Render(readOnly)
|
|
}
|
|
|
|
noAccess := "[ ] No Access"
|
|
if m.perms == packet.PermNoAccess {
|
|
noAccess = "[x] No Access"
|
|
}
|
|
if m.selected == NoAccessField {
|
|
noAccess = m.precomputedStyle.
|
|
PaddingRight(1).
|
|
Foreground(colors.Focus).
|
|
Render(noAccess)
|
|
} else {
|
|
noAccess = m.precomputedStyle.
|
|
PaddingRight(1).
|
|
Render(noAccess)
|
|
}
|
|
|
|
width := lipgloss.Width(noAccess) + lipgloss.Width(readOnly) + lipgloss.Width(readWrite)
|
|
padding := (m.nameWidth - (leftpad * 2) - width) / 2
|
|
|
|
readWrite = lipgloss.NewStyle().PaddingRight(padding).Render(readWrite)
|
|
readOnly = lipgloss.NewStyle().PaddingRight(padding).Render(readOnly)
|
|
|
|
permsHeader := lipgloss.NewStyle().
|
|
Width(m.nameWidth - leftpad).
|
|
Background(colors.Background).
|
|
Foreground(colors.Turquoise).
|
|
Render("Permissions for non-admins:")
|
|
// perms := lipgloss.JoinHorizontal(lipgloss.Top, readWrite, readOnly, noAccess)
|
|
perms := readWrite + readOnly + noAccess
|
|
perms = lipgloss.JoinVertical(lipgloss.Left, permsHeader, perms)
|
|
perms = lipgloss.NewStyle().PaddingLeft(leftpad).Render(perms)
|
|
|
|
update := lipgloss.NewStyle().
|
|
Width(m.nameWidth).
|
|
Align(lipgloss.Center).
|
|
Background(colors.Background).
|
|
Render(m.update)
|
|
|
|
content := flex.NewVertical(name, perms, colorText, update).WithGap(1).View()
|
|
|
|
return lipgloss.NewStyle().
|
|
Border(lipgloss.ThickBorder()).
|
|
Padding(1, 4).
|
|
Align(lipgloss.Center, lipgloss.Center).
|
|
BorderBackground(colors.Background).
|
|
BorderForeground(colors.White).
|
|
Background(colors.Background).
|
|
Foreground(colors.White).
|
|
Render(content)
|
|
}
|
|
|
|
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
|
switch msg := msg.(type) {
|
|
case tea.KeyMsg:
|
|
key := msg.Type
|
|
switch key {
|
|
case tea.KeyTab:
|
|
return m, m.cycle(1)
|
|
case tea.KeyShiftTab:
|
|
return m, m.cycle(-1)
|
|
|
|
default:
|
|
var cmd tea.Cmd
|
|
switch m.selected {
|
|
case NameField:
|
|
m.name, cmd = m.name.Update(msg)
|
|
case ColorField:
|
|
oldValue := m.color.Value()
|
|
position := m.color.Position()
|
|
m.color, cmd = m.color.Update(msg)
|
|
newValue := m.color.Value()
|
|
|
|
hex := "0123456789abcdefABCDEF"
|
|
invalid := false
|
|
for _, c := range newValue {
|
|
if !strings.ContainsRune(hex, c) {
|
|
invalid = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if invalid {
|
|
m.color.SetValue(oldValue)
|
|
m.color.SetCursor(position)
|
|
} else if len(m.color.Value()) == 6 {
|
|
m.lastColor = lipgloss.Color("#" + m.color.Value())
|
|
}
|
|
|
|
}
|
|
return m, cmd
|
|
}
|
|
}
|
|
|
|
return m, nil
|
|
}
|
|
|
|
func (m *Model) cycle(step int) tea.Cmd {
|
|
m.selected += step
|
|
if m.selected < 0 {
|
|
m.selected = FieldCount - 1
|
|
} else {
|
|
m.selected %= FieldCount
|
|
}
|
|
return m.updateFocus()
|
|
}
|
|
|
|
func (m *Model) updateFocus() tea.Cmd {
|
|
m.name.Blur()
|
|
m.color.Blur()
|
|
m.update = blurredUpdate()
|
|
switch m.selected {
|
|
case NameField:
|
|
return m.name.Focus()
|
|
case ColorField:
|
|
return m.color.Focus()
|
|
case NoAccessField, ReadOnlyField, ReadWriteField:
|
|
return nil
|
|
case UpdateField:
|
|
m.update = focusedUpdate()
|
|
return nil
|
|
default:
|
|
assert.Never("missing switch statement field in update focus", "selected", m.selected)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (m *Model) Select() tea.Cmd {
|
|
switch m.selected {
|
|
case NoAccessField:
|
|
m.perms = packet.PermNoAccess
|
|
return nil
|
|
case ReadOnlyField:
|
|
m.perms = packet.PermRead
|
|
return nil
|
|
case ReadWriteField:
|
|
m.perms = packet.PermReadWrite
|
|
return nil
|
|
}
|
|
|
|
if m.selected != UpdateField {
|
|
return nil
|
|
}
|
|
|
|
m.name.Input.Err = m.name.Input.Validate(m.name.Input.Value())
|
|
m.color.Err = m.color.Validate(m.color.Value())
|
|
if m.name.Input.Err != nil || m.color.Err != nil {
|
|
return nil
|
|
}
|
|
|
|
request := packet.UpdateFrequency{
|
|
Frequency: m.frequency,
|
|
Name: m.name.Input.Value(),
|
|
HexColor: "#" + m.color.Value(),
|
|
Perms: m.perms,
|
|
}
|
|
return gateway.Send(&request)
|
|
}
|