Added space nesting (#52)
authorunknown <ajbura@gmail.com>
Fri, 3 Sep 2021 12:28:01 +0000 (17:58 +0530)
committerunknown <ajbura@gmail.com>
Fri, 3 Sep 2021 12:28:01 +0000 (17:58 +0530)
18 files changed:
.github/FUNDING.yml
src/app/molecules/room-selector/RoomSelector.scss
src/app/organisms/navigation/Directs.jsx
src/app/organisms/navigation/Drawer.jsx
src/app/organisms/navigation/Drawer.scss
src/app/organisms/navigation/DrawerBreadcrumb.jsx [new file with mode: 0644]
src/app/organisms/navigation/DrawerBreadcrumb.scss [new file with mode: 0644]
src/app/organisms/navigation/DrawerHeader.jsx
src/app/organisms/navigation/Home.jsx
src/app/organisms/navigation/Selector.jsx
src/app/organisms/navigation/SideBar.jsx
src/app/organisms/navigation/SideBar.scss
src/app/templates/auth/Auth.jsx
src/client/action/navigation.js
src/client/state/RoomList.js
src/client/state/cons.js
src/client/state/navigation.js
src/index.scss

index 85499c1be90cded2a09c0ba347128975161d77dc..2189f7f413809a5f2f439437731c0bf7efc1987a 100644 (file)
@@ -1,2 +1,2 @@
-patreon: ajbura
+open_collective: cinny
 liberapay: ajbura
\ No newline at end of file
index 61e2cbc2fb7c03ceec92fea03eb4a776630af01d..9b40fcb132ec2793d734bd2d11b9d7cf1dc676a3 100644 (file)
       }
     }
   }
-  &:focus {
-    outline: none;
+  &:focus-within {
     background-color: var(--bg-surface-hover);
+    & button {
+      outline: none;
+    }
   }
   &:active {
     background-color: var(--bg-surface-active);
index 9c347e961a771d199ce18e8d7d259f95d61ce074..a907980bc561a01af0de956f43d1a38df27b825b 100644 (file)
@@ -17,13 +17,13 @@ function Directs() {
 
   const [, forceUpdate] = useState({});
 
-  function selectorChanged(activeRoomID, prevActiveRoomId) {
+  function selectorChanged(selectedRoomId, prevSelectedRoomId) {
     if (!drawerPostie.hasTopic('selector-change')) return;
     const addresses = [];
-    if (drawerPostie.hasSubscriber('selector-change', activeRoomID)) addresses.push(activeRoomID);
-    if (drawerPostie.hasSubscriber('selector-change', prevActiveRoomId)) addresses.push(prevActiveRoomId);
+    if (drawerPostie.hasSubscriber('selector-change', selectedRoomId)) addresses.push(selectedRoomId);
+    if (drawerPostie.hasSubscriber('selector-change', prevSelectedRoomId)) addresses.push(prevSelectedRoomId);
     if (addresses.length === 0) return;
-    drawerPostie.post('selector-change', addresses, activeRoomID);
+    drawerPostie.post('selector-change', addresses, selectedRoomId);
   }
 
   function unreadChanged(roomId) {
@@ -35,9 +35,9 @@ function Directs() {
   function roomListUpdated() {
     const { spaces, rooms, directs } = initMatrix.roomList;
     if (!(
-      spaces.has(navigation.getActiveRoomId())
-      || rooms.has(navigation.getActiveRoomId())
-      || directs.has(navigation.getActiveRoomId()))
+      spaces.has(navigation.selectedRoomId)
+      || rooms.has(navigation.selectedRoomId)
+      || directs.has(navigation.selectedRoomId))
     ) {
       selectRoom(null);
     }
@@ -62,6 +62,7 @@ function Directs() {
       key={id}
       roomId={id}
       drawerPostie={drawerPostie}
+      onClick={() => selectRoom(id)}
     />
   ));
 }
index d1dd011df81b4e5e06515734980a267fd7af6e97..f8c53eab77b4519e1f28fa886dd9e158e5e34cfd 100644 (file)
@@ -7,45 +7,40 @@ import navigation from '../../../client/state/navigation';
 import ScrollView from '../../atoms/scroll/ScrollView';
 
 import DrawerHeader from './DrawerHeader';
+import DrawerBreadcrumb from './DrawerBreadcrumb';
 import Home from './Home';
 import Directs from './Directs';
 
-function DrawerBradcrumb() {
-  return (
-    <div className="breadcrumb__wrapper">
-      <ScrollView horizontal vertical={false}>
-        <div>
-          {/* TODO: bradcrumb space paths when spaces become a thing */}
-        </div>
-      </ScrollView>
-    </div>
-  );
-}
-
 function Drawer() {
-  const [activeTab, setActiveTab] = useState('home');
+  const [selectedTab, setSelectedTab] = useState('home');
+  const [spaceId, setSpaceId] = useState(navigation.selectedSpaceId);
 
   function onTabChanged(tabId) {
-    setActiveTab(tabId);
+    setSelectedTab(tabId);
+  }
+  function onSpaceSelected(roomId) {
+    setSpaceId(roomId);
   }
 
   useEffect(() => {
     navigation.on(cons.events.navigation.TAB_CHANGED, onTabChanged);
+    navigation.on(cons.events.navigation.SPACE_SELECTED, onSpaceSelected);
     return () => {
       navigation.removeListener(cons.events.navigation.TAB_CHANGED, onTabChanged);
+      navigation.removeListener(cons.events.navigation.SPACE_SELECTED, onSpaceSelected);
     };
   }, []);
   return (
     <div className="drawer">
-      <DrawerHeader activeTab={activeTab} />
+      <DrawerHeader selectedTab={selectedTab} spaceId={spaceId} />
       <div className="drawer__content-wrapper">
-        <DrawerBradcrumb />
+        {selectedTab === 'home' && <DrawerBreadcrumb />}
         <div className="rooms__wrapper">
           <ScrollView autoHide>
             <div className="rooms-container">
               {
-                activeTab === 'home'
-                  ? <Home />
+                selectedTab === 'home'
+                  ? <Home spaceId={spaceId} />
                   : <Directs />
               }
             </div>
index e5d3f7104903ded5ea1193f9b106b57724f8086c..b240ac370e2476cd1bf7b50b709ed98d41f33087 100644 (file)
     @extend .drawer-flexBox;
   }
 }
-
-.breadcrumb__wrapper {
-  display: none;
-  height: var(--header-height);
-}
 .rooms__wrapper {
   @extend .drawer-flexItem;
+  position: relative;
 }
 
 .rooms-container {
   padding-bottom: var(--sp-extra-loose);
 
+  &::before {
+    position: absolute;
+    top: 0;
+    
+    content: '';
+    display: inline-block;
+    width: 100%;
+    height: 8px;
+    background-image: linear-gradient(
+      to bottom,
+      var(--bg-surface-low),
+      var(--bg-surface-low-transparent));
+  }
+
   & > .room-selector {
     width: calc(100% - var(--sp-extra-tight));
     margin-left: auto;
diff --git a/src/app/organisms/navigation/DrawerBreadcrumb.jsx b/src/app/organisms/navigation/DrawerBreadcrumb.jsx
new file mode 100644 (file)
index 0000000..4df5a6d
--- /dev/null
@@ -0,0 +1,68 @@
+import React, { useState, useEffect, useRef } from 'react';
+import './DrawerBreadcrumb.scss';
+
+import initMatrix from '../../../client/initMatrix';
+import cons from '../../../client/state/cons';
+import { selectSpace } from '../../../client/action/navigation';
+import navigation from '../../../client/state/navigation';
+
+import Text from '../../atoms/text/Text';
+import RawIcon from '../../atoms/system-icons/RawIcon';
+import Button from '../../atoms/button/Button';
+import ScrollView from '../../atoms/scroll/ScrollView';
+
+import ChevronRightIC from '../../../../public/res/ic/outlined/chevron-right.svg';
+
+function DrawerBreadcrumb() {
+  const [, forceUpdate] = useState({});
+  const scrollRef = useRef(null);
+  const mx = initMatrix.matrixClient;
+  const spacePath = navigation.selectedSpacePath;
+
+  function onSpaceSelected() {
+    forceUpdate({});
+    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);
+    };
+  }, []);
+
+  if (spacePath.length === 0) 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)}
+                >
+                  <Text variant="b2">{ mx.getRoom(spaceId).name }</Text>
+                </Button>
+              </React.Fragment>
+            ))
+          }
+          <div style={{ width: 'var(--sp-extra-tight)', height: '100%' }} />
+        </div>
+      </ScrollView>
+    </div>
+  );
+}
+
+export default DrawerBreadcrumb;
diff --git a/src/app/organisms/navigation/DrawerBreadcrumb.scss b/src/app/organisms/navigation/DrawerBreadcrumb.scss
new file mode 100644 (file)
index 0000000..80262a9
--- /dev/null
@@ -0,0 +1,60 @@
+.breadcrumb__wrapper {
+  height: var(--header-height);
+  position: relative;
+}
+
+.breadcrumb {
+  display: flex;
+  align-items: center;
+  height: 100%;
+  margin: 0 var(--sp-extra-tight);
+
+  &::before,
+  &::after {
+    flex-shrink: 0;
+    position: absolute;
+    right: 0;
+    z-index: 99;
+
+    content: '';
+    display: inline-block;
+    min-width: 8px;
+    width: 8px;
+    height: 100%;
+    background-image: linear-gradient(
+      to right,
+      var(--bg-surface-low-transparent),
+      var(--bg-surface-low)
+    );
+  }
+  &::before {
+    left: 0;
+    right: unset;
+    background-image: linear-gradient(
+      to left,
+      var(--bg-surface-low-transparent),
+      var(--bg-surface-low)
+    );
+  }
+
+  & > * {
+    flex-shrink: 0;
+  }
+
+  & > .btn-surface {
+    min-width: 0;
+    padding: var(--sp-extra-tight) 10px;
+    white-space: nowrap;
+    box-shadow: none;
+    & p {
+      max-width: 86px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+    }
+  }
+
+  &__btn--selected {
+    box-shadow: var(--bs-surface-border) !important;
+    background-color: var(--bg-surface);
+  }
+}
\ No newline at end of file
index 8915536719b14d79312883576c1c49d2c7e44edf..686d476341f51da948cbc8148abab99bbf084a2d 100644 (file)
@@ -1,9 +1,11 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 
+import initMatrix from '../../../client/initMatrix';
 import {
-  openPublicRooms, openCreateRoom, openInviteUser,
+  selectSpace, openPublicRooms, openCreateRoom, openInviteUser,
 } from '../../../client/action/navigation';
