260 lines
8.8 KiB
TypeScript
260 lines
8.8 KiB
TypeScript
import React, { ChangeEventHandler, KeyboardEventHandler, useState } from 'react';
|
|
import {
|
|
Box,
|
|
Icon,
|
|
IconButton,
|
|
Icons,
|
|
Input,
|
|
Scroll,
|
|
Switch,
|
|
Text,
|
|
toRem,
|
|
} from 'folds';
|
|
import { isKeyHotkey } from 'is-hotkey';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { Page, PageContent, PageHeader } from '../../../components/page';
|
|
import { SequenceCard } from '../../../components/sequence-card';
|
|
import { useSetting } from '../../../state/hooks/settings';
|
|
import { settingsAtom } from '../../../state/settings';
|
|
import { SettingTile } from '../../../components/setting-tile';
|
|
import { KeySymbol } from '../../../utils/key-symbol';
|
|
import { isMacOS } from '../../../utils/user-agent';
|
|
import { SequenceCardStyle } from '../styles.css';
|
|
|
|
function ThemeSelect() {
|
|
// Theme switching is locked to dark while the Dawn redesign rolls out — Vojo
|
|
// light is a separate plan. Kept as a read-only label so the existing
|
|
// SettingTile layout in the Appearance section still renders.
|
|
const { t } = useTranslation();
|
|
return (
|
|
<Text size="T300" priority="300">
|
|
{t('Settings.theme_dark')}
|
|
</Text>
|
|
);
|
|
}
|
|
|
|
function PageZoomInput() {
|
|
const [pageZoom, setPageZoom] = useSetting(settingsAtom, 'pageZoom');
|
|
const [currentZoom, setCurrentZoom] = useState(`${pageZoom}`);
|
|
|
|
const handleZoomChange: ChangeEventHandler<HTMLInputElement> = (evt) => {
|
|
setCurrentZoom(evt.target.value);
|
|
};
|
|
|
|
const handleZoomEnter: KeyboardEventHandler<HTMLInputElement> = (evt) => {
|
|
if (isKeyHotkey('escape', evt)) {
|
|
evt.stopPropagation();
|
|
setCurrentZoom(pageZoom.toString());
|
|
}
|
|
if (
|
|
isKeyHotkey('enter', evt) &&
|
|
'value' in evt.target &&
|
|
typeof evt.target.value === 'string'
|
|
) {
|
|
const newZoom = parseInt(evt.target.value, 10);
|
|
if (Number.isNaN(newZoom)) return;
|
|
const safeZoom = Math.max(Math.min(newZoom, 150), 75);
|
|
setPageZoom(safeZoom);
|
|
setCurrentZoom(safeZoom.toString());
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Input
|
|
style={{ width: toRem(100) }}
|
|
variant={pageZoom === parseInt(currentZoom, 10) ? 'Secondary' : 'Success'}
|
|
size="300"
|
|
radii="300"
|
|
type="number"
|
|
min="75"
|
|
max="150"
|
|
value={currentZoom}
|
|
onChange={handleZoomChange}
|
|
onKeyDown={handleZoomEnter}
|
|
after={<Text size="T300">%</Text>}
|
|
outlined
|
|
/>
|
|
);
|
|
}
|
|
|
|
function Appearance() {
|
|
const { t } = useTranslation();
|
|
const [monochromeMode, setMonochromeMode] = useSetting(settingsAtom, 'monochromeMode');
|
|
const [twitterEmoji, setTwitterEmoji] = useSetting(settingsAtom, 'twitterEmoji');
|
|
|
|
return (
|
|
<Box direction="Column" gap="100">
|
|
<Text size="L400">{t('Settings.appearance')}</Text>
|
|
<SequenceCard className={SequenceCardStyle} variant="Background" direction="Column">
|
|
<SettingTile title={t('Settings.theme')} after={<ThemeSelect />} />
|
|
</SequenceCard>
|
|
|
|
<SequenceCard className={SequenceCardStyle} variant="Background" direction="Column">
|
|
<SettingTile
|
|
title={t('Settings.monochrome_mode')}
|
|
after={<Switch variant="Primary" value={monochromeMode} onChange={setMonochromeMode} />}
|
|
/>
|
|
</SequenceCard>
|
|
|
|
<SequenceCard className={SequenceCardStyle} variant="Background" direction="Column">
|
|
<SettingTile
|
|
title={t('Settings.twitter_emoji')}
|
|
after={<Switch variant="Primary" value={twitterEmoji} onChange={setTwitterEmoji} />}
|
|
/>
|
|
</SequenceCard>
|
|
|
|
<SequenceCard className={SequenceCardStyle} variant="Background" direction="Column">
|
|
<SettingTile title={t('Settings.page_zoom')} after={<PageZoomInput />} />
|
|
</SequenceCard>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
function Editor() {
|
|
const { t } = useTranslation();
|
|
const [enterForNewline, setEnterForNewline] = useSetting(settingsAtom, 'enterForNewline');
|
|
const [isMarkdown, setIsMarkdown] = useSetting(settingsAtom, 'isMarkdown');
|
|
const [hideActivity, setHideActivity] = useSetting(settingsAtom, 'hideActivity');
|
|
|
|
return (
|
|
<Box direction="Column" gap="100">
|
|
<Text size="L400">{t('Settings.editor')}</Text>
|
|
<SequenceCard className={SequenceCardStyle} variant="Background" direction="Column">
|
|
<SettingTile
|
|
title={t('Settings.enter_newline')}
|
|
description={t('Settings.enter_newline_desc', {
|
|
key: isMacOS() ? KeySymbol.Command : 'Ctrl',
|
|
})}
|
|
after={<Switch variant="Primary" value={enterForNewline} onChange={setEnterForNewline} />}
|
|
/>
|
|
</SequenceCard>
|
|
<SequenceCard className={SequenceCardStyle} variant="Background" direction="Column">
|
|
<SettingTile
|
|
title={t('Settings.markdown')}
|
|
after={<Switch variant="Primary" value={isMarkdown} onChange={setIsMarkdown} />}
|
|
/>
|
|
</SequenceCard>
|
|
<SequenceCard className={SequenceCardStyle} variant="Background" direction="Column">
|
|
<SettingTile
|
|
title={t('Settings.hide_activity')}
|
|
description={t('Settings.hide_activity_desc')}
|
|
after={<Switch variant="Primary" value={hideActivity} onChange={setHideActivity} />}
|
|
/>
|
|
</SequenceCard>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
function Messages() {
|
|
const { t } = useTranslation();
|
|
const [hideMembershipEvents, setHideMembershipEvents] = useSetting(
|
|
settingsAtom,
|
|
'hideMembershipEvents'
|
|
);
|
|
const [hideNickAvatarEvents, setHideNickAvatarEvents] = useSetting(
|
|
settingsAtom,
|
|
'hideNickAvatarEvents'
|
|
);
|
|
const [mediaAutoLoad, setMediaAutoLoad] = useSetting(settingsAtom, 'mediaAutoLoad');
|
|
const [urlPreview, setUrlPreview] = useSetting(settingsAtom, 'urlPreview');
|
|
const [encUrlPreview, setEncUrlPreview] = useSetting(settingsAtom, 'encUrlPreview');
|
|
const [showHiddenEvents, setShowHiddenEvents] = useSetting(settingsAtom, 'showHiddenEvents');
|
|
|
|
return (
|
|
<Box direction="Column" gap="100">
|
|
<Text size="L400">{t('Settings.messages')}</Text>
|
|
<SequenceCard className={SequenceCardStyle} variant="Background" direction="Column">
|
|
<SettingTile
|
|
title={t('Settings.hide_membership')}
|
|
after={
|
|
<Switch
|
|
variant="Primary"
|
|
value={hideMembershipEvents}
|
|
onChange={setHideMembershipEvents}
|
|
/>
|
|
}
|
|
/>
|
|
</SequenceCard>
|
|
<SequenceCard className={SequenceCardStyle} variant="Background" direction="Column">
|
|
<SettingTile
|
|
title={t('Settings.hide_profile')}
|
|
after={
|
|
<Switch
|
|
variant="Primary"
|
|
value={hideNickAvatarEvents}
|
|
onChange={setHideNickAvatarEvents}
|
|
/>
|
|
}
|
|
/>
|
|
</SequenceCard>
|
|
<SequenceCard className={SequenceCardStyle} variant="Background" direction="Column">
|
|
<SettingTile
|
|
title={t('Settings.disable_media_auto_load')}
|
|
after={
|
|
<Switch
|
|
variant="Primary"
|
|
value={!mediaAutoLoad}
|
|
onChange={(v) => setMediaAutoLoad(!v)}
|
|
/>
|
|
}
|
|
/>
|
|
</SequenceCard>
|
|
<SequenceCard className={SequenceCardStyle} variant="Background" direction="Column">
|
|
<SettingTile
|
|
title={t('Settings.url_preview')}
|
|
after={<Switch variant="Primary" value={urlPreview} onChange={setUrlPreview} />}
|
|
/>
|
|
</SequenceCard>
|
|
<SequenceCard className={SequenceCardStyle} variant="Background" direction="Column">
|
|
<SettingTile
|
|
title={t('Settings.url_preview_encrypted')}
|
|
after={<Switch variant="Primary" value={encUrlPreview} onChange={setEncUrlPreview} />}
|
|
/>
|
|
</SequenceCard>
|
|
<SequenceCard className={SequenceCardStyle} variant="Background" direction="Column">
|
|
<SettingTile
|
|
title={t('Settings.show_hidden_events')}
|
|
after={
|
|
<Switch variant="Primary" value={showHiddenEvents} onChange={setShowHiddenEvents} />
|
|
}
|
|
/>
|
|
</SequenceCard>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
type GeneralProps = {
|
|
requestClose: () => void;
|
|
};
|
|
export function General({ requestClose }: GeneralProps) {
|
|
const { t } = useTranslation();
|
|
return (
|
|
<Page variant="SurfaceVariant">
|
|
<PageHeader outlined={false}>
|
|
<Box grow="Yes" gap="200">
|
|
<Box grow="Yes" alignItems="Center" gap="200">
|
|
<Text size="H3" truncate>
|
|
{t('Settings.general_title')}
|
|
</Text>
|
|
</Box>
|
|
<Box shrink="No">
|
|
<IconButton onClick={requestClose} variant="SurfaceVariant" aria-label={t('Settings.close')}>
|
|
<Icon src={Icons.Cross} />
|
|
</IconButton>
|
|
</Box>
|
|
</Box>
|
|
</PageHeader>
|
|
<Box grow="Yes">
|
|
<Scroll hideTrack visibility="Hover">
|
|
<PageContent style={{ paddingBottom: '1rem' }}>
|
|
<Box direction="Column" gap="700">
|
|
<Appearance />
|
|
<Editor />
|
|
<Messages />
|
|
</Box>
|
|
</PageContent>
|
|
</Scroll>
|
|
</Box>
|
|
</Page>
|
|
);
|
|
}
|