Add unread indicator to the timelineCard header icon (#7156)

Co-authored-by: J. Ryan Stinnett <jryans@gmail.com>
Co-authored-by: Travis Ralston <travisr@matrix.org>
pull/21833/head
Timo 2021-11-30 11:06:20 +01:00 committed by GitHub
parent 766d1ee3e8
commit 8905c5d2bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 92 additions and 33 deletions

View File

@ -103,7 +103,7 @@ limitations under the License.
mask-position: center; mask-position: center;
} }
$dot-size: 8px; $dot-size: 7px;
$pulse-color: $alert; $pulse-color: $alert;
.mx_RightPanel_pinnedMessagesButton { .mx_RightPanel_pinnedMessagesButton {
@ -111,36 +111,53 @@ $pulse-color: $alert;
mask-image: url('$(res)/img/element-icons/room/pin.svg'); mask-image: url('$(res)/img/element-icons/room/pin.svg');
mask-position: center; mask-position: center;
} }
}
.mx_RightPanel_headerButton_unreadIndicator_bg {
position: absolute;
right: 0;
top: 0;
margin: 4px;
width: $dot-size;
height: $dot-size;
border-radius: 50%;
transform: scale(1.6);
transform-origin: center center;
background: rgba($background, 1);
}
.mx_RightPanel_pinnedMessagesButton_unreadIndicator { .mx_RightPanel_headerButton_unreadIndicator {
position: absolute;
right: 0;
top: 0;
margin: 4px;
width: $dot-size;
height: $dot-size;
border-radius: 50%;
transform: scale(1);
background: rgba($pulse-color, 1);
box-shadow: 0 0 0 0 rgba($pulse-color, 1);
animation: mx_RightPanel_indicator_pulse 2s infinite;
animation-iteration-count: 1;
&.mx_Indicator_gray {
background: rgba($input-darker-fg-color, 1);
box-shadow: rgba($input-darker-fg-color, 1);
}
&::after {
content: "";
position: absolute; position: absolute;
right: 0; width: inherit;
height: inherit;
top: 0; top: 0;
margin: 4px; left: 0;
width: $dot-size;
height: $dot-size;
border-radius: 50%;
transform: scale(1); transform: scale(1);
background: rgba($pulse-color, 1); transform-origin: center center;
box-shadow: 0 0 0 0 rgba($pulse-color, 1); animation-name: mx_RightPanel_indicator_pulse_shadow;
animation: mx_RightPanel_indicator_pulse 2s infinite; animation-duration: inherit;
animation-iteration-count: 1; animation-iteration-count: inherit;
border-radius: 50%;
&::after { background: inherit;
content: "";
position: absolute;
width: inherit;
height: inherit;
top: 0;
left: 0;
transform: scale(1);
transform-origin: center center;
animation-name: mx_RightPanel_indicator_pulse_shadow;
animation-duration: inherit;
animation-iteration-count: inherit;
border-radius: 50%;
background: rgba($pulse-color, 1);
}
} }
} }

View File

@ -34,6 +34,8 @@ import { useReadPinnedEvents, usePinnedEvents } from './PinnedMessagesCard';
import { dispatchShowThreadsPanelEvent } from "../../../dispatcher/dispatch-actions/threads"; import { dispatchShowThreadsPanelEvent } from "../../../dispatcher/dispatch-actions/threads";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
import { NotificationColor } from "../../../stores/notifications/NotificationColor";
const ROOM_INFO_PHASES = [ const ROOM_INFO_PHASES = [
RightPanelPhases.RoomSummary, RightPanelPhases.RoomSummary,
@ -45,7 +47,24 @@ const ROOM_INFO_PHASES = [
RightPanelPhases.Room3pidMemberInfo, RightPanelPhases.Room3pidMemberInfo,
]; ];
const PinnedMessagesHeaderButton = ({ room, isHighlighted, onClick }) => { interface IUnreadIndicatorProps {
className: string;
}
const UnreadIndicator = ({ className }: IUnreadIndicatorProps) => {
return <React.Fragment>
<div className="mx_RightPanel_headerButton_unreadIndicator_bg" />
<div className={className} />
</React.Fragment>;
};
interface IHeaderButtonProps {
room: Room;
isHighlighted: boolean;
onClick: () => void;
}
const PinnedMessagesHeaderButton = ({ room, isHighlighted, onClick }: IHeaderButtonProps) => {
const pinningEnabled = useSettingValue("feature_pinning"); const pinningEnabled = useSettingValue("feature_pinning");
const pinnedEvents = usePinnedEvents(pinningEnabled && room); const pinnedEvents = usePinnedEvents(pinningEnabled && room);
const readPinnedEvents = useReadPinnedEvents(pinningEnabled && room); const readPinnedEvents = useReadPinnedEvents(pinningEnabled && room);
@ -53,7 +72,7 @@ const PinnedMessagesHeaderButton = ({ room, isHighlighted, onClick }) => {
let unreadIndicator; let unreadIndicator;
if (pinnedEvents.some(id => !readPinnedEvents.has(id))) { if (pinnedEvents.some(id => !readPinnedEvents.has(id))) {
unreadIndicator = <div className="mx_RightPanel_pinnedMessagesButton_unreadIndicator" />; unreadIndicator = <UnreadIndicator className="mx_RightPanel_headerButton_unreadIndicator" />;
} }
return <HeaderButton return <HeaderButton
@ -67,16 +86,30 @@ const PinnedMessagesHeaderButton = ({ room, isHighlighted, onClick }) => {
</HeaderButton>; </HeaderButton>;
}; };
const TimelineCardHeaderButton = ({ room, isHighlighted, onClick }) => { const TimelineCardHeaderButton = ({ room, isHighlighted, onClick }: IHeaderButtonProps) => {
if (!SettingsStore.getValue("feature_maximised_widgets")) return null; if (!SettingsStore.getValue("feature_maximised_widgets")) return null;
let unreadIndicator;
switch (RoomNotificationStateStore.instance.getRoomState(room).color) {
case NotificationColor.Grey:
unreadIndicator =
<UnreadIndicator className="mx_RightPanel_headerButton_unreadIndicator mx_Indicator_gray" />;
break;
case NotificationColor.Red:
unreadIndicator =
<UnreadIndicator className="mx_RightPanel_headerButton_unreadIndicator" />;
break;
default:
break;
}
return <HeaderButton return <HeaderButton
name="timelineCardButton" name="timelineCardButton"
title={_t("Chat")} title={_t("Chat")}
isHighlighted={isHighlighted} isHighlighted={isHighlighted}
onClick={onClick} onClick={onClick}
analytics={["Right Panel", "Timeline Panel Button", "click"]} analytics={["Right Panel", "Timeline Panel Button", "click"]}
/>; >
{ unreadIndicator }
</HeaderButton>;
}; };
interface IProps { interface IProps {

View File

@ -39,6 +39,8 @@ import { SearchScope } from './SearchBar';
import { ContextMenuTooltipButton } from '../../structures/ContextMenu'; import { ContextMenuTooltipButton } from '../../structures/ContextMenu';
import RoomContextMenu from "../context_menus/RoomContextMenu"; import RoomContextMenu from "../context_menus/RoomContextMenu";
import { contextMenuBelow } from './RoomTile'; import { contextMenuBelow } from './RoomTile';
import { RoomNotificationStateStore } from '../../../stores/notifications/RoomNotificationStateStore';
import { NOTIFICATION_STATE_UPDATE } from '../../../stores/notifications/NotificationState';
import { RightPanelPhases } from '../../../stores/RightPanelStorePhases'; import { RightPanelPhases } from '../../../stores/RightPanelStorePhases';
export interface ISearchInfo { export interface ISearchInfo {
@ -75,7 +77,8 @@ export default class RoomHeader extends React.Component<IProps, IState> {
constructor(props, context) { constructor(props, context) {
super(props, context); super(props, context);
const notiStore = RoomNotificationStateStore.instance.getRoomState(props.room);
notiStore.on(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate);
this.state = {}; this.state = {};
} }
@ -89,6 +92,8 @@ export default class RoomHeader extends React.Component<IProps, IState> {
if (cli) { if (cli) {
cli.removeListener("RoomState.events", this.onRoomStateEvents); cli.removeListener("RoomState.events", this.onRoomStateEvents);
} }
const notiStore = RoomNotificationStateStore.instance.getRoomState(this.props.room);
notiStore.removeListener(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate);
} }
private onRoomStateEvents = (event: MatrixEvent, state: RoomState) => { private onRoomStateEvents = (event: MatrixEvent, state: RoomState) => {
@ -100,6 +105,10 @@ export default class RoomHeader extends React.Component<IProps, IState> {
this.rateLimitedUpdate(); this.rateLimitedUpdate();
}; };
private onNotificationUpdate = () => {
this.forceUpdate();
};
private rateLimitedUpdate = throttle(() => { private rateLimitedUpdate = throttle(() => {
this.forceUpdate(); this.forceUpdate();
}, 500, { leading: true, trailing: true }); }, 500, { leading: true, trailing: true });