tangled
alpha
login
or
join now
treethought.xyz
/
attie
5
fork
atom
AT Protocol Terminal Interface Explorer
5
fork
atom
overview
issues
pulls
pipelines
initial work for full record view
Cam Sweeney
3 weeks ago
e4741310
214fd2e9
+120
-64
3 changed files
expand all
collapse all
unified
split
ui
app.go
collection.go
record.go
+31
-16
ui/app.go
···
16
16
)
17
17
18
18
type App struct {
19
19
-
client *at.Client
20
20
-
search *CommandPallete
21
21
-
repoView *RepoView
22
22
-
rlist *RecordsList
23
23
-
active tea.Model
24
24
-
err string
25
25
-
w, h int
19
19
+
client *at.Client
20
20
+
search *CommandPallete
21
21
+
repoView *RepoView
22
22
+
rlist *RecordsList
23
23
+
recordView *RecordView
24
24
+
active tea.Model
25
25
+
err string
26
26
+
w, h int
26
27
}
27
28
28
29
func NewApp() *App {
29
30
search := &CommandPallete{}
30
31
repoView := NewRepoView()
31
32
return &App{
32
32
-
client: at.NewClient(""),
33
33
-
search: search,
34
34
-
repoView: repoView,
35
35
-
rlist: NewRecordsList(nil),
36
36
-
active: search,
33
33
+
client: at.NewClient(""),
34
34
+
search: search,
35
35
+
repoView: repoView,
36
36
+
rlist: NewRecordsList(nil),
37
37
+
recordView: NewRecordView(false),
38
38
+
active: search,
37
39
}
38
40
}
39
41
···
45
47
cmds := []tea.Cmd{}
46
48
a.search.SetSize(a.w, a.h)
47
49
a.repoView.SetSize(a.w, a.h)
48
48
-
if a.rlist != nil {
49
49
-
a.rlist.SetSize(a.w, a.h)
50
50
-
}
50
50
+
a.rlist.SetSize(a.w, a.h)
51
51
+
a.recordView.SetSize(a.w, a.h)
51
52
return tea.Batch(cmds...)
52
53
}
53
53
-
54
54
55
55
func (a *App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
56
56
switch msg := msg.(type) {
···
72
72
case a.rlist:
73
73
a.active = a.repoView
74
74
return a, nil
75
75
+
case a.recordView:
76
76
+
a.active = a.rlist
77
77
+
return a, nil
75
78
}
76
79
}
77
80
···
89
92
90
93
case repoLoadedMsg:
91
94
cmd := a.repoView.SetRepo(msg.repo)
95
95
+
a.repoView.SetSize(a.w, a.h) // Set size before switching view
92
96
a.active = a.repoView
93
97
a.search.loading = false
94
98
return a, cmd
···
99
103
100
104
case recordsLoadedMsg:
101
105
cmd := a.rlist.SetRecords(msg.records)
106
106
+
a.rlist.SetSize(a.w, a.h) // Set size before switching view
102
107
a.active = a.rlist
103
108
a.search.loading = false
104
109
return a, cmd
110
110
+
111
111
+
case recordSelectedMsg:
112
112
+
a.recordView.SetRecord(msg.record)
113
113
+
a.recordView.SetSize(a.w, a.h) // Set size before switching view
114
114
+
a.active = a.recordView
115
115
+
return a, nil
105
116
106
117
case repoErrorMsg:
107
118
a.search.err = msg.err.Error()
···
163
174
164
175
type recordsLoadedMsg struct {
165
176
records []*agnostic.RepoListRecords_Record
177
177
+
}
178
178
+
179
179
+
type recordSelectedMsg struct {
180
180
+
record *agnostic.RepoListRecords_Record
166
181
}
167
182
168
183
type repoErrorMsg struct {
+75
ui/record.go
···
1
1
+
package ui
2
2
+
3
3
+
import (
4
4
+
"encoding/json"
5
5
+
"fmt"
6
6
+
7
7
+
"github.com/bluesky-social/indigo/api/agnostic"
8
8
+
"github.com/bluesky-social/indigo/atproto/syntax"
9
9
+
"github.com/charmbracelet/bubbles/viewport"
10
10
+
tea "github.com/charmbracelet/bubbletea"
11
11
+
"github.com/charmbracelet/lipgloss"
12
12
+
)
13
13
+
14
14
+
type RecordView struct {
15
15
+
record *agnostic.RepoListRecords_Record
16
16
+
vp viewport.Model
17
17
+
header string
18
18
+
preview bool
19
19
+
}
20
20
+
21
21
+
func NewRecordView(preview bool) *RecordView {
22
22
+
vp := viewport.New(80, 20)
23
23
+
return &RecordView{
24
24
+
vp: vp,
25
25
+
preview: preview,
26
26
+
}
27
27
+
}
28
28
+
29
29
+
func (rv *RecordView) SetSize(w, h int) {
30
30
+
rv.vp.Width = w
31
31
+
rv.vp.Height = h - lipgloss.Height(rv.header)
32
32
+
}
33
33
+
34
34
+
func (rv *RecordView) buildHeader() string {
35
35
+
if rv.record == nil {
36
36
+
return ""
37
37
+
}
38
38
+
uri, err := syntax.ParseATURI(rv.record.Uri)
39
39
+
if err != nil {
40
40
+
return headerStyle.Render(rv.record.Uri)
41
41
+
}
42
42
+
header := rv.record.Uri
43
43
+
if rv.preview {
44
44
+
header = fmt.Sprintf("%s/%s", uri.Collection(), uri.RecordKey().String())
45
45
+
}
46
46
+
return headerStyle.Render(header)
47
47
+
}
48
48
+
49
49
+
func (rv *RecordView) SetRecord(record *agnostic.RepoListRecords_Record) {
50
50
+
rv.record = record
51
51
+
if rv.record == nil || rv.record.Value == nil {
52
52
+
rv.vp.SetContent("")
53
53
+
return
54
54
+
}
55
55
+
data, err := json.MarshalIndent(rv.record.Value, "", " ")
56
56
+
if err != nil {
57
57
+
data = fmt.Appendf([]byte{}, "error marshaling record: %v", err)
58
58
+
}
59
59
+
rv.vp.SetContent(string(data))
60
60
+
rv.header = rv.buildHeader()
61
61
+
}
62
62
+
63
63
+
func (rv *RecordView) Init() tea.Cmd {
64
64
+
return rv.vp.Init()
65
65
+
}
66
66
+
67
67
+
func (rv *RecordView) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
68
68
+
var cmd tea.Cmd
69
69
+
rv.vp, cmd = rv.vp.Update(msg)
70
70
+
return rv, cmd
71
71
+
}
72
72
+
73
73
+
func (rv *RecordView) View() string {
74
74
+
return lipgloss.JoinVertical(lipgloss.Left, rv.header, rv.vp.View())
75
75
+
}
+14
-48
ui/records.go
ui/collection.go
···
1
1
package ui
2
2
3
3
import (
4
4
-
"encoding/json"
5
4
"fmt"
6
5
"strings"
7
6
8
7
"github.com/bluesky-social/indigo/api/agnostic"
9
8
"github.com/bluesky-social/indigo/atproto/syntax"
10
9
"github.com/charmbracelet/bubbles/list"
11
11
-
"github.com/charmbracelet/bubbles/viewport"
12
10
tea "github.com/charmbracelet/bubbletea"
13
11
"github.com/charmbracelet/lipgloss"
14
12
)
15
13
16
16
-
type RecordView struct {
17
17
-
record *agnostic.RepoListRecords_Record
18
18
-
vp viewport.Model
19
19
-
}
20
20
-
21
21
-
func NewRecordView() *RecordView {
22
22
-
vp := viewport.New(80, 20)
23
23
-
return &RecordView{
24
24
-
vp: vp,
25
25
-
}
26
26
-
}
27
27
-
28
28
-
func (rv *RecordView) SetSize(w, h int) {
29
29
-
rv.vp.Width = w
30
30
-
rv.vp.Height = h
31
31
-
}
32
32
-
33
33
-
func (rv *RecordView) SetRecord(record *agnostic.RepoListRecords_Record) {
34
34
-
rv.record = record
35
35
-
if rv.record == nil || rv.record.Value == nil {
36
36
-
rv.vp.SetContent("")
37
37
-
return
38
38
-
}
39
39
-
data, err := json.MarshalIndent(rv.record.Value, "", " ")
40
40
-
if err != nil {
41
41
-
data = fmt.Appendf([]byte{}, "error marshaling record: %v", err)
42
42
-
}
43
43
-
rv.vp.SetContent(string(data))
44
44
-
}
45
45
-
46
46
-
func (rv *RecordView) Init() tea.Cmd {
47
47
-
return nil
48
48
-
}
49
49
-
50
50
-
func (rv *RecordView) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
51
51
-
var cmd tea.Cmd
52
52
-
rv.vp, cmd = rv.vp.Update(msg)
53
53
-
return rv, cmd
54
54
-
}
55
55
-
56
56
-
func (rv *RecordView) View() string {
57
57
-
return rv.vp.View()
58
58
-
}
59
59
-
60
14
type RecordsList struct {
61
15
rlist list.Model
62
62
-
preview RecordView
16
16
+
preview *RecordView
63
17
header string
64
18
w, h int
65
19
}
···
108
62
l.SetFilteringEnabled(true)
109
63
rl := &RecordsList{
110
64
rlist: l,
111
111
-
preview: RecordView{},
65
65
+
preview: NewRecordView(true),
112
66
}
113
67
rl.SetRecords(records)
114
68
return rl
···
171
125
if item, ok := rl.rlist.SelectedItem().(RecordListItem); ok {
172
126
rl.preview.SetRecord(item.r)
173
127
}
128
128
+
switch msg := msg.(type) {
129
129
+
case tea.KeyMsg:
130
130
+
switch msg.String() {
131
131
+
case "enter":
132
132
+
if item, ok := rl.rlist.SelectedItem().(RecordListItem); ok {
133
133
+
return rl, func() tea.Msg {
134
134
+
return recordSelectedMsg{record: item.r}
135
135
+
}
136
136
+
}
137
137
+
}
138
138
+
}
139
139
+
174
140
return rl, cmd
175
141
}
176
142