feat: unified navigation

fix: install script copying build binary
chore: updated readme
This commit is contained in:
2026-01-25 21:49:34 -06:00
parent 1bbfc332d8
commit 6165a10481
10 changed files with 273 additions and 209 deletions

View File

@@ -17,13 +17,6 @@ import (
"playback/internal/ui/waveform"
)
// FocusedView represents which view has focus
type FocusedView int
const (
FocusWaveform FocusedView = iota
FocusTranscript
)
// Model is the main application model
type Model struct {
@@ -44,7 +37,6 @@ type Model struct {
transcript transcript.Model
// State
focused FocusedView
showHelp bool
width int
height int
@@ -66,7 +58,6 @@ func New(audioPath, transcriptPath string) Model {
header: header.New(),
waveform: waveform.New(),
transcript: transcript.New(),
focused: FocusWaveform,
}
return m
@@ -144,7 +135,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.updateLayout()
case tea.KeyMsg:
// Global keys
// All shortcuts are now global
switch {
case key.Matches(msg, m.keys.Quit):
m.quitting = true
@@ -157,38 +148,25 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case key.Matches(msg, m.keys.PlayPause):
m.player.Toggle()
case key.Matches(msg, m.keys.FocusWaveform):
m.focused = FocusWaveform
m.waveform.SetFocused(true)
m.transcript.SetFocused(false)
case key.Matches(msg, m.keys.FocusTranscript):
m.focused = FocusTranscript
m.waveform.SetFocused(false)
m.transcript.SetFocused(true)
case key.Matches(msg, m.keys.EnterEdit):
if m.focused == FocusTranscript {
m.player.Pause()
return m, m.launchEditor()
}
}
m.player.Pause()
return m, m.launchEditor()
// Context-specific keys
if m.focused == FocusWaveform {
switch {
case key.Matches(msg, m.keys.SeekForward):
m.player.SeekRelative(m.config.SeekStep)
case key.Matches(msg, m.keys.SeekBackward):
m.player.SeekRelative(-m.config.SeekStep)
case key.Matches(msg, m.keys.SeekForwardBig):
m.player.SeekRelative(m.config.BigSeekStep)
case key.Matches(msg, m.keys.SeekBackwardBig):
m.player.SeekRelative(-m.config.BigSeekStep)
}
}
// Seeking shortcuts (previously waveform-only, now global)
case key.Matches(msg, m.keys.SeekForward):
m.player.SeekRelative(m.config.SeekStep)
if m.focused == FocusTranscript {
case key.Matches(msg, m.keys.SeekBackward):
m.player.SeekRelative(-m.config.SeekStep)
case key.Matches(msg, m.keys.SeekForwardBig):
m.player.SeekRelative(m.config.BigSeekStep)
case key.Matches(msg, m.keys.SeekBackwardBig):
m.player.SeekRelative(-m.config.BigSeekStep)
// Transcript navigation and other keys forward to transcript
default:
cmd := m.transcript.Update(msg)
cmds = append(cmds, cmd)
}
@@ -262,9 +240,6 @@ func (m *Model) updateLayout() {
m.header.SetWidth(m.width)
m.waveform.SetSize(m.width, waveformHeight)
m.transcript.SetSize(m.width, transcriptHeight)
m.waveform.SetFocused(m.focused == FocusWaveform)
m.transcript.SetFocused(m.focused == FocusTranscript)
}
func (m Model) launchEditor() tea.Cmd {
@@ -272,7 +247,7 @@ func (m Model) launchEditor() tea.Cmd {
if t == nil {
return nil
}
lineNum := m.transcript.SelectedCueLineNumber()
lineNum := m.transcript.ActiveCueLineNumber()
c := exec.Command(m.config.Editor, fmt.Sprintf("+%d", lineNum), t.FilePath)
return tea.ExecProcess(c, func(err error) tea.Msg {
return VimExitedMsg{Path: t.FilePath, Err: err}
@@ -330,13 +305,6 @@ func (m Model) renderStatus() string {
modeStyle := ui.ModeStyle
mode := modeStyle.Render(m.transcript.ModeString())
// Focus indicator
focusStr := "Waveform"
if m.focused == FocusTranscript {
focusStr = "Transcript"
}
focus := ui.BaseStyle.Render(fmt.Sprintf("[%s]", focusStr))
// Status message
statusMsg := ui.StatusBarStyle.Render(m.statusMsg)
@@ -347,10 +315,8 @@ func (m Model) renderStatus() string {
lipgloss.Center,
mode,
" ",
focus,
" ",
statusMsg,
lipgloss.NewStyle().Width(m.width-lipgloss.Width(mode)-lipgloss.Width(focus)-lipgloss.Width(statusMsg)-lipgloss.Width(helpHint)-8).Render(""),
lipgloss.NewStyle().Width(m.width-lipgloss.Width(mode)-lipgloss.Width(statusMsg)-lipgloss.Width(helpHint)-4).Render(""),
helpHint,
)
}

View File

@@ -9,17 +9,11 @@ type KeyMap struct {
Help key.Binding
PlayPause key.Binding
// Focus
FocusWaveform key.Binding
FocusTranscript key.Binding
// Waveform navigation
// Navigation (global)
SeekForward key.Binding
SeekBackward key.Binding
SeekForwardBig key.Binding
SeekBackwardBig key.Binding
// Transcript navigation
ScrollUp key.Binding
ScrollDown key.Binding
PageUp key.Binding
@@ -46,14 +40,6 @@ func DefaultKeyMap() KeyMap {
key.WithKeys(" "),
key.WithHelp("space", "play/pause"),
),
FocusWaveform: key.NewBinding(
key.WithKeys("ctrl+k"),
key.WithHelp("ctrl+k", "focus waveform"),
),
FocusTranscript: key.NewBinding(
key.WithKeys("ctrl+j"),
key.WithHelp("ctrl+j", "focus transcript"),
),
SeekForward: key.NewBinding(
key.WithKeys("l", "right"),
key.WithHelp("l/→", "seek forward"),
@@ -72,27 +58,27 @@ func DefaultKeyMap() KeyMap {
),
ScrollUp: key.NewBinding(
key.WithKeys("k", "up"),
key.WithHelp("k/↑", "scroll up"),
key.WithHelp("k/↑", "previous cue"),
),
ScrollDown: key.NewBinding(
key.WithKeys("j", "down"),
key.WithHelp("j/↓", "scroll down"),
key.WithHelp("j/↓", "next cue"),
),
PageUp: key.NewBinding(
key.WithKeys("ctrl+u"),
key.WithHelp("ctrl+u", "page up"),
key.WithHelp("ctrl+u", "jump 5 cues up"),
),
PageDown: key.NewBinding(
key.WithKeys("ctrl+d"),
key.WithHelp("ctrl+d", "page down"),
key.WithHelp("ctrl+d", "jump 5 cues down"),
),
GoTop: key.NewBinding(
key.WithKeys("g"),
key.WithHelp("gg", "go to top"),
key.WithHelp("g", "go to first cue"),
),
GoBottom: key.NewBinding(
key.WithKeys("G"),
key.WithHelp("G", "go to bottom"),
key.WithHelp("G", "go to last cue"),
),
EnterEdit: key.NewBinding(
key.WithKeys("i"),
@@ -107,24 +93,21 @@ func (k KeyMap) HelpView() string {
Global:
space Play/Pause
ctrl+j Focus transcript
ctrl+k Focus waveform
q Quit
? Toggle help
Waveform (when focused):
Navigation (Global):
h / ← Seek backward (5s)
l / → Seek forward (5s)
H Seek backward (30s)
L Seek forward (30s)
Transcript (when focused):
j / ↓ Next cue
k / ↑ Previous cue
ctrl+d Jump 5 cues down
ctrl+u Jump 5 cues up
g Go to first cue
G Go to last cue
enter Seek audio to cue
Editing:
i Edit in $EDITOR at cue`
}