Added option to fav spaces (#52)
authorunknown <ajbura@gmail.com>
Sun, 5 Sep 2021 13:26:34 +0000 (18:56 +0530)
committerunknown <ajbura@gmail.com>
Sun, 5 Sep 2021 13:26:34 +0000 (18:56 +0530)
17 files changed:
public/res/ic/filled/star.svg [new file with mode: 0644]
public/res/ic/outlined/star.svg [new file with mode: 0644]
src/app/molecules/room-selector/RoomSelector.scss
src/app/organisms/navigation/Drawer.jsx
src/app/organisms/navigation/Drawer.scss
src/app/organisms/navigation/DrawerBreadcrumb.jsx
src/app/organisms/navigation/DrawerHeader.jsx
src/app/organisms/navigation/Selector.jsx
src/app/organisms/navigation/SideBar.jsx
src/app/organisms/navigation/SideBar.scss
src/app/organisms/room/PeopleDrawer.jsx
src/app/organisms/room/RoomViewCmdBar.jsx
src/client/action/navigation.js
src/client/action/room.js
src/client/state/RoomList.js
src/client/state/cons.js
src/client/state/navigation.js

diff --git a/public/res/ic/filled/star.svg b/public/res/ic/filled/star.svg
new file mode 100644 (file)
index 0000000..378c891
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+        viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
+<g>
+       <polygon points="12,2 15.1,8.6 22,9.6 17,14.8 18.2,22 12,18.6 5.8,22 7,14.8 2,9.6 8.9,8.6       "/>
+</g>
+</svg>
diff --git a/public/res/ic/outlined/star.svg b/public/res/ic/outlined/star.svg
new file mode 100644 (file)
index 0000000..290f159
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+        viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
+<path d="M12,6.7l1.7,3.7l4.1,0.6l-3,3.1l0.7,4.2l-3.5-2l-3.5,2l0.7-4.2l-3-3.1l4.1-0.6L12,6.7 M12,2L8.9,8.6L2,9.6l5,5.1L5.8,22
+       l6.2-3.4l6.2,3.4L17,14.8l5-5.1l-6.9-1.1L12,2L12,2z"/>
+</svg>
index 9b40fcb132ec2793d734bd2d11b9d7cf1dc676a3..ae3144bd2b591016db0904a37aac1c205b4267bb 100644 (file)
@@ -83,7 +83,7 @@
     margin: 0 !important;
   }
 
-  & .ic-btn-surface {
+  & .ic-btn {
     padding: 6px;
     border-radius: calc(var(--bo-radius) / 2);
   }
index f8c53eab77b4519e1f28fa886dd9e158e5e34cfd..b76347e538090817838e608f39386bd1d9442f58 100644 (file)
@@ -1,8 +1,10 @@
 import React, { useState, useEffect } from 'react';
 import './Drawer.scss';
 
+import initMatrix from '../../../client/initMatrix';
 import cons from '../../../client/state/cons';
 import navigation from '../../../client/state/navigation';
+import { selectTab, selectSpace } from '../../../client/action/navigation';
 
 import ScrollView from '../../atoms/scroll/ScrollView';
 
@@ -12,34 +14,42 @@ import Home from './Home';
 import Directs from './Directs';
 
 function Drawer() {
-  const [selectedTab, setSelectedTab] = useState('home');
+  const [selectedTab, setSelectedTab] = useState(navigation.selectedTab);
   const [spaceId, setSpaceId] = useState(navigation.selectedSpaceId);
 
-  function onTabChanged(tabId) {
+  function onTabSelected(tabId) {
     setSelectedTab(tabId);
   }
   function onSpaceSelected(roomId) {
     setSpaceId(roomId);
   }
+  function onRoomLeaved(roomId) {
+    const lRoomIndex = navigation.selectedSpacePath.indexOf(roomId);
+    if (lRoomIndex === -1) return;
+    if (lRoomIndex === 0) selectTab(cons.tabs.HOME);
+    else selectSpace(navigation.selectedSpacePath[lRoomIndex - 1]);
+  }
 
   useEffect(() => {
-    navigation.on(cons.events.navigation.TAB_CHANGED, onTabChanged);
+    navigation.on(cons.events.navigation.TAB_SELECTED, onTabSelected);
     navigation.on(cons.events.navigation.SPACE_SELECTED, onSpaceSelected);
+    initMatrix.roomList.on(cons.events.roomList.ROOM_LEAVED, onRoomLeaved);
     return () => {
-      navigation.removeListener(cons.events.navigation.TAB_CHANGED, onTabChanged);
+      navigation.removeListener(cons.events.navigation.TAB_SELECTED, onTabSelected);
       navigation.removeListener(cons.events.navigation.SPACE_SELECTED, onSpaceSelected);
+      initMatrix.roomList.removeListener(cons.events.roomList.ROOM_LEAVED, onRoomLeaved);
     };
   }, []);
   return (
     <div className="drawer">
       <DrawerHeader selectedTab={selectedTab} spaceId={spaceId} />
       <div className="drawer__content-wrapper">
-        {selectedTab === 'home' && <DrawerBreadcrumb />}
+        {selectedTab !== cons.tabs.DIRECTS && <DrawerBreadcrumb spaceId={spaceId} />}
         <div className="rooms__wrapper">
           <ScrollView autoHide>
             <div className="rooms-container">
               {
-                selectedTab === 'home'
+                selectedTab !== cons.tabs.DIRECTS
                   ? <Home spaceId={spaceId} />
                   : <Directs />
               }
index b240ac370e2476cd1bf7b50b709ed98d41f33087..e93761a2e97bdcc7d0c30dfd6fe4ea7d08dfa796 100644 (file)
     border-left: 1px solid var(--bg-surface-border);
   }
 
+  & .header__title-wrapper .text {
+    font-weight: 500;
+  }
+
   &__content-wrapper {
     @extend .drawer-flexItem;
     @extend .drawer-flexBox;
index 4df5a6dc1a488d87db1e492231e8adb5f6775305..e7843626b7fb0b0a5e29d32b90170b9602f6b089 100644 (file)
@@ -1,4 +1,5 @@
-import React, { useState, useEffect, useRef } from 'react';
+import React, { useEffect, useRef } from 'react';
+import PropTypes from 'prop-types';
 import './DrawerBreadcrumb.scss';
 
 import initMatrix from '../../../client/initMatrix';
@@ -13,50 +14,47 @@ import ScrollView from '../../atoms/scroll/ScrollView';
 
 import ChevronRightIC from '../../../../public/res/ic/outlined/chevron-right.svg';
 
-function DrawerBreadcrumb() {
-  const [, forceUpdate] = useState({});
+function DrawerBreadcrumb({ spaceId }) {
   const scrollRef = useRef(null);
   const mx = initMatrix.matrixClient;
   const spacePath = navigation.selectedSpacePath;
 
-  function onSpaceSelected() {
-    forceUpdate({});
+  useEffect(() => {
     requestAnimationFrame(() => {
       if (scrollRef?.current === null) return;
       scrollRef.current.scrollLeft = scrollRef.current.scrollWidth;
     });
-  }
-
-  useEffect(() => {
-    navigation.on(cons.events.navigation.SPACE_SELECTED, onSpaceSelected);
-    return () => {
-      navigation.removeListener(cons.events.navigation.SPACE_SELECTED, onSpaceSelected);
-    };
-  }, []);
+  }, [spaceId]);
 
-  if (spacePath.length === 0) return null;
+  if (spacePath.length === 1) return null;
 
   return (
     <div className="breadcrumb__wrapper">
       <ScrollView ref={scrollRef} horizontal vertical={false} invisible>
         <div className="breadcrumb">
-          <Button onClick={() => selectSpace(null)}>
-            <Text variant="b2">Home</Text>
-          </Button>
           {
-            spacePath.map((spaceId, index) => (
-              <React.Fragment
-                key={spaceId}
-              >
-                <RawIcon size="extra-small" src={ChevronRightIC} />
-                <Button
-                  className={index === spacePath.length - 1 ? 'breadcrumb__btn--selected' : ''}
-                  onClick={() => selectSpace(spaceId)}
+            spacePath.map((id, index) => {
+              if (index === 0) {
+                return (
+                  <Button key={id} onClick={() => selectSpace(id)}>
+                    <Text variant="b2">{id === cons.tabs.HOME ? 'Home' : mx.getRoom(id).name}</Text>
+                  </Button>
+                );
+              }
+              return (
+                <React.Fragment
+                  key={id}
                 >
-                  <Text variant="b2">{ mx.getRoom(spaceId).name }</Text>
-                </Button>
-              </React.Fragment>
-            ))
+                  <RawIcon size="extra-small" src={ChevronRightIC} />
+                  <Button
+                    className={index === spacePath.length - 1 ? 'breadcrumb__btn--selected' : ''}
+                    onClick={() => selectSpace(id)}
+                  >
+                    <Text variant="b2">{ mx.getRoom(id).name }</Text>
+                  </Button>
+                </React.Fragment>
+              );
+            })
           }
           <div style={{ width: 'var(--sp-extra-tight)', height: '100%' }} />
         </div>
@@ -65,4 +63,12 @@ function DrawerBreadcrumb() {
   );
 }
 
+DrawerBreadcrumb.defaultProps = {
+  spaceId: null,
+};
+
+DrawerBreadcrumb.propTypes = {
+  spaceId: PropTypes.string,
+};
+
 export default DrawerBreadcrumb;
index 686d476341f51da948cbc8148abab99bbf084a2d..220633c042fd65eac7583eb3b36cd27132dc4c2e 100644 (file)
@@ -1,11 +1,12 @@
-import React from 'react';
+import React, { useState } from 'react';
 import PropTypes from 'prop-types';
 
 import initMatrix from '../../../client/initMatrix';
+import cons from '../../../client/state/cons';
 import {
-  selectSpace, openPublicRooms, openCreateRoom, openInviteUser,
+  openPublicRooms, openCreateRoom, openInviteUser,
 } from '../../../client/action/navigation';
-import navigation from '../../../client/state/navigation';
+import { createSpaceShortcut, deleteSpaceShortcut } from '../../../client/action/room';
 
 import Text from '../../atoms/text/Text';
 import Header, { TitleWrapper } from '../../atoms/header/Header';
@@ -15,32 +16,37 @@ import ContextMenu, { MenuItem, MenuHeader } from '../../atoms/context-menu/Cont
 import PlusIC from '../../../../public/res/ic/outlined/plus.svg';
 import HashPlusIC from '../../../../public/res/ic/outlined/hash-plus.svg';
 import HashSearchIC from '../../../../public/res/ic/outlined/hash-search.svg';
-import ChevronLeftIC from '../../../../public/res/ic/outlined/chevron-left.svg';
+import StarIC from '../../../../public/res/ic/outlined/star.svg';
+import FilledStarIC from '../../../../public/res/ic/filled/star.svg';
 
 function DrawerHeader({ selectedTab, spaceId }) {
+  const [, forceUpdate] = useState({});
   const mx = initMatrix.matrixClient;
-  const tabName = selectedTab === 'home' ? 'Home' : 'Direct messages';
+  const tabName = selectedTab !== cons.tabs.DIRECTS ? 'Home' : 'Direct messages';
 
   const room = mx.getRoom(spaceId);
-  const spaceName = selectedTab === 'dm' ? null : (room?.name || null);
-
-  function handleBackClick() {
-    const spacePath = navigation.selectedSpacePath;
-    if (spacePath.length === 1) {
-      selectSpace(null);
-      return;
-    }
-    selectSpace(spacePath[spacePath.length - 2]);
-  }
+  const spaceName = selectedTab === cons.tabs.DIRECTS ? null : (room?.name || null);
 
   return (
     <Header>
       <TitleWrapper>
         <Text variant="s1">{spaceName || tabName}</Text>
       </TitleWrapper>
-      { spaceName && <IconButton onClick={handleBackClick} tooltip="Back" src={ChevronLeftIC} size="normal" /> }
-      { selectedTab === 'dm' && <IconButton onClick={() => openInviteUser()} tooltip="Start DM" src={PlusIC} size="normal" /> }
-      { selectSpace !== 'dm' && !spaceName && (
+      {spaceName && (
+        <IconButton
+          size="extra-small"
+          variant={initMatrix.roomList.spaceShortcut.has(spaceId) ? 'positive' : 'surface'}
+          tooltip={initMatrix.roomList.spaceShortcut.has(spaceId) ? 'Remove favourite' : 'Favourite'}
+          src={initMatrix.roomList.spaceShortcut.has(spaceId) ? FilledStarIC : StarIC}
+          onClick={() => {
+            if (initMatrix.roomList.spaceShortcut.has(spaceId)) deleteSpaceShortcut(spaceId);
+            else createSpaceShortcut(spaceId);
+            forceUpdate({});
+          }}
+        />
+      )}
+      { selectedTab === cons.tabs.DIRECTS && <IconButton onClick={() => openInviteUser()} tooltip="Start DM" src={PlusIC} size="normal" /> }
+      { selectedTab !== cons.tabs.DIRECTS && !spaceName && (
         <>
           <ContextMenu
             content={(hideMenu) => (
index 9430bb11676c0108c32f658850fe80dd4908c266..0ddc127401579f91369e5c2334495e3f5bdd942c 100644 (file)
@@ -5,20 +5,25 @@ import PropTypes from 'prop-types';
 import initMatrix from '../../../client/initMatrix';
 import { doesRoomHaveUnread } from '../../../util/matrixUtil';
 import navigation from '../../../client/state/navigation';
+import { createSpaceShortcut, deleteSpaceShortcut } from '../../../client/action/room';
 
+import IconButton from '../../atoms/button/IconButton';
 import RoomSelector from '../../molecules/room-selector/RoomSelector';
 
 import HashIC from '../../../../public/res/ic/outlined/hash.svg';
 import HashLockIC from '../../../../public/res/ic/outlined/hash-lock.svg';
 import SpaceIC from '../../../../public/res/ic/outlined/space.svg';
 import SpaceLockIC from '../../../../public/res/ic/outlined/space-lock.svg';
+import StarIC from '../../../../public/res/ic/outlined/star.svg';
+import FilledStarIC from '../../../../public/res/ic/filled/star.svg';
 
 function Selector({
   roomId, isDM, drawerPostie, onClick,
 }) {
   const mx = initMatrix.matrixClient;
   const room = mx.getRoom(roomId);
-  const imageSrc = room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null;
+  let imageSrc = room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null;
+  if (imageSrc === null) imageSrc = room.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null;
 
   const [isSelected, setIsSelected] = useState(navigation.selectedRoomId === roomId);
   const [, forceUpdate] = useState({});
@@ -60,6 +65,23 @@ function Selector({
       notificationCount={room.getUnreadNotificationCount('total') || 0}
       isAlert={room.getUnreadNotificationCount('highlight') !== 0}
       onClick={onClick}
+      options={(
+        !room.isSpaceRoom()
+          ? null
+          : (
+            <IconButton
+              size="extra-small"
+              variant={initMatrix.roomList.spaceShortcut.has(roomId) ? 'positive' : 'surface'}
+              tooltip={initMatrix.roomList.spaceShortcut.has(roomId) ? 'Remove favourite' : 'Favourite'}
+              src={initMatrix.roomList.spaceShortcut.has(roomId) ? FilledStarIC : StarIC}
+              onClick={() => {
+                if (initMatrix.roomList.spaceShortcut.has(roomId)) deleteSpaceShortcut(roomId);
+                else createSpaceShortcut(roomId);
+                forceUpdate({});
+              }}
+            />
+          )
+      )}
     />
   );
 }
index 1468bc66afb499910078e9b156ae3ac150f0b92f..b95636f99705f97ab24271919226525065c2e5c4 100644 (file)
@@ -6,7 +6,7 @@ import cons from '../../../client/state/cons';
 import colorMXID from '../../../util/colorMXID';
 import logout from '../../../client/action/logout';
 import {
-  changeTab, openInviteList, openPublicRooms, openSettings,
+  selectTab, openInviteList, openPublicRooms, openSettings,
 } from '../../../client/action/navigation';
 import navigation from '../../../client/state/navigation';
 
@@ -55,29 +55,37 @@ function ProfileAvatarMenu() {
 }
 
 function SideBar() {
-  const totalInviteCount = () => initMatrix.roomList.inviteRooms.size
-    + initMatrix.roomList.inviteSpaces.size
-    + initMatrix.roomList.inviteDirects.size;
+  const { roomList } = initMatrix;
+  const mx = initMatrix.matrixClient;
+  const totalInviteCount = () => roomList.inviteRooms.size
+    + roomList.inviteSpaces.size
+    + roomList.inviteDirects.size;
 
   const [totalInvites, updateTotalInvites] = useState(totalInviteCount());
-  const [selectedTab, setSelectedTab] = useState('home');
+  const [selectedTab, setSelectedTab] = useState(navigation.selectedTab);
+  const [, forceUpdate] = useState({});
 
-  function onTabChanged(tabId) {
+  function onTabSelected(tabId) {
     setSelectedTab(tabId);
   }
   function onInviteListChange() {
     updateTotalInvites(totalInviteCount());
   }
+  function onSpaceShortcutUpdated() {
+    forceUpdate({});
+  }
 
   useEffect(() => {
-    navigation.on(cons.events.navigation.TAB_CHANGED, onTabChanged);
+    navigation.on(cons.events.navigation.TAB_SELECTED, onTabSelected);
+    roomList.on(cons.events.roomList.SPACE_SHORTCUT_UPDATED, onSpaceShortcutUpdated);
     initMatrix.roomList.on(
       cons.events.roomList.INVITELIST_UPDATED,
       onInviteListChange,
     );
 
     return () => {
-      navigation.removeListener(cons.events.navigation.TAB_CHANGED, onTabChanged);
+      navigation.removeListener(cons.events.navigation.TAB_SELECTED, onTabSelected);
+      roomList.removeListener(cons.events.roomList.SPACE_SHORTCUT_UPDATED, onSpaceShortcutUpdated);
       initMatrix.roomList.removeListener(
         cons.events.roomList.INVITELIST_UPDATED,
         onInviteListChange,
@@ -91,12 +99,30 @@ function SideBar() {
         <ScrollView invisible>
           <div className="scrollable-content">
             <div className="featured-container">
-              <SidebarAvatar active={selectedTab === 'home'} onClick={() => changeTab('home')} tooltip="Home" iconSrc={HomeIC} />
-              <SidebarAvatar active={selectedTab === 'dm'} onClick={() => changeTab('dm')} tooltip="People" iconSrc={UserIC} />
+              <SidebarAvatar active={selectedTab === cons.tabs.HOME} onClick={() => selectTab(cons.tabs.HOME)} tooltip="Home" iconSrc={HomeIC} />
+              <SidebarAvatar active={selectedTab === cons.tabs.DIRECTS} onClick={() => selectTab(cons.tabs.DIRECTS)} tooltip="People" iconSrc={UserIC} />
               <SidebarAvatar onClick={() => openPublicRooms()} tooltip="Public rooms" iconSrc={HashSearchIC} />
             </div>
             <div className="sidebar-divider" />
-            <div className="space-container" />
+            <div className="space-container">
+              {
+                [...roomList.spaceShortcut].map((shortcut) => {
+                  const sRoomId = shortcut;
+                  const room = mx.getRoom(sRoomId);
+                  return (
+                    <SidebarAvatar
+                      active={selectedTab === sRoomId}
+                      key={sRoomId}
+                      tooltip={room.name}
+                      bgColor={colorMXID(room.roomId)}
+                      imageSrc={room.getAvatarUrl(initMatrix.matrixClient.baseUrl, 42, 42, 'crop') || null}
+                      text={room.name.slice(0, 1)}
+                      onClick={() => selectTab(shortcut)}
+                    />
+                  );
+                })
+              }
+            </div>
           </div>
         </ScrollView>
       </div>
index 09641fcc5112ffbc8c897a0f42b06ad5229aaa7c..48970fbdaa91cdb6ec0552a72b7995e13ac1a848 100644 (file)
@@ -44,7 +44,7 @@
       var(--bg-surface-low),
       var(--bg-surface-low-transparent));
     position: sticky;
-    bottom: 0;
+    bottom: -1px;
     left: 0;
   }
 }
index 2a7b18d8a5e517cb0f3b34dc52f1ffb2c59e2a33..ca975d13c1d46c451471fda10a143a1d279d50e8 100644 (file)
@@ -18,23 +18,15 @@ import PeopleSelector from '../../molecules/people-selector/PeopleSelector';
 import AddUserIC from '../../../../public/res/ic/outlined/add-user.svg';
 
 function getPowerLabel(powerLevel) {
-  switch (powerLevel) {
-    case 100:
-      return 'Admin';
-    case 50:
-      return 'Mod';
-    default:
-      return null;
-  }
+  if (powerLevel > 9000) return 'Goku';
+  if (powerLevel > 100) return 'Founder';
+  if (powerLevel === 100) return 'Admin';
+  if (powerLevel >= 50) return 'Mod';
+  return null;
 }
-function compare(m1, m2) {
-  let aName = m1.name;
-  let bName = m2.name;
-
-  // remove "#" from the room name
-  // To ignore it in sorting
-  aName = aName.replaceAll('#', '');
-  bName = bName.replaceAll('#', '');
+function AtoZ(m1, m2) {
+  const aName = m1.name;
+  const bName = m2.name;
 
   if (aName.toLowerCase() < bName.toLowerCase()) {
     return -1;
@@ -45,25 +37,18 @@ function compare(m1, m2) {
   return 0;
 }
 function sortByPowerLevel(m1, m2) {
-  let pl1 = String(m1.powerLevel);
-  let pl2 = String(m2.powerLevel);
+  const pl1 = m1.powerLevel;
+  const pl2 = m2.powerLevel;
 
-  if (pl1 === '100') pl1 = '90.9';
-  if (pl2 === '100') pl2 = '90.9';
-
-  if (pl1.toLowerCase() > pl2.toLowerCase()) {
-    return -1;
-  }
-  if (pl1.toLowerCase() < pl2.toLowerCase()) {
-    return 1;
-  }
+  if (pl1 > pl2) return -1;
+  if (pl1 < pl2) return 1;
   return 0;
 }
 
 function PeopleDrawer({ roomId }) {
   const PER_PAGE_MEMBER = 50;
   const room = initMatrix.matrixClient.getRoom(roomId);
-  const totalMemberList = room.getJoinedMembers().sort(compare).sort(sortByPowerLevel);
+  const totalMemberList = room.getJoinedMembers().sort(AtoZ).sort(sortByPowerLevel);
   const [memberList, updateMemberList] = useState([]);
   let isRoomChanged = false;
 
@@ -75,7 +60,7 @@ function PeopleDrawer({ roomId }) {
     updateMemberList(totalMemberList.slice(0, PER_PAGE_MEMBER));
     room.loadMembersIfNeeded().then(() => {
       if (isRoomChanged) return;
-      const newTotalMemberList = room.getJoinedMembers().sort(compare).sort(sortByPowerLevel);
+      const newTotalMemberList = room.getJoinedMembers().sort(AtoZ).sort(sortByPowerLevel);
       updateMemberList(newTotalMemberList.slice(0, PER_PAGE_MEMBER));
     });
 
index 7f8f80904cc356555992fb364bdc019609995e48..329d46a5d0d2b0032342e887b2f030d710b3325d 100644 (file)
@@ -10,6 +10,7 @@ import cons from '../../../client/state/cons';
 import { toggleMarkdown } from '../../../client/action/settings';
 import * as roomActions from '../../../client/action/room';
 import {
+  selectTab,
   selectRoom,
   openCreateRoom,
   openPublicRooms,
@@ -357,7 +358,8 @@ function RoomViewCmdBar({ roomId, roomTimeline, viewEvent }) {
   }
   function fireCmd(myCmd) {
     if (myCmd.prefix.match(/^>[*#@]$/)) {
-      selectRoom(myCmd.result.roomId);
+      if (cmd.prefix === '>*') selectTab(myCmd.result.roomId);
+      else selectRoom(myCmd.result.roomId);
       viewEvent.emit('cmd_fired');
     }
     if (myCmd.prefix === '/') {
index bf560d877ed8d80395e09115c0829b167832f251..5fa13040a5d8ede0f660f7f22632d0abae93e4dc 100644 (file)
@@ -1,9 +1,9 @@
 import appDispatcher from '../dispatcher';
 import cons from '../state/cons';
 
-function changeTab(tabId) {
+function selectTab(tabId) {
   appDispatcher.dispatch({
-    type: cons.actions.navigation.CHANGE_TAB,
+    type: cons.actions.navigation.SELECT_TAB,
     tabId,
   });
 }
@@ -78,7 +78,7 @@ function openReadReceipts(roomId, eventId) {
 }
 
 export {
-  changeTab,
+  selectTab,
   selectSpace,
   selectRoom,
   togglePeopleDrawer,
index 407a9e3acbedb45d19090c297731ab24ebc4127a..08e73e214c545fa67bc631be4248e55bdc8ee185 100644 (file)
@@ -190,7 +190,22 @@ async function invite(roomId, userId) {
   }
 }
 
+function createSpaceShortcut(roomId) {
+  appDispatcher.dispatch({
+    type: cons.actions.room.CREATE_SPACE_SHORTCUT,
+    roomId,
+  });
+}
+
+function deleteSpaceShortcut(roomId) {
+  appDispatcher.dispatch({
+    type: cons.actions.room.DELETE_SPACE_SHORTCUT,
+    roomId,
+  });
+}
+
 export {
   join, leave,
   create, invite,
+  createSpaceShortcut, deleteSpaceShortcut,
 };
index 4e88adc723a6c9376b279e83ba24f1ef10f4a5be..a47bf46980b985fb44eb82f93f52a9de36e755fd 100644 (file)
@@ -9,6 +9,8 @@ class RoomList extends EventEmitter {
     this.mDirects = this.getMDirects();
     this.roomIdToParents = new Map();
 
+    this.spaceShortcut = new Set();
+
     this.inviteDirects = new Set();
     this.inviteSpaces = new Set();
     this.inviteRooms = new Set();
@@ -20,11 +22,18 @@ class RoomList extends EventEmitter {
     this.processingRooms = new Map();
 
     this._populateRooms();
+    this._populateSpaceShortcut();
     this._listenEvents();
 
     appDispatcher.register(this.roomActions.bind(this));
   }
 
+  _updateSpaceShortcutData(shortcutList) {
+    const spaceContent = this.matrixClient.getAccountData(cons['in.cinny.spaces'])?.getContent() || {};
+    spaceContent.shortcut = shortcutList;
+    this.matrixClient.setAccountData(cons['in.cinny.spaces'], spaceContent);
+  }
+
   getSpaceChildren(roomId) {
     const space = this.matrixClient.getRoom(roomId);
     const mSpaceChild = space?.currentState.getStateEvents('m.space.child');
@@ -64,6 +73,12 @@ class RoomList extends EventEmitter {
     spaceChildren?.forEach((childRoomId) => {
       this.removeFromRoomIdToParents(childRoomId, roomId);
     });
+
+    if (this.spaceShortcut.has(roomId)) {
+      // if delete space has shortcut remove it.
+      this.spaceShortcut.delete(roomId);
+      this._updateSpaceShortcutData([...this.spaceShortcut]);
+    }
   }
 
   roomActions(action) {
@@ -106,6 +121,18 @@ class RoomList extends EventEmitter {
           });
         }
       },
+      [cons.actions.room.CREATE_SPACE_SHORTCUT]: () => {
+        if (this.spaceShortcut.has(action.roomId)) return;
+        this.spaceShortcut.add(action.roomId);
+        this._updateSpaceShortcutData([...this.spaceShortcut]);
+        this.emit(cons.events.roomList.SPACE_SHORTCUT_UPDATED, action.roomId);
+      },
+      [cons.actions.room.DELETE_SPACE_SHORTCUT]: () => {
+        if (!this.spaceShortcut.has(action.roomId)) return;
+        this.spaceShortcut.delete(action.roomId);
+        this._updateSpaceShortcutData([...this.spaceShortcut]);
+        this.emit(cons.events.roomList.SPACE_SHORTCUT_UPDATED, action.roomId);
+      },
     };
     actions[action.type]?.();
   }
@@ -125,6 +152,21 @@ class RoomList extends EventEmitter {
     return mDirectsId;
   }
 
+  _populateSpaceShortcut() {
+    this.spaceShortcut.clear();
+    const spacesContent = this.matrixClient.getAccountData(cons['in.cinny.spaces'])?.getContent();
+
+    if (spacesContent && Array.isArray(spacesContent?.shortcut)) {
+      spacesContent.shortcut.forEach((shortcut) => {
+        if (this.spaces.has(shortcut)) this.spaceShortcut.add(shortcut);
+      });
+      if (spacesContent.shortcut.length !== this.spaceShortcut.size) {
+        // update shortcut list from account data if shortcut space doesn't exist.
+        this._updateSpaceShortcutData([...this.spaceShortcut]);
+      }
+    }
+  }
+
   _populateRooms() {
     this.directs.clear();
     this.roomIdToParents.clear();
@@ -166,6 +208,12 @@ class RoomList extends EventEmitter {
   _listenEvents() {
     // Update roomList when m.direct changes
     this.matrixClient.on('accountData', (event) => {
+      if (event.getType() === cons['in.cinny.spaces']) {
+        this._populateSpaceShortcut();
+        this.emit(cons.events.roomList.SPACE_SHORTCUT_UPDATED);
+        return;
+      }
+
       if (event.getType() !== 'm.direct') return;
 
       const latestMDirects = this.getMDirects();
index e8b8d1586731480ecb152cf108d43a0d807935bc..7587120192cf24d3c0ea68e82de143815606df25 100644 (file)
@@ -6,9 +6,14 @@ const cons = {
     BASE_URL: 'cinny_hs_base_url',
   },
   DEVICE_DISPLAY_NAME: 'Cinny Web',
+  'in.cinny.spaces': 'in.cinny.spaces',
+  tabs: {
+    HOME: 'home',
+    DIRECTS: 'dm',
+  },
   actions: {
     navigation: {
-      CHANGE_TAB: 'CHANGE_TAB',
+      SELECT_TAB: 'SELECT_TAB',
       SELECT_SPACE: 'SELECT_SPACE',
       SELECT_ROOM: 'SELECT_ROOM',
       TOGGLE_PEOPLE_DRAWER: 'TOGGLE_PEOPLE_DRAWER',
@@ -24,6 +29,8 @@ const cons = {
       JOIN: 'JOIN',
       LEAVE: 'LEAVE',
       CREATE: 'CREATE',
+      CREATE_SPACE_SHORTCUT: 'CREATE_SPACE_SHORTCUT',
+      DELETE_SPACE_SHORTCUT: 'DELETE_SPACE_SHORTCUT',
       error: {
         CREATE: 'ERROR_CREATE',
       },
@@ -34,7 +41,7 @@ const cons = {
   },
   events: {
     navigation: {
-      TAB_CHANGED: 'TAB_CHANGED',
+      TAB_SELECTED: 'TAB_SELECTED',
       SPACE_SELECTED: 'SPACE_SELECTED',
       ROOM_SELECTED: 'ROOM_SELECTED',
       PEOPLE_DRAWER_TOGGLED: 'PEOPLE_DRAWER_TOGGLED',
@@ -54,6 +61,7 @@ const cons = {
       ROOM_CREATED: 'ROOM_CREATED',
       MY_RECEIPT_ARRIVED: 'MY_RECEIPT_ARRIVED',
       EVENT_ARRIVED: 'EVENT_ARRIVED',
+      SPACE_SHORTCUT_UPDATED: 'SPACE_SHORTCUT_UPDATED',
     },
     roomTimeline: {
       EVENT: 'EVENT',
index 084af25a4c7f89f936571d50c7b9c76cb34f5831..5188aad886e1a6c83a3cc2f310fc4f9196abced6 100644 (file)
@@ -6,19 +6,17 @@ class Navigation extends EventEmitter {
   constructor() {
     super();
 
-    this.selectedTab = 'home';
+    this.selectedTab = cons.tabs.HOME;
     this.selectedSpaceId = null;
-    this.selectedSpacePath = [];
+    this.selectedSpacePath = [cons.tabs.HOME];
+
     this.selectedRoomId = null;
     this.isPeopleDrawerVisible = true;
-
-    // TODO:
-    window.navigation = this;
   }
 
   _setSpacePath(roomId) {
-    if (roomId === null) {
-      this.selectedSpacePath = [];
+    if (roomId === null || roomId === cons.tabs.HOME) {
+      this.selectedSpacePath = [cons.tabs.HOME];
       return;
     }
     if (this.selectedSpacePath.includes(roomId)) {
@@ -31,14 +29,24 @@ class Navigation extends EventEmitter {
 
   navigate(action) {
     const actions = {
-      [cons.actions.navigation.CHANGE_TAB]: () => {
+      [cons.actions.navigation.SELECT_TAB]: () => {
         this.selectedTab = action.tabId;
-        this.emit(cons.events.navigation.TAB_CHANGED, this.selectedTab);
+        if (this.selectedTab !== cons.tabs.DIRECTS) {
+          if (this.selectedTab === cons.tabs.HOME) {
+            this.selectedSpacePath = [cons.tabs.HOME];
+            this.selectedSpaceId = null;
+          } else {
+            this.selectedSpacePath = [this.selectedTab];
+            this.selectedSpaceId = this.selectedTab;
+          }
+          this.emit(cons.events.navigation.SPACE_SELECTED, this.selectedSpaceId);
+        } else this.selectedSpaceId = null;
+        this.emit(cons.events.navigation.TAB_SELECTED, this.selectedTab);
       },
       [cons.actions.navigation.SELECT_SPACE]: () => {
         this._setSpacePath(action.roomId);
         this.selectedSpaceId = action.roomId;
-        this.emit(cons.events.navigation.SPACE_SELECTED, action.roomId);
+        this.emit(cons.events.navigation.SPACE_SELECTED, this.selectedSpaceId);
       },
       [cons.actions.navigation.SELECT_ROOM]: () => {
         const prevSelectedRoomId = this.selectedRoomId;