+import navigation from '../../../client/state/navigation';
 
 import Text from '../../atoms/text/Text';
 import Header, { TitleWrapper } from '../../atoms/header/Header';
@@ -13,16 +15,33 @@ 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';
+
+function DrawerHeader({ selectedTab, spaceId }) {
+  const mx = initMatrix.matrixClient;
+  const tabName = selectedTab === 'home' ? '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]);
+  }
 
-function DrawerHeader({ activeTab }) {
   return (
     <Header>
       <TitleWrapper>
-        <Text variant="s1">{(activeTab === 'home' ? 'Home' : 'Direct messages')}</Text>
+        <Text variant="s1">{spaceName || tabName}</Text>
       </TitleWrapper>
-      {(activeTab === 'dm')
-        ? <IconButton onClick={() => openInviteUser()} tooltip="Start DM" src={PlusIC} size="normal" />
-        : (
+      { 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 && (
+        <>
           <ContextMenu
             content={(hideMenu) => (
               <>
@@ -43,13 +62,19 @@ function DrawerHeader({ activeTab }) {
             )}
             render={(toggleMenu) => (<IconButton onClick={toggleMenu} tooltip="Add room" src={PlusIC} size="normal" />)}
           />
-        )}
+        </>
+      )}
       {/* <IconButton onClick={() => ''} tooltip="Menu" src={VerticalMenuIC} size="normal" /> */}
     </Header>
   );
 }
+
+DrawerHeader.defaultProps = {
+  spaceId: null,
+};
 DrawerHeader.propTypes = {
-  activeTab: PropTypes.string.isRequired,
+  selectedTab: PropTypes.string.isRequired,
+  spaceId: PropTypes.string,
 };
 
 export default DrawerHeader;
index a39ad5d9a84d491dc239bfe085ba0bb7c8eedbeb..120ceb7fe7934e19489b320f488ce4902b286a70 100644 (file)
@@ -1,9 +1,10 @@
 import React, { useState, useEffect } from 'react';
+import PropTypes from 'prop-types';
 
 import initMatrix from '../../../client/initMatrix';
 import cons from '../../../client/state/cons';
 import navigation from '../../../client/state/navigation';
-import { selectRoom } from '../../../client/action/navigation';
+import { selectSpace, selectRoom } from '../../../client/action/navigation';
 import Postie from '../../../util/Postie';
 
 import Text from '../../atoms/text/Text';
@@ -12,20 +13,32 @@ import Selector from './Selector';
 import { AtoZ } from './common';
 
 const drawerPostie = new Postie();
-function Home() {
+function Home({ spaceId }) {
+  const [, forceUpdate] = useState({});
   const { roomList } = initMatrix;
-  const spaceIds = [...roomList.spaces].sort(AtoZ);
-  const roomIds = [...roomList.rooms].sort(AtoZ);
+  let spaceIds = [];
+  let roomIds = [];
+  let directIds = [];
 
-  const [, forceUpdate] = useState({});
+  const spaceChildIds = roomList.getSpaceChildren(spaceId);
+  if (spaceChildIds) {
+    spaceIds = spaceChildIds.filter((roomId) => roomList.spaces.has(roomId)).sort(AtoZ);
+    roomIds = spaceChildIds.filter((roomId) => roomList.rooms.has(roomId)).sort(AtoZ);
+    directIds = spaceChildIds.filter((roomId) => roomList.directs.has(roomId)).sort(AtoZ);
+  } else {
+    spaceIds = [...roomList.spaces]
+      .filter((roomId) => !roomList.roomIdToParents.has(roomId)).sort(AtoZ);
+    roomIds = [...roomList.rooms]
+      .filter((roomId) => !roomList.roomIdToParents.has(roomId)).sort(AtoZ);
+  }
 
-  function selectorChanged(activeRoomID, prevActiveRoomId) {
+  function selectorChanged(selectedRoomId, prevSelectedRoomId) {
     if (!drawerPostie.hasTopic('selector-change')) return;
     const addresses = [];
-    if (drawerPostie.hasSubscriber('selector-change', activeRoomID)) addresses.push(activeRoomID);
-    if (drawerPostie.hasSubscriber('selector-change', prevActiveRoomId)) addresses.push(prevActiveRoomId);
+    if (drawerPostie.hasSubscriber('selector-change', selectedRoomId)) addresses.push(selectedRoomId);
+    if (drawerPostie.hasSubscriber('selector-change', prevSelectedRoomId)) addresses.push(prevSelectedRoomId);
     if (addresses.length === 0) return;
-    drawerPostie.post('selector-change', addresses, activeRoomID);
+    drawerPostie.post('selector-change', addresses, selectedRoomId);
   }
   function unreadChanged(roomId) {
     if (!drawerPostie.hasTopic('unread-change')) return;
@@ -36,9 +49,9 @@ function Home() {
   function roomListUpdated() {
     const { spaces, rooms, directs } = initMatrix.roomList;
     if (!(
-      spaces.has(navigation.getActiveRoomId())
-      || rooms.has(navigation.getActiveRoomId())
-      || directs.has(navigation.getActiveRoomId()))
+      spaces.has(navigation.selectedRoomId)
+      || rooms.has(navigation.selectedRoomId)
+      || directs.has(navigation.selectedRoomId))
     ) {
       selectRoom(null);
     }
@@ -67,6 +80,7 @@ function Home() {
           roomId={id}
           isDM={false}
           drawerPostie={drawerPostie}
+          onClick={() => selectSpace(id)}
         />
       ))}
 
@@ -77,10 +91,27 @@ function Home() {
           roomId={id}
           isDM={false}
           drawerPostie={drawerPostie}
+          onClick={() => selectRoom(id)}
         />
       )) }
+
+      { directIds.length !== 0 && <Text className="cat-header" variant="b3">People</Text> }
+      { directIds.map((id) => (
+        <Selector
+          key={id}
+          roomId={id}
+          drawerPostie={drawerPostie}
+          onClick={() => selectRoom(id)}
+        />
+      ))}
     </>
   );
 }
