From 4efc320f2399d55c30ed645b235bb9477de2711f Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 3 Sep 2021 17:58:01 +0530 Subject: [PATCH] Added space nesting (#52) --- .github/FUNDING.yml | 2 +- .../molecules/room-selector/RoomSelector.scss | 6 +- src/app/organisms/navigation/Directs.jsx | 15 ++-- src/app/organisms/navigation/Drawer.jsx | 31 ++++----- src/app/organisms/navigation/Drawer.scss | 20 ++++-- .../organisms/navigation/DrawerBreadcrumb.jsx | 68 +++++++++++++++++++ .../navigation/DrawerBreadcrumb.scss | 60 ++++++++++++++++ src/app/organisms/navigation/DrawerHeader.jsx | 41 ++++++++--- src/app/organisms/navigation/Home.jsx | 57 ++++++++++++---- src/app/organisms/navigation/Selector.jsx | 14 ++-- src/app/organisms/navigation/SideBar.jsx | 8 +-- src/app/organisms/navigation/SideBar.scss | 9 ++- src/app/templates/auth/Auth.jsx | 2 +- src/client/action/navigation.js | 8 +++ src/client/state/RoomList.js | 67 +++++++++++++++--- src/client/state/cons.js | 2 + src/client/state/navigation.js | 41 +++++++---- src/index.scss | 8 +++ 18 files changed, 368 insertions(+), 91 deletions(-) create mode 100644 src/app/organisms/navigation/DrawerBreadcrumb.jsx create mode 100644 src/app/organisms/navigation/DrawerBreadcrumb.scss diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 85499c1b..2189f7f4 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,2 @@ -patreon: ajbura +open_collective: cinny liberapay: ajbura \ No newline at end of file diff --git a/src/app/molecules/room-selector/RoomSelector.scss b/src/app/molecules/room-selector/RoomSelector.scss index 61e2cbc2..9b40fcb1 100644 --- a/src/app/molecules/room-selector/RoomSelector.scss +++ b/src/app/molecules/room-selector/RoomSelector.scss @@ -32,9 +32,11 @@ } } } - &:focus { - outline: none; + &:focus-within { background-color: var(--bg-surface-hover); + & button { + outline: none; + } } &:active { background-color: var(--bg-surface-active); diff --git a/src/app/organisms/navigation/Directs.jsx b/src/app/organisms/navigation/Directs.jsx index 9c347e96..a907980b 100644 --- a/src/app/organisms/navigation/Directs.jsx +++ b/src/app/organisms/navigation/Directs.jsx @@ -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)} /> )); } diff --git a/src/app/organisms/navigation/Drawer.jsx b/src/app/organisms/navigation/Drawer.jsx index d1dd011d..f8c53eab 100644 --- a/src/app/organisms/navigation/Drawer.jsx +++ b/src/app/organisms/navigation/Drawer.jsx @@ -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 ( -
- -
- {/* TODO: bradcrumb space paths when spaces become a thing */} -
-
-
- ); -} - 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 (
- +
- + {selectedTab === 'home' && }
{ - activeTab === 'home' - ? + selectedTab === 'home' + ? : }
diff --git a/src/app/organisms/navigation/Drawer.scss b/src/app/organisms/navigation/Drawer.scss index e5d3f710..b240ac37 100644 --- a/src/app/organisms/navigation/Drawer.scss +++ b/src/app/organisms/navigation/Drawer.scss @@ -23,18 +23,28 @@ @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 index 00000000..4df5a6dc --- /dev/null +++ b/src/app/organisms/navigation/DrawerBreadcrumb.jsx @@ -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 ( +
+ +
+ + { + spacePath.map((spaceId, index) => ( + + + + + )) + } +
+
+ +
+ ); +} + +export default DrawerBreadcrumb; diff --git a/src/app/organisms/navigation/DrawerBreadcrumb.scss b/src/app/organisms/navigation/DrawerBreadcrumb.scss new file mode 100644 index 00000000..80262a9d --- /dev/null +++ b/src/app/organisms/navigation/DrawerBreadcrumb.scss @@ -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 diff --git a/src/app/organisms/navigation/DrawerHeader.jsx b/src/app/organisms/navigation/DrawerHeader.jsx index 89155367..686d4763 100644 --- a/src/app/organisms/navigation/DrawerHeader.jsx +++ b/src/app/organisms/navigation/DrawerHeader.jsx @@ -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 (
- {(activeTab === 'home' ? 'Home' : 'Direct messages')} + {spaceName || tabName} - {(activeTab === 'dm') - ? openInviteUser()} tooltip="Start DM" src={PlusIC} size="normal" /> - : ( + { spaceName && } + { selectedTab === 'dm' && openInviteUser()} tooltip="Start DM" src={PlusIC} size="normal" /> } + { selectSpace !== 'dm' && !spaceName && ( + <> ( <> @@ -43,13 +62,19 @@ function DrawerHeader({ activeTab }) { )} render={(toggleMenu) => ()} /> - )} + + )} {/* ''} tooltip="Menu" src={VerticalMenuIC} size="normal" /> */}
); } + +DrawerHeader.defaultProps = { + spaceId: null, +}; DrawerHeader.propTypes = { - activeTab: PropTypes.string.isRequired, + selectedTab: PropTypes.string.isRequired, + spaceId: PropTypes.string, }; export default DrawerHeader; diff --git a/src/app/organisms/navigation/Home.jsx b/src/app/organisms/navigation/Home.jsx index a39ad5d9..120ceb7f 100644 --- a/src/app/organisms/navigation/Home.jsx +++ b/src/app/organisms/navigation/Home.jsx @@ -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() { - const { roomList } = initMatrix; - const spaceIds = [...roomList.spaces].sort(AtoZ); - const roomIds = [...roomList.rooms].sort(AtoZ); - +function Home({ spaceId }) { const [, forceUpdate] = useState({}); + const { roomList } = initMatrix; + let spaceIds = []; + let roomIds = []; + let directIds = []; - function selectorChanged(activeRoomID, prevActiveRoomId) { + 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(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 && People } + { directIds.map((id) => ( + selectRoom(id)} + /> + ))} ); } +Home.defaultProps = { + spaceId: null, +}; +Home.propTypes = { + spaceId: PropTypes.string, +}; export default Home; diff --git a/src/app/organisms/navigation/Selector.jsx b/src/app/organisms/navigation/Selector.jsx index 3c45d154..9430bb11 100644 --- a/src/app/organisms/navigation/Selector.jsx +++ b/src/app/organisms/navigation/Selector.jsx @@ -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; diff --git a/src/app/organisms/navigation/SideBar.jsx b/src/app/organisms/navigation/SideBar.jsx index 7d3511ba..1468bc66 100644 --- a/src/app/organisms/navigation/SideBar.jsx +++ b/src/app/organisms/navigation/SideBar.jsx @@ -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() {
- changeTab('home')} tooltip="Home" iconSrc={HomeIC} /> - changeTab('dm')} tooltip="People" iconSrc={UserIC} /> + changeTab('home')} tooltip="Home" iconSrc={HomeIC} /> + changeTab('dm')} tooltip="People" iconSrc={UserIC} /> openPublicRooms()} tooltip="Public rooms" iconSrc={HashSearchIC} />
diff --git a/src/app/organisms/navigation/SideBar.scss b/src/app/organisms/navigation/SideBar.scss index 0f4e6773..09641fcc 100644 --- a/src/app/organisms/navigation/SideBar.scss +++ b/src/app/organisms/navigation/SideBar.scss @@ -39,11 +39,10 @@ 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; diff --git a/src/app/templates/auth/Auth.jsx b/src/app/templates/auth/Auth.jsx index a8837ab4..74c4fd1d 100644 --- a/src/app/templates/auth/Auth.jsx +++ b/src/app/templates/auth/Auth.jsx @@ -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_\-.=/]+$/; diff --git a/src/client/action/navigation.js b/src/client/action/navigation.js index cf40b4ae..bf560d87 100644 --- a/src/client/action/navigation.js +++ b/src/client/action/navigation.js @@ -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, diff --git a/src/client/state/RoomList.js b/src/client/state/RoomList.js index 428d1040..4e88adc7 100644 --- a/src/client/state/RoomList.js +++ b/src/client/state/RoomList.js @@ -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); } diff --git a/src/client/state/cons.js b/src/client/state/cons.js index f5e92b0f..e8b8d158 100644 --- a/src/client/state/cons.js +++ b/src/client/state/cons.js @@ -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', diff --git a/src/client/state/navigation.js b/src/client/state/navigation.js index 5c108af9..084af25a 100644 --- a/src/client/state/navigation.js +++ b/src/client/state/navigation.js @@ -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; + + // TODO: + window.navigation = this; } - getActiveTab() { - return this.activeTab; - } - - 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; diff --git a/src/index.scss b/src/index.scss index a3819a95..678bb65a 100644 --- a/src/index.scss +++ b/src/index.scss @@ -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%); @@ -155,14 +157,18 @@ .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%); @@ -206,7 +212,9 @@ .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 */