// Copyright (C) 2026 Danilo M. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 as // published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. import QtQuick import "../.." // One section per action, in the order they are done, each hidden when it // has nothing and no error. Read-only: installing and building stay in the // terminal. Column { id: page required property var up readonly property var d: up.doc spacing: 16 // Rows carry `from` and `to` apart so the target can stand out; a row // with a single value (new, removed) leaves `from` empty. function tracked(list) { return list.map(r => ({ name: r.name, repo: r.repo, from: r.from, to: r.to })); } readonly property var sections: !d ? [] : [ { title: "Slackware upgrades", err: d.errors.mirror || "", rows: d.slackware.upgrade.map(r => ({ name: r.name, repo: "", from: r.from, to: r.to })) }, { title: "New in Slackware", err: "", rows: d.slackware.new.map(r => ({ name: r.name, repo: "", from: "", to: r.version })) }, { title: "Removed from Slackware", err: "", rows: d.slackware.removed.map(r => ({ name: r.name, repo: "", from: "", to: "removed " + r.date })) }, { title: "Upgrade here", err: d.errors.repo || "", rows: tracked(d.upgrade) }, { title: "Awaiting build", err: "", rows: tracked(d.build) }, { title: "Upstream newer", err: d.errors.nvchecker || "", rows: tracked(d.upstream) }, { title: "App updates", err: "", rows: tracked(d.apps) }, ] function repoColor(r) { return { "personal": Theme.blue, "my-sbo": Theme.mauve, "pentesting": Theme.peach, "SBo": Theme.teal }[r] || Theme.overlay; } function ago(t) { const m = Math.round((Date.now() / 1000 - t) / 60); return m < 1 ? "just now" : m < 60 ? m + " min ago" : Math.round(m / 60) + " h ago"; } Text { visible: page.d !== null && page.up.toInstall === 0 && page.up.toBuild === 0 && page.d.upstream.length === 0 && page.d.apps.length === 0 text: "Up to date" color: Theme.subtext font { family: Theme.fontFamily; pixelSize: Theme.fontSize } } Repeater { model: page.sections Column { id: section required property var modelData width: page.width spacing: 2 visible: modelData.rows.length > 0 || modelData.err !== "" // A long section starts folded, so 471 Slackware upgrades do not // bury everything below them. A refresh rebuilds the delegates // and folds it again. property bool open: modelData.rows.length <= 10 Item { width: page.width height: heading.implicitHeight + 6 Row { id: heading anchors.verticalCenter: parent.verticalCenter spacing: 8 Text { anchors.verticalCenter: parent.verticalCenter width: 14 text: section.open ? "\uf078" : "\uf054" color: Theme.subtext font { family: Theme.iconFamily; pixelSize: Theme.fontSize - 3 } } Text { text: section.modelData.title color: Theme.text font { family: Theme.fontFamily; pixelSize: Theme.fontSize + 1; bold: true } } Text { anchors.verticalCenter: parent.verticalCenter visible: section.modelData.rows.length > 0 text: section.modelData.rows.length color: Theme.subtext font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 1 } } } MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor onClicked: section.open = !section.open } } Text { visible: section.modelData.err !== "" text: section.modelData.err color: Theme.subtext font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 1; italic: true } } Repeater { model: section.open ? section.modelData.rows : [] Rectangle { required property var modelData required property int index width: page.width height: 26 radius: 4 // Every other row shaded, to carry the eye from the name // across to its version. color: index % 2 ? Qt.alpha(Theme.surface, 0.45) : "transparent" Text { anchors { left: parent.left; leftMargin: 8; right: repo.left; rightMargin: 8; verticalCenter: parent.verticalCenter } elide: Text.ElideRight textFormat: Text.PlainText text: modelData.name color: Theme.text font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 1 } } // The repo as a pill, one colour per repo. Never red, green, // yellow or the accent: those carry meaning elsewhere. Rectangle { id: repo readonly property color tint: page.repoColor(modelData.repo) anchors { right: ver.left; rightMargin: 16; verticalCenter: parent.verticalCenter } visible: modelData.repo !== "" width: visible ? repoText.implicitWidth + 14 : 0 height: repoText.implicitHeight + 4 radius: height / 2 color: Qt.alpha(tint, 0.18) border { width: 1; color: Qt.alpha(tint, 0.5) } Text { id: repoText anchors.centerIn: parent text: modelData.repo color: repo.tint font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 } } } // Monospace so digits line up down the column; the // version being moved to is the bright one. Row { id: ver anchors { right: parent.right; rightMargin: 8; verticalCenter: parent.verticalCenter } spacing: 6 Text { visible: modelData.from !== "" textFormat: Text.PlainText text: modelData.from + " →" color: Theme.subtext font { family: Theme.iconFamily; pixelSize: Theme.fontSize - 1 } } Text { textFormat: Text.PlainText text: modelData.to color: Theme.text font { family: Theme.iconFamily; pixelSize: Theme.fontSize - 1 } } } } } } } Row { spacing: 10 Text { anchors.verticalCenter: parent.verticalCenter text: page.up.checking ? "checking…" : page.d && page.d.checked ? "checked " + page.ago(page.d.checked) : "" color: Theme.subtext font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 1 } } Button { text: "Refresh" enabled: !page.up.checking onClicked: page.up.run(true) } } }