remove twemoji

This commit is contained in:
array-in-a-matrix 2023-04-03 22:59:00 -04:00
parent d6c8036a81
commit 5c5acbd763
12 changed files with 469 additions and 403 deletions

View file

@ -2,7 +2,6 @@ import React, { useState, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import './CreateRoom.scss';
import { twemojify } from '../../../util/twemojify';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons';
import navigation from '../../../client/state/navigation';
@ -92,7 +91,7 @@ function CreateRoomContent({ isSpace, parentId, onRequestClose }) {
topic,
joinRule,
alias: roomAlias,
isEncrypted: (isSpace || joinRule === 'public') ? false : isEncrypted,
isEncrypted: isSpace || joinRule === 'public' ? false : isEncrypted,
powerLevel,
isSpace,
parentId,
@ -131,36 +130,35 @@ function CreateRoomContent({ isSpace, parentId, onRequestClose }) {
const joinRules = ['invite', 'restricted', 'public'];
const joinRuleShortText = ['Private', 'Restricted', 'Public'];
const joinRuleText = ['Private (invite only)', 'Restricted (space member can join)', 'Public (anyone can join)'];
const joinRuleText = [
'Private (invite only)',
'Restricted (space member can join)',
'Public (anyone can join)',
];
const jrRoomIC = [HashLockIC, HashIC, HashGlobeIC];
const jrSpaceIC = [SpaceLockIC, SpaceIC, SpaceGlobeIC];
const handleJoinRule = (evt) => {
openReusableContextMenu(
'bottom',
getEventCords(evt, '.btn-surface'),
(closeMenu) => (
<>
<MenuHeader>Visibility (who can join)</MenuHeader>
{
joinRules.map((rule) => (
<MenuItem
key={rule}
variant={rule === joinRule ? 'positive' : 'surface'}
iconSrc={
isSpace
? jrSpaceIC[joinRules.indexOf(rule)]
: jrRoomIC[joinRules.indexOf(rule)]
}
onClick={() => { closeMenu(); setJoinRule(rule); }}
disabled={!parentId && rule === 'restricted'}
>
{ joinRuleText[joinRules.indexOf(rule)] }
</MenuItem>
))
}
</>
),
);
openReusableContextMenu('bottom', getEventCords(evt, '.btn-surface'), (closeMenu) => (
<>
<MenuHeader>Visibility (who can join)</MenuHeader>
{joinRules.map((rule) => (
<MenuItem
key={rule}
variant={rule === joinRule ? 'positive' : 'surface'}
iconSrc={
isSpace ? jrSpaceIC[joinRules.indexOf(rule)] : jrRoomIC[joinRules.indexOf(rule)]
}
onClick={() => {
closeMenu();
setJoinRule(rule);
}}
disabled={!parentId && rule === 'restricted'}
>
{joinRuleText[joinRules.indexOf(rule)]}
</MenuItem>
))}
</>
));
};
return (
@ -168,50 +166,64 @@ function CreateRoomContent({ isSpace, parentId, onRequestClose }) {
<form className="create-room__form" onSubmit={handleSubmit}>
<SettingTile
title="Visibility"
options={(
options={
<Button onClick={handleJoinRule} iconSrc={ChevronBottomIC}>
{joinRuleShortText[joinRules.indexOf(joinRule)]}
</Button>
)}
content={<Text variant="b3">{`Select who can join this ${isSpace ? 'space' : 'room'}.`}</Text>}
}
content={
<Text variant="b3">{`Select who can join this ${isSpace ? 'space' : 'room'}.`}</Text>
}
/>
{joinRule === 'public' && (
<div>
<Text className="create-room__address__label" variant="b2">{isSpace ? 'Space address' : 'Room address'}</Text>
<Text className="create-room__address__label" variant="b2">
{isSpace ? 'Space address' : 'Room address'}
</Text>
<div className="create-room__address">
<Text variant="b1">#</Text>
<Input
value={addressValue}
onChange={validateAddress}
state={(isValidAddress === false) ? 'error' : 'normal'}
state={isValidAddress === false ? 'error' : 'normal'}
forwardRef={addressRef}
placeholder="my_address"
required
/>
<Text variant="b1">{`:${userHs}`}</Text>
</div>
{isValidAddress === false && <Text className="create-room__address__tip" variant="b3"><span style={{ color: 'var(--bg-danger)' }}>{`#${addressValue}:${userHs} is already in use`}</span></Text>}
{isValidAddress === false && (
<Text className="create-room__address__tip" variant="b3">
<span
style={{ color: 'var(--bg-danger)' }}
>{`#${addressValue}:${userHs} is already in use`}</span>
</Text>
)}
</div>
)}
{!isSpace && joinRule !== 'public' && (
<SettingTile
title="Enable end-to-end encryption"
options={<Toggle isActive={isEncrypted} onToggle={setIsEncrypted} />}
content={<Text variant="b3">You cant disable this later. Bridges & most bots wont work yet.</Text>}
content={
<Text variant="b3">
You cant disable this later. Bridges & most bots wont work yet.
</Text>
}
/>
)}
<SettingTile
title="Select your role"
options={(
options={
<SegmentControl
selected={roleIndex}
segments={[{ text: 'Admin' }, { text: 'Founder' }]}
onSelect={setRoleIndex}
/>
)}
content={(
}
content={
<Text variant="b3">Selecting Admin sets 100 power level whereas Founder sets 101.</Text>
)}
}
/>
<Input name="topic" minHeight={174} resizable label="Topic (optional)" />
<div className="create-room__name-wrapper">
@ -231,7 +243,11 @@ function CreateRoomContent({ isSpace, parentId, onRequestClose }) {
<Text>{`Creating ${isSpace ? 'space' : 'room'}...`}</Text>
</div>
)}
{typeof creatingError === 'string' && <Text className="create-room__error" variant="b3">{creatingError}</Text>}
{typeof creatingError === 'string' && (
<Text className="create-room__error" variant="b3">
{creatingError}
</Text>
)}
</form>
</div>
);
@ -275,27 +291,22 @@ function CreateRoom() {
return (
<Dialog
isOpen={create !== null}
title={(
title={
<Text variant="s1" weight="medium" primary>
{parentId ? twemojify(room.name) : 'Home'}
{parentId ? room.name : 'Home'}
<span style={{ color: 'var(--tc-surface-low)' }}>
{` — create ${isSpace ? 'space' : 'room'}`}
</span>
</Text>
)}
}
contentOptions={<IconButton src={CrossIC} onClick={onRequestClose} tooltip="Close" />}
onRequestClose={onRequestClose}
>
{
create
? (
<CreateRoomContent
isSpace={isSpace}
parentId={parentId}
onRequestClose={onRequestClose}
/>
) : <div />
}
{create ? (
<CreateRoomContent isSpace={isSpace} parentId={parentId} onRequestClose={onRequestClose} />
) : (
<div />
)}
</Dialog>
);
}

View file

@ -55,7 +55,6 @@ const EmojiGroup = React.memo(({ name, groupEmojis }) => {
unicode: emoji.unicode,
shortcodes: emoji.shortcodes?.toString(),
hexcode: emoji.hexcode,
loading: 'lazy',
}),
base: TWEMOJI_BASE_URL,
})
@ -340,7 +339,7 @@ function EmojiBoard({ onSelect, searchRef }) {
</ScrollView>
</div>
<div ref={emojiInfo} className="emoji-board__content__info">
<div>{parse(twemoji.parse('🙂', { base: TWEMOJI_BASE_URL }))}</div>
<div>{parse('🙂')}</div>
<Text>:slight_smile:</Text>
</div>
</div>

View file

@ -2,7 +2,6 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import './EmojiVerification.scss';
import { twemojify } from '../../../util/twemojify';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons';
@ -30,8 +29,9 @@ function EmojiVerificationContent({ data, requestClose }) {
const beginVerification = async () => {
if (
isCrossVerified(mx.deviceId)
&& (mx.getCrossSigningId() === null || await mx.crypto.crossSigningInfo.isStoredInKeyCache('self_signing') === false)
isCrossVerified(mx.deviceId) &&
(mx.getCrossSigningId() === null ||
(await mx.crypto.crossSigningInfo.isStoredInKeyCache('self_signing')) === false)
) {
if (!hasPrivateKey(getDefaultSSKey())) {
const keyData = await accessSecretStorage('Emoji verification');
@ -106,16 +106,20 @@ function EmojiVerificationContent({ data, requestClose }) {
{sas.sas.emoji.map((emoji, i) => (
// eslint-disable-next-line react/no-array-index-key
<div className="emoji-verification__emoji-block" key={`${emoji[1]}-${i}`}>
<Text variant="h1">{twemojify(emoji[0])}</Text>
<Text variant="h1">{emoji[0]}</Text>
<Text>{emoji[1]}</Text>
</div>
))}
</div>
<div className="emoji-verification__buttons">
{process ? renderWait() : (
{process ? (
renderWait()
) : (
<>
<Button variant="primary" onClick={sasConfirm}>They match</Button>
<Button onClick={sasMismatch}>{'They don\'t match'}</Button>
<Button variant="primary" onClick={sasConfirm}>
They match
</Button>
<Button onClick={sasMismatch}>{"They don't match"}</Button>
</>
)}
</div>
@ -127,9 +131,7 @@ function EmojiVerificationContent({ data, requestClose }) {
return (
<div className="emoji-verification__content">
<Text>Please accept the request from other device.</Text>
<div className="emoji-verification__buttons">
{renderWait()}
</div>
<div className="emoji-verification__buttons">{renderWait()}</div>
</div>
);
}
@ -138,11 +140,13 @@ function EmojiVerificationContent({ data, requestClose }) {
<div className="emoji-verification__content">
<Text>Click accept to start the verification process.</Text>
<div className="emoji-verification__buttons">
{
process
? renderWait()
: <Button variant="primary" onClick={beginVerification}>Accept</Button>
}
{process ? (
renderWait()
) : (
<Button variant="primary" onClick={beginVerification}>
Accept
</Button>
)}
</div>
</div>
);
@ -180,19 +184,19 @@ function EmojiVerification() {
<Dialog
isOpen={data !== null}
className="emoji-verification"
title={(
title={
<Text variant="s1" weight="medium" primary>
Emoji verification
</Text>
)}
}
contentOptions={<IconButton src={CrossIC} onClick={requestClose} tooltip="Close" />}
onRequestClose={requestClose}
>
{
data !== null
? <EmojiVerificationContent data={data} requestClose={requestClose} />
: <div />
}
{data !== null ? (
<EmojiVerificationContent data={data} requestClose={requestClose} />
) : (
<div />
)}
</Dialog>
);
}

View file

@ -2,8 +2,6 @@ import React, { useState, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import './DrawerBreadcrumb.scss';
import { twemojify } from '../../../util/twemojify';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons';
import { selectTab, selectSpace } from '../../../client/action/navigation';
@ -49,8 +47,9 @@ function DrawerBreadcrumb({ spaceId }) {
}, [spaceId]);
function getHomeNotiExcept(childId) {
const orphans = roomList.getOrphans()
.filter((id) => (id !== childId))
const orphans = roomList
.getOrphans()
.filter((id) => id !== childId)
.filter((id) => !accountData.spaceShortcut.has(id));
let noti = null;
@ -94,36 +93,35 @@ function DrawerBreadcrumb({ spaceId }) {
<div className="drawer-breadcrumb__wrapper">
<ScrollView ref={scrollRef} horizontal vertical={false} invisible>
<div className="drawer-breadcrumb">
{
spacePath.map((id, index) => {
const noti = (id !== cons.tabs.HOME && index < spacePath.length)
? getNotiExcept(id, (index === spacePath.length - 1) ? null : spacePath[index + 1])
: getHomeNotiExcept((index === spacePath.length - 1) ? null : spacePath[index + 1]);
{spacePath.map((id, index) => {
const noti =
id !== cons.tabs.HOME && index < spacePath.length
? getNotiExcept(id, index === spacePath.length - 1 ? null : spacePath[index + 1])
: getHomeNotiExcept(index === spacePath.length - 1 ? null : spacePath[index + 1]);
return (
<React.Fragment
key={id}
return (
<React.Fragment key={id}>
{index !== 0 && <RawIcon size="extra-small" src={ChevronRightIC} />}
<Button
className={
index === spacePath.length - 1 ? 'drawer-breadcrumb__btn--selected' : ''
}
onClick={() => {
if (id === cons.tabs.HOME) selectTab(id);
else selectSpace(id);
}}
>
{ index !== 0 && <RawIcon size="extra-small" src={ChevronRightIC} />}
<Button
className={index === spacePath.length - 1 ? 'drawer-breadcrumb__btn--selected' : ''}
onClick={() => {
if (id === cons.tabs.HOME) selectTab(id);
else selectSpace(id);
}}
>
<Text variant="b2">{id === cons.tabs.HOME ? 'Home' : twemojify(mx.getRoom(id).name)}</Text>
{ noti !== null && (
<NotificationBadge
alert={noti.highlight !== 0}
content={noti.total > 0 ? abbreviateNumber(noti.total) : null}
/>
)}
</Button>
</React.Fragment>
);
})
}
<Text variant="b2">{id === cons.tabs.HOME ? 'Home' : mx.getRoom(id).name}</Text>
{noti !== null && (
<NotificationBadge
alert={noti.highlight !== 0}
content={noti.total > 0 ? abbreviateNumber(noti.total) : null}
/>
)}
</Button>
</React.Fragment>
);
})}
<div style={{ width: 'var(--sp-extra-tight)', height: '100%' }} />
</div>
</ScrollView>

View file

@ -2,13 +2,16 @@ import React from 'react';
import PropTypes from 'prop-types';
import './DrawerHeader.scss';
import { twemojify } from '../../../util/twemojify';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons';
import {
openPublicRooms, openCreateRoom, openSpaceManage, openJoinAlias,
openSpaceAddExisting, openInviteUser, openReusableContextMenu,
openPublicRooms,
openCreateRoom,
openSpaceManage,
openJoinAlias,
openSpaceAddExisting,
openInviteUser,
openReusableContextMenu,
} from '../../../client/action/navigation';
import { getEventCords } from '../../../util/common';
@ -40,46 +43,64 @@ export function HomeSpaceOptions({ spaceId, afterOptionSelect }) {
<MenuHeader>Add rooms or spaces</MenuHeader>
<MenuItem
iconSrc={SpacePlusIC}
onClick={() => { afterOptionSelect(); openCreateRoom(true, spaceId); }}
onClick={() => {
afterOptionSelect();
openCreateRoom(true, spaceId);
}}
disabled={!canManage}
>
Create new space
</MenuItem>
<MenuItem
iconSrc={HashPlusIC}
onClick={() => { afterOptionSelect(); openCreateRoom(false, spaceId); }}
onClick={() => {
afterOptionSelect();
openCreateRoom(false, spaceId);
}}
disabled={!canManage}
>
Create new room
</MenuItem>
{ !spaceId && (
{!spaceId && (
<MenuItem
iconSrc={HashGlobeIC}
onClick={() => { afterOptionSelect(); openPublicRooms(); }}
onClick={() => {
afterOptionSelect();
openPublicRooms();
}}
>
Explore public rooms
</MenuItem>
)}
{ !spaceId && (
{!spaceId && (
<MenuItem
iconSrc={PlusIC}
onClick={() => { afterOptionSelect(); openJoinAlias(); }}
onClick={() => {
afterOptionSelect();
openJoinAlias();
}}
>
Join with address
</MenuItem>
)}
{ spaceId && (
{spaceId && (
<MenuItem
iconSrc={PlusIC}
onClick={() => { afterOptionSelect(); openSpaceAddExisting(spaceId); }}
onClick={() => {
afterOptionSelect();
openSpaceAddExisting(spaceId);
}}
disabled={!canManage}
>
Add existing
</MenuItem>
)}
{ spaceId && (
{spaceId && (
<MenuItem
onClick={() => { afterOptionSelect(); openSpaceManage(spaceId); }}
onClick={() => {
afterOptionSelect();
openSpaceManage(spaceId);
}}
iconSrc={HashSearchIC}
>
Manage rooms
@ -102,24 +123,20 @@ function DrawerHeader({ selectedTab, spaceId }) {
const isDMTab = selectedTab === cons.tabs.DIRECTS;
const room = mx.getRoom(spaceId);
const spaceName = isDMTab ? null : (room?.name || null);
const spaceName = isDMTab ? null : room?.name || null;
const openSpaceOptions = (e) => {
e.preventDefault();
openReusableContextMenu(
'bottom',
getEventCords(e, '.header'),
(closeMenu) => <SpaceOptions roomId={spaceId} afterOptionSelect={closeMenu} />,
);
openReusableContextMenu('bottom', getEventCords(e, '.header'), (closeMenu) => (
<SpaceOptions roomId={spaceId} afterOptionSelect={closeMenu} />
));
};
const openHomeSpaceOptions = (e) => {
e.preventDefault();
openReusableContextMenu(
'right',
getEventCords(e, '.ic-btn'),
(closeMenu) => <HomeSpaceOptions spaceId={spaceId} afterOptionSelect={closeMenu} />,
);
openReusableContextMenu('right', getEventCords(e, '.ic-btn'), (closeMenu) => (
<HomeSpaceOptions spaceId={spaceId} afterOptionSelect={closeMenu} />
));
};
return (
@ -132,18 +149,31 @@ function DrawerHeader({ selectedTab, spaceId }) {
onMouseUp={(e) => blurOnBubbling(e, '.drawer-header__btn')}
>
<TitleWrapper>
<Text variant="s1" weight="medium" primary>{twemojify(spaceName)}</Text>
<Text variant="s1" weight="medium" primary>
{spaceName}
</Text>
</TitleWrapper>
<RawIcon size="small" src={ChevronBottomIC} />
</button>
) : (
<TitleWrapper>
<Text variant="s1" weight="medium" primary>{tabName}</Text>
<Text variant="s1" weight="medium" primary>
{tabName}
</Text>
</TitleWrapper>
)}
{ isDMTab && <IconButton onClick={() => openInviteUser()} tooltip="Start DM" src={PlusIC} size="small" /> }
{ !isDMTab && <IconButton onClick={openHomeSpaceOptions} tooltip="Add rooms/spaces" src={PlusIC} size="small" /> }
{isDMTab && (
<IconButton onClick={() => openInviteUser()} tooltip="Start DM" src={PlusIC} size="small" />
)}
{!isDMTab && (
<IconButton
onClick={openHomeSpaceOptions}
tooltip="Add rooms/spaces"
src={PlusIC}
size="small"
/>
)}
</Header>
);
}

View file

@ -1,6 +1,5 @@
import React, { useState, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import { twemojify } from '../../../util/twemojify';
import initMatrix from '../../../client/initMatrix';
import colorMXID from '../../../util/colorMXID';
@ -22,7 +21,9 @@ function ProfileEditor({ userId }) {
const user = mx.getUser(mx.getUserId());
const displayNameRef = useRef(null);
const [avatarSrc, setAvatarSrc] = useState(user.avatarUrl ? mx.mxcUrlToHttp(user.avatarUrl, 80, 80, 'crop') : null);
const [avatarSrc, setAvatarSrc] = useState(
user.avatarUrl ? mx.mxcUrlToHttp(user.avatarUrl, 80, 80, 'crop') : null
);
const [username, setUsername] = useState(user.displayName);
const [disabled, setDisabled] = useState(true);
@ -44,7 +45,7 @@ function ProfileEditor({ userId }) {
'Remove avatar',
'Are you sure that you want to remove avatar?',
'Remove',
'caution',
'caution'
);
if (isConfirmed) {
mx.setAvatarUrl('');
@ -79,7 +80,10 @@ function ProfileEditor({ userId }) {
<form
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()}`}
@ -87,7 +91,9 @@ function ProfileEditor({ userId }) {
value={mx.getUser(mx.getUserId()).displayName}
forwardRef={displayNameRef}
/>
<Button variant="primary" type="submit" disabled={disabled}>Save</Button>
<Button variant="primary" type="submit" disabled={disabled}>
Save
</Button>
<Button onClick={cancelDisplayNameChanges}>Cancel</Button>
</form>
);
@ -95,7 +101,9 @@ function ProfileEditor({ userId }) {
const renderInfo = () => (
<div className="profile-editor__info" style={{ marginBottom: avatarSrc ? '24px' : '0' }}>
<div>
<Text variant="h2" primary weight="medium">{twemojify(username) ?? userId}</Text>
<Text variant="h2" primary weight="medium">
{username ?? userId}
</Text>
<IconButton
src={PencilIC}
size="extra-small"
@ -116,9 +124,7 @@ function ProfileEditor({ userId }) {
onUpload={handleAvatarUpload}
onRequestRemove={() => handleAvatarUpload(null)}
/>
{
isEditing ? renderForm() : renderInfo()
}
{isEditing ? renderForm() : renderInfo()}
</div>
);
}

View file

@ -2,8 +2,6 @@ import React, { useState, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import './ProfileViewer.scss';
import { twemojify } from '../../../util/twemojify';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons';
import navigation from '../../../client/state/navigation';
@ -11,7 +9,11 @@ import { selectRoom, openReusableContextMenu } from '../../../client/action/navi
import * as roomActions from '../../../client/action/room';
import {
getUsername, getUsernameOfRoomMember, getPowerLabel, hasDMWith, hasDevices,
getUsername,
getUsernameOfRoomMember,
getPowerLabel,
hasDMWith,
hasDevices,
} from '../../../util/matrixUtil';
import { getEventCords } from '../../../util/common';
import colorMXID from '../../../util/colorMXID';
@ -34,25 +36,21 @@ import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
import { useForceUpdate } from '../../hooks/useForceUpdate';
import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog';
function ModerationTools({
roomId, userId,
}) {
function ModerationTools({ roomId, userId }) {
const mx = initMatrix.matrixClient;
const room = mx.getRoom(roomId);
const roomMember = room.getMember(userId);
const myPowerLevel = room.getMember(mx.getUserId())?.powerLevel || 0;
const powerLevel = roomMember?.powerLevel || 0;
const canIKick = (
roomMember?.membership === 'join'
&& room.currentState.hasSufficientPowerLevelFor('kick', myPowerLevel)
&& powerLevel < myPowerLevel
);
const canIBan = (
['join', 'leave'].includes(roomMember?.membership)
&& room.currentState.hasSufficientPowerLevelFor('ban', myPowerLevel)
&& powerLevel < myPowerLevel
);
const canIKick =
roomMember?.membership === 'join' &&
room.currentState.hasSufficientPowerLevelFor('kick', myPowerLevel) &&
powerLevel < myPowerLevel;
const canIBan =
['join', 'leave'].includes(roomMember?.membership) &&
room.currentState.hasSufficientPowerLevelFor('ban', myPowerLevel) &&
powerLevel < myPowerLevel;
const handleKick = (e) => {
e.preventDefault();
@ -120,13 +118,14 @@ function SessionInfo({ userId }) {
<div className="session-info__chips">
{devices === null && <Text variant="b2">Loading sessions...</Text>}
{devices?.length === 0 && <Text variant="b2">No session found.</Text>}
{devices !== null && (devices.map((device) => (
<Chip
key={device.deviceId}
iconSrc={ShieldEmptyIC}
text={device.getDisplayName() || device.deviceId}
/>
)))}
{devices !== null &&
devices.map((device) => (
<Chip
key={device.deviceId}
iconSrc={ShieldEmptyIC}
text={device.getDisplayName() || device.deviceId}
/>
))}
</div>
);
}
@ -137,7 +136,9 @@ function SessionInfo({ userId }) {
onClick={() => setIsVisible(!isVisible)}
iconSrc={isVisible ? ChevronBottomIC : ChevronRightIC}
>
<Text variant="b2">{`View ${devices?.length > 0 ? `${devices.length} ` : ''}sessions`}</Text>
<Text variant="b2">{`View ${
devices?.length > 0 ? `${devices.length} ` : ''
}sessions`}</Text>
</MenuItem>
{renderSessionChips()}
</div>
@ -164,7 +165,8 @@ function ProfileFooter({ roomId, userId, onRequestClose }) {
const myPowerlevel = room.getMember(mx.getUserId())?.powerLevel || 0;
const userPL = room.getMember(userId)?.powerLevel || 0;
const canIKick = room.currentState.hasSufficientPowerLevelFor('kick', myPowerlevel) && userPL < myPowerlevel;
const canIKick =
room.currentState.hasSufficientPowerLevelFor('kick', myPowerlevel) && userPL < myPowerlevel;
const isBanned = member?.membership === 'ban';
@ -246,31 +248,19 @@ function ProfileFooter({ roomId, userId, onRequestClose }) {
return (
<div className="profile-viewer__buttons">
<Button
variant="primary"
onClick={openDM}
disabled={isCreatingDM}
>
<Button variant="primary" onClick={openDM} disabled={isCreatingDM}>
{isCreatingDM ? 'Creating room...' : 'Message'}
</Button>
{ isBanned && canIKick && (
<Button
variant="positive"
onClick={() => roomActions.unban(roomId, userId)}
>
{isBanned && canIKick && (
<Button variant="positive" onClick={() => roomActions.unban(roomId, userId)}>
Unban
</Button>
)}
{ (isInvited ? canIKick : room.canInvite(mx.getUserId())) && isInvitable && (
<Button
onClick={toggleInvite}
disabled={isInviting}
>
{
isInvited
? `${isInviting ? 'Disinviting...' : 'Disinvite'}`
: `${isInviting ? 'Inviting...' : 'Invite'}`
}
{(isInvited ? canIKick : room.canInvite(mx.getUserId())) && isInvitable && (
<Button onClick={toggleInvite} disabled={isInviting}>
{isInvited
? `${isInviting ? 'Disinviting...' : 'Disinvite'}`
: `${isInviting ? 'Inviting...' : 'Invite'}`}
</Button>
)}
<Button
@ -278,11 +268,9 @@ function ProfileFooter({ roomId, userId, onRequestClose }) {
onClick={toggleIgnore}
disabled={isIgnoring}
>
{
isUserIgnored
? `${isIgnoring ? 'Unignoring...' : 'Unignore'}`
: `${isIgnoring ? 'Ignoring...' : 'Ignore'}`
}
{isUserIgnored
? `${isIgnoring ? 'Unignoring...' : 'Unignore'}`
: `${isIgnoring ? 'Ignoring...' : 'Ignore'}`}
</Button>
</div>
);
@ -326,8 +314,8 @@ function useRerenderOnProfileChange(roomId, userId) {
useEffect(() => {
const handleProfileChange = (mEvent, member) => {
if (
mEvent.getRoomId() === roomId
&& (member.userId === userId || member.userId === mx.getUserId())
mEvent.getRoomId() === roomId &&
(member.userId === userId || member.userId === mx.getUserId())
) {
forceUpdate();
}
@ -352,20 +340,22 @@ function ProfileViewer() {
const roomMember = room.getMember(userId);
const username = roomMember ? getUsernameOfRoomMember(roomMember) : getUsername(userId);
const avatarMxc = roomMember?.getMxcAvatarUrl?.() || mx.getUser(userId)?.avatarUrl;
const avatarUrl = (avatarMxc && avatarMxc !== 'null') ? mx.mxcUrlToHttp(avatarMxc, 80, 80, 'crop') : null;
const avatarUrl =
avatarMxc && avatarMxc !== 'null' ? mx.mxcUrlToHttp(avatarMxc, 80, 80, 'crop') : null;
const powerLevel = roomMember?.powerLevel || 0;
const myPowerLevel = room.getMember(mx.getUserId())?.powerLevel || 0;
const canChangeRole = (
room.currentState.maySendEvent('m.room.power_levels', mx.getUserId())
&& (powerLevel < myPowerLevel || userId === mx.getUserId())
);
const canChangeRole =
room.currentState.maySendEvent('m.room.power_levels', mx.getUserId()) &&
(powerLevel < myPowerLevel || userId === mx.getUserId());
const handleChangePowerLevel = async (newPowerLevel) => {
if (newPowerLevel === powerLevel) return;
const SHARED_POWER_MSG = 'You will not be able to undo this change as you are promoting the user to have the same power level as yourself. Are you sure?';
const DEMOTING_MYSELF_MSG = 'You will not be able to undo this change as you are demoting yourself. Are you sure?';
const SHARED_POWER_MSG =
'You will not be able to undo this change as you are promoting the user to have the same power level as yourself. Are you sure?';
const DEMOTING_MYSELF_MSG =
'You will not be able to undo this change as you are demoting yourself. Are you sure?';
const isSharedPower = newPowerLevel === myPowerLevel;
const isDemotingMyself = userId === mx.getUserId();
@ -374,7 +364,7 @@ function ProfileViewer() {
'Change power level',
isSharedPower ? SHARED_POWER_MSG : DEMOTING_MYSELF_MSG,
'Change',
'caution',
'caution'
);
if (!isConfirmed) return;
roomActions.setPowerLevel(roomId, userId, newPowerLevel);
@ -384,20 +374,16 @@ function ProfileViewer() {
};
const handlePowerSelector = (e) => {
openReusableContextMenu(
'bottom',
getEventCords(e, '.btn-surface'),
(closeMenu) => (
<PowerLevelSelector
value={powerLevel}
max={myPowerLevel}
onSelect={(pl) => {
closeMenu();
handleChangePowerLevel(pl);
}}
/>
),
);
openReusableContextMenu('bottom', getEventCords(e, '.btn-surface'), (closeMenu) => (
<PowerLevelSelector
value={powerLevel}
max={myPowerLevel}
onSelect={(pl) => {
closeMenu();
handleChangePowerLevel(pl);
}}
/>
));
};
return (
@ -405,8 +391,10 @@ function ProfileViewer() {
<div className="profile-viewer__user">
<Avatar imageSrc={avatarUrl} text={username} bgColor={colorMXID(userId)} size="large" />
<div className="profile-viewer__user__info">
<Text variant="s1" weight="medium">{twemojify(username)}</Text>
<Text variant="b2">{twemojify(userId)}</Text>
<Text variant="s1" weight="medium">
{username}
</Text>
<Text variant="b2">{userId}</Text>
</div>
<div className="profile-viewer__user__role">
<Text variant="b3">Role</Text>
@ -420,7 +408,7 @@ function ProfileViewer() {
</div>
<ModerationTools roomId={roomId} userId={userId} />
<SessionInfo userId={userId} />
{ userId !== mx.getUserId() && (
{userId !== mx.getUserId() && (
<ProfileFooter roomId={roomId} userId={userId} onRequestClose={closeDialog} />
)}
</div>

View file

@ -3,7 +3,6 @@ import React, { useState } from 'react';
import './CrossSigning.scss';
import FileSaver from 'file-saver';
import { Formik } from 'formik';
import { twemojify } from '../../../util/twemojify';
import initMatrix from '../../../client/initMatrix';
import { openReusableDialog } from '../../../client/action/navigation';
@ -22,15 +21,17 @@ import { useCrossSigningStatus } from '../../hooks/useCrossSigningStatus';
const failedDialog = () => {
const renderFailure = (requestClose) => (
<div className="cross-signing__failure">
<Text variant="h1">{twemojify('❌')}</Text>
<Text variant="h1">{'❌'}</Text>
<Text weight="medium">Failed to setup cross signing. Please try again.</Text>
<Button onClick={requestClose}>Close</Button>
</div>
);
openReusableDialog(
<Text variant="s1" weight="medium">Setup cross signing</Text>,
renderFailure,
<Text variant="s1" weight="medium">
Setup cross signing
</Text>,
renderFailure
);
};
@ -48,11 +49,11 @@ const securityKeyDialog = (key) => {
const renderSecurityKey = () => (
<div className="cross-signing__key">
<Text weight="medium">Please save this security key somewhere safe.</Text>
<Text className="cross-signing__key-text">
{key.encodedPrivateKey}
</Text>
<Text className="cross-signing__key-text">{key.encodedPrivateKey}</Text>
<div className="cross-signing__key-btn">
<Button variant="primary" onClick={() => copyKey(key)}>Copy</Button>
<Button variant="primary" onClick={() => copyKey(key)}>
Copy
</Button>
<Button onClick={() => downloadKey(key)}>Download</Button>
</div>
</div>
@ -62,8 +63,10 @@ const securityKeyDialog = (key) => {
downloadKey();
openReusableDialog(
<Text variant="s1" weight="medium">Security Key</Text>,
() => renderSecurityKey(),
<Text variant="s1" weight="medium">
Security Key
</Text>,
() => renderSecurityKey()
);
};
@ -112,7 +115,7 @@ function CrossSigningSetup() {
errors.phrase = 'Phrase must contain 8-127 characters with no space.';
}
if (values.confirmPhrase.length > 0 && values.confirmPhrase !== values.phrase) {
errors.confirmPhrase = 'Phrase don\'t match.';
errors.confirmPhrase = "Phrase don't match.";
}
return errors;
};
@ -121,10 +124,14 @@ function CrossSigningSetup() {
<div className="cross-signing__setup">
<div className="cross-signing__setup-entry">
<Text>
We will generate a <b>Security Key</b>,
which you can use to manage messages backup and session verification.
We will generate a <b>Security Key</b>, which you can use to manage messages backup and
session verification.
</Text>
{genWithPhrase !== false && <Button variant="primary" onClick={() => setup()} disabled={genWithPhrase !== undefined}>Generate Key</Button>}
{genWithPhrase !== false && (
<Button variant="primary" onClick={() => setup()} disabled={genWithPhrase !== undefined}>
Generate Key
</Button>
)}
{genWithPhrase === false && <Spinner size="small" />}
</div>
<Text className="cross-signing__setup-divider">OR</Text>
@ -133,9 +140,7 @@ function CrossSigningSetup() {
onSubmit={(values) => setup(values.phrase)}
validate={validator}
>
{({
values, errors, handleChange, handleSubmit,
}) => (
{({ values, errors, handleChange, handleSubmit }) => (
<form
className="cross-signing__setup-entry"
onSubmit={handleSubmit}
@ -143,8 +148,8 @@ function CrossSigningSetup() {
>
<Text>
Alternatively you can also set a <b>Security Phrase </b>
so you don't have to remember long Security Key,
and optionally save the Key as backup.
so you don't have to remember long Security Key, and optionally save the Key as
backup.
</Text>
<Input
name="phrase"
@ -155,7 +160,11 @@ function CrossSigningSetup() {
required
disabled={genWithPhrase !== undefined}
/>
{errors.phrase && <Text variant="b3" className="cross-signing__error">{errors.phrase}</Text>}
{errors.phrase && (
<Text variant="b3" className="cross-signing__error">
{errors.phrase}
</Text>
)}
<Input
name="confirmPhrase"
value={values.confirmPhrase}
@ -165,8 +174,16 @@ function CrossSigningSetup() {
required
disabled={genWithPhrase !== undefined}
/>
{errors.confirmPhrase && <Text variant="b3" className="cross-signing__error">{errors.confirmPhrase}</Text>}
{genWithPhrase !== true && <Button variant="primary" type="submit" disabled={genWithPhrase !== undefined}>Set Phrase & Generate Key</Button>}
{errors.confirmPhrase && (
<Text variant="b3" className="cross-signing__error">
{errors.confirmPhrase}
</Text>
)}
{genWithPhrase !== true && (
<Button variant="primary" type="submit" disabled={genWithPhrase !== undefined}>
Set Phrase & Generate Key
</Button>
)}
{genWithPhrase === true && <Spinner size="small" />}
</form>
)}
@ -177,31 +194,36 @@ function CrossSigningSetup() {
const setupDialog = () => {
openReusableDialog(
<Text variant="s1" weight="medium">Setup cross signing</Text>,
() => <CrossSigningSetup />,
<Text variant="s1" weight="medium">
Setup cross signing
</Text>,
() => <CrossSigningSetup />
);
};
function CrossSigningReset() {
return (
<div className="cross-signing__reset">
<Text variant="h1">{twemojify('✋🧑‍🚒🤚')}</Text>
<Text variant="h1">{'✋🧑‍🚒🤚'}</Text>
<Text weight="medium">Resetting cross-signing keys is permanent.</Text>
<Text>
Anyone you have verified with will see security alerts and your message backup will be lost.
You almost certainly do not want to do this,
unless you have lost <b>Security Key</b> or <b>Phrase</b> and
every session you can cross-sign from.
Anyone you have verified with will see security alerts and your message backup will be lost.
You almost certainly do not want to do this, unless you have lost <b>Security Key</b> or{' '}
<b>Phrase</b> and every session you can cross-sign from.
</Text>
<Button variant="danger" onClick={setupDialog}>Reset</Button>
<Button variant="danger" onClick={setupDialog}>
Reset
</Button>
</div>
);
}
const resetDialog = () => {
openReusableDialog(
<Text variant="s1" weight="medium">Reset cross signing</Text>,
() => <CrossSigningReset />,
<Text variant="s1" weight="medium">
Reset cross signing
</Text>,
() => <CrossSigningReset />
);
};
@ -210,12 +232,23 @@ function CrossSignin() {
return (
<SettingTile
title="Cross signing"
content={<Text variant="b3">Setup to verify and keep track of all your sessions. Also required to backup encrypted message.</Text>}
options={(
isCSEnabled
? <Button variant="danger" onClick={resetDialog}>Reset</Button>
: <Button variant="primary" onClick={setupDialog}>Setup</Button>
)}
content={
<Text variant="b3">
Setup to verify and keep track of all your sessions. Also required to backup encrypted
message.
</Text>
}
options={
isCSEnabled ? (
<Button variant="danger" onClick={resetDialog}>
Reset
</Button>
) : (
<Button variant="primary" onClick={setupDialog}>
Setup
</Button>
)
}
/>
);
}

View file

@ -2,7 +2,6 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import './KeyBackup.scss';
import { twemojify } from '../../../util/twemojify';
import initMatrix from '../../../client/initMatrix';
import { openReusableDialog } from '../../../client/action/navigation';
@ -34,10 +33,7 @@ function CreateKeyBackupDialog({ keyData }) {
let info;
try {
info = await mx.prepareKeyBackupVersion(
null,
{ secureSecretStorage: true },
);
info = await mx.prepareKeyBackupVersion(null, { secureSecretStorage: true });
info = await mx.createKeyBackupVersion(info);
await mx.scheduleAllGroupSessionsForBackup();
if (!mountStore.getItem()) return;
@ -65,7 +61,7 @@ function CreateKeyBackupDialog({ keyData }) {
)}
{done === true && (
<>
<Text variant="h1">{twemojify('✅')}</Text>
<Text variant="h1">{'✅'}</Text>
<Text>Successfully created backup</Text>
</>
)}
@ -104,12 +100,9 @@ function RestoreKeyBackupDialog({ keyData }) {
try {
const backupInfo = await mx.getKeyBackupVersion();
const info = await mx.restoreKeyBackupWithSecretStorage(
backupInfo,
undefined,
undefined,
{ progressCallback },
);
const info = await mx.restoreKeyBackupWithSecretStorage(backupInfo, undefined, undefined, {
progressCallback,
});
if (!mountStore.getItem()) return;
setStatus({ done: `Successfully restored backup keys (${info.imported}/${info.total}).` });
} catch (e) {
@ -138,7 +131,7 @@ function RestoreKeyBackupDialog({ keyData }) {
)}
{status.done && (
<>
<Text variant="h1">{twemojify('✅')}</Text>
<Text variant="h1">{'✅'}</Text>
<Text>{status.done}</Text>
</>
)}
@ -176,14 +169,16 @@ function DeleteKeyBackupDialog({ requestClose }) {
return (
<div className="key-backup__delete">
<Text variant="h1">{twemojify('🗑')}</Text>
<Text variant="h1">{'🗑'}</Text>
<Text weight="medium">Deleting key backup is permanent.</Text>
<Text>All encrypted messages keys stored on server will be deleted.</Text>
{
isDeleting
? <Spinner size="small" />
: <Button variant="danger" onClick={deleteBackup}>Delete</Button>
}
{isDeleting ? (
<Spinner size="small" />
) : (
<Button variant="danger" onClick={deleteBackup}>
Delete
</Button>
)}
</div>
);
}
@ -224,9 +219,11 @@ function KeyBackup() {
if (keyData === null) return;
openReusableDialog(
<Text variant="s1" weight="medium">Create Key Backup</Text>,
<Text variant="s1" weight="medium">
Create Key Backup
</Text>,
() => <CreateKeyBackupDialog keyData={keyData} />,
() => fetchKeyBackupVersion(),
() => fetchKeyBackupVersion()
);
};
@ -235,29 +232,44 @@ function KeyBackup() {
if (keyData === null) return;
openReusableDialog(
<Text variant="s1" weight="medium">Restore Key Backup</Text>,
() => <RestoreKeyBackupDialog keyData={keyData} />,
<Text variant="s1" weight="medium">
Restore Key Backup
</Text>,
() => <RestoreKeyBackupDialog keyData={keyData} />
);
};
const openDeleteKeyBackup = () => openReusableDialog(
<Text variant="s1" weight="medium">Delete Key Backup</Text>,
(requestClose) => (
<DeleteKeyBackupDialog
requestClose={(isDone) => {
if (isDone) setKeyBackup(null);
requestClose();
}}
/>
),
);
const openDeleteKeyBackup = () =>
openReusableDialog(
<Text variant="s1" weight="medium">
Delete Key Backup
</Text>,
(requestClose) => (
<DeleteKeyBackupDialog
requestClose={(isDone) => {
if (isDone) setKeyBackup(null);
requestClose();
}}
/>
)
);
const renderOptions = () => {
if (keyBackup === undefined) return <Spinner size="small" />;
if (keyBackup === null) return <Button variant="primary" onClick={openCreateKeyBackup}>Create Backup</Button>;
if (keyBackup === null)
return (
<Button variant="primary" onClick={openCreateKeyBackup}>
Create Backup
</Button>
);
return (
<>
<IconButton src={DownloadIC} variant="positive" onClick={openRestoreKeyBackup} tooltip="Restore backup" />
<IconButton
src={DownloadIC}
variant="positive"
onClick={openRestoreKeyBackup}
tooltip="Restore backup"
/>
<IconButton src={BinIC} onClick={openDeleteKeyBackup} tooltip="Delete backup" />
</>
);
@ -266,9 +278,12 @@ function KeyBackup() {
return (
<SettingTile
title="Encrypted messages backup"
content={(
content={
<>
<Text variant="b3">Online backup your encrypted messages keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Security Key.</Text>
<Text variant="b3">
Online backup your encrypted messages keys with your account data in case you lose
access to your sessions. Your keys will be secured with a unique Security Key.
</Text>
{!isCSEnabled && (
<InfoCard
style={{ marginTop: 'var(--sp-ultra-tight)' }}
@ -279,7 +294,7 @@ function KeyBackup() {
/>
)}
</>
)}
}
options={isCSEnabled ? renderOptions() : null}
/>
);

View file

@ -80,7 +80,7 @@ function AppearanceSection() {
{ text: 'Butter' },
{ text: 'Nord Dark' },
{ text: 'Cyberpunk' },
{ text: 'Almond Dark'},
{ text: 'Almond Dark' },
]}
onSelect={(index) => {
if (settings.useSystemTheme) toggleSystemTheme();
@ -328,28 +328,6 @@ function AboutSection() {
.
</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>

View file

@ -3,8 +3,6 @@ import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import './SpaceManage.scss';
import { twemojify } from '../../../util/twemojify';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons';
import navigation from '../../../client/state/navigation';
@ -37,32 +35,37 @@ function SpaceManageBreadcrumb({ path, onSelect }) {
<div className="space-manage-breadcrumb__wrapper">
<ScrollView horizontal vertical={false} invisible>
<div className="space-manage-breadcrumb">
{
path.map((item, index) => (
<React.Fragment key={item.roomId}>
{index > 0 && <RawIcon size="extra-small" src={ChevronRightIC} />}
<Button onClick={() => onSelect(item.roomId, item.name)}>
<Text variant="b2">{twemojify(item.name)}</Text>
</Button>
</React.Fragment>
))
}
{path.map((item, index) => (
<React.Fragment key={item.roomId}>
{index > 0 && <RawIcon size="extra-small" src={ChevronRightIC} />}
<Button onClick={() => onSelect(item.roomId, item.name)}>
<Text variant="b2">{item.name}</Text>
</Button>
</React.Fragment>
))}
</div>
</ScrollView>
</div>
);
}
SpaceManageBreadcrumb.propTypes = {
path: PropTypes.arrayOf(PropTypes.exact({
roomId: PropTypes.string,
name: PropTypes.string,
})).isRequired,
path: PropTypes.arrayOf(
PropTypes.exact({
roomId: PropTypes.string,
name: PropTypes.string,
})
).isRequired,
onSelect: PropTypes.func.isRequired,
};
function SpaceManageItem({
parentId, roomInfo, onSpaceClick, requestClose,
isSelected, onSelect, roomHierarchy,
parentId,
roomInfo,
onSpaceClick,
requestClose,
isSelected,
onSelect,
roomHierarchy,
}) {
const [isExpand, setIsExpand] = useState(false);
const [isJoining, setIsJoining] = useState(false);
@ -72,8 +75,11 @@ function SpaceManageItem({
const parentRoom = mx.getRoom(parentId);
const isSpace = roomInfo.room_type === 'm.space';
const roomId = roomInfo.room_id;
const canManage = parentRoom?.currentState.maySendStateEvent('m.space.child', mx.getUserId()) || false;
const isSuggested = parentRoom?.currentState.getStateEvents('m.space.child', roomId)?.getContent().suggested === true;
const canManage =
parentRoom?.currentState.maySendStateEvent('m.space.child', mx.getUserId()) || false;
const isSuggested =
parentRoom?.currentState.getStateEvents('m.space.child', roomId)?.getContent().suggested ===
true;
const room = mx.getRoom(roomId);
const isJoined = !!(room?.getMyMembership() === 'join' || null);
@ -103,17 +109,13 @@ function SpaceManageItem({
bgColor={colorMXID(roomId)}
imageSrc={isDM ? imageSrc : null}
iconColor="var(--ic-surface-low)"
iconSrc={
isDM
? null
: joinRuleToIconSrc((roomInfo.join_rules || roomInfo.join_rule), isSpace)
}
iconSrc={isDM ? null : joinRuleToIconSrc(roomInfo.join_rules || roomInfo.join_rule, isSpace)}
size="extra-small"
/>
);
const roomNameJSX = (
<Text>
{twemojify(name)}
{name}
<Text variant="b3" span>{`${roomInfo.num_joined_members} members`}</Text>
</Text>
);
@ -130,11 +132,11 @@ function SpaceManageItem({
);
return (
<div
className={`space-manage-item${isSpace ? '--space' : ''}`}
>
<div className={`space-manage-item${isSpace ? '--space' : ''}`}>
<div>
{canManage && <Checkbox isActive={isSelected} onToggle={() => onSelect(roomId)} variant="positive" />}
{canManage && (
<Checkbox isActive={isSelected} onToggle={() => onSelect(roomId)} variant="positive" />
)}
<button
className="space-manage-item__btn"
onClick={isSpace ? () => onSpaceClick(roomId, name) : null}
@ -145,13 +147,15 @@ function SpaceManageItem({
{isSuggested && <Text variant="b2">Suggested</Text>}
</button>
{roomInfo.topic && expandBtnJsx}
{
isJoined
? <Button onClick={handleOpen}>Open</Button>
: <Button variant="primary" onClick={handleJoin} disabled={isJoining}>{isJoining ? 'Joining...' : 'Join'}</Button>
}
{isJoined ? (
<Button onClick={handleOpen}>Open</Button>
) : (
<Button variant="primary" onClick={handleJoin} disabled={isJoining}>
{isJoining ? 'Joining...' : 'Join'}
</Button>
)}
</div>
{isExpand && roomInfo.topic && <Text variant="b2">{twemojify(roomInfo.topic, undefined, true)}</Text>}
{isExpand && roomInfo.topic && <Text variant="b2">{roomInfo.topic}</Text>}
</div>
);
}
@ -201,9 +205,11 @@ function SpaceManageFooter({ parentId, selected }) {
<div className="space-manage__footer">
{process && <Spinner size="small" />}
<Text weight="medium">{process || `${selected.length} item selected`}</Text>
{ !process && (
{!process && (
<>
<Button onClick={handleRemove} variant="danger">Remove</Button>
<Button onClick={handleRemove} variant="danger">
Remove
</Button>
<Button
onClick={() => handleToggleSuggested(!allSuggested)}
variant={allSuggested ? 'surface' : 'primary'}
@ -336,20 +342,19 @@ function SpaceManageContent({ roomId, requestClose }) {
if (!currentHierarchy) loadRoomHierarchy();
return (
<div className="space-manage__content">
{spacePath.length > 1 && (
<SpaceManageBreadcrumb path={spacePath} onSelect={addPathItem} />
)}
<Text variant="b3" weight="bold">Rooms and spaces</Text>
{spacePath.length > 1 && <SpaceManageBreadcrumb path={spacePath} onSelect={addPathItem} />}
<Text variant="b3" weight="bold">
Rooms and spaces
</Text>
<div className="space-manage__content-items">
{!isLoading && currentHierarchy?.rooms?.length === 1 && (
<Text>
Either the space contains private rooms or you need to join space to view it's rooms.
</Text>
)}
{currentHierarchy && (currentHierarchy.rooms?.map((roomInfo) => (
roomInfo.room_id === currentPath.roomId
? null
: (
{currentHierarchy &&
currentHierarchy.rooms?.map((roomInfo) =>
roomInfo.room_id === currentPath.roomId ? null : (
<SpaceManageItem
key={roomInfo.room_id}
isSelected={selected.includes(roomInfo.room_id)}
@ -361,7 +366,7 @@ function SpaceManageContent({ roomId, requestClose }) {
onSelect={handleSelected}
/>
)
)))}
)}
{!currentHierarchy && <Text>loading...</Text>}
</div>
{currentHierarchy?.canLoadMore && !isLoading && (
@ -410,20 +415,16 @@ function SpaceManage() {
<PopupWindow
isOpen={roomId !== null}
className="space-manage"
title={(
title={
<Text variant="s1" weight="medium" primary>
{roomId && twemojify(room.name)}
{roomId && room.name}
<span style={{ color: 'var(--tc-surface-low)' }}> manage rooms</span>
</Text>
)}
}
contentOptions={<IconButton src={CrossIC} onClick={requestClose} tooltip="Close" />}
onRequestClose={requestClose}
>
{
roomId
? <SpaceManageContent roomId={roomId} requestClose={requestClose} />
: <div />
}
{roomId ? <SpaceManageContent roomId={roomId} requestClose={requestClose} /> : <div />}
</PopupWindow>
);
}

View file

@ -2,8 +2,6 @@ import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import './SpaceSettings.scss';
import { twemojify } from '../../../util/twemojify';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons';
import navigation from '../../../client/state/navigation';
@ -48,23 +46,28 @@ const tabText = {
PERMISSIONS: 'Permissions',
};
const tabItems = [{
iconSrc: SettingsIC,
text: tabText.GENERAL,
disabled: false,
}, {
iconSrc: UserIC,
text: tabText.MEMBERS,
disabled: false,
}, {
iconSrc: EmojiIC,
text: tabText.EMOJIS,
disabled: false,
}, {
iconSrc: ShieldUserIC,
text: tabText.PERMISSIONS,
disabled: false,
}];
const tabItems = [
{
iconSrc: SettingsIC,
text: tabText.GENERAL,
disabled: false,
},
{
iconSrc: UserIC,
text: tabText.MEMBERS,
disabled: false,
},
{
iconSrc: EmojiIC,
text: tabText.EMOJIS,
disabled: false,
},
{
iconSrc: ShieldUserIC,
text: tabText.PERMISSIONS,
disabled: false,
},
];
function GeneralSettings({ roomId }) {
const isPinned = initMatrix.accountData.spaceShortcut.has(roomId);
@ -103,7 +106,7 @@ function GeneralSettings({ roomId }) {
'Leave space',
`Are you sure that you want to leave "${roomName}" space?`,
'Leave',
'danger',
'danger'
);
if (isConfirmed) leave(roomId);
}}
@ -165,12 +168,12 @@ function SpaceSettings() {
<PopupWindow
isOpen={isOpen}
className="space-settings"
title={(
title={
<Text variant="s1" weight="medium" primary>
{isOpen && twemojify(room.name)}
{isOpen && room.name}
<span style={{ color: 'var(--tc-surface-low)' }}> space settings</span>
</Text>
)}
}
contentOptions={<IconButton src={CrossIC} onClick={requestClose} tooltip="Close" />}
onRequestClose={requestClose}
>