Added unread symbol for Spaces, DMs and Home (#82)
This commit is contained in:
parent
fc0dc8aea0
commit
b07c50e580
14 changed files with 229 additions and 108 deletions
|
@ -12,7 +12,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&--alert {
|
&--alert {
|
||||||
background-color: var(--bg-danger);
|
background-color: var(--bg-positive);
|
||||||
& .text { color: white }
|
& .text { color: white }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -84,7 +84,10 @@ RoomSelector.propTypes = {
|
||||||
iconSrc: PropTypes.string,
|
iconSrc: PropTypes.string,
|
||||||
isSelected: PropTypes.bool,
|
isSelected: PropTypes.bool,
|
||||||
isUnread: PropTypes.bool.isRequired,
|
isUnread: PropTypes.bool.isRequired,
|
||||||
notificationCount: PropTypes.number.isRequired,
|
notificationCount: PropTypes.oneOfType([
|
||||||
|
PropTypes.string,
|
||||||
|
PropTypes.number,
|
||||||
|
]).isRequired,
|
||||||
isAlert: PropTypes.bool.isRequired,
|
isAlert: PropTypes.bool.isRequired,
|
||||||
options: PropTypes.node,
|
options: PropTypes.node,
|
||||||
onClick: PropTypes.func.isRequired,
|
onClick: PropTypes.func.isRequired,
|
||||||
|
|
|
@ -2,29 +2,22 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import './SidebarAvatar.scss';
|
import './SidebarAvatar.scss';
|
||||||
|
|
||||||
import Tippy from '@tippyjs/react';
|
|
||||||
import Avatar from '../../atoms/avatar/Avatar';
|
import Avatar from '../../atoms/avatar/Avatar';
|
||||||
import Text from '../../atoms/text/Text';
|
import Text from '../../atoms/text/Text';
|
||||||
|
import Tooltip from '../../atoms/tooltip/Tooltip';
|
||||||
import NotificationBadge from '../../atoms/badge/NotificationBadge';
|
import NotificationBadge from '../../atoms/badge/NotificationBadge';
|
||||||
import { blurOnBubbling } from '../../atoms/button/script';
|
import { blurOnBubbling } from '../../atoms/button/script';
|
||||||
|
|
||||||
const SidebarAvatar = React.forwardRef(({
|
const SidebarAvatar = React.forwardRef(({
|
||||||
tooltip, text, bgColor, imageSrc,
|
tooltip, text, bgColor, imageSrc,
|
||||||
iconSrc, active, onClick, notifyCount,
|
iconSrc, active, onClick, isUnread, notificationCount, isAlert,
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
let activeClass = '';
|
let activeClass = '';
|
||||||
if (active) activeClass = ' sidebar-avatar--active';
|
if (active) activeClass = ' sidebar-avatar--active';
|
||||||
return (
|
return (
|
||||||
<Tippy
|
<Tooltip
|
||||||
content={<Text variant="b1">{tooltip}</Text>}
|
content={<Text variant="b1">{tooltip}</Text>}
|
||||||
className="sidebar-avatar-tippy"
|
|
||||||
touch="hold"
|
|
||||||
arrow={false}
|
|
||||||
placement="right"
|
placement="right"
|
||||||
maxWidth={200}
|
|
||||||
delay={[0, 0]}
|
|
||||||
duration={[100, 0]}
|
|
||||||
offset={[0, 0]}
|
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
@ -40,9 +33,14 @@ const SidebarAvatar = React.forwardRef(({
|
||||||
iconSrc={iconSrc}
|
iconSrc={iconSrc}
|
||||||
size="normal"
|
size="normal"
|
||||||
/>
|
/>
|
||||||
{ notifyCount !== null && <NotificationBadge alert content={notifyCount} /> }
|
{ isUnread && (
|
||||||
|
<NotificationBadge
|
||||||
|
alert={isAlert}
|
||||||
|
content={notificationCount !== 0 ? notificationCount : null}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
</Tippy>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
SidebarAvatar.defaultProps = {
|
SidebarAvatar.defaultProps = {
|
||||||
|
@ -52,7 +50,9 @@ SidebarAvatar.defaultProps = {
|
||||||
imageSrc: null,
|
imageSrc: null,
|
||||||
active: false,
|
active: false,
|
||||||
onClick: null,
|
onClick: null,
|
||||||
notifyCount: null,
|
isUnread: false,
|
||||||
|
notificationCount: 0,
|
||||||
|
isAlert: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
SidebarAvatar.propTypes = {
|
SidebarAvatar.propTypes = {
|
||||||
|
@ -63,10 +63,12 @@ SidebarAvatar.propTypes = {
|
||||||
iconSrc: PropTypes.string,
|
iconSrc: PropTypes.string,
|
||||||
active: PropTypes.bool,
|
active: PropTypes.bool,
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
notifyCount: PropTypes.oneOfType([
|
isUnread: PropTypes.bool,
|
||||||
|
notificationCount: PropTypes.oneOfType([
|
||||||
PropTypes.string,
|
PropTypes.string,
|
||||||
PropTypes.number,
|
PropTypes.number,
|
||||||
]),
|
]),
|
||||||
|
isAlert: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SidebarAvatar;
|
export default SidebarAvatar;
|
||||||
|
|
|
@ -1,28 +1,18 @@
|
||||||
|
|
||||||
.sidebar-avatar-tippy {
|
|
||||||
padding: var(--sp-extra-tight) var(--sp-normal);
|
|
||||||
background-color: var(--bg-tooltip);
|
|
||||||
border-radius: var(--bo-radius);
|
|
||||||
box-shadow: var(--bs-popup);
|
|
||||||
|
|
||||||
.text {
|
|
||||||
color: var(--tc-tooltip);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-avatar {
|
.sidebar-avatar {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
& .notification-badge {
|
& .notification-badge {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: var(--sp-extra-tight);
|
right: 0;
|
||||||
top: calc(-1 * var(--sp-ultra-tight));
|
top: 0;
|
||||||
box-shadow: 0 0 0 2px var(--bg-surface-low);
|
box-shadow: 0 0 0 2px var(--bg-surface-low);
|
||||||
|
transform: translate(20%, -20%);
|
||||||
|
|
||||||
|
margin: 0 !important;
|
||||||
}
|
}
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
|
@ -37,7 +27,7 @@
|
||||||
content: "";
|
content: "";
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: -11px;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
|
|
||||||
|
@ -48,7 +38,8 @@
|
||||||
transition: height 200ms linear;
|
transition: height 200ms linear;
|
||||||
|
|
||||||
[dir=rtl] & {
|
[dir=rtl] & {
|
||||||
right: 0;
|
left: unset;
|
||||||
|
right: -11px;
|
||||||
border-radius: 4px 0 0 4px;
|
border-radius: 4px 0 0 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { AtoZ } from './common';
|
||||||
|
|
||||||
const drawerPostie = new Postie();
|
const drawerPostie = new Postie();
|
||||||
function Directs() {
|
function Directs() {
|
||||||
const { roomList } = initMatrix;
|
const { roomList, notifications } = initMatrix;
|
||||||
const directIds = [...roomList.directs].sort(AtoZ);
|
const directIds = [...roomList.directs].sort(AtoZ);
|
||||||
|
|
||||||
const [, forceUpdate] = useState({});
|
const [, forceUpdate] = useState({});
|
||||||
|
@ -26,11 +26,12 @@ function Directs() {
|
||||||
drawerPostie.post('selector-change', addresses, selectedRoomId);
|
drawerPostie.post('selector-change', addresses, selectedRoomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function unreadChanged(roomId) {
|
function notiChanged(roomId, total, prevTotal) {
|
||||||
if (!drawerPostie.hasTopic('unread-change')) return;
|
if (total === prevTotal) return;
|
||||||
if (!drawerPostie.hasSubscriber('unread-change', roomId)) return;
|
if (drawerPostie.hasTopicAndSubscriber('unread-change', roomId)) {
|
||||||
drawerPostie.post('unread-change', roomId);
|
drawerPostie.post('unread-change', roomId);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function roomListUpdated() {
|
function roomListUpdated() {
|
||||||
const { spaces, rooms, directs } = initMatrix.roomList;
|
const { spaces, rooms, directs } = initMatrix.roomList;
|
||||||
|
@ -47,13 +48,11 @@ function Directs() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
roomList.on(cons.events.roomList.ROOMLIST_UPDATED, roomListUpdated);
|
roomList.on(cons.events.roomList.ROOMLIST_UPDATED, roomListUpdated);
|
||||||
navigation.on(cons.events.navigation.ROOM_SELECTED, selectorChanged);
|
navigation.on(cons.events.navigation.ROOM_SELECTED, selectorChanged);
|
||||||
roomList.on(cons.events.roomList.MY_RECEIPT_ARRIVED, unreadChanged);
|
notifications.on(cons.events.notifications.NOTI_CHANGED, notiChanged);
|
||||||
roomList.on(cons.events.roomList.EVENT_ARRIVED, unreadChanged);
|
|
||||||
return () => {
|
return () => {
|
||||||
roomList.removeListener(cons.events.roomList.ROOMLIST_UPDATED, roomListUpdated);
|
roomList.removeListener(cons.events.roomList.ROOMLIST_UPDATED, roomListUpdated);
|
||||||
navigation.removeListener(cons.events.navigation.ROOM_SELECTED, selectorChanged);
|
navigation.removeListener(cons.events.navigation.ROOM_SELECTED, selectorChanged);
|
||||||
roomList.removeListener(cons.events.roomList.MY_RECEIPT_ARRIVED, unreadChanged);
|
notifications.removeListener(cons.events.notifications.NOTI_CHANGED, notiChanged);
|
||||||
roomList.removeListener(cons.events.roomList.EVENT_ARRIVED, unreadChanged);
|
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import './DrawerBreadcrumb.scss';
|
import './DrawerBreadcrumb.scss';
|
||||||
|
|
||||||
|
@ -6,51 +6,108 @@ import initMatrix from '../../../client/initMatrix';
|
||||||
import cons from '../../../client/state/cons';
|
import cons from '../../../client/state/cons';
|
||||||
import { selectSpace } from '../../../client/action/navigation';
|
import { selectSpace } from '../../../client/action/navigation';
|
||||||
import navigation from '../../../client/state/navigation';
|
import navigation from '../../../client/state/navigation';
|
||||||
|
import { abbreviateNumber } from '../../../util/common';
|
||||||
|
|
||||||
import Text from '../../atoms/text/Text';
|
import Text from '../../atoms/text/Text';
|
||||||
import RawIcon from '../../atoms/system-icons/RawIcon';
|
import RawIcon from '../../atoms/system-icons/RawIcon';
|
||||||
import Button from '../../atoms/button/Button';
|
import Button from '../../atoms/button/Button';
|
||||||
import ScrollView from '../../atoms/scroll/ScrollView';
|
import ScrollView from '../../atoms/scroll/ScrollView';
|
||||||
|
import NotificationBadge from '../../atoms/badge/NotificationBadge';
|
||||||
|
|
||||||
import ChevronRightIC from '../../../../public/res/ic/outlined/chevron-right.svg';
|
import ChevronRightIC from '../../../../public/res/ic/outlined/chevron-right.svg';
|
||||||
|
|
||||||
function DrawerBreadcrumb({ spaceId }) {
|
function DrawerBreadcrumb({ spaceId }) {
|
||||||
|
const [, forceUpdate] = useState({});
|
||||||
const scrollRef = useRef(null);
|
const scrollRef = useRef(null);
|
||||||
|
const { roomList, notifications } = initMatrix;
|
||||||
const mx = initMatrix.matrixClient;
|
const mx = initMatrix.matrixClient;
|
||||||
const spacePath = navigation.selectedSpacePath;
|
const spacePath = navigation.selectedSpacePath;
|
||||||
|
|
||||||
|
function onNotiChanged(roomId, total, prevTotal) {
|
||||||
|
if (total === prevTotal) return;
|
||||||
|
if (navigation.selectedSpacePath.includes(roomId)) {
|
||||||
|
forceUpdate({});
|
||||||
|
}
|
||||||
|
if (navigation.selectedSpacePath[0] === cons.tabs.HOME) {
|
||||||
|
if (!roomList.isOrphan(roomId)) return;
|
||||||
|
if (roomList.directs.has(roomId)) return;
|
||||||
|
forceUpdate({});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (scrollRef?.current === null) return;
|
if (scrollRef?.current === null) return;
|
||||||
scrollRef.current.scrollLeft = scrollRef.current.scrollWidth;
|
scrollRef.current.scrollLeft = scrollRef.current.scrollWidth;
|
||||||
});
|
});
|
||||||
|
notifications.on(cons.events.notifications.NOTI_CHANGED, onNotiChanged);
|
||||||
|
return () => {
|
||||||
|
notifications.removeListener(cons.events.notifications.NOTI_CHANGED, onNotiChanged);
|
||||||
|
};
|
||||||
}, [spaceId]);
|
}, [spaceId]);
|
||||||
|
|
||||||
if (spacePath.length === 1) return null;
|
if (spacePath.length === 1) return null;
|
||||||
|
|
||||||
|
function getHomeNotiExcept(childId) {
|
||||||
|
const orphans = roomList.getOrphans();
|
||||||
|
const childIndex = orphans.indexOf(childId);
|
||||||
|
if (childId !== -1) orphans.splice(childIndex, 1);
|
||||||
|
|
||||||
|
let noti = null;
|
||||||
|
|
||||||
|
orphans.forEach((roomId) => {
|
||||||
|
if (!notifications.hasNoti(roomId)) return;
|
||||||
|
if (noti === null) noti = { total: 0, highlight: 0 };
|
||||||
|
const childNoti = notifications.getNoti(roomId);
|
||||||
|
noti.total += childNoti.total;
|
||||||
|
noti.highlight += childNoti.highlight;
|
||||||
|
});
|
||||||
|
|
||||||
|
return noti;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNotiExcept(roomId, childId) {
|
||||||
|
if (!notifications.hasNoti(roomId)) return null;
|
||||||
|
|
||||||
|
const noti = notifications.getNoti(roomId);
|
||||||
|
if (!notifications.hasNoti(childId)) return noti;
|
||||||
|
if (noti.from === null) return noti;
|
||||||
|
if (noti.from.has(childId) && noti.from.size === 1) return null;
|
||||||
|
|
||||||
|
const childNoti = notifications.getNoti(childId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
total: noti.total - childNoti.total,
|
||||||
|
highlight: noti.highlight - childNoti.highlight,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="breadcrumb__wrapper">
|
<div className="breadcrumb__wrapper">
|
||||||
<ScrollView ref={scrollRef} horizontal vertical={false} invisible>
|
<ScrollView ref={scrollRef} horizontal vertical={false} invisible>
|
||||||
<div className="breadcrumb">
|
<div className="breadcrumb">
|
||||||
{
|
{
|
||||||
spacePath.map((id, index) => {
|
spacePath.map((id, index) => {
|
||||||
if (index === 0) {
|
const noti = (id !== cons.tabs.HOME && index < spacePath.length)
|
||||||
return (
|
? getNotiExcept(id, (index === spacePath.length - 1) ? null : spacePath[index + 1])
|
||||||
<Button key={id} onClick={() => selectSpace(id)}>
|
: getHomeNotiExcept((index === spacePath.length - 1) ? null : spacePath[index + 1]);
|
||||||
<Text variant="b2">{id === cons.tabs.HOME ? 'Home' : mx.getRoom(id).name}</Text>
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment
|
<React.Fragment
|
||||||
key={id}
|
key={id}
|
||||||
>
|
>
|
||||||
<RawIcon size="extra-small" src={ChevronRightIC} />
|
{ index !== 0 && <RawIcon size="extra-small" src={ChevronRightIC} />}
|
||||||
<Button
|
<Button
|
||||||
className={index === spacePath.length - 1 ? 'breadcrumb__btn--selected' : ''}
|
className={index === spacePath.length - 1 ? 'breadcrumb__btn--selected' : ''}
|
||||||
onClick={() => selectSpace(id)}
|
onClick={() => selectSpace(id)}
|
||||||
>
|
>
|
||||||
<Text variant="b2">{ mx.getRoom(id).name }</Text>
|
<Text variant="b2">{id === cons.tabs.HOME ? 'Home' : mx.getRoom(id).name}</Text>
|
||||||
|
{ noti !== null && (
|
||||||
|
<NotificationBadge
|
||||||
|
alert={noti.highlight !== 0}
|
||||||
|
content={noti.total > 0 ? abbreviateNumber(noti.total) : null}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
|
|
|
@ -51,6 +51,14 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& .notification-badge {
|
||||||
|
margin-left: var(--sp-extra-tight);
|
||||||
|
[dir=rtl] & {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: var(--sp-extra-tight);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__btn--selected {
|
&__btn--selected {
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { AtoZ } from './common';
|
||||||
const drawerPostie = new Postie();
|
const drawerPostie = new Postie();
|
||||||
function Home({ spaceId }) {
|
function Home({ spaceId }) {
|
||||||
const [, forceUpdate] = useState({});
|
const [, forceUpdate] = useState({});
|
||||||
const { roomList } = initMatrix;
|
const { roomList, notifications } = initMatrix;
|
||||||
let spaceIds = [];
|
let spaceIds = [];
|
||||||
let roomIds = [];
|
let roomIds = [];
|
||||||
let directIds = [];
|
let directIds = [];
|
||||||
|
@ -40,11 +40,12 @@ function Home({ spaceId }) {
|
||||||
if (addresses.length === 0) return;
|
if (addresses.length === 0) return;
|
||||||
drawerPostie.post('selector-change', addresses, selectedRoomId);
|
drawerPostie.post('selector-change', addresses, selectedRoomId);
|
||||||
}
|
}
|
||||||
function unreadChanged(roomId) {
|
function notiChanged(roomId, total, prevTotal) {
|
||||||
if (!drawerPostie.hasTopic('unread-change')) return;
|
if (total === prevTotal) return;
|
||||||
if (!drawerPostie.hasSubscriber('unread-change', roomId)) return;
|
if (drawerPostie.hasTopicAndSubscriber('unread-change', roomId)) {
|
||||||
drawerPostie.post('unread-change', roomId);
|
drawerPostie.post('unread-change', roomId);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function roomListUpdated() {
|
function roomListUpdated() {
|
||||||
const { spaces, rooms, directs } = initMatrix.roomList;
|
const { spaces, rooms, directs } = initMatrix.roomList;
|
||||||
|
@ -61,13 +62,11 @@ function Home({ spaceId }) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
roomList.on(cons.events.roomList.ROOMLIST_UPDATED, roomListUpdated);
|
roomList.on(cons.events.roomList.ROOMLIST_UPDATED, roomListUpdated);
|
||||||
navigation.on(cons.events.navigation.ROOM_SELECTED, selectorChanged);
|
navigation.on(cons.events.navigation.ROOM_SELECTED, selectorChanged);
|
||||||
roomList.on(cons.events.roomList.MY_RECEIPT_ARRIVED, unreadChanged);
|
notifications.on(cons.events.notifications.NOTI_CHANGED, notiChanged);
|
||||||
roomList.on(cons.events.roomList.EVENT_ARRIVED, unreadChanged);
|
|
||||||
return () => {
|
return () => {
|
||||||
roomList.removeListener(cons.events.roomList.ROOMLIST_UPDATED, roomListUpdated);
|
roomList.removeListener(cons.events.roomList.ROOMLIST_UPDATED, roomListUpdated);
|
||||||
navigation.removeListener(cons.events.navigation.ROOM_SELECTED, selectorChanged);
|
navigation.removeListener(cons.events.navigation.ROOM_SELECTED, selectorChanged);
|
||||||
roomList.removeListener(cons.events.roomList.MY_RECEIPT_ARRIVED, unreadChanged);
|
notifications.removeListener(cons.events.notifications.NOTI_CHANGED, notiChanged);
|
||||||
roomList.removeListener(cons.events.roomList.EVENT_ARRIVED, unreadChanged);
|
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,10 @@ import React, { useState, useEffect } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import initMatrix from '../../../client/initMatrix';
|
import initMatrix from '../../../client/initMatrix';
|
||||||
import { doesRoomHaveUnread } from '../../../util/matrixUtil';
|
|
||||||
import navigation from '../../../client/state/navigation';
|
import navigation from '../../../client/state/navigation';
|
||||||
import { openRoomOptions } from '../../../client/action/navigation';
|
import { openRoomOptions } from '../../../client/action/navigation';
|
||||||
import { createSpaceShortcut, deleteSpaceShortcut } from '../../../client/action/room';
|
import { createSpaceShortcut, deleteSpaceShortcut } from '../../../client/action/room';
|
||||||
import { getEventCords } from '../../../util/common';
|
import { getEventCords, abbreviateNumber } from '../../../util/common';
|
||||||
|
|
||||||
import IconButton from '../../atoms/button/IconButton';
|
import IconButton from '../../atoms/button/IconButton';
|
||||||
import RoomSelector from '../../molecules/room-selector/RoomSelector';
|
import RoomSelector from '../../molecules/room-selector/RoomSelector';
|
||||||
|
@ -24,6 +23,7 @@ function Selector({
|
||||||
roomId, isDM, drawerPostie, onClick,
|
roomId, isDM, drawerPostie, onClick,
|
||||||
}) {
|
}) {
|
||||||
const mx = initMatrix.matrixClient;
|
const mx = initMatrix.matrixClient;
|
||||||
|
const noti = initMatrix.notifications;
|
||||||
const room = mx.getRoom(roomId);
|
const room = mx.getRoom(roomId);
|
||||||
let imageSrc = room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null;
|
let imageSrc = room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null;
|
||||||
if (imageSrc === null) imageSrc = room.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null;
|
if (imageSrc === null) imageSrc = room.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null;
|
||||||
|
@ -54,9 +54,9 @@ function Selector({
|
||||||
name={room.name}
|
name={room.name}
|
||||||
roomId={roomId}
|
roomId={roomId}
|
||||||
iconSrc={room.getJoinRule() === 'invite' ? SpaceLockIC : SpaceIC}
|
iconSrc={room.getJoinRule() === 'invite' ? SpaceLockIC : SpaceIC}
|
||||||
isUnread={doesRoomHaveUnread(room)}
|
isUnread={noti.hasNoti(roomId)}
|
||||||
notificationCount={room.getUnreadNotificationCount('total') || 0}
|
notificationCount={abbreviateNumber(noti.getTotalNoti(roomId))}
|
||||||
isAlert={room.getUnreadNotificationCount('highlight') !== 0}
|
isAlert={noti.getHighlightNoti(roomId) !== 0}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
options={(
|
options={(
|
||||||
<IconButton
|
<IconButton
|
||||||
|
@ -85,9 +85,9 @@ function Selector({
|
||||||
// eslint-disable-next-line no-nested-ternary
|
// eslint-disable-next-line no-nested-ternary
|
||||||
iconSrc={isDM ? null : room.getJoinRule() === 'invite' ? HashLockIC : HashIC}
|
iconSrc={isDM ? null : room.getJoinRule() === 'invite' ? HashLockIC : HashIC}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
isUnread={doesRoomHaveUnread(room)}
|
isUnread={noti.hasNoti(roomId)}
|
||||||
notificationCount={room.getUnreadNotificationCount('total') || 0}
|
notificationCount={abbreviateNumber(noti.getTotalNoti(roomId))}
|
||||||
isAlert={room.getUnreadNotificationCount('highlight') !== 0}
|
isAlert={noti.getHighlightNoti(roomId) !== 0}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
options={(
|
options={(
|
||||||
<IconButton
|
<IconButton
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
selectTab, openInviteList, openPublicRooms, openSettings,
|
selectTab, openInviteList, openPublicRooms, openSettings,
|
||||||
} from '../../../client/action/navigation';
|
} from '../../../client/action/navigation';
|
||||||
import navigation from '../../../client/state/navigation';
|
import navigation from '../../../client/state/navigation';
|
||||||
|
import { abbreviateNumber } from '../../../util/common';
|
||||||
|
|
||||||
import ScrollView from '../../atoms/scroll/ScrollView';
|
import ScrollView from '../../atoms/scroll/ScrollView';
|
||||||
import SidebarAvatar from '../../molecules/sidebar-avatar/SidebarAvatar';
|
import SidebarAvatar from '../../molecules/sidebar-avatar/SidebarAvatar';
|
||||||
|
@ -55,7 +56,7 @@ function ProfileAvatarMenu() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function SideBar() {
|
function SideBar() {
|
||||||
const { roomList } = initMatrix;
|
const { roomList, notifications } = initMatrix;
|
||||||
const mx = initMatrix.matrixClient;
|
const mx = initMatrix.matrixClient;
|
||||||
const totalInviteCount = () => roomList.inviteRooms.size
|
const totalInviteCount = () => roomList.inviteRooms.size
|
||||||
+ roomList.inviteSpaces.size
|
+ roomList.inviteSpaces.size
|
||||||
|
@ -74,33 +75,83 @@ function SideBar() {
|
||||||
function onSpaceShortcutUpdated() {
|
function onSpaceShortcutUpdated() {
|
||||||
forceUpdate({});
|
forceUpdate({});
|
||||||
}
|
}
|
||||||
|
function onNotificationChanged(roomId, total, prevTotal) {
|
||||||
|
if (total === prevTotal) return;
|
||||||
|
forceUpdate({});
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
navigation.on(cons.events.navigation.TAB_SELECTED, onTabSelected);
|
navigation.on(cons.events.navigation.TAB_SELECTED, onTabSelected);
|
||||||
roomList.on(cons.events.roomList.SPACE_SHORTCUT_UPDATED, onSpaceShortcutUpdated);
|
roomList.on(cons.events.roomList.SPACE_SHORTCUT_UPDATED, onSpaceShortcutUpdated);
|
||||||
initMatrix.roomList.on(
|
roomList.on(cons.events.roomList.INVITELIST_UPDATED, onInviteListChange);
|
||||||
cons.events.roomList.INVITELIST_UPDATED,
|
notifications.on(cons.events.notifications.NOTI_CHANGED, onNotificationChanged);
|
||||||
onInviteListChange,
|
|
||||||
);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
navigation.removeListener(cons.events.navigation.TAB_SELECTED, onTabSelected);
|
navigation.removeListener(cons.events.navigation.TAB_SELECTED, onTabSelected);
|
||||||
roomList.removeListener(cons.events.roomList.SPACE_SHORTCUT_UPDATED, onSpaceShortcutUpdated);
|
roomList.removeListener(cons.events.roomList.SPACE_SHORTCUT_UPDATED, onSpaceShortcutUpdated);
|
||||||
initMatrix.roomList.removeListener(
|
roomList.removeListener(cons.events.roomList.INVITELIST_UPDATED, onInviteListChange);
|
||||||
cons.events.roomList.INVITELIST_UPDATED,
|
notifications.removeListener(cons.events.notifications.NOTI_CHANGED, onNotificationChanged);
|
||||||
onInviteListChange,
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
function getHomeNoti() {
|
||||||
|
const orphans = roomList.getOrphans();
|
||||||
|
let noti = null;
|
||||||
|
|
||||||
|
orphans.forEach((roomId) => {
|
||||||
|
if (!notifications.hasNoti(roomId)) return;
|
||||||
|
if (noti === null) noti = { total: 0, highlight: 0 };
|
||||||
|
const childNoti = notifications.getNoti(roomId);
|
||||||
|
noti.total += childNoti.total;
|
||||||
|
noti.highlight += childNoti.highlight;
|
||||||
|
});
|
||||||
|
|
||||||
|
return noti;
|
||||||
|
}
|
||||||
|
function getDMsNoti() {
|
||||||
|
if (roomList.directs.size === 0) return null;
|
||||||
|
let noti = null;
|
||||||
|
|
||||||
|
[...roomList.directs].forEach((roomId) => {
|
||||||
|
if (!notifications.hasNoti(roomId)) return;
|
||||||
|
if (noti === null) noti = { total: 0, highlight: 0 };
|
||||||
|
const childNoti = notifications.getNoti(roomId);
|
||||||
|
noti.total += childNoti.total;
|
||||||
|
noti.highlight += childNoti.highlight;
|
||||||
|
});
|
||||||
|
|
||||||
|
return noti;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: bellow operations are heavy.
|
||||||
|
// refactor this component into more smaller components.
|
||||||
|
const dmsNoti = getDMsNoti();
|
||||||
|
const homeNoti = getHomeNoti();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="sidebar">
|
<div className="sidebar">
|
||||||
<div className="sidebar__scrollable">
|
<div className="sidebar__scrollable">
|
||||||
<ScrollView invisible>
|
<ScrollView invisible>
|
||||||
<div className="scrollable-content">
|
<div className="scrollable-content">
|
||||||
<div className="featured-container">
|
<div className="featured-container">
|
||||||
<SidebarAvatar active={selectedTab === cons.tabs.HOME} onClick={() => selectTab(cons.tabs.HOME)} tooltip="Home" iconSrc={HomeIC} />
|
<SidebarAvatar
|
||||||
<SidebarAvatar active={selectedTab === cons.tabs.DIRECTS} onClick={() => selectTab(cons.tabs.DIRECTS)} tooltip="People" iconSrc={UserIC} />
|
active={selectedTab === cons.tabs.HOME}
|
||||||
|
onClick={() => selectTab(cons.tabs.HOME)}
|
||||||
|
tooltip="Home"
|
||||||
|
iconSrc={HomeIC}
|
||||||
|
isUnread={homeNoti !== null}
|
||||||
|
notificationCount={homeNoti !== null ? abbreviateNumber(homeNoti.total) : 0}
|
||||||
|
isAlert={homeNoti?.highlight > 0}
|
||||||
|
/>
|
||||||
|
<SidebarAvatar
|
||||||
|
active={selectedTab === cons.tabs.DIRECTS}
|
||||||
|
onClick={() => selectTab(cons.tabs.DIRECTS)}
|
||||||
|
tooltip="People"
|
||||||
|
iconSrc={UserIC}
|
||||||
|
isUnread={dmsNoti !== null}
|
||||||
|
notificationCount={dmsNoti !== null ? abbreviateNumber(dmsNoti.total) : 0}
|
||||||
|
isAlert={dmsNoti?.highlight > 0}
|
||||||
|
/>
|
||||||
<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" />
|
||||||
|
@ -117,6 +168,9 @@ function SideBar() {
|
||||||
bgColor={colorMXID(room.roomId)}
|
bgColor={colorMXID(room.roomId)}
|
||||||
imageSrc={room.getAvatarUrl(initMatrix.matrixClient.baseUrl, 42, 42, 'crop') || null}
|
imageSrc={room.getAvatarUrl(initMatrix.matrixClient.baseUrl, 42, 42, 'crop') || null}
|
||||||
text={room.name.slice(0, 1)}
|
text={room.name.slice(0, 1)}
|
||||||
|
isUnread={notifications.hasNoti(sRoomId)}
|
||||||
|
notificationCount={abbreviateNumber(notifications.getTotalNoti(sRoomId))}
|
||||||
|
isAlert={notifications.getHighlightNoti(sRoomId) !== 0}
|
||||||
onClick={() => selectTab(shortcut)}
|
onClick={() => selectTab(shortcut)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -131,7 +185,9 @@ function SideBar() {
|
||||||
<div className="sticky-container">
|
<div className="sticky-container">
|
||||||
{ totalInvites !== 0 && (
|
{ totalInvites !== 0 && (
|
||||||
<SidebarAvatar
|
<SidebarAvatar
|
||||||
notifyCount={totalInvites}
|
isUnread
|
||||||
|
notificationCount={totalInvites}
|
||||||
|
isAlert
|
||||||
onClick={() => openInviteList()}
|
onClick={() => openInviteList()}
|
||||||
tooltip="Invites"
|
tooltip="Invites"
|
||||||
iconSrc={InviteIC}
|
iconSrc={InviteIC}
|
||||||
|
|
|
@ -58,11 +58,27 @@ class Notifications extends EventEmitter {
|
||||||
return this.roomIdToNoti.get(roomId) || { total: 0, highlight: 0, from: null };
|
return this.roomIdToNoti.get(roomId) || { total: 0, highlight: 0, from: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTotalNoti(roomId) {
|
||||||
|
const { total } = this.getNoti(roomId);
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
getHighlightNoti(roomId) {
|
||||||
|
const { highlight } = this.getNoti(roomId);
|
||||||
|
return highlight;
|
||||||
|
}
|
||||||
|
|
||||||
|
getFromNoti(roomId) {
|
||||||
|
const { from } = this.getNoti(roomId);
|
||||||
|
return from;
|
||||||
|
}
|
||||||
|
|
||||||
hasNoti(roomId) {
|
hasNoti(roomId) {
|
||||||
return this.roomIdToNoti.has(roomId);
|
return this.roomIdToNoti.has(roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
_setNoti(roomId, total, highlight, childId) {
|
_setNoti(roomId, total, highlight, childId) {
|
||||||
|
const prevTotal = this.roomIdToNoti.get(roomId)?.total ?? null;
|
||||||
const noti = this.getNoti(roomId);
|
const noti = this.getNoti(roomId);
|
||||||
|
|
||||||
noti.total += total;
|
noti.total += total;
|
||||||
|
@ -73,7 +89,7 @@ class Notifications extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.roomIdToNoti.set(roomId, noti);
|
this.roomIdToNoti.set(roomId, noti);
|
||||||
this.emit(cons.events.notification.NOTI_CHANGED, roomId);
|
this.emit(cons.events.notifications.NOTI_CHANGED, roomId, noti.total, prevTotal);
|
||||||
|
|
||||||
const parentIds = this.roomList.roomIdToParents.get(roomId);
|
const parentIds = this.roomList.roomIdToParents.get(roomId);
|
||||||
if (typeof parentIds === 'undefined') return;
|
if (typeof parentIds === 'undefined') return;
|
||||||
|
@ -84,6 +100,7 @@ class Notifications extends EventEmitter {
|
||||||
if (this.roomIdToNoti.has(roomId) === false) return;
|
if (this.roomIdToNoti.has(roomId) === false) return;
|
||||||
|
|
||||||
const noti = this.getNoti(roomId);
|
const noti = this.getNoti(roomId);
|
||||||
|
const prevTotal = noti.total;
|
||||||
noti.total -= total;
|
noti.total -= total;
|
||||||
noti.highlight -= highlight;
|
noti.highlight -= highlight;
|
||||||
if (childId && noti.from !== null) {
|
if (childId && noti.from !== null) {
|
||||||
|
@ -91,10 +108,11 @@ class Notifications extends EventEmitter {
|
||||||
}
|
}
|
||||||
if (noti.from === null || noti.from.size === 0) {
|
if (noti.from === null || noti.from.size === 0) {
|
||||||
this.roomIdToNoti.delete(roomId);
|
this.roomIdToNoti.delete(roomId);
|
||||||
this.emit(cons.events.notification.FULL_READ, roomId);
|
this.emit(cons.events.notifications.FULL_READ, roomId);
|
||||||
|
this.emit(cons.events.notifications.NOTI_CHANGED, roomId, null, prevTotal);
|
||||||
} else {
|
} else {
|
||||||
this.roomIdToNoti.set(roomId, noti);
|
this.roomIdToNoti.set(roomId, noti);
|
||||||
this.emit(cons.events.notification.NOTI_CHANGED, roomId);
|
this.emit(cons.events.notifications.NOTI_CHANGED, roomId, noti.total, prevTotal);
|
||||||
}
|
}
|
||||||
|
|
||||||
const parentIds = this.roomList.roomIdToParents.get(roomId);
|
const parentIds = this.roomList.roomIdToParents.get(roomId);
|
||||||
|
@ -120,8 +138,6 @@ class Notifications extends EventEmitter {
|
||||||
|
|
||||||
this.matrixClient.on('Room.receipt', (mEvent, room) => {
|
this.matrixClient.on('Room.receipt', (mEvent, room) => {
|
||||||
if (mEvent.getType() === 'm.receipt') {
|
if (mEvent.getType() === 'm.receipt') {
|
||||||
if (typeof mEvent.event.room_id === 'string') return;
|
|
||||||
|
|
||||||
const content = mEvent.getContent();
|
const content = mEvent.getContent();
|
||||||
const readedEventId = Object.keys(content)[0];
|
const readedEventId = Object.keys(content)[0];
|
||||||
const readerUserId = Object.keys(content[readedEventId]['m.read'])[0];
|
const readerUserId = Object.keys(content[readedEventId]['m.read'])[0];
|
||||||
|
|
|
@ -41,6 +41,15 @@ class RoomList extends EventEmitter {
|
||||||
this.matrixClient.setAccountData(cons['in.cinny.spaces'], spaceContent);
|
this.matrixClient.setAccountData(cons['in.cinny.spaces'], spaceContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isOrphan(roomId) {
|
||||||
|
return !this.roomIdToParents.has(roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrphans() {
|
||||||
|
const rooms = [...this.spaces].concat([...this.rooms]);
|
||||||
|
return rooms.filter((roomId) => !this.roomIdToParents.has(roomId));
|
||||||
|
}
|
||||||
|
|
||||||
getSpaceChildren(roomId) {
|
getSpaceChildren(roomId) {
|
||||||
const space = this.matrixClient.getRoom(roomId);
|
const space = this.matrixClient.getRoom(roomId);
|
||||||
const mSpaceChild = space?.currentState.getStateEvents('m.space.child');
|
const mSpaceChild = space?.currentState.getStateEvents('m.space.child');
|
||||||
|
@ -254,15 +263,6 @@ class RoomList extends EventEmitter {
|
||||||
this.matrixClient.on('Room.name', () => {
|
this.matrixClient.on('Room.name', () => {
|
||||||
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
|
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
|
||||||
});
|
});
|
||||||
this.matrixClient.on('Room.receipt', (event, room) => {
|
|
||||||
if (event.getType() === 'm.receipt') {
|
|
||||||
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.matrixClient.on('RoomState.events', (mEvent) => {
|
this.matrixClient.on('RoomState.events', (mEvent) => {
|
||||||
if (mEvent.getType() === 'm.space.child') {
|
if (mEvent.getType() === 'm.space.child') {
|
||||||
|
@ -387,16 +387,6 @@ class RoomList extends EventEmitter {
|
||||||
}
|
}
|
||||||
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
|
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;
|
|
||||||
if (event.getSender() === this.matrixClient.getUserId()) return;
|
|
||||||
this.emit(cons.events.roomList.EVENT_ARRIVED, room.roomId);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export default RoomList;
|
export default RoomList;
|
||||||
|
|
|
@ -67,11 +67,9 @@ const cons = {
|
||||||
ROOM_JOINED: 'ROOM_JOINED',
|
ROOM_JOINED: 'ROOM_JOINED',
|
||||||
ROOM_LEAVED: 'ROOM_LEAVED',
|
ROOM_LEAVED: 'ROOM_LEAVED',
|
||||||
ROOM_CREATED: 'ROOM_CREATED',
|
ROOM_CREATED: 'ROOM_CREATED',
|
||||||
MY_RECEIPT_ARRIVED: 'MY_RECEIPT_ARRIVED',
|
|
||||||
EVENT_ARRIVED: 'EVENT_ARRIVED',
|
|
||||||
SPACE_SHORTCUT_UPDATED: 'SPACE_SHORTCUT_UPDATED',
|
SPACE_SHORTCUT_UPDATED: 'SPACE_SHORTCUT_UPDATED',
|
||||||
},
|
},
|
||||||
notification: {
|
notifications: {
|
||||||
NOTI_CHANGED: 'NOTI_CHANGED',
|
NOTI_CHANGED: 'NOTI_CHANGED',
|
||||||
FULL_READ: 'FULL_READ',
|
FULL_READ: 'FULL_READ',
|
||||||
},
|
},
|
||||||
|
|
|
@ -220,6 +220,8 @@
|
||||||
--bg-surface-low: hsl(64, 6%, 10%);
|
--bg-surface-low: hsl(64, 6%, 10%);
|
||||||
--bg-surface-low-transparent: hsla(64, 6%, 14%, 0);
|
--bg-surface-low-transparent: hsla(64, 6%, 14%, 0);
|
||||||
|
|
||||||
|
--bg-badge: #c4c1ab;
|
||||||
|
|
||||||
|
|
||||||
/* text color | --tc-[background type]-[priority]: value */
|
/* text color | --tc-[background type]-[priority]: value */
|
||||||
--tc-surface-high: rgb(255, 251, 222, 94%);
|
--tc-surface-high: rgb(255, 251, 222, 94%);
|
||||||
|
|
Loading…
Reference in a new issue