mirror of
https://github.com/element-hq/element-web
synced 2024-10-23 11:15:47 +03:00
Merge pull request #5736 from matrix-org/t3chguy/spaces4.5
Spaces suggested rooms support
This commit is contained in:
commit
98338f1505
16 changed files with 215 additions and 100 deletions
|
@ -203,8 +203,9 @@ limitations under the License.
|
||||||
.mx_SpaceRoomDirectory_actions {
|
.mx_SpaceRoomDirectory_actions {
|
||||||
width: 180px;
|
width: 180px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
height: min-content;
|
margin-left: 28px;
|
||||||
margin: auto 0 auto 28px;
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
.mx_AccessibleButton {
|
.mx_AccessibleButton {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
@ -223,9 +224,5 @@ limitations under the License.
|
||||||
line-height: $font-15px;
|
line-height: $font-15px;
|
||||||
color: $secondary-fg-color;
|
color: $secondary-fg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Checkbox {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,8 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// XXX: We shouldn't be using TemporaryTile anywhere - delete it.
|
.mx_DecoratedRoomAvatar, .mx_ExtraTile {
|
||||||
.mx_DecoratedRoomAvatar, .mx_TemporaryTile {
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&.mx_DecoratedRoomAvatar_cutout .mx_BaseAvatar {
|
&.mx_DecoratedRoomAvatar_cutout .mx_BaseAvatar {
|
||||||
|
|
|
@ -22,7 +22,7 @@ import MultiInviter from './utils/MultiInviter';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
import * as sdk from './';
|
import * as sdk from './';
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
import InviteDialog, {KIND_DM, KIND_INVITE, KIND_SPACE_INVITE} from "./components/views/dialogs/InviteDialog";
|
import InviteDialog, {KIND_DM, KIND_INVITE} from "./components/views/dialogs/InviteDialog";
|
||||||
import CommunityPrototypeInviteDialog from "./components/views/dialogs/CommunityPrototypeInviteDialog";
|
import CommunityPrototypeInviteDialog from "./components/views/dialogs/CommunityPrototypeInviteDialog";
|
||||||
import {CommunityPrototypeStore} from "./stores/CommunityPrototypeStore";
|
import {CommunityPrototypeStore} from "./stores/CommunityPrototypeStore";
|
||||||
|
|
||||||
|
@ -50,11 +50,10 @@ export function showStartChatInviteDialog(initialText) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showRoomInviteDialog(roomId) {
|
export function showRoomInviteDialog(roomId) {
|
||||||
const isSpace = MatrixClientPeg.get()?.getRoom(roomId)?.isSpaceRoom();
|
|
||||||
// This dialog handles the room creation internally - we don't need to worry about it.
|
// This dialog handles the room creation internally - we don't need to worry about it.
|
||||||
Modal.createTrackedDialog(
|
Modal.createTrackedDialog(
|
||||||
"Invite Users", isSpace ? "Space" : "Room", InviteDialog, {
|
"Invite Users", "", InviteDialog, {
|
||||||
kind: isSpace ? KIND_SPACE_INVITE : KIND_INVITE,
|
kind: KIND_INVITE,
|
||||||
roomId,
|
roomId,
|
||||||
},
|
},
|
||||||
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true,
|
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true,
|
||||||
|
|
|
@ -79,6 +79,8 @@ export default class VoipUserMapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onNewInvitedRoom(invitedRoom: Room) {
|
public async onNewInvitedRoom(invitedRoom: Room) {
|
||||||
|
if (!CallHandler.sharedInstance().getSupportsVirtualRooms()) return;
|
||||||
|
|
||||||
const inviterId = invitedRoom.getDMInviter();
|
const inviterId = invitedRoom.getDMInviter();
|
||||||
console.log(`Checking virtual-ness of room ID ${invitedRoom.roomId}, invited by ${inviterId}`);
|
console.log(`Checking virtual-ness of room ID ${invitedRoom.roomId}, invited by ${inviterId}`);
|
||||||
const result = await CallHandler.sharedInstance().sipNativeLookup(inviterId);
|
const result = await CallHandler.sharedInstance().sipNativeLookup(inviterId);
|
||||||
|
|
|
@ -64,6 +64,7 @@ export interface ISpaceSummaryEvent {
|
||||||
state_key: string;
|
state_key: string;
|
||||||
content: {
|
content: {
|
||||||
order?: string;
|
order?: string;
|
||||||
|
suggested?: boolean;
|
||||||
auto_join?: boolean;
|
auto_join?: boolean;
|
||||||
via?: string;
|
via?: string;
|
||||||
};
|
};
|
||||||
|
@ -91,7 +92,7 @@ const SubSpace: React.FC<ISubspaceProps> = ({
|
||||||
const name = space.name || space.canonical_alias || space.aliases?.[0] || _t("Unnamed Space");
|
const name = space.name || space.canonical_alias || space.aliases?.[0] || _t("Unnamed Space");
|
||||||
|
|
||||||
const evContent = event?.getContent();
|
const evContent = event?.getContent();
|
||||||
const [autoJoin, _setAutoJoin] = useState(evContent?.auto_join);
|
const [suggested, _setSuggested] = useState(evContent?.suggested);
|
||||||
const [removed, _setRemoved] = useState(!evContent?.via);
|
const [removed, _setRemoved] = useState(!evContent?.via);
|
||||||
|
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
|
@ -102,12 +103,12 @@ const SubSpace: React.FC<ISubspaceProps> = ({
|
||||||
let actions;
|
let actions;
|
||||||
if (editing && queueAction) {
|
if (editing && queueAction) {
|
||||||
if (event && cli.getRoom(event.getRoomId())?.currentState.maySendStateEvent(event.getType(), cli.getUserId())) {
|
if (event && cli.getRoom(event.getRoomId())?.currentState.maySendStateEvent(event.getType(), cli.getUserId())) {
|
||||||
const setAutoJoin = () => {
|
const setSuggested = () => {
|
||||||
_setAutoJoin(v => {
|
_setSuggested(v => {
|
||||||
queueAction({
|
queueAction({
|
||||||
event,
|
event,
|
||||||
removed,
|
removed,
|
||||||
autoJoin: !v,
|
suggested: !v,
|
||||||
});
|
});
|
||||||
return !v;
|
return !v;
|
||||||
});
|
});
|
||||||
|
@ -118,7 +119,7 @@ const SubSpace: React.FC<ISubspaceProps> = ({
|
||||||
queueAction({
|
queueAction({
|
||||||
event,
|
event,
|
||||||
removed: !v,
|
removed: !v,
|
||||||
autoJoin,
|
suggested,
|
||||||
});
|
});
|
||||||
return !v;
|
return !v;
|
||||||
});
|
});
|
||||||
|
@ -131,7 +132,7 @@ const SubSpace: React.FC<ISubspaceProps> = ({
|
||||||
} else {
|
} else {
|
||||||
actions = <React.Fragment>
|
actions = <React.Fragment>
|
||||||
<FormButton kind="danger" onClick={setRemoved} label={_t("Remove from Space")} />
|
<FormButton kind="danger" onClick={setRemoved} label={_t("Remove from Space")} />
|
||||||
<StyledCheckbox checked={autoJoin} onChange={setAutoJoin} />
|
<StyledCheckbox checked={suggested} onChange={setSuggested} />
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -182,8 +183,8 @@ const SubSpace: React.FC<ISubspaceProps> = ({
|
||||||
|
|
||||||
interface IAction {
|
interface IAction {
|
||||||
event: MatrixEvent;
|
event: MatrixEvent;
|
||||||
|
suggested: boolean;
|
||||||
removed: boolean;
|
removed: boolean;
|
||||||
autoJoin: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IRoomTileProps {
|
interface IRoomTileProps {
|
||||||
|
@ -199,7 +200,7 @@ const RoomTile = ({ room, event, editing, queueAction, onPreviewClick, onJoinCli
|
||||||
const name = room.name || room.canonical_alias || room.aliases?.[0] || _t("Unnamed Room");
|
const name = room.name || room.canonical_alias || room.aliases?.[0] || _t("Unnamed Room");
|
||||||
|
|
||||||
const evContent = event?.getContent();
|
const evContent = event?.getContent();
|
||||||
const [autoJoin, _setAutoJoin] = useState(evContent?.auto_join);
|
const [suggested, _setSuggested] = useState(evContent?.suggested);
|
||||||
const [removed, _setRemoved] = useState(!evContent?.via);
|
const [removed, _setRemoved] = useState(!evContent?.via);
|
||||||
|
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
|
@ -209,12 +210,12 @@ const RoomTile = ({ room, event, editing, queueAction, onPreviewClick, onJoinCli
|
||||||
let actions;
|
let actions;
|
||||||
if (editing && queueAction) {
|
if (editing && queueAction) {
|
||||||
if (event && cli.getRoom(event.getRoomId())?.currentState.maySendStateEvent(event.getType(), cli.getUserId())) {
|
if (event && cli.getRoom(event.getRoomId())?.currentState.maySendStateEvent(event.getType(), cli.getUserId())) {
|
||||||
const setAutoJoin = () => {
|
const setSuggested = () => {
|
||||||
_setAutoJoin(v => {
|
_setSuggested(v => {
|
||||||
queueAction({
|
queueAction({
|
||||||
event,
|
event,
|
||||||
removed,
|
removed,
|
||||||
autoJoin: !v,
|
suggested: !v,
|
||||||
});
|
});
|
||||||
return !v;
|
return !v;
|
||||||
});
|
});
|
||||||
|
@ -225,7 +226,7 @@ const RoomTile = ({ room, event, editing, queueAction, onPreviewClick, onJoinCli
|
||||||
queueAction({
|
queueAction({
|
||||||
event,
|
event,
|
||||||
removed: !v,
|
removed: !v,
|
||||||
autoJoin,
|
suggested,
|
||||||
});
|
});
|
||||||
return !v;
|
return !v;
|
||||||
});
|
});
|
||||||
|
@ -238,7 +239,7 @@ const RoomTile = ({ room, event, editing, queueAction, onPreviewClick, onJoinCli
|
||||||
} else {
|
} else {
|
||||||
actions = <React.Fragment>
|
actions = <React.Fragment>
|
||||||
<FormButton kind="danger" onClick={setRemoved} label={_t("Remove from Space")} />
|
<FormButton kind="danger" onClick={setRemoved} label={_t("Remove from Space")} />
|
||||||
<StyledCheckbox checked={autoJoin} onChange={setAutoJoin} />
|
<StyledCheckbox checked={suggested} onChange={setSuggested} />
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -445,10 +446,10 @@ const SpaceRoomDirectory: React.FC<IProps> = ({ space, initialText = "", onFinis
|
||||||
|
|
||||||
const onSaveButtonClicked = () => {
|
const onSaveButtonClicked = () => {
|
||||||
// TODO setBusy
|
// TODO setBusy
|
||||||
pendingActions.current.forEach(({event, autoJoin, removed}) => {
|
pendingActions.current.forEach(({event, suggested, removed}) => {
|
||||||
const content = {
|
const content = {
|
||||||
...event.getContent(),
|
...event.getContent(),
|
||||||
auto_join: autoJoin,
|
suggested,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (removed) {
|
if (removed) {
|
||||||
|
@ -463,7 +464,7 @@ const SpaceRoomDirectory: React.FC<IProps> = ({ space, initialText = "", onFinis
|
||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
adminButton = <React.Fragment>
|
adminButton = <React.Fragment>
|
||||||
<FormButton label={_t("Save changes")} onClick={onSaveButtonClicked} />
|
<FormButton label={_t("Save changes")} onClick={onSaveButtonClicked} />
|
||||||
<span>{ _t("All users join by default") }</span>
|
<span>{ _t("Promoted to users") }</span>
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
} else {
|
} else {
|
||||||
adminButton = <FormButton label={_t("Manage rooms")} onClick={onManageButtonClicked} />;
|
adminButton = <FormButton label={_t("Manage rooms")} onClick={onManageButtonClicked} />;
|
||||||
|
|
|
@ -557,7 +557,7 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
|
||||||
case Phase.PublicCreateRooms:
|
case Phase.PublicCreateRooms:
|
||||||
return <SpaceSetupFirstRooms
|
return <SpaceSetupFirstRooms
|
||||||
space={this.props.space}
|
space={this.props.space}
|
||||||
title={_t("What discussions do you want to have?")}
|
title={_t("What are some things you want to discuss?")}
|
||||||
description={_t("We'll create rooms for each topic.")}
|
description={_t("We'll create rooms for each topic.")}
|
||||||
onFinished={() => this.setState({ phase: Phase.PublicShare })}
|
onFinished={() => this.setState({ phase: Phase.PublicShare })}
|
||||||
/>;
|
/>;
|
||||||
|
|
|
@ -27,7 +27,7 @@ export default class InfoDialog extends React.Component {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
description: PropTypes.node,
|
description: PropTypes.node,
|
||||||
button: PropTypes.oneOfType(PropTypes.string, PropTypes.bool),
|
button: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
|
||||||
onFinished: PropTypes.func,
|
onFinished: PropTypes.func,
|
||||||
hasCloseButton: PropTypes.bool,
|
hasCloseButton: PropTypes.bool,
|
||||||
onKeyDown: PropTypes.func,
|
onKeyDown: PropTypes.func,
|
||||||
|
|
|
@ -48,7 +48,6 @@ import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
|
||||||
|
|
||||||
export const KIND_DM = "dm";
|
export const KIND_DM = "dm";
|
||||||
export const KIND_INVITE = "invite";
|
export const KIND_INVITE = "invite";
|
||||||
export const KIND_SPACE_INVITE = "space_invite";
|
|
||||||
export const KIND_CALL_TRANSFER = "call_transfer";
|
export const KIND_CALL_TRANSFER = "call_transfer";
|
||||||
|
|
||||||
const INITIAL_ROOMS_SHOWN = 3; // Number of rooms to show at first
|
const INITIAL_ROOMS_SHOWN = 3; // Number of rooms to show at first
|
||||||
|
@ -310,7 +309,7 @@ interface IInviteDialogProps {
|
||||||
// not provided.
|
// not provided.
|
||||||
kind: string,
|
kind: string,
|
||||||
|
|
||||||
// The room ID this dialog is for. Only required for KIND_INVITE and KIND_SPACE_INVITE.
|
// The room ID this dialog is for. Only required for KIND_INVITE.
|
||||||
roomId: string,
|
roomId: string,
|
||||||
|
|
||||||
// The call to transfer. Only required for KIND_CALL_TRANSFER.
|
// The call to transfer. Only required for KIND_CALL_TRANSFER.
|
||||||
|
@ -349,8 +348,8 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
if ((props.kind === KIND_INVITE || props.kind === KIND_SPACE_INVITE) && !props.roomId) {
|
if ((props.kind === KIND_INVITE) && !props.roomId) {
|
||||||
throw new Error("When using KIND_INVITE or KIND_SPACE_INVITE a roomId is required for an InviteDialog");
|
throw new Error("When using KIND_INVITE a roomId is required for an InviteDialog");
|
||||||
} else if (props.kind === KIND_CALL_TRANSFER && !props.call) {
|
} else if (props.kind === KIND_CALL_TRANSFER && !props.call) {
|
||||||
throw new Error("When using KIND_CALL_TRANSFER a call is required for an InviteDialog");
|
throw new Error("When using KIND_CALL_TRANSFER a call is required for an InviteDialog");
|
||||||
}
|
}
|
||||||
|
@ -1027,7 +1026,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
sectionSubname = _t("May include members not in %(communityName)s", {communityName});
|
sectionSubname = _t("May include members not in %(communityName)s", {communityName});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.kind === KIND_INVITE || this.props.kind === KIND_SPACE_INVITE) {
|
if (this.props.kind === KIND_INVITE) {
|
||||||
sectionName = kind === 'recents' ? _t("Recently Direct Messaged") : _t("Suggestions");
|
sectionName = kind === 'recents' ? _t("Recently Direct Messaged") : _t("Suggestions");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1248,25 +1247,31 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
}
|
}
|
||||||
buttonText = _t("Go");
|
buttonText = _t("Go");
|
||||||
goButtonFn = this._startDm;
|
goButtonFn = this._startDm;
|
||||||
} else if (this.props.kind === KIND_INVITE || this.props.kind === KIND_SPACE_INVITE) {
|
} else if (this.props.kind === KIND_INVITE) {
|
||||||
title = this.props.kind === KIND_INVITE ? _t("Invite to this room") : _t("Invite to this space");
|
const room = MatrixClientPeg.get()?.getRoom(this.props.roomId);
|
||||||
|
const isSpace = room?.isSpaceRoom();
|
||||||
|
title = isSpace
|
||||||
|
? _t("Invite to %(spaceName)s", {
|
||||||
|
spaceName: room.name || _t("Unnamed Space"),
|
||||||
|
})
|
||||||
|
: _t("Invite to this room");
|
||||||
|
|
||||||
let helpTextUntranslated;
|
let helpTextUntranslated;
|
||||||
if (this.props.kind === KIND_INVITE) {
|
if (isSpace) {
|
||||||
if (identityServersEnabled) {
|
if (identityServersEnabled) {
|
||||||
helpTextUntranslated = _td("Invite someone using their name, email address, username " +
|
helpTextUntranslated = _td("Invite someone using their name, email address, username " +
|
||||||
"(like <userId/>) or <a>share this room</a>.");
|
"(like <userId/>) or <a>share this space</a>.");
|
||||||
} else {
|
} else {
|
||||||
helpTextUntranslated = _td("Invite someone using their name, username " +
|
helpTextUntranslated = _td("Invite someone using their name, username " +
|
||||||
"(like <userId/>) or <a>share this room</a>.");
|
"(like <userId/>) or <a>share this space</a>.");
|
||||||
}
|
}
|
||||||
} else { // KIND_SPACE_INVITE
|
} else {
|
||||||
if (identityServersEnabled) {
|
if (identityServersEnabled) {
|
||||||
helpTextUntranslated = _td("Invite someone using their name, email address, username " +
|
helpTextUntranslated = _td("Invite someone using their name, email address, username " +
|
||||||
"(like <userId/>) or <a>share this space</a>.");
|
"(like <userId/>) or <a>share this room</a>.");
|
||||||
} else {
|
} else {
|
||||||
helpTextUntranslated = _td("Invite someone using their name, username " +
|
helpTextUntranslated = _td("Invite someone using their name, username " +
|
||||||
"(like <userId/>) or <a>share this space</a>.");
|
"(like <userId/>) or <a>share this room</a>.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -28,7 +28,7 @@ interface IProps {
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
avatar: React.ReactElement;
|
avatar: React.ReactElement;
|
||||||
notificationState: NotificationState;
|
notificationState?: NotificationState;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,8 +36,7 @@ interface IState {
|
||||||
hover: boolean;
|
hover: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove with community invites in the room list: https://github.com/vector-im/element-web/issues/14456
|
export default class ExtraTile extends React.Component<IProps, IState> {
|
||||||
export default class TemporaryTile extends React.Component<IProps, IState> {
|
|
||||||
constructor(props: IProps) {
|
constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
@ -57,18 +56,21 @@ export default class TemporaryTile extends React.Component<IProps, IState> {
|
||||||
public render(): React.ReactElement {
|
public render(): React.ReactElement {
|
||||||
// XXX: We copy classes because it's easier
|
// XXX: We copy classes because it's easier
|
||||||
const classes = classNames({
|
const classes = classNames({
|
||||||
|
'mx_ExtraTile': true,
|
||||||
'mx_RoomTile': true,
|
'mx_RoomTile': true,
|
||||||
'mx_TemporaryTile': true,
|
|
||||||
'mx_RoomTile_selected': this.props.isSelected,
|
'mx_RoomTile_selected': this.props.isSelected,
|
||||||
'mx_RoomTile_minimized': this.props.isMinimized,
|
'mx_RoomTile_minimized': this.props.isMinimized,
|
||||||
});
|
});
|
||||||
|
|
||||||
const badge = (
|
let badge;
|
||||||
|
if (this.props.notificationState) {
|
||||||
|
badge = (
|
||||||
<NotificationBadge
|
<NotificationBadge
|
||||||
notification={this.props.notificationState}
|
notification={this.props.notificationState}
|
||||||
forceCount={false}
|
forceCount={false}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let name = this.props.displayName;
|
let name = this.props.displayName;
|
||||||
if (typeof name !== 'string') name = '';
|
if (typeof name !== 'string') name = '';
|
||||||
|
@ -76,7 +78,7 @@ export default class TemporaryTile extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
const nameClasses = classNames({
|
const nameClasses = classNames({
|
||||||
"mx_RoomTile_name": true,
|
"mx_RoomTile_name": true,
|
||||||
"mx_RoomTile_nameHasUnreadEvents": this.props.notificationState.isUnread,
|
"mx_RoomTile_nameHasUnreadEvents": this.props.notificationState?.isUnread,
|
||||||
});
|
});
|
||||||
|
|
||||||
let nameContainer = (
|
let nameContainer = (
|
|
@ -16,9 +16,10 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as React from "react";
|
import React, { ReactComponentElement } from "react";
|
||||||
import { Dispatcher } from "flux";
|
import { Dispatcher } from "flux";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
import * as fbEmitter from "fbemitter";
|
||||||
|
|
||||||
import { _t, _td } from "../../../languageHandler";
|
import { _t, _td } from "../../../languageHandler";
|
||||||
import { RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex";
|
import { RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex";
|
||||||
|
@ -33,7 +34,7 @@ import RoomSublist from "./RoomSublist";
|
||||||
import { ActionPayload } from "../../../dispatcher/payloads";
|
import { ActionPayload } from "../../../dispatcher/payloads";
|
||||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
import GroupAvatar from "../avatars/GroupAvatar";
|
import GroupAvatar from "../avatars/GroupAvatar";
|
||||||
import TemporaryTile from "./TemporaryTile";
|
import ExtraTile from "./ExtraTile";
|
||||||
import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
|
import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
|
||||||
import { NotificationColor } from "../../../stores/notifications/NotificationColor";
|
import { NotificationColor } from "../../../stores/notifications/NotificationColor";
|
||||||
import { Action } from "../../../dispatcher/actions";
|
import { Action } from "../../../dispatcher/actions";
|
||||||
|
@ -47,9 +48,11 @@ import { IconizedContextMenuOption, IconizedContextMenuOptionList } from "../con
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore";
|
import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore";
|
||||||
import CallHandler from "../../../CallHandler";
|
import CallHandler from "../../../CallHandler";
|
||||||
import SpaceStore from "../../../stores/SpaceStore";
|
import SpaceStore, { SUGGESTED_ROOMS } from "../../../stores/SpaceStore";
|
||||||
import { showAddExistingRooms, showCreateNewRoom } from "../../../utils/space";
|
import { showAddExistingRooms, showCreateNewRoom } from "../../../utils/space";
|
||||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||||
|
import { ISpaceSummaryRoom } from "../../structures/SpaceRoomDirectory";
|
||||||
|
import RoomAvatar from "../avatars/RoomAvatar";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
onKeyDown: (ev: React.KeyboardEvent) => void;
|
onKeyDown: (ev: React.KeyboardEvent) => void;
|
||||||
|
@ -63,6 +66,8 @@ interface IProps {
|
||||||
interface IState {
|
interface IState {
|
||||||
sublists: ITagMap;
|
sublists: ITagMap;
|
||||||
isNameFiltering: boolean;
|
isNameFiltering: boolean;
|
||||||
|
currentRoomId?: string;
|
||||||
|
suggestedRooms: ISpaceSummaryRoom[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const TAG_ORDER: TagID[] = [
|
const TAG_ORDER: TagID[] = [
|
||||||
|
@ -75,6 +80,7 @@ const TAG_ORDER: TagID[] = [
|
||||||
|
|
||||||
DefaultTagID.LowPriority,
|
DefaultTagID.LowPriority,
|
||||||
DefaultTagID.ServerNotice,
|
DefaultTagID.ServerNotice,
|
||||||
|
DefaultTagID.Suggested,
|
||||||
DefaultTagID.Archived,
|
DefaultTagID.Archived,
|
||||||
];
|
];
|
||||||
const CUSTOM_TAGS_BEFORE_TAG = DefaultTagID.LowPriority;
|
const CUSTOM_TAGS_BEFORE_TAG = DefaultTagID.LowPriority;
|
||||||
|
@ -242,6 +248,12 @@ const TAG_AESTHETICS: ITagAestheticsMap = {
|
||||||
isInvite: false,
|
isInvite: false,
|
||||||
defaultHidden: true,
|
defaultHidden: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
[DefaultTagID.Suggested]: {
|
||||||
|
sectionLabel: _td("Suggested Rooms"),
|
||||||
|
isInvite: false,
|
||||||
|
defaultHidden: false,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function customTagAesthetics(tagId: TagID): ITagAesthetics {
|
function customTagAesthetics(tagId: TagID): ITagAesthetics {
|
||||||
|
@ -260,6 +272,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
||||||
private dispatcherRef;
|
private dispatcherRef;
|
||||||
private customTagStoreRef;
|
private customTagStoreRef;
|
||||||
private tagAesthetics: ITagAestheticsMap;
|
private tagAesthetics: ITagAestheticsMap;
|
||||||
|
private roomStoreToken: fbEmitter.EventSubscription;
|
||||||
|
|
||||||
constructor(props: IProps) {
|
constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -267,6 +280,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
||||||
this.state = {
|
this.state = {
|
||||||
sublists: {},
|
sublists: {},
|
||||||
isNameFiltering: !!RoomListStore.instance.getFirstNameFilterCondition(),
|
isNameFiltering: !!RoomListStore.instance.getFirstNameFilterCondition(),
|
||||||
|
suggestedRooms: SpaceStore.instance.suggestedRooms,
|
||||||
};
|
};
|
||||||
|
|
||||||
// shallow-copy from the template as we need to make modifications to it
|
// shallow-copy from the template as we need to make modifications to it
|
||||||
|
@ -274,20 +288,30 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
||||||
this.updateDmAddRoomAction();
|
this.updateDmAddRoomAction();
|
||||||
|
|
||||||
this.dispatcherRef = defaultDispatcher.register(this.onAction);
|
this.dispatcherRef = defaultDispatcher.register(this.onAction);
|
||||||
|
this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidMount(): void {
|
public componentDidMount(): void {
|
||||||
|
SpaceStore.instance.on(SUGGESTED_ROOMS, this.updateSuggestedRooms);
|
||||||
RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.updateLists);
|
RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.updateLists);
|
||||||
this.customTagStoreRef = CustomRoomTagStore.addListener(this.updateLists);
|
this.customTagStoreRef = CustomRoomTagStore.addListener(this.updateLists);
|
||||||
this.updateLists(); // trigger the first update
|
this.updateLists(); // trigger the first update
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
|
SpaceStore.instance.off(SUGGESTED_ROOMS, this.updateSuggestedRooms);
|
||||||
RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.updateLists);
|
RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.updateLists);
|
||||||
defaultDispatcher.unregister(this.dispatcherRef);
|
defaultDispatcher.unregister(this.dispatcherRef);
|
||||||
if (this.customTagStoreRef) this.customTagStoreRef.remove();
|
if (this.customTagStoreRef) this.customTagStoreRef.remove();
|
||||||
|
if (this.roomStoreToken) this.roomStoreToken.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onRoomViewStoreUpdate = () => {
|
||||||
|
this.setState({
|
||||||
|
currentRoomId: RoomViewStore.getRoomId(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
private updateDmAddRoomAction() {
|
private updateDmAddRoomAction() {
|
||||||
const dmTagAesthetics = objectShallowClone(TAG_AESTHETICS[DefaultTagID.DM]);
|
const dmTagAesthetics = objectShallowClone(TAG_AESTHETICS[DefaultTagID.DM]);
|
||||||
if (CallHandler.sharedInstance().getSupportsPstnProtocol()) {
|
if (CallHandler.sharedInstance().getSupportsPstnProtocol()) {
|
||||||
|
@ -319,7 +343,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
private getRoomDelta = (roomId: string, delta: number, unread = false) => {
|
private getRoomDelta = (roomId: string, delta: number, unread = false) => {
|
||||||
const lists = RoomListStore.instance.orderedLists;
|
const lists = RoomListStore.instance.orderedLists;
|
||||||
const rooms: Room = [];
|
const rooms: Room[] = [];
|
||||||
TAG_ORDER.forEach(t => {
|
TAG_ORDER.forEach(t => {
|
||||||
let listRooms = lists[t];
|
let listRooms = lists[t];
|
||||||
|
|
||||||
|
@ -340,6 +364,10 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
||||||
return room;
|
return room;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private updateSuggestedRooms = (suggestedRooms: ISpaceSummaryRoom[]) => {
|
||||||
|
this.setState({ suggestedRooms });
|
||||||
|
};
|
||||||
|
|
||||||
private updateLists = () => {
|
private updateLists = () => {
|
||||||
const newLists = RoomListStore.instance.orderedLists;
|
const newLists = RoomListStore.instance.orderedLists;
|
||||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||||
|
@ -394,7 +422,44 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
||||||
dis.dispatch({ action: Action.ViewRoomDirectory, initialText });
|
dis.dispatch({ action: Action.ViewRoomDirectory, initialText });
|
||||||
};
|
};
|
||||||
|
|
||||||
private renderCommunityInvites(): TemporaryTile[] {
|
private renderSuggestedRooms(): ReactComponentElement<typeof ExtraTile>[] {
|
||||||
|
return this.state.suggestedRooms.map(room => {
|
||||||
|
const name = room.name || room.canonical_alias || room.aliases.pop() || _t("Empty room");
|
||||||
|
const avatar = (
|
||||||
|
<RoomAvatar
|
||||||
|
oobData={{
|
||||||
|
name,
|
||||||
|
avatarUrl: room.avatar_url,
|
||||||
|
}}
|
||||||
|
width={32}
|
||||||
|
height={32}
|
||||||
|
resizeMethod="crop"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
const viewRoom = () => {
|
||||||
|
defaultDispatcher.dispatch({
|
||||||
|
action: "view_room",
|
||||||
|
room_id: room.room_id,
|
||||||
|
oobData: {
|
||||||
|
avatarUrl: room.avatar_url,
|
||||||
|
name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<ExtraTile
|
||||||
|
isMinimized={this.props.isMinimized}
|
||||||
|
isSelected={this.state.currentRoomId === room.room_id}
|
||||||
|
displayName={name}
|
||||||
|
avatar={avatar}
|
||||||
|
onClick={viewRoom}
|
||||||
|
key={`suggestedRoomTile_${room.room_id}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderCommunityInvites(): ReactComponentElement<typeof ExtraTile>[] {
|
||||||
// TODO: Put community invites in a more sensible place (not in the room list)
|
// TODO: Put community invites in a more sensible place (not in the room list)
|
||||||
// See https://github.com/vector-im/element-web/issues/14456
|
// See https://github.com/vector-im/element-web/issues/14456
|
||||||
return MatrixClientPeg.get().getGroups().filter(g => {
|
return MatrixClientPeg.get().getGroups().filter(g => {
|
||||||
|
@ -415,7 +480,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<TemporaryTile
|
<ExtraTile
|
||||||
isMinimized={this.props.isMinimized}
|
isMinimized={this.props.isMinimized}
|
||||||
isSelected={false}
|
isSelected={false}
|
||||||
displayName={g.name}
|
displayName={g.name}
|
||||||
|
@ -447,7 +512,14 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
for (const orderedTagId of tagOrder) {
|
for (const orderedTagId of tagOrder) {
|
||||||
const orderedRooms = this.state.sublists[orderedTagId] || [];
|
const orderedRooms = this.state.sublists[orderedTagId] || [];
|
||||||
const extraTiles = orderedTagId === DefaultTagID.Invite ? this.renderCommunityInvites() : null;
|
|
||||||
|
let extraTiles = null;
|
||||||
|
if (orderedTagId === DefaultTagID.Invite) {
|
||||||
|
extraTiles = this.renderCommunityInvites();
|
||||||
|
} else if (orderedTagId === DefaultTagID.Suggested) {
|
||||||
|
extraTiles = this.renderSuggestedRooms();
|
||||||
|
}
|
||||||
|
|
||||||
const totalTiles = orderedRooms.length + (extraTiles ? extraTiles.length : 0);
|
const totalTiles = orderedRooms.length + (extraTiles ? extraTiles.length : 0);
|
||||||
if (totalTiles === 0 && !ALWAYS_VISIBLE_TAGS.includes(orderedTagId)) {
|
if (totalTiles === 0 && !ALWAYS_VISIBLE_TAGS.includes(orderedTagId)) {
|
||||||
continue; // skip tag - not needed
|
continue; // skip tag - not needed
|
||||||
|
@ -470,7 +542,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
||||||
isMinimized={this.props.isMinimized}
|
isMinimized={this.props.isMinimized}
|
||||||
onResize={this.props.onResize}
|
onResize={this.props.onResize}
|
||||||
showSkeleton={showSkeleton}
|
showSkeleton={showSkeleton}
|
||||||
extraBadTilesThatShouldntExist={extraTiles}
|
extraTiles={extraTiles}
|
||||||
/>);
|
/>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {createRef} from "react";
|
import { createRef, ReactComponentElement } 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 { RovingAccessibleButton, RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex";
|
import { RovingAccessibleButton, RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex";
|
||||||
|
@ -48,7 +48,7 @@ import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNo
|
||||||
import RoomListLayoutStore from "../../../stores/room-list/RoomListLayoutStore";
|
import RoomListLayoutStore from "../../../stores/room-list/RoomListLayoutStore";
|
||||||
import { arrayFastClone, arrayHasOrderChange } from "../../../utils/arrays";
|
import { arrayFastClone, arrayHasOrderChange } from "../../../utils/arrays";
|
||||||
import { objectExcluding, objectHasDiff } from "../../../utils/objects";
|
import { objectExcluding, objectHasDiff } from "../../../utils/objects";
|
||||||
import TemporaryTile from "./TemporaryTile";
|
import ExtraTile from "./ExtraTile";
|
||||||
import { ListNotificationState } from "../../../stores/notifications/ListNotificationState";
|
import { ListNotificationState } from "../../../stores/notifications/ListNotificationState";
|
||||||
import IconizedContextMenu from "../context_menus/IconizedContextMenu";
|
import IconizedContextMenu from "../context_menus/IconizedContextMenu";
|
||||||
|
|
||||||
|
@ -73,9 +73,7 @@ interface IProps {
|
||||||
onResize: () => void;
|
onResize: () => void;
|
||||||
showSkeleton?: boolean;
|
showSkeleton?: boolean;
|
||||||
|
|
||||||
// TODO: Don't use this. It's for community invites, and community invites shouldn't be here.
|
extraTiles?: ReactComponentElement<typeof ExtraTile>[];
|
||||||
// You should feel bad if you use this.
|
|
||||||
extraBadTilesThatShouldntExist?: TemporaryTile[];
|
|
||||||
|
|
||||||
// TODO: Account for https://github.com/vector-im/element-web/issues/14179
|
// TODO: Account for https://github.com/vector-im/element-web/issues/14179
|
||||||
}
|
}
|
||||||
|
@ -95,7 +93,7 @@ interface IState {
|
||||||
isExpanded: boolean; // used for the for expand of the sublist when the room list is being filtered
|
isExpanded: boolean; // used for the for expand of the sublist when the room list is being filtered
|
||||||
height: number;
|
height: number;
|
||||||
rooms: Room[];
|
rooms: Room[];
|
||||||
filteredExtraTiles?: TemporaryTile[];
|
filteredExtraTiles?: ReactComponentElement<typeof ExtraTile>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class RoomSublist extends React.Component<IProps, IState> {
|
export default class RoomSublist extends React.Component<IProps, IState> {
|
||||||
|
@ -153,12 +151,12 @@ export default class RoomSublist extends React.Component<IProps, IState> {
|
||||||
return padding;
|
return padding;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get extraTiles(): TemporaryTile[] | null {
|
private get extraTiles(): ReactComponentElement<typeof ExtraTile>[] | null {
|
||||||
if (this.state.filteredExtraTiles) {
|
if (this.state.filteredExtraTiles) {
|
||||||
return this.state.filteredExtraTiles;
|
return this.state.filteredExtraTiles;
|
||||||
}
|
}
|
||||||
if (this.props.extraBadTilesThatShouldntExist) {
|
if (this.props.extraTiles) {
|
||||||
return this.props.extraBadTilesThatShouldntExist;
|
return this.props.extraTiles;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -177,7 +175,7 @@ export default class RoomSublist extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidUpdate(prevProps: Readonly<IProps>, prevState: Readonly<IState>) {
|
public componentDidUpdate(prevProps: Readonly<IProps>, prevState: Readonly<IState>) {
|
||||||
const prevExtraTiles = prevState.filteredExtraTiles || prevProps.extraBadTilesThatShouldntExist;
|
const prevExtraTiles = prevState.filteredExtraTiles || prevProps.extraTiles;
|
||||||
// as the rooms can come in one by one we need to reevaluate
|
// as the rooms can come in one by one we need to reevaluate
|
||||||
// the amount of available rooms to cap the amount of requested visible rooms by the layout
|
// the amount of available rooms to cap the amount of requested visible rooms by the layout
|
||||||
if (RoomSublist.calcNumTiles(prevState.rooms, prevExtraTiles) !== this.numTiles) {
|
if (RoomSublist.calcNumTiles(prevState.rooms, prevExtraTiles) !== this.numTiles) {
|
||||||
|
@ -200,8 +198,8 @@ export default class RoomSublist extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
// If we're supposed to handle extra tiles, take the performance hit and re-render all the
|
// If we're supposed to handle extra tiles, take the performance hit and re-render all the
|
||||||
// time so we don't have to consider them as part of the visible room optimization.
|
// time so we don't have to consider them as part of the visible room optimization.
|
||||||
const prevExtraTiles = this.props.extraBadTilesThatShouldntExist || [];
|
const prevExtraTiles = this.props.extraTiles || [];
|
||||||
const nextExtraTiles = (nextState.filteredExtraTiles || nextProps.extraBadTilesThatShouldntExist) || [];
|
const nextExtraTiles = (nextState.filteredExtraTiles || nextProps.extraTiles) || [];
|
||||||
if (prevExtraTiles.length > 0 || nextExtraTiles.length > 0) {
|
if (prevExtraTiles.length > 0 || nextExtraTiles.length > 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -249,10 +247,10 @@ export default class RoomSublist extends React.Component<IProps, IState> {
|
||||||
private onListsUpdated = () => {
|
private onListsUpdated = () => {
|
||||||
const stateUpdates: IState & any = {}; // &any is to avoid a cast on the initializer
|
const stateUpdates: IState & any = {}; // &any is to avoid a cast on the initializer
|
||||||
|
|
||||||
if (this.props.extraBadTilesThatShouldntExist) {
|
if (this.props.extraTiles) {
|
||||||
const nameCondition = RoomListStore.instance.getFirstNameFilterCondition();
|
const nameCondition = RoomListStore.instance.getFirstNameFilterCondition();
|
||||||
if (nameCondition) {
|
if (nameCondition) {
|
||||||
stateUpdates.filteredExtraTiles = this.props.extraBadTilesThatShouldntExist
|
stateUpdates.filteredExtraTiles = this.props.extraTiles
|
||||||
.filter(t => nameCondition.matches(t.props.displayName || ""));
|
.filter(t => nameCondition.matches(t.props.displayName || ""));
|
||||||
} else if (this.state.filteredExtraTiles) {
|
} else if (this.state.filteredExtraTiles) {
|
||||||
stateUpdates.filteredExtraTiles = null;
|
stateUpdates.filteredExtraTiles = null;
|
||||||
|
|
|
@ -107,7 +107,8 @@ const SpaceCreateMenu = ({ onFinished }) => {
|
||||||
if (visibility === null) {
|
if (visibility === null) {
|
||||||
body = <React.Fragment>
|
body = <React.Fragment>
|
||||||
<h2>{ _t("Create a space") }</h2>
|
<h2>{ _t("Create a space") }</h2>
|
||||||
<p>{ _t("Organise rooms into spaces, for just you or anyone") }</p>
|
<p>{ _t("Spaces are new ways to group rooms and people. " +
|
||||||
|
"To join an existing space you’ll need an invite") }</p>
|
||||||
|
|
||||||
<SpaceCreateMenuType
|
<SpaceCreateMenuType
|
||||||
title={_t("Public")}
|
title={_t("Public")}
|
||||||
|
@ -117,12 +118,12 @@ const SpaceCreateMenu = ({ onFinished }) => {
|
||||||
/>
|
/>
|
||||||
<SpaceCreateMenuType
|
<SpaceCreateMenuType
|
||||||
title={_t("Private")}
|
title={_t("Private")}
|
||||||
description={_t("Invite only space, best for yourself or teams")}
|
description={_t("Invite only, best for yourself or teams")}
|
||||||
className="mx_SpaceCreateMenuType_private"
|
className="mx_SpaceCreateMenuType_private"
|
||||||
onClick={() => setVisibility(Visibility.Private)}
|
onClick={() => setVisibility(Visibility.Private)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/*<p>{ _t("Looking to join an existing space?") }</p>*/}
|
<p>{ _t("You can change this later") }</p>
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
} else {
|
} else {
|
||||||
body = <React.Fragment>
|
body = <React.Fragment>
|
||||||
|
@ -134,9 +135,7 @@ const SpaceCreateMenu = ({ onFinished }) => {
|
||||||
|
|
||||||
<h2>
|
<h2>
|
||||||
{
|
{
|
||||||
visibility === Visibility.Public
|
visibility === Visibility.Public ? _t("Your public space") : _t("Your private space")
|
||||||
? _t("Personalise your public space")
|
|
||||||
: _t("Personalise your private space")
|
|
||||||
}
|
}
|
||||||
</h2>
|
</h2>
|
||||||
<p>
|
<p>
|
||||||
|
|
|
@ -988,14 +988,15 @@
|
||||||
"Name": "Name",
|
"Name": "Name",
|
||||||
"Description": "Description",
|
"Description": "Description",
|
||||||
"Create a space": "Create a space",
|
"Create a space": "Create a space",
|
||||||
"Organise rooms into spaces, for just you or anyone": "Organise rooms into spaces, for just you or anyone",
|
"Spaces are new ways to group rooms and people. To join an existing space you’ll need an invite": "Spaces are new ways to group rooms and people. To join an existing space you’ll need an invite",
|
||||||
"Public": "Public",
|
"Public": "Public",
|
||||||
"Open space for anyone, best for communities": "Open space for anyone, best for communities",
|
"Open space for anyone, best for communities": "Open space for anyone, best for communities",
|
||||||
"Private": "Private",
|
"Private": "Private",
|
||||||
"Invite only space, best for yourself or teams": "Invite only space, best for yourself or teams",
|
"Invite only, best for yourself or teams": "Invite only, best for yourself or teams",
|
||||||
|
"You can change this later": "You can change this later",
|
||||||
"Go back": "Go back",
|
"Go back": "Go back",
|
||||||
"Personalise your public space": "Personalise your public space",
|
"Your public space": "Your public space",
|
||||||
"Personalise your private space": "Personalise your private space",
|
"Your private space": "Your private space",
|
||||||
"Give it a photo, name and description to help you identify it.": "Give it a photo, name and description to help you identify it.",
|
"Give it a photo, name and description to help you identify it.": "Give it a photo, name and description to help you identify it.",
|
||||||
"You can change these at any point.": "You can change these at any point.",
|
"You can change these at any point.": "You can change these at any point.",
|
||||||
"Creating...": "Creating...",
|
"Creating...": "Creating...",
|
||||||
|
@ -1532,7 +1533,9 @@
|
||||||
"Low priority": "Low priority",
|
"Low priority": "Low priority",
|
||||||
"System Alerts": "System Alerts",
|
"System Alerts": "System Alerts",
|
||||||
"Historical": "Historical",
|
"Historical": "Historical",
|
||||||
|
"Suggested Rooms": "Suggested Rooms",
|
||||||
"Custom Tag": "Custom Tag",
|
"Custom Tag": "Custom Tag",
|
||||||
|
"Empty room": "Empty room",
|
||||||
"Can't see what you’re looking for?": "Can't see what you’re looking for?",
|
"Can't see what you’re looking for?": "Can't see what you’re looking for?",
|
||||||
"Start a new chat": "Start a new chat",
|
"Start a new chat": "Start a new chat",
|
||||||
"Explore all public rooms": "Explore all public rooms",
|
"Explore all public rooms": "Explore all public rooms",
|
||||||
|
@ -2194,10 +2197,12 @@
|
||||||
"Start a conversation with someone using their name or username (like <userId/>).": "Start a conversation with someone using their name or username (like <userId/>).",
|
"Start a conversation with someone using their name or username (like <userId/>).": "Start a conversation with someone using their name or username (like <userId/>).",
|
||||||
"This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>": "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>",
|
"This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>": "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>",
|
||||||
"Go": "Go",
|
"Go": "Go",
|
||||||
"Invite someone using their name, email address, username (like <userId/>) or <a>share this room</a>.": "Invite someone using their name, email address, username (like <userId/>) or <a>share this room</a>.",
|
"Invite to %(spaceName)s": "Invite to %(spaceName)s",
|
||||||
"Invite someone using their name, username (like <userId/>) or <a>share this room</a>.": "Invite someone using their name, username (like <userId/>) or <a>share this room</a>.",
|
"Unnamed Space": "Unnamed Space",
|
||||||
"Invite someone using their name, email address, username (like <userId/>) or <a>share this space</a>.": "Invite someone using their name, email address, username (like <userId/>) or <a>share this space</a>.",
|
"Invite someone using their name, email address, username (like <userId/>) or <a>share this space</a>.": "Invite someone using their name, email address, username (like <userId/>) or <a>share this space</a>.",
|
||||||
"Invite someone using their name, username (like <userId/>) or <a>share this space</a>.": "Invite someone using their name, username (like <userId/>) or <a>share this space</a>.",
|
"Invite someone using their name, username (like <userId/>) or <a>share this space</a>.": "Invite someone using their name, username (like <userId/>) or <a>share this space</a>.",
|
||||||
|
"Invite someone using their name, email address, username (like <userId/>) or <a>share this room</a>.": "Invite someone using their name, email address, username (like <userId/>) or <a>share this room</a>.",
|
||||||
|
"Invite someone using their name, username (like <userId/>) or <a>share this room</a>.": "Invite someone using their name, username (like <userId/>) or <a>share this room</a>.",
|
||||||
"Transfer": "Transfer",
|
"Transfer": "Transfer",
|
||||||
"a new master key signature": "a new master key signature",
|
"a new master key signature": "a new master key signature",
|
||||||
"a new cross-signing key signature": "a new cross-signing key signature",
|
"a new cross-signing key signature": "a new cross-signing key signature",
|
||||||
|
@ -2595,14 +2600,13 @@
|
||||||
"Drop file here to upload": "Drop file here to upload",
|
"Drop file here to upload": "Drop file here to upload",
|
||||||
"You have %(count)s unread notifications in a prior version of this room.|other": "You have %(count)s unread notifications in a prior version of this room.",
|
"You have %(count)s unread notifications in a prior version of this room.|other": "You have %(count)s unread notifications in a prior version of this room.",
|
||||||
"You have %(count)s unread notifications in a prior version of this room.|one": "You have %(count)s unread notification in a prior version of this room.",
|
"You have %(count)s unread notifications in a prior version of this room.|one": "You have %(count)s unread notification in a prior version of this room.",
|
||||||
"Unnamed Space": "Unnamed Space",
|
|
||||||
"Undo": "Undo",
|
"Undo": "Undo",
|
||||||
"Remove from Space": "Remove from Space",
|
"Remove from Space": "Remove from Space",
|
||||||
"No permissions": "No permissions",
|
"No permissions": "No permissions",
|
||||||
"You're in this space": "You're in this space",
|
"You're in this space": "You're in this space",
|
||||||
"You're in this room": "You're in this room",
|
"You're in this room": "You're in this room",
|
||||||
"Save changes": "Save changes",
|
"Save changes": "Save changes",
|
||||||
"All users join by default": "All users join by default",
|
"Promoted to users": "Promoted to users",
|
||||||
"Manage rooms": "Manage rooms",
|
"Manage rooms": "Manage rooms",
|
||||||
"Find a room...": "Find a room...",
|
"Find a room...": "Find a room...",
|
||||||
"Accept Invite": "Accept Invite",
|
"Accept Invite": "Accept Invite",
|
||||||
|
@ -2634,7 +2638,7 @@
|
||||||
"Invite your teammates": "Invite your teammates",
|
"Invite your teammates": "Invite your teammates",
|
||||||
"Invite by username": "Invite by username",
|
"Invite by username": "Invite by username",
|
||||||
"Inviting...": "Inviting...",
|
"Inviting...": "Inviting...",
|
||||||
"What discussions do you want to have?": "What discussions do you want to have?",
|
"What are some things you want to discuss?": "What are some things you want to discuss?",
|
||||||
"We'll create rooms for each topic.": "We'll create rooms for each topic.",
|
"We'll create rooms for each topic.": "We'll create rooms for each topic.",
|
||||||
"What projects are you working on?": "What projects are you working on?",
|
"What projects are you working on?": "What projects are you working on?",
|
||||||
"We'll create rooms for each of them. You can add existing rooms after setup.": "We'll create rooms for each of them. You can add existing rooms after setup.",
|
"We'll create rooms for each of them. You can add existing rooms after setup.": "We'll create rooms for each of them. You can add existing rooms after setup.",
|
||||||
|
|
|
@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {throttle, sortBy} from "lodash";
|
import {sortBy, throttle} from "lodash";
|
||||||
import {EventType} from "matrix-js-sdk/src/@types/event";
|
import {EventType, RoomType} from "matrix-js-sdk/src/@types/event";
|
||||||
import {Room} from "matrix-js-sdk/src/models/room";
|
import {Room} from "matrix-js-sdk/src/models/room";
|
||||||
import {MatrixEvent} from "matrix-js-sdk/src/models/event";
|
import {MatrixEvent} from "matrix-js-sdk/src/models/event";
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@ import {EnhancedMap, mapDiff} from "../utils/maps";
|
||||||
import {setHasDiff} from "../utils/sets";
|
import {setHasDiff} from "../utils/sets";
|
||||||
import {objectDiff} from "../utils/objects";
|
import {objectDiff} from "../utils/objects";
|
||||||
import {arrayHasDiff} from "../utils/arrays";
|
import {arrayHasDiff} from "../utils/arrays";
|
||||||
|
import {ISpaceSummaryEvent, ISpaceSummaryRoom} from "../components/structures/SpaceRoomDirectory";
|
||||||
|
|
||||||
type SpaceKey = string | symbol;
|
type SpaceKey = string | symbol;
|
||||||
|
|
||||||
|
@ -41,11 +42,14 @@ interface IState {}
|
||||||
const ACTIVE_SPACE_LS_KEY = "mx_active_space";
|
const ACTIVE_SPACE_LS_KEY = "mx_active_space";
|
||||||
|
|
||||||
export const HOME_SPACE = Symbol("home-space");
|
export const HOME_SPACE = Symbol("home-space");
|
||||||
|
export const SUGGESTED_ROOMS = Symbol("suggested-rooms");
|
||||||
|
|
||||||
export const UPDATE_TOP_LEVEL_SPACES = Symbol("top-level-spaces");
|
export const UPDATE_TOP_LEVEL_SPACES = Symbol("top-level-spaces");
|
||||||
export const UPDATE_SELECTED_SPACE = Symbol("selected-space");
|
export const UPDATE_SELECTED_SPACE = Symbol("selected-space");
|
||||||
// Space Room ID/HOME_SPACE will be emitted when a Space's children change
|
// Space Room ID/HOME_SPACE will be emitted when a Space's children change
|
||||||
|
|
||||||
|
const MAX_SUGGESTED_ROOMS = 20;
|
||||||
|
|
||||||
const partitionSpacesAndRooms = (arr: Room[]): [Room[], Room[]] => { // [spaces, rooms]
|
const partitionSpacesAndRooms = (arr: Room[]): [Room[], Room[]] => { // [spaces, rooms]
|
||||||
return arr.reduce((result, room: Room) => {
|
return arr.reduce((result, room: Room) => {
|
||||||
result[room.isSpaceRoom() ? 0 : 1].push(room);
|
result[room.isSpaceRoom() ? 0 : 1].push(room);
|
||||||
|
@ -85,6 +89,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
private spaceFilteredRooms = new Map<string | symbol, Set<string>>();
|
private spaceFilteredRooms = new Map<string | symbol, Set<string>>();
|
||||||
// The space currently selected in the Space Panel - if null then `Home` is selected
|
// The space currently selected in the Space Panel - if null then `Home` is selected
|
||||||
private _activeSpace?: Room = null;
|
private _activeSpace?: Room = null;
|
||||||
|
private _suggestedRooms: ISpaceSummaryRoom[] = [];
|
||||||
|
|
||||||
public get spacePanelSpaces(): Room[] {
|
public get spacePanelSpaces(): Room[] {
|
||||||
return this.rootSpaces;
|
return this.rootSpaces;
|
||||||
|
@ -94,11 +99,16 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
return this._activeSpace || null;
|
return this._activeSpace || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setActiveSpace(space: Room | null) {
|
public get suggestedRooms(): ISpaceSummaryRoom[] {
|
||||||
|
return this._suggestedRooms;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async setActiveSpace(space: Room | null) {
|
||||||
if (space === this.activeSpace) return;
|
if (space === this.activeSpace) return;
|
||||||
|
|
||||||
this._activeSpace = space;
|
this._activeSpace = space;
|
||||||
this.emit(UPDATE_SELECTED_SPACE, this.activeSpace);
|
this.emit(UPDATE_SELECTED_SPACE, this.activeSpace);
|
||||||
|
this.emit(SUGGESTED_ROOMS, this._suggestedRooms = []);
|
||||||
|
|
||||||
// persist space selected
|
// persist space selected
|
||||||
if (space) {
|
if (space) {
|
||||||
|
@ -106,11 +116,29 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
} else {
|
} else {
|
||||||
window.localStorage.removeItem(ACTIVE_SPACE_LS_KEY);
|
window.localStorage.removeItem(ACTIVE_SPACE_LS_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (space) {
|
||||||
|
try {
|
||||||
|
const data: {
|
||||||
|
rooms: ISpaceSummaryRoom[];
|
||||||
|
events: ISpaceSummaryEvent[];
|
||||||
|
} = await this.matrixClient.getSpaceSummary(space.roomId, 0, true, false, MAX_SUGGESTED_ROOMS);
|
||||||
|
if (this._activeSpace === space) {
|
||||||
|
this._suggestedRooms = data.rooms.filter(roomInfo => {
|
||||||
|
return roomInfo.room_type !== RoomType.Space && !this.matrixClient.getRoom(roomInfo.room_id);
|
||||||
|
});
|
||||||
|
this.emit(SUGGESTED_ROOMS, this._suggestedRooms);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public addRoomToSpace(space: Room, roomId: string, via: string[], autoJoin = false) {
|
public addRoomToSpace(space: Room, roomId: string, via: string[], suggested = false, autoJoin = false) {
|
||||||
return this.matrixClient.sendStateEvent(space.roomId, EventType.SpaceChild, {
|
return this.matrixClient.sendStateEvent(space.roomId, EventType.SpaceChild, {
|
||||||
via,
|
via,
|
||||||
|
suggested,
|
||||||
auto_join: autoJoin,
|
auto_join: autoJoin,
|
||||||
}, roomId);
|
}, roomId);
|
||||||
}
|
}
|
||||||
|
@ -327,6 +355,12 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
// this.onRoomUpdate(room);
|
// this.onRoomUpdate(room);
|
||||||
this.onRoomsUpdate();
|
this.onRoomsUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const numSuggestedRooms = this._suggestedRooms.length;
|
||||||
|
this._suggestedRooms = this._suggestedRooms.filter(r => r.room_id !== room.roomId);
|
||||||
|
if (numSuggestedRooms !== this._suggestedRooms.length) {
|
||||||
|
this.emit(SUGGESTED_ROOMS, this._suggestedRooms);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private onRoomState = (ev: MatrixEvent) => {
|
private onRoomState = (ev: MatrixEvent) => {
|
||||||
|
@ -408,6 +442,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async onAction(payload: ActionPayload) {
|
protected async onAction(payload: ActionPayload) {
|
||||||
|
if (!SettingsStore.getValue("feature_spaces")) return;
|
||||||
switch (payload.action) {
|
switch (payload.action) {
|
||||||
case "view_room": {
|
case "view_room": {
|
||||||
const room = this.matrixClient?.getRoom(payload.room_id);
|
const room = this.matrixClient?.getRoom(payload.room_id);
|
||||||
|
|
|
@ -409,7 +409,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<any> {
|
private async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<any> {
|
||||||
if (cause === RoomUpdateCause.NewRoom) {
|
if (cause === RoomUpdateCause.NewRoom && room.getMyMembership() === "invite") {
|
||||||
// Let the visibility provider know that there is a new invited room. It would be nice
|
// Let the visibility provider know that there is a new invited room. It would be nice
|
||||||
// if this could just be an event that things listen for but the point of this is that
|
// if this could just be an event that things listen for but the point of this is that
|
||||||
// we delay doing anything about this room until the VoipUserMapper had had a chance
|
// we delay doing anything about this room until the VoipUserMapper had had a chance
|
||||||
|
|
|
@ -24,6 +24,7 @@ export enum DefaultTagID {
|
||||||
Favourite = "m.favourite",
|
Favourite = "m.favourite",
|
||||||
DM = "im.vector.fake.direct",
|
DM = "im.vector.fake.direct",
|
||||||
ServerNotice = "m.server_notice",
|
ServerNotice = "m.server_notice",
|
||||||
|
Suggested = "im.vector.fake.suggested",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const OrderedDefaultTagIDs = [
|
export const OrderedDefaultTagIDs = [
|
||||||
|
@ -33,6 +34,7 @@ export const OrderedDefaultTagIDs = [
|
||||||
DefaultTagID.Untagged,
|
DefaultTagID.Untagged,
|
||||||
DefaultTagID.LowPriority,
|
DefaultTagID.LowPriority,
|
||||||
DefaultTagID.ServerNotice,
|
DefaultTagID.ServerNotice,
|
||||||
|
DefaultTagID.Suggested,
|
||||||
DefaultTagID.Archived,
|
DefaultTagID.Archived,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue