1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
|
--!nonstrict
-- Sourcehut Builds - Panel that shows the
--
-- You can configure the glyphs shown for the different statuses, as well as
-- when there are no recent builds.
local EmptyState = require("./empty_state.luau")
local StringUtils = require("./string_utils.luau")
local K = require("./constants.luau")
--------------------------------------------------------------------------------
-- State
--------------------------------------------------------------------------------
local isOpen = false
-- I'm being lazy and not cloning this because my assumption is that I'll always
-- replace the object from the network call and not modify it. But I know I will
-- forget and break that assumption, so this comment is left here because
-- whatever bug you're finding with stale state or bad empty state is caused by
-- not cloning empty state across the files.
local buildState: SourcehutBuildsState = EmptyState
local pollerStatus: PollerStatus = K.poller.state.pending
--------------------------------------------------------------------------------
-- Internal Functions
--------------------------------------------------------------------------------
------------------------------------------------------------ Translation Helpers
local function emptyStateLabel(): string
if pollerStatus.type == "pending" then
return noctalia.tr("panel.loading.label")
elseif pollerStatus.type == "error" then
return noctalia.tr("panel.error.label", {error = pollerStatus.error})
else
return noctalia.tr("panel.empty.label")
end
end
------------------------------------------------------------------------ Actions
local function openBuild(build: SourcehutBuild, task: SourcehutBuildTask | nil)
local url = K.sourcehut.web_url .. buildState.user .. '/job/' .. build.id
if task ~= nil then
url = url .. "#task-" .. task.name
end
noctalia.runAsync("gio open '" .. url .. "' || xdg-open '" .. url .. "'")
end
--------------------------------------------------------------------- UI: Glyphs
local function buildGlyph(status: SourcehutBuildStatus): string
if status == "PENDING" then
return "circle"
elseif status == "RUNNING" then
return "circle-caret-right"
elseif status == "FAILED" then
return "x"
elseif status == "SUCCESS" then
return "check"
elseif status == "TIMEOUT" then
return "clock-x"
elseif status == "CANCELLED" then
return "circle-off"
end
return "question-mark"
end
local function taskGlyph(status: SourcehutBuildTaskStatus): string
if status == "PENDING" then
return "circle-dotted"
elseif status == "RUNNING" then
return "circle-caret-right"
elseif status == "FAILED" then
return "x"
elseif status == "SUCCESS" then
return "check"
elseif status == "SKIPPED" then
return "circle-dashed-minus"
end
return "question-mark"
end
----------------------------------------------------------------- UI: Components
local function taskButton(build: SourcehutBuild, task: SourcehutBuildTask): UiNode
return ui.button({
glyph = taskGlyph(task.status),
controlSize = "sm",
variant = "ghost",
tooltip = noctalia.tr("panel.task.tooltip", {
task = task.name,
status = StringUtils.capitalize(task.status),
}),
onClick = function() openBuild(build, task) end
})
end
local function buildButton(build: SourcehutBuild): UiNode
return ui.button({
glyph = buildGlyph(build.status),
text = noctalia.tr("panel.build.label", {
user = buildState.user,
repo = build.tags[1],
id = build.id
}),
controlSize = "sm",
variant = "ghost",
tooltip = noctalia.tr("panel.build.tooltip", {
status = StringUtils.capitalize(build.status),
}),
onClick = function() openBuild(build) end
})
end
local function buildRow(build: SourcehutBuild): UiNode
local tasks: {[number]: UiNode} = {}
for _k, task in ipairs(build.tasks) do
tasks[#tasks + 1] = taskButton(build, task)
end
return ui.row({align = "start", justify = "space_between", gap = 2, flexGrow = 1}, {
buildButton(build),
ui.row({justify = "end", gap = 0, flexGrow = 0}, tasks)
})
end
local function buildRows(builds: {[number]: SourcehutBuild}): UiNode
local build_rows: {[number]: UiNode} = {}
for _, build in ipairs(builds) do
build_rows[#build_rows+1] = buildRow(build)
end
return ui.column({ flexGrow = 1, align = "top", justify = "start" }, build_rows)
end
-- Renders the panel
local function render()
local body
local header = ui.row({ align = "center", gap = 8 }, {
ui.label({
text = noctalia.tr("panel.title"),
fontSize = K.panel.theme.fontSize.heading,
fontWeight = K.panel.theme.fontWeight.heading,
flexGrow = 1
}),
})
if #buildState.builds == 0 then
body = ui.column({ flexGrow = 1, align = "center", justify = "center" }, {
ui.label({ text = emptyStateLabel(), color = "on_surface_variant" }),
})
else
body = buildRows(buildState.builds)
end
if isOpen then
panel.render(ui.column({ flexGrow = 1, gap = 10, align = "stretch" }, { header, body }))
end
end
---------------------------------------------------------------- Event Listeners
local function onPollerStateChanged(value: PollerStatus)
pollerStatus = value
render()
end
local function onBuildsStateChanged(value: SourcehutBuildsState)
buildState = value
render()
end
--------------------------------------------------------------------------------
-- Listeners
--------------------------------------------------------------------------------
-- Synchronizes the panel state
noctalia.state.watch(K.events.poller_state_updated, onPollerStateChanged)
noctalia.state.watch(K.events.builds_state_updated, onBuildsStateChanged)
--------------------------------------------------------------------------------
-- Public API
--------------------------------------------------------------------------------
function onOpen(_context)
isOpen = true
noctalia.state.set(K.events.panel_state_updated, isOpen)
render()
end
function onClose()
isOpen = false
noctalia.state.set(K.events.panel_state_updated, isOpen)
end
function onToggle(value: boolean)
isOpen = value
render()
end
function onConfigChanged()
render()
end
|