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:
commit
98d098a3eb
3 changed files with 148 additions and 8 deletions
|
@ -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;
|
||||||
|
|
|
@ -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 = (
|
||||||
|
|
100
src/stores/CommunityPrototypeStore.ts
Normal file
100
src/stores/CommunityPrototypeStore.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue