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-02 22:53:38 +03:00
|
|
|
import React 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 {
|
|
|
|
ContextMenu,
|
|
|
|
ContextMenuButton,
|
|
|
|
MenuItemRadio,
|
|
|
|
MenuItemCheckbox,
|
|
|
|
MenuItem,
|
|
|
|
} from "../../structures/ContextMenu";
|
2020-06-10 08:09:15 +03:00
|
|
|
import { DefaultTagID, TagID } from "../../../stores/room-list/models";
|
2020-06-24 21:23:09 +03:00
|
|
|
import { MessagePreviewStore } 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 { TagSpecificNotificationState } from "../../../stores/notifications/TagSpecificNotificationState";
|
|
|
|
import { INotificationState } from "../../../stores/notifications/INotificationState";
|
|
|
|
import NotificationBadge from "./NotificationBadge";
|
|
|
|
import { NotificationColor } from "../../../stores/notifications/NotificationColor";
|
2020-07-07 16:07:35 +03:00
|
|
|
import { Volume } from "../../../RoomNotifsTypes";
|
2020-07-08 01:14:04 +03:00
|
|
|
import RoomListStore from "../../../stores/room-list/RoomListStore2";
|
|
|
|
import RoomListActions from "../../../actions/RoomListActions";
|
2020-05-08 21:53:05 +03:00
|
|
|
|
2020-06-29 05:03:04 +03:00
|
|
|
// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14231
|
|
|
|
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231
|
|
|
|
|
2020-05-14 21:53:00 +03:00
|
|
|
/*******************************************************************
|
|
|
|
* CAUTION *
|
|
|
|
*******************************************************************
|
|
|
|
* This is a work in progress implementation and isn't complete or *
|
|
|
|
* even useful as a component. Please avoid using it until this *
|
|
|
|
* warning disappears. *
|
2020-05-21 20:53:16 +03:00
|
|
|
*******************************************************************/
|
2020-05-14 21:53:00 +03:00
|
|
|
|
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-05-23 03:05:09 +03:00
|
|
|
notificationState: INotificationState;
|
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-05 21:38:45 +03:00
|
|
|
const messagePreviewId = (roomId: string) => `mx_RoomTile2_messagePreview_${roomId}`;
|
|
|
|
|
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-06-30 01:02:10 +03:00
|
|
|
const chevronFace = "none";
|
|
|
|
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({
|
|
|
|
mx_RoomTile2_contextMenu_activeRow: active,
|
|
|
|
});
|
|
|
|
|
|
|
|
let activeIcon;
|
|
|
|
if (active) {
|
|
|
|
activeIcon = <span className="mx_IconizedContextMenu_icon mx_RoomTile2_iconCheck" />;
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
export default class RoomTile2 extends React.Component<IProps, IState> {
|
2020-06-29 05:03:04 +03:00
|
|
|
// TODO: a11y: https://github.com/vector-im/riot-web/issues/14180
|
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-22 23:52:17 +03:00
|
|
|
notificationState: new TagSpecificNotificationState(this.props.room, this.props.tag),
|
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-05-12 06:09:32 +03:00
|
|
|
}
|
|
|
|
|
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-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-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',
|
2020-06-29 05:03:04 +03:00
|
|
|
// TODO: Support show_room_tile in new room list: https://github.com/vector-im/riot-web/issues/14233
|
2020-05-12 01:29:32 +03:00
|
|
|
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-06-30 02:16:51 +03:00
|
|
|
private onNotificationsMenuOpenClick = (ev: InputEvent) => {
|
|
|
|
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-06-10 08:09:15 +03:00
|
|
|
private onGeneralMenuOpenClick = (ev: InputEvent) => {
|
|
|
|
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-08 01:14:04 +03:00
|
|
|
if (tagId === DefaultTagID.Favourite) {
|
|
|
|
const roomTags = RoomListStore.instance.getTagsForRoom(this.props.room);
|
|
|
|
const isFavourite = roomTags.includes(DefaultTagID.Favourite);
|
|
|
|
const removeTag = isFavourite ? DefaultTagID.Favourite : DefaultTagID.LowPriority;
|
|
|
|
const addTag = isFavourite ? null : DefaultTagID.Favourite;
|
|
|
|
dis.dispatch(RoomListActions.tagRoom(
|
|
|
|
MatrixClientPeg.get(),
|
|
|
|
this.props.room,
|
|
|
|
removeTag,
|
|
|
|
addTag,
|
|
|
|
undefined,
|
|
|
|
0
|
|
|
|
));
|
|
|
|
} else {
|
|
|
|
console.log(`Unexpected tag ${tagId} applied to ${this.props.room.room_id}`);
|
|
|
|
}
|
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-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-01 15:59:50 +03:00
|
|
|
// 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-02 22:59:28 +03:00
|
|
|
if (MatrixClientPeg.get().isGuest() || !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-06-30 02:16:51 +03:00
|
|
|
<div className="mx_IconizedContextMenu mx_IconizedContextMenu_compact mx_RoomTile2_contextMenu">
|
|
|
|
<div className="mx_IconizedContextMenu_optionList">
|
2020-07-01 00:32:59 +03:00
|
|
|
<NotifOption
|
|
|
|
label={_t("Use default")}
|
|
|
|
active={state === ALL_MESSAGES}
|
|
|
|
iconClassName="mx_RoomTile2_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}
|
|
|
|
iconClassName="mx_RoomTile2_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-01 15:59:50 +03:00
|
|
|
iconClassName="mx_RoomTile2_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}
|
|
|
|
iconClassName="mx_RoomTile2_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>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const classes = classNames("mx_RoomTile2_notificationsButton", {
|
|
|
|
// Show bell icon for the default case too.
|
2020-07-03 02:14:51 +03:00
|
|
|
mx_RoomTile2_iconBell: state === ALL_MESSAGES,
|
2020-07-03 01:20:16 +03:00
|
|
|
mx_RoomTile2_iconBellDot: state === ALL_MESSAGES_LOUD,
|
|
|
|
mx_RoomTile2_iconBellMentions: state === MENTIONS_ONLY,
|
2020-06-30 02:16:51 +03:00
|
|
|
mx_RoomTile2_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
|
|
|
|
mx_RoomTile2_notificationsButton_show: state === MUTE,
|
2020-06-30 02:16:51 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
return (
|
|
|
|
<React.Fragment>
|
|
|
|
<ContextMenuButton
|
|
|
|
className={classes}
|
|
|
|
onClick={this.onNotificationsMenuOpenClick}
|
|
|
|
label={_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-07-02 22:59:28 +03:00
|
|
|
// TODO: We could do with a proper invite context menu, unlike what showContextMenu suggests
|
2020-07-01 04:42:02 +03:00
|
|
|
|
2020-07-08 01:14:04 +03:00
|
|
|
const roomTags = RoomListStore.instance.getTagsForRoom(this.props.room);
|
|
|
|
|
|
|
|
const isFavorite = roomTags.includes(DefaultTagID.Favourite);
|
|
|
|
const favoriteClassName = isFavorite ? "mx_RoomTile2_iconFavorite" : "mx_RoomTile2_iconStar"
|
|
|
|
|
2020-06-10 08:09:15 +03:00
|
|
|
let contextMenu = null;
|
2020-07-02 01:06:26 +03:00
|
|
|
if (this.state.generalMenuPosition) {
|
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-06-30 01:02:10 +03:00
|
|
|
<div className="mx_IconizedContextMenu mx_IconizedContextMenu_compact mx_RoomTile2_contextMenu">
|
2020-06-10 08:09:15 +03:00
|
|
|
<div className="mx_IconizedContextMenu_optionList">
|
2020-07-05 20:23:57 +03:00
|
|
|
<MenuItemCheckbox
|
|
|
|
onClick={(e) => this.onTagRoom(e, DefaultTagID.Favourite)}
|
2020-07-08 01:14:04 +03:00
|
|
|
active={isFavorite}
|
2020-07-05 20:23:57 +03:00
|
|
|
label={_t("Favourite")}
|
|
|
|
>
|
2020-07-08 01:14:04 +03:00
|
|
|
<span className={classNames("mx_IconizedContextMenu_icon", favoriteClassName)} />
|
2020-07-01 00:32:59 +03:00
|
|
|
<span className="mx_IconizedContextMenu_label">{_t("Favourite")}</span>
|
2020-07-05 20:23:57 +03:00
|
|
|
</MenuItemCheckbox>
|
|
|
|
<MenuItem onClick={this.onOpenRoomSettings} label={_t("Settings")}>
|
2020-07-01 00:11:12 +03:00
|
|
|
<span className="mx_IconizedContextMenu_icon mx_RoomTile2_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-01 00:11:12 +03:00
|
|
|
<div className="mx_IconizedContextMenu_optionList mx_RoomTile2_contextMenu_redRow">
|
2020-07-05 20:23:57 +03:00
|
|
|
<MenuItem onClick={this.onLeaveRoomClick} label={_t("Leave Room")}>
|
2020-07-01 00:11:12 +03:00
|
|
|
<span className="mx_IconizedContextMenu_icon mx_RoomTile2_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>
|
|
|
|
<ContextMenuButton
|
|
|
|
className="mx_RoomTile2_menuButton"
|
|
|
|
onClick={this.onGeneralMenuOpenClick}
|
|
|
|
label={_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 {
|
2020-06-29 05:03:04 +03:00
|
|
|
// TODO: Invites: https://github.com/vector-im/riot-web/issues/14198
|
|
|
|
// TODO: a11y proper: https://github.com/vector-im/riot-web/issues/14180
|
2020-05-08 21:53:05 +03:00
|
|
|
|
|
|
|
const classes = classNames({
|
2020-06-05 01:34:04 +03:00
|
|
|
'mx_RoomTile2': true,
|
2020-06-05 23:08:20 +03:00
|
|
|
'mx_RoomTile2_selected': this.state.selected,
|
2020-07-02 01:06:26 +03:00
|
|
|
'mx_RoomTile2_hasMenuOpen': !!(this.state.generalMenuPosition || this.state.notificationsMenuPosition),
|
2020-06-11 23:39:28 +03:00
|
|
|
'mx_RoomTile2_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-05 21:59:29 +03:00
|
|
|
<div className="mx_RoomTile2_badgeContainer" aria-hidden="true">
|
2020-07-02 23:11:31 +03:00
|
|
|
<NotificationBadge
|
|
|
|
notification={this.state.notificationState}
|
|
|
|
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
|
|
|
// TODO: the original RoomTile uses state for the room name. Do we need to?
|
|
|
|
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-05 21:38:45 +03:00
|
|
|
<div className="mx_RoomTile2_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-07-05 03:07:46 +03:00
|
|
|
const notificationColor = this.state.notificationState.color;
|
2020-06-05 06:21:04 +03:00
|
|
|
const nameClasses = classNames({
|
|
|
|
"mx_RoomTile2_name": true,
|
|
|
|
"mx_RoomTile2_nameWithPreview": !!messagePreview,
|
2020-07-05 03:07:46 +03:00
|
|
|
"mx_RoomTile2_nameHasUnreadEvents": notificationColor >= NotificationColor.Bold,
|
2020-06-05 06:21:04 +03:00
|
|
|
});
|
|
|
|
|
2020-06-11 23:39:28 +03:00
|
|
|
let nameContainer = (
|
|
|
|
<div className="mx_RoomTile2_nameContainer">
|
|
|
|
<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
|
|
|
|
} else if (notificationColor >= NotificationColor.Red) {
|
|
|
|
ariaLabel += " " + _t("%(count)s unread messages including mentions.", {
|
|
|
|
count: this.state.notificationState.count,
|
|
|
|
});
|
|
|
|
} else if (notificationColor >= NotificationColor.Grey) {
|
|
|
|
ariaLabel += " " + _t("%(count)s unread messages.", {
|
|
|
|
count: this.state.notificationState.count,
|
|
|
|
});
|
|
|
|
} else if (notificationColor >= NotificationColor.Bold) {
|
|
|
|
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-05-08 21:53:05 +03:00
|
|
|
return (
|
|
|
|
<React.Fragment>
|
2020-06-30 01:02:10 +03:00
|
|
|
<RovingTabIndexWrapper>
|
2020-05-08 21:53:05 +03:00
|
|
|
{({onFocus, isActive, ref}) =>
|
|
|
|
<AccessibleButton
|
|
|
|
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-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-05-08 21:53:05 +03:00
|
|
|
</AccessibleButton>
|
|
|
|
}
|
|
|
|
</RovingTabIndexWrapper>
|
|
|
|
</React.Fragment>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|