Merge pull request #4774 from matrix-org/travis/room-list/presence-globes
Add presence indicators and globes to new room list
This commit is contained in:
commit
25e353f7e5
9 changed files with 269 additions and 2 deletions
|
@ -189,6 +189,7 @@
|
||||||
@import "./views/rooms/_RoomSublist2.scss";
|
@import "./views/rooms/_RoomSublist2.scss";
|
||||||
@import "./views/rooms/_RoomTile.scss";
|
@import "./views/rooms/_RoomTile.scss";
|
||||||
@import "./views/rooms/_RoomTile2.scss";
|
@import "./views/rooms/_RoomTile2.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";
|
||||||
|
|
|
@ -32,6 +32,13 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_RoomTile2_avatarContainer {
|
.mx_RoomTile2_avatarContainer {
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.mx_RoomTileIcon {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomTile2_nameContainer {
|
.mx_RoomTile2_nameContainer {
|
||||||
|
|
69
res/css/views/rooms/_RoomTileIcon.scss
Normal file
69
res/css/views/rooms/_RoomTileIcon.scss
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
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: $roomlist2-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;
|
||||||
|
}
|
6
res/img/globe.svg
Normal file
6
res/img/globe.svg
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="icon">
|
||||||
|
<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>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -186,6 +186,10 @@ $roomtile2-preview-color: #9e9e9e;
|
||||||
$roomtile2-default-badge-bg-color: #61708b;
|
$roomtile2-default-badge-bg-color: #61708b;
|
||||||
$roomtile2-selected-bg-color: #FFF;
|
$roomtile2-selected-bg-color: #FFF;
|
||||||
|
|
||||||
|
$presence-online: $accent-color;
|
||||||
|
$presence-away: orange; // TODO: Get color
|
||||||
|
$presence-offline: #E3E8F0;
|
||||||
|
|
||||||
// ********************
|
// ********************
|
||||||
|
|
||||||
$roomtile-name-color: #61708b;
|
$roomtile-name-color: #61708b;
|
||||||
|
|
|
@ -145,6 +145,7 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
|
||||||
key={`room-${room.roomId}`}
|
key={`room-${room.roomId}`}
|
||||||
showMessagePreview={this.props.layout.showPreviews}
|
showMessagePreview={this.props.layout.showPreviews}
|
||||||
isMinimized={this.props.isMinimized}
|
isMinimized={this.props.isMinimized}
|
||||||
|
tag={this.props.layout.tagId}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ import React, { createRef } from "react";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex";
|
import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex";
|
||||||
import AccessibleButton, {ButtonEvent} from "../../views/elements/AccessibleButton";
|
import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton";
|
||||||
import RoomAvatar from "../../views/avatars/RoomAvatar";
|
import RoomAvatar from "../../views/avatars/RoomAvatar";
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
import { Key } from "../../../Keyboard";
|
import { Key } from "../../../Keyboard";
|
||||||
|
@ -31,6 +31,7 @@ import { _t } from "../../../languageHandler";
|
||||||
import { ContextMenu, ContextMenuButton } from "../../structures/ContextMenu";
|
import { ContextMenu, ContextMenuButton } from "../../structures/ContextMenu";
|
||||||
import { DefaultTagID, TagID } from "../../../stores/room-list/models";
|
import { DefaultTagID, TagID } from "../../../stores/room-list/models";
|
||||||
import { MessagePreviewStore } from "../../../stores/MessagePreviewStore";
|
import { MessagePreviewStore } from "../../../stores/MessagePreviewStore";
|
||||||
|
import RoomTileIcon from "./RoomTileIcon";
|
||||||
|
|
||||||
/*******************************************************************
|
/*******************************************************************
|
||||||
* CAUTION *
|
* CAUTION *
|
||||||
|
@ -44,6 +45,7 @@ interface IProps {
|
||||||
room: Room;
|
room: Room;
|
||||||
showMessagePreview: boolean;
|
showMessagePreview: boolean;
|
||||||
isMinimized: boolean;
|
isMinimized: boolean;
|
||||||
|
tag: TagID;
|
||||||
|
|
||||||
// TODO: Allow falsifying counts (for invites and stuff)
|
// TODO: Allow falsifying counts (for invites and stuff)
|
||||||
// TODO: Transparency? Was this ever used?
|
// TODO: Transparency? Was this ever used?
|
||||||
|
@ -303,7 +305,8 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
|
||||||
role="treeitem"
|
role="treeitem"
|
||||||
>
|
>
|
||||||
<div className="mx_RoomTile2_avatarContainer">
|
<div className="mx_RoomTile2_avatarContainer">
|
||||||
<RoomAvatar room={this.props.room} width={avatarSize} height={avatarSize}/>
|
<RoomAvatar room={this.props.room} width={avatarSize} height={avatarSize} />
|
||||||
|
<RoomTileIcon room={this.props.room} tag={this.props.tag} />
|
||||||
</div>
|
</div>
|
||||||
{nameContainer}
|
{nameContainer}
|
||||||
<div className="mx_RoomTile2_badgeContainer">
|
<div className="mx_RoomTile2_badgeContainer">
|
||||||
|
|
150
src/components/views/rooms/RoomTileIcon.tsx
Normal file
150
src/components/views/rooms/RoomTileIcon.tsx
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
/*
|
||||||
|
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";
|
||||||
|
|
||||||
|
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",
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
room: Room;
|
||||||
|
tag: TagID;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (this.props.tag === DefaultTagID.DM && this.props.room.getJoinedMemberCount() === 2) {
|
||||||
|
// Track presence, if available
|
||||||
|
if (isPresenceEnabled()) {
|
||||||
|
const otherUserId = DMRoomMap.shared().getUserIdForRoomId(this.props.room.roomId);
|
||||||
|
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 <span className={`mx_RoomTileIcon mx_RoomTileIcon_${this.state.icon.toLowerCase()}`} />;
|
||||||
|
}
|
||||||
|
}
|
26
src/utils/presence.ts
Normal file
26
src/utils/presence.ts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
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 { MatrixClientPeg } from "../MatrixClientPeg";
|
||||||
|
import SdkConfig from "../SdkConfig";
|
||||||
|
|
||||||
|
export function isPresenceEnabled() {
|
||||||
|
const hsUrl = MatrixClientPeg.get().baseUrl;
|
||||||
|
const urls = SdkConfig.get()['enable_presence_by_hs_url'];
|
||||||
|
if (!urls) return true;
|
||||||
|
if (urls[hsUrl] || urls[hsUrl] === undefined) return true;
|
||||||
|
return false;
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue