Add option to create room/space
authorAjay Bura <ajbura@gmail.com>
Sat, 26 Feb 2022 15:30:52 +0000 (21:00 +0530)
committerAjay Bura <ajbura@gmail.com>
Sat, 26 Feb 2022 15:30:52 +0000 (21:00 +0530)
Signed-off-by: Ajay Bura <ajbura@gmail.com>
src/app/organisms/create-room/CreateRoom.jsx
src/app/organisms/invite-user/InviteUser.jsx
src/app/organisms/navigation/DrawerHeader.jsx
src/app/organisms/profile-viewer/ProfileViewer.jsx
src/app/organisms/pw/Dialogs.jsx
src/app/organisms/pw/Windows.jsx
src/client/action/navigation.js
src/client/action/room.js
src/client/state/cons.js
src/client/state/navigation.js

index 6c4968ab0b404eebedc17574679fcaccb13cd832..20e7609fa93429c0076ec094e6c87a8dfcd2703d 100644 (file)
@@ -2,79 +2,87 @@ 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 { 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 { selectRoom } from '../../../client/action/navigation';
+import { isRoomAliasAvailable, getIdServer } from '../../../util/matrixUtil';
+import { getEventCords } from '../../../util/common';
 
 import Text from '../../atoms/text/Text';
 import Button from '../../atoms/button/Button';
 import Toggle from '../../atoms/button/Toggle';
 import IconButton from '../../atoms/button/IconButton';
+import { MenuHeader, MenuItem } from '../../atoms/context-menu/ContextMenu';
 import Input from '../../atoms/input/Input';
 import Spinner from '../../atoms/spinner/Spinner';
 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 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';
 
-function CreateRoom({ isOpen, onRequestClose }) {
-  const [isPublic, togglePublic] = useState(false);
-  const [isEncrypted, toggleEncrypted] = useState(true);
-  const [isValidAddress, updateIsValidAddress] = useState(null);
-  const [isCreatingRoom, updateIsCreatingRoom] = useState(false);
-  const [creatingError, updateCreatingError] = useState(null);
+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);
 
-  const [titleValue, updateTitleValue] = useState(undefined);
-  const [topicValue, updateTopicValue] = useState(undefined);
-  const [addressValue, updateAddressValue] = useState(undefined);
+  const [isValidAddress, setIsValidAddress] = useState(null);
+  const [addressValue, setAddressValue] = useState(undefined);
   const [roleIndex, setRoleIndex] = useState(0);
 
   const addressRef = useRef(null);
-  const topicRef = useRef(null);
-  const nameRef = useRef(null);
-
-  const userId = initMatrix.matrixClient.getUserId();
-  const hsString = userId.slice(userId.indexOf(':'));
-
-  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();
-  };
+
+  const mx = initMatrix.matrixClient;
+  const userHs = getIdServer(mx.getUserId());
 
   useEffect(() => {
     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);
     return () => {
       roomList.removeListener(cons.events.roomList.ROOM_CREATED, onCreated);
     };
   }, []);
 
