Merge branch 'ajbura:dev' into profile-editor
This commit is contained in:
commit
95bb0ac6d4
39 changed files with 813 additions and 186 deletions
5
.github/workflows/docker.yaml
vendored
5
.github/workflows/docker.yaml
vendored
|
|
@ -1,9 +1,8 @@
|
||||||
name: Publish Docker image
|
name: Publish Docker image
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
release:
|
||||||
branches:
|
types: [published]
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
push_to_registry:
|
push_to_registry:
|
||||||
|
|
|
||||||
2
.github/workflows/netlify-dev.yaml
vendored
2
.github/workflows/netlify-dev.yaml
vendored
|
|
@ -1,4 +1,4 @@
|
||||||
name: 'Deploy to Netlify'
|
name: 'Deploy to Netlify (dev)'
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|
|
||||||
7
.github/workflows/netlify-prod.yaml
vendored
7
.github/workflows/netlify-prod.yaml
vendored
|
|
@ -1,9 +1,8 @@
|
||||||
name: 'Deploy to Netlify'
|
name: 'Deploy to Netlify (prod)'
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
release:
|
||||||
branches:
|
types: [published]
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@
|
||||||
|
|
||||||
Cinny is a [Matrix](https://matrix.org) client focusing primarily on simple, elegant and secure interface.
|
Cinny is a [Matrix](https://matrix.org) client focusing primarily on simple, elegant and secure interface.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Building and Running
|
## Building and Running
|
||||||
|
|
||||||
### Building from source
|
### Building from source
|
||||||
|
|
|
||||||
12
public/res/ic/outlined/bell-off.svg
Normal file
12
public/res/ic/outlined/bell-off.svg
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<path d="M12,22c1.1,0,2-0.9,2-2h-4C10,21.1,10.9,22,12,22z"/>
|
||||||
|
<path d="M20.1,18.1L20.1,18.1L16,14L9.2,7.2L7.8,5.8L5.9,3.9L4.5,5.3l2.1,2.1C6.2,8.2,6,9.1,6,10v6H4v2h13.2l1.5,1.5L20.1,18.1z
|
||||||
|
M8,16v-6c0-0.4,0.1-0.7,0.1-1l7,7H8z"/>
|
||||||
|
<path d="M12,6c2.2,0,4,1.8,4,4v1.2l2,2V10c0-3-2.2-5.4-5-5.9V3h-2v1.1c-0.6,0.1-1.1,0.3-1.6,0.5L11,6.1C11.3,6.1,11.6,6,12,6z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 810 B |
13
public/res/ic/outlined/bell-ping.svg
Normal file
13
public/res/ic/outlined/bell-ping.svg
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<circle cx="17" cy="8" r="3"/>
|
||||||
|
<path d="M12,22c1.1,0,2-0.9,2-2h-4C10,21.1,10.9,22,12,22z"/>
|
||||||
|
<path d="M18,12.9C17.7,13,17.3,13,17,13s-0.7,0-1-0.1V16H8v-6c0-2.2,1.8-4,4-4c0.1,0,0.3,0,0.4,0c0.3-0.7,0.7-1.3,1.3-1.8
|
||||||
|
c-0.2-0.1-0.5-0.1-0.7-0.2V3h-2v1.1C8.2,4.6,6,7,6,10v6H4v2h16v-2h-2V12.9z"/>
|
||||||
|
<path d="M6.3,4.3L4.9,2.9C3.1,4.7,2,7.2,2,10h2C4,7.8,4.9,5.8,6.3,4.3z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 819 B |
12
public/res/ic/outlined/bell-ring.svg
Normal file
12
public/res/ic/outlined/bell-ring.svg
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<path d="M12,22c1.1,0,2-0.9,2-2h-4C10,21.1,10.9,22,12,22z"/>
|
||||||
|
<path d="M18,10c0-3-2.2-5.4-5-5.9V3h-2v1.1C8.2,4.6,6,7,6,10v6H4v2h16v-2h-2V10z M16,16H8v-6c0-2.2,1.8-4,4-4s4,1.8,4,4V16z"/>
|
||||||
|
<path d="M6.3,4.3L4.9,2.9C3.1,4.7,2,7.2,2,10h2C4,7.8,4.9,5.8,6.3,4.3z"/>
|
||||||
|
<path d="M19.1,2.9l-1.4,1.4C19.1,5.8,20,7.8,20,10h2C22,7.2,20.9,4.7,19.1,2.9z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 796 B |
|
|
@ -4,8 +4,7 @@
|
||||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||||
<g>
|
<g>
|
||||||
<path d="M12,4c2.8,0,5,2.2,5,5v4v0.8l0.6,0.6l0.6,0.6H5.8l0.6-0.6L7,13.8V13V9C7,6.2,9.2,4,12,4 M12,2C8.1,2,5,5.1,5,9v4l-2,2v2h18
|
<path d="M12,22c1.1,0,2-0.9,2-2h-4C10,21.1,10.9,22,12,22z"/>
|
||||||
v-2l-2-2V9C19,5.1,15.9,2,12,2L12,2z"/>
|
<path d="M18,16v-6c0-3-2.2-5.4-5-5.9V3h-2v1.1C8.2,4.6,6,7,6,10v6H4v2h16v-2H18z M16,16H8v-6c0-2.2,1.8-4,4-4s4,1.8,4,4V16z"/>
|
||||||
<path d="M9,19c0,1.7,1.3,3,3,3s3-1.3,3-3H9z"/>
|
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 671 B After Width: | Height: | Size: 640 B |
|
|
@ -8,7 +8,7 @@ function NotificationBadge({ alert, content }) {
|
||||||
const notificationClass = alert ? ' notification-badge--alert' : '';
|
const notificationClass = alert ? ' notification-badge--alert' : '';
|
||||||
return (
|
return (
|
||||||
<div className={`notification-badge${notificationClass}`}>
|
<div className={`notification-badge${notificationClass}`}>
|
||||||
{content && <Text variant="b3">{content}</Text>}
|
{content !== null && <Text variant="b3">{content}</Text>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,18 @@
|
||||||
min-width: 16px;
|
min-width: 16px;
|
||||||
min-height: 8px;
|
min-height: 8px;
|
||||||
padding: 0 var(--sp-ultra-tight);
|
padding: 0 var(--sp-ultra-tight);
|
||||||
background-color: var(--tc-surface-low);
|
background-color: var(--bg-badge);
|
||||||
border-radius: var(--bo-radius);
|
border-radius: var(--bo-radius);
|
||||||
|
|
||||||
.text {
|
.text {
|
||||||
color: white;
|
color: var(--tc-badge);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
&--alert {
|
&--alert {
|
||||||
background-color: var(--bg-danger);
|
background-color: var(--bg-positive);
|
||||||
|
& .text { color: white }
|
||||||
}
|
}
|
||||||
|
|
||||||
&:empty {
|
&:empty {
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ Button.defaultProps = {
|
||||||
Button.propTypes = {
|
Button.propTypes = {
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
variant: PropTypes.oneOf(['surface', 'primary', 'caution', 'danger']),
|
variant: PropTypes.oneOf(['surface', 'primary', 'positive', 'caution', 'danger']),
|
||||||
iconSrc: PropTypes.string,
|
iconSrc: PropTypes.string,
|
||||||
type: PropTypes.oneOf(['button', 'submit']),
|
type: PropTypes.oneOf(['button', 'submit']),
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
.btn-surface,
|
.btn-surface,
|
||||||
.btn-primary,
|
.btn-primary,
|
||||||
|
.btn-positive,
|
||||||
.btn-caution,
|
.btn-caution,
|
||||||
.btn-danger {
|
.btn-danger {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
|
@ -67,6 +68,13 @@
|
||||||
@include state.focus(var(--bs-primary-outline));
|
@include state.focus(var(--bs-primary-outline));
|
||||||
@include state.active(var(--bg-primary-active));
|
@include state.active(var(--bg-primary-active));
|
||||||
}
|
}
|
||||||
|
.btn-positive {
|
||||||
|
box-shadow: var(--bs-positive-border);
|
||||||
|
@include color(var(--tc-positive-high), var(--ic-positive-normal));
|
||||||
|
@include state.hover(var(--bg-positive-hover));
|
||||||
|
@include state.focus(var(--bs-positive-outline));
|
||||||
|
@include state.active(var(--bg-positive-active));
|
||||||
|
}
|
||||||
.btn-caution {
|
.btn-caution {
|
||||||
box-shadow: var(--bs-caution-border);
|
box-shadow: var(--bs-caution-border);
|
||||||
@include color(var(--tc-caution-high), var(--ic-caution-normal));
|
@include color(var(--tc-caution-high), var(--ic-caution-normal));
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ MenuItem.defaultProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuItem.propTypes = {
|
MenuItem.propTypes = {
|
||||||
variant: PropTypes.oneOf(['surface', 'caution', 'danger']),
|
variant: PropTypes.oneOf(['surface', 'positive', 'caution', 'danger']),
|
||||||
iconSrc: PropTypes.string,
|
iconSrc: PropTypes.string,
|
||||||
type: PropTypes.oneOf(['button', 'submit']),
|
type: PropTypes.oneOf(['button', 'submit']),
|
||||||
onClick: PropTypes.func.isRequired,
|
onClick: PropTypes.func.isRequired,
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,9 @@
|
||||||
|
|
||||||
.text {
|
.text {
|
||||||
color: var(--tc-surface-low);
|
color: var(--tc-surface-low);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(:first-child) {
|
&:not(:first-child) {
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,12 @@ import NotificationBadge from '../../atoms/badge/NotificationBadge';
|
||||||
import { blurOnBubbling } from '../../atoms/button/script';
|
import { blurOnBubbling } from '../../atoms/button/script';
|
||||||
|
|
||||||
function RoomSelectorWrapper({
|
function RoomSelectorWrapper({
|
||||||
isSelected, onClick, content, options,
|
isSelected, isUnread, onClick, content, options,
|
||||||
}) {
|
}) {
|
||||||
|
let myClass = isUnread ? ' room-selector--unread' : '';
|
||||||
|
myClass += isSelected ? ' room-selector--selected' : '';
|
||||||
return (
|
return (
|
||||||
<div className={`room-selector${isSelected ? ' room-selector--selected' : ''}`}>
|
<div className={`room-selector${myClass}`}>
|
||||||
<button
|
<button
|
||||||
className="room-selector__content"
|
className="room-selector__content"
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -31,6 +33,7 @@ RoomSelectorWrapper.defaultProps = {
|
||||||
};
|
};
|
||||||
RoomSelectorWrapper.propTypes = {
|
RoomSelectorWrapper.propTypes = {
|
||||||
isSelected: PropTypes.bool.isRequired,
|
isSelected: PropTypes.bool.isRequired,
|
||||||
|
isUnread: PropTypes.bool.isRequired,
|
||||||
onClick: PropTypes.func.isRequired,
|
onClick: PropTypes.func.isRequired,
|
||||||
content: PropTypes.node.isRequired,
|
content: PropTypes.node.isRequired,
|
||||||
options: PropTypes.node,
|
options: PropTypes.node,
|
||||||
|
|
@ -44,6 +47,7 @@ function RoomSelector({
|
||||||
return (
|
return (
|
||||||
<RoomSelectorWrapper
|
<RoomSelectorWrapper
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
|
isUnread={isUnread}
|
||||||
content={(
|
content={(
|
||||||
<>
|
<>
|
||||||
<Avatar
|
<Avatar
|
||||||
|
|
@ -68,6 +72,7 @@ function RoomSelector({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
RoomSelector.defaultProps = {
|
RoomSelector.defaultProps = {
|
||||||
|
isSelected: false,
|
||||||
imageSrc: null,
|
imageSrc: null,
|
||||||
iconSrc: null,
|
iconSrc: null,
|
||||||
options: null,
|
options: null,
|
||||||
|
|
@ -77,9 +82,12 @@ RoomSelector.propTypes = {
|
||||||
roomId: PropTypes.string.isRequired,
|
roomId: PropTypes.string.isRequired,
|
||||||
imageSrc: PropTypes.string,
|
imageSrc: PropTypes.string,
|
||||||
iconSrc: PropTypes.string,
|
iconSrc: PropTypes.string,
|
||||||
isSelected: PropTypes.bool.isRequired,
|
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,
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,13 @@
|
||||||
border-radius: var(--bo-radius);
|
border-radius: var(--bo-radius);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
&--unread {
|
||||||
|
.room-selector__content > .text {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--tc-surface-high);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&--selected {
|
&--selected {
|
||||||
background-color: var(--bg-surface);
|
background-color: var(--bg-surface);
|
||||||
border-color: var(--bg-surface-border);
|
border-color: var(--bg-surface-border);
|
||||||
|
|
|
||||||
|
|
@ -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,10 +26,11 @@ 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() {
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue