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-30 01:54:04 +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-07-30 01:54:04 +03:00
|
|
|
import defaultDispatcher 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-08-25 04:19:28 +03:00
|
|
|
import { ChevronFace, ContextMenuTooltipButton } from "../../structures/ContextMenu";
|
2020-06-10 08:09:15 +03:00
|
|
|
import { DefaultTagID, TagID } from "../../../stores/room-list/models";
|
2021-01-15 16:54:38 +03:00
|
|
|
import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore";
|
2020-07-01 01:24:46 +03:00
|
|
|
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
|
2020-08-29 03:11:08 +03:00
|
|
|
import { 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-30 01:54:04 +03:00
|
|
|
import { ActionPayload } from "../../../dispatcher/payloads";
|
2020-07-09 04:26:25 +03:00
|
|
|
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
|
2020-07-27 16:40:41 +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-07-30 01:54:04 +03:00
|
|
|
import { EchoChamber } from "../../../stores/local-echo/EchoChamber";
|
2020-07-30 18:15:19 +03:00
|
|
|
import { CachedRoomKey, RoomEchoChamber } from "../../../stores/local-echo/RoomEchoChamber";
|
|
|
|
import { PROPERTY_UPDATED } from "../../../stores/local-echo/GenericEchoChamber";
|
2020-08-04 23:42:39 +03:00
|
|
|
import IconizedContextMenu, {
|
|
|
|
IconizedContextMenuCheckbox,
|
|
|
|
IconizedContextMenuOption,
|
2020-08-25 04:19:28 +03:00
|
|
|
IconizedContextMenuOptionList,
|
2020-08-29 03:11:08 +03:00
|
|
|
IconizedContextMenuRadio,
|
2020-08-04 23:42:39 +03:00
|
|
|
} from "../context_menus/IconizedContextMenu";
|
2020-08-25 04:19:28 +03:00
|
|
|
import { CommunityPrototypeStore, IRoomProfile } from "../../../stores/CommunityPrototypeStore";
|
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-07-02 01:06:26 +03:00
|
|
|
type PartialDOMRect = Pick<DOMRect, "left" | "bottom">;
|
|
|
|
|
2020-05-23 03:05:09 +03:00
|
|
|
interface IState {
|
2020-06-05 23:08:20 +03:00
|
|
|
selected: boolean;
|
2020-07-02 01:06:26 +03:00
|
|
|
notificationsMenuPosition: PartialDOMRect;
|
|
|
|
generalMenuPosition: PartialDOMRect;
|
2020-07-24 22:09:26 +03:00
|
|
|
messagePreview?: string;
|
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-24 22:58:21 +03:00
|
|
|
export default class RoomTile extends React.PureComponent<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-07-30 18:15:19 +03:00
|
|
|
private roomProps: RoomEchoChamber;
|
2020-05-08 21:53:05 +03:00
|
|
|
|
2020-05-12 01:20:26 +03:00
|
|
|
constructor(props: IProps) {
|
|
|
|
super(props);
|
|
|
|
|
|
|
|
this.state = {
|
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-07-24 22:09:26 +03:00
|
|
|
|
|
|
|
// generatePreview() will return nothing if the user has previews disabled
|
|
|
|
messagePreview: this.generatePreview(),
|
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);
|
2021-01-15 16:54:38 +03:00
|
|
|
MessagePreviewStore.instance.on(
|
|
|
|
MessagePreviewStore.getPreviewChangedEventName(this.props.room),
|
|
|
|
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-07-30 01:54:04 +03:00
|
|
|
this.roomProps = EchoChamber.forRoom(this.props.room);
|
|
|
|
this.roomProps.on(PROPERTY_UPDATED, this.onRoomPropertyUpdate);
|
2021-01-15 17:34:56 +03:00
|
|
|
CommunityPrototypeStore.instance.on(
|
|
|
|
CommunityPrototypeStore.getUpdateEventName(this.props.room.roomId),
|
|
|
|
this.onCommunityUpdate,
|
|
|
|
);
|
2021-01-28 23:36:33 +03:00
|
|
|
this.props.room.on("Room.name", this.onRoomNameUpdate);
|
|
|
|
}
|
|
|
|
|
|
|
|
private onRoomNameUpdate = (room) => {
|
|
|
|
this.forceUpdate();
|
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-30 01:54:04 +03:00
|
|
|
private onRoomPropertyUpdate = (property: CachedRoomKey) => {
|
|
|
|
if (property === CachedRoomKey.NotificationVolume) this.onNotificationUpdate();
|
|
|
|
// else ignore - not important for this tile
|
|
|
|
};
|
|
|
|
|
2020-07-02 22:59:28 +03:00
|
|
|
private get showContextMenu(): boolean {
|
2020-08-14 14:01:16 +03:00
|
|
|
return this.props.tag !== DefaultTagID.Invite;
|
2020-07-02 22:59:28 +03:00
|
|
|
}
|
|
|
|
|
2020-07-05 21:38:45 +03:00
|
|
|
private get showMessagePreview(): boolean {
|
|
|
|
return !this.props.isMinimized && this.props.showMessagePreview;
|
|
|
|
}
|
|
|
|
|
2020-08-11 05:52:05 +03:00
|
|
|
public componentDidUpdate(prevProps: Readonly<IProps>, prevState: Readonly<IState>) {
|
|
|
|
if (prevProps.showMessagePreview !== this.props.showMessagePreview && this.showMessagePreview) {
|
|
|
|
this.setState({messagePreview: this.generatePreview()});
|
|
|
|
}
|
2021-01-15 16:54:38 +03:00
|
|
|
if (prevProps.room?.roomId !== this.props.room?.roomId) {
|
|
|
|
MessagePreviewStore.instance.off(
|
|
|
|
MessagePreviewStore.getPreviewChangedEventName(prevProps.room),
|
|
|
|
this.onRoomPreviewChanged,
|
|
|
|
);
|
|
|
|
MessagePreviewStore.instance.on(
|
|
|
|
MessagePreviewStore.getPreviewChangedEventName(this.props.room),
|
|
|
|
this.onRoomPreviewChanged,
|
|
|
|
);
|
2021-01-15 17:34:56 +03:00
|
|
|
CommunityPrototypeStore.instance.off(
|
|
|
|
CommunityPrototypeStore.getUpdateEventName(prevProps.room?.roomId),
|
|
|
|
this.onCommunityUpdate,
|
|
|
|
);
|
|
|
|
CommunityPrototypeStore.instance.on(
|
|
|
|
CommunityPrototypeStore.getUpdateEventName(this.props.room?.roomId),
|
|
|
|
this.onCommunityUpdate,
|
|
|
|
);
|
2021-01-29 14:31:21 +03:00
|
|
|
prevProps.room?.off("Room.name", this.onRoomNameUpdate);
|
|
|
|
this.props.room?.on("Room.name", this.onRoomNameUpdate);
|
2021-01-15 16:54:38 +03:00
|
|
|
}
|
2020-08-11 05:52:05 +03:00
|
|
|
}
|
|
|
|
|
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);
|
2021-01-15 16:54:38 +03:00
|
|
|
MessagePreviewStore.instance.off(
|
|
|
|
MessagePreviewStore.getPreviewChangedEventName(this.props.room),
|
|
|
|
this.onRoomPreviewChanged,
|
|
|
|
);
|
2021-01-15 17:34:56 +03:00
|
|
|
CommunityPrototypeStore.instance.off(
|
|
|
|
CommunityPrototypeStore.getUpdateEventName(this.props.room.roomId),
|
|
|
|
this.onCommunityUpdate,
|
|
|
|
);
|
2021-01-28 23:36:33 +03:00
|
|
|
this.props.room.off("Room.name", this.onRoomNameUpdate);
|
2020-05-26 00:54:02 +03:00
|
|
|
}
|
2020-07-07 12:34:42 +03:00
|
|
|
defaultDispatcher.unregister(this.dispatcherRef);
|
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-08-25 04:19:28 +03:00
|
|
|
private onCommunityUpdate = (roomId: string) => {
|
|
|
|
if (roomId !== this.props.room.roomId) return;
|
|
|
|
this.forceUpdate(); // we don't have anything to actually update
|
|
|
|
};
|
|
|
|
|
2020-07-24 07:24:07 +03:00
|
|
|
private onRoomPreviewChanged = (room: Room) => {
|
|
|
|
if (this.props.room && room.roomId === this.props.room.roomId) {
|
2020-07-24 22:09:26 +03:00
|
|
|
// generatePreview() will return nothing if the user has previews disabled
|
|
|
|
this.setState({messagePreview: this.generatePreview()});
|
2020-07-24 07:24:07 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-07-24 22:09:26 +03:00
|
|
|
private generatePreview(): string | null {
|
|
|
|
if (!this.showMessagePreview) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return MessagePreviewStore.instance.getPreviewForRoom(this.props.room, this.props.tag);
|
|
|
|
}
|
|
|
|
|
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: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,
|
2020-08-29 03:11:08 +03:00
|
|
|
0,
|
2020-07-08 01:14:04 +03:00
|
|
|
));
|
|
|
|
} 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-30 01:54:04 +03:00
|
|
|
this.roomProps.notificationVolume = newState;
|
2020-07-01 00:53:30 +03:00
|
|
|
|
2020-07-30 01:54:04 +03:00
|
|
|
const key = (ev as React.KeyboardEvent).key;
|
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-08-14 14:01:16 +03:00
|
|
|
if (MatrixClientPeg.get().isGuest() || this.props.tag === DefaultTagID.Archived ||
|
|
|
|
!this.showContextMenu || this.props.isMinimized
|
|
|
|
) {
|
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-30 01:54:04 +03:00
|
|
|
const state = this.roomProps.notificationVolume;
|
2020-07-01 00:32:59 +03:00
|
|
|
|
2020-06-30 02:16:51 +03:00
|
|
|
let contextMenu = null;
|
2020-07-02 01:06:26 +03:00
|
|
|
if (this.state.notificationsMenuPosition) {
|
2020-08-04 23:42:39 +03:00
|
|
|
contextMenu = <IconizedContextMenu
|
|
|
|
{...contextMenuBelow(this.state.notificationsMenuPosition)}
|
|
|
|
onFinished={this.onCloseNotificationsMenu}
|
|
|
|
className="mx_RoomTile_contextMenu"
|
|
|
|
compact
|
|
|
|
>
|
|
|
|
<IconizedContextMenuOptionList first>
|
2020-08-04 19:20:17 +03:00
|
|
|
<IconizedContextMenuRadio
|
2020-08-04 23:42:39 +03:00
|
|
|
label={_t("Use default")}
|
|
|
|
active={state === ALL_MESSAGES}
|
|
|
|
iconClassName="mx_RoomTile_iconBell"
|
|
|
|
onClick={this.onClickAllNotifs}
|
|
|
|
/>
|
2020-08-04 19:20:17 +03:00
|
|
|
<IconizedContextMenuRadio
|
2020-08-04 23:42:39 +03:00
|
|
|
label={_t("All messages")}
|
|
|
|
active={state === ALL_MESSAGES_LOUD}
|
|
|
|
iconClassName="mx_RoomTile_iconBellDot"
|
|
|
|
onClick={this.onClickAlertMe}
|
|
|
|
/>
|
2020-08-04 19:20:17 +03:00
|
|
|
<IconizedContextMenuRadio
|
2020-08-04 23:42:39 +03:00
|
|
|
label={_t("Mentions & Keywords")}
|
|
|
|
active={state === MENTIONS_ONLY}
|
|
|
|
iconClassName="mx_RoomTile_iconBellMentions"
|
|
|
|
onClick={this.onClickMentions}
|
|
|
|
/>
|
2020-08-04 19:20:17 +03:00
|
|
|
<IconizedContextMenuRadio
|
2020-08-04 23:42:39 +03:00
|
|
|
label={_t("None")}
|
|
|
|
active={state === MUTE}
|
|
|
|
iconClassName="mx_RoomTile_iconBellCrossed"
|
|
|
|
onClick={this.onClickMute}
|
|
|
|
/>
|
|
|
|
</IconizedContextMenuOptionList>
|
|
|
|
</IconizedContextMenu>;
|
2020-06-30 02:16:51 +03:00
|
|
|
}
|
|
|
|
|
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) {
|
2020-08-04 23:42:39 +03:00
|
|
|
contextMenu = <IconizedContextMenu
|
|
|
|
{...contextMenuBelow(this.state.generalMenuPosition)}
|
|
|
|
onFinished={this.onCloseGeneralMenu}
|
|
|
|
className="mx_RoomTile_contextMenu"
|
|
|
|
compact
|
|
|
|
>
|
|
|
|
<IconizedContextMenuOptionList red>
|
|
|
|
<IconizedContextMenuOption
|
|
|
|
iconClassName="mx_RoomTile_iconSignOut"
|
|
|
|
label={_t("Forget Room")}
|
|
|
|
onClick={this.onForgetRoomClick}
|
|
|
|
/>
|
|
|
|
</IconizedContextMenuOptionList>
|
|
|
|
</IconizedContextMenu>;
|
2020-07-13 18:35:03 +03:00
|
|
|
} 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-08-04 23:42:39 +03:00
|
|
|
contextMenu = <IconizedContextMenu
|
|
|
|
{...contextMenuBelow(this.state.generalMenuPosition)}
|
|
|
|
onFinished={this.onCloseGeneralMenu}
|
|
|
|
className="mx_RoomTile_contextMenu"
|
|
|
|
compact
|
|
|
|
>
|
|
|
|
<IconizedContextMenuOptionList>
|
|
|
|
<IconizedContextMenuCheckbox
|
|
|
|
onClick={(e) => this.onTagRoom(e, DefaultTagID.Favourite)}
|
|
|
|
active={isFavorite}
|
|
|
|
label={favouriteLabel}
|
|
|
|
iconClassName="mx_RoomTile_iconStar"
|
|
|
|
/>
|
|
|
|
<IconizedContextMenuCheckbox
|
|
|
|
onClick={(e) => this.onTagRoom(e, DefaultTagID.LowPriority)}
|
|
|
|
active={isLowPriority}
|
|
|
|
label={lowPriorityLabel}
|
|
|
|
iconClassName="mx_RoomTile_iconArrowDown"
|
|
|
|
/>
|
|
|
|
|
|
|
|
<IconizedContextMenuOption
|
|
|
|
onClick={this.onOpenRoomSettings}
|
|
|
|
label={_t("Settings")}
|
|
|
|
iconClassName="mx_RoomTile_iconSettings"
|
|
|
|
/>
|
|
|
|
</IconizedContextMenuOptionList>
|
|
|
|
<IconizedContextMenuOptionList red>
|
|
|
|
<IconizedContextMenuOption
|
|
|
|
onClick={this.onLeaveRoomClick}
|
|
|
|
label={_t("Leave Room")}
|
|
|
|
iconClassName="mx_RoomTile_iconSignOut"
|
|
|
|
/>
|
|
|
|
</IconizedContextMenuOptionList>
|
|
|
|
</IconizedContextMenu>;
|
2020-06-10 08:09:15 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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-08-25 04:19:28 +03:00
|
|
|
let roomProfile: IRoomProfile = {displayName: null, avatarMxc: null};
|
|
|
|
if (this.props.tag === DefaultTagID.Invite) {
|
|
|
|
roomProfile = CommunityPrototypeStore.instance.getInviteProfile(this.props.room.roomId);
|
|
|
|
}
|
|
|
|
|
|
|
|
let name = roomProfile.displayName || 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-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-08-25 04:19:28 +03:00
|
|
|
oobData={({avatarUrl: roomProfile.avatarMxc})}
|
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-06-05 06:21:04 +03:00
|
|
|
let messagePreview = null;
|
2020-07-24 22:09:26 +03:00
|
|
|
if (this.showMessagePreview && this.state.messagePreview) {
|
|
|
|
messagePreview = (
|
|
|
|
<div className="mx_RoomTile_messagePreview" id={messagePreviewId(this.props.room.roomId)}>
|
|
|
|
{this.state.messagePreview}
|
|
|
|
</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-08-14 14:01:16 +03:00
|
|
|
const props: Partial<React.ComponentProps<typeof AccessibleTooltipButton>> = {};
|
2020-07-17 20:16:00 +03:00
|
|
|
let Button: React.ComponentType<React.ComponentProps<typeof AccessibleButton>> = AccessibleButton;
|
|
|
|
if (this.props.isMinimized) {
|
|
|
|
Button = AccessibleTooltipButton;
|
2020-08-14 14:01:16 +03:00
|
|
|
props.title = name;
|
|
|
|
// force the tooltip to hide whilst we are showing the context menu
|
|
|
|
props.forceHide = !!this.state.generalMenuPosition;
|
2020-07-17 20:16:00 +03:00
|
|
|
}
|
|
|
|
|
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-08-14 14:01:16 +03:00
|
|
|
{...props}
|
2020-05-08 21:53:05 +03:00
|
|
|
onFocus={onFocus}
|
|
|
|
tabIndex={isActive ? 0 : -1}
|
|
|
|
inputRef={ref}
|
|
|
|
className={classes}
|
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-07-17 20:16:00 +03:00
|
|
|
</Button>
|
2020-05-08 21:53:05 +03:00
|
|
|
}
|
|
|
|
</RovingTabIndexWrapper>
|
|
|
|
</React.Fragment>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|