Show unsent message warning on Space panel button

This commit is contained in:
Michael Telatynski 2021-09-10 09:15:54 +01:00
parent 329bc8a89e
commit bbe714257e
6 changed files with 43 additions and 9 deletions

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from "react"; import React, { MouseEvent } from "react";
import classNames from "classnames"; import classNames from "classnames";
import { formatCount } from "../../../utils/FormattingUtils"; import { formatCount } from "../../../utils/FormattingUtils";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
@ -22,6 +22,9 @@ import AccessibleButton from "../elements/AccessibleButton";
import { XOR } from "../../../@types/common"; import { XOR } from "../../../@types/common";
import { NOTIFICATION_STATE_UPDATE, NotificationState } from "../../../stores/notifications/NotificationState"; import { NOTIFICATION_STATE_UPDATE, NotificationState } from "../../../stores/notifications/NotificationState";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import Tooltip from "../elements/Tooltip";
import { _t } from "../../../languageHandler";
import { NotificationColor } from "../../../stores/notifications/NotificationColor";
interface IProps { interface IProps {
notification: NotificationState; notification: NotificationState;
@ -39,6 +42,7 @@ interface IProps {
} }
interface IClickableProps extends IProps, React.InputHTMLAttributes<Element> { interface IClickableProps extends IProps, React.InputHTMLAttributes<Element> {
showUnsentTooltip?: boolean;
/** /**
* If specified will return an AccessibleButton instead of a div. * If specified will return an AccessibleButton instead of a div.
*/ */
@ -47,6 +51,7 @@ interface IClickableProps extends IProps, React.InputHTMLAttributes<Element> {
interface IState { interface IState {
showCounts: boolean; // whether or not to show counts. Independent of props.forceCount showCounts: boolean; // whether or not to show counts. Independent of props.forceCount
showTooltip: boolean;
} }
@replaceableComponent("views.rooms.NotificationBadge") @replaceableComponent("views.rooms.NotificationBadge")
@ -59,6 +64,7 @@ export default class NotificationBadge extends React.PureComponent<XOR<IProps, I
this.state = { this.state = {
showCounts: SettingsStore.getValue("Notifications.alwaysShowBadgeCounts", this.roomId), showCounts: SettingsStore.getValue("Notifications.alwaysShowBadgeCounts", this.roomId),
showTooltip: false,
}; };
this.countWatcherRef = SettingsStore.watchSetting( this.countWatcherRef = SettingsStore.watchSetting(
@ -93,9 +99,22 @@ export default class NotificationBadge extends React.PureComponent<XOR<IProps, I
this.forceUpdate(); // notification state changed - update this.forceUpdate(); // notification state changed - update
}; };
private onMouseOver = (e: MouseEvent) => {
e.stopPropagation();
this.setState({
showTooltip: true,
});
};
private onMouseLeave = () => {
this.setState({
showTooltip: false,
});
};
public render(): React.ReactElement { public render(): React.ReactElement {
/* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */ /* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */
const { notification, forceCount, roomId, onClick, ...props } = this.props; const { notification, showUnsentTooltip, forceCount, roomId, onClick, ...props } = this.props;
// Don't show a badge if we don't need to // Don't show a badge if we don't need to
if (notification.isIdle) return null; if (notification.isIdle) return null;
@ -124,9 +143,23 @@ export default class NotificationBadge extends React.PureComponent<XOR<IProps, I
}); });
if (onClick) { if (onClick) {
let tooltip;
if (showUnsentTooltip && this.state.showTooltip && notification.color === NotificationColor.Unsent) {
tooltip = (
<Tooltip className="mx_RoleButton_tooltip" label={_t("Message didn't send. Click for info.")} />
);
}
return ( return (
<AccessibleButton {...props} className={classes} onClick={onClick}> <AccessibleButton
{...props}
className={classes}
onClick={onClick}
onMouseOver={this.onMouseOver}
onMouseLeave={this.onMouseLeave}
>
<span className="mx_NotificationBadge_count">{ symbol }</span> <span className="mx_NotificationBadge_count">{ symbol }</span>
{ tooltip }
</AccessibleButton> </AccessibleButton>
); );
} }

View file

@ -93,6 +93,7 @@ export const SpaceButton: React.FC<IButtonProps> = ({
notification={notificationState} notification={notificationState}
aria-label={ariaLabel} aria-label={ariaLabel}
tabIndex={tabIndex} tabIndex={tabIndex}
showUnsentTooltip={true}
/> />
</div>; </div>;
} }

View file

@ -1587,6 +1587,7 @@
"Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites.": "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites.", "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites.": "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites.",
"Enable encryption in settings.": "Enable encryption in settings.", "Enable encryption in settings.": "Enable encryption in settings.",
"End-to-end encryption isn't enabled": "End-to-end encryption isn't enabled", "End-to-end encryption isn't enabled": "End-to-end encryption isn't enabled",
"Message didn't send. Click for info.": "Message didn't send. Click for info.",
"Unpin": "Unpin", "Unpin": "Unpin",
"View message": "View message", "View message": "View message",
"%(duration)ss": "%(duration)ss", "%(duration)ss": "%(duration)ss",

View file

@ -21,4 +21,5 @@ export enum NotificationColor {
Bold, // no badge, show as unread Bold, // no badge, show as unread
Grey, // unread notified messages Grey, // unread notified messages
Red, // unread pings Red, // unread pings
Unsent, // some messages failed to send
} }

View file

@ -88,7 +88,7 @@ export class RoomNotificationState extends NotificationState implements IDestroy
if (getUnsentMessages(this.room).length > 0) { if (getUnsentMessages(this.room).length > 0) {
// When there are unsent messages we show a red `!` // When there are unsent messages we show a red `!`
this._color = NotificationColor.Red; this._color = NotificationColor.Unsent;
this._symbol = "!"; this._symbol = "!";
this._count = 1; // not used, technically this._count = 1; // not used, technically
} else if (RoomNotifs.getRoomNotifsState(this.room.roomId) === RoomNotifs.MUTE) { } else if (RoomNotifs.getRoomNotifsState(this.room.roomId) === RoomNotifs.MUTE) {

View file

@ -30,10 +30,6 @@ export class SpaceNotificationState extends NotificationState {
super(); super();
} }
public get symbol(): string {
return null; // This notification state doesn't support symbols
}
public setRooms(rooms: Room[]) { public setRooms(rooms: Room[]) {
const oldRooms = this.rooms; const oldRooms = this.rooms;
const diff = arrayDiff(oldRooms, rooms); const diff = arrayDiff(oldRooms, rooms);
@ -54,7 +50,7 @@ export class SpaceNotificationState extends NotificationState {
} }
public getFirstRoomWithNotifications() { public getFirstRoomWithNotifications() {
return this.rooms.find((room) => room.getUnreadNotificationCount() > 0).roomId; return Object.values(this.states).find(state => state.color >= this.color)?.room.roomId;
} }
public destroy() { public destroy() {
@ -79,6 +75,8 @@ export class SpaceNotificationState extends NotificationState {
this._color = Math.max(this.color, state.color); this._color = Math.max(this.color, state.color);
} }
this._symbol = this._color === NotificationColor.Unsent ? "!" : null;
// finally, publish an update if needed // finally, publish an update if needed
this.emitIfUpdated(snapshot); this.emitIfUpdated(snapshot);
} }