From 494a8e071c773a4bedacc23ac5bbd1323345e042 Mon Sep 17 00:00:00 2001 From: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> Date: Tue, 12 Nov 2024 19:11:42 +0530 Subject: [PATCH 1/2] feat(notifications): implement persistent notification system with toast notification --- src/lang/ar-ye.json | 4 +- src/lang/be-by.json | 4 +- src/lang/bn-bd.json | 4 +- src/lang/cs-cz.json | 4 +- src/lang/de-de.json | 4 +- src/lang/en-us.json | 4 +- src/lang/es-sv.json | 4 +- src/lang/fr-fr.json | 4 +- src/lang/hi-in.json | 4 +- src/lang/hu-hu.json | 4 +- src/lang/id-id.json | 4 +- src/lang/ir-fa.json | 4 +- src/lang/it-it.json | 4 +- src/lang/ja-jp.json | 4 +- src/lang/ko-kr.json | 4 +- src/lang/ml-in.json | 4 +- src/lang/mm-unicode.json | 4 +- src/lang/mm-zawgyi.json | 4 +- src/lang/pl-pl.json | 4 +- src/lang/pt-br.json | 4 +- src/lang/pu-in.json | 4 +- src/lang/ru-ru.json | 4 +- src/lang/tl-ph.json | 4 +- src/lang/tr-tr.json | 4 +- src/lang/uk-ua.json | 4 +- src/lang/uz-uz.json | 4 +- src/lang/vi-vn.json | 4 +- src/lang/zh-cn.json | 4 +- src/lang/zh-hant.json | 4 +- src/lang/zh-tw.json | 4 +- src/lib/acode.js | 26 +++ src/lib/main.js | 6 +- src/lib/notificationManager.js | 250 ++++++++++++++++++++++++ src/sidebarApps/index.js | 1 + src/sidebarApps/notification/index.js | 60 ++++++ src/sidebarApps/notification/style.scss | 142 ++++++++++++++ src/styles/main.scss | 120 +++++++++++- 37 files changed, 693 insertions(+), 32 deletions(-) create mode 100644 src/lib/notificationManager.js create mode 100644 src/sidebarApps/notification/index.js create mode 100644 src/sidebarApps/notification/style.scss diff --git a/src/lang/ar-ye.json b/src/lang/ar-ye.json index 2352e42df..9166d7feb 100644 --- a/src/lang/ar-ye.json +++ b/src/lang/ar-ye.json @@ -388,5 +388,7 @@ "delete entries": "Are you sure you want to delete {count} items?", "deleting items": "Deleting {count} items...", "import project zip": "Import Project(zip)", - "changelog": "Change Log" + "changelog": "Change Log", + "notifications": "Notifications", + "no_unread_notifications": "No unread notifications" } diff --git a/src/lang/be-by.json b/src/lang/be-by.json index 488edf542..3f15d31b8 100644 --- a/src/lang/be-by.json +++ b/src/lang/be-by.json @@ -388,5 +388,7 @@ "delete entries": "Are you sure you want to delete {count} items?", "deleting items": "Deleting {count} items...", "import project zip": "Import Project(zip)", - "changelog": "Change Log" + "changelog": "Change Log", + "notifications": "Notifications", + "no_unread_notifications": "No unread notifications" } diff --git a/src/lang/bn-bd.json b/src/lang/bn-bd.json index 4aedbfb7e..071a97fab 100644 --- a/src/lang/bn-bd.json +++ b/src/lang/bn-bd.json @@ -388,5 +388,7 @@ "delete entries": "Are you sure you want to delete {count} items?", "deleting items": "Deleting {count} items...", "import project zip": "Import Project(zip)", - "changelog": "Change Log" + "changelog": "Change Log", + "notifications": "Notifications", + "no_unread_notifications": "No unread notifications" } diff --git a/src/lang/cs-cz.json b/src/lang/cs-cz.json index 7601abb27..45d461315 100644 --- a/src/lang/cs-cz.json +++ b/src/lang/cs-cz.json @@ -388,5 +388,7 @@ "delete entries": "Are you sure you want to delete {count} items?", "deleting items": "Deleting {count} items...", "import project zip": "Import Project(zip)", - "changelog": "Change Log" + "changelog": "Change Log", + "notifications": "Notifications", + "no_unread_notifications": "No unread notifications" } diff --git a/src/lang/de-de.json b/src/lang/de-de.json index 5dda4e5f0..4c3d22d59 100644 --- a/src/lang/de-de.json +++ b/src/lang/de-de.json @@ -388,5 +388,7 @@ "delete entries": "Sind sie sicher, das Sie {count} Einträge löschen wollen?", "deleting items": "Löschen von {count} Einträgen...", "import project zip": "Projekt(-Zip) importieren", - "changelog": "Änderungsbericht" + "changelog": "Änderungsbericht", + "notifications": "Notifications", + "no_unread_notifications": "No unread notifications" } diff --git a/src/lang/en-us.json b/src/lang/en-us.json index c602d9ff0..288181481 100644 --- a/src/lang/en-us.json +++ b/src/lang/en-us.json @@ -389,5 +389,7 @@ "delete entries": "Are you sure you want to delete {count} items?", "deleting items": "Deleting {count} items...", "import project zip": "Import Project(zip)", - "changelog": "Change Log" + "changelog": "Change Log", + "notifications": "Notifications", + "no_unread_notifications": "No unread notifications" } diff --git a/src/lang/es-sv.json b/src/lang/es-sv.json index 2646e0446..d76718ca2 100644 --- a/src/lang/es-sv.json +++ b/src/lang/es-sv.json @@ -388,5 +388,7 @@ "delete entries": "Are you sure you want to delete {count} items?", "deleting items": "Deleting {count} items...", "import project zip": "Import Project(zip)", - "changelog": "Change Log" + "changelog": "Change Log", + "notifications": "Notifications", + "no_unread_notifications": "No unread notifications" } diff --git a/src/lang/fr-fr.json b/src/lang/fr-fr.json index 11886892a..4c3de3f87 100644 --- a/src/lang/fr-fr.json +++ b/src/lang/fr-fr.json @@ -388,5 +388,7 @@ "delete entries": "Are you sure you want to delete {count} items?", "deleting items": "Deleting {count} items...", "import project zip": "Import Project(zip)", - "changelog": "Change Log" + "changelog": "Change Log", + "notifications": "Notifications", + "no_unread_notifications": "No unread notifications" } diff --git a/src/lang/hi-in.json b/src/lang/hi-in.json index 72df39228..1f9f55d62 100644 --- a/src/lang/hi-in.json +++ b/src/lang/hi-in.json @@ -389,5 +389,7 @@ "delete entries": "Are you sure you want to delete {count} items?", "deleting items": "Deleting {count} items...", "import project zip": "Import Project(zip)", - "changelog": "Change Log" + "changelog": "Change Log", + "notifications": "Notifications", + "no_unread_notifications": "No unread notifications" } diff --git a/src/lang/hu-hu.json b/src/lang/hu-hu.json index 9a12ac35b..b4f0c389b 100644 --- a/src/lang/hu-hu.json +++ b/src/lang/hu-hu.json @@ -388,5 +388,7 @@ "delete entries": "Biztosan töröl {count} elemet?", "deleting items": "{count} elem törlése…", "import project zip": "Projekt importálása zip-ből", - "changelog": "Változásnapló" + "changelog": "Változásnapló", + "notifications": "Notifications", + "no_unread_notifications": "No unread notifications" } diff --git a/src/lang/id-id.json b/src/lang/id-id.json index e21115fbf..b41eedeae 100644 --- a/src/lang/id-id.json +++ b/src/lang/id-id.json @@ -390,5 +390,7 @@ "delete entries": "Are you sure you want to delete {count} items?", "deleting items": "Deleting {count} items...", "import project zip": "Import Project(zip)", - "changelog": "Change Log" + "changelog": "Change Log", + "notifications": "Notifications", + "no_unread_notifications": "No unread notifications" } diff --git a/src/lang/ir-fa.json b/src/lang/ir-fa.json index 90e66cf8f..531413a5e 100644 --- a/src/lang/ir-fa.json +++ b/src/lang/ir-fa.json @@ -389,5 +389,7 @@ "delete entries": "Are you sure you want to delete {count} items?", "deleting items": "Deleting {count} items...", "import project zip": "Import Project(zip)", - "changelog": "Change Log" + "changelog": "Change Log", + "notifications": "Notifications", + "no_unread_notifications": "No unread notifications" } diff --git a/src/lang/it-it.json b/src/lang/it-it.json index ff28c2ccb..e73d8f05b 100644 --- a/src/lang/it-it.json +++ b/src/lang/it-it.json @@ -388,5 +388,7 @@ "delete entries": "Are you sure you want to delete {count} items?", "deleting items": "Deleting {count} items...", "import project zip": "Import Project(zip)", - "changelog": "Change Log" + "changelog": "Change Log", + "notifications": "Notifications", + "no_unread_notifications": "No unread notifications" } diff --git a/src/lang/ja-jp.json b/src/lang/ja-jp.json index 634e3e443..7f37d338e 100644 --- a/src/lang/ja-jp.json +++ b/src/lang/ja-jp.json @@ -388,5 +388,7 @@ "delete entries": "Are you sure you want to delete {count} items?", "deleting items": "Deleting {count} items...", "import project zip": "Import Project(zip)", - "changelog": "Change Log" + "changelog": "Change Log", + "notifications": "Notifications", + "no_unread_notifications": "No unread notifications" } diff --git a/src/lang/ko-kr.json b/src/lang/ko-kr.json index 977242e10..8f6b5cf6e 100644 --- a/src/lang/ko-kr.json +++ b/src/lang/ko-kr.json @@ -388,5 +388,7 @@ "delete entries": "Are you sure you want to delete {count} items?", "deleting items": "Deleting {count} items...", "import project zip": "Import Project(zip)", - "changelog": "Change Log" + "changelog": "Change Log", + "notifications": "Notifications", + "no_unread_notifications": "No unread notifications" } diff --git a/src/lang/ml-in.json b/src/lang/ml-in.json index 9af5d8745..b800a32b8 100644 --- a/src/lang/ml-in.json +++ b/src/lang/ml-in.json @@ -388,5 +388,7 @@ "delete entries": "Are you sure you want to delete {count} items?", "deleting items": "Deleting {count} items...", "import project zip": "Import Project(zip)", - "changelog": "Change Log" + "changelog": "Change Log", + "notifications": "Notifications", + "no_unread_notifications": "No unread notifications" } diff --git a/src/lang/mm-unicode.json b/src/lang/mm-unicode.json index 55eda463c..79f349a6f 100644 --- a/src/lang/mm-unicode.json +++ b/src/lang/mm-unicode.json @@ -388,5 +388,7 @@ "delete entries": "Are you sure you want to delete {count} items?", "deleting items": "Deleting {count} items...", "import project zip": "Import Project(zip)", - "changelog": "Change Log" + "changelog": "Change Log", + "notifications": "Notifications", + "no_unread_notifications": "No unread notifications" } diff --git a/src/lang/mm-zawgyi.json b/src/lang/mm-zawgyi.json index cc7985d06..28e219e86 100644 --- a/src/lang/mm-zawgyi.json +++ b/src/lang/mm-zawgyi.json @@ -388,5 +388,7 @@ "delete entries": "Are you sure you want to delete {count} items?", "deleting items": "Deleting {count} items...", "import project zip": "Import Project(zip)", - "changelog": "Change Log" + "changelog": "Change Log", + "notifications": "Notifications", + "no_unread_notifications": "No unread notifications" } diff --git a/src/lang/pl-pl.json b/src/lang/pl-pl.json index 84db2e8fd..a4ae54a04 100644 --- a/src/lang/pl-pl.json +++ b/src/lang/pl-pl.json @@ -388,5 +388,7 @@ "delete entries": "Are you sure you want to delete {count} items?", "deleting items": "Deleting {count} items...", "import project zip": "Import Project(zip)", - "changelog": "Change Log" + "changelog": "Change Log", + "notifications": "Notifications", + "no_unread_notifications": "No unread notifications" } diff --git a/src/lang/pt-br.json b/src/lang/pt-br.json index aced1abfe..002a5d29e 100644 --- a/src/lang/pt-br.json +++ b/src/lang/pt-br.json @@ -388,5 +388,7 @@ "delete entries": "Are you sure you want to delete {count} items?", "deleting items": "Deleting {count} items...", "import project zip": "Import Project(zip)", - "changelog": "Change Log" + "changelog": "Change Log", + "notifications": "Notifications", + "no_unread_notifications": "No unread notifications" } diff --git a/src/lang/pu-in.json b/src/lang/pu-in.json index 0c27599e8..0e820ca2d 100644 --- a/src/lang/pu-in.json +++ b/src/lang/pu-in.json @@ -388,5 +388,7 @@ "delete entries": "Are you sure you want to delete {count} items?", "deleting items": "Deleting {count} items...", "import project zip": "Import Project(zip)", - "changelog": "Change Log" + "changelog": "Change Log", + "notifications": "Notifications", + "no_unread_notifications": "No unread notifications" } diff --git a/src/lang/ru-ru.json b/src/lang/ru-ru.json index 6e4f04ecc..1e9c7f684 100644 --- a/src/lang/ru-ru.json +++ b/src/lang/ru-ru.json @@ -389,5 +389,7 @@ "delete entries": "Are you sure you want to delete {count} items?", "deleting items": "Deleting {count} items...", "import project zip": "Import Project(zip)", - "changelog": "Change Log" + "changelog": "Change Log", + "notifications": "Notifications", + "no_unread_notifications": "No unread notifications" } diff --git a/src/lang/tl-ph.json b/src/lang/tl-ph.json index c495606b2..2e8b6f1ef 100644 --- a/src/lang/tl-ph.json +++ b/src/lang/tl-ph.json @@ -388,5 +388,7 @@ "delete entries": "Are you sure you want to delete {count} items?", "deleting items": "Deleting {count} items...", "import project zip": "Import Project(zip)", - "changelog": "Change Log" + "changelog": "Change Log", + "notifications": "Notifications", + "no_unread_notifications": "No unread notifications" } diff --git a/src/lang/tr-tr.json b/src/lang/tr-tr.json index 175412e0e..c0bf553ff 100644 --- a/src/lang/tr-tr.json +++ b/src/lang/tr-tr.json @@ -388,5 +388,7 @@ "delete entries": "Are you sure you want to delete {count} items?", "deleting items": "Deleting {count} items...", "import project zip": "Import Project(zip)", - "changelog": "Change Log" + "changelog": "Change Log", + "notifications": "Notifications", + "no_unread_notifications": "No unread notifications" } diff --git a/src/lang/uk-ua.json b/src/lang/uk-ua.json index 95acbcd52..68fc10565 100644 --- a/src/lang/uk-ua.json +++ b/src/lang/uk-ua.json @@ -388,5 +388,7 @@ "delete entries": "Are you sure you want to delete {count} items?", "deleting items": "Deleting {count} items...", "import project zip": "Import Project(zip)", - "changelog": "Change Log" + "changelog": "Change Log", + "notifications": "Notifications", + "no_unread_notifications": "No unread notifications" } diff --git a/src/lang/uz-uz.json b/src/lang/uz-uz.json index e5c097846..60728f7c5 100644 --- a/src/lang/uz-uz.json +++ b/src/lang/uz-uz.json @@ -388,5 +388,7 @@ "delete entries": "Are you sure you want to delete {count} items?", "deleting items": "Deleting {count} items...", "import project zip": "Import Project(zip)", - "changelog": "Change Log" + "changelog": "Change Log", + "notifications": "Notifications", + "no_unread_notifications": "No unread notifications" } diff --git a/src/lang/vi-vn.json b/src/lang/vi-vn.json index c5b282270..33e6f1cf5 100644 --- a/src/lang/vi-vn.json +++ b/src/lang/vi-vn.json @@ -389,5 +389,7 @@ "delete entries": "Are you sure you want to delete {count} items?", "deleting items": "Deleting {count} items...", "import project zip": "Import Project(zip)", - "changelog": "Change Log" + "changelog": "Change Log", + "notifications": "Notifications", + "no_unread_notifications": "No unread notifications" } diff --git a/src/lang/zh-cn.json b/src/lang/zh-cn.json index 25c1c12cd..da2107015 100644 --- a/src/lang/zh-cn.json +++ b/src/lang/zh-cn.json @@ -388,5 +388,7 @@ "delete entries": "Are you sure you want to delete {count} items?", "deleting items": "Deleting {count} items...", "import project zip": "Import Project(zip)", - "changelog": "Change Log" + "changelog": "Change Log", + "notifications": "Notifications", + "no_unread_notifications": "No unread notifications" } diff --git a/src/lang/zh-hant.json b/src/lang/zh-hant.json index f035e00f6..006f2fb0f 100644 --- a/src/lang/zh-hant.json +++ b/src/lang/zh-hant.json @@ -388,5 +388,7 @@ "delete entries": "Are you sure you want to delete {count} items?", "deleting items": "Deleting {count} items...", "import project zip": "Import Project(zip)", - "changelog": "Change Log" + "changelog": "Change Log", + "notifications": "Notifications", + "no_unread_notifications": "No unread notifications" } diff --git a/src/lang/zh-tw.json b/src/lang/zh-tw.json index 8218c4740..4de70f9b4 100644 --- a/src/lang/zh-tw.json +++ b/src/lang/zh-tw.json @@ -388,5 +388,7 @@ "delete entries": "Are you sure you want to delete {count} items?", "deleting items": "Deleting {count} items...", "import project zip": "Import Project(zip)", - "changelog": "Change Log" + "changelog": "Change Log", + "notifications": "Notifications", + "no_unread_notifications": "No unread notifications" } diff --git a/src/lib/acode.js b/src/lib/acode.js index c3b6423f4..238a2c064 100644 --- a/src/lib/acode.js +++ b/src/lib/acode.js @@ -22,6 +22,7 @@ import commands from "lib/commands"; import EditorFile from "lib/editorFile"; import files from "lib/fileList"; import fonts from "lib/fonts"; +import NotificationManager from "lib/notificationManager"; import openFolder from "lib/openFolder"; import projects from "lib/projects"; import selectionMenu from "lib/selectionMenu"; @@ -416,4 +417,29 @@ export default class Acode { url = await helpers.toInternalUri(url); return url; } + /** + * Push a notification + * @param {string} title Title of the notification + * @param {string} message Message body of the notification + * @param {Object} options Notification options + * @param {string} [options.icon] Icon for the notification, can be a URL or a base64 encoded image or icon class or svg string + * @param {boolean} [options.autoClose=true] Whether notification should auto close + * @param {Function} [options.action=null] Action callback when notification is clicked + * @param {('info'|'warning'|'error'|'success')} [options.type='info'] Type of notification + */ + pushNotification( + title, + message, + { icon, autoClose = true, action = null, type = "info" } = {}, + ) { + const nm = new NotificationManager(); + nm.pushNotification({ + title, + message, + icon, + autoClose, + action, + type, + }); + } } diff --git a/src/lib/main.js b/src/lib/main.js index 5d48caf4a..e7ecb7856 100644 --- a/src/lib/main.js +++ b/src/lib/main.js @@ -50,6 +50,7 @@ import { setKeyBindings } from "ace/commands"; import { initModes } from "ace/modelist"; import { keydownState } from "handlers/keyboard"; import { initFileList } from "lib/fileList"; +import NotificationManager from "lib/notificationManager"; import { addedFolder } from "lib/openFolder"; import { getEncoding, initEncodings } from "utils/encodings"; import constants from "./constants"; @@ -369,7 +370,10 @@ async function loadApp() { }); //#endregion - window.log("info", "Started app and services..."); + const notificationManager = new NotificationManager(); + notificationManager.init(); + + window.log("info", "Started app and its services..."); new EditorFile(); diff --git a/src/lib/notificationManager.js b/src/lib/notificationManager.js new file mode 100644 index 000000000..3d23fa5f3 --- /dev/null +++ b/src/lib/notificationManager.js @@ -0,0 +1,250 @@ +import sidebarApps from "sidebarApps"; + +// Singleton instance +let instance = null; + +export default class NotificationManager { + DEFAULT_ICON = ``; + MAX_NOTIFICATIONS = 20; + notifications = []; + REFRESH_INTERVAL = 60000; // 1 minute refresh interval + timeUpdateInterval = null; + + constructor() { + if (instance) { + return instance; + } + this.notifications = this.loadNotifications(); + instance = this; + } + + init() { + document.body.appendChild( +
, + ); + this.renderNotifications(); + this.startTimeUpdates(); + + sidebarApps + .get("notification") + ?.querySelector(".notifications-container") + .addEventListener("click", this.handleClick.bind(this)); + } + + startTimeUpdates() { + if (this.timeUpdateInterval) { + clearInterval(this.timeUpdateInterval); + } + + this.timeUpdateInterval = setInterval(() => { + this.updateNotificationTimes(); + }, this.REFRESH_INTERVAL); + } + + updateNotificationTimes() { + const container = sidebarApps + .get("notification") + ?.querySelector(".notifications-container"); + + if (!container) return; + + container.querySelectorAll(".notification-time").forEach((timeElement) => { + const notificationItem = timeElement.closest(".notification-item"); + const id = notificationItem?.dataset.id; + if (!id) return; + + const notification = this.notifications.find( + (n) => n.id === Number.parseInt(id), + ); + if (notification) { + timeElement.textContent = this.formatTime(notification.time); + } + }); + } + + loadNotifications() { + try { + const notifications = + JSON.parse(localStorage.getItem("notifications")) || []; + return notifications.map((n) => ({ + ...n, + time: new Date(n.time), + })); + } catch { + return []; + } + } + + saveNotifications() { + localStorage.setItem("notifications", JSON.stringify(this.notifications)); + } + + renderNotifications() { + const container = sidebarApps + .get("notification") + ?.querySelector(".notifications-container"); + if (!container) return; + + if (this.notifications.length === 0) { + container.innerHTML = `
${strings["no_unread_notifications"]}
`; + return; + } + + container.innerHTML = ""; + this.notifications.forEach((notification) => { + container.appendChild(this.createNotificationElement(notification)); + }); + } + + handleClick(e) { + const dismissButton = e.target.closest(".action-button"); + if (!dismissButton) return; + + e.stopPropagation(); + const notificationElement = dismissButton.closest(".notification-item"); + if (!notificationElement) return; + + const id = notificationElement.dataset.id; + if (id) { + const index = this.notifications.findIndex( + (n) => n.id === Number.parseInt(id), + ); + if (index > -1) { + notificationElement.remove(); + this.notifications.splice(index, 1); + this.saveNotifications(); + this.renderNotifications(); + } + } + } + + createNotificationElement(notification) { + const element = ( +
+ ); + element.innerHTML = ` +
+ ${this.parseIcon(notification.icon)} +
+
+
+ ${notification.title} + ${this.formatTime(notification.time)} +
+
${notification.message}
+
+
Dismiss
+
+
+ `; + if (notification.action) { + element.addEventListener("click", () => + notification.action(notification), + ); + } + return element; + } + + createToastNotification(notification) { + const element = ( +
+ ); + element.innerHTML = ` +
${this.parseIcon(notification.icon)}
+
+
+ ${notification.title} +
+
${notification.message}
+
+ ${notification.autoClose ? "" : ``} + `; + if (notification.action) { + element.addEventListener("click", () => + notification.action(notification), + ); + } + if (notification.autoClose) { + setTimeout(() => { + element.classList.add("hiding"); + setTimeout(() => element.remove(), 300); + }, 5000); + } + return element; + } + + addNotification(notification) { + this.notifications.unshift(notification); + + // Remove oldest if exceeding limit + if (this.notifications.length > this.MAX_NOTIFICATIONS) { + this.notifications.pop(); + } + + this.saveNotifications(); + + this.renderNotifications(); + + // show toast notification + document + .querySelector(".notification-toast-container") + ?.appendChild(this.createToastNotification(notification)); + } + + pushNotification({ + title, + message, + icon, + autoClose = true, + action = null, + type = "info", + }) { + const notification = { + id: Date.now(), + title, + message, + icon, + action, + autoClose, + type, + time: new Date(), + }; + this.addNotification(notification); + } + + parseIcon(icon) { + if (!icon) return this.DEFAULT_ICON; + if (icon.startsWith("`; + return ``; + } + + formatTime(date) { + const now = new Date(); + const diff = Math.floor((now - date) / 1000); + + if (diff < 60) return "Just now"; + if (diff < 3600) return `${Math.floor(diff / 60)}m`; + if (diff < 86400) return `${Math.floor(diff / 3600)}h`; + if (diff < 604800) return `${Math.floor(diff / 86400)}d`; + + return date.toLocaleDateString(); + } + + clearAll() { + this.notifications = []; + this.saveNotifications(); + this.renderNotifications(); + if (this.timeUpdateInterval) { + clearInterval(this.timeUpdateInterval); + this.timeUpdateInterval = null; + } + } +} diff --git a/src/sidebarApps/index.js b/src/sidebarApps/index.js index 8990ad0ce..ae8fb56ec 100644 --- a/src/sidebarApps/index.js +++ b/src/sidebarApps/index.js @@ -73,6 +73,7 @@ async function loadApps() { add(...(await import("./files")).default); add(...(await import("./searchInFiles")).default); add(...(await import("./extensions")).default); + add(...(await import("./notification")).default); } /** diff --git a/src/sidebarApps/notification/index.js b/src/sidebarApps/notification/index.js new file mode 100644 index 000000000..ea72b599e --- /dev/null +++ b/src/sidebarApps/notification/index.js @@ -0,0 +1,60 @@ +import NotificationManager from "lib/notificationManager"; +import "./style.scss"; +import Sidebar from "components/sidebar"; + +/**@type {HTMLElement} */ +let container; +/** @type {HTMLElement} */ +let $notificationContainer = null; + +let notificationManager; + +export default [ + "notifications", // icon + "notification", // id + strings["notifications"], // title + initApp, // init function + false, // prepend + onSelected, // onSelected function +]; + +const $header = ( +
+
+ {strings["notifications"]} + notificationManager.clearAll()} + > +
+
+); + +/** + * Initialize files app + * @param {HTMLElement} el + */ +function initApp(el) { + container = el; + container.classList.add("notifications"); + container.content = $header; + $notificationContainer = ( +
+ ); + container.append($notificationContainer); + + notificationManager = new NotificationManager(); + + Sidebar.on("show", onSelected); +} + +/** + * On selected handler for files app + * @param {HTMLElement} el + */ +function onSelected(el) { + const $scrollableLists = container.getAll(":scope .scroll[data-scroll-top]"); + $scrollableLists.forEach(($el) => { + $el.scrollTop = $el.dataset.scrollTop; + }); +} diff --git a/src/sidebarApps/notification/style.scss b/src/sidebarApps/notification/style.scss new file mode 100644 index 000000000..2a13ef239 --- /dev/null +++ b/src/sidebarApps/notification/style.scss @@ -0,0 +1,142 @@ +.container.notifications{ + display: flex; + flex-direction: column; + + .header{ + padding: 10px; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid var(--border-color); + + .title { + font-size: 20px; + font-weight: 600; + color: var(--primary-text-color); + display: flex; + align-items: center; + gap: 4px; + } + + .clear-all { + font-size: 12px; + color: var(--secondary-text-color); + background: none; + padding: 5px; + } + } + + .notifications-container { + flex: 1; + overflow-y: auto; + padding: 12px; + display: flex; + flex-direction: column; + gap: 8px; + + .empty-state { + text-align: center; + color: var(--secondary-text-color); + padding: 20px; + font-size: 14px; + } + .notification-item { + padding: 10px 12px; + border-radius: 6px; + background: var(--popup-background-color); + display: flex; + gap: 10px; + align-items: flex-start; + animation: slideIn 0.3s ease-out; + border: 1px solid var(--popup-border-color); + transition: all 0.2s ease; + + &:hover { + background: rgba($color: #000000, $alpha: 0.2); + border-color: var(--popup-border-color); + } + .notification-icon { + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + color: var(--primary-text-color); + } + .notification-content { + flex: 1; + min-width: 0; + + .notification-title { + font-size: 13px; + font-weight: 500; + margin-bottom: 4px; + white-space: nowrap; + overflow: hidden; + word-wrap: break-word; + color: var(--primary-text-color); + display: flex; + justify-content: space-between; + align-items: center; + + .notification-time { + font-size: 11px; + color: var(--secondary-text-color); + } + } + .notification-message { + font-size: 12px; + color: var(--secondary-text-color); + line-height: 1.4; + } + .notification-actions { + margin-top: 8px; + display: flex; + justify-content: flex-end; + + .action-button { + font-size: 11px; + padding: 6px 12px; + border-radius: 4px; + background: transparent; + color: var(--secondary-text-color); + cursor: pointer; + transition: all 0.2s ease; + border: 1px solid var(--border-color); + display: flex; + align-items: center; + gap: 4px; + + &:hover { + background: rgba($color: var(--border-color), $alpha: 0.6); + color: var(--primary-text-color); + border-color: var(--border-color); + } + &:active { + transform: translateY(1px); + } + &::before { + content: '×'; + font-size: 14px; + line-height: 1; + opacity: 0.8; + } + } + } + } + } + } +} + + +@keyframes slideIn { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} diff --git a/src/styles/main.scss b/src/styles/main.scss index ac193c466..1884d5160 100644 --- a/src/styles/main.scss +++ b/src/styles/main.scss @@ -710,4 +710,122 @@ input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-results-button, input[type="search"]::-webkit-search-results-decoration { -webkit-appearance: none; -} \ No newline at end of file +} + +.notification-toast-container { + position: absolute; + bottom: 20px; + right: 20px; + display: flex; + flex-direction: column; + gap: 8px; + z-index: 1000; + + .notification-toast { + padding: 12px; + border-radius: 6px; + background: var(--secondary-color); + min-width: 300px; + max-width: 400px; + display: flex; + gap: 12px; + align-items: flex-start; + box-shadow: 0 4px 12px var(--box-shadow-color); + animation: toastSlideIn 0.3s ease-out; + transition: all 0.3s ease; + border: 1px solid var(--border-color); + word-break: break-word; + white-space: normal; + + &.hiding { + transform: translateX(120%); + opacity: 0; + } + + .close-icon { + cursor: pointer; + font-size: 14px; + color: var(--secondary-text-color); + margin-left: auto; + + &:hover { + color: var(--button-background-color); + } + } + + .notification-icon { + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + color: var(--primary-text-color); + } + + .notification-content { + flex: 1; + min-width: 0; + + .notification-title { + font-size: 13px; + font-weight: 500; + margin-bottom: 4px; + color: var(--primary-text-color); + display: flex; + justify-content: space-between; + align-items: center; + } + + .notification-message { + font-size: 12px; + color: var(--secondary-text-color); + line-height: 1.4; + overflow-wrap: break-word; + hyphens: auto; + } + } + + &.success { + .notification-icon { + color: #48c158; + } + } + + &.warning { + .notification-icon { + color: var(--danger-text-color); + } + } + + &.error { + .notification-icon { + color: var(--error-text-color); + } + } + + &.info { + .notification-icon { + color: var(--primary-text-color); + } + } + } + + @media (max-width: 768px) { + .notification-toast { + min-width: auto; + max-width: calc(100vw - 40px); + } + } +} + +@keyframes toastSlideIn { + from { + transform: translateX(120%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} From 35489d3eaa82c765067ffcae7ab1bbb6138591a2 Mon Sep 17 00:00:00 2001 From: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> Date: Tue, 12 Nov 2024 20:18:16 +0530 Subject: [PATCH 2/2] refactor(plugins): migrate plugin update alerts to notification system - Replace header icon indicator with notification-based alerts in case of plugin alerts - Remove legacy plugin update icon from header UI --- src/lib/main.js | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/src/lib/main.js b/src/lib/main.js index e7ecb7856..e6c0acee6 100644 --- a/src/lib/main.js +++ b/src/lib/main.js @@ -380,25 +380,16 @@ async function loadApp() { checkPluginsUpdate() .then((updates) => { if (!updates.length) return; - const $icon = ( - { + acode.pushNotification( + "Plugin Updates", + `${updates.length} plugin${updates.length > 1 ? "s" : ""} ${updates.length > 1 ? "have" : "has"} new version${updates.length > 1 ? "s" : ""} available.`, + { + icon: "extension", + action: () => { plugins(updates); - $icon.remove(); - }} - attr-action="" - style={{ fontSize: "1.2rem" }} - className="icon notifications" - > + }, + }, ); - - if ($editMenuToggler.isConnected) { - $header.insertBefore($icon, $editMenuToggler); - } else if ($runBtn.isConnected) { - $header.insertBefore($icon, $runBtn); - } else { - $header.insertBefore($icon, $menuToggler); - } }) .catch(console.error);