forked from mirror/cinny
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:
parent
3110505b21
commit
b387370aaf
4 changed files with 341 additions and 155 deletions
|
@ -1,13 +1,13 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { Input, toRem } from 'folds';
|
||||
import { isKeyHotkey } from 'is-hotkey';
|
||||
import './Settings.scss';
|
||||
|
||||
import { clearCacheAndReload, logoutClient } from '../../../client/initMatrix';
|
||||
import cons from '../../../client/state/cons';
|
||||
import settings from '../../../client/state/settings';
|
||||
import navigation from '../../../client/state/navigation';
|
||||
import {
|
||||
toggleSystemTheme,
|
||||
} from '../../../client/action/settings';
|
||||
import { toggleSystemTheme } from '../../../client/action/settings';
|
||||
import { usePermissionState } from '../../hooks/usePermission';
|
||||
|
||||
import Text from '../../atoms/text/Text';
|
||||
|
@ -55,14 +55,41 @@ function AppearanceSection() {
|
|||
const [messageLayout, setMessageLayout] = useSetting(settingsAtom, 'messageLayout');
|
||||
const [messageSpacing, setMessageSpacing] = useSetting(settingsAtom, 'messageSpacing');
|
||||
const [twitterEmoji, setTwitterEmoji] = useSetting(settingsAtom, 'twitterEmoji');
|
||||
const [pageZoom, setPageZoom] = useSetting(settingsAtom, 'pageZoom');
|
||||
const [isMarkdown, setIsMarkdown] = useSetting(settingsAtom, 'isMarkdown');
|
||||
const [hideMembershipEvents, setHideMembershipEvents] = useSetting(settingsAtom, 'hideMembershipEvents');
|
||||
const [hideNickAvatarEvents, setHideNickAvatarEvents] = useSetting(settingsAtom, 'hideNickAvatarEvents');
|
||||
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');
|
||||
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 (
|
||||
<div className="settings-appearance">
|
||||
|
@ -70,17 +97,20 @@ function AppearanceSection() {
|
|||
<MenuHeader>Theme</MenuHeader>
|
||||
<SettingTile
|
||||
title="Follow system theme"
|
||||
options={(
|
||||
options={
|
||||
<Toggle
|
||||
isActive={settings.useSystemTheme}
|
||||
onToggle={() => { toggleSystemTheme(); updateState({}); }}
|
||||
onToggle={() => {
|
||||
toggleSystemTheme();
|
||||
updateState({});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
}
|
||||
content={<Text variant="b3">Use light or dark mode based on the system settings.</Text>}
|
||||
/>
|
||||
<SettingTile
|
||||
title="Theme"
|
||||
content={(
|
||||
content={
|
||||
<SegmentedControls
|
||||
selected={settings.useSystemTheme ? -1 : settings.getThemeIndex()}
|
||||
segments={[
|
||||
|
@ -95,18 +125,38 @@ function AppearanceSection() {
|
|||
updateState({});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
}
|
||||
/>
|
||||
<SettingTile
|
||||
title="Use Twitter Emoji"
|
||||
options={(
|
||||
<Toggle
|
||||
isActive={twitterEmoji}
|
||||
onToggle={() => setTwitterEmoji(!twitterEmoji)}
|
||||
/>
|
||||
)}
|
||||
options={
|
||||
<Toggle isActive={twitterEmoji} onToggle={() => setTwitterEmoji(!twitterEmoji)} />
|
||||
}
|
||||
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 className="settings-appearance__card">
|
||||
<MenuHeader>Room messages</MenuHeader>
|
||||
|
@ -114,113 +164,106 @@ function AppearanceSection() {
|
|||
title="Message Layout"
|
||||
content={
|
||||
<SegmentedControls
|
||||
selected={messageLayout}
|
||||
segments={[
|
||||
{ text: 'Modern' },
|
||||
{ text: 'Compact' },
|
||||
{ text: 'Bubble' },
|
||||
]}
|
||||
onSelect={(index) => setMessageLayout(index)}
|
||||
/>
|
||||
selected={messageLayout}
|
||||
segments={[{ text: 'Modern' }, { text: 'Compact' }, { text: 'Bubble' }]}
|
||||
onSelect={(index) => setMessageLayout(index)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SettingTile
|
||||
title="Message Spacing"
|
||||
content={
|
||||
<SegmentedControls
|
||||
selected={spacings.findIndex((s) => s === messageSpacing)}
|
||||
segments={[
|
||||
{ text: 'No' },
|
||||
{ text: 'XXS' },
|
||||
{ text: 'XS' },
|
||||
{ text: 'S' },
|
||||
{ text: 'M' },
|
||||
{ text: 'L' },
|
||||
]}
|
||||
onSelect={(index) => {
|
||||
setMessageSpacing(spacings[index])
|
||||
}}
|
||||
/>
|
||||
selected={spacings.findIndex((s) => s === messageSpacing)}
|
||||
segments={[
|
||||
{ text: 'No' },
|
||||
{ text: 'XXS' },
|
||||
{ text: 'XS' },
|
||||
{ text: 'S' },
|
||||
{ text: 'M' },
|
||||
{ text: 'L' },
|
||||
]}
|
||||
onSelect={(index) => {
|
||||
setMessageSpacing(spacings[index]);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SettingTile
|
||||
title="Use ENTER for Newline"
|
||||
options={(
|
||||
options={
|
||||
<Toggle
|
||||
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
|
||||
title="Markdown formatting"
|
||||
options={(
|
||||
<Toggle
|
||||
isActive={isMarkdown}
|
||||
onToggle={() => setIsMarkdown(!isMarkdown) }
|
||||
/>
|
||||
)}
|
||||
options={<Toggle isActive={isMarkdown} onToggle={() => setIsMarkdown(!isMarkdown)} />}
|
||||
content={<Text variant="b3">Format messages with markdown syntax before sending.</Text>}
|
||||
/>
|
||||
<SettingTile
|
||||
title="Hide membership events"
|
||||
options={(
|
||||
options={
|
||||
<Toggle
|
||||
isActive={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
|
||||
title="Hide nick/avatar events"
|
||||
options={(
|
||||
options={
|
||||
<Toggle
|
||||
isActive={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
|
||||
title="Disable media auto load"
|
||||
options={(
|
||||
<Toggle
|
||||
isActive={!mediaAutoLoad}
|
||||
onToggle={() => setMediaAutoLoad(!mediaAutoLoad)}
|
||||
/>
|
||||
)}
|
||||
content={<Text variant="b3">Prevent images and videos from auto loading to save bandwidth.</Text>}
|
||||
options={
|
||||
<Toggle isActive={!mediaAutoLoad} onToggle={() => setMediaAutoLoad(!mediaAutoLoad)} />
|
||||
}
|
||||
content={
|
||||
<Text variant="b3">Prevent images and videos from auto loading to save bandwidth.</Text>
|
||||
}
|
||||
/>
|
||||
<SettingTile
|
||||
title="Url Preview"
|
||||
options={(
|
||||
<Toggle
|
||||
isActive={urlPreview}
|
||||
onToggle={() => setUrlPreview(!urlPreview)}
|
||||
/>
|
||||
)}
|
||||
options={<Toggle isActive={urlPreview} onToggle={() => setUrlPreview(!urlPreview)} />}
|
||||
content={<Text variant="b3">Show url preview for link in messages.</Text>}
|
||||
/>
|
||||
<SettingTile
|
||||
title="Url Preview in Encrypted Room"
|
||||
options={(
|
||||
<Toggle
|
||||
isActive={encUrlPreview}
|
||||
onToggle={() => setEncUrlPreview(!encUrlPreview)}
|
||||
/>
|
||||
)}
|
||||
options={
|
||||
<Toggle isActive={encUrlPreview} onToggle={() => setEncUrlPreview(!encUrlPreview)} />
|
||||
}
|
||||
content={<Text variant="b3">Show url preview for link in encrypted messages.</Text>}
|
||||
/>
|
||||
<SettingTile
|
||||
title="Show hidden events"
|
||||
options={(
|
||||
options={
|
||||
<Toggle
|
||||
isActive={showHiddenEvents}
|
||||
onToggle={() => setShowHiddenEvents(!showHiddenEvents)}
|
||||
/>
|
||||
)}
|
||||
}
|
||||
content={<Text variant="b3">Show hidden state and message events.</Text>}
|
||||
/>
|
||||
</div>
|
||||
|
@ -229,19 +272,29 @@ function AppearanceSection() {
|
|||
}
|
||||
|
||||
function NotificationsSection() {
|
||||
const notifPermission = usePermissionState('notifications', window.Notification?.permission ?? "denied");
|
||||
const [showNotifications, setShowNotifications] = useSetting(settingsAtom, 'showNotifications')
|
||||
const [isNotificationSounds, setIsNotificationSounds] = useSetting(settingsAtom, 'isNotificationSounds')
|
||||
const notifPermission = usePermissionState(
|
||||
'notifications',
|
||||
window.Notification?.permission ?? 'denied'
|
||||
);
|
||||
const [showNotifications, setShowNotifications] = useSetting(settingsAtom, 'showNotifications');
|
||||
const [isNotificationSounds, setIsNotificationSounds] = useSetting(
|
||||
settingsAtom,
|
||||
'isNotificationSounds'
|
||||
);
|
||||
|
||||
const renderOptions = () => {
|
||||
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') {
|
||||
return <Text>Permission Denied</Text>
|
||||
return <Text>Permission Denied</Text>;
|
||||
}
|
||||
|
||||
|
||||
if (notifPermission === 'granted') {
|
||||
return (
|
||||
<Toggle
|
||||
|
@ -256,9 +309,11 @@ function NotificationsSection() {
|
|||
return (
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => window.Notification.requestPermission().then(() => {
|
||||
setShowNotifications(window.Notification?.permission === 'granted');
|
||||
})}
|
||||
onClick={() =>
|
||||
window.Notification.requestPermission().then(() => {
|
||||
setShowNotifications(window.Notification?.permission === 'granted');
|
||||
})
|
||||
}
|
||||
>
|
||||
Request permission
|
||||
</Button>
|
||||
|
@ -276,12 +331,12 @@ function NotificationsSection() {
|
|||
/>
|
||||
<SettingTile
|
||||
title="Notification Sound"
|
||||
options={(
|
||||
options={
|
||||
<Toggle
|
||||
isActive={isNotificationSounds}
|
||||
onToggle={() => setIsNotificationSounds(!isNotificationSounds)}
|
||||
/>
|
||||
)}
|
||||
}
|
||||
content={<Text variant="b3">Play sound when new messages arrive.</Text>}
|
||||
/>
|
||||
</div>
|
||||
|
@ -295,8 +350,12 @@ function NotificationsSection() {
|
|||
function EmojiSection() {
|
||||
return (
|
||||
<>
|
||||
<div className="settings-emoji__card"><ImagePackUser /></div>
|
||||
<div className="settings-emoji__card"><ImagePackGlobal /></div>
|
||||
<div className="settings-emoji__card">
|
||||
<ImagePackUser />
|
||||
</div>
|
||||
<div className="settings-emoji__card">
|
||||
<ImagePackGlobal />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -314,21 +373,29 @@ function SecuritySection() {
|
|||
<MenuHeader>Export/Import encryption keys</MenuHeader>
|
||||
<SettingTile
|
||||
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 />
|
||||
</>
|
||||
)}
|
||||
}
|
||||
/>
|
||||
<SettingTile
|
||||
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 />
|
||||
</>
|
||||
)}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -337,7 +404,7 @@ function SecuritySection() {
|
|||
|
||||
function AboutSection() {
|
||||
const mx = useMatrixClient();
|
||||
|
||||
|
||||
return (
|
||||
<div className="settings-about">
|
||||
<div className="settings-about__card">
|
||||
|
@ -347,14 +414,21 @@ function AboutSection() {
|
|||
<div>
|
||||
<Text variant="h2" weight="medium">
|
||||
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>Yet another matrix client</Text>
|
||||
|
||||
<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={() => clearCacheAndReload(mx)} variant="danger">Clear cache & reload</Button>
|
||||
<Button onClick={() => clearCacheAndReload(mx)} variant="danger">
|
||||
Clear cache & reload
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -364,20 +438,104 @@ function AboutSection() {
|
|||
<div className="settings-about__credits">
|
||||
<ul>
|
||||
<li>
|
||||
{/* 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>
|
||||
{/* 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>
|
||||
</li>
|
||||
<li>
|
||||
{/* 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>
|
||||
{/* 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>
|
||||
</li>
|
||||
<li>
|
||||
{/* 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>
|
||||
{/* 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>
|
||||
</li>
|
||||
<li>
|
||||
{/* 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>
|
||||
{/* 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>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -393,32 +551,38 @@ export const tabText = {
|
|||
SECURITY: 'Security',
|
||||
ABOUT: 'About',
|
||||
};
|
||||
const tabItems = [{
|
||||
text: tabText.APPEARANCE,
|
||||
iconSrc: SunIC,
|
||||
disabled: false,
|
||||
render: () => <AppearanceSection />,
|
||||
}, {
|
||||
text: tabText.NOTIFICATIONS,
|
||||
iconSrc: BellIC,
|
||||
disabled: false,
|
||||
render: () => <NotificationsSection />,
|
||||
}, {
|
||||
text: tabText.EMOJI,
|
||||
iconSrc: EmojiIC,
|
||||
disabled: false,
|
||||
render: () => <EmojiSection />,
|
||||
}, {
|
||||
text: tabText.SECURITY,
|
||||
iconSrc: LockIC,
|
||||
disabled: false,
|
||||
render: () => <SecuritySection />,
|
||||
}, {
|
||||
text: tabText.ABOUT,
|
||||
iconSrc: InfoIC,
|
||||
disabled: false,
|
||||
render: () => <AboutSection />,
|
||||
}];
|
||||
const tabItems = [
|
||||
{
|
||||
text: tabText.APPEARANCE,
|
||||
iconSrc: SunIC,
|
||||
disabled: false,
|
||||
render: () => <AppearanceSection />,
|
||||
},
|
||||
{
|
||||
text: tabText.NOTIFICATIONS,
|
||||
iconSrc: BellIC,
|
||||
disabled: false,
|
||||
render: () => <NotificationsSection />,
|
||||
},
|
||||
{
|
||||
text: tabText.EMOJI,
|
||||
iconSrc: EmojiIC,
|
||||
disabled: false,
|
||||
render: () => <EmojiSection />,
|
||||
},
|
||||
{
|
||||
text: tabText.SECURITY,
|
||||
iconSrc: LockIC,
|
||||
disabled: false,
|
||||
render: () => <SecuritySection />,
|
||||
},
|
||||
{
|
||||
text: tabText.ABOUT,
|
||||
iconSrc: InfoIC,
|
||||
disabled: false,
|
||||
render: () => <AboutSection />,
|
||||
},
|
||||
];
|
||||
|
||||
function useWindowToggle(setSelectedTab) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
@ -447,7 +611,14 @@ function Settings() {
|
|||
|
||||
const handleTabChange = (tabItem) => setSelectedTab(tabItem);
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
@ -456,15 +627,19 @@ function Settings() {
|
|||
<PopupWindow
|
||||
isOpen={isOpen}
|
||||
className="settings-window"
|
||||
title={<Text variant="s1" weight="medium" primary>Settings</Text>}
|
||||
contentOptions={(
|
||||
title={
|
||||
<Text variant="s1" weight="medium" primary>
|
||||
Settings
|
||||
</Text>
|
||||
}
|
||||
contentOptions={
|
||||
<>
|
||||
<Button variant="danger" iconSrc={PowerIC} onClick={handleLogout}>
|
||||
Logout
|
||||
</Button>
|
||||
<IconButton src={CrossIC} onClick={requestClose} tooltip="Close" />
|
||||
</>
|
||||
)}
|
||||
}
|
||||
onRequestClose={requestClose}
|
||||
>
|
||||
{isOpen && (
|
||||
|
@ -475,9 +650,7 @@ function Settings() {
|
|||
defaultSelected={tabItems.findIndex((tab) => tab.text === selectedTab.text)}
|
||||
onSelect={handleTabChange}
|
||||
/>
|
||||
<div className="settings-window__cards-wrapper">
|
||||
{ selectedTab.render() }
|
||||
</div>
|
||||
<div className="settings-window__cards-wrapper">{selectedTab.render()}</div>
|
||||
</div>
|
||||
)}
|
||||
</PopupWindow>
|
||||
|
|
|
@ -26,6 +26,30 @@ import { getMxIdLocalPart } from '../../utils/matrix';
|
|||
import { useSelectedRoom } from '../../hooks/router/useSelectedRoom';
|
||||
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() {
|
||||
const roomToUnread = useAtomValue(roomToUnreadAtom);
|
||||
|
||||
|
@ -233,6 +257,8 @@ type ClientNonUIFeaturesProps = {
|
|||
export function ClientNonUIFeatures({ children }: ClientNonUIFeaturesProps) {
|
||||
return (
|
||||
<>
|
||||
<SystemEmojiFeature />
|
||||
<PageZoomFeature />
|
||||
<FaviconUpdater />
|
||||
<InviteNotifications />
|
||||
<MessageNotifications />
|
||||
|
|
|
@ -32,25 +32,11 @@ import { SpecVersions } from './SpecVersions';
|
|||
import Windows from '../../organisms/pw/Windows';
|
||||
import Dialogs from '../../organisms/pw/Dialogs';
|
||||
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 { useSyncState } from '../../hooks/useSyncState';
|
||||
import { stopPropagation } from '../../utils/keyboard';
|
||||
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() {
|
||||
return (
|
||||
<SplashScreen>
|
||||
|
@ -198,7 +184,7 @@ export function ClientRoot({ children }: ClientRootProps) {
|
|||
{startState.status === AsyncStatus.Error && (
|
||||
<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">
|
||||
Retry
|
||||
</Text>
|
||||
|
@ -220,7 +206,6 @@ export function ClientRoot({ children }: ClientRootProps) {
|
|||
<Windows />
|
||||
<Dialogs />
|
||||
<ReusableContextMenu />
|
||||
<SystemEmojiFeature />
|
||||
</MediaConfigProvider>
|
||||
</CapabilitiesProvider>
|
||||
)}
|
||||
|
|
|
@ -10,6 +10,7 @@ export interface Settings {
|
|||
isMarkdown: boolean;
|
||||
editorToolbar: boolean;
|
||||
twitterEmoji: boolean;
|
||||
pageZoom: number;
|
||||
|
||||
isPeopleDrawer: boolean;
|
||||
memberSortFilterIndex: number;
|
||||
|
@ -33,6 +34,7 @@ const defaultSettings: Settings = {
|
|||
isMarkdown: true,
|
||||
editorToolbar: false,
|
||||
twitterEmoji: false,
|
||||
pageZoom: 100,
|
||||
|
||||
isPeopleDrawer: true,
|
||||
memberSortFilterIndex: 0,
|
||||
|
|
Loading…
Reference in a new issue