- hypr/apps.lua - hypr/autostart.lua - hypr/envs.lua - hypr/hyprland.lua - hypr/hyprsunset.conf - hypr/input.lua - hypr/looknfeel.lua - hypr/omasettings.lua - hypr/xdph.conf - omarchy/branding/about.txt - omarchy/branding/screensaver.txt - omarchy/extensions/omarchy-menu.jsonc - omarchy/hooks/battery-low.d/play-warning-sound.sample - omarchy/hooks/font-set.d/show-font-notification.sample - omarchy/hooks/post-boot.d/weather.sample - omarchy/hooks/post-update.d/install-voxtype.hook - omarchy/hooks/post-update.d/setup-agent.hook - omarchy/hooks/post-update.d/setup-fingerprint.hook - omarchy/hooks/post-update.d/show-update-notification.sample - omarchy/hooks/pre-refresh-pacman.d/add-custom-repo.sample - omarchy/hooks/theme-set.d/show-theme-notification.sample - omarchy/shell.json - omarchy/shell.toml - omarchy/theme.name - omarchy/themes/azure-glow/README.md - omarchy/themes/azure-glow/alacritty.toml - omarchy/themes/azure-glow/btop.theme - omarchy/themes/azure-glow/hyprland.conf - omarchy/themes/azure-glow/hyprlock.conf - omarchy/themes/azure-glow/icons.theme - … 269 more
498 lines
18 KiB
QML
498 lines
18 KiB
QML
pragma ComponentBehavior: Bound
|
|
|
|
import QtQuick
|
|
import QtQuick.Controls
|
|
import QtQuick.Layouts
|
|
import qs.Commons
|
|
import qs.Ui
|
|
import "../Presentation.js" as Presentation
|
|
|
|
Rectangle {
|
|
id: page
|
|
|
|
required property bool open
|
|
required property real topInset
|
|
required property color foreground
|
|
required property string fontFamily
|
|
required property color panelBackground
|
|
|
|
required property var rows
|
|
required property var updateStates
|
|
required property var marketplaceMap
|
|
required property bool marketplaceFetching
|
|
required property bool marketplaceFetchFailed
|
|
required property bool checking
|
|
required property bool updateRunning
|
|
required property bool updatingAll
|
|
required property int pendingCount
|
|
required property string summary
|
|
|
|
required property var iconFor
|
|
required property var whatsNewUrlFor
|
|
|
|
required property bool autoCheckEnabled
|
|
required property real autoCheckIntervalHours
|
|
readonly property var autoCheckIntervalChoices: [1, 3, 6, 12, 24]
|
|
|
|
signal closeRequested
|
|
signal tabRequested(int direction)
|
|
signal openUrlRequested(string url)
|
|
signal updatePluginRequested(string sourceKey)
|
|
signal updateAllRequested
|
|
signal autoCheckEnabledRequested(bool value)
|
|
signal autoCheckIntervalRequested(int hours)
|
|
|
|
visible: open
|
|
color: panelBackground
|
|
|
|
// Preserve scroll and delegate state after the page has been opened once.
|
|
property bool _stayLoaded: false
|
|
onOpenChanged: {
|
|
if (open) _stayLoaded = true
|
|
}
|
|
|
|
function statusText(key) {
|
|
var state = updateStates[key]
|
|
if (!state) return "Pending"
|
|
if (state === "CHECK") return "Checking…"
|
|
if (state === "CURRENT") return "Up to date"
|
|
if (state === "UPDATE") return "Update available"
|
|
if (state === "LOCAL_CHANGES") return "Local changes"
|
|
if (state === "LOCAL") return "Local plugin"
|
|
if (state === "ERROR") return "Error"
|
|
return state
|
|
}
|
|
|
|
function statusColor(key) {
|
|
var state = updateStates[key]
|
|
if (state === "UPDATE") return Style.selectedStateColor(foreground, Color.accent)
|
|
if (state === "ERROR") return Color.urgent
|
|
if (state === "CURRENT") return Qt.darker(foreground, 1.6)
|
|
if (state === "LOCAL_CHANGES" || state === "LOCAL") return Qt.darker(foreground, 1.5)
|
|
return Qt.darker(foreground, 1.4)
|
|
}
|
|
|
|
function verificationText(id, sourceKey) {
|
|
var entry = marketplaceMap[String(id)]
|
|
if (entry) {
|
|
if (entry.verified === true) return "Verified"
|
|
if (entry.snapshotStatus === "update-unverified") return "Update Unverified"
|
|
return "Unverified"
|
|
}
|
|
if (marketplaceFetching) return "Checking verification…"
|
|
if (marketplaceFetchFailed) return "Verification unavailable"
|
|
return "Not listed"
|
|
}
|
|
|
|
function verificationColor(id, sourceKey) {
|
|
var entry = marketplaceMap[String(id)]
|
|
if (entry && entry.verified === true) return Color.accent
|
|
if (entry && entry.snapshotStatus === "update-unverified")
|
|
return Qt.hsla(0.12, 0.75, 0.55, 1)
|
|
return Qt.darker(foreground, 1.6)
|
|
}
|
|
|
|
// Swallows clicks that land in a gap between controls (margins, spacing,
|
|
// below a short list) so they don't fall through to whatever is rendered
|
|
// behind this page - the main plugin list is still visible/enabled there.
|
|
// Declared before the Loader so its buttons/rows still take priority for
|
|
// clicks that actually land on them; same pattern as the dialogs'
|
|
// full-page MouseArea (panel/dialogs/Confirm.qml, Install.qml).
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
onClicked: {}
|
|
}
|
|
|
|
Loader {
|
|
anchors.fill: parent
|
|
active: page.open || page._stayLoaded
|
|
|
|
sourceComponent: Component {
|
|
Item {
|
|
PanelKeyCatcher {
|
|
anchors.fill: parent
|
|
onCloseRequested: page.closeRequested()
|
|
onTabRequested: function(direction) { page.tabRequested(direction) }
|
|
}
|
|
|
|
ColumnLayout {
|
|
anchors.fill: parent
|
|
anchors.margins: Style.space(16)
|
|
anchors.topMargin: page.topInset
|
|
spacing: Style.space(10)
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: Style.space(8)
|
|
|
|
Label {
|
|
text: "Check for updates"
|
|
color: page.foreground
|
|
font.family: page.fontFamily
|
|
font.pixelSize: Style.font.body
|
|
font.bold: true
|
|
Layout.fillWidth: true
|
|
}
|
|
|
|
Button {
|
|
text: "Back"
|
|
foreground: page.foreground
|
|
accent: Color.accent
|
|
fontFamily: page.fontFamily
|
|
fontSize: Style.font.bodySmall
|
|
horizontalPadding: Style.space(10)
|
|
verticalPadding: Style.space(5)
|
|
onClicked: page.closeRequested()
|
|
}
|
|
}
|
|
|
|
Toggle {
|
|
Layout.fillWidth: true
|
|
label: "Auto-check for updates"
|
|
description: page.autoCheckEnabled
|
|
? "Checks in the background on shell start and every " + page.autoCheckIntervalHours + "h"
|
|
: "Only checks when you open this page or click Update"
|
|
checked: page.autoCheckEnabled
|
|
foreground: page.foreground
|
|
accent: Color.accent
|
|
fontFamily: page.fontFamily
|
|
onClicked: page.autoCheckEnabledRequested(!page.autoCheckEnabled)
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
visible: page.autoCheckEnabled
|
|
spacing: Style.space(6)
|
|
|
|
Label {
|
|
text: "Check every"
|
|
color: Qt.darker(page.foreground, 1.5)
|
|
font.family: page.fontFamily
|
|
font.pixelSize: Style.font.bodySmall
|
|
}
|
|
|
|
ButtonGroup {
|
|
options: page.autoCheckIntervalChoices.map(function(h) {
|
|
return { value: String(h), label: h + "h" }
|
|
})
|
|
value: String(page.autoCheckIntervalHours)
|
|
foreground: page.foreground
|
|
accent: Color.accent
|
|
fontFamily: page.fontFamily
|
|
fontSize: Style.font.caption
|
|
onChanged: function(v) { page.autoCheckIntervalRequested(Number(v)) }
|
|
}
|
|
|
|
Item {
|
|
Layout.fillWidth: true
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: checkProgress
|
|
visible: page.checking
|
|
Layout.fillWidth: true
|
|
Layout.preferredHeight: 3
|
|
radius: 1.5
|
|
color: Qt.rgba(page.foreground.r, page.foreground.g, page.foreground.b, 0.15)
|
|
clip: true
|
|
|
|
Rectangle {
|
|
id: checkProgressChunk
|
|
width: checkProgress.width * 0.4
|
|
height: checkProgress.height
|
|
radius: checkProgress.radius
|
|
color: Style.selectedStateColor(page.foreground, Color.accent)
|
|
|
|
NumberAnimation on x {
|
|
running: page.checking
|
|
loops: Animation.Infinite
|
|
from: -checkProgressChunk.width
|
|
to: checkProgress.width
|
|
duration: 1100
|
|
easing.type: Easing.InOutQuad
|
|
}
|
|
}
|
|
}
|
|
|
|
ListView {
|
|
id: updateList
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
clip: true
|
|
spacing: Style.space(4)
|
|
model: page.rows
|
|
ScrollBar.vertical: ScrollBar {
|
|
policy: ScrollBar.AsNeeded
|
|
implicitWidth: Style.space(6)
|
|
contentItem: Rectangle {
|
|
implicitWidth: Style.space(6)
|
|
implicitHeight: Style.space(6)
|
|
radius: width / 2
|
|
color: Util.alpha(page.foreground, 0.45)
|
|
}
|
|
}
|
|
|
|
delegate: Rectangle {
|
|
id: updateRow
|
|
|
|
required property var modelData
|
|
width: updateList.width
|
|
height: Math.max(Style.space(72), row.implicitHeight + Style.space(24))
|
|
radius: Style.cornerRadius > 0 ? Style.cornerRadius : 4
|
|
color: hover.hovered
|
|
? Style.hoverFillFor(page.foreground, Color.accent)
|
|
: "transparent"
|
|
|
|
RowLayout {
|
|
id: row
|
|
anchors.fill: parent
|
|
anchors.leftMargin: Style.space(10)
|
|
anchors.topMargin: Style.space(8)
|
|
anchors.rightMargin: Style.space(10)
|
|
anchors.bottomMargin: Style.space(12)
|
|
spacing: Style.space(10)
|
|
|
|
Rectangle {
|
|
id: updateIcon
|
|
Layout.preferredWidth: Style.space(28)
|
|
Layout.preferredHeight: Layout.preferredWidth
|
|
radius: 6
|
|
clip: true
|
|
color: Presentation.iconColor(updateRow.modelData.name)
|
|
|
|
Text {
|
|
anchors.centerIn: parent
|
|
width: parent.width - 4
|
|
horizontalAlignment: Text.AlignHCenter
|
|
elide: Text.ElideRight
|
|
maximumLineCount: 1
|
|
text: page.iconFor(updateRow.modelData.id) || updateRow.modelData.name.trim().charAt(0).toUpperCase()
|
|
textFormat: Text.PlainText
|
|
color: "white"
|
|
font.family: page.fontFamily
|
|
font.pixelSize: Style.font.bodySmall
|
|
font.bold: true
|
|
}
|
|
}
|
|
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
Layout.alignment: Qt.AlignVCenter
|
|
spacing: Style.space(2)
|
|
|
|
Label {
|
|
text: updateRow.modelData.name
|
|
textFormat: Text.PlainText
|
|
color: page.foreground
|
|
font.family: page.fontFamily
|
|
font.pixelSize: Style.font.body
|
|
font.bold: true
|
|
Layout.fillWidth: true
|
|
elide: Label.ElideRight
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
Layout.topMargin: Style.space(8)
|
|
spacing: Style.space(5)
|
|
|
|
Label {
|
|
text: page.statusText(updateRow.modelData.sourceKey)
|
|
textFormat: Text.PlainText
|
|
color: page.statusColor(updateRow.modelData.sourceKey)
|
|
font.family: page.fontFamily
|
|
font.pixelSize: Style.font.caption
|
|
}
|
|
|
|
Label {
|
|
text: "·"
|
|
textFormat: Text.PlainText
|
|
color: Qt.darker(page.foreground, 2.0)
|
|
font.family: page.fontFamily
|
|
font.pixelSize: Style.font.caption
|
|
}
|
|
|
|
Rectangle {
|
|
readonly property bool updateUnverified: !!page.marketplaceMap[String(updateRow.modelData.id)]
|
|
&& page.marketplaceMap[String(updateRow.modelData.id)].verified !== true
|
|
&& page.marketplaceMap[String(updateRow.modelData.id)].snapshotStatus === "update-unverified"
|
|
implicitWidth: verificationLabel.implicitWidth + Style.space(10)
|
|
implicitHeight: Style.space(16)
|
|
radius: height / 2
|
|
color: updateUnverified ? Qt.rgba(0.85, 0.65, 0.13, 0.18) : "transparent"
|
|
|
|
Label {
|
|
id: verificationLabel
|
|
anchors.centerIn: parent
|
|
text: page.verificationText(updateRow.modelData.id, updateRow.modelData.sourceKey)
|
|
textFormat: Text.PlainText
|
|
color: page.verificationColor(updateRow.modelData.id, updateRow.modelData.sourceKey)
|
|
font.family: page.fontFamily
|
|
font.pixelSize: Style.font.caption
|
|
}
|
|
}
|
|
}
|
|
|
|
Text {
|
|
id: whatsNewLink
|
|
readonly property string url: page.whatsNewUrlFor(updateRow.modelData.sourceKey, updateRow.modelData.id)
|
|
visible: page.updateStates[String(updateRow.modelData.sourceKey)] === "UPDATE" && url !== ""
|
|
text: "What's new ↗"
|
|
textFormat: Text.PlainText
|
|
color: Color.accent
|
|
font.family: page.fontFamily
|
|
font.pixelSize: Style.font.caption
|
|
font.underline: whatsNewLinkHover.hovered
|
|
ToolTip.text: url
|
|
ToolTip.visible: whatsNewLinkHover.hovered
|
|
ToolTip.delay: 400
|
|
|
|
HoverHandler {
|
|
id: whatsNewLinkHover
|
|
cursorShape: Qt.PointingHandCursor
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: page.openUrlRequested(whatsNewLink.url)
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
id: checkRing
|
|
visible: page.updateStates[updateRow.modelData.sourceKey] === "CHECK"
|
|
|| page.updateStates[updateRow.modelData.sourceKey] === undefined
|
|
Layout.alignment: Qt.AlignVCenter
|
|
Layout.preferredWidth: Style.space(18)
|
|
Layout.preferredHeight: Style.space(18)
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
radius: width / 2
|
|
color: "transparent"
|
|
border.width: 2
|
|
border.color: Qt.rgba(page.foreground.r, page.foreground.g, page.foreground.b, 0.18)
|
|
}
|
|
|
|
Item {
|
|
id: checkRingArc
|
|
anchors.fill: parent
|
|
visible: page.updateStates[updateRow.modelData.sourceKey] === "CHECK"
|
|
|| page.updateStates[updateRow.modelData.sourceKey] === undefined
|
|
|
|
RotationAnimation on rotation {
|
|
running: checkRingArc.visible
|
|
loops: Animation.Infinite
|
|
from: 0
|
|
to: 360
|
|
duration: 900
|
|
}
|
|
|
|
Canvas {
|
|
anchors.fill: parent
|
|
onPaint: {
|
|
var context = getContext("2d")
|
|
context.reset()
|
|
context.strokeStyle = Style.selectedStateColor(page.foreground, Color.accent)
|
|
context.lineWidth = 2
|
|
context.lineCap = "round"
|
|
var radius = width / 2 - 2
|
|
context.beginPath()
|
|
context.arc(width / 2, height / 2, radius, -Math.PI / 2, Math.PI / 3, false)
|
|
context.stroke()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Button {
|
|
id: statusButton
|
|
|
|
readonly property string updateState: String(page.updateStates[updateRow.modelData.sourceKey] || "")
|
|
visible: updateState === "CURRENT" || updateState === "UPDATE" || updateState === "LOCAL_CHANGES"
|
|
|| updateState === "LOCAL" || updateState === "ERROR"
|
|
text: updateState === "UPDATE" ? "\uEAC2 UPDATE" : "\uF00C"
|
|
enabled: updateState === "UPDATE" && !page.updateRunning
|
|
onClicked: page.updatePluginRequested(updateRow.modelData.sourceKey)
|
|
bordered: true
|
|
borderSpec: statusButton.hot ? Border.none()
|
|
: Border.controlSpec("normal", statusButton.foreground, Color.accent)
|
|
foreground: updateState === "ERROR" ? Color.urgent : page.foreground
|
|
accent: Color.accent
|
|
fontFamily: page.fontFamily
|
|
fontSize: Style.font.caption
|
|
horizontalPadding: Style.space(8)
|
|
verticalPadding: Style.space(3)
|
|
Layout.alignment: Qt.AlignVCenter
|
|
}
|
|
}
|
|
|
|
HoverHandler {
|
|
id: hover
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.bottom: parent.bottom
|
|
anchors.leftMargin: Style.space(10)
|
|
anchors.rightMargin: Style.space(10)
|
|
height: 1
|
|
color: Qt.rgba(page.foreground.r, page.foreground.g, page.foreground.b, 0.12)
|
|
}
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: Style.space(8)
|
|
Layout.maximumHeight: implicitHeight
|
|
|
|
Label {
|
|
text: page.checking
|
|
? (page.pendingCount > 0
|
|
? "Checking… " + page.pendingCount + " update" + (page.pendingCount > 1 ? "s" : "") + " found"
|
|
: "Checking…")
|
|
: (page.pendingCount > 0
|
|
? page.pendingCount + " update" + (page.pendingCount > 1 ? "s" : "") + " available"
|
|
: "No updates available")
|
|
color: Qt.darker(page.foreground, 1.5)
|
|
font.family: page.fontFamily
|
|
font.pixelSize: Style.font.bodySmall
|
|
}
|
|
|
|
Label {
|
|
visible: page.summary !== ""
|
|
text: page.summary
|
|
textFormat: Text.PlainText
|
|
color: Style.selectedStateColor(page.foreground, Color.accent)
|
|
font.family: page.fontFamily
|
|
font.pixelSize: Style.font.bodySmall
|
|
}
|
|
|
|
Item {
|
|
Layout.fillWidth: true
|
|
}
|
|
|
|
Button {
|
|
text: page.updatingAll ? "Updating all…" : "Update all"
|
|
enabled: page.pendingCount > 0 && !page.checking && !page.updateRunning
|
|
visible: page.pendingCount > 0 && !page.checking
|
|
foreground: page.foreground
|
|
accent: Color.accent
|
|
fontFamily: page.fontFamily
|
|
fontSize: Style.font.bodySmall
|
|
horizontalPadding: Style.space(12)
|
|
verticalPadding: Style.space(3)
|
|
onClicked: page.updateAllRequested()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|