mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 21:45:31 +09:00
refactor(app): merge duplicated components and stores (#3383)
This commit is contained in:
1 parent
c16d414d93
commit
5695510980
202 files changed
+2924
-6532
No files matched your search
@@ -23,7 +23,7 @@ import {Input} from '@app/features/ui/components/form/FormInput';
|
||||
import FocusRing from '@app/features/ui/focus_ring/FocusRing';
|
||||
import * as UserSettingsCommands from '@app/features/user/commands/UserSettingsCommands';
|
||||
import styles from '@app/features/user/components/modals/CustomStatusBottomSheet.module.css';
|
||||
import {type CustomStatus, normalizeCustomStatus} from '@app/features/user/state/CustomStatus';
|
||||
import {buildDraftStatus, normalizeCustomStatus} from '@app/features/user/state/CustomStatus';
|
||||
import Users from '@app/features/user/state/Users';
|
||||
import {handleAccountLimitedError} from '@app/features/user/utils/AccountLimitUtils';
|
||||
import {msg} from '@lingui/core/macro';
|
||||
@@ -67,19 +67,6 @@ interface CustomStatusBottomSheetProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const buildDraftStatus = (params: {
|
||||
text: string;
|
||||
emojiId: string | null;
|
||||
emojiName: string | null;
|
||||
expiresAt: string | null;
|
||||
}): CustomStatus | null => {
|
||||
return normalizeCustomStatus({
|
||||
text: params.text || null,
|
||||
emojiId: params.emojiId,
|
||||
emojiName: params.emojiName,
|
||||
expiresAt: params.expiresAt,
|
||||
});
|
||||
};
|
||||
export const CustomStatusBottomSheet = observer(({isOpen, onClose}: CustomStatusBottomSheetProps) => {
|
||||
const {i18n} = useLingui();
|
||||
const currentUser = Users.getCurrentUser();
|
||||
|
||||
@@ -28,7 +28,7 @@ import {getRelativeDayLabelLower} from '@app/features/ui/utils/RelativeDayLabels
|
||||
import * as UserSettingsCommands from '@app/features/user/commands/UserSettingsCommands';
|
||||
import styles from '@app/features/user/components/modals/CustomStatusModal.module.css';
|
||||
import {ProfilePreview} from '@app/features/user/components/profile/ProfilePreview';
|
||||
import {type CustomStatus, normalizeCustomStatus} from '@app/features/user/state/CustomStatus';
|
||||
import {buildDraftStatus, type CustomStatus, normalizeCustomStatus} from '@app/features/user/state/CustomStatus';
|
||||
import UserSettings from '@app/features/user/state/UserSettings';
|
||||
import Users from '@app/features/user/state/Users';
|
||||
import {handleAccountLimitedError} from '@app/features/user/utils/AccountLimitUtils';
|
||||
@@ -129,19 +129,6 @@ const formatRelativeDayTimeLabel = (reference: Date, target: Date): TimeLabel =>
|
||||
const dayLabel = getFormattedShortDate(target, getCurrentLocale());
|
||||
return {dayLabel, timeString};
|
||||
};
|
||||
const buildDraftStatus = (params: {
|
||||
text: string;
|
||||
emojiId: string | null;
|
||||
emojiName: string | null;
|
||||
expiresAt: string | null;
|
||||
}): CustomStatus | null => {
|
||||
return normalizeCustomStatus({
|
||||
text: params.text || null,
|
||||
emojiId: params.emojiId,
|
||||
emojiName: params.emojiName,
|
||||
expiresAt: params.expiresAt,
|
||||
});
|
||||
};
|
||||
export const CustomStatusModal = observer(() => {
|
||||
const {i18n} = useLingui();
|
||||
const initialStatus = normalizeCustomStatus(UserSettings.customStatus);
|
||||
|
||||
@@ -13,7 +13,7 @@ import {getCachedNumberFormat} from '@app/features/i18n/utils/IntlCache';
|
||||
import {Message as MessageModel} from '@app/features/messaging/models/MessagingMessage';
|
||||
import {remFromPx} from '@app/features/theme/layout/RemFromPx';
|
||||
import {Button} from '@app/features/ui/button/Button';
|
||||
import type {ComboboxOption} from '@app/features/ui/components/form/FormCombobox';
|
||||
import {type ComboboxOption, resolveNearestNumericOption} from '@app/features/ui/components/form/FormCombobox';
|
||||
import {Switch} from '@app/features/ui/components/form/FormSwitch';
|
||||
import {MockAvatar} from '@app/features/ui/components/MockAvatar';
|
||||
import MobileLayout from '@app/features/ui/state/MobileLayout';
|
||||
@@ -115,18 +115,6 @@ const getNearestTtsRate = (value: number): number => {
|
||||
return nearest;
|
||||
};
|
||||
|
||||
const resolveTtsRateInput = (
|
||||
inputValue: string,
|
||||
options: ReadonlyArray<ComboboxOption<number>>,
|
||||
): number | undefined => {
|
||||
const numericMatch = inputValue.trim().match(/([0-9]+(?:\.[0-9]+)?)/);
|
||||
if (!numericMatch) return undefined;
|
||||
const parsedValue = Number(numericMatch[1]);
|
||||
if (!Number.isFinite(parsedValue)) return undefined;
|
||||
return options.reduce((nearest, option) =>
|
||||
Math.abs(option.value - parsedValue) < Math.abs(nearest.value - parsedValue) ? option : nearest,
|
||||
).value;
|
||||
};
|
||||
const AccessibilityTabPreview = observer(() => {
|
||||
const {i18n} = useLingui();
|
||||
const alwaysUnderlineLinks = Accessibility.alwaysUnderlineLinks;
|
||||
@@ -308,7 +296,7 @@ export const AccessibilityTtsTabContent: React.FC = observer(() => {
|
||||
value={selectedTtsRate}
|
||||
options={ttsRateOptions}
|
||||
onChange={handleRateChange}
|
||||
autoSelectValueFromInput={resolveTtsRateInput}
|
||||
autoSelectValueFromInput={resolveNearestNumericOption}
|
||||
controlWidth="small"
|
||||
dataFlx="user.accessibility-tab.accessibility-tts-tab-content.select.tts-rate"
|
||||
data-flx="user.accessibility-tab.accessibility-tts-tab-content.compact-combobox-row.rate-change"
|
||||
|
||||
@@ -13,7 +13,6 @@ import {I18N_WEBLATE_DOMAIN, I18N_WEBLATE_URL} from '@app/features/app/config/I1
|
||||
import RuntimeConfig from '@app/features/app/state/RuntimeConfig';
|
||||
import * as EmojiUtils from '@app/features/expressions/utils/EmojiUtils';
|
||||
import Spellcheck from '@app/features/messaging/state/Spellcheck';
|
||||
import type {SpellcheckEngine} from '@app/features/platform/types/Electron';
|
||||
import {Button} from '@app/features/ui/button/Button';
|
||||
import {Combobox, type ComboboxFilterOption, type ComboboxOption} from '@app/features/ui/components/form/FormCombobox';
|
||||
import {Switch} from '@app/features/ui/components/form/FormSwitch';
|
||||
@@ -25,6 +24,7 @@ import * as UserSettingsCommands from '@app/features/user/commands/UserSettingsC
|
||||
import styles from '@app/features/user/components/modals/tabs/LanguageTab.module.css';
|
||||
import UserSettings from '@app/features/user/state/UserSettings';
|
||||
import * as LocaleUtils from '@app/features/user/utils/LocaleUtils';
|
||||
import type {SpellcheckEngine} from '@app/types/electron.d';
|
||||
import {TimeFormatTypes} from '@fluxer/constants/src/UserConstants';
|
||||
import {getFormattedTime} from '@fluxer/date_utils/src/DateFormatting';
|
||||
import {localeUses12Hour} from '@fluxer/date_utils/src/DateHourCycle';
|
||||
|
||||
@@ -10,6 +10,7 @@ import {Notifications} from '@app/features/user/components/modals/tabs/notificat
|
||||
import {Sounds} from '@app/features/user/components/modals/tabs/notifications_tab/NotificationsTabSounds';
|
||||
import {TextToSpeech} from '@app/features/user/components/modals/tabs/notifications_tab/TextToSpeech';
|
||||
import {useSoundSettings} from '@app/features/user/components/modals/tabs/notifications_tab/useSoundSettings';
|
||||
import inlineStyles from '@app/features/user/components/modals/tabs/TabInline.module.css';
|
||||
import {msg} from '@lingui/core/macro';
|
||||
import {useLingui} from '@lingui/react/macro';
|
||||
import {observer} from 'mobx-react-lite';
|
||||
@@ -23,7 +24,19 @@ const TEXT_TO_SPEECH_NOTIFICATIONS_DESCRIPTOR = msg({
|
||||
message: 'Text-to-speech notifications',
|
||||
comment: 'Notifications settings: section heading for text-to-speech notification behavior.',
|
||||
});
|
||||
const NotificationsTab: React.FC = observer(() => {
|
||||
const CONTROL_SPEECH_COMMANDS_AND_NARRATION_FOR_INCOMING_CONTENT_DESCRIPTOR = msg({
|
||||
message: 'Control speech commands and narration for incoming content.',
|
||||
comment: 'Description text in the inline.',
|
||||
});
|
||||
const INLINE_MENTION_PREFERENCE_DESCRIPTOR = msg({
|
||||
message: 'Mention preference',
|
||||
comment: 'Short label in the inline. Keep it concise.',
|
||||
});
|
||||
const INLINE_TEXT_TO_SPEECH_NOTIFICATIONS_DESCRIPTOR = msg({
|
||||
message: 'Text-to-speech notifications',
|
||||
comment: 'Short label in the inline. Keep it concise.',
|
||||
});
|
||||
const NotificationsSections = observer(({inline}: {inline: boolean}) => {
|
||||
const {i18n} = useLingui();
|
||||
const browserNotificationsEnabled = Notification.browserNotificationsEnabled;
|
||||
const unreadMessageBadgeEnabled = Notification.unreadMessageBadgeEnabled;
|
||||
@@ -41,55 +54,70 @@ const NotificationsTab: React.FC = observer(() => {
|
||||
handleSoundOverrideReset,
|
||||
handleAllOverridesReset,
|
||||
} = useSoundSettings();
|
||||
const flx = inline ? 'user.notifications-tab.inline.notifications-inline-content' : 'user.notifications-tab';
|
||||
return (
|
||||
<SettingsTabContainer data-flx="user.notifications-tab.settings-tab-container">
|
||||
<SettingsTabContent data-flx="user.notifications-tab.settings-tab-content">
|
||||
<SettingsSection
|
||||
id="notifications"
|
||||
title={i18n._(GENERAL_DESCRIPTOR)}
|
||||
data-flx="user.notifications-tab.notifications"
|
||||
>
|
||||
<Notifications
|
||||
browserNotificationsEnabled={browserNotificationsEnabled}
|
||||
unreadMessageBadgeEnabled={unreadMessageBadgeEnabled}
|
||||
data-flx="user.notifications-tab.notifications--2"
|
||||
/>
|
||||
</SettingsSection>
|
||||
<SettingsSection
|
||||
id="mention-preference"
|
||||
title={i18n._(MENTION_PREFERENCE_SECTION_TITLE_DESCRIPTOR)}
|
||||
data-flx="user.notifications-tab.mention-preference"
|
||||
>
|
||||
<MentionPreferenceTabContent data-flx="user.notifications-tab.mention-preference-tab-content" />
|
||||
</SettingsSection>
|
||||
<SettingsSection id="sounds" title={i18n._(SOUNDS_DESCRIPTOR)} data-flx="user.notifications-tab.sounds">
|
||||
<Sounds
|
||||
soundSettings={soundSettings}
|
||||
soundTypeLabels={soundTypeLabels}
|
||||
customSounds={customSounds}
|
||||
isSoundEnabled={Sound.isSoundTypeEnabled}
|
||||
onToggleAllSounds={handleToggleAllSounds}
|
||||
onToggleSound={handleToggleSound}
|
||||
onPreviewSound={handlePreviewSound}
|
||||
onUploadClick={handleUploadClick}
|
||||
onCustomSoundDelete={handleCustomSoundDelete}
|
||||
onMasterVolumeChange={handleMasterVolumeChange}
|
||||
onSoundOverrideChange={handleSoundOverrideChange}
|
||||
onSoundOverrideReset={handleSoundOverrideReset}
|
||||
onAllOverridesReset={handleAllOverridesReset}
|
||||
data-flx="user.notifications-tab.sounds--2"
|
||||
/>
|
||||
</SettingsSection>
|
||||
<SettingsSection
|
||||
id="text-to-speech"
|
||||
title={i18n._(TEXT_TO_SPEECH_NOTIFICATIONS_DESCRIPTOR)}
|
||||
data-flx="user.notifications-tab.text-to-speech"
|
||||
>
|
||||
<TextToSpeech data-flx="user.notifications-tab.text-to-speech--2" />
|
||||
</SettingsSection>
|
||||
</SettingsTabContent>
|
||||
</SettingsTabContainer>
|
||||
<>
|
||||
<SettingsSection id="notifications" title={i18n._(GENERAL_DESCRIPTOR)} data-flx={`${flx}.notifications`}>
|
||||
<Notifications
|
||||
browserNotificationsEnabled={browserNotificationsEnabled}
|
||||
unreadMessageBadgeEnabled={unreadMessageBadgeEnabled}
|
||||
data-flx={`${flx}.notifications--2`}
|
||||
/>
|
||||
</SettingsSection>
|
||||
<SettingsSection
|
||||
id="mention-preference"
|
||||
title={i18n._(inline ? INLINE_MENTION_PREFERENCE_DESCRIPTOR : MENTION_PREFERENCE_SECTION_TITLE_DESCRIPTOR)}
|
||||
data-flx={`${flx}.mention-preference`}
|
||||
>
|
||||
<MentionPreferenceTabContent data-flx={`${flx}.mention-preference-tab-content`} />
|
||||
</SettingsSection>
|
||||
<SettingsSection id="sounds" title={i18n._(SOUNDS_DESCRIPTOR)} data-flx={`${flx}.sounds`}>
|
||||
<Sounds
|
||||
soundSettings={soundSettings}
|
||||
soundTypeLabels={soundTypeLabels}
|
||||
customSounds={customSounds}
|
||||
isSoundEnabled={Sound.isSoundTypeEnabled}
|
||||
onToggleAllSounds={handleToggleAllSounds}
|
||||
onToggleSound={handleToggleSound}
|
||||
onPreviewSound={handlePreviewSound}
|
||||
onUploadClick={handleUploadClick}
|
||||
onCustomSoundDelete={handleCustomSoundDelete}
|
||||
onMasterVolumeChange={handleMasterVolumeChange}
|
||||
onSoundOverrideChange={handleSoundOverrideChange}
|
||||
onSoundOverrideReset={handleSoundOverrideReset}
|
||||
onAllOverridesReset={handleAllOverridesReset}
|
||||
data-flx={`${flx}.sounds--2`}
|
||||
/>
|
||||
</SettingsSection>
|
||||
<SettingsSection
|
||||
id="text-to-speech"
|
||||
title={i18n._(
|
||||
inline ? INLINE_TEXT_TO_SPEECH_NOTIFICATIONS_DESCRIPTOR : TEXT_TO_SPEECH_NOTIFICATIONS_DESCRIPTOR,
|
||||
)}
|
||||
description={inline ? i18n._(CONTROL_SPEECH_COMMANDS_AND_NARRATION_FOR_INCOMING_CONTENT_DESCRIPTOR) : undefined}
|
||||
data-flx={`${flx}.text-to-speech`}
|
||||
>
|
||||
<TextToSpeech data-flx={`${flx}.text-to-speech--2`} />
|
||||
</SettingsSection>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
const NotificationsTab: React.FC = () => (
|
||||
<SettingsTabContainer data-flx="user.notifications-tab.settings-tab-container">
|
||||
<SettingsTabContent data-flx="user.notifications-tab.settings-tab-content">
|
||||
<NotificationsSections inline={false} data-flx="user.notifications-tab.notifications-sections" />
|
||||
</SettingsTabContent>
|
||||
</SettingsTabContainer>
|
||||
);
|
||||
|
||||
export const NotificationsInlineContent: React.FC = () => (
|
||||
<div
|
||||
className={inlineStyles.container}
|
||||
data-flx="user.notifications-tab.inline.notifications-inline-content.container"
|
||||
>
|
||||
<NotificationsSections inline={true} data-flx="user.notifications-tab.inline.notifications-sections" />
|
||||
</div>
|
||||
);
|
||||
|
||||
export default NotificationsTab;
|
||||
File renamed without changes.
+1
-1
@@ -6,11 +6,11 @@ import {
|
||||
AccessibilityScreenReaderTabContent,
|
||||
AccessibilityTtsTabContent,
|
||||
} from '@app/features/user/components/modals/tabs/AccessibilityTab';
|
||||
import styles from '@app/features/user/components/modals/tabs/accessibility_tab/AccessibilityTabInline.module.css';
|
||||
import {AnimationTabContent} from '@app/features/user/components/modals/tabs/accessibility_tab/AnimationTab';
|
||||
import {KeyboardTabContent} from '@app/features/user/components/modals/tabs/accessibility_tab/KeyboardTab';
|
||||
import {MotionTabContent} from '@app/features/user/components/modals/tabs/accessibility_tab/MotionTab';
|
||||
import {VisualTabContent} from '@app/features/user/components/modals/tabs/accessibility_tab/VisualTab';
|
||||
import styles from '@app/features/user/components/modals/tabs/TabInline.module.css';
|
||||
import {msg} from '@lingui/core/macro';
|
||||
import {useLingui} from '@lingui/react/macro';
|
||||
import {observer} from 'mobx-react-lite';
|
||||
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
import {StreamerModeGate} from '@app/features/streamer_mode/components/StreamerModeGate';
|
||||
import StreamerMode from '@app/features/streamer_mode/state/StreamerMode';
|
||||
import {AccountSecuritySections} from '@app/features/user/components/modals/tabs/account_security_tab/AccountSecuritySections';
|
||||
import styles from '@app/features/user/components/modals/tabs/account_security_tab/AccountSecurityTabInline.module.css';
|
||||
import styles from '@app/features/user/components/modals/tabs/TabInline.module.css';
|
||||
import {
|
||||
getAccountSectionForLegacySection,
|
||||
getAccountSectionForNestedTab,
|
||||
|
||||
+3
-46
@@ -8,17 +8,13 @@ import * as ModalCommands from '@app/features/ui/commands/ModalCommands';
|
||||
import {modal} from '@app/features/ui/commands/ModalCommands';
|
||||
import {Switch} from '@app/features/ui/components/form/FormSwitch';
|
||||
import MobileLayout from '@app/features/ui/state/MobileLayout';
|
||||
import {
|
||||
getCachedDesktopWindowBehaviorSettings,
|
||||
getDesktopWindowBehaviorSettings,
|
||||
relaunchDesktopApp,
|
||||
setDesktopWindowBehaviorSettings,
|
||||
} from '@app/features/ui/utils/DesktopWindowBehaviorUtils';
|
||||
import {relaunchDesktopApp} from '@app/features/ui/utils/DesktopWindowBehaviorUtils';
|
||||
import {useDesktopWindowBehaviorSettings} from '@app/features/user/components/modals/tabs/advanced_settings_tab/AdvancedDesktopControls';
|
||||
import type {DesktopWindowBehaviorSettings} from '@app/types/electron.d';
|
||||
import {msg, ph} from '@lingui/core/macro';
|
||||
import {Trans, useLingui} from '@lingui/react/macro';
|
||||
import {observer} from 'mobx-react-lite';
|
||||
import {useCallback, useLayoutEffect, useState} from 'react';
|
||||
import {useCallback} from 'react';
|
||||
|
||||
const ENABLE_TEXT_SELECTION_DESCRIPTOR = msg({
|
||||
message: 'Enable text selection',
|
||||
@@ -61,45 +57,6 @@ function hasDesktopMiddleClickAutoscrollPendingRestart(settings: DesktopWindowBe
|
||||
return settings !== null && settings.middleClickAutoscroll !== settings.activeMiddleClickAutoscroll;
|
||||
}
|
||||
|
||||
function useDesktopWindowBehaviorSettings() {
|
||||
const cachedDesktopWindowBehavior = getCachedDesktopWindowBehaviorSettings();
|
||||
const [desktopWindowBehavior, setDesktopWindowBehavior] = useState<DesktopWindowBehaviorSettings | null>(
|
||||
cachedDesktopWindowBehavior,
|
||||
);
|
||||
const [desktopWindowBehaviorBusy, setDesktopWindowBehaviorBusy] = useState(cachedDesktopWindowBehavior === null);
|
||||
useLayoutEffect(() => {
|
||||
let mounted = true;
|
||||
if (getCachedDesktopWindowBehaviorSettings() !== null) {
|
||||
setDesktopWindowBehaviorBusy(false);
|
||||
return () => {
|
||||
mounted = false;
|
||||
};
|
||||
}
|
||||
const initDesktopWindowBehavior = async () => {
|
||||
const settings = await getDesktopWindowBehaviorSettings();
|
||||
if (!mounted) return;
|
||||
if (settings !== null) {
|
||||
setDesktopWindowBehavior(settings);
|
||||
}
|
||||
setDesktopWindowBehaviorBusy(false);
|
||||
};
|
||||
void initDesktopWindowBehavior();
|
||||
return () => {
|
||||
mounted = false;
|
||||
};
|
||||
}, []);
|
||||
const updateDesktopWindowBehavior = useCallback(async (settings: Partial<DesktopWindowBehaviorSettings>) => {
|
||||
setDesktopWindowBehaviorBusy(true);
|
||||
const nextSettings = await setDesktopWindowBehaviorSettings(settings);
|
||||
if (nextSettings !== null) {
|
||||
setDesktopWindowBehavior(nextSettings);
|
||||
}
|
||||
setDesktopWindowBehaviorBusy(false);
|
||||
return nextSettings;
|
||||
}, []);
|
||||
return {desktopWindowBehavior, desktopWindowBehaviorBusy, updateDesktopWindowBehavior};
|
||||
}
|
||||
|
||||
function useChromiumScrollingRestartModal() {
|
||||
const {i18n} = useLingui();
|
||||
return useCallback(() => {
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ const LATER_DESCRIPTOR = msg({
|
||||
comment: 'Short confirmation button label in advanced settings.',
|
||||
});
|
||||
|
||||
function useDesktopWindowBehaviorSettings() {
|
||||
export function useDesktopWindowBehaviorSettings() {
|
||||
const cachedDesktopWindowBehavior = getCachedDesktopWindowBehaviorSettings();
|
||||
const [desktopWindowBehavior, setDesktopWindowBehavior] = useState<DesktopWindowBehaviorSettings | null>(
|
||||
cachedDesktopWindowBehavior,
|
||||
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
+1
-1
@@ -7,13 +7,13 @@ import {
|
||||
HdrTabContent,
|
||||
shouldShowHdrSettings,
|
||||
} from '@app/features/user/components/modals/tabs/appearance_tab/AppearanceTabHdrTab';
|
||||
import styles from '@app/features/user/components/modals/tabs/appearance_tab/AppearanceTabInline.module.css';
|
||||
import {ChannelListTabContent} from '@app/features/user/components/modals/tabs/appearance_tab/ChannelListTab';
|
||||
import {InterfaceTabContent} from '@app/features/user/components/modals/tabs/appearance_tab/InterfaceTab';
|
||||
import {MessagesTabContent} from '@app/features/user/components/modals/tabs/appearance_tab/MessagesTab';
|
||||
import {AppZoomLevelTabContent} from '@app/features/user/components/modals/tabs/appearance_tab/ScalingTab';
|
||||
import {StreamerModeTabContent} from '@app/features/user/components/modals/tabs/appearance_tab/StreamerModeTab';
|
||||
import {ThemeTabContent} from '@app/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent';
|
||||
import styles from '@app/features/user/components/modals/tabs/TabInline.module.css';
|
||||
import {shouldShowAppZoomLevel} from '@app/features/user/components/settings_utils/AppZoomLevelUtils';
|
||||
import {msg} from '@lingui/core/macro';
|
||||
import {useLingui} from '@lingui/react/macro';
|
||||
|
||||
@@ -12,7 +12,7 @@ import Channels from '@app/features/channel/state/Channels';
|
||||
import {getCachedNumberFormat} from '@app/features/i18n/utils/IntlCache';
|
||||
import {Message as MessageModel} from '@app/features/messaging/models/MessagingMessage';
|
||||
import {isNewMessageGroup} from '@app/features/messaging/utils/MessageGroupingUtils';
|
||||
import type {ComboboxOption} from '@app/features/ui/components/form/FormCombobox';
|
||||
import {type ComboboxOption, resolveNearestNumericOption} from '@app/features/ui/components/form/FormCombobox';
|
||||
import {Switch} from '@app/features/ui/components/form/FormSwitch';
|
||||
import {canResetSliderValue, SliderResetIconButton} from '@app/features/ui/components/slider/SliderResetIconButton';
|
||||
import type {RadioOption} from '@app/features/ui/radio_group/RadioGroup';
|
||||
@@ -104,18 +104,6 @@ const getNearestMessageGroupSpacing = (value: number): number => {
|
||||
return nearest;
|
||||
};
|
||||
|
||||
const resolveMessageGroupSpacingInput = (
|
||||
inputValue: string,
|
||||
options: ReadonlyArray<ComboboxOption<number>>,
|
||||
): number | undefined => {
|
||||
const numericMatch = inputValue.trim().match(/([0-9]+(?:\.[0-9]+)?)/);
|
||||
if (!numericMatch) return undefined;
|
||||
const parsedValue = Number(numericMatch[1]);
|
||||
if (!Number.isFinite(parsedValue)) return undefined;
|
||||
return options.reduce((nearest, option) =>
|
||||
Math.abs(option.value - parsedValue) < Math.abs(nearest.value - parsedValue) ? option : nearest,
|
||||
).value;
|
||||
};
|
||||
const MessagesPreview: React.FC = observer(() => {
|
||||
const {i18n} = useLingui();
|
||||
const {messageDisplayCompact} = UserSettings;
|
||||
@@ -288,7 +276,7 @@ export const MessagesTabContent: React.FC = observer(() => {
|
||||
value={selectedMessageGroupSpacing}
|
||||
options={messageGroupSpacingOptions}
|
||||
onChange={(value) => AccessibilityCommands.update(getMessageGroupSpacingPatch(messageDisplayCompact, value))}
|
||||
autoSelectValueFromInput={resolveMessageGroupSpacingInput}
|
||||
autoSelectValueFromInput={resolveNearestNumericOption}
|
||||
controlWidth="small"
|
||||
menuMinWidth={128}
|
||||
aria-label={i18n._(SPACE_BETWEEN_MESSAGE_GROUPS_DESCRIPTOR)}
|
||||
|
||||
@@ -8,7 +8,7 @@ import Accessibility, {
|
||||
} from '@app/features/accessibility/state/Accessibility';
|
||||
import {APP_ZOOM_LEVEL_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors';
|
||||
import {getCachedNumberFormat} from '@app/features/i18n/utils/IntlCache';
|
||||
import type {ComboboxOption} from '@app/features/ui/components/form/FormCombobox';
|
||||
import {type ComboboxOption, resolveNearestNumericOption} from '@app/features/ui/components/form/FormCombobox';
|
||||
import {Slider} from '@app/features/ui/components/Slider';
|
||||
import {canResetSliderValue, SliderResetIconButton} from '@app/features/ui/components/slider/SliderResetIconButton';
|
||||
import {formatRoundedPercentage, roundPercentage} from '@app/features/ui/utils/PercentageFormatting';
|
||||
@@ -52,19 +52,6 @@ const getNearestFontSize = (value: number): number => {
|
||||
return nearest;
|
||||
};
|
||||
|
||||
const resolveFontSizeInput = (
|
||||
inputValue: string,
|
||||
options: ReadonlyArray<ComboboxOption<number>>,
|
||||
): number | undefined => {
|
||||
const numericMatch = inputValue.trim().match(/([0-9]+(?:\.[0-9]+)?)/);
|
||||
if (!numericMatch) return undefined;
|
||||
const parsedValue = Number(numericMatch[1]);
|
||||
if (!Number.isFinite(parsedValue)) return undefined;
|
||||
return options.reduce((nearest, option) =>
|
||||
Math.abs(option.value - parsedValue) < Math.abs(nearest.value - parsedValue) ? option : nearest,
|
||||
).value;
|
||||
};
|
||||
|
||||
function canResetFontSize(): boolean {
|
||||
return canResetSliderValue(Accessibility.fontSize, DEFAULT_FONT_SIZE);
|
||||
}
|
||||
@@ -104,7 +91,7 @@ export const FontSizeTabContent: React.FC = observer(() => {
|
||||
value={selectedFontSize}
|
||||
options={options}
|
||||
onChange={(value) => AccessibilityCommands.update({fontSize: value})}
|
||||
autoSelectValueFromInput={resolveFontSizeInput}
|
||||
autoSelectValueFromInput={resolveNearestNumericOption}
|
||||
controlWidth="small"
|
||||
menuMinWidth={128}
|
||||
aria-label={i18n._(CHAT_FONT_SIZE_DESCRIPTOR)}
|
||||
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
+1
-1
@@ -1,9 +1,9 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {SettingsSection} from '@app/features/app/components/dialogs/shared/SettingsSection';
|
||||
import styles from '@app/features/user/components/modals/tabs/chat_settings_tab/ChatSettingsTabInline.module.css';
|
||||
import {InputTabContent} from '@app/features/user/components/modals/tabs/chat_settings_tab/ChatSettingsTabInputTab';
|
||||
import {DisplayTabContent} from '@app/features/user/components/modals/tabs/chat_settings_tab/DisplayTab';
|
||||
import styles from '@app/features/user/components/modals/tabs/TabInline.module.css';
|
||||
import {msg} from '@lingui/core/macro';
|
||||
import {useLingui} from '@lingui/react/macro';
|
||||
import {observer} from 'mobx-react-lite';
|
||||
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
-100
@@ -1,100 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {SettingsSection} from '@app/features/app/components/dialogs/shared/SettingsSection';
|
||||
import {GENERAL_DESCRIPTOR, SOUNDS_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors';
|
||||
import Notification from '@app/features/ui/state/Notification';
|
||||
import Sound from '@app/features/ui/state/Sound';
|
||||
import {MentionPreferenceTabContent} from '@app/features/user/components/modals/tabs/notifications_tab/MentionPreferenceTab';
|
||||
import {Notifications} from '@app/features/user/components/modals/tabs/notifications_tab/Notifications';
|
||||
import styles from '@app/features/user/components/modals/tabs/notifications_tab/NotificationsTabInline.module.css';
|
||||
import {Sounds} from '@app/features/user/components/modals/tabs/notifications_tab/NotificationsTabSounds';
|
||||
import {TextToSpeech} from '@app/features/user/components/modals/tabs/notifications_tab/TextToSpeech';
|
||||
import {useSoundSettings} from '@app/features/user/components/modals/tabs/notifications_tab/useSoundSettings';
|
||||
import {msg} from '@lingui/core/macro';
|
||||
import {useLingui} from '@lingui/react/macro';
|
||||
import {observer} from 'mobx-react-lite';
|
||||
import type React from 'react';
|
||||
|
||||
const CONTROL_SPEECH_COMMANDS_AND_NARRATION_FOR_INCOMING_CONTENT_DESCRIPTOR = msg({
|
||||
message: 'Control speech commands and narration for incoming content.',
|
||||
comment: 'Description text in the inline.',
|
||||
});
|
||||
const MENTION_PREFERENCE_DESCRIPTOR = msg({
|
||||
message: 'Mention preference',
|
||||
comment: 'Short label in the inline. Keep it concise.',
|
||||
});
|
||||
const TEXT_TO_SPEECH_NOTIFICATIONS_DESCRIPTOR = msg({
|
||||
message: 'Text-to-speech notifications',
|
||||
comment: 'Short label in the inline. Keep it concise.',
|
||||
});
|
||||
export const NotificationsInlineContent: React.FC = observer(() => {
|
||||
const {i18n} = useLingui();
|
||||
const browserNotificationsEnabled = Notification.browserNotificationsEnabled;
|
||||
const unreadMessageBadgeEnabled = Notification.unreadMessageBadgeEnabled;
|
||||
const soundSettings = Sound.settings;
|
||||
const {
|
||||
soundTypeLabels,
|
||||
customSounds,
|
||||
handleToggleAllSounds,
|
||||
handleToggleSound,
|
||||
handlePreviewSound,
|
||||
handleUploadClick,
|
||||
handleCustomSoundDelete,
|
||||
handleMasterVolumeChange,
|
||||
handleSoundOverrideChange,
|
||||
handleSoundOverrideReset,
|
||||
handleAllOverridesReset,
|
||||
} = useSoundSettings();
|
||||
return (
|
||||
<div className={styles.container} data-flx="user.notifications-tab.inline.notifications-inline-content.container">
|
||||
<SettingsSection
|
||||
id="notifications"
|
||||
title={i18n._(GENERAL_DESCRIPTOR)}
|
||||
data-flx="user.notifications-tab.inline.notifications-inline-content.notifications"
|
||||
>
|
||||
<Notifications
|
||||
browserNotificationsEnabled={browserNotificationsEnabled}
|
||||
unreadMessageBadgeEnabled={unreadMessageBadgeEnabled}
|
||||
data-flx="user.notifications-tab.inline.notifications-inline-content.notifications--2"
|
||||
/>
|
||||
</SettingsSection>
|
||||
<SettingsSection
|
||||
id="mention-preference"
|
||||
title={i18n._(MENTION_PREFERENCE_DESCRIPTOR)}
|
||||
data-flx="user.notifications-tab.inline.notifications-inline-content.mention-preference"
|
||||
>
|
||||
<MentionPreferenceTabContent data-flx="user.notifications-tab.inline.notifications-inline-content.mention-preference-tab-content" />
|
||||
</SettingsSection>
|
||||
<SettingsSection
|
||||
id="sounds"
|
||||
title={i18n._(SOUNDS_DESCRIPTOR)}
|
||||
data-flx="user.notifications-tab.inline.notifications-inline-content.sounds"
|
||||
>
|
||||
<Sounds
|
||||
soundSettings={soundSettings}
|
||||
soundTypeLabels={soundTypeLabels}
|
||||
customSounds={customSounds}
|
||||
isSoundEnabled={Sound.isSoundTypeEnabled}
|
||||
onToggleAllSounds={handleToggleAllSounds}
|
||||
onToggleSound={handleToggleSound}
|
||||
onPreviewSound={handlePreviewSound}
|
||||
onUploadClick={handleUploadClick}
|
||||
onCustomSoundDelete={handleCustomSoundDelete}
|
||||
onMasterVolumeChange={handleMasterVolumeChange}
|
||||
onSoundOverrideChange={handleSoundOverrideChange}
|
||||
onSoundOverrideReset={handleSoundOverrideReset}
|
||||
onAllOverridesReset={handleAllOverridesReset}
|
||||
data-flx="user.notifications-tab.inline.notifications-inline-content.sounds--2"
|
||||
/>
|
||||
</SettingsSection>
|
||||
<SettingsSection
|
||||
id="text-to-speech"
|
||||
title={i18n._(TEXT_TO_SPEECH_NOTIFICATIONS_DESCRIPTOR)}
|
||||
description={i18n._(CONTROL_SPEECH_COMMANDS_AND_NARRATION_FOR_INCOMING_CONTENT_DESCRIPTOR)}
|
||||
data-flx="user.notifications-tab.inline.notifications-inline-content.text-to-speech"
|
||||
>
|
||||
<TextToSpeech data-flx="user.notifications-tab.inline.notifications-inline-content.text-to-speech--2" />
|
||||
</SettingsSection>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
+1
-1
@@ -7,9 +7,9 @@ import {CommunicationTabContent as CommunicationTab} from '@app/features/user/co
|
||||
import {ConnectionsTabContent as ConnectionsTab} from '@app/features/user/components/modals/tabs/privacy_safety_tab/ConnectionsTab';
|
||||
import {DataDeletionTabContent as DataDeletionTab} from '@app/features/user/components/modals/tabs/privacy_safety_tab/DataDeletionTab';
|
||||
import {DataExportTabContent as DataExportTab} from '@app/features/user/components/modals/tabs/privacy_safety_tab/DataExportTab';
|
||||
import styles from '@app/features/user/components/modals/tabs/privacy_safety_tab/PrivacySafetyTabInline.module.css';
|
||||
import {ProfilePrivacyTabContent as ProfilePrivacyTab} from '@app/features/user/components/modals/tabs/privacy_safety_tab/ProfilePrivacyTab';
|
||||
import {SensitiveContentTabContent as SensitiveContentTab} from '@app/features/user/components/modals/tabs/privacy_safety_tab/SensitiveContentTab';
|
||||
import styles from '@app/features/user/components/modals/tabs/TabInline.module.css';
|
||||
import {useUserSettingsMutationController} from '@app/features/user/hooks/useUserSettingsMutationController';
|
||||
import {msg} from '@lingui/core/macro';
|
||||
import {useLingui} from '@lingui/react/macro';
|
||||
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
+1
-1
@@ -3,9 +3,9 @@
|
||||
import {SettingsSection} from '@app/features/app/components/dialogs/shared/SettingsSection';
|
||||
import {AUDIO_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors';
|
||||
import MediaPermission from '@app/features/permissions/system/state/MediaPermission';
|
||||
import styles from '@app/features/user/components/modals/tabs/TabInline.module.css';
|
||||
import {VideoTab} from '@app/features/user/components/modals/tabs/UserVideoTab';
|
||||
import {VoiceTab} from '@app/features/user/components/modals/tabs/UserVoiceTab';
|
||||
import styles from '@app/features/user/components/modals/tabs/voice_video_tab/VoiceVideoTabInline.module.css';
|
||||
import Users from '@app/features/user/state/Users';
|
||||
import VoiceSettings from '@app/features/voice/state/VoiceSettings';
|
||||
import {MediaDeviceRefreshType, refreshMediaDeviceLists} from '@app/features/voice/utils/MediaDeviceRefresh';
|
||||
|
||||
Reference in new issue
Block a user