feat: Add chat seperator and restore last viewed chat on restart

This commit is contained in:
2026-04-02 00:47:44 +02:00
parent bbb6deb0a2
commit 5d7e045764
10 changed files with 412 additions and 18 deletions

View File

@@ -6,12 +6,15 @@ Offline-first storage layer that keeps messages, users, rooms, reactions, bans,
```
persistence/
├── index.ts Barrel (exports DatabaseService)
├── app-resume.storage.ts localStorage helpers for launch settings and last viewed chat
├── index.ts Barrel (exports DatabaseService and storage helpers)
├── database.service.ts Platform-agnostic facade
├── browser-database.service.ts IndexedDB backend (web)
└── electron-database.service.ts IPC/SQLite backend (desktop)
```
`app-resume.storage.ts` is the one exception to the `DatabaseService` facade. It stores lightweight UI-level launch preferences and the last viewed room/channel snapshot in `localStorage`, which would be unnecessary overhead to route through IndexedDB or SQLite.
## Platform routing
```mermaid

View File

@@ -0,0 +1,114 @@
import {
STORAGE_KEY_GENERAL_SETTINGS,
STORAGE_KEY_LAST_VIEWED_CHAT
} from '../../core/constants';
export interface GeneralSettings {
reopenLastViewedChat: boolean;
}
export const DEFAULT_GENERAL_SETTINGS: GeneralSettings = {
reopenLastViewedChat: true
};
export interface LastViewedChatSnapshot {
userId: string;
roomId: string;
channelId: string | null;
}
export function loadGeneralSettingsFromStorage(): GeneralSettings {
try {
const raw = localStorage.getItem(STORAGE_KEY_GENERAL_SETTINGS);
if (!raw) {
return { ...DEFAULT_GENERAL_SETTINGS };
}
return normaliseGeneralSettings(JSON.parse(raw) as Partial<GeneralSettings>);
} catch {
return { ...DEFAULT_GENERAL_SETTINGS };
}
}
export function saveGeneralSettingsToStorage(patch: Partial<GeneralSettings>): GeneralSettings {
const nextSettings = normaliseGeneralSettings({
...loadGeneralSettingsFromStorage(),
...patch
});
try {
localStorage.setItem(STORAGE_KEY_GENERAL_SETTINGS, JSON.stringify(nextSettings));
} catch {}
return nextSettings;
}
export function loadLastViewedChatFromStorage(userId?: string | null): LastViewedChatSnapshot | null {
try {
const raw = localStorage.getItem(STORAGE_KEY_LAST_VIEWED_CHAT);
if (!raw) {
return null;
}
const snapshot = normaliseLastViewedChatSnapshot(JSON.parse(raw) as Partial<LastViewedChatSnapshot>);
if (!snapshot) {
return null;
}
if (userId && snapshot.userId !== userId) {
return null;
}
return snapshot;
} catch {
return null;
}
}
export function saveLastViewedChatToStorage(snapshot: LastViewedChatSnapshot): void {
const normalised = normaliseLastViewedChatSnapshot(snapshot);
if (!normalised) {
return;
}
try {
localStorage.setItem(STORAGE_KEY_LAST_VIEWED_CHAT, JSON.stringify(normalised));
} catch {}
}
export function clearLastViewedChatFromStorage(): void {
try {
localStorage.removeItem(STORAGE_KEY_LAST_VIEWED_CHAT);
} catch {}
}
function normaliseGeneralSettings(raw: Partial<GeneralSettings>): GeneralSettings {
return {
reopenLastViewedChat:
typeof raw.reopenLastViewedChat === 'boolean'
? raw.reopenLastViewedChat
: DEFAULT_GENERAL_SETTINGS.reopenLastViewedChat
};
}
function normaliseLastViewedChatSnapshot(raw: Partial<LastViewedChatSnapshot>): LastViewedChatSnapshot | null {
const userId = typeof raw.userId === 'string' ? raw.userId.trim() : '';
const roomId = typeof raw.roomId === 'string' ? raw.roomId.trim() : '';
const channelId = typeof raw.channelId === 'string'
? raw.channelId.trim() || null
: null;
if (!userId || !roomId) {
return null;
}
return {
userId,
roomId,
channelId
};
}

View File

@@ -1 +1,2 @@
export * from './app-resume.storage';
export * from './database.service';