diff --git a/src/app/features/settings/Settings.tsx b/src/app/features/settings/Settings.tsx index 4349a41c2..751e54e0e 100644 --- a/src/app/features/settings/Settings.tsx +++ b/src/app/features/settings/Settings.tsx @@ -33,16 +33,9 @@ import { About } from './about'; import { UseStateProvider } from '../../components/UseStateProvider'; import { stopPropagation } from '../../utils/keyboard'; import { LogoutDialog } from '../../components/LogoutDialog'; +import { SettingsPages } from './pages'; -export enum SettingsPages { - GeneralPage, - AccountPage, - NotificationPage, - DevicesPage, - EmojisStickersPage, - DeveloperToolsPage, - AboutPage, -} +export { SettingsPages }; type SettingsMenuItem = { page: SettingsPages; diff --git a/src/app/features/settings/SettingsLazy.tsx b/src/app/features/settings/SettingsLazy.tsx new file mode 100644 index 000000000..8764d2bdb --- /dev/null +++ b/src/app/features/settings/SettingsLazy.tsx @@ -0,0 +1,43 @@ +import React, { lazy, Suspense } from 'react'; +import { Box, Spinner, config } from 'folds'; +import type { SettingsPages } from './pages'; + +// Settings is a large, rarely-open dialog (all its pages, pickers, the +// denoise tester…). Load it on first open instead of in the entry chunk, and +// warm the chunk once the app is idle so opening it rarely waits. +const loadSettings = () => import('./Settings'); +const SettingsDialog = lazy(() => loadSettings().then((m) => ({ default: m.Settings }))); +if (typeof window !== 'undefined') { + const warm = () => { + loadSettings().catch(() => undefined); + }; + if ('requestIdleCallback' in window) window.requestIdleCallback(warm, { timeout: 10_000 }); + else setTimeout(warm, 5_000); +} + +type SettingsProps = { + initialPage?: SettingsPages; + requestClose: () => void; +}; + +export function Settings(props: SettingsProps) { + return ( + + + + } + > + + + ); +} diff --git a/src/app/features/settings/account/Profile.tsx b/src/app/features/settings/account/Profile.tsx index 4e08626ab..44f1036f6 100644 --- a/src/app/features/settings/account/Profile.tsx +++ b/src/app/features/settings/account/Profile.tsx @@ -71,6 +71,7 @@ import { makePresetId, normalizeLabel, } from '../../../utils/statusPresets'; +import { STATUS_EXPIRY_KEY, STATUS_MSG_KEY } from '../../../utils/statusKeys'; import { writeStatusExpiry } from '../../../utils/statusExpiry'; type ProfileProps = { @@ -370,8 +371,7 @@ function ProfileDisplayName({ profile, userId }: ProfileProps) { ); } -export const STATUS_EXPIRY_KEY = (id: string) => `lotus-status-expiry-${id}`; -export const STATUS_MSG_KEY = (id: string) => `lotus-status-msg-${id}`; +export { STATUS_EXPIRY_KEY, STATUS_MSG_KEY }; const CLEAR_AFTER_OPTIONS = [ { label: 'Never', value: '0' }, diff --git a/src/app/features/settings/index.ts b/src/app/features/settings/index.ts index 90e269730..3b9385629 100644 --- a/src/app/features/settings/index.ts +++ b/src/app/features/settings/index.ts @@ -1 +1,2 @@ -export * from './Settings'; +export { SettingsPages } from './pages'; +export { Settings } from './SettingsLazy'; diff --git a/src/app/features/settings/pages.ts b/src/app/features/settings/pages.ts new file mode 100644 index 000000000..94d9db903 --- /dev/null +++ b/src/app/features/settings/pages.ts @@ -0,0 +1,11 @@ +/** Settings dialog pages. Kept apart from Settings.tsx so callers can name a + * page without pulling the dialog into the entry chunk. */ +export enum SettingsPages { + GeneralPage, + AccountPage, + NotificationPage, + DevicesPage, + EmojisStickersPage, + DeveloperToolsPage, + AboutPage, +} diff --git a/src/app/pages/client/ClientNonUIFeatures.tsx b/src/app/pages/client/ClientNonUIFeatures.tsx index 2c1c659c1..70ce60266 100644 --- a/src/app/pages/client/ClientNonUIFeatures.tsx +++ b/src/app/pages/client/ClientNonUIFeatures.tsx @@ -63,7 +63,7 @@ import { migrateLocalMuteTimers, unmuteRoom, } from '../../features/room-nav/muteTimers'; -import { STATUS_EXPIRY_KEY, STATUS_MSG_KEY } from '../../features/settings/account/Profile'; +import { STATUS_EXPIRY_KEY, STATUS_MSG_KEY } from '../../utils/statusKeys'; import { noteLocalStatusWrite } from '../../utils/ownStatusSync'; import { setPresenceWithRetry } from '../../utils/presenceWrite'; import { useDeepLinkNavigate } from '../../hooks/useDeepLinkNavigate'; diff --git a/src/app/utils/statusKeys.ts b/src/app/utils/statusKeys.ts new file mode 100644 index 000000000..f31cd00ea --- /dev/null +++ b/src/app/utils/statusKeys.ts @@ -0,0 +1,3 @@ +/** localStorage keys for this device's copy of the status message and its expiry. */ +export const STATUS_EXPIRY_KEY = (id: string) => `lotus-status-expiry-${id}`; +export const STATUS_MSG_KEY = (id: string) => `lotus-status-msg-${id}`; diff --git a/vite.config.js b/vite.config.js index 0d6b2ef7b..1aaf5c5f6 100644 --- a/vite.config.js +++ b/vite.config.js @@ -251,6 +251,25 @@ const vendorChunks = (id) => { if (id.includes('node_modules/immer')) return 'immer'; if (id.includes('node_modules/folds')) return 'folds'; if (id.includes('node_modules/emojibase')) return 'emojibase'; + // HTML sanitising (sanitize-html pulls in postcss + htmlparser2) and colour + // maths are needed at startup but are big, stable libraries: keep them in + // their own cacheable chunks instead of wherever the chunk graph drops them + // (once Settings became lazy they landed in the entry chunk). + if ( + id.includes('node_modules/sanitize-html') || + id.includes('node_modules/postcss') || + id.includes('node_modules/htmlparser2') || + id.includes('node_modules/domhandler') || + id.includes('node_modules/domutils') || + id.includes('node_modules/dom-serializer') || + id.includes('node_modules/entities') || + id.includes('node_modules/parse-srcset') || + id.includes('node_modules/nanoid') || + id.includes('node_modules/picocolors') || + id.includes('node_modules/source-map-js') + ) + return 'sanitize-html'; + if (id.includes('node_modules/chroma-js')) return 'chroma'; }; export default defineConfig({