diff --git a/res/css/structures/_LeftPanel.scss b/res/css/structures/_LeftPanel.scss
index 35d9f0e7da..899824bc57 100644
--- a/res/css/structures/_LeftPanel.scss
+++ b/res/css/structures/_LeftPanel.scss
@@ -23,6 +23,14 @@ limitations under the License.
flex: 0 0 auto;
}
+// TODO: Remove temporary indicator of new room list implementation.
+// This border is meant to visually distinguish between the two components when the
+// user has turned on the new room list implementation, at least until the designs
+// themselves give it away.
+.mx_LeftPanel2 .mx_LeftPanel {
+ border-left: 5px #e26dff solid;
+}
+
.mx_LeftPanel_container.collapsed {
min-width: unset;
/* Collapsed LeftPanel 50px */
diff --git a/src/ContentMessages.tsx b/src/ContentMessages.tsx
index 249ad8381c..25445b1c74 100644
--- a/src/ContentMessages.tsx
+++ b/src/ContentMessages.tsx
@@ -31,6 +31,7 @@ import Spinner from "./components/views/elements/Spinner";
// Polyfill for Canvas.toBlob API using Canvas.toDataURL
import "blueimp-canvas-to-blob";
+import { Action } from "./dispatcher/actions";
const MAX_WIDTH = 800;
const MAX_HEIGHT = 600;
@@ -529,7 +530,7 @@ export default class ContentMessages {
dis.dispatch({action: 'upload_started'});
// Focus the composer view
- dis.dispatch({action: 'focus_composer'});
+ dis.fire(Action.FocusComposer);
function onProgress(ev) {
upload.total = ev.total;
diff --git a/src/components/structures/LeftPanel.js b/src/components/structures/LeftPanel.js
index a1b4f49c56..05cd97df2a 100644
--- a/src/components/structures/LeftPanel.js
+++ b/src/components/structures/LeftPanel.js
@@ -26,7 +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";
+import {Action} from "../../dispatcher/actions";
const LeftPanel = createReactClass({
@@ -198,7 +198,7 @@ const LeftPanel = createReactClass({
onSearchCleared: function(source) {
if (source === "keyboard") {
- dis.dispatch({action: 'focus_composer'});
+ dis.fire(Action.FocusComposer);
}
this.setState({searchExpanded: false});
},
@@ -274,28 +274,15 @@ const LeftPanel = createReactClass({
breadcrumbs = ();
}
- let roomList = null;
- if (SettingsStore.isFeatureEnabled("feature_new_room_list")) {
- roomList = ;
- } else {
- roomList = ;
- }
+ const roomList = ;
return (
diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx
new file mode 100644
index 0000000000..c9a4948539
--- /dev/null
+++ b/src/components/structures/LeftPanel2.tsx
@@ -0,0 +1,154 @@
+/*
+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 TagPanel from "./TagPanel";
+import classNames from "classnames";
+import dis from "../../dispatcher/dispatcher";
+import AccessibleButton from "../views/elements/AccessibleButton";
+import { _t } from "../../languageHandler";
+import SearchBox from "./SearchBox";
+import RoomList2 from "../views/rooms/RoomList2";
+import TopLeftMenuButton from "./TopLeftMenuButton";
+import { Action } from "../../dispatcher/actions";
+
+/*******************************************************************
+ * 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. *
+ *******************************************************************/
+
+interface IProps {
+ // TODO: Support collapsed state
+}
+
+interface IState {
+ searchExpanded: boolean;
+ searchFilter: string; // TODO: Move search into room list?
+}
+
+export default class LeftPanel2 extends React.Component
{
+ // TODO: Properly support TagPanel
+ // TODO: Properly support searching/filtering
+ // TODO: Properly support breadcrumbs
+ // TODO: Properly support TopLeftMenu (User Settings)
+ // TODO: a11y
+ // TODO: actually make this useful in general (match design proposals)
+ // TODO: Fadable support (is this still needed?)
+
+ constructor(props: IProps) {
+ super(props);
+
+ this.state = {
+ searchExpanded: false,
+ searchFilter: "",
+ };
+ }
+
+ private onSearch = (term: string): void => {
+ this.setState({searchFilter: term});
+ };
+
+ private onSearchCleared = (source: string): void => {
+ if (source === "keyboard") {
+ dis.fire(Action.FocusComposer);
+ }
+ this.setState({searchExpanded: false});
+ }
+
+ private onSearchFocus = (): void => {
+ this.setState({searchExpanded: true});
+ };
+
+ private onSearchBlur = (event: FocusEvent): void => {
+ const target = event.target as HTMLInputElement;
+ if (target.value.length === 0) {
+ this.setState({searchExpanded: false});
+ }
+ }
+
+ public render(): React.ReactNode {
+ const tagPanel = (
+
+
+
+ );
+
+ const exploreButton = (
+
+
dis.dispatch({action: 'view_room_directory'})}>
+ {_t("Explore")}
+
+
+ );
+
+ const searchBox = ( {/*TODO*/}}
+ onSearch={this.onSearch}
+ onCleared={this.onSearchCleared}
+ onFocus={this.onSearchFocus}
+ onBlur={this.onSearchBlur}
+ collapsed={false}/>); // TODO: Collapsed support
+
+ // TODO: Improve props for RoomList2
+ const roomList = {/*TODO*/}}
+ resizeNotifier={null}
+ collapsed={false}
+ searchFilter={this.state.searchFilter}
+ onFocus={() => {/*TODO*/}}
+ onBlur={() => {/*TODO*/}}
+ />;
+
+ // TODO: Breadcrumbs
+ // TODO: Conference handling / calls
+
+ const containerClasses = classNames({
+ "mx_LeftPanel_container": true,
+ "mx_fadable": true,
+ "collapsed": false, // TODO: Collapsed support
+ "mx_LeftPanel_container_hasTagPanel": true, // TODO: TagPanel support
+ "mx_fadable_faded": false,
+ "mx_LeftPanel2": true, // TODO: Remove flag when RoomList2 ships (used as an indicator)
+ });
+
+ return (
+
+ );
+ }
+}
diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx
index 7fcaadf7a5..0504e3a76a 100644
--- a/src/components/structures/LoggedInView.tsx
+++ b/src/components/structures/LoggedInView.tsx
@@ -51,6 +51,8 @@ import {
showToast as showServerLimitToast,
hideToast as hideServerLimitToast
} from "../../toasts/ServerLimitToast";
+import { Action } from "../../dispatcher/actions";
+import LeftPanel2 from "./LeftPanel2";
// 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.
@@ -358,7 +360,7 @@ class LoggedInView extends React.PureComponent {
// refocusing during a paste event will make the
// paste end up in the newly focused element,
// so dispatch synchronously before paste happens
- dis.dispatch({action: 'focus_composer'}, true);
+ dis.fire(Action.FocusComposer, true);
}
};
@@ -508,7 +510,7 @@ class LoggedInView extends React.PureComponent {
if (!isClickShortcut && ev.key !== Key.TAB && !canElementReceiveInput(ev.target)) {
// synchronous dispatch so we focus before key generates input
- dis.dispatch({action: 'focus_composer'}, true);
+ dis.fire(Action.FocusComposer, true);
ev.stopPropagation();
// we should *not* preventDefault() here as
// that would prevent typing in the now-focussed composer
@@ -667,6 +669,20 @@ class LoggedInView extends React.PureComponent {
bodyClasses += ' mx_MatrixChat_useCompactLayout';
}
+ let leftPanel = (
+
+ );
+ if (SettingsStore.isFeatureEnabled("feature_new_room_list")) {
+ // TODO: Supply props like collapsed and disabled to LeftPanel2
+ leftPanel = (
+
+ );
+ }
+
return (
{
-
+ { leftPanel }
{ pageElement }
diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx
index 7aaedcfb09..69f91047b7 100644
--- a/src/components/structures/MatrixChat.tsx
+++ b/src/components/structures/MatrixChat.tsx
@@ -347,7 +347,7 @@ export default class MatrixChat extends React.PureComponent {
Analytics.trackPageChange(durationMs);
}
if (this.focusComposer) {
- dis.dispatch({action: 'focus_composer'});
+ dis.fire(Action.FocusComposer);
this.focusComposer = false;
}
}
@@ -1363,7 +1363,7 @@ export default class MatrixChat extends React.PureComponent {
showNotificationsToast();
}
- dis.dispatch({action: 'focus_composer'});
+ dis.fire(Action.FocusComposer);
this.setState({
ready: true,
});
diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js
index dd4b9759d6..65d062cfaa 100644
--- a/src/components/structures/RoomStatusBar.js
+++ b/src/components/structures/RoomStatusBar.js
@@ -26,6 +26,7 @@ import {MatrixClientPeg} from '../../MatrixClientPeg';
import Resend from '../../Resend';
import dis from '../../dispatcher/dispatcher';
import {messageForResourceLimitError, messageForSendError} from '../../utils/ErrorUtils';
+import {Action} from "../../dispatcher/actions";
const STATUS_BAR_HIDDEN = 0;
const STATUS_BAR_EXPANDED = 1;
@@ -127,12 +128,12 @@ export default createReactClass({
_onResendAllClick: function() {
Resend.resendUnsentEvents(this.props.room);
- dis.dispatch({action: 'focus_composer'});
+ dis.fire(Action.FocusComposer);
},
_onCancelAllClick: function() {
Resend.cancelUnsentEvents(this.props.room);
- dis.dispatch({action: 'focus_composer'});
+ dis.fire(Action.FocusComposer);
},
_onRoomLocalEchoUpdated: function(event, room, oldEventId, oldStatus) {
diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js
index 49d7e3c238..0ff997ee09 100644
--- a/src/components/structures/RoomView.js
+++ b/src/components/structures/RoomView.js
@@ -55,6 +55,7 @@ import {haveTileForEvent} from "../views/rooms/EventTile";
import RoomContext from "../../contexts/RoomContext";
import MatrixClientContext from "../../contexts/MatrixClientContext";
import { shieldStatusForRoom } from '../../utils/ShieldUtils';
+import {Action} from "../../dispatcher/actions";
const DEBUG = false;
let debuglog = function() {};
@@ -1162,7 +1163,7 @@ export default createReactClass({
ev.dataTransfer.files, this.state.room.roomId, this.context,
);
this.setState({ draggingFile: false });
- dis.dispatch({action: 'focus_composer'});
+ dis.fire(Action.FocusComposer);
},
onDragLeaveOrEnd: function(ev) {
@@ -1368,7 +1369,7 @@ export default createReactClass({
event: null,
});
}
- dis.dispatch({action: 'focus_composer'});
+ dis.fire(Action.FocusComposer);
},
onLeaveClick: function() {
@@ -1479,7 +1480,7 @@ export default createReactClass({
// jump down to the bottom of this room, where new events are arriving
jumpToLiveTimeline: function() {
this._messagePanel.jumpToLiveTimeline();
- dis.dispatch({action: 'focus_composer'});
+ dis.fire(Action.FocusComposer);
},
// jump up to wherever our read marker is
diff --git a/src/components/views/elements/ReplyThread.js b/src/components/views/elements/ReplyThread.js
index e7f7196ac6..e96d9ced11 100644
--- a/src/components/views/elements/ReplyThread.js
+++ b/src/components/views/elements/ReplyThread.js
@@ -26,6 +26,7 @@ import {makeUserPermalink, RoomPermalinkCreator} from "../../../utils/permalinks
import SettingsStore from "../../../settings/SettingsStore";
import escapeHtml from "escape-html";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
+import {Action} from "../../../dispatcher/actions";
// This component does no cycle detection, simply because the only way to make such a cycle would be to
// craft event_id's, using a homeserver that generates predictable event IDs; even then the impact would
@@ -290,7 +291,7 @@ export default class ReplyThread extends React.Component {
events,
}, this.loadNextEvent);
- dis.dispatch({action: 'focus_composer'});
+ dis.fire(Action.FocusComposer);
}
render() {
diff --git a/src/components/views/rooms/EditMessageComposer.js b/src/components/views/rooms/EditMessageComposer.js
index b70ef6255c..78c7de887d 100644
--- a/src/components/views/rooms/EditMessageComposer.js
+++ b/src/components/views/rooms/EditMessageComposer.js
@@ -31,6 +31,7 @@ import {EventStatus} from 'matrix-js-sdk';
import BasicMessageComposer from "./BasicMessageComposer";
import {Key} from "../../../Keyboard";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
+import {Action} from "../../../dispatcher/actions";
function _isReply(mxEvent) {
const relatesTo = mxEvent.getContent()["m.relates_to"];
@@ -157,7 +158,7 @@ export default class EditMessageComposer extends React.Component {
dis.dispatch({action: 'edit_event', event: nextEvent});
} else {
dis.dispatch({action: 'edit_event', event: null});
- dis.dispatch({action: 'focus_composer'});
+ dis.fire(Action.FocusComposer);
}
event.preventDefault();
}
@@ -165,7 +166,7 @@ export default class EditMessageComposer extends React.Component {
_cancelEdit = () => {
dis.dispatch({action: "edit_event", event: null});
- dis.dispatch({action: 'focus_composer'});
+ dis.fire(Action.FocusComposer);
}
_isContentModified(newContent) {
@@ -195,7 +196,7 @@ export default class EditMessageComposer extends React.Component {
// close the event editing and focus composer
dis.dispatch({action: "edit_event", event: null});
- dis.dispatch({action: 'focus_composer'});
+ dis.fire(Action.FocusComposer);
};
_cancelPreviousPendingEdit() {
diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js
index 3098c62433..25ad192ea4 100644
--- a/src/components/views/rooms/SendMessageComposer.js
+++ b/src/components/views/rooms/SendMessageComposer.js
@@ -44,6 +44,7 @@ import {Key} from "../../../Keyboard";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import RateLimitedFunc from '../../../ratelimitedfunc';
+import {Action} from "../../../dispatcher/actions";
function addReplyToMessageContent(content, repliedToEvent, permalinkCreator) {
const replyContent = ReplyThread.makeReplyMixIn(repliedToEvent);
@@ -364,7 +365,7 @@ export default class SendMessageComposer extends React.Component {
onAction = (payload) => {
switch (payload.action) {
case 'reply_to_event':
- case 'focus_composer':
+ case Action.FocusComposer:
this._editorRef && this._editorRef.focus();
break;
case 'insert_mention':
diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts
index 7e76ea5ccb..71493d6e44 100644
--- a/src/dispatcher/actions.ts
+++ b/src/dispatcher/actions.ts
@@ -53,4 +53,9 @@ export enum Action {
* Provide status information for an ongoing update check. Should be used with a CheckUpdatesPayload.
*/
CheckUpdates = "check_updates",
+
+ /**
+ * Focuses the user's cursor to the composer. No additional payload information required.
+ */
+ FocusComposer = "focus_composer",
}