import QtQuick
import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Ui
import "Model.js" as Model
Panel {
id: root
moduleName: "notify.desktop"
ipcTarget: ""
manageIpc: false
property var anchorItem: null
property var hostWidget: null
readonly property var barIdentity: hostWidget || root
readonly property color foreground: bar ? bar.foreground : Color.foreground
readonly property color mutedForeground: Qt.darker(foreground, 1.4)
readonly property string fontFamily: bar ? bar.fontFamily : Style.font.family
readonly property string home: Quickshell.env("HOME")
readonly property string configDir: home + "/.config/notify"
readonly property string mainConfigPath: configDir + "/notify.json"
property var profiles: []
property string activeName: ""
property string statusMessage: ""
property bool statusError: false
readonly property var activeProfile: Model.byName(root.profiles, root.activeName)
function scanCommand() {
var dir = Model.shellQuote(root.configDir)
var main = Model.shellQuote(root.mainConfigPath)
return "mkdir -p " + dir + "; printf '@@CONFIG\\n'; cat " + main + " 2>/dev/null || true; printf '\\n'; "
+ "for f in " + dir + "/*.json; do "
+ "[ \"$f\" = " + main + " ] && continue; "
+ "[ -f \"$f\" ] || continue; printf '@@FILE %s\\n' \"${f##*/}\"; cat \"$f\"; printf '\\n'; "
+ "done"
}
function setStatus(message, error) {
root.statusMessage = String(message || "")
root.statusError = error === true
statusTimer.restart()
}
function refresh() {
if (!scanProc.running) scanProc.running = true
}
function parseDump(content) {
var dump = Model.parseDump(content)
root.profiles = dump.profiles
root.activeName = dump.active
if (root.activeName === "" && root.profiles.length > 0)
root.activeName = root.profiles[0].name
}
function activate(name) {
var parsed = {}
try {
parsed = JSON.parse(mainConfigFile.text() || "{}")
} catch (e) {
parsed = {}
}
parsed.active = String(name)
root.activeName = String(name)
mainConfigFile.setText(JSON.stringify(parsed, null, 2) + "\n")
root.setStatus("Active profile: " + root.activeName, false)
root.refresh()
}
function openFolder() {
folderProc.running = true
}
function open() {
root.controller.show()
Qt.callLater(root.refresh)
}
function close() { root.controller.hide() }
onOpenedChanged: if (opened) root.refresh()
Component.onCompleted: root.refresh()
Process {
id: scanProc
command: ["sh", "-c", root.scanCommand()]
stdout: StdioCollector {
waitForEnd: true
onStreamFinished: {
root.parseDump(text)
}
}
}
FileView {
id: mainConfigFile
path: root.mainConfigPath
atomicWrites: true
printErrors: false
}
Process {
id: folderProc
command: ["xdg-open", root.configDir]
}
Timer {
id: statusTimer
interval: 3500
onTriggered: root.statusMessage = ""
}
KeyboardPanel {
id: popup
anchorItem: root.anchorItem
owner: root.barIdentity
bar: root.bar
open: root.opened
focusTarget: keyCatcher
contentWidth: popup.fittedContentWidth(Style.space(420))
contentHeight: popup.fittedContentHeight(contentColumn.implicitHeight)
PanelKeyCatcher {
id: keyCatcher
anchors.fill: parent
onCloseRequested: root.close()
Column {
id: contentColumn
anchors.fill: parent
spacing: Style.space(10)
Column {
width: parent.width
spacing: Style.space(3)
Text {
text: "Notify profiles"
color: root.foreground
font.family: root.fontFamily
font.pixelSize: Style.font.body
font.bold: true
}
Text {
text: "Profiles from ~/.config/notify"
color: root.mutedForeground
font.family: root.fontFamily
font.pixelSize: Style.font.caption
}
}
BorderSurface {
visible: root.activeProfile !== null
width: parent.width
implicitHeight: activeSummary.implicitHeight + Style.space(20)
color: Color.popups.background
borderSpec: Border.flat(Color.accent, 1)
radius: Style.cornerRadius
Column {
id: activeSummary
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Style.space(12)
anchors.rightMargin: Style.space(12)
spacing: Style.space(4)
Text {
text: "Loaded configuration"
color: root.mutedForeground
font.family: root.fontFamily
font.pixelSize: Style.font.caption
}
Text {
text: root.activeProfile ? root.activeProfile.name + ".json" : ""
color: root.foreground
font.family: root.fontFamily
font.pixelSize: Style.font.body
font.bold: true
}
}
}
Text {
visible: root.profiles.length === 0
text: "No profiles found. Create a .json file in ~/.config/notify."
color: root.mutedForeground
font.family: root.fontFamily
font.pixelSize: Style.font.bodySmall
wrapMode: Text.WordWrap
width: parent.width
}
Column {
width: parent.width
spacing: Style.space(4)
Repeater {
model: root.profiles
Item {
id: profileRow
required property var modelData
width: parent.width
implicitHeight: 30
Button {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
text: profileRow.modelData.name + " · server configured" + (profileRow.modelData.name === root.activeName ? " · active" : "")
leftAlign: true
active: profileRow.modelData.name === root.activeName
foreground: root.foreground
fontSize: Style.font.bodySmall
onClicked: root.activate(profileRow.modelData.name)
}
}
}
}
Button {
width: parent.width
text: "Open config folder"
foreground: root.foreground
fontSize: Style.font.bodySmall
onClicked: root.openFolder()
}
Text {
visible: root.statusMessage !== ""
text: root.statusMessage
color: root.statusError ? Color.urgent : root.mutedForeground
font.family: root.fontFamily
font.pixelSize: Style.font.bodySmall
width: parent.width
}
}
}
}
}