Add join with address option (#420, #447)
authorAjay Bura <ajbura@gmail.com>
Tue, 3 May 2022 10:31:50 +0000 (16:01 +0530)
committerAjay Bura <ajbura@gmail.com>
Tue, 3 May 2022 10:31:50 +0000 (16:01 +0530)
src/app/organisms/emoji-verification/EmojiVerification.jsx
src/app/organisms/join-alias/JoinAlias.jsx [new file with mode: 0644]
src/app/organisms/join-alias/JoinAlias.scss [new file with mode: 0644]
src/app/organisms/navigation/DrawerHeader.jsx
src/app/organisms/pw/Dialogs.jsx
src/client/action/navigation.js
src/client/state/cons.js
src/client/state/navigation.js

index 7d67e09a0016bbc9ef96a83f29f03b6f603692ac..3d63b3c3d4b5a3cfd00e79e01e492f7ec81b59fd 100644 (file)
@@ -125,7 +125,7 @@ function EmojiVerificationContent({ data, requestClose }) {
 
   return (
     <div className="emoji-verification__content">
-      <Text>Click accept to start the verification process</Text>
+      <Text>Click accept to start the verification process.</Text>
       <div className="emoji-verification__buttons">
         {
           process
diff --git a/src/app/organisms/join-alias/JoinAlias.jsx b/src/app/organisms/join-alias/JoinAlias.jsx
new file mode 100644 (file)
index 0000000..0078d76
--- /dev/null
@@ -0,0 +1,155 @@
+import React, { useState, useEffect } from 'react';
+import PropTypes from 'prop-types';
+import './JoinAlias.scss';
+
+import initMatrix from '../../../client/initMatrix';
+import cons from '../../../client/state/cons';
+import navigation from '../../../client/state/navigation';
+import { join } from '../../../client/action/room';
+import { selectRoom, selectSpace } from '../../../client/action/navigation';
+
+import Text from '../../atoms/text/Text';
+import IconButton from '../../atoms/button/IconButton';
+import Button from '../../atoms/button/Button';
+import Input from '../../atoms/input/Input';
+import Spinner from '../../atoms/spinner/Spinner';
+import Dialog from '../../molecules/dialog/Dialog';
+
+import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
+
+import { useStore } from '../../hooks/useStore';
+
+const ALIAS_OR_ID_REG = /^[#|!].+:.+\..+$/;
+
+function JoinAliasContent({ term, requestClose }) {
+  const [process, setProcess] = useState(false);
+  const [error, setError] = useState(undefined);
+  const [lastJoinId, setLastJoinId] = useState(undefined);
+
+  const mx = initMatrix.matrixClient;
+  const mountStore = useStore();
+
+  const openRoom = (roomId) => {
+    const room = mx.getRoom(roomId);
+    if (!room) return;
+    if (room.isSpaceRoom()) selectSpace(roomId);
+    else selectRoom(roomId);
+    requestClose();
+  };
+
+  useEffect(() => {
+    const handleJoin = (roomId) => {
+      if (lastJoinId !== roomId) return;
+      openRoom(roomId);
+    };
+    initMatrix.roomList.on(cons.events.roomList.ROOM_JOINED, handleJoin);
+    return () => {
+      initMatrix.roomList.removeListener(cons.events.roomList.ROOM_JOINED, handleJoin);
+    };
+  }, [lastJoinId]);
+
+  const handleSubmit = async (e) => {
+    e.preventDefault();
+    mountStore.setItem(true);
+    const alias = e.target.alias.value;
+    if (alias?.trim() === '') return;
+    if (alias.match(ALIAS_OR_ID_REG) === null) {
+      setError('Invalid address.');
+      return;
+    }
+    setProcess('Looking for address...');
+    setError(undefined);
+    let via;
+    if (alias.startsWith('#')) {
+      try {
+        const aliasData = await mx.resolveRoomAlias(alias);
+        via = aliasData?.servers || [];
+        if (mountStore.getItem()) {
+          setProcess(`Joining ${alias}...`);
+        }
+      } catch (err) {
+        if (!mountStore.getItem()) return;
+        setProcess(false);
+        setError(`Unable to find room/space with ${alias}. Either room/space is private or doesn't exist.`);
+      }
+    }
+    try {
+      const roomId = await join(alias, false, via);
+      if (!mountStore.getItem()) return;
+      setLastJoinId(roomId);
+      openRoom(roomId);
+    } catch {
+      if (!mountStore.getItem()) return;
+      setProcess(false);
+      setError(`Unable to join ${alias}. Either room/space is private or doesn't exist.`);
+    }
+  };
+
+  return (
+    <form className="join-alias" onSubmit={handleSubmit}>
+      <Input
+        label="Address"
+        value={term}
+        name="alias"
+        required
+      />
+      {error && <Text className="join-alias__error" variant="b3">{error}</Text>}
+      <div className="join-alias__btn">
+        {
+          process
+            ? (
+              <>
+                <Spinner size="small" />
+                <Text>{process}</Text>
+              </>
+            )
+            : <Button variant="primary" type="submit">Join</Button>
+        }
+      </div>
+    </form>
+  );
+}
+JoinAliasContent.defaultProps = {
+  term: undefined,
+};
+JoinAliasContent.propTypes = {
+  term: PropTypes.string,
+  requestClose: PropTypes.func.isRequired,
+};
+
+function useWindowToggle() {
+  const [data, setData] = useState(null);
+
+  useEffect(() => {
+    const handleOpen = (term) => {
+      setData({ term });
+    };
+    navigation.on(cons.events.navigation.JOIN_ALIAS_OPENED, handleOpen);
+    return () => {
+      navigation.removeListener(cons.events.navigation.JOIN_ALIAS_OPENED, handleOpen);
+    };
+  }, []);
+
+  const onRequestClose = () => setData(null);
+
+  return [data, onRequestClose];
+}
+
+function JoinAlias() {
+  const [data, requestClose] = useWindowToggle();
+
+  return (
+    <Dialog
+      isOpen={data !== null}
+      title={(
+        <Text variant="s1" weight="medium" primary>Join with address</Text>
+      )}
+      contentOptions={<IconButton src={CrossIC} onClick={requestClose} tooltip="Close" />}
+      onRequestClose={requestClose}
+    >
+      { data ? <JoinAliasContent term={data.term} requestClose={requestClose} /> : <div /> }
+    </Dialog>
+  );
+}
+
+export default JoinAlias;
diff --git a/src/app/organisms/join-alias/JoinAlias.scss b/src/app/organisms/join-alias/JoinAlias.scss
new file mode 100644 (file)
index 0000000..b3684b0
--- /dev/null
@@ -0,0 +1,20 @@
+@use '../../partials/dir';
+
+.join-alias {
+  padding: var(--sp-normal);
+  @include dir.side(padding, var(--sp-normal), var(--sp-extra-tight));
+
+  & > *:not(:first-child) {
+    margin-top: var(--sp-normal);
+  }
+
+  &__error {
+    color: var(--tc-danger-high);
+    margin-top: var(--sp-extra-tight) !important;
+  }
+
+  &__btn {
+    display: flex;
+    gap: var(--sp-normal);
+  }
+}
\ No newline at end of file
index 3613c6432a6a360bc97fd58b5494793c29c36f4c..ba1882b70bde1f004b86b260054974ee2c8d3c4e 100644 (file)
@@ -7,7 +7,7 @@ import { twemojify } from '../../../util/twemojify';
 import initMatrix from '../../../client/initMatrix';
 import cons from '../../../client/state/cons';
 import {
-  openPublicRooms, openCreateRoom, openSpaceManage,
+  openPublicRooms, openCreateRoom, openSpaceManage, openJoinAlias,
   openSpaceAddExisting, openInviteUser, openReusableContextMenu,
 } from '../../../client/action/navigation';
 import { getEventCords } from '../../../util/common';
@@ -60,6 +60,14 @@ export function HomeSpaceOptions({ spaceId, afterOptionSelect }) {
           Join public room
         </MenuItem>
       )}
+      { !spaceId && (
+        <MenuItem
+          iconSrc={PlusIC}
+          onClick={() => { afterOptionSelect(); openJoinAlias(); }}
+        >
+          Join with address
+        </MenuItem>
+      )}
       { spaceId && (
         <MenuItem
           iconSrc={PlusIC}
index 28cb47ad26262fa03cb7242bcd24e475d2242b7e..a51d07e14d45e6d3bf8b1e89ce21c3874fe84dc3 100644 (file)
@@ -7,6 +7,7 @@ import SpaceAddExisting from '../../molecules/space-add-existing/SpaceAddExistin
 import Search from '../search/Search';
 import ViewSource from '../view-source/ViewSource';
 import CreateRoom from '../create-room/CreateRoom';
+import JoinAlias from '../join-alias/JoinAlias';
 import EmojiVerification from '../emoji-verification/EmojiVerification';
 
 import ReusableDialog from '../../molecules/dialog/ReusableDialog';
@@ -19,6 +20,7 @@ function Dialogs() {
       <ProfileViewer />
       <ShortcutSpaces />
       <CreateRoom />
+      <JoinAlias />
       <SpaceAddExisting />
       <Search />
       <EmojiVerification />
index 70f270b6fb3344375209c420eac53044a54957b8..1292d56dfba92a92078f4aba98a90843298b7709 100644 (file)
@@ -86,6 +86,13 @@ export function openCreateRoom(isSpace = false, parentId = null) {
   });
 }
 
+export function openJoinAlias(term) {
+  appDispatcher.dispatch({
+    type: cons.actions.navigation.OPEN_JOIN_ALIAS,
+    term,
+  });
+}
+
 export function openInviteUser(roomId, searchTerm) {
   appDispatcher.dispatch({
     type: cons.actions.navigation.OPEN_INVITE_USER,
index 789ed587769dd0add0ad9ce1ff3fbcf7c4f9333e..ec8ba266e32916004e3624112d4e65b00d1cc638 100644 (file)
@@ -38,6 +38,7 @@ const cons = {
       OPEN_INVITE_LIST: 'OPEN_INVITE_LIST',
       OPEN_PUBLIC_ROOMS: 'OPEN_PUBLIC_ROOMS',
       OPEN_CREATE_ROOM: 'OPEN_CREATE_ROOM',
+      OPEN_JOIN_ALIAS: 'OPEN_JOIN_ALIAS',
       OPEN_INVITE_USER: 'OPEN_INVITE_USER',
       OPEN_PROFILE_VIEWER: 'OPEN_PROFILE_VIEWER',
       OPEN_SETTINGS: 'OPEN_SETTINGS',
@@ -86,6 +87,7 @@ const cons = {
       INVITE_LIST_OPENED: 'INVITE_LIST_OPENED',
       PUBLIC_ROOMS_OPENED: 'PUBLIC_ROOMS_OPENED',
       CREATE_ROOM_OPENED: 'CREATE_ROOM_OPENED',
+      JOIN_ALIAS_OPENED: 'JOIN_ALIAS_OPENED',
       INVITE_USER_OPENED: 'INVITE_USER_OPENED',
       SETTINGS_OPENED: 'SETTINGS_OPENED',
       PROFILE_VIEWER_OPENED: 'PROFILE_VIEWER_OPENED',
index 7e7ab6957273f307736c31f0fcb117038bd2b37b..cc1e173124d1dfc9e27f93adff94981e36d33c13 100644 (file)
@@ -122,6 +122,12 @@ class Navigation extends EventEmitter {
           action.parentId,
         );
       },
+      [cons.actions.navigation.OPEN_JOIN_ALIAS]: () => {
+        this.emit(
+          cons.events.navigation.JOIN_ALIAS_OPENED,
+          action.term,
+        );
+      },
       [cons.actions.navigation.OPEN_INVITE_USER]: () => {
         this.emit(cons.events.navigation.INVITE_USER_OPENED, action.roomId, action.searchTerm);
       },