Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions src/components/views/rooms/RoomList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,9 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
}

public render() {
const cli = MatrixClientPeg.get();
const userId = cli.getUserId();

let explorePrompt: JSX.Element;
if (!this.props.isMinimized) {
if (this.state.isNameFiltering) {
Expand All @@ -568,21 +571,23 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
{ this.props.activeSpace ? _t("Explore rooms") : _t("Explore all public rooms") }
</AccessibleButton>
</div>;
} else if (this.props.activeSpace) {
} else if (
this.props.activeSpace?.canInvite(userId) || this.props.activeSpace?.getMyMembership() === "join"
) {
explorePrompt = <div className="mx_RoomList_explorePrompt">
<div>{ _t("Quick actions") }</div>
{ this.props.activeSpace.canInvite(MatrixClientPeg.get().getUserId()) && <AccessibleButton
{ this.props.activeSpace.canInvite(userId) && <AccessibleButton
className="mx_RoomList_explorePrompt_spaceInvite"
onClick={this.onSpaceInviteClick}
>
{_t("Invite people")}
</AccessibleButton> }
<AccessibleButton
{ this.props.activeSpace.getMyMembership() === "join" && <AccessibleButton
className="mx_RoomList_explorePrompt_spaceExplore"
onClick={this.onExplore}
>
{_t("Explore rooms")}
</AccessibleButton>
</AccessibleButton> }
</div>;
} else if (Object.values(this.state.sublists).some(list => list.length > 0)) {
const unfilteredLists = RoomListStore.instance.unfilteredLists
Expand Down
22 changes: 18 additions & 4 deletions src/components/views/spaces/SpacePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ import SpaceCreateMenu from "./SpaceCreateMenu";
import {SpaceItem} from "./SpaceTreeLevel";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import {useEventEmitter} from "../../../hooks/useEventEmitter";
import SpaceStore, {HOME_SPACE, UPDATE_SELECTED_SPACE, UPDATE_TOP_LEVEL_SPACES} from "../../../stores/SpaceStore";
import SpaceStore, {
HOME_SPACE,
UPDATE_INVITED_SPACES,
UPDATE_SELECTED_SPACE,
UPDATE_TOP_LEVEL_SPACES,
} from "../../../stores/SpaceStore";
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
import {SpaceNotificationState} from "../../../stores/notifications/SpaceNotificationState";
import NotificationBadge from "../rooms/NotificationBadge";
Expand Down Expand Up @@ -105,19 +110,21 @@ const SpaceButton: React.FC<IButtonProps> = ({
</li>;
}

const useSpaces = (): [Room[], Room | null] => {
const useSpaces = (): [Room[], Room[], Room | null] => {
const [invites, setInvites] = useState<Room[]>(SpaceStore.instance.invitedSpaces);
useEventEmitter(SpaceStore.instance, UPDATE_INVITED_SPACES, setInvites);
const [spaces, setSpaces] = useState<Room[]>(SpaceStore.instance.spacePanelSpaces);
useEventEmitter(SpaceStore.instance, UPDATE_TOP_LEVEL_SPACES, setSpaces);
const [activeSpace, setActiveSpace] = useState<Room>(SpaceStore.instance.activeSpace);
useEventEmitter(SpaceStore.instance, UPDATE_SELECTED_SPACE, setActiveSpace);
return [spaces, activeSpace];
return [invites, spaces, activeSpace];
};

const SpacePanel = () => {
// We don't need the handle as we position the menu in a constant location
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<void>();
const [spaces, activeSpace] = useSpaces();
const [invites, spaces, activeSpace] = useSpaces();
const [isPanelCollapsed, setPanelCollapsed] = useState(true);

const newClasses = classNames("mx_SpaceButton_new", {
Expand Down Expand Up @@ -209,6 +216,13 @@ const SpacePanel = () => {
notificationState={SpaceStore.instance.getNotificationState(HOME_SPACE)}
isNarrow={isPanelCollapsed}
/>
{ invites.map(s => <SpaceItem
key={s.roomId}
space={s}
activeSpaces={activeSpaces}
isPanelCollapsed={isPanelCollapsed}
onExpand={() => setPanelCollapsed(false)}
/>) }
{ spaces.map(s => <SpaceItem
key={s.roomId}
space={s}
Expand Down
11 changes: 9 additions & 2 deletions src/components/views/spaces/SpaceTreeLevel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ import RoomViewStore from "../../../stores/RoomViewStore";
import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPanelPhasePayload";
import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
import {EventType} from "matrix-js-sdk/src/@types/event";
import {StaticNotificationState} from "../../../stores/notifications/StaticNotificationState";
import {NotificationColor} from "../../../stores/notifications/NotificationColor";

interface IItemProps {
space?: Room;
Expand All @@ -67,7 +69,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
super(props);

this.state = {
collapsed: !props.isNested, // default to collapsed for root items
collapsed: !props.isNested, // default to collapsed for root items
contextMenuPosition: null,
};
}
Expand All @@ -83,6 +85,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
}

private onContextMenu = (ev: React.MouseEvent) => {
if (this.props.space.getMyMembership() !== "join") return;
ev.preventDefault();
ev.stopPropagation();
this.setState({
Expand Down Expand Up @@ -185,6 +188,8 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
};

private renderContextMenu(): React.ReactElement {
if (this.props.space.getMyMembership() !== "join") return null;

let contextMenu = null;
if (this.state.contextMenuPosition) {
const userId = this.context.getUserId();
Expand Down Expand Up @@ -300,7 +305,9 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
mx_SpaceButton_hasMenuOpen: !!this.state.contextMenuPosition,
mx_SpaceButton_narrow: isNarrow,
});
const notificationState = SpaceStore.instance.getNotificationState(space.roomId);
const notificationState = space.getMyMembership() === "invite"
? StaticNotificationState.forSymbol("!", NotificationColor.Red)
: SpaceStore.instance.getNotificationState(space.roomId);

let childItems;
if (childSpaces && !collapsed) {
Expand Down
54 changes: 39 additions & 15 deletions src/stores/SpaceStore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ 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_INVITED_SPACES = Symbol("invited-spaces");
export const UPDATE_SELECTED_SPACE = Symbol("selected-space");
// Space Room ID/HOME_SPACE will be emitted when a Space's children change

Expand Down Expand Up @@ -93,6 +94,11 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
// The space currently selected in the Space Panel - if null then `Home` is selected
private _activeSpace?: Room = null;
private _suggestedRooms: ISpaceSummaryRoom[] = [];
private _invitedSpaces = new Set<Room>();

public get invitedSpaces(): Room[] {
return Array.from(this._invitedSpaces);
}

public get spacePanelSpaces(): Room[] {
return this.rootSpaces;
Expand All @@ -117,7 +123,12 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
// view last selected room from space
const roomId = window.localStorage.getItem(getSpaceContextKey(this.activeSpace));

if (roomId && this.matrixClient?.getRoom(roomId)?.getMyMembership() === "join") {
// if the space being selected is an invite then always view that invite
// else if the last viewed room in this space is joined then view that
// else view space home or home depending on what is being clicked on
if (space?.getMyMembership !== "invite" &&
this.matrixClient?.getRoom(roomId)?.getMyMembership() === "join"
) {
defaultDispatcher.dispatch({
action: "view_room",
room_id: roomId,
Expand Down Expand Up @@ -214,25 +225,27 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
return sortBy(parents, r => r.roomId)?.[0] || null;
}

public getSpaces = () => {
return this.matrixClient.getRooms().filter(r => r.isSpaceRoom() && r.getMyMembership() === "join");
};

public getSpaceFilteredRoomIds = (space: Room | null): Set<string> => {
return this.spaceFilteredRooms.get(space?.roomId || HOME_SPACE) || new Set();
};

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 [visibleSpaces, visibleRooms] = partitionSpacesAndRooms(this.matrixClient.getVisibleRooms());
const [joinedSpaces, invitedSpaces] = visibleSpaces.reduce((arr, s) => {
if (s.getMyMembership() === "join") {
arr[0].push(s);
} else if (s.getMyMembership() === "invite") {
arr[1].push(s);
}
return arr;
}, [[], []]);

const unseenChildren = new Set<Room>(visibleRooms);
// exclude invited spaces from unseenChildren as they will be forcibly shown at the top level of the treeview
const unseenChildren = new Set<Room>([...visibleRooms, ...joinedSpaces]);
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);
const spaces = sortBy(joinedSpaces, space => space.roomId);

// TODO handle cleaning up links when a Space is removed
spaces.forEach(space => {
Expand Down Expand Up @@ -296,13 +309,18 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {

this.onRoomsUpdate(); // TODO only do this if a change has happened
this.emit(UPDATE_TOP_LEVEL_SPACES, this.spacePanelSpaces);

// build initial state of invited spaces as we would have missed the emitted events about the room at launch
this._invitedSpaces = new Set(invitedSpaces);
this.emit(UPDATE_INVITED_SPACES, this.invitedSpaces);
}, 100, {trailing: true, leading: true});

onSpaceUpdate = () => {
this.rebuild();
}

private showInHomeSpace = (room: Room) => {
if (room.isSpaceRoom()) return false;
return !this.parentMap.get(room.roomId)?.size // put all orphaned rooms in the Home Space
|| DMRoomMap.shared().getUserIdForRoomId(room.roomId) // put all DMs in the Home Space
|| RoomListStore.instance.getTagsForRoom(room).includes(DefaultTagID.Favourite) // show all favourites
Expand Down Expand Up @@ -333,8 +351,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
const oldFilteredRooms = this.spaceFilteredRooms;
this.spaceFilteredRooms = new Map();

// put all invites (rooms & spaces) in the Home Space
const invites = this.matrixClient.getRooms().filter(r => r.getMyMembership() === "invite");
// put all room invites in the Home Space
const invites = visibleRooms.filter(r => !r.isSpaceRoom() && r.getMyMembership() === "invite");
this.spaceFilteredRooms.set(HOME_SPACE, new Set<string>(invites.map(room => room.roomId)));

visibleRooms.forEach(room => {
Expand Down Expand Up @@ -392,8 +410,14 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
});
}, 100, {trailing: true, leading: true});

private onRoom = (room: Room) => {
if (room?.isSpaceRoom()) {
private onRoom = (room: Room, membership?: string, oldMembership?: string) => {
if ((membership || room.getMyMembership()) === "invite") {
this._invitedSpaces.add(room);
this.emit(UPDATE_INVITED_SPACES, this.invitedSpaces);
} else if (oldMembership === "invite") {
this._invitedSpaces.delete(room);
this.emit(UPDATE_INVITED_SPACES, this.invitedSpaces);
} else if (room?.isSpaceRoom()) {
this.onSpaceUpdate();
this.emit(room.roomId);
} else {
Expand Down
6 changes: 1 addition & 5 deletions src/stores/room-list/RoomListStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -599,11 +599,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
private getPlausibleRooms(): Room[] {
if (!this.matrixClient) return [];

let rooms = [
...this.matrixClient.getVisibleRooms(),
// also show space invites in the room list
...this.matrixClient.getRooms().filter(r => r.isSpaceRoom() && r.getMyMembership() === "invite"),
].filter(r => VisibilityProvider.instance.isRoomVisible(r));
let rooms = this.matrixClient.getVisibleRooms().filter(r => VisibilityProvider.instance.isRoomVisible(r));

if (this.prefilterConditions.length > 0) {
rooms = rooms.filter(r => {
Expand Down