Bare minimum for rendering a room list

This is non-interactive and missing most features users will expect to have
pull/21833/head
Travis Ralston 2020-05-08 12:53:05 -06:00
parent e88788f4e9
commit cb3d17ee28
3 changed files with 287 additions and 14 deletions

View File

@ -156,7 +156,7 @@ export default class RoomList2 extends React.Component<IProps, IState> {
const sublist = this.sublistRefs[tagId];
if (sublist) sublist.current.setHeight(height);
// TODO: Check overflow
// TODO: Check overflow (see old impl)
// Don't store a height for collapsed sublists
if (!this.sublistCollapseStates[tagId]) {
@ -178,6 +178,7 @@ export default class RoomList2 extends React.Component<IProps, IState> {
}
private collectSublistRef(tagId: string, ref: React.RefObject<RoomSublist2>) {
// TODO: Is this needed?
if (!ref) {
delete this.sublistRefs[tagId];
} else {
@ -206,11 +207,9 @@ export default class RoomList2 extends React.Component<IProps, IState> {
const aesthetics: ITagAesthetics = TAG_AESTHETICS[orderedTagId];
if (!aesthetics) throw new Error(`Tag ${orderedTagId} does not have aesthetics`);
const onAddRoomFn = () => {
if (!aesthetics.onAddRoom) return;
aesthetics.onAddRoom(dis);
};
const onAddRoomFn = aesthetics.onAddRoom ? () => aesthetics.onAddRoom(dis) : null;
components.push(<RoomSublist2
key={`sublist-${orderedTagId}`}
forRooms={true}
rooms={orderedRooms}
startAsHidden={aesthetics.defaultHidden}

View File

@ -17,7 +17,17 @@ limitations under the License.
*/
import * as React from "react";
import { createRef } from "react";
import { Room } from "matrix-js-sdk/src/models/room";
import classNames from 'classnames';
import IndicatorScrollbar from "../../structures/IndicatorScrollbar";
import * as RoomNotifs from '../../../RoomNotifs';
import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex";
import { _t } from "../../../languageHandler";
import AccessibleButton from "../../views/elements/AccessibleButton";
import AccessibleTooltipButton from "../../views/elements/AccessibleTooltipButton";
import * as FormattingUtils from '../../../utils/FormattingUtils';
import RoomTile2 from "./RoomTile2";
interface IProps {
forRooms: boolean;
@ -40,21 +50,167 @@ interface IProps {
interface IState {
}
// TODO: Actually write stub
// TODO: Finish stub
export default class RoomSublist2 extends React.Component<IProps, IState> {
private headerButton = createRef();
public setHeight(size: number) {
// TODO: Do a thing
}
public render() {
// TODO: Proper rendering
private hasTiles(): boolean {
return this.numTiles > 0;
}
private get numTiles(): number {
// TODO: Account for group invites
return (this.props.rooms || []).length;
}
private onAddRoom = (e) => {
e.stopPropagation();
if (this.props.onAddRoom) this.props.onAddRoom();
};
private renderTiles(): React.ReactElement[] {
const tiles: React.ReactElement[] = [];
if (this.props.rooms) {
for (const room of this.props.rooms) {
tiles.push(<RoomTile2 room={room} key={`room-${room.roomId}`}/>);
}
}
return tiles;
}
private renderHeader(): React.ReactElement {
const notifications = !this.props.isInvite
? RoomNotifs.aggregateNotificationCount(this.props.rooms)
: {count: 0, highlight: true};
const notifCount = notifications.count;
const notifHighlight = notifications.highlight;
// TODO: Title on collapsed
// TODO: Incoming call box
let chevron = null;
if (this.hasTiles()) {
const chevronClasses = classNames({
'mx_RoomSubList_chevron': true,
'mx_RoomSubList_chevronRight': false, // isCollapsed
'mx_RoomSubList_chevronDown': true, // !isCollapsed
});
chevron = (<div className={chevronClasses}/>);
}
const rooms = this.props.rooms.map(r => (
<div key={r.roomId}>{r.name} ({r.roomId})</div>
));
return (
<div>
<h4>{this.props.label}</h4>
{rooms}
<RovingTabIndexWrapper inputRef={this.headerButton}>
{({onFocus, isActive, ref}) => {
const tabIndex = isActive ? 0 : -1;
let badge;
if (true) { // !isCollapsed
const badgeClasses = classNames({
'mx_RoomSubList_badge': true,
'mx_RoomSubList_badgeHighlight': notifHighlight,
});
// Wrap the contents in a div and apply styles to the child div so that the browser default outline works
if (notifCount > 0) {
badge = (
<AccessibleButton
tabIndex={tabIndex}
className={badgeClasses}
aria-label={_t("Jump to first unread room.")}
>
<div>
{FormattingUtils.formatCount(notifCount)}
</div>
</AccessibleButton>
);
} else if (this.props.isInvite && this.hasTiles()) {
// Render the `!` badge for invites
badge = (
<AccessibleButton
tabIndex={tabIndex}
className={badgeClasses}
aria-label={_t("Jump to first invite.")}
>
<div>
{FormattingUtils.formatCount(this.numTiles)}
</div>
</AccessibleButton>
);
}
}
let addRoomButton = null;
if (!!this.props.onAddRoom) {
addRoomButton = (
<AccessibleTooltipButton
tabIndex={tabIndex}
onClick={this.onAddRoom}
className="mx_RoomSubList_addRoom"
title={this.props.addRoomLabel || _t("Add room")}
/>
);
}
// TODO: a11y
return (
<div className={"mx_RoomSubList_labelContainer"}>
<AccessibleButton
inputRef={ref}
tabIndex={tabIndex}
className={"mx_RoomSubList_label"}
role="treeitem"
aria-level="1"
>
{chevron}
<span>{this.props.label}</span>
</AccessibleButton>
{badge}
{addRoomButton}
</div>
);
}}
</RovingTabIndexWrapper>
);
}
public render(): React.ReactElement {
// TODO: Proper rendering
// TODO: Error boundary
const tiles = this.renderTiles();
const classes = classNames({
// TODO: Proper collapse support
'mx_RoomSubList': true,
'mx_RoomSubList_hidden': false, // len && isCollapsed
'mx_RoomSubList_nonEmpty': this.hasTiles(), // len && !isCollapsed
});
let content = null;
if (tiles.length > 0) {
// TODO: Lazy list rendering
// TODO: Whatever scrolling magic needs to happen here
content = (
<IndicatorScrollbar className='mx_RoomSubList_scroll'>
{tiles}
</IndicatorScrollbar>
)
}
// TODO: onKeyDown support
return (
<div
className={classes}
role="group"
aria-label={this.props.label}
>
{this.renderHeader()}
{content}
</div>
);
}

View File

@ -0,0 +1,118 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 New Vector Ltd
Copyright 2018 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2019, 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, { createRef } from "react";
import { Room } from "matrix-js-sdk/src/models/room";
import classNames from "classnames";
import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex";
import AccessibleButton from "../../views/elements/AccessibleButton";
import RoomAvatar from "../../views/avatars/RoomAvatar";
interface IProps {
room: Room;
// TODO: Allow faslifying counts (for invites and stuff)
// TODO: Transparency?
// TODO: Incoming call?
// TODO: onClick
}
interface IState {
}
// TODO: Finish stub
export default class RoomTile2 extends React.Component<IProps, IState> {
private roomTile = createRef();
// TODO: Custom status
// TODO: Lock icon
// TODO: DM indicator
// TODO: Presence indicator
// TODO: e2e shields
// TODO: Handle changes to room aesthetics (name, join rules, etc)
// TODO: scrollIntoView?
// TODO: hover, badge, etc
// TODO: isSelected for hover effects
// TODO: Context menu
// TODO: a11y
public render(): React.ReactElement {
// TODO: Collapsed state
// TODO: Invites
// TODO: a11y proper
// TODO: Render more than bare minimum
const classes = classNames({
'mx_RoomTile': true,
// 'mx_RoomTile_selected': this.state.selected,
// 'mx_RoomTile_unread': this.props.unread,
// 'mx_RoomTile_unreadNotify': notifBadges,
// 'mx_RoomTile_highlight': mentionBadges,
// 'mx_RoomTile_invited': isInvite,
// 'mx_RoomTile_menuDisplayed': isMenuDisplayed,
'mx_RoomTile_noBadges': true, // !badges
// 'mx_RoomTile_transparent': this.props.transparent,
// 'mx_RoomTile_hasSubtext': subtext && !this.props.collapsed,
});
const avatarClasses = classNames({
'mx_RoomTile_avatar': true,
});
// TODO: the original RoomTile uses state for the room name. Do we need to?
let name = this.props.room.name;
if (typeof name !== 'string') name = '';
name = name.replace(":", ":\u200b"); // add a zero-width space to allow linewrapping after the colon
const nameClasses = classNames({
'mx_RoomTile_name': true,
'mx_RoomTile_invite': false,
'mx_RoomTile_badgeShown': false,
});
return (
<React.Fragment>
<RovingTabIndexWrapper inputRef={this.roomTile}>
{({onFocus, isActive, ref}) =>
<AccessibleButton
onFocus={onFocus}
tabIndex={isActive ? 0 : -1}
inputRef={ref}
className={classes}
role="treeitem"
>
<div className={avatarClasses}>
<div className="mx_RoomTile_avatar_container">
<RoomAvatar room={this.props.room} width={24} height={24}/>
</div>
</div>
<div className="mx_RoomTile_nameContainer">
<div className="mx_RoomTile_labelContainer">
<div title={name} className={nameClasses} tabIndex={-1} dir="auto">
{name}
</div>
</div>
</div>
</AccessibleButton>
}
</RovingTabIndexWrapper>
</React.Fragment>
);
}
}