Add option to create room/space
Signed-off-by: Ajay Bura <ajbura@gmail.com>
This commit is contained in:
parent
2eee3736df
commit
79afc7649d
10 changed files with 365 additions and 208 deletions
|
@ -2,79 +2,87 @@ import React, { useState, useEffect, useRef } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import './CreateRoom.scss';
|
import './CreateRoom.scss';
|
||||||
|
|
||||||
|
import { twemojify } from '../../../util/twemojify';
|
||||||
import initMatrix from '../../../client/initMatrix';
|
import initMatrix from '../../../client/initMatrix';
|
||||||
import cons from '../../../client/state/cons';
|
import cons from '../../../client/state/cons';
|
||||||
import { isRoomAliasAvailable } from '../../../util/matrixUtil';
|
import navigation from '../../../client/state/navigation';
|
||||||
|
import { selectRoom, openReusableContextMenu } from '../../../client/action/navigation';
|
||||||
import * as roomActions from '../../../client/action/room';
|
import * as roomActions from '../../../client/action/room';
|
||||||
import { selectRoom } from '../../../client/action/navigation';
|
import { isRoomAliasAvailable, getIdServer } from '../../../util/matrixUtil';
|
||||||
|
import { getEventCords } from '../../../util/common';
|
||||||
|
|
||||||
import Text from '../../atoms/text/Text';
|
import Text from '../../atoms/text/Text';
|
||||||
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 IconButton from '../../atoms/button/IconButton';
|
import IconButton from '../../atoms/button/IconButton';
|
||||||
|
import { MenuHeader, MenuItem } from '../../atoms/context-menu/ContextMenu';
|
||||||
import Input from '../../atoms/input/Input';
|
import Input from '../../atoms/input/Input';
|
||||||
import Spinner from '../../atoms/spinner/Spinner';
|
import Spinner from '../../atoms/spinner/Spinner';
|
||||||
import SegmentControl from '../../atoms/segmented-controls/SegmentedControls';
|
import SegmentControl from '../../atoms/segmented-controls/SegmentedControls';
|
||||||
import PopupWindow from '../../molecules/popup-window/PopupWindow';
|
import Dialog from '../../molecules/dialog/Dialog';
|
||||||
import SettingTile from '../../molecules/setting-tile/SettingTile';
|
import SettingTile from '../../molecules/setting-tile/SettingTile';
|
||||||
|
|
||||||
import HashPlusIC from '../../../../public/res/ic/outlined/hash-plus.svg';
|
import HashPlusIC from '../../../../public/res/ic/outlined/hash-plus.svg';
|
||||||
|
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';
|
||||||
import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
|
import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
|
||||||
|
|
||||||
function CreateRoom({ isOpen, onRequestClose }) {
|
function CreateRoomContent({ isSpace, parentId, onRequestClose }) {
|
||||||
const [isPublic, togglePublic] = useState(false);
|
const [joinRule, setJoinRule] = useState(parentId ? 'restricted' : 'invite');
|
||||||
const [isEncrypted, toggleEncrypted] = useState(true);
|
const [isEncrypted, setIsEncrypted] = useState(true);
|
||||||
const [isValidAddress, updateIsValidAddress] = useState(null);
|
const [isCreatingRoom, setIsCreatingRoom] = useState(false);
|
||||||
const [isCreatingRoom, updateIsCreatingRoom] = useState(false);
|
const [creatingError, setCreatingError] = useState(null);
|
||||||
const [creatingError, updateCreatingError] = useState(null);
|
|
||||||
|
|
||||||
const [titleValue, updateTitleValue] = useState(undefined);
|
const [isValidAddress, setIsValidAddress] = useState(null);
|
||||||
const [topicValue, updateTopicValue] = useState(undefined);
|
const [addressValue, setAddressValue] = useState(undefined);
|
||||||
const [addressValue, updateAddressValue] = useState(undefined);
|
|
||||||
const [roleIndex, setRoleIndex] = useState(0);
|
const [roleIndex, setRoleIndex] = useState(0);
|
||||||
|
|
||||||
const addressRef = useRef(null);
|
const addressRef = useRef(null);
|
||||||
const topicRef = useRef(null);
|
|
||||||
const nameRef = useRef(null);
|
|
||||||
|
|
||||||
const userId = initMatrix.matrixClient.getUserId();
|
const mx = initMatrix.matrixClient;
|
||||||
const hsString = userId.slice(userId.indexOf(':'));
|
const userHs = getIdServer(mx.getUserId());
|
||||||
|
|
||||||
function resetForm() {
|
|
||||||
togglePublic(false);
|
|
||||||
toggleEncrypted(true);
|
|
||||||
updateIsValidAddress(null);
|
|
||||||
updateIsCreatingRoom(false);
|
|
||||||
updateCreatingError(null);
|
|
||||||
updateTitleValue(undefined);
|
|
||||||
updateTopicValue(undefined);
|
|
||||||
updateAddressValue(undefined);
|
|
||||||
setRoleIndex(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const onCreated = (roomId) => {
|
|
||||||
resetForm();
|
|
||||||
selectRoom(roomId);
|
|
||||||
onRequestClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { roomList } = initMatrix;
|
const { roomList } = initMatrix;
|
||||||
|
const onCreated = (roomId) => {
|
||||||
|
setJoinRule(false);
|
||||||
|
setIsEncrypted(true);
|
||||||
|
setIsValidAddress(null);
|
||||||
|
setIsCreatingRoom(false);
|
||||||
|
setCreatingError(null);
|
||||||
|
setAddressValue(undefined);
|
||||||
|
setRoleIndex(0);
|
||||||
|
|
||||||
|
if (!mx.getRoom(roomId)?.isSpaceRoom()) {
|
||||||
|
selectRoom(roomId);
|
||||||
|
}
|
||||||
|
onRequestClose();
|
||||||
|
};
|
||||||
roomList.on(cons.events.roomList.ROOM_CREATED, onCreated);
|
roomList.on(cons.events.roomList.ROOM_CREATED, onCreated);
|
||||||
return () => {
|
return () => {
|
||||||
roomList.removeListener(cons.events.roomList.ROOM_CREATED, onCreated);
|
roomList.removeListener(cons.events.roomList.ROOM_CREATED, onCreated);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
async function createRoom() {
|
const handleSubmit = async (evt) => {
|
||||||
|
evt.preventDefault();
|
||||||
|
const { target } = evt;
|
||||||
|
|
||||||
if (isCreatingRoom) return;
|
if (isCreatingRoom) return;
|
||||||
updateIsCreatingRoom(true);
|
setIsCreatingRoom(true);
|
||||||
updateCreatingError(null);
|
setCreatingError(null);
|
||||||
const name = nameRef.current.value;
|
|
||||||
let topic = topicRef.current.value;
|
const name = target.name.value;
|
||||||
|
let topic = target.topic.value;
|
||||||
if (topic.trim() === '') topic = undefined;
|
if (topic.trim() === '') topic = undefined;
|
||||||
let roomAlias;
|
let roomAlias;
|
||||||
if (isPublic) {
|
if (joinRule === 'public') {
|
||||||
roomAlias = addressRef?.current?.value;
|
roomAlias = addressRef?.current?.value;
|
||||||
if (roomAlias.trim() === '') roomAlias = undefined;
|
if (roomAlias.trim() === '') roomAlias = undefined;
|
||||||
}
|
}
|
||||||
|
@ -82,78 +90,116 @@ function CreateRoom({ isOpen, onRequestClose }) {
|
||||||
const powerLevel = roleIndex === 1 ? 101 : undefined;
|
const powerLevel = roleIndex === 1 ? 101 : undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await roomActions.create({
|
await roomActions.createRoom({
|
||||||
name, topic, isPublic, roomAlias, isEncrypted, powerLevel,
|
name,
|
||||||
|
topic,
|
||||||
|
joinRule,
|
||||||
|
alias: roomAlias,
|
||||||
|
isEncrypted: (isSpace || joinRule === 'public') ? false : isEncrypted,
|
||||||
|
powerLevel,
|
||||||
|
isSpace,
|
||||||
|
parentId,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.message === 'M_UNKNOWN: Invalid characters in room alias') {
|
if (e.message === 'M_UNKNOWN: Invalid characters in room alias') {
|
||||||
updateCreatingError('ERROR: Invalid characters in room address');
|
setCreatingError('ERROR: Invalid characters in address');
|
||||||
updateIsValidAddress(false);
|
setIsValidAddress(false);
|
||||||
} else if (e.message === 'M_ROOM_IN_USE: Room alias already taken') {
|
} else if (e.message === 'M_ROOM_IN_USE: Room alias already taken') {
|
||||||
updateCreatingError('ERROR: Room address is already in use');
|
setCreatingError('ERROR: This address is already in use');
|
||||||
updateIsValidAddress(false);
|
setIsValidAddress(false);
|
||||||
} else updateCreatingError(e.message);
|
} else setCreatingError(e.message);
|
||||||
updateIsCreatingRoom(false);
|
setIsCreatingRoom(false);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function validateAddress(e) {
|
const validateAddress = (e) => {
|
||||||
const myAddress = e.target.value;
|
const myAddress = e.target.value;
|
||||||
updateIsValidAddress(null);
|
setIsValidAddress(null);
|
||||||
updateAddressValue(e.target.value);
|
setAddressValue(e.target.value);
|
||||||
updateCreatingError(null);
|
setCreatingError(null);
|
||||||
|
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
if (myAddress !== addressRef.current.value) return;
|
if (myAddress !== addressRef.current.value) return;
|
||||||
const roomAlias = addressRef.current.value;
|
const roomAlias = addressRef.current.value;
|
||||||
if (roomAlias === '') return;
|
if (roomAlias === '') return;
|
||||||
const roomAddress = `#${roomAlias}${hsString}`;
|
const roomAddress = `#${roomAlias}:${userHs}`;
|
||||||
|
|
||||||
if (await isRoomAliasAvailable(roomAddress)) {
|
if (await isRoomAliasAvailable(roomAddress)) {
|
||||||
updateIsValidAddress(true);
|
setIsValidAddress(true);
|
||||||
} else {
|
} else {
|
||||||
updateIsValidAddress(false);
|
setIsValidAddress(false);
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
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)]
|
||||||
}
|
}
|
||||||
function handleTitleChange(e) {
|
onClick={() => { closeMenu(); setJoinRule(rule); }}
|
||||||
if (e.target.value.trim() === '') updateTitleValue(undefined);
|
disabled={!parentId && rule === 'restricted'}
|
||||||
updateTitleValue(e.target.value);
|
>
|
||||||
}
|
{ joinRuleText[joinRules.indexOf(rule)] }
|
||||||
function handleTopicChange(e) {
|
</MenuItem>
|
||||||
if (e.target.value.trim() === '') updateTopicValue(undefined);
|
))
|
||||||
updateTopicValue(e.target.value);
|
|
||||||
}
|
}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PopupWindow
|
|
||||||
isOpen={isOpen}
|
|
||||||
title="Create room"
|
|
||||||
contentOptions={<IconButton src={CrossIC} onClick={onRequestClose} tooltip="Close" />}
|
|
||||||
onRequestClose={onRequestClose}
|
|
||||||
>
|
|
||||||
<div className="create-room">
|
<div className="create-room">
|
||||||
<form className="create-room__form" onSubmit={(e) => { e.preventDefault(); createRoom(); }}>
|
<form className="create-room__form" onSubmit={handleSubmit}>
|
||||||
<SettingTile
|
<SettingTile
|
||||||
title="Make room public"
|
title="Visibility"
|
||||||
options={<Toggle isActive={isPublic} onToggle={togglePublic} />}
|
options={(
|
||||||
content={<Text variant="b3">Public room can be joined by anyone.</Text>}
|
<Button onClick={handleJoinRule} iconSrc={ChevronBottomIC}>
|
||||||
|
{joinRuleShortText[joinRules.indexOf(joinRule)]}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
content={<Text variant="b3">{`Select who can join this ${isSpace ? 'space' : 'room'}.`}</Text>}
|
||||||
/>
|
/>
|
||||||
{isPublic && (
|
{joinRule === 'public' && (
|
||||||
<div>
|
<div>
|
||||||
<Text className="create-room__address__label" variant="b2">Room address</Text>
|
<Text className="create-room__address__label" variant="b2">{isSpace ? 'Space address' : 'Room address'}</Text>
|
||||||
<div className="create-room__address">
|
<div className="create-room__address">
|
||||||
<Text variant="b1">#</Text>
|
<Text variant="b1">#</Text>
|
||||||
<Input value={addressValue} onChange={validateAddress} state={(isValidAddress === false) ? 'error' : 'normal'} forwardRef={addressRef} placeholder="my_room" required />
|
<Input
|
||||||
<Text variant="b1">{hsString}</Text>
|
value={addressValue}
|
||||||
|
onChange={validateAddress}
|
||||||
|
state={(isValidAddress === false) ? 'error' : 'normal'}
|
||||||
|
forwardRef={addressRef}
|
||||||
|
placeholder="my_address"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Text variant="b1">{`:${userHs}`}</Text>
|
||||||
</div>
|
</div>
|
||||||
{isValidAddress === false && <Text className="create-room__address__tip" variant="b3"><span style={{ color: 'var(--bg-danger)' }}>{`#${addressValue}${hsString} 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>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!isPublic && (
|
{!isSpace && joinRule !== 'public' && (
|
||||||
<SettingTile
|
<SettingTile
|
||||||
title="Enable end-to-end encryption"
|
title="Enable end-to-end encryption"
|
||||||
options={<Toggle isActive={isEncrypted} onToggle={toggleEncrypted} />}
|
options={<Toggle isActive={isEncrypted} onToggle={setIsEncrypted} />}
|
||||||
content={<Text variant="b3">You can’t disable this later. Bridges & most bots won’t work yet.</Text>}
|
content={<Text variant="b3">You can’t disable this later. Bridges & most bots won’t work yet.</Text>}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -170,27 +216,91 @@ function CreateRoom({ isOpen, onRequestClose }) {
|
||||||
<Text variant="b3">Override the default (100) power level.</Text>
|
<Text variant="b3">Override the default (100) power level.</Text>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Input value={topicValue} onChange={handleTopicChange} forwardRef={topicRef} minHeight={174} resizable label="Topic (optional)" />
|
<Input name="topic" minHeight={174} resizable label="Topic (optional)" />
|
||||||
<div className="create-room__name-wrapper">
|
<div className="create-room__name-wrapper">
|
||||||
<Input value={titleValue} onChange={handleTitleChange} forwardRef={nameRef} label="Room name" required />
|
<Input name="name" label={`${isSpace ? 'Space' : 'Room'} name`} required />
|
||||||
<Button disabled={isValidAddress === false || isCreatingRoom} iconSrc={HashPlusIC} type="submit" variant="primary">Create</Button>
|
<Button
|
||||||
|
disabled={isValidAddress === false || isCreatingRoom}
|
||||||
|
iconSrc={isSpace ? SpacePlusIC : HashPlusIC}
|
||||||
|
type="submit"
|
||||||
|
variant="primary"
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{isCreatingRoom && (
|
{isCreatingRoom && (
|
||||||
<div className="create-room__loading">
|
<div className="create-room__loading">
|
||||||
<Spinner size="small" />
|
<Spinner size="small" />
|
||||||
<Text>Creating room...</Text>
|
<Text>{`Creating ${isSpace ? 'space' : 'room'}...`}</Text>
|
||||||
</div>
|
</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>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</PopupWindow>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
CreateRoomContent.defaultProps = {
|
||||||
CreateRoom.propTypes = {
|
parentId: null,
|
||||||
isOpen: PropTypes.bool.isRequired,
|
};
|
||||||
|
CreateRoomContent.propTypes = {
|
||||||
|
isSpace: PropTypes.bool.isRequired,
|
||||||
|
parentId: PropTypes.string,
|
||||||
onRequestClose: PropTypes.func.isRequired,
|
onRequestClose: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default CreateRoom;
|
export default CreateRoom;
|
||||||
|
|
|
@ -117,12 +117,7 @@ function InviteUser({
|
||||||
procUserError.delete(userId);
|
procUserError.delete(userId);
|
||||||
updateUserProcError(getMapCopy(procUserError));
|
updateUserProcError(getMapCopy(procUserError));
|
||||||
|
|
||||||
const result = await roomActions.create({
|
const result = await roomActions.createDM(userId);
|
||||||
isPublic: false,
|
|
||||||
isEncrypted: true,
|
|
||||||
isDirect: true,
|
|
||||||
invite: [userId],
|
|
||||||
});
|
|
||||||
roomIdToUserId.set(result.room_id, userId);
|
roomIdToUserId.set(result.room_id, userId);
|
||||||
updateRoomIdToUserId(getMapCopy(roomIdToUserId));
|
updateRoomIdToUserId(getMapCopy(roomIdToUserId));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -38,20 +38,20 @@ function HomeSpaceOptions({ spaceId, afterOptionSelect }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MenuHeader>Add rooms or spaces</MenuHeader>
|
<MenuHeader>Add rooms or spaces</MenuHeader>
|
||||||
<MenuItem
|
|
||||||
iconSrc={HashPlusIC}
|
|
||||||
onClick={() => { afterOptionSelect(); openCreateRoom(); }}
|
|
||||||
disabled={!canManage}
|
|
||||||
>
|
|
||||||
Create new room
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
iconSrc={SpacePlusIC}
|
iconSrc={SpacePlusIC}
|
||||||
onClick={() => { afterOptionSelect(); }}
|
onClick={() => { afterOptionSelect(); openCreateRoom(true, spaceId); }}
|
||||||
disabled={!canManage}
|
disabled={!canManage}
|
||||||
>
|
>
|
||||||
Create new space
|
Create new space
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
iconSrc={HashPlusIC}
|
||||||
|
onClick={() => { afterOptionSelect(); openCreateRoom(false, spaceId); }}
|
||||||
|
disabled={!canManage}
|
||||||
|
>
|
||||||
|
Create new room
|
||||||
|
</MenuItem>
|
||||||
{ !spaceId && (
|
{ !spaceId && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
iconSrc={HashGlobeIC}
|
iconSrc={HashGlobeIC}
|
||||||
|
|
|
@ -200,11 +200,7 @@ function ProfileFooter({ roomId, userId, onRequestClose }) {
|
||||||
// Create new DM
|
// Create new DM
|
||||||
try {
|
try {
|
||||||
setIsCreatingDM(true);
|
setIsCreatingDM(true);
|
||||||
await roomActions.create({
|
await roomActions.createDM(userId);
|
||||||
isEncrypted: true,
|
|
||||||
isDirect: true,
|
|
||||||
invite: [userId],
|
|
||||||
});
|
|
||||||
} catch {
|
} catch {
|
||||||
if (isMountedRef.current === false) return;
|
if (isMountedRef.current === false) return;
|
||||||
setIsCreatingDM(false);
|
setIsCreatingDM(false);
|
||||||
|
|
|
@ -5,6 +5,7 @@ import ProfileViewer from '../profile-viewer/ProfileViewer';
|
||||||
import SpaceAddExisting from '../../molecules/space-add-existing/SpaceAddExisting';
|
import SpaceAddExisting from '../../molecules/space-add-existing/SpaceAddExisting';
|
||||||
import Search from '../search/Search';
|
import Search from '../search/Search';
|
||||||
import ViewSource from '../view-source/ViewSource';
|
import ViewSource from '../view-source/ViewSource';
|
||||||
|
import CreateRoom from '../create-room/CreateRoom';
|
||||||
|
|
||||||
function Dialogs() {
|
function Dialogs() {
|
||||||
return (
|
return (
|
||||||
|
@ -12,6 +13,7 @@ function Dialogs() {
|
||||||
<ReadReceipts />
|
<ReadReceipts />
|
||||||
<ViewSource />
|
<ViewSource />
|
||||||
<ProfileViewer />
|
<ProfileViewer />
|
||||||
|
<CreateRoom />
|
||||||
<SpaceAddExisting />
|
<SpaceAddExisting />
|
||||||
<Search />
|
<Search />
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -5,7 +5,6 @@ import navigation from '../../../client/state/navigation';
|
||||||
|
|
||||||
import InviteList from '../invite-list/InviteList';
|
import InviteList from '../invite-list/InviteList';
|
||||||
import PublicRooms from '../public-rooms/PublicRooms';
|
import PublicRooms from '../public-rooms/PublicRooms';
|
||||||
import CreateRoom from '../create-room/CreateRoom';
|
|
||||||
import InviteUser from '../invite-user/InviteUser';
|
import InviteUser from '../invite-user/InviteUser';
|
||||||
import Settings from '../settings/Settings';
|
import Settings from '../settings/Settings';
|
||||||
import SpaceSettings from '../space-settings/SpaceSettings';
|
import SpaceSettings from '../space-settings/SpaceSettings';
|
||||||
|
@ -16,7 +15,6 @@ function Windows() {
|
||||||
const [publicRooms, changePublicRooms] = useState({
|
const [publicRooms, changePublicRooms] = useState({
|
||||||
isOpen: false, searchTerm: undefined,
|
isOpen: false, searchTerm: undefined,
|
||||||
});
|
});
|
||||||
const [isCreateRoom, changeCreateRoom] = useState(false);
|
|
||||||
const [inviteUser, changeInviteUser] = useState({
|
const [inviteUser, changeInviteUser] = useState({
|
||||||
isOpen: false, roomId: undefined, term: undefined,
|
isOpen: false, roomId: undefined, term: undefined,
|
||||||
});
|
});
|
||||||
|
@ -31,9 +29,6 @@ function Windows() {
|
||||||
searchTerm,
|
searchTerm,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function openCreateRoom() {
|
|
||||||
changeCreateRoom(true);
|
|
||||||
}
|
|
||||||
function openInviteUser(roomId, searchTerm) {
|
function openInviteUser(roomId, searchTerm) {
|
||||||
changeInviteUser({
|
changeInviteUser({
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
|
@ -48,13 +43,11 @@ function Windows() {
|
||||||
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.CREATE_ROOM_OPENED, openCreateRoom);
|
|
||||||
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);
|
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.CREATE_ROOM_OPENED, openCreateRoom);
|
|
||||||
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);
|
navigation.removeListener(cons.events.navigation.SETTINGS_OPENED, openSettings);
|
||||||
};
|
};
|
||||||
|
@ -71,10 +64,6 @@ function Windows() {
|
||||||
searchTerm={publicRooms.searchTerm}
|
searchTerm={publicRooms.searchTerm}
|
||||||
onRequestClose={() => changePublicRooms({ isOpen: false, searchTerm: undefined })}
|
onRequestClose={() => changePublicRooms({ isOpen: false, searchTerm: undefined })}
|
||||||
/>
|
/>
|
||||||
<CreateRoom
|
|
||||||
isOpen={isCreateRoom}
|
|
||||||
onRequestClose={() => changeCreateRoom(false)}
|
|
||||||
/>
|
|
||||||
<InviteUser
|
<InviteUser
|
||||||
isOpen={inviteUser.isOpen}
|
isOpen={inviteUser.isOpen}
|
||||||
roomId={inviteUser.roomId}
|
roomId={inviteUser.roomId}
|
||||||
|
|
|
@ -65,9 +65,11 @@ export function openPublicRooms(searchTerm) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function openCreateRoom() {
|
export function openCreateRoom(isSpace = false, parentId = null) {
|
||||||
appDispatcher.dispatch({
|
appDispatcher.dispatch({
|
||||||
type: cons.actions.navigation.OPEN_CREATE_ROOM,
|
type: cons.actions.navigation.OPEN_CREATE_ROOM,
|
||||||
|
isSpace,
|
||||||
|
parentId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import initMatrix from '../initMatrix';
|
import initMatrix from '../initMatrix';
|
||||||
import appDispatcher from '../dispatcher';
|
import appDispatcher from '../dispatcher';
|
||||||
import cons from '../state/cons';
|
import cons from '../state/cons';
|
||||||
|
import { getIdServer } from '../../util/matrixUtil';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://github.com/matrix-org/matrix-react-sdk/blob/1e6c6e9d800890c732d60429449bc280de01a647/src/Rooms.js#L73
|
* https://github.com/matrix-org/matrix-react-sdk/blob/1e6c6e9d800890c732d60429449bc280de01a647/src/Rooms.js#L73
|
||||||
|
@ -125,36 +126,37 @@ function leave(roomId) {
|
||||||
}).catch();
|
}).catch();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async function create(options, isDM = false) {
|
||||||
* Create a room.
|
|
||||||
* @param {Object} opts
|
|
||||||
* @param {string} [opts.name]
|
|
||||||
* @param {string} [opts.topic]
|
|
||||||
* @param {boolean} [opts.isPublic=false] Sets room visibility to public
|
|
||||||
* @param {string} [opts.roomAlias] Sets the room address
|
|
||||||
* @param {boolean} [opts.isEncrypted=false] Makes room encrypted
|
|
||||||
* @param {boolean} [opts.isDirect=false] Makes room as direct message
|
|
||||||
* @param {string[]} [opts.invite=[]] An array of userId's to invite
|
|
||||||
* @param{number} [opts.powerLevel=100] My power level
|
|
||||||
*/
|
|
||||||
async function create(opts) {
|
|
||||||
const mx = initMatrix.matrixClient;
|
const mx = initMatrix.matrixClient;
|
||||||
const customPowerLevels = [101];
|
try {
|
||||||
const options = {
|
const result = await mx.createRoom(options);
|
||||||
name: opts.name,
|
if (isDM && typeof options.invite?.[0] === 'string') {
|
||||||
topic: opts.topic,
|
await addRoomToMDirect(result.room_id, options.invite[0]);
|
||||||
visibility: opts.isPublic === true ? 'public' : 'private',
|
}
|
||||||
room_alias_name: opts.roomAlias,
|
appDispatcher.dispatch({
|
||||||
is_direct: opts.isDirect === true,
|
type: cons.actions.room.CREATE,
|
||||||
invite: opts.invite || [],
|
roomId: result.room_id,
|
||||||
initial_state: [],
|
isDM,
|
||||||
preset: opts.isDirect === true ? 'trusted_private_chat' : undefined,
|
});
|
||||||
power_level_content_override: customPowerLevels.indexOf(opts.powerLevel) === -1 ? undefined : {
|
return result;
|
||||||
users: { [initMatrix.matrixClient.getUserId()]: opts.powerLevel },
|
} catch (e) {
|
||||||
},
|
const errcodes = ['M_UNKNOWN', 'M_BAD_JSON', 'M_ROOM_IN_USE', 'M_INVALID_ROOM_STATE', 'M_UNSUPPORTED_ROOM_VERSION'];
|
||||||
};
|
if (errcodes.includes(e.errcode)) {
|
||||||
|
throw new Error(e);
|
||||||
|
}
|
||||||
|
throw new Error('Something went wrong!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (opts.isPublic !== true && opts.isEncrypted === true) {
|
async function createDM(userId, isEncrypted = true) {
|
||||||
|
const options = {
|
||||||
|
is_direct: true,
|
||||||
|
invite: [userId],
|
||||||
|
visibility: 'private',
|
||||||
|
preset: 'trusted_private_chat',
|
||||||
|
initial_state: [],
|
||||||
|
};
|
||||||
|
if (isEncrypted) {
|
||||||
options.initial_state.push({
|
options.initial_state.push({
|
||||||
type: 'm.room.encryption',
|
type: 'm.room.encryption',
|
||||||
state_key: '',
|
state_key: '',
|
||||||
|
@ -164,28 +166,87 @@ async function create(opts) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
const result = await create(options, true);
|
||||||
const result = await mx.createRoom(options);
|
|
||||||
if (opts.isDirect === true && typeof opts.invite[0] !== 'undefined') {
|
|
||||||
await addRoomToMDirect(result.room_id, opts.invite[0]);
|
|
||||||
}
|
|
||||||
appDispatcher.dispatch({
|
|
||||||
type: cons.actions.room.CREATE,
|
|
||||||
roomId: result.room_id,
|
|
||||||
isDM: opts.isDirect === true,
|
|
||||||
});
|
|
||||||
return result;
|
return result;
|
||||||
} catch (e) {
|
}
|
||||||
const errcodes = ['M_UNKNOWN', 'M_BAD_JSON', 'M_ROOM_IN_USE', 'M_INVALID_ROOM_STATE', 'M_UNSUPPORTED_ROOM_VERSION'];
|
|
||||||
if (errcodes.find((errcode) => errcode === e.errcode)) {
|
async function createRoom(opts) {
|
||||||
appDispatcher.dispatch({
|
// joinRule: 'public' | 'invite' | 'restricted'
|
||||||
type: cons.actions.room.error.CREATE,
|
const { name, topic, joinRule } = opts;
|
||||||
error: e,
|
const alias = opts.alias ?? undefined;
|
||||||
|
const parentId = opts.parentId ?? undefined;
|
||||||
|
const isSpace = opts.isSpace ?? false;
|
||||||
|
const isEncrypted = opts.isEncrypted ?? false;
|
||||||
|
const powerLevel = opts.powerLevel ?? undefined;
|
||||||
|
const blockFederation = opts.blockFederation ?? false;
|
||||||
|
|
||||||
|
const mx = initMatrix.matrixClient;
|
||||||
|
const visibility = joinRule === 'public' ? 'public' : 'private';
|
||||||
|
const options = {
|
||||||
|
creation_content: undefined,
|
||||||
|
name,
|
||||||
|
topic,
|
||||||
|
visibility,
|
||||||
|
room_alias_name: alias,
|
||||||
|
initial_state: [],
|
||||||
|
power_level_content_override: undefined,
|
||||||
|
};
|
||||||
|
if (isSpace) {
|
||||||
|
options.creation_content = { type: 'm.space' };
|
||||||
|
}
|
||||||
|
if (blockFederation) {
|
||||||
|
options.creation_content = { 'm.federate': false };
|
||||||
|
}
|
||||||
|
if (isEncrypted) {
|
||||||
|
options.initial_state.push({
|
||||||
|
type: 'm.room.encryption',
|
||||||
|
state_key: '',
|
||||||
|
content: {
|
||||||
|
algorithm: 'm.megolm.v1.aes-sha2',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
throw new Error(e);
|
|
||||||
}
|
}
|
||||||
throw new Error('Something went wrong!');
|
if (powerLevel) {
|
||||||
|
options.power_level_content_override = {
|
||||||
|
users: {
|
||||||
|
[mx.getUserId()]: powerLevel,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
if (parentId) {
|
||||||
|
options.initial_state.push({
|
||||||
|
type: 'm.space.parent',
|
||||||
|
state_key: parentId,
|
||||||
|
content: {
|
||||||
|
canonical: true,
|
||||||
|
via: [getIdServer(mx.getUserId())],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (parentId && joinRule === 'restricted') {
|
||||||
|
options.initial_state.push({
|
||||||
|
type: 'm.room.join_rules',
|
||||||
|
content: {
|
||||||
|
join_rule: 'restricted',
|
||||||
|
allow: [{
|
||||||
|
type: 'm.room_membership',
|
||||||
|
room_id: parentId,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await create(options);
|
||||||
|
|
||||||
|
if (parentId) {
|
||||||
|
await mx.sendStateEvent(parentId, 'm.space.child', {
|
||||||
|
auto_join: false,
|
||||||
|
suggested: false,
|
||||||
|
via: [getIdServer(mx.getUserId())],
|
||||||
|
}, result.room_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function invite(roomId, userId) {
|
async function invite(roomId, userId) {
|
||||||
|
@ -242,7 +303,8 @@ function deleteSpaceShortcut(roomId) {
|
||||||
|
|
||||||
export {
|
export {
|
||||||
join, leave,
|
join, leave,
|
||||||
create, invite, kick, ban, unban,
|
createDM, createRoom,
|
||||||
|
invite, kick, ban, unban,
|
||||||
setPowerLevel,
|
setPowerLevel,
|
||||||
createSpaceShortcut, deleteSpaceShortcut,
|
createSpaceShortcut, deleteSpaceShortcut,
|
||||||
};
|
};
|
||||||
|
|
|
@ -53,9 +53,6 @@ const cons = {
|
||||||
CREATE: 'CREATE',
|
CREATE: 'CREATE',
|
||||||
CREATE_SPACE_SHORTCUT: 'CREATE_SPACE_SHORTCUT',
|
CREATE_SPACE_SHORTCUT: 'CREATE_SPACE_SHORTCUT',
|
||||||
DELETE_SPACE_SHORTCUT: 'DELETE_SPACE_SHORTCUT',
|
DELETE_SPACE_SHORTCUT: 'DELETE_SPACE_SHORTCUT',
|
||||||
error: {
|
|
||||||
CREATE: 'ERROR_CREATE',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
TOGGLE_SYSTEM_THEME: 'TOGGLE_SYSTEM_THEME',
|
TOGGLE_SYSTEM_THEME: 'TOGGLE_SYSTEM_THEME',
|
||||||
|
|
|
@ -113,7 +113,11 @@ class Navigation extends EventEmitter {
|
||||||
this.emit(cons.events.navigation.PUBLIC_ROOMS_OPENED, action.searchTerm);
|
this.emit(cons.events.navigation.PUBLIC_ROOMS_OPENED, action.searchTerm);
|
||||||
},
|
},
|
||||||
[cons.actions.navigation.OPEN_CREATE_ROOM]: () => {
|
[cons.actions.navigation.OPEN_CREATE_ROOM]: () => {
|
||||||
this.emit(cons.events.navigation.CREATE_ROOM_OPENED);
|
this.emit(
|
||||||
|
cons.events.navigation.CREATE_ROOM_OPENED,
|
||||||
|
action.isSpace,
|
||||||
|
action.parentId,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[cons.actions.navigation.OPEN_INVITE_USER]: () => {
|
[cons.actions.navigation.OPEN_INVITE_USER]: () => {
|
||||||
this.emit(cons.events.navigation.INVITE_USER_OPENED, action.roomId, action.searchTerm);
|
this.emit(cons.events.navigation.INVITE_USER_OPENED, action.roomId, action.searchTerm);
|
||||||
|
|
Loading…
Reference in a new issue