import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as QQC2
import org.kde.plasma.components as PlasmaComponents3
import org.kde.kirigami as Kirigami
import "Status.js" as Status
// The live CI wall: every job running right now, a card each, tiled to
// fill the space — GitGud's /users/ci/live, as a window of its own or as
// the widget itself on the desktop (wall mode).
//
// Up to six cards side by side (fewer when they'd get too narrow to read
// a log line in); past that the columns fill up from the right in pairs,
// as on the web and in the Android app. The order is the operator's:
// oldest-started first, or newest (the header's buttons, or R). So is
// what it shows: your own jobs, or one organization's.
//
// All state lives in `store` (main.qml): it polls /ci/live while any
// wall is on screen, so a window and a desktop wall share one feed.
Item {
id: wall
required property var store
// Leave the header's window-only buttons out on the desktop.
property bool inWindow: false
property bool fullScreen: false
property bool translucent: false
signal toggleFullScreen()
readonly property var cards: store.wallCards
readonly property var ordered: cards ? (store.wallNewestFirst ? cards.slice().reverse() : cards) : null
// One column per 13 grid units of width, at most six; more (up to six)
// when a column would stack more cards than fit at a readable height
// — a small desktop widget would otherwise draw slivers.
readonly property int widthCols: Math.max(1, Math.min(6, Math.floor(grid.width / (Kirigami.Units.gridUnit * 13))))
readonly property int rowsFit: Math.max(1, Math.floor(grid.height / (Kirigami.Units.gridUnit * 3.5)))
readonly property int maxCols: cards ? Math.min(6, Math.max(widthCols, Math.ceil(cards.length / rowsFit))) : widthCols
readonly property var columns: ordered ? Status.wallSplit(ordered, maxCols) : []
// A small desktop widget: the header keeps only what it must.
readonly property bool narrow: width < Kirigami.Units.gridUnit * 34
Component.onCompleted: store.wallViewers += 1
Component.onDestruction: store.wallViewers -= 1
ColumnLayout {
anchors.fill: parent
anchors.margins: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.smallSpacing
// ── header ──────────────────────────────────────────────────────
RowLayout {
Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing
Rectangle {
id: liveDot
Layout.preferredWidth: Kirigami.Units.smallSpacing * 2.5
Layout.preferredHeight: width
radius: width / 2
color: wall.store.wallError === "" ? Kirigami.Theme.neutralTextColor
: Kirigami.Theme.negativeTextColor
SequentialAnimation on opacity {
loops: Animation.Infinite
NumberAnimation { from: 1; to: 0.3; duration: 900 }
NumberAnimation { from: 0.3; to: 1; duration: 900 }
}
}
Kirigami.Heading {
level: 3
text: i18n("Live CI")
}
PlasmaComponents3.ComboBox {
id: scopeBox
// "" is the token owner's own wall; otherwise an org handle.
readonly property var scopes: [""].concat(wall.store.wallOrgs.map(function(o) { return o.handle }))
model: scopes.map(function(s) { return s === "" ? i18n("Mine") : "@" + s })
currentIndex: Math.max(0, scopes.indexOf(wall.store.wallScope))
onActivated: function(index) { wall.store.setWallScope(scopes[index]) }
PlasmaComponents3.ToolTip { text: i18n("Whose running jobs to show") }
}
PlasmaComponents3.Label {
visible: wall.cards !== null && !wall.narrow
opacity: 0.7
text: wall.cards ? i18np("1 job", "%1 jobs", wall.cards.length) : ""
}
// The tiling at a glance: a bar per column, split by its cards.
Row {
visible: wall.columns.length > 0 && !wall.narrow
spacing: 2
Repeater {
model: wall.columns
Column {
id: glyphColumn
required property var modelData
readonly property int cards: modelData.length
spacing: 1
Repeater {
model: glyphColumn.cards
Rectangle {
width: 5
height: (14 - (glyphColumn.cards - 1)) / glyphColumn.cards
radius: 1
color: Kirigami.Theme.disabledTextColor
}
}
}
}
}
PlasmaComponents3.Label {
visible: wall.store.wallError !== "" && wall.cards !== null
color: Kirigami.Theme.negativeTextColor
font: Kirigami.Theme.smallFont
text: i18n("offline · retrying")
PlasmaComponents3.ToolTip { text: wall.store.wallError }
}
Item { Layout.fillWidth: true }
QQC2.ButtonGroup { id: orderGroup }
PlasmaComponents3.ToolButton {
checkable: true
checked: !wall.store.wallNewestFirst
QQC2.ButtonGroup.group: orderGroup
icon.name: "view-sort-ascending"
text: i18n("Oldest first")
display: wall.narrow ? QQC2.AbstractButton.IconOnly : QQC2.AbstractButton.TextOnly
onClicked: wall.store.setWallNewestFirst(false)
PlasmaComponents3.ToolTip { text: i18n("Oldest first (R flips the order)") }
}
PlasmaComponents3.ToolButton {
checkable: true
checked: wall.store.wallNewestFirst
QQC2.ButtonGroup.group: orderGroup
icon.name: "view-sort-descending"
text: i18n("Newest first")
display: wall.narrow ? QQC2.AbstractButton.IconOnly : QQC2.AbstractButton.TextOnly
onClicked: wall.store.setWallNewestFirst(true)
PlasmaComponents3.ToolTip { text: i18n("Newest first (R flips the order)") }
}
PlasmaComponents3.ToolButton {
visible: wall.inWindow
checkable: true
checked: wall.translucent
icon.name: "transform-opacity"
text: i18n("Translucent")
display: QQC2.AbstractButton.IconOnly
onClicked: wall.store.setWallTranslucent(!wall.translucent)
PlasmaComponents3.ToolTip { text: i18n("See-through background (T)") }
}
PlasmaComponents3.ToolButton {
visible: wall.inWindow
icon.name: wall.fullScreen ? "view-restore" : "view-fullscreen"
text: wall.fullScreen ? i18n("Exit Full Screen") : i18n("Full Screen")
display: QQC2.AbstractButton.IconOnly
onClicked: wall.toggleFullScreen()
PlasmaComponents3.ToolTip { text: wall.fullScreen ? i18n("Exit Full Screen (Esc)") : i18n("Full Screen (F11)") }
}
PlasmaComponents3.ToolButton {
icon.name: "internet-web-browser"
text: i18n("Open in Browser")
display: QQC2.AbstractButton.IconOnly
onClicked: Qt.openUrlExternally(wall.store.wallWebUrl())
PlasmaComponents3.ToolTip { text: i18n("Open in Browser") }
}
}
// ── cards ───────────────────────────────────────────────────────
Item {
id: grid
Layout.fillWidth: true
Layout.fillHeight: true
PlasmaComponents3.Label {
anchors.centerIn: parent
width: parent.width - Kirigami.Units.gridUnit * 2
visible: wall.ordered === null || wall.ordered.length === 0
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
opacity: 0.7
text: !wall.store.wallConfigured
? i18n("Configure the widget to connect to your GitGud instance")
: wall.ordered === null
? (wall.store.wallError !== "" ? wall.store.wallError : i18n("Loading…"))
: i18n("Nothing running. Jobs appear here the moment a runner picks them up.")
}
RowLayout {
anchors.fill: parent
spacing: Kirigami.Units.smallSpacing
Repeater {
model: wall.columns
ColumnLayout {
id: column
required property var modelData
Layout.fillWidth: true
Layout.fillHeight: true
Layout.preferredWidth: 1 // equal shares
spacing: Kirigami.Units.smallSpacing
Repeater {
model: column.modelData
WallCard {
required property var modelData
Layout.fillWidth: true
Layout.fillHeight: true
Layout.preferredHeight: 1 // equal shares
card: modelData
translucent: wall.translucent
now: wall.store.now
}
}
}
}
}
}
}
}
neiam
/gitgud-plasmoid
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
Log in to mark this repository.
10.1 KiB · text
History
495b6f2