2020-05-08 21:53:05 +03:00
|
|
|
/*
|
|
|
|
Copyright 2015, 2016 OpenMarket Ltd
|
|
|
|
Copyright 2017 New Vector Ltd
|
|
|
|
Copyright 2018 Michael Telatynski <7t3chguy@gmail.com>
|
|
|
|
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2020-07-07 12:34:42 +03:00
|
|
|
import React, {createRef} from "react";
|
2020-05-08 21:53:05 +03:00
|
|
|
import { Room } from "matrix-js-sdk/src/models/room";
|
|
|
|
import classNames from "classnames";
|
|
|
|
import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex";
|
2020-06-16 23:43:48 +03:00
|
|
|
import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton";
|
2020-05-14 22:45:17 +03:00
|
|
|
import dis from '../../../dispatcher/dispatcher';
|
2020-05-12 01:29:32 +03:00
|
|
|
import { Key } from "../../../Keyboard";
|
2020-06-05 23:08:20 +03:00
|
|
|
import ActiveRoomObserver from "../../../ActiveRoomObserver";
|
2020-06-10 08:09:15 +03:00
|
|
|
import { _t } from "../../../languageHandler";
|
2020-07-05 20:23:57 +03:00
|
|
|
import {
|
2020-07-07 17:01:27 +03:00
|
|
|
ChevronFace,
|
2020-07-05 20:23:57 +03:00
|
|
|
ContextMenu,
|
2020-07-17 20:16:00 +03:00
|
|
|
ContextMenuTooltipButton,
|
2020-07-05 20:23:57 +03:00
|
|
|
MenuItemRadio,
|
|
|
|
MenuItemCheckbox,
|
|
|
|
MenuItem,
|
|
|
|
} from "../../structures/ContextMenu";
|
2020-06-10 08:09:15 +03:00
|
|
|
import { DefaultTagID, TagID } from "../../../stores/room-list/models";
|
2020-07-24 07:24:07 +03:00
|
|
|
import { MessagePreviewStore, ROOM_PREVIEW_CHANGED } from "../../../stores/room-list/MessagePreviewStore";
|
2020-07-01 01:24:46 +03:00
|
|
|
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
|
2020-07-05 03:07:46 +03:00
|
|
|
import {
|
|
|
|
getRoomNotifsState,
|
|
|
|
setRoomNotifsState,
|
|
|
|
ALL_MESSAGES,
|
|
|
|
ALL_MESSAGES_LOUD,
|
|
|
|
MENTIONS_ONLY,
|
|
|
|
MUTE,
|
|
|
|
} from "../../../RoomNotifs";
|
2020-07-01 00:53:30 +03:00
|
|
|
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
2020-06-30 22:34:44 +03:00
|
|
|
import NotificationBadge from "./NotificationBadge";
|
2020-07-07 16:07:35 +03:00
|
|
|
import { Volume } from "../../../RoomNotifsTypes";
|
2020-07-18 00:11:34 +03:00
|
|
|
import RoomListStore from "../../../stores/room-list/RoomListStore";
|
2020-07-08 01:14:04 +03:00
|
|
|
import RoomListActions from "../../../actions/RoomListActions";
|
2020-07-07 12:34:42 +03:00
|
|
|
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
|
|
|
import {ActionPayload} from "../../../dispatcher/payloads";
|
2020-07-09 04:26:25 +03:00
|
|
|
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
|
2020-07-27 16:39:30 +03:00
|
|
|
import {NOTIFICATION_STATE_UPDATE, NotificationState} from "../../../stores/notifications/NotificationState";
|
2020-07-17 20:16:00 +03:00
|
|
|
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
2020-05-08 21:53:05 +03:00
|
|
|
|
|
|
|
interface IProps {
|
|
|
|
room: Room;
|
2020-06-05 06:21:04 +03:00
|
|
|
showMessagePreview: boolean;
|
2020-06-11 23:39:28 +03:00
|
|
|
isMinimized: boolean;
|
2020-06-16 21:13:12 +03:00
|
|
|
tag: TagID;
|
2020-05-08 21:53:05 +03:00
|
|
|
|
2020-06-29 05:03:04 +03:00
|
|
|
// TODO: Incoming call boxes: https://github.com/vector-im/riot-web/issues/14177
|
2020-05-08 21:53:05 +03:00
|
|
|
}
|
|
|
|
|
2020-07-02 01:06:26 +03:00
|
|
|
type PartialDOMRect = Pick<DOMRect, "left" | "bottom">;
|
|
|
|
|
2020-05-23 03:05:09 +03:00
|
|
|
interface IState {
|
2020-05-12 01:20:26 +03:00
|
|
|
hover: boolean;
|
2020-06-05 23:08:20 +03:00
|
|
|
selected: boolean;
|
2020-07-02 01:06:26 +03:00
|
|
|
notificationsMenuPosition: PartialDOMRect;
|
|
|
|
generalMenuPosition: PartialDOMRect;
|
2020-05-08 21:53:05 +03:00
|
|
|
}
|
|
|
|
|
2020-07-18 00:43:29 +03:00
|
|
|
const messagePreviewId = (roomId: string) => `mx_RoomTile_messagePreview_${roomId}`;
|
2020-07-05 21:38:45 +03:00
|
|
|
|
2020-07-02 01:06:26 +03:00
|
|
|
const contextMenuBelow = (elementRect: PartialDOMRect) => {
|
2020-07-01 16:05:33 +03:00
|
|
|
// align the context menu's icons with the icon which opened the context menu
|
|
|
|
const left = elementRect.left + window.pageXOffset - 9;
|
2020-07-02 01:09:02 +03:00
|
|
|
const top = elementRect.bottom + window.pageYOffset + 17;
|
2020-07-02 01:56:57 +03:00
|
|
|
const chevronFace = ChevronFace.None;
|
2020-06-30 01:02:10 +03:00
|
|
|
return {left, top, chevronFace};
|
|
|
|
};
|
|
|
|
|
2020-07-01 00:32:59 +03:00
|
|
|
interface INotifOptionProps {
|
|
|
|
active: boolean;
|
|
|
|
iconClassName: string;
|
|
|
|
label: string;
|
2020-07-01 00:53:30 +03:00
|
|
|
onClick(ev: ButtonEvent);
|
2020-07-01 00:32:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
const NotifOption: React.FC<INotifOptionProps> = ({active, onClick, iconClassName, label}) => {
|
|
|
|
const classes = classNames({
|
2020-07-18 00:43:29 +03:00
|
|
|
mx_RoomTile_contextMenu_activeRow: active,
|
2020-07-01 00:32:59 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
let activeIcon;
|
|
|
|
if (active) {
|
2020-07-18 00:43:29 +03:00
|
|
|
activeIcon = <span className="mx_IconizedContextMenu_icon mx_RoomTile_iconCheck" />;
|
2020-07-01 00:32:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<MenuItemRadio className={classes} onClick={onClick} active={active} label={label}>
|
|
|
|
<span className={classNames("mx_IconizedContextMenu_icon", iconClassName)} />
|
|
|
|
<span className="mx_IconizedContextMenu_label">{ label }</span>
|
|
|
|
{ activeIcon }
|
|
|
|
</MenuItemRadio>
|
|
|
|
);
|
|
|
|
};
|
2020-05-08 21:53:05 +03:00
|
|
|
|
2020-07-18 00:43:29 +03:00
|
|
|
export default class RoomTile extends React.Component<IProps, IState> {
|
2020-07-07 12:34:42 +03:00
|
|
|
private dispatcherRef: string;
|
|
|
|
private roomTileRef = createRef<HTMLDivElement>();
|
2020-07-27 16:39:30 +03:00
|
|
|
private notificationState: NotificationState;
|
2020-05-08 21:53:05 +03:00
|
|
|
|
2020-05-12 01:20:26 +03:00
|
|
|
constructor(props: IProps) {
|
|
|
|
super(props);
|
|
|
|
|
|
|
|
this.state = {
|
|
|
|
hover: false,
|
2020-06-05 23:08:20 +03:00
|
|
|
selected: ActiveRoomObserver.activeRoomId === this.props.room.roomId,
|
2020-07-02 01:06:26 +03:00
|
|
|
notificationsMenuPosition: null,
|
|
|
|
generalMenuPosition: null,
|
2020-05-12 06:09:32 +03:00
|
|
|
};
|
2020-05-26 00:54:02 +03:00
|
|
|
|
2020-06-05 23:08:20 +03:00
|
|
|
ActiveRoomObserver.addListener(this.props.room.roomId, this.onActiveRoomUpdate);
|
2020-07-07 12:34:42 +03:00
|
|
|
this.dispatcherRef = defaultDispatcher.register(this.onAction);
|
2020-07-24 07:24:07 +03:00
|
|
|
MessagePreviewStore.instance.on(ROOM_PREVIEW_CHANGED, this.onRoomPreviewChanged);
|
2020-07-27 16:39:30 +03:00
|
|
|
this.notificationState = RoomNotificationStateStore.instance.getRoomState(this.props.room);
|
|
|
|
this.notificationState.on(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate);
|
2020-05-12 06:09:32 +03:00
|
|
|
}
|
|
|
|
|
2020-07-27 16:39:30 +03:00
|
|
|
private onNotificationUpdate = () => {
|
|
|
|
this.forceUpdate(); // notification state changed - update
|
|
|
|
};
|
|
|
|
|
2020-07-02 22:59:28 +03:00
|
|
|
private get showContextMenu(): boolean {
|
|
|
|
return !this.props.isMinimized && this.props.tag !== DefaultTagID.Invite;
|
|
|
|
}
|
|
|
|
|
2020-07-05 21:38:45 +03:00
|
|
|
private get showMessagePreview(): boolean {
|
|
|
|
return !this.props.isMinimized && this.props.showMessagePreview;
|
|
|
|
}
|
|
|
|
|
2020-07-07 12:34:42 +03:00
|
|
|
public componentDidMount() {
|
|
|
|
// when we're first rendered (or our sublist is expanded) make sure we are visible if we're active
|
|
|
|
if (this.state.selected) {
|
|
|
|
this.scrollIntoView();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-12 06:09:32 +03:00
|
|
|
public componentWillUnmount() {
|
2020-05-27 01:20:51 +03:00
|
|
|
if (this.props.room) {
|
2020-06-05 23:08:20 +03:00
|
|
|
ActiveRoomObserver.removeListener(this.props.room.roomId, this.onActiveRoomUpdate);
|
2020-05-26 00:54:02 +03:00
|
|
|
}
|
2020-07-07 12:34:42 +03:00
|
|
|
defaultDispatcher.unregister(this.dispatcherRef);
|
2020-07-24 07:24:07 +03:00
|
|
|
MessagePreviewStore.instance.off(ROOM_PREVIEW_CHANGED, this.onRoomPreviewChanged);
|
2020-07-27 16:39:30 +03:00
|
|
|
this.notificationState.off(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate);
|
2020-05-12 01:20:26 +03:00
|
|
|
}
|
|
|
|
|
2020-07-07 12:34:42 +03:00
|
|
|
private onAction = (payload: ActionPayload) => {
|
|
|
|
if (payload.action === "view_room" && payload.room_id === this.props.room.roomId && payload.show_room_tile) {
|
|
|
|
setImmediate(() => {
|
|
|
|
this.scrollIntoView();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-07-24 07:24:07 +03:00
|
|
|
private onRoomPreviewChanged = (room: Room) => {
|
|
|
|
if (this.props.room && room.roomId === this.props.room.roomId) {
|
|
|
|
this.forceUpdate(); // we don't have any state to set, so just complain that we need an update
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-07-07 12:34:42 +03:00
|
|
|
private scrollIntoView = () => {
|
|
|
|
if (!this.roomTileRef.current) return;
|
|
|
|
this.roomTileRef.current.scrollIntoView({
|
|
|
|
block: "nearest",
|
|
|
|
behavior: "auto",
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2020-05-12 01:20:26 +03:00
|
|
|
private onTileMouseEnter = () => {
|
|
|
|
this.setState({hover: true});
|
|
|
|
};
|
|
|
|
|
|
|
|
private onTileMouseLeave = () => {
|
|
|
|
this.setState({hover: false});
|
|
|
|
};
|
|
|
|
|
2020-05-12 01:29:32 +03:00
|
|
|
private onTileClick = (ev: React.KeyboardEvent) => {
|
2020-07-02 01:06:26 +03:00
|
|
|
ev.preventDefault();
|
|
|
|
ev.stopPropagation();
|
2020-05-12 01:29:32 +03:00
|
|
|
dis.dispatch({
|
|
|
|
action: 'view_room',
|
|
|
|
show_room_tile: true, // make sure the room is visible in the list
|
|
|
|
room_id: this.props.room.roomId,
|
|
|
|
clear_search: (ev && (ev.key === Key.ENTER || ev.key === Key.SPACE)),
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2020-06-05 23:08:20 +03:00
|
|
|
private onActiveRoomUpdate = (isActive: boolean) => {
|
|
|
|
this.setState({selected: isActive});
|
|
|
|
};
|
|
|
|
|
2020-07-02 01:56:57 +03:00
|
|
|
private onNotificationsMenuOpenClick = (ev: React.MouseEvent) => {
|
2020-06-30 02:16:51 +03:00
|
|
|
ev.preventDefault();
|
|
|
|
ev.stopPropagation();
|
2020-07-02 01:06:26 +03:00
|
|
|
const target = ev.target as HTMLButtonElement;
|
|
|
|
this.setState({notificationsMenuPosition: target.getBoundingClientRect()});
|
2020-06-30 02:16:51 +03:00
|
|
|
};
|
|
|
|
|
2020-07-02 01:06:26 +03:00
|
|
|
private onCloseNotificationsMenu = () => {
|
|
|
|
this.setState({notificationsMenuPosition: null});
|
2020-06-30 02:16:51 +03:00
|
|
|
};
|
|
|
|
|
2020-07-02 01:56:57 +03:00
|
|
|
private onGeneralMenuOpenClick = (ev: React.MouseEvent) => {
|
2020-06-10 08:09:15 +03:00
|
|
|
ev.preventDefault();
|
|
|
|
ev.stopPropagation();
|
2020-07-02 01:06:26 +03:00
|
|
|
const target = ev.target as HTMLButtonElement;
|
|
|
|
this.setState({generalMenuPosition: target.getBoundingClientRect()});
|
2020-06-10 08:09:15 +03:00
|
|
|
};
|
|
|
|
|
2020-07-02 01:06:26 +03:00
|
|
|
private onContextMenu = (ev: React.MouseEvent) => {
|
2020-07-02 22:59:28 +03:00
|
|
|
// If we don't have a context menu to show, ignore the action.
|
|
|
|
if (!this.showContextMenu) return;
|
|
|
|
|
2020-06-10 08:09:15 +03:00
|
|
|
ev.preventDefault();
|
|
|
|
ev.stopPropagation();
|
2020-07-02 01:06:26 +03:00
|
|
|
this.setState({
|
|
|
|
generalMenuPosition: {
|
|
|
|
left: ev.clientX,
|
|
|
|
bottom: ev.clientY,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
private onCloseGeneralMenu = () => {
|
|
|
|
this.setState({generalMenuPosition: null});
|
2020-06-10 08:09:15 +03:00
|
|
|
};
|
|
|
|
|
2020-06-11 00:05:29 +03:00
|
|
|
private onTagRoom = (ev: ButtonEvent, tagId: TagID) => {
|
2020-06-10 08:09:15 +03:00
|
|
|
ev.preventDefault();
|
|
|
|
ev.stopPropagation();
|
|
|
|
|
2020-07-21 11:50:20 +03:00
|
|
|
if (tagId === DefaultTagID.Favourite || tagId === DefaultTagID.LowPriority) {
|
|
|
|
const inverseTag = tagId === DefaultTagID.Favourite ? DefaultTagID.LowPriority : DefaultTagID.Favourite;
|
2020-07-21 15:43:42 +03:00
|
|
|
const isApplied = RoomListStore.instance.getTagsForRoom(this.props.room).includes(tagId);
|
|
|
|
const removeTag = isApplied ? tagId : inverseTag;
|
|
|
|
const addTag = isApplied ? null : tagId;
|
2020-07-08 01:14:04 +03:00
|
|
|
dis.dispatch(RoomListActions.tagRoom(
|
|
|
|
MatrixClientPeg.get(),
|
|
|
|
this.props.room,
|
|
|
|
removeTag,
|
|
|
|
addTag,
|
|
|
|
undefined,
|
|
|
|
0
|
|
|
|
));
|
|
|
|
} else {
|
2020-07-10 03:19:38 +03:00
|
|
|
console.warn(`Unexpected tag ${tagId} applied to ${this.props.room.room_id}`);
|
2020-07-08 01:14:04 +03:00
|
|
|
}
|
2020-07-06 12:18:49 +03:00
|
|
|
|
|
|
|
if ((ev as React.KeyboardEvent).key === Key.ENTER) {
|
|
|
|
// Implements https://www.w3.org/TR/wai-aria-practices/#keyboard-interaction-12
|
|
|
|
this.setState({generalMenuPosition: null}); // hide the menu
|
|
|
|
}
|
2020-06-10 08:09:15 +03:00
|
|
|
};
|
|
|
|
|
2020-06-11 00:05:29 +03:00
|
|
|
private onLeaveRoomClick = (ev: ButtonEvent) => {
|
2020-06-10 08:09:15 +03:00
|
|
|
ev.preventDefault();
|
|
|
|
ev.stopPropagation();
|
|
|
|
|
|
|
|
dis.dispatch({
|
|
|
|
action: 'leave_room',
|
|
|
|
room_id: this.props.room.roomId,
|
|
|
|
});
|
2020-07-02 01:06:26 +03:00
|
|
|
this.setState({generalMenuPosition: null}); // hide the menu
|
2020-06-10 08:09:15 +03:00
|
|
|
};
|
|
|
|
|
2020-07-13 18:35:03 +03:00
|
|
|
private onForgetRoomClick = (ev: ButtonEvent) => {
|
|
|
|
ev.preventDefault();
|
|
|
|
ev.stopPropagation();
|
|
|
|
|
|
|
|
dis.dispatch({
|
|
|
|
action: 'forget_room',
|
|
|
|
room_id: this.props.room.roomId,
|
|
|
|
});
|
|
|
|
this.setState({generalMenuPosition: null}); // hide the menu
|
|
|
|
};
|
|
|
|
|
2020-06-11 00:05:29 +03:00
|
|
|
private onOpenRoomSettings = (ev: ButtonEvent) => {
|
2020-06-10 08:09:15 +03:00
|
|
|
ev.preventDefault();
|
|
|
|
ev.stopPropagation();
|
|
|
|
|
|
|
|
dis.dispatch({
|
|
|
|
action: 'open_room_settings',
|
|
|
|
room_id: this.props.room.roomId,
|
|
|
|
});
|
2020-07-02 01:06:26 +03:00
|
|
|
this.setState({generalMenuPosition: null}); // hide the menu
|
2020-06-10 08:09:15 +03:00
|
|
|
};
|
|
|
|
|
2020-07-06 12:18:49 +03:00
|
|
|
private async saveNotifState(ev: ButtonEvent, newState: Volume) {
|
2020-07-01 00:53:30 +03:00
|
|
|
ev.preventDefault();
|
|
|
|
ev.stopPropagation();
|
|
|
|
if (MatrixClientPeg.get().isGuest()) return;
|
|
|
|
|
2020-07-06 12:18:49 +03:00
|
|
|
// get key before we go async and React discards the nativeEvent
|
|
|
|
const key = (ev as React.KeyboardEvent).key;
|
2020-07-01 00:53:30 +03:00
|
|
|
try {
|
2020-07-01 15:59:50 +03:00
|
|
|
// TODO add local echo - https://github.com/vector-im/riot-web/issues/14280
|
2020-07-01 00:53:30 +03:00
|
|
|
await setRoomNotifsState(this.props.room.roomId, newState);
|
|
|
|
} catch (error) {
|
|
|
|
// TODO: some form of error notification to the user to inform them that their state change failed.
|
2020-07-14 05:25:02 +03:00
|
|
|
// See https://github.com/vector-im/riot-web/issues/14281
|
2020-07-01 00:53:30 +03:00
|
|
|
console.error(error);
|
|
|
|
}
|
|
|
|
|
2020-07-06 12:18:49 +03:00
|
|
|
if (key === Key.ENTER) {
|
|
|
|
// Implements https://www.w3.org/TR/wai-aria-practices/#keyboard-interaction-12
|
|
|
|
this.setState({notificationsMenuPosition: null}); // hide the menu
|
|
|
|
}
|
2020-07-01 00:53:30 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
private onClickAllNotifs = ev => this.saveNotifState(ev, ALL_MESSAGES);
|
|
|
|
private onClickAlertMe = ev => this.saveNotifState(ev, ALL_MESSAGES_LOUD);
|
|
|
|
private onClickMentions = ev => this.saveNotifState(ev, MENTIONS_ONLY);
|
|
|
|
private onClickMute = ev => this.saveNotifState(ev, MUTE);
|
|
|
|
|
2020-07-03 00:21:10 +03:00
|
|
|
private renderNotificationsMenu(isActive: boolean): React.ReactElement {
|
2020-07-13 18:35:03 +03:00
|
|
|
if (MatrixClientPeg.get().isGuest() || this.props.tag === DefaultTagID.Archived || !this.showContextMenu) {
|
2020-07-01 16:28:00 +03:00
|
|
|
// the menu makes no sense in these cases so do not show one
|
|
|
|
return null;
|
|
|
|
}
|
2020-06-30 02:16:51 +03:00
|
|
|
|
2020-07-01 00:32:59 +03:00
|
|
|
const state = getRoomNotifsState(this.props.room.roomId);
|
|
|
|
|
2020-06-30 02:16:51 +03:00
|
|
|
let contextMenu = null;
|
2020-07-02 01:06:26 +03:00
|
|
|
if (this.state.notificationsMenuPosition) {
|
2020-06-30 02:16:51 +03:00
|
|
|
contextMenu = (
|
2020-07-02 01:06:26 +03:00
|
|
|
<ContextMenu {...contextMenuBelow(this.state.notificationsMenuPosition)} onFinished={this.onCloseNotificationsMenu}>
|
2020-07-18 00:43:29 +03:00
|
|
|
<div className="mx_IconizedContextMenu mx_IconizedContextMenu_compact mx_RoomTile_contextMenu">
|
2020-06-30 02:16:51 +03:00
|
|
|
<div className="mx_IconizedContextMenu_optionList">
|
2020-07-01 00:32:59 +03:00
|
|
|
<NotifOption
|
|
|
|
label={_t("Use default")}
|
|
|
|
active={state === ALL_MESSAGES}
|
2020-07-18 00:43:29 +03:00
|
|
|
iconClassName="mx_RoomTile_iconBell"
|
2020-07-01 00:53:30 +03:00
|
|
|
onClick={this.onClickAllNotifs}
|
2020-07-01 00:32:59 +03:00
|
|
|
/>
|
|
|
|
<NotifOption
|
|
|
|
label={_t("All messages")}
|
|
|
|
active={state === ALL_MESSAGES_LOUD}
|
2020-07-18 00:43:29 +03:00
|
|
|
iconClassName="mx_RoomTile_iconBellDot"
|
2020-07-01 00:53:30 +03:00
|
|
|
onClick={this.onClickAlertMe}
|
2020-07-01 00:32:59 +03:00
|
|
|
/>
|
|
|
|
<NotifOption
|
|
|
|
label={_t("Mentions & Keywords")}
|
|
|
|
active={state === MENTIONS_ONLY}
|
2020-07-18 00:43:29 +03:00
|
|
|
iconClassName="mx_RoomTile_iconBellMentions"
|
2020-07-01 00:53:30 +03:00
|
|
|
onClick={this.onClickMentions}
|
2020-07-01 00:32:59 +03:00
|
|
|
/>
|
|
|
|
<NotifOption
|
|
|
|
label={_t("None")}
|
|
|
|
active={state === MUTE}
|
2020-07-18 00:43:29 +03:00
|
|
|
iconClassName="mx_RoomTile_iconBellCrossed"
|
2020-07-01 00:53:30 +03:00
|
|
|
onClick={this.onClickMute}
|
2020-07-01 00:32:59 +03:00
|
|
|
/>
|
2020-06-30 02:16:51 +03:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</ContextMenu>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-07-18 00:43:29 +03:00
|
|
|
const classes = classNames("mx_RoomTile_notificationsButton", {
|
2020-06-30 02:16:51 +03:00
|
|
|
// Show bell icon for the default case too.
|
2020-07-18 00:43:29 +03:00
|
|
|
mx_RoomTile_iconBell: state === ALL_MESSAGES,
|
|
|
|
mx_RoomTile_iconBellDot: state === ALL_MESSAGES_LOUD,
|
|
|
|
mx_RoomTile_iconBellMentions: state === MENTIONS_ONLY,
|
|
|
|
mx_RoomTile_iconBellCrossed: state === MUTE,
|
2020-07-02 22:33:06 +03:00
|
|
|
|
|
|
|
// Only show the icon by default if the room is overridden to muted.
|
|
|
|
// TODO: [FTUE Notifications] Probably need to detect global mute state
|
2020-07-18 00:43:29 +03:00
|
|
|
mx_RoomTile_notificationsButton_show: state === MUTE,
|
2020-06-30 02:16:51 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
return (
|
|
|
|
<React.Fragment>
|
2020-07-17 20:16:00 +03:00
|
|
|
<ContextMenuTooltipButton
|
2020-06-30 02:16:51 +03:00
|
|
|
className={classes}
|
|
|
|
onClick={this.onNotificationsMenuOpenClick}
|
2020-07-17 20:16:00 +03:00
|
|
|
title={_t("Notification options")}
|
2020-07-02 01:06:26 +03:00
|
|
|
isExpanded={!!this.state.notificationsMenuPosition}
|
2020-07-03 00:21:10 +03:00
|
|
|
tabIndex={isActive ? 0 : -1}
|
2020-06-30 02:16:51 +03:00
|
|
|
/>
|
|
|
|
{contextMenu}
|
|
|
|
</React.Fragment>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-06-10 08:09:15 +03:00
|
|
|
private renderGeneralMenu(): React.ReactElement {
|
2020-07-02 22:59:28 +03:00
|
|
|
if (!this.showContextMenu) return null; // no menu to show
|
2020-06-11 23:39:28 +03:00
|
|
|
|
2020-06-10 08:09:15 +03:00
|
|
|
let contextMenu = null;
|
2020-07-13 18:35:03 +03:00
|
|
|
if (this.state.generalMenuPosition && this.props.tag === DefaultTagID.Archived) {
|
|
|
|
contextMenu = (
|
|
|
|
<ContextMenu {...contextMenuBelow(this.state.generalMenuPosition)} onFinished={this.onCloseGeneralMenu}>
|
2020-07-18 00:43:29 +03:00
|
|
|
<div className="mx_IconizedContextMenu mx_IconizedContextMenu_compact mx_RoomTile_contextMenu">
|
|
|
|
<div className="mx_IconizedContextMenu_optionList mx_RoomTile_contextMenu_redRow">
|
2020-07-13 18:35:03 +03:00
|
|
|
<MenuItem onClick={this.onForgetRoomClick} label={_t("Leave Room")}>
|
2020-07-18 00:43:29 +03:00
|
|
|
<span className="mx_IconizedContextMenu_icon mx_RoomTile_iconSignOut" />
|
2020-07-13 18:35:03 +03:00
|
|
|
<span className="mx_IconizedContextMenu_label">{_t("Forget Room")}</span>
|
|
|
|
</MenuItem>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</ContextMenu>
|
|
|
|
);
|
|
|
|
} else if (this.state.generalMenuPosition) {
|
2020-07-21 11:50:20 +03:00
|
|
|
const roomTags = RoomListStore.instance.getTagsForRoom(this.props.room);
|
|
|
|
|
|
|
|
const isFavorite = roomTags.includes(DefaultTagID.Favourite);
|
|
|
|
const favouriteLabel = isFavorite ? _t("Favourited") : _t("Favourite");
|
|
|
|
|
|
|
|
const isLowPriority = roomTags.includes(DefaultTagID.LowPriority);
|
|
|
|
const lowPriorityLabel = _t("Low Priority");
|
|
|
|
|
2020-06-10 08:09:15 +03:00
|
|
|
contextMenu = (
|
2020-07-02 01:06:26 +03:00
|
|
|
<ContextMenu {...contextMenuBelow(this.state.generalMenuPosition)} onFinished={this.onCloseGeneralMenu}>
|
2020-07-18 00:43:29 +03:00
|
|
|
<div className="mx_IconizedContextMenu mx_IconizedContextMenu_compact mx_RoomTile_contextMenu">
|
2020-06-10 08:09:15 +03:00
|
|
|
<div className="mx_IconizedContextMenu_optionList">
|
2020-07-05 20:23:57 +03:00
|
|
|
<MenuItemCheckbox
|
2020-07-21 11:50:20 +03:00
|
|
|
className={isFavorite ? "mx_RoomTile_contextMenu_activeRow" : ""}
|
2020-07-05 20:23:57 +03:00
|
|
|
onClick={(e) => this.onTagRoom(e, DefaultTagID.Favourite)}
|
2020-07-08 01:14:04 +03:00
|
|
|
active={isFavorite}
|
2020-07-08 18:07:38 +03:00
|
|
|
label={favouriteLabel}
|
2020-07-05 20:23:57 +03:00
|
|
|
>
|
2020-07-21 11:50:20 +03:00
|
|
|
<span className="mx_IconizedContextMenu_icon mx_RoomTile_iconStar" />
|
2020-07-08 18:07:38 +03:00
|
|
|
<span className="mx_IconizedContextMenu_label">{favouriteLabel}</span>
|
2020-07-05 20:23:57 +03:00
|
|
|
</MenuItemCheckbox>
|
2020-07-21 11:50:20 +03:00
|
|
|
<MenuItemCheckbox
|
|
|
|
className={isLowPriority ? "mx_RoomTile_contextMenu_activeRow" : ""}
|
|
|
|
onClick={(e) => this.onTagRoom(e, DefaultTagID.LowPriority)}
|
|
|
|
active={isLowPriority}
|
|
|
|
label={lowPriorityLabel}
|
|
|
|
>
|
|
|
|
<span className="mx_IconizedContextMenu_icon mx_RoomTile_iconArrowDown" />
|
|
|
|
<span className="mx_IconizedContextMenu_label">{lowPriorityLabel}</span>
|
|
|
|
</MenuItemCheckbox>
|
2020-07-05 20:23:57 +03:00
|
|
|
<MenuItem onClick={this.onOpenRoomSettings} label={_t("Settings")}>
|
2020-07-18 00:43:29 +03:00
|
|
|
<span className="mx_IconizedContextMenu_icon mx_RoomTile_iconSettings" />
|
2020-07-01 00:32:59 +03:00
|
|
|
<span className="mx_IconizedContextMenu_label">{_t("Settings")}</span>
|
2020-07-05 20:23:57 +03:00
|
|
|
</MenuItem>
|
2020-06-10 08:09:15 +03:00
|
|
|
</div>
|
2020-07-18 00:43:29 +03:00
|
|
|
<div className="mx_IconizedContextMenu_optionList mx_RoomTile_contextMenu_redRow">
|
2020-07-05 20:23:57 +03:00
|
|
|
<MenuItem onClick={this.onLeaveRoomClick} label={_t("Leave Room")}>
|
2020-07-18 00:43:29 +03:00
|
|
|
<span className="mx_IconizedContextMenu_icon mx_RoomTile_iconSignOut" />
|
2020-07-01 00:32:59 +03:00
|
|
|
<span className="mx_IconizedContextMenu_label">{_t("Leave Room")}</span>
|
2020-07-05 20:23:57 +03:00
|
|
|
</MenuItem>
|
2020-06-10 08:09:15 +03:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</ContextMenu>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<React.Fragment>
|
2020-07-17 20:16:00 +03:00
|
|
|
<ContextMenuTooltipButton
|
2020-07-18 00:43:29 +03:00
|
|
|
className="mx_RoomTile_menuButton"
|
2020-06-10 08:09:15 +03:00
|
|
|
onClick={this.onGeneralMenuOpenClick}
|
2020-07-17 20:16:00 +03:00
|
|
|
title={_t("Room options")}
|
2020-07-02 01:06:26 +03:00
|
|
|
isExpanded={!!this.state.generalMenuPosition}
|
2020-06-10 08:09:15 +03:00
|
|
|
/>
|
|
|
|
{contextMenu}
|
|
|
|
</React.Fragment>
|
2020-06-18 16:32:43 +03:00
|
|
|
);
|
2020-06-10 08:09:15 +03:00
|
|
|
}
|
|
|
|
|
2020-05-08 21:53:05 +03:00
|
|
|
public render(): React.ReactElement {
|
|
|
|
const classes = classNames({
|
2020-07-18 00:43:29 +03:00
|
|
|
'mx_RoomTile': true,
|
|
|
|
'mx_RoomTile_selected': this.state.selected,
|
|
|
|
'mx_RoomTile_hasMenuOpen': !!(this.state.generalMenuPosition || this.state.notificationsMenuPosition),
|
|
|
|
'mx_RoomTile_minimized': this.props.isMinimized,
|
2020-05-08 21:53:05 +03:00
|
|
|
});
|
|
|
|
|
2020-07-01 01:24:46 +03:00
|
|
|
const roomAvatar = <DecoratedRoomAvatar
|
|
|
|
room={this.props.room}
|
2020-07-01 14:28:32 +03:00
|
|
|
avatarSize={32}
|
2020-07-01 01:24:46 +03:00
|
|
|
tag={this.props.tag}
|
|
|
|
displayBadge={this.props.isMinimized}
|
2020-07-01 01:39:25 +03:00
|
|
|
/>;
|
2020-07-01 01:24:46 +03:00
|
|
|
|
2020-07-01 01:39:25 +03:00
|
|
|
let badge: React.ReactNode;
|
2020-07-01 01:24:46 +03:00
|
|
|
if (!this.props.isMinimized) {
|
2020-07-05 21:59:29 +03:00
|
|
|
// aria-hidden because we summarise the unread count/highlight status in a manual aria-label below
|
2020-07-02 23:11:31 +03:00
|
|
|
badge = (
|
2020-07-18 00:43:29 +03:00
|
|
|
<div className="mx_RoomTile_badgeContainer" aria-hidden="true">
|
2020-07-02 23:11:31 +03:00
|
|
|
<NotificationBadge
|
2020-07-27 16:39:30 +03:00
|
|
|
notification={this.notificationState}
|
2020-07-02 23:11:31 +03:00
|
|
|
forceCount={false}
|
|
|
|
roomId={this.props.room.roomId}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
);
|
2020-07-01 01:24:46 +03:00
|
|
|
}
|
2020-05-12 06:09:32 +03:00
|
|
|
|
2020-05-08 21:53:05 +03:00
|
|
|
let name = this.props.room.name;
|
|
|
|
if (typeof name !== 'string') name = '';
|
|
|
|
name = name.replace(":", ":\u200b"); // add a zero-width space to allow linewrapping after the colon
|
|
|
|
|
2020-06-05 06:21:04 +03:00
|
|
|
let messagePreview = null;
|
2020-07-05 21:38:45 +03:00
|
|
|
if (this.showMessagePreview) {
|
2020-06-11 03:37:59 +03:00
|
|
|
// The preview store heavily caches this info, so should be safe to hammer.
|
2020-06-26 01:26:07 +03:00
|
|
|
const text = MessagePreviewStore.instance.getPreviewForRoom(this.props.room, this.props.tag);
|
2020-06-11 03:37:59 +03:00
|
|
|
|
|
|
|
// Only show the preview if there is one to show.
|
|
|
|
if (text) {
|
|
|
|
messagePreview = (
|
2020-07-18 00:43:29 +03:00
|
|
|
<div className="mx_RoomTile_messagePreview" id={messagePreviewId(this.props.room.roomId)}>
|
2020-06-11 03:37:59 +03:00
|
|
|
{text}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2020-05-12 01:20:26 +03:00
|
|
|
}
|
|
|
|
|
2020-06-05 06:21:04 +03:00
|
|
|
const nameClasses = classNames({
|
2020-07-18 00:43:29 +03:00
|
|
|
"mx_RoomTile_name": true,
|
|
|
|
"mx_RoomTile_nameWithPreview": !!messagePreview,
|
2020-07-27 16:39:30 +03:00
|
|
|
"mx_RoomTile_nameHasUnreadEvents": this.notificationState.isUnread,
|
2020-06-05 06:21:04 +03:00
|
|
|
});
|
|
|
|
|
2020-06-11 23:39:28 +03:00
|
|
|
let nameContainer = (
|
2020-07-18 00:43:29 +03:00
|
|
|
<div className="mx_RoomTile_nameContainer">
|
2020-06-11 23:39:28 +03:00
|
|
|
<div title={name} className={nameClasses} tabIndex={-1} dir="auto">
|
|
|
|
{name}
|
|
|
|
</div>
|
|
|
|
{messagePreview}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
if (this.props.isMinimized) nameContainer = null;
|
|
|
|
|
2020-07-05 03:07:46 +03:00
|
|
|
let ariaLabel = name;
|
|
|
|
// The following labels are written in such a fashion to increase screen reader efficiency (speed).
|
|
|
|
if (this.props.tag === DefaultTagID.Invite) {
|
|
|
|
// append nothing
|
2020-07-27 16:39:30 +03:00
|
|
|
} else if (this.notificationState.hasMentions) {
|
2020-07-05 03:07:46 +03:00
|
|
|
ariaLabel += " " + _t("%(count)s unread messages including mentions.", {
|
2020-07-27 16:39:30 +03:00
|
|
|
count: this.notificationState.count,
|
2020-07-05 03:07:46 +03:00
|
|
|
});
|
2020-07-27 16:39:30 +03:00
|
|
|
} else if (this.notificationState.hasUnreadCount) {
|
2020-07-05 03:07:46 +03:00
|
|
|
ariaLabel += " " + _t("%(count)s unread messages.", {
|
2020-07-27 16:39:30 +03:00
|
|
|
count: this.notificationState.count,
|
2020-07-05 03:07:46 +03:00
|
|
|
});
|
2020-07-27 16:39:30 +03:00
|
|
|
} else if (this.notificationState.isUnread) {
|
2020-07-05 03:07:46 +03:00
|
|
|
ariaLabel += " " + _t("Unread messages.");
|
|
|
|
}
|
|
|
|
|
2020-07-05 21:38:45 +03:00
|
|
|
let ariaDescribedBy: string;
|
|
|
|
if (this.showMessagePreview) {
|
|
|
|
ariaDescribedBy = messagePreviewId(this.props.room.roomId);
|
|
|
|
}
|
|
|
|
|
2020-07-17 20:16:00 +03:00
|
|
|
let Button: React.ComponentType<React.ComponentProps<typeof AccessibleButton>> = AccessibleButton;
|
|
|
|
if (this.props.isMinimized) {
|
|
|
|
Button = AccessibleTooltipButton;
|
|
|
|
}
|
|
|
|
|
2020-05-08 21:53:05 +03:00
|
|
|
return (
|
|
|
|
<React.Fragment>
|
2020-07-07 12:34:42 +03:00
|
|
|
<RovingTabIndexWrapper inputRef={this.roomTileRef}>
|
2020-05-08 21:53:05 +03:00
|
|
|
{({onFocus, isActive, ref}) =>
|
2020-07-17 20:16:00 +03:00
|
|
|
<Button
|
2020-05-08 21:53:05 +03:00
|
|
|
onFocus={onFocus}
|
|
|
|
tabIndex={isActive ? 0 : -1}
|
|
|
|
inputRef={ref}
|
|
|
|
className={classes}
|
2020-05-12 01:20:26 +03:00
|
|
|
onMouseEnter={this.onTileMouseEnter}
|
|
|
|
onMouseLeave={this.onTileMouseLeave}
|
2020-05-12 01:29:32 +03:00
|
|
|
onClick={this.onTileClick}
|
2020-07-02 01:06:26 +03:00
|
|
|
onContextMenu={this.onContextMenu}
|
2020-07-05 03:07:46 +03:00
|
|
|
role="treeitem"
|
|
|
|
aria-label={ariaLabel}
|
|
|
|
aria-selected={this.state.selected}
|
2020-07-05 21:38:45 +03:00
|
|
|
aria-describedby={ariaDescribedBy}
|
2020-07-17 20:16:00 +03:00
|
|
|
title={this.props.isMinimized ? name : undefined}
|
2020-05-08 21:53:05 +03:00
|
|
|
>
|
2020-07-01 01:24:46 +03:00
|
|
|
{roomAvatar}
|
2020-06-11 23:39:28 +03:00
|
|
|
{nameContainer}
|
2020-07-02 23:11:31 +03:00
|
|
|
{badge}
|
2020-06-10 08:09:15 +03:00
|
|
|
{this.renderGeneralMenu()}
|
2020-07-07 13:46:33 +03:00
|
|
|
{this.renderNotificationsMenu(isActive)}
|
2020-07-17 20:16:00 +03:00
|
|
|
</Button>
|
2020-05-08 21:53:05 +03:00
|
|
|
}
|
|
|
|
</RovingTabIndexWrapper>
|
|
|
|
</React.Fragment>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|