aboutsummaryrefslogtreecommitdiff
path: root/sourcehut-builds/panel.luau
blob: a72072199562b5dad761bac969714ab470633ca7 (plain)
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