Merge pull request #6228 from matrix-org/gsouquet/fix-17673

pull/21833/head
Germain 2021-06-23 09:08:43 +01:00 committed by GitHub
commit 35b8f79fbd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 81 additions and 45 deletions

View File

@ -39,6 +39,9 @@ import ProgressBar from "../elements/ProgressBar";
import {SpaceFeedbackPrompt} from "../../structures/SpaceRoomView"; import {SpaceFeedbackPrompt} from "../../structures/SpaceRoomView";
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar"; import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
import QueryMatcher from "../../../autocomplete/QueryMatcher"; import QueryMatcher from "../../../autocomplete/QueryMatcher";
import TruncatedList from "../elements/TruncatedList";
import EntityTile from "../rooms/EntityTile";
import BaseAvatar from "../avatars/BaseAvatar";
interface IProps extends IDialogProps { interface IProps extends IDialogProps {
matrixClient: MatrixClient; matrixClient: MatrixClient;
@ -204,6 +207,17 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
setSelectedToAdd(new Set(selectedToAdd)); setSelectedToAdd(new Set(selectedToAdd));
} : null; } : null;
const [truncateAt, setTruncateAt] = useState(20);
function overflowTile(overflowCount, totalCount) {
const text = _t("and %(count)s others...", { count: overflowCount });
return (
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
} name={text} presenceState="online" suppressOnHover={true}
onClick={() => setTruncateAt(totalCount)} />
);
}
return <div className="mx_AddExistingToSpace"> return <div className="mx_AddExistingToSpace">
<SearchBox <SearchBox
className="mx_textinput_icon mx_textinput_search" className="mx_textinput_icon mx_textinput_search"
@ -216,16 +230,21 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
{ rooms.length > 0 ? ( { rooms.length > 0 ? (
<div className="mx_AddExistingToSpace_section"> <div className="mx_AddExistingToSpace_section">
<h3>{ _t("Rooms") }</h3> <h3>{ _t("Rooms") }</h3>
{ rooms.map(room => { <TruncatedList
return <Entry truncateAt={truncateAt}
createOverflowElement={overflowTile}
getChildren={(start, end) => rooms.slice(start, end).map(room =>
<Entry
key={room.roomId} key={room.roomId}
room={room} room={room}
checked={selectedToAdd.has(room)} checked={selectedToAdd.has(room)}
onChange={onChange ? (checked) => { onChange={onChange ? (checked) => {
onChange(checked, room); onChange(checked, room);
} : null} } : null}
/>; />,
}) } )}
getChildCount={() => rooms.length}
/>
</div> </div>
) : undefined } ) : undefined }

View File

