Implement more meta-spaces (#7077)

This commit is contained in:
Michael Telatynski 2021-11-11 13:07:41 +00:00 committed by GitHub
parent dadac386fe
commit 5ad3261cb2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
55 changed files with 970 additions and 353 deletions

View file

@ -119,6 +119,7 @@ export const SpaceFeedbackPrompt = ({ onClick }: { onClick?: () => void }) => {
rageshakeLabel: "spaces-feedback",
rageshakeData: Object.fromEntries([
"Spaces.allRoomsInHome",
"Spaces.enabledMetaSpaces",
].map(k => [k, SettingsStore.getValue(k)])),
});
}}

View file

@ -34,13 +34,15 @@ import SpaceCreateMenu from "./SpaceCreateMenu";
import { SpaceButton, SpaceItem } from "./SpaceTreeLevel";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
import SpaceStore, {
HOME_SPACE,
import SpaceStore from "../../../stores/spaces/SpaceStore";
import {
MetaSpace,
SpaceKey,
UPDATE_HOME_BEHAVIOUR,
UPDATE_INVITED_SPACES,
UPDATE_SELECTED_SPACE,
UPDATE_TOP_LEVEL_SPACES,
} from "../../../stores/SpaceStore";
} from "../../../stores/spaces";
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
import { RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex";
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
@ -53,17 +55,21 @@ import SettingsStore from "../../../settings/SettingsStore";
import { SettingLevel } from "../../../settings/SettingLevel";
import UIStore from "../../../stores/UIStore";
const useSpaces = (): [Room[], Room[], Room | null] => {
const useSpaces = (): [Room[], MetaSpace[], Room[], SpaceKey] => {
const invites = useEventEmitterState<Room[]>(SpaceStore.instance, UPDATE_INVITED_SPACES, () => {
return SpaceStore.instance.invitedSpaces;
});
const spaces = useEventEmitterState<Room[]>(SpaceStore.instance, UPDATE_TOP_LEVEL_SPACES, () => {
return SpaceStore.instance.spacePanelSpaces;
});
const activeSpace = useEventEmitterState<Room>(SpaceStore.instance, UPDATE_SELECTED_SPACE, () => {
const [metaSpaces, actualSpaces] = useEventEmitterState<[MetaSpace[], Room[]]>(
SpaceStore.instance, UPDATE_TOP_LEVEL_SPACES,
() => [
SpaceStore.instance.enabledMetaSpaces,
SpaceStore.instance.spacePanelSpaces,
],
);
const activeSpace = useEventEmitterState<SpaceKey>(SpaceStore.instance, UPDATE_SELECTED_SPACE, () => {
return SpaceStore.instance.activeSpace;
});
return [invites, spaces, activeSpace];
return [invites, metaSpaces, actualSpaces, activeSpace];
};
interface IInnerSpacePanelProps {
@ -99,37 +105,76 @@ const HomeButtonContextMenu = ({ onFinished, ...props }: ComponentProps<typeof S
</IconizedContextMenu>;
};
interface IHomeButtonProps {
interface IMetaSpaceButtonProps extends ComponentProps<typeof SpaceButton> {
selected: boolean;
isPanelCollapsed: boolean;
}
const HomeButton = ({ selected, isPanelCollapsed }: IHomeButtonProps) => {
const allRoomsInHome = useEventEmitterState(SpaceStore.instance, UPDATE_HOME_BEHAVIOUR, () => {
return SpaceStore.instance.allRoomsInHome;
});
type MetaSpaceButtonProps = Pick<IMetaSpaceButtonProps, "selected" | "isPanelCollapsed">;
const MetaSpaceButton = ({ selected, isPanelCollapsed, ...props }: IMetaSpaceButtonProps) => {
return <li
className={classNames("mx_SpaceItem", {
"collapsed": isPanelCollapsed,
})}
role="treeitem"
>
<SpaceButton
className="mx_SpaceButton_home"
onClick={() => SpaceStore.instance.setActiveSpace(null)}
selected={selected}
label={allRoomsInHome ? _t("All rooms") : _t("Home")}
notificationState={allRoomsInHome
? RoomNotificationStateStore.instance.globalState
: SpaceStore.instance.getNotificationState(HOME_SPACE)}
isNarrow={isPanelCollapsed}
ContextMenuComponent={HomeButtonContextMenu}
contextMenuTooltip={_t("Options")}
/>
<SpaceButton {...props} selected={selected} isNarrow={isPanelCollapsed} />
</li>;
};
const HomeButton = ({ selected, isPanelCollapsed }: MetaSpaceButtonProps) => {
const allRoomsInHome = useEventEmitterState(SpaceStore.instance, UPDATE_HOME_BEHAVIOUR, () => {
return SpaceStore.instance.allRoomsInHome;
});
return <MetaSpaceButton
spaceKey={MetaSpace.Home}
className="mx_SpaceButton_home"
selected={selected}
isPanelCollapsed={isPanelCollapsed}
label={allRoomsInHome ? _t("All rooms") : _t("Home")}
notificationState={allRoomsInHome
? RoomNotificationStateStore.instance.globalState
: SpaceStore.instance.getNotificationState(MetaSpace.Home)}
ContextMenuComponent={HomeButtonContextMenu}
contextMenuTooltip={_t("Options")}
/>;
};
const FavouritesButton = ({ selected, isPanelCollapsed }: MetaSpaceButtonProps) => {
return <MetaSpaceButton
spaceKey={MetaSpace.Favourites}
className="mx_SpaceButton_favourites"
selected={selected}
isPanelCollapsed={isPanelCollapsed}
label={_t("Favourites")}
notificationState={SpaceStore.instance.getNotificationState(MetaSpace.Favourites)}
/>;
};
const PeopleButton = ({ selected, isPanelCollapsed }: MetaSpaceButtonProps) => {
return <MetaSpaceButton
spaceKey={MetaSpace.People}
className="mx_SpaceButton_people"
selected={selected}
isPanelCollapsed={isPanelCollapsed}
label={_t("People")}
notificationState={SpaceStore.instance.getNotificationState(MetaSpace.People)}
/>;
};
const OrphansButton = ({ selected, isPanelCollapsed }: MetaSpaceButtonProps) => {
return <MetaSpaceButton
spaceKey={MetaSpace.Orphans}
className="mx_SpaceButton_orphans"
selected={selected}
isPanelCollapsed={isPanelCollapsed}
label={_t("Other rooms")}
notificationState={SpaceStore.instance.getNotificationState(MetaSpace.Orphans)}
/>;
};
const CreateSpaceButton = ({
isPanelCollapsed,
setPanelCollapsed,
@ -181,13 +226,25 @@ const CreateSpaceButton = ({
</li>;
};
const metaSpaceComponentMap: Record<MetaSpace, typeof HomeButton> = {
[MetaSpace.Home]: HomeButton,
[MetaSpace.Favourites]: FavouritesButton,
[MetaSpace.People]: PeopleButton,
[MetaSpace.Orphans]: OrphansButton,
};
// Optimisation based on https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/api/droppable.md#recommended-droppable--performance-optimisation
const InnerSpacePanel = React.memo<IInnerSpacePanelProps>(({ children, isPanelCollapsed, setPanelCollapsed }) => {
const [invites, spaces, activeSpace] = useSpaces();
const [invites, metaSpaces, actualSpaces, activeSpace] = useSpaces();
const activeSpaces = activeSpace ? [activeSpace] : [];
const metaSpacesSection = metaSpaces.map(key => {
const Component = metaSpaceComponentMap[key];
return <Component key={key} selected={activeSpace === key} isPanelCollapsed={isPanelCollapsed} />;
});
return <div className="mx_SpaceTreeLevel">
<HomeButton selected={!activeSpace} isPanelCollapsed={isPanelCollapsed} />
{ metaSpacesSection }
{ invites.map(s => (
<SpaceItem
key={s.roomId}
@ -197,7 +254,7 @@ const InnerSpacePanel = React.memo<IInnerSpacePanelProps>(({ children, isPanelCo
onExpand={() => setPanelCollapsed(false)}
/>
)) }
{ spaces.map((s, i) => (
{ actualSpaces.map((s, i) => (
<Draggable key={s.roomId} draggableId={s.roomId} index={i}>
{ (provided, snapshot) => (
<SpaceItem

View file

@ -16,7 +16,6 @@ limitations under the License.
import React, {
createRef,
MouseEvent,
InputHTMLAttributes,
LegacyRef,
ComponentProps,
@ -26,14 +25,15 @@ import classNames from "classnames";
import { Room } from "matrix-js-sdk/src/models/room";
import RoomAvatar from "../avatars/RoomAvatar";
import SpaceStore from "../../../stores/SpaceStore";
import SpaceTreeLevelLayoutStore from "../../../stores/SpaceTreeLevelLayoutStore";
import SpaceStore from "../../../stores/spaces/SpaceStore";
import { SpaceKey } from "../../../stores/spaces";
import SpaceTreeLevelLayoutStore from "../../../stores/spaces/SpaceTreeLevelLayoutStore";
import NotificationBadge from "../rooms/NotificationBadge";
import { _t } from "../../../languageHandler";
import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton";
import { toRightOf, useContextMenu } from "../../structures/ContextMenu";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import AccessibleButton from "../elements/AccessibleButton";
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
import { NotificationColor } from "../../../stores/notifications/NotificationColor";
import { getKeyBindingsManager, RoomListAction } from "../../../KeyBindingsManager";
@ -43,8 +43,9 @@ import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { DraggableProvidedDragHandleProps } from "react-beautiful-dnd";
import { useRovingTabIndex } from "../../../accessibility/RovingTabIndex";
interface IButtonProps extends Omit<ComponentProps<typeof AccessibleTooltipButton>, "title"> {
interface IButtonProps extends Omit<ComponentProps<typeof AccessibleTooltipButton>, "title" | "onClick"> {
space?: Room;
spaceKey?: SpaceKey;
className?: string;
selected?: boolean;
label: string;
@ -53,14 +54,14 @@ interface IButtonProps extends Omit<ComponentProps<typeof AccessibleTooltipButto
isNarrow?: boolean;
avatarSize?: number;
ContextMenuComponent?: ComponentType<ComponentProps<typeof SpaceContextMenu>>;
onClick(ev: MouseEvent): void;
onClick?(ev?: ButtonEvent): void;
}
export const SpaceButton: React.FC<IButtonProps> = ({
space,
spaceKey,
className,
selected,
onClick,
label,
contextMenuTooltip,
notificationState,
@ -88,7 +89,7 @@ export const SpaceButton: React.FC<IButtonProps> = ({
notifBadge = <div className="mx_SpacePanel_badgeContainer">
<NotificationBadge
onClick={() => SpaceStore.instance.setActiveRoomInSpace(space || null)}
onClick={() => SpaceStore.instance.setActiveRoomInSpace(spaceKey ?? space.roomId)}
forceCount={false}
notification={notificationState}
aria-label={ariaLabel}
@ -116,7 +117,7 @@ export const SpaceButton: React.FC<IButtonProps> = ({
mx_SpaceButton_narrow: isNarrow,
})}
title={label}
onClick={onClick}
onClick={spaceKey ? () => SpaceStore.instance.setActiveSpace(spaceKey) : props.onClick}
onContextMenu={openMenu}
forceHide={!isNarrow || menuDisplayed}
inputRef={handle}
@ -146,7 +147,7 @@ export const SpaceButton: React.FC<IButtonProps> = ({
interface IItemProps extends InputHTMLAttributes<HTMLLIElement> {
space?: Room;
activeSpaces: Room[];
activeSpaces: SpaceKey[];
isNested?: boolean;
isPanelCollapsed?: boolean;
onExpand?: Function;
@ -258,7 +259,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
private onClick = (ev: React.MouseEvent) => {
ev.preventDefault();
ev.stopPropagation();
SpaceStore.instance.setActiveSpace(this.props.space);
SpaceStore.instance.setActiveSpace(this.props.space.roomId);
};
render() {
@ -316,7 +317,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
{...restDragHandleProps}
space={space}
className={isInvite ? "mx_SpaceButton_invite" : undefined}
selected={activeSpaces.includes(space)}
selected={activeSpaces.includes(space.roomId)}
label={space.name}
contextMenuTooltip={_t("Space options")}
notificationState={notificationState}
@ -337,7 +338,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
interface ITreeLevelProps {
spaces: Room[];
activeSpaces: Room[];
activeSpaces: SpaceKey[];
isNested?: boolean;
parents: Set<string>;
}