mirror of
https://github.com/element-hq/element-web
synced 2024-11-26 19:26:04 +03:00
Initial breakout for room list rewrite
This does a number of things (sorry): * Estimates the type changes needed to the dispatcher (later to be replaced by https://github.com/matrix-org/matrix-react-sdk/pull/4593) * Sets up the stack for a whole new room list store, and later components for usage. * Create a proxy class to ensure the app still functions as expected when the various stores are enabled/disabled * Demonstrates a possible structure for algorithms
This commit is contained in:
parent
82b55ffd77
commit
08419d195e
21 changed files with 794 additions and 47 deletions
|
@ -117,6 +117,7 @@
|
|||
"@babel/register": "^7.7.4",
|
||||
"@peculiar/webcrypto": "^1.0.22",
|
||||
"@types/classnames": "^2.2.10",
|
||||
"@types/flux": "^3.1.9",
|
||||
"@types/modernizr": "^3.5.3",
|
||||
"@types/qrcode": "^1.3.4",
|
||||
"@types/react": "16.9",
|
||||
|
|
|
@ -15,11 +15,12 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import { asyncAction } from './actionCreators';
|
||||
import RoomListStore, {TAG_DM} from '../stores/RoomListStore';
|
||||
import Modal from '../Modal';
|
||||
import * as Rooms from '../Rooms';
|
||||
import { _t } from '../languageHandler';
|
||||
import * as sdk from '../index';
|
||||
import {RoomListStoreTempProxy} from "../stores/room-list/RoomListStoreTempProxy";
|
||||
import {DefaultTagID} from "../stores/room-list/models";
|
||||
|
||||
const RoomListActions = {};
|
||||
|
||||
|
@ -44,7 +45,7 @@ RoomListActions.tagRoom = function(matrixClient, room, oldTag, newTag, oldIndex,
|
|||
|
||||
// Is the tag ordered manually?
|
||||
if (newTag && !newTag.match(/^(m\.lowpriority|im\.vector\.fake\.(invite|recent|direct|archived))$/)) {
|
||||
const lists = RoomListStore.getRoomLists();
|
||||
const lists = RoomListStoreTempProxy.getRoomLists();
|
||||
const newList = [...lists[newTag]];
|
||||
|
||||
newList.sort((a, b) => a.tags[newTag].order - b.tags[newTag].order);
|
||||
|
@ -73,11 +74,11 @@ RoomListActions.tagRoom = function(matrixClient, room, oldTag, newTag, oldIndex,
|
|||
const roomId = room.roomId;
|
||||
|
||||
// Evil hack to get DMs behaving
|
||||
if ((oldTag === undefined && newTag === TAG_DM) ||
|
||||
(oldTag === TAG_DM && newTag === undefined)
|
||||
if ((oldTag === undefined && newTag === DefaultTagID.DM) ||
|
||||
(oldTag === DefaultTagID.DM && newTag === undefined)
|
||||
) {
|
||||
return Rooms.guessAndSetDMRoom(
|
||||
room, newTag === TAG_DM,
|
||||
room, newTag === DefaultTagID.DM,
|
||||
).catch((err) => {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Failed to set direct chat tag " + err);
|
||||
|
@ -91,10 +92,10 @@ RoomListActions.tagRoom = function(matrixClient, room, oldTag, newTag, oldIndex,
|
|||
const hasChangedSubLists = oldTag !== newTag;
|
||||
|
||||
// More evilness: We will still be dealing with moving to favourites/low prio,
|
||||
// but we avoid ever doing a request with TAG_DM.
|
||||
// but we avoid ever doing a request with DefaultTagID.DM.
|
||||
//
|
||||
// if we moved lists, remove the old tag
|
||||
if (oldTag && oldTag !== TAG_DM &&
|
||||
if (oldTag && oldTag !== DefaultTagID.DM &&
|
||||
hasChangedSubLists
|
||||
) {
|
||||
const promiseToDelete = matrixClient.deleteRoomTag(
|
||||
|
@ -112,7 +113,7 @@ RoomListActions.tagRoom = function(matrixClient, room, oldTag, newTag, oldIndex,
|
|||
}
|
||||
|
||||
// if we moved lists or the ordering changed, add the new tag
|
||||
if (newTag && newTag !== TAG_DM &&
|
||||
if (newTag && newTag !== DefaultTagID.DM &&
|
||||
(hasChangedSubLists || metaData)
|
||||
) {
|
||||
// metaData is the body of the PUT to set the tag, so it must
|
||||
|
|
|
@ -26,6 +26,7 @@ import * as VectorConferenceHandler from '../../VectorConferenceHandler';
|
|||
import SettingsStore from '../../settings/SettingsStore';
|
||||
import {_t} from "../../languageHandler";
|
||||
import Analytics from "../../Analytics";
|
||||
import RoomList2 from "../views/rooms/RoomList2";
|
||||
|
||||
|
||||
const LeftPanel = createReactClass({
|
||||
|
@ -273,6 +274,29 @@ const LeftPanel = createReactClass({
|
|||
breadcrumbs = (<RoomBreadcrumbs collapsed={this.props.collapsed} />);
|
||||
}
|
||||
|
||||
let roomList = null;
|
||||
if (SettingsStore.isFeatureEnabled("feature_new_room_list")) {
|
||||
roomList = <RoomList2
|
||||
onKeyDown={this._onKeyDown}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
collapsed={this.props.collapsed}
|
||||
searchFilter={this.state.searchFilter}
|
||||
ref={this.collectRoomList}
|
||||
onFocus={this._onFocus}
|
||||
onBlur={this._onBlur}
|
||||
/>;
|
||||
} else {
|
||||
roomList = <RoomList
|
||||
onKeyDown={this._onKeyDown}
|
||||
onFocus={this._onFocus}
|
||||
onBlur={this._onBlur}
|
||||
ref={this.collectRoomList}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
collapsed={this.props.collapsed}
|
||||
searchFilter={this.state.searchFilter}
|
||||
ConferenceHandler={VectorConferenceHandler} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={containerClasses}>
|
||||
{ tagPanelContainer }
|
||||
|
@ -284,15 +308,7 @@ const LeftPanel = createReactClass({
|
|||
{ exploreButton }
|
||||
{ searchBox }
|
||||
</div>
|
||||
<RoomList
|
||||
onKeyDown={this._onKeyDown}
|
||||
onFocus={this._onFocus}
|
||||
onBlur={this._onBlur}
|
||||
ref={this.collectRoomList}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
collapsed={this.props.collapsed}
|
||||
searchFilter={this.state.searchFilter}
|
||||
ConferenceHandler={VectorConferenceHandler} />
|
||||
{roomList}
|
||||
</aside>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -31,7 +31,6 @@ import dis from '../../dispatcher';
|
|||
import sessionStore from '../../stores/SessionStore';
|
||||
import {MatrixClientPeg, MatrixClientCreds} from '../../MatrixClientPeg';
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import RoomListStore from "../../stores/RoomListStore";
|
||||
|
||||
import TagOrderActions from '../../actions/TagOrderActions';
|
||||
import RoomListActions from '../../actions/RoomListActions';
|
||||
|
@ -42,6 +41,8 @@ import * as KeyboardShortcuts from "../../accessibility/KeyboardShortcuts";
|
|||
import HomePage from "./HomePage";
|
||||
import ResizeNotifier from "../../utils/ResizeNotifier";
|
||||
import PlatformPeg from "../../PlatformPeg";
|
||||
import { RoomListStoreTempProxy } from "../../stores/room-list/RoomListStoreTempProxy";
|
||||
import { DefaultTagID } from "../../stores/room-list/models";
|
||||
// We need to fetch each pinned message individually (if we don't already have it)
|
||||
// so each pinned message may trigger a request. Limit the number per room for sanity.
|
||||
// NB. this is just for server notices rather than pinned messages in general.
|
||||
|
@ -297,18 +298,18 @@ class LoggedInView extends React.PureComponent<IProps, IState> {
|
|||
};
|
||||
|
||||
onRoomStateEvents = (ev, state) => {
|
||||
const roomLists = RoomListStore.getRoomLists();
|
||||
if (roomLists['m.server_notice'] && roomLists['m.server_notice'].some(r => r.roomId === ev.getRoomId())) {
|
||||
const roomLists = RoomListStoreTempProxy.getRoomLists();
|
||||
if (roomLists[DefaultTagID.ServerNotice] && roomLists[DefaultTagID.ServerNotice].some(r => r.roomId === ev.getRoomId())) {
|
||||
this._updateServerNoticeEvents();
|
||||
}
|
||||
};
|
||||
|
||||
_updateServerNoticeEvents = async () => {
|
||||
const roomLists = RoomListStore.getRoomLists();
|
||||
if (!roomLists['m.server_notice']) return [];
|
||||
const roomLists = RoomListStoreTempProxy.getRoomLists();
|
||||
if (!roomLists[DefaultTagID.ServerNotice]) return [];
|
||||
|
||||
const pinnedEvents = [];
|
||||
for (const room of roomLists['m.server_notice']) {
|
||||
for (const room of roomLists[DefaultTagID.ServerNotice]) {
|
||||
const pinStateEvent = room.currentState.getStateEvents("m.room.pinned_events", "");
|
||||
|
||||
if (!pinStateEvent || !pinStateEvent.getContent().pinned) continue;
|
||||
|
|
|
@ -34,8 +34,9 @@ import {humanizeTime} from "../../../utils/humanize";
|
|||
import createRoom, {canEncryptToAllUsers} from "../../../createRoom";
|
||||
import {inviteMultipleToRoom} from "../../../RoomInvite";
|
||||
import SettingsStore from '../../../settings/SettingsStore';
|
||||
import RoomListStore, {TAG_DM} from "../../../stores/RoomListStore";
|
||||
import {Key} from "../../../Keyboard";
|
||||
import {RoomListStoreTempProxy} from "../../../stores/room-list/RoomListStoreTempProxy";
|
||||
import {DefaultTagID} from "../../../stores/room-list/models";
|
||||
|
||||
export const KIND_DM = "dm";
|
||||
export const KIND_INVITE = "invite";
|
||||
|
@ -343,10 +344,10 @@ export default class InviteDialog extends React.PureComponent {
|
|||
_buildRecents(excludedTargetIds: Set<string>): {userId: string, user: RoomMember, lastActive: number} {
|
||||
const rooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals(); // map of userId => js-sdk Room
|
||||
|
||||
// Also pull in all the rooms tagged as TAG_DM so we don't miss anything. Sometimes the
|
||||
// Also pull in all the rooms tagged as DefaultTagID.DM so we don't miss anything. Sometimes the
|
||||
// room list doesn't tag the room for the DMRoomMap, but does for the room list.
|
||||
const taggedRooms = RoomListStore.getRoomLists();
|
||||
const dmTaggedRooms = taggedRooms[TAG_DM];
|
||||
const taggedRooms = RoomListStoreTempProxy.getRoomLists();
|
||||
const dmTaggedRooms = taggedRooms[DefaultTagID.DM];
|
||||
const myUserId = MatrixClientPeg.get().getUserId();
|
||||
for (const dmRoom of dmTaggedRooms) {
|
||||
const otherMembers = dmRoom.getJoinedMembers().filter(u => u.userId !== myUserId);
|
||||
|
|
|
@ -29,7 +29,6 @@ import rate_limited_func from "../../../ratelimitedfunc";
|
|||
import * as Rooms from '../../../Rooms';
|
||||
import DMRoomMap from '../../../utils/DMRoomMap';
|
||||
import TagOrderStore from '../../../stores/TagOrderStore';
|
||||
import RoomListStore, {TAG_DM} from '../../../stores/RoomListStore';
|
||||
import CustomRoomTagStore from '../../../stores/CustomRoomTagStore';
|
||||
import GroupStore from '../../../stores/GroupStore';
|
||||
import RoomSubList from '../../structures/RoomSubList';
|
||||
|
@ -41,6 +40,8 @@ import * as Receipt from "../../../utils/Receipt";
|
|||
import {Resizer} from '../../../resizer';
|
||||
import {Layout, Distributor} from '../../../resizer/distributors/roomsublist2';
|
||||
import {RovingTabIndexProvider} from "../../../accessibility/RovingTabIndex";
|
||||
import {RoomListStoreTempProxy} from "../../../stores/room-list/RoomListStoreTempProxy";
|
||||
import {DefaultTagID} from "../../../stores/room-list/models";
|
||||
import * as Unread from "../../../Unread";
|
||||
import RoomViewStore from "../../../stores/RoomViewStore";
|
||||
|
||||
|
@ -161,7 +162,7 @@ export default createReactClass({
|
|||
this.updateVisibleRooms();
|
||||
});
|
||||
|
||||
this._roomListStoreToken = RoomListStore.addListener(() => {
|
||||
this._roomListStoreToken = RoomListStoreTempProxy.addListener(() => {
|
||||
this._delayedRefreshRoomList();
|
||||
});
|
||||
|
||||
|
@ -521,7 +522,7 @@ export default createReactClass({
|
|||
},
|
||||
|
||||
getTagNameForRoomId: function(roomId) {
|
||||
const lists = RoomListStore.getRoomLists();
|
||||
const lists = RoomListStoreTempProxy.getRoomLists();
|
||||
for (const tagName of Object.keys(lists)) {
|
||||
for (const room of lists[tagName]) {
|
||||
// Should be impossible, but guard anyways.
|
||||
|
@ -541,7 +542,7 @@ export default createReactClass({
|
|||
},
|
||||
|
||||
getRoomLists: function() {
|
||||
const lists = RoomListStore.getRoomLists();
|
||||
const lists = RoomListStoreTempProxy.getRoomLists();
|
||||
|
||||
const filteredLists = {};
|
||||
|
||||
|
@ -773,10 +774,10 @@ export default createReactClass({
|
|||
incomingCall: incomingCallIfTaggedAs('m.favourite'),
|
||||
},
|
||||
{
|
||||
list: this.state.lists[TAG_DM],
|
||||
list: this.state.lists[DefaultTagID.DM],
|
||||
label: _t('Direct Messages'),
|
||||
tagName: TAG_DM,
|
||||
incomingCall: incomingCallIfTaggedAs(TAG_DM),
|
||||
tagName: DefaultTagID.DM,
|
||||
incomingCall: incomingCallIfTaggedAs(DefaultTagID.DM),
|
||||
onAddRoom: () => {dis.dispatch({action: 'view_create_chat'});},
|
||||
addRoomLabel: _t("Start chat"),
|
||||
},
|
||||
|
|
130
src/components/views/rooms/RoomList2.tsx
Normal file
130
src/components/views/rooms/RoomList2.tsx
Normal file
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
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";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { Layout } from '../../../resizer/distributors/roomsublist2';
|
||||
import { RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex";
|
||||
import { ResizeNotifier } from "../../../utils/ResizeNotifier";
|
||||
import RoomListStore from "../../../stores/room-list/RoomListStore2";
|
||||
|
||||
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 {
|
||||
}
|
||||
|
||||
// TODO: Actually write stub
|
||||
export class RoomSublist2 extends React.Component<any, any> {
|
||||
public setHeight(size: number) {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.loadSublistSizes();
|
||||
this.prepareLayouts();
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
RoomListStore.instance.addListener(() => {
|
||||
console.log(RoomListStore.instance.orderedLists);
|
||||
});
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
private prepareLayouts() {
|
||||
this.unfilteredLayout = new Layout((tagId: string, height: number) => {
|
||||
const sublist = this.sublistRefs[tagId];
|
||||
if (sublist) sublist.current.setHeight(height);
|
||||
|
||||
// TODO: Check overflow
|
||||
|
||||
// Don't store a height for collapsed sublists
|
||||
if (!this.sublistCollapseStates[tagId]) {
|
||||
this.sublistSizes[tagId] = height;
|
||||
this.saveSublistSizes();
|
||||
}
|
||||
}, this.sublistSizes, this.sublistCollapseStates, {
|
||||
allowWhitespace: false,
|
||||
handleHeight: 1,
|
||||
});
|
||||
|
||||
this.filteredLayout = new Layout((tagId: string, height: number) => {
|
||||
const sublist = this.sublistRefs[tagId];
|
||||
if (sublist) sublist.current.setHeight(height);
|
||||
}, null, null, {
|
||||
allowWhitespace: false,
|
||||
handleHeight: 0,
|
||||
});
|
||||
}
|
||||
|
||||
private collectSublistRef(tagId: string, ref: React.RefObject<RoomSublist2>) {
|
||||
if (!ref) {
|
||||
delete this.sublistRefs[tagId];
|
||||
} else {
|
||||
this.sublistRefs[tagId] = ref;
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<RovingTabIndexProvider handleHomeEnd={true} onKeyDown={this.props.onKeyDown}>
|
||||
{({onKeyDownHandler}) => (
|
||||
<div
|
||||
onFocus={this.props.onFocus}
|
||||
onBlur={this.props.onBlur}
|
||||
onKeyDown={onKeyDownHandler}
|
||||
className="mx_RoomList"
|
||||
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}
|
||||
>{_t("TODO")}</div>
|
||||
)}
|
||||
</RovingTabIndexProvider>
|
||||
);
|
||||
}
|
||||
}
|
28
src/dispatcher-types.ts
Normal file
28
src/dispatcher-types.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
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 flux from "flux";
|
||||
import dis from "./dispatcher";
|
||||
|
||||
// TODO: Merge this with the dispatcher and centralize types
|
||||
|
||||
export interface ActionPayload {
|
||||
[property: string]: any; // effectively "extends Object"
|
||||
action: string;
|
||||
}
|
||||
|
||||
// For ease of reference in TypeScript classes
|
||||
export const defaultDispatcher: flux.Dispatcher<ActionPayload> = dis;
|
|
@ -406,6 +406,7 @@
|
|||
"Render simple counters in room header": "Render simple counters in room header",
|
||||
"Multiple integration managers": "Multiple integration managers",
|
||||
"Try out new ways to ignore people (experimental)": "Try out new ways to ignore people (experimental)",
|
||||
"Use the improved room list component (refresh to apply changes)": "Use the improved room list component (refresh to apply changes)",
|
||||
"Support adding custom themes": "Support adding custom themes",
|
||||
"Enable cross-signing to verify per-user instead of per-session": "Enable cross-signing to verify per-user instead of per-session",
|
||||
"Show info about bridges in room settings": "Show info about bridges in room settings",
|
||||
|
@ -1116,6 +1117,7 @@
|
|||
"Low priority": "Low priority",
|
||||
"Historical": "Historical",
|
||||
"System Alerts": "System Alerts",
|
||||
"TODO": "TODO",
|
||||
"This room": "This room",
|
||||
"Joining room …": "Joining room …",
|
||||
"Loading …": "Loading …",
|
||||
|
|
|
@ -131,6 +131,12 @@ export const SETTINGS = {
|
|||
supportedLevels: LEVELS_FEATURE,
|
||||
default: false,
|
||||
},
|
||||
"feature_new_room_list": {
|
||||
isFeature: true,
|
||||
displayName: _td("Use the improved room list component (refresh to apply changes)"),
|
||||
supportedLevels: LEVELS_FEATURE,
|
||||
default: false,
|
||||
},
|
||||
"feature_custom_themes": {
|
||||
isFeature: true,
|
||||
displayName: _td("Support adding custom themes"),
|
||||
|
|
|
@ -15,10 +15,10 @@ limitations under the License.
|
|||
*/
|
||||
import dis from '../dispatcher';
|
||||
import * as RoomNotifs from '../RoomNotifs';
|
||||
import RoomListStore from './RoomListStore';
|
||||
import EventEmitter from 'events';
|
||||
import { throttle } from "lodash";
|
||||
import SettingsStore from "../settings/SettingsStore";
|
||||
import {RoomListStoreTempProxy} from "./room-list/RoomListStoreTempProxy";
|
||||
|
||||
const STANDARD_TAGS_REGEX = /^(m\.(favourite|lowpriority|server_notice)|im\.vector\.fake\.(invite|recent|direct|archived))$/;
|
||||
|
||||
|
@ -60,7 +60,7 @@ class CustomRoomTagStore extends EventEmitter {
|
|||
trailing: true,
|
||||
},
|
||||
);
|
||||
this._roomListStoreToken = RoomListStore.addListener(() => {
|
||||
this._roomListStoreToken = RoomListStoreTempProxy.addListener(() => {
|
||||
this._setState({tags: this._getUpdatedTags()});
|
||||
});
|
||||
dis.register(payload => this._onDispatch(payload));
|
||||
|
@ -85,7 +85,7 @@ class CustomRoomTagStore extends EventEmitter {
|
|||
}
|
||||
|
||||
getSortedTags() {
|
||||
const roomLists = RoomListStore.getRoomLists();
|
||||
const roomLists = RoomListStoreTempProxy.getRoomLists();
|
||||
|
||||
const tagNames = Object.keys(this._state.tags).sort();
|
||||
const prefixes = tagNames.map((name, i) => {
|
||||
|
@ -140,7 +140,7 @@ class CustomRoomTagStore extends EventEmitter {
|
|||
return;
|
||||
}
|
||||
|
||||
const newTagNames = Object.keys(RoomListStore.getRoomLists())
|
||||
const newTagNames = Object.keys(RoomListStoreTempProxy.getRoomLists())
|
||||
.filter((tagName) => {
|
||||
return !tagName.match(STANDARD_TAGS_REGEX);
|
||||
}).sort();
|
||||
|
|
|
@ -112,11 +112,19 @@ class RoomListStore extends Store {
|
|||
constructor() {
|
||||
super(dis);
|
||||
|
||||
this._checkDisabled();
|
||||
this._init();
|
||||
this._getManualComparator = this._getManualComparator.bind(this);
|
||||
this._recentsComparator = this._recentsComparator.bind(this);
|
||||
}
|
||||
|
||||
_checkDisabled() {
|
||||
this.disabled = SettingsStore.isFeatureEnabled("feature_new_room_list");
|
||||
if (this.disabled) {
|
||||
console.warn("DISABLING LEGACY ROOM LIST STORE");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the sorting algorithm used by the RoomListStore.
|
||||
* @param {string} algorithm The new algorithm to use. Should be one of the ALGO_* constants.
|
||||
|
@ -133,6 +141,8 @@ class RoomListStore extends Store {
|
|||
}
|
||||
|
||||
_init() {
|
||||
if (this.disabled) return;
|
||||
|
||||
// Initialise state
|
||||
const defaultLists = {
|
||||
"m.server_notice": [/* { room: js-sdk room, category: string } */],
|
||||
|
@ -160,6 +170,8 @@ class RoomListStore extends Store {
|
|||
}
|
||||
|
||||
_setState(newState) {
|
||||
if (this.disabled) return;
|
||||
|
||||
// If we're changing the lists, transparently change the presentation lists (which
|
||||
// is given to requesting components). This dramatically simplifies our code elsewhere
|
||||
// while also ensuring we don't need to update all the calling components to support
|
||||
|
@ -176,6 +188,8 @@ class RoomListStore extends Store {
|
|||
}
|
||||
|
||||
__onDispatch(payload) {
|
||||
if (this.disabled) return;
|
||||
|
||||
const logicallyReady = this._matrixClient && this._state.ready;
|
||||
switch (payload.action) {
|
||||
case 'setting_updated': {
|
||||
|
@ -202,6 +216,9 @@ class RoomListStore extends Store {
|
|||
break;
|
||||
}
|
||||
|
||||
this._checkDisabled();
|
||||
if (this.disabled) return;
|
||||
|
||||
// Always ensure that we set any state needed for settings here. It is possible that
|
||||
// setting updates trigger on startup before we are ready to sync, so we want to make
|
||||
// sure that the right state is in place before we actually react to those changes.
|
||||
|
|
213
src/stores/room-list/RoomListStore2.ts
Normal file
213
src/stores/room-list/RoomListStore2.ts
Normal file
|
@ -0,0 +1,213 @@
|
|||
/*
|
||||
Copyright 2018, 2019 New Vector 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 {Store} from 'flux/utils';
|
||||
import {Room} from "matrix-js-sdk/src/models/room";
|
||||
import {MatrixClient} from "matrix-js-sdk/src/client";
|
||||
import { ActionPayload, defaultDispatcher } from "../../dispatcher-types";
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import { OrderedDefaultTagIDs, DefaultTagID, TagID } from "./models";
|
||||
import { IAlgorithm, ITagMap, ITagSortingMap, ListAlgorithm, SortAlgorithm } from "./algorithms/IAlgorithm";
|
||||
import TagOrderStore from "../TagOrderStore";
|
||||
import { getAlgorithmInstance } from "./algorithms";
|
||||
|
||||
interface IState {
|
||||
tagsEnabled?: boolean;
|
||||
|
||||
preferredSort?: SortAlgorithm;
|
||||
preferredAlgorithm?: ListAlgorithm;
|
||||
}
|
||||
|
||||
class _RoomListStore extends Store<ActionPayload> {
|
||||
private state: IState = {};
|
||||
private matrixClient: MatrixClient;
|
||||
private initialListsGenerated = false;
|
||||
private enabled = false;
|
||||
private algorithm: IAlgorithm;
|
||||
|
||||
private readonly watchedSettings = [
|
||||
'RoomList.orderAlphabetically',
|
||||
'RoomList.orderByImportance',
|
||||
'feature_custom_tags',
|
||||
];
|
||||
|
||||
constructor() {
|
||||
super(defaultDispatcher);
|
||||
|
||||
this.checkEnabled();
|
||||
this.reset();
|
||||
for (const settingName of this.watchedSettings) SettingsStore.monitorSetting(settingName, null);
|
||||
}
|
||||
|
||||
public get orderedLists(): ITagMap {
|
||||
if (!this.algorithm) return {}; // No tags yet.
|
||||
return this.algorithm.getOrderedRooms();
|
||||
}
|
||||
|
||||
// TODO: Remove enabled flag when the old RoomListStore goes away
|
||||
private checkEnabled() {
|
||||
this.enabled = SettingsStore.isFeatureEnabled("feature_new_room_list");
|
||||
if (this.enabled) {
|
||||
console.log("ENABLING NEW ROOM LIST STORE");
|
||||
}
|
||||
}
|
||||
|
||||
private reset(): void {
|
||||
// We don't call setState() because it'll cause changes to emitted which could
|
||||
// crash the app during logout/signin/etc.
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
private readAndCacheSettingsFromStore() {
|
||||
const tagsEnabled = SettingsStore.isFeatureEnabled("feature_custom_tags");
|
||||
const orderByImportance = SettingsStore.getValue("RoomList.orderByImportance");
|
||||
const orderAlphabetically = SettingsStore.getValue("RoomList.orderAlphabetically");
|
||||
this.setState({
|
||||
tagsEnabled,
|
||||
preferredSort: orderAlphabetically ? SortAlgorithm.Alphabetic : SortAlgorithm.Recent,
|
||||
preferredAlgorithm: orderByImportance ? ListAlgorithm.Importance : ListAlgorithm.Natural,
|
||||
});
|
||||
this.setAlgorithmClass();
|
||||
}
|
||||
|
||||
protected __onDispatch(payload: ActionPayload): void {
|
||||
if (payload.action === 'MatrixActions.sync') {
|
||||
// Filter out anything that isn't the first PREPARED sync.
|
||||
if (!(payload.prevState === 'PREPARED' && payload.state !== 'PREPARED')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.checkEnabled();
|
||||
if (!this.enabled) return;
|
||||
|
||||
this.matrixClient = payload.matrixClient;
|
||||
|
||||
// Update any settings here, as some may have happened before we were logically ready.
|
||||
this.readAndCacheSettingsFromStore();
|
||||
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.regenerateAllLists();
|
||||
}
|
||||
|
||||
// TODO: Remove this once the RoomListStore becomes default
|
||||
if (!this.enabled) return;
|
||||
|
||||
if (payload.action === 'on_client_not_viable' || payload.action === 'on_logged_out') {
|
||||
// Reset state without causing updates as the client will have been destroyed
|
||||
// and downstream code will throw NPE errors.
|
||||
this.reset();
|
||||
this.matrixClient = null;
|
||||
this.initialListsGenerated = false; // we'll want to regenerate them
|
||||
}
|
||||
|
||||
// Everything below here requires a MatrixClient or some sort of logical readiness.
|
||||
const logicallyReady = this.matrixClient && this.initialListsGenerated;
|
||||
if (!logicallyReady) return;
|
||||
|
||||
if (payload.action === 'setting_updated') {
|
||||
if (this.watchedSettings.includes(payload.settingName)) {
|
||||
this.readAndCacheSettingsFromStore();
|
||||
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.regenerateAllLists(); // regenerate the lists now
|
||||
}
|
||||
} else if (payload.action === 'MatrixActions.Room.receipt') {
|
||||
// First see if the receipt event is for our own user. If it was, trigger
|
||||
// a room update (we probably read the room on a different device).
|
||||
const myUserId = this.matrixClient.getUserId();
|
||||
for (const eventId of Object.keys(payload.event.getContent())) {
|
||||
const receiptUsers = Object.keys(payload.event.getContent()[eventId]['m.read'] || {});
|
||||
if (receiptUsers.includes(myUserId)) {
|
||||
// TODO: Update room now that it's been read
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (payload.action === 'MatrixActions.Room.tags') {
|
||||
// TODO: Update room from tags
|
||||
} else if (payload.action === 'MatrixActions.room.timeline') {
|
||||
// TODO: Update room from new events
|
||||
} else if (payload.action === 'MatrixActions.Event.decrypted') {
|
||||
// TODO: Update room from decrypted event
|
||||
} else if (payload.action === 'MatrixActions.accountData' && payload.event_type === 'm.direct') {
|
||||
// TODO: Update DMs
|
||||
} else if (payload.action === 'MatrixActions.Room.myMembership') {
|
||||
// TODO: Update room from membership change
|
||||
} else if (payload.action === 'MatrixActions.room') {
|
||||
// TODO: Update room from creation/join
|
||||
} else if (payload.action === 'view_room') {
|
||||
// TODO: Update sticky room
|
||||
}
|
||||
}
|
||||
|
||||
private getSortAlgorithmFor(tagId: TagID): SortAlgorithm {
|
||||
switch (tagId) {
|
||||
case DefaultTagID.Invite:
|
||||
case DefaultTagID.Untagged:
|
||||
case DefaultTagID.Archived:
|
||||
case DefaultTagID.LowPriority:
|
||||
case DefaultTagID.DM:
|
||||
return this.state.preferredSort;
|
||||
case DefaultTagID.Favourite:
|
||||
default:
|
||||
return SortAlgorithm.Manual;
|
||||
}
|
||||
}
|
||||
|
||||
private setState(newState: IState) {
|
||||
if (!this.enabled) return;
|
||||
|
||||
this.state = Object.assign(this.state, newState);
|
||||
this.__emitChange();
|
||||
}
|
||||
|
||||
private setAlgorithmClass() {
|
||||
this.algorithm = getAlgorithmInstance(this.state.preferredAlgorithm);
|
||||
}
|
||||
|
||||
private async regenerateAllLists() {
|
||||
console.log("REGEN");
|
||||
const tags: ITagSortingMap = {};
|
||||
for (const tagId of OrderedDefaultTagIDs) {
|
||||
tags[tagId] = this.getSortAlgorithmFor(tagId);
|
||||
}
|
||||
|
||||
if (this.state.tagsEnabled) {
|
||||
// TODO: Find a more reliable way to get tags
|
||||
const roomTags = TagOrderStore.getOrderedTags() || [];
|
||||
console.log("rtags", roomTags);
|
||||
}
|
||||
|
||||
await this.algorithm.populateTags(tags);
|
||||
await this.algorithm.setKnownRooms(this.matrixClient.getRooms());
|
||||
|
||||
this.initialListsGenerated = true;
|
||||
|
||||
// TODO: How do we asynchronously update the store's state? or do we just give in and make it all sync?
|
||||
}
|
||||
}
|
||||
|
||||
export default class RoomListStore {
|
||||
private static internalInstance: _RoomListStore;
|
||||
|
||||
public static get instance(): _RoomListStore {
|
||||
if (!RoomListStore.internalInstance) {
|
||||
RoomListStore.internalInstance = new _RoomListStore();
|
||||
}
|
||||
|
||||
return RoomListStore.internalInstance;
|
||||
}
|
||||
}
|
49
src/stores/room-list/RoomListStoreTempProxy.ts
Normal file
49
src/stores/room-list/RoomListStoreTempProxy.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
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 { TagID } from "./models";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import RoomListStore from "./RoomListStore2";
|
||||
import OldRoomListStore from "../RoomListStore";
|
||||
|
||||
/**
|
||||
* Temporary RoomListStore proxy. Should be replaced with RoomListStore2 when
|
||||
* it is available to everyone.
|
||||
*
|
||||
* TODO: Remove this when RoomListStore gets fully replaced.
|
||||
*/
|
||||
export class RoomListStoreTempProxy {
|
||||
public static isUsingNewStore(): boolean {
|
||||
return SettingsStore.isFeatureEnabled("feature_new_room_list");
|
||||
}
|
||||
|
||||
public static addListener(handler: () => void) {
|
||||
if (RoomListStoreTempProxy.isUsingNewStore()) {
|
||||
return RoomListStore.instance.addListener(handler);
|
||||
} else {
|
||||
return OldRoomListStore.addListener(handler);
|
||||
}
|
||||
}
|
||||
|
||||
public static getRoomLists(): {[tagId in TagID]: Room[]} {
|
||||
if (RoomListStoreTempProxy.isUsingNewStore()) {
|
||||
return RoomListStore.instance.orderedLists;
|
||||
} else {
|
||||
return OldRoomListStore.getRoomLists();
|
||||
}
|
||||
}
|
||||
}
|
100
src/stores/room-list/algorithms/ChaoticAlgorithm.ts
Normal file
100
src/stores/room-list/algorithms/ChaoticAlgorithm.ts
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
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 { IAlgorithm, ITagMap, ITagSortingMap, ListAlgorithm } from "./IAlgorithm";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
|
||||
import { DefaultTagID } from "../models";
|
||||
|
||||
/**
|
||||
* A demonstration/temporary algorithm to verify the API surface works.
|
||||
* TODO: Remove this before shipping
|
||||
*/
|
||||
export class ChaoticAlgorithm implements IAlgorithm {
|
||||
|
||||
private cached: ITagMap = {};
|
||||
private sortAlgorithms: ITagSortingMap;
|
||||
private rooms: Room[] = [];
|
||||
|
||||
constructor(private representativeAlgorithm: ListAlgorithm) {
|
||||
}
|
||||
|
||||
getOrderedRooms(): ITagMap {
|
||||
return this.cached;
|
||||
}
|
||||
|
||||
async populateTags(tagSortingMap: ITagSortingMap): Promise<any> {
|
||||
if (!tagSortingMap) throw new Error(`Map cannot be null or empty`);
|
||||
this.sortAlgorithms = tagSortingMap;
|
||||
this.setKnownRooms(this.rooms); // regenerate the room lists
|
||||
}
|
||||
|
||||
handleRoomUpdate(room): Promise<boolean> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
setKnownRooms(rooms: Room[]): Promise<any> {
|
||||
if (isNullOrUndefined(rooms)) throw new Error(`Array of rooms cannot be null`);
|
||||
if (!this.sortAlgorithms) throw new Error(`Cannot set known rooms without a tag sorting map`);
|
||||
|
||||
this.rooms = rooms;
|
||||
|
||||
const newTags = {};
|
||||
for (const tagId in this.sortAlgorithms) {
|
||||
// noinspection JSUnfilteredForInLoop
|
||||
newTags[tagId] = [];
|
||||
}
|
||||
|
||||
// If we can avoid doing work, do so.
|
||||
if (!rooms.length) {
|
||||
this.cached = newTags;
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Remove logging
|
||||
console.log('setting known rooms - regen in progress');
|
||||
console.log({alg: this.representativeAlgorithm});
|
||||
|
||||
// Step through each room and determine which tags it should be in.
|
||||
// We don't care about ordering or sorting here - we're simply organizing things.
|
||||
for (const room of rooms) {
|
||||
const tags = room.tags;
|
||||
let inTag = false;
|
||||
for (const tagId in tags) {
|
||||
// noinspection JSUnfilteredForInLoop
|
||||
if (isNullOrUndefined(newTags[tagId])) {
|
||||
// skip the tag if we don't know about it
|
||||
continue;
|
||||
}
|
||||
|
||||
inTag = true;
|
||||
|
||||
// noinspection JSUnfilteredForInLoop
|
||||
newTags[tagId].push(room);
|
||||
}
|
||||
|
||||
// If the room wasn't pushed to a tag, push it to the untagged tag.
|
||||
if (!inTag) {
|
||||
newTags[DefaultTagID.Untagged].push(room);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Do sorting
|
||||
|
||||
// Finally, assign the tags to our cache
|
||||
this.cached = newTags;
|
||||
}
|
||||
}
|
95
src/stores/room-list/algorithms/IAlgorithm.ts
Normal file
95
src/stores/room-list/algorithms/IAlgorithm.ts
Normal file
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
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 { TagID } from "../models";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
|
||||
|
||||
export enum SortAlgorithm {
|
||||
Manual = "MANUAL",
|
||||
Alphabetic = "ALPHABETIC",
|
||||
Recent = "RECENT",
|
||||
}
|
||||
|
||||
export enum ListAlgorithm {
|
||||
// Orders Red > Grey > Bold > Idle
|
||||
Importance = "IMPORTANCE",
|
||||
|
||||
// Orders however the SortAlgorithm decides
|
||||
Natural = "NATURAL",
|
||||
}
|
||||
|
||||
export enum Category {
|
||||
Red = "RED",
|
||||
Grey = "GREY",
|
||||
Bold = "BOLD",
|
||||
Idle = "IDLE",
|
||||
}
|
||||
|
||||
export interface ITagSortingMap {
|
||||
// @ts-ignore - TypeScript really wants this to be [tagId: string] but we know better.
|
||||
[tagId: TagID]: SortAlgorithm;
|
||||
}
|
||||
|
||||
export interface ITagMap {
|
||||
// @ts-ignore - TypeScript really wants this to be [tagId: string] but we know better.
|
||||
[tagId: TagID]: Room[];
|
||||
}
|
||||
|
||||
// TODO: Convert IAlgorithm to an abstract class?
|
||||
// TODO: Add locking support to avoid concurrent writes
|
||||
// TODO: EventEmitter support
|
||||
|
||||
/**
|
||||
* Represents an algorithm for the RoomListStore to use
|
||||
*/
|
||||
export interface IAlgorithm {
|
||||
/**
|
||||
* Asks the Algorithm to regenerate all lists, using the tags given
|
||||
* as reference for which lists to generate and which way to generate
|
||||
* them.
|
||||
* @param {ITagSortingMap} tagSortingMap The tags to generate.
|
||||
* @returns {Promise<*>} A promise which resolves when complete.
|
||||
*/
|
||||
populateTags(tagSortingMap: ITagSortingMap): Promise<any>;
|
||||
|
||||
/**
|
||||
* Gets an ordered set of rooms for the all known tags.
|
||||
* @returns {ITagMap} The cached list of rooms, ordered,
|
||||
* for each tag. May be empty, but never null/undefined.
|
||||
*/
|
||||
getOrderedRooms(): ITagMap;
|
||||
|
||||
/**
|
||||
* Seeds the Algorithm with a set of rooms. The algorithm will discard all
|
||||
* previously known information and instead use these rooms instead.
|
||||
* @param {Room[]} rooms The rooms to force the algorithm to use.
|
||||
* @returns {Promise<*>} A promise which resolves when complete.
|
||||
*/
|
||||
setKnownRooms(rooms: Room[]): Promise<any>;
|
||||
|
||||
/**
|
||||
* Asks the Algorithm to update its knowledge of a room. For example, when
|
||||
* a user tags a room, joins/creates a room, or leaves a room the Algorithm
|
||||
* should be told that the room's info might have changed. The Algorithm
|
||||
* may no-op this request if no changes are required.
|
||||
* @param {Room} room The room which might have affected sorting.
|
||||
* @returns {Promise<boolean>} A promise which resolve to true or false
|
||||
* depending on whether or not getOrderedRooms() should be called after
|
||||
* processing.
|
||||
*/
|
||||
handleRoomUpdate(room: Room): Promise<boolean>;
|
||||
}
|
36
src/stores/room-list/algorithms/index.ts
Normal file
36
src/stores/room-list/algorithms/index.ts
Normal 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 { IAlgorithm, ListAlgorithm } from "./IAlgorithm";
|
||||
import { ChaoticAlgorithm } from "./ChaoticAlgorithm";
|
||||
|
||||
const ALGORITHM_FACTORIES: { [algorithm in ListAlgorithm]: () => IAlgorithm } = {
|
||||
[ListAlgorithm.Natural]: () => new ChaoticAlgorithm(ListAlgorithm.Natural),
|
||||
[ListAlgorithm.Importance]: () => new ChaoticAlgorithm(ListAlgorithm.Importance),
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets an instance of the defined algorithm
|
||||
* @param {ListAlgorithm} algorithm The algorithm to get an instance of.
|
||||
* @returns {IAlgorithm} The algorithm instance.
|
||||
*/
|
||||
export function getAlgorithmInstance(algorithm: ListAlgorithm): IAlgorithm {
|
||||
if (!ALGORITHM_FACTORIES[algorithm]) {
|
||||
throw new Error(`${algorithm} is not a known algorithm`);
|
||||
}
|
||||
|
||||
return ALGORITHM_FACTORIES[algorithm]();
|
||||
}
|
36
src/stores/room-list/models.ts
Normal file
36
src/stores/room-list/models.ts
Normal 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.
|
||||
*/
|
||||
|
||||
export enum DefaultTagID {
|
||||
Invite = "im.vector.fake.invite",
|
||||
Untagged = "im.vector.fake.recent", // legacy: used to just be 'recent rooms' but now it's all untagged rooms
|
||||
Archived = "im.vector.fake.archived",
|
||||
LowPriority = "m.lowpriority",
|
||||
Favourite = "m.favourite",
|
||||
DM = "im.vector.fake.direct",
|
||||
ServerNotice = "m.server_notice",
|
||||
}
|
||||
export const OrderedDefaultTagIDs = [
|
||||
DefaultTagID.Invite,
|
||||
DefaultTagID.Favourite,
|
||||
DefaultTagID.DM,
|
||||
DefaultTagID.Untagged,
|
||||
DefaultTagID.LowPriority,
|
||||
DefaultTagID.ServerNotice,
|
||||
DefaultTagID.Archived,
|
||||
];
|
||||
|
||||
export type TagID = string | DefaultTagID;
|
|
@ -14,7 +14,7 @@ import DMRoomMap from '../../../../src/utils/DMRoomMap.js';
|
|||
import GroupStore from '../../../../src/stores/GroupStore.js';
|
||||
|
||||
import { MatrixClient, Room, RoomMember } from 'matrix-js-sdk';
|
||||
import {TAG_DM} from "../../../../src/stores/RoomListStore";
|
||||
import {DefaultTagID} from "../../../../src/stores/room-list/models";
|
||||
|
||||
function generateRoomId() {
|
||||
return '!' + Math.random().toString().slice(2, 10) + ':domain';
|
||||
|
@ -153,7 +153,7 @@ describe('RoomList', () => {
|
|||
// Set up the room that will be moved such that it has the correct state for a room in
|
||||
// the section for oldTag
|
||||
if (['m.favourite', 'm.lowpriority'].includes(oldTag)) movingRoom.tags = {[oldTag]: {}};
|
||||
if (oldTag === TAG_DM) {
|
||||
if (oldTag === DefaultTagID.DM) {
|
||||
// Mock inverse m.direct
|
||||
DMRoomMap.shared().roomToUser = {
|
||||
[movingRoom.roomId]: '@someotheruser:domain',
|
||||
|
@ -180,7 +180,7 @@ describe('RoomList', () => {
|
|||
// TODO: Re-enable dragging tests when we support dragging again.
|
||||
describe.skip('does correct optimistic update when dragging from', () => {
|
||||
it('rooms to people', () => {
|
||||
expectCorrectMove(undefined, TAG_DM);
|
||||
expectCorrectMove(undefined, DefaultTagID.DM);
|
||||
});
|
||||
|
||||
it('rooms to favourites', () => {
|
||||
|
@ -195,15 +195,15 @@ describe('RoomList', () => {
|
|||
// Whe running the app live, it updates when some other event occurs (likely the
|
||||
// m.direct arriving) that these tests do not fire.
|
||||
xit('people to rooms', () => {
|
||||
expectCorrectMove(TAG_DM, undefined);
|
||||
expectCorrectMove(DefaultTagID.DM, undefined);
|
||||
});
|
||||
|
||||
it('people to favourites', () => {
|
||||
expectCorrectMove(TAG_DM, 'm.favourite');
|
||||
expectCorrectMove(DefaultTagID.DM, 'm.favourite');
|
||||
});
|
||||
|
||||
it('people to lowpriority', () => {
|
||||
expectCorrectMove(TAG_DM, 'm.lowpriority');
|
||||
expectCorrectMove(DefaultTagID.DM, 'm.lowpriority');
|
||||
});
|
||||
|
||||
it('low priority to rooms', () => {
|
||||
|
@ -211,7 +211,7 @@ describe('RoomList', () => {
|
|||
});
|
||||
|
||||
it('low priority to people', () => {
|
||||
expectCorrectMove('m.lowpriority', TAG_DM);
|
||||
expectCorrectMove('m.lowpriority', DefaultTagID.DM);
|
||||
});
|
||||
|
||||
it('low priority to low priority', () => {
|
||||
|
@ -223,7 +223,7 @@ describe('RoomList', () => {
|
|||
});
|
||||
|
||||
it('favourites to people', () => {
|
||||
expectCorrectMove('m.favourite', TAG_DM);
|
||||
expectCorrectMove('m.favourite', DefaultTagID.DM);
|
||||
});
|
||||
|
||||
it('favourites to low priority', () => {
|
||||
|
|
|
@ -14,7 +14,8 @@
|
|||
"jsx": "react",
|
||||
"types": [
|
||||
"node",
|
||||
"react"
|
||||
"react",
|
||||
"flux"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
|
|
13
yarn.lock
13
yarn.lock
|
@ -1218,6 +1218,19 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
|
||||
integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==
|
||||
|
||||
"@types/fbemitter@*":
|
||||
version "2.0.32"
|
||||
resolved "https://registry.yarnpkg.com/@types/fbemitter/-/fbemitter-2.0.32.tgz#8ed204da0f54e9c8eaec31b1eec91e25132d082c"
|
||||
integrity sha1-jtIE2g9U6cjq7DGx7skeJRMtCCw=
|
||||
|
||||
"@types/flux@^3.1.9":
|
||||
version "3.1.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/flux/-/flux-3.1.9.tgz#ddfc9641ee2e2e6cb6cd730c6a48ef82e2076711"
|
||||
integrity sha512-bSbDf4tTuN9wn3LTGPnH9wnSSLtR5rV7UPWFpM00NJ1pSTBwCzeZG07XsZ9lBkxwngrqjDtM97PLt5IuIdCQUA==
|
||||
dependencies:
|
||||
"@types/fbemitter" "*"
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/glob@^7.1.1":
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"
|
||||
|
|
Loading…
Reference in a new issue