mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2026-08-15 18:43:59 +00:00
Notifications: minor renaming + house keeping. Bring back the close history when clearing all notifications
This commit is contained in:
+61
-49
@@ -8,6 +8,7 @@ import qs.Services
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// Current date
|
||||
property var date: new Date()
|
||||
|
||||
// Returns a Unix Timestamp (in seconds)
|
||||
@@ -15,62 +16,73 @@ Singleton {
|
||||
return Math.floor(date / 1000)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Formats a Date object into a YYYYMMDD-HHMMSS string.
|
||||
* @param {Date} [date=new Date()] - The date to format. Defaults to the current date and time.
|
||||
* @returns {string} The formatted date string.
|
||||
*/
|
||||
function getFormattedTimestamp(date = new Date()) {
|
||||
const year = date.getFullYear()
|
||||
|
||||
// getMonth() is zero-based, so we add 1
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
|
||||
return `${year}${month}${day}-${hours}${minutes}${seconds}`
|
||||
}
|
||||
|
||||
// Format an easy to read approximate duration ex: 4h32m
|
||||
// Used to display the time remaining on the Battery widget, computer uptime, etc..
|
||||
function formatVagueHumanReadableDuration(totalSeconds) {
|
||||
if (typeof totalSeconds !== 'number' || totalSeconds < 0) {
|
||||
return '0s'
|
||||
Timer {
|
||||
interval: 1000
|
||||
repeat: true
|
||||
running: true
|
||||
onTriggered: root.date = new Date()
|
||||
}
|
||||
|
||||
// Floor the input to handle decimal seconds
|
||||
totalSeconds = Math.floor(totalSeconds)
|
||||
// Formats a Date object into a YYYYMMDD-HHMMSS string.
|
||||
function getFormattedTimestamp(date) {
|
||||
if (!date) {
|
||||
date = new Date()
|
||||
}
|
||||
const year = date.getFullYear()
|
||||
|
||||
const days = Math.floor(totalSeconds / 86400)
|
||||
const hours = Math.floor((totalSeconds % 86400) / 3600)
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60)
|
||||
const seconds = totalSeconds % 60
|
||||
// getMonth() is zero-based, so we add 1
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
|
||||
const parts = []
|
||||
if (days)
|
||||
parts.push(`${days}d`)
|
||||
if (hours)
|
||||
parts.push(`${hours}h`)
|
||||
if (minutes)
|
||||
parts.push(`${minutes}m`)
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
|
||||
// Only show seconds if no hours and no minutes
|
||||
if (!hours && !minutes) {
|
||||
parts.push(`${seconds}s`)
|
||||
return `${year}${month}${day}-${hours}${minutes}${seconds}`
|
||||
}
|
||||
|
||||
return parts.join('')
|
||||
}
|
||||
// Format an easy to read approximate duration ex: 4h32m
|
||||
// Used to display the time remaining on the Battery widget, computer uptime, etc..
|
||||
function formatVagueHumanReadableDuration(totalSeconds) {
|
||||
if (typeof totalSeconds !== 'number' || totalSeconds < 0) {
|
||||
return '0s'
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 1000
|
||||
repeat: true
|
||||
running: true
|
||||
// Floor the input to handle decimal seconds
|
||||
totalSeconds = Math.floor(totalSeconds)
|
||||
|
||||
onTriggered: root.date = new Date()
|
||||
}
|
||||
const days = Math.floor(totalSeconds / 86400)
|
||||
const hours = Math.floor((totalSeconds % 86400) / 3600)
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60)
|
||||
const seconds = totalSeconds % 60
|
||||
|
||||
const parts = []
|
||||
if (days)
|
||||
parts.push(`${days}d`)
|
||||
if (hours)
|
||||
parts.push(`${hours}h`)
|
||||
if (minutes)
|
||||
parts.push(`${minutes}m`)
|
||||
|
||||
// Only show seconds if no hours and no minutes
|
||||
if (!hours && !minutes) {
|
||||
parts.push(`${seconds}s`)
|
||||
}
|
||||
|
||||
return parts.join('')
|
||||
}
|
||||
|
||||
// Format a date into
|
||||
function formatRelativeTime(date) {
|
||||
if (!date)
|
||||
return ""
|
||||
const diff = Date.now() - date.getTime()
|
||||
if (diff < 60000)
|
||||
return "now"
|
||||
if (diff < 3600000)
|
||||
return `${Math.floor(diff / 60000)}m ago`
|
||||
if (diff < 86400000)
|
||||
return `${Math.floor(diff / 3600000)}h ago`
|
||||
return `${Math.floor(diff / 86400000)}d ago`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ NIconButton {
|
||||
function computeUnreadCount() {
|
||||
var since = lastSeenTs()
|
||||
var count = 0
|
||||
var model = NotificationService.notificationHistory
|
||||
var model = NotificationService.historyList
|
||||
for (var i = 0; i < model.count; i++) {
|
||||
var item = model.get(i)
|
||||
var ts = item.timestamp instanceof Date ? item.timestamp.getTime() : item.timestamp
|
||||
|
||||
@@ -19,7 +19,7 @@ Variants {
|
||||
readonly property real scaling: ScalingService.getScreenScale(modelData)
|
||||
|
||||
// Access the notification model from the service - UPDATED NAME
|
||||
property ListModel notificationModel: NotificationService.activeNotifications
|
||||
property ListModel notificationModel: NotificationService.activeList
|
||||
|
||||
// If no notification display activated in settings, then show them all
|
||||
active: Settings.isLoaded && modelData && (notificationModel.count > 0) ? (Settings.data.notifications.monitors.includes(modelData.name) || (Settings.data.notifications.monitors.length === 0)) : false
|
||||
@@ -256,7 +256,7 @@ Variants {
|
||||
}
|
||||
|
||||
NText {
|
||||
text: `${model.appName || "Unknown App"} · ${NotificationService.formatTimestamp(model.timestamp)}`
|
||||
text: `${model.appName || "Unknown App"} · ${Time.formatRelativeTime(model.timestamp)}`
|
||||
color: Color.mSecondary
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
}
|
||||
|
||||
@@ -55,7 +55,11 @@ NPanel {
|
||||
icon: "trash"
|
||||
tooltipText: "Clear history"
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: NotificationService.clearHistory()
|
||||
onClicked: {
|
||||
NotificationService.clearHistory()
|
||||
// Close panel as there is nothing more to see.
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
@@ -75,7 +79,7 @@ NPanel {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
visible: NotificationService.notificationHistory.count === 0
|
||||
visible: NotificationService.historyList.count === 0
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
Item {
|
||||
@@ -119,11 +123,11 @@ NPanel {
|
||||
horizontalPolicy: ScrollBar.AlwaysOff
|
||||
verticalPolicy: ScrollBar.AsNeeded
|
||||
|
||||
model: NotificationService.notificationHistory
|
||||
model: NotificationService.historyList
|
||||
spacing: Style.marginM * scaling
|
||||
clip: true
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
visible: NotificationService.notificationHistory.count > 0
|
||||
visible: NotificationService.historyList.count > 0
|
||||
|
||||
delegate: Rectangle {
|
||||
property string notificationId: model.id
|
||||
@@ -200,7 +204,7 @@ NPanel {
|
||||
}
|
||||
|
||||
NText {
|
||||
text: NotificationService.formatTimestamp(model.timestamp)
|
||||
text: Time.formatRelativeTime(model.timestamp)
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
color: Color.mSecondary
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ Singleton {
|
||||
property string historyFile: Quickshell.env("NOCTALIA_NOTIF_HISTORY_FILE") || (Settings.cacheDir + "notifications.json")
|
||||
|
||||
// Models
|
||||
property ListModel activeNotifications: ListModel {}
|
||||
property ListModel notificationHistory: ListModel {}
|
||||
property ListModel activeList: ListModel {}
|
||||
property ListModel historyList: ListModel {}
|
||||
|
||||
// Internal state
|
||||
property var activeMap: ({})
|
||||
@@ -39,6 +39,8 @@ Singleton {
|
||||
visible: true
|
||||
cache: false
|
||||
asynchronous: true
|
||||
mipmap: true
|
||||
antialiasing: true
|
||||
|
||||
onStatusChanged: {
|
||||
if (imageQueue.length === 0)
|
||||
@@ -88,11 +90,11 @@ Singleton {
|
||||
notification.tracked = true
|
||||
notification.closed.connect(() => removeActive(data.id))
|
||||
|
||||
activeNotifications.insert(0, data)
|
||||
while (activeNotifications.count > maxVisible) {
|
||||
const last = activeNotifications.get(activeNotifications.count - 1)
|
||||
activeList.insert(0, data)
|
||||
while (activeList.count > maxVisible) {
|
||||
const last = activeList.get(activeList.count - 1)
|
||||
activeMap[last.id]?.dismiss()
|
||||
activeNotifications.remove(activeNotifications.count - 1)
|
||||
activeList.remove(activeList.count - 1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,8 +149,8 @@ Singleton {
|
||||
}
|
||||
|
||||
function updateImagePath(id, path) {
|
||||
updateModel(activeNotifications, id, "cachedImage", path)
|
||||
updateModel(notificationHistory, id, "cachedImage", path)
|
||||
updateModel(activeList, id, "cachedImage", path)
|
||||
updateModel(historyList, id, "cachedImage", path)
|
||||
saveHistory()
|
||||
}
|
||||
|
||||
@@ -162,9 +164,9 @@ Singleton {
|
||||
}
|
||||
|
||||
function removeActive(id) {
|
||||
for (var i = 0; i < activeNotifications.count; i++) {
|
||||
if (activeNotifications.get(i).id === id) {
|
||||
activeNotifications.remove(i)
|
||||
for (var i = 0; i < activeList.count; i++) {
|
||||
if (activeList.get(i).id === id) {
|
||||
activeList.remove(i)
|
||||
delete activeMap[id]
|
||||
break
|
||||
}
|
||||
@@ -175,13 +177,13 @@ Singleton {
|
||||
Timer {
|
||||
interval: 1000
|
||||
repeat: true
|
||||
running: activeNotifications.count > 0
|
||||
running: activeList.count > 0
|
||||
onTriggered: {
|
||||
const now = Date.now()
|
||||
const durations = [3000, 8000, 15000] // low, normal, critical
|
||||
|
||||
for (var i = activeNotifications.count - 1; i >= 0; i--) {
|
||||
const notif = activeNotifications.get(i)
|
||||
for (var i = activeList.count - 1; i >= 0; i--) {
|
||||
const notif = activeList.get(i)
|
||||
const elapsed = now - notif.timestamp.getTime()
|
||||
|
||||
if (elapsed >= durations[notif.urgency] || elapsed >= 8000) {
|
||||
@@ -194,16 +196,15 @@ Singleton {
|
||||
|
||||
// History management
|
||||
function addToHistory(data) {
|
||||
notificationHistory.insert(0, data)
|
||||
historyList.insert(0, data)
|
||||
|
||||
while (notificationHistory.count > maxHistory) {
|
||||
const old = notificationHistory.get(notificationHistory.count - 1)
|
||||
while (historyList.count > maxHistory) {
|
||||
const old = historyList.get(historyList.count - 1)
|
||||
if (old.cachedImage && !old.cachedImage.startsWith("image://")) {
|
||||
Quickshell.execDetached(["rm", "-f", old.cachedImage])
|
||||
}
|
||||
notificationHistory.remove(notificationHistory.count - 1)
|
||||
historyList.remove(historyList.count - 1)
|
||||
}
|
||||
|
||||
saveHistory()
|
||||
}
|
||||
|
||||
@@ -215,7 +216,7 @@ Singleton {
|
||||
onLoaded: loadHistory()
|
||||
onLoadFailed: error => {
|
||||
if (error === 2)
|
||||
writeAdapter()
|
||||
writeAdapter()
|
||||
}
|
||||
|
||||
JsonAdapter {
|
||||
@@ -237,8 +238,8 @@ Singleton {
|
||||
function performSave() {
|
||||
try {
|
||||
const items = []
|
||||
for (var i = 0; i < notificationHistory.count; i++) {
|
||||
const n = notificationHistory.get(i)
|
||||
for (var i = 0; i < historyList.count; i++) {
|
||||
const n = historyList.get(i)
|
||||
const copy = Object.assign({}, n)
|
||||
copy.timestamp = n.timestamp.getTime()
|
||||
items.push(copy)
|
||||
@@ -253,7 +254,7 @@ Singleton {
|
||||
|
||||
function loadHistory() {
|
||||
try {
|
||||
notificationHistory.clear()
|
||||
historyList.clear()
|
||||
for (const item of adapter.notifications || []) {
|
||||
let time = item.timestamp
|
||||
if (typeof time === "number") {
|
||||
@@ -273,17 +274,17 @@ Singleton {
|
||||
cachedImage = Settings.cacheDirImagesNotifications + imageId + ".png"
|
||||
}
|
||||
}
|
||||
|
||||
notificationHistory.append({
|
||||
"id": item.id || "",
|
||||
"summary": item.summary || "",
|
||||
"body": item.body || "",
|
||||
"appName": item.appName || "",
|
||||
"urgency": item.urgency || 1,
|
||||
"timestamp": time,
|
||||
"originalImage": item.originalImage || "",
|
||||
"cachedImage": cachedImage
|
||||
})
|
||||
|
||||
historyList.append({
|
||||
"id": item.id || "",
|
||||
"summary": item.summary || "",
|
||||
"body": item.body || "",
|
||||
"appName": item.appName || "",
|
||||
"urgency": item.urgency || 1,
|
||||
"timestamp": time,
|
||||
"originalImage": item.originalImage || "",
|
||||
"cachedImage": cachedImage
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.error("Notifications", "Load failed:", e)
|
||||
@@ -337,7 +338,7 @@ Singleton {
|
||||
|
||||
function dismissAllActive() {
|
||||
Object.values(activeMap).forEach(n => n.dismiss())
|
||||
activeNotifications.clear()
|
||||
activeList.clear()
|
||||
activeMap = {}
|
||||
}
|
||||
|
||||
@@ -356,14 +357,14 @@ Singleton {
|
||||
}
|
||||
|
||||
function removeFromHistory(notificationId) {
|
||||
for (let i = 0; i < notificationHistory.count; i++) {
|
||||
const notif = notificationHistory.get(i)
|
||||
for (var i = 0; i < historyList.count; i++) {
|
||||
const notif = historyList.get(i)
|
||||
if (notif.id === notificationId) {
|
||||
// Delete cached image if it exists
|
||||
if (notif.cachedImage && !notif.cachedImage.startsWith("image://")) {
|
||||
Quickshell.execDetached(["rm", "-f", notif.cachedImage])
|
||||
}
|
||||
notificationHistory.remove(i)
|
||||
historyList.remove(i)
|
||||
saveHistory()
|
||||
return true
|
||||
}
|
||||
@@ -378,22 +379,9 @@ Singleton {
|
||||
} catch (e) {
|
||||
Logger.error("Notifications", "Failed to clear cache directory:", e)
|
||||
}
|
||||
|
||||
notificationHistory.clear()
|
||||
saveHistory()
|
||||
}
|
||||
|
||||
function formatTimestamp(timestamp) {
|
||||
if (!timestamp)
|
||||
return ""
|
||||
const diff = Date.now() - timestamp.getTime()
|
||||
if (diff < 60000)
|
||||
return "now"
|
||||
if (diff < 3600000)
|
||||
return `${Math.floor(diff / 60000)}m ago`
|
||||
if (diff < 86400000)
|
||||
return `${Math.floor(diff / 3600000)}h ago`
|
||||
return `${Math.floor(diff / 86400000)}d ago`
|
||||
historyList.clear()
|
||||
saveHistory()
|
||||
}
|
||||
|
||||
// Signals & connections
|
||||
|
||||
Reference in New Issue
Block a user