Merge branch 'refactor-use-settings' into 'main'
Refactor useSettings hook, parse with zod schema See merge request soapbox-pub/soapbox!2940
This commit is contained in:
commit
0f6a4a0744
40 changed files with 110 additions and 92 deletions
|
@ -20,7 +20,7 @@ interface IAnimatedNumber {
|
|||
}
|
||||
|
||||
const AnimatedNumber: React.FC<IAnimatedNumber> = ({ value, obfuscate }) => {
|
||||
const reduceMotion = useSettings().get('reduceMotion');
|
||||
const { reduceMotion } = useSettings();
|
||||
|
||||
const [direction, setDirection] = useState(1);
|
||||
const [displayedValue, setDisplayedValue] = useState<number>(value);
|
||||
|
|
|
@ -13,7 +13,7 @@ interface IEmoji {
|
|||
}
|
||||
|
||||
const Emoji: React.FC<IEmoji> = ({ emoji, emojiMap, hovered }) => {
|
||||
const autoPlayGif = useSettings().get('autoPlayGif');
|
||||
const { autoPlayGif } = useSettings();
|
||||
|
||||
// @ts-ignore
|
||||
if (unicodeMapping[emoji]) {
|
||||
|
|
|
@ -20,7 +20,7 @@ interface IReactionsBar {
|
|||
}
|
||||
|
||||
const ReactionsBar: React.FC<IReactionsBar> = ({ announcementId, reactions, addReaction, removeReaction, emojiMap }) => {
|
||||
const reduceMotion = useSettings().get('reduceMotion');
|
||||
const { reduceMotion } = useSettings();
|
||||
|
||||
const handleEmojiPick = (data: Emoji) => {
|
||||
addReaction(announcementId, (data as NativeEmoji).native.replace(/:/g, ''));
|
||||
|
|
|
@ -23,7 +23,7 @@ const Helmet: React.FC<IHelmet> = ({ children }) => {
|
|||
const instance = useInstance();
|
||||
const { unreadChatsCount } = useStatContext();
|
||||
const unreadCount = useAppSelector((state) => getNotifTotals(state) + unreadChatsCount);
|
||||
const demetricator = useSettings().get('demetricator');
|
||||
const { demetricator } = useSettings();
|
||||
|
||||
const hasUnreadNotifications = React.useMemo(() => !(unreadCount < 1 || demetricator), [unreadCount, demetricator]);
|
||||
|
||||
|
|
|
@ -72,8 +72,7 @@ const Item: React.FC<IItem> = ({
|
|||
last,
|
||||
total,
|
||||
}) => {
|
||||
const settings = useSettings();
|
||||
const autoPlayGif = settings.get('autoPlayGif') === true;
|
||||
const { autoPlayGif } = useSettings();
|
||||
const { mediaPreview } = useSoapboxConfig();
|
||||
|
||||
const handleMouseEnter: React.MouseEventHandler<HTMLVideoElement> = ({ currentTarget: video }) => {
|
||||
|
|
|
@ -9,9 +9,8 @@ interface INavlinks {
|
|||
}
|
||||
|
||||
const Navlinks: React.FC<INavlinks> = ({ type }) => {
|
||||
const settings = useSettings();
|
||||
const { locale } = useSettings();
|
||||
const { copyright, navlinks } = useSoapboxConfig();
|
||||
const locale = settings.get('locale') as string;
|
||||
|
||||
return (
|
||||
<footer className='relative mx-auto mt-auto max-w-7xl py-8'>
|
||||
|
|
|
@ -35,8 +35,7 @@ const QuotedStatus: React.FC<IQuotedStatus> = ({ status, onCancel, compose }) =>
|
|||
const intl = useIntl();
|
||||
const history = useHistory();
|
||||
|
||||
const settings = useSettings();
|
||||
const displayMedia = settings.get('displayMedia');
|
||||
const { displayMedia } = useSettings();
|
||||
|
||||
const overlay = useRef<HTMLDivElement>(null);
|
||||
|
||||
|
|
|
@ -27,14 +27,13 @@ const ScrollTopButton: React.FC<IScrollTopButton> = ({
|
|||
autoloadThreshold = 50,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const settings = useSettings();
|
||||
const { autoloadTimelines } = useSettings();
|
||||
|
||||
// Whether we are scrolled past the `threshold`.
|
||||
const [scrolled, setScrolled] = useState<boolean>(false);
|
||||
// Whether we are scrolled above the `autoloadThreshold`.
|
||||
const [scrolledTop, setScrolledTop] = useState<boolean>(false);
|
||||
|
||||
const autoload = settings.get('autoloadTimelines') === true;
|
||||
const visible = count > 0 && scrolled;
|
||||
|
||||
/** Number of pixels scrolled down from the top of the page. */
|
||||
|
@ -44,10 +43,10 @@ const ScrollTopButton: React.FC<IScrollTopButton> = ({
|
|||
|
||||
/** Unload feed items if scrolled to the top. */
|
||||
const maybeUnload = useCallback(() => {
|
||||
if (autoload && scrolledTop && count) {
|
||||
if (autoloadTimelines && scrolledTop && count) {
|
||||
onClick();
|
||||
}
|
||||
}, [autoload, scrolledTop, count, onClick]);
|
||||
}, [autoloadTimelines, scrolledTop, count, onClick]);
|
||||
|
||||
/** Set state while scrolling. */
|
||||
const handleScroll = useCallback(throttle(() => {
|
||||
|
|
|
@ -106,8 +106,7 @@ const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
|
|||
useWindowScroll = true,
|
||||
}, ref) => {
|
||||
const history = useHistory();
|
||||
const settings = useSettings();
|
||||
const autoloadMore = settings.get('autoloadMore');
|
||||
const { autoloadMore } = useSettings();
|
||||
|
||||
// Preserve scroll position
|
||||
const scrollDataKey = `soapbox:scrollData:${scrollKey}`;
|
||||
|
|
|
@ -24,7 +24,7 @@ const SidebarNavigation = () => {
|
|||
|
||||
const instance = useInstance();
|
||||
const features = useFeatures();
|
||||
const settings = useSettings();
|
||||
const { isDeveloper } = useSettings();
|
||||
const { account } = useOwnAccount();
|
||||
const groupsPath = useGroupsPath();
|
||||
|
||||
|
@ -71,7 +71,7 @@ const SidebarNavigation = () => {
|
|||
});
|
||||
}
|
||||
|
||||
if (settings.get('isDeveloper')) {
|
||||
if (isDeveloper) {
|
||||
menu.push({
|
||||
to: '/developers',
|
||||
icon: require('@tabler/icons/code.svg'),
|
||||
|
|
|
@ -13,7 +13,7 @@ interface ISiteLogo extends React.ComponentProps<'img'> {
|
|||
/** Display the most appropriate site logo based on the theme and configuration. */
|
||||
const SiteLogo: React.FC<ISiteLogo> = ({ className, theme, ...rest }) => {
|
||||
const { logo, logoDarkMode } = useSoapboxConfig();
|
||||
const settings = useSettings();
|
||||
const { demo } = useSettings();
|
||||
|
||||
let darkMode = useTheme() === 'dark';
|
||||
if (theme === 'dark') darkMode = true;
|
||||
|
@ -26,7 +26,7 @@ const SiteLogo: React.FC<ISiteLogo> = ({ className, theme, ...rest }) => {
|
|||
// Use the right logo if provided, then use fallbacks.
|
||||
const getSrc = () => {
|
||||
// In demo mode, use the Soapbox logo.
|
||||
if (settings.get('demo')) return soapboxLogo;
|
||||
if (demo) return soapboxLogo;
|
||||
|
||||
return (darkMode && logoDarkMode)
|
||||
? logoDarkMode
|
||||
|
|
|
@ -136,7 +136,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
|
|||
const me = useAppSelector(state => state.me);
|
||||
const { groupRelationship } = useGroupRelationship(status.group?.id);
|
||||
const features = useFeatures();
|
||||
const settings = useSettings();
|
||||
const { boostModal, deleteModal } = useSettings();
|
||||
const soapboxConfig = useSoapboxConfig();
|
||||
|
||||
const { allowedEmoji } = soapboxConfig;
|
||||
|
@ -208,7 +208,6 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
|
|||
const handleReblogClick: React.EventHandler<React.MouseEvent> = e => {
|
||||
if (me) {
|
||||
const modalReblog = () => dispatch(toggleReblog(status));
|
||||
const boostModal = settings.get('boostModal');
|
||||
if ((e && e.shiftKey) || !boostModal) {
|
||||
modalReblog();
|
||||
} else {
|
||||
|
@ -229,7 +228,6 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
|
|||
|
||||
const doDeleteStatus = (withRedraft = false) => {
|
||||
dispatch((_, getState) => {
|
||||
const deleteModal = settings.get('deleteModal');
|
||||
if (!deleteModal) {
|
||||
dispatch(deleteStatus(status.id, withRedraft));
|
||||
} else {
|
||||
|
|
|
@ -75,8 +75,7 @@ const Status: React.FC<IStatus> = (props) => {
|
|||
const history = useHistory();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const settings = useSettings();
|
||||
const displayMedia = settings.get('displayMedia') as string;
|
||||
const { displayMedia, boostModal } = useSettings();
|
||||
const didShowCard = useRef(false);
|
||||
const node = useRef<HTMLDivElement>(null);
|
||||
const overlay = useRef<HTMLDivElement>(null);
|
||||
|
@ -155,7 +154,6 @@ const Status: React.FC<IStatus> = (props) => {
|
|||
|
||||
const handleHotkeyBoost = (e?: KeyboardEvent): void => {
|
||||
const modalReblog = () => dispatch(toggleReblog(actualStatus));
|
||||
const boostModal = settings.get('boostModal');
|
||||
if ((e && e.shiftKey) || !boostModal) {
|
||||
modalReblog();
|
||||
} else {
|
||||
|
|
|
@ -38,12 +38,11 @@ const SensitiveContentOverlay = React.forwardRef<HTMLDivElement, ISensitiveConte
|
|||
const { account } = useOwnAccount();
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
const settings = useSettings();
|
||||
const { displayMedia, deleteModal } = useSettings();
|
||||
const { links } = useSoapboxConfig();
|
||||
|
||||
const isUnderReview = status.visibility === 'self';
|
||||
const isOwnStatus = status.getIn(['account', 'id']) === account?.id;
|
||||
const displayMedia = settings.get('displayMedia') as string;
|
||||
|
||||
const [visible, setVisible] = useState<boolean>(defaultMediaVisibility(status, displayMedia));
|
||||
|
||||
|
@ -58,7 +57,6 @@ const SensitiveContentOverlay = React.forwardRef<HTMLDivElement, ISensitiveConte
|
|||
};
|
||||
|
||||
const handleDeleteStatus = () => {
|
||||
const deleteModal = settings.get('deleteModal');
|
||||
if (!deleteModal) {
|
||||
dispatch(deleteStatus(status.id, false));
|
||||
} else {
|
||||
|
|
|
@ -22,8 +22,7 @@ export interface IStillImage {
|
|||
|
||||
/** Renders images on a canvas, only playing GIFs if autoPlayGif is enabled. */
|
||||
const StillImage: React.FC<IStillImage> = ({ alt, className, src, style, letterboxed = false, showExt = false, onError }) => {
|
||||
const settings = useSettings();
|
||||
const autoPlayGif = settings.get('autoPlayGif');
|
||||
const { autoPlayGif } = useSettings();
|
||||
|
||||
const canvas = useRef<HTMLCanvasElement>(null);
|
||||
const img = useRef<HTMLImageElement>(null);
|
||||
|
|
|
@ -5,8 +5,6 @@ import { toggleMainWindow } from 'soapbox/actions/chats';
|
|||
import { useAppDispatch, useOwnAccount, useSettings } from 'soapbox/hooks';
|
||||
import { IChat, useChat } from 'soapbox/queries/chats';
|
||||
|
||||
type WindowState = 'open' | 'minimized';
|
||||
|
||||
const ChatContext = createContext<any>({
|
||||
isOpen: false,
|
||||
needsAcceptance: false,
|
||||
|
@ -26,7 +24,7 @@ interface IChatProvider {
|
|||
const ChatProvider: React.FC<IChatProvider> = ({ children }) => {
|
||||
const history = useHistory();
|
||||
const dispatch = useAppDispatch();
|
||||
const settings = useSettings();
|
||||
const { chats } = useSettings();
|
||||
const { account } = useOwnAccount();
|
||||
|
||||
const path = history.location.pathname;
|
||||
|
@ -38,9 +36,8 @@ const ChatProvider: React.FC<IChatProvider> = ({ children }) => {
|
|||
|
||||
const { data: chat } = useChat(currentChatId as string);
|
||||
|
||||
const mainWindowState = settings.getIn(['chats', 'mainWindow']) as WindowState;
|
||||
const needsAcceptance = !chat?.accepted && chat?.created_by_account !== account?.id;
|
||||
const isOpen = mainWindowState === 'open';
|
||||
const isOpen = chats.mainWindow === 'open';
|
||||
|
||||
const changeScreen = (screen: ChatWidgetScreens, currentChatId?: string | null) => {
|
||||
setCurrentChatId(currentChatId || null);
|
||||
|
|
|
@ -18,7 +18,7 @@ const AboutPage: React.FC = () => {
|
|||
const soapboxConfig = useSoapboxConfig();
|
||||
|
||||
const [pageHtml, setPageHtml] = useState<string>('');
|
||||
const [locale, setLocale] = useState<string>(settings.get('locale'));
|
||||
const [locale, setLocale] = useState<string>(settings.locale);
|
||||
|
||||
const { aboutPages } = soapboxConfig;
|
||||
|
||||
|
|
|
@ -15,10 +15,7 @@ interface IMediaItem {
|
|||
}
|
||||
|
||||
const MediaItem: React.FC<IMediaItem> = ({ attachment, onOpenMedia }) => {
|
||||
const settings = useSettings();
|
||||
const autoPlayGif = settings.get('autoPlayGif');
|
||||
const displayMedia = settings.get('displayMedia');
|
||||
|
||||
const { autoPlayGif, displayMedia } = useSettings();
|
||||
const [visible, setVisible] = useState<boolean>(displayMedia !== 'hide_all' && !attachment.status?.sensitive || displayMedia === 'show_all');
|
||||
|
||||
const handleMouseEnter: React.MouseEventHandler<HTMLVideoElement> = e => {
|
||||
|
|
|
@ -32,7 +32,7 @@ const AccountTimeline: React.FC<IAccountTimeline> = ({ params, withReplies = fal
|
|||
const [accountLoading, setAccountLoading] = useState<boolean>(!account);
|
||||
|
||||
const path = withReplies ? `${account?.id}:with_replies` : account?.id;
|
||||
const showPins = settings.getIn(['account_timeline', 'shows', 'pinned']) === true && !withReplies;
|
||||
const showPins = settings.account_timeline.shows.pinned && !withReplies;
|
||||
const statusIds = useAppSelector(state => getStatusIds(state, { type: `account:${path}`, prefix: 'account_timeline' }));
|
||||
const featuredStatusIds = useAppSelector(state => getStatusIds(state, { type: `account:${account?.id}:pinned`, prefix: 'account_timeline' }));
|
||||
|
||||
|
|
|
@ -41,11 +41,10 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
|
|||
const history = useHistory();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const settings = useSettings();
|
||||
const { locale } = useSettings();
|
||||
const features = useFeatures();
|
||||
const instance = useInstance();
|
||||
|
||||
const locale = settings.get('locale');
|
||||
const needsConfirmation = instance.pleroma.metadata.account_activation_required;
|
||||
const needsApproval = instance.registrations.approval_required;
|
||||
const supportsEmailList = features.emailList;
|
||||
|
|
|
@ -18,7 +18,7 @@ const CommunityTimeline = () => {
|
|||
const dispatch = useAppDispatch();
|
||||
|
||||
const settings = useSettings();
|
||||
const onlyMedia = !!settings.getIn(['community', 'other', 'onlyMedia'], false);
|
||||
const onlyMedia = settings.community.other.onlyMedia;
|
||||
const next = useAppSelector(state => state.timelines.get('community')?.next);
|
||||
|
||||
const timelineId = 'community';
|
||||
|
|
|
@ -71,7 +71,7 @@ const EventHeader: React.FC<IEventHeader> = ({ status }) => {
|
|||
const history = useHistory();
|
||||
|
||||
const features = useFeatures();
|
||||
const settings = useSettings();
|
||||
const { boostModal } = useSettings();
|
||||
const { account: ownAccount } = useOwnAccount();
|
||||
const isStaff = ownAccount ? ownAccount.staff : false;
|
||||
const isAdmin = ownAccount ? ownAccount.admin : false;
|
||||
|
@ -123,7 +123,6 @@ const EventHeader: React.FC<IEventHeader> = ({ status }) => {
|
|||
|
||||
const handleReblogClick = () => {
|
||||
const modalReblog = () => dispatch(toggleReblog(status));
|
||||
const boostModal = settings.get('boostModal');
|
||||
if (!boostModal) {
|
||||
modalReblog();
|
||||
} else {
|
||||
|
|
|
@ -28,8 +28,7 @@ const EventInformation: React.FC<IEventInformation> = ({ params }) => {
|
|||
const status = useAppSelector(state => getStatus(state, { id: params.statusId })) as StatusEntity;
|
||||
|
||||
const { tileServer } = useSoapboxConfig();
|
||||
const settings = useSettings();
|
||||
const displayMedia = settings.get('displayMedia') as string;
|
||||
const { displayMedia } = useSettings();
|
||||
|
||||
const [isLoaded, setIsLoaded] = useState<boolean>(!!status);
|
||||
const [showMedia, setShowMedia] = useState<boolean>(defaultMediaVisibility(status, displayMedia));
|
||||
|
|
|
@ -25,8 +25,8 @@ const NotificationFilterBar = () => {
|
|||
const settings = useSettings();
|
||||
const features = useFeatures();
|
||||
|
||||
const selectedFilter = settings.getIn(['notifications', 'quickFilter', 'active']) as string;
|
||||
const advancedMode = settings.getIn(['notifications', 'quickFilter', 'advanced']);
|
||||
const selectedFilter = settings.notifications.quickFilter.active;
|
||||
const advancedMode = settings.notifications.quickFilter.advanced;
|
||||
|
||||
const onClick = (notificationType: string) => () => dispatch(setFilter(notificationType));
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import get from 'lodash/get';
|
||||
import React from 'react';
|
||||
|
||||
import { Toggle } from 'soapbox/components/ui';
|
||||
|
||||
import type { Map as ImmutableMap } from 'immutable';
|
||||
import { Settings } from 'soapbox/schemas/soapbox/settings';
|
||||
|
||||
interface ISettingToggle {
|
||||
/** Unique identifier for the Toggle. */
|
||||
id?: string;
|
||||
/** The full user settings map. */
|
||||
settings: ImmutableMap<string, any>;
|
||||
settings: Settings;
|
||||
/** Array of key names leading into the setting map. */
|
||||
settingPath: string[];
|
||||
/** Callback when the setting is toggled. */
|
||||
|
@ -25,7 +25,7 @@ const SettingToggle: React.FC<ISettingToggle> = ({ id, settings, settingPath, on
|
|||
return (
|
||||
<Toggle
|
||||
id={id}
|
||||
checked={!!settings.getIn(settingPath)}
|
||||
checked={!!get(settings, settingPath)}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -50,8 +50,8 @@ const Notifications = () => {
|
|||
const intl = useIntl();
|
||||
const settings = useSettings();
|
||||
|
||||
const showFilterBar = settings.getIn(['notifications', 'quickFilter', 'show']);
|
||||
const activeFilter = settings.getIn(['notifications', 'quickFilter', 'active']);
|
||||
const showFilterBar = settings.notifications.quickFilter.show;
|
||||
const activeFilter = settings.notifications.quickFilter.active;
|
||||
const notifications = useAppSelector(state => getNotifications(state));
|
||||
const isLoading = useAppSelector(state => state.notifications.isLoading);
|
||||
// const isUnread = useAppSelector(state => state.notifications.unread > 0);
|
||||
|
|
|
@ -139,7 +139,7 @@ const Preferences = () => {
|
|||
<SelectDropdown
|
||||
className='max-w-[200px]'
|
||||
items={languages}
|
||||
defaultValue={settings.get('locale') as string | undefined}
|
||||
defaultValue={settings.locale}
|
||||
onChange={(event: React.ChangeEvent<HTMLSelectElement>) => onSelectChange(event, ['locale'])}
|
||||
/>
|
||||
</ListItem>
|
||||
|
@ -148,7 +148,7 @@ const Preferences = () => {
|
|||
<SelectDropdown
|
||||
className='max-w-[200px]'
|
||||
items={displayMediaOptions}
|
||||
defaultValue={settings.get('displayMedia') as string | undefined}
|
||||
defaultValue={settings.displayMedia}
|
||||
onChange={(event: React.ChangeEvent<HTMLSelectElement>) => onSelectChange(event, ['displayMedia'])}
|
||||
/>
|
||||
</ListItem>
|
||||
|
@ -158,7 +158,7 @@ const Preferences = () => {
|
|||
<SelectDropdown
|
||||
className='max-w-[200px]'
|
||||
items={defaultPrivacyOptions}
|
||||
defaultValue={settings.get('defaultPrivacy') as string | undefined}
|
||||
defaultValue={settings.defaultPrivacy}
|
||||
onChange={(event: React.ChangeEvent<HTMLSelectElement>) => onSelectChange(event, ['defaultPrivacy'])}
|
||||
/>
|
||||
</ListItem>
|
||||
|
@ -169,7 +169,7 @@ const Preferences = () => {
|
|||
<SelectDropdown
|
||||
className='max-w-[200px]'
|
||||
items={defaultContentTypeOptions}
|
||||
defaultValue={settings.get('defaultContentType') as string | undefined}
|
||||
defaultValue={settings.defaultContentType}
|
||||
onChange={(event: React.ChangeEvent<HTMLSelectElement>) => onSelectChange(event, ['defaultContentType'])}
|
||||
/>
|
||||
</ListItem>
|
||||
|
|
|
@ -23,13 +23,13 @@ const CommunityTimeline = () => {
|
|||
|
||||
const instance = useInstance();
|
||||
const settings = useSettings();
|
||||
const onlyMedia = !!settings.getIn(['public', 'other', 'onlyMedia'], false);
|
||||
const onlyMedia = settings.public.other.onlyMedia;
|
||||
const next = useAppSelector(state => state.timelines.get('public')?.next);
|
||||
|
||||
const timelineId = 'public';
|
||||
|
||||
const explanationBoxExpanded = settings.get('explanationBox');
|
||||
const showExplanationBox = settings.get('showExplanationBox');
|
||||
const explanationBoxExpanded = settings.explanationBox;
|
||||
const showExplanationBox = settings.showExplanationBox;
|
||||
|
||||
const dismissExplanationBox = () => {
|
||||
dispatch(changeSetting(['showExplanationBox'], false));
|
||||
|
|
|
@ -10,9 +10,9 @@ interface IPinnedHostsPicker {
|
|||
|
||||
const PinnedHostsPicker: React.FC<IPinnedHostsPicker> = ({ host: activeHost }) => {
|
||||
const settings = useSettings();
|
||||
const pinnedHosts = settings.getIn(['remote_timeline', 'pinnedHosts']) as any;
|
||||
const pinnedHosts = settings.remote_timeline.pinnedHosts;
|
||||
|
||||
if (!pinnedHosts || pinnedHosts.isEmpty()) return null;
|
||||
if (!pinnedHosts.length) return null;
|
||||
|
||||
return (
|
||||
<HStack className='mb-4' space={2}>
|
||||
|
|
|
@ -27,10 +27,10 @@ const RemoteTimeline: React.FC<IRemoteTimeline> = ({ params }) => {
|
|||
const settings = useSettings();
|
||||
|
||||
const timelineId = 'remote';
|
||||
const onlyMedia = !!settings.getIn(['remote', 'other', 'onlyMedia']);
|
||||
const onlyMedia = settings.remote.other.onlyMedia;
|
||||
const next = useAppSelector(state => state.timelines.get('remote')?.next);
|
||||
|
||||
const pinned: boolean = (settings.getIn(['remote_timeline', 'pinnedHosts']) as any).includes(instance);
|
||||
const pinned = settings.remote_timeline.pinnedHosts.includes(instance);
|
||||
|
||||
const handleCloseClick: React.MouseEventHandler = () => {
|
||||
history.push('/timeline/fediverse');
|
||||
|
|
|
@ -26,8 +26,6 @@ import { defaultMediaVisibility, textForScreenReader } from 'soapbox/utils/statu
|
|||
import DetailedStatus from './detailed-status';
|
||||
import ThreadStatus from './thread-status';
|
||||
|
||||
type DisplayMedia = 'default' | 'hide_all' | 'show_all';
|
||||
|
||||
const getAncestorsIds = createSelector([
|
||||
(_: RootState, statusId: string | undefined) => statusId,
|
||||
(state: RootState) => state.contexts.inReplyTos,
|
||||
|
@ -96,9 +94,8 @@ const Thread = (props: IThread) => {
|
|||
const dispatch = useAppDispatch();
|
||||
const history = useHistory();
|
||||
const intl = useIntl();
|
||||
const settings = useSettings();
|
||||
const { displayMedia } = useSettings();
|
||||
|
||||
const displayMedia = settings.get('displayMedia') as DisplayMedia;
|
||||
const isUnderReview = status?.visibility === 'self';
|
||||
|
||||
const { ancestorsIds, descendantsIds } = useAppSelector((state) => {
|
||||
|
|
|
@ -25,7 +25,7 @@ const InstanceInfoPanel: React.FC<IInstanceInfoPanel> = ({ host }) => {
|
|||
|
||||
const settings = useSettings();
|
||||
const remoteInstance: any = useAppSelector(state => getRemoteInstance(state, host));
|
||||
const pinned: boolean = (settings.getIn(['remote_timeline', 'pinnedHosts']) as any).includes(host);
|
||||
const pinned = settings.remote_timeline.pinnedHosts.includes(host);
|
||||
|
||||
const handlePinHost = () => {
|
||||
if (!pinned) {
|
||||
|
|
|
@ -7,10 +7,9 @@ import { useInstance, useSettings, useSoapboxConfig } from 'soapbox/hooks';
|
|||
const PromoPanel: React.FC = () => {
|
||||
const instance = useInstance();
|
||||
const { promoPanel } = useSoapboxConfig();
|
||||
const settings = useSettings();
|
||||
const { locale } = useSettings();
|
||||
|
||||
const promoItems = promoPanel.get('items');
|
||||
const locale = settings.get('locale');
|
||||
|
||||
if (!promoItems || promoItems.isEmpty()) return null;
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import ThemeSelector from './theme-selector';
|
|||
/** Stateful theme selector. */
|
||||
const ThemeToggle: React.FC = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const themeMode = useSettings().get('themeMode');
|
||||
const { themeMode } = useSettings();
|
||||
|
||||
const handleChange = (themeMode: string) => {
|
||||
dispatch(changeSetting(['themeMode'], themeMode));
|
||||
|
|
|
@ -6,7 +6,7 @@ import { useSettings } from 'soapbox/hooks';
|
|||
import ReducedMotion from './reduced-motion';
|
||||
|
||||
const OptionalMotion = (props: MotionProps) => {
|
||||
const reduceMotion = useSettings().get('reduceMotion');
|
||||
const { reduceMotion } = useSettings();
|
||||
|
||||
return (
|
||||
reduceMotion ? <ReducedMotion {...props} /> : <Motion {...props} />
|
||||
|
|
|
@ -43,7 +43,7 @@ const WrappedRoute: React.FC<IWrappedRoute> = ({
|
|||
const history = useHistory();
|
||||
|
||||
const { account } = useOwnAccount();
|
||||
const settings = useSettings();
|
||||
const { isDeveloper } = useSettings();
|
||||
|
||||
const renderComponent = ({ match }: RouteComponentProps) => {
|
||||
if (Page) {
|
||||
|
@ -81,7 +81,7 @@ const WrappedRoute: React.FC<IWrappedRoute> = ({
|
|||
|
||||
const authorized = [
|
||||
account || publicRoute,
|
||||
developerOnly ? settings.get('isDeveloper') : true,
|
||||
developerOnly ? isDeveloper : true,
|
||||
staffOnly ? account && account.staff : true,
|
||||
adminOnly ? account && account.admin : true,
|
||||
].every(c => c);
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { useMemo } from 'react';
|
||||
|
||||
import { getSettings } from 'soapbox/actions/settings';
|
||||
import { settingsSchema } from 'soapbox/schemas/soapbox/settings';
|
||||
|
||||
import { useAppSelector } from './useAppSelector';
|
||||
|
||||
import type { Map as ImmutableMap } from 'immutable';
|
||||
|
||||
/** Get the user settings from the store */
|
||||
export const useSettings = (): ImmutableMap<string, any> => {
|
||||
return useAppSelector((state) => getSettings(state));
|
||||
export const useSettings = () => {
|
||||
const data = useAppSelector((state) => getSettings(state));
|
||||
return useMemo(() => settingsSchema.parse(data.toJS()), [data]);
|
||||
};
|
||||
|
|
|
@ -8,12 +8,10 @@ type Theme = 'light' | 'dark';
|
|||
* regardless of whether that's by system theme or direct setting.
|
||||
*/
|
||||
const useTheme = (): Theme => {
|
||||
const settings = useSettings();
|
||||
const { themeMode } = useSettings();
|
||||
const systemTheme = useSystemTheme();
|
||||
|
||||
const userTheme = settings.get('themeMode');
|
||||
const darkMode = userTheme === 'dark' || (userTheme === 'system' && systemTheme === 'dark');
|
||||
|
||||
const darkMode = themeMode === 'dark' || (themeMode === 'system' && systemTheme === 'dark');
|
||||
return darkMode ? 'dark' : 'light';
|
||||
};
|
||||
|
||||
|
|
|
@ -20,18 +20,17 @@ interface ISoapboxHead {
|
|||
/** Injects metadata into site head with Helmet. */
|
||||
const SoapboxHead: React.FC<ISoapboxHead> = ({ children }) => {
|
||||
const { locale, direction } = useLocale();
|
||||
const settings = useSettings();
|
||||
const { demo, reduceMotion, underlineLinks, demetricator } = useSettings();
|
||||
const soapboxConfig = useSoapboxConfig();
|
||||
|
||||
const demo = !!settings.get('demo');
|
||||
const darkMode = useTheme() === 'dark';
|
||||
const themeCss = generateThemeCss(demo ? normalizeSoapboxConfig({ brandColor: '#0482d8' }) : soapboxConfig);
|
||||
const dsn = soapboxConfig.sentryDsn;
|
||||
|
||||
const bodyClass = clsx('h-full bg-white text-base dark:bg-gray-800', {
|
||||
'no-reduce-motion': !settings.get('reduceMotion'),
|
||||
'underline-links': settings.get('underlineLinks'),
|
||||
'demetricator': settings.get('demetricator'),
|
||||
'no-reduce-motion': !reduceMotion,
|
||||
'underline-links': underlineLinks,
|
||||
'demetricator': demetricator,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -2,6 +2,8 @@ import { z } from 'zod';
|
|||
|
||||
import { locales } from 'soapbox/messages';
|
||||
|
||||
import { coerceObject } from '../utils';
|
||||
|
||||
const skinToneSchema = z.union([
|
||||
z.literal(1), z.literal(2), z.literal(3), z.literal(4), z.literal(5), z.literal(6),
|
||||
]);
|
||||
|
@ -14,6 +16,7 @@ const settingsSchema = z.object({
|
|||
autoPlayGif: z.boolean().catch(true),
|
||||
displayMedia: z.enum(['default', 'hide_all', 'show_all']).catch('default'),
|
||||
expandSpoilers: z.boolean().catch(false),
|
||||
preserveSpoilers: z.boolean().catch(false),
|
||||
unfollowModal: z.boolean().catch(false),
|
||||
boostModal: z.boolean().catch(false),
|
||||
deleteModal: z.boolean().catch(true),
|
||||
|
@ -29,6 +32,47 @@ const settingsSchema = z.object({
|
|||
systemFont: z.boolean().catch(false),
|
||||
demetricator: z.boolean().catch(false),
|
||||
isDeveloper: z.boolean().catch(false),
|
||||
demo: z.boolean().catch(false),
|
||||
chats: coerceObject({
|
||||
mainWindow: z.enum(['minimized', 'open']).catch('minimized'),
|
||||
sound: z.boolean().catch(true),
|
||||
}),
|
||||
home: coerceObject({
|
||||
shows: coerceObject({
|
||||
reblog: z.boolean().catch(true),
|
||||
reply: z.boolean().catch(true),
|
||||
}),
|
||||
}),
|
||||
account_timeline: coerceObject({
|
||||
shows: coerceObject({
|
||||
pinned: z.boolean().catch(true),
|
||||
}),
|
||||
}),
|
||||
remote_timeline: coerceObject({
|
||||
pinnedHosts: z.string().array().catch([]),
|
||||
}),
|
||||
public: coerceObject({
|
||||
other: coerceObject({
|
||||
onlyMedia: z.boolean().catch(false),
|
||||
}),
|
||||
}),
|
||||
community: coerceObject({
|
||||
other: coerceObject({
|
||||
onlyMedia: z.boolean().catch(false),
|
||||
}),
|
||||
}),
|
||||
remote: coerceObject({
|
||||
other: coerceObject({
|
||||
onlyMedia: z.boolean().catch(false),
|
||||
}),
|
||||
}),
|
||||
notifications: coerceObject({
|
||||
quickFilter: coerceObject({
|
||||
active: z.string().catch('all'),
|
||||
advanced: z.boolean().catch(false),
|
||||
show: z.boolean().catch(true),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
type Settings = z.infer<typeof settingsSchema>;
|
||||
|
|
Loading…
Reference in a new issue