Added space nesting (#52)

This commit is contained in:
unknown 2021-09-03 17:58:01 +05:30
parent 6c1a602bdc
commit 4efc320f23
18 changed files with 368 additions and 91 deletions

2
.github/FUNDING.yml vendored
View file

@ -1,2 +1,2 @@
patreon: ajbura open_collective: cinny
liberapay: ajbura liberapay: ajbura

View file

@ -32,9 +32,11 @@
} }
} }
} }
&:focus { &:focus-within {
outline: none;
background-color: var(--bg-surface-hover); background-color: var(--bg-surface-hover);
& button {
outline: none;
}
} }
&:active { &:active {
background-color: var(--bg-surface-active); background-color: var(--bg-surface-active);

View file

@ -17,13 +17,13 @@ function Directs() {
const [, forceUpdate] = useState({}); const [, forceUpdate] = useState({});
function selectorChanged(activeRoomID, prevActiveRoomId) { function selectorChanged(selectedRoomId, prevSelectedRoomId) {
if (!drawerPostie.hasTopic('selector-change')) return; if (!drawerPostie.hasTopic('selector-change')) return;
const addresses = []; const addresses = [];
if (drawerPostie.hasSubscriber('selector-change', activeRoomID)) addresses.push(activeRoomID); if (drawerPostie.hasSubscriber('selector-change', selectedRoomId)) addresses.push(selectedRoomId);
if (drawerPostie.hasSubscriber('selector-change', prevActiveRoomId)) addresses.push(prevActiveRoomId); if (drawerPostie.hasSubscriber('selector-change', prevSelectedRoomId)) addresses.push(prevSelectedRoomId);
if (addresses.length === 0) return; if (addresses.length === 0) return;
drawerPostie.post('selector-change', addresses, activeRoomID); drawerPostie.post('selector-change', addresses, selectedRoomId);
} }
function unreadChanged(roomId) { function unreadChanged(roomId) {
@ -35,9 +35,9 @@ function Directs() {
function roomListUpdated() { function roomListUpdated() {
const { spaces, rooms, directs } = initMatrix.roomList; const { spaces, rooms, directs } = initMatrix.roomList;
if (!( if (!(
spaces.has(navigation.getActiveRoomId()) spaces.has(navigation.selectedRoomId)
|| rooms.has(navigation.getActiveRoomId()) || rooms.has(navigation.selectedRoomId)
|| directs.has(navigation.getActiveRoomId())) || directs.has(navigation.selectedRoomId))
) { ) {
selectRoom(null); selectRoom(null);
} }
@ -62,6 +62,7 @@ function Directs() {
key={id} key={id}
roomId={id} roomId={id}
drawerPostie={drawerPostie} drawerPostie={drawerPostie}
onClick={() => selectRoom(id)}
/> />
)); ));
} }

View file

@ -7,45 +7,40 @@ import navigation from '../../../client/state/navigation';
import ScrollView from '../../atoms/scroll/ScrollView'; import ScrollView from '../../atoms/scroll/ScrollView';
import DrawerHeader from './DrawerHeader'; import DrawerHeader from './DrawerHeader';
import DrawerBreadcrumb from './DrawerBreadcrumb';
import Home from './Home'; import Home from './Home';
import Directs from './Directs'; 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() { function Drawer() {
const [activeTab, setActiveTab] = useState('home'); const [selectedTab, setSelectedTab] = useState('home');
const [spaceId, setSpaceId] = useState(navigation.selectedSpaceId);
function onTabChanged(tabId) { function onTabChanged(tabId) {
setActiveTab(tabId); setSelectedTab(tabId);
}
function onSpaceSelected(roomId) {
setSpaceId(roomId);
} }
useEffect(() => { useEffect(() => {
navigation.on(cons.events.navigation.TAB_CHANGED, onTabChanged); navigation.on(cons.events.navigation.TAB_CHANGED, onTabChanged);
navigation.on(cons.events.navigation.SPACE_SELECTED, onSpaceSelected);
return () => { return () => {
navigation.removeListener(cons.events.navigation.TAB_CHANGED, onTabChanged); navigation.removeListener(cons.events.navigation.TAB_CHANGED, onTabChanged);
navigation.removeListener(cons.events.navigation.SPACE_SELECTED, onSpaceSelected);
}; };
}, []); }, []);
return ( return (
<div className="drawer"> <div className="drawer">
<DrawerHeader activeTab={activeTab} /> <DrawerHeader selectedTab={selectedTab} spaceId={spaceId} />
<div className="drawer__content-wrapper"> <div className="drawer__content-wrapper">
<DrawerBradcrumb /> {selectedTab === 'home' && <DrawerBreadcrumb />}
<div className="rooms__wrapper"> <div className="rooms__wrapper">
<ScrollView autoHide> <ScrollView autoHide>
<div className="rooms-container"> <div className="rooms-container">
{ {
activeTab === 'home' selectedTab === 'home'
? <Home /> ? <Home spaceId={spaceId} />
: <Directs /> : <Directs />
} }
</div> </div>

View file

@ -23,18 +23,28 @@
@extend .drawer-flexBox; @extend .drawer-flexBox;
} }
} }
.breadcrumb__wrapper {
display: none;
height: var(--header-height);
}
.rooms__wrapper { .rooms__wrapper {
@extend .drawer-flexItem; @extend .drawer-flexItem;
position: relative;
} }
.rooms-container { .rooms-container {
padding-bottom: var(--sp-extra-loose); 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 { & > .room-selector {
width: calc(100% - var(--sp-extra-tight)); width: calc(100% - var(--sp-extra-tight));
margin-left: auto; margin-left: auto;

View file

@ -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;

View file

@ -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);
}
}

View file

@ -1,9 +1,11 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import initMatrix from '../../../client/initMatrix';
import { import {
openPublicRooms, openCreateRoom, openInviteUser, selectSpace, openPublicRooms, openCreateRoom, openInviteUser,
} from '../../../client/action/navigation'; } from '../../../client/action/navigation';
import navigation from '../../../client/state/navigation';
import Text from '../../atoms/text/Text'; import Text from '../../atoms/text/Text';
import Header, { TitleWrapper } from '../../atoms/header/Header'; 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 PlusIC from '../../../../public/res/ic/outlined/plus.svg';
import HashPlusIC from '../../../../public/res/ic/outlined/hash-plus.svg'; import HashPlusIC from '../../../../public/res/ic/outlined/hash-plus.svg';
import HashSearchIC from '../../../../public/res/ic/outlined/hash-search.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 ( return (
<Header> <Header>
<TitleWrapper> <TitleWrapper>
<Text variant="s1">{(activeTab === 'home' ? 'Home' : 'Direct messages')}</Text> <Text variant="s1">{spaceName || tabName}</Text>
</TitleWrapper> </TitleWrapper>
{(activeTab === 'dm') { spaceName && <IconButton onClick={handleBackClick} tooltip="Back" src={ChevronLeftIC} size="normal" /> }
? <IconButton onClick={() => openInviteUser()} tooltip="Start DM" src={PlusIC} size="normal" /> { selectedTab === 'dm' && <IconButton onClick={() => openInviteUser()} tooltip="Start DM" src={PlusIC} size="normal" /> }
: ( { selectSpace !== 'dm' && !spaceName && (
<>
<ContextMenu <ContextMenu
content={(hideMenu) => ( content={(hideMenu) => (
<> <>
@ -43,13 +62,19 @@ function DrawerHeader({ activeTab }) {
)} )}
render={(toggleMenu) => (<IconButton onClick={toggleMenu} tooltip="Add room" src={PlusIC} size="normal" />)} render={(toggleMenu) => (<IconButton onClick={toggleMenu} tooltip="Add room" src={PlusIC} size="normal" />)}
/> />
</>
)} )}
{/* <IconButton onClick={() => ''} tooltip="Menu" src={VerticalMenuIC} size="normal" /> */} {/* <IconButton onClick={() => ''} tooltip="Menu" src={VerticalMenuIC} size="normal" /> */}
</Header> </Header>
); );
} }
DrawerHeader.defaultProps = {
spaceId: null,
};
DrawerHeader.propTypes = { DrawerHeader.propTypes = {
activeTab: PropTypes.string.isRequired, selectedTab: PropTypes.string.isRequired,
spaceId: PropTypes.string,
}; };
export default DrawerHeader; export default DrawerHeader;

View file

@ -1,9 +1,10 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import initMatrix from '../../../client/initMatrix'; import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons'; import cons from '../../../client/state/cons';
import navigation from '../../../client/state/navigation'; 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 Postie from '../../../util/Postie';
import Text from '../../atoms/text/Text'; import Text from '../../atoms/text/Text';
@ -12,20 +13,32 @@ import Selector from './Selector';
import { AtoZ } from './common'; import { AtoZ } from './common';
const drawerPostie = new Postie(); const drawerPostie = new Postie();
function Home() { function Home({ spaceId }) {
const { roomList } = initMatrix;
const spaceIds = [...roomList.spaces].sort(AtoZ);
const roomIds = [...roomList.rooms].sort(AtoZ);
const [, forceUpdate] = useState({}); 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; if (!drawerPostie.hasTopic('selector-change')) return;
const addresses = []; const addresses = [];
if (drawerPostie.hasSubscriber('selector-change', activeRoomID)) addresses.push(activeRoomID); if (drawerPostie.hasSubscriber('selector-change', selectedRoomId)) addresses.push(selectedRoomId);
if (drawerPostie.hasSubscriber('selector-change', prevActiveRoomId)) addresses.push(prevActiveRoomId); if (drawerPostie.hasSubscriber('selector-change', prevSelectedRoomId)) addresses.push(prevSelectedRoomId);
if (addresses.length === 0) return; if (addresses.length === 0) return;
drawerPostie.post('selector-change', addresses, activeRoomID); drawerPostie.post('selector-change', addresses, selectedRoomId);
} }
function unreadChanged(roomId) { function unreadChanged(roomId) {
if (!drawerPostie.hasTopic('unread-change')) return; if (!drawerPostie.hasTopic('unread-change')) return;
@ -36,9 +49,9 @@ function Home() {
function roomListUpdated() { function roomListUpdated() {
const { spaces, rooms, directs } = initMatrix.roomList; const { spaces, rooms, directs } = initMatrix.roomList;
if (!( if (!(
spaces.has(navigation.getActiveRoomId()) spaces.has(navigation.selectedRoomId)
|| rooms.has(navigation.getActiveRoomId()) || rooms.has(navigation.selectedRoomId)
|| directs.has(navigation.getActiveRoomId())) || directs.has(navigation.selectedRoomId))
) { ) {
selectRoom(null); selectRoom(null);
} }
@ -67,6 +80,7 @@ function Home() {
roomId={id} roomId={id}
isDM={false} isDM={false}
drawerPostie={drawerPostie} drawerPostie={drawerPostie}
onClick={() => selectSpace(id)}
/> />
))} ))}
@ -77,10 +91,27 @@ function Home() {
roomId={id} roomId={id}
isDM={false} isDM={false}
drawerPostie={drawerPostie} 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; export default Home;

View file

@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
import initMatrix from '../../../client/initMatrix'; import initMatrix from '../../../client/initMatrix';
import { doesRoomHaveUnread } from '../../../util/matrixUtil'; import { doesRoomHaveUnread } from '../../../util/matrixUtil';
import { selectRoom } from '../../../client/action/navigation';
import navigation from '../../../client/state/navigation'; import navigation from '../../../client/state/navigation';
import RoomSelector from '../../molecules/room-selector/RoomSelector'; 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 SpaceIC from '../../../../public/res/ic/outlined/space.svg';
import SpaceLockIC from '../../../../public/res/ic/outlined/space-lock.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 mx = initMatrix.matrixClient;
const room = mx.getRoom(roomId); const room = mx.getRoom(roomId);
const imageSrc = room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null; 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({}); const [, forceUpdate] = useState({});
function selectorChanged(activeRoomId) { function selectorChanged(selectedRoomId) {
setIsSelected(activeRoomId === roomId); setIsSelected(selectedRoomId === roomId);
} }
function changeNotificationBadge() { function changeNotificationBadge() {
forceUpdate({}); forceUpdate({});
@ -58,7 +59,7 @@ function Selector({ roomId, isDM, drawerPostie }) {
isUnread={doesRoomHaveUnread(room)} isUnread={doesRoomHaveUnread(room)}
notificationCount={room.getUnreadNotificationCount('total') || 0} notificationCount={room.getUnreadNotificationCount('total') || 0}
isAlert={room.getUnreadNotificationCount('highlight') !== 0} isAlert={room.getUnreadNotificationCount('highlight') !== 0}
onClick={() => selectRoom(roomId)} onClick={onClick}
/> />
); );
} }
@ -71,6 +72,7 @@ Selector.propTypes = {
roomId: PropTypes.string.isRequired, roomId: PropTypes.string.isRequired,
isDM: PropTypes.bool, isDM: PropTypes.bool,
drawerPostie: PropTypes.shape({}).isRequired, drawerPostie: PropTypes.shape({}).isRequired,
onClick: PropTypes.func.isRequired,
}; };
export default Selector; export default Selector;

