Add setting for page zoom (#1835)

* add setting for page zoom

* parse integer in zoom change listener

* fix zoom input width

* fix null gets saved as page zoom
This commit is contained in:
Ajay Bura 2024-07-23 19:22:53 +05:30 committed by GitHub
parent 3110505b21
commit b387370aaf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 341 additions and 155 deletions

View file

@ -1,13 +1,13 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Input, toRem } from 'folds';
import { isKeyHotkey } from 'is-hotkey';
import './Settings.scss'; import './Settings.scss';
import { clearCacheAndReload, logoutClient } from '../../../client/initMatrix'; import { clearCacheAndReload, logoutClient } from '../../../client/initMatrix';
import cons from '../../../client/state/cons'; import cons from '../../../client/state/cons';
import settings from '../../../client/state/settings'; import settings from '../../../client/state/settings';
import navigation from '../../../client/state/navigation'; import navigation from '../../../client/state/navigation';
import { import { toggleSystemTheme } from '../../../client/action/settings';
toggleSystemTheme,
} from '../../../client/action/settings';
import { usePermissionState } from '../../hooks/usePermission'; import { usePermissionState } from '../../hooks/usePermission';
import Text from '../../atoms/text/Text'; import Text from '../../atoms/text/Text';
@ -55,14 +55,41 @@ function AppearanceSection() {
const [messageLayout, setMessageLayout] = useSetting(settingsAtom, 'messageLayout'); const [messageLayout, setMessageLayout] = useSetting(settingsAtom, 'messageLayout');
const [messageSpacing, setMessageSpacing] = useSetting(settingsAtom, 'messageSpacing'); const [messageSpacing, setMessageSpacing] = useSetting(settingsAtom, 'messageSpacing');
const [twitterEmoji, setTwitterEmoji] = useSetting(settingsAtom, 'twitterEmoji'); const [twitterEmoji, setTwitterEmoji] = useSetting(settingsAtom, 'twitterEmoji');
const [pageZoom, setPageZoom] = useSetting(settingsAtom, 'pageZoom');
const [isMarkdown, setIsMarkdown] = useSetting(settingsAtom, 'isMarkdown'); const [isMarkdown, setIsMarkdown] = useSetting(settingsAtom, 'isMarkdown');
const [hideMembershipEvents, setHideMembershipEvents] = useSetting(settingsAtom, 'hideMembershipEvents'); const [hideMembershipEvents, setHideMembershipEvents] = useSetting(
const [hideNickAvatarEvents, setHideNickAvatarEvents] = useSetting(settingsAtom, 'hideNickAvatarEvents'); settingsAtom,
'hideMembershipEvents'
);
const [hideNickAvatarEvents, setHideNickAvatarEvents] = useSetting(
settingsAtom,
'hideNickAvatarEvents'
);
const [mediaAutoLoad, setMediaAutoLoad] = useSetting(settingsAtom, 'mediaAutoLoad'); const [mediaAutoLoad, setMediaAutoLoad] = useSetting(settingsAtom, 'mediaAutoLoad');
const [urlPreview, setUrlPreview] = useSetting(settingsAtom, 'urlPreview'); const [urlPreview, setUrlPreview] = useSetting(settingsAtom, 'urlPreview');
const [encUrlPreview, setEncUrlPreview] = useSetting(settingsAtom, 'encUrlPreview'); const [encUrlPreview, setEncUrlPreview] = useSetting(settingsAtom, 'encUrlPreview');
const [showHiddenEvents, setShowHiddenEvents] = useSetting(settingsAtom, 'showHiddenEvents'); const [showHiddenEvents, setShowHiddenEvents] = useSetting(settingsAtom, 'showHiddenEvents');
const spacings = ['0', '100', '200', '300', '400', '500'] const spacings = ['0', '100', '200', '300', '400', '500'];
const [currentZoom, setCurrentZoom] = useState(`${pageZoom}`);
const handleZoomChange = (evt) => {
setCurrentZoom(evt.target.value);
};
const handleZoomEnter = (evt) => {
if (isKeyHotkey('escape', evt)) {
evt.stopPropagation();
setCurrentZoom(pageZoom);
}
if (isKeyHotkey('enter', evt)) {
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);
}
};
return ( return (
<div className="settings-appearance"> <div className="settings-appearance">
@ -70,17 +97,20 @@ function AppearanceSection() {
<MenuHeader>Theme</MenuHeader> <MenuHeader>Theme</MenuHeader>
<SettingTile <SettingTile
title="Follow system theme" title="Follow system theme"
options={( options={
<Toggle <Toggle
isActive={settings.useSystemTheme} isActive={settings.useSystemTheme}
onToggle={() => { toggleSystemTheme(); updateState({}); }} onToggle={() => {
toggleSystemTheme();
updateState({});
}}
/> />
)} }
content={<Text variant="b3">Use light or dark mode based on the system settings.</Text>} content={<Text variant="b3">Use light or dark mode based on the system settings.</Text>}
/> />
<SettingTile <SettingTile
title="Theme" title="Theme"
content={( content={
<SegmentedControls <SegmentedControls
selected={settings.useSystemTheme ? -1 : settings.getThemeIndex()} selected={settings.useSystemTheme ? -1 : settings.getThemeIndex()}
segments={[ segments={[
@ -95,18 +125,38 @@ function AppearanceSection() {
updateState({}); updateState({});
}} }}
/> />
)} }
/> />
<SettingTile <SettingTile
title="Use Twitter Emoji" title="Use Twitter Emoji"
options={( options={
<Toggle <Toggle isActive={twitterEmoji} onToggle={() => setTwitterEmoji(!twitterEmoji)} />
isActive={twitterEmoji} }
onToggle={() => setTwitterEmoji(!twitterEmoji)}
/>
)}
content={<Text variant="b3">Use Twitter emoji instead of system emoji.</Text>} content={<Text variant="b3">Use Twitter emoji instead of system emoji.</Text>}
/> />
<SettingTile
title="Page Zoom"
options={
<Input
style={{ width: toRem(150) }}
variant={pageZoom === parseInt(currentZoom, 10) ? 'Background' : 'Primary'}
size="400"
type="number"
min="75"
max="150"
value={currentZoom}
onChange={handleZoomChange}
onKeyDown={handleZoomEnter}
outlined
after={<Text variant="b2">%</Text>}
/>
}
content={
<Text variant="b3">
Change page zoom to scale user interface between 75% to 150%. Default: 100%
</Text>
}
/>
</div> </div>
<div className="settings-appearance__card"> <div className="settings-appearance__card">
<MenuHeader>Room messages</MenuHeader> <MenuHeader>Room messages</MenuHeader>
@ -114,113 +164,106 @@ function AppearanceSection() {
title="Message Layout" title="Message Layout"
content={ content={
<SegmentedControls <SegmentedControls
selected={messageLayout} selected={messageLayout}
segments={[ segments={[{ text: 'Modern' }, { text: 'Compact' }, { text: 'Bubble' }]}
{ text: 'Modern' }, onSelect={(index) => setMessageLayout(index)}
{ text: 'Compact' }, />
{ text: 'Bubble' },
]}
onSelect={(index) => setMessageLayout(index)}
/>
} }
/> />
<SettingTile <SettingTile
title="Message Spacing" title="Message Spacing"
content={ content={
<SegmentedControls <SegmentedControls
selected={spacings.findIndex((s) => s === messageSpacing)} selected={spacings.findIndex((s) => s === messageSpacing)}
segments={[ segments={[
{ text: 'No' }, { text: 'No' },
{ text: 'XXS' }, { text: 'XXS' },
{ text: 'XS' }, { text: 'XS' },
{ text: 'S' }, { text: 'S' },
{ text: 'M' }, { text: 'M' },
{ text: 'L' }, { text: 'L' },
]} ]}
onSelect={(index) => { onSelect={(index) => {
setMessageSpacing(spacings[index]) setMessageSpacing(spacings[index]);
}} }}
/> />
} }
/> />
<SettingTile <SettingTile
title="Use ENTER for Newline" title="Use ENTER for Newline"
options={( options={
<Toggle <Toggle
isActive={enterForNewline} isActive={enterForNewline}
onToggle={() => setEnterForNewline(!enterForNewline) } onToggle={() => setEnterForNewline(!enterForNewline)}
/> />
)} }
content={<Text variant="b3">{`Use ${isMacOS() ? KeySymbol.Command : 'Ctrl'} + ENTER to send message and ENTER for newline.`}</Text>} content={
<Text variant="b3">{`Use ${
isMacOS() ? KeySymbol.Command : 'Ctrl'
} + ENTER to send message and ENTER for newline.`}</Text>
}
/> />
<SettingTile <SettingTile
title="Markdown formatting" title="Markdown formatting"
options={( options={<Toggle isActive={isMarkdown} onToggle={() => setIsMarkdown(!isMarkdown)} />}
<Toggle
isActive={isMarkdown}
onToggle={() => setIsMarkdown(!isMarkdown) }
/>
)}
content={<Text variant="b3">Format messages with markdown syntax before sending.</Text>} content={<Text variant="b3">Format messages with markdown syntax before sending.</Text>}
/> />
<SettingTile <SettingTile
title="Hide membership events" title="Hide membership events"
options={( options={
<Toggle <Toggle
isActive={hideMembershipEvents} isActive={hideMembershipEvents}
onToggle={() => setHideMembershipEvents(!hideMembershipEvents)} onToggle={() => setHideMembershipEvents(!hideMembershipEvents)}
/> />
)} }
content={<Text variant="b3">Hide membership change messages from room timeline. (Join, Leave, Invite, Kick and Ban)</Text>} content={
<Text variant="b3">
Hide membership change messages from room timeline. (Join, Leave, Invite, Kick and
Ban)
</Text>
}
/> />
<SettingTile <SettingTile
title="Hide nick/avatar events" title="Hide nick/avatar events"
options={( options={
<Toggle <Toggle
isActive={hideNickAvatarEvents} isActive={hideNickAvatarEvents}
onToggle={() => setHideNickAvatarEvents(!hideNickAvatarEvents)} onToggle={() => setHideNickAvatarEvents(!hideNickAvatarEvents)}
/> />
)} }
content={<Text variant="b3">Hide nick and avatar change messages from room timeline.</Text>} content={
<Text variant="b3">Hide nick and avatar change messages from room timeline.</Text>
}
/> />
<SettingTile <SettingTile
title="Disable media auto load" title="Disable media auto load"
options={( options={
<Toggle <Toggle isActive={!mediaAutoLoad} onToggle={() => setMediaAutoLoad(!mediaAutoLoad)} />
isActive={!mediaAutoLoad} }
onToggle={() => setMediaAutoLoad(!mediaAutoLoad)} content={
/> <Text variant="b3">Prevent images and videos from auto loading to save bandwidth.</Text>
)} }
content={<Text variant="b3">Prevent images and videos from auto loading to save bandwidth.</Text>}
/> />
<SettingTile <SettingTile
title="Url Preview" title="Url Preview"
options={( options={<Toggle isActive={urlPreview} onToggle={() => setUrlPreview(!urlPreview)} />}
<Toggle
isActive={urlPreview}
onToggle={() => setUrlPreview(!urlPreview)}
/>
)}
content={<Text variant="b3">Show url preview for link in messages.</Text>} content={<Text variant="b3">Show url preview for link in messages.</Text>}
/> />
<SettingTile <SettingTile
title="Url Preview in Encrypted Room" title="Url Preview in Encrypted Room"
options={( options={
<Toggle <Toggle isActive={encUrlPreview} onToggle={() => setEncUrlPreview(!encUrlPreview)} />
isActive={encUrlPreview} }
onToggle={() => setEncUrlPreview(!encUrlPreview)}
/>
)}
content={<Text variant="b3">Show url preview for link in encrypted messages.</Text>} content={<Text variant="b3">Show url preview for link in encrypted messages.</Text>}
/> />
<SettingTile <SettingTile
title="Show hidden events" title="Show hidden events"
options={( options={
<Toggle <Toggle
isActive={showHiddenEvents} isActive={showHiddenEvents}
onToggle={() => setShowHiddenEvents(!showHiddenEvents)} onToggle={() => setShowHiddenEvents(!showHiddenEvents)}
/> />
)} }
content={<Text variant="b3">Show hidden state and message events.</Text>} content={<Text variant="b3">Show hidden state and message events.</Text>}
/> />
</div> </div>
@ -229,17 +272,27 @@ function AppearanceSection() {
} }
function NotificationsSection() { function NotificationsSection() {
const notifPermission = usePermissionState('notifications', window.Notification?.permission ?? "denied"); const notifPermission = usePermissionState(
const [showNotifications, setShowNotifications] = useSetting(settingsAtom, 'showNotifications') 'notifications',
const [isNotificationSounds, setIsNotificationSounds] = useSetting(settingsAtom, 'isNotificationSounds') window.Notification?.permission ?? 'denied'
);
const [showNotifications, setShowNotifications] = useSetting(settingsAtom, 'showNotifications');
const [isNotificationSounds, setIsNotificationSounds] = useSetting(
settingsAtom,
'isNotificationSounds'
);
const renderOptions = () => { const renderOptions = () => {
if (window.Notification === undefined) { if (window.Notification === undefined) {
return <Text className="settings-notifications__not-supported">Not supported in this browser.</Text>; return (
<Text className="settings-notifications__not-supported">
Not supported in this browser.
</Text>
);
} }
if (notifPermission === 'denied') { if (notifPermission === 'denied') {
return <Text>Permission Denied</Text> return <Text>Permission Denied</Text>;
} }
if (notifPermission === 'granted') { if (notifPermission === 'granted') {
@ -256,9 +309,11 @@ function NotificationsSection() {
return ( return (
<Button <Button
variant="primary" variant="primary"
onClick={() => window.Notification.requestPermission().then(() => { onClick={() =>
setShowNotifications(window.Notification?.permission === 'granted'); window.Notification.requestPermission().then(() => {
})} setShowNotifications(window.Notification?.permission === 'granted');
})
}
> >
Request permission Request permission
</Button> </Button>
@ -276,12 +331,12 @@ function NotificationsSection() {
/> />
<SettingTile <SettingTile
title="Notification Sound" title="Notification Sound"
options={( options={
<Toggle <Toggle
isActive={isNotificationSounds} isActive={isNotificationSounds}
onToggle={() => setIsNotificationSounds(!isNotificationSounds)} onToggle={() => setIsNotificationSounds(!isNotificationSounds)}
/> />
)} }
content={<Text variant="b3">Play sound when new messages arrive.</Text>} content={<Text variant="b3">Play sound when new messages arrive.</Text>}
/> />
</div> </div>
@ -295,8 +350,12 @@ function NotificationsSection() {
function EmojiSection() { function EmojiSection() {
return ( return (
<> <>
<div className="settings-emoji__card"><ImagePackUser /></div> <div className="settings-emoji__card">
<div className="settings-emoji__card"><ImagePackGlobal /></div> <ImagePackUser />
</div>
<div className="settings-emoji__card">
<ImagePackGlobal />
</div>
</> </>
); );
} }
@ -314,21 +373,29 @@ function SecuritySection() {
<MenuHeader>Export/Import encryption keys</MenuHeader> <MenuHeader>Export/Import encryption keys</MenuHeader>
<SettingTile <SettingTile
title="Export E2E room keys" title="Export E2E room keys"
content={( content={
<> <>
<Text variant="b3">Export end-to-end encryption room keys to decrypt old messages in other session. In order to encrypt keys you need to set a password, which will be used while importing.</Text> <Text variant="b3">
Export end-to-end encryption room keys to decrypt old messages in other session. In
order to encrypt keys you need to set a password, which will be used while
importing.
</Text>
<ExportE2ERoomKeys /> <ExportE2ERoomKeys />
</> </>
)} }
/> />
<SettingTile <SettingTile
title="Import E2E room keys" title="Import E2E room keys"
content={( content={
<> <>
<Text variant="b3">{'To decrypt older messages, Export E2EE room keys from Element (Settings > Security & Privacy > Encryption > Cryptography) and import them here. Imported keys are encrypted so you\'ll have to enter the password you set in order to decrypt it.'}</Text> <Text variant="b3">
{
"To decrypt older messages, Export E2EE room keys from Element (Settings > Security & Privacy > Encryption > Cryptography) and import them here. Imported keys are encrypted so you'll have to enter the password you set in order to decrypt it."
}
</Text>
<ImportE2ERoomKeys /> <ImportE2ERoomKeys />
</> </>
)} }
/> />
</div> </div>
</div> </div>
@ -347,14 +414,21 @@ function AboutSection() {
<div> <div>
<Text variant="h2" weight="medium"> <Text variant="h2" weight="medium">
Cinny Cinny
<span className="text text-b3" style={{ margin: '0 var(--sp-extra-tight)' }}>{`v${cons.version}`}</span> <span
className="text text-b3"
style={{ margin: '0 var(--sp-extra-tight)' }}
>{`v${cons.version}`}</span>
</Text> </Text>
<Text>Yet another matrix client</Text> <Text>Yet another matrix client</Text>
<div className="settings-about__btns"> <div className="settings-about__btns">
<Button onClick={() => window.open('https://github.com/ajbura/cinny')}>Source code</Button> <Button onClick={() => window.open('https://github.com/ajbura/cinny')}>
Source code
</Button>
<Button onClick={() => window.open('https://cinny.in/#sponsor')}>Support</Button> <Button onClick={() => window.open('https://cinny.in/#sponsor')}>Support</Button>
<Button onClick={() => clearCacheAndReload(mx)} variant="danger">Clear cache & reload</Button> <Button onClick={() => clearCacheAndReload(mx)} variant="danger">
Clear cache & reload
</Button>
</div> </div>
</div> </div>
</div> </div>
@ -364,20 +438,104 @@ function AboutSection() {
<div className="settings-about__credits"> <div className="settings-about__credits">
<ul> <ul>
<li> <li>
{/* eslint-disable-next-line react/jsx-one-expression-per-line */ } {/* eslint-disable-next-line react/jsx-one-expression-per-line */}
<Text>The <a href="https://github.com/matrix-org/matrix-js-sdk" rel="noreferrer noopener" target="_blank">matrix-js-sdk</a> is © <a href="https://matrix.org/foundation" rel="noreferrer noopener" target="_blank">The Matrix.org Foundation C.I.C</a> used under the terms of <a href="http://www.apache.org/licenses/LICENSE-2.0" rel="noreferrer noopener" target="_blank">Apache 2.0</a>.</Text> <Text>
The{' '}
<a
href="https://github.com/matrix-org/matrix-js-sdk"
rel="noreferrer noopener"
target="_blank"
>
matrix-js-sdk
</a>{' '}
is ©{' '}
<a href="https://matrix.org/foundation" rel="noreferrer noopener" target="_blank">
The Matrix.org Foundation C.I.C
</a>{' '}
used under the terms of{' '}
<a
href="http://www.apache.org/licenses/LICENSE-2.0"
rel="noreferrer noopener"
target="_blank"
>
Apache 2.0
</a>
.
</Text>
</li> </li>
<li> <li>
{/* eslint-disable-next-line react/jsx-one-expression-per-line */ } {/* eslint-disable-next-line react/jsx-one-expression-per-line */}
<Text>The <a href="https://github.com/mozilla/twemoji-colr" target="_blank" rel="noreferrer noopener">twemoji-colr</a> font is © <a href="https://mozilla.org/" target="_blank" rel="noreferrer noopener">Mozilla Foundation</a> used under the terms of <a href="http://www.apache.org/licenses/LICENSE-2.0" target="_blank" rel="noreferrer noopener">Apache 2.0</a>.</Text> <Text>
The{' '}
<a
href="https://github.com/mozilla/twemoji-colr"
target="_blank"
rel="noreferrer noopener"
>
twemoji-colr
</a>{' '}
font is ©{' '}
<a href="https://mozilla.org/" target="_blank" rel="noreferrer noopener">
Mozilla Foundation
</a>{' '}
used under the terms of{' '}
<a
href="http://www.apache.org/licenses/LICENSE-2.0"
target="_blank"
rel="noreferrer noopener"
>
Apache 2.0
</a>
.
</Text>
</li> </li>
<li> <li>
{/* eslint-disable-next-line react/jsx-one-expression-per-line */ } {/* eslint-disable-next-line react/jsx-one-expression-per-line */}
<Text>The <a href="https://twemoji.twitter.com" target="_blank" rel="noreferrer noopener">Twemoji</a> emoji art is © <a href="https://twemoji.twitter.com" target="_blank" rel="noreferrer noopener">Twitter, Inc and other contributors</a> used under the terms of <a href="https://creativecommons.org/licenses/by/4.0/" target="_blank" rel="noreferrer noopener">CC-BY 4.0</a>.</Text> <Text>
The{' '}
<a href="https://twemoji.twitter.com" target="_blank" rel="noreferrer noopener">
Twemoji
</a>{' '}
emoji art is ©{' '}
<a href="https://twemoji.twitter.com" target="_blank" rel="noreferrer noopener">
Twitter, Inc and other contributors
</a>{' '}
used under the terms of{' '}
<a
href="https://creativecommons.org/licenses/by/4.0/"
target="_blank"
rel="noreferrer noopener"
>
CC-BY 4.0
</a>
.
</Text>
</li> </li>
<li> <li>
{/* eslint-disable-next-line react/jsx-one-expression-per-line */ } {/* eslint-disable-next-line react/jsx-one-expression-per-line */}
<Text>The <a href="https://material.io/design/sound/sound-resources.html" target="_blank" rel="noreferrer noopener">Material sound resources</a> are © <a href="https://google.com" target="_blank" rel="noreferrer noopener">Google</a> used under the terms of <a href="https://creativecommons.org/licenses/by/4.0/" target="_blank" rel="noreferrer noopener">CC-BY 4.0</a>.</Text> <Text>
The{' '}
<a
href="https://material.io/design/sound/sound-resources.html"
target="_blank"
rel="noreferrer noopener"
>
Material sound resources
</a>{' '}
are ©{' '}
<a href="https://google.com" target="_blank" rel="noreferrer noopener">
Google
</a>{' '}
used under the terms of{' '}
<a
href="https://creativecommons.org/licenses/by/4.0/"
target="_blank"
rel="noreferrer noopener"
>
CC-BY 4.0
</a>
.
</Text>
</li> </li>
</ul> </ul>
</div> </div>
@ -393,32 +551,38 @@ export const tabText = {
SECURITY: 'Security', SECURITY: 'Security',
ABOUT: 'About', ABOUT: 'About',
}; };
const tabItems = [{ const tabItems = [
text: tabText.APPEARANCE, {
iconSrc: SunIC, text: tabText.APPEARANCE,
disabled: false, iconSrc: SunIC,
render: () => <AppearanceSection />, disabled: false,
}, { render: () => <AppearanceSection />,
text: tabText.NOTIFICATIONS, },
iconSrc: BellIC, {
disabled: false, text: tabText.NOTIFICATIONS,
render: () => <NotificationsSection />, iconSrc: BellIC,
}, { disabled: false,
text: tabText.EMOJI, render: () => <NotificationsSection />,
iconSrc: EmojiIC, },
disabled: false, {
render: () => <EmojiSection />, text: tabText.EMOJI,
}, { iconSrc: EmojiIC,
text: tabText.SECURITY, disabled: false,
iconSrc: LockIC, render: () => <EmojiSection />,
disabled: false, },
render: () => <SecuritySection />, {
}, { text: tabText.SECURITY,
text: tabText.ABOUT, iconSrc: LockIC,
iconSrc: InfoIC, disabled: false,
disabled: false, render: () => <SecuritySection />,
render: () => <AboutSection />, },
}]; {
text: tabText.ABOUT,
iconSrc: InfoIC,
disabled: false,
render: () => <AboutSection />,
},
];
function useWindowToggle(setSelectedTab) { function useWindowToggle(setSelectedTab) {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
@ -447,7 +611,14 @@ function Settings() {
const handleTabChange = (tabItem) => setSelectedTab(tabItem); const handleTabChange = (tabItem) => setSelectedTab(tabItem);
const handleLogout = async () => { const handleLogout = async () => {
if (await confirmDialog('Logout', 'Are you sure that you want to logout your session?', 'Logout', 'danger')) { if (
await confirmDialog(
'Logout',
'Are you sure that you want to logout your session?',
'Logout',
'danger'
)
) {
logoutClient(mx); logoutClient(mx);
} }
}; };
@ -456,15 +627,19 @@ function Settings() {
<PopupWindow <PopupWindow
isOpen={isOpen} isOpen={isOpen}
className="settings-window" className="settings-window"
title={<Text variant="s1" weight="medium" primary>Settings</Text>} title={
contentOptions={( <Text variant="s1" weight="medium" primary>
Settings
</Text>
}
contentOptions={
<> <>
<Button variant="danger" iconSrc={PowerIC} onClick={handleLogout}> <Button variant="danger" iconSrc={PowerIC} onClick={handleLogout}>
Logout Logout
</Button> </Button>
<IconButton src={CrossIC} onClick={requestClose} tooltip="Close" /> <IconButton src={CrossIC} onClick={requestClose} tooltip="Close" />
</> </>
)} }
onRequestClose={requestClose} onRequestClose={requestClose}
> >
{isOpen && ( {isOpen && (
@ -475,9 +650,7 @@ function Settings() {
defaultSelected={tabItems.findIndex((tab) => tab.text === selectedTab.text)} defaultSelected={tabItems.findIndex((tab) => tab.text === selectedTab.text)}
onSelect={handleTabChange} onSelect={handleTabChange}
/> />
<div className="settings-window__cards-wrapper"> <div className="settings-window__cards-wrapper">{selectedTab.render()}</div>
{ selectedTab.render() }
</div>
</div> </div>
)} )}
</PopupWindow> </PopupWindow>

View file

@ -26,6 +26,30 @@ import { getMxIdLocalPart } from '../../utils/matrix';
import { useSelectedRoom } from '../../hooks/router/useSelectedRoom'; import { useSelectedRoom } from '../../hooks/router/useSelectedRoom';
import { useInboxNotificationsSelected } from '../../hooks/router/useInbox'; import { useInboxNotificationsSelected } from '../../hooks/router/useInbox';
function SystemEmojiFeature() {
const [twitterEmoji] = useSetting(settingsAtom, 'twitterEmoji');
if (twitterEmoji) {
document.documentElement.style.setProperty('--font-emoji', 'Twemoji');
} else {
document.documentElement.style.setProperty('--font-emoji', 'Twemoji_DISABLED');
}
return null;
}
function PageZoomFeature() {
const [pageZoom] = useSetting(settingsAtom, 'pageZoom');
if (pageZoom === 100) {
document.documentElement.style.removeProperty('font-size');
} else {
document.documentElement.style.setProperty('font-size', `calc(1em * ${pageZoom / 100})`);
}
return null;
}
function FaviconUpdater() { function FaviconUpdater() {
const roomToUnread = useAtomValue(roomToUnreadAtom); const roomToUnread = useAtomValue(roomToUnreadAtom);
@ -233,6 +257,8 @@ type ClientNonUIFeaturesProps = {
export function ClientNonUIFeatures({ children }: ClientNonUIFeaturesProps) { export function ClientNonUIFeatures({ children }: ClientNonUIFeaturesProps) {
return ( return (
<> <>
<SystemEmojiFeature />
<PageZoomFeature />
<FaviconUpdater /> <FaviconUpdater />
<InviteNotifications /> <InviteNotifications />
<MessageNotifications /> <MessageNotifications />

View file

@ -32,25 +32,11 @@ import { SpecVersions } from './SpecVersions';
import Windows from '../../organisms/pw/Windows'; import Windows from '../../organisms/pw/Windows';
import Dialogs from '../../organisms/pw/Dialogs'; import Dialogs from '../../organisms/pw/Dialogs';
import ReusableContextMenu from '../../atoms/context-menu/ReusableContextMenu'; import ReusableContextMenu from '../../atoms/context-menu/ReusableContextMenu';
import { useSetting } from '../../state/hooks/settings';
import { settingsAtom } from '../../state/settings';
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback'; import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
import { useSyncState } from '../../hooks/useSyncState'; import { useSyncState } from '../../hooks/useSyncState';
import { stopPropagation } from '../../utils/keyboard'; import { stopPropagation } from '../../utils/keyboard';
import { SyncStatus } from './SyncStatus'; import { SyncStatus } from './SyncStatus';
function SystemEmojiFeature() {
const [twitterEmoji] = useSetting(settingsAtom, 'twitterEmoji');
if (twitterEmoji) {
document.documentElement.style.setProperty('--font-emoji', 'Twemoji');
} else {
document.documentElement.style.setProperty('--font-emoji', 'Twemoji_DISABLED');
}
return null;
}
function ClientRootLoading() { function ClientRootLoading() {
return ( return (
<SplashScreen> <SplashScreen>
@ -198,7 +184,7 @@ export function ClientRoot({ children }: ClientRootProps) {
{startState.status === AsyncStatus.Error && ( {startState.status === AsyncStatus.Error && (
<Text>{`Failed to load. ${startState.error.message}`}</Text> <Text>{`Failed to load. ${startState.error.message}`}</Text>
)} )}
<Button variant="Critical" onClick={loadMatrix}> <Button variant="Critical" onClick={mx ? () => startMatrix(mx) : loadMatrix}>
<Text as="span" size="B400"> <Text as="span" size="B400">
Retry Retry
</Text> </Text>
@ -220,7 +206,6 @@ export function ClientRoot({ children }: ClientRootProps) {
<Windows /> <Windows />
<Dialogs /> <Dialogs />
<ReusableContextMenu /> <ReusableContextMenu />
<SystemEmojiFeature />
</MediaConfigProvider> </MediaConfigProvider>
</CapabilitiesProvider> </CapabilitiesProvider>
)} )}

View file

@ -10,6 +10,7 @@ export interface Settings {
isMarkdown: boolean; isMarkdown: boolean;
editorToolbar: boolean; editorToolbar: boolean;
twitterEmoji: boolean; twitterEmoji: boolean;
pageZoom: number;
isPeopleDrawer: boolean; isPeopleDrawer: boolean;
memberSortFilterIndex: number; memberSortFilterIndex: number;
@ -33,6 +34,7 @@ const defaultSettings: Settings = {
isMarkdown: true, isMarkdown: true,
editorToolbar: false, editorToolbar: false,
twitterEmoji: false, twitterEmoji: false,
pageZoom: 100,
isPeopleDrawer: true, isPeopleDrawer: true,
memberSortFilterIndex: 0, memberSortFilterIndex: 0,