From 7bd6bb6eb652c4c88a8f2d004383ab7a789260f9 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 7 Nov 2018 13:19:29 +0100 Subject: [PATCH 01/51] make MatrixDispatcher constructor public so we can create one for each open room --- src/dispatcher.js | 36 ++------------------------- src/matrix-dispatcher.js | 53 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 34 deletions(-) create mode 100644 src/matrix-dispatcher.js diff --git a/src/dispatcher.js b/src/dispatcher.js index 48c8dc86e9..4dc6e1e37d 100644 --- a/src/dispatcher.js +++ b/src/dispatcher.js @@ -17,42 +17,10 @@ limitations under the License. 'use strict'; -const flux = require("flux"); - -class MatrixDispatcher extends flux.Dispatcher { - /** - * @param {Object|function} payload Required. The payload to dispatch. - * If an Object, must contain at least an 'action' key. - * If a function, must have the signature (dispatch) => {...}. - * @param {boolean=} sync Optional. Pass true to dispatch - * synchronously. This is useful for anything triggering - * an operation that the browser requires user interaction - * for. - */ - dispatch(payload, sync) { - // Allow for asynchronous dispatching by accepting payloads that have the - // type `function (dispatch) {...}` - if (typeof payload === 'function') { - payload((action) => { - this.dispatch(action, sync); - }); - return; - } - - if (sync) { - super.dispatch(payload); - } else { - // Unless the caller explicitly asked for us to dispatch synchronously, - // we always set a timeout to do this: The flux dispatcher complains - // if you dispatch from within a dispatch, so rather than action - // handlers having to worry about not calling anything that might - // then dispatch, we just do dispatches asynchronously. - setTimeout(super.dispatch.bind(this, payload), 0); - } - } -} +import MatrixDispatcher from "./matrix-dispatcher"; if (global.mxDispatcher === undefined) { global.mxDispatcher = new MatrixDispatcher(); } + module.exports = global.mxDispatcher; diff --git a/src/matrix-dispatcher.js b/src/matrix-dispatcher.js new file mode 100644 index 0000000000..fb81ed837f --- /dev/null +++ b/src/matrix-dispatcher.js @@ -0,0 +1,53 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd +Copyright 2017 New Vector Ltd + +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. +*/ + +'use strict'; + +const flux = require("flux"); + +export default class MatrixDispatcher extends flux.Dispatcher { + /** + * @param {Object|function} payload Required. The payload to dispatch. + * If an Object, must contain at least an 'action' key. + * If a function, must have the signature (dispatch) => {...}. + * @param {boolean=} sync Optional. Pass true to dispatch + * synchronously. This is useful for anything triggering + * an operation that the browser requires user interaction + * for. + */ + dispatch(payload, sync) { + // Allow for asynchronous dispatching by accepting payloads that have the + // type `function (dispatch) {...}` + if (typeof payload === 'function') { + payload((action) => { + this.dispatch(action, sync); + }); + return; + } + + if (sync) { + super.dispatch(payload); + } else { + // Unless the caller explicitly asked for us to dispatch synchronously, + // we always set a timeout to do this: The flux dispatcher complains + // if you dispatch from within a dispatch, so rather than action + // handlers having to worry about not calling anything that might + // then dispatch, we just do dispatches asynchronously. + setTimeout(super.dispatch.bind(this, payload), 0); + } + } +} From 869c81eb9059900d2a6ac1d99aab5a4acd407253 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 7 Nov 2018 13:21:14 +0100 Subject: [PATCH 02/51] cram OpenRoomsStore between RoomViewStore and dispatcher the idea is that it will keep a RoomViewStore for every room on the screen, and also keep track of which one is the current one. For now, it just replicates the existing functionality of having just 1 room on the screen. Since the RoomViewStore just has access to a local dispatcher and not the global anymore, all dispatching of actions needs to be moved to the OpenRoomsStore, so room alias resolving, event forwarding, ... is moved there. --- src/stores/OpenRoomsStore.js | 166 +++++++++++++++++++++++++++++++++++ src/stores/RoomViewStore.js | 45 ++-------- 2 files changed, 174 insertions(+), 37 deletions(-) create mode 100644 src/stores/OpenRoomsStore.js diff --git a/src/stores/OpenRoomsStore.js b/src/stores/OpenRoomsStore.js new file mode 100644 index 0000000000..f82671e58d --- /dev/null +++ b/src/stores/OpenRoomsStore.js @@ -0,0 +1,166 @@ +/* +Copyright 2018 New Vector Ltd + +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 MatrixDispatcher from '../matrix-dispatcher'; +import dis from '../dispatcher'; +import {RoomViewStore} from './RoomViewStore'; +import {Store} from 'flux/utils'; +import MatrixClientPeg from '../MatrixClientPeg'; + +/** + * A class for keeping track of the RoomViewStores of the rooms shown on the screen. + * Routes the dispatcher actions to the store of currently active room. + */ +class OpenRoomsStore extends Store { + constructor() { + super(dis); + + // Initialise state + this._state = { + room: { + store: null, + dispatcher: null + }, + }; + + this._forwardingEvent = null; + } + + getRoomStore() { + return this._state.room.store; + } + + getCurrentRoomStore() { + return this.getRoomStore(); // just one room for now + } + + _setState(newState) { + this._state = Object.assign(this._state, newState); + this.__emitChange(); + } + + _cleanupRoom() { + const room = this._state.room; + room.dispatcher.unregister(room.store.getDispatchToken()); + this._setState({ + room: { + store: null, + dispatcher: null + }, + }); + } + + _createRoom() { + const dispatcher = new MatrixDispatcher(); + this._setState({ + room: { + store: new RoomViewStore(dispatcher), + dispatcher, + }, + }); + } + + _forwardAction(payload) { + if (this._state.room.dispatcher) { + this._state.room.dispatcher.dispatch(payload, true); + } + } + + async _resolveRoomAlias(payload) { + try { + const result = await MatrixClientPeg.get() + .getRoomIdForAlias(payload.room_alias); + dis.dispatch({ + action: 'view_room', + room_id: result.room_id, + event_id: payload.event_id, + highlighted: payload.highlighted, + room_alias: payload.room_alias, + auto_join: payload.auto_join, + oob_data: payload.oob_data, + }); + } catch(err) { + this._forwardAction({ + action: 'view_room_error', + room_id: null, + room_alias: payload.room_alias, + err: err, + }); + } + } + + __onDispatch(payload) { + switch (payload.action) { + // view_room: + // - room_alias: '#somealias:matrix.org' + // - room_id: '!roomid123:matrix.org' + // - event_id: '$213456782:matrix.org' + // - event_offset: 100 + // - highlighted: true + case 'view_room': + console.log("!!! OpenRoomsStore: view_room", payload); + if (!payload.room_id && payload.room_alias) { + this._resolveRoomAlias(payload); + } + const currentStore = this.getCurrentRoomStore(); + if (currentStore && + (!payload.room_alias || payload.room_alias !== currentStore.getRoomAlias()) && + (!currentStore.getRoomId() || payload.room_id !== currentStore.getRoomId()) + ) { + console.log("OpenRoomsStore: _cleanupRoom"); + this._cleanupRoom(); + } + if (!this._state.room.store) { + console.log("OpenRoomsStore: _createRoom"); + this._createRoom(); + } + console.log("OpenRoomsStore: _forwardAction"); + this._forwardAction(payload); + if (this._forwardingEvent) { + dis.dispatch({ + action: 'send_event', + room_id: payload.room_id, + event: this._forwardingEvent, + }); + this._forwardingEvent = null; + } + break; + case 'view_my_groups': + case 'view_group': + this._forwardAction(payload); + this._cleanupRoom(); + break; + case 'will_join': + case 'cancel_join': + case 'join_room': + case 'join_room_error': + case 'on_logged_out': + case 'reply_to_event': + case 'open_room_settings': + case 'close_settings': + this._forwardAction(payload); + break; + case 'forward_event': + this._forwardingEvent = payload.event; + break; + } + } +} + +let singletonOpenRoomsStore = null; +if (!singletonOpenRoomsStore) { + singletonOpenRoomsStore = new OpenRoomsStore(); +} +module.exports = singletonOpenRoomsStore; diff --git a/src/stores/RoomViewStore.js b/src/stores/RoomViewStore.js index f15925f480..43964bc6c3 100644 --- a/src/stores/RoomViewStore.js +++ b/src/stores/RoomViewStore.js @@ -53,8 +53,8 @@ const INITIAL_STATE = { * with a subset of the js-sdk. * ``` */ -class RoomViewStore extends Store { - constructor() { +export class RoomViewStore extends Store { + constructor(dis) { super(dis); // Initialise state @@ -85,6 +85,8 @@ class RoomViewStore extends Store { }); break; case 'view_room_error': + // should not go over dispatcher anymore + // but be internal to RoomViewStore this._viewRoomError(payload); break; case 'will_join': @@ -150,22 +152,11 @@ class RoomViewStore extends Store { // pull the user out of Room Settings isEditingSettings: false, }; - - if (this._state.forwardingEvent) { - dis.dispatch({ - action: 'send_event', - room_id: newState.roomId, - event: this._state.forwardingEvent, - }); - } - this._setState(newState); - if (payload.auto_join) { this._joinRoom(payload); } } else if (payload.room_alias) { - // Resolve the alias and then do a second dispatch with the room ID acquired this._setState({ roomId: null, initialEventId: null, @@ -175,25 +166,6 @@ class RoomViewStore extends Store { roomLoading: true, roomLoadError: null, }); - MatrixClientPeg.get().getRoomIdForAlias(payload.room_alias).done( - (result) => { - dis.dispatch({ - action: 'view_room', - room_id: result.room_id, - event_id: payload.event_id, - highlighted: payload.highlighted, - room_alias: payload.room_alias, - auto_join: payload.auto_join, - oob_data: payload.oob_data, - }); - }, (err) => { - dis.dispatch({ - action: 'view_room_error', - room_id: null, - room_alias: payload.room_alias, - err: err, - }); - }); } } @@ -330,8 +302,7 @@ class RoomViewStore extends Store { } } -let singletonRoomViewStore = null; -if (!singletonRoomViewStore) { - singletonRoomViewStore = new RoomViewStore(); -} -module.exports = singletonRoomViewStore; +const MatrixDispatcher = require("../matrix-dispatcher"); +const blubber = new RoomViewStore(new MatrixDispatcher()); + +export default blubber; From df8539d6bc59c0857fdaf8613da891558bd70445 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 7 Nov 2018 13:24:35 +0100 Subject: [PATCH 03/51] pass the RoomViewStore down with a prop instead of global var. this will allow to have more than 1 RoomView further on --- src/components/structures/LoggedInView.js | 1 + src/components/structures/RightPanel.js | 2 +- src/components/structures/RoomView.js | 33 ++++++++++--------- src/components/views/rooms/MemberInfo.js | 4 +-- src/components/views/rooms/MessageComposer.js | 11 ++++--- .../views/rooms/MessageComposerInput.js | 6 ++-- src/components/views/rooms/ReplyPreview.js | 5 ++- 7 files changed, 32 insertions(+), 30 deletions(-) diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index 635c5de44e..b81597a901 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -429,6 +429,7 @@ const LoggedInView = React.createClass({ switch (this.props.page_type) { case PageTypes.RoomView: page_element = ; } else if (this.state.phase === RightPanel.Phase.RoomMemberInfo) { - panel = ; + panel = ; } else if (this.state.phase === RightPanel.Phase.GroupMemberInfo) { panel = { try { await cli.invite(roomId, member.userId); diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index dc927f0e0a..5ac788fb1d 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -22,7 +22,6 @@ import MatrixClientPeg from '../../../MatrixClientPeg'; import Modal from '../../../Modal'; import sdk from '../../../index'; import dis from '../../../dispatcher'; -import RoomViewStore from '../../../stores/RoomViewStore'; import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore"; import Stickerpicker from './Stickerpicker'; import { makeRoomPermalink } from '../../../matrix-to'; @@ -63,7 +62,7 @@ export default class MessageComposer extends React.Component { isRichTextEnabled: SettingsStore.getValue('MessageComposerInput.isRichTextEnabled'), }, showFormatting: SettingsStore.getValue('MessageComposer.showFormatting'), - isQuoting: Boolean(RoomViewStore.getQuotingEvent()), + isQuoting: Boolean(this.props.roomViewStore.getQuotingEvent()), tombstone: this._getRoomTombstone(), }; } @@ -75,7 +74,7 @@ export default class MessageComposer extends React.Component { // XXX: fragile as all hell - fixme somehow, perhaps with a dedicated Room.encryption event or something. MatrixClientPeg.get().on("event", this.onEvent); MatrixClientPeg.get().on("RoomState.events", this._onRoomStateEvents); - this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate); + this._roomStoreToken = this.props.roomViewStore.addListener(this._onRoomViewStoreUpdate); this._waitForOwnMember(); } @@ -124,7 +123,7 @@ export default class MessageComposer extends React.Component { } _onRoomViewStoreUpdate() { - const isQuoting = Boolean(RoomViewStore.getQuotingEvent()); + const isQuoting = Boolean(this.props.roomViewStore.getQuotingEvent()); if (this.state.isQuoting === isQuoting) return; this.setState({ isQuoting }); } @@ -153,7 +152,7 @@ export default class MessageComposer extends React.Component { ); } - const isQuoting = Boolean(RoomViewStore.getQuotingEvent()); + const isQuoting = Boolean(this.props.roomViewStore.getQuotingEvent()); let replyToWarning = null; if (isQuoting) { replyToWarning =

{ @@ -357,6 +356,7 @@ export default class MessageComposer extends React.Component { controls.push( this.messageComposerInput = c} key="controls_input" onResize={this.props.onResize} @@ -461,4 +461,5 @@ MessageComposer.propTypes = { // string representing the current room app drawer state showApps: PropTypes.bool, + roomViewStore: PropTypes.object.isRequired, }; diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index 14d394ab41..da41dba212 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -58,7 +58,6 @@ import {asciiRegexp, unicodeRegexp, shortnameToUnicode, emojioneList, asciiList, import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore"; import {makeUserPermalink} from "../../../matrix-to"; import ReplyPreview from "./ReplyPreview"; -import RoomViewStore from '../../../stores/RoomViewStore'; import ReplyThread from "../elements/ReplyThread"; import {ContentHelpers} from 'matrix-js-sdk'; @@ -150,6 +149,7 @@ export default class MessageComposerInput extends React.Component { onFilesPasted: PropTypes.func, onInputStateChanged: PropTypes.func, + roomViewStore: PropTypes.object.isRequired, }; client: MatrixClient; @@ -1120,7 +1120,7 @@ export default class MessageComposerInput extends React.Component { return true; } - const replyingToEv = RoomViewStore.getQuotingEvent(); + const replyingToEv = this.props.roomViewStore.getQuotingEvent(); const mustSendHTML = Boolean(replyingToEv); if (this.state.isRichTextEnabled) { @@ -1589,7 +1589,7 @@ export default class MessageComposerInput extends React.Component { return (

- + this.autocomplete = e} room={this.props.room} diff --git a/src/components/views/rooms/ReplyPreview.js b/src/components/views/rooms/ReplyPreview.js index 46e2826634..04ff9d0778 100644 --- a/src/components/views/rooms/ReplyPreview.js +++ b/src/components/views/rooms/ReplyPreview.js @@ -18,7 +18,6 @@ import React from 'react'; import dis from '../../../dispatcher'; import sdk from '../../../index'; import { _t } from '../../../languageHandler'; -import RoomViewStore from '../../../stores/RoomViewStore'; import SettingsStore from "../../../settings/SettingsStore"; function cancelQuoting() { @@ -38,7 +37,7 @@ export default class ReplyPreview extends React.Component { this._onRoomViewStoreUpdate = this._onRoomViewStoreUpdate.bind(this); - this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate); + this._roomStoreToken = this.props.roomViewStore.addListener(this._onRoomViewStoreUpdate); this._onRoomViewStoreUpdate(); } @@ -50,7 +49,7 @@ export default class ReplyPreview extends React.Component { } _onRoomViewStoreUpdate() { - const event = RoomViewStore.getQuotingEvent(); + const event = this.props.roomViewStore.getQuotingEvent(); if (this.state.event !== event) { this.setState({ event }); } From 43efa29ef8d284da1286867fb48525166196bd52 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 7 Nov 2018 13:25:42 +0100 Subject: [PATCH 04/51] track active room with OpenRoomsStore --- src/ActiveRoomObserver.js | 15 ++++++++------- src/components/views/rooms/RoomTile.js | 4 ++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/ActiveRoomObserver.js b/src/ActiveRoomObserver.js index d6fbb460b5..ee3212f611 100644 --- a/src/ActiveRoomObserver.js +++ b/src/ActiveRoomObserver.js @@ -14,10 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -import RoomViewStore from './stores/RoomViewStore'; +import OpenRoomsStore from './stores/OpenRoomsStore'; /** - * Consumes changes from the RoomViewStore and notifies specific things + * Consumes changes from the OpenRoomsStore and notifies specific things * about when the active room changes. Unlike listening for RoomViewStore * changes, you can subscribe to only changes relevant to a particular * room. @@ -28,11 +28,11 @@ import RoomViewStore from './stores/RoomViewStore'; class ActiveRoomObserver { constructor() { this._listeners = {}; - - this._activeRoomId = RoomViewStore.getRoomId(); + const roomStore = OpenRoomsStore.getCurrentRoomStore(); + this._activeRoomId = roomStore && roomStore.getRoomId(); // TODO: We could self-destruct when the last listener goes away, or at least // stop listening. - this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate.bind(this)); + this._roomStoreToken = OpenRoomsStore.addListener(this._onOpenRoomsStoreUpdate.bind(this)); } addListener(roomId, listener) { @@ -59,12 +59,13 @@ class ActiveRoomObserver { } } - _onRoomViewStoreUpdate() { + _onOpenRoomsStoreUpdate() { // emit for the old room ID if (this._activeRoomId) this._emit(this._activeRoomId); + const activeRoomStore = OpenRoomsStore.getCurrentRoomStore(); // update our cache - this._activeRoomId = RoomViewStore.getRoomId(); + this._activeRoomId = activeRoomStore && activeRoomStore.getRoomId(); // and emit for the new one if (this._activeRoomId) this._emit(this._activeRoomId); diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index faa08c7001..71be2df5a4 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -116,9 +116,9 @@ module.exports = React.createClass({ } }, - _onActiveRoomChange: function() { + _onActiveRoomChange: function(activeRoomId) { this.setState({ - selected: this.props.room.roomId === RoomViewStore.getRoomId(), + selected: this.props.room.roomId === activeRoomId, }); }, From 78d5d7ac0c55e0b6c50a0a56ec170795d0ef8dd9 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 7 Nov 2018 16:28:58 +0100 Subject: [PATCH 05/51] correctly detected collapsed rhs --- src/components/structures/MainSplit.js | 2 +- src/components/structures/RoomGridView.js | 92 +++++++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 src/components/structures/RoomGridView.js diff --git a/src/components/structures/MainSplit.js b/src/components/structures/MainSplit.js index 6fd0274f1a..5c69ef6745 100644 --- a/src/components/structures/MainSplit.js +++ b/src/components/structures/MainSplit.js @@ -55,7 +55,7 @@ export default class MainSplit extends React.Component { } componentDidMount() { - if (this.props.panel && !this.collapsedRhs) { + if (this.props.panel && !this.props.collapsedRhs) { this._createResizer(); } } diff --git a/src/components/structures/RoomGridView.js b/src/components/structures/RoomGridView.js new file mode 100644 index 0000000000..d472767dbb --- /dev/null +++ b/src/components/structures/RoomGridView.js @@ -0,0 +1,92 @@ +/* +Copyright 2017 Vector Creations Ltd. +Copyright 2017, 2018 New Vector Ltd. + +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 from 'react'; +import PropTypes from 'prop-types'; + +export default class RoomGridView extends React.Component { + /* displayName: 'GroupView', + + propTypes: { + groupId: PropTypes.string.isRequired, + }, + + childContextTypes = { + groupStore: PropTypes.instanceOf(GroupStore), + };*/ + + getInitialState() { + return { + rooms: [], + }; + } + + componentWillMount() { + this._unmounted = false; + this._initGroupStore(this.props.groupId); + this._dispatcherRef = dis.register(this._onAction); + } + + componentWillUnmount() { + this._unmounted = true; + if (this._groupStoreRegistration) { + this._groupStoreRegistration.unregister(); + } + dis.unregister(this._dispatcherRef); + } + + componentWillReceiveProps(newProps) { + if (this.props.groupId != newProps.groupId) { + this.setState(this.getInitialState(), () => { + this._initGroupStore(newProps.groupId); + }); + } + } + + _initGroupStore(groupId) { + if (this._groupStoreRegistration) { + this._groupStoreRegistration.unregister(); + } + this._groupStoreRegistration = GroupStore.registerListener(groupId, this.onGroupStoreUpdated); + } + + onGroupStoreUpdated() { + if (this._unmounted) return; + this.setState({ + rooms: GroupStore.getGroupRooms(this.props.groupId), + }); + } + + _onAction(payload) { + switch (payload.action) { + default: + break; + } + } + + render() { + const rooms = this.state.rooms.slice(0, 6); + return
+ { rooms.map(room => { +
+ +
+ }) } +
+ } + +} From 720bc11aa403e98650cf3952232518c9628bd3d5 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 7 Nov 2018 16:29:17 +0100 Subject: [PATCH 06/51] avoid using roomviewstore for detecting selected room --- src/ActiveRoomObserver.js | 4 ++++ src/components/views/rooms/RoomTile.js | 3 +-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ActiveRoomObserver.js b/src/ActiveRoomObserver.js index ee3212f611..2561a2e6a3 100644 --- a/src/ActiveRoomObserver.js +++ b/src/ActiveRoomObserver.js @@ -35,6 +35,10 @@ class ActiveRoomObserver { this._roomStoreToken = OpenRoomsStore.addListener(this._onOpenRoomsStoreUpdate.bind(this)); } + getActiveRoomId() { + return this._activeRoomId; + } + addListener(roomId, listener) { if (!this._listeners[roomId]) this._listeners[roomId] = []; this._listeners[roomId].push(listener); diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 71be2df5a4..2bc06ecc7a 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -29,7 +29,6 @@ import * as RoomNotifs from '../../../RoomNotifs'; import * as FormattingUtils from '../../../utils/FormattingUtils'; import AccessibleButton from '../elements/AccessibleButton'; import ActiveRoomObserver from '../../../ActiveRoomObserver'; -import RoomViewStore from '../../../stores/RoomViewStore'; module.exports = React.createClass({ displayName: 'RoomTile', @@ -61,7 +60,7 @@ module.exports = React.createClass({ roomName: this.props.room.name, notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId), notificationCount: this.props.room.getUnreadNotificationCount(), - selected: this.props.room.roomId === RoomViewStore.getRoomId(), + selected: this.props.room.roomId === ActiveRoomObserver.getActiveRoomId(), }); }, From f95b26179fbc558c1200f6e5c3e8476a5969084a Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 7 Nov 2018 16:30:02 +0100 Subject: [PATCH 07/51] make copy of initial state, as there can be multiple instances now --- src/stores/RoomViewStore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stores/RoomViewStore.js b/src/stores/RoomViewStore.js index 43964bc6c3..5ec0c67f2d 100644 --- a/src/stores/RoomViewStore.js +++ b/src/stores/RoomViewStore.js @@ -58,7 +58,7 @@ export class RoomViewStore extends Store { super(dis); // Initialise state - this._state = INITIAL_STATE; + this._state = Object.assign({}, INITIAL_STATE); } _setState(newState) { From d7924ad1a8e4876ab8f016f535c7b91165326a08 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 7 Nov 2018 16:30:29 +0100 Subject: [PATCH 08/51] less ambigious name for local dispatcher --- src/stores/RoomViewStore.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/stores/RoomViewStore.js b/src/stores/RoomViewStore.js index 5ec0c67f2d..af1264921a 100644 --- a/src/stores/RoomViewStore.js +++ b/src/stores/RoomViewStore.js @@ -54,8 +54,8 @@ const INITIAL_STATE = { * ``` */ export class RoomViewStore extends Store { - constructor(dis) { - super(dis); + constructor(dispatcher) { + super(dispatcher); // Initialise state this._state = Object.assign({}, INITIAL_STATE); From 6ec6303b973c0fd51928e61591472784a85563b3 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 7 Nov 2018 16:32:12 +0100 Subject: [PATCH 09/51] support opening all rooms of a group in OpenRoomsStore using new view_group_grid action --- src/stores/OpenRoomsStore.js | 112 +++++++++++++++++++++++++++-------- 1 file changed, 87 insertions(+), 25 deletions(-) diff --git a/src/stores/OpenRoomsStore.js b/src/stores/OpenRoomsStore.js index f82671e58d..6904689157 100644 --- a/src/stores/OpenRoomsStore.js +++ b/src/stores/OpenRoomsStore.js @@ -16,9 +16,21 @@ limitations under the License. import MatrixDispatcher from '../matrix-dispatcher'; import dis from '../dispatcher'; import {RoomViewStore} from './RoomViewStore'; +import GroupStore from './GroupStore'; import {Store} from 'flux/utils'; import MatrixClientPeg from '../MatrixClientPeg'; + +function matchesRoom(payload, roomStore) { + if (!roomStore) { + return false; + } + if (payload.room_alias) { + return payload.room_alias === roomStore.getRoomAlias(); + } + return payload.room_id === roomStore.getRoomId(); +} + /** * A class for keeping track of the RoomViewStores of the rooms shown on the screen. * Routes the dispatcher actions to the store of currently active room. @@ -29,21 +41,30 @@ class OpenRoomsStore extends Store { // Initialise state this._state = { - room: { - store: null, - dispatcher: null - }, + rooms: [], + currentIndex: null, + group_id: null, }; this._forwardingEvent = null; } - getRoomStore() { - return this._state.room.store; + getRoomStores() { + return this._state.rooms.map((r) => r.store); } getCurrentRoomStore() { - return this.getRoomStore(); // just one room for now + const currentRoom = this._getCurrentRoom(); + if (currentRoom) { + return currentRoom.store; + } + } + + _getCurrentRoom() { + const index = this._state.currentIndex; + if (index !== null && index < this._state.rooms.length) { + return this._state.rooms[index]; + } } _setState(newState) { @@ -51,30 +72,41 @@ class OpenRoomsStore extends Store { this.__emitChange(); } - _cleanupRoom() { + _hasRoom(payload) { + return this._roomIndex(payload) !== -1; + } + + _roomIndex(payload) { + return this._state.rooms.findIndex((r) => matchesRoom(payload, r.store)); + } + + _cleanupRooms() { const room = this._state.room; - room.dispatcher.unregister(room.store.getDispatchToken()); + this._state.rooms.forEach((room) => { + room.dispatcher.unregister(room.store.getDispatchToken()); + }); this._setState({ - room: { - store: null, - dispatcher: null - }, + rooms: [], + group_id: null, + currentIndex: null }); } _createRoom() { const dispatcher = new MatrixDispatcher(); this._setState({ - room: { + rooms: [{ store: new RoomViewStore(dispatcher), dispatcher, - }, + }], + currentIndex: 0, }); } _forwardAction(payload) { - if (this._state.room.dispatcher) { - this._state.room.dispatcher.dispatch(payload, true); + const currentRoom = this._getCurrentRoom(); + if (currentRoom) { + currentRoom.dispatcher.dispatch(payload, true); } } @@ -101,6 +133,10 @@ class OpenRoomsStore extends Store { } } + _setCurrentGroupRoom(index) { + this._setState({currentIndex: index}); + } + __onDispatch(payload) { switch (payload.action) { // view_room: @@ -115,14 +151,15 @@ class OpenRoomsStore extends Store { this._resolveRoomAlias(payload); } const currentStore = this.getCurrentRoomStore(); - if (currentStore && - (!payload.room_alias || payload.room_alias !== currentStore.getRoomAlias()) && - (!currentStore.getRoomId() || payload.room_id !== currentStore.getRoomId()) - ) { - console.log("OpenRoomsStore: _cleanupRoom"); - this._cleanupRoom(); + if (matchesRoom(payload, currentStore)) { + if (this._hasRoom(payload)) { + const roomIndex = this._roomIndex(payload); + this._setState({currentIndex: roomIndex}); + } else { + this._cleanupRooms(); + } } - if (!this._state.room.store) { + if (!this.getCurrentRoomStore()) { console.log("OpenRoomsStore: _createRoom"); this._createRoom(); } @@ -140,7 +177,7 @@ class OpenRoomsStore extends Store { case 'view_my_groups': case 'view_group': this._forwardAction(payload); - this._cleanupRoom(); + this._cleanupRooms(); break; case 'will_join': case 'cancel_join': @@ -155,6 +192,31 @@ class OpenRoomsStore extends Store { case 'forward_event': this._forwardingEvent = payload.event; break; + case 'view_group_grid': + if (payload.group_id !== this._state.group_id) { + this._cleanupRooms(); + // TODO: register to GroupStore updates + const rooms = GroupStore.getGroupRooms(payload.group_id); + const roomStores = rooms.map((room) => { + const dispatcher = new MatrixDispatcher(); + const store = new RoomViewStore(dispatcher); + // set room id of store + dispatcher.dispatch({ + action: 'view_room', + room_id: room.roomId + }, true); + return { + store, + dispatcher, + }; + }); + this._setState({ + rooms: roomStores, + group_id: payload.group_id, + }); + this._setCurrentGroupRoom(0); + } + break; } } } From d4748c91df73a6b1e96daf9c46afe4446966d55e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 7 Nov 2018 16:33:14 +0100 Subject: [PATCH 10/51] add first draft of RoomGridView --- res/css/_components.scss | 1 + res/css/structures/_GroupGridView.scss | 65 +++++++++++++++ src/components/structures/GroupGridView.js | 84 ++++++++++++++++++++ src/components/structures/RoomGridView.js | 92 ---------------------- 4 files changed, 150 insertions(+), 92 deletions(-) create mode 100644 res/css/structures/_GroupGridView.scss create mode 100644 src/components/structures/GroupGridView.js delete mode 100644 src/components/structures/RoomGridView.js diff --git a/res/css/_components.scss b/res/css/_components.scss index 92e243e8d1..16bb4938c1 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -5,6 +5,7 @@ @import "./structures/_ContextualMenu.scss"; @import "./structures/_CreateRoom.scss"; @import "./structures/_FilePanel.scss"; +@import "./structures/_GroupGridView.scss"; @import "./structures/_GroupView.scss"; @import "./structures/_HomePage.scss"; @import "./structures/_LeftPanel.scss"; diff --git a/res/css/structures/_GroupGridView.scss b/res/css/structures/_GroupGridView.scss new file mode 100644 index 0000000000..ed0d824388 --- /dev/null +++ b/res/css/structures/_GroupGridView.scss @@ -0,0 +1,65 @@ +/* +Copyright 2017 Vector Creations Ltd + +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. +*/ + +.mx_GroupGridView { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + grid-template-rows: 1fr 1fr; + grid-column-gap: 10px; + grid-row-gap: 10px; + background-color: red; +} + +.mx_RoomGridView_emptyTile::before { + display: block; + margin-top: 100px; + text-align: center; + content: "no room in this tile yet"; +} + +.mx_RoomGridView_tile > .mx_RoomView { + height: 100%; +} + +.mx_GroupGridView > *:nth-child(1) { + grid-column: 1; + grid-row: 1; +} + +.mx_GroupGridView > *:nth-child(2) { + grid-column: 2; + grid-row: 1; +} + +.mx_GroupGridView > *:nth-child(3) { + grid-column: 3; + grid-row: 1; +} + +.mx_GroupGridView > *:nth-child(4) { + grid-column: 1; + grid-row: 2; +} + +.mx_GroupGridView > *:nth-child(5) { + grid-column: 2; + grid-row: 2; +} + +.mx_GroupGridView > *:nth-child(6) { + grid-column: 3; + grid-row: 2; +} diff --git a/src/components/structures/GroupGridView.js b/src/components/structures/GroupGridView.js new file mode 100644 index 0000000000..282919f019 --- /dev/null +++ b/src/components/structures/GroupGridView.js @@ -0,0 +1,84 @@ +/* +Copyright 2017 Vector Creations Ltd. +Copyright 2017, 2018 New Vector Ltd. + +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 from 'react'; +import PropTypes from 'prop-types'; +import OpenRoomsStore from '../../stores/OpenRoomsStore'; +import dis from '../../dispatcher'; +import RoomView from './RoomView'; + +export default class RoomGridView extends React.Component { + + constructor(props) { + super(props); + this.state = { + roomStores: OpenRoomsStore.getRoomStores(), + }; + } + + componentWillMount() { + this._unmounted = false; + this._openRoomsStoreRegistration = OpenRoomsStore.addListener(this.onRoomsChanged); + this._dispatcherRef = dis.register(this._onAction); + } + + componentWillUnmount() { + this._unmounted = true; + if (this._openRoomsStoreRegistration) { + this._openRoomsStoreRegistration.unregister(); + } + dis.unregister(this._dispatcherRef); + } + + onRoomsChanged() { + if (this._unmounted) return; + this.setState({ + roomStores: OpenRoomsStore.getRoomStores(), + }); + } + + _onAction(payload) { + switch (payload.action) { + default: + break; + } + } + + render() { + let roomStores = this.state.roomStores.slice(0, 6); + const emptyCount = 6 - roomStores.length; + if (emptyCount) { + const emptyTiles = Array.from({length: emptyCount}, () => null); + roomStores = roomStores.concat(emptyTiles); + } + return (
+ { roomStores.map(roomStore => { + if (roomStore) { + return (
+ +
); + } else { + return (
); + } + }) } +
); + } + +} diff --git a/src/components/structures/RoomGridView.js b/src/components/structures/RoomGridView.js deleted file mode 100644 index d472767dbb..0000000000 --- a/src/components/structures/RoomGridView.js +++ /dev/null @@ -1,92 +0,0 @@ -/* -Copyright 2017 Vector Creations Ltd. -Copyright 2017, 2018 New Vector Ltd. - -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 from 'react'; -import PropTypes from 'prop-types'; - -export default class RoomGridView extends React.Component { - /* displayName: 'GroupView', - - propTypes: { - groupId: PropTypes.string.isRequired, - }, - - childContextTypes = { - groupStore: PropTypes.instanceOf(GroupStore), - };*/ - - getInitialState() { - return { - rooms: [], - }; - } - - componentWillMount() { - this._unmounted = false; - this._initGroupStore(this.props.groupId); - this._dispatcherRef = dis.register(this._onAction); - } - - componentWillUnmount() { - this._unmounted = true; - if (this._groupStoreRegistration) { - this._groupStoreRegistration.unregister(); - } - dis.unregister(this._dispatcherRef); - } - - componentWillReceiveProps(newProps) { - if (this.props.groupId != newProps.groupId) { - this.setState(this.getInitialState(), () => { - this._initGroupStore(newProps.groupId); - }); - } - } - - _initGroupStore(groupId) { - if (this._groupStoreRegistration) { - this._groupStoreRegistration.unregister(); - } - this._groupStoreRegistration = GroupStore.registerListener(groupId, this.onGroupStoreUpdated); - } - - onGroupStoreUpdated() { - if (this._unmounted) return; - this.setState({ - rooms: GroupStore.getGroupRooms(this.props.groupId), - }); - } - - _onAction(payload) { - switch (payload.action) { - default: - break; - } - } - - render() { - const rooms = this.state.rooms.slice(0, 6); - return
- { rooms.map(room => { -
- -
- }) } -
- } - -} From 399d3c5c24da0f8f7ac5c1e47464f14560375b3a Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 7 Nov 2018 16:34:10 +0100 Subject: [PATCH 11/51] wire up view_group_grid action from community context menu to new view --- src/PageTypes.js | 1 + src/components/structures/LoggedInView.js | 12 +++++++++++- src/components/structures/MatrixChat.js | 10 ++++++++++ .../views/context_menus/TagTileContextMenu.js | 12 ++++++++++++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/PageTypes.js b/src/PageTypes.js index 60111723fb..e4e1916c8b 100644 --- a/src/PageTypes.js +++ b/src/PageTypes.js @@ -19,6 +19,7 @@ limitations under the License. export default { HomePage: "home_page", RoomView: "room_view", + GroupGridView: "group_grid_view", UserSettings: "user_settings", RoomDirectory: "room_directory", UserView: "user_view", diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index b81597a901..8c6f192016 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -31,6 +31,7 @@ import sessionStore from '../../stores/SessionStore'; import MatrixClientPeg from '../../MatrixClientPeg'; import SettingsStore from "../../settings/SettingsStore"; import RoomListStore from "../../stores/RoomListStore"; +import OpenRoomsStore from "../../stores/OpenRoomsStore"; import TagOrderActions from '../../actions/TagOrderActions'; import RoomListActions from '../../actions/RoomListActions'; @@ -416,6 +417,7 @@ const LoggedInView = React.createClass({ const RoomDirectory = sdk.getComponent('structures.RoomDirectory'); const HomePage = sdk.getComponent('structures.HomePage'); const GroupView = sdk.getComponent('structures.GroupView'); + const GroupGridView = sdk.getComponent('structures.GroupGridView'); const MyGroups = sdk.getComponent('structures.MyGroups'); const MatrixToolbar = sdk.getComponent('globals.MatrixToolbar'); const CookieBar = sdk.getComponent('globals.CookieBar'); @@ -428,6 +430,12 @@ const LoggedInView = React.createClass({ switch (this.props.page_type) { case PageTypes.RoomView: + if (!OpenRoomsStore.getCurrentRoomStore()) { + console.warn(`LoggedInView: getCurrentRoomStore not set!`); + } + else if (OpenRoomsStore.getCurrentRoomStore().getRoomId() !== this.props.currentRoomId) { + console.warn(`LoggedInView: room id in store not the same as in props: ${OpenRoomsStore.getCurrentRoomStore().getRoomId()} & ${this.props.currentRoomId}`); + } page_element = ; break; - + case PageTypes.GroupGridView: + page_element = ; + break; case PageTypes.UserSettings: page_element = @@ -65,6 +74,9 @@ export default class TagTileContextMenu extends React.Component { /> { _t('View Community') }
+
+ { _t('View as grid') } +

From fdd324a9437793bbdfb66c4e228cfca9f95f3c4d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 8 Nov 2018 11:06:26 +0100 Subject: [PATCH 12/51] basic divider lines for tiles --- res/css/structures/_GroupGridView.scss | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/res/css/structures/_GroupGridView.scss b/res/css/structures/_GroupGridView.scss index ed0d824388..4c54cb49b1 100644 --- a/res/css/structures/_GroupGridView.scss +++ b/res/css/structures/_GroupGridView.scss @@ -18,8 +18,6 @@ limitations under the License. display: grid; grid-template-columns: 1fr 1fr 1fr; grid-template-rows: 1fr 1fr; - grid-column-gap: 10px; - grid-row-gap: 10px; background-color: red; } @@ -30,6 +28,11 @@ limitations under the License. content: "no room in this tile yet"; } +.mx_RoomGridView_tile { + border-right: 1px solid $panel-divider-color; + border-bottom: 1px solid $panel-divider-color; +} + .mx_RoomGridView_tile > .mx_RoomView { height: 100%; } From b68df0420b29769aacb4f2790da16ea8a01c1efe Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 8 Nov 2018 12:25:36 +0100 Subject: [PATCH 13/51] fix errors when trying to switch room --- src/components/structures/GroupGridView.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/structures/GroupGridView.js b/src/components/structures/GroupGridView.js index 282919f019..b72e689bc5 100644 --- a/src/components/structures/GroupGridView.js +++ b/src/components/structures/GroupGridView.js @@ -28,6 +28,7 @@ export default class RoomGridView extends React.Component { this.state = { roomStores: OpenRoomsStore.getRoomStores(), }; + this.onRoomsChanged = this.onRoomsChanged.bind(this); } componentWillMount() { @@ -39,7 +40,7 @@ export default class RoomGridView extends React.Component { componentWillUnmount() { this._unmounted = true; if (this._openRoomsStoreRegistration) { - this._openRoomsStoreRegistration.unregister(); + this._openRoomsStoreRegistration.remove(); } dis.unregister(this._dispatcherRef); } From cf0f75cad4eaf7050a8cc106f74ce3c3c0b8f147 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 21 Nov 2018 14:04:25 +0000 Subject: [PATCH 14/51] allow changing active room in grid by clicking it --- res/css/structures/_GroupGridView.scss | 11 ++++++++--- src/components/structures/GroupGridView.js | 13 ++++++++++--- src/components/structures/MatrixChat.js | 2 +- src/components/structures/RoomView.js | 6 +++++- .../views/context_menus/TagTileContextMenu.js | 2 +- src/stores/OpenRoomsStore.js | 16 ++++++++++------ 6 files changed, 35 insertions(+), 15 deletions(-) diff --git a/res/css/structures/_GroupGridView.scss b/res/css/structures/_GroupGridView.scss index 4c54cb49b1..130d3e89f6 100644 --- a/res/css/structures/_GroupGridView.scss +++ b/res/css/structures/_GroupGridView.scss @@ -21,19 +21,24 @@ limitations under the License. background-color: red; } -.mx_RoomGridView_emptyTile::before { + +.mx_GroupGridView_emptyTile::before { display: block; margin-top: 100px; text-align: center; content: "no room in this tile yet"; } -.mx_RoomGridView_tile { +.mx_GroupGridView_tile { border-right: 1px solid $panel-divider-color; border-bottom: 1px solid $panel-divider-color; } -.mx_RoomGridView_tile > .mx_RoomView { +.mx_GroupGridView_activeTile { + border: 1px solid red !important; +} + +.mx_GroupGridView_tile > .mx_RoomView { height: 100%; } diff --git a/src/components/structures/GroupGridView.js b/src/components/structures/GroupGridView.js index b72e689bc5..09bc0e300a 100644 --- a/src/components/structures/GroupGridView.js +++ b/src/components/structures/GroupGridView.js @@ -20,6 +20,7 @@ import PropTypes from 'prop-types'; import OpenRoomsStore from '../../stores/OpenRoomsStore'; import dis from '../../dispatcher'; import RoomView from './RoomView'; +import classNames from 'classnames'; export default class RoomGridView extends React.Component { @@ -49,6 +50,7 @@ export default class RoomGridView extends React.Component { if (this._unmounted) return; this.setState({ roomStores: OpenRoomsStore.getRoomStores(), + currentRoomStore: OpenRoomsStore.getCurrentRoomStore(), }); } @@ -67,16 +69,21 @@ export default class RoomGridView extends React.Component { roomStores = roomStores.concat(emptyTiles); } return (
- { roomStores.map(roomStore => { + { roomStores.map((roomStore) => { if (roomStore) { - return (
+ const isActive = roomStore === this.state.currentRoomStore; + const tileClasses = classNames({ + "mx_GroupGridView_tile": true, + "mx_GroupGridView_activeTile": isActive, + }); + return (
); } else { - return (
); + return (
); } }) }
); diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index c42daf2923..3d3aa52e57 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -627,7 +627,7 @@ export default React.createClass({ case 'view_group': this._viewGroup(payload); break; - case 'view_group_grid': + case 'group_grid_view': this._viewGroupGrid(payload); break; case 'view_home_page': diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index b1730ed96a..c7a0677262 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -1470,6 +1470,10 @@ module.exports = React.createClass({ } }, + _onMainClicked: function() { + dis.dispatch({action: 'group_grid_set_active', room_id: this.state.room.roomId }); + }, + render: function() { const RoomHeader = sdk.getComponent('rooms.RoomHeader'); const MessageComposer = sdk.getComponent('rooms.MessageComposer'); @@ -1817,7 +1821,7 @@ module.exports = React.createClass({ const rightPanel = this.state.room ? : undefined; return ( -
+
Date: Thu, 22 Nov 2018 10:18:11 +0000 Subject: [PATCH 15/51] also give empty tiles a key --- src/components/structures/GroupGridView.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/GroupGridView.js b/src/components/structures/GroupGridView.js index 09bc0e300a..7a51604f0d 100644 --- a/src/components/structures/GroupGridView.js +++ b/src/components/structures/GroupGridView.js @@ -69,7 +69,7 @@ export default class RoomGridView extends React.Component { roomStores = roomStores.concat(emptyTiles); } return (
- { roomStores.map((roomStore) => { + { roomStores.map((roomStore, i) => { if (roomStore) { const isActive = roomStore === this.state.currentRoomStore; const tileClasses = classNames({ @@ -83,7 +83,7 @@ export default class RoomGridView extends React.Component { /> ); } else { - return (
); + return (
); } }) }
); From 2ceef0094437b3c3269d341c373138af964b2a70 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 22 Nov 2018 11:01:19 +0000 Subject: [PATCH 16/51] style active room rect, and make it not jump --- res/css/structures/_GroupGridView.scss | 27 +++++++++++++++++++++++++- res/css/structures/_MatrixChat.scss | 3 ++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/res/css/structures/_GroupGridView.scss b/res/css/structures/_GroupGridView.scss index 130d3e89f6..9e5c5c3379 100644 --- a/res/css/structures/_GroupGridView.scss +++ b/res/css/structures/_GroupGridView.scss @@ -35,9 +35,34 @@ limitations under the License. } .mx_GroupGridView_activeTile { - border: 1px solid red !important; + position: relative; } +.mx_GroupGridView_activeTile:before, +.mx_GroupGridView_activeTile:after { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + content: ""; + pointer-events: none; + z-index: 10000; +} + +.mx_GroupGridView_activeTile:before { + border-radius: 14px; + border: 8px solid rgba(134, 193, 165, 0.5); + margin: -8px; +} + +.mx_GroupGridView_activeTile:after { + border-radius: 8px; + border: 2px solid rgba(134, 193, 165, 1); + margin: -2px; +} + + .mx_GroupGridView_tile > .mx_RoomView { height: 100%; } diff --git a/res/css/structures/_MatrixChat.scss b/res/css/structures/_MatrixChat.scss index 1ccbd19391..a843bb7fee 100644 --- a/res/css/structures/_MatrixChat.scss +++ b/res/css/structures/_MatrixChat.scss @@ -80,7 +80,8 @@ limitations under the License. Empirically this stops the MessagePanel's width exploding outwards when gemini is in 'prevented' mode */ - overflow-x: auto; + // disabling this for now as it clips the active room rect on the grid view + // overflow-x: auto; /* To fix https://github.com/vector-im/riot-web/issues/3298 where Safari needed height 100% all the way down to the HomePage. Height does not From fbfbefe4fe76b2344b568dbc2d747bf3da74ecae Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 22 Nov 2018 12:23:07 +0000 Subject: [PATCH 17/51] also forward actions from room dispatcher to global one avoiding replay if the action would be forwarded back to the same room dispatcher also some fixing & renaming in OpenRoomsStore --- src/ActiveRoomObserver.js | 4 +- src/components/structures/GroupGridView.js | 4 +- src/components/structures/LoggedInView.js | 8 +- src/stores/OpenRoomsStore.js | 161 +++++++++++++-------- 4 files changed, 105 insertions(+), 72 deletions(-) diff --git a/src/ActiveRoomObserver.js b/src/ActiveRoomObserver.js index 2561a2e6a3..f3850dd87c 100644 --- a/src/ActiveRoomObserver.js +++ b/src/ActiveRoomObserver.js @@ -28,7 +28,7 @@ import OpenRoomsStore from './stores/OpenRoomsStore'; class ActiveRoomObserver { constructor() { this._listeners = {}; - const roomStore = OpenRoomsStore.getCurrentRoomStore(); + const roomStore = OpenRoomsStore.getActiveRoomStore(); this._activeRoomId = roomStore && roomStore.getRoomId(); // TODO: We could self-destruct when the last listener goes away, or at least // stop listening. @@ -67,7 +67,7 @@ class ActiveRoomObserver { // emit for the old room ID if (this._activeRoomId) this._emit(this._activeRoomId); - const activeRoomStore = OpenRoomsStore.getCurrentRoomStore(); + const activeRoomStore = OpenRoomsStore.getActiveRoomStore(); // update our cache this._activeRoomId = activeRoomStore && activeRoomStore.getRoomId(); diff --git a/src/components/structures/GroupGridView.js b/src/components/structures/GroupGridView.js index 7a51604f0d..e6c0dfd31e 100644 --- a/src/components/structures/GroupGridView.js +++ b/src/components/structures/GroupGridView.js @@ -50,7 +50,7 @@ export default class RoomGridView extends React.Component { if (this._unmounted) return; this.setState({ roomStores: OpenRoomsStore.getRoomStores(), - currentRoomStore: OpenRoomsStore.getCurrentRoomStore(), + activeRoomStore: OpenRoomsStore.getActiveRoomStore(), }); } @@ -71,7 +71,7 @@ export default class RoomGridView extends React.Component { return (
{ roomStores.map((roomStore, i) => { if (roomStore) { - const isActive = roomStore === this.state.currentRoomStore; + const isActive = roomStore === this.state.activeRoomStore; const tileClasses = classNames({ "mx_GroupGridView_tile": true, "mx_GroupGridView_activeTile": isActive, diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index 8c6f192016..56f3fc89f4 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -430,14 +430,14 @@ const LoggedInView = React.createClass({ switch (this.props.page_type) { case PageTypes.RoomView: - if (!OpenRoomsStore.getCurrentRoomStore()) { + if (!OpenRoomsStore.getActiveRoomStore()) { console.warn(`LoggedInView: getCurrentRoomStore not set!`); } - else if (OpenRoomsStore.getCurrentRoomStore().getRoomId() !== this.props.currentRoomId) { - console.warn(`LoggedInView: room id in store not the same as in props: ${OpenRoomsStore.getCurrentRoomStore().getRoomId()} & ${this.props.currentRoomId}`); + else if (OpenRoomsStore.getActiveRoomStore().getRoomId() !== this.props.currentRoomId) { + console.warn(`LoggedInView: room id in store not the same as in props: ${OpenRoomsStore.getActiveRoomStore().getRoomId()} & ${this.props.currentRoomId}`); } page_element = r.store); } - getCurrentRoomStore() { - const currentRoom = this._getCurrentRoom(); - if (currentRoom) { - return currentRoom.store; + getActiveRoomStore() { + const openRoom = this._getActiveOpenRoom(); + if (openRoom) { + return openRoom.store; } } - _getCurrentRoom() { + _getActiveOpenRoom() { const index = this._state.currentIndex; if (index !== null && index < this._state.rooms.length) { return this._state.rooms[index]; @@ -80,31 +80,79 @@ class OpenRoomsStore extends Store { return this._state.rooms.findIndex((r) => matchesRoom(payload, r.store)); } - _cleanupRooms() { - const room = this._state.room; + _cleanupOpenRooms() { this._state.rooms.forEach((room) => { + room.dispatcher.unregister(room.dispatcherRef); room.dispatcher.unregister(room.store.getDispatchToken()); }); this._setState({ rooms: [], group_id: null, - currentIndex: null + currentIndex: null, }); } - _createRoom() { + _createOpenRoom(room_id, room_alias) { const dispatcher = new MatrixDispatcher(); + // forward all actions coming from the room dispatcher + // to the global one + const dispatcherRef = dispatcher.register((action) => { + action.grid_src_room_id = room_id; + action.grid_src_room_alias = room_alias; + this.getDispatcher().dispatch(action); + }); + const openRoom = { + store: new RoomViewStore(dispatcher), + dispatcher, + dispatcherRef, + }; + + dispatcher.dispatch({ + action: 'view_room', + room_id: room_id, + room_alias: room_alias, + }, true); + + return openRoom; + } + + _setSingleOpenRoom(payload) { this._setState({ - rooms: [{ - store: new RoomViewStore(dispatcher), - dispatcher, - }], + rooms: [this._createOpenRoom(payload.room_id, payload.room_alias)], + currentIndex: 0, + }); + } + + _setGroupOpenRooms(group_id) { + this._cleanupOpenRooms(); + // TODO: register to GroupStore updates + const rooms = GroupStore.getGroupRooms(group_id); + const openRooms = rooms.map((room) => { + return this._createOpenRoom(room.roomId); + }); + this._setState({ + rooms: openRooms, + group_id: group_id, currentIndex: 0, }); } _forwardAction(payload) { - const currentRoom = this._getCurrentRoom(); + // don't forward an event to a room dispatcher + // if the event originated from that dispatcher, as this + // would cause the event to be observed twice in that + // dispatcher + if (payload.grid_src_room_id || payload.grid_src_room_alias) { + const srcPayload = { + room_id: payload.grid_src_room_id, + room_alias: payload.grid_src_room_alias, + }; + const srcIndex = this._roomIndex(srcPayload); + if (srcIndex === this._state.currentIndex) { + return; + } + } + const currentRoom = this._getActiveOpenRoom(); if (currentRoom) { currentRoom.dispatcher.dispatch(payload, true); } @@ -114,7 +162,7 @@ class OpenRoomsStore extends Store { try { const result = await MatrixClientPeg.get() .getRoomIdForAlias(payload.room_alias); - dis.dispatch({ + this.getDispatcher().dispatch({ action: 'view_room', room_id: result.room_id, event_id: payload.event_id, @@ -133,6 +181,36 @@ class OpenRoomsStore extends Store { } } + _viewRoom(payload) { + console.log("!!! OpenRoomsStore: view_room", payload); + if (!payload.room_id && payload.room_alias) { + this._resolveRoomAlias(payload); + } + const currentStore = this.getActiveRoomStore(); + if (!matchesRoom(payload, currentStore)) { + if (this._hasRoom(payload)) { + const roomIndex = this._roomIndex(payload); + this._setState({currentIndex: roomIndex}); + } else { + this._cleanupOpenRooms(); + } + } + if (!this.getActiveRoomStore()) { + console.log("OpenRoomsStore: _setSingleOpenRoom"); + this._setSingleOpenRoom(payload); + } + console.log("OpenRoomsStore: _forwardAction"); + this._forwardAction(payload); + if (this._forwardingEvent) { + this.getDispatcher().dispatch({ + action: 'send_event', + room_id: payload.room_id, + event: this._forwardingEvent, + }); + this._forwardingEvent = null; + } + } + __onDispatch(payload) { switch (payload.action) { // view_room: @@ -142,38 +220,12 @@ class OpenRoomsStore extends Store { // - event_offset: 100 // - highlighted: true case 'view_room': - console.log("!!! OpenRoomsStore: view_room", payload); - if (!payload.room_id && payload.room_alias) { - this._resolveRoomAlias(payload); - } - const currentStore = this.getCurrentRoomStore(); - if (matchesRoom(payload, currentStore)) { - if (this._hasRoom(payload)) { - const roomIndex = this._roomIndex(payload); - this._setState({currentIndex: roomIndex}); - } else { - this._cleanupRooms(); - } - } - if (!this.getCurrentRoomStore()) { - console.log("OpenRoomsStore: _createRoom"); - this._createRoom(); - } - console.log("OpenRoomsStore: _forwardAction"); - this._forwardAction(payload); - if (this._forwardingEvent) { - dis.dispatch({ - action: 'send_event', - room_id: payload.room_id, - event: this._forwardingEvent, - }); - this._forwardingEvent = null; - } + this._viewRoom(payload); break; case 'view_my_groups': case 'view_group': this._forwardAction(payload); - this._cleanupRooms(); + this._cleanupOpenRooms(); break; case 'will_join': case 'cancel_join': @@ -183,6 +235,7 @@ class OpenRoomsStore extends Store { case 'reply_to_event': case 'open_room_settings': case 'close_settings': + case 'focus_composer': this._forwardAction(payload); break; case 'forward_event': @@ -198,27 +251,7 @@ class OpenRoomsStore extends Store { break; case 'group_grid_view': if (payload.group_id !== this._state.group_id) { - this._cleanupRooms(); - // TODO: register to GroupStore updates - const rooms = GroupStore.getGroupRooms(payload.group_id); - const roomStores = rooms.map((room) => { - const dispatcher = new MatrixDispatcher(); - const store = new RoomViewStore(dispatcher); - // set room id of store - dispatcher.dispatch({ - action: 'view_room', - room_id: room.roomId - }, true); - return { - store, - dispatcher, - }; - }); - this._setState({ - rooms: roomStores, - group_id: payload.group_id, - currentIndex: 0, - }); + this._setGroupOpenRooms(payload.group_id); } break; } From 9a24249fb5805bdbf9c623d2b5cbe5997ef0d85a Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 22 Nov 2018 15:13:03 +0000 Subject: [PATCH 18/51] emit focus_composer after updating the active room in GroupGridView also change the active room from there so RoomView is oblivious to grid view stuff --- src/components/structures/GroupGridView.js | 25 +++++++++++++++++++++- src/components/structures/RoomView.js | 6 +----- src/stores/OpenRoomsStore.js | 6 ++++++ 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/components/structures/GroupGridView.js b/src/components/structures/GroupGridView.js index e6c0dfd31e..0181966d49 100644 --- a/src/components/structures/GroupGridView.js +++ b/src/components/structures/GroupGridView.js @@ -28,10 +28,22 @@ export default class RoomGridView extends React.Component { super(props); this.state = { roomStores: OpenRoomsStore.getRoomStores(), + activeRoomStore: OpenRoomsStore.getActiveRoomStore(), }; this.onRoomsChanged = this.onRoomsChanged.bind(this); } + componentDidUpdate(_, prevState) { + const store = this.state.activeRoomStore; + if (store) { + store.getDispatcher().dispatch({action: 'focus_composer'}); + } + } + + componentDidMount() { + this.componentDidUpdate(); + } + componentWillMount() { this._unmounted = false; this._openRoomsStoreRegistration = OpenRoomsStore.addListener(this.onRoomsChanged); @@ -61,6 +73,16 @@ export default class RoomGridView extends React.Component { } } + _setActive(i) { + const store = OpenRoomsStore.getRoomStoreAt(i); + if (store !== this.state.activeRoomStore) { + dis.dispatch({ + action: 'group_grid_set_active', + room_id: store.getRoomId(), + }); + } + } + render() { let roomStores = this.state.roomStores.slice(0, 6); const emptyCount = 6 - roomStores.length; @@ -76,10 +98,11 @@ export default class RoomGridView extends React.Component { "mx_GroupGridView_tile": true, "mx_GroupGridView_activeTile": isActive, }); - return (
+ return (
{this._setActive(i)}} key={roomStore.getRoomId()} className={tileClasses}>
); } else { diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index c7a0677262..b1730ed96a 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -1470,10 +1470,6 @@ module.exports = React.createClass({ } }, - _onMainClicked: function() { - dis.dispatch({action: 'group_grid_set_active', room_id: this.state.room.roomId }); - }, - render: function() { const RoomHeader = sdk.getComponent('rooms.RoomHeader'); const MessageComposer = sdk.getComponent('rooms.MessageComposer'); @@ -1821,7 +1817,7 @@ module.exports = React.createClass({ const rightPanel = this.state.room ? : undefined; return ( -
+
= 0 && index < this._state.rooms.length) { + return this._state.rooms[index].store; + } + } + _getActiveOpenRoom() { const index = this._state.currentIndex; if (index !== null && index < this._state.rooms.length) { From 44200a6f78b025041e8a6a090e145d611bf1d312 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 22 Nov 2018 15:15:24 +0000 Subject: [PATCH 19/51] only listen and dispatch to room-local dispatcher in room view, composer --- src/components/structures/RoomView.js | 59 +++++++++---------- src/components/views/rooms/MessageComposer.js | 10 ++-- .../views/rooms/MessageComposerInput.js | 12 ++-- 3 files changed, 39 insertions(+), 42 deletions(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index b1730ed96a..f6b9381080 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -35,7 +35,6 @@ const ContentMessages = require("../../ContentMessages"); const Modal = require("../../Modal"); const sdk = require('../../index'); const CallHandler = require('../../CallHandler'); -const dis = require("../../dispatcher"); const Tinter = require("../../Tinter"); const rate_limited_func = require('../../ratelimitedfunc'); const ObjectUtils = require('../../ObjectUtils'); @@ -151,7 +150,7 @@ module.exports = React.createClass({ }, componentWillMount: function() { - this.dispatcherRef = dis.register(this.onAction); + this.dispatcherRef = this.props.roomViewStore.getDispatcher().register(this.onAction); MatrixClientPeg.get().on("Room", this.onRoom); MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline); MatrixClientPeg.get().on("Room.name", this.onRoomName); @@ -200,7 +199,7 @@ module.exports = React.createClass({ editingRoomSettings: store.isEditingSettings(), }; - if (this.state.editingRoomSettings && !newState.editingRoomSettings) dis.dispatch({action: 'focus_composer'}); + if (this.state.editingRoomSettings && !newState.editingRoomSettings) this.props.roomViewStore.getDispatcher().dispatch({action: 'focus_composer'}); // Temporary logging to diagnose https://github.com/vector-im/riot-web/issues/4307 console.log( @@ -362,7 +361,7 @@ module.exports = React.createClass({ // XXX: EVIL HACK to autofocus inviting on empty rooms. // We use the setTimeout to avoid racing with focus_composer. - if (this.state.room && + if (this.props.isActive !== false && this.state.room && this.state.room.getJoinedMemberCount() == 1 && this.state.room.getLiveTimeline() && this.state.room.getLiveTimeline().getEvents() && @@ -416,7 +415,7 @@ module.exports = React.createClass({ roomView.removeEventListener('dragleave', this.onDragLeaveOrEnd); roomView.removeEventListener('dragend', this.onDragLeaveOrEnd); } - dis.unregister(this.dispatcherRef); + this.props.roomViewStore.getDispatcher().unregister(this.dispatcherRef); if (MatrixClientPeg.get()) { MatrixClientPeg.get().removeListener("Room", this.onRoom); MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline); @@ -791,7 +790,7 @@ module.exports = React.createClass({ }, onSearchResultsResize: function() { - dis.dispatch({ action: 'timeline_resize' }, true); + this.props.roomViewStore.getDispatcher().dispatch({ action: 'timeline_resize' }, true); }, onSearchResultsFillRequest: function(backwards) { @@ -812,7 +811,7 @@ module.exports = React.createClass({ onInviteButtonClick: function() { // call AddressPickerDialog - dis.dispatch({ + this.props.roomViewStore.getDispatcher().dispatch({ action: 'view_invite', roomId: this.state.room.roomId, }); @@ -834,7 +833,7 @@ module.exports = React.createClass({ // Join this room once the user has registered and logged in const signUrl = this.props.thirdPartyInvite ? this.props.thirdPartyInvite.inviteSignUrl : undefined; - dis.dispatch({ + this.props.roomViewStore.getDispatcher().dispatch({ action: 'do_after_sync_prepared', deferred_action: { action: 'join_room', @@ -844,7 +843,7 @@ module.exports = React.createClass({ // Don't peek whilst registering otherwise getPendingEventList complains // Do this by indicating our intention to join - dis.dispatch({ + this.props.roomViewStore.getDispatcher().dispatch({ action: 'will_join', }); @@ -855,20 +854,20 @@ module.exports = React.createClass({ if (submitted) { this.props.onRegistered(credentials); } else { - dis.dispatch({ + this.props.roomViewStore.getDispatcher().dispatch({ action: 'cancel_after_sync_prepared', }); - dis.dispatch({ + this.props.roomViewStore.getDispatcher().dispatch({ action: 'cancel_join', }); } }, onDifferentServerClicked: (ev) => { - dis.dispatch({action: 'start_registration'}); + this.props.roomViewStore.getDispatcher().dispatch({action: 'start_registration'}); close(); }, onLoginClick: (ev) => { - dis.dispatch({action: 'start_login'}); + this.props.roomViewStore.getDispatcher().dispatch({action: 'start_login'}); close(); }, }).close; @@ -878,7 +877,7 @@ module.exports = React.createClass({ Promise.resolve().then(() => { const signUrl = this.props.thirdPartyInvite ? this.props.thirdPartyInvite.inviteSignUrl : undefined; - dis.dispatch({ + this.props.roomViewStore.getDispatcher().dispatch({ action: 'join_room', opts: { inviteSignUrl: signUrl, viaServers: this.props.viaServers }, }); @@ -934,10 +933,10 @@ module.exports = React.createClass({ }, uploadFile: async function(file) { - dis.dispatch({action: 'focus_composer'}); + this.props.roomViewStore.getDispatcher().dispatch({action: 'focus_composer'}); if (MatrixClientPeg.get().isGuest()) { - dis.dispatch({action: 'require_registration'}); + this.props.roomViewStore.getDispatcher().dispatch({action: 'require_registration'}); return; } @@ -961,14 +960,14 @@ module.exports = React.createClass({ } // Send message_sent callback, for things like _checkIfAlone because after all a file is still a message. - dis.dispatch({ + this.props.roomViewStore.getDispatcher().dispatch({ action: 'message_sent', }); }, injectSticker: function(url, info, text) { if (MatrixClientPeg.get().isGuest()) { - dis.dispatch({action: 'require_registration'}); + this.props.roomViewStore.getDispatcher().dispatch({action: 'require_registration'}); return; } @@ -1169,7 +1168,7 @@ module.exports = React.createClass({ }, onSettingsClick: function() { - dis.dispatch({ action: 'open_room_settings' }); + this.props.roomViewStore.getDispatcher().dispatch({ action: 'open_room_settings' }); }, onSettingsSaveClick: function() { @@ -1202,31 +1201,31 @@ module.exports = React.createClass({ }); // still editing room settings } else { - dis.dispatch({ action: 'close_settings' }); + this.props.roomViewStore.getDispatcher().dispatch({ action: 'close_settings' }); } }).finally(() => { this.setState({ uploadingRoomSettings: false, }); - dis.dispatch({ action: 'close_settings' }); + this.props.roomViewStore.getDispatcher().dispatch({ action: 'close_settings' }); }).done(); }, onCancelClick: function() { console.log("updateTint from onCancelClick"); this.updateTint(); - dis.dispatch({ action: 'close_settings' }); + this.props.roomViewStore.getDispatcher().dispatch({ action: 'close_settings' }); if (this.state.forwardingEvent) { - dis.dispatch({ + this.props.roomViewStore.getDispatcher().dispatch({ action: 'forward_event', event: null, }); } - dis.dispatch({action: 'focus_composer'}); + this.props.roomViewStore.getDispatcher().dispatch({action: 'focus_composer'}); }, onLeaveClick: function() { - dis.dispatch({ + this.props.roomViewStore.getDispatcher().dispatch({ action: 'leave_room', room_id: this.state.room.roomId, }); @@ -1234,7 +1233,7 @@ module.exports = React.createClass({ onForgetClick: function() { MatrixClientPeg.get().forget(this.state.room.roomId).done(function() { - dis.dispatch({ action: 'view_next_room' }); + this.props.roomViewStore.getDispatcher().dispatch({ action: 'view_next_room' }); }, function(err) { const errCode = err.errcode || _t("unknown error code"); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); @@ -1251,7 +1250,7 @@ module.exports = React.createClass({ rejecting: true, }); MatrixClientPeg.get().leave(this.state.roomId).done(function() { - dis.dispatch({ action: 'view_next_room' }); + this.props.roomViewStore.getDispatcher().dispatch({ action: 'view_next_room' }); self.setState({ rejecting: false, }); @@ -1277,7 +1276,7 @@ module.exports = React.createClass({ // using /leave rather than /join. In the short term though, we // just ignore them. // https://github.com/vector-im/vector-web/issues/1134 - dis.dispatch({ + this.props.roomViewStore.getDispatcher().dispatch({ action: 'view_room_directory', }); }, @@ -1296,7 +1295,7 @@ module.exports = React.createClass({ // jump down to the bottom of this room, where new events are arriving jumpToLiveTimeline: function() { this.refs.messagePanel.jumpToLiveTimeline(); - dis.dispatch({action: 'focus_composer'}); + this.props.roomViewStore.getDispatcher().dispatch({action: 'focus_composer'}); }, // jump up to wherever our read marker is @@ -1386,7 +1385,7 @@ module.exports = React.createClass({ }, onFullscreenClick: function() { - dis.dispatch({ + this.props.roomViewStore.getDispatcher().dispatch({ action: 'video_fullscreen', fullscreen: true, }, true); diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 5ac788fb1d..acbaea9ddc 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -130,7 +130,7 @@ export default class MessageComposer extends React.Component { onUploadClick(ev) { if (MatrixClientPeg.get().isGuest()) { - dis.dispatch({action: 'require_registration'}); + this.props.roomViewStore.getDispatcher().dispatch({action: 'require_registration'}); return; } @@ -192,7 +192,7 @@ export default class MessageComposer extends React.Component { if (!call) { return; } - dis.dispatch({ + this.props.roomViewStore.getDispatcher().dispatch({ action: 'hangup', // hangup the call for this room, which may not be the room in props // (e.g. conferences which will hangup the 1:1 room instead) @@ -201,7 +201,7 @@ export default class MessageComposer extends React.Component { } onCallClick(ev) { - dis.dispatch({ + this.props.roomViewStore.getDispatcher().dispatch({ action: 'place_call', type: ev.shiftKey ? "screensharing" : "video", room_id: this.props.room.roomId, @@ -209,7 +209,7 @@ export default class MessageComposer extends React.Component { } onVoiceCallClick(ev) { - dis.dispatch({ + this.props.roomViewStore.getDispatcher().dispatch({ action: 'place_call', type: "voice", room_id: this.props.room.roomId, @@ -245,7 +245,7 @@ export default class MessageComposer extends React.Component { ev.preventDefault(); const replacementRoomId = this.state.tombstone.getContent()['replacement_room']; - dis.dispatch({ + this.props.roomViewStore.getDispatcher().dispatch({ action: 'view_room', highlighted: true, room_id: replacementRoomId, diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index da41dba212..c54c0b1ab6 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -41,8 +41,6 @@ import sdk from '../../../index'; import { _t, _td } from '../../../languageHandler'; import Analytics from '../../../Analytics'; -import dis from '../../../dispatcher'; - import * as RichText from '../../../RichText'; import * as HtmlUtils from '../../../HtmlUtils'; import Autocomplete from './Autocomplete'; @@ -120,7 +118,7 @@ function onSendMessageFailed(err, room) { // XXX: temporary logging to try to diagnose // https://github.com/vector-im/riot-web/issues/3148 console.log('MessageComposer got send failure: ' + err.name + '('+err+')'); - dis.dispatch({ + this.props.roomViewStore.getDispatcher().dispatch({ action: 'message_send_failed', }); } @@ -344,12 +342,12 @@ export default class MessageComposerInput extends React.Component { } componentWillMount() { - this.dispatcherRef = dis.register(this.onAction); + this.dispatcherRef = this.props.roomViewStore.getDispatcher().register(this.onAction); this.historyManager = new ComposerHistoryManager(this.props.room.roomId, 'mx_slate_composer_history_'); } componentWillUnmount() { - dis.unregister(this.dispatcherRef); + this.props.roomViewStore.getDispatcher().unregister(this.dispatcherRef); } _collectEditor = (e) => { @@ -1208,14 +1206,14 @@ export default class MessageComposerInput extends React.Component { // Clear reply_to_event as we put the message into the queue // if the send fails, retry will handle resending. - dis.dispatch({ + this.props.roomViewStore.getDispatcher().dispatch({ action: 'reply_to_event', event: null, }); } this.client.sendMessage(this.props.room.roomId, content).then((res) => { - dis.dispatch({ + this.props.roomViewStore.getDispatcher().dispatch({ action: 'message_sent', }); }).catch((e) => { From 74becf71d8c889bc5710076f02f5ab06166f521b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 22 Nov 2018 16:00:11 +0000 Subject: [PATCH 20/51] add right panel back to grid view --- res/css/structures/_GroupGridView.scss | 25 ++++++++---- src/components/structures/GroupGridView.js | 45 +++++++++++++--------- 2 files changed, 44 insertions(+), 26 deletions(-) diff --git a/res/css/structures/_GroupGridView.scss b/res/css/structures/_GroupGridView.scss index 9e5c5c3379..8795f958eb 100644 --- a/res/css/structures/_GroupGridView.scss +++ b/res/css/structures/_GroupGridView.scss @@ -14,13 +14,22 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_GroupGridView { +.mx_GroupGridView_rooms { display: grid; grid-template-columns: 1fr 1fr 1fr; grid-template-rows: 1fr 1fr; - background-color: red; + flex: 1 1 0; } +.mx_GroupGridView { + display: flex; + flex-direction: column; +} + +.mx_GroupGridView > .mx_MainSplit { + flex: 1 1 0; + display: flex; +} .mx_GroupGridView_emptyTile::before { display: block; @@ -67,32 +76,32 @@ limitations under the License. height: 100%; } -.mx_GroupGridView > *:nth-child(1) { +.mx_GroupGridView_rooms > *:nth-child(1) { grid-column: 1; grid-row: 1; } -.mx_GroupGridView > *:nth-child(2) { +.mx_GroupGridView_rooms > *:nth-child(2) { grid-column: 2; grid-row: 1; } -.mx_GroupGridView > *:nth-child(3) { +.mx_GroupGridView_rooms > *:nth-child(3) { grid-column: 3; grid-row: 1; } -.mx_GroupGridView > *:nth-child(4) { +.mx_GroupGridView_rooms > *:nth-child(4) { grid-column: 1; grid-row: 2; } -.mx_GroupGridView > *:nth-child(5) { +.mx_GroupGridView_rooms > *:nth-child(5) { grid-column: 2; grid-row: 2; } -.mx_GroupGridView > *:nth-child(6) { +.mx_GroupGridView_rooms > *:nth-child(6) { grid-column: 3; grid-row: 2; } diff --git a/src/components/structures/GroupGridView.js b/src/components/structures/GroupGridView.js index 0181966d49..d6268ff14c 100644 --- a/src/components/structures/GroupGridView.js +++ b/src/components/structures/GroupGridView.js @@ -21,6 +21,8 @@ import OpenRoomsStore from '../../stores/OpenRoomsStore'; import dis from '../../dispatcher'; import RoomView from './RoomView'; import classNames from 'classnames'; +import MainSplit from './MainSplit'; +import RightPanel from './RightPanel'; export default class RoomGridView extends React.Component { @@ -90,25 +92,32 @@ export default class RoomGridView extends React.Component { const emptyTiles = Array.from({length: emptyCount}, () => null); roomStores = roomStores.concat(emptyTiles); } + const activeRoomId = this.state.activeRoomStore && this.state.activeRoomStore.getRoomId(); + const rightPanel = activeRoomId ? : undefined; + return (
- { roomStores.map((roomStore, i) => { - if (roomStore) { - const isActive = roomStore === this.state.activeRoomStore; - const tileClasses = classNames({ - "mx_GroupGridView_tile": true, - "mx_GroupGridView_activeTile": isActive, - }); - return (
{this._setActive(i)}} key={roomStore.getRoomId()} className={tileClasses}> - -
); - } else { - return (
); - } - }) } + +
+ { roomStores.map((roomStore, i) => { + if (roomStore) { + const isActive = roomStore === this.state.activeRoomStore; + const tileClasses = classNames({ + "mx_GroupGridView_tile": true, + "mx_GroupGridView_activeTile": isActive, + }); + return (
{this._setActive(i)}} key={roomStore.getRoomId()} className={tileClasses}> + +
); + } else { + return (
); + } + }) } +
+
); } From ec070ea782af50fcd3bf537bd713484a4056097f Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 22 Nov 2018 16:06:05 +0000 Subject: [PATCH 21/51] use % instead of fr units for grid, make size independant of content --- res/css/structures/_GroupGridView.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/css/structures/_GroupGridView.scss b/res/css/structures/_GroupGridView.scss index 8795f958eb..14c46d1370 100644 --- a/res/css/structures/_GroupGridView.scss +++ b/res/css/structures/_GroupGridView.scss @@ -16,8 +16,8 @@ limitations under the License. .mx_GroupGridView_rooms { display: grid; - grid-template-columns: 1fr 1fr 1fr; - grid-template-rows: 1fr 1fr; + grid-template-columns: repeat(3, calc(100% / 3)); + grid-template-rows: repeat(2, calc(100% / 2)); flex: 1 1 0; } From f593bff3c30113c6eb94e3309716cf62e5b2aabd Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 22 Nov 2018 17:03:23 +0000 Subject: [PATCH 22/51] show right panel tabs inside panel instead of room header in grid mode --- res/css/structures/_GroupGridView.scss | 26 +++++++++++++++++++++- res/css/views/rooms/_MemberList.scss | 4 ++++ src/components/structures/GroupGridView.js | 13 +++++++++-- src/components/structures/RoomView.js | 7 +++++- src/components/views/rooms/RoomHeader.js | 2 +- 5 files changed, 47 insertions(+), 5 deletions(-) diff --git a/res/css/structures/_GroupGridView.scss b/res/css/structures/_GroupGridView.scss index 14c46d1370..0464026ed4 100644 --- a/res/css/structures/_GroupGridView.scss +++ b/res/css/structures/_GroupGridView.scss @@ -14,6 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ +.mx_GroupGridView { + display: flex; + flex-direction: column; +} + .mx_GroupGridView_rooms { display: grid; grid-template-columns: repeat(3, calc(100% / 3)); @@ -21,11 +26,30 @@ limitations under the License. flex: 1 1 0; } -.mx_GroupGridView { + +.mx_GroupGridView_rightPanel { display: flex; flex-direction: column; + + .mx_GroupGridView_tabs { + flex: 0 0 52px; + border-bottom: 1px solid $primary-hairline-color; + display: flex; + align-items: center; + + > div { + justify-content: flex-end; + width: 100%; + margin-right: 10px; + } + } + + .mx_RightPanel { + flex: 1 0 auto !important; + } } + .mx_GroupGridView > .mx_MainSplit { flex: 1 1 0; display: flex; diff --git a/res/css/views/rooms/_MemberList.scss b/res/css/views/rooms/_MemberList.scss index 2695ebcf31..8e59eb85d5 100644 --- a/res/css/views/rooms/_MemberList.scss +++ b/res/css/views/rooms/_MemberList.scss @@ -53,6 +53,10 @@ limitations under the License. .mx_MemberList_query, .mx_GroupMemberList_query, .mx_GroupRoomList_query { + flex: 0 0 auto; +} + +.mx_MemberList .gm-scrollbar-container { flex: 1 1 0; } diff --git a/src/components/structures/GroupGridView.js b/src/components/structures/GroupGridView.js index d6268ff14c..abfd343309 100644 --- a/src/components/structures/GroupGridView.js +++ b/src/components/structures/GroupGridView.js @@ -23,6 +23,7 @@ import RoomView from './RoomView'; import classNames from 'classnames'; import MainSplit from './MainSplit'; import RightPanel from './RightPanel'; +import RoomHeaderButtons from '../views/right_panel/RoomHeaderButtons'; export default class RoomGridView extends React.Component { @@ -93,7 +94,15 @@ export default class RoomGridView extends React.Component { roomStores = roomStores.concat(emptyTiles); } const activeRoomId = this.state.activeRoomStore && this.state.activeRoomStore.getRoomId(); - const rightPanel = activeRoomId ? : undefined; + let rightPanel; + if (activeRoomId) { + rightPanel = ( +
+
+ +
+ ); + } return (
@@ -107,7 +116,7 @@ export default class RoomGridView extends React.Component { }); return (
{this._setActive(i)}} key={roomStore.getRoomId()} className={tileClasses}> diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index f6b9381080..af389c804a 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -1509,6 +1509,7 @@ module.exports = React.createClass({
@@ -1555,6 +1556,7 @@ module.exports = React.createClass({
@@ -1813,11 +1815,14 @@ module.exports = React.createClass({ }, ); - const rightPanel = this.state.room ? : undefined; + const rightPanel = this.state.room && !this.props.isGrid ? + : + undefined; return (
+ { !this.props.isGrid ? : undefined }
); From c8243357eac5509d6e920df0928450c939165d98 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 22 Nov 2018 18:32:57 +0000 Subject: [PATCH 23/51] disable editor history/persistence when in grid to avoid pesky bug --- src/components/structures/RoomView.js | 1 + src/components/views/rooms/MessageComposer.js | 1 + .../views/rooms/MessageComposerInput.js | 21 ++++++++++++++++++- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index af389c804a..681b1221e1 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -1690,6 +1690,7 @@ module.exports = React.createClass({ this.messageComposerInput = c} key="controls_input" + isGrid={this.props.isGrid} onResize={this.props.onResize} room={this.props.room} placeholder={placeholderText} diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index c54c0b1ab6..0740667242 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -132,6 +132,18 @@ function rangeEquals(a: Range, b: Range): boolean { && a.isBackward === b.isBackward); } +class NoopHistoryManager { + getItem() {} + save() {} + + get currentIndex() { return 0; } + set currentIndex(_) {} + + get history() { return []; } + set history(_) {} +} + + /* * The textInput part of the MessageComposer */ @@ -343,7 +355,14 @@ export default class MessageComposerInput extends React.Component { componentWillMount() { this.dispatcherRef = this.props.roomViewStore.getDispatcher().register(this.onAction); - this.historyManager = new ComposerHistoryManager(this.props.room.roomId, 'mx_slate_composer_history_'); + if (this.props.isGrid) { + + + + this.historyManager = new NoopHistoryManager(); + } else { + this.historyManager = new ComposerHistoryManager(this.props.room.roomId, 'mx_slate_composer_history_'); + } } componentWillUnmount() { From b0c84591d7300df0c46f948cc1b461c7b930b9a4 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 22 Nov 2018 18:35:14 +0000 Subject: [PATCH 24/51] show focus glow below dialogs (at z-index 4000) --- res/css/structures/_GroupGridView.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/structures/_GroupGridView.scss b/res/css/structures/_GroupGridView.scss index 0464026ed4..9e51af25f9 100644 --- a/res/css/structures/_GroupGridView.scss +++ b/res/css/structures/_GroupGridView.scss @@ -80,7 +80,7 @@ limitations under the License. bottom: 0; content: ""; pointer-events: none; - z-index: 10000; + z-index: 3500; } .mx_GroupGridView_activeTile:before { From 0ffd77762acd0283faa5bee217343328db9d7261 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 22 Nov 2018 18:54:27 +0000 Subject: [PATCH 25/51] make menu option look somewhat better --- res/img/icons-gridview.svg | 103 ++++++++++++++++++ .../views/context_menus/TagTileContextMenu.js | 6 + src/i18n/strings/en_EN.json | 3 +- 3 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 res/img/icons-gridview.svg diff --git a/res/img/icons-gridview.svg b/res/img/icons-gridview.svg new file mode 100644 index 0000000000..862ca63765 --- /dev/null +++ b/res/img/icons-gridview.svg @@ -0,0 +1,103 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/src/components/views/context_menus/TagTileContextMenu.js b/src/components/views/context_menus/TagTileContextMenu.js index b8930efefe..5cd5aea27a 100644 --- a/src/components/views/context_menus/TagTileContextMenu.js +++ b/src/components/views/context_menus/TagTileContextMenu.js @@ -75,6 +75,12 @@ export default class TagTileContextMenu extends React.Component { { _t('View Community') }
+ { _t('View as grid') }

diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index c137253a43..f592ad6441 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1348,5 +1348,6 @@ "Import": "Import", "Failed to set direct chat tag": "Failed to set direct chat tag", "Failed to remove tag %(tagName)s from room": "Failed to remove tag %(tagName)s from room", - "Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room" + "Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room", + "View as grid": "View as grid" } From 368ef9e8e89b0e75c26e4912049002d4558a495f Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 22 Nov 2018 18:57:07 +0000 Subject: [PATCH 26/51] hack so we don't revert to single room view when viewing grid --- src/stores/OpenRoomsStore.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/stores/OpenRoomsStore.js b/src/stores/OpenRoomsStore.js index f67108d35a..764f54d7c2 100644 --- a/src/stores/OpenRoomsStore.js +++ b/src/stores/OpenRoomsStore.js @@ -102,10 +102,15 @@ class OpenRoomsStore extends Store { const dispatcher = new MatrixDispatcher(); // forward all actions coming from the room dispatcher // to the global one - const dispatcherRef = dispatcher.register((action) => { - action.grid_src_room_id = room_id; - action.grid_src_room_alias = room_alias; - this.getDispatcher().dispatch(action); + const dispatcherRef = dispatcher.register((payload) => { + // block a view_room action for the same room because it will switch to + // single room mode in MatrixChat + if (payload.action === 'view_room' && room_id === payload.room_id) { + return; + } + payload.grid_src_room_id = room_id; + payload.grid_src_room_alias = room_alias; + this.getDispatcher().dispatch(payload); }); const openRoom = { store: new RoomViewStore(dispatcher), From 04bb13bb7b1d711c3a51b86d46640475ff130f60 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 22 Nov 2018 18:57:37 +0000 Subject: [PATCH 27/51] emit join error over own dispatcher, meh --- src/stores/RoomViewStore.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/stores/RoomViewStore.js b/src/stores/RoomViewStore.js index af1264921a..1e0f5c6a4f 100644 --- a/src/stores/RoomViewStore.js +++ b/src/stores/RoomViewStore.js @@ -14,7 +14,6 @@ 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 dis from '../dispatcher'; import {Store} from 'flux/utils'; import MatrixClientPeg from '../MatrixClientPeg'; import sdk from '../index'; @@ -191,7 +190,7 @@ export class RoomViewStore extends Store { // stream yet, and that's the point at which we'd consider // the user joined to the room. }, (err) => { - dis.dispatch({ + this.getDispatcher().dispatch({ action: 'join_room_error', err: err, }); From 1810c17d24f2165d268c7c35be96a35b1b009758 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 22 Nov 2018 18:58:12 +0000 Subject: [PATCH 28/51] remove trace, add comment, ... --- src/components/structures/MatrixChat.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 3d3aa52e57..b67dc7b352 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -837,8 +837,7 @@ export default React.createClass({ // room name and avatar from an invite email) _viewRoom: function(roomInfo) { this.focusComposer = true; - console.log("!!! MatrixChat._viewRoom", roomInfo); - console.trace(); + console.log("!!! MatrixChat._viewRoom", roomInfo); const newState = { currentRoomId: roomInfo.room_id || null, @@ -887,6 +886,9 @@ export default React.createClass({ if (roomInfo.event_id && roomInfo.highlighted) { presentedId += "/" + roomInfo.event_id; } + + + // TODO: only emit this when we're not in grid mode? this.notifyNewScreen('room/' + presentedId); newState.ready = true; this.setState(newState); From 39289e57ac46b401a95bff3e53c7addfed3b761d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 22 Nov 2018 19:06:58 +0000 Subject: [PATCH 29/51] fix correct room being highlighted in left panel --- src/ActiveRoomObserver.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/ActiveRoomObserver.js b/src/ActiveRoomObserver.js index f3850dd87c..c276cccb5d 100644 --- a/src/ActiveRoomObserver.js +++ b/src/ActiveRoomObserver.js @@ -55,24 +55,23 @@ class ActiveRoomObserver { } } - _emit(roomId) { + _emit(roomId, newActiveRoomId) { if (!this._listeners[roomId]) return; for (const l of this._listeners[roomId]) { - l.call(); + l.call(l, newActiveRoomId); } } _onOpenRoomsStoreUpdate() { - // emit for the old room ID - if (this._activeRoomId) this._emit(this._activeRoomId); - const activeRoomStore = OpenRoomsStore.getActiveRoomStore(); + const newActiveRoomId = activeRoomStore && activeRoomStore.getRoomId(); + // emit for the old room ID + if (this._activeRoomId) this._emit(this._activeRoomId, newActiveRoomId); // update our cache - this._activeRoomId = activeRoomStore && activeRoomStore.getRoomId(); - + this._activeRoomId = newActiveRoomId; // and emit for the new one - if (this._activeRoomId) this._emit(this._activeRoomId); + if (this._activeRoomId) this._emit(this._activeRoomId, this._activeRoomId); } } From 7b6c8633772c0deff41e59f1d2b582ff5efeeb27 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 14 Dec 2018 15:53:52 +0100 Subject: [PATCH 30/51] fix lint --- src/components/structures/GroupGridView.js | 19 +++++++++++-------- src/stores/OpenRoomsStore.js | 7 ++++--- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/components/structures/GroupGridView.js b/src/components/structures/GroupGridView.js index abfd343309..b80eeced0c 100644 --- a/src/components/structures/GroupGridView.js +++ b/src/components/structures/GroupGridView.js @@ -16,7 +16,6 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import OpenRoomsStore from '../../stores/OpenRoomsStore'; import dis from '../../dispatcher'; import RoomView from './RoomView'; @@ -114,13 +113,17 @@ export default class RoomGridView extends React.Component { "mx_GroupGridView_tile": true, "mx_GroupGridView_activeTile": isActive, }); - return (
{this._setActive(i)}} key={roomStore.getRoomId()} className={tileClasses}> - -
); + return (
{this._setActive(i);}} + key={roomStore.getRoomId()} + className={tileClasses} + > + +
); } else { return (
); } diff --git a/src/stores/OpenRoomsStore.js b/src/stores/OpenRoomsStore.js index 764f54d7c2..fabed60c9e 100644 --- a/src/stores/OpenRoomsStore.js +++ b/src/stores/OpenRoomsStore.js @@ -182,7 +182,7 @@ class OpenRoomsStore extends Store { auto_join: payload.auto_join, oob_data: payload.oob_data, }); - } catch(err) { + } catch (err) { this._forwardAction({ action: 'view_room_error', room_id: null, @@ -223,6 +223,7 @@ class OpenRoomsStore extends Store { } __onDispatch(payload) { + let proposedIndex; switch (payload.action) { // view_room: // - room_alias: '#somealias:matrix.org' @@ -253,10 +254,10 @@ class OpenRoomsStore extends Store { this._forwardingEvent = payload.event; break; case 'group_grid_set_active': - const proposedIndex = this._roomIndex(payload); + proposedIndex = this._roomIndex(payload); if (proposedIndex !== -1) { this._setState({ - currentIndex: proposedIndex + currentIndex: proposedIndex, }); } break; From b63bd5ea5453c08d05dd6002c5c6c6d704647b00 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 7 Jan 2019 14:59:00 +0100 Subject: [PATCH 31/51] allow right panel to be hidden (although container is still visible) --- src/components/structures/GroupGridView.js | 4 ++-- src/components/structures/LoggedInView.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/structures/GroupGridView.js b/src/components/structures/GroupGridView.js index b80eeced0c..ece4e5fa23 100644 --- a/src/components/structures/GroupGridView.js +++ b/src/components/structures/GroupGridView.js @@ -97,8 +97,8 @@ export default class RoomGridView extends React.Component { if (activeRoomId) { rightPanel = (
-
- +
+ { !this.props.collapsedRhs ? : undefined }
); } diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index 74274a5be2..67d7d41701 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -452,7 +452,7 @@ const LoggedInView = React.createClass({ />; break; case PageTypes.GroupGridView: - page_element = ; + page_element = ; break; case PageTypes.UserSettings: page_element = Date: Mon, 7 Jan 2019 15:20:39 +0100 Subject: [PATCH 32/51] clear width of right panel container when collapsed in grid view --- src/components/structures/GroupGridView.js | 2 +- src/components/structures/MainSplit.js | 15 +++++++++------ src/resizer/resizer.js | 10 +++++++++- src/resizer/sizer.js | 8 ++++++++ 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/components/structures/GroupGridView.js b/src/components/structures/GroupGridView.js index ece4e5fa23..c8d5f59323 100644 --- a/src/components/structures/GroupGridView.js +++ b/src/components/structures/GroupGridView.js @@ -104,7 +104,7 @@ export default class RoomGridView extends React.Component { } return (
- +
{ roomStores.map((roomStore, i) => { if (roomStore) { diff --git a/src/components/structures/MainSplit.js b/src/components/structures/MainSplit.js index 0427130eea..aa27930ce0 100644 --- a/src/components/structures/MainSplit.js +++ b/src/components/structures/MainSplit.js @@ -71,14 +71,17 @@ export default class MainSplit extends React.Component { } componentDidUpdate(prevProps) { - const wasExpanded = !this.props.collapsedRhs && prevProps.collapsedRhs; - const wasCollapsed = this.props.collapsedRhs && !prevProps.collapsedRhs; - const wasPanelSet = this.props.panel && !prevProps.panel; - const wasPanelCleared = !this.props.panel && prevProps.panel; + const shouldAllowResizing = + !this.props.disableSizing && + !this.props.collapsedRhs && + this.props.panel; - if (wasExpanded || wasPanelSet) { + if (shouldAllowResizing && !this.resizer) { this._createResizer(); - } else if (wasCollapsed || wasPanelCleared) { + } else if (!shouldAllowResizing && this.resizer) { + if (this.props.disableSizing) { + this.resizer.clearItemSizes(); + } this.resizer.detach(); this.resizer = null; } diff --git a/src/resizer/resizer.js b/src/resizer/resizer.js index 0e113b3664..ff2120b341 100644 --- a/src/resizer/resizer.js +++ b/src/resizer/resizer.js @@ -43,6 +43,14 @@ export class Resizer { this._onMouseDown = this._onMouseDown.bind(this); } + clearItemSizes() { + const handles = this._getResizeHandles(); + handles.forEach((handle) => { + const {sizer, item} = this._createSizerAndDistributor(handle); + sizer.clearItemSize(item); + }); + } + setClassNames(classNames) { this.classNames = classNames; } @@ -134,7 +142,7 @@ export class Resizer { const distributor = new this.distributorCtor( sizer, item, id, this.distributorCfg, items, this.container); - return {sizer, distributor}; + return {sizer, distributor, item}; } _getResizableItems() { diff --git a/src/resizer/sizer.js b/src/resizer/sizer.js index 303214854b..0e2236814e 100644 --- a/src/resizer/sizer.js +++ b/src/resizer/sizer.js @@ -82,6 +82,14 @@ class Sizer { } } + clearItemSize(item, size) { + if (this.vertical) { + item.style.height = null; + } else { + item.style.width = null; + } + } + /** @param {MouseEvent} event the mouse event @return {number} the distance between the cursor and the edge of the container, From 8fa7ec0fac7df925de97cc400691999b7ecc1e65 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 7 Jan 2019 16:03:24 +0100 Subject: [PATCH 33/51] fix lint --- src/components/structures/GroupGridView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/GroupGridView.js b/src/components/structures/GroupGridView.js index c8d5f59323..4c488941d0 100644 --- a/src/components/structures/GroupGridView.js +++ b/src/components/structures/GroupGridView.js @@ -104,7 +104,7 @@ export default class RoomGridView extends React.Component { } return (
- +
{ roomStores.map((roomStore, i) => { if (roomStore) { From c2f6fc381cf63a63bc4c9fab093ecccacbb5bbd9 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 7 Jan 2019 16:13:35 +0100 Subject: [PATCH 34/51] add feature flag for grid view --- .../views/context_menus/TagTileContextMenu.js | 23 +++++++++++-------- src/i18n/strings/en_EN.json | 3 ++- src/settings/Settings.js | 6 +++++ 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/components/views/context_menus/TagTileContextMenu.js b/src/components/views/context_menus/TagTileContextMenu.js index 5cd5aea27a..ad93783485 100644 --- a/src/components/views/context_menus/TagTileContextMenu.js +++ b/src/components/views/context_menus/TagTileContextMenu.js @@ -21,6 +21,7 @@ import dis from '../../../dispatcher'; import TagOrderActions from '../../../actions/TagOrderActions'; import MatrixClientPeg from '../../../MatrixClientPeg'; import sdk from '../../../index'; +import SettingsStore from "../../../settings/SettingsStore"; export default class TagTileContextMenu extends React.Component { static propTypes = { @@ -64,6 +65,18 @@ export default class TagTileContextMenu extends React.Component { render() { const TintableSvg = sdk.getComponent("elements.TintableSvg"); + let gridViewOption; + if (SettingsStore.isFeatureEnabled("feature_gridview")) { + gridViewOption = (
+ + { _t('View as grid') } +
); + } return
{ _t('View Community') }
-
- - { _t('View as grid') } -
+ { gridViewOption }
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 8822bd6388..4fab9ec438 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1405,5 +1405,6 @@ "Failed to set direct chat tag": "Failed to set direct chat tag", "Failed to remove tag %(tagName)s from room": "Failed to remove tag %(tagName)s from room", "Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room", - "View as grid": "View as grid" + "View as grid": "View as grid", + "Allow up to 6 rooms in a community to be shown simultaneously in a grid via the context menu": "Allow up to 6 rooms in a community to be shown simultaneously in a grid via the context menu" } diff --git a/src/settings/Settings.js b/src/settings/Settings.js index 14f4bdc6dd..8edec434bf 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -102,6 +102,12 @@ export const SETTINGS = { supportedLevels: LEVELS_FEATURE, default: false, }, + "feature_gridview": { + isFeature: true, + displayName: _td("Allow up to 6 rooms in a community to be shown simultaneously in a grid via the context menu"), + supportedLevels: LEVELS_FEATURE, + default: false, + }, "MessageComposerInput.dontSuggestEmoji": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, displayName: _td('Disable Emoji suggestions while typing'), From aedc220b62a2414251c792fb111b9d421d7e17f6 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 7 Jan 2019 16:33:23 +0100 Subject: [PATCH 35/51] fix (some) lint warnings --- src/components/structures/GroupGridView.js | 2 -- src/stores/OpenRoomsStore.js | 18 +++++++++--------- src/utils/Timer.js | 1 - 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/components/structures/GroupGridView.js b/src/components/structures/GroupGridView.js index 4c488941d0..2eb3c357df 100644 --- a/src/components/structures/GroupGridView.js +++ b/src/components/structures/GroupGridView.js @@ -25,7 +25,6 @@ import RightPanel from './RightPanel'; import RoomHeaderButtons from '../views/right_panel/RoomHeaderButtons'; export default class RoomGridView extends React.Component { - constructor(props) { super(props); this.state = { @@ -132,5 +131,4 @@ export default class RoomGridView extends React.Component {
); } - } diff --git a/src/stores/OpenRoomsStore.js b/src/stores/OpenRoomsStore.js index fabed60c9e..21f02fe28d 100644 --- a/src/stores/OpenRoomsStore.js +++ b/src/stores/OpenRoomsStore.js @@ -98,18 +98,18 @@ class OpenRoomsStore extends Store { }); } - _createOpenRoom(room_id, room_alias) { + _createOpenRoom(roomId, roomAlias) { const dispatcher = new MatrixDispatcher(); // forward all actions coming from the room dispatcher // to the global one const dispatcherRef = dispatcher.register((payload) => { // block a view_room action for the same room because it will switch to // single room mode in MatrixChat - if (payload.action === 'view_room' && room_id === payload.room_id) { + if (payload.action === 'view_room' && roomId === payload.room_id) { return; } - payload.grid_src_room_id = room_id; - payload.grid_src_room_alias = room_alias; + payload.grid_src_room_id = roomId; + payload.grid_src_room_alias = roomAlias; this.getDispatcher().dispatch(payload); }); const openRoom = { @@ -120,8 +120,8 @@ class OpenRoomsStore extends Store { dispatcher.dispatch({ action: 'view_room', - room_id: room_id, - room_alias: room_alias, + room_id: roomId, + room_alias: roomAlias, }, true); return openRoom; @@ -134,16 +134,16 @@ class OpenRoomsStore extends Store { }); } - _setGroupOpenRooms(group_id) { + _setGroupOpenRooms(groupId) { this._cleanupOpenRooms(); // TODO: register to GroupStore updates - const rooms = GroupStore.getGroupRooms(group_id); + const rooms = GroupStore.getGroupRooms(groupId); const openRooms = rooms.map((room) => { return this._createOpenRoom(room.roomId); }); this._setState({ rooms: openRooms, - group_id: group_id, + group_id: groupId, currentIndex: 0, }); } diff --git a/src/utils/Timer.js b/src/utils/Timer.js index aeac0887c9..4953cf54f7 100644 --- a/src/utils/Timer.js +++ b/src/utils/Timer.js @@ -26,7 +26,6 @@ Once a timer is finished or aborted, it can't be started again a new one through `clone()` or `cloneIfRun()`. */ export default class Timer { - constructor(timeout) { this._timeout = timeout; this._onTimeout = this._onTimeout.bind(this); From c6952ba5b603a3d5285355f3f69b3e28ed9e717b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 7 Jan 2019 16:56:35 +0100 Subject: [PATCH 36/51] fix some more lint warnings, as limit is 16 now --- src/components/views/right_panel/HeaderButtons.js | 1 - src/utils/Timer.js | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/views/right_panel/HeaderButtons.js b/src/components/views/right_panel/HeaderButtons.js index f0479eb8be..3f5f58121d 100644 --- a/src/components/views/right_panel/HeaderButtons.js +++ b/src/components/views/right_panel/HeaderButtons.js @@ -78,7 +78,6 @@ export default class HeaderButtons extends React.Component { // till show_right_panel, just without the fromHeader flag // as that would hide the right panel again dis.dispatch(Object.assign({}, payload, {fromHeader: false})); - } this.setState({ phase: payload.phase, diff --git a/src/utils/Timer.js b/src/utils/Timer.js index 4953cf54f7..ca06237fbf 100644 --- a/src/utils/Timer.js +++ b/src/utils/Timer.js @@ -69,6 +69,7 @@ export default class Timer { /** * if not started before, starts the timer. + * @returns {Timer} the same timer */ start() { if (!this.isRunning()) { @@ -80,6 +81,7 @@ export default class Timer { /** * (re)start the timer. If it's running, reset the timeout. If not, start it. + * @returns {Timer} the same timer */ restart() { if (this.isRunning()) { @@ -97,6 +99,7 @@ export default class Timer { /** * if the timer is running, abort it, * and reject the promise for this timer. + * @returns {Timer} the same timer */ abort() { if (this.isRunning()) { From c9272c48e0834f953743ccb01911743c0bfa663a Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 8 Jan 2019 12:05:06 +0100 Subject: [PATCH 37/51] undo unneeded changes --- src/components/structures/MainSplit.js | 4 ---- src/resizer/resizer.js | 10 +--------- src/resizer/sizer.js | 8 -------- 3 files changed, 1 insertion(+), 21 deletions(-) diff --git a/src/components/structures/MainSplit.js b/src/components/structures/MainSplit.js index aa27930ce0..e1bbde1d97 100644 --- a/src/components/structures/MainSplit.js +++ b/src/components/structures/MainSplit.js @@ -72,16 +72,12 @@ export default class MainSplit extends React.Component { componentDidUpdate(prevProps) { const shouldAllowResizing = - !this.props.disableSizing && !this.props.collapsedRhs && this.props.panel; if (shouldAllowResizing && !this.resizer) { this._createResizer(); } else if (!shouldAllowResizing && this.resizer) { - if (this.props.disableSizing) { - this.resizer.clearItemSizes(); - } this.resizer.detach(); this.resizer = null; } diff --git a/src/resizer/resizer.js b/src/resizer/resizer.js index ff2120b341..0e113b3664 100644 --- a/src/resizer/resizer.js +++ b/src/resizer/resizer.js @@ -43,14 +43,6 @@ export class Resizer { this._onMouseDown = this._onMouseDown.bind(this); } - clearItemSizes() { - const handles = this._getResizeHandles(); - handles.forEach((handle) => { - const {sizer, item} = this._createSizerAndDistributor(handle); - sizer.clearItemSize(item); - }); - } - setClassNames(classNames) { this.classNames = classNames; } @@ -142,7 +134,7 @@ export class Resizer { const distributor = new this.distributorCtor( sizer, item, id, this.distributorCfg, items, this.container); - return {sizer, distributor, item}; + return {sizer, distributor}; } _getResizableItems() { diff --git a/src/resizer/sizer.js b/src/resizer/sizer.js index 0e2236814e..303214854b 100644 --- a/src/resizer/sizer.js +++ b/src/resizer/sizer.js @@ -82,14 +82,6 @@ class Sizer { } } - clearItemSize(item, size) { - if (this.vertical) { - item.style.height = null; - } else { - item.style.width = null; - } - } - /** @param {MouseEvent} event the mouse event @return {number} the distance between the cursor and the edge of the container, From 7227049c2a98db1b1c7fee33a342e7be7a86055b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 8 Jan 2019 12:05:39 +0100 Subject: [PATCH 38/51] add right panel toggle button to room header when in grid --- res/img/feather-icons/toggle-right-panel.svg | 17 +++++++++++++++++ src/components/structures/GroupGridView.js | 7 ++++--- src/components/views/rooms/RoomHeader.js | 17 +++++++++++++++++ 3 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 res/img/feather-icons/toggle-right-panel.svg diff --git a/res/img/feather-icons/toggle-right-panel.svg b/res/img/feather-icons/toggle-right-panel.svg new file mode 100644 index 0000000000..4cadf89564 --- /dev/null +++ b/res/img/feather-icons/toggle-right-panel.svg @@ -0,0 +1,17 @@ + + + + Group 2 + Created with Sketch. + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/structures/GroupGridView.js b/src/components/structures/GroupGridView.js index 2eb3c357df..b5e511db41 100644 --- a/src/components/structures/GroupGridView.js +++ b/src/components/structures/GroupGridView.js @@ -96,14 +96,14 @@ export default class RoomGridView extends React.Component { if (activeRoomId) { rightPanel = (
-
- { !this.props.collapsedRhs ? : undefined } +
+
); } return (
- +
{ roomStores.map((roomStore, i) => { if (roomStore) { @@ -118,6 +118,7 @@ export default class RoomGridView extends React.Component { className={tileClasses} > ; } + let toggleRightPanelButton; + if (this.props.isGrid) { + toggleRightPanelButton = + + ; + } + return (
@@ -420,6 +436,7 @@ module.exports = React.createClass({ { cancelButton } { rightRow } { !this.props.isGrid ? : undefined } + { toggleRightPanelButton }
); From 56726ba8e592bc9496f5f6c95ce3211ffd908478 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 8 Jan 2019 12:10:42 +0100 Subject: [PATCH 39/51] fix lint --- src/UserActivity.js | 1 + src/components/views/rooms/RoomHeader.js | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/UserActivity.js b/src/UserActivity.js index 4e3667274c..145b23e36e 100644 --- a/src/UserActivity.js +++ b/src/UserActivity.js @@ -44,6 +44,7 @@ class UserActivity { * Can be called multiple times with the same already running timer, which is a NO-OP. * Can be called before the user becomes active, in which case it is only started * later on when the user does become active. + * @param {Timer} timer the timer to use */ timeWhileActive(timer) { // important this happens first diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 12258154ef..91ca73dd59 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -420,7 +420,11 @@ module.exports = React.createClass({ let toggleRightPanelButton; if (this.props.isGrid) { - toggleRightPanelButton = + toggleRightPanelButton = + ; } From 52c5610660c5dcec972fed536e02677aadf73324 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 8 Jan 2019 14:35:06 +0100 Subject: [PATCH 40/51] update copy --- src/components/views/context_menus/TagTileContextMenu.js | 2 +- src/i18n/strings/en_EN.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/context_menus/TagTileContextMenu.js b/src/components/views/context_menus/TagTileContextMenu.js index ad93783485..de20f42a7b 100644 --- a/src/components/views/context_menus/TagTileContextMenu.js +++ b/src/components/views/context_menus/TagTileContextMenu.js @@ -74,7 +74,7 @@ export default class TagTileContextMenu extends React.Component { width="15" height="15" /> - { _t('View as grid') } + { _t('View as Grid') }
); } return
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4fab9ec438..8c544ee3b6 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1405,6 +1405,6 @@ "Failed to set direct chat tag": "Failed to set direct chat tag", "Failed to remove tag %(tagName)s from room": "Failed to remove tag %(tagName)s from room", "Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room", - "View as grid": "View as grid", + "View as Grid": "View as Grid", "Allow up to 6 rooms in a community to be shown simultaneously in a grid via the context menu": "Allow up to 6 rooms in a community to be shown simultaneously in a grid via the context menu" } From 9e67dbf0086c822062c80b8031717d87f40c9324 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 8 Jan 2019 14:35:26 +0100 Subject: [PATCH 41/51] change icon --- res/img/icons-gridview.svg | 103 ------------------ .../views/context_menus/TagTileContextMenu.js | 2 +- 2 files changed, 1 insertion(+), 104 deletions(-) delete mode 100644 res/img/icons-gridview.svg diff --git a/res/img/icons-gridview.svg b/res/img/icons-gridview.svg deleted file mode 100644 index 862ca63765..0000000000 --- a/res/img/icons-gridview.svg +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - diff --git a/src/components/views/context_menus/TagTileContextMenu.js b/src/components/views/context_menus/TagTileContextMenu.js index de20f42a7b..8ce09bdb2c 100644 --- a/src/components/views/context_menus/TagTileContextMenu.js +++ b/src/components/views/context_menus/TagTileContextMenu.js @@ -70,7 +70,7 @@ export default class TagTileContextMenu extends React.Component { gridViewOption = (
From 254427461da694087279202f1f8781ef277a5166 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 9 Jan 2019 12:48:13 +0100 Subject: [PATCH 42/51] fix PR feedback --- res/css/structures/_GroupGridView.scss | 10 ++++------ res/themes/dark/css/_dark.scss | 4 ++++ res/themes/dharma/css/_dharma.scss | 3 +++ res/themes/light/css/_base.scss | 4 ++++ src/components/structures/GroupGridView.js | 12 ++---------- src/components/views/rooms/MessageComposerInput.js | 3 --- src/i18n/strings/en_EN.json | 3 ++- src/stores/RoomViewStore.js | 4 ++-- 8 files changed, 21 insertions(+), 22 deletions(-) diff --git a/res/css/structures/_GroupGridView.scss b/res/css/structures/_GroupGridView.scss index 9e51af25f9..3a1ff165f1 100644 --- a/res/css/structures/_GroupGridView.scss +++ b/res/css/structures/_GroupGridView.scss @@ -26,7 +26,6 @@ limitations under the License. flex: 1 1 0; } - .mx_GroupGridView_rightPanel { display: flex; flex-direction: column; @@ -55,11 +54,11 @@ limitations under the License. display: flex; } -.mx_GroupGridView_emptyTile::before { +.mx_GroupGridView_emptyTile { display: block; margin-top: 100px; text-align: center; - content: "no room in this tile yet"; + user-select: none; } .mx_GroupGridView_tile { @@ -85,17 +84,16 @@ limitations under the License. .mx_GroupGridView_activeTile:before { border-radius: 14px; - border: 8px solid rgba(134, 193, 165, 0.5); + border: 8px solid $gridview-focus-border-glow-color; margin: -8px; } .mx_GroupGridView_activeTile:after { border-radius: 8px; - border: 2px solid rgba(134, 193, 165, 1); + border: 2px solid $gridview-focus-border-color; margin: -2px; } - .mx_GroupGridView_tile > .mx_RoomView { height: 100%; } diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 636db5b39e..10d512d576 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -162,6 +162,10 @@ $lightbox-bg-color: #454545; $lightbox-fg-color: #ffffff; $lightbox-border-color: #ffffff; +/*** GroupGridView ***/ +$gridview-focus-border-glow-color: rgba(134, 193, 165, 0.5); +$gridview-focus-border-color: rgba(134, 193, 165, 1); + $imagebody-giflabel: rgba(1, 1, 1, 0.7); $imagebody-giflabel-border: rgba(1, 1, 1, 0.2); diff --git a/res/themes/dharma/css/_dharma.scss b/res/themes/dharma/css/_dharma.scss index 08a287ad71..934e18b2a9 100644 --- a/res/themes/dharma/css/_dharma.scss +++ b/res/themes/dharma/css/_dharma.scss @@ -183,6 +183,9 @@ $lightbox-bg-color: #454545; $lightbox-fg-color: #ffffff; $lightbox-border-color: #ffffff; +/*** GroupGridView ***/ +$gridview-focus-border-glow-color: rgba(134, 193, 165, 0.5); +$gridview-focus-border-color: rgba(134, 193, 165, 1); // unused? $progressbar-color: #000; diff --git a/res/themes/light/css/_base.scss b/res/themes/light/css/_base.scss index 9fcb58d7f1..aaab5cd93a 100644 --- a/res/themes/light/css/_base.scss +++ b/res/themes/light/css/_base.scss @@ -175,6 +175,10 @@ $lightbox-bg-color: #454545; $lightbox-fg-color: #ffffff; $lightbox-border-color: #ffffff; +/*** GroupGridView ***/ +$gridview-focus-border-glow-color: rgba(134, 193, 165, 0.5); +$gridview-focus-border-color: rgba(134, 193, 165, 1); + $imagebody-giflabel: rgba(0, 0, 0, 0.7); $imagebody-giflabel-border: rgba(0, 0, 0, 0.2); diff --git a/src/components/structures/GroupGridView.js b/src/components/structures/GroupGridView.js index b5e511db41..a1a9e1b183 100644 --- a/src/components/structures/GroupGridView.js +++ b/src/components/structures/GroupGridView.js @@ -18,6 +18,7 @@ limitations under the License. import React from 'react'; import OpenRoomsStore from '../../stores/OpenRoomsStore'; import dis from '../../dispatcher'; +import {_t} from '../../languageHandler'; import RoomView from './RoomView'; import classNames from 'classnames'; import MainSplit from './MainSplit'; @@ -48,7 +49,6 @@ export default class RoomGridView extends React.Component { componentWillMount() { this._unmounted = false; this._openRoomsStoreRegistration = OpenRoomsStore.addListener(this.onRoomsChanged); - this._dispatcherRef = dis.register(this._onAction); } componentWillUnmount() { @@ -56,7 +56,6 @@ export default class RoomGridView extends React.Component { if (this._openRoomsStoreRegistration) { this._openRoomsStoreRegistration.remove(); } - dis.unregister(this._dispatcherRef); } onRoomsChanged() { @@ -67,13 +66,6 @@ export default class RoomGridView extends React.Component { }); } - _onAction(payload) { - switch (payload.action) { - default: - break; - } - } - _setActive(i) { const store = OpenRoomsStore.getRoomStoreAt(i); if (store !== this.state.activeRoomStore) { @@ -125,7 +117,7 @@ export default class RoomGridView extends React.Component { />
); } else { - return (
); + return (
{_t("No room in this tile yet.")}
); } }) }
diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index 0740667242..4e7b4d3bbf 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -356,9 +356,6 @@ export default class MessageComposerInput extends React.Component { componentWillMount() { this.dispatcherRef = this.props.roomViewStore.getDispatcher().register(this.onAction); if (this.props.isGrid) { - - - this.historyManager = new NoopHistoryManager(); } else { this.historyManager = new ComposerHistoryManager(this.props.room.roomId, 'mx_slate_composer_history_'); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 8c544ee3b6..d6d70dfadd 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1406,5 +1406,6 @@ "Failed to remove tag %(tagName)s from room": "Failed to remove tag %(tagName)s from room", "Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room", "View as Grid": "View as Grid", - "Allow up to 6 rooms in a community to be shown simultaneously in a grid via the context menu": "Allow up to 6 rooms in a community to be shown simultaneously in a grid via the context menu" + "Allow up to 6 rooms in a community to be shown simultaneously in a grid via the context menu": "Allow up to 6 rooms in a community to be shown simultaneously in a grid via the context menu", + "No room in this tile yet.": "No room in this tile yet." } diff --git a/src/stores/RoomViewStore.js b/src/stores/RoomViewStore.js index 02c76d05d9..a0b831ad17 100644 --- a/src/stores/RoomViewStore.js +++ b/src/stores/RoomViewStore.js @@ -307,6 +307,6 @@ export class RoomViewStore extends Store { } const MatrixDispatcher = require("../matrix-dispatcher"); -const blubber = new RoomViewStore(new MatrixDispatcher()); +const backwardsCompatInstance = new RoomViewStore(new MatrixDispatcher()); -export default blubber; +export default backwardsCompatInstance; From 91a7c146e44752ad855c860588def52cc4218aa3 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 8 Jan 2019 10:54:38 -0600 Subject: [PATCH 43/51] Fix lint warning in create key backup --- .../views/dialogs/keybackup/CreateKeyBackupDialog.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js index c593a9b3ea..1be969bf8b 100644 --- a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js +++ b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js @@ -344,7 +344,10 @@ export default React.createClass({ _renderPhaseShowKey: function() { let bodyText; if (this.state.setPassPhrase) { - bodyText = _t("As a safety net, you can use it to restore your encrypted message history if you forget your Recovery Passphrase."); + bodyText = _t( + "As a safety net, you can use it to restore your encrypted message " + + "history if you forget your Recovery Passphrase.", + ); } else { bodyText = _t("As a safety net, you can use it to restore your encrypted message history."); } From 2bfe6e7500fbef6994211d2bf1088949584cf0e4 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 8 Jan 2019 10:55:01 -0600 Subject: [PATCH 44/51] Fix React nesting error in create key backup --- .../views/dialogs/keybackup/CreateKeyBackupDialog.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js index 1be969bf8b..be728916dd 100644 --- a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js +++ b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js @@ -355,7 +355,7 @@ export default React.createClass({ return

{_t("Make a copy of this Recovery Key and keep it safe.")}

{bodyText}

-

+

{_t("Your Recovery Key")}
@@ -372,7 +372,7 @@ export default React.createClass({
-

+ ; }, From 7dbc970347f5ee738f8ddb7de4e7aa99e8827b0b Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 8 Jan 2019 13:57:22 -0600 Subject: [PATCH 45/51] Mark KeyBackupPanel as a pure component KeyBackupPanel depends only on its own state and its children are pure, so it can be pure as well. This avoids some unnecessary re-renders. --- src/components/views/settings/KeyBackupPanel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/settings/KeyBackupPanel.js b/src/components/views/settings/KeyBackupPanel.js index 050c726ba4..1448b9d239 100644 --- a/src/components/views/settings/KeyBackupPanel.js +++ b/src/components/views/settings/KeyBackupPanel.js @@ -21,7 +21,7 @@ import MatrixClientPeg from '../../../MatrixClientPeg'; import { _t } from '../../../languageHandler'; import Modal from '../../../Modal'; -export default class KeyBackupPanel extends React.Component { +export default class KeyBackupPanel extends React.PureComponent { constructor(props) { super(props); From 731f9ee7dfe1a0e901c74ff1f9722a8001d84332 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 8 Jan 2019 18:05:48 -0600 Subject: [PATCH 46/51] Display key backup upload progress in Settings This adds a summary of the keys currently waiting for backup, which may be useful for following a large upload as it progresses. --- .../views/settings/KeyBackupPanel.js | 28 +++++++++++++++++++ src/i18n/strings/en_EN.json | 2 ++ 2 files changed, 30 insertions(+) diff --git a/src/components/views/settings/KeyBackupPanel.js b/src/components/views/settings/KeyBackupPanel.js index 1448b9d239..8aa50fbdcf 100644 --- a/src/components/views/settings/KeyBackupPanel.js +++ b/src/components/views/settings/KeyBackupPanel.js @@ -28,6 +28,8 @@ export default class KeyBackupPanel extends React.PureComponent { this._startNewBackup = this._startNewBackup.bind(this); this._deleteBackup = this._deleteBackup.bind(this); this._verifyDevice = this._verifyDevice.bind(this); + this._onKeyBackupSessionsRemaining = + this._onKeyBackupSessionsRemaining.bind(this); this._onKeyBackupStatus = this._onKeyBackupStatus.bind(this); this._restoreBackup = this._restoreBackup.bind(this); @@ -36,6 +38,7 @@ export default class KeyBackupPanel extends React.PureComponent { loading: true, error: null, backupInfo: null, + sessionsRemaining: 0, }; } @@ -43,6 +46,10 @@ export default class KeyBackupPanel extends React.PureComponent { this._loadBackupStatus(); MatrixClientPeg.get().on('crypto.keyBackupStatus', this._onKeyBackupStatus); + MatrixClientPeg.get().on( + 'crypto.keyBackupSessionsRemaining', + this._onKeyBackupSessionsRemaining, + ); } componentWillUnmount() { @@ -50,9 +57,19 @@ export default class KeyBackupPanel extends React.PureComponent { if (MatrixClientPeg.get()) { MatrixClientPeg.get().removeListener('crypto.keyBackupStatus', this._onKeyBackupStatus); + MatrixClientPeg.get().removeListener( + 'crypto.keyBackupSessionsRemaining', + this._onKeyBackupSessionsRemaining, + ); } } + _onKeyBackupSessionsRemaining(sessionsRemaining) { + this.setState({ + sessionsRemaining, + }); + } + _onKeyBackupStatus() { this._loadBackupStatus(); } @@ -153,6 +170,16 @@ export default class KeyBackupPanel extends React.PureComponent { ); } + let uploadStatus; + const { sessionsRemaining } = this.state; + if (sessionsRemaining > 0) { + uploadStatus = _t("Backing up %(sessionsRemaining)s keys...", { + sessionsRemaining, + }); + } else { + uploadStatus = _t("All keys backed up"); + } + let backupSigStatuses = this.state.backupSigStatus.sigs.map((sig, i) => { const deviceName = sig.device.getDisplayName() || sig.device.deviceId; const validity = sub => @@ -217,6 +244,7 @@ export default class KeyBackupPanel extends React.PureComponent { {_t("Backup version: ")}{this.state.backupInfo.version}
{_t("Algorithm: ")}{this.state.backupInfo.algorithm}
{clientBackupStatus}
+ {uploadStatus}
{backupSigStatuses}


not uploading keys to this backup": "This device is not uploading keys to this backup", + "Backing up %(sessionsRemaining)s keys...": "Backing up %(sessionsRemaining)s keys...", + "All keys backed up": "All keys backed up", "Backup has a valid signature from this device": "Backup has a valid signature from this device", "Backup has a valid signature from verified device ": "Backup has a valid signature from verified device ", "Backup has a valid signature from unverified device ": "Backup has a valid signature from unverified device ", From 365a7273d80a5aad91a0593b2499786da997b126 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 9 Jan 2019 05:24:15 -0600 Subject: [PATCH 47/51] Move initial key backup to background Since the initial key backup can take several minutes for some users, this moves the upload step to the background. The create key backup flow now only marks all sessions for backup synchronously, with the actual backup happening later. The key backup panel in Settings gains a new row to show a summary of upload status. Users are directed there if they wish to know if the backup is done. The text in various related dialogs has also been tweaked to fit the new flow. --- .../keybackup/CreateKeyBackupDialog.js | 17 ++++++++------- .../views/settings/KeyBackupPanel.js | 21 ++++++++++++------- src/i18n/strings/en_EN.json | 10 ++++----- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js index be728916dd..89a9e9e47d 100644 --- a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js +++ b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js @@ -21,7 +21,7 @@ import { scorePassword } from '../../../../utils/PasswordScorer'; import FileSaver from 'file-saver'; -import { _t, _td } from '../../../../languageHandler'; +import { _t } from '../../../../languageHandler'; const PHASE_PASSPHRASE = 0; const PHASE_PASSPHRASE_CONFIRM = 1; @@ -102,7 +102,7 @@ export default React.createClass({ info = await MatrixClientPeg.get().createKeyBackupVersion( this._keyBackupInfo, ); - await MatrixClientPeg.get().backupAllGroupSessions(info.version); + await MatrixClientPeg.get().scheduleAllGroupSessionsForBackup(); this.setState({ phase: PHASE_DONE, }); @@ -408,7 +408,6 @@ export default React.createClass({ _renderBusyPhase: function(text) { const Spinner = sdk.getComponent('views.elements.Spinner'); return
-

{_t(text)}

; }, @@ -416,8 +415,10 @@ export default React.createClass({ _renderPhaseDone: function() { const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); return
-

{_t("Backup created")}

-

{_t("Your encryption keys are now being backed up to your Homeserver.")}

+

{_t( + "Your encryption keys are now being backed up in the background " + + "to your Homeserver. The initial backup could take several minutes. " + + "You can view key backup upload progress in Settings.")}

not uploading keys to this backup", {}, + "This device is not using key backup", {}, {b: x => {x}}, ); } let uploadStatus; const { sessionsRemaining } = this.state; - if (sessionsRemaining > 0) { - uploadStatus = _t("Backing up %(sessionsRemaining)s keys...", { - sessionsRemaining, - }); + if (!MatrixClientPeg.get().getKeyBackupEnabled()) { + // No upload status to show when backup disabled. + uploadStatus = ""; + } else if (sessionsRemaining > 0) { + uploadStatus =
+ {_t("Backing up %(sessionsRemaining)s keys...", { sessionsRemaining })}
+
; } else { - uploadStatus = _t("All keys backed up"); + uploadStatus =
+ {_t("All keys backed up")}
+
; } let backupSigStatuses = this.state.backupSigStatus.sigs.map((sig, i) => { @@ -244,7 +249,7 @@ export default class KeyBackupPanel extends React.PureComponent { {_t("Backup version: ")}{this.state.backupInfo.version}
{_t("Algorithm: ")}{this.state.backupInfo.algorithm}
{clientBackupStatus}
- {uploadStatus}
+ {uploadStatus}
{backupSigStatuses}


not uploading keys to this backup": "This device is not uploading keys to this backup", + "This device is using key backup": "This device is using key backup", + "This device is not using key backup": "This device is not using key backup", "Backing up %(sessionsRemaining)s keys...": "Backing up %(sessionsRemaining)s keys...", "All keys backed up": "All keys backed up", "Backup has a valid signature from this device": "Backup has a valid signature from this device", @@ -1388,15 +1388,15 @@ "Print it and store it somewhere safe": "Print it and store it somewhere safe", "Save it on a USB key or backup drive": "Save it on a USB key or backup drive", "Copy it to your personal cloud storage": "Copy it to your personal cloud storage", - "Backup created": "Backup created", - "Your encryption keys are now being backed up to your Homeserver.": "Your encryption keys are now being backed up to your Homeserver.", + "Your encryption keys are now being backed up in the background to your Homeserver. The initial backup could take several minutes. You can view key backup upload progress in Settings.": "Your encryption keys are now being backed up in the background to your Homeserver. The initial backup could take several minutes. You can view key backup upload progress in Settings.", "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another device.": "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another device.", "Set up Secure Message Recovery": "Set up Secure Message Recovery", "Create a Recovery Passphrase": "Create a Recovery Passphrase", "Confirm Recovery Passphrase": "Confirm Recovery Passphrase", "Recovery Key": "Recovery Key", "Keep it safe": "Keep it safe", - "Backing up...": "Backing up...", + "Starting backup...": "Starting backup...", + "Backup Started": "Backup Started", "Create Key Backup": "Create Key Backup", "Unable to create key backup": "Unable to create key backup", "Retry": "Retry", From 40e8e48e08d175d9714e28fb08e2f2a8ee823079 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 9 Jan 2019 19:05:21 +0100 Subject: [PATCH 48/51] fix grid growing wider than viewport --- res/css/structures/_GroupGridView.scss | 1 + res/css/structures/_MatrixChat.scss | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/res/css/structures/_GroupGridView.scss b/res/css/structures/_GroupGridView.scss index 3a1ff165f1..541052175d 100644 --- a/res/css/structures/_GroupGridView.scss +++ b/res/css/structures/_GroupGridView.scss @@ -24,6 +24,7 @@ limitations under the License. grid-template-columns: repeat(3, calc(100% / 3)); grid-template-rows: repeat(2, calc(100% / 2)); flex: 1 1 0; + min-width: 0; } .mx_GroupGridView_rightPanel { diff --git a/res/css/structures/_MatrixChat.scss b/res/css/structures/_MatrixChat.scss index a843bb7fee..6d8b79ecb2 100644 --- a/res/css/structures/_MatrixChat.scss +++ b/res/css/structures/_MatrixChat.scss @@ -73,7 +73,8 @@ limitations under the License. .mx_MatrixChat > :not(.mx_LeftPanel_container):not(.mx_ResizeHandle) { background-color: $primary-bg-color; - flex: 1; + flex: 1 1 0; + min-width: 0; /* Experimental fix for https://github.com/vector-im/vector-web/issues/947 and https://github.com/vector-im/vector-web/issues/946. From 8e4d8ccca7a18363ea16fc0a4e985dc00b81ebf9 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 9 Jan 2019 18:10:35 +0000 Subject: [PATCH 49/51] Add spaces back to async arrow functions As per https://github.com/matrix-org/matrix-js-sdk/pull/821 Requires https://github.com/matrix-org/matrix-js-sdk/pull/821 --- package.json | 4 ++-- .../views/dialogs/keybackup/NewRecoveryMethodDialog.js | 2 +- src/autocomplete/CommunityProvider.js | 2 +- src/components/structures/GroupView.js | 2 +- .../views/context_menus/GroupInviteTileContextMenu.js | 2 +- .../views/context_menus/StatusMessageContextMenu.js | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 155d3d1b23..1b628067fc 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "start:init": "babel src -d lib --source-maps --copy-files", "lint": "eslint src/", "lintall": "eslint src/ test/", - "lintwithexclusions": "eslint --max-warnings 16 --ignore-path .eslintignore.errorfiles src test", + "lintwithexclusions": "eslint --max-warnings 14 --ignore-path .eslintignore.errorfiles src test", "clean": "rimraf lib", "prepublish": "npm run clean && npm run build && git rev-parse HEAD > git-revision.txt", "test": "karma start --single-run=true --browsers ChromeHeadless", @@ -118,7 +118,7 @@ "babel-preset-react": "^6.24.1", "chokidar": "^1.6.1", "concurrently": "^4.0.1", - "eslint": "^5.8.0", + "eslint": "^5.12.0", "eslint-config-google": "^0.7.1", "eslint-plugin-babel": "^5.2.1", "eslint-plugin-flowtype": "^2.30.0", diff --git a/src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js b/src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js index 6db6fe5c3e..c97ce58c07 100644 --- a/src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js +++ b/src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js @@ -32,7 +32,7 @@ export default class NewRecoveryMethodDialog extends React.PureComponent { dis.dispatch({ action: 'view_user_settings' }); } - onSetupClick = async() => { + onSetupClick = async () => { // TODO: Should change to a restore key backup flow that checks the // recovery passphrase while at the same time also cross-signing the // device as well in a single flow. Since we don't have that yet, we'll diff --git a/src/autocomplete/CommunityProvider.js b/src/autocomplete/CommunityProvider.js index b85c09b320..d164fab46a 100644 --- a/src/autocomplete/CommunityProvider.js +++ b/src/autocomplete/CommunityProvider.js @@ -61,7 +61,7 @@ export default class CommunityProvider extends AutocompleteProvider { if (command) { const joinedGroups = cli.getGroups().filter(({myMembership}) => myMembership === 'join'); - const groups = (await Promise.all(joinedGroups.map(async({groupId}) => { + const groups = (await Promise.all(joinedGroups.map(async ({groupId}) => { try { return FlairStore.getGroupProfileCached(cli, groupId); } catch (e) { // if FlairStore failed, fall back to just groupId diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index 937e07d31e..834fcd2340 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -781,7 +781,7 @@ export default React.createClass({ ), button: _t("Leave"), danger: this.state.isUserPrivileged, - onFinished: async(confirmed) => { + onFinished: async (confirmed) => { if (!confirmed) return; this.setState({membershipBusy: true}); diff --git a/src/components/views/context_menus/GroupInviteTileContextMenu.js b/src/components/views/context_menus/GroupInviteTileContextMenu.js index 2dd611843a..e30acca16d 100644 --- a/src/components/views/context_menus/GroupInviteTileContextMenu.js +++ b/src/components/views/context_menus/GroupInviteTileContextMenu.js @@ -48,7 +48,7 @@ export default class GroupInviteTileContextMenu extends React.Component { Modal.createTrackedDialog('Reject community invite', '', QuestionDialog, { title: _t('Reject invitation'), description: _t('Are you sure you want to reject the invitation?'), - onFinished: async(shouldLeave) => { + onFinished: async (shouldLeave) => { if (!shouldLeave) return; // FIXME: controller shouldn't be loading a view :( diff --git a/src/components/views/context_menus/StatusMessageContextMenu.js b/src/components/views/context_menus/StatusMessageContextMenu.js index f07220db44..d062fc2a3e 100644 --- a/src/components/views/context_menus/StatusMessageContextMenu.js +++ b/src/components/views/context_menus/StatusMessageContextMenu.js @@ -35,7 +35,7 @@ export default class StatusMessageContextMenu extends React.Component { }; } - _onClearClick = async(e) => { + _onClearClick = async (e) => { await MatrixClientPeg.get()._unstable_setStatusMessage(""); this.setState({message: ""}); }; From f269b5fbbcb08cde6232d89821ef2767fcdf7aa4 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 9 Jan 2019 19:14:30 +0000 Subject: [PATCH 50/51] Fix spaceless async in ignored files too --- .eslintignore.errorfiles | 1 - package.json | 2 +- src/components/structures/UserSettings.js | 2 +- src/components/views/rooms/MemberInfo.js | 2 +- src/components/views/rooms/MessageComposerInput.js | 2 +- 5 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.eslintignore.errorfiles b/.eslintignore.errorfiles index 0b4266c0b5..1a12432c87 100644 --- a/.eslintignore.errorfiles +++ b/.eslintignore.errorfiles @@ -18,7 +18,6 @@ src/components/structures/ScrollPanel.js src/components/structures/SearchBox.js src/components/structures/TimelinePanel.js src/components/structures/UploadBar.js -src/components/structures/UserSettings.js src/components/views/avatars/BaseAvatar.js src/components/views/avatars/MemberAvatar.js src/components/views/create_room/RoomAlias.js diff --git a/package.json b/package.json index 1b628067fc..8804c0911b 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "start:init": "babel src -d lib --source-maps --copy-files", "lint": "eslint src/", "lintall": "eslint src/ test/", - "lintwithexclusions": "eslint --max-warnings 14 --ignore-path .eslintignore.errorfiles src test", + "lintwithexclusions": "eslint --max-warnings 19 --ignore-path .eslintignore.errorfiles src test", "clean": "rimraf lib", "prepublish": "npm run clean && npm run build && git rev-parse HEAD > git-revision.txt", "test": "karma start --single-run=true --browsers ChromeHeadless", diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index bb31510cf6..02b94b408d 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -835,7 +835,7 @@ module.exports = React.createClass({ SettingsStore.getLabsFeatures().forEach((featureId) => { // TODO: this ought to be a separate component so that we don't need // to rebind the onChange each time we render - const onChange = async(e) => { + const onChange = async (e) => { const checked = e.target.checked; if (featureId === "feature_lazyloading") { const confirmed = await this._onLazyLoadChanging(checked); diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index 226adb910f..2b50ff5e48 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -714,7 +714,7 @@ module.exports = withMatrixClient(React.createClass({ if (!member || !member.membership || member.membership === 'leave') { const roomId = member && member.roomId ? member.roomId : this.props.roomId; - const onInviteUserButton = async() => { + const onInviteUserButton = async () => { try { // We use a MultiInviter to re-use the invite logic, even though // we're only inviting one user. diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index 4e7b4d3bbf..f39d7aad6e 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -1274,7 +1274,7 @@ export default class MessageComposerInput extends React.Component { } }; - selectHistory = async(up) => { + selectHistory = async (up) => { const delta = up ? -1 : 1; // True if we are not currently selecting history, but composing a message From b9719abbc2da9813bd3ece940a4d1ddd7beaf4a9 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 9 Jan 2019 19:36:34 +0000 Subject: [PATCH 51/51] Oops, missed some asyncs --- src/components/views/rooms/MessageComposerInput.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index f39d7aad6e..80f90d37b4 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -1322,7 +1322,7 @@ export default class MessageComposerInput extends React.Component { return true; }; - onTab = async(e) => { + onTab = async (e) => { this.setState({ someCompletions: null, }); @@ -1344,7 +1344,7 @@ export default class MessageComposerInput extends React.Component { up ? this.autocomplete.onUpArrow() : this.autocomplete.onDownArrow(); }; - onEscape = async(e) => { + onEscape = async (e) => { e.preventDefault(); if (this.autocomplete) { this.autocomplete.onEscape(e); @@ -1363,7 +1363,7 @@ export default class MessageComposerInput extends React.Component { /* If passed null, restores the original editor content from state.originalEditorState. * If passed a non-null displayedCompletion, modifies state.originalEditorState to compute new state.editorState. */ - setDisplayedCompletion = async(displayedCompletion: ?Completion): boolean => { + setDisplayedCompletion = async (displayedCompletion: ?Completion): boolean => { const activeEditorState = this.state.originalEditorState || this.state.editorState; if (displayedCompletion == null) {