Merge pull request #5106 from matrix-org/t3chguy/room-list/14608
Switch out the globe icon and colour it depending on theme
This commit is contained in:
commit
be1d390a93
10 changed files with 189 additions and 251 deletions
|
@ -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";
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
3
res/img/element-icons/roomlist/decorated-avatar-mask.svg
Normal file
3
res/img/element-icons/roomlist/decorated-avatar-mask.svg
Normal 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 |
|
@ -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 |
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()}`}
|
|
||||||
/>;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1188,8 +1188,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.",
|
||||||
|
@ -1895,6 +1893,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.",
|
||||||
|
|
Loading…
Reference in a new issue