mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-04 08:15:41 +00:00
An installed app had no title bar of its own, no way to save attachments except a zip, and "pop out" stayed inside the page. A new window was a second copy of the app that had to restore the session itself, and on start-up that evicts any login made without "Remember me" from the list every window shares. - Title bar: `display_override` with `window-controls-overlay`, one `--titlebar-inset` variable at the page roots and skeletons, and a drag strip with the window title. 0 without an overlay, so nothing moves. - Save all to a folder: `showDirectoryPicker`, asking the device policy before the picker and before each file, with the zip as the fallback. - Pop-out routes `/popout/message/<id>` and `/popout/compose`, on offer in an installed app or when the desktop bridge has windows. Hidden in Lite. - A pop-out borrows the main window's session and never evicts or starts a token timer. Every renewal now takes a Web Lock, so two windows can't send the same refresh token, which Stalwart 1.0 may treat as a replay. - A draft moves to its window under the same id. A closing window hands unsaved text to the main window to save, since its own request wouldn't survive the close. - Read state, drafts, theme and language follow across windows, and `html[data-desktop]` gates the app-like behaviour in `app/desktop.css`.
43 lines
1.7 KiB
TypeScript
43 lines
1.7 KiB
TypeScript
/**
|
|
* Keeps what the windows of the app share up to date without a reload: theme,
|
|
* language and settings. Each is a persisted store, so a change in one window
|
|
* reaches the others as a `storage` event. Read state and drafts go through
|
|
* lib/popout/channel.ts, and each window keeps its own account (see
|
|
* local-data/desktop-offline-decisions.md).
|
|
*
|
|
* Only in pop-outs and the desktop app: tabs of a browser keep today's
|
|
* behaviour, where a change shows at the next load.
|
|
*/
|
|
|
|
import { useLocaleStore } from '@/stores/locale-store';
|
|
import { useSettingsStore } from '@/stores/settings-store';
|
|
import { useThemeStore } from '@/stores/theme-store';
|
|
import { hasDesktopWindows } from '@/lib/popout/support';
|
|
import { isPopoutWindow } from '@/lib/popout/window-role';
|
|
|
|
export function windowSyncWanted(): boolean {
|
|
return isPopoutWindow() || hasDesktopWindows();
|
|
}
|
|
|
|
/** Starts listening; returns the stop. Does nothing where windowSyncWanted says no. */
|
|
export function startWindowSync(): () => void {
|
|
if (!windowSyncWanted()) return () => {};
|
|
const onStorage = (event: StorageEvent) => {
|
|
switch (event.key) {
|
|
case 'theme-storage':
|
|
// Rehydrating sets the state; this puts it on the page.
|
|
void Promise.resolve(useThemeStore.persist.rehydrate()).then(() => useThemeStore.getState().initializeTheme());
|
|
break;
|
|
case 'locale-storage':
|
|
// IntlProvider switches the catalog when the store changes.
|
|
void useLocaleStore.persist.rehydrate();
|
|
break;
|
|
case 'settings-storage':
|
|
void useSettingsStore.persist.rehydrate();
|
|
break;
|
|
}
|
|
};
|
|
window.addEventListener('storage', onStorage);
|
|
return () => window.removeEventListener('storage', onStorage);
|
|
}
|