--!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