-  async function createRoom() {
+  const handleSubmit = async (evt) => {
+    evt.preventDefault();
+    const { target } = evt;
+
     if (isCreatingRoom) return;
-    updateIsCreatingRoom(true);
-    updateCreatingError(null);
-    const name = nameRef.current.value;
-    let topic = topicRef.current.value;
+    setIsCreatingRoom(true);
+    setCreatingError(null);
+
+    const name = target.name.value;
+    let topic = target.topic.value;
     if (topic.trim() === '') topic = undefined;
     let roomAlias;
-    if (isPublic) {
+    if (joinRule === 'public') {
       roomAlias = addressRef?.current?.value;
       if (roomAlias.trim() === '') roomAlias = undefined;
     }
@@ -82,115 +90,217 @@ function CreateRoom({ isOpen, onRequestClose }) {
     const powerLevel = roleIndex === 1 ? 101 : undefined;
 
     try {
-      await roomActions.create({
-        name, topic, isPublic, roomAlias, isEncrypted, powerLevel,
+      await roomActions.createRoom({
+        name,
+        topic,
+        joinRule,
+        alias: roomAlias,
+        isEncrypted: (isSpace || joinRule === 'public') ? false : isEncrypted,
+        powerLevel,
+        isSpace,
+        parentId,
       });
     } catch (e) {
       if (e.message === 'M_UNKNOWN: Invalid characters in room alias') {
-        updateCreatingError('ERROR: Invalid characters in room address');
-        updateIsValidAddress(false);
+        setCreatingError('ERROR: Invalid characters in address');
+        setIsValidAddress(false);
       } else if (e.message === 'M_ROOM_IN_USE: Room alias already taken') {
-        updateCreatingError('ERROR: Room address is already in use');
-        updateIsValidAddress(false);
-      } else updateCreatingError(e.message);
-      updateIsCreatingRoom(false);
+        setCreatingError('ERROR: This address is already in use');
+        setIsValidAddress(false);
+      } else setCreatingError(e.message);
+      setIsCreatingRoom(false);
     }
-  }
+  };
 
-  function validateAddress(e) {
+  const validateAddress = (e) => {
     const myAddress = e.target.value;
-    updateIsValidAddress(null);
-    updateAddressValue(e.target.value);
-    updateCreatingError(null);
+    setIsValidAddress(null);
+    setAddressValue(e.target.value);
+    setCreatingError(null);
 
     setTimeout(async () => {
       if (myAddress !== addressRef.current.value) return;
       const roomAlias = addressRef.current.value;
       if (roomAlias === '') return;
-      const roomAddress = `#${roomAlias}${hsString}`;
+      const roomAddress = `#${roomAlias}:${userHs}`;
 
       if (await isRoomAliasAvailable(roomAddress)) {
-        updateIsValidAddress(true);
+        setIsValidAddress(true);
       } else {
-        updateIsValidAddress(false);
+        setIsValidAddress(false);
       }
     }, 1000);
-  }
-  function handleTitleChange(e) {
-    if (e.target.value.trim() === '') updateTitleValue(undefined);
-    updateTitleValue(e.target.value);
-  }
-  function handleTopicChange(e) {
-    if (e.target.value.trim() === '') updateTopicValue(undefined);
-    updateTopicValue(e.target.value);
-  }
+  };
+
+  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>
+            ))
+          }
+        </>
+      ),
+    );
+  };
 
   return (
-    <PopupWindow
-      isOpen={isOpen}
-      title="Create room"
-      contentOptions={<IconButton src={CrossIC} onClick={onRequestClose} tooltip="Close" />}
-      onRequestClose={onRequestClose}
-    >
-      <div className="create-room">
-        <form className="create-room__form" onSubmit={(e) => { e.preventDefault(); createRoom(); }}>
+    <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' && (
           <SettingTile
-            title="Make room public"
-            options={<Toggle isActive={isPublic} onToggle={togglePublic} />}
-            content={<Text variant="b3">Public room can be joined by anyone.</Text>}
+            title="Enable end-to-end encryption"
+            options={<Toggle isActive={isEncrypted} onToggle={setIsEncrypted} />}
+            content={<Text variant="b3">You can’t disable this later. Bridges & most bots won’t work yet.</Text>}
           />
-          {isPublic && (
-            <div>
-              <Text className="create-room__address__label" variant="b2">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_room" required />
-                <Text variant="b1">{hsString}</Text>
-              </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>}
-            </div>
-          )}
-          {!isPublic && (
-            <SettingTile
-              title="Enable end-to-end encryption"
-              options={<Toggle isActive={isEncrypted} onToggle={toggleEncrypted} />}
-              content={<Text variant="b3">You can’t disable this later. Bridges & most bots won’t work yet.</Text>}
+        )}
+        <SettingTile
+          title="Select your role"
+          options={(
+            <SegmentControl
+              selected={roleIndex}
+              segments={[{ text: 'Admin' }, { text: 'Founder' }]}
+              onSelect={setRoleIndex}
             />
           )}
