Simple structuring of the room list itself

This covers the larger parts of the design, but doesn't deal with the nuances of hover states, badge sizing, etc.
pull/21833/head
Travis Ralston 2020-06-04 21:21:04 -06:00
parent 4c1bc50649
commit 0c15b2bdb6
8 changed files with 205 additions and 116 deletions

View File

@ -16,7 +16,27 @@ limitations under the License.
@import "../../../../node_modules/react-resizable/css/styles.css";
.mx_RoomList2 .mx_RoomSubList2_labelContainer {
z-index: 12;
background-color: purple;
.mx_RoomSublist2 {
// The sublist is a column of rows, essentially
display: flex;
flex-direction: column;
margin-left: 8px;
margin-top: 12px;
margin-bottom: 12px;
.mx_RoomSublist2_headerContainer {
text-transform: uppercase;
opacity: 0.5;
line-height: $font-16px;
font-size: $font-12px;
padding-bottom: 8px;
}
.mx_RoomSublist2_resizeBox {
// Create another flexbox column for the tiles
display: flex;
flex-direction: column;
overflow: hidden;
}
}

View File

@ -14,5 +14,81 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// Note: the room tile expects to be in a flexbox column container
.mx_RoomTile2 {
width: 100%;
padding-bottom: 12px;
// The tile is also a flexbox row itself
display: flex;
flex-wrap: wrap;
.mx_RoomTile2_avatarContainer {
margin-right: 8px;
}
.mx_RoomTile2_nameContainer {
// Create a new column layout flexbox for the name parts
display: flex;
flex-direction: column;
justify-content: center;
.mx_RoomTile2_name,
.mx_RoomTile2_messagePreview {
margin: 0 2px;
}
// TODO: Ellipsis on the name and preview
.mx_RoomTile2_name {
font-weight: 600;
font-size: $font-14px;
line-height: $font-19px;
}
.mx_RoomTile2_messagePreview {
font-size: $font-13px;
line-height: $font-18px;
color: $roomtile2-preview-color;
}
}
.mx_RoomTile2_badgeContainer {
flex-grow: 1;
// Create another flexbox row because it's super easy to position the badge at
// the end this way.
display: flex;
align-items: center;
justify-content: flex-end;
.mx_RoomTile2_badge {
background-color: $roomtile2-badge-color;
&:not(.mx_RoomTile2_badgeEmpty) {
border-radius: 16px;
font-size: $font-10px;
line-height: $font-14px;
text-align: center;
font-weight: bold;
margin-right: 14px;
color: #fff; // TODO: Variable
// TODO: Confirm padding on counted badges
padding: 2px 5px;
}
&.mx_RoomTile2_badgeEmpty {
width: 6px;
height: 6px;
border-radius: 6px;
margin-right: 18px;
}
&.mx_RoomTile2_badgeHighlight {
// TODO: Use a more specific variable
background-color: $warning-color;
}
}
}
}

View File

@ -172,6 +172,11 @@ $header-divider-color: #91A1C0;
// ********************
// TODO: Update variables for new room list
// TODO: Dark theme
$roomtile2-preview-color: #9e9e9e;
$roomtile2-badge-color: #61708b;
$roomtile-name-color: #61708b;
$roomtile-badge-fg-color: $accent-fg-color;
$roomtile-selected-color: #212121;

View File

@ -96,7 +96,7 @@ const TAG_AESTHETICS: {
defaultHidden: false,
},
[DefaultTagID.DM]: {
sectionLabel: _td("Direct Messages"),
sectionLabel: _td("People"),
isInvite: false,
defaultHidden: false,
addRoomLabel: _td("Start chat"),
@ -200,6 +200,7 @@ export default class RoomList2 extends React.Component<IProps, IState> {
addRoomLabel={aesthetics.addRoomLabel}
isInvite={aesthetics.isInvite}
layout={this.state.layouts.get(orderedTagId)}
showMessagePreviews={orderedTagId === DefaultTagID.DM}
/>
);
}

View File

@ -20,15 +20,13 @@ import * as React from "react";
import { createRef } from "react";
import { Room } from "matrix-js-sdk/src/models/room";
import classNames from 'classnames';
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";
import { ResizableBox, ResizeCallbackData } from "react-resizable";
import { ListLayout } from "../../../stores/room-list/ListLayout";
import { DefaultTagID, TagID } from "../../../stores/room-list/models";
/*******************************************************************
* CAUTION *
@ -43,6 +41,7 @@ interface IProps {
rooms?: Room[];
startAsHidden: boolean;
label: string;
showMessagePreviews: boolean;
onAddRoom?: () => void;
addRoomLabel: string;
isInvite: boolean;
@ -93,7 +92,13 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
if (this.props.rooms) {
for (const room of this.props.rooms) {
tiles.push(<RoomTile2 room={room} key={`room-${room.roomId}`}/>);
tiles.push(
<RoomTile2
room={room}
key={`room-${room.roomId}`}
showMessagePreview={this.props.showMessagePreviews}
/>
);
}
}
@ -101,25 +106,16 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
}
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: Handle badge count
// 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_RoomSublist2_chevron': true,
'mx_RoomSublist2_chevronRight': false, // isCollapsed
'mx_RoomSublist2_chevronDown': true, // !isCollapsed
});
chevron = (<div className={chevronClasses}/>);
}
return (
<RovingTabIndexWrapper inputRef={this.headerButton}>
{({onFocus, isActive, ref}) => {
@ -127,52 +123,55 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
const tabIndex = isActive ? 0 : -1;
// TODO: Collapsed state
let badge;
if (true) { // !isCollapsed
const badgeClasses = classNames({
'mx_RoomSublist2_badge': true,
'mx_RoomSublist2_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>
);
}
}
// TODO: Handle badge count
// let badge;
// if (true) { // !isCollapsed
// const showCount = localStorage.getItem("mx_rls_count") || notifHighlight;
// const badgeClasses = classNames({
// 'mx_RoomSublist2_badge': true,
// 'mx_RoomSublist2_badgeHighlight': notifHighlight,
// 'mx_RoomSublist2_badgeEmpty': !showCount,
// });
// // Wrap the contents in a div and apply styles to the child div so that the browser default outline works
// if (notifCount > 0) {
// const count = <div>{FormattingUtils.formatCount(notifCount)}</div>;
// badge = (
// <AccessibleButton
// tabIndex={tabIndex}
// className={badgeClasses}
// aria-label={_t("Jump to first unread room.")}
// >
// {showCount ? count : null}
// </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_RoomSublist2_addButton"
title={this.props.addRoomLabel || _t("Add room")}
/>
);
}
// TODO: Aux button
// let addRoomButton = null;
// if (!!this.props.onAddRoom) {
// addRoomButton = (
// <AccessibleTooltipButton
// tabIndex={tabIndex}
// onClick={this.onAddRoom}
// className="mx_RoomSublist2_addButton"
// title={this.props.addRoomLabel || _t("Add room")}
// />
// );
// }
// TODO: a11y (see old component)
return (
@ -184,11 +183,8 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
role="treeitem"
aria-level="1"
>
{chevron}
<span>{this.props.label}</span>
</AccessibleButton>
{badge}
{addRoomButton}
</div>
);
}}
@ -243,13 +239,14 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
// TODO: CSS TBD
// TODO: Show N more instead of infinity more?
// TODO: Safely use the same height of a tile, not hardcoded hacks
const moreTileHeightPx = `${layout.tileHeight}px`;
visibleTiles.splice(visibleTiles.length - 1, 1, (
<div
onClick={this.onShowAllClick}
style={{height: '34px', lineHeight: '34px', backgroundColor: 'green', cursor: 'pointer'}}
style={{height: moreTileHeightPx, lineHeight: moreTileHeightPx, backgroundColor: 'transparent', cursor: 'pointer'}}
key='showall'
>
{_t("Show %(n)s more rooms", {n: numMissing})}
{_t("Show %(n)s more", {n: numMissing})}
</div>
));
}

View File

@ -51,6 +51,7 @@ enum NotificationColor {
interface IProps {
room: Room;
showMessagePreview: boolean;
// TODO: Allow falsifying counts (for invites and stuff)
// TODO: Transparency? Was this ever used?
@ -192,33 +193,22 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
// TODO: a11y proper
// TODO: Render more than bare minimum
const hasBadge = this.state.notificationState.color > NotificationColor.Bold;
const isUnread = this.state.notificationState.color > NotificationColor.None;
const classes = classNames({
'mx_RoomTile2': true,
// 'mx_RoomTile_selected': this.state.selected,
'mx_RoomTile2_unread': isUnread,
'mx_RoomTile2_unreadNotify': this.state.notificationState.color >= NotificationColor.Grey,
'mx_RoomTile2_highlight': this.state.notificationState.color >= NotificationColor.Red,
'mx_RoomTile2_invited': this.roomIsInvite,
// 'mx_RoomTile_menuDisplayed': isMenuDisplayed,
'mx_RoomTile2_noBadges': !hasBadge,
// 'mx_RoomTile_transparent': this.props.transparent,
// 'mx_RoomTile_hasSubtext': subtext && !this.props.collapsed,
});
const avatarClasses = classNames({
'mx_RoomTile2_avatar': true,
});
let badge;
const hasBadge = this.state.notificationState.color > NotificationColor.Bold;
if (hasBadge) {
const hasNotif = this.state.notificationState.color >= NotificationColor.Red;
const isEmptyBadge = !localStorage.getItem("mx_rl_rt_badgeCount");
const badgeClasses = classNames({
'mx_RoomTile2_badge': true,
'mx_RoomTile2_badgeButton': false, // this.state.badgeHover || isMenuDisplayed
'mx_RoomTile2_badgeHighlight': hasNotif,
'mx_RoomTile2_badgeEmpty': isEmptyBadge,
});
badge = <div className={badgeClasses}>{this.state.notificationState.symbol}</div>;
const symbol = this.state.notificationState.symbol;
badge = <div className={badgeClasses}>{isEmptyBadge ? null : symbol}</div>;
}
// TODO: the original RoomTile uses state for the room name. Do we need to?
@ -226,20 +216,21 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
if (typeof name !== 'string') name = '';
name = name.replace(":", ":\u200b"); // add a zero-width space to allow linewrapping after the colon
const nameClasses = classNames({
'mx_RoomTile2_name': true,
'mx_RoomTile2_invite': this.roomIsInvite,
'mx_RoomTile2_badgeShown': hasBadge,
});
// TODO: Support collapsed state properly
let tooltip = null;
if (false) { // isCollapsed
if (this.state.hover) {
tooltip = <Tooltip className="mx_RoomTile2_tooltip" label={this.props.room.name} />
}
// TODO: Tooltip?
let messagePreview = null;
if (this.props.showMessagePreview) {
// TODO: Actually get the real message preview from state
messagePreview = <div className="mx_RoomTile2_messagePreview">I just ate a pie.</div>;
}
const nameClasses = classNames({
"mx_RoomTile2_name": true,
"mx_RoomTile2_nameWithPreview": !!messagePreview,
});
const avatarSize = 32;
return (
<React.Fragment>
<RovingTabIndexWrapper inputRef={this.roomTile}>
@ -254,20 +245,18 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
onClick={this.onTileClick}
role="treeitem"
>
<div className={avatarClasses}>
<div className="mx_RoomTile2_avatarContainer">
<RoomAvatar room={this.props.room} width={24} height={24}/>
</div>
<div className="mx_RoomTile2_avatarContainer">
<RoomAvatar room={this.props.room} width={avatarSize} height={avatarSize}/>
</div>
<div className="mx_RoomTile2_nameContainer">
<div className="mx_RoomTile2_labelContainer">
<div title={name} className={nameClasses} tabIndex={-1} dir="auto">
{name}
</div>
<div title={name} className={nameClasses} tabIndex={-1} dir="auto">
{name}
</div>
{messagePreview}
</div>
<div className="mx_RoomTile2_badgeContainer">
{badge}
</div>
{tooltip}
</AccessibleButton>
}
</RovingTabIndexWrapper>

View File

@ -1090,6 +1090,7 @@
"Low priority": "Low priority",
"Historical": "Historical",
"System Alerts": "System Alerts",
"People": "People",
"This room": "This room",
"Joining room …": "Joining room …",
"Loading …": "Loading …",
@ -1133,10 +1134,7 @@
"Securely back up your keys to avoid losing them. <a>Learn more.</a>": "Securely back up your keys to avoid losing them. <a>Learn more.</a>",
"Not now": "Not now",
"Don't ask me again": "Don't ask me again",
"Jump to first unread room.": "Jump to first unread room.",
"Jump to first invite.": "Jump to first invite.",
"Add room": "Add room",
"Show %(n)s more rooms": "Show %(n)s more rooms",
"Show %(n)s more": "Show %(n)s more",
"Options": "Options",
"%(count)s unread messages including mentions.|other": "%(count)s unread messages including mentions.",
"%(count)s unread messages including mentions.|one": "1 unread mention.",
@ -2017,6 +2015,9 @@
"Sent messages will be stored until your connection has returned.": "Sent messages will be stored until your connection has returned.",
"Active call": "Active call",
"There's no one else here! Would you like to <inviteText>invite others</inviteText> or <nowarnText>stop warning about the empty room</nowarnText>?": "There's no one else here! Would you like to <inviteText>invite others</inviteText> or <nowarnText>stop warning about the empty room</nowarnText>?",
"Jump to first unread room.": "Jump to first unread room.",
"Jump to first invite.": "Jump to first invite.",
"Add room": "Add room",
"You seem to be uploading files, are you sure you want to quit?": "You seem to be uploading files, are you sure you want to quit?",
"You seem to be in a call, are you sure you want to quit?": "You seem to be in a call, are you sure you want to quit?",
"Search failed": "Search failed",

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
const TILE_HEIGHT_PX = 34;
const TILE_HEIGHT_PX = 44;
interface ISerializedListLayout {
numTiles: number;