Merge branch 'develop' of https://github.com/matrix-org/matrix-react-sdk into t3chguy/room-list/14617

This commit is contained in:
Michael Telatynski 2020-08-14 18:58:26 +01:00
commit 21cdf5073c
17 changed files with 231 additions and 261 deletions

View File

@ -184,7 +184,6 @@
@import "./views/rooms/_RoomRecoveryReminder.scss"; @import "./views/rooms/_RoomRecoveryReminder.scss";
@import "./views/rooms/_RoomSublist.scss"; @import "./views/rooms/_RoomSublist.scss";
@import "./views/rooms/_RoomTile.scss"; @import "./views/rooms/_RoomTile.scss";
@import "./views/rooms/_RoomTileIcon.scss";
@import "./views/rooms/_RoomUpgradeWarningBar.scss"; @import "./views/rooms/_RoomUpgradeWarningBar.scss";
@import "./views/rooms/_SearchBar.scss"; @import "./views/rooms/_SearchBar.scss";
@import "./views/rooms/_SendMessageComposer.scss"; @import "./views/rooms/_SendMessageComposer.scss";

View File

@ -108,13 +108,12 @@ limitations under the License.
.mx_TagPanel .mx_TagTile.mx_TagTile_selected::before { .mx_TagPanel .mx_TagTile.mx_TagTile_selected::before {
content: ''; content: '';
height: calc(100% + 16px); height: 100%;
background-color: $accent-color; background-color: $accent-color;
width: 5px; width: 4px;
position: absolute; position: absolute;
left: -15px; left: -12px;
border-radius: 0 3px 3px 0; border-radius: 0 3px 3px 0;
top: -8px; // (16px from height / 2)
} }
.mx_TagPanel .mx_TagTile.mx_AccessibleButton:focus { .mx_TagPanel .mx_TagTile.mx_AccessibleButton:focus {

View File

@ -18,10 +18,49 @@ limitations under the License.
.mx_DecoratedRoomAvatar, .mx_TemporaryTile { .mx_DecoratedRoomAvatar, .mx_TemporaryTile {
position: relative; position: relative;
.mx_RoomTileIcon { &.mx_DecoratedRoomAvatar_cutout .mx_BaseAvatar {
mask-image: url('$(res)/img/element-icons/roomlist/decorated-avatar-mask.svg');
mask-position: center;
mask-size: contain;
mask-repeat: no-repeat;
}
.mx_DecoratedRoomAvatar_icon {
position: absolute; position: absolute;
bottom: 0; bottom: -2px;
right: 0; right: -2px;
margin: 4px;
width: 8px;
height: 8px;
border-radius: 50%;
}
.mx_DecoratedRoomAvatar_icon::before {
content: '';
width: 8px;
height: 8px;
position: absolute;
border-radius: 8px;
}
.mx_DecoratedRoomAvatar_icon_globe::before {
mask-position: center;
mask-size: contain;
mask-repeat: no-repeat;
background: $secondary-fg-color;
mask-image: url('$(res)/img/globe.svg');
}
.mx_DecoratedRoomAvatar_icon_offline::before {
background-color: $presence-offline;
}
.mx_DecoratedRoomAvatar_icon_online::before {
background-color: $presence-online;
}
.mx_DecoratedRoomAvatar_icon_away::before {
background-color: $presence-away;
} }
.mx_NotificationBadge, .mx_RoomTile_badgeContainer { .mx_NotificationBadge, .mx_RoomTile_badgeContainer {

View File

@ -41,7 +41,7 @@ limitations under the License.
width: calc(10px + 48px + 100% + 8px); width: calc(10px + 48px + 100% + 8px);
// safe area + action bar // safe area + action bar
height: calc(20px + 100%); height: calc(20px + 100%);
top: -20px; top: -12px;
left: -58px; left: -58px;
z-index: -1; z-index: -1;
cursor: initial; cursor: initial;

View File

@ -1,69 +0,0 @@
/*
Copyright 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.
*/
.mx_RoomTileIcon {
width: 12px;
height: 12px;
border-radius: 12px;
background-color: $roomlist-bg-color; // to match the room list itself
}
.mx_RoomTileIcon_globe::before {
content: '';
width: 8px;
height: 8px;
top: 2px;
left: 2px;
position: absolute;
mask-position: center;
mask-size: contain;
mask-repeat: no-repeat;
background: $primary-fg-color;
mask-image: url('$(res)/img/globe.svg');
}
.mx_RoomTileIcon_offline::before {
content: '';
width: 8px;
height: 8px;
top: 2px;
left: 2px;
position: absolute;
border-radius: 8px;
background-color: $presence-offline;
}
.mx_RoomTileIcon_online::before {
content: '';
width: 8px;
height: 8px;
top: 2px;
left: 2px;
position: absolute;
border-radius: 8px;
background-color: $presence-online;
}
.mx_RoomTileIcon_away::before {
content: '';
width: 8px;
height: 8px;
top: 2px;
left: 2px;
position: absolute;
border-radius: 8px;
background-color: $presence-away;
}

View File

@ -0,0 +1,3 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M30.7161 22.2903C31.5425 20.3595 32 18.2332 32 16C32 7.16344 24.8366 0 16 0C7.16344 0 0 7.16344 0 16C0 24.8366 7.16344 32 16 32C18.2332 32 20.3595 31.5425 22.2903 30.7161C20.8956 29.6174 20 27.9133 20 26C20 22.6863 22.6863 20 26 20C27.9133 20 29.6174 20.8956 30.7161 22.2903Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 444 B

View File

@ -1,6 +1,3 @@
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="icon"> <path fill-rule="evenodd" clip-rule="evenodd" d="M4 8C6.20914 8 8 6.20914 8 4C8 1.79086 6.20914 0 4 0C1.79086 0 0 1.79086 0 4C0 6.20914 1.79086 8 4 8ZM6.53332 5.3468L6.24528 6.23562C6.23346 6.27208 6.21342 6.30534 6.1867 6.33282L5.90913 6.61831L5.59095 6.94559L5.52111 7.01743C5.4003 7.14169 5.19279 7.10832 5.11702 6.95246L4.97216 6.65447C4.96052 6.63052 4.94513 6.60859 4.92657 6.58949L4.63641 6.29104L4.39185 6.0395C4.34478 5.99108 4.28013 5.96377 4.2126 5.96377H3.83831C3.74261 5.96377 3.6553 5.90914 3.61347 5.82307L3.38884 5.36098C3.37228 5.32692 3.36368 5.28955 3.36368 5.25168V4.47996C3.36368 4.38629 3.31132 4.30048 3.22802 4.25764L2.78114 4.02781C2.74577 4.00962 2.70657 4.00013 2.6668 4.00013H2.19658C2.12905 4.00013 2.0644 3.97282 2.01733 3.9244L1.53085 3.42402C1.48223 3.37402 1.45672 3.30599 1.46046 3.23635L1.49316 2.62739C1.4977 2.54285 1.51357 2.45929 1.54034 2.37897L1.65227 2.0432C1.87098 1.38707 2.38792 0.873558 3.0455 0.659224L3.05974 0.654815C3.66901 0.466234 4.32388 0.487134 4.91987 0.714181C4.94274 0.722892 4.96343 0.736497 4.98049 0.754043L5.42152 1.20768C5.51586 1.30471 5.51586 1.45919 5.42152 1.55622L4.70716 2.291C4.66179 2.33766 4.63641 2.40018 4.63641 2.46527V2.71165C4.63641 2.86997 4.49111 2.98843 4.33603 2.95652L3.34587 2.75283C3.1908 2.72093 3.0455 2.83938 3.0455 2.99771V3.09559C3.0455 3.23366 3.15743 3.34559 3.2955 3.34559H3.75004C3.88812 3.34559 4.00004 3.45752 4.00004 3.59559V3.75013C4.00004 3.8882 4.11197 4.00013 4.25004 4.00013H5.16715C5.23467 4.00013 5.29933 4.02745 5.3464 4.07586L5.56243 4.29807C5.58132 4.3175 5.60324 4.33373 5.62734 4.34612L6.19093 4.63596C6.21503 4.64836 6.23695 4.66459 6.25584 4.68402L6.47475 4.90918C6.52012 4.95584 6.5455 5.01836 6.5455 5.08345V5.26973C6.5455 5.2959 6.54139 5.3219 6.53332 5.3468Z" fill="#A9B2BC"/>
<path id="sea" fill-rule="evenodd" clip-rule="evenodd" d="M4 8C6.20914 8 8 6.20914 8 4C8 1.79086 6.20914 0 4 0C1.79086 0 0 1.79086 0 4C0 6.20914 1.79086 8 4 8ZM4.6693 2.43613C4.8306 2.64728 4.94732 2.80007 4.45289 2.80007C4.14732 2.80007 3.84175 2.74171 3.58076 2.69186C3.15847 2.61121 2.85289 2.55285 2.85289 2.80007C2.85289 3.00007 3.65289 3.40007 4.45289 3.80007C5.25289 4.20007 6.05289 4.60007 6.05289 4.80007C6.05289 5.20007 6.05289 7.60007 5.25289 7.20007C4.45289 6.80007 2.45289 5.20007 2.45289 4.80007C2.45289 4.65277 2.18168 4.39698 1.85897 4.09263C1.30535 3.57051 0.600192 2.90547 0.852893 2.40007C1.25289 1.60007 2.85289 6.51479e-05 5.25289 0.800065C4.98623 1.06673 4.45289 1.68007 4.45289 2.00007C4.45289 2.15285 4.56961 2.30564 4.6693 2.43613Z" fill="#2E2F32"/>
<path id="earth" d="M4.45294 2.80007C5.25294 2.80007 4.45294 2.40007 4.45294 2.00007C4.45294 1.68007 4.98627 1.06673 5.25294 0.800065C2.85294 6.51479e-05 1.25294 1.60007 0.852941 2.40007C0.452941 3.20007 2.45294 4.40007 2.45294 4.80007C2.45294 5.20007 4.45294 6.80007 5.25294 7.20007C6.05294 7.60007 6.05294 5.20007 6.05294 4.80007C6.05294 4.40007 2.85294 3.20007 2.85294 2.80007C2.85294 2.40007 3.65294 2.80007 4.45294 2.80007Z" />
</g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -15,6 +15,7 @@ $room-highlight-color: #343a46;
// typical text (dark-on-white in light skin) // typical text (dark-on-white in light skin)
$primary-fg-color: $text-primary-color; $primary-fg-color: $text-primary-color;
$secondary-fg-color: $primary-fg-color;
$primary-bg-color: $bg-color; $primary-bg-color: $bg-color;
$muted-fg-color: $header-panel-text-primary-color; $muted-fg-color: $header-panel-text-primary-color;

View File

@ -23,6 +23,7 @@ $header-panel-bg-color: #f3f8fd;
// typical text (dark-on-white in light skin) // typical text (dark-on-white in light skin)
$primary-fg-color: #2e2f32; $primary-fg-color: #2e2f32;
$secondary-fg-color: $primary-fg-color;
$primary-bg-color: #ffffff; $primary-bg-color: #ffffff;
$muted-fg-color: #61708b; // Commonly used in headings and relevant alt text $muted-fg-color: #61708b; // Commonly used in headings and relevant alt text

View File

@ -733,7 +733,7 @@ export const Commands = [
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const room = cli.getRoom(roomId); const room = cli.getRoom(roomId);
if (!room) return reject(_t("Command failed")); if (!room) return reject(_t("Command failed"));
const member = room.getMember(args); const member = room.getMember(userId);
if (!member || getEffectiveMembership(member.membership) === EffectiveMembership.Leave) { if (!member || getEffectiveMembership(member.membership) === EffectiveMembership.Leave) {
return reject(_t("Could not find user in room")); return reject(_t("Could not find user in room"));
} }

View File

@ -14,15 +14,22 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from 'react'; import React from "react";
import classNames from "classnames";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import { User } from "matrix-js-sdk/src/models/user";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { TagID } from '../../../stores/room-list/models'; import { TagID } from '../../../stores/room-list/models';
import RoomAvatar from "./RoomAvatar"; import RoomAvatar from "./RoomAvatar";
import RoomTileIcon from "../rooms/RoomTileIcon";
import NotificationBadge from '../rooms/NotificationBadge'; import NotificationBadge from '../rooms/NotificationBadge';
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
import { NotificationState } from "../../../stores/notifications/NotificationState"; import { NotificationState } from "../../../stores/notifications/NotificationState";
import {isPresenceEnabled} from "../../../utils/presence";
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import {_t} from "../../../languageHandler";
import TextWithTooltip from "../elements/TextWithTooltip";
import DMRoomMap from "../../../utils/DMRoomMap";
interface IProps { interface IProps {
room: Room; room: Room;
@ -36,18 +43,134 @@ interface IProps {
interface IState { interface IState {
notificationState?: NotificationState; notificationState?: NotificationState;
icon: Icon;
}
enum Icon {
// Note: the names here are used in CSS class names
None = "NONE", // ... except this one
Globe = "GLOBE",
PresenceOnline = "ONLINE",
PresenceAway = "AWAY",
PresenceOffline = "OFFLINE",
}
function tooltipText(variant: Icon) {
switch (variant) {
case Icon.Globe:
return _t("This room is public");
case Icon.PresenceOnline:
return _t("Online");
case Icon.PresenceAway:
return _t("Away");
case Icon.PresenceOffline:
return _t("Offline");
}
} }
export default class DecoratedRoomAvatar extends React.PureComponent<IProps, IState> { export default class DecoratedRoomAvatar extends React.PureComponent<IProps, IState> {
private _dmUser: User;
private isUnmounted = false;
private isWatchingTimeline = false;
constructor(props: IProps) { constructor(props: IProps) {
super(props); super(props);
this.state = { this.state = {
notificationState: RoomNotificationStateStore.instance.getRoomState(this.props.room), notificationState: RoomNotificationStateStore.instance.getRoomState(this.props.room),
icon: this.calculateIcon(),
}; };
} }
public componentWillUnmount() {
this.isUnmounted = true;
if (this.isWatchingTimeline) this.props.room.off('Room.timeline', this.onRoomTimeline);
this.dmUser = null; // clear listeners, if any
}
private get isPublicRoom(): boolean {
const joinRules = this.props.room.currentState.getStateEvents("m.room.join_rules", "");
const joinRule = joinRules && joinRules.getContent().join_rule;
return joinRule === 'public';
}
private get dmUser(): User {
return this._dmUser;
}
private set dmUser(val: User) {
const oldUser = this._dmUser;
this._dmUser = val;
if (oldUser && oldUser !== this._dmUser) {
oldUser.off('User.currentlyActive', this.onPresenceUpdate);
oldUser.off('User.presence', this.onPresenceUpdate);
}
if (this._dmUser && oldUser !== this._dmUser) {
this._dmUser.on('User.currentlyActive', this.onPresenceUpdate);
this._dmUser.on('User.presence', this.onPresenceUpdate);
}
}
private onRoomTimeline = (ev: MatrixEvent, room: Room) => {
if (this.isUnmounted) return;
// apparently these can happen?
if (!room) return;
if (this.props.room.roomId !== room.roomId) return;
if (ev.getType() === 'm.room.join_rules' || ev.getType() === 'm.room.member') {
this.setState({icon: this.calculateIcon()});
}
};
private onPresenceUpdate = () => {
if (this.isUnmounted) return;
let newIcon = this.getPresenceIcon();
if (newIcon !== this.state.icon) this.setState({icon: newIcon});
};
private getPresenceIcon(): Icon {
if (!this.dmUser) return Icon.None;
let icon = Icon.None;
const isOnline = this.dmUser.currentlyActive || this.dmUser.presence === 'online';
if (isOnline) {
icon = Icon.PresenceOnline;
} else if (this.dmUser.presence === 'offline') {
icon = Icon.PresenceOffline;
} else if (this.dmUser.presence === 'unavailable') {
icon = Icon.PresenceAway;
}
return icon;
}
private calculateIcon(): Icon {
let icon = Icon.None;
// We look at the DMRoomMap and not the tag here so that we don't exclude DMs in Favourites
const otherUserId = DMRoomMap.shared().getUserIdForRoomId(this.props.room.roomId);
if (otherUserId && this.props.room.getJoinedMemberCount() === 2) {
// Track presence, if available
if (isPresenceEnabled()) {
if (otherUserId) {
this.dmUser = MatrixClientPeg.get().getUser(otherUserId);
icon = this.getPresenceIcon();
}
}
} else {
// Track publicity
icon = this.isPublicRoom ? Icon.Globe : Icon.None;
if (!this.isWatchingTimeline) {
this.props.room.on('Room.timeline', this.onRoomTimeline);
this.isWatchingTimeline = true;
}
}
return icon;
}
public render(): React.ReactNode { public render(): React.ReactNode {
let badge: React.ReactNode; let badge: React.ReactNode;
if (this.props.displayBadge) { if (this.props.displayBadge) {
@ -58,7 +181,19 @@ export default class DecoratedRoomAvatar extends React.PureComponent<IProps, ISt
/>; />;
} }
return <div className="mx_DecoratedRoomAvatar"> let icon;
if (this.state.icon !== Icon.None) {
icon = <TextWithTooltip
tooltip={tooltipText(this.state.icon)}
class={`mx_DecoratedRoomAvatar_icon mx_DecoratedRoomAvatar_icon_${this.state.icon.toLowerCase()}`}
/>;
}
const classes = classNames("mx_DecoratedRoomAvatar", {
mx_DecoratedRoomAvatar_cutout: icon,
});
return <div className={classes}>
<RoomAvatar <RoomAvatar
room={this.props.room} room={this.props.room}
width={this.props.avatarSize} width={this.props.avatarSize}
@ -66,7 +201,7 @@ export default class DecoratedRoomAvatar extends React.PureComponent<IProps, ISt
oobData={this.props.oobData} oobData={this.props.oobData}
viewAvatarOnClick={this.props.viewAvatarOnClick} viewAvatarOnClick={this.props.viewAvatarOnClick}
/> />
<RoomTileIcon room={this.props.room} /> {icon}
{badge} {badge}
</div>; </div>;
} }

View File

@ -18,6 +18,7 @@ limitations under the License.
import React, {useEffect} from 'react'; import React, {useEffect} from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { EventStatus } from 'matrix-js-sdk';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
@ -114,13 +115,19 @@ export default class MessageActionBar extends React.PureComponent {
static contextType = RoomContext; static contextType = RoomContext;
componentDidMount() { componentDidMount() {
this.props.mxEvent.on("Event.decrypted", this.onDecrypted); if (this.props.mxEvent.status && this.props.mxEvent.status !== EventStatus.SENT) {
this.props.mxEvent.on("Event.status", this.onSent);
}
if (this.props.mxEvent.isBeingDecrypted()) {
this.props.mxEvent.once("Event.decrypted", this.onDecrypted);
}
this.props.mxEvent.on("Event.beforeRedaction", this.onBeforeRedaction); this.props.mxEvent.on("Event.beforeRedaction", this.onBeforeRedaction);
} }
componentWillUnmount() { componentWillUnmount() {
this.props.mxEvent.removeListener("Event.decrypted", this.onDecrypted); this.props.mxEvent.off("Event.status", this.onSent);
this.props.mxEvent.removeListener("Event.beforeRedaction", this.onBeforeRedaction); this.props.mxEvent.off("Event.decrypted", this.onDecrypted);
this.props.mxEvent.off("Event.beforeRedaction", this.onBeforeRedaction);
} }
onDecrypted = () => { onDecrypted = () => {
@ -134,6 +141,11 @@ export default class MessageActionBar extends React.PureComponent {
this.forceUpdate(); this.forceUpdate();
}; };
onSent = () => {
// When an event is sent and echoed the possible actions change.
this.forceUpdate();
};
onFocusChange = (focused) => { onFocusChange = (focused) => {
if (!this.props.onFocusChange) { if (!this.props.onFocusChange) {
return; return;

View File

@ -1,168 +0,0 @@
/*
Copyright 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.
*/
import React from "react";
import { Room } from "matrix-js-sdk/src/models/room";
import { DefaultTagID, TagID } from "../../../stores/room-list/models";
import { User } from "matrix-js-sdk/src/models/user";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import DMRoomMap from "../../../utils/DMRoomMap";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { isPresenceEnabled } from "../../../utils/presence";
import { _t } from "../../../languageHandler";
import TextWithTooltip from "../elements/TextWithTooltip";
enum Icon {
// Note: the names here are used in CSS class names
None = "NONE", // ... except this one
Globe = "GLOBE",
PresenceOnline = "ONLINE",
PresenceAway = "AWAY",
PresenceOffline = "OFFLINE",
}
function tooltipText(variant: Icon) {
switch (variant) {
case Icon.Globe:
return _t("This room is public");
case Icon.PresenceOnline:
return _t("Online");
case Icon.PresenceAway:
return _t("Away");
case Icon.PresenceOffline:
return _t("Offline");
}
}
interface IProps {
room: Room;
}
interface IState {
icon: Icon;
}
export default class RoomTileIcon extends React.Component<IProps, IState> {
private _dmUser: User;
private isUnmounted = false;
private isWatchingTimeline = false;
constructor(props: IProps) {
super(props);
this.state = {
icon: this.calculateIcon(),
};
}
private get isPublicRoom(): boolean {
const joinRules = this.props.room.currentState.getStateEvents("m.room.join_rules", "");
const joinRule = joinRules && joinRules.getContent().join_rule;
return joinRule === 'public';
}
private get dmUser(): User {
return this._dmUser;
}
private set dmUser(val: User) {
const oldUser = this._dmUser;
this._dmUser = val;
if (oldUser && oldUser !== this._dmUser) {
oldUser.off('User.currentlyActive', this.onPresenceUpdate);
oldUser.off('User.presence', this.onPresenceUpdate);
}
if (this._dmUser && oldUser !== this._dmUser) {
this._dmUser.on('User.currentlyActive', this.onPresenceUpdate);
this._dmUser.on('User.presence', this.onPresenceUpdate);
}
}
public componentWillUnmount() {
this.isUnmounted = true;
if (this.isWatchingTimeline) this.props.room.off('Room.timeline', this.onRoomTimeline);
this.dmUser = null; // clear listeners, if any
}
private onRoomTimeline = (ev: MatrixEvent, room: Room) => {
if (this.isUnmounted) return;
// apparently these can happen?
if (!room) return;
if (this.props.room.roomId !== room.roomId) return;
if (ev.getType() === 'm.room.join_rules' || ev.getType() === 'm.room.member') {
this.setState({icon: this.calculateIcon()});
}
};
private onPresenceUpdate = () => {
if (this.isUnmounted) return;
let newIcon = this.getPresenceIcon();
if (newIcon !== this.state.icon) this.setState({icon: newIcon});
};
private getPresenceIcon(): Icon {
if (!this.dmUser) return Icon.None;
let icon = Icon.None;
const isOnline = this.dmUser.currentlyActive || this.dmUser.presence === 'online';
if (isOnline) {
icon = Icon.PresenceOnline;
} else if (this.dmUser.presence === 'offline') {
icon = Icon.PresenceOffline;
} else if (this.dmUser.presence === 'unavailable') {
icon = Icon.PresenceAway;
}
return icon;
}
private calculateIcon(): Icon {
let icon = Icon.None;
// We look at the DMRoomMap and not the tag here so that we don't exclude DMs in Favourites
const otherUserId = DMRoomMap.shared().getUserIdForRoomId(this.props.room.roomId);
if (otherUserId && this.props.room.getJoinedMemberCount() === 2) {
// Track presence, if available
if (isPresenceEnabled()) {
if (otherUserId) {
this.dmUser = MatrixClientPeg.get().getUser(otherUserId);
icon = this.getPresenceIcon();
}
}
} else {
// Track publicity
icon = this.isPublicRoom ? Icon.Globe : Icon.None;
if (!this.isWatchingTimeline) {
this.props.room.on('Room.timeline', this.onRoomTimeline);
this.isWatchingTimeline = true;
}
}
return icon;
}
public render(): React.ReactElement {
if (this.state.icon === Icon.None) return null;
return <TextWithTooltip
tooltip={tooltipText(this.state.icon)}
class={`mx_RoomTileIcon mx_RoomTileIcon_${this.state.icon.toLowerCase()}`}
/>;
}
}

View File

@ -448,6 +448,8 @@
"Multiple integration managers": "Multiple integration managers", "Multiple integration managers": "Multiple integration managers",
"Try out new ways to ignore people (experimental)": "Try out new ways to ignore people (experimental)", "Try out new ways to ignore people (experimental)": "Try out new ways to ignore people (experimental)",
"Support adding custom themes": "Support adding custom themes", "Support adding custom themes": "Support adding custom themes",
"Show message previews for reactions in DMs": "Show message previews for reactions in DMs",
"Show message previews for reactions in all rooms": "Show message previews for reactions in all rooms",
"Enable advanced debugging for the room list": "Enable advanced debugging for the room list", "Enable advanced debugging for the room list": "Enable advanced debugging for the room list",
"Show info about bridges in room settings": "Show info about bridges in room settings", "Show info about bridges in room settings": "Show info about bridges in room settings",
"Font size": "Font size", "Font size": "Font size",
@ -1187,8 +1189,6 @@
"%(count)s unread messages.|other": "%(count)s unread messages.", "%(count)s unread messages.|other": "%(count)s unread messages.",
"%(count)s unread messages.|one": "1 unread message.", "%(count)s unread messages.|one": "1 unread message.",
"Unread messages.": "Unread messages.", "Unread messages.": "Unread messages.",
"This room is public": "This room is public",
"Away": "Away",
"Add a topic": "Add a topic", "Add a topic": "Add a topic",
"Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.", "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.",
"This room has already been upgraded.": "This room has already been upgraded.", "This room has already been upgraded.": "This room has already been upgraded.",
@ -1894,6 +1894,8 @@
"Take picture": "Take picture", "Take picture": "Take picture",
"Remove for everyone": "Remove for everyone", "Remove for everyone": "Remove for everyone",
"Remove for me": "Remove for me", "Remove for me": "Remove for me",
"This room is public": "This room is public",
"Away": "Away",
"User Status": "User Status", "User Status": "User Status",
"powered by Matrix": "powered by Matrix", "powered by Matrix": "powered by Matrix",
"This homeserver would like to make sure you are not a robot.": "This homeserver would like to make sure you are not a robot.", "This homeserver would like to make sure you are not a robot.": "This homeserver would like to make sure you are not a robot.",

View File

@ -158,6 +158,18 @@ export const SETTINGS: {[setting: string]: ISetting} = {
supportedLevels: LEVELS_FEATURE, supportedLevels: LEVELS_FEATURE,
default: false, default: false,
}, },
"feature_roomlist_preview_reactions_dms": {
isFeature: true,
displayName: _td("Show message previews for reactions in DMs"),
supportedLevels: LEVELS_FEATURE,
default: false,
},
"feature_roomlist_preview_reactions_all": {
isFeature: true,
displayName: _td("Show message previews for reactions in all rooms"),
supportedLevels: LEVELS_FEATURE,
default: false,
},
"advancedRoomListLogging": { "advancedRoomListLogging": {
// TODO: Remove flag before launch: https://github.com/vector-im/element-web/issues/14231 // TODO: Remove flag before launch: https://github.com/vector-im/element-web/issues/14231
displayName: _td("Enable advanced debugging for the room list"), displayName: _td("Enable advanced debugging for the room list"),

View File

@ -27,5 +27,5 @@ export interface IPreview {
* @param tagId Optional. The tag where the room the event was sent in resides. * @param tagId Optional. The tag where the room the event was sent in resides.
* @returns The preview. * @returns The preview.
*/ */
getTextFor(event: MatrixEvent, tagId?: TagID): string; getTextFor(event: MatrixEvent, tagId?: TagID): string | null;
} }

View File

@ -19,9 +19,16 @@ import { TagID } from "../models";
import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils"; import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import SettingsStore from "../../../settings/SettingsStore";
import DMRoomMap from "../../../utils/DMRoomMap";
export class ReactionEventPreview implements IPreview { export class ReactionEventPreview implements IPreview {
public getTextFor(event: MatrixEvent, tagId?: TagID): string { public getTextFor(event: MatrixEvent, tagId?: TagID): string {
const showDms = SettingsStore.isFeatureEnabled("feature_roomlist_preview_reactions_dms");
const showAll = SettingsStore.isFeatureEnabled("feature_roomlist_preview_reactions_all");
if (!showAll && (!showDms || DMRoomMap.shared().getUserIdForRoomId(event.getRoomId()))) return null;
const relation = event.getRelation(); const relation = event.getRelation();
if (!relation) return null; // invalid reaction (probably redacted) if (!relation) return null; // invalid reaction (probably redacted)