2022-03-21 05:16:11 +01:00
import React , { useState , useEffect } from 'react' ;
2021-07-28 15:15:52 +02:00
import './Settings.scss' ;
2021-07-31 10:21:19 +02:00
import initMatrix from '../../../client/initMatrix' ;
2021-11-06 10:45:35 +01:00
import cons from '../../../client/state/cons' ;
2021-07-28 15:15:52 +02:00
import settings from '../../../client/state/settings' ;
2022-03-21 05:16:11 +01:00
import navigation from '../../../client/state/navigation' ;
2022-01-29 15:20:51 +01:00
import {
2023-10-09 13:26:54 +02:00
toggleSystemTheme ,
2022-03-18 04:37:11 +01:00
toggleNotifications , toggleNotificationSounds ,
2022-01-29 15:20:51 +01:00
} from '../../../client/action/settings' ;
import { usePermission } from '../../hooks/usePermission' ;
2021-07-28 15:15:52 +02:00
import Text from '../../atoms/text/Text' ;
import IconButton from '../../atoms/button/IconButton' ;
2021-07-31 18:20:15 +02:00
import Button from '../../atoms/button/Button' ;
2021-08-11 09:41:55 +02:00
import Toggle from '../../atoms/button/Toggle' ;
2022-03-21 05:16:11 +01:00
import Tabs from '../../atoms/tabs/Tabs' ;
import { MenuHeader } from '../../atoms/context-menu/ContextMenu' ;
2021-07-28 15:15:52 +02:00
import SegmentedControls from '../../atoms/segmented-controls/SegmentedControls' ;
2022-03-21 05:16:11 +01:00
import PopupWindow from '../../molecules/popup-window/PopupWindow' ;
2021-07-28 15:15:52 +02:00
import SettingTile from '../../molecules/setting-tile/SettingTile' ;
2021-12-06 05:52:45 +01:00
import ImportE2ERoomKeys from '../../molecules/import-export-e2e-room-keys/ImportE2ERoomKeys' ;
import ExportE2ERoomKeys from '../../molecules/import-export-e2e-room-keys/ExportE2ERoomKeys' ;
2022-08-06 05:34:23 +02:00
import { ImagePackUser , ImagePackGlobal } from '../../molecules/image-pack/ImagePack' ;
2022-09-04 09:40:07 +02:00
import GlobalNotification from '../../molecules/global-notification/GlobalNotification' ;
import KeywordNotification from '../../molecules/global-notification/KeywordNotification' ;
2022-09-05 05:00:45 +02:00
import IgnoreUserList from '../../molecules/global-notification/IgnoreUserList' ;
2021-07-28 15:15:52 +02:00
2021-09-13 05:25:58 +02:00
import ProfileEditor from '../profile-editor/ProfileEditor' ;
2022-04-24 12:12:24 +02:00
import CrossSigning from './CrossSigning' ;
import KeyBackup from './KeyBackup' ;
2022-03-23 14:14:38 +01:00
import DeviceManage from './DeviceManage' ;
2021-09-13 05:25:58 +02:00
2021-07-31 16:23:08 +02:00
import SunIC from '../../../../public/res/ic/outlined/sun.svg' ;
2022-08-06 05:34:23 +02:00
import EmojiIC from '../../../../public/res/ic/outlined/emoji.svg' ;
2021-07-31 16:23:08 +02:00
import LockIC from '../../../../public/res/ic/outlined/lock.svg' ;
2022-01-29 15:20:51 +01:00
import BellIC from '../../../../public/res/ic/outlined/bell.svg' ;
2021-07-31 16:23:08 +02:00
import InfoIC from '../../../../public/res/ic/outlined/info.svg' ;
2021-12-19 15:35:13 +01:00
import PowerIC from '../../../../public/res/ic/outlined/power.svg' ;
2021-07-28 15:15:52 +02:00
import CrossIC from '../../../../public/res/ic/outlined/cross.svg' ;
2021-07-31 18:20:15 +02:00
import CinnySVG from '../../../../public/res/svg/cinny.svg' ;
2022-04-25 16:51:21 +02:00
import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog' ;
2023-10-06 04:44:06 +02:00
import { useSetting } from '../../state/hooks/settings' ;
import { settingsAtom } from '../../state/settings' ;
2021-07-31 18:20:15 +02:00
2021-07-31 16:23:08 +02:00
function AppearanceSection ( ) {
2021-08-11 09:41:55 +02:00
const [ , updateState ] = useState ( { } ) ;
2023-10-06 04:44:06 +02:00
const [ messageLayout , setMessageLayout ] = useSetting ( settingsAtom , 'messageLayout' ) ;
const [ messageSpacing , setMessageSpacing ] = useSetting ( settingsAtom , 'messageSpacing' ) ;
const [ useSystemEmoji , setUseSystemEmoji ] = useSetting ( settingsAtom , 'useSystemEmoji' ) ;
2023-10-09 13:26:54 +02:00
const [ isMarkdown , setIsMarkdown ] = useSetting ( settingsAtom , 'isMarkdown' ) ;
2023-10-06 04:44:06 +02:00
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' ]
2021-07-31 16:23:08 +02:00
return (
2022-03-21 05:16:11 +01:00
< div className = "settings-appearance" >
< div className = "settings-appearance__card" >
< MenuHeader > Theme < / MenuHeader >
< SettingTile
title = "Follow system theme"
options = { (
< Toggle
isActive = { settings . useSystemTheme }
onToggle = { ( ) => { toggleSystemTheme ( ) ; updateState ( { } ) ; } }
2022-01-29 15:20:51 +01:00
/ >
2022-03-21 05:16:11 +01:00
) }
content = { < Text variant = "b3" > Use light or dark mode based on the system settings . < / Text > }
/ >
2022-07-09 14:38:35 +02:00
< SettingTile
title = "Theme"
content = { (
< SegmentedControls
selected = { settings . useSystemTheme ? - 1 : settings . getThemeIndex ( ) }
segments = { [
{ text : 'Light' } ,
{ text : 'Silver' } ,
{ text : 'Dark' } ,
{ text : 'Butter' } ,
] }
onSelect = { ( index ) => {
if ( settings . useSystemTheme ) toggleSystemTheme ( ) ;
settings . setTheme ( index ) ;
updateState ( { } ) ;
} }
/ >
2021-12-12 16:23:32 +01:00
) }
2022-07-09 14:38:35 +02:00
/ >
2023-10-06 04:44:06 +02:00
< SettingTile
title = "Use System Emoji"
options = { (
< Toggle
isActive = { useSystemEmoji }
onToggle = { ( ) => setUseSystemEmoji ( ! useSystemEmoji ) }
/ >
) }
content = { < Text variant = "b3" > Use system emoji instead of Twitter emojis . < / Text > }
/ >
2022-03-21 05:16:11 +01:00
< / div >
< div className = "settings-appearance__card" >
< MenuHeader > Room messages < / MenuHeader >
2023-10-06 04:44:06 +02:00
< SettingTile
title = "Message Layout"
content = {
< SegmentedControls
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 ] )
} }
/ >
}
/ >
2022-03-21 05:16:11 +01:00
< SettingTile
2023-10-09 13:26:54 +02:00
title = "Inline Markdown formatting"
2022-03-21 05:16:11 +01:00
options = { (
< Toggle
2023-10-09 13:26:54 +02:00
isActive = { isMarkdown }
onToggle = { ( ) => setIsMarkdown ( ! isMarkdown ) }
2022-03-21 05:16:11 +01:00
/ >
) }
2023-10-09 13:26:54 +02:00
content = { < Text variant = "b3" > Format messages with inline markdown syntax before sending . < / Text > }
2022-03-21 05:16:11 +01:00
/ >
< SettingTile
title = "Hide membership events"
options = { (
< Toggle
2023-10-06 04:44:06 +02:00
isActive = { hideMembershipEvents }
onToggle = { ( ) => setHideMembershipEvents ( ! hideMembershipEvents ) }
2022-03-21 05:16:11 +01:00
/ >
) }
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 = { (
< Toggle
2023-10-06 04:44:06 +02:00
isActive = { hideNickAvatarEvents }
onToggle = { ( ) => setHideNickAvatarEvents ( ! hideNickAvatarEvents ) }
2022-03-21 05:16:11 +01:00
/ >
) }
content = { < Text variant = "b3" > Hide nick and avatar change messages from room timeline . < / Text > }
/ >
2023-10-06 04:44:06 +02:00
< 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 > }
/ >
< SettingTile
title = "Show hidden events"
options = { (
< Toggle
isActive = { showHiddenEvents }
onToggle = { ( ) => setShowHiddenEvents ( ! showHiddenEvents ) }
/ >
) }
content = { < Text variant = "b3" > Show hidden state and message events . < / Text > }
/ >
2022-03-21 05:16:11 +01:00
< / div >
2021-07-31 16:23:08 +02:00
< / div >
) ;
}
2022-01-29 15:20:51 +01:00
function NotificationsSection ( ) {
const [ permission , setPermission ] = usePermission ( 'notifications' , window . Notification ? . permission ) ;
const [ , updateState ] = useState ( { } ) ;
const renderOptions = ( ) => {
if ( window . Notification === undefined ) {
2022-03-21 05:16:11 +01:00
return < Text className = "settings-notifications__not-supported" > Not supported in this browser . < / Text > ;
2022-01-29 15:20:51 +01:00
}
if ( permission === 'granted' ) {
return (
< Toggle
isActive = { settings . _showNotifications }
onToggle = { ( ) => {
toggleNotifications ( ) ;
setPermission ( window . Notification ? . permission ) ;
updateState ( { } ) ;
} }
/ >
) ;
}
return (
< Button
variant = "primary"
onClick = { ( ) => window . Notification . requestPermission ( ) . then ( setPermission ) }
>
Request permission
< / Button >
) ;
} ;
return (
2022-09-04 09:40:07 +02:00
< >
< div className = "settings-notifications" >
< MenuHeader > Notification & Sound < / MenuHeader >
< SettingTile
title = "Desktop notification"
options = { renderOptions ( ) }
content = { < Text variant = "b3" > Show desktop notification when new messages arrive . < / Text > }
/ >
< SettingTile
title = "Notification Sound"
options = { (
< Toggle
isActive = { settings . isNotificationSounds }
onToggle = { ( ) => { toggleNotificationSounds ( ) ; updateState ( { } ) ; } }
/ >
) }
content = { < Text variant = "b3" > Play sound when new messages arrive . < / Text > }
/ >
< / div >
< GlobalNotification / >
< KeywordNotification / >
2022-09-05 05:00:45 +02:00
< IgnoreUserList / >
2022-09-04 09:40:07 +02:00
< / >
2022-01-29 15:20:51 +01:00
) ;
}
2022-08-06 05:34:23 +02:00
function EmojiSection ( ) {
return (
< >
< div className = "settings-emoji__card" > < ImagePackUser / > < / div >
< div className = "settings-emoji__card" > < ImagePackGlobal / > < / div >
< / >
) ;
}
2021-07-31 16:23:08 +02:00
function SecuritySection ( ) {
2021-07-31 18:20:15 +02:00
return (
2022-03-21 05:16:11 +01:00
< div className = "settings-security" >
< div className = "settings-security__card" >
2022-04-24 12:12:24 +02:00
< MenuHeader > Cross signing and backup < / MenuHeader >
< CrossSigning / >
< KeyBackup / >
2022-03-21 05:16:11 +01:00
< / div >
2022-03-23 14:14:38 +01:00
< DeviceManage / >
2022-03-21 05:16:11 +01:00
< div className = "settings-security__card" >
2022-04-24 12:12:24 +02:00
< MenuHeader > Export / Import encryption keys < / MenuHeader >
2022-03-21 05:16:11 +01:00
< SettingTile
title = "Export E2E room keys"
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 >
< ExportE2ERoomKeys / >
< / >
) }
/ >
< SettingTile
title = "Import E2E room keys"
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 >
< ImportE2ERoomKeys / >
< / >
) }
/ >
< / div >
2021-07-31 18:20:15 +02:00
< / div >
) ;
2021-07-31 16:23:08 +02:00
}
function AboutSection ( ) {
return (
2022-03-21 05:16:11 +01:00
< div className = "settings-about" >
< div className = "settings-about__card" >
< MenuHeader > Application < / MenuHeader >
< div className = "settings-about__branding" >
< img width = "60" height = "60" src = { CinnySVG } alt = "Cinny logo" / >
< div >
< Text variant = "h2" weight = "medium" >
Cinny
< span className = "text text-b3" style = { { margin : '0 var(--sp-extra-tight)' } } > { ` v ${ cons . version } ` } < / span >
< / Text >
< Text > Yet another matrix client < / Text >
2021-07-31 18:20:15 +02:00
2022-03-21 05:16:11 +01:00
< div className = "settings-about__btns" >
< Button onClick = { ( ) => window . open ( 'https://github.com/ajbura/cinny' ) } > Source code < / Button >
< Button onClick = { ( ) => window . open ( 'https://cinny.in/#sponsor' ) } > Support < / Button >
2022-09-17 12:18:45 +02:00
< Button onClick = { ( ) => initMatrix . clearCacheAndReload ( ) } variant = "danger" > Clear cache & reload < / Button >
2022-03-21 05:16:11 +01:00
< / div >
2021-07-31 18:20:15 +02:00
< / div >
< / div >
< / div >
2022-03-21 05:16:11 +01:00
< div className = "settings-about__card" >
< MenuHeader > Credits < / MenuHeader >
< 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 >
< / 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 >
< / 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 >
< / li >
< / ul >
< / div >
2022-01-31 07:45:58 +01:00
< / div >
2021-07-31 16:23:08 +02:00
< / div >
) ;
}
2022-04-24 12:12:24 +02:00
export const tabText = {
2022-03-21 05:16:11 +01:00
APPEARANCE : 'Appearance' ,
NOTIFICATIONS : 'Notifications' ,
2022-08-06 05:34:23 +02:00
EMOJI : 'Emoji' ,
2022-03-21 05:16:11 +01:00
SECURITY : 'Security' ,
ABOUT : 'About' ,
} ;
const tabItems = [ {
text : tabText . APPEARANCE ,
iconSrc : SunIC ,
disabled : false ,
render : ( ) => < AppearanceSection / > ,
} , {
text : tabText . NOTIFICATIONS ,
iconSrc : BellIC ,
disabled : false ,
render : ( ) => < NotificationsSection / > ,
2022-08-06 05:34:23 +02:00
} , {
text : tabText . EMOJI ,
iconSrc : EmojiIC ,
disabled : false ,
render : ( ) => < EmojiSection / > ,
2022-03-21 05:16:11 +01:00
} , {
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 ) ;
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 ) ;
2021-07-31 16:23:08 +02:00
2022-03-21 05:16:11 +01:00
const handleTabChange = ( tabItem ) => setSelectedTab ( tabItem ) ;
2022-04-25 16:51:21 +02:00
const handleLogout = async ( ) => {
if ( await confirmDialog ( 'Logout' , 'Are you sure that you want to logout your session?' , 'Logout' , 'danger' ) ) {
2022-09-17 12:18:45 +02:00
initMatrix . logout ( ) ;
2022-04-25 16:51:21 +02:00
}
2021-12-19 15:35:13 +01:00
} ;
2021-07-28 15:15:52 +02:00
return (
< PopupWindow
isOpen = { isOpen }
2022-03-21 05:16:11 +01:00
className = "settings-window"
title = { < Text variant = "s1" weight = "medium" primary > Settings < / Text > }
contentOptions = { (
2021-12-19 15:35:13 +01:00
< >
2022-03-21 05:16:11 +01:00
< Button variant = "danger" iconSrc = { PowerIC } onClick = { handleLogout } >
2021-12-19 15:35:13 +01:00
Logout
2022-03-21 05:16:11 +01:00
< / Button >
< IconButton src = { CrossIC } onClick = { requestClose } tooltip = "Close" / >
2021-12-19 15:35:13 +01:00
< / >
) }
2022-03-21 05:16:11 +01:00
onRequestClose = { requestClose }
2021-07-28 15:15:52 +02:00
>
2022-03-21 05:16:11 +01:00
{ isOpen && (
< div className = "settings-window__content" >
< ProfileEditor userId = { initMatrix . matrixClient . getUserId ( ) } / >
< Tabs
items = { tabItems }
defaultSelected = { tabItems . findIndex ( ( tab ) => tab . text === selectedTab . text ) }
onSelect = { handleTabChange }
/ >
< div className = "settings-window__cards-wrapper" >
{ selectedTab . render ( ) }
< / div >
< / div >
) }
2021-07-28 15:15:52 +02:00
< / PopupWindow >
) ;
}
export default Settings ;