@ -40,6 +40,9 @@ import NotificationBadge from "../rooms/NotificationBadge";
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
import { sortRooms } from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm"; import { sortRooms } from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm";
import QueryMatcher from "../../../autocomplete/QueryMatcher"; import QueryMatcher from "../../../autocomplete/QueryMatcher";
import TruncatedList from "../elements/TruncatedList";
import EntityTile from "../rooms/EntityTile";
import BaseAvatar from "../avatars/BaseAvatar";
const AVATAR_SIZE = 30; const AVATAR_SIZE = 30;
@ -196,6 +199,17 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
}).match(lcQuery); }).match(lcQuery);
} }
const [truncateAt, setTruncateAt] = useState(20);
function overflowTile(overflowCount, totalCount) {
const text = _t("and %(count)s others...", { count: overflowCount });
return (
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
} name={text} presenceState="online" suppressOnHover={true}
onClick={() => setTruncateAt(totalCount)} />
);
}
return <BaseDialog return <BaseDialog
title={_t("Forward message")} title={_t("Forward message")}
className="mx_ForwardDialog" className="mx_ForwardDialog"
@ -228,7 +242,10 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
<AutoHideScrollbar className="mx_ForwardList_content"> <AutoHideScrollbar className="mx_ForwardList_content">
{ rooms.length > 0 ? ( { rooms.length > 0 ? (
<div className="mx_ForwardList_results"> <div className="mx_ForwardList_results">
{ rooms.map(room => <TruncatedList
truncateAt={truncateAt}
createOverflowElement={overflowTile}
getChildren={(start, end) => rooms.slice(start, end).map(room =>
<Entry <Entry
key={room.roomId} key={room.roomId}
room={room} room={room}
@ -236,7 +253,9 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
matrixClient={cli} matrixClient={cli}
onFinished={onFinished} onFinished={onFinished}
/>, />,
) } )}
getChildCount={() => rooms.length}
/>
</div> </div>
) : <span className="mx_ForwardList_noResults"> ) : <span className="mx_ForwardList_noResults">
{ _t("No results") } { _t("No results") }

View File

@ -16,31 +16,29 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
@replaceableComponent("views.elements.TruncatedList") interface IProps {
export default class TruncatedList extends React.Component {
static propTypes = {
// The number of elements to show before truncating. If negative, no truncation is done. // The number of elements to show before truncating. If negative, no truncation is done.
truncateAt: PropTypes.number, truncateAt?: number;
// The className to apply to the wrapping div // The className to apply to the wrapping div
className: PropTypes.string, className?: string;
// A function that returns the children to be rendered into the element. // A function that returns the children to be rendered into the element.
// function getChildren(start: number, end: number): Array<React.Node>
// The start element is included, the end is not (as in `slice`). // The start element is included, the end is not (as in `slice`).
// If omitted, the React child elements will be used. This parameter can be used // If omitted, the React child elements will be used. This parameter can be used
// to avoid creating unnecessary React elements. // to avoid creating unnecessary React elements.
getChildren: PropTypes.func, getChildren?: (start: number, end: number) => Array<React.ReactNode>;
// A function that should return the total number of child element available. // A function that should return the total number of child element available.
// Required if getChildren is supplied. // Required if getChildren is supplied.
getChildCount: PropTypes.func, getChildCount?: () => number;
// A function which will be invoked when an overflow element is required. // A function which will be invoked when an overflow element is required.
// This will be inserted after the children. // This will be inserted after the children.
createOverflowElement: PropTypes.func, createOverflowElement?: (overflowCount: number, totalCount: number) => React.ReactNode;
}; }
@replaceableComponent("views.elements.TruncatedList")
export default class TruncatedList extends React.Component<IProps> {
static defaultProps ={ static defaultProps ={
truncateAt: 2, truncateAt: 2,
createOverflowElement(overflowCount, totalCount) { createOverflowElement(overflowCount, totalCount) {
@ -50,7 +48,7 @@ export default class TruncatedList extends React.Component {
}, },
}; };
_getChildren(start, end) { private getChildren(start: number, end: number): Array<React.ReactNode> {
if (this.props.getChildren && this.props.getChildCount) { if (this.props.getChildren && this.props.getChildCount) {
return this.props.getChildren(start, end); return this.props.getChildren(start, end);
} else { } else {
@ -63,7 +61,7 @@ export default class TruncatedList extends React.Component {
} }
} }
_getChildCount() { private getChildCount(): number {
if (this.props.getChildren && this.props.getChildCount) { if (this.props.getChildren && this.props.getChildCount) {
return this.props.getChildCount(); return this.props.getChildCount();
} else { } else {
@ -73,10 +71,10 @@ export default class TruncatedList extends React.Component {
} }
} }
render() { public render() {
let overflowNode = null; let overflowNode = null;
const totalChildren = this._getChildCount(); const totalChildren = this.getChildCount();
let upperBound = totalChildren; let upperBound = totalChildren;
if (this.props.truncateAt >= 0) { if (this.props.truncateAt >= 0) {
const overflowCount = totalChildren - this.props.truncateAt; const overflowCount = totalChildren - this.props.truncateAt;
@ -87,7 +85,7 @@ export default class TruncatedList extends React.Component {
upperBound = this.props.truncateAt; upperBound = this.props.truncateAt;
} }
} }
const childNodes = this._getChildren(0, upperBound); const childNodes = this.getChildren(0, upperBound);
return ( return (
<div className={this.props.className}> <div className={this.props.className}>