+Home.defaultProps = {
+  spaceId: null,
+};
+Home.propTypes = {
+  spaceId: PropTypes.string,
+};
 
 export default Home;
index 3c45d15453418455ca7c1246e34322f4eaf6baff..9430bb11676c0108c32f658850fe80dd4908c266 100644 (file)
@@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
 
 import initMatrix from '../../../client/initMatrix';
 import { doesRoomHaveUnread } from '../../../util/matrixUtil';
-import { selectRoom } from '../../../client/action/navigation';
 import navigation from '../../../client/state/navigation';
 
 import RoomSelector from '../../molecules/room-selector/RoomSelector';
@@ -14,16 +13,18 @@ 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';
 
-function Selector({ roomId, isDM, drawerPostie }) {
+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;
 
-  const [isSelected, setIsSelected] = useState(navigation.getActiveRoomId() === roomId);
+  const [isSelected, setIsSelected] = useState(navigation.selectedRoomId === roomId);
   const [, forceUpdate] = useState({});
 
-  function selectorChanged(activeRoomId) {
-    setIsSelected(activeRoomId === roomId);
+  function selectorChanged(selectedRoomId) {
+    setIsSelected(selectedRoomId === roomId);
   }
   function changeNotificationBadge() {
     forceUpdate({});
@@ -58,7 +59,7 @@ function Selector({ roomId, isDM, drawerPostie }) {
       isUnread={doesRoomHaveUnread(room)}
       notificationCount={room.getUnreadNotificationCount('total') || 0}
       isAlert={room.getUnreadNotificationCount('highlight') !== 0}
-      onClick={() => selectRoom(roomId)}
+      onClick={onClick}
     />
   );
 }
@@ -71,6 +72,7 @@ Selector.propTypes = {
   roomId: PropTypes.string.isRequired,
   isDM: PropTypes.bool,
   drawerPostie: PropTypes.shape({}).isRequired,
+  onClick: PropTypes.func.isRequired,
 };
 
 export default Selector;
index 7d3511baed916b051ce523afdaf47a08200db5db..1468bc66afb499910078e9b156ae3ac150f0b92f 100644 (file)
@@ -60,10 +60,10 @@ function SideBar() {
     + initMatrix.roomList.inviteDirects.size;
 
   const [totalInvites, updateTotalInvites] = useState(totalInviteCount());
-  const [activeTab, setActiveTab] = useState('home');
+  const [selectedTab, setSelectedTab] = useState('home');
 
   function onTabChanged(tabId) {
-    setActiveTab(tabId);
+    setSelectedTab(tabId);
   }
   function onInviteListChange() {
     updateTotalInvites(totalInviteCount());
@@ -91,8 +91,8 @@ function SideBar() {
         <ScrollView invisible>
           <div className="scrollable-content">
             <div className="featured-container">
-              <SidebarAvatar active={activeTab === 'home'} onClick={() => changeTab('home')} tooltip="Home" iconSrc={HomeIC} />
-              <SidebarAvatar active={activeTab === 'dm'} onClick={() => changeTab('dm')} tooltip="People" iconSrc={UserIC} />
+              <SidebarAvatar active={selectedTab === 'home'} onClick={() => changeTab('home')} tooltip="Home" iconSrc={HomeIC} />
+              <SidebarAvatar active={selectedTab === 'dm'} onClick={() => changeTab('dm')} tooltip="People" iconSrc={UserIC} />
               <SidebarAvatar onClick={() => openPublicRooms()} tooltip="Public rooms" iconSrc={HashSearchIC} />
             </div>
             <div className="sidebar-divider" />
index 0f4e6773d760641e381e4b212422c135199fd7a8..09641fcc5112ffbc8c897a0f42b06ad5229aaa7c 100644 (file)
     height: 8px;
 
     background: transparent;
-    // background-image: linear-gradient(to top,  var(--bg-surface-low), transparent);
-    // It produce bug in safari
-    // To fix it, we have to set the color as a fully transparent version of that exact color. like:
-    // background-image: linear-gradient(to top,  rgb(255, 255, 255), rgba(255, 255, 255, 0));
-    // TODO: fix this bug while implementing spaces
+    background-image: linear-gradient(
+      to top,
+      var(--bg-surface-low),
+      var(--bg-surface-low-transparent));
     position: sticky;
     bottom: 0;
     left: 0;
index a8837ab4911ab3ab2af7a8eaace4aff454818f75..74c4fd1dfc2f74e67d04d69bae1ecf487c00602d 100644 (file)
@@ -13,7 +13,7 @@ import Spinner from '../../atoms/spinner/Spinner';
 
 import CinnySvg from '../../../../public/res/svg/cinny.svg';
 
-// This regex validates historical usernames, which don't satisy today's username requirements.
+// This regex validates historical usernames, which don't satisfy today's username requirements.
 // See https://matrix.org/docs/spec/appendices#id13 for more info.
 const LOCALPART_LOGIN_REGEX = /.*/;
 const LOCALPART_SIGNUP_REGEX = /^[a-z0-9_\-.=/]+$/;
index cf40b4ae2e979e92e043bc5b349a228f19fd9c0a..bf560d877ed8d80395e09115c0829b167832f251 100644 (file)
@@ -8,6 +8,13 @@ function changeTab(tabId) {
   });
 }
 
+function selectSpace(roomId) {
+  appDispatcher.dispatch({
+    type: cons.actions.navigation.SELECT_SPACE,
+    roomId,
+  });
+}
+
 function selectRoom(roomId) {
   appDispatcher.dispatch({
     type: cons.actions.navigation.SELECT_ROOM,
@@ -72,6 +79,7 @@ function openReadReceipts(roomId, eventId) {
 
 export {
   changeTab,
+  selectSpace,
   selectRoom,
   togglePeopleDrawer,
   openInviteList,
index 428d1040e339dead0ef9d2e0c2614662698a60fc..4e88adc723a6c9376b279e83ba24f1ef10f4a5be 100644 (file)
@@ -7,6 +7,7 @@ class RoomList extends EventEmitter {
     super();
     this.matrixClient = matrixClient;
     this.mDirects = this.getMDirects();
+    this.roomIdToParents = new Map();
 
     this.inviteDirects = new Set();
     this.inviteSpaces = new Set();
@@ -24,13 +25,54 @@ class RoomList extends EventEmitter {
     appDispatcher.register(this.roomActions.bind(this));
   }
 
+  getSpaceChildren(roomId) {
+    const space = this.matrixClient.getRoom(roomId);
+    const mSpaceChild = space?.currentState.getStateEvents('m.space.child');
+    const children = mSpaceChild?.map((mEvent) => {
+      if (Object.keys(mEvent.event.content).length === 0) return null;
+      return mEvent.event.state_key;
+    });
+    return children?.filter((child) => child !== null);
+  }
+
+  addToRoomIdToParents(roomId, parentRoomId) {
+    if (!this.roomIdToParents.has(roomId)) {
+      this.roomIdToParents.set(roomId, new Set());
+    }
+    const parents = this.roomIdToParents.get(roomId);
+    parents.add(parentRoomId);
+  }
+
+  removeFromRoomIdToParents(roomId, parentRoomId) {
+    if (!this.roomIdToParents.has(roomId)) return;
+    const parents = this.roomIdToParents.get(roomId);
+    parents.delete(parentRoomId);
+    if (parents.size === 0) this.roomIdToParents.delete(roomId);
+  }
+
+  addToSpaces(roomId) {
+    this.spaces.add(roomId);
+    const spaceChildren = this.getSpaceChildren(roomId);
+    spaceChildren?.forEach((childRoomId) => {
+      this.addToRoomIdToParents(childRoomId, roomId);
+    });
+  }
+
+  deleteFromSpaces(roomId) {
+    this.spaces.delete(roomId);
+    const spaceChildren = this.getSpaceChildren(roomId);
+    spaceChildren?.forEach((childRoomId) => {
+      this.removeFromRoomIdToParents(childRoomId, roomId);
+    });
+  }
+
   roomActions(action) {
     const addRoom = (roomId, isDM) => {
       const myRoom = this.matrixClient.getRoom(roomId);
       if (myRoom === null) return false;
 
       if (isDM) this.directs.add(roomId);
-      else if (myRoom.isSpaceRoom()) this.spaces.add(roomId);
+      else if (myRoom.isSpaceRoom()) this.addToSpaces(roomId);
       else this.rooms.add(roomId);
       return true;
     };
@@ -85,6 +127,7 @@ class RoomList extends EventEmitter {
 
   _populateRooms() {
     this.directs.clear();
+    this.roomIdToParents.clear();
     this.spaces.clear();
     this.rooms.clear();
     this.inviteDirects.clear();
@@ -109,7 +152,7 @@ class RoomList extends EventEmitter {
       if (room.getMyMembership() !== 'join') return;
 
       if (this.mDirects.has(roomId)) this.directs.add(roomId);
-      else if (room.isSpaceRoom()) this.spaces.add(roomId);
+      else if (room.isSpaceRoom()) this.addToSpaces(roomId);
       else this.rooms.add(roomId);
     });
   }
@@ -165,8 +208,16 @@ class RoomList extends EventEmitter {
       }
     });
 
-    this.matrixClient.on('RoomState.events', (event) => {
-      if (event.getType() !== 'm.room.join_rules') return;
+    this.matrixClient.on('RoomState.events', (mEvent) => {
+      if (mEvent.getType() === 'm.space.child') {
+        const { event } = mEvent;
+        const isRoomAdded = Object.keys(event.content).length > 0;
+        if (isRoomAdded) this.addToRoomIdToParents(event.state_key, event.room_id);
+        else this.removeFromRoomIdToParents(event.state_key, event.room_id);
+        this.emit(cons.events.roomList.ROOMLIST_UPDATED);
+        return;
+      }
+      if (mEvent.getType() !== 'm.room.join_rules') return;
 
       this.emit(cons.events.roomList.ROOMLIST_UPDATED);
     });
@@ -207,7 +258,7 @@ class RoomList extends EventEmitter {
           const procRoomInfo = this.processingRooms.get(roomId);
 
           if (procRoomInfo.isDM) this.directs.add(roomId);
-          else if (room.isSpaceRoom()) this.spaces.add(roomId);
+          else if (room.isSpaceRoom()) this.addToSpaces(roomId);
           else this.rooms.add(roomId);
 
           if (procRoomInfo.task === 'CREATE') this.emit(cons.events.roomList.ROOM_CREATED, roomId);
@@ -218,7 +269,7 @@ class RoomList extends EventEmitter {
           return;
         }
         if (room.isSpaceRoom()) {
-          this.spaces.add(roomId);
+          this.addToSpaces(roomId);
 
           this.emit(cons.events.roomList.ROOM_JOINED, roomId);
           this.emit(cons.events.roomList.ROOMLIST_UPDATED);
@@ -269,12 +320,12 @@ class RoomList extends EventEmitter {
       }
       // when room is not a DM add/remove it from rooms.
       if (membership === 'leave' || membership === 'kick' || membership === 'ban') {
-        if (room.isSpaceRoom()) this.spaces.delete(roomId);
+        if (room.isSpaceRoom()) this.deleteFromSpaces(roomId);
         else this.rooms.delete(roomId);
         this.emit(cons.events.roomList.ROOM_LEAVED, roomId);
       }
       if (membership === 'join') {
-        if (room.isSpaceRoom()) this.spaces.add(roomId);
+        if (room.isSpaceRoom()) this.addToSpaces(roomId);
         else this.rooms.add(roomId);
         this.emit(cons.events.roomList.ROOM_JOINED, roomId);
       }
index f5e92b0f527b85f022ff4a55b985b63170585017..e8b8d1586731480ecb152cf108d43a0d807935bc 100644 (file)
@@ -9,6 +9,7 @@ const cons = {
   actions: {
     navigation: {
       CHANGE_TAB: 'CHANGE_TAB',
+      SELECT_SPACE: 'SELECT_SPACE',
       SELECT_ROOM: 'SELECT_ROOM',
       TOGGLE_PEOPLE_DRAWER: 'TOGGLE_PEOPLE_DRAWER',
       OPEN_INVITE_LIST: 'OPEN_INVITE_LIST',
@@ -34,6 +35,7 @@ const cons = {
   events: {
     navigation: {
       TAB_CHANGED: 'TAB_CHANGED',
+      SPACE_SELECTED: 'SPACE_SELECTED',
       ROOM_SELECTED: 'ROOM_SELECTED',
       PEOPLE_DRAWER_TOGGLED: 'PEOPLE_DRAWER_TOGGLED',
       INVITE_LIST_OPENED: 'INVITE_LIST_OPENED',
index 5c108af908bd8020027794b48abdc78748116890..084af25a4c7f89f936571d50c7b9c76cb34f5831 100644 (file)
@@ -6,29 +6,44 @@ class Navigation extends EventEmitter {
   constructor() {
     super();
 
-    this.activeTab = 'home';
-    this.activeRoomId = null;
+    this.selectedTab = 'home';
+    this.selectedSpaceId = null;
+    this.selectedSpacePath = [];
+    this.selectedRoomId = null;
     this.isPeopleDrawerVisible = true;
-  }
 
-  getActiveTab() {
-    return this.activeTab;
+    // TODO:
+    window.navigation = this;
   }
 
-  getActiveRoomId() {
-    return this.activeRoomId;
+  _setSpacePath(roomId) {
+    if (roomId === null) {
+      this.selectedSpacePath = [];
+      return;
+    }
+    if (this.selectedSpacePath.includes(roomId)) {
+      const spIndex = this.selectedSpacePath.indexOf(roomId);
+      this.selectedSpacePath = this.selectedSpacePath.slice(0, spIndex + 1);
+      return;
+    }
+    this.selectedSpacePath.push(roomId);
   }
 
   navigate(action) {
     const actions = {
       [cons.actions.navigation.CHANGE_TAB]: () => {
-        this.activeTab = action.tabId;
-        this.emit(cons.events.navigation.TAB_CHANGED, this.activeTab);
+        this.selectedTab = action.tabId;
+        this.emit(cons.events.navigation.TAB_CHANGED, this.selectedTab);
+      },
+      [cons.actions.navigation.SELECT_SPACE]: () => {
+        this._setSpacePath(action.roomId);
+        this.selectedSpaceId = action.roomId;
+        this.emit(cons.events.navigation.SPACE_SELECTED, action.roomId);
       },
       [cons.actions.navigation.SELECT_ROOM]: () => {
-        const prevActiveRoomId = this.activeRoomId;
-        this.activeRoomId = action.roomId;
-        this.emit(cons.events.navigation.ROOM_SELECTED, this.activeRoomId, prevActiveRoomId);
+        const prevSelectedRoomId = this.selectedRoomId;
+        this.selectedRoomId = action.roomId;
+        this.emit(cons.events.navigation.ROOM_SELECTED, this.selectedRoomId, prevSelectedRoomId);
       },
       [cons.actions.navigation.TOGGLE_PEOPLE_DRAWER]: () => {
         this.isPeopleDrawerVisible = !this.isPeopleDrawerVisible;
index a3819a9563bd19137a6534ead363feae31144341..678bb65a38d961d03b2bdc0c109b77f96b676309 100644 (file)
@@ -4,7 +4,9 @@
 
   /* background color | --bg-[background type]: value */
   --bg-surface: #FFFFFF;
+  --bg-surface-transparent: #FFFFFF00;
   --bg-surface-low: #F6F6F6;
+  --bg-surface-low-transparent: #F6F6F600;
   --bg-surface-hover: rgba(0, 0, 0, 3%);
   --bg-surface-active: rgba(0, 0, 0, 5%);
   --bg-surface-border: rgba(0, 0, 0, 6%);
 .silver-theme {
   /* background color | --bg-[background type]: value */
   --bg-surface: hsl(0, 0%, 95%);
+  --bg-surface-transparent: hsla(0, 0%, 95%, 0);
   --bg-surface-low: hsl(0, 0%, 91%);
+  --bg-surface-low-transparent: hsla(0, 0%, 91%, 0);
 }
 
 .dark-theme,
 .butter-theme {
   /* background color | --bg-[background type]: value */
   --bg-surface: hsl(208, 8%, 20%);
+  --bg-surface-transparent: hsla(208, 8%, 20%, 0);
   --bg-surface-low: hsl(208, 8%, 16%);
+  --bg-surface-low-transparent: hsla(208, 8%, 16%, 0);
   --bg-surface-hover: rgba(255, 255, 255, 3%);
   --bg-surface-active: rgba(255, 255, 255, 5%);
   --bg-surface-border: rgba(0, 0, 0, 20%);
 .butter-theme {
   /* background color | --bg-[background type]: value */
   --bg-surface: hsl(64, 6%, 14%);
+  --bg-surface-transparent: hsla(64, 6%, 14%, 0);
   --bg-surface-low: hsl(64, 6%, 10%);
+  --bg-surface-low-transparent: hsla(64, 6%, 14%, 0);
 
   
   /* text color | --tc-[background type]-[priority]: value */