Files
cinny/src/app/organisms/create-room/CreateRoom.jsx
T

304 lines
10 KiB
React
Raw Normal View History

2021-11-23 16:24:12 +05:30
import React, { useState, useEffect, useRef } from 'react';
2021-07-28 18:45:52 +05:30
import PropTypes from 'prop-types';
2021-08-31 18:43:31 +05:30
import './CreateRoom.scss';
2021-07-28 18:45:52 +05:30
2022-02-26 21:00:52 +05:30
import { twemojify } from '../../../util/twemojify';
2021-07-28 18:45:52 +05:30
import initMatrix from '../../../client/initMatrix';
2021-11-23 16:24:12 +05:30
import cons from '../../../client/state/cons';
2022-02-26 21:00:52 +05:30
import navigation from '../../../client/state/navigation';
import { selectRoom, openReusableContextMenu } from '../../../client/action/navigation';
2021-07-28 18:45:52 +05:30
import * as roomActions from '../../../client/action/room';
2022-02-26 21:00:52 +05:30
import { isRoomAliasAvailable, getIdServer } from '../../../util/matrixUtil';
import { getEventCords } from '../../../util/common';
2021-07-28 18:45:52 +05:30
import Text from '../../atoms/text/Text';
import Button from '../../atoms/button/Button';
import Toggle from '../../atoms/button/Toggle';
import IconButton from '../../atoms/button/IconButton';
2022-02-26 21:00:52 +05:30
import { MenuHeader, MenuItem } from '../../atoms/context-menu/ContextMenu';
2021-07-28 18:45:52 +05:30
import Input from '../../atoms/input/Input';
import Spinner from '../../atoms/spinner/Spinner';
2021-10-29 14:59:16 +05:30
import SegmentControl from '../../atoms/segmented-controls/SegmentedControls';
2022-02-26 21:00:52 +05:30
import Dialog from '../../molecules/dialog/Dialog';
2021-07-28 18:45:52 +05:30
import SettingTile from '../../molecules/setting-tile/SettingTile';
import HashPlusIC from '../../../../public/res/ic/outlined/hash-plus.svg';
2022-02-26 21:00:52 +05:30
import SpacePlusIC from '../../../../public/res/ic/outlined/space-plus.svg';
import HashIC from '../../../../public/res/ic/outlined/hash.svg';
import HashLockIC from '../../../../public/res/ic/outlined/hash-lock.svg';
import HashGlobeIC from '../../../../public/res/ic/outlined/hash-globe.svg';
import SpaceIC from '../../../../public/res/ic/outlined/space.svg';
import SpaceLockIC from '../../../../public/res/ic/outlined/space-lock.svg';
import SpaceGlobeIC from '../../../../public/res/ic/outlined/space-globe.svg';
import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg';
2021-07-28 18:45:52 +05:30
import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
2022-02-26 21:00:52 +05:30
function CreateRoomContent({ isSpace, parentId, onRequestClose }) {
const [joinRule, setJoinRule] = useState(parentId ? 'restricted' : 'invite');
const [isEncrypted, setIsEncrypted] = useState(true);
const [isCreatingRoom, setIsCreatingRoom] = useState(false);
const [creatingError, setCreatingError] = useState(null);
2021-07-28 18:45:52 +05:30
2022-02-26 21:00:52 +05:30
const [isValidAddress, setIsValidAddress] = useState(null);
const [addressValue, setAddressValue] = useState(undefined);
2021-10-29 14:59:16 +05:30
const [roleIndex, setRoleIndex] = useState(0);
2021-07-28 18:45:52 +05:30
const addressRef = useRef(null);
2022-02-26 21:00:52 +05:30
const mx = initMatrix.matrixClient;
const userHs = getIdServer(mx.getUserId());
2021-11-23 16:24:12 +05:30
useEffect(() => {
const { roomList } = initMatrix;
2022-02-26 21:00:52 +05:30
const onCreated = (roomId) => {
setIsCreatingRoom(false);
setCreatingError(null);
2022-02-27 10:50:27 +05:30
setIsValidAddress(null);
2022-02-26 21:00:52 +05:30
setAddressValue(undefined);
if (!mx.getRoom(roomId)?.isSpaceRoom()) {
selectRoom(roomId);
}
onRequestClose();
};
2021-11-23 16:24:12 +05:30
roomList.on(cons.events.roomList.ROOM_CREATED, onCreated);
return () => {
roomList.removeListener(cons.events.roomList.ROOM_CREATED, onCreated);
};
}, []);
2022-02-26 21:00:52 +05:30
const handleSubmit = async (evt) => {
evt.preventDefault();
const { target } = evt;
2021-07-28 18:45:52 +05:30
if (isCreatingRoom) return;
2022-02-26 21:00:52 +05:30
setIsCreatingRoom(true);
setCreatingError(null);
const name = target.name.value;
let topic = target.topic.value;
2021-07-28 18:45:52 +05:30
if (topic.trim() === '') topic = undefined;
let roomAlias;
2022-02-26 21:00:52 +05:30
if (joinRule === 'public') {
2021-07-28 18:45:52 +05:30
roomAlias = addressRef?.current?.value;
if (roomAlias.trim() === '') roomAlias = undefined;
}
2021-10-29 14:59:16 +05:30
const powerLevel = roleIndex === 1 ? 101 : undefined;
2021-07-28 18:45:52 +05:30
try {
2022-02-26 21:00:52 +05:30
await roomActions.createRoom({
name,
topic,
joinRule,
alias: roomAlias,
isEncrypted: (isSpace || joinRule === 'public') ? false : isEncrypted,
powerLevel,
isSpace,
parentId,
2021-07-28 18:45:52 +05:30
});
} catch (e) {
if (e.message === 'M_UNKNOWN: Invalid characters in room alias') {
2022-02-26 21:00:52 +05:30
setCreatingError('ERROR: Invalid characters in address');
setIsValidAddress(false);
2021-07-28 18:45:52 +05:30
} else if (e.message === 'M_ROOM_IN_USE: Room alias already taken') {
2022-02-26 21:00:52 +05:30
setCreatingError('ERROR: This address is already in use');
setIsValidAddress(false);
} else setCreatingError(e.message);
setIsCreatingRoom(false);
2021-07-28 18:45:52 +05:30
}
2022-02-26 21:00:52 +05:30
};
2021-07-28 18:45:52 +05:30
2022-02-26 21:00:52 +05:30
const validateAddress = (e) => {
2021-07-28 18:45:52 +05:30
const myAddress = e.target.value;
2022-02-26 21:00:52 +05:30
setIsValidAddress(null);
setAddressValue(e.target.value);
setCreatingError(null);
2021-07-28 18:45:52 +05:30
setTimeout(async () => {
if (myAddress !== addressRef.current.value) return;
const roomAlias = addressRef.current.value;
if (roomAlias === '') return;
2022-02-26 21:00:52 +05:30
const roomAddress = `#${roomAlias}:${userHs}`;
2021-07-28 18:45:52 +05:30
if (await isRoomAliasAvailable(roomAddress)) {
2022-02-26 21:00:52 +05:30
setIsValidAddress(true);
2021-07-28 18:45:52 +05:30
} else {
2022-02-26 21:00:52 +05:30
setIsValidAddress(false);
2021-07-28 18:45:52 +05:30
}
}, 1000);
2022-02-26 21:00:52 +05:30
};
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 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>
))
}
</>
),
);
};
2021-07-28 18:45:52 +05:30
return (
2022-02-26 21:00:52 +05:30
<div className="create-room">
<form className="create-room__form" onSubmit={handleSubmit}>
<SettingTile
title="Visibility"
options={(
<Button onClick={handleJoinRule} iconSrc={ChevronBottomIC}>
{joinRuleShortText[joinRules.indexOf(joinRule)]}
</Button>
)}
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>
<div className="create-room__address">
<Text variant="b1">#</Text>
<Input
value={addressValue}
onChange={validateAddress}
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>}
</div>
)}
{!isSpace && joinRule !== 'public' && (
2021-07-28 18:45:52 +05:30
<SettingTile
2022-02-26 21:00:52 +05:30
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>}
2021-07-28 18:45:52 +05:30
/>
2022-02-26 21:00:52 +05:30
)}
<SettingTile
title="Select your role"
options={(
<SegmentControl
selected={roleIndex}
segments={[{ text: 'Admin' }, { text: 'Founder' }]}
onSelect={setRoleIndex}
2021-07-28 18:45:52 +05:30
/>
)}
2022-02-26 21:00:52 +05:30
content={(
2022-03-24 18:47:53 +05:30
<Text variant="b3">Founder (101) override the default Admin (100) power level.</Text>
2021-07-28 18:45:52 +05:30
)}
2022-02-26 21:00:52 +05:30
/>
<Input name="topic" minHeight={174} resizable label="Topic (optional)" />
<div className="create-room__name-wrapper">
<Input name="name" label={`${isSpace ? 'Space' : 'Room'} name`} required />
<Button
disabled={isValidAddress === false || isCreatingRoom}
iconSrc={isSpace ? SpacePlusIC : HashPlusIC}
type="submit"
variant="primary"
>
Create
</Button>
</div>
{isCreatingRoom && (
<div className="create-room__loading">
<Spinner size="small" />
<Text>{`Creating ${isSpace ? 'space' : 'room'}...`}</Text>
</div>
)}
{typeof creatingError === 'string' && <Text className="create-room__error" variant="b3">{creatingError}</Text>}
</form>
</div>
2021-07-28 18:45:52 +05:30
);
}
2022-02-26 21:00:52 +05:30
CreateRoomContent.defaultProps = {
parentId: null,
};
CreateRoomContent.propTypes = {
isSpace: PropTypes.bool.isRequired,
parentId: PropTypes.string,
2021-07-28 18:45:52 +05:30
onRequestClose: PropTypes.func.isRequired,
};
2022-02-26 21:00:52 +05:30
function useWindowToggle() {
const [create, setCreate] = useState(null);
useEffect(() => {
const handleOpen = (isSpace, parentId) => {
setCreate({
isSpace,
parentId,
});
};
navigation.on(cons.events.navigation.CREATE_ROOM_OPENED, handleOpen);
return () => {
navigation.removeListener(cons.events.navigation.CREATE_ROOM_OPENED, handleOpen);
};
}, []);
const onRequestClose = () => setCreate(null);
return [create, onRequestClose];
}
function CreateRoom() {
const [create, onRequestClose] = useWindowToggle();
const { isSpace, parentId } = create ?? {};
const mx = initMatrix.matrixClient;
const room = mx.getRoom(parentId);
return (
<Dialog
isOpen={create !== null}
title={(
<Text variant="s1" weight="medium" primary>
{parentId ? twemojify(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 />
}
</Dialog>
);
}
2021-08-31 18:43:31 +05:30
export default CreateRoom;