-          <SettingTile
-            title="Select your role"
-            options={(
-              <SegmentControl
-                selected={roleIndex}
-                segments={[{ text: 'Admin' }, { text: 'Founder' }]}
-                onSelect={setRoleIndex}
-              />
-            )}
-            content={(
-              <Text variant="b3">Override the default (100) power level.</Text>
-            )}
-          />
-          <Input value={topicValue} onChange={handleTopicChange} forwardRef={topicRef} minHeight={174} resizable label="Topic (optional)" />
-          <div className="create-room__name-wrapper">
-            <Input value={titleValue} onChange={handleTitleChange} forwardRef={nameRef} label="Room name" required />
-            <Button disabled={isValidAddress === false || isCreatingRoom} iconSrc={HashPlusIC} type="submit" variant="primary">Create</Button>
-          </div>
-          {isCreatingRoom && (
-            <div className="create-room__loading">
-              <Spinner size="small" />
-              <Text>Creating room...</Text>
-            </div>
+          content={(
+            <Text variant="b3">Override the default (100) power level.</Text>
           )}
-          {typeof creatingError === 'string' && <Text className="create-room__error" variant="b3">{creatingError}</Text>}
-        </form>
-      </div>
-    </PopupWindow>
+        />
+        <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>
   );
 }
-
-CreateRoom.propTypes = {
-  isOpen: PropTypes.bool.isRequired,
+CreateRoomContent.defaultProps = {
+  parentId: null,
+};
+CreateRoomContent.propTypes = {
+  isSpace: PropTypes.bool.isRequired,
+  parentId: PropTypes.string,
   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;
index e7d7f51069d37efa6f6ebc4aa8953e9c2c6690d3..c03d3ad99bd0258ad2c405547563c4be2a837566 100644 (file)
@@ -117,12 +117,7 @@ function InviteUser({
       procUserError.delete(userId);
       updateUserProcError(getMapCopy(procUserError));
 
-      const result = await roomActions.create({
-        isPublic: false,
-        isEncrypted: true,
-        isDirect: true,
-        invite: [userId],
-      });
+      const result = await roomActions.createDM(userId);
       roomIdToUserId.set(result.room_id, userId);
       updateRoomIdToUserId(getMapCopy(roomIdToUserId));
     } catch (e) {
index 5052865c52538dfda6e65bffc62237f4a3c92e3e..ab8d68a0cf5edad5a9460ff269219df9904ff19d 100644 (file)
@@ -39,18 +39,18 @@ function HomeSpaceOptions({ spaceId, afterOptionSelect }) {
     <>
       <MenuHeader>Add rooms or spaces</MenuHeader>
       <MenuItem
-        iconSrc={HashPlusIC}
-        onClick={() => { afterOptionSelect(); openCreateRoom(); }}
+        iconSrc={SpacePlusIC}
+        onClick={() => { afterOptionSelect(); openCreateRoom(true, spaceId); }}
         disabled={!canManage}
       >
-        Create new room
+        Create new space
       </MenuItem>
       <MenuItem
-        iconSrc={SpacePlusIC}
-        onClick={() => { afterOptionSelect(); }}
+        iconSrc={HashPlusIC}
+        onClick={() => { afterOptionSelect(); openCreateRoom(false, spaceId); }}
         disabled={!canManage}
       >
-        Create new space
+        Create new room
       </MenuItem>
       { !spaceId && (
         <MenuItem
index ca4a4ea96bc1a75feacaca7c6641a82898878374..343d813c97a49e8c89ec8f9459f8b7f9c43ed6e2 100644 (file)
@@ -200,11 +200,7 @@ function ProfileFooter({ roomId, userId, onRequestClose }) {
     // Create new DM
     try {
       setIsCreatingDM(true);
-      await roomActions.create({
-        isEncrypted: true,
-        isDirect: true,
-        invite: [userId],
-      });
+      await roomActions.createDM(userId);
     } catch {
       if (isMountedRef.current === false) return;
       setIsCreatingDM(false);
index 55c37f36a607da131f830224a97b81f8e1d1577a..ca79818a137dd3c3e10301009e3ce8d160fae0ff 100644 (file)
@@ -5,6 +5,7 @@ import ProfileViewer from '../profile-viewer/ProfileViewer';
 import SpaceAddExisting from '../../molecules/space-add-existing/SpaceAddExisting';
 import Search from '../search/Search';
 import ViewSource from '../view-source/ViewSource';
+import CreateRoom from '../create-room/CreateRoom';
 
 function Dialogs() {
   return (
@@ -12,6 +13,7 @@ function Dialogs() {
       <ReadReceipts />
       <ViewSource />
       <ProfileViewer />
+      <CreateRoom />
       <SpaceAddExisting />
       <Search />
     </>
index b1669a14977c789e878f0868a1fc96b870e9b0a6..ae2bc1a68a227b7de5b43ec7798f811f23f4ceca 100644 (file)
@@ -5,7 +5,6 @@ import navigation from '../../../client/state/navigation';
 
 import InviteList from '../invite-list/InviteList';
 import PublicRooms from '../public-rooms/PublicRooms';
-import CreateRoom from '../create-room/CreateRoom';
 import InviteUser from '../invite-user/InviteUser';
 import Settings from '../settings/Settings';
 import SpaceSettings from '../space-settings/SpaceSettings';
@@ -16,7 +15,6 @@ function Windows() {
   const [publicRooms, changePublicRooms] = useState({
     isOpen: false, searchTerm: undefined,
   });
-  const [isCreateRoom, changeCreateRoom] = useState(false);
   const [inviteUser, changeInviteUser] = useState({
     isOpen: false, roomId: undefined, term: undefined,
   });
@@ -31,9 +29,6 @@ function Windows() {
       searchTerm,
     });
   }
-  function openCreateRoom() {
-    changeCreateRoom(true);
-  }
   function openInviteUser(roomId, searchTerm) {
     changeInviteUser({
       isOpen: true,
@@ -48,13 +43,11 @@ function Windows() {
   useEffect(() => {
     navigation.on(cons.events.navigation.INVITE_LIST_OPENED, openInviteList);
     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.SETTINGS_OPENED, openSettings);
     return () => {
       navigation.removeListener(cons.events.navigation.INVITE_LIST_OPENED, openInviteList);
       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.SETTINGS_OPENED, openSettings);
     };
@@ -71,10 +64,6 @@ function Windows() {
         searchTerm={publicRooms.searchTerm}
         onRequestClose={() => changePublicRooms({ isOpen: false, searchTerm: undefined })}
       />
-      <CreateRoom
-        isOpen={isCreateRoom}
-        onRequestClose={() => changeCreateRoom(false)}
-      />
       <InviteUser
         isOpen={inviteUser.isOpen}
         roomId={inviteUser.roomId}
index ab01e38a3a83327fe94bb3076f765db71913c03c..817caddf6ca3712bcd0190ef99f1e43daf65f57b 100644 (file)
@@ -65,9 +65,11 @@ export function openPublicRooms(searchTerm) {
   });
 }
 
-export function openCreateRoom() {
+export function openCreateRoom(isSpace = false, parentId = null) {
   appDispatcher.dispatch({
     type: cons.actions.navigation.OPEN_CREATE_ROOM,
+    isSpace,
+    parentId,
   });
 }
 
index 83fbfc7c32598ccbbcb5ca138b162bb13ff06ff2..c6145bed3a29f184f6e97aaecb0658ce7fb10e54 100644 (file)
@@ -1,6 +1,7 @@
 import initMatrix from '../initMatrix';
 import appDispatcher from '../dispatcher';
 import cons from '../state/cons';
+import { getIdServer } from '../../util/matrixUtil';
 
 /**
  * https://github.com/matrix-org/matrix-react-sdk/blob/1e6c6e9d800890c732d60429449bc280de01a647/src/Rooms.js#L73
@@ -125,69 +126,129 @@ function leave(roomId) {
     }).catch();
 }
 
-/**
- * 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) {
+async function create(options, isDM = false) {
   const mx = initMatrix.matrixClient;
-  const customPowerLevels = [101];
-  const options = {
-    name: opts.name,
-    topic: opts.topic,
-    visibility: opts.isPublic === true ? 'public' : 'private',
-    room_alias_name: opts.roomAlias,
-    is_direct: opts.isDirect === true,
-    invite: opts.invite || [],
-    initial_state: [],
-    preset: opts.isDirect === true ? 'trusted_private_chat' : undefined,
-    power_level_content_override: customPowerLevels.indexOf(opts.powerLevel) === -1 ? undefined : {
-      users: { [initMatrix.matrixClient.getUserId()]: opts.powerLevel },
-    },
-  };
-
-  if (opts.isPublic !== true && opts.isEncrypted === true) {
-    options.initial_state.push({
-      type: 'm.room.encryption',
-      state_key: '',
-      content: {
-        algorithm: 'm.megolm.v1.aes-sha2',
-      },
-    });
-  }
-
   try {
     const result = await mx.createRoom(options);
-    if (opts.isDirect === true && typeof opts.invite[0] !== 'undefined') {
-      await addRoomToMDirect(result.room_id, opts.invite[0]);
+    if (isDM && typeof options.invite?.[0] === 'string') {
+      await addRoomToMDirect(result.room_id, options.invite[0]);
     }
     appDispatcher.dispatch({
       type: cons.actions.room.CREATE,
       roomId: result.room_id,
-      isDM: opts.isDirect === true,
+      isDM,
     });
     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)) {
-      appDispatcher.dispatch({
-        type: cons.actions.room.error.CREATE,
-        error: e,
-      });
+    if (errcodes.includes(e.errcode)) {
       throw new Error(e);
     }
     throw new Error('Something went wrong!');
   }
 }
 
+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({
+      type: 'm.room.encryption',
+      state_key: '',
+      content: {
+        algorithm: 'm.megolm.v1.aes-sha2',
+      },
+    });
+  }
+
+  const result = await create(options, true);
+  return result;
+}
+
+async function createRoom(opts) {
+  // joinRule: 'public' | 'invite' | 'restricted'
+  const { name, topic, joinRule } = opts;
+  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',
+      },
+    });
+  }
+  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) {
   const mx = initMatrix.matrixClient;
 
@@ -242,7 +303,8 @@ function deleteSpaceShortcut(roomId) {
 
 export {
   join, leave,
-  create, invite, kick, ban, unban,
+  createDM, createRoom,
+  invite, kick, ban, unban,
   setPowerLevel,
   createSpaceShortcut, deleteSpaceShortcut,
 };
index 8ff6eede85825ea9977f0a49ccc51b050bc2cd46..e9413cbb2bfc5ef95d41136f928d78162092eb0b 100644 (file)
@@ -53,9 +53,6 @@ const cons = {
       CREATE: 'CREATE',
       CREATE_SPACE_SHORTCUT: 'CREATE_SPACE_SHORTCUT',
       DELETE_SPACE_SHORTCUT: 'DELETE_SPACE_SHORTCUT',
-      error: {
-        CREATE: 'ERROR_CREATE',
-      },
     },
     settings: {
       TOGGLE_SYSTEM_THEME: 'TOGGLE_SYSTEM_THEME',
index ca0839563fe11c6cf0e0a2bd225a301716f82155..ee8d06c28eaefe8f2132fd67f40d7c004fe53875 100644 (file)
@@ -113,7 +113,11 @@ class Navigation extends EventEmitter {
         this.emit(cons.events.navigation.PUBLIC_ROOMS_OPENED, action.searchTerm);
       },
       [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]: () => {
         this.emit(cons.events.navigation.INVITE_USER_OPENED, action.roomId, action.searchTerm);