Refactor SpaceButton to be more reusable and add context menu to Home button
parent
b3a28bde89
commit
67ef263940
|
@ -297,7 +297,7 @@ $activeBorderColor: $secondary-fg-color;
|
||||||
.mx_SpaceButton:hover,
|
.mx_SpaceButton:hover,
|
||||||
.mx_SpaceButton:focus-within,
|
.mx_SpaceButton:focus-within,
|
||||||
.mx_SpaceButton_hasMenuOpen {
|
.mx_SpaceButton_hasMenuOpen {
|
||||||
&:not(.mx_SpaceButton_home):not(.mx_SpaceButton_invite) {
|
&:not(.mx_SpaceButton_invite) {
|
||||||
// Hide the badge container on hover because it'll be a menu button
|
// Hide the badge container on hover because it'll be a menu button
|
||||||
.mx_SpacePanel_badgeContainer {
|
.mx_SpacePanel_badgeContainer {
|
||||||
width: 0;
|
width: 0;
|
||||||
|
|
|
@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { CSSProperties, RefObject, useRef, useState } from "react";
|
import React, { CSSProperties, RefObject, SyntheticEvent, useRef, useState } from "react";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
|
@ -461,10 +461,14 @@ type ContextMenuTuple<T> = [boolean, RefObject<T>, () => void, () => void, (val:
|
||||||
export const useContextMenu = <T extends any = HTMLElement>(): ContextMenuTuple<T> => {
|
export const useContextMenu = <T extends any = HTMLElement>(): ContextMenuTuple<T> => {
|
||||||
const button = useRef<T>(null);
|
const button = useRef<T>(null);
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const open = () => {
|
const open = (ev?: SyntheticEvent) => {
|
||||||
|
ev?.preventDefault();
|
||||||
|
ev?.stopPropagation();
|
||||||
setIsOpen(true);
|
setIsOpen(true);
|
||||||
};
|
};
|
||||||
const close = () => {
|
const close = (ev?: SyntheticEvent) => {
|
||||||
|
ev?.preventDefault();
|
||||||
|
ev?.stopPropagation();
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,201 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useContext } from "react";
|
||||||
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||||
|
|
||||||
|
import {
|
||||||
|
IProps as IContextMenuProps,
|
||||||
|
} from "../../structures/ContextMenu";
|
||||||
|
import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from "./IconizedContextMenu";
|
||||||
|
import { _t } from "../../../languageHandler";
|
||||||
|
import {
|
||||||
|
shouldShowSpaceSettings,
|
||||||
|
showAddExistingRooms,
|
||||||
|
showCreateNewRoom,
|
||||||
|
showSpaceInvite,
|
||||||
|
showSpaceSettings,
|
||||||
|
} from "../../../utils/space";
|
||||||
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
import { ButtonEvent } from "../elements/AccessibleButton";
|
||||||
|
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||||
|
import RoomViewStore from "../../../stores/RoomViewStore";
|
||||||
|
import { SetRightPanelPhasePayload } from "../../../dispatcher/payloads/SetRightPanelPhasePayload";
|
||||||
|
import { Action } from "../../../dispatcher/actions";
|
||||||
|
import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
|
||||||
|
|
||||||
|
interface IProps extends IContextMenuProps {
|
||||||
|
space: Room;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SpaceContextMenu = ({ space, onFinished, ...props }: IProps) => {
|
||||||
|
const cli = useContext(MatrixClientContext);
|
||||||
|
const userId = cli.getUserId();
|
||||||
|
|
||||||
|
let inviteOption;
|
||||||
|
if (space.getJoinRule() === "public" || space.canInvite(userId)) {
|
||||||
|
const onInviteClick = (ev: ButtonEvent) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
showSpaceInvite(space);
|
||||||
|
onFinished();
|
||||||
|
};
|
||||||
|
|
||||||
|
inviteOption = (
|
||||||
|
<IconizedContextMenuOption
|
||||||
|
className="mx_SpacePanel_contextMenu_inviteButton"
|
||||||
|
iconClassName="mx_SpacePanel_iconInvite"
|
||||||
|
label={_t("Invite people")}
|
||||||
|
onClick={onInviteClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let settingsOption;
|
||||||
|
let leaveSection;
|
||||||
|
if (shouldShowSpaceSettings(space)) {
|
||||||
|
const onSettingsClick = (ev: ButtonEvent) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
showSpaceSettings(space);
|
||||||
|
onFinished();
|
||||||
|
};
|
||||||
|
|
||||||
|
settingsOption = (
|
||||||
|
<IconizedContextMenuOption
|
||||||
|
iconClassName="mx_SpacePanel_iconSettings"
|
||||||
|
label={_t("Settings")}
|
||||||
|
onClick={onSettingsClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const onLeaveClick = (ev: ButtonEvent) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
defaultDispatcher.dispatch({
|
||||||
|
action: "leave_room",
|
||||||
|
room_id: space.roomId,
|
||||||
|
});
|
||||||
|
onFinished();
|
||||||
|
};
|
||||||
|
|
||||||
|
leaveSection = <IconizedContextMenuOptionList red first>
|
||||||
|
<IconizedContextMenuOption
|
||||||
|
iconClassName="mx_SpacePanel_iconLeave"
|
||||||
|
label={_t("Leave space")}
|
||||||
|
onClick={onLeaveClick}
|
||||||
|
/>
|
||||||
|
</IconizedContextMenuOptionList>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const canAddRooms = space.currentState.maySendStateEvent(EventType.SpaceChild, userId);
|
||||||
|
|
||||||
|
let newRoomSection;
|
||||||
|
if (space.currentState.maySendStateEvent(EventType.SpaceChild, userId)) {
|
||||||
|
const onNewRoomClick = (ev: ButtonEvent) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
showCreateNewRoom(space);
|
||||||
|
onFinished();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onAddExistingRoomClick = (ev: ButtonEvent) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
showAddExistingRooms(space);
|
||||||
|
onFinished();
|
||||||
|
};
|
||||||
|
|
||||||
|
newRoomSection = <IconizedContextMenuOptionList first>
|
||||||
|
<IconizedContextMenuOption
|
||||||
|
iconClassName="mx_SpacePanel_iconPlus"
|
||||||
|
label={_t("Create new room")}
|
||||||
|
onClick={onNewRoomClick}
|
||||||
|
/>
|
||||||
|
<IconizedContextMenuOption
|
||||||
|
iconClassName="mx_SpacePanel_iconHash"
|
||||||
|
label={_t("Add existing room")}
|
||||||
|
onClick={onAddExistingRoomClick}
|
||||||
|
/>
|
||||||
|
</IconizedContextMenuOptionList>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const onMembersClick = (ev: ButtonEvent) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
if (!RoomViewStore.getRoomId()) {
|
||||||
|
defaultDispatcher.dispatch({
|
||||||
|
action: "view_room",
|
||||||
|
room_id: space.roomId,
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
|
||||||
|
action: Action.SetRightPanelPhase,
|
||||||
|
phase: RightPanelPhases.SpaceMemberList,
|
||||||
|
refireParams: { space: space },
|
||||||
|
});
|
||||||
|
onFinished();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onExploreRoomsClick = (ev: ButtonEvent) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
defaultDispatcher.dispatch({
|
||||||
|
action: "view_room",
|
||||||
|
room_id: space.roomId,
|
||||||
|
});
|
||||||
|
onFinished();
|
||||||
|
};
|
||||||
|
|
||||||
|
return <IconizedContextMenu
|
||||||
|
{...props}
|
||||||
|
onFinished={onFinished}
|
||||||
|
className="mx_SpacePanel_contextMenu"
|
||||||
|
compact
|
||||||
|
>
|
||||||
|
<div className="mx_SpacePanel_contextMenu_header">
|
||||||
|
{ space.name }
|
||||||
|
</div>
|
||||||
|
<IconizedContextMenuOptionList first>
|
||||||
|
{ inviteOption }
|
||||||
|
<IconizedContextMenuOption
|
||||||
|
iconClassName="mx_SpacePanel_iconMembers"
|
||||||
|
label={_t("Members")}
|
||||||
|
onClick={onMembersClick}
|
||||||
|
/>
|
||||||
|
{ settingsOption }
|
||||||
|
<IconizedContextMenuOption
|
||||||
|
iconClassName="mx_SpacePanel_iconExplore"
|
||||||
|
label={canAddRooms ? _t("Manage & explore rooms") : _t("Explore rooms")}
|
||||||
|
onClick={onExploreRoomsClick}
|
||||||
|
/>
|
||||||
|
</IconizedContextMenuOptionList>
|
||||||
|
{ newRoomSection }
|
||||||
|
{ leaveSection }
|
||||||
|
</IconizedContextMenu>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SpaceContextMenu;
|
||||||
|
|
|
@ -14,107 +14,35 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { Dispatch, ReactNode, SetStateAction, useEffect, useState } from "react";
|
import React, { ComponentProps, Dispatch, ReactNode, SetStateAction, useEffect, useState } from "react";
|
||||||
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
|
import { DragDropContext, Draggable, Droppable } from "react-beautiful-dnd";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import RoomAvatar from "../avatars/RoomAvatar";
|
|
||||||
import { useContextMenu } from "../../structures/ContextMenu";
|
import { useContextMenu } from "../../structures/ContextMenu";
|
||||||
import SpaceCreateMenu from "./SpaceCreateMenu";
|
import SpaceCreateMenu from "./SpaceCreateMenu";
|
||||||
import { SpaceItem } from "./SpaceTreeLevel";
|
import { SpaceButton, SpaceItem } from "./SpaceTreeLevel";
|
||||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||||
import { useEventEmitter } from "../../../hooks/useEventEmitter";
|
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
|
||||||
import SpaceStore, {
|
import SpaceStore, {
|
||||||
HOME_SPACE,
|
HOME_SPACE,
|
||||||
|
UPDATE_HOME_BEHAVIOUR,
|
||||||
UPDATE_INVITED_SPACES,
|
UPDATE_INVITED_SPACES,
|
||||||
UPDATE_SELECTED_SPACE,
|
UPDATE_SELECTED_SPACE,
|
||||||
UPDATE_TOP_LEVEL_SPACES,
|
UPDATE_TOP_LEVEL_SPACES,
|
||||||
} from "../../../stores/SpaceStore";
|
} from "../../../stores/SpaceStore";
|
||||||
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
||||||
import NotificationBadge from "../rooms/NotificationBadge";
|
import { RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex";
|
||||||
import {
|
|
||||||
RovingAccessibleButton,
|
|
||||||
RovingAccessibleTooltipButton,
|
|
||||||
RovingTabIndexProvider,
|
|
||||||
} from "../../../accessibility/RovingTabIndex";
|
|
||||||
import { Key } from "../../../Keyboard";
|
import { Key } from "../../../Keyboard";
|
||||||
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
|
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
|
||||||
import { NotificationState } from "../../../stores/notifications/NotificationState";
|
import SpaceContextMenu from "../context_menus/SpaceContextMenu";
|
||||||
|
import IconizedContextMenu, {
|
||||||
interface IButtonProps {
|
IconizedContextMenuCheckbox,
|
||||||
space?: Room;
|
IconizedContextMenuOptionList,
|
||||||
className?: string;
|
} from "../context_menus/IconizedContextMenu";
|
||||||
selected?: boolean;
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
tooltip?: string;
|
import { SettingLevel } from "../../../settings/SettingLevel";
|
||||||
notificationState?: NotificationState;
|
|
||||||
isNarrow?: boolean;
|
|
||||||
onClick(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SpaceButton: React.FC<IButtonProps> = ({
|
|
||||||
space,
|
|
||||||
className,
|
|
||||||
selected,
|
|
||||||
onClick,
|
|
||||||
tooltip,
|
|
||||||
notificationState,
|
|
||||||
isNarrow,
|
|
||||||
children,
|
|
||||||
}) => {
|
|
||||||
const classes = classNames("mx_SpaceButton", className, {
|
|
||||||
mx_SpaceButton_active: selected,
|
|
||||||
mx_SpaceButton_narrow: isNarrow,
|
|
||||||
});
|
|
||||||
|
|
||||||
let avatar = <div className="mx_SpaceButton_avatarPlaceholder"><div className="mx_SpaceButton_icon" /></div>;
|
|
||||||
if (space) {
|
|
||||||
avatar = <RoomAvatar width={32} height={32} room={space} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
let notifBadge;
|
|
||||||
if (notificationState) {
|
|
||||||
notifBadge = <div className="mx_SpacePanel_badgeContainer">
|
|
||||||
<NotificationBadge
|
|
||||||
onClick={() => SpaceStore.instance.setActiveRoomInSpace(space)}
|
|
||||||
forceCount={false}
|
|
||||||
notification={notificationState}
|
|
||||||
/>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
let button;
|
|
||||||
if (isNarrow) {
|
|
||||||
button = (
|
|
||||||
<RovingAccessibleTooltipButton className={classes} title={tooltip} onClick={onClick} role="treeitem">
|
|
||||||
<div className="mx_SpaceButton_selectionWrapper">
|
|
||||||
{ avatar }
|
|
||||||
{ notifBadge }
|
|
||||||
{ children }
|
|
||||||
</div>
|
|
||||||
</RovingAccessibleTooltipButton>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
button = (
|
|
||||||
<RovingAccessibleButton className={classes} onClick={onClick} role="treeitem">
|
|
||||||
<div className="mx_SpaceButton_selectionWrapper">
|
|
||||||
{ avatar }
|
|
||||||
<span className="mx_SpaceButton_name">{ tooltip }</span>
|
|
||||||
{ notifBadge }
|
|
||||||
{ children }
|
|
||||||
</div>
|
|
||||||
</RovingAccessibleButton>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <li className={classNames({
|
|
||||||
"mx_SpaceItem": true,
|
|
||||||
"collapsed": isNarrow,
|
|
||||||
})}>
|
|
||||||
{ button }
|
|
||||||
</li>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const useSpaces = (): [Room[], Room[], Room | null] => {
|
const useSpaces = (): [Room[], Room[], Room | null] => {
|
||||||
const invites = useEventEmitterState<Room[]>(SpaceStore.instance, UPDATE_INVITED_SPACES, () => {
|
const invites = useEventEmitterState<Room[]>(SpaceStore.instance, UPDATE_INVITED_SPACES, () => {
|
||||||
|
@ -135,30 +63,108 @@ interface IInnerSpacePanelProps {
|
||||||
setPanelCollapsed: Dispatch<SetStateAction<boolean>>;
|
setPanelCollapsed: Dispatch<SetStateAction<boolean>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const HomeButtonContextMenu = ({ onFinished, ...props }: ComponentProps<typeof SpaceContextMenu>) => {
|
||||||
|
const allRoomsInHome = useEventEmitterState(SpaceStore.instance, UPDATE_HOME_BEHAVIOUR, () => {
|
||||||
|
return SpaceStore.instance.allRoomsInHome;
|
||||||
|
});
|
||||||
|
|
||||||
|
return <IconizedContextMenu
|
||||||
|
{...props}
|
||||||
|
onFinished={onFinished}
|
||||||
|
className="mx_SpacePanel_contextMenu"
|
||||||
|
compact
|
||||||
|
>
|
||||||
|
<div className="mx_SpacePanel_contextMenu_header">
|
||||||
|
{ _t("Home") }
|
||||||
|
</div>
|
||||||
|
<IconizedContextMenuOptionList first>
|
||||||
|
<IconizedContextMenuCheckbox
|
||||||
|
iconClassName="mx_SpacePanel_iconSettings"
|
||||||
|
label={_t("Show all rooms in home")}
|
||||||
|
active={allRoomsInHome}
|
||||||
|
onClick={() => {
|
||||||
|
SettingsStore.setValue("Spaces.all_rooms_in_home", null, SettingLevel.ACCOUNT, !allRoomsInHome);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</IconizedContextMenuOptionList>
|
||||||
|
</IconizedContextMenu>;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IHomeButtonProps {
|
||||||
|
selected: boolean;
|
||||||
|
isPanelCollapsed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const HomeButton = ({ selected, isPanelCollapsed }: IHomeButtonProps) => {
|
||||||
|
const allRoomsInHome = useEventEmitterState(SpaceStore.instance, UPDATE_HOME_BEHAVIOUR, () => {
|
||||||
|
return SpaceStore.instance.allRoomsInHome;
|
||||||
|
});
|
||||||
|
|
||||||
|
return <li className={classNames("mx_SpaceItem", {
|
||||||
|
"collapsed": isPanelCollapsed,
|
||||||
|
})}>
|
||||||
|
<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")}
|
||||||
|
/>
|
||||||
|
</li>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CreateSpaceButton = ({
|
||||||
|
isPanelCollapsed,
|
||||||
|
setPanelCollapsed,
|
||||||
|
}: Pick<IInnerSpacePanelProps, "isPanelCollapsed" | "setPanelCollapsed">) => {
|
||||||
|
// 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>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isPanelCollapsed && menuDisplayed) {
|
||||||
|
closeMenu();
|
||||||
|
}
|
||||||
|
}, [isPanelCollapsed]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
let contextMenu = null;
|
||||||
|
if (menuDisplayed) {
|
||||||
|
contextMenu = <SpaceCreateMenu onFinished={closeMenu} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const onNewClick = menuDisplayed ? closeMenu : () => {
|
||||||
|
if (!isPanelCollapsed) setPanelCollapsed(true);
|
||||||
|
openMenu();
|
||||||
|
};
|
||||||
|
|
||||||
|
return <li className={classNames("mx_SpaceItem", {
|
||||||
|
"collapsed": isPanelCollapsed,
|
||||||
|
})}>
|
||||||
|
<SpaceButton
|
||||||
|
className={classNames("mx_SpaceButton_new", {
|
||||||
|
mx_SpaceButton_newCancel: menuDisplayed,
|
||||||
|
})}
|
||||||
|
label={menuDisplayed ? _t("Cancel") : _t("Create a space")}
|
||||||
|
onClick={onNewClick}
|
||||||
|
isNarrow={isPanelCollapsed}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{ contextMenu }
|
||||||
|
</li>;
|
||||||
|
};
|
||||||
|
|
||||||
// Optimisation based on https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/api/droppable.md#recommended-droppable--performance-optimisation
|
// 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 InnerSpacePanel = React.memo<IInnerSpacePanelProps>(({ children, isPanelCollapsed, setPanelCollapsed }) => {
|
||||||
const [invites, spaces, activeSpace] = useSpaces();
|
const [invites, spaces, activeSpace] = useSpaces();
|
||||||
const activeSpaces = activeSpace ? [activeSpace] : [];
|
const activeSpaces = activeSpace ? [activeSpace] : [];
|
||||||
|
|
||||||
let homeTooltip: string;
|
|
||||||
let homeNotificationState: NotificationState;
|
|
||||||
if (SpaceStore.instance.allRoomsInHome) {
|
|
||||||
homeTooltip = _t("All rooms");
|
|
||||||
homeNotificationState = RoomNotificationStateStore.instance.globalState;
|
|
||||||
} else {
|
|
||||||
homeTooltip = _t("Home");
|
|
||||||
homeNotificationState = SpaceStore.instance.getNotificationState(HOME_SPACE);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div className="mx_SpaceTreeLevel">
|
return <div className="mx_SpaceTreeLevel">
|
||||||
<SpaceButton
|
<HomeButton selected={!activeSpace} isPanelCollapsed={isPanelCollapsed} />
|
||||||
className="mx_SpaceButton_home"
|
|
||||||
onClick={() => SpaceStore.instance.setActiveSpace(null)}
|
|
||||||
selected={!activeSpace}
|
|
||||||
tooltip={homeTooltip}
|
|
||||||
notificationState={homeNotificationState}
|
|
||||||
isNarrow={isPanelCollapsed}
|
|
||||||
/>
|
|
||||||
{ invites.map(s => (
|
{ invites.map(s => (
|
||||||
<SpaceItem
|
<SpaceItem
|
||||||
key={s.roomId}
|
key={s.roomId}
|
||||||
|
@ -188,26 +194,13 @@ const InnerSpacePanel = React.memo<IInnerSpacePanelProps>(({ children, isPanelCo
|
||||||
</Draggable>
|
</Draggable>
|
||||||
)) }
|
)) }
|
||||||
{ children }
|
{ children }
|
||||||
|
<CreateSpaceButton isPanelCollapsed={isPanelCollapsed} setPanelCollapsed={setPanelCollapsed} />
|
||||||
</div>;
|
</div>;
|
||||||
});
|
});
|
||||||
|
|
||||||
const SpacePanel = () => {
|
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 [isPanelCollapsed, setPanelCollapsed] = useState(true);
|
const [isPanelCollapsed, setPanelCollapsed] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isPanelCollapsed && menuDisplayed) {
|
|
||||||
closeMenu();
|
|
||||||
}
|
|
||||||
}, [isPanelCollapsed]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
||||||
|
|
||||||
let contextMenu = null;
|
|
||||||
if (menuDisplayed) {
|
|
||||||
contextMenu = <SpaceCreateMenu onFinished={closeMenu} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
const onKeyDown = (ev: React.KeyboardEvent) => {
|
const onKeyDown = (ev: React.KeyboardEvent) => {
|
||||||
let handled = true;
|
let handled = true;
|
||||||
|
|
||||||
|
@ -269,11 +262,6 @@ const SpacePanel = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onNewClick = menuDisplayed ? closeMenu : () => {
|
|
||||||
if (!isPanelCollapsed) setPanelCollapsed(true);
|
|
||||||
openMenu();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DragDropContext onDragEnd={result => {
|
<DragDropContext onDragEnd={result => {
|
||||||
if (!result.destination) return; // dropped outside the list
|
if (!result.destination) return; // dropped outside the list
|
||||||
|
@ -301,15 +289,6 @@ const SpacePanel = () => {
|
||||||
>
|
>
|
||||||
{ provided.placeholder }
|
{ provided.placeholder }
|
||||||
</InnerSpacePanel>
|
</InnerSpacePanel>
|
||||||
|
|
||||||
<SpaceButton
|
|
||||||
className={classNames("mx_SpaceButton_new", {
|
|
||||||
mx_SpaceButton_newCancel: menuDisplayed,
|
|
||||||
})}
|
|
||||||
tooltip={menuDisplayed ? _t("Cancel") : _t("Create a space")}
|
|
||||||
onClick={onNewClick}
|
|
||||||
isNarrow={isPanelCollapsed}
|
|
||||||
/>
|
|
||||||
</AutoHideScrollbar>
|
</AutoHideScrollbar>
|
||||||
) }
|
) }
|
||||||
</Droppable>
|
</Droppable>
|
||||||
|
@ -318,7 +297,6 @@ const SpacePanel = () => {
|
||||||
onClick={() => setPanelCollapsed(!isPanelCollapsed)}
|
onClick={() => setPanelCollapsed(!isPanelCollapsed)}
|
||||||
title={isPanelCollapsed ? _t("Expand space panel") : _t("Collapse space panel")}
|
title={isPanelCollapsed ? _t("Expand space panel") : _t("Collapse space panel")}
|
||||||
/>
|
/>
|
||||||
{ contextMenu }
|
|
||||||
</ul>
|
</ul>
|
||||||
) }
|
) }
|
||||||
</RovingTabIndexProvider>
|
</RovingTabIndexProvider>
|
||||||
|
|
|
@ -14,7 +14,14 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { createRef, InputHTMLAttributes, LegacyRef } from "react";
|
import React, {
|
||||||
|
createRef,
|
||||||
|
MouseEvent,
|
||||||
|
InputHTMLAttributes,
|
||||||
|
LegacyRef,
|
||||||
|
ComponentProps,
|
||||||
|
ComponentType,
|
||||||
|
} from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
|
||||||
|
@ -23,31 +30,104 @@ import SpaceStore from "../../../stores/SpaceStore";
|
||||||
import SpaceTreeLevelLayoutStore from "../../../stores/SpaceTreeLevelLayoutStore";
|
import SpaceTreeLevelLayoutStore from "../../../stores/SpaceTreeLevelLayoutStore";
|
||||||
import NotificationBadge from "../rooms/NotificationBadge";
|
import NotificationBadge from "../rooms/NotificationBadge";
|
||||||
import { RovingAccessibleTooltipButton } from "../../../accessibility/roving/RovingAccessibleTooltipButton";
|
import { RovingAccessibleTooltipButton } from "../../../accessibility/roving/RovingAccessibleTooltipButton";
|
||||||
import IconizedContextMenu, {
|
|
||||||
IconizedContextMenuOption,
|
|
||||||
IconizedContextMenuOptionList,
|
|
||||||
} from "../context_menus/IconizedContextMenu";
|
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton";
|
import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton";
|
||||||
import { toRightOf } from "../../structures/ContextMenu";
|
import { toRightOf, useContextMenu } from "../../structures/ContextMenu";
|
||||||
import {
|
|
||||||
shouldShowSpaceSettings,
|
|
||||||
showAddExistingRooms,
|
|
||||||
showCreateNewRoom,
|
|
||||||
showSpaceInvite,
|
|
||||||
showSpaceSettings,
|
|
||||||
} from "../../../utils/space";
|
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
|
||||||
import { Action } from "../../../dispatcher/actions";
|
|
||||||
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 { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
|
||||||
import { NotificationColor } from "../../../stores/notifications/NotificationColor";
|
import { NotificationColor } from "../../../stores/notifications/NotificationColor";
|
||||||
import { getKeyBindingsManager, RoomListAction } from "../../../KeyBindingsManager";
|
import { getKeyBindingsManager, RoomListAction } from "../../../KeyBindingsManager";
|
||||||
|
import { NotificationState } from "../../../stores/notifications/NotificationState";
|
||||||
|
import SpaceContextMenu from "../context_menus/SpaceContextMenu";
|
||||||
|
|
||||||
|
interface IButtonProps extends Omit<ComponentProps<typeof RovingAccessibleTooltipButton>, "title"> {
|
||||||
|
space?: Room;
|
||||||
|
className?: string;
|
||||||
|
selected?: boolean;
|
||||||
|
label: string;
|
||||||
|
contextMenuTooltip?: string;
|
||||||
|
notificationState?: NotificationState;
|
||||||
|
isNarrow?: boolean;
|
||||||
|
avatarSize?: number;
|
||||||
|
ContextMenuComponent?: ComponentType<ComponentProps<typeof SpaceContextMenu>>;
|
||||||
|
onClick(ev: MouseEvent): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SpaceButton: React.FC<IButtonProps> = ({
|
||||||
|
space,
|
||||||
|
className,
|
||||||
|
selected,
|
||||||
|
onClick,
|
||||||
|
label,
|
||||||
|
contextMenuTooltip,
|
||||||
|
notificationState,
|
||||||
|
avatarSize,
|
||||||
|
isNarrow,
|
||||||
|
children,
|
||||||
|
ContextMenuComponent,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<HTMLElement>();
|
||||||
|
|
||||||
|
let avatar = <div className="mx_SpaceButton_avatarPlaceholder"><div className="mx_SpaceButton_icon" /></div>;
|
||||||
|
if (space) {
|
||||||
|
avatar = <RoomAvatar width={avatarSize} height={avatarSize} room={space} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
let notifBadge;
|
||||||
|
if (notificationState) {
|
||||||
|
notifBadge = <div className="mx_SpacePanel_badgeContainer">
|
||||||
|
<NotificationBadge
|
||||||
|
onClick={() => SpaceStore.instance.setActiveRoomInSpace(space || null)}
|
||||||
|
forceCount={false}
|
||||||
|
notification={notificationState}
|
||||||
|
/>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
let contextMenu: JSX.Element;
|
||||||
|
if (menuDisplayed && ContextMenuComponent) {
|
||||||
|
contextMenu = <ContextMenuComponent
|
||||||
|
{...toRightOf(handle.current?.getBoundingClientRect(), 0)}
|
||||||
|
space={space}
|
||||||
|
onFinished={closeMenu}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RovingAccessibleTooltipButton
|
||||||
|
{...props}
|
||||||
|
className={classNames("mx_SpaceButton", className, {
|
||||||
|
mx_SpaceButton_active: selected,
|
||||||
|
mx_SpaceButton_hasMenuOpen: menuDisplayed,
|
||||||
|
mx_SpaceButton_narrow: isNarrow,
|
||||||
|
})}
|
||||||
|
title={label}
|
||||||
|
onClick={onClick}
|
||||||
|
onContextMenu={openMenu}
|
||||||
|
forceHide={!isNarrow || menuDisplayed}
|
||||||
|
role="treeitem"
|
||||||
|
inputRef={handle}
|
||||||
|
>
|
||||||
|
{ children }
|
||||||
|
<div className="mx_SpaceButton_selectionWrapper">
|
||||||
|
{ avatar }
|
||||||
|
{ !isNarrow && <span className="mx_SpaceButton_name">{ label }</span> }
|
||||||
|
{ notifBadge }
|
||||||
|
|
||||||
|
{ ContextMenuComponent && <ContextMenuTooltipButton
|
||||||
|
className="mx_SpaceButton_menuButton"
|
||||||
|
onClick={openMenu}
|
||||||
|
title={contextMenuTooltip}
|
||||||
|
isExpanded={menuDisplayed}
|
||||||
|
/> }
|
||||||
|
|
||||||
|
{ contextMenu }
|
||||||
|
</div>
|
||||||
|
</RovingAccessibleTooltipButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
interface IItemProps extends InputHTMLAttributes<HTMLLIElement> {
|
interface IItemProps extends InputHTMLAttributes<HTMLLIElement> {
|
||||||
space?: Room;
|
space?: Room;
|
||||||
|
@ -61,7 +141,6 @@ interface IItemProps extends InputHTMLAttributes<HTMLLIElement> {
|
||||||
|
|
||||||
interface IItemState {
|
interface IItemState {
|
||||||
collapsed: boolean;
|
collapsed: boolean;
|
||||||
contextMenuPosition: Pick<DOMRect, "right" | "top" | "height">;
|
|
||||||
childSpaces: Room[];
|
childSpaces: Room[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +160,6 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
collapsed: collapsed,
|
collapsed: collapsed,
|
||||||
contextMenuPosition: null,
|
|
||||||
childSpaces: this.childSpaces,
|
childSpaces: this.childSpaces,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -124,19 +202,6 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
};
|
};
|
||||||
|
|
||||||
private onContextMenu = (ev: React.MouseEvent) => {
|
|
||||||
if (this.props.space.getMyMembership() !== "join") return;
|
|
||||||
ev.preventDefault();
|
|
||||||
ev.stopPropagation();
|
|
||||||
this.setState({
|
|
||||||
contextMenuPosition: {
|
|
||||||
right: ev.clientX,
|
|
||||||
top: ev.clientY,
|
|
||||||
height: 0,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
private onKeyDown = (ev: React.KeyboardEvent) => {
|
private onKeyDown = (ev: React.KeyboardEvent) => {
|
||||||
let handled = true;
|
let handled = true;
|
||||||
const action = getKeyBindingsManager().getRoomListAction(ev);
|
const action = getKeyBindingsManager().getRoomListAction(ev);
|
||||||
|
@ -180,188 +245,6 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
|
||||||
SpaceStore.instance.setActiveSpace(this.props.space);
|
SpaceStore.instance.setActiveSpace(this.props.space);
|
||||||
};
|
};
|
||||||
|
|
||||||
private onMenuOpenClick = (ev: React.MouseEvent) => {
|
|
||||||
ev.preventDefault();
|
|
||||||
ev.stopPropagation();
|
|
||||||
const target = ev.target as HTMLButtonElement;
|
|
||||||
this.setState({ contextMenuPosition: target.getBoundingClientRect() });
|
|
||||||
};
|
|
||||||
|
|
||||||
private onMenuClose = () => {
|
|
||||||
this.setState({ contextMenuPosition: null });
|
|
||||||
};
|
|
||||||
|
|
||||||
private onInviteClick = (ev: ButtonEvent) => {
|
|
||||||
ev.preventDefault();
|
|
||||||
ev.stopPropagation();
|
|
||||||
|
|
||||||
showSpaceInvite(this.props.space);
|
|
||||||
this.setState({ contextMenuPosition: null }); // also close the menu
|
|
||||||
};
|
|
||||||
|
|
||||||
private onSettingsClick = (ev: ButtonEvent) => {
|
|
||||||
ev.preventDefault();
|
|
||||||
ev.stopPropagation();
|
|
||||||
|
|
||||||
showSpaceSettings(this.props.space);
|
|
||||||
this.setState({ contextMenuPosition: null }); // also close the menu
|
|
||||||
};
|
|
||||||
|
|
||||||
private onLeaveClick = (ev: ButtonEvent) => {
|
|
||||||
ev.preventDefault();
|
|
||||||
ev.stopPropagation();
|
|
||||||
|
|
||||||
defaultDispatcher.dispatch({
|
|
||||||
action: "leave_room",
|
|
||||||
room_id: this.props.space.roomId,
|
|
||||||
});
|
|
||||||
this.setState({ contextMenuPosition: null }); // also close the menu
|
|
||||||
};
|
|
||||||
|
|
||||||
private onNewRoomClick = (ev: ButtonEvent) => {
|
|
||||||
ev.preventDefault();
|
|
||||||
ev.stopPropagation();
|
|
||||||
|
|
||||||
showCreateNewRoom(this.props.space);
|
|
||||||
this.setState({ contextMenuPosition: null }); // also close the menu
|
|
||||||
};
|
|
||||||
|
|
||||||
private onAddExistingRoomClick = (ev: ButtonEvent) => {
|
|
||||||
ev.preventDefault();
|
|
||||||
ev.stopPropagation();
|
|
||||||
|
|
||||||
showAddExistingRooms(this.props.space);
|
|
||||||
this.setState({ contextMenuPosition: null }); // also close the menu
|
|
||||||
};
|
|
||||||
|
|
||||||
private onMembersClick = (ev: ButtonEvent) => {
|
|
||||||
ev.preventDefault();
|
|
||||||
ev.stopPropagation();
|
|
||||||
|
|
||||||
if (!RoomViewStore.getRoomId()) {
|
|
||||||
defaultDispatcher.dispatch({
|
|
||||||
action: "view_room",
|
|
||||||
room_id: this.props.space.roomId,
|
|
||||||
}, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
|
|
||||||
action: Action.SetRightPanelPhase,
|
|
||||||
phase: RightPanelPhases.SpaceMemberList,
|
|
||||||
refireParams: { space: this.props.space },
|
|
||||||
});
|
|
||||||
this.setState({ contextMenuPosition: null }); // also close the menu
|
|
||||||
};
|
|
||||||
|
|
||||||
private onExploreRoomsClick = (ev: ButtonEvent) => {
|
|
||||||
ev.preventDefault();
|
|
||||||
ev.stopPropagation();
|
|
||||||
|
|
||||||
defaultDispatcher.dispatch({
|
|
||||||
action: "view_room",
|
|
||||||
room_id: this.props.space.roomId,
|
|
||||||
});
|
|
||||||
this.setState({ contextMenuPosition: null }); // also close the menu
|
|
||||||
};
|
|
||||||
|
|
||||||
private renderContextMenu(): React.ReactElement {
|
|
||||||
if (this.props.space.getMyMembership() !== "join") return null;
|
|
||||||
|
|
||||||
let contextMenu = null;
|
|
||||||
if (this.state.contextMenuPosition) {
|
|
||||||
const userId = this.context.getUserId();
|
|
||||||
|
|
||||||
let inviteOption;
|
|
||||||
if (this.props.space.getJoinRule() === "public" || this.props.space.canInvite(userId)) {
|
|
||||||
inviteOption = (
|
|
||||||
<IconizedContextMenuOption
|
|
||||||
className="mx_SpacePanel_contextMenu_inviteButton"
|
|
||||||
iconClassName="mx_SpacePanel_iconInvite"
|
|
||||||
label={_t("Invite people")}
|
|
||||||
onClick={this.onInviteClick}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let settingsOption;
|
|
||||||
let leaveSection;
|
|
||||||
if (shouldShowSpaceSettings(this.props.space)) {
|
|
||||||
settingsOption = (
|
|
||||||
<IconizedContextMenuOption
|
|
||||||
iconClassName="mx_SpacePanel_iconSettings"
|
|
||||||
label={_t("Settings")}
|
|
||||||
onClick={this.onSettingsClick}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
leaveSection = <IconizedContextMenuOptionList red first>
|
|
||||||
<IconizedContextMenuOption
|
|
||||||
iconClassName="mx_SpacePanel_iconLeave"
|
|
||||||
label={_t("Leave space")}
|
|
||||||
onClick={this.onLeaveClick}
|
|
||||||
/>
|
|
||||||
</IconizedContextMenuOptionList>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const canAddRooms = this.props.space.currentState.maySendStateEvent(EventType.SpaceChild, userId);
|
|
||||||
|
|
||||||
let newRoomSection;
|
|
||||||
if (this.props.space.currentState.maySendStateEvent(EventType.SpaceChild, userId)) {
|
|
||||||
newRoomSection = <IconizedContextMenuOptionList first>
|
|
||||||
<IconizedContextMenuOption
|
|
||||||
iconClassName="mx_SpacePanel_iconPlus"
|
|
||||||
label={_t("Create new room")}
|
|
||||||
onClick={this.onNewRoomClick}
|
|
||||||
/>
|
|
||||||
<IconizedContextMenuOption
|
|
||||||
iconClassName="mx_SpacePanel_iconHash"
|
|
||||||
label={_t("Add existing room")}
|
|
||||||
onClick={this.onAddExistingRoomClick}
|
|
||||||
/>
|
|
||||||
</IconizedContextMenuOptionList>;
|
|
||||||
}
|
|
||||||
|
|
||||||
contextMenu = <IconizedContextMenu
|
|
||||||
{...toRightOf(this.state.contextMenuPosition, 0)}
|
|
||||||
onFinished={this.onMenuClose}
|
|
||||||
className="mx_SpacePanel_contextMenu"
|
|
||||||
compact
|
|
||||||
>
|
|
||||||
<div className="mx_SpacePanel_contextMenu_header">
|
|
||||||
{ this.props.space.name }
|
|
||||||
</div>
|
|
||||||
<IconizedContextMenuOptionList first>
|
|
||||||
{ inviteOption }
|
|
||||||
<IconizedContextMenuOption
|
|
||||||
iconClassName="mx_SpacePanel_iconMembers"
|
|
||||||
label={_t("Members")}
|
|
||||||
onClick={this.onMembersClick}
|
|
||||||
/>
|
|
||||||
{ settingsOption }
|
|
||||||
<IconizedContextMenuOption
|
|
||||||
iconClassName="mx_SpacePanel_iconExplore"
|
|
||||||
label={canAddRooms ? _t("Manage & explore rooms") : _t("Explore rooms")}
|
|
||||||
onClick={this.onExploreRoomsClick}
|
|
||||||
/>
|
|
||||||
</IconizedContextMenuOptionList>
|
|
||||||
{ newRoomSection }
|
|
||||||
{ leaveSection }
|
|
||||||
</IconizedContextMenu>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<ContextMenuTooltipButton
|
|
||||||
className="mx_SpaceButton_menuButton"
|
|
||||||
onClick={this.onMenuOpenClick}
|
|
||||||
title={_t("Space options")}
|
|
||||||
isExpanded={!!this.state.contextMenuPosition}
|
|
||||||
/>
|
|
||||||
{ contextMenu }
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const { space, activeSpaces, isNested, isPanelCollapsed, onExpand, parents, innerRef,
|
const { space, activeSpaces, isNested, isPanelCollapsed, onExpand, parents, innerRef,
|
||||||
|
@ -369,7 +252,6 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
|
||||||
|
|
||||||
const collapsed = this.isCollapsed;
|
const collapsed = this.isCollapsed;
|
||||||
|
|
||||||
const isActive = activeSpaces.includes(space);
|
|
||||||
const itemClasses = classNames(this.props.className, {
|
const itemClasses = classNames(this.props.className, {
|
||||||
"mx_SpaceItem": true,
|
"mx_SpaceItem": true,
|
||||||
"mx_SpaceItem_narrow": isPanelCollapsed,
|
"mx_SpaceItem_narrow": isPanelCollapsed,
|
||||||
|
@ -378,12 +260,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
|
||||||
});
|
});
|
||||||
|
|
||||||
const isInvite = space.getMyMembership() === "invite";
|
const isInvite = space.getMyMembership() === "invite";
|
||||||
const classes = classNames("mx_SpaceButton", {
|
|
||||||
mx_SpaceButton_active: isActive,
|
|
||||||
mx_SpaceButton_hasMenuOpen: !!this.state.contextMenuPosition,
|
|
||||||
mx_SpaceButton_narrow: isPanelCollapsed,
|
|
||||||
mx_SpaceButton_invite: isInvite,
|
|
||||||
});
|
|
||||||
const notificationState = isInvite
|
const notificationState = isInvite
|
||||||
? StaticNotificationState.forSymbol("!", NotificationColor.Red)
|
? StaticNotificationState.forSymbol("!", NotificationColor.Red)
|
||||||
: SpaceStore.instance.getNotificationState(space.roomId);
|
: SpaceStore.instance.getNotificationState(space.roomId);
|
||||||
|
@ -398,19 +275,6 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
let notifBadge;
|
|
||||||
if (notificationState) {
|
|
||||||
notifBadge = <div className="mx_SpacePanel_badgeContainer">
|
|
||||||
<NotificationBadge
|
|
||||||
onClick={() => SpaceStore.instance.setActiveRoomInSpace(space)}
|
|
||||||
forceCount={false}
|
|
||||||
notification={notificationState}
|
|
||||||
/>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const avatarSize = isNested ? 24 : 32;
|
|
||||||
|
|
||||||
const toggleCollapseButton = this.state.childSpaces?.length ?
|
const toggleCollapseButton = this.state.childSpaces?.length ?
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
className="mx_SpaceButton_toggleCollapse"
|
className="mx_SpaceButton_toggleCollapse"
|
||||||
|
@ -421,25 +285,23 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li {...otherProps} className={itemClasses} ref={innerRef}>
|
<li {...otherProps} className={itemClasses} ref={innerRef}>
|
||||||
<RovingAccessibleTooltipButton
|
<SpaceButton
|
||||||
className={classes}
|
space={space}
|
||||||
title={space.name}
|
className={isInvite ? "mx_SpaceButton_invite" : undefined}
|
||||||
|
selected={activeSpaces.includes(space)}
|
||||||
|
label={space.name}
|
||||||
|
contextMenuTooltip={_t("Space options")}
|
||||||
|
notificationState={notificationState}
|
||||||
|
isNarrow={isPanelCollapsed}
|
||||||
|
avatarSize={isNested ? 24 : 32}
|
||||||
onClick={this.onClick}
|
onClick={this.onClick}
|
||||||
onContextMenu={this.onContextMenu}
|
|
||||||
forceHide={!isPanelCollapsed || !!this.state.contextMenuPosition}
|
|
||||||
role="treeitem"
|
|
||||||
aria-expanded={!collapsed}
|
|
||||||
inputRef={this.buttonRef}
|
|
||||||
onKeyDown={this.onKeyDown}
|
onKeyDown={this.onKeyDown}
|
||||||
|
aria-expanded={!collapsed}
|
||||||
|
ContextMenuComponent={this.props.space.getMyMembership() === "join"
|
||||||
|
? SpaceContextMenu : undefined}
|
||||||
>
|
>
|
||||||
{ toggleCollapseButton }
|
{ toggleCollapseButton }
|
||||||
<div className="mx_SpaceButton_selectionWrapper">
|
</SpaceButton>
|
||||||
<RoomAvatar width={avatarSize} height={avatarSize} room={space} />
|
|
||||||
{ !isPanelCollapsed && <span className="mx_SpaceButton_name">{ space.name }</span> }
|
|
||||||
{ notifBadge }
|
|
||||||
{ this.renderContextMenu() }
|
|
||||||
</div>
|
|
||||||
</RovingAccessibleTooltipButton>
|
|
||||||
|
|
||||||
{ childItems }
|
{ childItems }
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -1019,8 +1019,10 @@
|
||||||
"Address": "Address",
|
"Address": "Address",
|
||||||
"Creating...": "Creating...",
|
"Creating...": "Creating...",
|
||||||
"Create": "Create",
|
"Create": "Create",
|
||||||
"All rooms": "All rooms",
|
|
||||||
"Home": "Home",
|
"Home": "Home",
|
||||||
|
"Show all rooms in home": "Show all rooms in home",
|
||||||
|
"All rooms": "All rooms",
|
||||||
|
"Options": "Options",
|
||||||
"Expand space panel": "Expand space panel",
|
"Expand space panel": "Expand space panel",
|
||||||
"Collapse space panel": "Collapse space panel",
|
"Collapse space panel": "Collapse space panel",
|
||||||
"Click to copy": "Click to copy",
|
"Click to copy": "Click to copy",
|
||||||
|
@ -1050,16 +1052,9 @@
|
||||||
"Preview Space": "Preview Space",
|
"Preview Space": "Preview Space",
|
||||||
"Allow people to preview your space before they join.": "Allow people to preview your space before they join.",
|
"Allow people to preview your space before they join.": "Allow people to preview your space before they join.",
|
||||||
"Recommended for public spaces.": "Recommended for public spaces.",
|
"Recommended for public spaces.": "Recommended for public spaces.",
|
||||||
"Settings": "Settings",
|
|
||||||
"Leave space": "Leave space",
|
|
||||||
"Create new room": "Create new room",
|
|
||||||
"Add existing room": "Add existing room",
|
|
||||||
"Members": "Members",
|
|
||||||
"Manage & explore rooms": "Manage & explore rooms",
|
|
||||||
"Explore rooms": "Explore rooms",
|
|
||||||
"Space options": "Space options",
|
|
||||||
"Expand": "Expand",
|
"Expand": "Expand",
|
||||||
"Collapse": "Collapse",
|
"Collapse": "Collapse",
|
||||||
|
"Space options": "Space options",
|
||||||
"Remove": "Remove",
|
"Remove": "Remove",
|
||||||
"This bridge was provisioned by <user />.": "This bridge was provisioned by <user />.",
|
"This bridge was provisioned by <user />.": "This bridge was provisioned by <user />.",
|
||||||
"This bridge is managed by <user />.": "This bridge is managed by <user />.",
|
"This bridge is managed by <user />.": "This bridge is managed by <user />.",
|
||||||
|
@ -1583,8 +1578,11 @@
|
||||||
"Start chat": "Start chat",
|
"Start chat": "Start chat",
|
||||||
"Rooms": "Rooms",
|
"Rooms": "Rooms",
|
||||||
"Add room": "Add room",
|
"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",
|
"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",
|
"You do not have permissions to add rooms to this space": "You do not have permissions to add rooms to this space",
|
||||||
|
"Explore rooms": "Explore rooms",
|
||||||
"Explore community rooms": "Explore community rooms",
|
"Explore community rooms": "Explore community rooms",
|
||||||
"Explore public rooms": "Explore public rooms",
|
"Explore public rooms": "Explore public rooms",
|
||||||
"Low priority": "Low priority",
|
"Low priority": "Low priority",
|
||||||
|
@ -1662,6 +1660,7 @@
|
||||||
"Low Priority": "Low Priority",
|
"Low Priority": "Low Priority",
|
||||||
"Invite People": "Invite People",
|
"Invite People": "Invite People",
|
||||||
"Copy Room Link": "Copy Room Link",
|
"Copy Room Link": "Copy Room Link",
|
||||||
|
"Settings": "Settings",
|
||||||
"Leave Room": "Leave Room",
|
"Leave Room": "Leave Room",
|
||||||
"Room options": "Room options",
|
"Room options": "Room options",
|
||||||
"%(count)s unread messages including mentions.|other": "%(count)s unread messages including mentions.",
|
"%(count)s unread messages including mentions.|other": "%(count)s unread messages including mentions.",
|
||||||
|
@ -1755,13 +1754,13 @@
|
||||||
"The homeserver the user you’re verifying is connected to": "The homeserver the user you’re verifying is connected to",
|
"The homeserver the user you’re verifying is connected to": "The homeserver the user you’re verifying is connected to",
|
||||||
"Yours, or the other users’ internet connection": "Yours, or the other users’ internet connection",
|
"Yours, or the other users’ internet connection": "Yours, or the other users’ internet connection",
|
||||||
"Yours, or the other users’ session": "Yours, or the other users’ session",
|
"Yours, or the other users’ session": "Yours, or the other users’ session",
|
||||||
|
"Members": "Members",
|
||||||
"Nothing pinned, yet": "Nothing pinned, yet",
|
"Nothing pinned, yet": "Nothing pinned, yet",
|
||||||
"If you have permissions, open the menu on any message and select <b>Pin</b> to stick them here.": "If you have permissions, open the menu on any message and select <b>Pin</b> to stick them here.",
|
"If you have permissions, open the menu on any message and select <b>Pin</b> to stick them here.": "If you have permissions, open the menu on any message and select <b>Pin</b> to stick them here.",
|
||||||
"Pinned messages": "Pinned messages",
|
"Pinned messages": "Pinned messages",
|
||||||
"Room Info": "Room Info",
|
"Room Info": "Room Info",
|
||||||
"You can only pin up to %(count)s widgets|other": "You can only pin up to %(count)s widgets",
|
"You can only pin up to %(count)s widgets|other": "You can only pin up to %(count)s widgets",
|
||||||
"Unpin a widget to view it in this panel": "Unpin a widget to view it in this panel",
|
"Unpin a widget to view it in this panel": "Unpin a widget to view it in this panel",
|
||||||
"Options": "Options",
|
|
||||||
"Set my room layout for everyone": "Set my room layout for everyone",
|
"Set my room layout for everyone": "Set my room layout for everyone",
|
||||||
"Widgets": "Widgets",
|
"Widgets": "Widgets",
|
||||||
"Edit widgets, bridges & bots": "Edit widgets, bridges & bots",
|
"Edit widgets, bridges & bots": "Edit widgets, bridges & bots",
|
||||||
|
@ -2563,6 +2562,8 @@
|
||||||
"Source URL": "Source URL",
|
"Source URL": "Source URL",
|
||||||
"Collapse reply thread": "Collapse reply thread",
|
"Collapse reply thread": "Collapse reply thread",
|
||||||
"Report": "Report",
|
"Report": "Report",
|
||||||
|
"Leave space": "Leave space",
|
||||||
|
"Manage & explore rooms": "Manage & explore rooms",
|
||||||
"Clear status": "Clear status",
|
"Clear status": "Clear status",
|
||||||
"Update status": "Update status",
|
"Update status": "Update status",
|
||||||
"Set status": "Set status",
|
"Set status": "Set status",
|
||||||
|
|
Loading…
Reference in New Issue