Redesign app/user settings (#404)
* Redesign app settings Signed-off-by: Ajay Bura <ajbura@gmail.com> * Redesign user profile in settings Signed-off-by: Ajay Bura <ajbura@gmail.com> * Update string Signed-off-by: Ajay Bura <ajbura@gmail.com> * Fix bug Signed-off-by: Ajay Bura <ajbura@gmail.com>
This commit is contained in:
parent
abb81b6390
commit
50bf90fada
10 changed files with 369 additions and 295 deletions
|
@ -74,7 +74,7 @@ Tabs.defaultProps = {
|
||||||
|
|
||||||
Tabs.propTypes = {
|
Tabs.propTypes = {
|
||||||
items: PropTypes.arrayOf(
|
items: PropTypes.arrayOf(
|
||||||
PropTypes.exact({
|
PropTypes.shape({
|
||||||
iconSrc: PropTypes.string,
|
iconSrc: PropTypes.string,
|
||||||
text: PropTypes.string,
|
text: PropTypes.string,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
|
@ -84,4 +84,4 @@ Tabs.propTypes = {
|
||||||
onSelect: PropTypes.func.isRequired,
|
onSelect: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { Tabs as default };
|
export default Tabs;
|
||||||
|
|
|
@ -72,7 +72,7 @@ function ProfileAvatarMenu() {
|
||||||
return (
|
return (
|
||||||
<SidebarAvatar
|
<SidebarAvatar
|
||||||
onClick={openSettings}
|
onClick={openSettings}
|
||||||
tooltip={profile.displayName}
|
tooltip="Settings"
|
||||||
avatar={(
|
avatar={(
|
||||||
<Avatar
|
<Avatar
|
||||||
text={profile.displayName}
|
text={profile.displayName}
|
||||||
|
|
|
@ -1,35 +1,44 @@
|
||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { twemojify } from '../../../util/twemojify';
|
||||||
|
|
||||||
import initMatrix from '../../../client/initMatrix';
|
import initMatrix from '../../../client/initMatrix';
|
||||||
import colorMXID from '../../../util/colorMXID';
|
import colorMXID from '../../../util/colorMXID';
|
||||||
|
|
||||||
|
import Text from '../../atoms/text/Text';
|
||||||
|
import IconButton from '../../atoms/button/IconButton';
|
||||||
import Button from '../../atoms/button/Button';
|
import Button from '../../atoms/button/Button';
|
||||||
import ImageUpload from '../../molecules/image-upload/ImageUpload';
|
import ImageUpload from '../../molecules/image-upload/ImageUpload';
|
||||||
import Input from '../../atoms/input/Input';
|
import Input from '../../atoms/input/Input';
|
||||||
|
|
||||||
|
import PencilIC from '../../../../public/res/ic/outlined/pencil.svg';
|
||||||
|
|
||||||
import './ProfileEditor.scss';
|
import './ProfileEditor.scss';
|
||||||
|
|
||||||
// TODO Fix bug that prevents 'Save' button from enabling up until second changed.
|
// TODO Fix bug that prevents 'Save' button from enabling up until second changed.
|
||||||
function ProfileEditor({
|
function ProfileEditor({ userId }) {
|
||||||
userId,
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
}) {
|
|
||||||
const mx = initMatrix.matrixClient;
|
const mx = initMatrix.matrixClient;
|
||||||
|
const user = mx.getUser(mx.getUserId());
|
||||||
|
|
||||||
const displayNameRef = useRef(null);
|
const displayNameRef = useRef(null);
|
||||||
const bgColor = colorMXID(userId);
|
const [avatarSrc, setAvatarSrc] = useState(user.avatarUrl ? mx.mxcUrlToHttp(user.avatarUrl, 80, 80, 'crop') : null);
|
||||||
const [avatarSrc, setAvatarSrc] = useState(null);
|
const [username, setUsername] = useState(user.displayName);
|
||||||
const [disabled, setDisabled] = useState(true);
|
const [disabled, setDisabled] = useState(true);
|
||||||
|
|
||||||
let username = mx.getUser(mx.getUserId()).displayName;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
let isMounted = true;
|
||||||
mx.getProfileInfo(mx.getUserId()).then((info) => {
|
mx.getProfileInfo(mx.getUserId()).then((info) => {
|
||||||
|
if (!isMounted) return;
|
||||||
setAvatarSrc(info.avatar_url ? mx.mxcUrlToHttp(info.avatar_url, 80, 80, 'crop') : null);
|
setAvatarSrc(info.avatar_url ? mx.mxcUrlToHttp(info.avatar_url, 80, 80, 'crop') : null);
|
||||||
|
setUsername(info.displayname);
|
||||||
});
|
});
|
||||||
|
return () => {
|
||||||
|
isMounted = false;
|
||||||
|
};
|
||||||
}, [userId]);
|
}, [userId]);
|
||||||
|
|
||||||
// Sets avatar URL and updates the avatar component in profile editor to reflect new upload
|
const handleAvatarUpload = (url) => {
|
||||||
function handleAvatarUpload(url) {
|
|
||||||
if (url === null) {
|
if (url === null) {
|
||||||
if (confirm('Are you sure you want to remove avatar?')) {
|
if (confirm('Are you sure you want to remove avatar?')) {
|
||||||
mx.setAvatarUrl('');
|
mx.setAvatarUrl('');
|
||||||
|
@ -39,48 +48,72 @@ function ProfileEditor({
|
||||||
}
|
}
|
||||||
mx.setAvatarUrl(url);
|
mx.setAvatarUrl(url);
|
||||||
setAvatarSrc(mx.mxcUrlToHttp(url, 80, 80, 'crop'));
|
setAvatarSrc(mx.mxcUrlToHttp(url, 80, 80, 'crop'));
|
||||||
}
|
};
|
||||||
|
|
||||||
function saveDisplayName() {
|
const saveDisplayName = () => {
|
||||||
const newDisplayName = displayNameRef.current.value;
|
const newDisplayName = displayNameRef.current.value;
|
||||||
if (newDisplayName !== null && newDisplayName !== username) {
|
if (newDisplayName !== null && newDisplayName !== username) {
|
||||||
mx.setDisplayName(newDisplayName);
|
mx.setDisplayName(newDisplayName);
|
||||||
username = newDisplayName;
|
setUsername(newDisplayName);
|
||||||
setDisabled(true);
|
setDisabled(true);
|
||||||
|
setIsEditing(false);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
function onDisplayNameInputChange() {
|
const onDisplayNameInputChange = () => {
|
||||||
setDisabled(username === displayNameRef.current.value || displayNameRef.current.value == null);
|
setDisabled(username === displayNameRef.current.value || displayNameRef.current.value == null);
|
||||||
}
|
};
|
||||||
function cancelDisplayNameChanges() {
|
const cancelDisplayNameChanges = () => {
|
||||||
displayNameRef.current.value = username;
|
displayNameRef.current.value = username;
|
||||||
onDisplayNameInputChange();
|
onDisplayNameInputChange();
|
||||||
}
|
setIsEditing(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
const renderForm = () => (
|
||||||
<form
|
<form
|
||||||
className="profile-editor"
|
className="profile-editor__form"
|
||||||
|
style={{ marginBottom: avatarSrc ? '24px' : '0' }}
|
||||||
onSubmit={(e) => { e.preventDefault(); saveDisplayName(); }}
|
onSubmit={(e) => { e.preventDefault(); saveDisplayName(); }}
|
||||||
>
|
>
|
||||||
|
<Input
|
||||||
|
label={`Display name of ${mx.getUserId()}`}
|
||||||
|
onChange={onDisplayNameInputChange}
|
||||||
|
value={mx.getUser(mx.getUserId()).displayName}
|
||||||
|
forwardRef={displayNameRef}
|
||||||
|
/>
|
||||||
|
<Button variant="primary" type="submit" disabled={disabled}>Save</Button>
|
||||||
|
<Button onClick={cancelDisplayNameChanges}>Cancel</Button>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderInfo = () => (
|
||||||
|
<div className="profile-editor__info" style={{ marginBottom: avatarSrc ? '24px' : '0' }}>
|
||||||
|
<div>
|
||||||
|
<Text variant="h2" primary weight="medium">{twemojify(username)}</Text>
|
||||||
|
<IconButton
|
||||||
|
src={PencilIC}
|
||||||
|
size="extra-small"
|
||||||
|
tooltip="Edit"
|
||||||
|
onClick={() => setIsEditing(true)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Text variant="b2">{mx.getUserId()}</Text>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="profile-editor">
|
||||||
<ImageUpload
|
<ImageUpload
|
||||||
text={username}
|
text={username}
|
||||||
bgColor={bgColor}
|
bgColor={colorMXID(userId)}
|
||||||
imageSrc={avatarSrc}
|
imageSrc={avatarSrc}
|
||||||
onUpload={handleAvatarUpload}
|
onUpload={handleAvatarUpload}
|
||||||
onRequestRemove={() => handleAvatarUpload(null)}
|
onRequestRemove={() => handleAvatarUpload(null)}
|
||||||
/>
|
/>
|
||||||
<div className="profile-editor__input-wrapper">
|
{
|
||||||
<Input
|
isEditing ? renderForm() : renderInfo()
|
||||||
label={`Display name of ${mx.getUserId()}`}
|
}
|
||||||
onChange={onDisplayNameInputChange}
|
</div>
|
||||||
value={mx.getUser(mx.getUserId()).displayName}
|
|
||||||
forwardRef={displayNameRef}
|
|
||||||
/>
|
|
||||||
<Button variant="primary" type="submit" disabled={disabled}>Save</Button>
|
|
||||||
<Button onClick={cancelDisplayNameChanges}>Cancel</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,28 +1,41 @@
|
||||||
@use '../../partials/dir';
|
@use '../../partials/dir';
|
||||||
|
@use '../../partials/flex';
|
||||||
|
|
||||||
.profile-editor {
|
.profile-editor {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-editor__input-wrapper {
|
.profile-editor__info,
|
||||||
flex: 1;
|
.profile-editor__form {
|
||||||
min-width: 0;
|
@extend .cp-fx__item-one;
|
||||||
margin-top: 10px;
|
@include dir.side(margin, var(--sp-loose), 0);
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
}
|
||||||
|
|
||||||
|
.profile-editor__info {
|
||||||
|
flex-direction: column;
|
||||||
|
& > div:first-child {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.ic-btn {
|
||||||
|
margin: 0 var(--sp-extra-tight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-editor__form {
|
||||||
|
margin-top: 10px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
align-items: flex-end;
|
||||||
|
|
||||||
& > .input-container {
|
& > .input-container {
|
||||||
flex: 1;
|
@extend .cp-fx__item-one;
|
||||||
}
|
}
|
||||||
& > button {
|
& > button {
|
||||||
height: 46px;
|
height: 46px;
|
||||||
margin-top: var(--sp-normal);
|
margin-top: var(--sp-normal);
|
||||||
}
|
|
||||||
|
|
||||||
& > * {
|
|
||||||
@include dir.side(margin, var(--sp-normal), 0);
|
@include dir.side(margin, var(--sp-normal), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -18,7 +18,6 @@ function Windows() {
|
||||||
const [inviteUser, changeInviteUser] = useState({
|
const [inviteUser, changeInviteUser] = useState({
|
||||||
isOpen: false, roomId: undefined, term: undefined,
|
isOpen: false, roomId: undefined, term: undefined,
|
||||||
});
|
});
|
||||||
const [settings, changeSettings] = useState(false);
|
|
||||||
|
|
||||||
function openInviteList() {
|
function openInviteList() {
|
||||||
changeInviteList(true);
|
changeInviteList(true);
|
||||||
|
@ -36,20 +35,15 @@ function Windows() {
|
||||||
searchTerm,
|
searchTerm,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function openSettings() {
|
|
||||||
changeSettings(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
navigation.on(cons.events.navigation.INVITE_LIST_OPENED, openInviteList);
|
navigation.on(cons.events.navigation.INVITE_LIST_OPENED, openInviteList);
|
||||||
navigation.on(cons.events.navigation.PUBLIC_ROOMS_OPENED, openPublicRooms);
|
navigation.on(cons.events.navigation.PUBLIC_ROOMS_OPENED, openPublicRooms);
|
||||||
navigation.on(cons.events.navigation.INVITE_USER_OPENED, openInviteUser);
|
navigation.on(cons.events.navigation.INVITE_USER_OPENED, openInviteUser);
|
||||||
navigation.on(cons.events.navigation.SETTINGS_OPENED, openSettings);
|
|
||||||
return () => {
|
return () => {
|
||||||
navigation.removeListener(cons.events.navigation.INVITE_LIST_OPENED, openInviteList);
|
navigation.removeListener(cons.events.navigation.INVITE_LIST_OPENED, openInviteList);
|
||||||
navigation.removeListener(cons.events.navigation.PUBLIC_ROOMS_OPENED, openPublicRooms);
|
navigation.removeListener(cons.events.navigation.PUBLIC_ROOMS_OPENED, openPublicRooms);
|
||||||
navigation.removeListener(cons.events.navigation.INVITE_USER_OPENED, openInviteUser);
|
navigation.removeListener(cons.events.navigation.INVITE_USER_OPENED, openInviteUser);
|
||||||
navigation.removeListener(cons.events.navigation.SETTINGS_OPENED, openSettings);
|
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
@ -70,10 +64,7 @@ function Windows() {
|
||||||
searchTerm={inviteUser.searchTerm}
|
searchTerm={inviteUser.searchTerm}
|
||||||
onRequestClose={() => changeInviteUser({ isOpen: false, roomId: undefined })}
|
onRequestClose={() => changeInviteUser({ isOpen: false, roomId: undefined })}
|
||||||
/>
|
/>
|
||||||
<Settings
|
<Settings />
|
||||||
isOpen={settings}
|
|
||||||
onRequestClose={() => changeSettings(false)}
|
|
||||||
/>
|
|
||||||
<SpaceSettings />
|
<SpaceSettings />
|
||||||
<SpaceManage />
|
<SpaceManage />
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import './Settings.scss';
|
import './Settings.scss';
|
||||||
|
|
||||||
import initMatrix from '../../../client/initMatrix';
|
import initMatrix 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 {
|
import {
|
||||||
toggleSystemTheme, toggleMarkdown, toggleMembershipEvents, toggleNickAvatarEvents,
|
toggleSystemTheme, toggleMarkdown, toggleMembershipEvents, toggleNickAvatarEvents,
|
||||||
toggleNotifications, toggleNotificationSounds,
|
toggleNotifications, toggleNotificationSounds,
|
||||||
|
@ -16,16 +16,17 @@ import Text from '../../atoms/text/Text';
|
||||||
import IconButton from '../../atoms/button/IconButton';
|
import IconButton from '../../atoms/button/IconButton';
|
||||||
import Button from '../../atoms/button/Button';
|
import Button from '../../atoms/button/Button';
|
||||||
import Toggle from '../../atoms/button/Toggle';
|
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 SegmentedControls from '../../atoms/segmented-controls/SegmentedControls';
|
||||||
|
|
||||||
import PopupWindow, { PWContentSelector } from '../../molecules/popup-window/PopupWindow';
|
import PopupWindow from '../../molecules/popup-window/PopupWindow';
|
||||||
import SettingTile from '../../molecules/setting-tile/SettingTile';
|
import SettingTile from '../../molecules/setting-tile/SettingTile';
|
||||||
import ImportE2ERoomKeys from '../../molecules/import-export-e2e-room-keys/ImportE2ERoomKeys';
|
import ImportE2ERoomKeys from '../../molecules/import-export-e2e-room-keys/ImportE2ERoomKeys';
|
||||||
import ExportE2ERoomKeys from '../../molecules/import-export-e2e-room-keys/ExportE2ERoomKeys';
|
import ExportE2ERoomKeys from '../../molecules/import-export-e2e-room-keys/ExportE2ERoomKeys';
|
||||||
|
|
||||||
import ProfileEditor from '../profile-editor/ProfileEditor';
|
import ProfileEditor from '../profile-editor/ProfileEditor';
|
||||||
|
|
||||||
import SettingsIC from '../../../../public/res/ic/outlined/settings.svg';
|
|
||||||
import SunIC from '../../../../public/res/ic/outlined/sun.svg';
|
import SunIC from '../../../../public/res/ic/outlined/sun.svg';
|
||||||
import LockIC from '../../../../public/res/ic/outlined/lock.svg';
|
import LockIC from '../../../../public/res/ic/outlined/lock.svg';
|
||||||
import BellIC from '../../../../public/res/ic/outlined/bell.svg';
|
import BellIC from '../../../../public/res/ic/outlined/bell.svg';
|
||||||
|
@ -35,85 +36,74 @@ import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
|
||||||
|
|
||||||
import CinnySVG from '../../../../public/res/svg/cinny.svg';
|
import CinnySVG from '../../../../public/res/svg/cinny.svg';
|
||||||
|
|
||||||
function GeneralSection() {
|
|
||||||
return (
|
|
||||||
<div className="settings-content">
|
|
||||||
<SettingTile
|
|
||||||
title=""
|
|
||||||
content={(
|
|
||||||
<ProfileEditor userId={initMatrix.matrixClient.getUserId()} />
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function AppearanceSection() {
|
function AppearanceSection() {
|
||||||
const [, updateState] = useState({});
|
const [, updateState] = useState({});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="settings-content">
|
<div className="settings-appearance">
|
||||||
<SettingTile
|
<div className="settings-appearance__card">
|
||||||
title="Follow system theme"
|
<MenuHeader>Theme</MenuHeader>
|
||||||
options={(
|
<SettingTile
|
||||||
<Toggle
|
title="Follow system theme"
|
||||||
isActive={settings.useSystemTheme}
|
options={(
|
||||||
onToggle={() => { toggleSystemTheme(); updateState({}); }}
|
<Toggle
|
||||||
/>
|
isActive={settings.useSystemTheme}
|
||||||
)}
|
onToggle={() => { toggleSystemTheme(); updateState({}); }}
|
||||||
content={<Text variant="b3">Use light or dark mode based on the system's settings.</Text>}
|
|
||||||
/>
|
|
||||||
{(() => {
|
|
||||||
if (!settings.useSystemTheme) {
|
|
||||||
return (
|
|
||||||
<SettingTile
|
|
||||||
title="Theme"
|
|
||||||
content={(
|
|
||||||
<SegmentedControls
|
|
||||||
selected={settings.getThemeIndex()}
|
|
||||||
segments={[
|
|
||||||
{ text: 'Light' },
|
|
||||||
{ text: 'Silver' },
|
|
||||||
{ text: 'Dark' },
|
|
||||||
{ text: 'Butter' },
|
|
||||||
]}
|
|
||||||
onSelect={(index) => settings.setTheme(index)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
);
|
)}
|
||||||
}
|
content={<Text variant="b3">Use light or dark mode based on the system settings.</Text>}
|
||||||
})()}
|
/>
|
||||||
<SettingTile
|
{!settings.useSystemTheme && (
|
||||||
title="Markdown formatting"
|
<SettingTile
|
||||||
options={(
|
title="Theme"
|
||||||
<Toggle
|
content={(
|
||||||
isActive={settings.isMarkdown}
|
<SegmentedControls
|
||||||
onToggle={() => { toggleMarkdown(); updateState({}); }}
|
selected={settings.getThemeIndex()}
|
||||||
|
segments={[
|
||||||
|
{ text: 'Light' },
|
||||||
|
{ text: 'Silver' },
|
||||||
|
{ text: 'Dark' },
|
||||||
|
{ text: 'Butter' },
|
||||||
|
]}
|
||||||
|
onSelect={(index) => settings.setTheme(index)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
content={<Text variant="b3">Format messages with markdown syntax before sending.</Text>}
|
</div>
|
||||||
/>
|
<div className="settings-appearance__card">
|
||||||
<SettingTile
|
<MenuHeader>Room messages</MenuHeader>
|
||||||
title="Hide membership events"
|
<SettingTile
|
||||||
options={(
|
title="Markdown formatting"
|
||||||
<Toggle
|
options={(
|
||||||
isActive={settings.hideMembershipEvents}
|
<Toggle
|
||||||
onToggle={() => { toggleMembershipEvents(); updateState({}); }}
|
isActive={settings.isMarkdown}
|
||||||
/>
|
onToggle={() => { toggleMarkdown(); updateState({}); }}
|
||||||
)}
|
/>
|
||||||
content={<Text variant="b3">Hide membership change messages from room timeline. (Join, Leave, Invite, Kick and Ban)</Text>}
|
)}
|
||||||
/>
|
content={<Text variant="b3">Format messages with markdown syntax before sending.</Text>}
|
||||||
<SettingTile
|
/>
|
||||||
title="Hide nick/avatar events"
|
<SettingTile
|
||||||
options={(
|
title="Hide membership events"
|
||||||
<Toggle
|
options={(
|
||||||
isActive={settings.hideNickAvatarEvents}
|
<Toggle
|
||||||
onToggle={() => { toggleNickAvatarEvents(); updateState({}); }}
|
isActive={settings.hideMembershipEvents}
|
||||||
/>
|
onToggle={() => { toggleMembershipEvents(); updateState({}); }}
|
||||||
)}
|
/>
|
||||||
content={<Text variant="b3">Hide nick and avatar change messages from room timeline.</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={(
|
||||||
|
<Toggle
|
||||||
|
isActive={settings.hideNickAvatarEvents}
|
||||||
|
onToggle={() => { toggleNickAvatarEvents(); updateState({}); }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
content={<Text variant="b3">Hide nick and avatar change messages from room timeline.</Text>}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -125,7 +115,7 @@ function NotificationsSection() {
|
||||||
|
|
||||||
const renderOptions = () => {
|
const renderOptions = () => {
|
||||||
if (window.Notification === undefined) {
|
if (window.Notification === undefined) {
|
||||||
return <Text className="set-notifications__not-supported">Not supported in this browser.</Text>;
|
return <Text className="settings-notifications__not-supported">Not supported in this browser.</Text>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (permission === 'granted') {
|
if (permission === 'granted') {
|
||||||
|
@ -152,21 +142,22 @@ function NotificationsSection() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="set-notifications settings-content">
|
<div className="settings-notifications">
|
||||||
|
<MenuHeader>Notification & Sound</MenuHeader>
|
||||||
<SettingTile
|
<SettingTile
|
||||||
title="Show desktop notifications"
|
title="Desktop notification"
|
||||||
options={renderOptions()}
|
options={renderOptions()}
|
||||||
content={<Text variant="b3">Show notifications when new messages arrive.</Text>}
|
content={<Text variant="b3">Show desktop notification when new messages arrive.</Text>}
|
||||||
/>
|
/>
|
||||||
<SettingTile
|
<SettingTile
|
||||||
title="Play notification sounds"
|
title="Notification Sound"
|
||||||
options={(
|
options={(
|
||||||
<Toggle
|
<Toggle
|
||||||
isActive={settings.isNotificationSounds}
|
isActive={settings.isNotificationSounds}
|
||||||
onToggle={() => { toggleNotificationSounds(); updateState({}); }}
|
onToggle={() => { toggleNotificationSounds(); updateState({}); }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
content={<Text variant="b3">Play a sound when new messages arrive.</Text>}
|
content={<Text variant="b3">Play sound when new messages arrive.</Text>}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -174,153 +165,173 @@ function NotificationsSection() {
|
||||||
|
|
||||||
function SecuritySection() {
|
function SecuritySection() {
|
||||||
return (
|
return (
|
||||||
<div className="set-security settings-content">
|
<div className="settings-security">
|
||||||
<SettingTile
|
<div className="settings-security__card">
|
||||||
title={`Device ID: ${initMatrix.matrixClient.getDeviceId()}`}
|
<MenuHeader>Device Info</MenuHeader>
|
||||||
/>
|
<SettingTile
|
||||||
<SettingTile
|
title={`Device ID: ${initMatrix.matrixClient.getDeviceId()}`}
|
||||||
title={`Device key: ${initMatrix.matrixClient.getDeviceEd25519Key().match(/.{1,4}/g).join(' ')}`}
|
/>
|
||||||
content={<Text variant="b3">Use this device ID-key combo to verify or manage this session from Element client.</Text>}
|
<SettingTile
|
||||||
/>
|
title={`Device key: ${initMatrix.matrixClient.getDeviceEd25519Key().match(/.{1,4}/g).join(' ')}`}
|
||||||
<SettingTile
|
content={<Text variant="b3">Use this device ID-key combo to verify or manage this session from Element client.</Text>}
|
||||||
title="Export E2E room keys"
|
/>
|
||||||
content={(
|
</div>
|
||||||
<>
|
<div className="settings-security__card">
|
||||||
<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>
|
<MenuHeader>Encryption</MenuHeader>
|
||||||
<ExportE2ERoomKeys />
|
<SettingTile
|
||||||
</>
|
title="Export E2E room keys"
|
||||||
)}
|
content={(
|
||||||
/>
|
<>
|
||||||
<SettingTile
|
<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>
|
||||||
title="Import E2E room keys"
|
<ExportE2ERoomKeys />
|
||||||
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 />
|
<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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AboutSection() {
|
function AboutSection() {
|
||||||
return (
|
return (
|
||||||
<div className="settings-content set__about">
|
<div className="settings-about">
|
||||||
<div className="set-about__branding">
|
<div className="settings-about__card">
|
||||||
<img width="60" height="60" src={CinnySVG} alt="Cinny logo" />
|
<MenuHeader>Application</MenuHeader>
|
||||||
<div>
|
<div className="settings-about__branding">
|
||||||
<Text variant="h2" weight="medium">
|
<img width="60" height="60" src={CinnySVG} alt="Cinny logo" />
|
||||||
Cinny
|
<div>
|
||||||
<span className="text text-b3" style={{ margin: '0 var(--sp-extra-tight)' }}>{`v${cons.version}`}</span>
|
<Text variant="h2" weight="medium">
|
||||||
</Text>
|
Cinny
|
||||||
<Text>Yet another matrix client</Text>
|
<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="set-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>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="set-about__credits">
|
<div className="settings-about__card">
|
||||||
<Text variant="s1" weight="medium">Credits</Text>
|
<MenuHeader>Credits</MenuHeader>
|
||||||
<ul>
|
<div className="settings-about__credits">
|
||||||
<li>
|
<ul>
|
||||||
{/* eslint-disable-next-line react/jsx-one-expression-per-line */ }
|
<li>
|
||||||
<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 */ }
|
||||||
</li>
|
<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 */ }
|
<li>
|
||||||
<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 */ }
|
||||||
</li>
|
<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 */ }
|
<li>
|
||||||
<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 */ }
|
||||||
</li>
|
<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>
|
||||||
</ul>
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Settings({ isOpen, onRequestClose }) {
|
const tabText = {
|
||||||
const settingSections = [{
|
APPEARANCE: 'Appearance',
|
||||||
name: 'General',
|
NOTIFICATIONS: 'Notifications',
|
||||||
iconSrc: SettingsIC,
|
SECURITY: 'Security',
|
||||||
render() {
|
ABOUT: 'About',
|
||||||
return <GeneralSection />;
|
};
|
||||||
},
|
const tabItems = [{
|
||||||
}, {
|
text: tabText.APPEARANCE,
|
||||||
name: 'Appearance',
|
iconSrc: SunIC,
|
||||||
iconSrc: SunIC,
|
disabled: false,
|
||||||
render() {
|
render: () => <AppearanceSection />,
|
||||||
return <AppearanceSection />;
|
}, {
|
||||||
},
|
text: tabText.NOTIFICATIONS,
|
||||||
}, {
|
iconSrc: BellIC,
|
||||||
name: 'Notifications',
|
disabled: false,
|
||||||
iconSrc: BellIC,
|
render: () => <NotificationsSection />,
|
||||||
render() {
|
}, {
|
||||||
return <NotificationsSection />;
|
text: tabText.SECURITY,
|
||||||
},
|
iconSrc: LockIC,
|
||||||
}, {
|
disabled: false,
|
||||||
name: 'Security & Privacy',
|
render: () => <SecuritySection />,
|
||||||
iconSrc: LockIC,
|
}, {
|
||||||
render() {
|
text: tabText.ABOUT,
|
||||||
return <SecuritySection />;
|
iconSrc: InfoIC,
|
||||||
},
|
disabled: false,
|
||||||
}, {
|
render: () => <AboutSection />,
|
||||||
name: 'Help & About',
|
}];
|
||||||
iconSrc: InfoIC,
|
|
||||||
render() {
|
|
||||||
return <AboutSection />;
|
|
||||||
},
|
|
||||||
}];
|
|
||||||
const [selectedSection, setSelectedSection] = useState(settingSections[0]);
|
|
||||||
|
|
||||||
|
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 = () => {
|
const handleLogout = () => {
|
||||||
if (confirm('Confirm logout')) logout();
|
if (confirm('Confirm logout')) logout();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PopupWindow
|
<PopupWindow
|
||||||
className="settings-window"
|
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onRequestClose={onRequestClose}
|
className="settings-window"
|
||||||
title="Settings"
|
title={<Text variant="s1" weight="medium" primary>Settings</Text>}
|
||||||
contentTitle={selectedSection.name}
|
contentOptions={(
|
||||||
drawer={(
|
|
||||||
<>
|
<>
|
||||||
{
|
<Button variant="danger" iconSrc={PowerIC} onClick={handleLogout}>
|
||||||
settingSections.map((section) => (
|
|
||||||
<PWContentSelector
|
|
||||||
key={section.name}
|
|
||||||
selected={selectedSection.name === section.name}
|
|
||||||
onClick={() => setSelectedSection(section)}
|
|
||||||
iconSrc={section.iconSrc}
|
|
||||||
>
|
|
||||||
{section.name}
|
|
||||||
</PWContentSelector>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
<PWContentSelector
|
|
||||||
variant="danger"
|
|
||||||
onClick={handleLogout}
|
|
||||||
iconSrc={PowerIC}
|
|
||||||
>
|
|
||||||
Logout
|
Logout
|
||||||
</PWContentSelector>
|
</Button>
|
||||||
|
<IconButton src={CrossIC} onClick={requestClose} tooltip="Close" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
contentOptions={<IconButton src={CrossIC} onClick={onRequestClose} tooltip="Close" />}
|
onRequestClose={requestClose}
|
||||||
>
|
>
|
||||||
{selectedSection.render()}
|
{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>
|
||||||
|
)}
|
||||||
</PopupWindow>
|
</PopupWindow>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Settings.propTypes = {
|
|
||||||
isOpen: PropTypes.bool.isRequired,
|
|
||||||
onRequestClose: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Settings;
|
export default Settings;
|
||||||
|
|
|
@ -2,40 +2,67 @@
|
||||||
@use '../../partials/dir';
|
@use '../../partials/dir';
|
||||||
|
|
||||||
.settings-window {
|
.settings-window {
|
||||||
& .pw__drawer__content {
|
& .pw {
|
||||||
@extend .cp-fx__column;
|
background-color: var(--bg-surface-low);
|
||||||
min-height: 100%;
|
}
|
||||||
padding-bottom: var(--sp-extra-tight);
|
|
||||||
|
|
||||||
& > .pw-content-selector:last-child {
|
.header .btn-danger {
|
||||||
margin-top: auto;
|
margin: 0 var(--sp-tight);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .profile-editor {
|
||||||
|
padding: var(--sp-loose) var(--sp-extra-loose);
|
||||||
|
}
|
||||||
|
|
||||||
|
& .tabs__content {
|
||||||
|
padding: 0 var(--sp-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__cards-wrapper {
|
||||||
|
padding: 0 var(--sp-normal);
|
||||||
|
@include dir.side(padding, var(--sp-normal), var(--sp-extra-tight));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.settings-window__card {
|
||||||
|
margin: var(--sp-normal) 0;
|
||||||
|
background-color: var(--bg-surface);
|
||||||
|
border-radius: var(--bo-radius);
|
||||||
|
box-shadow: var(--bs-surface-border);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
& > .context-menu__header:first-child {
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.settings-appearance__card,
|
||||||
|
.settings-notifications,
|
||||||
|
.settings-security__card,
|
||||||
|
.settings-about__card {
|
||||||
|
@extend .settings-window__card;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-window__cards-wrapper{
|
||||||
|
& .setting-tile {
|
||||||
|
margin: 0 var(--sp-normal);
|
||||||
|
margin-top: var(--sp-normal);
|
||||||
|
padding-bottom: 16px;
|
||||||
|
border-bottom: 1px solid var(--bg-surface-border);
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
& .pw__content-container {
|
|
||||||
min-height: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-content {
|
.settings-notifications {
|
||||||
@include dir.side(margin, var(--sp-normal), var(--sp-extra-tight));
|
|
||||||
|
|
||||||
& .setting-tile {
|
|
||||||
margin-top: var(--sp-normal);
|
|
||||||
border-bottom: 1px solid var(--bg-surface-border);
|
|
||||||
padding-bottom: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.set-notifications {
|
|
||||||
&__not-supported {
|
&__not-supported {
|
||||||
padding: 0 var(--sp-ultra-tight);
|
padding: 0 var(--sp-ultra-tight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.set-about {
|
.settings-about {
|
||||||
&__branding {
|
&__branding {
|
||||||
margin-top: var(--sp-extra-tight);
|
padding: var(--sp-normal);
|
||||||
margin-bottom: var(--sp-normal);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
& > div {
|
& > div {
|
||||||
|
@ -44,16 +71,17 @@
|
||||||
|
|
||||||
}
|
}
|
||||||
&__btns {
|
&__btns {
|
||||||
margin: 0;
|
& button {
|
||||||
margin-top: var(--sp-normal);
|
margin-top: var(--sp-tight);
|
||||||
& button:last-child {
|
@include dir.side(margin, 0, var(--sp-tight));
|
||||||
margin: 0 var(--sp-tight)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__credits {
|
&__credits {
|
||||||
margin-top: var(--sp-loose);
|
padding: 0 var(--sp-normal);
|
||||||
& ul {
|
& ul {
|
||||||
|
color: var(--tc-surface-low);
|
||||||
|
padding: var(--sp-normal);
|
||||||
margin: var(--sp-extra-tight) 0;
|
margin: var(--sp-extra-tight) 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,12 +9,8 @@
|
||||||
padding: var(--sp-loose) var(--sp-extra-loose);
|
padding: var(--sp-loose) var(--sp-extra-loose);
|
||||||
}
|
}
|
||||||
|
|
||||||
& .tabs {
|
& .tabs__content {
|
||||||
box-shadow: inset 0 -1px 0 var(--bg-surface-border);
|
padding: 0 var(--sp-normal);
|
||||||
|
|
||||||
&__content {
|
|
||||||
padding: 0 var(--sp-normal);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__cards-wrapper {
|
&__cards-wrapper {
|
||||||
|
|
|
@ -95,9 +95,10 @@ export function openProfileViewer(userId, roomId) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function openSettings() {
|
export function openSettings(tabText) {
|
||||||
appDispatcher.dispatch({
|
appDispatcher.dispatch({
|
||||||
type: cons.actions.navigation.OPEN_SETTINGS,
|
type: cons.actions.navigation.OPEN_SETTINGS,
|
||||||
|
tabText,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -129,12 +129,13 @@ class Navigation extends EventEmitter {
|
||||||
this.emit(cons.events.navigation.PROFILE_VIEWER_OPENED, action.userId, action.roomId);
|
this.emit(cons.events.navigation.PROFILE_VIEWER_OPENED, action.userId, action.roomId);
|
||||||
},
|
},
|
||||||
[cons.actions.navigation.OPEN_SETTINGS]: () => {
|
[cons.actions.navigation.OPEN_SETTINGS]: () => {
|
||||||
this.emit(cons.events.navigation.SETTINGS_OPENED);
|
this.emit(cons.events.navigation.SETTINGS_OPENED, action.tabText);
|
||||||
},
|
},
|
||||||
[cons.actions.navigation.OPEN_EMOJIBOARD]: () => {
|
[cons.actions.navigation.OPEN_EMOJIBOARD]: () => {
|
||||||
this.emit(
|
this.emit(
|
||||||
cons.events.navigation.EMOJIBOARD_OPENED,
|
cons.events.navigation.EMOJIBOARD_OPENED,
|
||||||
action.cords, action.requestEmojiCallback,
|
action.cords,
|
||||||
|
action.requestEmojiCallback,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[cons.actions.navigation.OPEN_READRECEIPTS]: () => {
|
[cons.actions.navigation.OPEN_READRECEIPTS]: () => {
|
||||||
|
|
Loading…
Reference in a new issue