View file

@ -60,10 +60,10 @@ function SideBar() {
+ initMatrix.roomList.inviteDirects.size; + initMatrix.roomList.inviteDirects.size;
const [totalInvites, updateTotalInvites] = useState(totalInviteCount()); const [totalInvites, updateTotalInvites] = useState(totalInviteCount());
const [activeTab, setActiveTab] = useState('home'); const [selectedTab, setSelectedTab] = useState('home');
function onTabChanged(tabId) { function onTabChanged(tabId) {
setActiveTab(tabId); setSelectedTab(tabId);
} }
function onInviteListChange() { function onInviteListChange() {
updateTotalInvites(totalInviteCount()); updateTotalInvites(totalInviteCount());
@ -91,8 +91,8 @@ function SideBar() {
<ScrollView invisible> <ScrollView invisible>
<div className="scrollable-content"> <div className="scrollable-content">
<div className="featured-container"> <div className="featured-container">
<SidebarAvatar active={activeTab === 'home'} onClick={() => changeTab('home')} tooltip="Home" iconSrc={HomeIC} /> <SidebarAvatar active={selectedTab === 'home'} onClick={() => changeTab('home')} tooltip="Home" iconSrc={HomeIC} />
<SidebarAvatar active={activeTab === 'dm'} onClick={() => changeTab('dm')} tooltip="People" iconSrc={UserIC} /> <SidebarAvatar active={selectedTab === 'dm'} onClick={() => changeTab('dm')} tooltip="People" iconSrc={UserIC} />
<SidebarAvatar onClick={() => openPublicRooms()} tooltip="Public rooms" iconSrc={HashSearchIC} /> <SidebarAvatar onClick={() => openPublicRooms()} tooltip="Public rooms" iconSrc={HashSearchIC} />
</div> </div>
<div className="sidebar-divider" /> <div className="sidebar-divider" />

View file

@ -39,11 +39,10 @@
height: 8px; height: 8px;
background: transparent; background: transparent;
// background-image: linear-gradient(to top, var(--bg-surface-low), transparent); background-image: linear-gradient(
// It produce bug in safari to top,
// To fix it, we have to set the color as a fully transparent version of that exact color. like: var(--bg-surface-low),
// background-image: linear-gradient(to top, rgb(255, 255, 255), rgba(255, 255, 255, 0)); var(--bg-surface-low-transparent));
// TODO: fix this bug while implementing spaces
position: sticky; position: sticky;
bottom: 0; bottom: 0;
left: 0; left: 0;

View file

@ -13,7 +13,7 @@ import Spinner from '../../atoms/spinner/Spinner';
import CinnySvg from '../../../../public/res/svg/cinny.svg'; 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. // See https://matrix.org/docs/spec/appendices#id13 for more info.
const LOCALPART_LOGIN_REGEX = /.*/; const LOCALPART_LOGIN_REGEX = /.*/;
const LOCALPART_SIGNUP_REGEX = /^[a-z0-9_\-.=/]+$/; const LOCALPART_SIGNUP_REGEX = /^[a-z0-9_\-.=/]+$/;

View file

@ -8,6 +8,13 @@ function changeTab(tabId) {
}); });
} }
function selectSpace(roomId) {
appDispatcher.dispatch({
type: cons.actions.navigation.SELECT_SPACE,
roomId,
});
}
function selectRoom(roomId) { function selectRoom(roomId) {
appDispatcher.dispatch({ appDispatcher.dispatch({
type: cons.actions.navigation.SELECT_ROOM, type: cons.actions.navigation.SELECT_ROOM,
@ -72,6 +79,7 @@ function openReadReceipts(roomId, eventId) {
export { export {
changeTab, changeTab,
selectSpace,
selectRoom, selectRoom,
togglePeopleDrawer, togglePeopleDrawer,
openInviteList, openInviteList,

View file

@ -7,6 +7,7 @@ class RoomList extends EventEmitter {
super(); super();
this.matrixClient = matrixClient; this.matrixClient = matrixClient;
this.mDirects = this.getMDirects(); this.mDirects = this.getMDirects();
this.roomIdToParents = new Map();
this.inviteDirects = new Set(); this.inviteDirects = new Set();
this.inviteSpaces = new Set(); this.inviteSpaces = new Set();
@ -24,13 +25,54 @@ class RoomList extends EventEmitter {
appDispatcher.register(this.roomActions.bind(this)); 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) { roomActions(action) {
const addRoom = (roomId, isDM) => { const addRoom = (roomId, isDM) => {
const myRoom = this.matrixClient.getRoom(roomId); const myRoom = this.matrixClient.getRoom(roomId);
if (myRoom === null) return false; if (myRoom === null) return false;
if (isDM) this.directs.add(roomId); 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); else this.rooms.add(roomId);
return true; return true;
}; };
@ -85,6 +127,7 @@ class RoomList extends EventEmitter {
_populateRooms() { _populateRooms() {
this.directs.clear(); this.directs.clear();
this.roomIdToParents.clear();
this.spaces.clear(); this.spaces.clear();
this.rooms.clear(); this.rooms.clear();
this.inviteDirects.clear(); this.inviteDirects.clear();
@ -109,7 +152,7 @@ class RoomList extends EventEmitter {
if (room.getMyMembership() !== 'join') return; if (room.getMyMembership() !== 'join') return;
if (this.mDirects.has(roomId)) this.directs.add(roomId); 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); else this.rooms.add(roomId);
}); });
} }
@ -165,8 +208,16 @@ class RoomList extends EventEmitter {
} }
}); });
this.matrixClient.on('RoomState.events', (event) => { this.matrixClient.on('RoomState.events', (mEvent) => {
if (event.getType() !== 'm.room.join_rules') return; 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); this.emit(cons.events.roomList.ROOMLIST_UPDATED);
}); });
@ -207,7 +258,7 @@ class RoomList extends EventEmitter {
const procRoomInfo = this.processingRooms.get(roomId); const procRoomInfo = this.processingRooms.get(roomId);
if (procRoomInfo.isDM) this.directs.add(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); else this.rooms.add(roomId);
if (procRoomInfo.task === 'CREATE') this.emit(cons.events.roomList.ROOM_CREATED, roomId); if (procRoomInfo.task === 'CREATE') this.emit(cons.events.roomList.ROOM_CREATED, roomId);
@ -218,7 +269,7 @@ class RoomList extends EventEmitter {
return; return;
} }
if (room.isSpaceRoom()) { if (room.isSpaceRoom()) {
this.spaces.add(roomId); this.addToSpaces(roomId);
this.emit(cons.events.roomList.ROOM_JOINED, roomId); this.emit(cons.events.roomList.ROOM_JOINED, roomId);
this.emit(cons.events.roomList.ROOMLIST_UPDATED); 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. // when room is not a DM add/remove it from rooms.
if (membership === 'leave' || membership === 'kick' || membership === 'ban') { 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); else this.rooms.delete(roomId);
this.emit(cons.events.roomList.ROOM_LEAVED, roomId); this.emit(cons.events.roomList.ROOM_LEAVED, roomId);
} }
if (membership === 'join') { if (membership === 'join') {
if (room.isSpaceRoom()) this.spaces.add(roomId); if (room.isSpaceRoom()) this.addToSpaces(roomId);
else this.rooms.add(roomId); else this.rooms.add(roomId);
this.emit(cons.events.roomList.ROOM_JOINED, roomId); this.emit(cons.events.roomList.ROOM_JOINED, roomId);
} }

View file

@ -9,6 +9,7 @@ const cons = {
actions: { actions: {
navigation: { navigation: {
CHANGE_TAB: 'CHANGE_TAB', CHANGE_TAB: 'CHANGE_TAB',
SELECT_SPACE: 'SELECT_SPACE',
SELECT_ROOM: 'SELECT_ROOM', SELECT_ROOM: 'SELECT_ROOM',
TOGGLE_PEOPLE_DRAWER: 'TOGGLE_PEOPLE_DRAWER', TOGGLE_PEOPLE_DRAWER: 'TOGGLE_PEOPLE_DRAWER',
OPEN_INVITE_LIST: 'OPEN_INVITE_LIST', OPEN_INVITE_LIST: 'OPEN_INVITE_LIST',
@ -34,6 +35,7 @@ const cons = {
events: { events: {
navigation: { navigation: {
TAB_CHANGED: 'TAB_CHANGED', TAB_CHANGED: 'TAB_CHANGED',
SPACE_SELECTED: 'SPACE_SELECTED',
ROOM_SELECTED: 'ROOM_SELECTED', ROOM_SELECTED: 'ROOM_SELECTED',
PEOPLE_DRAWER_TOGGLED: 'PEOPLE_DRAWER_TOGGLED', PEOPLE_DRAWER_TOGGLED: 'PEOPLE_DRAWER_TOGGLED',
INVITE_LIST_OPENED: 'INVITE_LIST_OPENED', INVITE_LIST_OPENED: 'INVITE_LIST_OPENED',

View file

@ -6,29 +6,44 @@ class Navigation extends EventEmitter {
constructor() { constructor() {
super(); super();
this.activeTab = 'home'; this.selectedTab = 'home';
this.activeRoomId = null; this.selectedSpaceId = null;
this.selectedSpacePath = [];
this.selectedRoomId = null;
this.isPeopleDrawerVisible = true; this.isPeopleDrawerVisible = true;
// TODO:
window.navigation = this;
} }
getActiveTab() { _setSpacePath(roomId) {
return this.activeTab; if (roomId === null) {
this.selectedSpacePath = [];
return;
} }
if (this.selectedSpacePath.includes(roomId)) {
getActiveRoomId() { const spIndex = this.selectedSpacePath.indexOf(roomId);
return this.activeRoomId; this.selectedSpacePath = this.selectedSpacePath.slice(0, spIndex + 1);
return;
}
this.selectedSpacePath.push(roomId);
} }
navigate(action) { navigate(action) {
const actions = { const actions = {
[cons.actions.navigation.CHANGE_TAB]: () => { [cons.actions.navigation.CHANGE_TAB]: () => {
this.activeTab = action.tabId; this.selectedTab = action.tabId;
this.emit(cons.events.navigation.TAB_CHANGED, this.activeTab); 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]: () => { [cons.actions.navigation.SELECT_ROOM]: () => {
const prevActiveRoomId = this.activeRoomId; const prevSelectedRoomId = this.selectedRoomId;
this.activeRoomId = action.roomId; this.selectedRoomId = action.roomId;
this.emit(cons.events.navigation.ROOM_SELECTED, this.activeRoomId, prevActiveRoomId); this.emit(cons.events.navigation.ROOM_SELECTED, this.selectedRoomId, prevSelectedRoomId);
}, },
[cons.actions.navigation.TOGGLE_PEOPLE_DRAWER]: () => { [cons.actions.navigation.TOGGLE_PEOPLE_DRAWER]: () => {
this.isPeopleDrawerVisible = !this.isPeopleDrawerVisible; this.isPeopleDrawerVisible = !this.isPeopleDrawerVisible;

View file

@ -4,7 +4,9 @@
/* background color | --bg-[background type]: value */ /* background color | --bg-[background type]: value */
--bg-surface: #FFFFFF; --bg-surface: #FFFFFF;
--bg-surface-transparent: #FFFFFF00;
--bg-surface-low: #F6F6F6; --bg-surface-low: #F6F6F6;
--bg-surface-low-transparent: #F6F6F600;
--bg-surface-hover: rgba(0, 0, 0, 3%); --bg-surface-hover: rgba(0, 0, 0, 3%);
--bg-surface-active: rgba(0, 0, 0, 5%); --bg-surface-active: rgba(0, 0, 0, 5%);
--bg-surface-border: rgba(0, 0, 0, 6%); --bg-surface-border: rgba(0, 0, 0, 6%);
@ -155,14 +157,18 @@
.silver-theme { .silver-theme {
/* background color | --bg-[background type]: value */ /* background color | --bg-[background type]: value */
--bg-surface: hsl(0, 0%, 95%); --bg-surface: hsl(0, 0%, 95%);
--bg-surface-transparent: hsla(0, 0%, 95%, 0);
--bg-surface-low: hsl(0, 0%, 91%); --bg-surface-low: hsl(0, 0%, 91%);
--bg-surface-low-transparent: hsla(0, 0%, 91%, 0);
} }
.dark-theme, .dark-theme,
.butter-theme { .butter-theme {
/* background color | --bg-[background type]: value */ /* background color | --bg-[background type]: value */
--bg-surface: hsl(208, 8%, 20%); --bg-surface: hsl(208, 8%, 20%);
--bg-surface-transparent: hsla(208, 8%, 20%, 0);
--bg-surface-low: hsl(208, 8%, 16%); --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-hover: rgba(255, 255, 255, 3%);
--bg-surface-active: rgba(255, 255, 255, 5%); --bg-surface-active: rgba(255, 255, 255, 5%);
--bg-surface-border: rgba(0, 0, 0, 20%); --bg-surface-border: rgba(0, 0, 0, 20%);
@ -206,7 +212,9 @@
.butter-theme { .butter-theme {
/* background color | --bg-[background type]: value */ /* background color | --bg-[background type]: value */
--bg-surface: hsl(64, 6%, 14%); --bg-surface: hsl(64, 6%, 14%);
--bg-surface-transparent: hsla(64, 6%, 14%, 0);
--bg-surface-low: hsl(64, 6%, 10%); --bg-surface-low: hsl(64, 6%, 10%);
--bg-surface-low-transparent: hsla(64, 6%, 14%, 0);
/* text color | --tc-[background type]-[priority]: value */ /* text color | --tc-[background type]-[priority]: value */