feat: Add "Appearance" pane to preferences (#816)
Co-authored-by: Mo <mo@standardnotes.com>
This commit is contained in:
@@ -9,21 +9,22 @@ import {
|
|||||||
import VisuallyHidden from '@reach/visually-hidden';
|
import VisuallyHidden from '@reach/visually-hidden';
|
||||||
import { FunctionComponent } from 'preact';
|
import { FunctionComponent } from 'preact';
|
||||||
import { IconType, Icon } from './Icon';
|
import { IconType, Icon } from './Icon';
|
||||||
import { useEffect, useState } from 'preact/hooks';
|
|
||||||
|
|
||||||
export type DropdownItem = {
|
export type DropdownItem = {
|
||||||
icon?: IconType;
|
icon?: IconType;
|
||||||
iconClassName?: string;
|
iconClassName?: string;
|
||||||
label: string;
|
label: string;
|
||||||
value: string;
|
value: string;
|
||||||
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DropdownProps = {
|
type DropdownProps = {
|
||||||
id: string;
|
id: string;
|
||||||
label: string;
|
label: string;
|
||||||
items: DropdownItem[];
|
items: DropdownItem[];
|
||||||
defaultValue: string;
|
value: string;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string, item: DropdownItem) => void;
|
||||||
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ListboxButtonProps = DropdownItem & {
|
type ListboxButtonProps = DropdownItem & {
|
||||||
@@ -59,20 +60,18 @@ export const Dropdown: FunctionComponent<DropdownProps> = ({
|
|||||||
id,
|
id,
|
||||||
label,
|
label,
|
||||||
items,
|
items,
|
||||||
defaultValue,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
|
disabled,
|
||||||
}) => {
|
}) => {
|
||||||
const [value, setValue] = useState(defaultValue);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setValue(defaultValue);
|
|
||||||
}, [defaultValue]);
|
|
||||||
|
|
||||||
const labelId = `${id}-label`;
|
const labelId = `${id}-label`;
|
||||||
|
|
||||||
const handleChange = (value: string) => {
|
const handleChange = (value: string) => {
|
||||||
setValue(value);
|
const selectedItem = items.find(
|
||||||
onChange(value);
|
(item) => item.value === value
|
||||||
|
) as DropdownItem;
|
||||||
|
|
||||||
|
onChange(value, selectedItem);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -82,6 +81,7 @@ export const Dropdown: FunctionComponent<DropdownProps> = ({
|
|||||||
value={value}
|
value={value}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
aria-labelledby={labelId}
|
aria-labelledby={labelId}
|
||||||
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
<ListboxButton
|
<ListboxButton
|
||||||
className="sn-dropdown-button"
|
className="sn-dropdown-button"
|
||||||
@@ -106,6 +106,7 @@ export const Dropdown: FunctionComponent<DropdownProps> = ({
|
|||||||
className="sn-dropdown-item"
|
className="sn-dropdown-item"
|
||||||
value={item.value}
|
value={item.value}
|
||||||
label={item.label}
|
label={item.label}
|
||||||
|
disabled={item.disabled}
|
||||||
>
|
>
|
||||||
{item.icon ? (
|
{item.icon ? (
|
||||||
<div className="flex mr-3">
|
<div className="flex mr-3">
|
||||||
|
|||||||
@@ -49,6 +49,19 @@ const toggleFocusMode = (enabled: boolean) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const sortThemes = (a: SNTheme, b: SNTheme) => {
|
||||||
|
const aIsLayerable = a.isLayerable();
|
||||||
|
const bIsLayerable = b.isLayerable();
|
||||||
|
|
||||||
|
if (aIsLayerable && !bIsLayerable) {
|
||||||
|
return 1;
|
||||||
|
} else if (!aIsLayerable && bIsLayerable) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const QuickSettingsMenu: FunctionComponent<MenuProps> = observer(
|
const QuickSettingsMenu: FunctionComponent<MenuProps> = observer(
|
||||||
({ application, appState }) => {
|
({ application, appState }) => {
|
||||||
const {
|
const {
|
||||||
@@ -79,23 +92,7 @@ const QuickSettingsMenu: FunctionComponent<MenuProps> = observer(
|
|||||||
const themes = application.getDisplayableItems(
|
const themes = application.getDisplayableItems(
|
||||||
ContentType.Theme
|
ContentType.Theme
|
||||||
) as SNTheme[];
|
) as SNTheme[];
|
||||||
setThemes(
|
setThemes(themes.sort(sortThemes));
|
||||||
themes.sort((a, b) => {
|
|
||||||
const aIsLayerable = a.isLayerable();
|
|
||||||
const bIsLayerable = b.isLayerable();
|
|
||||||
|
|
||||||
if (aIsLayerable && !bIsLayerable) {
|
|
||||||
return 1;
|
|
||||||
} else if (!aIsLayerable && bIsLayerable) {
|
|
||||||
return -1;
|
|
||||||
} else {
|
|
||||||
return a.name.toLowerCase() <
|
|
||||||
b.name.toLowerCase()
|
|
||||||
? -1
|
|
||||||
: 1;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
setDefaultThemeOn(
|
setDefaultThemeOn(
|
||||||
!themes.find((theme) => theme.active && !theme.isLayerable())
|
!themes.find((theme) => theme.active && !theme.isLayerable())
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ const PREFERENCES_MENU_ITEMS: PreferencesMenuItem[] = [
|
|||||||
const READY_PREFERENCES_MENU_ITEMS: PreferencesMenuItem[] = [
|
const READY_PREFERENCES_MENU_ITEMS: PreferencesMenuItem[] = [
|
||||||
{ id: 'account', label: 'Account', icon: 'user' },
|
{ id: 'account', label: 'Account', icon: 'user' },
|
||||||
{ id: 'general', label: 'General', icon: 'settings' },
|
{ id: 'general', label: 'General', icon: 'settings' },
|
||||||
|
{ id: 'appearance', label: 'Appearance', icon: 'themes' },
|
||||||
{ id: 'security', label: 'Security', icon: 'security' },
|
{ id: 'security', label: 'Security', icon: 'security' },
|
||||||
{ id: 'backups', label: 'Backups', icon: 'restore' },
|
{ id: 'backups', label: 'Backups', icon: 'restore' },
|
||||||
{ id: 'listed', label: 'Listed', icon: 'listed' },
|
{ id: 'listed', label: 'Listed', icon: 'listed' },
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { AppState } from '@/ui_models/app_state';
|
|||||||
import { useEffect, useMemo } from 'preact/hooks';
|
import { useEffect, useMemo } from 'preact/hooks';
|
||||||
import { ExtensionPane } from './panes/ExtensionPane';
|
import { ExtensionPane } from './panes/ExtensionPane';
|
||||||
import { Backups } from '@/preferences/panes/Backups';
|
import { Backups } from '@/preferences/panes/Backups';
|
||||||
|
import { Appearance } from './panes/Appearance';
|
||||||
|
|
||||||
interface PreferencesProps extends MfaProps {
|
interface PreferencesProps extends MfaProps {
|
||||||
application: WebApplication;
|
application: WebApplication;
|
||||||
@@ -42,7 +43,7 @@ const PaneSelector: FunctionComponent<
|
|||||||
<AccountPreferences application={application} appState={appState} />
|
<AccountPreferences application={application} appState={appState} />
|
||||||
);
|
);
|
||||||
case 'appearance':
|
case 'appearance':
|
||||||
return null;
|
return <Appearance application={application} />;
|
||||||
case 'security':
|
case 'security':
|
||||||
return (
|
return (
|
||||||
<Security
|
<Security
|
||||||
|
|||||||
196
app/assets/javascripts/preferences/panes/Appearance.tsx
Normal file
196
app/assets/javascripts/preferences/panes/Appearance.tsx
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
import { Dropdown, DropdownItem } from '@/components/Dropdown';
|
||||||
|
import { PremiumModalProvider, usePremiumModal } from '@/components/Premium';
|
||||||
|
import { sortThemes } from '@/components/QuickSettingsMenu/QuickSettingsMenu';
|
||||||
|
import { HorizontalSeparator } from '@/components/shared/HorizontalSeparator';
|
||||||
|
import { Switch } from '@/components/Switch';
|
||||||
|
import { WebApplication } from '@/ui_models/application';
|
||||||
|
import { Features } from '@standardnotes/features';
|
||||||
|
import {
|
||||||
|
ContentType,
|
||||||
|
FeatureIdentifier,
|
||||||
|
FeatureStatus,
|
||||||
|
PrefKey,
|
||||||
|
SNTheme,
|
||||||
|
} from '@standardnotes/snjs';
|
||||||
|
import { observer } from 'mobx-react-lite';
|
||||||
|
import { FunctionComponent } from 'preact';
|
||||||
|
import { useEffect, useState } from 'preact/hooks';
|
||||||
|
import {
|
||||||
|
PreferencesGroup,
|
||||||
|
PreferencesPane,
|
||||||
|
PreferencesSegment,
|
||||||
|
Subtitle,
|
||||||
|
Title,
|
||||||
|
Text,
|
||||||
|
} from '../components';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
application: WebApplication;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AppearancePane: FunctionComponent<Props> = observer(({ application }) => {
|
||||||
|
const premiumModal = usePremiumModal();
|
||||||
|
const isEntitledToMidnightTheme =
|
||||||
|
application.getFeatureStatus(FeatureIdentifier.MidnightTheme) ===
|
||||||
|
FeatureStatus.Entitled;
|
||||||
|
|
||||||
|
const [themeItems, setThemeItems] = useState<DropdownItem[]>([]);
|
||||||
|
const [autoLightTheme, setAutoLightTheme] = useState<string>(
|
||||||
|
() =>
|
||||||
|
application.getPreference(
|
||||||
|
PrefKey.AutoLightThemeIdentifier,
|
||||||
|
'Default'
|
||||||
|
) as string
|
||||||
|
);
|
||||||
|
const [autoDarkTheme, setAutoDarkTheme] = useState<string>(
|
||||||
|
() =>
|
||||||
|
application.getPreference(
|
||||||
|
PrefKey.AutoDarkThemeIdentifier,
|
||||||
|
isEntitledToMidnightTheme ? FeatureIdentifier.MidnightTheme : 'Default'
|
||||||
|
) as string
|
||||||
|
);
|
||||||
|
const [useDeviceSettings, setUseDeviceSettings] = useState(
|
||||||
|
() =>
|
||||||
|
application.getPreference(PrefKey.UseSystemColorScheme, false) as boolean
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const themesAsItems: DropdownItem[] = (
|
||||||
|
application.getDisplayableItems(ContentType.Theme) as SNTheme[]
|
||||||
|
)
|
||||||
|
.filter((theme) => !theme.isLayerable())
|
||||||
|
.sort(sortThemes)
|
||||||
|
.map((theme) => {
|
||||||
|
return {
|
||||||
|
label: theme.name,
|
||||||
|
value: theme.identifier as string,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
Features.filter(
|
||||||
|
(feature) =>
|
||||||
|
feature.content_type === ContentType.Theme && !feature.layerable
|
||||||
|
).forEach((theme) => {
|
||||||
|
if (
|
||||||
|
themesAsItems.findIndex((item) => item.value === theme.identifier) ===
|
||||||
|
-1
|
||||||
|
) {
|
||||||
|
themesAsItems.push({
|
||||||
|
label: theme.name as string,
|
||||||
|
value: theme.identifier,
|
||||||
|
icon: 'premium-feature',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
themesAsItems.unshift({
|
||||||
|
label: 'Default',
|
||||||
|
value: 'Default',
|
||||||
|
});
|
||||||
|
|
||||||
|
setThemeItems(themesAsItems);
|
||||||
|
}, [application]);
|
||||||
|
|
||||||
|
const toggleUseDeviceSettings = () => {
|
||||||
|
application.setPreference(PrefKey.UseSystemColorScheme, !useDeviceSettings);
|
||||||
|
if (!application.getPreference(PrefKey.AutoLightThemeIdentifier)) {
|
||||||
|
application.setPreference(
|
||||||
|
PrefKey.AutoLightThemeIdentifier,
|
||||||
|
autoLightTheme as FeatureIdentifier
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!application.getPreference(PrefKey.AutoDarkThemeIdentifier)) {
|
||||||
|
application.setPreference(
|
||||||
|
PrefKey.AutoDarkThemeIdentifier,
|
||||||
|
autoDarkTheme as FeatureIdentifier
|
||||||
|
);
|
||||||
|
}
|
||||||
|
setUseDeviceSettings(!useDeviceSettings);
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeAutoLightTheme = (value: string, item: DropdownItem) => {
|
||||||
|
if (item.icon === 'premium-feature') {
|
||||||
|
premiumModal.activate(`${item.label} theme`);
|
||||||
|
} else {
|
||||||
|
application.setPreference(
|
||||||
|
PrefKey.AutoLightThemeIdentifier,
|
||||||
|
value as FeatureIdentifier
|
||||||
|
);
|
||||||
|
setAutoLightTheme(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeAutoDarkTheme = (value: string, item: DropdownItem) => {
|
||||||
|
if (item.icon === 'premium-feature') {
|
||||||
|
premiumModal.activate(`${item.label} theme`);
|
||||||
|
} else {
|
||||||
|
application.setPreference(
|
||||||
|
PrefKey.AutoDarkThemeIdentifier,
|
||||||
|
value as FeatureIdentifier
|
||||||
|
);
|
||||||
|
setAutoDarkTheme(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PreferencesPane>
|
||||||
|
<PreferencesGroup>
|
||||||
|
<PreferencesSegment>
|
||||||
|
<Title>Themes</Title>
|
||||||
|
<div className="mt-2">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<Subtitle>Use system color scheme</Subtitle>
|
||||||
|
<Text>
|
||||||
|
Automatically change active theme based on your system settings.
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
onChange={toggleUseDeviceSettings}
|
||||||
|
checked={useDeviceSettings}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<HorizontalSeparator classes="mt-5 mb-3" />
|
||||||
|
<div>
|
||||||
|
<Subtitle>Automatic Light Theme</Subtitle>
|
||||||
|
<Text>Theme to be used for system light mode:</Text>
|
||||||
|
<div className="mt-2">
|
||||||
|
<Dropdown
|
||||||
|
id="auto-light-theme-dropdown"
|
||||||
|
label="Select the automatic light theme"
|
||||||
|
items={themeItems}
|
||||||
|
value={autoLightTheme}
|
||||||
|
onChange={changeAutoLightTheme}
|
||||||
|
disabled={!useDeviceSettings}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<HorizontalSeparator classes="mt-5 mb-3" />
|
||||||
|
<div>
|
||||||
|
<Subtitle>Automatic Dark Theme</Subtitle>
|
||||||
|
<Text>Theme to be used for system dark mode:</Text>
|
||||||
|
<div className="mt-2">
|
||||||
|
<Dropdown
|
||||||
|
id="auto-dark-theme-dropdown"
|
||||||
|
label="Select the automatic dark theme"
|
||||||
|
items={themeItems}
|
||||||
|
value={autoDarkTheme}
|
||||||
|
onChange={changeAutoDarkTheme}
|
||||||
|
disabled={!useDeviceSettings}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PreferencesSegment>
|
||||||
|
</PreferencesGroup>
|
||||||
|
</PreferencesPane>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Appearance: FunctionComponent<Props> = observer(
|
||||||
|
({ application }) => (
|
||||||
|
<PremiumModalProvider state={application.getAppState().features}>
|
||||||
|
<AppearancePane application={application} />
|
||||||
|
</PremiumModalProvider>
|
||||||
|
)
|
||||||
|
);
|
||||||
@@ -157,7 +157,7 @@ export const EmailBackups = observer(({ application }: Props) => {
|
|||||||
id="def-editor-dropdown"
|
id="def-editor-dropdown"
|
||||||
label="Select email frequency"
|
label="Select email frequency"
|
||||||
items={emailFrequencyOptions}
|
items={emailFrequencyOptions}
|
||||||
defaultValue={emailFrequency}
|
value={emailFrequency}
|
||||||
onChange={(item) => {
|
onChange={(item) => {
|
||||||
updateEmailFrequency(item as EmailBackupFrequency);
|
updateEmailFrequency(item as EmailBackupFrequency);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ const getDefaultEditor = (application: WebApplication) => {
|
|||||||
|
|
||||||
export const Defaults: FunctionComponent<Props> = ({ application }) => {
|
export const Defaults: FunctionComponent<Props> = ({ application }) => {
|
||||||
const [editorItems, setEditorItems] = useState<DropdownItem[]>([]);
|
const [editorItems, setEditorItems] = useState<DropdownItem[]>([]);
|
||||||
const [defaultEditorValue] = useState(
|
const [defaultEditorValue, setDefaultEditorValue] = useState(
|
||||||
() =>
|
() =>
|
||||||
getDefaultEditor(application)?.package_info?.identifier || 'plain-editor'
|
getDefaultEditor(application)?.package_info?.identifier || 'plain-editor'
|
||||||
);
|
);
|
||||||
@@ -128,6 +128,7 @@ export const Defaults: FunctionComponent<Props> = ({ application }) => {
|
|||||||
}, [application]);
|
}, [application]);
|
||||||
|
|
||||||
const setDefaultEditor = (value: string) => {
|
const setDefaultEditor = (value: string) => {
|
||||||
|
setDefaultEditorValue(value as FeatureIdentifier);
|
||||||
const editors = application.componentManager.componentsForArea(
|
const editors = application.componentManager.componentsForArea(
|
||||||
ComponentArea.Editor
|
ComponentArea.Editor
|
||||||
);
|
);
|
||||||
@@ -155,7 +156,7 @@ export const Defaults: FunctionComponent<Props> = ({ application }) => {
|
|||||||
id="def-editor-dropdown"
|
id="def-editor-dropdown"
|
||||||
label="Select the default editor"
|
label="Select the default editor"
|
||||||
items={editorItems}
|
items={editorItems}
|
||||||
defaultValue={defaultEditorValue}
|
value={defaultEditorValue}
|
||||||
onChange={setDefaultEditor}
|
onChange={setDefaultEditor}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
UuidString,
|
UuidString,
|
||||||
FeatureStatus,
|
FeatureStatus,
|
||||||
PayloadSource,
|
PayloadSource,
|
||||||
|
PrefKey,
|
||||||
} from '@standardnotes/snjs';
|
} from '@standardnotes/snjs';
|
||||||
|
|
||||||
const CACHED_THEMES_KEY = 'cachedThemes';
|
const CACHED_THEMES_KEY = 'cachedThemes';
|
||||||
@@ -19,6 +20,53 @@ export class ThemeManager extends ApplicationService {
|
|||||||
private unregisterDesktop!: () => void;
|
private unregisterDesktop!: () => void;
|
||||||
private unregisterStream!: () => void;
|
private unregisterStream!: () => void;
|
||||||
|
|
||||||
|
constructor(application: WebApplication) {
|
||||||
|
super(application);
|
||||||
|
this.colorSchemeEventHandler = this.colorSchemeEventHandler.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private colorSchemeEventHandler(event: MediaQueryListEvent) {
|
||||||
|
this.setThemeAsPerColorScheme(event.matches);
|
||||||
|
}
|
||||||
|
|
||||||
|
private setThemeAsPerColorScheme(prefersDarkColorScheme: boolean) {
|
||||||
|
const useDeviceThemeSettings = this.application.getPreference(
|
||||||
|
PrefKey.UseSystemColorScheme,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
if (useDeviceThemeSettings) {
|
||||||
|
const preference = prefersDarkColorScheme
|
||||||
|
? PrefKey.AutoDarkThemeIdentifier
|
||||||
|
: PrefKey.AutoLightThemeIdentifier;
|
||||||
|
const themes = this.application.getDisplayableItems(
|
||||||
|
ContentType.Theme
|
||||||
|
) as SNTheme[];
|
||||||
|
|
||||||
|
const enableDefaultTheme = () => {
|
||||||
|
const activeTheme = themes.find(
|
||||||
|
(theme) => theme.active && !theme.isLayerable()
|
||||||
|
);
|
||||||
|
if (activeTheme) this.application.toggleTheme(activeTheme);
|
||||||
|
};
|
||||||
|
|
||||||
|
const themeIdentifier = this.application.getPreference(
|
||||||
|
preference,
|
||||||
|
'Default'
|
||||||
|
) as string;
|
||||||
|
if (themeIdentifier === 'Default') {
|
||||||
|
enableDefaultTheme();
|
||||||
|
} else {
|
||||||
|
const theme = themes.find(
|
||||||
|
(theme) => theme.package_info.identifier === themeIdentifier
|
||||||
|
);
|
||||||
|
if (theme && !theme.active) {
|
||||||
|
this.application.toggleTheme(theme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async onAppEvent(event: ApplicationEvent) {
|
async onAppEvent(event: ApplicationEvent) {
|
||||||
super.onAppEvent(event);
|
super.onAppEvent(event);
|
||||||
if (event === ApplicationEvent.SignedOut) {
|
if (event === ApplicationEvent.SignedOut) {
|
||||||
@@ -32,6 +80,15 @@ export class ThemeManager extends ApplicationService {
|
|||||||
await this.activateCachedThemes();
|
await this.activateCachedThemes();
|
||||||
} else if (event === ApplicationEvent.FeaturesUpdated) {
|
} else if (event === ApplicationEvent.FeaturesUpdated) {
|
||||||
this.reloadThemeStatus();
|
this.reloadThemeStatus();
|
||||||
|
} else if (event === ApplicationEvent.Launched) {
|
||||||
|
window
|
||||||
|
.matchMedia('(prefers-color-scheme: dark)')
|
||||||
|
.addEventListener('change', this.colorSchemeEventHandler);
|
||||||
|
} else if (event === ApplicationEvent.PreferencesChanged) {
|
||||||
|
const prefersDarkColorScheme = window.matchMedia(
|
||||||
|
'(prefers-color-scheme: dark)'
|
||||||
|
);
|
||||||
|
this.setThemeAsPerColorScheme(prefersDarkColorScheme.matches);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,6 +103,9 @@ export class ThemeManager extends ApplicationService {
|
|||||||
this.unregisterStream();
|
this.unregisterStream();
|
||||||
(this.unregisterDesktop as unknown) = undefined;
|
(this.unregisterDesktop as unknown) = undefined;
|
||||||
(this.unregisterStream as unknown) = undefined;
|
(this.unregisterStream as unknown) = undefined;
|
||||||
|
window
|
||||||
|
.matchMedia('(prefers-color-scheme: dark)')
|
||||||
|
.removeEventListener('change', this.colorSchemeEventHandler);
|
||||||
super.deinit();
|
super.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
ComponentViewer,
|
ComponentViewer,
|
||||||
SNTag,
|
SNTag,
|
||||||
NoteViewController,
|
NoteViewController,
|
||||||
|
SNTheme,
|
||||||
} from '@standardnotes/snjs';
|
} from '@standardnotes/snjs';
|
||||||
import pull from 'lodash/pull';
|
import pull from 'lodash/pull';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -87,7 +87,7 @@
|
|||||||
"@reach/tooltip": "^0.16.2",
|
"@reach/tooltip": "^0.16.2",
|
||||||
"@standardnotes/components": "1.4.0",
|
"@standardnotes/components": "1.4.0",
|
||||||
"@standardnotes/features": "1.24.3",
|
"@standardnotes/features": "1.24.3",
|
||||||
"@standardnotes/snjs": "2.40.0",
|
"@standardnotes/snjs": "2.40.3",
|
||||||
"@standardnotes/settings": "^1.9.0",
|
"@standardnotes/settings": "^1.9.0",
|
||||||
"@standardnotes/sncrypto-web": "1.6.0",
|
"@standardnotes/sncrypto-web": "1.6.0",
|
||||||
"mobx": "^6.3.5",
|
"mobx": "^6.3.5",
|
||||||
|
|||||||
25
yarn.lock
25
yarn.lock
@@ -2615,10 +2615,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@standardnotes/components/-/components-1.4.0.tgz#0bb790965683b3fa56e3231b95cad9871e1db271"
|
resolved "https://registry.yarnpkg.com/@standardnotes/components/-/components-1.4.0.tgz#0bb790965683b3fa56e3231b95cad9871e1db271"
|
||||||
integrity sha512-8Zo2WV7Q+pWdmAf+rG3NCNtVM4N1P52T1sDijapz8xqtArT28wxWZkJ+qfBJ0lT5GmXxYZl8rY/tAkx4hQ5zSA==
|
integrity sha512-8Zo2WV7Q+pWdmAf+rG3NCNtVM4N1P52T1sDijapz8xqtArT28wxWZkJ+qfBJ0lT5GmXxYZl8rY/tAkx4hQ5zSA==
|
||||||
|
|
||||||
"@standardnotes/domain-events@^2.16.8":
|
"@standardnotes/domain-events@^2.17.0":
|
||||||
version "2.16.8"
|
version "2.17.0"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/domain-events/-/domain-events-2.16.8.tgz#74e6a9879c9b4476d92a16253ae9d75cab4b8162"
|
resolved "https://registry.yarnpkg.com/@standardnotes/domain-events/-/domain-events-2.17.0.tgz#e44f7927ccae9440d5ecc3d947ad0c47f4255cab"
|
||||||
integrity sha512-VHXEtXSNb01ensASq0d1iB4yMUdgBVlfeHp2LQNwK8fwtvdQOyPCfnOFdMaUjW+Y/nBFRz6swDtrLFLeknzlcw==
|
integrity sha512-N7KCp4QplDdDVu8icnKQEYH2T0dHJJOMyanw4aSsQdK3FIqVnZE/oP3+kcNoPRwbX0m+HSiGkZYGRKQgOvNdiQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/auth" "^3.15.2"
|
"@standardnotes/auth" "^3.15.2"
|
||||||
|
|
||||||
@@ -2630,6 +2630,11 @@
|
|||||||
"@standardnotes/auth" "^3.15.2"
|
"@standardnotes/auth" "^3.15.2"
|
||||||
"@standardnotes/common" "^1.8.0"
|
"@standardnotes/common" "^1.8.0"
|
||||||
|
|
||||||
|
"@standardnotes/settings@^1.10.0":
|
||||||
|
version "1.10.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@standardnotes/settings/-/settings-1.10.0.tgz#b46d4805c9a1cfb3fa3d9b3915daae9dd5661789"
|
||||||
|
integrity sha512-FUy4RDI7nFnbOGAaX5wMvBz+6Yto5tXGXDAi7bnWALZByTIlN8bkFZ2CNk2skjpzeOxBjqhCRi+GRCcuMqZ70A==
|
||||||
|
|
||||||
"@standardnotes/settings@^1.9.0":
|
"@standardnotes/settings@^1.9.0":
|
||||||
version "1.9.0"
|
version "1.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/settings/-/settings-1.9.0.tgz#0f01da5f6782363e4d77ee584b40f8614c555626"
|
resolved "https://registry.yarnpkg.com/@standardnotes/settings/-/settings-1.9.0.tgz#0f01da5f6782363e4d77ee584b40f8614c555626"
|
||||||
@@ -2649,16 +2654,16 @@
|
|||||||
buffer "^6.0.3"
|
buffer "^6.0.3"
|
||||||
libsodium-wrappers "^0.7.9"
|
libsodium-wrappers "^0.7.9"
|
||||||
|
|
||||||
"@standardnotes/snjs@2.40.0":
|
"@standardnotes/snjs@2.40.3":
|
||||||
version "2.40.0"
|
version "2.40.3"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.40.0.tgz#8b4e96bd11bdf4ea7f5c9d1cff869ad1a309245a"
|
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.40.3.tgz#976dc1cce5f1633432331247bd5251bdde8d916d"
|
||||||
integrity sha512-UBlMFp8Dj88snSKmi9vUVn97/EM5aidIWhZXQcFa1/OZHg7HVUbIPrLSVy8ftY6WNCPDXjNSE0cy2fozN9W2rQ==
|
integrity sha512-esIAk2ymDTcABwnBoDA2n0tgZ5sOM0P/Wc4gCasjiR+9aVKoOhmY0lNF9u5E7ix+deuKs9DgE0fxKonSoYrHxA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/auth" "^3.15.2"
|
"@standardnotes/auth" "^3.15.2"
|
||||||
"@standardnotes/common" "^1.8.0"
|
"@standardnotes/common" "^1.8.0"
|
||||||
"@standardnotes/domain-events" "^2.16.8"
|
"@standardnotes/domain-events" "^2.17.0"
|
||||||
"@standardnotes/features" "^1.24.3"
|
"@standardnotes/features" "^1.24.3"
|
||||||
"@standardnotes/settings" "^1.9.0"
|
"@standardnotes/settings" "^1.10.0"
|
||||||
"@standardnotes/sncrypto-common" "^1.6.0"
|
"@standardnotes/sncrypto-common" "^1.6.0"
|
||||||
|
|
||||||
"@svgr/babel-plugin-add-jsx-attribute@^5.4.0":
|
"@svgr/babel-plugin-add-jsx-attribute@^5.4.0":
|
||||||
|
|||||||
Reference in New Issue
Block a user