pl-fe: allow user to change ui color

Signed-off-by: mkljczk <git@mkljczk.pl>
This commit is contained in:
mkljczk 2024-12-06 11:12:02 +01:00
parent a535b80546
commit 156a0b0826
6 changed files with 41 additions and 4 deletions

View file

@ -17,13 +17,16 @@ const FE_NAME = 'pl_fe';
type SettingOpts = { type SettingOpts = {
/** Whether to display an alert when settings are saved. */ /** Whether to display an alert when settings are saved. */
showAlert?: boolean; showAlert?: boolean;
save?: boolean;
} }
const saveSuccessMessage = defineMessage({ id: 'settings.save.success', defaultMessage: 'Your preferences have been saved!' }); const saveSuccessMessage = defineMessage({ id: 'settings.save.success', defaultMessage: 'Your preferences have been saved!' });
const changeSetting = (path: string[], value: any, opts?: SettingOpts) => { const changeSetting = (path: string[], value: any, opts?: SettingOpts) => {
useSettingsStore.getState().changeSetting(path, value); useSettingsStore.getState().changeSetting(path, value);
return saveSettings(opts);
if (opts?.save !== false) return saveSettings(opts);
return () => {};
}; };
const saveSettings = (opts?: SettingOpts) => const saveSettings = (opts?: SettingOpts) =>

View file

@ -1,7 +1,8 @@
import debounce from 'lodash/debounce';
import React from 'react'; import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { changeSetting } from 'pl-fe/actions/settings'; import { changeSetting, saveSettings } from 'pl-fe/actions/settings';
import List, { ListItem } from 'pl-fe/components/list'; import List, { ListItem } from 'pl-fe/components/list';
import Form from 'pl-fe/components/ui/form'; import Form from 'pl-fe/components/ui/form';
import { Mutliselect, SelectDropdown } from 'pl-fe/features/forms'; import { Mutliselect, SelectDropdown } from 'pl-fe/features/forms';
@ -9,10 +10,15 @@ import SettingToggle from 'pl-fe/features/notifications/components/setting-toggl
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch'; import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
import { useFeatures } from 'pl-fe/hooks/use-features'; import { useFeatures } from 'pl-fe/hooks/use-features';
import { useInstance } from 'pl-fe/hooks/use-instance'; import { useInstance } from 'pl-fe/hooks/use-instance';
import { usePlFeConfig } from 'pl-fe/hooks/use-pl-fe-config';
import { useSettings } from 'pl-fe/hooks/use-settings'; import { useSettings } from 'pl-fe/hooks/use-settings';
import colors from 'pl-fe/utils/colors';
import { PaletteListItem } from '../theme-editor';
import ThemeToggle from '../ui/components/theme-toggle'; import ThemeToggle from '../ui/components/theme-toggle';
import type { AppDispatch } from 'pl-fe/store';
const languages = { const languages = {
en: 'English', en: 'English',
ar: 'العربية', ar: 'العربية',
@ -91,13 +97,19 @@ const messages = defineMessages({
content_type_plaintext: { id: 'preferences.options.content_type_plaintext', defaultMessage: 'Plain text' }, content_type_plaintext: { id: 'preferences.options.content_type_plaintext', defaultMessage: 'Plain text' },
content_type_markdown: { id: 'preferences.options.content_type_markdown', defaultMessage: 'Markdown' }, content_type_markdown: { id: 'preferences.options.content_type_markdown', defaultMessage: 'Markdown' },
content_type_html: { id: 'preferences.options.content_type_html', defaultMessage: 'HTML' }, content_type_html: { id: 'preferences.options.content_type_html', defaultMessage: 'HTML' },
brandColor: { id: 'preferences.options.brand_color', defaultMessage: 'Base color' },
}); });
const debouncedSave = debounce((dispatch: AppDispatch) => {
dispatch(saveSettings({ showAlert: true }));
}, 1000);
const Preferences = () => { const Preferences = () => {
const intl = useIntl(); const intl = useIntl();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const features = useFeatures(); const features = useFeatures();
const settings = useSettings(); const settings = useSettings();
const plFeConfig = usePlFeConfig();
const instance = useInstance(); const instance = useInstance();
const onSelectChange = (event: React.ChangeEvent<HTMLSelectElement>, path: string[]) => { const onSelectChange = (event: React.ChangeEvent<HTMLSelectElement>, path: string[]) => {
@ -109,7 +121,14 @@ const Preferences = () => {
}; };
const onToggleChange = (key: string[], checked: boolean) => { const onToggleChange = (key: string[], checked: boolean) => {
dispatch(changeSetting(key, checked, { showAlert: true })); dispatch(changeSetting(key, checked));
};
const onBrandColorChange = (brandColor: string) => {
if (!settings.theme?.brandColor && brandColor === (plFeConfig.brandColor || '#d80482')) return;
dispatch(changeSetting(['theme', 'brandColor'], brandColor, { showAlert: true, save: false }));
debouncedSave(dispatch);
}; };
const displayMediaOptions = React.useMemo(() => ({ const displayMediaOptions = React.useMemo(() => ({
@ -152,7 +171,14 @@ const Preferences = () => {
<ListItem label={<FormattedMessage id='preferences.fields.theme' defaultMessage='Theme' />}> <ListItem label={<FormattedMessage id='preferences.fields.theme' defaultMessage='Theme' />}>
<ThemeToggle /> <ThemeToggle />
</ListItem> </ListItem>
<PaletteListItem
label={intl.formatMessage(messages.brandColor)}
palette={colors(settings.theme?.brandColor || plFeConfig.brandColor || '#d80482')}
onChange={(palette) => onBrandColorChange(palette['500'])}
/>
</List>
<List>
<ListItem label={<FormattedMessage id='preferences.fields.language_label' defaultMessage='Display language' />}> <ListItem label={<FormattedMessage id='preferences.fields.language_label' defaultMessage='Display language' />}>
<SelectDropdown <SelectDropdown
className='max-w-[200px]' className='max-w-[200px]'

View file

@ -283,4 +283,4 @@ const ColorListItem: React.FC<IColorListItem> = ({ label, value, onChange }) =>
); );
}; };
export { ThemeEditor as default }; export { ThemeEditor as default, PaletteListItem };

View file

@ -1266,6 +1266,7 @@
"preferences.fields.wrench_label": "Display wrench reaction button", "preferences.fields.wrench_label": "Display wrench reaction button",
"preferences.hints.demetricator": "Decrease social media anxiety by hiding all numbers from the site.", "preferences.hints.demetricator": "Decrease social media anxiety by hiding all numbers from the site.",
"preferences.notifications.advanced": "Show all notification categories", "preferences.notifications.advanced": "Show all notification categories",
"preferences.options.brand_color": "Base color",
"preferences.options.content_type_html": "HTML", "preferences.options.content_type_html": "HTML",
"preferences.options.content_type_markdown": "Markdown", "preferences.options.content_type_markdown": "Markdown",
"preferences.options.content_type_mfm": "MFM", "preferences.options.content_type_mfm": "MFM",

View file

@ -38,6 +38,12 @@ const settingsSchema = v.object({
knownLanguages: v.fallback(v.array(v.string()), []), knownLanguages: v.fallback(v.array(v.string()), []),
showWrenchButton: v.fallback(v.boolean(), false), showWrenchButton: v.fallback(v.boolean(), false),
theme: v.fallback(v.optional(v.object({
brandColor: v.fallback(v.string(), ''),
accentColor: v.fallback(v.string(), ''),
colors: v.any(),
})), undefined),
systemFont: v.fallback(v.boolean(), false), systemFont: v.fallback(v.boolean(), false),
demetricator: v.fallback(v.boolean(), false), demetricator: v.fallback(v.boolean(), false),

View file

@ -62,6 +62,7 @@ const useSettingsStore = create<State>()(mutative((set) => ({
}), }),
changeSetting: (path: string[], value: any) => set((state: State) => { changeSetting: (path: string[], value: any) => set((state: State) => {
state.userSettings.saved = false;
changeSetting(state.userSettings, path, value); changeSetting(state.userSettings, path, value);
mergeSettings(state); mergeSettings(state);