From 1fd1405e308a8266469c97dd75606093eaf9836a Mon Sep 17 00:00:00 2001 From: Sudo-Ivan Date: Sat, 3 Jan 2026 16:06:45 -0600 Subject: [PATCH] feat(storage): implement MicronStorage for tab management, enhance TileCache with state management, and update DialogUtils and NotificationUtils for improved user interactions --- meshchatx/src/frontend/js/DialogUtils.js | 6 +- meshchatx/src/frontend/js/ElectronUtils.js | 13 +++ meshchatx/src/frontend/js/GlobalState.js | 7 ++ meshchatx/src/frontend/js/MicronStorage.js | 109 ++++++++++++++++++ .../src/frontend/js/NotificationUtils.js | 23 +++- meshchatx/src/frontend/js/TileCache.js | 53 ++++++++- 6 files changed, 201 insertions(+), 10 deletions(-) create mode 100644 meshchatx/src/frontend/js/MicronStorage.js diff --git a/meshchatx/src/frontend/js/DialogUtils.js b/meshchatx/src/frontend/js/DialogUtils.js index c6c27c8..330c823 100644 --- a/meshchatx/src/frontend/js/DialogUtils.js +++ b/meshchatx/src/frontend/js/DialogUtils.js @@ -16,8 +16,10 @@ class DialogUtils { // running inside electron, use ipc confirm return window.electron.confirm(message); } else { - // running inside normal browser, use browser alert - return window.confirm(message); + // running inside normal browser, use custom confirm dialog + return new Promise((resolve) => { + GlobalEmitter.emit("confirm", { message, resolve }); + }); } } diff --git a/meshchatx/src/frontend/js/ElectronUtils.js b/meshchatx/src/frontend/js/ElectronUtils.js index ba05e0b..6b3e5fd 100644 --- a/meshchatx/src/frontend/js/ElectronUtils.js +++ b/meshchatx/src/frontend/js/ElectronUtils.js @@ -9,6 +9,19 @@ class ElectronUtils { } } + static shutdown() { + if (window.electron) { + window.electron.shutdown(); + } + } + + static async getMemoryUsage() { + if (window.electron) { + return await window.electron.getMemoryUsage(); + } + return null; + } + static showPathInFolder(path) { if (window.electron) { window.electron.showPathInFolder(path); diff --git a/meshchatx/src/frontend/js/GlobalState.js b/meshchatx/src/frontend/js/GlobalState.js index fd73676..de1b275 100644 --- a/meshchatx/src/frontend/js/GlobalState.js +++ b/meshchatx/src/frontend/js/GlobalState.js @@ -3,6 +3,13 @@ import { reactive } from "vue"; // global state const globalState = reactive({ unreadConversationsCount: 0, + activeCallTab: "phone", + blockedDestinations: [], + config: { + banished_effect_enabled: true, + banished_text: "BANISHED", + banished_color: "#dc2626", + }, }); export default globalState; diff --git a/meshchatx/src/frontend/js/MicronStorage.js b/meshchatx/src/frontend/js/MicronStorage.js new file mode 100644 index 0000000..d37d5d9 --- /dev/null +++ b/meshchatx/src/frontend/js/MicronStorage.js @@ -0,0 +1,109 @@ +const DB_NAME = "micron_editor_db"; +const DB_VERSION = 1; +const STORE_NAME = "tabs"; + +class MicronStorage { + constructor() { + this.db = null; + this.initPromise = this.init(); + } + + async init() { + return new Promise((resolve, reject) => { + if (this.db) { + resolve(this.db); + return; + } + + const idb = + window.indexedDB || + window.mozIndexedDB || + window.webkitIndexedDB || + window.msIndexedDB || + globalThis.indexedDB; + if (!idb) { + reject("IndexedDB not supported"); + return; + } + + const request = idb.open(DB_NAME, DB_VERSION); + + request.onerror = (event) => { + console.error("IndexedDB error:", event.target.errorCode); + reject("IndexedDB error: " + event.target.errorCode); + }; + + request.onupgradeneeded = (event) => { + const db = event.target.result; + if (!db.objectStoreNames.contains(STORE_NAME)) { + db.createObjectStore(STORE_NAME, { keyPath: "id", autoIncrement: true }); + } + }; + + request.onsuccess = (event) => { + this.db = event.target.result; + resolve(this.db); + }; + }); + } + + async saveTabs(tabs) { + await this.initPromise; + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([STORE_NAME], "readwrite"); + const store = transaction.objectStore(STORE_NAME); + + // Clear existing tabs before saving new ones to maintain order and structure + const clearRequest = store.clear(); + + clearRequest.onsuccess = () => { + if (tabs.length === 0) { + return; + } + + // Ensure we are storing plain objects, not Vue proxies or other non-cloneable objects. + // JSON.parse/stringify is a safe way to strip proxies and ensure serializability + // for these simple tab objects. + const plainTabs = JSON.parse(JSON.stringify(tabs)); + + plainTabs.forEach((tab) => { + store.add(tab); + }); + }; + + transaction.oncomplete = () => resolve(); + transaction.onerror = (event) => reject(event.target.error); + }); + } + + async loadTabs() { + await this.initPromise; + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([STORE_NAME], "readonly"); + const store = transaction.objectStore(STORE_NAME); + const request = store.getAll(); + + request.onsuccess = () => { + resolve(request.result); + }; + + request.onerror = () => { + reject(request.error); + }; + }); + } + + async clearAll() { + await this.initPromise; + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([STORE_NAME], "readwrite"); + const store = transaction.objectStore(STORE_NAME); + const request = store.clear(); + + request.onsuccess = () => resolve(); + request.onerror = () => reject(request.error); + }); + } +} + +export const micronStorage = new MicronStorage(); diff --git a/meshchatx/src/frontend/js/NotificationUtils.js b/meshchatx/src/frontend/js/NotificationUtils.js index d12f4b8..c9e5748 100644 --- a/meshchatx/src/frontend/js/NotificationUtils.js +++ b/meshchatx/src/frontend/js/NotificationUtils.js @@ -1,5 +1,9 @@ class NotificationUtils { static showIncomingCallNotification() { + if (window.electron) { + window.electron.showNotification("Incoming Call", "Someone is calling you."); + return; + } Notification.requestPermission().then((result) => { if (result === "granted") { new window.Notification("Incoming Call", { @@ -11,6 +15,10 @@ class NotificationUtils { } static showMissedCallNotification(from) { + if (window.electron) { + window.electron.showNotification("Missed Call", `You missed a call from ${from}.`); + return; + } Notification.requestPermission().then((result) => { if (result === "granted") { new window.Notification("Missed Call", { @@ -22,6 +30,10 @@ class NotificationUtils { } static showNewVoicemailNotification(from) { + if (window.electron) { + window.electron.showNotification("New Voicemail", `You have a new voicemail from ${from}.`); + return; + } Notification.requestPermission().then((result) => { if (result === "granted") { new window.Notification("New Voicemail", { @@ -32,11 +44,18 @@ class NotificationUtils { }); } - static showNewMessageNotification() { + static showNewMessageNotification(from, content) { + if (window.electron) { + window.electron.showNotification( + "New Message", + from ? `${from}: ${content || "Sent a message."}` : "Someone sent you a message." + ); + return; + } Notification.requestPermission().then((result) => { if (result === "granted") { new window.Notification("New Message", { - body: "Someone sent you a message.", + body: from ? `${from}: ${content || "Sent a message."}` : "Someone sent you a message.", tag: "new_message", // only ever show one new message notification at a time }); } diff --git a/meshchatx/src/frontend/js/TileCache.js b/meshchatx/src/frontend/js/TileCache.js index 04a671d..5e19c72 100644 --- a/meshchatx/src/frontend/js/TileCache.js +++ b/meshchatx/src/frontend/js/TileCache.js @@ -1,6 +1,7 @@ const DB_NAME = "meshchat_map_cache"; -const DB_VERSION = 1; +const DB_VERSION = 2; const STORE_NAME = "tiles"; +const STATE_STORE = "map_state"; class TileCache { constructor() { @@ -10,7 +11,20 @@ class TileCache { async init() { return new Promise((resolve, reject) => { - const request = indexedDB.open(DB_NAME, DB_VERSION); + const idb = + window.indexedDB || + window.mozIndexedDB || + window.webkitIndexedDB || + window.msIndexedDB || + globalThis.indexedDB; + + if (!idb) { + console.warn("IndexedDB not supported, map caching will be disabled"); + reject("IndexedDB not supported"); + return; + } + + const request = idb.open(DB_NAME, DB_VERSION); request.onerror = (event) => reject("IndexedDB error: " + event.target.errorCode); @@ -19,6 +33,9 @@ class TileCache { if (!db.objectStoreNames.contains(STORE_NAME)) { db.createObjectStore(STORE_NAME); } + if (!db.objectStoreNames.contains(STATE_STORE)) { + db.createObjectStore(STATE_STORE); + } }; request.onsuccess = (event) => { @@ -52,17 +69,41 @@ class TileCache { }); } - async clear() { + async getMapState(key) { await this.initPromise; return new Promise((resolve, reject) => { - const transaction = this.db.transaction([STORE_NAME], "readwrite"); - const store = transaction.objectStore(STORE_NAME); - const request = store.clear(); + const transaction = this.db.transaction([STATE_STORE], "readonly"); + const store = transaction.objectStore(STATE_STORE); + const request = store.get(key); + + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error); + }); + } + + async setMapState(key, data) { + await this.initPromise; + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([STATE_STORE], "readwrite"); + const store = transaction.objectStore(STATE_STORE); + const request = store.put(data, key); request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); } + + async clear() { + await this.initPromise; + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([STORE_NAME, STATE_STORE], "readwrite"); + transaction.objectStore(STORE_NAME).clear(); + transaction.objectStore(STATE_STORE).clear(); + + transaction.oncomplete = () => resolve(); + transaction.onerror = () => reject(transaction.error); + }); + } } export default new TileCache();