import React, { useState, useEffect } from 'react'; import './Settings.scss'; import initMatrix from '../../../client/initMatrix'; import cons from '../../../client/state/cons'; import settings from '../../../client/state/settings'; import navigation from '../../../client/state/navigation'; import { toggleSystemTheme, toggleNotifications, toggleNotificationSounds, } from '../../../client/action/settings'; import { usePermission } from '../../hooks/usePermission'; import Text from '../../atoms/text/Text'; import IconButton from '../../atoms/button/IconButton'; import Button from '../../atoms/button/Button'; import Toggle from '../../atoms/button/Toggle'; import Tabs from '../../atoms/tabs/Tabs'; import { MenuHeader } from '../../atoms/context-menu/ContextMenu'; import SegmentedControls from '../../atoms/segmented-controls/SegmentedControls'; import PopupWindow from '../../molecules/popup-window/PopupWindow'; import SettingTile from '../../molecules/setting-tile/SettingTile'; import ImportE2ERoomKeys from '../../molecules/import-export-e2e-room-keys/ImportE2ERoomKeys'; import ExportE2ERoomKeys from '../../molecules/import-export-e2e-room-keys/ExportE2ERoomKeys'; import { ImagePackUser, ImagePackGlobal } from '../../molecules/image-pack/ImagePack'; import GlobalNotification from '../../molecules/global-notification/GlobalNotification'; import KeywordNotification from '../../molecules/global-notification/KeywordNotification'; import IgnoreUserList from '../../molecules/global-notification/IgnoreUserList'; import ProfileEditor from '../profile-editor/ProfileEditor'; import CrossSigning from './CrossSigning'; import KeyBackup from './KeyBackup'; import DeviceManage from './DeviceManage'; import SunIC from '../../../../public/res/ic/outlined/sun.svg'; import EmojiIC from '../../../../public/res/ic/outlined/emoji.svg'; import LockIC from '../../../../public/res/ic/outlined/lock.svg'; import BellIC from '../../../../public/res/ic/outlined/bell.svg'; import InfoIC from '../../../../public/res/ic/outlined/info.svg'; import PowerIC from '../../../../public/res/ic/outlined/power.svg'; import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; import CinnySVG from '../../../../public/res/svg/cinny.svg'; import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog'; import { useSetting } from '../../state/hooks/settings'; import { settingsAtom } from '../../state/settings'; function AppearanceSection() { const [, updateState] = useState({}); const [messageLayout, setMessageLayout] = useSetting(settingsAtom, 'messageLayout'); const [messageSpacing, setMessageSpacing] = useSetting(settingsAtom, 'messageSpacing'); const [useSystemEmoji, setUseSystemEmoji] = useSetting(settingsAtom, 'useSystemEmoji'); const [isMarkdown, setIsMarkdown] = useSetting(settingsAtom, 'isMarkdown'); const [hideMembershipEvents, setHideMembershipEvents] = useSetting(settingsAtom, 'hideMembershipEvents'); const [hideNickAvatarEvents, setHideNickAvatarEvents] = useSetting(settingsAtom, 'hideNickAvatarEvents'); const [mediaAutoLoad, setMediaAutoLoad] = useSetting(settingsAtom, 'mediaAutoLoad'); const [showHiddenEvents, setShowHiddenEvents] = useSetting(settingsAtom, 'showHiddenEvents'); const spacings = ['0', '100', '200', '300', '400', '500'] return (
Theme { toggleSystemTheme(); updateState({}); }} /> )} content={Use light or dark mode based on the system settings.} /> { if (settings.useSystemTheme) toggleSystemTheme(); settings.setTheme(index); updateState({}); }} /> )} /> setUseSystemEmoji(!useSystemEmoji)} /> )} content={Use system emoji instead of Twitter emojis.} />
Room messages setMessageLayout(index)} /> } /> s === messageSpacing)} segments={[ { text: 'No' }, { text: 'XXS' }, { text: 'XS' }, { text: 'S' }, { text: 'M' }, { text: 'L' }, ]} onSelect={(index) => { setMessageSpacing(spacings[index]) }} /> } /> setIsMarkdown(!isMarkdown) } /> )} content={Format messages with inline markdown syntax before sending.} /> setHideMembershipEvents(!hideMembershipEvents)} /> )} content={Hide membership change messages from room timeline. (Join, Leave, Invite, Kick and Ban)} /> setHideNickAvatarEvents(!hideNickAvatarEvents)} /> )} content={Hide nick and avatar change messages from room timeline.} /> setMediaAutoLoad(!mediaAutoLoad)} /> )} content={Prevent images and videos from auto loading to save bandwidth.} /> setShowHiddenEvents(!showHiddenEvents)} /> )} content={Show hidden state and message events.} />
); } function NotificationsSection() { const [permission, setPermission] = usePermission('notifications', window.Notification?.permission); const [, updateState] = useState({}); const renderOptions = () => { if (window.Notification === undefined) { return Not supported in this browser.; } if (permission === 'granted') { return ( { toggleNotifications(); setPermission(window.Notification?.permission); updateState({}); }} /> ); } return ( ); }; return ( <>
Notification & Sound Show desktop notification when new messages arrive.} /> { toggleNotificationSounds(); updateState({}); }} /> )} content={Play sound when new messages arrive.} />
); } function EmojiSection() { return ( <>
); } function SecuritySection() { return (
Cross signing and backup
Export/Import encryption keys 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. )} /> {'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.'} )} />
); } function AboutSection() { return (
Application
Cinny logo
Cinny {`v${cons.version}`} Yet another matrix client
Credits
); } export const tabText = { APPEARANCE: 'Appearance', NOTIFICATIONS: 'Notifications', EMOJI: 'Emoji', SECURITY: 'Security', ABOUT: 'About', }; const tabItems = [{ text: tabText.APPEARANCE, iconSrc: SunIC, disabled: false, render: () => , }, { text: tabText.NOTIFICATIONS, iconSrc: BellIC, disabled: false, render: () => , }, { text: tabText.EMOJI, iconSrc: EmojiIC, disabled: false, render: () => , }, { text: tabText.SECURITY, iconSrc: LockIC, disabled: false, render: () => , }, { text: tabText.ABOUT, iconSrc: InfoIC, disabled: false, render: () => , }]; function useWindowToggle(setSelectedTab) { const [isOpen, setIsOpen] = useState(false); useEffect(() => { const openSettings = (tab) => { const tabItem = tabItems.find((item) => item.text === tab); if (tabItem) setSelectedTab(tabItem); setIsOpen(true); }; navigation.on(cons.events.navigation.SETTINGS_OPENED, openSettings); return () => { navigation.removeListener(cons.events.navigation.SETTINGS_OPENED, openSettings); }; }, []); const requestClose = () => setIsOpen(false); return [isOpen, requestClose]; } function Settings() { const [selectedTab, setSelectedTab] = useState(tabItems[0]); const [isOpen, requestClose] = useWindowToggle(setSelectedTab); const handleTabChange = (tabItem) => setSelectedTab(tabItem); const handleLogout = async () => { if (await confirmDialog('Logout', 'Are you sure that you want to logout your session?', 'Logout', 'danger')) { initMatrix.logout(); } }; return ( Settings} contentOptions={( <> )} onRequestClose={requestClose} > {isOpen && (
tab.text === selectedTab.text)} onSelect={handleTabChange} />
{ selectedTab.render() }
)}
); } export default Settings;