From 2d947fb56b580cc91507c0279f46aa1f343b6e3e Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Wed, 17 Apr 2024 19:50:42 -0400 Subject: [PATCH] Realtime sync UI preferences between tabs --- app/stores/AuthStore.ts | 11 +++++----- app/stores/UiStore.ts | 47 +++++++++++++++++++++++++++++++---------- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/app/stores/AuthStore.ts b/app/stores/AuthStore.ts index f855b1b12..5f7a75981 100644 --- a/app/stores/AuthStore.ts +++ b/app/stores/AuthStore.ts @@ -108,22 +108,21 @@ export default class AuthStore extends Store { // signin/signout events in other tabs and follow suite. window.addEventListener("storage", (event) => { if (event.key === AUTH_STORE && event.newValue) { - const data: PersistedData | null | undefined = JSON.parse( - event.newValue - ); + const newData: PersistedData | null = JSON.parse(event.newValue); + // data may be null if key is deleted in localStorage - if (!data) { + if (!newData) { return; } // If we're not signed in then hydrate from the received data, otherwise if // we are signed in and the received data contains no user then sign out if (this.authenticated) { - if (data.user === null) { + if (newData.user === null) { void this.logout(false, false); } } else { - this.rehydrate(data); + this.rehydrate(newData); } } }); diff --git a/app/stores/UiStore.ts b/app/stores/UiStore.ts index 7abf4acf1..1f05f1e4c 100644 --- a/app/stores/UiStore.ts +++ b/app/stores/UiStore.ts @@ -20,6 +20,16 @@ export enum SystemTheme { Dark = "dark", } +type PersistedData = { + languagePromptDismissed: boolean | undefined; + theme: Theme; + sidebarCollapsed: boolean; + sidebarWidth: number; + sidebarRightWidth: number; + tocVisible: boolean; + commentsExpanded: string[]; +}; + class UiStore { // has the user seen the prompt to change the UI language and actioned it @observable @@ -74,7 +84,15 @@ class UiStore { constructor() { // Rehydrate - const data: Partial = Storage.get(UI_STORE) || {}; + const data: PersistedData = Storage.get(UI_STORE) || {}; + this.languagePromptDismissed = data.languagePromptDismissed; + this.sidebarCollapsed = !!data.sidebarCollapsed; + this.sidebarWidth = data.sidebarWidth || defaultTheme.sidebarWidth; + this.sidebarRightWidth = + data.sidebarRightWidth || defaultTheme.sidebarRightWidth; + this.tocVisible = !!data.tocVisible; + this.commentsExpanded = data.commentsExpanded || []; + this.theme = data.theme || Theme.System; // system theme listeners if (window.matchMedia) { @@ -93,15 +111,22 @@ class UiStore { } } - // persisted keys - this.languagePromptDismissed = data.languagePromptDismissed; - this.sidebarCollapsed = !!data.sidebarCollapsed; - this.sidebarWidth = data.sidebarWidth || defaultTheme.sidebarWidth; - this.sidebarRightWidth = - data.sidebarRightWidth || defaultTheme.sidebarRightWidth; - this.tocVisible = !!data.tocVisible; - this.commentsExpanded = data.commentsExpanded || []; - this.theme = data.theme || Theme.System; + window.addEventListener("storage", (event) => { + if (event.key === UI_STORE && event.newValue) { + const newData: PersistedData | null = JSON.parse(event.newValue); + + // data may be null if key is deleted in localStorage + if (!newData) { + return; + } + + // Note: we do not sync all properties here, sidebar widths cause fighting between windows + this.theme = newData.theme; + this.languagePromptDismissed = newData.languagePromptDismissed; + this.sidebarCollapsed = !!newData.sidebarCollapsed; + this.tocVisible = !!newData.tocVisible; + } + }); autorun(() => { Storage.set(UI_STORE, this.asJson); @@ -265,7 +290,7 @@ class UiStore { } @computed - get asJson() { + get asJson(): PersistedData { return { tocVisible: this.tocVisible, sidebarCollapsed: this.sidebarCollapsed,