Merge branches 'develop' and 't3chguy/nad/rampage' of github.com:matrix-org/matrix-react-sdk into t3chguy/nad/rampage

 Conflicts:
	src/components/views/elements/AccessibleTooltipButton.tsx
This commit is contained in:
Michael Telatynski 2020-07-16 15:47:12 +01:00
commit a704eefdd5
22 changed files with 259 additions and 110 deletions

View file

@ -26,23 +26,3 @@ limitations under the License.
margin: 0 -10px 0 0; margin: 0 -10px 0 0;
padding: 0 10px 0 0; padding: 0 10px 0 0;
} }
.mx_MainSplit > .mx_ResizeHandle_horizontal:hover {
position: relative;
&::before {
position: absolute;
left: 4px;
top: 50%;
transform: translate(0, -50%);
height: 30%;
width: 4px;
border-radius: 4px;
content: ' ';
background-color: $primary-fg-color;
opacity: 0.8;
}
}

View file

@ -78,23 +78,3 @@ limitations under the License.
*/ */
height: 100%; height: 100%;
} }
.mx_MatrixChat > .mx_ResizeHandle_horizontal:hover {
position: relative;
&::before {
position: absolute;
left: -2px;
top: 50%;
transform: translate(0, -50%);
height: 30%;
width: 4px;
border-radius: 4px;
content: ' ';
background-color: $primary-fg-color;
opacity: 0.8;
}
}

View file

@ -97,7 +97,7 @@ $irc-line-height: $font-18px;
} }
> .mx_EventTile_e2eIcon { > .mx_EventTile_e2eIcon {
position: relative; position: absolute;
right: unset; right: unset;
left: unset; left: unset;
top: 0; top: 0;

View file

@ -43,6 +43,7 @@ import SdkConfig from "./SdkConfig";
import { ensureDMExists } from "./createRoom"; import { ensureDMExists } from "./createRoom";
import { ViewUserPayload } from "./dispatcher/payloads/ViewUserPayload"; import { ViewUserPayload } from "./dispatcher/payloads/ViewUserPayload";
import { Action } from "./dispatcher/actions"; import { Action } from "./dispatcher/actions";
import { EffectiveMembership, getEffectiveMembership } from "./utils/membership";
// XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816 // XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816
interface HTMLInputEvent extends Event { interface HTMLInputEvent extends Event {
@ -730,9 +731,11 @@ export const Commands = [
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const room = cli.getRoom(roomId); const room = cli.getRoom(roomId);
if (!room) return reject(_t("Command failed")); if (!room) return reject(_t("Command failed"));
const member = room.getMember(args);
if (!member || getEffectiveMembership(member.membership) === EffectiveMembership.Leave) {
return reject(_t("Could not find user in room"));
}
const powerLevelEvent = room.currentState.getStateEvents('m.room.power_levels', ''); const powerLevelEvent = room.currentState.getStateEvents('m.room.power_levels', '');
if (!powerLevelEvent.getContent().users[args]) return reject(_t("Could not find user in room"));
return success(cli.setPowerLevel(roomId, userId, powerLevel, powerLevelEvent)); return success(cli.setPowerLevel(roomId, userId, powerLevel, powerLevelEvent));
} }
} }

View file

@ -23,12 +23,11 @@ import React, {
useRef, useRef,
useReducer, useReducer,
Reducer, Reducer,
RefObject,
Dispatch, Dispatch,
} from "react"; } from "react";
import {Key} from "../Keyboard"; import {Key} from "../Keyboard";
import AccessibleButton from "../components/views/elements/AccessibleButton"; import {FocusHandler, Ref} from "./roving/types";
/** /**
* Module to simplify implementing the Roving TabIndex accessibility technique * Module to simplify implementing the Roving TabIndex accessibility technique
@ -45,9 +44,7 @@ import AccessibleButton from "../components/views/elements/AccessibleButton";
const DOCUMENT_POSITION_PRECEDING = 2; const DOCUMENT_POSITION_PRECEDING = 2;
type Ref = RefObject<HTMLElement>; export interface IState {
interface IState {
activeRef: Ref; activeRef: Ref;
refs: Ref[]; refs: Ref[];
} }
@ -156,7 +153,7 @@ interface IProps {
children(renderProps: { children(renderProps: {
onKeyDownHandler(ev: React.KeyboardEvent); onKeyDownHandler(ev: React.KeyboardEvent);
}); });
onKeyDown?(ev: React.KeyboardEvent); onKeyDown?(ev: React.KeyboardEvent, state: IState);
} }
export const RovingTabIndexProvider: React.FC<IProps> = ({children, handleHomeEnd, onKeyDown}) => { export const RovingTabIndexProvider: React.FC<IProps> = ({children, handleHomeEnd, onKeyDown}) => {
@ -193,7 +190,7 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({children, handleHomeEn
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
} else if (onKeyDown) { } else if (onKeyDown) {
return onKeyDown(ev); return onKeyDown(ev, state);
} }
}, [context.state, onKeyDown, handleHomeEnd]); }, [context.state, onKeyDown, handleHomeEnd]);
@ -202,8 +199,6 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({children, handleHomeEn
</RovingTabIndexContext.Provider>; </RovingTabIndexContext.Provider>;
}; };
type FocusHandler = () => void;
// Hook to register a roving tab index // Hook to register a roving tab index
// inputRef parameter specifies the ref to use // inputRef parameter specifies the ref to use
// onFocus should be called when the index gained focus in any manner // onFocus should be called when the index gained focus in any manner
@ -244,28 +239,7 @@ export const useRovingTabIndex = (inputRef: Ref): [FocusHandler, boolean, Ref] =
return [onFocus, isActive, ref]; return [onFocus, isActive, ref];
}; };
interface IRovingTabIndexWrapperProps { // re-export the semantic helper components for simplicity
inputRef?: Ref; export {RovingTabIndexWrapper} from "./roving/RovingTabIndexWrapper";
children(renderProps: { export {RovingAccessibleButton} from "./roving/RovingAccessibleButton";
onFocus: FocusHandler; export {RovingAccessibleTooltipButton} from "./roving/RovingAccessibleTooltipButton";
isActive: boolean;
ref: Ref;
});
}
// Wrapper to allow use of useRovingTabIndex outside of React Functional Components.
export const RovingTabIndexWrapper: React.FC<IRovingTabIndexWrapperProps> = ({children, inputRef}) => {
const [onFocus, isActive, ref] = useRovingTabIndex(inputRef);
return children({onFocus, isActive, ref});
};
interface IRovingAccessibleButtonProps extends React.ComponentProps<typeof AccessibleButton> {
inputRef?: Ref;
}
// Wrapper to allow use of useRovingTabIndex for simple AccessibleButtons outside of React Functional Components.
export const RovingAccessibleButton: React.FC<IRovingAccessibleButtonProps> = ({inputRef, ...props}) => {
const [onFocus, isActive, ref] = useRovingTabIndex(inputRef);
return <AccessibleButton {...props} onFocus={onFocus} inputRef={ref} tabIndex={isActive ? 0 : -1} />;
};

View file

@ -0,0 +1,69 @@
/*
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 from "react";
import {IState, RovingTabIndexProvider} from "./RovingTabIndex";
import {Key} from "../Keyboard";
interface IProps extends Omit<React.HTMLProps<HTMLDivElement>, "onKeyDown"> {
}
// This component implements the Toolbar design pattern from the WAI-ARIA Authoring Practices guidelines.
// https://www.w3.org/TR/wai-aria-practices-1.1/#toolbar
// All buttons passed in children must use RovingTabIndex to set `onFocus`, `isActive`, `ref`
const Toolbar: React.FC<IProps> = ({children, ...props}) => {
const onKeyDown = (ev: React.KeyboardEvent, state: IState) => {
const target = ev.target as HTMLElement;
let handled = true;
switch (ev.key) {
case Key.ARROW_UP:
case Key.ARROW_DOWN:
if (target.hasAttribute('aria-haspopup')) {
target.click();
}
break;
case Key.ARROW_LEFT:
case Key.ARROW_RIGHT:
if (state.refs.length > 0) {
const i = state.refs.findIndex(r => r === state.activeRef);
const delta = ev.key === Key.ARROW_RIGHT ? 1 : -1;
state.refs.slice((i + delta) % state.refs.length)[0].current.focus();
}
break;
// HOME and END are handled by RovingTabIndexProvider
default:
handled = false;
}
if (handled) {
ev.preventDefault();
ev.stopPropagation();
}
};
return <RovingTabIndexProvider handleHomeEnd={true} onKeyDown={onKeyDown}>
{({onKeyDownHandler}) => <div {...props} onKeyDown={onKeyDownHandler} role="toolbar">
{ children }
</div>}
</RovingTabIndexProvider>;
};
export default Toolbar;

View file

@ -0,0 +1,32 @@
/*
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 from "react";
import AccessibleButton from "../../components/views/elements/AccessibleButton";
import {useRovingTabIndex} from "../RovingTabIndex";
import {Ref} from "./types";
interface IProps extends Omit<React.ComponentProps<typeof AccessibleButton>, "onFocus" | "inputRef" | "tabIndex"> {
inputRef?: Ref;
}
// Wrapper to allow use of useRovingTabIndex for simple AccessibleButtons outside of React Functional Components.
export const RovingAccessibleButton: React.FC<IProps> = ({inputRef, ...props}) => {
const [onFocus, isActive, ref] = useRovingTabIndex(inputRef);
return <AccessibleButton {...props} onFocus={onFocus} inputRef={ref} tabIndex={isActive ? 0 : -1} />;
};

View file

@ -0,0 +1,32 @@
/*
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 from "react";
import AccessibleTooltipButton from "../../components/views/elements/AccessibleTooltipButton";
import {useRovingTabIndex} from "../RovingTabIndex";
import {Ref} from "./types";
interface IProps extends Omit<React.ComponentProps<typeof AccessibleTooltipButton>, "onFocus" | "inputRef" | "tabIndex"> {
inputRef?: Ref;
}
// Wrapper to allow use of useRovingTabIndex for simple AccessibleTooltipButtons outside of React Functional Components.
export const RovingAccessibleTooltipButton: React.FC<IProps> = ({inputRef, ...props}) => {
const [onFocus, isActive, ref] = useRovingTabIndex(inputRef);
return <AccessibleTooltipButton {...props} onFocus={onFocus} inputRef={ref} tabIndex={isActive ? 0 : -1} />;
};

View file

@ -0,0 +1,36 @@
/*
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 from "react";
import AccessibleButton from "../../components/views/elements/AccessibleButton";
import {useRovingTabIndex} from "../RovingTabIndex";
import {FocusHandler, Ref} from "./types";
interface IProps {
inputRef?: Ref;
children(renderProps: {
onFocus: FocusHandler;
isActive: boolean;
ref: Ref;
});
}
// Wrapper to allow use of useRovingTabIndex outside of React Functional Components.
export const RovingTabIndexWrapper: React.FC<IProps> = ({children, inputRef}) => {
const [onFocus, isActive, ref] = useRovingTabIndex(inputRef);
return children({onFocus, isActive, ref});
};

View file

@ -0,0 +1,21 @@
/*
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 {RefObject} from "react";
export type Ref = RefObject<HTMLElement>;
export type FocusHandler = () => void;

View file

@ -233,6 +233,9 @@ export class ContextMenu extends React.PureComponent<IProps, IState> {
switch (ev.key) { switch (ev.key) {
case Key.TAB: case Key.TAB:
case Key.ESCAPE: case Key.ESCAPE:
// close on left and right arrows too for when it is a context menu on a <Toolbar />
case Key.ARROW_LEFT:
case Key.ARROW_RIGHT:
this.props.onFinished(); this.props.onFinished();
break; break;
case Key.ARROW_UP: case Key.ARROW_UP:

View file

@ -269,7 +269,7 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
const firstRoom = this.listContainerRef.current.querySelector<HTMLDivElement>(".mx_RoomTile2"); const firstRoom = this.listContainerRef.current.querySelector<HTMLDivElement>(".mx_RoomTile2");
if (firstRoom) { if (firstRoom) {
firstRoom.click(); firstRoom.click();
this.onSearch(""); // clear the search field return true; // to get the field to clear
} }
}; };

View file

@ -675,12 +675,16 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
case 'hide_left_panel': case 'hide_left_panel':
this.setState({ this.setState({
collapseLhs: true, collapseLhs: true,
}, () => {
this.state.resizeNotifier.notifyLeftHandleResized();
}); });
break; break;
case 'focus_room_filter': // for CtrlOrCmd+K to work by expanding the left panel first case 'focus_room_filter': // for CtrlOrCmd+K to work by expanding the left panel first
case 'show_left_panel': case 'show_left_panel':
this.setState({ this.setState({
collapseLhs: false, collapseLhs: false,
}, () => {
this.state.resizeNotifier.notifyLeftHandleResized();
}); });
break; break;
case 'panel_disable': { case 'panel_disable': {

View file

@ -28,8 +28,8 @@ import { Action } from "../../dispatcher/actions";
interface IProps { interface IProps {
onQueryUpdate: (newQuery: string) => void; onQueryUpdate: (newQuery: string) => void;
isMinimized: boolean; isMinimized: boolean;
onVerticalArrow(ev: React.KeyboardEvent); onVerticalArrow(ev: React.KeyboardEvent): void;
onEnter(ev: React.KeyboardEvent); onEnter(ev: React.KeyboardEvent): boolean;
} }
interface IState { interface IState {
@ -107,7 +107,13 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
} else if (ev.key === Key.ARROW_UP || ev.key === Key.ARROW_DOWN) { } else if (ev.key === Key.ARROW_UP || ev.key === Key.ARROW_DOWN) {
this.props.onVerticalArrow(ev); this.props.onVerticalArrow(ev);
} else if (ev.key === Key.ENTER) { } else if (ev.key === Key.ENTER) {
this.props.onEnter(ev); const shouldClear = this.props.onEnter(ev);
if (shouldClear) {
// wrap in set immediate to delay it so that we don't clear the filter & then change room
setImmediate(() => {
this.clearInput();
});
}
} }
}; };

View file

@ -66,7 +66,7 @@ export default class DecoratedRoomAvatar extends React.PureComponent<IProps, ISt
oobData={this.props.oobData} oobData={this.props.oobData}
viewAvatarOnClick={this.props.viewAvatarOnClick} viewAvatarOnClick={this.props.viewAvatarOnClick}
/> />
<RoomTileIcon room={this.props.room} tag={this.props.tag} /> <RoomTileIcon room={this.props.room} />
{badge} {badge}
</div>; </div>;
} }

View file

@ -16,7 +16,7 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import classnames from 'classnames'; import classNames from 'classnames';
import AccessibleButton from "./AccessibleButton"; import AccessibleButton from "./AccessibleButton";
import {IProps} from "./AccessibleButton"; import {IProps} from "./AccessibleButton";
@ -53,15 +53,11 @@ export default class AccessibleTooltipButton extends React.PureComponent<IToolti
}; };
render() { render() {
const {title, tooltip, children, ...props} = this.props; const {title, tooltip, children, tooltipClassName, ...props} = this.props;
const tooltipClassName = classnames(
"mx_AccessibleTooltipButton_tooltip",
this.props.tooltipClassName,
);
const tip = this.state.hover ? <Tooltip const tip = this.state.hover ? <Tooltip
className="mx_AccessibleTooltipButton_container" className="mx_AccessibleTooltipButton_container"
tooltipClassName={tooltipClassName} tooltipClassName={classNames("mx_AccessibleTooltipButton_tooltip", tooltipClassName)}
label={tooltip || title} label={tooltip || title}
/> : <div />; /> : <div />;
return ( return (

View file

@ -25,9 +25,12 @@ import dis from '../../../dispatcher/dispatcher';
import {aboveLeftOf, ContextMenu, ContextMenuButton, useContextMenu} from '../../structures/ContextMenu'; import {aboveLeftOf, ContextMenu, ContextMenuButton, useContextMenu} from '../../structures/ContextMenu';
import { isContentActionable, canEditContent } from '../../../utils/EventUtils'; import { isContentActionable, canEditContent } from '../../../utils/EventUtils';
import RoomContext from "../../../contexts/RoomContext"; import RoomContext from "../../../contexts/RoomContext";
import Toolbar from "../../../accessibility/Toolbar";
import {RovingAccessibleButton, useRovingTabIndex} from "../../../accessibility/RovingTabIndex";
const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFocusChange}) => { const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFocusChange}) => {
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
const [onFocus, isActive, ref] = useRovingTabIndex(button);
useEffect(() => { useEffect(() => {
onFocusChange(menuDisplayed); onFocusChange(menuDisplayed);
}, [onFocusChange, menuDisplayed]); }, [onFocusChange, menuDisplayed]);
@ -57,7 +60,9 @@ const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFo
label={_t("Options")} label={_t("Options")}
onClick={openMenu} onClick={openMenu}
isExpanded={menuDisplayed} isExpanded={menuDisplayed}
inputRef={button} inputRef={ref}
onFocus={onFocus}
tabIndex={isActive ? 0 : -1}
/> />
{ contextMenu } { contextMenu }
@ -66,6 +71,7 @@ const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFo
const ReactButton = ({mxEvent, reactions, onFocusChange}) => { const ReactButton = ({mxEvent, reactions, onFocusChange}) => {
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
const [onFocus, isActive, ref] = useRovingTabIndex(button);
useEffect(() => { useEffect(() => {
onFocusChange(menuDisplayed); onFocusChange(menuDisplayed);
}, [onFocusChange, menuDisplayed]); }, [onFocusChange, menuDisplayed]);
@ -85,7 +91,9 @@ const ReactButton = ({mxEvent, reactions, onFocusChange}) => {
label={_t("React")} label={_t("React")}
onClick={openMenu} onClick={openMenu}
isExpanded={menuDisplayed} isExpanded={menuDisplayed}
inputRef={button} inputRef={ref}
onFocus={onFocus}
tabIndex={isActive ? 0 : -1}
/> />
{ contextMenu } { contextMenu }
@ -148,8 +156,6 @@ export default class MessageActionBar extends React.PureComponent {
}; };
render() { render() {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
let reactButton; let reactButton;
let replyButton; let replyButton;
let editButton; let editButton;
@ -161,7 +167,7 @@ export default class MessageActionBar extends React.PureComponent {
); );
} }
if (this.context.canReply) { if (this.context.canReply) {
replyButton = <AccessibleButton replyButton = <RovingAccessibleButton
className="mx_MessageActionBar_maskButton mx_MessageActionBar_replyButton" className="mx_MessageActionBar_maskButton mx_MessageActionBar_replyButton"
title={_t("Reply")} title={_t("Reply")}
onClick={this.onReplyClick} onClick={this.onReplyClick}
@ -169,7 +175,7 @@ export default class MessageActionBar extends React.PureComponent {
} }
} }
if (canEditContent(this.props.mxEvent)) { if (canEditContent(this.props.mxEvent)) {
editButton = <AccessibleButton editButton = <RovingAccessibleButton
className="mx_MessageActionBar_maskButton mx_MessageActionBar_editButton" className="mx_MessageActionBar_maskButton mx_MessageActionBar_editButton"
title={_t("Edit")} title={_t("Edit")}
onClick={this.onEditClick} onClick={this.onEditClick}
@ -177,7 +183,7 @@ export default class MessageActionBar extends React.PureComponent {
} }
// aria-live=off to not have this read out automatically as navigating around timeline, gets repetitive. // aria-live=off to not have this read out automatically as navigating around timeline, gets repetitive.
return <div className="mx_MessageActionBar" role="toolbar" aria-label={_t("Message Actions")} aria-live="off"> return <Toolbar className="mx_MessageActionBar" aria-label={_t("Message Actions")} aria-live="off">
{reactButton} {reactButton}
{replyButton} {replyButton}
{editButton} {editButton}
@ -188,6 +194,6 @@ export default class MessageActionBar extends React.PureComponent {
permalinkCreator={this.props.permalinkCreator} permalinkCreator={this.props.permalinkCreator}
onFocusChange={this.onFocusChange} onFocusChange={this.onFocusChange}
/> />
</div>; </Toolbar>;
} }
} }

View file

@ -25,7 +25,8 @@ import { UPDATE_EVENT } from "../../../stores/AsyncStore";
import { CSSTransition } from "react-transition-group"; import { CSSTransition } from "react-transition-group";
import RoomListStore from "../../../stores/room-list/RoomListStore2"; import RoomListStore from "../../../stores/room-list/RoomListStore2";
import { DefaultTagID } from "../../../stores/room-list/models"; import { DefaultTagID } from "../../../stores/room-list/models";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import { RovingAccessibleTooltipButton } from "../../../accessibility/RovingTabIndex";
import Toolbar from "../../../accessibility/Toolbar";
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367 // TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367
@ -86,7 +87,7 @@ export default class RoomBreadcrumbs2 extends React.PureComponent<IProps, IState
const roomTags = RoomListStore.instance.getTagsForRoom(r); const roomTags = RoomListStore.instance.getTagsForRoom(r);
const roomTag = roomTags.includes(DefaultTagID.DM) ? DefaultTagID.DM : roomTags[0]; const roomTag = roomTags.includes(DefaultTagID.DM) ? DefaultTagID.DM : roomTags[0];
return ( return (
<AccessibleTooltipButton <RovingAccessibleTooltipButton
className="mx_RoomBreadcrumbs2_crumb" className="mx_RoomBreadcrumbs2_crumb"
key={r.roomId} key={r.roomId}
onClick={() => this.viewRoom(r, i)} onClick={() => this.viewRoom(r, i)}
@ -101,7 +102,7 @@ export default class RoomBreadcrumbs2 extends React.PureComponent<IProps, IState
displayBadge={true} displayBadge={true}
forceCount={true} forceCount={true}
/> />
</AccessibleTooltipButton> </RovingAccessibleTooltipButton>
); );
}); });
@ -112,9 +113,9 @@ export default class RoomBreadcrumbs2 extends React.PureComponent<IProps, IState
appear={true} in={this.state.doAnimation} timeout={640} appear={true} in={this.state.doAnimation} timeout={640}
classNames='mx_RoomBreadcrumbs2' classNames='mx_RoomBreadcrumbs2'
> >
<div className='mx_RoomBreadcrumbs2'> <Toolbar className='mx_RoomBreadcrumbs2'>
{tiles.slice(this.state.skipFirst ? 1 : 0)} {tiles.slice(this.state.skipFirst ? 1 : 0)}
</div> </Toolbar>
</CSSTransition> </CSSTransition>
); );
} else { } else {

View file

@ -49,7 +49,6 @@ function tooltipText(variant: Icon) {
interface IProps { interface IProps {
room: Room; room: Room;
tag: TagID;
} }
interface IState { interface IState {
@ -137,10 +136,11 @@ export default class RoomTileIcon extends React.Component<IProps, IState> {
private calculateIcon(): Icon { private calculateIcon(): Icon {
let icon = Icon.None; let icon = Icon.None;
if (this.props.tag === DefaultTagID.DM && this.props.room.getJoinedMemberCount() === 2) { // We look at the DMRoomMap and not the tag here so that we don't exclude DMs in Favourites
const otherUserId = DMRoomMap.shared().getUserIdForRoomId(this.props.room.roomId);
if (otherUserId && this.props.room.getJoinedMemberCount() === 2) {
// Track presence, if available // Track presence, if available
if (isPresenceEnabled()) { if (isPresenceEnabled()) {
const otherUserId = DMRoomMap.shared().getUserIdForRoomId(this.props.room.roomId);
if (otherUserId) { if (otherUserId) {
this.dmUser = MatrixClientPeg.get().getUser(otherUserId); this.dmUser = MatrixClientPeg.get().getUser(otherUserId);
icon = this.getPresenceIcon(); icon = this.getPresenceIcon();

View file

@ -33,7 +33,6 @@ import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
import RoomListLayoutStore from "./RoomListLayoutStore"; import RoomListLayoutStore from "./RoomListLayoutStore";
import { MarkedExecution } from "../../utils/MarkedExecution"; import { MarkedExecution } from "../../utils/MarkedExecution";
import { AsyncStoreWithClient } from "../AsyncStoreWithClient"; import { AsyncStoreWithClient } from "../AsyncStoreWithClient";
import { MatrixClientPeg } from "../../MatrixClientPeg";
interface IState { interface IState {
tagsEnabled?: boolean; tagsEnabled?: boolean;

View file

@ -158,6 +158,7 @@ export class Algorithm extends EventEmitter {
filterCondition.off(FILTER_CHANGED, this.handleFilterChange.bind(this)); filterCondition.off(FILTER_CHANGED, this.handleFilterChange.bind(this));
if (this.allowedByFilter.has(filterCondition)) { if (this.allowedByFilter.has(filterCondition)) {
this.allowedByFilter.delete(filterCondition); this.allowedByFilter.delete(filterCondition);
this.recalculateFilteredRooms();
// If we removed the last filter, tell consumers that we've "updated" our filtered // If we removed the last filter, tell consumers that we've "updated" our filtered
// view. This will trick them into getting the complete room list. // view. This will trick them into getting the complete room list.

View file

@ -21,6 +21,7 @@ import { EventEmitter } from "events";
import GroupStore from "../../GroupStore"; import GroupStore from "../../GroupStore";
import { arrayHasDiff } from "../../../utils/arrays"; import { arrayHasDiff } from "../../../utils/arrays";
import { IDestroyable } from "../../../utils/IDestroyable"; import { IDestroyable } from "../../../utils/IDestroyable";
import DMRoomMap from "../../../utils/DMRoomMap";
/** /**
* A filter condition for the room list which reveals rooms which * A filter condition for the room list which reveals rooms which
@ -28,6 +29,7 @@ import { IDestroyable } from "../../../utils/IDestroyable";
*/ */
export class CommunityFilterCondition extends EventEmitter implements IFilterCondition, IDestroyable { export class CommunityFilterCondition extends EventEmitter implements IFilterCondition, IDestroyable {
private roomIds: string[] = []; private roomIds: string[] = [];
private userIds: string[] = [];
constructor(private community: Group) { constructor(private community: Group) {
super(); super();
@ -43,15 +45,19 @@ export class CommunityFilterCondition extends EventEmitter implements IFilterCon
} }
public isVisible(room: Room): boolean { public isVisible(room: Room): boolean {
return this.roomIds.includes(room.roomId); return this.roomIds.includes(room.roomId) ||
this.userIds.includes(DMRoomMap.shared().getUserIdForRoomId(room.roomId));
} }
private onStoreUpdate = async (): Promise<any> => { private onStoreUpdate = async (): Promise<any> => {
// We don't actually know if the room list changed for the community, so just // We don't actually know if the room list changed for the community, so just check it again.
// check it again.
const beforeRoomIds = this.roomIds; const beforeRoomIds = this.roomIds;
this.roomIds = (await GroupStore.getGroupRooms(this.community.groupId)).map(r => r.roomId); this.roomIds = (await GroupStore.getGroupRooms(this.community.groupId)).map(r => r.roomId);
if (arrayHasDiff(beforeRoomIds, this.roomIds)) {
const beforeUserIds = this.userIds;
this.userIds = (await GroupStore.getGroupMembers(this.community.groupId)).map(u => u.userId);
if (arrayHasDiff(beforeRoomIds, this.roomIds) || arrayHasDiff(beforeUserIds, this.userIds)) {
this.emit(FILTER_CHANGED); this.emit(FILTER_CHANGED);
} }
}; };