aboutsummaryrefslogtreecommitdiff
path: root/sourcehut-builds/panel.luau
diff options
context:
space:
mode:
Diffstat (limited to 'sourcehut-builds/panel.luau')
-rw-r--r--sourcehut-builds/panel.luau204
1 files changed, 204 insertions, 0 deletions
diff --git a/sourcehut-builds/panel.luau b/sourcehut-builds/panel.luau
new file mode 100644
index 0000000..a720721
--- /dev/null
+++ b/sourcehut-builds/panel.luau
@@ -0,0 +1,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