Merge pull request #5789 from matrix-org/t3chguy/spaces4.11
Tweak and fix some space features
This commit is contained in:
commit
760b11f214
27 changed files with 343 additions and 194 deletions
|
@ -49,11 +49,12 @@ export function showStartChatInviteDialog(initialText) {
|
|||
);
|
||||
}
|
||||
|
||||
export function showRoomInviteDialog(roomId) {
|
||||
export function showRoomInviteDialog(roomId, initialText = "") {
|
||||
// This dialog handles the room creation internally - we don't need to worry about it.
|
||||
Modal.createTrackedDialog(
|
||||
"Invite Users", "", InviteDialog, {
|
||||
kind: KIND_INVITE,
|
||||
initialText,
|
||||
roomId,
|
||||
},
|
||||
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true,
|
||||
|
|
|
@ -16,9 +16,11 @@ limitations under the License.
|
|||
|
||||
import * as React from "react";
|
||||
import { createRef } from "react";
|
||||
import classNames from "classnames";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
|
||||
import GroupFilterPanel from "./GroupFilterPanel";
|
||||
import CustomRoomTagPanel from "./CustomRoomTagPanel";
|
||||
import classNames from "classnames";
|
||||
import dis from "../../dispatcher/dispatcher";
|
||||
import { _t } from "../../languageHandler";
|
||||
import RoomList from "../views/rooms/RoomList";
|
||||
|
@ -40,6 +42,7 @@ import RoomListNumResults from "../views/rooms/RoomListNumResults";
|
|||
import LeftPanelWidget from "./LeftPanelWidget";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
import {mediaFromMxc} from "../../customisations/Media";
|
||||
import SpaceStore, {UPDATE_SELECTED_SPACE} from "../../stores/SpaceStore";
|
||||
|
||||
interface IProps {
|
||||
isMinimized: boolean;
|
||||
|
@ -49,6 +52,7 @@ interface IProps {
|
|||
interface IState {
|
||||
showBreadcrumbs: boolean;
|
||||
showGroupFilterPanel: boolean;
|
||||
activeSpace?: Room;
|
||||
}
|
||||
|
||||
// List of CSS classes which should be included in keyboard navigation within the room list
|
||||
|
@ -74,11 +78,13 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||
this.state = {
|
||||
showBreadcrumbs: BreadcrumbsStore.instance.visible,
|
||||
showGroupFilterPanel: SettingsStore.getValue('TagPanel.enableTagPanel'),
|
||||
activeSpace: SpaceStore.instance.activeSpace,
|
||||
};
|
||||
|
||||
BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
||||
RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
||||
OwnProfileStore.instance.on(UPDATE_EVENT, this.onBackgroundImageUpdate);
|
||||
SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.updateActiveSpace);
|
||||
this.bgImageWatcherRef = SettingsStore.watchSetting(
|
||||
"RoomList.backgroundImage", null, this.onBackgroundImageUpdate);
|
||||
this.groupFilterPanelWatcherRef = SettingsStore.watchSetting("TagPanel.enableTagPanel", null, () => {
|
||||
|
@ -96,9 +102,14 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||
BreadcrumbsStore.instance.off(UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
||||
RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
||||
OwnProfileStore.instance.off(UPDATE_EVENT, this.onBackgroundImageUpdate);
|
||||
SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.updateActiveSpace);
|
||||
this.props.resizeNotifier.off("middlePanelResizedNoisy", this.onResize);
|
||||
}
|
||||
|
||||
private updateActiveSpace = (activeSpace: Room) => {
|
||||
this.setState({ activeSpace });
|
||||
};
|
||||
|
||||
private onExplore = () => {
|
||||
dis.fire(Action.ViewRoomDirectory);
|
||||
};
|
||||
|
@ -381,7 +392,9 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||
onEnter={this.onEnter}
|
||||
/>
|
||||
<AccessibleTooltipButton
|
||||
className="mx_LeftPanel_exploreButton"
|
||||
className={classNames("mx_LeftPanel_exploreButton", {
|
||||
mx_LeftPanel_exploreButton_space: !!this.state.activeSpace,
|
||||
})}
|
||||
onClick={this.onExplore}
|
||||
title={_t("Explore rooms")}
|
||||
/>
|
||||
|
@ -407,6 +420,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||
onBlur={this.onBlur}
|
||||
isMinimized={this.props.isMinimized}
|
||||
onResize={this.onResize}
|
||||
activeSpace={this.state.activeSpace}
|
||||
/>;
|
||||
|
||||
const containerClasses = classNames({
|
||||
|
|
|
@ -74,7 +74,6 @@ function canElementReceiveInput(el) {
|
|||
interface IProps {
|
||||
matrixClient: MatrixClient;
|
||||
onRegistered: (credentials: IMatrixClientCreds) => Promise<MatrixClient>;
|
||||
viaServers?: string[];
|
||||
hideToSRUsers: boolean;
|
||||
resizeNotifier: ResizeNotifier;
|
||||
// eslint-disable-next-line camelcase
|
||||
|
@ -143,9 +142,6 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
// transitioned to PWLU)
|
||||
onRegistered: PropTypes.func,
|
||||
|
||||
// Used by the RoomView to handle joining rooms
|
||||
viaServers: PropTypes.arrayOf(PropTypes.string),
|
||||
|
||||
// and lots and lots of other stuff.
|
||||
};
|
||||
|
||||
|
@ -625,11 +621,9 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
case PageTypes.RoomView:
|
||||
pageElement = <RoomView
|
||||
ref={this._roomView}
|
||||
autoJoin={this.props.autoJoin}
|
||||
onRegistered={this.props.onRegistered}
|
||||
threepidInvite={this.props.threepidInvite}
|
||||
oobData={this.props.roomOobData}
|
||||
viaServers={this.props.viaServers}
|
||||
key={this.props.currentRoomId || 'roomview'}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
justCreatedOpts={this.props.roomJustCreatedOpts}
|
||||
|
|
|
@ -202,7 +202,6 @@ interface IState {
|
|||
ready: boolean;
|
||||
threepidInvite?: IThreepidInvite,
|
||||
roomOobData?: object;
|
||||
viaServers?: string[];
|
||||
pendingInitialSync?: boolean;
|
||||
justRegistered?: boolean;
|
||||
roomJustCreatedOpts?: IOpts;
|
||||
|
@ -929,7 +928,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
page_type: PageTypes.RoomView,
|
||||
threepidInvite: roomInfo.threepid_invite,
|
||||
roomOobData: roomInfo.oob_data,
|
||||
viaServers: roomInfo.via_servers,
|
||||
ready: true,
|
||||
roomJustCreatedOpts: roomInfo.justCreatedOpts,
|
||||
}, () => {
|
||||
|
|
|
@ -112,10 +112,6 @@ interface IProps {
|
|||
inviterName?: string;
|
||||
};
|
||||
|
||||
// Servers the RoomView can use to try and assist joins
|
||||
viaServers?: string[];
|
||||
|
||||
autoJoin?: boolean;
|
||||
resizeNotifier: ResizeNotifier;
|
||||
justCreatedOpts?: IOpts;
|
||||
|
||||
|
@ -450,9 +446,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
// now not joined because the js-sdk peeking API will clobber our historical room,
|
||||
// making it impossible to indicate a newly joined room.
|
||||
if (!joining && roomId) {
|
||||
if (this.props.autoJoin) {
|
||||
this.onJoinButtonClicked();
|
||||
} else if (!room && shouldPeek) {
|
||||
if (!room && shouldPeek) {
|
||||
console.info("Attempting to peek into room %s", roomId);
|
||||
this.setState({
|
||||
peekLoading: true,
|
||||
|
@ -1123,7 +1117,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
const signUrl = this.props.threepidInvite?.signUrl;
|
||||
dis.dispatch({
|
||||
action: 'join_room',
|
||||
opts: { inviteSignUrl: signUrl, viaServers: this.props.viaServers },
|
||||
opts: { inviteSignUrl: signUrl },
|
||||
_type: "unknown", // TODO: instrumentation
|
||||
});
|
||||
return Promise.resolve();
|
||||
|
|
|
@ -32,6 +32,8 @@ export default class SearchBox extends React.Component {
|
|||
onKeyDown: PropTypes.func,
|
||||
className: PropTypes.string,
|
||||
placeholder: PropTypes.string.isRequired,
|
||||
autoFocus: PropTypes.bool,
|
||||
initialValue: PropTypes.string,
|
||||
|
||||
// If true, the search box will focus and clear itself
|
||||
// on room search focus action (it would be nicer to take
|
||||
|
@ -49,7 +51,7 @@ export default class SearchBox extends React.Component {
|
|||
this._search = createRef();
|
||||
|
||||
this.state = {
|
||||
searchTerm: "",
|
||||
searchTerm: this.props.initialValue || "",
|
||||
blurred: true,
|
||||
};
|
||||
}
|
||||
|
@ -158,6 +160,7 @@ export default class SearchBox extends React.Component {
|
|||
onBlur={this._onBlur}
|
||||
placeholder={ placeholder }
|
||||
autoComplete="off"
|
||||
autoFocus={this.props.autoFocus}
|
||||
/>
|
||||
{ clearButton }
|
||||
</div>
|
||||
|
|
|
@ -15,7 +15,8 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React, {useMemo, useState} from "react";
|
||||
import Room from "matrix-js-sdk/src/models/room";
|
||||
import {Room} from "matrix-js-sdk/src/models/room";
|
||||
import {MatrixClient} from "matrix-js-sdk/src/client";
|
||||
import {EventType, RoomType} from "matrix-js-sdk/src/@types/event";
|
||||
import classNames from "classnames";
|
||||
import {sortBy} from "lodash";
|
||||
|
@ -77,7 +78,6 @@ export interface ISpaceSummaryEvent {
|
|||
|
||||
interface ITileProps {
|
||||
room: ISpaceSummaryRoom;
|
||||
editing?: boolean;
|
||||
suggested?: boolean;
|
||||
selected?: boolean;
|
||||
numChildRooms?: number;
|
||||
|
@ -88,7 +88,6 @@ interface ITileProps {
|
|||
|
||||
const Tile: React.FC<ITileProps> = ({
|
||||
room,
|
||||
editing,
|
||||
suggested,
|
||||
selected,
|
||||
hasPermissions,
|
||||
|
@ -170,12 +169,6 @@ const Tile: React.FC<ITileProps> = ({
|
|||
</div>
|
||||
</React.Fragment>;
|
||||
|
||||
if (editing) {
|
||||
return <div className="mx_SpaceRoomDirectory_roomTile">
|
||||
{ content }
|
||||
</div>
|
||||
}
|
||||
|
||||
let childToggle;
|
||||
let childSection;
|
||||
if (children) {
|
||||
|
@ -201,7 +194,7 @@ const Tile: React.FC<ITileProps> = ({
|
|||
className={classNames("mx_SpaceRoomDirectory_roomTile", {
|
||||
mx_SpaceRoomDirectory_subspace: room.room_type === RoomType.Space,
|
||||
})}
|
||||
onClick={hasPermissions ? onToggleClick : onPreviewClick}
|
||||
onClick={(hasPermissions && onToggleClick) ? onToggleClick : onPreviewClick}
|
||||
>
|
||||
{ content }
|
||||
{ childToggle }
|
||||
|
@ -240,7 +233,7 @@ export const showRoom = (room: ISpaceSummaryRoom, viaServers?: string[], autoJoi
|
|||
interface IHierarchyLevelProps {
|
||||
spaceId: string;
|
||||
rooms: Map<string, ISpaceSummaryRoom>;
|
||||
relations: EnhancedMap<string, Map<string, ISpaceSummaryEvent>>;
|
||||
relations: Map<string, Map<string, ISpaceSummaryEvent>>;
|
||||
parents: Set<string>;
|
||||
selectedMap?: Map<string, Set<string>>;
|
||||
onViewRoomClick(roomId: string, autoJoin: boolean): void;
|
||||
|
@ -260,7 +253,7 @@ export const HierarchyLevel = ({
|
|||
const space = cli.getRoom(spaceId);
|
||||
const hasPermissions = space?.currentState.maySendStateEvent(EventType.SpaceChild, cli.getUserId())
|
||||
|
||||
const sortedChildren = sortBy([...relations.get(spaceId)?.values()], ev => ev.content.order || null);
|
||||
const sortedChildren = sortBy([...(relations.get(spaceId)?.values() || [])], ev => ev.content.order || null);
|
||||
const [subspaces, childRooms] = sortedChildren.reduce((result, ev: ISpaceSummaryEvent) => {
|
||||
const roomId = ev.state_key;
|
||||
if (!rooms.has(roomId)) return result;
|
||||
|
@ -316,23 +309,15 @@ export const HierarchyLevel = ({
|
|||
</React.Fragment>
|
||||
};
|
||||
|
||||
const SpaceRoomDirectory: React.FC<IProps> = ({ space, initialText = "", onFinished }) => {
|
||||
// mutate argument refreshToken to force a reload
|
||||
export const useSpaceSummary = (cli: MatrixClient, space: Room, refreshToken?: any): [
|
||||
ISpaceSummaryRoom[],
|
||||
Map<string, Map<string, ISpaceSummaryEvent>>,
|
||||
Map<string, Set<string>>,
|
||||
Map<string, Set<string>>,
|
||||
] | [] => {
|
||||
// TODO pagination
|
||||
const cli = MatrixClientPeg.get();
|
||||
const userId = cli.getUserId();
|
||||
const [query, setQuery] = useState(initialText);
|
||||
|
||||
const onCreateRoomClick = () => {
|
||||
dis.dispatch({
|
||||
action: 'view_create_room',
|
||||
public: true,
|
||||
});
|
||||
onFinished();
|
||||
};
|
||||
|
||||
const [selected, setSelected] = useState(new Map<string, Set<string>>()); // Map<parentId, Set<childId>>
|
||||
|
||||
const [rooms, parentChildMap, childParentMap, viaMap] = useAsyncMemo(async () => {
|
||||
return useAsyncMemo(async () => {
|
||||
try {
|
||||
const data = await cli.getSpaceSummary(space.roomId);
|
||||
|
||||
|
@ -350,13 +335,31 @@ const SpaceRoomDirectory: React.FC<IProps> = ({ space, initialText = "", onFinis
|
|||
}
|
||||
});
|
||||
|
||||
return [data.rooms as ISpaceSummaryRoom[], parentChildRelations, childParentRelations, viaMap];
|
||||
return [data.rooms as ISpaceSummaryRoom[], parentChildRelations, viaMap, childParentRelations];
|
||||
} catch (e) {
|
||||
console.error(e); // TODO
|
||||
}
|
||||
|
||||
return [];
|
||||
}, [space], []);
|
||||
}, [space, refreshToken], []);
|
||||
};
|
||||
|
||||
const SpaceRoomDirectory: React.FC<IProps> = ({ space, initialText = "", onFinished }) => {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const userId = cli.getUserId();
|
||||
const [query, setQuery] = useState(initialText);
|
||||
|
||||
const onCreateRoomClick = () => {
|
||||
dis.dispatch({
|
||||
action: 'view_create_room',
|
||||
public: true,
|
||||
});
|
||||
onFinished();
|
||||
};
|
||||
|
||||
const [selected, setSelected] = useState(new Map<string, Set<string>>()); // Map<parentId, Set<childId>>
|
||||
|
||||
const [rooms, parentChildMap, viaMap, childParentMap] = useSpaceSummary(cli, space);
|
||||
|
||||
const roomsMap = useMemo(() => {
|
||||
if (!rooms) return null;
|
||||
|
@ -570,6 +573,8 @@ const SpaceRoomDirectory: React.FC<IProps> = ({ space, initialText = "", onFinis
|
|||
className="mx_textinput_icon mx_textinput_search"
|
||||
placeholder={ _t("Search names and description") }
|
||||
onSearch={setQuery}
|
||||
autoFocus={true}
|
||||
initialValue={initialText}
|
||||
/>
|
||||
|
||||
{ content }
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, {RefObject, useContext, useRef, useState} from "react";
|
||||
import React, {RefObject, useContext, useMemo, useRef, useState} from "react";
|
||||
import {EventType, RoomType} from "matrix-js-sdk/src/@types/event";
|
||||
import {Room} from "matrix-js-sdk/src/models/room";
|
||||
import {EventSubscription} from "fbemitter";
|
||||
|
@ -26,7 +26,6 @@ import AccessibleButton from "../views/elements/AccessibleButton";
|
|||
import RoomName from "../views/elements/RoomName";
|
||||
import RoomTopic from "../views/elements/RoomTopic";
|
||||
import InlineSpinner from "../views/elements/InlineSpinner";
|
||||
import FormButton from "../views/elements/FormButton";
|
||||
import {inviteMultipleToRoom, showRoomInviteDialog} from "../../RoomInvite";
|
||||
import {useRoomMembers} from "../../hooks/useRoomMembers";
|
||||
import createRoom, {IOpts, Preset} from "../../createRoom";
|
||||
|
@ -47,9 +46,7 @@ import {SetRightPanelPhasePayload} from "../../dispatcher/payloads/SetRightPanel
|
|||
import {useStateArray} from "../../hooks/useStateArray";
|
||||
import SpacePublicShare from "../views/spaces/SpacePublicShare";
|
||||
import {showAddExistingRooms, showCreateNewRoom, shouldShowSpaceSettings, showSpaceSettings} from "../../utils/space";
|
||||
import {HierarchyLevel, ISpaceSummaryEvent, ISpaceSummaryRoom, showRoom} from "./SpaceRoomDirectory";
|
||||
import {useAsyncMemo} from "../../hooks/useAsyncMemo";
|
||||
import {EnhancedMap} from "../../utils/maps";
|
||||
import {HierarchyLevel, ISpaceSummaryRoom, showRoom, useSpaceSummary} from "./SpaceRoomDirectory";
|
||||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||
import MemberAvatar from "../views/avatars/MemberAvatar";
|
||||
import {useStateToggle} from "../../hooks/useStateToggle";
|
||||
|
@ -124,30 +121,36 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
|
|||
}
|
||||
|
||||
joinButtons = <>
|
||||
<FormButton
|
||||
label={_t("Reject")}
|
||||
<AccessibleButton
|
||||
kind="secondary"
|
||||
onClick={() => {
|
||||
setBusy(true);
|
||||
onRejectButtonClicked();
|
||||
}} />
|
||||
<FormButton
|
||||
label={_t("Accept")}
|
||||
}}
|
||||
>
|
||||
{ _t("Reject") }
|
||||
</AccessibleButton>
|
||||
<AccessibleButton
|
||||
kind="primary"
|
||||
onClick={() => {
|
||||
setBusy(true);
|
||||
onJoinButtonClicked();
|
||||
}}
|
||||
/>
|
||||
>
|
||||
{ _t("Accept") }
|
||||
</AccessibleButton>
|
||||
</>;
|
||||
} else {
|
||||
joinButtons = (
|
||||
<FormButton
|
||||
label={_t("Join")}
|
||||
<AccessibleButton
|
||||
kind="primary"
|
||||
onClick={() => {
|
||||
setBusy(true);
|
||||
onJoinButtonClicked();
|
||||
}}
|
||||
/>
|
||||
>
|
||||
{ _t("Join") }
|
||||
</AccessibleButton>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -223,7 +226,7 @@ const SpaceLanding = ({ space }) => {
|
|||
|
||||
const canAddRooms = myMembership === "join" && space.currentState.maySendStateEvent(EventType.SpaceChild, userId);
|
||||
|
||||
const [_, forceUpdate] = useStateToggle(false); // TODO
|
||||
const [refreshToken, forceUpdate] = useStateToggle(false);
|
||||
|
||||
let addRoomButtons;
|
||||
if (canAddRooms) {
|
||||
|
@ -253,26 +256,13 @@ const SpaceLanding = ({ space }) => {
|
|||
</AccessibleButton>;
|
||||
}
|
||||
|
||||
const [loading, roomsMap, relations, numRooms] = useAsyncMemo(async () => {
|
||||
try {
|
||||
const data = await cli.getSpaceSummary(space.roomId, undefined, myMembership !== "join");
|
||||
|
||||
const parentChildRelations = new EnhancedMap<string, Map<string, ISpaceSummaryEvent>>();
|
||||
data.events.map((ev: ISpaceSummaryEvent) => {
|
||||
if (ev.type === EventType.SpaceChild) {
|
||||
parentChildRelations.getOrCreate(ev.room_id, new Map()).set(ev.state_key, ev);
|
||||
}
|
||||
});
|
||||
|
||||
const roomsMap = new Map<string, ISpaceSummaryRoom>(data.rooms.map(r => [r.room_id, r]));
|
||||
const numRooms = data.rooms.filter(r => r.room_type !== RoomType.Space).length;
|
||||
return [false, roomsMap, parentChildRelations, numRooms];
|
||||
} catch (e) {
|
||||
console.error(e); // TODO
|
||||
}
|
||||
|
||||
return [false];
|
||||
}, [space, _], [true]);
|
||||
const [rooms, relations, viaMap] = useSpaceSummary(cli, space, refreshToken);
|
||||
const [roomsMap, numRooms] = useMemo(() => {
|
||||
if (!rooms) return [];
|
||||
const roomsMap = new Map<string, ISpaceSummaryRoom>(rooms.map(r => [r.room_id, r]));
|
||||
const numRooms = rooms.filter(r => r.room_type !== RoomType.Space).length;
|
||||
return [roomsMap, numRooms];
|
||||
}, [rooms]);
|
||||
|
||||
let previewRooms;
|
||||
if (roomsMap) {
|
||||
|
@ -287,11 +277,11 @@ const SpaceLanding = ({ space }) => {
|
|||
relations={relations}
|
||||
parents={new Set()}
|
||||
onViewRoomClick={(roomId, autoJoin) => {
|
||||
showRoom(roomsMap.get(roomId), [], autoJoin);
|
||||
showRoom(roomsMap.get(roomId), Array.from(viaMap.get(roomId) || []), autoJoin);
|
||||
}}
|
||||
/>
|
||||
</AutoHideScrollbar>;
|
||||
} else if (loading) {
|
||||
} else if (!rooms) {
|
||||
previewRooms = <InlineSpinner />;
|
||||
} else {
|
||||
previewRooms = <p>{_t("Your server does not support showing space hierarchies.")}</p>;
|
||||
|
@ -407,11 +397,13 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
|
|||
{ fields }
|
||||
|
||||
<div className="mx_SpaceRoomView_buttons">
|
||||
<FormButton
|
||||
label={buttonLabel}
|
||||
<AccessibleButton
|
||||
kind="primary"
|
||||
disabled={busy}
|
||||
onClick={onClick}
|
||||
/>
|
||||
>
|
||||
{ buttonLabel }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
|
@ -419,14 +411,16 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
|
|||
const SpaceSetupPublicShare = ({ space, onFinished }) => {
|
||||
return <div className="mx_SpaceRoomView_publicShare">
|
||||
<h1>{ _t("Share %(name)s", { name: space.name }) }</h1>
|
||||
<div className="mx_SpacePublicShare_description">
|
||||
<div className="mx_SpaceRoomView_description">
|
||||
{ _t("It's just you at the moment, it will be even better with others.") }
|
||||
</div>
|
||||
|
||||
<SpacePublicShare space={space} onFinished={onFinished} />
|
||||
<SpacePublicShare space={space} />
|
||||
|
||||
<div className="mx_SpaceRoomView_buttons">
|
||||
<FormButton label={_t("Go to my first room")} onClick={onFinished} />
|
||||
<AccessibleButton kind="primary" onClick={onFinished}>
|
||||
{ _t("Go to my first room") }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
|
@ -545,7 +539,9 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
|
|||
</div>
|
||||
|
||||
<div className="mx_SpaceRoomView_buttons">
|
||||
<FormButton label={buttonLabel} disabled={busy} onClick={onClick} />
|
||||
<AccessibleButton kind="primary" disabled={busy} onClick={onClick}>
|
||||
{ buttonLabel }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
|
@ -630,6 +626,8 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
|
|||
};
|
||||
|
||||
private goToFirstRoom = async () => {
|
||||
// TODO actually go to the first room
|
||||
|
||||
const childRooms = SpaceStore.instance.getChildRooms(this.props.space.roomId);
|
||||
if (childRooms.length) {
|
||||
const room = childRooms[0];
|
||||
|
|
|
@ -22,7 +22,6 @@ import {MatrixClient} from "matrix-js-sdk/src/client";
|
|||
import {_t} from '../../../languageHandler';
|
||||
import {IDialogProps} from "./IDialogProps";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import FormButton from "../elements/FormButton";
|
||||
import Dropdown from "../elements/Dropdown";
|
||||
import SearchBox from "../../structures/SearchBox";
|
||||
import SpaceStore from "../../../stores/SpaceStore";
|
||||
|
@ -110,7 +109,7 @@ const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space,
|
|||
const title = <React.Fragment>
|
||||
<RoomAvatar room={selectedSpace} height={40} width={40} />
|
||||
<div>
|
||||
<h1>{ _t("Add existing spaces/rooms") }</h1>
|
||||
<h1>{ _t("Add existing rooms") }</h1>
|
||||
{ spaceOptionSection }
|
||||
</div>
|
||||
</React.Fragment>;
|
||||
|
@ -128,29 +127,9 @@ const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space,
|
|||
className="mx_textinput_icon mx_textinput_search"
|
||||
placeholder={ _t("Filter your rooms and spaces") }
|
||||
onSearch={setQuery}
|
||||
autoComplete={true}
|
||||
/>
|
||||
<AutoHideScrollbar className="mx_AddExistingToSpaceDialog_content" id="mx_AddExistingToSpaceDialog">
|
||||
{ spaces.length > 0 ? (
|
||||
<div className="mx_AddExistingToSpaceDialog_section mx_AddExistingToSpaceDialog_section_spaces">
|
||||
<h3>{ _t("Spaces") }</h3>
|
||||
{ spaces.map(space => {
|
||||
return <Entry
|
||||
key={space.roomId}
|
||||
room={space}
|
||||
checked={selectedToAdd.has(space)}
|
||||
onChange={(checked) => {
|
||||
if (checked) {
|
||||
selectedToAdd.add(space);
|
||||
} else {
|
||||
selectedToAdd.delete(space);
|
||||
}
|
||||
setSelectedToAdd(new Set(selectedToAdd));
|
||||
}}
|
||||
/>;
|
||||
}) }
|
||||
</div>
|
||||
) : null }
|
||||
|
||||
{ rooms.length > 0 ? (
|
||||
<div className="mx_AddExistingToSpaceDialog_section">
|
||||
<h3>{ _t("Rooms") }</h3>
|
||||
|
@ -172,6 +151,27 @@ const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space,
|
|||
</div>
|
||||
) : undefined }
|
||||
|
||||
{ spaces.length > 0 ? (
|
||||
<div className="mx_AddExistingToSpaceDialog_section mx_AddExistingToSpaceDialog_section_spaces">
|
||||
<h3>{ _t("Spaces") }</h3>
|
||||
{ spaces.map(space => {
|
||||
return <Entry
|
||||
key={space.roomId}
|
||||
room={space}
|
||||
checked={selectedToAdd.has(space)}
|
||||
onChange={(checked) => {
|
||||
if (checked) {
|
||||
selectedToAdd.add(space);
|
||||
} else {
|
||||
selectedToAdd.delete(space);
|
||||
}
|
||||
setSelectedToAdd(new Set(selectedToAdd));
|
||||
}}
|
||||
/>;
|
||||
}) }
|
||||
</div>
|
||||
) : null }
|
||||
|
||||
{ spaces.length + rooms.length < 1 ? <span className="mx_AddExistingToSpaceDialog_noResults">
|
||||
{ _t("No results") }
|
||||
</span> : undefined }
|
||||
|
@ -185,8 +185,8 @@ const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space,
|
|||
</AccessibleButton>
|
||||
</span>
|
||||
|
||||
<FormButton
|
||||
label={busy ? _t("Applying...") : _t("Apply")}
|
||||
<AccessibleButton
|
||||
kind="primary"
|
||||
disabled={busy || selectedToAdd.size < 1}
|
||||
onClick={async () => {
|
||||
setBusy(true);
|
||||
|
@ -200,7 +200,9 @@ const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space,
|
|||
}
|
||||
setBusy(false);
|
||||
}}
|
||||
/>
|
||||
>
|
||||
{ busy ? _t("Adding...") : _t("Add") }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</BaseDialog>;
|
||||
};
|
||||
|
|
|
@ -28,7 +28,6 @@ import {getTopic} from "../elements/RoomTopic";
|
|||
import {avatarUrlForRoom} from "../../../Avatar";
|
||||
import ToggleSwitch from "../elements/ToggleSwitch";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import FormButton from "../elements/FormButton";
|
||||
import Modal from "../../../Modal";
|
||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||
import {allSettled} from "../../../utils/promise";
|
||||
|
@ -134,16 +133,17 @@ const SpaceSettingsDialog: React.FC<IProps> = ({ matrixClient: cli, space, onFin
|
|||
/>
|
||||
</div>
|
||||
|
||||
<FormButton
|
||||
<AccessibleButton
|
||||
kind="danger"
|
||||
label={_t("Leave Space")}
|
||||
onClick={() => {
|
||||
defaultDispatcher.dispatch({
|
||||
action: "leave_room",
|
||||
room_id: space.roomId,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
>
|
||||
{ _t("Leave Space") }
|
||||
</AccessibleButton>
|
||||
|
||||
<div className="mx_SpaceSettingsDialog_buttons">
|
||||
<AccessibleButton onClick={() => Modal.createDialog(DevtoolsDialog, {roomId: space.roomId})}>
|
||||
|
@ -152,7 +152,9 @@ const SpaceSettingsDialog: React.FC<IProps> = ({ matrixClient: cli, space, onFin
|
|||
<AccessibleButton onClick={onFinished} disabled={busy} kind="link">
|
||||
{ _t("Cancel") }
|
||||
</AccessibleButton>
|
||||
<FormButton onClick={onSave} disabled={busy} label={busy ? _t("Saving...") : _t("Save Changes")} />
|
||||
<AccessibleButton onClick={onSave} disabled={busy} kind="primary">
|
||||
{ busy ? _t("Saving...") : _t("Save Changes") }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</div>
|
||||
</BaseDialog>;
|
||||
|
|
|
@ -28,6 +28,7 @@ import defaultDispatcher from "../../../dispatcher/dispatcher";
|
|||
import {ViewUserPayload} from "../../../dispatcher/payloads/ViewUserPayload";
|
||||
import {Action} from "../../../dispatcher/actions";
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
import SpaceStore from "../../../stores/SpaceStore";
|
||||
|
||||
const NewRoomIntro = () => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
|
@ -100,17 +101,48 @@ const NewRoomIntro = () => {
|
|||
});
|
||||
}
|
||||
|
||||
let buttons;
|
||||
if (room.canInvite(cli.getUserId())) {
|
||||
const onInviteClick = () => {
|
||||
dis.dispatch({ action: "view_invite", roomId });
|
||||
};
|
||||
let parentSpace;
|
||||
if (
|
||||
SpaceStore.instance.activeSpace?.canInvite(cli.getUserId()) &&
|
||||
SpaceStore.instance.getSpaceFilteredRoomIds(SpaceStore.instance.activeSpace).has(room.roomId)
|
||||
) {
|
||||
parentSpace = SpaceStore.instance.activeSpace;
|
||||
}
|
||||
|
||||
let buttons;
|
||||
if (parentSpace) {
|
||||
buttons = <div className="mx_NewRoomIntro_buttons">
|
||||
<AccessibleButton className="mx_NewRoomIntro_inviteButton" kind="primary" onClick={onInviteClick}>
|
||||
<AccessibleButton
|
||||
className="mx_NewRoomIntro_inviteButton"
|
||||
kind="primary"
|
||||
onClick={() => {
|
||||
dis.dispatch({ action: "view_invite", roomId });
|
||||
}}
|
||||
>
|
||||
{_t("Invite to %(spaceName)s", { spaceName: parentSpace.name })}
|
||||
</AccessibleButton>
|
||||
{ room.canInvite(cli.getUserId()) && <AccessibleButton
|
||||
className="mx_NewRoomIntro_inviteButton"
|
||||
kind="primary_outline"
|
||||
onClick={() => {
|
||||
dis.dispatch({ action: "view_invite", roomId });
|
||||
}}
|
||||
>
|
||||
{_t("Invite to just this room")}
|
||||
</AccessibleButton> }
|
||||
</div>;
|
||||
} else if (room.canInvite(cli.getUserId())) {
|
||||
buttons = <div className="mx_NewRoomIntro_buttons">
|
||||
<AccessibleButton
|
||||
className="mx_NewRoomIntro_inviteButton"
|
||||
kind="primary"
|
||||
onClick={() => {
|
||||
dis.dispatch({ action: "view_invite", roomId });
|
||||
}}
|
||||
>
|
||||
{_t("Invite to this room")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
const avatarUrl = room.currentState.getStateEvents(EventType.RoomAvatar, "")?.getContent()?.url;
|
||||
|
|
|
@ -20,6 +20,7 @@ import React, { ReactComponentElement } from "react";
|
|||
import { Dispatcher } from "flux";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import * as fbEmitter from "fbemitter";
|
||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||
|
||||
import { _t, _td } from "../../../languageHandler";
|
||||
import { RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex";
|
||||
|
@ -48,12 +49,15 @@ import { IconizedContextMenuOption, IconizedContextMenuOptionList } from "../con
|
|||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore";
|
||||
import CallHandler from "../../../CallHandler";
|
||||
import SpaceStore, { SUGGESTED_ROOMS } from "../../../stores/SpaceStore";
|
||||
import SpaceStore, {SUGGESTED_ROOMS} from "../../../stores/SpaceStore";
|
||||
import { showAddExistingRooms, showCreateNewRoom } from "../../../utils/space";
|
||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import RoomAvatar from "../avatars/RoomAvatar";
|
||||
import { ISpaceSummaryRoom } from "../../structures/SpaceRoomDirectory";
|
||||
import { showRoomInviteDialog } from "../../../RoomInvite";
|
||||
import Modal from "../../../Modal";
|
||||
import SpacePublicShare from "../spaces/SpacePublicShare";
|
||||
import InfoDialog from "../dialogs/InfoDialog";
|
||||
|
||||
interface IProps {
|
||||
onKeyDown: (ev: React.KeyboardEvent) => void;
|
||||
|
@ -62,6 +66,7 @@ interface IProps {
|
|||
onResize: () => void;
|
||||
resizeNotifier: ResizeNotifier;
|
||||
isMinimized: boolean;
|
||||
activeSpace: Room;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
@ -194,8 +199,8 @@ const TAG_AESTHETICS: ITagAestheticsMap = {
|
|||
: _t("You do not have permissions to add rooms to this space")}
|
||||
/>
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Explore space rooms")}
|
||||
iconClassName="mx_RoomList_iconExplore"
|
||||
label={_t("Explore rooms")}
|
||||
iconClassName="mx_RoomList_iconBrowse"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
@ -424,6 +429,25 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
|||
dis.dispatch({ action: Action.ViewRoomDirectory, initialText });
|
||||
};
|
||||
|
||||
private onSpaceInviteClick = () => {
|
||||
const initialText = RoomListStore.instance.getFirstNameFilterCondition()?.search;
|
||||
if (this.props.activeSpace.getJoinRule() === "public") {
|
||||
const modal = Modal.createTrackedDialog("Space Invite", "User Menu", InfoDialog, {
|
||||
title: _t("Invite to %(spaceName)s", { spaceName: this.props.activeSpace.name }),
|
||||
description: <React.Fragment>
|
||||
<span>{ _t("Share your public space") }</span>
|
||||
<SpacePublicShare space={this.props.activeSpace} onFinished={() => modal.close()} />
|
||||
</React.Fragment>,
|
||||
fixedWidth: false,
|
||||
button: false,
|
||||
className: "mx_SpacePanel_sharePublicSpace",
|
||||
hasCloseButton: true,
|
||||
});
|
||||
} else {
|
||||
showRoomInviteDialog(this.props.activeSpace.roomId, initialText);
|
||||
}
|
||||
};
|
||||
|
||||
private renderSuggestedRooms(): ReactComponentElement<typeof ExtraTile>[] {
|
||||
return this.state.suggestedRooms.map(room => {
|
||||
const name = room.name || room.canonical_alias || room.aliases.pop() || _t("Empty room");
|
||||
|
@ -569,7 +593,23 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
|||
kind="link"
|
||||
onClick={this.onExplore}
|
||||
>
|
||||
{_t("Explore all public rooms")}
|
||||
{ this.props.activeSpace ? _t("Explore rooms") : _t("Explore all public rooms") }
|
||||
</AccessibleButton>
|
||||
</div>;
|
||||
} else if (this.props.activeSpace) {
|
||||
explorePrompt = <div className="mx_RoomList_explorePrompt">
|
||||
<div>{ _t("Quick actions") }</div>
|
||||
{ this.props.activeSpace.canInvite(MatrixClientPeg.get().getUserId()) && <AccessibleButton
|
||||
className="mx_RoomList_explorePrompt_spaceInvite"
|
||||
onClick={this.onSpaceInviteClick}
|
||||
>
|
||||
{_t("Invite people")}
|
||||
</AccessibleButton> }
|
||||
<AccessibleButton
|
||||
className="mx_RoomList_explorePrompt_spaceExplore"
|
||||
onClick={this.onExplore}
|
||||
>
|
||||
{_t("Explore rooms")}
|
||||
</AccessibleButton>
|
||||
</div>;
|
||||
} else if (Object.values(this.state.sublists).some(list => list.length > 0)) {
|
||||
|
|
|
@ -21,7 +21,6 @@ import {EventType, RoomType, RoomCreateTypeField} from "matrix-js-sdk/src/@types
|
|||
import {_t} from "../../../languageHandler";
|
||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||
import {ChevronFace, ContextMenu} from "../../structures/ContextMenu";
|
||||
import FormButton from "../elements/FormButton";
|
||||
import createRoom, {IStateEvent, Preset} from "../../../createRoom";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import SpaceBasicSettings from "./SpaceBasicSettings";
|
||||
|
@ -89,6 +88,7 @@ const SpaceCreateMenu = ({ onFinished }) => {
|
|||
power_level_content_override: {
|
||||
// Only allow Admins to write to the timeline to prevent hidden sync spam
|
||||
events_default: 100,
|
||||
...Visibility.Public ? { invite: 0 } : {},
|
||||
},
|
||||
},
|
||||
spinner: false,
|
||||
|
@ -148,11 +148,9 @@ const SpaceCreateMenu = ({ onFinished }) => {
|
|||
|
||||
<SpaceBasicSettings setAvatar={setAvatar} name={name} setName={setName} topic={topic} setTopic={setTopic} />
|
||||
|
||||
<FormButton
|
||||
label={busy ? _t("Creating...") : _t("Create")}
|
||||
onClick={onSpaceCreateClick}
|
||||
disabled={!name && !busy}
|
||||
/>
|
||||
<AccessibleButton kind="primary" onClick={onSpaceCreateClick} disabled={!name && !busy}>
|
||||
{ busy ? _t("Creating...") : _t("Create") }
|
||||
</AccessibleButton>
|
||||
</React.Fragment>;
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ import {showRoomInviteDialog} from "../../../RoomInvite";
|
|||
|
||||
interface IProps {
|
||||
space: Room;
|
||||
onFinished(): void;
|
||||
onFinished?(): void;
|
||||
}
|
||||
|
||||
const SpacePublicShare = ({ space, onFinished }: IProps) => {
|
||||
|
@ -54,7 +54,7 @@ const SpacePublicShare = ({ space, onFinished }: IProps) => {
|
|||
className="mx_SpacePublicShare_inviteButton"
|
||||
onClick={() => {
|
||||
showRoomInviteDialog(space.roomId);
|
||||
onFinished();
|
||||
if (onFinished) onFinished();
|
||||
}}
|
||||
>
|
||||
<h3>{ _t("Invite people") }</h3>
|
||||
|
|
|
@ -30,9 +30,14 @@ import IconizedContextMenu, {
|
|||
import {_t} from "../../../languageHandler";
|
||||
import {ContextMenuTooltipButton} from "../../../accessibility/context_menu/ContextMenuTooltipButton";
|
||||
import {toRightOf} from "../../structures/ContextMenu";
|
||||
import {shouldShowSpaceSettings, showCreateNewRoom, showSpaceSettings} from "../../../utils/space";
|
||||
import {
|
||||
shouldShowSpaceSettings,
|
||||
showAddExistingRooms,
|
||||
showCreateNewRoom,
|
||||
showSpaceSettings,
|
||||
} from "../../../utils/space";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import {ButtonEvent} from "../elements/AccessibleButton";
|
||||
import AccessibleButton, {ButtonEvent} from "../elements/AccessibleButton";
|
||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||
import Modal from "../../../Modal";
|
||||
import SpacePublicShare from "./SpacePublicShare";
|
||||
|
@ -127,7 +132,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
|
|||
|
||||
if (this.props.space.getJoinRule() === "public") {
|
||||
const modal = Modal.createTrackedDialog("Space Invite", "User Menu", InfoDialog, {
|
||||
title: _t("Invite members"),
|
||||
title: _t("Invite to %(spaceName)s", { spaceName: this.props.space.name }),
|
||||
description: <React.Fragment>
|
||||
<span>{ _t("Share your public space") }</span>
|
||||
<SpacePublicShare space={this.props.space} onFinished={() => modal.close()} />
|
||||
|
@ -170,6 +175,14 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
|
|||
this.setState({contextMenuPosition: null}); // also close the menu
|
||||
};
|
||||
|
||||
private onAddExistingRoomClick = (ev: ButtonEvent) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
showAddExistingRooms(this.context, this.props.space);
|
||||
this.setState({contextMenuPosition: null}); // also close the menu
|
||||
};
|
||||
|
||||
private onMembersClick = (ev: ButtonEvent) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
@ -236,15 +249,20 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
|
|||
</IconizedContextMenuOptionList>;
|
||||
}
|
||||
|
||||
let newRoomOption;
|
||||
let newRoomSection;
|
||||
if (this.props.space.currentState.maySendStateEvent(EventType.SpaceChild, userId)) {
|
||||
newRoomOption = (
|
||||
newRoomSection = <IconizedContextMenuOptionList first>
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_SpacePanel_iconPlus"
|
||||
label={_t("New room")}
|
||||
label={_t("Create new room")}
|
||||
onClick={this.onNewRoomClick}
|
||||
/>
|
||||
);
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_SpacePanel_iconHash"
|
||||
label={_t("Add existing room")}
|
||||
onClick={this.onAddExistingRoomClick}
|
||||
/>
|
||||
</IconizedContextMenuOptionList>;
|
||||
}
|
||||
|
||||
contextMenu = <IconizedContextMenu
|
||||
|
@ -274,8 +292,8 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
|
|||
label={_t("Explore rooms")}
|
||||
onClick={this.onExploreRoomsClick}
|
||||
/>
|
||||
{ newRoomOption }
|
||||
</IconizedContextMenuOptionList>
|
||||
{ newRoomSection }
|
||||
{ leaveSection }
|
||||
</IconizedContextMenu>;
|
||||
}
|
||||
|
@ -335,7 +353,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
|
|||
const avatarSize = isNested ? 24 : 32;
|
||||
|
||||
const toggleCollapseButton = childSpaces && childSpaces.length ?
|
||||
<button
|
||||
<AccessibleButton
|
||||
className="mx_SpaceButton_toggleCollapse"
|
||||
onClick={evt => this.toggleCollapse(evt)}
|
||||
/> : null;
|
||||
|
|
|
@ -1012,11 +1012,12 @@
|
|||
"Share invite link": "Share invite link",
|
||||
"Invite people": "Invite people",
|
||||
"Invite with email or username": "Invite with email or username",
|
||||
"Invite members": "Invite members",
|
||||
"Invite to %(spaceName)s": "Invite to %(spaceName)s",
|
||||
"Share your public space": "Share your public space",
|
||||
"Settings": "Settings",
|
||||
"Leave space": "Leave space",
|
||||
"New room": "New room",
|
||||
"Create new room": "Create new room",
|
||||
"Add existing room": "Add existing room",
|
||||
"Space Home": "Space Home",
|
||||
"Members": "Members",
|
||||
"Explore rooms": "Explore rooms",
|
||||
|
@ -1479,6 +1480,7 @@
|
|||
"<a>Add a topic</a> to help people know what it is about.": "<a>Add a topic</a> to help people know what it is about.",
|
||||
"You created this room.": "You created this room.",
|
||||
"%(displayName)s created this room.": "%(displayName)s created this room.",
|
||||
"Invite to just this room": "Invite to just this room",
|
||||
"Add a photo, so people can easily spot your room.": "Add a photo, so people can easily spot your room.",
|
||||
"This is the start of <roomName/>.": "This is the start of <roomName/>.",
|
||||
"No pinned messages.": "No pinned messages.",
|
||||
|
@ -1525,11 +1527,8 @@
|
|||
"Start chat": "Start chat",
|
||||
"Rooms": "Rooms",
|
||||
"Add room": "Add room",
|
||||
"Create new room": "Create new room",
|
||||
"You do not have permissions to create new rooms in this space": "You do not have permissions to create new rooms in this space",
|
||||
"Add existing room": "Add existing room",
|
||||
"You do not have permissions to add rooms to this space": "You do not have permissions to add rooms to this space",
|
||||
"Explore space rooms": "Explore space rooms",
|
||||
"Explore community rooms": "Explore community rooms",
|
||||
"Explore public rooms": "Explore public rooms",
|
||||
"Low priority": "Low priority",
|
||||
|
@ -1541,6 +1540,7 @@
|
|||
"Can't see what you’re looking for?": "Can't see what you’re looking for?",
|
||||
"Start a new chat": "Start a new chat",
|
||||
"Explore all public rooms": "Explore all public rooms",
|
||||
"Quick actions": "Quick actions",
|
||||
"Use the + to make a new room or explore existing ones below": "Use the + to make a new room or explore existing ones below",
|
||||
"%(count)s results|other": "%(count)s results",
|
||||
"%(count)s results|one": "%(count)s result",
|
||||
|
@ -2004,14 +2004,13 @@
|
|||
"%(networkName)s rooms": "%(networkName)s rooms",
|
||||
"Matrix rooms": "Matrix rooms",
|
||||
"Space selection": "Space selection",
|
||||
"Add existing spaces/rooms": "Add existing spaces/rooms",
|
||||
"Add existing rooms": "Add existing rooms",
|
||||
"Filter your rooms and spaces": "Filter your rooms and spaces",
|
||||
"Spaces": "Spaces",
|
||||
"Don't want to add an existing room?": "Don't want to add an existing room?",
|
||||
"Create a new room": "Create a new room",
|
||||
"Applying...": "Applying...",
|
||||
"Apply": "Apply",
|
||||
"Failed to add rooms to space": "Failed to add rooms to space",
|
||||
"Adding...": "Adding...",
|
||||
"Matrix ID": "Matrix ID",
|
||||
"Matrix Room ID": "Matrix Room ID",
|
||||
"email address": "email address",
|
||||
|
@ -2203,7 +2202,6 @@
|
|||
"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>",
|
||||
"Go": "Go",
|
||||
"Invite to %(spaceName)s": "Invite to %(spaceName)s",
|
||||
"Unnamed Space": "Unnamed Space",
|
||||
"Invite to %(roomName)s": "Invite to %(roomName)s",
|
||||
"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>.",
|
||||
|
|
|
@ -273,7 +273,10 @@ class RoomViewStore extends Store<ActionPayload> {
|
|||
const cli = MatrixClientPeg.get();
|
||||
const address = this.state.roomAlias || this.state.roomId;
|
||||
try {
|
||||
await retry<void, MatrixError>(() => cli.joinRoom(address, payload.opts), NUM_JOIN_RETRY, (err) => {
|
||||
await retry<void, MatrixError>(() => cli.joinRoom(address, {
|
||||
viaServers: payload.via_servers,
|
||||
...payload.opts,
|
||||
}), NUM_JOIN_RETRY, (err) => {
|
||||
// if we received a Gateway timeout then retry
|
||||
return err.httpStatus === 504;
|
||||
});
|
||||
|
|
|
@ -34,6 +34,7 @@ import {setHasDiff} from "../utils/sets";
|
|||
import {objectDiff} from "../utils/objects";
|
||||
import {arrayHasDiff} from "../utils/arrays";
|
||||
import {ISpaceSummaryEvent, ISpaceSummaryRoom} from "../components/structures/SpaceRoomDirectory";
|
||||
import RoomViewStore from "./RoomViewStore";
|
||||
|
||||
type SpaceKey = string | symbol;
|
||||
|
||||
|
@ -195,15 +196,18 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
return this.spaceFilteredRooms.get(space?.roomId || HOME_SPACE) || new Set();
|
||||
};
|
||||
|
||||
public rebuild = throttle(() => { // exported for tests
|
||||
const visibleRooms = this.matrixClient.getVisibleRooms();
|
||||
|
||||
// Sort spaces by room ID to force the loop breaking to be deterministic
|
||||
const spaces = sortBy(this.getSpaces(), space => space.roomId);
|
||||
const unseenChildren = new Set<Room>([...visibleRooms, ...spaces]);
|
||||
private rebuild = throttle(() => {
|
||||
// get all most-upgraded rooms & spaces except spaces which have been left (historical)
|
||||
const visibleRooms = this.matrixClient.getVisibleRooms().filter(r => {
|
||||
return !r.isSpaceRoom() || r.getMyMembership() === "join";
|
||||
});
|
||||
|
||||
const unseenChildren = new Set<Room>(visibleRooms);
|
||||
const backrefs = new EnhancedMap<string, Set<string>>();
|
||||
|
||||
// Sort spaces by room ID to force the cycle breaking to be deterministic
|
||||
const spaces = sortBy(visibleRooms.filter(r => r.isSpaceRoom()), space => space.roomId);
|
||||
|
||||
// TODO handle cleaning up links when a Space is removed
|
||||
spaces.forEach(space => {
|
||||
const children = this.getChildren(space.roomId);
|
||||
|
@ -216,7 +220,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
|
||||
const [rootSpaces, orphanedRooms] = partitionSpacesAndRooms(Array.from(unseenChildren));
|
||||
|
||||
// untested algorithm to handle full-cycles
|
||||
// somewhat algorithm to handle full-cycles
|
||||
const detachedNodes = new Set<Room>(spaces);
|
||||
|
||||
const markTreeChildren = (rootSpace: Room, unseen: Set<Room>) => {
|
||||
|
@ -365,6 +369,11 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
this.onRoomsUpdate();
|
||||
}
|
||||
|
||||
// if the user was looking at the room and then joined select that space
|
||||
if (room.getMyMembership() === "join" && room.roomId === RoomViewStore.getRoomId()) {
|
||||
this.setActiveSpace(room);
|
||||
}
|
||||
|
||||
const numSuggestedRooms = this._suggestedRooms.length;
|
||||
this._suggestedRooms = this._suggestedRooms.filter(r => r.room_id !== room.roomId);
|
||||
if (numSuggestedRooms !== this._suggestedRooms.length) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue