From bc675bbf499992b32609a19197150b558d5d4f72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicole=20Miko=C5=82ajczyk?= Date: Thu, 22 May 2025 17:55:57 +0200 Subject: [PATCH] pl-fe: Allow adjusting interface size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nicole Mikołajczyk --- packages/pl-fe/src/components/ui/avatar.tsx | 15 ++-- .../pl-fe/src/components/ui/step-slider.tsx | 81 +++++++++++++++++++ .../pl-fe/src/features/preferences/index.tsx | 13 +++ packages/pl-fe/src/init/pl-fe-head.tsx | 10 ++- packages/pl-fe/src/locales/en.json | 1 + .../src/pages/dashboard/theme-editor.tsx | 2 +- packages/pl-fe/src/schemas/pl-fe/settings.ts | 1 + 7 files changed, 114 insertions(+), 9 deletions(-) create mode 100644 packages/pl-fe/src/components/ui/step-slider.tsx diff --git a/packages/pl-fe/src/components/ui/avatar.tsx b/packages/pl-fe/src/components/ui/avatar.tsx index 21b164ef4..45f48f0a0 100644 --- a/packages/pl-fe/src/components/ui/avatar.tsx +++ b/packages/pl-fe/src/components/ui/avatar.tsx @@ -59,12 +59,15 @@ const Avatar = (props: IAvatar) => { }).catch(() => setColor(undefined)); }, [src, isCat]); - const style: React.CSSProperties = React.useMemo(() => ({ - width: size, - height: size, - fontSize: size, - color, - }), [size, color]); + const style: React.CSSProperties = React.useMemo(() => { + const value = `${size / 16}rem`; + return { + width: value, + height: value, + fontSize: value, + color, + }; + }, [size, color]); if (disableUserProvidedMedia) { if (isAvatarMissing || !alt || isDefaultAvatar(src)) return null; diff --git a/packages/pl-fe/src/components/ui/step-slider.tsx b/packages/pl-fe/src/components/ui/step-slider.tsx new file mode 100644 index 000000000..3d57a651a --- /dev/null +++ b/packages/pl-fe/src/components/ui/step-slider.tsx @@ -0,0 +1,81 @@ +import throttle from 'lodash/throttle'; +import React, { useRef } from 'react'; + +import { getPointerPosition } from 'pl-fe/features/video'; + +interface IStepSlider { + /** Value between 0 and the amount of steps minus one. */ + value: number; + /** Steps available in the slider. */ + steps: number; + /** Callback when the value changes. */ + onChange(value: number): void; +} + +/** Slider allowing selecting integers in a given range. */ +const StepSlider: React.FC = ({ value, steps, onChange }) => { + const node = useRef(null); + + const handleMouseDown: React.MouseEventHandler = e => { + document.addEventListener('mousemove', handleMouseSlide, true); + document.addEventListener('mouseup', handleMouseUp, true); + document.addEventListener('touchmove', handleMouseSlide, true); + document.addEventListener('touchend', handleMouseUp, true); + + handleMouseSlide(e); + + e.preventDefault(); + e.stopPropagation(); + }; + + const handleMouseUp = () => { + document.removeEventListener('mousemove', handleMouseSlide, true); + document.removeEventListener('mouseup', handleMouseUp, true); + document.removeEventListener('touchmove', handleMouseSlide, true); + document.removeEventListener('touchend', handleMouseUp, true); + }; + + const handleMouseSlide = throttle(e => { + if (node.current) { + const { x } = getPointerPosition(node.current, e); + + if (!isNaN(x)) { + let slideamt = x; + + if (x > 1) { + slideamt = 1; + } else if (x < 0) { + slideamt = 0; + } + + slideamt = Math.floor((slideamt + 0.5 / steps) * (steps - 1)); + onChange(slideamt); + } + } + }, 60); + + return ( +
+
+
+ {[...Array(steps).fill(undefined)].map((_, step) => ( + + ))} + +
+ ); +}; + +export { StepSlider as default }; diff --git a/packages/pl-fe/src/features/preferences/index.tsx b/packages/pl-fe/src/features/preferences/index.tsx index 9dbccda8e..763f0900e 100644 --- a/packages/pl-fe/src/features/preferences/index.tsx +++ b/packages/pl-fe/src/features/preferences/index.tsx @@ -7,6 +7,7 @@ import List, { ListItem } from 'pl-fe/components/list'; import Button from 'pl-fe/components/ui/button'; import Form from 'pl-fe/components/ui/form'; import HStack from 'pl-fe/components/ui/hstack'; +import StepSlider from 'pl-fe/components/ui/step-slider'; import { Mutliselect, SelectDropdown } from 'pl-fe/features/forms'; import SettingToggle from 'pl-fe/features/settings/components/setting-toggle'; import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch'; @@ -90,6 +91,8 @@ const languages = { type Language = keyof typeof languages; +const INTERFACE_SIZES = ['sm', 'md', 'lg', 'xl'] as const; + const messages = defineMessages({ heading: { id: 'column.preferences', defaultMessage: 'Preferences' }, displayPostsDefault: { id: 'preferences.fields.display_media.default', defaultMessage: 'Hide posts marked as sensitive' }, @@ -139,6 +142,11 @@ const Preferences = () => { debouncedSave(dispatch); }; + const onInterfaceSizeChange = (value: number) => { + dispatch(changeSetting(['theme', 'interfaceSize'], INTERFACE_SIZES[value], { showAlert: true, save: false })); + debouncedSave(dispatch); + }; + const onThemeReset = () => { dispatch(changeSetting(['themeMode'], plFeConfig.defaultSettings.themeMode, { save: false })); dispatch(changeSetting(['theme', 'brandColor'], undefined, { showAlert: true })); @@ -192,6 +200,11 @@ const Preferences = () => { onChange={(palette) => onBrandColorChange(palette['500'])} allowTintChange={false} /> +
}> +
+ +
+ diff --git a/packages/pl-fe/src/init/pl-fe-head.tsx b/packages/pl-fe/src/init/pl-fe-head.tsx index d0676f930..1c710e003 100644 --- a/packages/pl-fe/src/init/pl-fe-head.tsx +++ b/packages/pl-fe/src/init/pl-fe-head.tsx @@ -15,7 +15,7 @@ const Helmet = React.lazy(() => import('pl-fe/components/helmet')); const PlFeHead = () => { const locale = useLocale(); const direction = useLocaleDirection(locale); - const { reduceMotion, underlineLinks, demetricator, systemFont } = useSettings(); + const { reduceMotion, underlineLinks, demetricator, systemFont, theme: themeSettings } = useSettings(); const plFeConfig = usePlFeConfig(); const theme = useTheme(); @@ -40,7 +40,13 @@ const PlFeHead = () => { return ( - + {themeCss && } {['dark', 'black'].includes(theme) && } diff --git a/packages/pl-fe/src/locales/en.json b/packages/pl-fe/src/locales/en.json index a4ecad543..02590534e 100644 --- a/packages/pl-fe/src/locales/en.json +++ b/packages/pl-fe/src/locales/en.json @@ -1345,6 +1345,7 @@ "preferences.fields.display_media.hide_all": "Always hide media posts", "preferences.fields.display_media.show_all": "Always show posts", "preferences.fields.implicit_addressing_label": "Include mentions in post content when replying", + "preferences.fields.interface_size": "Interface size", "preferences.fields.known_languages_label": "Languages you know", "preferences.fields.language_label": "Display language", "preferences.fields.media_display_label": "Sensitive content", diff --git a/packages/pl-fe/src/pages/dashboard/theme-editor.tsx b/packages/pl-fe/src/pages/dashboard/theme-editor.tsx index 3baee2064..08dbf10c4 100644 --- a/packages/pl-fe/src/pages/dashboard/theme-editor.tsx +++ b/packages/pl-fe/src/pages/dashboard/theme-editor.tsx @@ -254,7 +254,7 @@ interface IPaletteListItem { /** Palette editor inside a ListItem. */ const PaletteListItem: React.FC = ({ label, palette, onChange, resetKey, allowTintChange }) => typeof palette === 'string' ? null : ( - {label}
}> + {label}}> ); diff --git a/packages/pl-fe/src/schemas/pl-fe/settings.ts b/packages/pl-fe/src/schemas/pl-fe/settings.ts index 7b98817f1..ad78489c0 100644 --- a/packages/pl-fe/src/schemas/pl-fe/settings.ts +++ b/packages/pl-fe/src/schemas/pl-fe/settings.ts @@ -55,6 +55,7 @@ const settingsSchema = v.object({ brandColor: v.fallback(v.string(), ''), accentColor: v.fallback(v.string(), ''), colors: v.any(), + interfaceSize: v.fallback(v.picklist(['sm', 'md', 'lg', 'xl']), 'md'), })), undefined), systemFont: v.fallback(v.boolean(), false),