2020-03-20 23:38:20 +03:00
|
|
|
/*
|
|
|
|
Copyright 2015, 2016 OpenMarket Ltd
|
|
|
|
Copyright 2017, 2018 Vector Creations Ltd
|
|
|
|
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 * as React from "react";
|
2020-04-30 22:21:50 +03:00
|
|
|
import { _t, _td } from "../../../languageHandler";
|
2020-03-20 23:38:20 +03:00
|
|
|
import { Layout } from '../../../resizer/distributors/roomsublist2';
|
|
|
|
import { RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex";
|
|
|
|
import { ResizeNotifier } from "../../../utils/ResizeNotifier";
|
2020-06-04 00:07:12 +03:00
|
|
|
import RoomListStore, { LISTS_UPDATE_EVENT, RoomListStore2 } from "../../../stores/room-list/RoomListStore2";
|
2020-04-30 22:21:50 +03:00
|
|
|
import { ITagMap } from "../../../stores/room-list/algorithms/models";
|
|
|
|
import { DefaultTagID, TagID } from "../../../stores/room-list/models";
|
|
|
|
import { Dispatcher } from "flux";
|
2020-05-14 22:45:17 +03:00
|
|
|
import dis from "../../../dispatcher/dispatcher";
|
2020-05-12 01:12:45 +03:00
|
|
|
import RoomSublist2 from "./RoomSublist2";
|
2020-05-14 22:45:17 +03:00
|
|
|
import { ActionPayload } from "../../../dispatcher/payloads";
|
2020-05-29 16:59:06 +03:00
|
|
|
import { NameFilterCondition } from "../../../stores/room-list/filters/NameFilterCondition";
|
2020-03-20 23:38:20 +03:00
|
|
|
|
2020-05-14 21:53:00 +03:00
|
|
|
/*******************************************************************
|
|
|
|
* CAUTION *
|
|
|
|
*******************************************************************
|
|
|
|
* This is a work in progress implementation and isn't complete or *
|
|
|
|
* even useful as a component. Please avoid using it until this *
|
|
|
|
* warning disappears. *
|
2020-05-21 20:53:16 +03:00
|
|
|
*******************************************************************/
|
2020-05-14 21:53:00 +03:00
|
|
|
|
2020-03-20 23:38:20 +03:00
|
|
|
interface IProps {
|
|
|
|
onKeyDown: (ev: React.KeyboardEvent) => void;
|
|
|
|
onFocus: (ev: React.FocusEvent) => void;
|
|
|
|
onBlur: (ev: React.FocusEvent) => void;
|
|
|
|
resizeNotifier: ResizeNotifier;
|
|
|
|
collapsed: boolean;
|
|
|
|
searchFilter: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface IState {
|
2020-04-30 22:21:50 +03:00
|
|
|
sublists: ITagMap;
|
2020-06-04 00:07:12 +03:00
|
|
|
heights: Map<TagID, number>;
|
2020-03-20 23:38:20 +03:00
|
|
|
}
|
|
|
|
|
2020-04-30 22:21:50 +03:00
|
|
|
const TAG_ORDER: TagID[] = [
|
|
|
|
// -- Community Invites Placeholder --
|
|
|
|
|
|
|
|
DefaultTagID.Invite,
|
|
|
|
DefaultTagID.Favourite,
|
|
|
|
DefaultTagID.DM,
|
|
|
|
DefaultTagID.Untagged,
|
|
|
|
|
|
|
|
// -- Custom Tags Placeholder --
|
|
|
|
|
|
|
|
DefaultTagID.LowPriority,
|
|
|
|
DefaultTagID.ServerNotice,
|
|
|
|
DefaultTagID.Archived,
|
|
|
|
];
|
|
|
|
const COMMUNITY_TAGS_BEFORE_TAG = DefaultTagID.Invite;
|
|
|
|
const CUSTOM_TAGS_BEFORE_TAG = DefaultTagID.LowPriority;
|
|
|
|
const ALWAYS_VISIBLE_TAGS: TagID[] = [
|
|
|
|
DefaultTagID.DM,
|
|
|
|
DefaultTagID.Untagged,
|
|
|
|
];
|
|
|
|
|
|
|
|
interface ITagAesthetics {
|
|
|
|
sectionLabel: string;
|
|
|
|
addRoomLabel?: string;
|
|
|
|
onAddRoom?: (dispatcher: Dispatcher<ActionPayload>) => void;
|
|
|
|
isInvite: boolean;
|
|
|
|
defaultHidden: boolean;
|
2020-03-20 23:38:20 +03:00
|
|
|
}
|
|
|
|
|
2020-04-30 22:21:50 +03:00
|
|
|
const TAG_AESTHETICS: {
|
|
|
|
// @ts-ignore - TS wants this to be a string but we know better
|
|
|
|
[tagId: TagID]: ITagAesthetics;
|
|
|
|
} = {
|
|
|
|
[DefaultTagID.Invite]: {
|
|
|
|
sectionLabel: _td("Invites"),
|
|
|
|
isInvite: true,
|
|
|
|
defaultHidden: false,
|
|
|
|
},
|
|
|
|
[DefaultTagID.Favourite]: {
|
|
|
|
sectionLabel: _td("Favourites"),
|
|
|
|
isInvite: false,
|
|
|
|
defaultHidden: false,
|
|
|
|
},
|
|
|
|
[DefaultTagID.DM]: {
|
|
|
|
sectionLabel: _td("Direct Messages"),
|
|
|
|
isInvite: false,
|
|
|
|
defaultHidden: false,
|
|
|
|
addRoomLabel: _td("Start chat"),
|
|
|
|
onAddRoom: (dispatcher: Dispatcher<ActionPayload>) => dispatcher.dispatch({action: 'view_create_chat'}),
|
|
|
|
},
|
|
|
|
[DefaultTagID.Untagged]: {
|
|
|
|
sectionLabel: _td("Rooms"),
|
|
|
|
isInvite: false,
|
|
|
|
defaultHidden: false,
|
|
|
|
addRoomLabel: _td("Create room"),
|
|
|
|
onAddRoom: (dispatcher: Dispatcher<ActionPayload>) => dispatcher.dispatch({action: 'view_create_room'}),
|
|
|
|
},
|
|
|
|
[DefaultTagID.LowPriority]: {
|
|
|
|
sectionLabel: _td("Low priority"),
|
|
|
|
isInvite: false,
|
|
|
|
defaultHidden: false,
|
|
|
|
},
|
|
|
|
[DefaultTagID.ServerNotice]: {
|
|
|
|
sectionLabel: _td("System Alerts"),
|
|
|
|
isInvite: false,
|
|
|
|
defaultHidden: false,
|
|
|
|
},
|
|
|
|
[DefaultTagID.Archived]: {
|
|
|
|
sectionLabel: _td("Historical"),
|
|
|
|
isInvite: false,
|
|
|
|
defaultHidden: true,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2020-03-20 23:38:20 +03:00
|
|
|
export default class RoomList2 extends React.Component<IProps, IState> {
|
|
|
|
private sublistRefs: { [tagId: string]: React.RefObject<RoomSublist2> } = {};
|
|
|
|
private sublistSizes: { [tagId: string]: number } = {};
|
|
|
|
private sublistCollapseStates: { [tagId: string]: boolean } = {};
|
|
|
|
private unfilteredLayout: Layout;
|
|
|
|
private filteredLayout: Layout;
|
2020-05-29 16:59:06 +03:00
|
|
|
private searchFilter: NameFilterCondition = new NameFilterCondition();
|
2020-06-04 00:07:12 +03:00
|
|
|
private currentTagResize: TagID = null;
|
2020-03-20 23:38:20 +03:00
|
|
|
|
|
|
|
constructor(props: IProps) {
|
|
|
|
super(props);
|
|
|
|
|
2020-06-04 00:07:12 +03:00
|
|
|
this.state = {
|
|
|
|
sublists: {},
|
|
|
|
heights: new Map<TagID, number>(),
|
|
|
|
};
|
2020-03-20 23:38:20 +03:00
|
|
|
this.loadSublistSizes();
|
|
|
|
}
|
|
|
|
|
2020-05-29 16:59:06 +03:00
|
|
|
public componentDidUpdate(prevProps: Readonly<IProps>): void {
|
|
|
|
if (prevProps.searchFilter !== this.props.searchFilter) {
|
|
|
|
const hadSearch = !!this.searchFilter.search.trim();
|
|
|
|
const haveSearch = !!this.props.searchFilter.trim();
|
|
|
|
this.searchFilter.search = this.props.searchFilter;
|
|
|
|
if (!hadSearch && haveSearch) {
|
|
|
|
// started a new filter - add the condition
|
|
|
|
RoomListStore.instance.addFilter(this.searchFilter);
|
|
|
|
} else if (hadSearch && !haveSearch) {
|
|
|
|
// cleared a filter - remove the condition
|
|
|
|
RoomListStore.instance.removeFilter(this.searchFilter);
|
|
|
|
} // else the filter hasn't changed enough for us to care here
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-20 23:38:20 +03:00
|
|
|
public componentDidMount(): void {
|
2020-06-04 00:07:12 +03:00
|
|
|
RoomListStore.instance.on(LISTS_UPDATE_EVENT, (store: RoomListStore2) => {
|
|
|
|
const newLists = store.orderedLists;
|
|
|
|
console.log("new lists", newLists);
|
|
|
|
|
|
|
|
const heightMap = new Map<TagID, number>();
|
|
|
|
for (const tagId of Object.keys(newLists)) {
|
|
|
|
heightMap.set(tagId, store.layout.getPixelHeight(tagId));
|
|
|
|
}
|
|
|
|
|
|
|
|
this.setState({sublists: newLists, heights: heightMap});
|
2020-03-20 23:38:20 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private loadSublistSizes() {
|
|
|
|
const sizesJson = window.localStorage.getItem("mx_roomlist_sizes");
|
|
|
|
if (sizesJson) this.sublistSizes = JSON.parse(sizesJson);
|
|
|
|
|
|
|
|
const collapsedJson = window.localStorage.getItem("mx_roomlist_collapsed");
|
|
|
|
if (collapsedJson) this.sublistCollapseStates = JSON.parse(collapsedJson);
|
|
|
|
}
|
|
|
|
|
|
|
|
private saveSublistSizes() {
|
|
|
|
window.localStorage.setItem("mx_roomlist_sizes", JSON.stringify(this.sublistSizes));
|
|
|
|
window.localStorage.setItem("mx_roomlist_collapsed", JSON.stringify(this.sublistCollapseStates));
|
|
|
|
}
|
|
|
|
|
2020-06-04 00:07:12 +03:00
|
|
|
private onResizerMouseDown = (ev: React.MouseEvent) => {
|
|
|
|
const hr = ev.target as HTMLHRElement;
|
|
|
|
this.currentTagResize = hr.getAttribute("data-id");
|
|
|
|
};
|
|
|
|
|
|
|
|
private onResizerMouseUp = (ev: React.MouseEvent) => {
|
|
|
|
this.currentTagResize = null;
|
|
|
|
};
|
|
|
|
|
|
|
|
private onMouseMove = (ev: React.MouseEvent) => {
|
|
|
|
ev.preventDefault();
|
|
|
|
if (this.currentTagResize) {
|
|
|
|
const pixelHeight = this.state.heights.get(this.currentTagResize);
|
|
|
|
RoomListStore.instance.layout.setPixelHeight(this.currentTagResize, pixelHeight + ev.movementY);
|
|
|
|
this.state.heights.set(this.currentTagResize, RoomListStore.instance.layout.getPixelHeight(this.currentTagResize));
|
|
|
|
this.forceUpdate();
|
|
|
|
}
|
|
|
|
};
|
2020-03-20 23:38:20 +03:00
|
|
|
|
2020-04-30 22:21:50 +03:00
|
|
|
private renderSublists(): React.ReactElement[] {
|
|
|
|
const components: React.ReactElement[] = [];
|
|
|
|
|
|
|
|
for (const orderedTagId of TAG_ORDER) {
|
|
|
|
if (COMMUNITY_TAGS_BEFORE_TAG === orderedTagId) {
|
|
|
|
// Populate community invites if we have the chance
|
|
|
|
// TODO
|
|
|
|
}
|
|
|
|
if (CUSTOM_TAGS_BEFORE_TAG === orderedTagId) {
|
|
|
|
// Populate custom tags if needed
|
|
|
|
// TODO
|
|
|
|
}
|
|
|
|
|
|
|
|
const orderedRooms = this.state.sublists[orderedTagId] || [];
|
|
|
|
if (orderedRooms.length === 0 && !ALWAYS_VISIBLE_TAGS.includes(orderedTagId)) {
|
|
|
|
continue; // skip tag - not needed
|
|
|
|
}
|
|
|
|
|
|
|
|
const aesthetics: ITagAesthetics = TAG_AESTHETICS[orderedTagId];
|
|
|
|
if (!aesthetics) throw new Error(`Tag ${orderedTagId} does not have aesthetics`);
|
|
|
|
|
2020-05-08 21:53:05 +03:00
|
|
|
const onAddRoomFn = aesthetics.onAddRoom ? () => aesthetics.onAddRoom(dis) : null;
|
2020-04-30 22:21:50 +03:00
|
|
|
components.push(<RoomSublist2
|
2020-05-08 21:53:05 +03:00
|
|
|
key={`sublist-${orderedTagId}`}
|
2020-04-30 22:21:50 +03:00
|
|
|
forRooms={true}
|
|
|
|
rooms={orderedRooms}
|
|
|
|
startAsHidden={aesthetics.defaultHidden}
|
|
|
|
label={_t(aesthetics.sectionLabel)}
|
|
|
|
onAddRoom={onAddRoomFn}
|
|
|
|
addRoomLabel={aesthetics.addRoomLabel}
|
|
|
|
isInvite={aesthetics.isInvite}
|
2020-06-04 00:07:12 +03:00
|
|
|
height={this.state.heights.get(orderedTagId)}
|
|
|
|
/>);
|
|
|
|
components.push(<hr
|
|
|
|
key={`resizer-${orderedTagId}`}
|
|
|
|
data-id={orderedTagId}
|
|
|
|
className="mx_RoomList2_resizer"
|
|
|
|
onMouseDown={this.onResizerMouseDown}
|
|
|
|
onMouseUp={this.onResizerMouseUp}
|
2020-04-30 22:21:50 +03:00
|
|
|
/>);
|
|
|
|
}
|
|
|
|
|
|
|
|
return components;
|
|
|
|
}
|
|
|
|
|
2020-03-20 23:38:20 +03:00
|
|
|
public render() {
|
2020-04-30 22:21:50 +03:00
|
|
|
const sublists = this.renderSublists();
|
2020-03-20 23:38:20 +03:00
|
|
|
return (
|
|
|
|
<RovingTabIndexProvider handleHomeEnd={true} onKeyDown={this.props.onKeyDown}>
|
|
|
|
{({onKeyDownHandler}) => (
|
|
|
|
<div
|
|
|
|
onFocus={this.props.onFocus}
|
|
|
|
onBlur={this.props.onBlur}
|
|
|
|
onKeyDown={onKeyDownHandler}
|
2020-06-04 00:07:12 +03:00
|
|
|
onMouseUp={this.onResizerMouseUp}
|
|
|
|
onMouseMove={this.onMouseMove}
|
|
|
|
className="mx_RoomList mx_RoomList2"
|
2020-03-20 23:38:20 +03:00
|
|
|
role="tree"
|
|
|
|
aria-label={_t("Rooms")}
|
|
|
|
// Firefox sometimes makes this element focusable due to
|
|
|
|
// overflow:scroll;, so force it out of tab order.
|
|
|
|
tabIndex={-1}
|
2020-04-30 22:21:50 +03:00
|
|
|
>{sublists}</div>
|
2020-03-20 23:38:20 +03:00
|
|
|
)}
|
|
|
|
</RovingTabIndexProvider>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|