Merge pull request #5143 from matrix-org/travis/communities/proto/invites

Communities v2 prototype: Override invite aesthetics for community-as-room invites
This commit is contained in:
Travis Ralston 2020-08-26 08:32:29 -06:00 committed by GitHub
commit 98d098a3eb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 148 additions and 8 deletions

View file

@ -26,6 +26,8 @@ import classNames from 'classnames';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import SdkConfig from "../../../SdkConfig"; import SdkConfig from "../../../SdkConfig";
import IdentityAuthClient from '../../../IdentityAuthClient'; import IdentityAuthClient from '../../../IdentityAuthClient';
import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore";
import {UPDATE_EVENT} from "../../../stores/AsyncStore";
const MessageCase = Object.freeze({ const MessageCase = Object.freeze({
NotLoggedIn: "NotLoggedIn", NotLoggedIn: "NotLoggedIn",
@ -100,6 +102,7 @@ export default createReactClass({
componentDidMount: function() { componentDidMount: function() {
this._checkInvitedEmail(); this._checkInvitedEmail();
CommunityPrototypeStore.instance.on(UPDATE_EVENT, this._onCommunityUpdate);
}, },
componentDidUpdate: function(prevProps, prevState) { componentDidUpdate: function(prevProps, prevState) {
@ -108,6 +111,10 @@ export default createReactClass({
} }
}, },
componentWillUnmount: function() {
CommunityPrototypeStore.instance.off(UPDATE_EVENT, this._onCommunityUpdate);
},
_checkInvitedEmail: async function() { _checkInvitedEmail: async function() {
// If this is an invite and we've been told what email address was // If this is an invite and we've been told what email address was
// invited, fetch the user's account emails and discovery bindings so we // invited, fetch the user's account emails and discovery bindings so we
@ -143,6 +150,13 @@ export default createReactClass({
} }
}, },
_onCommunityUpdate: function (roomId) {
if (this.props.room && this.props.room.roomId !== roomId) {
return;
}
this.forceUpdate(); // we have nothing to update
},
_getMessageCase() { _getMessageCase() {
const isGuest = MatrixClientPeg.get().isGuest(); const isGuest = MatrixClientPeg.get().isGuest();
@ -219,8 +233,15 @@ export default createReactClass({
} }
}, },
_communityProfile: function() {
if (this.props.room) return CommunityPrototypeStore.instance.getInviteProfile(this.props.room.roomId);
return {displayName: null, avatarMxc: null};
},
_roomName: function(atStart = false) { _roomName: function(atStart = false) {
const name = this.props.room ? this.props.room.name : this.props.roomAlias; let name = this.props.room ? this.props.room.name : this.props.roomAlias;
const profile = this._communityProfile();
if (profile.displayName) name = profile.displayName;
if (name) { if (name) {
return name; return name;
} else if (atStart) { } else if (atStart) {
@ -439,7 +460,10 @@ export default createReactClass({
} }
case MessageCase.Invite: { case MessageCase.Invite: {
const RoomAvatar = sdk.getComponent("views.avatars.RoomAvatar"); const RoomAvatar = sdk.getComponent("views.avatars.RoomAvatar");
const avatar = <RoomAvatar room={this.props.room} oobData={this.props.oobData} />; const oobData = Object.assign({}, this.props.oobData, {
avatarUrl: this._communityProfile().avatarMxc,
});
const avatar = <RoomAvatar room={this.props.room} oobData={oobData} />;
const inviteMember = this._getInviteMember(); const inviteMember = this._getInviteMember();
let inviterElement; let inviterElement;

View file

@ -27,7 +27,7 @@ import defaultDispatcher from '../../../dispatcher/dispatcher';
import { Key } from "../../../Keyboard"; import { Key } from "../../../Keyboard";
import ActiveRoomObserver from "../../../ActiveRoomObserver"; import ActiveRoomObserver from "../../../ActiveRoomObserver";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import { ChevronFace, ContextMenuTooltipButton, MenuItemRadio } from "../../structures/ContextMenu"; import { ChevronFace, ContextMenuTooltipButton } from "../../structures/ContextMenu";
import { DefaultTagID, TagID } from "../../../stores/room-list/models"; import { DefaultTagID, TagID } from "../../../stores/room-list/models";
import { MessagePreviewStore, ROOM_PREVIEW_CHANGED } from "../../../stores/room-list/MessagePreviewStore"; import { MessagePreviewStore, ROOM_PREVIEW_CHANGED } from "../../../stores/room-list/MessagePreviewStore";
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar"; import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
@ -47,8 +47,11 @@ import { PROPERTY_UPDATED } from "../../../stores/local-echo/GenericEchoChamber"
import IconizedContextMenu, { import IconizedContextMenu, {
IconizedContextMenuCheckbox, IconizedContextMenuCheckbox,
IconizedContextMenuOption, IconizedContextMenuOption,
IconizedContextMenuOptionList, IconizedContextMenuRadio IconizedContextMenuOptionList,
IconizedContextMenuRadio
} from "../context_menus/IconizedContextMenu"; } from "../context_menus/IconizedContextMenu";
import { CommunityPrototypeStore, IRoomProfile } from "../../../stores/CommunityPrototypeStore";
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
interface IProps { interface IProps {
room: Room; room: Room;
@ -101,6 +104,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
this.notificationState.on(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate); this.notificationState.on(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate);
this.roomProps = EchoChamber.forRoom(this.props.room); this.roomProps = EchoChamber.forRoom(this.props.room);
this.roomProps.on(PROPERTY_UPDATED, this.onRoomPropertyUpdate); this.roomProps.on(PROPERTY_UPDATED, this.onRoomPropertyUpdate);
CommunityPrototypeStore.instance.on(UPDATE_EVENT, this.onCommunityUpdate);
} }
private onNotificationUpdate = () => { private onNotificationUpdate = () => {
@ -140,6 +144,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
defaultDispatcher.unregister(this.dispatcherRef); defaultDispatcher.unregister(this.dispatcherRef);
MessagePreviewStore.instance.off(ROOM_PREVIEW_CHANGED, this.onRoomPreviewChanged); MessagePreviewStore.instance.off(ROOM_PREVIEW_CHANGED, this.onRoomPreviewChanged);
this.notificationState.off(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate); this.notificationState.off(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate);
CommunityPrototypeStore.instance.off(UPDATE_EVENT, this.onCommunityUpdate);
} }
private onAction = (payload: ActionPayload) => { private onAction = (payload: ActionPayload) => {
@ -150,6 +155,11 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
} }
}; };
private onCommunityUpdate = (roomId: string) => {
if (roomId !== this.props.room.roomId) return;
this.forceUpdate(); // we don't have anything to actually update
};
private onRoomPreviewChanged = (room: Room) => { private onRoomPreviewChanged = (room: Room) => {
if (this.props.room && room.roomId === this.props.room.roomId) { if (this.props.room && room.roomId === this.props.room.roomId) {
// generatePreview() will return nothing if the user has previews disabled // generatePreview() will return nothing if the user has previews disabled
@ -461,11 +471,21 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
'mx_RoomTile_minimized': this.props.isMinimized, 'mx_RoomTile_minimized': this.props.isMinimized,
}); });
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
const roomAvatar = <DecoratedRoomAvatar const roomAvatar = <DecoratedRoomAvatar
room={this.props.room} room={this.props.room}
avatarSize={32} avatarSize={32}
tag={this.props.tag} tag={this.props.tag}
displayBadge={this.props.isMinimized} displayBadge={this.props.isMinimized}
oobData={({avatarUrl: roomProfile.avatarMxc})}
/>; />;
let badge: React.ReactNode; let badge: React.ReactNode;
@ -482,10 +502,6 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
); );
} }
let name = this.props.room.name;
if (typeof name !== 'string') name = '';
name = name.replace(":", ":\u200b"); // add a zero-width space to allow linewrapping after the colon
let messagePreview = null; let messagePreview = null;
if (this.showMessagePreview && this.state.messagePreview) { if (this.showMessagePreview && this.state.messagePreview) {
messagePreview = ( messagePreview = (

View file

@ -0,0 +1,100 @@
/*
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 { AsyncStoreWithClient } from "./AsyncStoreWithClient";
import defaultDispatcher from "../dispatcher/dispatcher";
import { ActionPayload } from "../dispatcher/payloads";
import { Room } from "matrix-js-sdk/src/models/room";
import { EffectiveMembership, getEffectiveMembership } from "../utils/membership";
import SettingsStore from "../settings/SettingsStore";
import * as utils from "matrix-js-sdk/src/utils";
import { UPDATE_EVENT } from "./AsyncStore";
interface IState {
// nothing of value - we use account data
}
export interface IRoomProfile {
displayName: string;
avatarMxc: string;
}
export class CommunityPrototypeStore extends AsyncStoreWithClient<IState> {
private static internalInstance = new CommunityPrototypeStore();
private constructor() {
super(defaultDispatcher, {});
}
public static get instance(): CommunityPrototypeStore {
return CommunityPrototypeStore.internalInstance;
}
protected async onAction(payload: ActionPayload): Promise<any> {
if (!this.matrixClient || !SettingsStore.getValue("feature_communities_v2_prototypes")) {
return;
}
if (payload.action === "MatrixActions.Room.myMembership") {
const room: Room = payload.room;
const membership = getEffectiveMembership(payload.membership);
const oldMembership = getEffectiveMembership(payload.oldMembership);
if (membership === oldMembership) return;
if (membership === EffectiveMembership.Invite) {
try {
const path = utils.encodeUri("/rooms/$roomId/group_info", {$roomId: room.roomId});
const profile = await this.matrixClient._http.authedRequest(
undefined, "GET", path,
undefined, undefined,
{prefix: "/_matrix/client/unstable/im.vector.custom"});
// we use global account data because per-room account data on invites is unreliable
await this.matrixClient.setAccountData("im.vector.group_info." + room.roomId, profile);
} catch (e) {
console.warn("Non-fatal error getting group information for invite:", e);
}
}
} else if (payload.action === "MatrixActions.accountData") {
if (payload.event_type.startsWith("im.vector.group_info.")) {
this.emit(UPDATE_EVENT, payload.event_type.substring("im.vector.group_info.".length));
}
}
}
public getInviteProfile(roomId: string): IRoomProfile {
if (!this.matrixClient) return {displayName: null, avatarMxc: null};
const room = this.matrixClient.getRoom(roomId);
if (SettingsStore.getValue("feature_communities_v2_prototypes")) {
const data = this.matrixClient.getAccountData("im.vector.group_info." + roomId);
if (data && data.getContent()) {
return {displayName: data.getContent().name, avatarMxc: data.getContent().avatar_url};
}
}
return {displayName: room.name, avatarMxc: room.avatar_url};
}
protected async onReady(): Promise<any> {
for (const room of this.matrixClient.getRooms()) {
const myMember = room.currentState.getMembers().find(m => m.userId === this.matrixClient.getUserId());
if (!myMember) continue;
if (getEffectiveMembership(myMember.membership) === EffectiveMembership.Invite) {
// Fake an update for anything that might have started listening before the invite
// data was available (eg: RoomPreviewBar after a refresh)
this.emit(UPDATE_EVENT, room.roomId);
}
}
}
}