className="channel-selector__content"
type="button"
onClick={onClick}
- onMouseUp={(e) => blurOnBubbling(e, '.channel-selector__wrapper')}
+ onMouseUp={(e) => blurOnBubbling(e, '.channel-selector')}
>
{content}
</button>
--- /dev/null
+import React, { useState, useEffect } from 'react';
+
+import initMatrix from '../../../client/initMatrix';
+import cons from '../../../client/state/cons';
+import navigation from '../../../client/state/navigation';
+import { selectRoom } from '../../../client/action/navigation';
+import Postie from '../../../util/Postie';
+
+import Selector from './Selector';
+
+import { AtoZ } from './common';
+
+const drawerPostie = new Postie();
+function Directs() {
+ const { roomList } = initMatrix;
+ const directIds = [...roomList.directs].sort(AtoZ);
+
+ const [, forceUpdate] = useState({});
+
+ function selectorChanged(activeRoomID, prevActiveRoomId) {
+ 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 (addresses.length === 0) return;
+ drawerPostie.post('selector-change', addresses, activeRoomID);
+ }
+
+ function unreadChanged(roomId) {
+ if (!drawerPostie.hasTopic('unread-change')) return;
+ if (!drawerPostie.hasSubscriber('unread-change', roomId)) return;
+ drawerPostie.post('unread-change', roomId);
+ }
+
+ function roomListUpdated() {
+ const { spaces, rooms, directs } = initMatrix.roomList;
+ if (!(
+ spaces.has(navigation.getActiveRoomId())
+ || rooms.has(navigation.getActiveRoomId())
+ || directs.has(navigation.getActiveRoomId()))
+ ) {
+ selectRoom(null);
+ }
+ forceUpdate({});
+ }
+
+ useEffect(() => {
+ roomList.on(cons.events.roomList.ROOMLIST_UPDATED, roomListUpdated);
+ navigation.on(cons.events.navigation.ROOM_SELECTED, selectorChanged);
+ roomList.on(cons.events.roomList.MY_RECEIPT_ARRIVED, unreadChanged);
+ roomList.on(cons.events.roomList.EVENT_ARRIVED, unreadChanged);
+ return () => {
+ roomList.removeListener(cons.events.roomList.ROOMLIST_UPDATED, roomListUpdated);
+ navigation.removeListener(cons.events.navigation.ROOM_SELECTED, selectorChanged);
+ roomList.removeListener(cons.events.roomList.MY_RECEIPT_ARRIVED, unreadChanged);
+ roomList.removeListener(cons.events.roomList.EVENT_ARRIVED, unreadChanged);
+ };
+ }, []);
+
+ return directIds.map((id) => (
+ <Selector
+ key={id}
+ roomId={id}
+ drawerPostie={drawerPostie}
+ />
+ ));
+}
+
+export default Directs;
import React, { useState, useEffect } from 'react';
-import PropTypes from 'prop-types';
import './Drawer.scss';
-import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons';
-import { doesRoomHaveUnread } from '../../../util/matrixUtil';
-import {
- selectRoom, openPublicChannels, openCreateChannel, openInviteUser,
-} from '../../../client/action/navigation';
import navigation from '../../../client/state/navigation';
-import Header, { TitleWrapper } from '../../atoms/header/Header';
-import Text from '../../atoms/text/Text';
-import IconButton from '../../atoms/button/IconButton';
import ScrollView from '../../atoms/scroll/ScrollView';
-import ContextMenu, { MenuItem, MenuHeader } from '../../atoms/context-menu/ContextMenu';
-import ChannelSelector from '../../molecules/channel-selector/ChannelSelector';
-import PlusIC from '../../../../public/res/ic/outlined/plus.svg';
-// import VerticalMenuIC from '../../../../public/res/ic/outlined/vertical-menu.svg';
-import HashIC from '../../../../public/res/ic/outlined/hash.svg';
-import HashLockIC from '../../../../public/res/ic/outlined/hash-lock.svg';
-import HashPlusIC from '../../../../public/res/ic/outlined/hash-plus.svg';
-import HashSearchIC from '../../../../public/res/ic/outlined/hash-search.svg';
-import SpaceIC from '../../../../public/res/ic/outlined/space.svg';
-import SpaceLockIC from '../../../../public/res/ic/outlined/space-lock.svg';
-
-function AtoZ(aId, bId) {
- let aName = initMatrix.matrixClient.getRoom(aId).name;
- let bName = initMatrix.matrixClient.getRoom(bId).name;
-
- // remove "#" from the room name
- // To ignore it in sorting
- aName = aName.replaceAll('#', '');
- bName = bName.replaceAll('#', '');
-
- if (aName.toLowerCase() < bName.toLowerCase()) {
- return -1;
- }
- if (aName.toLowerCase() > bName.toLowerCase()) {
- return 1;
- }
- return 0;
-}
-
-function DrawerHeader({ activeTab }) {
- return (
- <Header>
- <TitleWrapper>
- <Text variant="s1">{(activeTab === 'home' ? 'Home' : 'Direct messages')}</Text>
- </TitleWrapper>
- {(activeTab === 'dm')
- ? <IconButton onClick={() => openInviteUser()} tooltip="Start DM" src={PlusIC} size="normal" />
- : (
- <ContextMenu
- content={(hideMenu) => (
- <>
- <MenuHeader>Add channel</MenuHeader>
- <MenuItem
- iconSrc={HashPlusIC}
- onClick={() => { hideMenu(); openCreateChannel(); }}
- >
- Create new channel
- </MenuItem>
- <MenuItem
- iconSrc={HashSearchIC}
- onClick={() => { hideMenu(); openPublicChannels(); }}
- >
- Add Public channel
- </MenuItem>
- </>
- )}
- render={(toggleMenu) => (<IconButton onClick={toggleMenu} tooltip="Add channel" src={PlusIC} size="normal" />)}
- />
- )}
- {/* <IconButton onClick={() => ''} tooltip="Menu" src={VerticalMenuIC} size="normal" /> */}
- </Header>
- );
-}
-DrawerHeader.propTypes = {
- activeTab: PropTypes.string.isRequired,
-};
+import DrawerHeader from './DrawerHeader';
+import Home from './Home';
+import Directs from './Directs';
function DrawerBradcrumb() {
return (
);
}
-function renderSelector(room, roomId, isSelected, isDM) {
- const mx = initMatrix.matrixClient;
- let imageSrc = room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 24, 24, 'crop');
- if (typeof imageSrc === 'undefined') imageSrc = null;
-
- return (
- <ChannelSelector
- key={roomId}
- name={room.name}
- roomId={roomId}
- imageSrc={isDM ? imageSrc : null}
- iconSrc={
- isDM
- ? null
- : (() => {
- if (room.isSpaceRoom()) {
- return (room.getJoinRule() === 'invite' ? SpaceLockIC : SpaceIC);
- }
- return (room.getJoinRule() === 'invite' ? HashLockIC : HashIC);
- })()
- }
- isSelected={isSelected}
- isUnread={doesRoomHaveUnread(room)}
- notificationCount={room.getUnreadNotificationCount('total')}
- isAlert={room.getUnreadNotificationCount('highlight') !== 0}
- onClick={() => selectRoom(roomId)}
- />
- );
-}
-
-function Directs({ selectedRoomId }) {
- const mx = initMatrix.matrixClient;
- const directIds = [...initMatrix.roomList.directs].sort(AtoZ);
-
- return directIds.map((id) => renderSelector(mx.getRoom(id), id, selectedRoomId === id, true));
-}
-Directs.defaultProps = { selectedRoomId: null };
-Directs.propTypes = { selectedRoomId: PropTypes.string };
-
-function Home({ selectedRoomId }) {
- const mx = initMatrix.matrixClient;
- const spaceIds = [...initMatrix.roomList.spaces].sort(AtoZ);
- const roomIds = [...initMatrix.roomList.rooms].sort(AtoZ);
-
- return (
- <>
- { spaceIds.length !== 0 && <Text className="cat-header" variant="b3">Spaces</Text> }
- { spaceIds.map((id) => renderSelector(mx.getRoom(id), id, selectedRoomId === id, false)) }
- { roomIds.length !== 0 && <Text className="cat-header" variant="b3">Channels</Text> }
- { roomIds.map((id) => renderSelector(mx.getRoom(id), id, selectedRoomId === id, false)) }
- </>
- );
-}
-Home.defaultProps = { selectedRoomId: null };
-Home.propTypes = { selectedRoomId: PropTypes.string };
-
-function Channels({ activeTab }) {
- const [selectedRoomId, changeSelectedRoomId] = useState(null);
- const [, updateState] = useState();
-
- const selectHandler = (roomId) => changeSelectedRoomId(roomId);
- const handleDataChanges = () => updateState({});
-
- const onRoomListChange = () => {
- const { spaces, rooms, directs } = initMatrix.roomList;
- if (!(
- spaces.has(selectedRoomId)
- || rooms.has(selectedRoomId)
- || directs.has(selectedRoomId))
- ) {
- selectRoom(null);
- }
- };
-
- useEffect(() => {
- navigation.on(cons.events.navigation.ROOM_SELECTED, selectHandler);
- initMatrix.roomList.on(cons.events.roomList.ROOMLIST_UPDATED, handleDataChanges);
-
- return () => {
- navigation.removeListener(cons.events.navigation.ROOM_SELECTED, selectHandler);
- initMatrix.roomList.removeListener(cons.events.roomList.ROOMLIST_UPDATED, handleDataChanges);
- };
- }, []);
- useEffect(() => {
- initMatrix.roomList.on(cons.events.roomList.ROOMLIST_UPDATED, onRoomListChange);
-
- return () => {
- initMatrix.roomList.removeListener(cons.events.roomList.ROOMLIST_UPDATED, onRoomListChange);
- };
- }, [selectedRoomId]);
-
- return (
- <div className="channels-container">
- {
- activeTab === 'home'
- ? <Home selectedRoomId={selectedRoomId} />
- : <Directs selectedRoomId={selectedRoomId} />
- }
- </div>
- );
-}
-Channels.propTypes = {
- activeTab: PropTypes.string.isRequired,
-};
-
function Drawer() {
const [activeTab, setActiveTab] = useState('home');
<DrawerBradcrumb />
<div className="channels__wrapper">
<ScrollView autoHide>
- <Channels activeTab={activeTab} />
+ <div className="channels-container">
+ {
+ activeTab === 'home'
+ ? <Home />
+ : <Directs />
+ }
+ </div>
</ScrollView>
</div>
</div>
--- /dev/null
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import {
+ openPublicChannels, openCreateChannel, openInviteUser,
+} from '../../../client/action/navigation';
+
+import Text from '../../atoms/text/Text';
+import Header, { TitleWrapper } from '../../atoms/header/Header';
+import IconButton from '../../atoms/button/IconButton';
+import ContextMenu, { MenuItem, MenuHeader } from '../../atoms/context-menu/ContextMenu';
+
+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';
+
+function DrawerHeader({ activeTab }) {
+ return (
+ <Header>
+ <TitleWrapper>
+ <Text variant="s1">{(activeTab === 'home' ? 'Home' : 'Direct messages')}</Text>
+ </TitleWrapper>
+ {(activeTab === 'dm')
+ ? <IconButton onClick={() => openInviteUser()} tooltip="Start DM" src={PlusIC} size="normal" />
+ : (
+ <ContextMenu
+ content={(hideMenu) => (
+ <>
+ <MenuHeader>Add channel</MenuHeader>
+ <MenuItem
+ iconSrc={HashPlusIC}
+ onClick={() => { hideMenu(); openCreateChannel(); }}
+ >
+ Create new channel
+ </MenuItem>
+ <MenuItem
+ iconSrc={HashSearchIC}
+ onClick={() => { hideMenu(); openPublicChannels(); }}
+ >
+ Add Public channel
+ </MenuItem>
+ </>
+ )}
+ render={(toggleMenu) => (<IconButton onClick={toggleMenu} tooltip="Add channel" src={PlusIC} size="normal" />)}
+ />
+ )}
+ {/* <IconButton onClick={() => ''} tooltip="Menu" src={VerticalMenuIC} size="normal" /> */}
+ </Header>
+ );
+}
+DrawerHeader.propTypes = {
+ activeTab: PropTypes.string.isRequired,
+};
+
+export default DrawerHeader;
--- /dev/null
+import React, { useState, useEffect } from 'react';
+
+import initMatrix from '../../../client/initMatrix';
+import cons from '../../../client/state/cons';
+import navigation from '../../../client/state/navigation';
+import { selectRoom } from '../../../client/action/navigation';
+import Postie from '../../../util/Postie';
+
+import Text from '../../atoms/text/Text';
+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);
+
+ const [, forceUpdate] = useState({});
+
+ function selectorChanged(activeRoomID, prevActiveRoomId) {
+ 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 (addresses.length === 0) return;
+ drawerPostie.post('selector-change', addresses, activeRoomID);
+ }
+ function unreadChanged(roomId) {
+ if (!drawerPostie.hasTopic('unread-change')) return;
+ if (!drawerPostie.hasSubscriber('unread-change', roomId)) return;
+ drawerPostie.post('unread-change', roomId);
+ }
+
+ function roomListUpdated() {
+ const { spaces, rooms, directs } = initMatrix.roomList;
+ if (!(
+ spaces.has(navigation.getActiveRoomId())
+ || rooms.has(navigation.getActiveRoomId())
+ || directs.has(navigation.getActiveRoomId()))
+ ) {
+ selectRoom(null);
+ }
+ forceUpdate({});
+ }
+
+ useEffect(() => {
+ roomList.on(cons.events.roomList.ROOMLIST_UPDATED, roomListUpdated);
+ navigation.on(cons.events.navigation.ROOM_SELECTED, selectorChanged);
+ roomList.on(cons.events.roomList.MY_RECEIPT_ARRIVED, unreadChanged);
+ roomList.on(cons.events.roomList.EVENT_ARRIVED, unreadChanged);
+ return () => {
+ roomList.removeListener(cons.events.roomList.ROOMLIST_UPDATED, roomListUpdated);
+ navigation.removeListener(cons.events.navigation.ROOM_SELECTED, selectorChanged);
+ roomList.removeListener(cons.events.roomList.MY_RECEIPT_ARRIVED, unreadChanged);
+ roomList.removeListener(cons.events.roomList.EVENT_ARRIVED, unreadChanged);
+ };
+ }, []);
+
+ return (
+ <>
+ { spaceIds.length !== 0 && <Text className="cat-header" variant="b3">Spaces</Text> }
+ { spaceIds.map((id) => (
+ <Selector
+ key={id}
+ roomId={id}
+ isDM={false}
+ drawerPostie={drawerPostie}
+ />
+ ))}
+
+ { roomIds.length !== 0 && <Text className="cat-header" variant="b3">Channels</Text> }
+ { roomIds.map((id) => (
+ <Selector
+ key={id}
+ roomId={id}
+ isDM={false}
+ drawerPostie={drawerPostie}
+ />
+ )) }
+ </>
+ );
+}
+
+export default Home;
--- /dev/null
+/* eslint-disable react/prop-types */
+import React, { useState, useEffect } from 'react';
+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 ChannelSelector from '../../molecules/channel-selector/ChannelSelector';
+
+import HashIC from '../../../../public/res/ic/outlined/hash.svg';
+import HashLockIC from '../../../../public/res/ic/outlined/hash-lock.svg';
+import SpaceIC from '../../../../public/res/ic/outlined/space.svg';
+import SpaceLockIC from '../../../../public/res/ic/outlined/space-lock.svg';
+
+function Selector({ roomId, isDM, drawerPostie }) {
+ 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 [, forceUpdate] = useState({});
+
+ function selectorChanged(activeRoomId) {
+ setIsSelected(activeRoomId === roomId);
+ }
+ function changeNotificationBadge() {
+ forceUpdate({});
+ }
+
+ useEffect(() => {
+ drawerPostie.subscribe('selector-change', roomId, selectorChanged);
+ drawerPostie.subscribe('unread-change', roomId, changeNotificationBadge);
+ return () => {
+ drawerPostie.unsubscribe('selector-change', roomId);
+ drawerPostie.unsubscribe('unread-change', roomId);
+ };
+ }, []);
+
+ return (
+ <ChannelSelector
+ key={roomId}
+ name={room.name}
+ roomId={roomId}
+ imageSrc={isDM ? imageSrc : null}
+ iconSrc={
+ isDM
+ ? null
+ : (() => {
+ if (room.isSpaceRoom()) {
+ return (room.getJoinRule() === 'invite' ? SpaceLockIC : SpaceIC);
+ }
+ return (room.getJoinRule() === 'invite' ? HashLockIC : HashIC);
+ })()
+ }
+ isSelected={isSelected}
+ isUnread={doesRoomHaveUnread(room)}
+ notificationCount={room.getUnreadNotificationCount('total') || 0}
+ isAlert={room.getUnreadNotificationCount('highlight') !== 0}
+ onClick={() => selectRoom(roomId)}
+ />
+ );
+}
+
+Selector.defaultProps = {
+ isDM: true,
+};
+
+Selector.propTypes = {
+ roomId: PropTypes.string.isRequired,
+ isDM: PropTypes.bool,
+ drawerPostie: PropTypes.shape({}).isRequired,
+};
+
+export default Selector;
--- /dev/null
+import initMatrix from '../../../client/initMatrix';
+
+function AtoZ(aId, bId) {
+ let aName = initMatrix.matrixClient.getRoom(aId).name;
+ let bName = initMatrix.matrixClient.getRoom(bId).name;
+
+ // remove "#" from the room name
+ // To ignore it in sorting
+ aName = aName.replaceAll('#', '');
+ bName = bName.replaceAll('#', '');
+
+ if (aName.toLowerCase() < bName.toLowerCase()) {
+ return -1;
+ }
+ if (aName.toLowerCase() > bName.toLowerCase()) {
+ return 1;
+ }
+ return 0;
+}
+
+export { AtoZ };
this.matrixClient.on('Room.name', () => {
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
});
- this.matrixClient.on('Room.receipt', (event) => {
+ this.matrixClient.on('Room.receipt', (event, room) => {
if (event.getType() === 'm.receipt') {
- const evContent = event.getContent();
- const userId = Object.keys(evContent[Object.keys(evContent)[0]]['m.read'])[0];
- if (userId !== this.matrixClient.getUserId()) return;
- this.emit(cons.events.roomList.ROOMLIST_UPDATED);
+ const content = event.getContent();
+ const userReadEventId = Object.keys(content)[0];
+ const eventReaderUserId = Object.keys(content[userReadEventId]['m.read'])[0];
+ if (eventReaderUserId !== this.matrixClient.getUserId()) return;
+ this.emit(cons.events.roomList.MY_RECEIPT_ARRIVED, room.roomId);
}
});
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
});
- this.matrixClient.on('Room.timeline', () => {
- this.emit(cons.events.roomList.ROOMLIST_UPDATED);
+ this.matrixClient.on('Room.timeline', (event, room) => {
+ const supportEvents = ['m.room.message', 'm.room.encrypted', 'm.sticker'];
+ if (!supportEvents.includes(event.getType())) return;
+
+ const lastTimelineEvent = room.timeline[room.timeline.length - 1];
+ if (lastTimelineEvent.getId() !== event.getId()) return;
+ this.emit(cons.events.roomList.EVENT_ARRIVED, room.roomId);
});
}
}
ROOM_JOINED: 'ROOM_JOINED',
ROOM_LEAVED: 'ROOM_LEAVED',
ROOM_CREATED: 'ROOM_CREATED',
+ MY_RECEIPT_ARRIVED: 'MY_RECEIPT_ARRIVED',
+ EVENT_ARRIVED: 'EVENT_ARRIVED',
},
roomTimeline: {
EVENT: 'EVENT',
constructor() {
super();
- this.activeTab = 'channels';
+ this.activeTab = 'home';
this.activeRoomId = null;
this.isPeopleDrawerVisible = true;
}
this.emit(cons.events.navigation.TAB_CHANGED, this.activeTab);
},
[cons.actions.navigation.SELECT_ROOM]: () => {
+ const prevActiveRoomId = this.activeRoomId;
this.activeRoomId = action.roomId;
- this.emit(cons.events.navigation.ROOM_SELECTED, this.activeRoomId);
+ this.emit(cons.events.navigation.ROOM_SELECTED, this.activeRoomId, prevActiveRoomId);
},
[cons.actions.navigation.TOGGLE_PEOPLE_DRAWER]: () => {
this.isPeopleDrawerVisible = !this.isPeopleDrawerVisible;