8.5 KiB · text History 49b9ecf
import QtQuick
import QtQuick.Window
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

// One run broken out of the popup into its own top-level window, so it
// can sit beside the editor while it builds. It reads the same `store`
// state as the popup (details, open jobs, logs), and the store keeps it
// refreshing at the fast pace until the run finishes. Created by
// store.openRunWindow(); closing it destroys it.
Window {
    id: runWindow

    required property var store
    required property string fullName
    required property int runNumber

    readonly property string runKey: Status.runKey(fullName, runNumber)
    readonly property var detail: store.runDetails[runKey] || null
    // Until the detail lands, the popup's list row is the best we have.
    readonly property var run: detail ? detail.run : (store.findRun(fullName, runNumber) || { number: runNumber })
    readonly property string status: Status.runStatus(run)
    readonly property bool inFlight: Status.isActive(status)
    readonly property bool watching: store.watchingRun(runKey)
    readonly property bool eachJob: store.notifyingEachJob(runKey)
    // A job to bring into view once it's on screen (from a notification).
    property string focusJobId: ""

    // Not a child of the panel: a normal window with its own taskbar entry.
    transientParent: null
    flags: Qt.Window
    width: Kirigami.Units.gridUnit * 36
    height: Kirigami.Units.gridUnit * 32
    minimumWidth: Kirigami.Units.gridUnit * 20
    minimumHeight: Kirigami.Units.gridUnit * 12
    title: run.workflow_name || run.workflow_path
           ? i18n("%1 #%2 — %3", fullName, runNumber, Status.runTitle(run))
           : i18n("%1 #%2", fullName, runNumber)

    Kirigami.Theme.colorSet: Kirigami.Theme.Window
    Kirigami.Theme.inherit: false
    color: Kirigami.Theme.backgroundColor

    onClosing: {
        store.forgetRunWindow(runWindow.runKey, runWindow)
        runWindow.destroy()
    }

    function focusJob(id) {
        focusJobId = id
        Qt.callLater(scrollToJob)
    }

    function scrollToJob() {
        if (focusJobId === "") return
        var item = body.jobItem(focusJobId)
        if (!item) return   // not loaded yet: onDetailChanged tries again
        var flick = scroll.contentItem as Flickable
        var y = item.mapToItem(body, 0, 0).y
        flick.contentY = Math.max(0, Math.min(y, flick.contentHeight - flick.height))
        focusJobId = ""
    }

    onDetailChanged: if (focusJobId !== "") Qt.callLater(scrollToJob)

    function present() {
        if (visibility === Window.Minimized) showNormal()
        else show()
        raise()
        requestActivate()
    }

    ColumnLayout {
        anchors.fill: parent
        anchors.margins: Kirigami.Units.largeSpacing
        spacing: Kirigami.Units.smallSpacing

        // ── header ──────────────────────────────────────────────────────
        RowLayout {
            Layout.fillWidth: true
            spacing: Kirigami.Units.largeSpacing

            StatusCircle {
                status: runWindow.status
                Layout.alignment: Qt.AlignTop
                Layout.preferredWidth: Kirigami.Units.iconSizes.medium
                Layout.preferredHeight: Kirigami.Units.iconSizes.medium
            }

            ColumnLayout {
                Layout.fillWidth: true
                spacing: 0

                Kirigami.Heading {
                    Layout.fillWidth: true
                    level: 2
                    elide: Text.ElideRight
                    text: Status.runTitle(runWindow.run) + " #" + runWindow.runNumber
                }

                PlasmaComponents3.Label {
                    Layout.fillWidth: true
                    elide: Text.ElideRight
                    opacity: 0.7
                    text: runWindow.fullName
                }

                PlasmaComponents3.Label {
                    Layout.fillWidth: true
                    wrapMode: Text.WordWrap
                    font: Kirigami.Theme.smallFont
                    opacity: 0.7
                    text: Status.runMeta(runWindow.run)
                }

                PlasmaComponents3.Label {
                    Layout.fillWidth: true
                    font: Kirigami.Theme.smallFont
                    textFormat: Text.StyledText
                    // `store.now` ticks so relative times stay fresh.
                    text: (runWindow.store.now, ["<font color=\"" + Status.color(runWindow.status, Kirigami.Theme) + "\">"
                                                 + Status.label(runWindow.status) + "</font>",
                           Status.relativeTime(runWindow.run.inserted_at),
                           Status.duration(runWindow.run.started_at, runWindow.run.completed_at)]
                          .filter(function(s) { return s !== "" }).join(" · "))
                }
            }

            PlasmaComponents3.BusyIndicator {
                visible: runWindow.store.busy
                Layout.alignment: Qt.AlignTop
                Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
                Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
            }

            PlasmaComponents3.CheckBox {
                Layout.alignment: Qt.AlignTop
                visible: runWindow.inFlight || runWindow.eachJob
                checked: runWindow.eachJob
                text: i18n("Each job")
                onToggled: runWindow.store.toggleEachJob(runWindow.fullName, runWindow.runNumber)
                PlasmaComponents3.ToolTip { text: i18n("Also notify as each job finishes") }
            }

            PlasmaComponents3.ToolButton {
                id: bellButton
                Layout.alignment: Qt.AlignTop
                visible: runWindow.inFlight || runWindow.watching
                checkable: true
                checked: runWindow.watching
                icon.name: runWindow.watching ? "notifications" : "notification-inactive"
                text: i18n("Notify When Finished")
                display: QQC2.AbstractButton.IconOnly
                onToggled: runWindow.store.toggleWatchRun(runWindow.fullName, runWindow.runNumber)
                PlasmaComponents3.ToolTip {
                    text: runWindow.watching ? i18n("You'll be notified when this run finishes")
                                             : i18n("Notify When Finished")
                }
            }

            PlasmaComponents3.ToolButton {
                Layout.alignment: Qt.AlignTop
                icon.name: "view-refresh"
                text: i18n("Refresh")
                display: QQC2.AbstractButton.IconOnly
                onClicked: runWindow.store.loadRunDetail(runWindow.fullName, runWindow.runNumber)
                PlasmaComponents3.ToolTip { text: i18n("Refresh") }
            }

            PlasmaComponents3.ToolButton {
                Layout.alignment: Qt.AlignTop
                icon.name: "internet-web-browser"
                text: i18n("Open in Browser")
                display: QQC2.AbstractButton.IconOnly
                onClicked: Qt.openUrlExternally(runWindow.store.api.webUrl(runWindow.fullName, runWindow.runNumber))
                PlasmaComponents3.ToolTip { text: i18n("Open in Browser") }
            }
        }

        PlasmaComponents3.Label {
            visible: runWindow.store.lastError !== ""
            Layout.fillWidth: true
            wrapMode: Text.WordWrap
            color: Kirigami.Theme.negativeTextColor
            font: Kirigami.Theme.smallFont
            text: runWindow.store.lastError
        }

        Kirigami.Separator { Layout.fillWidth: true }

        // ── jobs ────────────────────────────────────────────────────────
        // A desktop window, so the desktop style's scroll view.
        QQC2.ScrollView {
            id: scroll
            Layout.fillWidth: true
            Layout.fillHeight: true
            contentWidth: availableWidth

            RunBody {
                id: body
                width: scroll.availableWidth
                store: runWindow.store
                fullName: runWindow.fullName
                runNumber: runWindow.runNumber
                run: runWindow.run
                detail: runWindow.detail
                inWindow: true
            }
        }
    }
}