Tweaks to informational architecture 1.1 (#7052)
* Move user avatar to Space panel * Add room list header for 'Home' or 'Space Name' to room list Add existing Space context menus to room list header * Re-add pending room join spinner * Iterate RoomListHeader plus context menu * Iterate space context menu * Iterate room list + interactions * Move DND to new iA model * Replace composer custom status management with usermenu one * Cull Quick Actions * Iterate minimized room list state * delint * Merge the RoomListNumResults into the RoomListHeader * Make the search shortcut prompt semi-bold * Iterate RoomListHeader based on design review * Iterate UserMenu based on feedback * Add name to expanded spacepanel usermenu button * i18n * Make room sub list aux button components more generic * Change left panel explore button to only refer to room directory * Iterate RoomListHeader * Fix custom user status input field width in Chrome * Bring back Notification settings button * delint * i18n * post-merge fix * iterate pr * Remove unused state * update copy * Apply suggestions from PR review * delint * Update invite iconography * Iterate Space context menu to match Figma * Fix chevron alignment * Fix edge case for RoomListHeader on metaspaces * Wire up general rageshake-driven feedback mechanism * Add IA1.1 info toast * add missing alt attribute * delint * delint * tweak ia toast priority * e2e test account for new toast * autofocus feedback field and remove old subheading * tweak copy * Iterate space panel colours to match Figma * Iterate PR * delint * Fix feedback submission with object setting values * iterate based on review * Tweak colours and update splash image * Tweaks based on review * Remove room list prompt, made redundant by the big fat `+` * Fix edge cases around User Menu positioning and dnd * Add missing import, bad merge? * Update aria label in e2e test * Fix room list space rooms context menu explore button behaviour * Tweak copy * Revert order of options in the UserMenu * Tweak copy * i18n
This commit is contained in:
parent
c09e0efdb9
commit
8fe582b094
49 changed files with 1433 additions and 1483 deletions
|
@ -49,7 +49,6 @@ import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInse
|
|||
import { Action } from "../../../dispatcher/actions";
|
||||
import EditorModel from "../../../editor/model";
|
||||
import EmojiPicker from '../emojipicker/EmojiPicker';
|
||||
import MemberStatusMessageAvatar from "../avatars/MemberStatusMessageAvatar";
|
||||
import UIStore, { UI_EVENTS } from '../../../stores/UIStore';
|
||||
import Modal from "../../../Modal";
|
||||
import { RelationType } from 'matrix-js-sdk/src/@types/event';
|
||||
|
@ -61,16 +60,6 @@ import PollCreateDialog from "../elements/PollCreateDialog";
|
|||
let instanceCount = 0;
|
||||
const NARROW_MODE_BREAKPOINT = 500;
|
||||
|
||||
interface IComposerAvatarProps {
|
||||
me: RoomMember;
|
||||
}
|
||||
|
||||
function ComposerAvatar(props: IComposerAvatarProps) {
|
||||
return <div className="mx_MessageComposer_avatar">
|
||||
<MemberStatusMessageAvatar member={props.me} width={24} height={24} />
|
||||
</div>;
|
||||
}
|
||||
|
||||
interface ISendButtonProps {
|
||||
onClick: () => void;
|
||||
title?: string; // defaults to something generic
|
||||
|
@ -567,7 +556,6 @@ export default class MessageComposer extends React.Component<IProps, IState> {
|
|||
|
||||
render() {
|
||||
const controls = [
|
||||
this.state.me && !this.props.compact ? <ComposerAvatar key="controls_avatar" me={this.state.me} /> : null,
|
||||
this.props.e2eStatus ?
|
||||
<E2EIcon key="e2eIcon" status={this.props.e2eStatus} className="mx_MessageComposer_e2eIcon" /> :
|
||||
null,
|
||||
|
|
|
@ -14,8 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { createRef, ReactComponentElement } from "react";
|
||||
import { Dispatcher } from "flux";
|
||||
import React, { ComponentType, createRef, ReactComponentElement, RefObject } from "react";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import * as fbEmitter from "fbemitter";
|
||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||
|
@ -27,9 +26,8 @@ import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/Roo
|
|||
import RoomViewStore from "../../../stores/RoomViewStore";
|
||||
import { ITagMap } from "../../../stores/room-list/algorithms/models";
|
||||
import { DefaultTagID, isCustomTag, TagID } from "../../../stores/room-list/models";
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||
import RoomSublist from "./RoomSublist";
|
||||
import RoomSublist, { IAuxButtonProps } from "./RoomSublist";
|
||||
import { ActionPayload } from "../../../dispatcher/payloads";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import GroupAvatar from "../avatars/GroupAvatar";
|
||||
|
@ -41,18 +39,28 @@ import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNo
|
|||
import CustomRoomTagStore from "../../../stores/CustomRoomTagStore";
|
||||
import { arrayFastClone, arrayHasDiff } from "../../../utils/arrays";
|
||||
import { objectShallowClone, objectWithOnly } from "../../../utils/objects";
|
||||
import { IconizedContextMenuOption, IconizedContextMenuOptionList } from "../context_menus/IconizedContextMenu";
|
||||
import IconizedContextMenu, {
|
||||
IconizedContextMenuOption,
|
||||
IconizedContextMenuOptionList,
|
||||
} from "../context_menus/IconizedContextMenu";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore";
|
||||
import SpaceStore from "../../../stores/spaces/SpaceStore";
|
||||
import { ISuggestedRoom, MetaSpace, SpaceKey, UPDATE_SUGGESTED_ROOMS } from "../../../stores/spaces";
|
||||
import { showAddExistingRooms, showCreateNewRoom, showSpaceInvite } from "../../../utils/space";
|
||||
import {
|
||||
ISuggestedRoom,
|
||||
MetaSpace,
|
||||
SpaceKey,
|
||||
UPDATE_SUGGESTED_ROOMS,
|
||||
UPDATE_SELECTED_SPACE,
|
||||
} from "../../../stores/spaces";
|
||||
import { shouldShowSpaceInvite, showAddExistingRooms, showCreateNewRoom, showSpaceInvite } from "../../../utils/space";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import RoomAvatar from "../avatars/RoomAvatar";
|
||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
|
||||
import { UIComponent } from "../../../settings/UIFeature";
|
||||
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
|
||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
|
||||
import { ChevronFace, ContextMenuTooltipButton, useContextMenu } from "../../structures/ContextMenu";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
|
||||
interface IProps {
|
||||
|
@ -95,9 +103,7 @@ const ALWAYS_VISIBLE_TAGS: TagID[] = [
|
|||
interface ITagAesthetics {
|
||||
sectionLabel: string;
|
||||
sectionLabelRaw?: string;
|
||||
addRoomLabel?: string;
|
||||
onAddRoom?: (dispatcher?: Dispatcher<ActionPayload>) => void;
|
||||
addRoomContextMenu?: (onFinished: () => void) => React.ReactNode;
|
||||
AuxButtonComponent?: ComponentType<IAuxButtonProps>;
|
||||
isInvite: boolean;
|
||||
defaultHidden: boolean;
|
||||
}
|
||||
|
@ -107,6 +113,194 @@ interface ITagAestheticsMap {
|
|||
[tagId: TagID]: ITagAesthetics;
|
||||
}
|
||||
|
||||
const auxButtonContextMenuPosition = (handle: RefObject<HTMLDivElement>) => {
|
||||
const rect = handle.current.getBoundingClientRect();
|
||||
return {
|
||||
chevronFace: ChevronFace.None,
|
||||
left: rect.left - 7,
|
||||
top: rect.top + rect.height,
|
||||
};
|
||||
};
|
||||
|
||||
const DmAuxButton = ({ tabIndex, dispatcher = defaultDispatcher }: IAuxButtonProps) => {
|
||||
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<HTMLDivElement>();
|
||||
const activeSpace = useEventEmitterState<Room>(SpaceStore.instance, UPDATE_SELECTED_SPACE, () => {
|
||||
return SpaceStore.instance.activeSpaceRoom;
|
||||
});
|
||||
|
||||
const showCreateRooms = shouldShowComponent(UIComponent.CreateRooms);
|
||||
const showInviteUsers = shouldShowComponent(UIComponent.InviteUsers);
|
||||
|
||||
if (activeSpace && (showCreateRooms || showInviteUsers)) {
|
||||
let contextMenu: JSX.Element;
|
||||
if (menuDisplayed) {
|
||||
const canInvite = shouldShowSpaceInvite(activeSpace);
|
||||
|
||||
contextMenu = <IconizedContextMenu {...auxButtonContextMenuPosition(handle)} onFinished={closeMenu} compact>
|
||||
<IconizedContextMenuOptionList first>
|
||||
{ showCreateRooms && <IconizedContextMenuOption
|
||||
label={_t("Start new chat")}
|
||||
iconClassName="mx_RoomList_iconStartChat"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
closeMenu();
|
||||
defaultDispatcher.dispatch({ action: "view_create_chat" });
|
||||
}}
|
||||
/> }
|
||||
{ showInviteUsers && <IconizedContextMenuOption
|
||||
label={_t("Invite to space")}
|
||||
iconClassName="mx_RoomList_iconInvite"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
closeMenu();
|
||||
showSpaceInvite(activeSpace);
|
||||
}}
|
||||
disabled={!canInvite}
|
||||
tooltip={canInvite ? undefined
|
||||
: _t("You do not have permissions to invite people to this space")}
|
||||
/> }
|
||||
</IconizedContextMenuOptionList>
|
||||
</IconizedContextMenu>;
|
||||
}
|
||||
|
||||
return <>
|
||||
<ContextMenuTooltipButton
|
||||
tabIndex={tabIndex}
|
||||
onClick={openMenu}
|
||||
className="mx_RoomSublist_auxButton"
|
||||
tooltipClassName="mx_RoomSublist_addRoomTooltip"
|
||||
aria-label={_t("Add people")}
|
||||
title={_t("Add people")}
|
||||
isExpanded={menuDisplayed}
|
||||
inputRef={handle}
|
||||
/>
|
||||
|
||||
{ contextMenu }
|
||||
</>;
|
||||
} else if (!activeSpace && showCreateRooms) {
|
||||
return <AccessibleTooltipButton
|
||||
tabIndex={tabIndex}
|
||||
onClick={() => dispatcher.dispatch({ action: 'view_create_chat' })}
|
||||
className="mx_RoomSublist_auxButton"
|
||||
tooltipClassName="mx_RoomSublist_addRoomTooltip"
|
||||
aria-label={_t("Start chat")}
|
||||
title={_t("Start chat")}
|
||||
/>;
|
||||
}
|
||||
};
|
||||
|
||||
const UntaggedAuxButton = ({ tabIndex }: IAuxButtonProps) => {
|
||||
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<HTMLDivElement>();
|
||||
const activeSpace = useEventEmitterState<Room>(SpaceStore.instance, UPDATE_SELECTED_SPACE, () => {
|
||||
return SpaceStore.instance.activeSpaceRoom;
|
||||
});
|
||||
|
||||
const showCreateRoom = shouldShowComponent(UIComponent.CreateRooms);
|
||||
|
||||
let contextMenuContent: JSX.Element;
|
||||
if (menuDisplayed && activeSpace) {
|
||||
const canAddRooms = activeSpace.currentState.maySendStateEvent(EventType.SpaceChild,
|
||||
MatrixClientPeg.get().getUserId());
|
||||
|
||||
contextMenuContent = <IconizedContextMenuOptionList first>
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Explore rooms")}
|
||||
iconClassName="mx_RoomList_iconExplore"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
closeMenu();
|
||||
defaultDispatcher.dispatch({
|
||||
action: "view_room",
|
||||
room_id: activeSpace.roomId,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{
|
||||
showCreateRoom
|
||||
? (<>
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Create new room")}
|
||||
iconClassName="mx_RoomList_iconCreateNewRoom"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
closeMenu();
|
||||
showCreateNewRoom(activeSpace);
|
||||
}}
|
||||
disabled={!canAddRooms}
|
||||
tooltip={canAddRooms ? undefined
|
||||
: _t("You do not have permissions to create new rooms in this space")}
|
||||
/>
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Add existing room")}
|
||||
iconClassName="mx_RoomList_iconAddExistingRoom"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
closeMenu();
|
||||
showAddExistingRooms(activeSpace);
|
||||
}}
|
||||
disabled={!canAddRooms}
|
||||
tooltip={canAddRooms ? undefined
|
||||
: _t("You do not have permissions to add rooms to this space")}
|
||||
/>
|
||||
</>)
|
||||
: null
|
||||
}
|
||||
</IconizedContextMenuOptionList>;
|
||||
} else if (menuDisplayed) {
|
||||
contextMenuContent = <IconizedContextMenuOptionList first>
|
||||
{ showCreateRoom && <IconizedContextMenuOption
|
||||
label={_t("Create new room")}
|
||||
iconClassName="mx_RoomList_iconCreateNewRoom"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
closeMenu();
|
||||
defaultDispatcher.dispatch({ action: "view_create_room" });
|
||||
}}
|
||||
/> }
|
||||
<IconizedContextMenuOption
|
||||
label={CommunityPrototypeStore.instance.getSelectedCommunityId()
|
||||
? _t("Explore community rooms")
|
||||
: _t("Explore public rooms")}
|
||||
iconClassName="mx_RoomList_iconExplore"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
closeMenu();
|
||||
defaultDispatcher.fire(Action.ViewRoomDirectory);
|
||||
}}
|
||||
/>
|
||||
</IconizedContextMenuOptionList>;
|
||||
}
|
||||
|
||||
let contextMenu: JSX.Element;
|
||||
if (menuDisplayed) {
|
||||
contextMenu = <IconizedContextMenu {...auxButtonContextMenuPosition(handle)} onFinished={closeMenu} compact>
|
||||
{ contextMenuContent }
|
||||
</IconizedContextMenu>;
|
||||
}
|
||||
|
||||
return <>
|
||||
<ContextMenuTooltipButton
|
||||
tabIndex={tabIndex}
|
||||
onClick={openMenu}
|
||||
className="mx_RoomSublist_auxButton"
|
||||
tooltipClassName="mx_RoomSublist_addRoomTooltip"
|
||||
aria-label={_td("Add room")}
|
||||
title={_td("Add room")}
|
||||
isExpanded={menuDisplayed}
|
||||
inputRef={handle}
|
||||
/>
|
||||
|
||||
{ contextMenu }
|
||||
</>;
|
||||
};
|
||||
|
||||
const TAG_AESTHETICS: ITagAestheticsMap = {
|
||||
[DefaultTagID.Invite]: {
|
||||
sectionLabel: _td("Invites"),
|
||||
|
@ -122,93 +316,13 @@ const TAG_AESTHETICS: ITagAestheticsMap = {
|
|||
sectionLabel: _td("People"),
|
||||
isInvite: false,
|
||||
defaultHidden: false,
|
||||
addRoomLabel: _td("Start chat"),
|
||||
onAddRoom: (dispatcher?: Dispatcher<ActionPayload>) => {
|
||||
(dispatcher || defaultDispatcher).dispatch({ action: 'view_create_chat' });
|
||||
},
|
||||
AuxButtonComponent: DmAuxButton,
|
||||
},
|
||||
[DefaultTagID.Untagged]: {
|
||||
sectionLabel: _td("Rooms"),
|
||||
isInvite: false,
|
||||
defaultHidden: false,
|
||||
addRoomLabel: _td("Add room"),
|
||||
addRoomContextMenu: (onFinished: () => void) => {
|
||||
if (SpaceStore.instance.activeSpaceRoom) {
|
||||
const userId = MatrixClientPeg.get().getUserId();
|
||||
const space = SpaceStore.instance.activeSpaceRoom;
|
||||
const canAddRooms = space.currentState.maySendStateEvent(EventType.SpaceChild, userId);
|
||||
|
||||
return <IconizedContextMenuOptionList first>
|
||||
{
|
||||
shouldShowComponent(UIComponent.CreateRooms)
|
||||
? (<>
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Create new room")}
|
||||
iconClassName="mx_RoomList_iconPlus"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onFinished();
|
||||
showCreateNewRoom(space);
|
||||
}}
|
||||
disabled={!canAddRooms}
|
||||
tooltip={canAddRooms ? undefined
|
||||
: _t("You do not have permissions to create new rooms in this space")}
|
||||
/>
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Add existing room")}
|
||||
iconClassName="mx_RoomList_iconHash"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onFinished();
|
||||
showAddExistingRooms(space);
|
||||
}}
|
||||
disabled={!canAddRooms}
|
||||
tooltip={canAddRooms ? undefined
|
||||
: _t("You do not have permissions to add rooms to this space")}
|
||||
/>
|
||||
</>)
|
||||
: null
|
||||
}
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Explore rooms")}
|
||||
iconClassName="mx_RoomList_iconBrowse"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onFinished();
|
||||
defaultDispatcher.fire(Action.ViewRoomDirectory);
|
||||
}}
|
||||
/>
|
||||
</IconizedContextMenuOptionList>;
|
||||
}
|
||||
|
||||
return <IconizedContextMenuOptionList first>
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Create new room")}
|
||||
iconClassName="mx_RoomList_iconPlus"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onFinished();
|
||||
defaultDispatcher.dispatch({ action: "view_create_room" });
|
||||
}}
|
||||
/>
|
||||
<IconizedContextMenuOption
|
||||
label={CommunityPrototypeStore.instance.getSelectedCommunityId()
|
||||
? _t("Explore community rooms")
|
||||
: _t("Explore public rooms")}
|
||||
iconClassName="mx_RoomList_iconExplore"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onFinished();
|
||||
defaultDispatcher.fire(Action.ViewRoomDirectory);
|
||||
}}
|
||||
/>
|
||||
</IconizedContextMenuOptionList>;
|
||||
},
|
||||
AuxButtonComponent: UntaggedAuxButton,
|
||||
},
|
||||
[DefaultTagID.LowPriority]: {
|
||||
sectionLabel: _td("Low priority"),
|
||||
|
@ -296,7 +410,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
|||
const currentRoomId = RoomViewStore.getRoomId();
|
||||
const room = this.getRoomDelta(currentRoomId, viewRoomDeltaPayload.delta, viewRoomDeltaPayload.unread);
|
||||
if (room) {
|
||||
dis.dispatch({
|
||||
defaultDispatcher.dispatch({
|
||||
action: Action.ViewRoom,
|
||||
room_id: room.roomId,
|
||||
show_room_tile: true, // to make sure the room gets scrolled into view
|
||||
|
@ -375,17 +489,19 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
|||
|
||||
private onStartChat = () => {
|
||||
const initialText = RoomListStore.instance.getFirstNameFilterCondition()?.search;
|
||||
dis.dispatch({ action: "view_create_chat", initialText });
|
||||
defaultDispatcher.dispatch({ action: "view_create_chat", initialText });
|
||||
};
|
||||
|
||||
private onExplore = () => {
|
||||
const initialText = RoomListStore.instance.getFirstNameFilterCondition()?.search;
|
||||
dis.dispatch({ action: Action.ViewRoomDirectory, initialText });
|
||||
};
|
||||
|
||||
private onSpaceInviteClick = () => {
|
||||
const initialText = RoomListStore.instance.getFirstNameFilterCondition()?.search;
|
||||
showSpaceInvite(this.context.getRoom(this.props.activeSpace), initialText);
|
||||
if (this.props.activeSpace[0] === "!") {
|
||||
defaultDispatcher.dispatch({
|
||||
action: "view_room",
|
||||
room_id: SpaceStore.instance.activeSpace,
|
||||
});
|
||||
} else {
|
||||
const initialText = RoomListStore.instance.getFirstNameFilterCondition()?.search;
|
||||
defaultDispatcher.dispatch({ action: Action.ViewRoomDirectory, initialText });
|
||||
}
|
||||
};
|
||||
|
||||
private renderSuggestedRooms(): ReactComponentElement<typeof ExtraTile>[] {
|
||||
|
@ -508,9 +624,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
|||
forRooms={true}
|
||||
startAsHidden={aesthetics.defaultHidden}
|
||||
label={aesthetics.sectionLabelRaw ? aesthetics.sectionLabelRaw : _t(aesthetics.sectionLabel)}
|
||||
onAddRoom={aesthetics.onAddRoom}
|
||||
addRoomLabel={aesthetics.addRoomLabel ? _t(aesthetics.addRoomLabel) : aesthetics.addRoomLabel}
|
||||
addRoomContextMenu={aesthetics.addRoomContextMenu}
|
||||
AuxButtonComponent={aesthetics.AuxButtonComponent}
|
||||
isMinimized={this.props.isMinimized}
|
||||
showSkeleton={showSkeleton}
|
||||
extraTiles={extraTiles}
|
||||
|
@ -528,10 +642,6 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const userId = cli.getUserId();
|
||||
const activeSpace = this.props.activeSpace[0] === "!" ? cli.getRoom(this.props.activeSpace) : null;
|
||||
|
||||
let explorePrompt: JSX.Element;
|
||||
if (!this.props.isMinimized) {
|
||||
if (this.state.isNameFiltering) {
|
||||
|
@ -549,58 +659,9 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
|||
kind="link"
|
||||
onClick={this.onExplore}
|
||||
>
|
||||
{ activeSpace ? _t("Explore rooms") : _t("Explore all public rooms") }
|
||||
{ this.props.activeSpace[0] === "!" ? _t("Explore rooms") : _t("Explore all public rooms") }
|
||||
</AccessibleButton>
|
||||
</div>;
|
||||
} else if (
|
||||
activeSpace?.canInvite(userId) ||
|
||||
activeSpace?.getMyMembership() === "join" ||
|
||||
activeSpace?.getJoinRule() === JoinRule.Public
|
||||
) {
|
||||
const spaceName = activeSpace.name;
|
||||
const canInvite = activeSpace?.canInvite(userId) || activeSpace?.getJoinRule() === JoinRule.Public;
|
||||
explorePrompt = <div className="mx_RoomList_explorePrompt">
|
||||
<div>{ _t("Quick actions") }</div>
|
||||
{ canInvite && <AccessibleTooltipButton
|
||||
className="mx_RoomList_explorePrompt_spaceInvite"
|
||||
onClick={this.onSpaceInviteClick}
|
||||
title={_t("Invite to %(spaceName)s", { spaceName })}
|
||||
>
|
||||
{ _t("Invite people") }
|
||||
</AccessibleTooltipButton> }
|
||||
{ activeSpace?.getMyMembership() === "join" && <AccessibleTooltipButton
|
||||
className="mx_RoomList_explorePrompt_spaceExplore"
|
||||
onClick={this.onExplore}
|
||||
title={_t("Explore %(spaceName)s", { spaceName })}
|
||||
>
|
||||
{ _t("Explore rooms") }
|
||||
</AccessibleTooltipButton> }
|
||||
</div>;
|
||||
} else if (Object.values(this.state.sublists).some(list => list.length > 0)) {
|
||||
const unfilteredLists = RoomListStore.instance.unfilteredLists;
|
||||
const unfilteredRooms = unfilteredLists[DefaultTagID.Untagged] || [];
|
||||
const unfilteredHistorical = unfilteredLists[DefaultTagID.Archived] || [];
|
||||
const unfilteredFavourite = unfilteredLists[DefaultTagID.Favourite] || [];
|
||||
// show a prompt to join/create rooms if the user is in 0 rooms and no historical
|
||||
if (unfilteredRooms.length < 1 && unfilteredHistorical.length < 1 && unfilteredFavourite.length < 1) {
|
||||
explorePrompt = <div className="mx_RoomList_explorePrompt">
|
||||
<div>{ _t("Use the + to make a new room or explore existing ones below") }</div>
|
||||
<AccessibleButton
|
||||
className="mx_RoomList_explorePrompt_startChat"
|
||||
kind="link"
|
||||
onClick={this.onStartChat}
|
||||
>
|
||||
{ _t("Start a new chat") }
|
||||
</AccessibleButton>
|
||||
<AccessibleButton
|
||||
className="mx_RoomList_explorePrompt_explore"
|
||||
kind="link"
|
||||
onClick={this.onExplore}
|
||||
>
|
||||
{ _t("Explore all public rooms") }
|
||||
</AccessibleButton>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
367
src/components/views/rooms/RoomListHeader.tsx
Normal file
367
src/components/views/rooms/RoomListHeader.tsx
Normal file
|
@ -0,0 +1,367 @@
|
|||
/*
|
||||
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, useEffect, useState } from "react";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { useEventEmitter, useEventEmitterState } from "../../../hooks/useEventEmitter";
|
||||
import SpaceStore from "../../../stores/spaces/SpaceStore";
|
||||
import { ChevronFace, ContextMenuTooltipButton, useContextMenu } from "../../structures/ContextMenu";
|
||||
import SpaceContextMenu from "../context_menus/SpaceContextMenu";
|
||||
import { HomeButtonContextMenu } from "../spaces/SpacePanel";
|
||||
import IconizedContextMenu, {
|
||||
IconizedContextMenuOption,
|
||||
IconizedContextMenuOptionList,
|
||||
} from "../context_menus/IconizedContextMenu";
|
||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
import { shouldShowSpaceInvite, showCreateNewRoom, showSpaceInvite } from "../../../utils/space";
|
||||
import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore";
|
||||
import { ButtonEvent } from "../elements/AccessibleButton";
|
||||
import Modal from "../../../Modal";
|
||||
import EditCommunityPrototypeDialog from "../dialogs/EditCommunityPrototypeDialog";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
|
||||
import ErrorDialog from "../dialogs/ErrorDialog";
|
||||
import { showCommunityInviteDialog } from "../../../RoomInvite";
|
||||
import { useDispatcher } from "../../../hooks/useDispatcher";
|
||||
import InlineSpinner from "../elements/InlineSpinner";
|
||||
import TooltipButton from "../elements/TooltipButton";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore";
|
||||
import {
|
||||
getMetaSpaceName,
|
||||
MetaSpace,
|
||||
SpaceKey,
|
||||
UPDATE_HOME_BEHAVIOUR,
|
||||
UPDATE_SELECTED_SPACE,
|
||||
} from "../../../stores/spaces";
|
||||
|
||||
const contextMenuBelow = (elementRect: DOMRect) => {
|
||||
// align the context menu's icons with the icon which opened the context menu
|
||||
const left = elementRect.left + window.pageXOffset;
|
||||
const top = elementRect.bottom + window.pageYOffset + 12;
|
||||
const chevronFace = ChevronFace.None;
|
||||
return { left, top, chevronFace };
|
||||
};
|
||||
|
||||
const PrototypeCommunityContextMenu = (props) => {
|
||||
const communityId = CommunityPrototypeStore.instance.getSelectedCommunityId();
|
||||
|
||||
let settingsOption;
|
||||
if (CommunityPrototypeStore.instance.isAdminOf(communityId)) {
|
||||
const onCommunitySettingsClick = (ev: ButtonEvent) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
Modal.createTrackedDialog('Edit Community', '', EditCommunityPrototypeDialog, {
|
||||
communityId: CommunityPrototypeStore.instance.getSelectedCommunityId(),
|
||||
});
|
||||
props.onFinished();
|
||||
};
|
||||
|
||||
settingsOption = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_UserMenu_iconSettings"
|
||||
label={_t("Settings")}
|
||||
aria-label={_t("Community settings")}
|
||||
onClick={onCommunitySettingsClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const onCommunityMembersClick = (ev: ButtonEvent) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
// We'd ideally just pop open a right panel with the member list, but the current
|
||||
// way the right panel is structured makes this exceedingly difficult. Instead, we'll
|
||||
// switch to the general room and open the member list there as it should be in sync
|
||||
// anyways.
|
||||
const chat = CommunityPrototypeStore.instance.getSelectedCommunityGeneralChat();
|
||||
if (chat) {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: chat.roomId,
|
||||
}, true);
|
||||
dis.dispatch({ action: Action.SetRightPanelPhase, phase: RightPanelPhases.RoomMemberList });
|
||||
} else {
|
||||
// "This should never happen" clauses go here for the prototype.
|
||||
Modal.createTrackedDialog('Failed to find general chat', '', ErrorDialog, {
|
||||
title: _t('Failed to find the general chat for this community'),
|
||||
description: _t("Failed to find the general chat for this community"),
|
||||
});
|
||||
}
|
||||
props.onFinished();
|
||||
};
|
||||
|
||||
return <IconizedContextMenu {...props} compact>
|
||||
<IconizedContextMenuOptionList first>
|
||||
{ settingsOption }
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_UserMenu_iconMembers"
|
||||
label={_t("Members")}
|
||||
onClick={onCommunityMembersClick}
|
||||
/>
|
||||
</IconizedContextMenuOptionList>
|
||||
</IconizedContextMenu>;
|
||||
};
|
||||
|
||||
const useJoiningRooms = (): Set<string> => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
const [joiningRooms, setJoiningRooms] = useState(new Set<string>());
|
||||
useDispatcher(defaultDispatcher, payload => {
|
||||
switch (payload.action) {
|
||||
case Action.JoinRoom:
|
||||
setJoiningRooms(new Set(joiningRooms.add(payload.roomId)));
|
||||
break;
|
||||
case Action.JoinRoomReady:
|
||||
case Action.JoinRoomError:
|
||||
if (joiningRooms.delete(payload.roomId)) {
|
||||
setJoiningRooms(new Set(joiningRooms));
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
useEventEmitter(cli, "Room", (room: Room) => {
|
||||
if (joiningRooms.delete(room.roomId)) {
|
||||
setJoiningRooms(new Set(joiningRooms));
|
||||
}
|
||||
});
|
||||
|
||||
return joiningRooms;
|
||||
};
|
||||
|
||||
interface IProps {
|
||||
onVisibilityChange?(): void;
|
||||
}
|
||||
|
||||
const RoomListHeader = ({ onVisibilityChange }: IProps) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
const [mainMenuDisplayed, mainMenuHandle, openMainMenu, closeMainMenu] = useContextMenu<HTMLDivElement>();
|
||||
const [plusMenuDisplayed, plusMenuHandle, openPlusMenu, closePlusMenu] = useContextMenu<HTMLDivElement>();
|
||||
const [spaceKey, activeSpace] = useEventEmitterState<[SpaceKey, Room | null]>(
|
||||
SpaceStore.instance,
|
||||
UPDATE_SELECTED_SPACE,
|
||||
() => [SpaceStore.instance.activeSpace, SpaceStore.instance.activeSpaceRoom],
|
||||
);
|
||||
const allRoomsInHome = useEventEmitterState(SpaceStore.instance, UPDATE_HOME_BEHAVIOUR, () => {
|
||||
return SpaceStore.instance.allRoomsInHome;
|
||||
});
|
||||
const joiningRooms = useJoiningRooms();
|
||||
|
||||
const count = useEventEmitterState(RoomListStore.instance, LISTS_UPDATE_EVENT, () => {
|
||||
if (RoomListStore.instance.getFirstNameFilterCondition()) {
|
||||
return Object.values(RoomListStore.instance.orderedLists).flat(1).length;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (onVisibilityChange) {
|
||||
onVisibilityChange();
|
||||
}
|
||||
}, [count, onVisibilityChange]);
|
||||
|
||||
if (typeof count === "number") {
|
||||
return <div className="mx_LeftPanel_roomListFilterCount">
|
||||
{ _t("%(count)s results", { count }) }
|
||||
</div>;
|
||||
}
|
||||
|
||||
const communityId = CommunityPrototypeStore.instance.getSelectedCommunityId();
|
||||
|
||||
let contextMenu: JSX.Element;
|
||||
if (mainMenuDisplayed) {
|
||||
let ContextMenuComponent;
|
||||
if (activeSpace) {
|
||||
ContextMenuComponent = SpaceContextMenu;
|
||||
} else if (communityId) {
|
||||
ContextMenuComponent = PrototypeCommunityContextMenu;
|
||||
} else {
|
||||
ContextMenuComponent = HomeButtonContextMenu;
|
||||
}
|
||||
|
||||
contextMenu = <ContextMenuComponent
|
||||
{...contextMenuBelow(mainMenuHandle.current.getBoundingClientRect())}
|
||||
space={activeSpace}
|
||||
onFinished={closeMainMenu}
|
||||
hideHeader={true}
|
||||
/>;
|
||||
} else if (plusMenuDisplayed && activeSpace) {
|
||||
let inviteOption: JSX.Element;
|
||||
if (shouldShowSpaceInvite(activeSpace)) {
|
||||
inviteOption = <IconizedContextMenuOption
|
||||
label={_t("Invite")}
|
||||
iconClassName="mx_RoomListHeader_iconInvite"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
showSpaceInvite(activeSpace);
|
||||
closePlusMenu();
|
||||
}}
|
||||
/>;
|
||||
} else if (CommunityPrototypeStore.instance.canInviteTo(communityId)) {
|
||||
inviteOption = <IconizedContextMenuOption
|
||||
iconClassName="mx_RoomListHeader_iconInvite"
|
||||
label={_t("Invite")}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
showCommunityInviteDialog(CommunityPrototypeStore.instance.getSelectedCommunityId());
|
||||
closePlusMenu();
|
||||
}}
|
||||
/>;
|
||||
}
|
||||
|
||||
let createNewRoomOption: JSX.Element;
|
||||
if (activeSpace?.currentState.maySendStateEvent(EventType.RoomAvatar, cli.getUserId())) {
|
||||
createNewRoomOption = <IconizedContextMenuOption
|
||||
iconClassName="mx_RoomListHeader_iconCreateRoom"
|
||||
label={_t("Create new room")}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
showCreateNewRoom(activeSpace);
|
||||
closePlusMenu();
|
||||
}}
|
||||
/>;
|
||||
}
|
||||
|
||||
contextMenu = <IconizedContextMenu
|
||||
{...contextMenuBelow(plusMenuHandle.current.getBoundingClientRect())}
|
||||
onFinished={closePlusMenu}
|
||||
compact
|
||||
>
|
||||
<IconizedContextMenuOptionList first>
|
||||
{ inviteOption }
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Start new chat")}
|
||||
iconClassName="mx_RoomListHeader_iconStartChat"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
defaultDispatcher.dispatch({ action: "view_create_chat" });
|
||||
closePlusMenu();
|
||||
}}
|
||||
/>
|
||||
{ createNewRoomOption }
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Explore rooms")}
|
||||
iconClassName="mx_RoomListHeader_iconExplore"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
defaultDispatcher.dispatch({ action: Action.ViewRoomDirectory });
|
||||
closePlusMenu();
|
||||
}}
|
||||
/>
|
||||
</IconizedContextMenuOptionList>
|
||||
</IconizedContextMenu>;
|
||||
} else if (plusMenuDisplayed) {
|
||||
contextMenu = <IconizedContextMenu
|
||||
{...contextMenuBelow(plusMenuHandle.current.getBoundingClientRect())}
|
||||
onFinished={closePlusMenu}
|
||||
compact
|
||||
>
|
||||
<IconizedContextMenuOptionList first>
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Start new chat")}
|
||||
iconClassName="mx_RoomListHeader_iconStartChat"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
defaultDispatcher.dispatch({ action: "view_create_chat" });
|
||||
closePlusMenu();
|
||||
}}
|
||||
/>
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Create new room")}
|
||||
iconClassName="mx_RoomListHeader_iconCreateRoom"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
defaultDispatcher.dispatch({ action: "view_create_room" });
|
||||
closePlusMenu();
|
||||
}}
|
||||
/>
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Join public room")}
|
||||
iconClassName="mx_RoomListHeader_iconExplore"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
defaultDispatcher.dispatch({ action: Action.ViewRoomDirectory });
|
||||
closePlusMenu();
|
||||
}}
|
||||
/>
|
||||
</IconizedContextMenuOptionList>
|
||||
</IconizedContextMenu>;
|
||||
}
|
||||
|
||||
let title: string;
|
||||
if (activeSpace) {
|
||||
title = activeSpace.name;
|
||||
} else if (communityId) {
|
||||
title = CommunityPrototypeStore.instance.getSelectedCommunityName();
|
||||
} else {
|
||||
title = getMetaSpaceName(spaceKey as MetaSpace, allRoomsInHome);
|
||||
}
|
||||
|
||||
let pendingRoomJoinSpinner;
|
||||
if (joiningRooms.size) {
|
||||
pendingRoomJoinSpinner = <InlineSpinner>
|
||||
<TooltipButton helpText={_t(
|
||||
"Currently joining %(count)s rooms",
|
||||
{ count: joiningRooms.size },
|
||||
)} />
|
||||
</InlineSpinner>;
|
||||
}
|
||||
|
||||
let contextMenuButton: JSX.Element = <div className="mx_RoomListHeader_contextLessTitle">{ title }</div>;
|
||||
if (activeSpace || spaceKey === MetaSpace.Home) {
|
||||
contextMenuButton = <ContextMenuTooltipButton
|
||||
inputRef={mainMenuHandle}
|
||||
onClick={openMainMenu}
|
||||
isExpanded={mainMenuDisplayed}
|
||||
className="mx_RoomListHeader_contextMenuButton"
|
||||
title={activeSpace
|
||||
? _t("%(spaceName)s menu", { spaceName: activeSpace.name })
|
||||
: _t("Home options")}
|
||||
>
|
||||
{ title }
|
||||
</ContextMenuTooltipButton>;
|
||||
}
|
||||
|
||||
return <div className="mx_RoomListHeader">
|
||||
{ contextMenuButton }
|
||||
{ pendingRoomJoinSpinner }
|
||||
<ContextMenuTooltipButton
|
||||
inputRef={plusMenuHandle}
|
||||
onClick={openPlusMenu}
|
||||
isExpanded={plusMenuDisplayed}
|
||||
className="mx_RoomListHeader_plusButton"
|
||||
title={_t("Add")}
|
||||
/>
|
||||
|
||||
{ contextMenu }
|
||||
</div>;
|
||||
};
|
||||
|
||||
export default RoomListHeader;
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
Copyright 2020 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, { useEffect, useState } from "react";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore";
|
||||
import { useEventEmitter } from "../../../hooks/useEventEmitter";
|
||||
import SpaceStore from "../../../stores/spaces/SpaceStore";
|
||||
|
||||
interface IProps {
|
||||
onVisibilityChange?: () => void;
|
||||
}
|
||||
|
||||
const RoomListNumResults: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||
const [count, setCount] = useState<number>(null);
|
||||
useEventEmitter(RoomListStore.instance, LISTS_UPDATE_EVENT, () => {
|
||||
if (RoomListStore.instance.getFirstNameFilterCondition()) {
|
||||
const numRooms = Object.values(RoomListStore.instance.orderedLists).flat(1).length;
|
||||
setCount(numRooms);
|
||||
} else {
|
||||
setCount(null);
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (onVisibilityChange) {
|
||||
onVisibilityChange();
|
||||
}
|
||||
}, [count, onVisibilityChange]);
|
||||
|
||||
if (typeof count !== "number") return null;
|
||||
|
||||
return <div className="mx_LeftPanel_roomListFilterCount">
|
||||
{ SpaceStore.instance.spacePanelSpaces.length
|
||||
? _t("%(count)s results in all spaces", { count })
|
||||
: _t("%(count)s results", { count })
|
||||
}
|
||||
</div>;
|
||||
};
|
||||
|
||||
export default RoomListNumResults;
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import { createRef, ReactComponentElement } from "react";
|
||||
import { ComponentType, createRef, ReactComponentElement } from "react";
|
||||
import { normalize } from "matrix-js-sdk/src/utils";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import classNames from 'classnames';
|
||||
|
@ -52,11 +52,9 @@ import { arrayFastClone, arrayHasOrderChange } from "../../../utils/arrays";
|
|||
import { objectExcluding, objectHasDiff } from "../../../utils/objects";
|
||||
import ExtraTile from "./ExtraTile";
|
||||
import { ListNotificationState } from "../../../stores/notifications/ListNotificationState";
|
||||
import IconizedContextMenu from "../context_menus/IconizedContextMenu";
|
||||
import { getKeyBindingsManager, RoomListAction } from "../../../KeyBindingsManager";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
|
||||
import { UIComponent } from "../../../settings/UIFeature";
|
||||
import { Dispatcher } from "flux";
|
||||
|
||||
const SHOW_N_BUTTON_HEIGHT = 28; // As defined by CSS
|
||||
const RESIZE_HANDLE_HEIGHT = 4; // As defined by CSS
|
||||
|
@ -67,13 +65,16 @@ const MAX_PADDING_HEIGHT = SHOW_N_BUTTON_HEIGHT + RESIZE_HANDLE_HEIGHT;
|
|||
// HACK: We really shouldn't have to do this.
|
||||
polyfillTouchEvent();
|
||||
|
||||
export interface IAuxButtonProps {
|
||||
tabIndex: number;
|
||||
dispatcher?: Dispatcher<ActionPayload>;
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
forRooms: boolean;
|
||||
startAsHidden: boolean;
|
||||
label: string;
|
||||
onAddRoom?: () => void;
|
||||
addRoomContextMenu?: (onFinished: () => void) => React.ReactNode;
|
||||
addRoomLabel: string;
|
||||
AuxButtonComponent?: ComponentType<IAuxButtonProps>;
|
||||
isMinimized: boolean;
|
||||
tagId: TagID;
|
||||
showSkeleton?: boolean;
|
||||
|
@ -81,8 +82,6 @@ interface IProps {
|
|||
resizeNotifier: ResizeNotifier;
|
||||
extraTiles?: ReactComponentElement<typeof ExtraTile>[];
|
||||
onListCollapse?: (isExpanded: boolean) => void;
|
||||
|
||||
// TODO: Account for https://github.com/vector-im/element-web/issues/14179
|
||||
}
|
||||
|
||||
// TODO: Use re-resizer's NumberSize when it is exposed as the type
|
||||
|
@ -95,7 +94,6 @@ type PartialDOMRect = Pick<DOMRect, "left" | "top" | "height">;
|
|||
|
||||
interface IState {
|
||||
contextMenuPosition: PartialDOMRect;
|
||||
addRoomContextMenuPosition: PartialDOMRect;
|
||||
isResizing: boolean;
|
||||
isExpanded: boolean; // used for the for expand of the sublist when the room list is being filtered
|
||||
height: number;
|
||||
|
@ -123,7 +121,6 @@ export default class RoomSublist extends React.Component<IProps, IState> {
|
|||
this.notificationState = RoomNotificationStateStore.instance.getListState(this.props.tagId);
|
||||
this.state = {
|
||||
contextMenuPosition: null,
|
||||
addRoomContextMenuPosition: null,
|
||||
isResizing: false,
|
||||
isExpanded: this.isBeingFiltered ? this.isBeingFiltered : !this.layout.isCollapsed,
|
||||
height: 0, // to be fixed in a moment, we need `rooms` to calculate this.
|
||||
|
@ -313,11 +310,6 @@ export default class RoomSublist extends React.Component<IProps, IState> {
|
|||
}
|
||||
};
|
||||
|
||||
private onAddRoom = (e) => {
|
||||
e.stopPropagation();
|
||||
if (this.props.onAddRoom) this.props.onAddRoom();
|
||||
};
|
||||
|
||||
private applyHeightChange(newHeight: number) {
|
||||
const heightInTiles = Math.ceil(this.layout.pixelsToTiles(newHeight - this.padding));
|
||||
this.layout.visibleTiles = Math.min(this.numTiles, heightInTiles);
|
||||
|
@ -395,21 +387,10 @@ export default class RoomSublist extends React.Component<IProps, IState> {
|
|||
});
|
||||
};
|
||||
|
||||
private onAddRoomContextMenu = (ev: React.MouseEvent) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
const target = ev.target as HTMLButtonElement;
|
||||
this.setState({ addRoomContextMenuPosition: target.getBoundingClientRect() });
|
||||
};
|
||||
|
||||
private onCloseMenu = () => {
|
||||
this.setState({ contextMenuPosition: null });
|
||||
};
|
||||
|
||||
private onCloseAddRoomMenu = () => {
|
||||
this.setState({ addRoomContextMenuPosition: null });
|
||||
};
|
||||
|
||||
private onUnreadFirstChanged = () => {
|
||||
const isUnreadFirst = RoomListStore.instance.getListOrder(this.props.tagId) === ListAlgorithm.Importance;
|
||||
const newAlgorithm = isUnreadFirst ? ListAlgorithm.Natural : ListAlgorithm.Importance;
|
||||
|
@ -627,18 +608,6 @@ export default class RoomSublist extends React.Component<IProps, IState> {
|
|||
</div>
|
||||
</ContextMenu>
|
||||
);
|
||||
} else if (this.state.addRoomContextMenuPosition) {
|
||||
contextMenu = (
|
||||
<IconizedContextMenu
|
||||
chevronFace={ChevronFace.None}
|
||||
left={this.state.addRoomContextMenuPosition.left - 7} // center align with the handle
|
||||
top={this.state.addRoomContextMenuPosition.top + this.state.addRoomContextMenuPosition.height}
|
||||
onFinished={this.onCloseAddRoomMenu}
|
||||
compact
|
||||
>
|
||||
{ this.props.addRoomContextMenu(this.onCloseAddRoomMenu) }
|
||||
</IconizedContextMenu>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -677,30 +646,9 @@ export default class RoomSublist extends React.Component<IProps, IState> {
|
|||
);
|
||||
|
||||
let addRoomButton = null;
|
||||
if (!!this.props.onAddRoom && shouldShowComponent(UIComponent.CreateRooms)) {
|
||||
addRoomButton = (
|
||||
<AccessibleTooltipButton
|
||||
tabIndex={tabIndex}
|
||||
onClick={this.onAddRoom}
|
||||
className="mx_RoomSublist_auxButton"
|
||||
tooltipClassName="mx_RoomSublist_addRoomTooltip"
|
||||
aria-label={this.props.addRoomLabel || _t("Add room")}
|
||||
title={this.props.addRoomLabel}
|
||||
/>
|
||||
);
|
||||
} else if (this.props.addRoomContextMenu) {
|
||||
// We assume that shouldShowComponent() is checked by the context menu itself.
|
||||
addRoomButton = (
|
||||
<ContextMenuTooltipButton
|
||||
tabIndex={tabIndex}
|
||||
onClick={this.onAddRoomContextMenu}
|
||||
className="mx_RoomSublist_auxButton"
|
||||
tooltipClassName="mx_RoomSublist_addRoomTooltip"
|
||||
aria-label={this.props.addRoomLabel || _t("Add room")}
|
||||
title={this.props.addRoomLabel}
|
||||
isExpanded={!!this.state.addRoomContextMenuPosition}
|
||||
/>
|
||||
);
|
||||
if (this.props.AuxButtonComponent) {
|
||||
const AuxButtonComponent = this.props.AuxButtonComponent;
|
||||
addRoomButton = <AuxButtonComponent tabIndex={tabIndex} />;
|
||||
}
|
||||
|
||||
const collapseClasses = classNames({
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue