Add nightwatch75/topgrade-wrapper 0.0.2 (#114)
A bar plugin for topgrade: the glyph carries the number of packages waiting, and the panel counts them and starts the upgrade in a terminal window. topgrade has no "how many packages?" mode, so counting is two-staged. `topgrade --dry-run --no-self-update` reports the steps topgrade would actually run, which is how the user's own topgrade.toml decides what gets counted; each "Dry running:" command is then matched against a table of 13 package managers, and every match is asked once, read-only, to list what it has pending. The count is the length of that list, so the bar number and the list a panel row expands into are the same answer and expanding costs no second trip to a mirror. Steps nothing can count are named rather than folded into the total, and a partly covered step names the manager that could not answer, so an Arch box with an AUR helper but no pacman-contrib never reads its AUR total as the whole system update. The upgrade never runs in the background: it opens a terminal so package managers can prompt and sudo can ask on the tty. The plugin writes no files and never rewrites topgrade's configuration. Co-authored-by: nightwatch75 <nightwatch75@users.noreply.github.com>
This commit is contained in:
co-authored by
nightwatch75
parent
990c777059
commit
f6983da41a
@@ -0,0 +1,143 @@
|
||||
--!nonstrict
|
||||
-- topgrade-wrapper — bar widget: pending-update badge and panel toggle.
|
||||
--
|
||||
-- A pure renderer over the shared state the engine (service.luau) publishes on
|
||||
-- "topgrade_state"; the count is therefore already there when the panel has
|
||||
-- never been opened, and every bar showing the widget agrees. Actions are sent
|
||||
-- back as "topgrade_request" entries rather than run here, so one engine owns
|
||||
-- the topgrade process no matter how many widget instances exist.
|
||||
--
|
||||
-- Click mapping:
|
||||
-- Left click — open/close the panel
|
||||
-- Right click — check for updates now
|
||||
--
|
||||
-- There is deliberately no middle-click handler: every bar widget carries a
|
||||
-- built-in `middle` binding that opens its own settings, and a user binding wins
|
||||
-- over a script callback, so one here would be dead code. Claiming the gesture
|
||||
-- back needs [widget.actions], which is plugin_api 14.
|
||||
|
||||
local PANEL_ID = "nightwatch75/topgrade-wrapper:panel"
|
||||
local REQUEST_KEY = "topgrade_request"
|
||||
local STATE_KEY = "topgrade_state"
|
||||
|
||||
local snapshot = nil
|
||||
|
||||
local function tr(key, args)
|
||||
return noctalia.tr(key, args)
|
||||
end
|
||||
|
||||
-- The nonce is monotonic across writers (every widget instance and the panel):
|
||||
-- each seeds from the last request already in the shared state.
|
||||
local function request(action)
|
||||
local prev = noctalia.state.get(REQUEST_KEY)
|
||||
local nonce = (type(prev) == "table" and tonumber(prev.nonce) or 0) + 1
|
||||
noctalia.state.set(REQUEST_KEY, { nonce = nonce, action = action })
|
||||
end
|
||||
|
||||
-- "pacman 5 · Flatpak 1" — the per-manager breakdown, non-zero entries only.
|
||||
local function breakdown()
|
||||
if snapshot == nil or type(snapshot.counts) ~= "table" then
|
||||
return ""
|
||||
end
|
||||
local parts = {}
|
||||
for _, entry in ipairs(snapshot.counts) do
|
||||
if entry.n > 0 then
|
||||
table.insert(parts, tr("count." .. entry.key) .. " " .. entry.n)
|
||||
end
|
||||
end
|
||||
return table.concat(parts, " · ")
|
||||
end
|
||||
|
||||
local function statusLabel()
|
||||
if snapshot == nil then
|
||||
return tr("status_idle")
|
||||
end
|
||||
local phase = snapshot.phase
|
||||
if phase == "missing" then
|
||||
return tr("status_missing")
|
||||
elseif phase == "checking" then
|
||||
local current = snapshot.step
|
||||
if current ~= nil and current ~= "" then
|
||||
return tr("status_checking_step", { step = current })
|
||||
end
|
||||
return tr("status_checking")
|
||||
elseif phase == "running" then
|
||||
return tr("status_running")
|
||||
elseif phase == "error" then
|
||||
return snapshot.err or tr("status_error")
|
||||
elseif phase == "clean" then
|
||||
return tr("status_clean")
|
||||
elseif phase == "ready" then
|
||||
return noctalia.trp("status_ready", snapshot.total or 0, {})
|
||||
end
|
||||
return tr("status_idle")
|
||||
end
|
||||
|
||||
-- Updates are "pending" only while they are worth interrupting the user for:
|
||||
-- a dismissed result stays visible in the panel but takes the bar back to its
|
||||
-- resting colour.
|
||||
local function pending()
|
||||
return snapshot ~= nil
|
||||
and snapshot.phase == "ready"
|
||||
and snapshot.dismissed ~= true
|
||||
and (snapshot.total or 0) > 0
|
||||
end
|
||||
|
||||
local function render()
|
||||
barWidget.setGlyph(noctalia.getConfig("glyph"))
|
||||
|
||||
local phase = snapshot ~= nil and snapshot.phase or "idle"
|
||||
if phase == "missing" or phase == "error" then
|
||||
barWidget.setGlyphColor("error")
|
||||
elseif phase == "checking" or phase == "running" then
|
||||
barWidget.setGlyphColor("secondary")
|
||||
elseif pending() then
|
||||
barWidget.setGlyphColor("primary")
|
||||
else
|
||||
barWidget.setGlyphColor("on_surface")
|
||||
end
|
||||
|
||||
if pending() and noctalia.getConfig("show_count") == true then
|
||||
barWidget.setText(tostring(snapshot.total))
|
||||
else
|
||||
barWidget.setText("")
|
||||
end
|
||||
|
||||
-- A semantic row label, not the plugin name: the tooltip hangs off this
|
||||
-- plugin's own glyph, so restating "Topgrade Wrapper" only costs width.
|
||||
local rows = { { key = tr("tooltip_status"), value = statusLabel() } }
|
||||
local detail = breakdown()
|
||||
if detail ~= "" then
|
||||
table.insert(rows, { key = tr("tooltip_pending"), value = detail })
|
||||
end
|
||||
if snapshot ~= nil and snapshot.checkedAt ~= nil and snapshot.checkedAt ~= "" then
|
||||
table.insert(rows, { key = tr("tooltip_checked"), value = snapshot.checkedAt })
|
||||
end
|
||||
table.insert(rows, { key = "", value = tr("tooltip_hints") })
|
||||
barWidget.setTooltip(rows)
|
||||
end
|
||||
|
||||
noctalia.state.watch(STATE_KEY, function(value)
|
||||
if type(value) == "table" then
|
||||
snapshot = value
|
||||
render()
|
||||
end
|
||||
end)
|
||||
|
||||
-- Periodic re-render keeps the glyph and count in sync with widget-setting
|
||||
-- edits, which do not move the engine's state.
|
||||
function update()
|
||||
render()
|
||||
end
|
||||
|
||||
function onClick()
|
||||
noctalia.togglePanel(PANEL_ID)
|
||||
end
|
||||
|
||||
function onRightClick()
|
||||
request("check")
|
||||
end
|
||||
|
||||
noctalia.setUpdateInterval(1000)
|
||||
snapshot = noctalia.state.get(STATE_KEY)
|
||||
render()
|
||||
Reference in New Issue
Block a user