Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Communities v2 prototype: Override invite aesthetics for community-as-room invites #5143

Merged
merged 1 commit into from
Aug 26, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions src/components/views/rooms/RoomPreviewBar.js
Original file line number Diff line number Diff line change
@@ -26,6 +26,8 @@ import classNames from 'classnames';
import { _t } from '../../../languageHandler';
import SdkConfig from "../../../SdkConfig";
import IdentityAuthClient from '../../../IdentityAuthClient';
import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore";
import {UPDATE_EVENT} from "../../../stores/AsyncStore";

const MessageCase = Object.freeze({
NotLoggedIn: "NotLoggedIn",
@@ -100,6 +102,7 @@ export default createReactClass({

componentDidMount: function() {
this._checkInvitedEmail();
CommunityPrototypeStore.instance.on(UPDATE_EVENT, this._onCommunityUpdate);
},

componentDidUpdate: function(prevProps, prevState) {
@@ -108,6 +111,10 @@ export default createReactClass({
}
},

componentWillUnmount: function() {
CommunityPrototypeStore.instance.off(UPDATE_EVENT, this._onCommunityUpdate);
},

_checkInvitedEmail: async function() {
// 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
@@ -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() {
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) {
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) {
return name;
} else if (atStart) {
@@ -439,7 +460,10 @@ export default createReactClass({
}
case MessageCase.Invite: {
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();
let inviterElement;
28 changes: 22 additions & 6 deletions src/components/views/rooms/RoomTile.tsx
Original file line number Diff line number Diff line change
@@ -27,7 +27,7 @@ import defaultDispatcher from '../../../dispatcher/dispatcher';
import { Key } from "../../../Keyboard";
import ActiveRoomObserver from "../../../ActiveRoomObserver";
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 { MessagePreviewStore, ROOM_PREVIEW_CHANGED } from "../../../stores/room-list/MessagePreviewStore";
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
@@ -47,8 +47,11 @@ import { PROPERTY_UPDATED } from "../../../stores/local-echo/GenericEchoChamber"
import IconizedContextMenu, {
IconizedContextMenuCheckbox,
IconizedContextMenuOption,
IconizedContextMenuOptionList, IconizedContextMenuRadio
IconizedContextMenuOptionList,
IconizedContextMenuRadio
} from "../context_menus/IconizedContextMenu";
import { CommunityPrototypeStore, IRoomProfile } from "../../../stores/CommunityPrototypeStore";
import { UPDATE_EVENT } from "../../../stores/AsyncStore";

interface IProps {
room: Room;
@@ -101,6 +104,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
this.notificationState.on(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate);
this.roomProps = EchoChamber.forRoom(this.props.room);
this.roomProps.on(PROPERTY_UPDATED, this.onRoomPropertyUpdate);
CommunityPrototypeStore.instance.on(UPDATE_EVENT, this.onCommunityUpdate);
}

private onNotificationUpdate = () => {
@@ -140,6 +144,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
defaultDispatcher.unregister(this.dispatcherRef);
MessagePreviewStore.instance.off(ROOM_PREVIEW_CHANGED, this.onRoomPreviewChanged);
this.notificationState.off(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate);
CommunityPrototypeStore.instance.off(UPDATE_EVENT, this.onCommunityUpdate);
}

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) => {
if (this.props.room && room.roomId === this.props.room.roomId) {
// 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,
});

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
room={this.props.room}
avatarSize={32}
tag={this.props.tag}
displayBadge={this.props.isMinimized}
oobData={({avatarUrl: roomProfile.avatarMxc})}
/>;

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;
if (this.showMessagePreview && this.state.messagePreview) {
messagePreview = (
100 changes: 100 additions & 0 deletions src/stores/CommunityPrototypeStore.ts
Original file line number Diff line number Diff line change
@@ -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"});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No particular need to change anything now, but just pointing out that im.vector should probably be replaced with io.element for new work.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 will keep in mind for the next one (which should hopefully not need a generic custom namespace and instead can be associated to an MSC)

// 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);
}
}
}
}