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
parent
766d1ee3e8
commit
8905c5d2bb
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 });
|
||||||
|
|
Loading…
Reference in New Issue