From 18faa2d8ba27b5bb3c5d9b50a44a7248de304f17 Mon Sep 17 00:00:00 2001
From: Travis Ralston <travpc@gmail.com>
Date: Sun, 15 Oct 2017 21:18:39 -0600
Subject: [PATCH 01/21] CSS for new pinned events indicator

Signed-off-by: Travis Ralston <travpc@gmail.com>
---
 src/skins/vector/css/_components.scss              |  4 ++--
 .../matrix-react-sdk/views/rooms/_RoomHeader.scss  | 14 ++++++++++++++
 2 files changed, 16 insertions(+), 2 deletions(-)

diff --git a/src/skins/vector/css/_components.scss b/src/skins/vector/css/_components.scss
index 82c2c186..f8c69c74 100644
--- a/src/skins/vector/css/_components.scss
+++ b/src/skins/vector/css/_components.scss
@@ -55,6 +55,8 @@
 @import "./matrix-react-sdk/views/rooms/_MemberInfo.scss";
 @import "./matrix-react-sdk/views/rooms/_MemberList.scss";
 @import "./matrix-react-sdk/views/rooms/_MessageComposer.scss";
+@import "./matrix-react-sdk/views/rooms/_PinnedEventTile.scss";
+@import "./matrix-react-sdk/views/rooms/_PinnedEventsPanel.scss";
 @import "./matrix-react-sdk/views/rooms/_PresenceLabel.scss";
 @import "./matrix-react-sdk/views/rooms/_RoomHeader.scss";
 @import "./matrix-react-sdk/views/rooms/_RoomList.scss";
@@ -68,8 +70,6 @@
 @import "./matrix-react-sdk/views/voip/_CallView.scss";
 @import "./matrix-react-sdk/views/voip/_IncomingCallbox.scss";
 @import "./matrix-react-sdk/views/voip/_VideoView.scss";
-@import "./matrix-react-sdk/views/rooms/_PinnedEventsPanel.scss";
-@import "./matrix-react-sdk/views/rooms/_PinnedEventTile.scss";
 @import "./vector-web/_fonts.scss";
 @import "./vector-web/structures/_CompatibilityPage.scss";
 @import "./vector-web/structures/_HomePage.scss";
diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/_RoomHeader.scss b/src/skins/vector/css/matrix-react-sdk/views/rooms/_RoomHeader.scss
index 05cdfba8..3adf5a41 100644
--- a/src/skins/vector/css/matrix-react-sdk/views/rooms/_RoomHeader.scss
+++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/_RoomHeader.scss
@@ -224,3 +224,17 @@ limitations under the License.
 .mx_RoomHeader_voipButtons {
     margin-top: 18px;
 }
+
+.mx_RoomHeader_pinnedButton {
+    position: relative;
+}
+
+.mx_RoomHeader_unreadPinsIndicator {
+    position: absolute;
+    right: 0;
+    bottom: 4px;
+    width: 8px;
+    height: 8px;
+    border-radius: 8px;
+    background-color: $warning-color;
+}
\ No newline at end of file

From f3b1c6cc5bee759175c30f0c165de0bc5607ebd3 Mon Sep 17 00:00:00 2001
From: Travis Ralston <travpc@gmail.com>
Date: Wed, 18 Oct 2017 08:50:34 -0600
Subject: [PATCH 02/21] Add newlines to end of file

Signed-off-by: Travis Ralston <travpc@gmail.com>
---
 .../vector/css/matrix-react-sdk/views/rooms/_RoomHeader.scss    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/_RoomHeader.scss b/src/skins/vector/css/matrix-react-sdk/views/rooms/_RoomHeader.scss
index 3adf5a41..ce9f39ef 100644
--- a/src/skins/vector/css/matrix-react-sdk/views/rooms/_RoomHeader.scss
+++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/_RoomHeader.scss
@@ -237,4 +237,4 @@ limitations under the License.
     height: 8px;
     border-radius: 8px;
     background-color: $warning-color;
-}
\ No newline at end of file
+}

From 19320c37dfc8bacb4b5f072ba256c9e881b10b4c Mon Sep 17 00:00:00 2001
From: Richard Lewis <richard@smetco.co.uk>
Date: Fri, 27 Oct 2017 19:57:05 +0100
Subject: [PATCH 03/21] Add back bottom border to widget title bar

---
 package-lock.json                             | 98 +++++++++----------
 .../views/rooms/_AppsDrawer.scss              |  2 +-
 2 files changed, 47 insertions(+), 53 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 64e2e49a..67707dec 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "riot-web",
-  "version": "0.12.2",
+  "version": "0.12.7",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -260,6 +260,16 @@
       "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=",
       "dev": true
     },
+    "array-includes": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz",
+      "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=",
+      "dev": true,
+      "requires": {
+        "define-properties": "1.1.2",
+        "es-abstract": "1.9.0"
+      }
+    },
     "array-map": {
       "version": "0.0.0",
       "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz",
@@ -299,16 +309,6 @@
       "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=",
       "dev": true
     },
-    "array.prototype.find": {
-      "version": "2.0.4",
-      "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.0.4.tgz",
-      "integrity": "sha1-VWpcU2LAhkgyPdrrnenRS8GGTJA=",
-      "dev": true,
-      "requires": {
-        "define-properties": "1.1.2",
-        "es-abstract": "1.9.0"
-      }
-    },
     "arraybuffer.slice": {
       "version": "0.0.6",
       "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz",
@@ -3202,16 +3202,36 @@
       }
     },
     "eslint-plugin-react": {
-      "version": "6.10.3",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-6.10.3.tgz",
-      "integrity": "sha1-xUNb6wZ3ThLH2y9qut3L+QDNP3g=",
+      "version": "7.4.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.4.0.tgz",
+      "integrity": "sha512-tvjU9u3VqmW2vVuYnE8Qptq+6ji4JltjOjJ9u7VAOxVYkUkyBZWRvNYKbDv5fN+L6wiA+4we9+qQahZ0m63XEA==",
       "dev": true,
       "requires": {
-        "array.prototype.find": "2.0.4",
-        "doctrine": "1.2.3",
+        "doctrine": "2.0.0",
         "has": "1.0.1",
-        "jsx-ast-utils": "1.4.1",
-        "object.assign": "4.0.4"
+        "jsx-ast-utils": "2.0.1",
+        "prop-types": "15.6.0"
+      },
+      "dependencies": {
+        "doctrine": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz",
+          "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=",
+          "dev": true,
+          "requires": {
+            "esutils": "2.0.2",
+            "isarray": "1.0.0"
+          }
+        },
+        "jsx-ast-utils": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz",
+          "integrity": "sha1-6AGxs5mF4g//yHtA43SAgOLcrH8=",
+          "dev": true,
+          "requires": {
+            "array-includes": "3.0.3"
+          }
+        }
       }
     },
     "espree": {
@@ -4657,8 +4677,7 @@
         "tweetnacl": {
           "version": "0.14.5",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "uid-number": {
           "version": "0.0.6",
@@ -5696,12 +5715,6 @@
         "verror": "1.10.0"
       }
     },
-    "jsx-ast-utils": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz",
-      "integrity": "sha1-OGchPo3Xm/Ho8jAMDPwe+xgsDfE=",
-      "dev": true
-    },
     "karma": {
       "version": "1.7.1",
       "resolved": "https://registry.npmjs.org/karma/-/karma-1.7.1.tgz",
@@ -6108,9 +6121,9 @@
       "dev": true
     },
     "matrix-js-sdk": {
-      "version": "0.8.2",
-      "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-0.8.2.tgz",
-      "integrity": "sha1-e7mrVoXrNCFLOFlMiDn++pY2ViE=",
+      "version": "0.8.5",
+      "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-0.8.5.tgz",
+      "integrity": "sha1-1ZAVTx53ADVyZw+p28rH5APnbk8=",
       "requires": {
         "another-json": "0.2.0",
         "bluebird": "3.5.1",
@@ -6130,9 +6143,9 @@
       }
     },
     "matrix-react-sdk": {
-      "version": "0.10.2",
-      "resolved": "https://registry.npmjs.org/matrix-react-sdk/-/matrix-react-sdk-0.10.2.tgz",
-      "integrity": "sha1-TNSwkN1P4Jsl4Yh5Z/XOE17U8q4=",
+      "version": "0.10.7",
+      "resolved": "https://registry.npmjs.org/matrix-react-sdk/-/matrix-react-sdk-0.10.7.tgz",
+      "integrity": "sha1-pi7GrlMwbNRJi0eYYAU04ORBMcA=",
       "requires": {
         "babel-runtime": "6.26.0",
         "bluebird": "3.5.1",
@@ -6155,7 +6168,7 @@
         "isomorphic-fetch": "2.2.1",
         "linkifyjs": "2.1.5",
         "lodash": "4.17.4",
-        "matrix-js-sdk": "0.8.2",
+        "matrix-js-sdk": "0.8.5",
         "optimist": "0.6.1",
         "prop-types": "15.6.0",
         "react": "15.6.2",
@@ -6611,25 +6624,6 @@
       "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=",
       "dev": true
     },
-    "object.assign": {
-      "version": "4.0.4",
-      "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.0.4.tgz",
-      "integrity": "sha1-scnMBE7xuf5jYG/BQau7MuFHMMw=",
-      "dev": true,
-      "requires": {
-        "define-properties": "1.1.2",
-        "function-bind": "1.1.1",
-        "object-keys": "1.0.11"
-      },
-      "dependencies": {
-        "object-keys": {
-          "version": "1.0.11",
-          "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz",
-          "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=",
-          "dev": true
-        }
-      }
-    },
     "object.entries": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.0.4.tgz",
diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/_AppsDrawer.scss b/src/skins/vector/css/matrix-react-sdk/views/rooms/_AppsDrawer.scss
index 14b8cb45..f9c09287 100644
--- a/src/skins/vector/css/matrix-react-sdk/views/rooms/_AppsDrawer.scss
+++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/_AppsDrawer.scss
@@ -74,7 +74,7 @@ limitations under the License.
     margin: 0;
     padding: 2px 10px;
     // background-color: $e2e-verified-color;
-    // border-bottom: 1px solid $primary-hairline-color;
+    border-bottom: 1px solid $primary-hairline-color;
     font-size: 10px;
 }
 

From ea9b6300828af0d58c46e4c5202e0e52a9a5e4e4 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 30 Oct 2017 12:44:50 +0000
Subject: [PATCH 04/21] Refactor and add Account Data stuffs

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
 .../views/dialogs/DevtoolsDialog.js           | 463 +++++++++++++-----
 src/i18n/strings/en_EN.json                   |   4 +
 .../views/dialogs/_DevtoolsDialog.scss        | 103 ++++
 3 files changed, 457 insertions(+), 113 deletions(-)

diff --git a/src/components/views/dialogs/DevtoolsDialog.js b/src/components/views/dialogs/DevtoolsDialog.js
index 7760ea84..2c854f99 100644
--- a/src/components/views/dialogs/DevtoolsDialog.js
+++ b/src/components/views/dialogs/DevtoolsDialog.js
@@ -15,35 +15,24 @@ limitations under the License.
 */
 
 import React from 'react';
+import PropTypes from 'prop-types';
 import sdk from 'matrix-react-sdk';
 import { _t } from 'matrix-react-sdk/lib/languageHandler';
 import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
 
-class SendCustomEvent extends React.Component {
-    static propTypes = {
-        roomId: React.PropTypes.string.isRequired,
-        onBack: React.PropTypes.func.isRequired,
-
-        eventType: React.PropTypes.string.isRequired,
-        evContent: React.PropTypes.string.isRequired,
+class DevtoolsComponent extends React.Component {
+    static contextTypes = {
+        roomId: PropTypes.string.isRequired,
     };
+}
 
-    static defaultProps = {
-        eventType: '',
-        evContent: '{\n\n}',
-    };
+class GenericEditor extends DevtoolsComponent {
+    // static propTypes = {onBack: PropTypes.func.isRequired};
 
     constructor(props, context) {
         super(props, context);
-        this._send = this._send.bind(this);
-        this.onBack = this.onBack.bind(this);
         this._onChange = this._onChange.bind(this);
-
-        this.state = {
-            message: null,
-            input_eventType: this.props.eventType,
-            input_evContent: this.props.evContent,
-        };
+        this.onBack = this.onBack.bind(this);
     }
 
     onBack() {
@@ -54,6 +43,10 @@ class SendCustomEvent extends React.Component {
         }
     }
 
+    _onChange(e) {
+        this.setState({[e.target.id]: e.target.type === 'checkbox' ? e.target.checked : e.target.value});
+    }
+
     _buttons() {
         return <div className="mx_Dialog_buttons">
             <button onClick={this.onBack}>{ _t('Back') }</button>
@@ -61,19 +54,64 @@ class SendCustomEvent extends React.Component {
         </div>;
     }
 
+    textInput(id, label) {
+        return <div className="mx_UserSettings_profileTableRow">
+            <div className="mx_UserSettings_profileLabelCell">
+                <label htmlFor="displayName">{ label }</label>
+            </div>
+            <div className="mx_UserSettings_profileInputCell">
+                <input id={id} onChange={this._onChange} value={this.state[id]} className="mx_EditableText" size="32" />
+            </div>
+        </div>;
+    }
+}
+
+class SendCustomEvent extends GenericEditor {
+    static getLabel() { return _t('Send Custom Event'); }
+
+    static propTypes = {
+        onBack: PropTypes.func.isRequired,
+        forceStateEvent: PropTypes.bool,
+        inputs: PropTypes.object,
+    };
+
+    constructor(props, context) {
+        super(props, context);
+        this._send = this._send.bind(this);
+
+        const {eventType, stateKey, evContent} = Object.assign({
+            eventType: '',
+            stateKey: '',
+            evContent: '{\n\n}',
+        }, this.props.inputs);
+
+        this.state = {
+            isStateEvent: Boolean(this.props.forceStateEvent),
+
+            eventType,
+            stateKey,
+            evContent,
+        };
+    }
+
     send(content) {
-        return MatrixClientPeg.get().sendEvent(this.props.roomId, this.state.input_eventType, content);
+        const cli = MatrixClientPeg.get();
+        if (this.state.isStateEvent) {
+            return cli.sendStateEvent(this.context.roomId, this.state.eventType, content, this.state.stateKey);
+        } else {
+            return cli.sendEvent(this.context.roomId, this.state.eventType, content);
+        }
     }
 
     async _send() {
-        if (this.state.input_eventType === '') {
+        if (this.state.eventType === '') {
             this.setState({ message: _t('You must specify an event type!') });
             return;
         }
 
         let message;
         try {
-            const content = JSON.parse(this.state.input_evContent);
+            const content = JSON.parse(this.state.evContent);
             await this.send(content);
             message = _t('Event sent!');
         } catch (e) {
@@ -82,14 +120,6 @@ class SendCustomEvent extends React.Component {
         this.setState({ message });
     }
 
-    _additionalFields() {
-        return <div />;
-    }
-
-    _onChange(e) {
-        this.setState({[`input_${e.target.id}`]: e.target.value});
-    }
-
     render() {
         if (this.state.message) {
             return <div>
@@ -102,87 +132,176 @@ class SendCustomEvent extends React.Component {
 
         return <div>
             <div className="mx_Dialog_content">
-                { this._additionalFields() }
-                <div className="mx_TextInputDialog_label">
-                    <label htmlFor="eventType"> { _t('Event Type') } </label>
-                </div>
-                <div>
-                    <input id="eventType" onChange={this._onChange} value={this.state.input_eventType} className="mx_TextInputDialog_input" size="64" />
-                </div>
+                { this.textInput('eventType', _t('Event Type')) }
+                { this.state.isStateEvent && this.textInput('stateKey', _t('State Key')) }
 
-                <div className="mx_TextInputDialog_label">
+                <br />
+
+                <div className="mx_UserSettings_profileLabelCell">
                     <label htmlFor="evContent"> { _t('Event Content') } </label>
                 </div>
                 <div>
-                    <textarea id="evContent" onChange={this._onChange} value={this.state.input_evContent} className="mx_TextInputDialog_input" cols="63" rows="5" />
+                    <textarea id="evContent" onChange={this._onChange} value={this.state.evContent} className="mx_TextInputDialog_input" cols="63" rows="5" />
                 </div>
             </div>
-            { this._buttons() }
+            <div className="mx_Dialog_buttons">
+                <button onClick={this.onBack}>{ _t('Back') }</button>
+                { !this.state.message && <button onClick={this._send}>{ _t('Send') }</button> }
+                { !this.state.message && !this.props.forceStateEvent && <div style={{float: "right"}}>
+                    <input id="isStateEvent" className="tgl tgl-flip" type="checkbox" onChange={this._onChange} checked={this.state.isStateEvent} />
+                    <label className="tgl-btn" data-tg-off="Event" data-tg-on="State Event" htmlFor="isStateEvent" />
+                </div> }
+            </div>
         </div>;
     }
 }
 
-class SendCustomStateEvent extends SendCustomEvent {
+class SendAccountData extends GenericEditor {
+    static getLabel() { return _t('Send Account Data'); }
+
     static propTypes = {
-        roomId: React.PropTypes.string.isRequired,
-        onBack: React.PropTypes.func.isRequired,
-
-        eventType: React.PropTypes.string.isRequired,
-        evContent: React.PropTypes.string.isRequired,
-        stateKey: React.PropTypes.string.isRequired,
-    };
-
-    static defaultProps = {
-        eventType: '',
-        evContent: '{\n\n}',
-        stateKey: '',
+        isRoomAccountData: PropTypes.bool,
+        forceMode: PropTypes.bool,
+        inputs: PropTypes.object,
     };
 
     constructor(props, context) {
         super(props, context);
-        this.state['input_stateKey'] = this.props.stateKey;
+        this._send = this._send.bind(this);
+
+        const {eventType, evContent} = Object.assign({
+            eventType: '',
+            evContent: '{\n\n}',
+        }, this.props.inputs);
+
+        this.state = {
+            isRoomAccountData: Boolean(this.props.isRoomAccountData),
+
+            eventType,
+            evContent,
+        };
     }
 
     send(content) {
         const cli = MatrixClientPeg.get();
-        return cli.sendStateEvent(this.props.roomId, this.state.input_eventType, content, this.state.input_stateKey);
+        if (this.state.isRoomAccountData) {
+            return cli.setRoomAccountData(this.context.roomId, this.state.eventType, content);
+        }
+        return cli.setAccountData(this.state.eventType, content);
     }
 
-    _additionalFields() {
+    async _send() {
+        if (this.state.eventType === '') {
+            this.setState({ message: _t('You must specify an event type!') });
+            return;
+        }
+
+        let message;
+        try {
+            const content = JSON.parse(this.state.evContent);
+            await this.send(content);
+            message = _t('Event sent!');
+        } catch (e) {
+            message = _t('Failed to send custom event.') + ' (' + e.toString() + ')';
+        }
+        this.setState({ message });
+    }
+
+    render() {
+        if (this.state.message) {
+            return <div>
+                <div className="mx_Dialog_content">
+                    { this.state.message }
+                </div>
+                { this._buttons() }
+            </div>;
+        }
+
         return <div>
-            <div className="mx_TextInputDialog_label">
-                <label htmlFor="stateKey"> { _t('State Key') } </label>
+            <div className="mx_Dialog_content">
+                { this.textInput('eventType', _t('Event Type')) }
+                <br />
+
+                <div className="mx_UserSettings_profileLabelCell">
+                    <label htmlFor="evContent"> { _t('Event Content') } </label>
+                </div>
+                <div>
+                    <textarea id="evContent" onChange={this._onChange} value={this.state.evContent} className="mx_TextInputDialog_input" cols="63" rows="5" />
+                </div>
             </div>
-            <div>
-                <input id="stateKey" onChange={this._onChange} value={this.state.input_stateKey} className="mx_TextInputDialog_input" size="64" />
+            <div className="mx_Dialog_buttons">
+                <button onClick={this.onBack}>{ _t('Back') }</button>
+                { !this.state.message && <button onClick={this._send}>{ _t('Send') }</button> }
+                { !this.state.message && <div style={{float: "right"}}>
+                    <input id="isRoomAccountData" className="tgl tgl-flip" type="checkbox" onChange={this._onChange} checked={this.state.isRoomAccountData} disabled={this.props.forceMode} />
+                    <label className="tgl-btn" data-tg-off="Account Data" data-tg-on="Room Data" htmlFor="isRoomAccountData" />
+                </div> }
             </div>
         </div>;
     }
 }
 
-class RoomStateExplorer extends React.Component {
+class FilteredList extends React.Component {
     static propTypes = {
-        setMode: React.PropTypes.func.isRequired,
-        roomId: React.PropTypes.string.isRequired,
-        onBack: React.PropTypes.func.isRequired,
+        children: PropTypes.any,
+    };
+
+    constructor(props, context) {
+        super(props, context);
+        this.onQuery = this.onQuery.bind(this);
+
+        this.state = {
+            query: '',
+        };
+    }
+
+    onQuery(ev) {
+        this.setState({ query: ev.target.value });
+    }
+
+    filterChildren() {
+        if (this.state.query) {
+            const lowerQuery = this.state.query.toLowerCase();
+            return this.props.children.filter((child) => child.key.toLowerCase().includes(lowerQuery));
+        }
+        return this.props.children;
+    }
+
+    render() {
+        return <div>
+            <input size="64"
+                   onChange={this.onQuery}
+                   value={this.state.query}
+                   placeholder={_t('Filter results')}
+                   className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query" />
+            { this.filterChildren() }
+        </div>;
+    }
+}
+
+class RoomStateExplorer extends DevtoolsComponent {
+    static getLabel() { return _t('Explore Room State'); }
+
+
+    static propTypes = {
+        onBack: PropTypes.func.isRequired,
     };
 
     constructor(props, context) {
         super(props, context);
 
-        const room = MatrixClientPeg.get().getRoom(this.props.roomId);
+        const room = MatrixClientPeg.get().getRoom(this.context.roomId);
         this.roomStateEvents = room.currentState.events;
 
         this.onBack = this.onBack.bind(this);
         this.editEv = this.editEv.bind(this);
-        this.onQuery = this.onQuery.bind(this);
-    }
 
-    state = {
-        query: '',
-        eventType: null,
-        event: null,
-    };
+        this.state = {
+            eventType: null,
+            event: null,
+            editing: false,
+        };
+    }
 
     browseEventType(eventType) {
         return () => {
@@ -197,7 +316,9 @@ class RoomStateExplorer extends React.Component {
     }
 
     onBack() {
-        if (this.state.event) {
+        if (this.state.editing) {
+            this.setState({ editing: false });
+        } else if (this.state.event) {
             this.setState({ event: null });
         } else if (this.state.eventType) {
             this.setState({ eventType: null });
@@ -207,20 +328,19 @@ class RoomStateExplorer extends React.Component {
     }
 
     editEv() {
-        const ev = this.state.event;
-        this.props.setMode(SendCustomStateEvent, {
-            eventType: ev.getType(),
-            evContent: JSON.stringify(ev.getContent(), null, '\t'),
-            stateKey: ev.getStateKey(),
-        });
-    }
-
-    onQuery(ev) {
-        this.setState({ query: ev.target.value });
+        this.setState({ editing: true });
     }
 
     render() {
         if (this.state.event) {
+            if (this.state.editing) {
+                return <SendCustomEvent forceStateEvent={true} onBack={this.onBack} inputs={{
+                    eventType: this.state.event.getType(),
+                    evContent: JSON.stringify(this.state.event.getContent(), null, '\t'),
+                    stateKey: this.state.event.getStateKey(),
+                }} />;
+            }
+
             return <div className="mx_ViewSource">
                 <div className="mx_Dialog_content">
                     <pre>{ JSON.stringify(this.state.event.event, null, 2) }</pre>
@@ -234,11 +354,9 @@ class RoomStateExplorer extends React.Component {
 
         const rows = [];
 
+        const classes = 'mx_DevTools_RoomStateExplorer_button';
         if (this.state.eventType === null) {
             Object.keys(this.roomStateEvents).forEach((evType) => {
-                // Skip this entry if does not contain search query
-                if (this.state.query && !evType.toLowerCase().includes(this.state.query.toLowerCase())) return;
-
                 const stateGroup = this.roomStateEvents[evType];
                 const stateKeys = Object.keys(stateGroup);
 
@@ -249,7 +367,7 @@ class RoomStateExplorer extends React.Component {
                     onClickFn = this.onViewSourceClick(stateGroup[stateKeys[0]]);
                 }
 
-                rows.push(<button className="mx_DevTools_RoomStateExplorer_button" key={evType} onClick={onClickFn}>
+                rows.push(<button className={classes} key={evType} onClick={onClickFn}>
                     { evType }
                 </button>);
             });
@@ -257,12 +375,8 @@ class RoomStateExplorer extends React.Component {
             const evType = this.state.eventType;
             const stateGroup = this.roomStateEvents[evType];
             Object.keys(stateGroup).forEach((stateKey) => {
-                // Skip this entry if does not contain search query
-                if (this.state.query && !stateKey.toLowerCase().includes(this.state.query.toLowerCase())) return;
-
                 const ev = stateGroup[stateKey];
-                rows.push(<button className="mx_DevTools_RoomStateExplorer_button" key={stateKey}
-                                  onClick={this.onViewSourceClick(ev)}>
+                rows.push(<button className={classes} key={stateKey} onClick={this.onViewSourceClick(ev)}>
                     { stateKey }
                 </button>);
             });
@@ -270,8 +384,9 @@ class RoomStateExplorer extends React.Component {
 
         return <div>
             <div className="mx_Dialog_content">
-                <input onChange={this.onQuery} placeholder={_t('Filter results')} size="64" className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query" value={this.state.query} />
-                { rows }
+                <FilteredList>
+                    { rows }
+                </FilteredList>
             </div>
             <div className="mx_Dialog_buttons">
                 <button onClick={this.onBack}>{ _t('Back') }</button>
@@ -280,40 +395,157 @@ class RoomStateExplorer extends React.Component {
     }
 }
 
-export default class DevtoolsDialog extends React.Component {
+class AccountDataExplorer extends DevtoolsComponent {
+    static getLabel() { return _t('Explore Account Data'); }
+
     static propTypes = {
-        roomId: React.PropTypes.string.isRequired,
-        onFinished: React.PropTypes.func.isRequired,
+        onBack: PropTypes.func.isRequired,
     };
 
-    state = {
-        mode: null,
-        modeArgs: {},
+    constructor(props, context) {
+        super(props, context);
+
+        this.onBack = this.onBack.bind(this);
+        this.editEv = this.editEv.bind(this);
+        this._onChange = this._onChange.bind(this);
+
+        this.state = {
+            isRoomAccountData: false,
+            event: null,
+            editing: false,
+        };
+    }
+
+    getData() {
+        const cli = MatrixClientPeg.get();
+        if (this.state.isRoomAccountData) {
+            return cli.getRoom(this.context.roomId).accountData;
+        }
+        return cli.store.accountData;
+    }
+
+    onViewSourceClick(event) {
+        return () => {
+            this.setState({ event });
+        };
+    }
+
+    onBack() {
+        if (this.state.editing) {
+            this.setState({ editing: false });
+        } else if (this.state.event) {
+            this.setState({ event: null });
+        } else {
+            this.props.onBack();
+        }
+    }
+
+    _onChange(e) {
+        this.setState({[e.target.id]: e.target.type === 'checkbox' ? e.target.checked : e.target.value});
+    }
+
+    editEv() {
+        this.setState({ editing: true });
+    }
+
+    render() {
+        if (this.state.event) {
+            if (this.state.editing) {
+                return <SendAccountData isRoomAccountData={this.state.isRoomAccountData} onBack={this.onBack} inputs={{
+                    eventType: this.state.event.getType(),
+                    evContent: JSON.stringify(this.state.event.getContent(), null, '\t'),
+                }} forceMode={true} />;
+            }
+
+            return <div className="mx_ViewSource">
+                <div className="mx_Dialog_content">
+                    <pre>{ JSON.stringify(this.state.event.event, null, 2) }</pre>
+                </div>
+                <div className="mx_Dialog_buttons">
+                    <button onClick={this.onBack}>{ _t('Back') }</button>
+                    <button onClick={this.editEv}>{ _t('Edit') }</button>
+                </div>
+            </div>;
+        }
+
+        const rows = [];
+
+        const classes = 'mx_DevTools_RoomStateExplorer_button';
+
+        const data = this.getData();
+        Object.keys(data).forEach((evType) => {
+            const ev = data[evType];
+            rows.push(<button className={classes} key={evType} onClick={this.onViewSourceClick(ev)}>
+                { evType }
+            </button>);
+        });
+
+        return <div>
+            <div className="mx_Dialog_content">
+                <FilteredList>
+                    { rows }
+                </FilteredList>
+            </div>
+            <div className="mx_Dialog_buttons">
+                <button onClick={this.onBack}>{ _t('Back') }</button>
+                { !this.state.message && <div style={{float: "right"}}>
+                    <input id="isRoomAccountData" className="tgl tgl-flip" type="checkbox" onChange={this._onChange} checked={this.state.isRoomAccountData} />
+                    <label className="tgl-btn" data-tg-off="Account Data" data-tg-on="Room Data" htmlFor="isRoomAccountData" />
+                </div> }
+            </div>
+        </div>;
+    }
+}
+
+const Entries = [
+    SendCustomEvent,
+    RoomStateExplorer,
+    SendAccountData,
+    AccountDataExplorer,
+];
+
+export default class DevtoolsDialog extends React.Component {
+    static childContextTypes = {
+        roomId: PropTypes.string.isRequired,
+        // client: PropTypes.instanceOf(MatixClient),
+    };
+
+    static propTypes = {
+        roomId: PropTypes.string.isRequired,
+        onFinished: PropTypes.func.isRequired,
     };
 
     constructor(props, context) {
         super(props, context);
         this.onBack = this.onBack.bind(this);
-        this.setMode = this.setMode.bind(this);
         this.onCancel = this.onCancel.bind(this);
+
+        this.state = {
+            mode: null,
+        };
     }
 
     componentWillUnmount() {
         this._unmounted = true;
     }
 
+    getChildContext() {
+        return { roomId: this.props.roomId };
+    }
+
     _setMode(mode) {
         return () => {
-            this.setMode(mode);
+            this.setState({ mode });
         };
     }
 
-    setMode(mode, modeArgs={}) {
-        this.setState({ mode, modeArgs });
-    }
-
     onBack() {
-        this.setState({ mode: null });
+        if (this.prevMode) {
+            this.setState({ mode: this.prevMode });
+            this.prevMode = null;
+        } else {
+            this.setState({ mode: null });
+        }
     }
 
     onCancel() {
@@ -324,14 +556,20 @@ export default class DevtoolsDialog extends React.Component {
         let body;
 
         if (this.state.mode) {
-            body =
-                <this.state.mode {...this.props} {...this.state.modeArgs} onBack={this.onBack} setMode={this.setMode} />;
+            body = <div>
+                <div style={{float: 'left'}}>{ this.state.mode.getLabel() }</div>
+                <div style={{float: 'right'}}>Room ID: { this.props.roomId }</div>
+                <div style={{clear: 'both'}} />
+                <this.state.mode onBack={this.onBack} />
+            </div>;
         } else {
             body = <div>
                 <div className="mx_Dialog_content">
-                    <button onClick={this._setMode(SendCustomEvent)}>{ _t('Send Custom Event') }</button>
-                    <button onClick={this._setMode(SendCustomStateEvent)}>{ _t('Send Custom State Event') }</button>
-                    <button onClick={this._setMode(RoomStateExplorer)}>{ _t('Explore Room State') }</button>
+                    <div>Room ID: { this.props.roomId }</div>
+                    { Entries.map((Entry) => {
+                        const label = Entry.getLabel();
+                        return <button key={label} onClick={this._setMode(Entry)}>{ _t(label) }</button>;
+                    }) }
                 </div>
                 <div className="mx_Dialog_buttons">
                     <button onClick={this.onCancel}>{ _t('Cancel') }</button>
@@ -342,7 +580,6 @@ export default class DevtoolsDialog extends React.Component {
         const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
         return (
             <BaseDialog className="mx_QuestionDialog" onFinished={this.props.onFinished} title={_t('Developer Tools')}>
-                <div>Room ID: { this.props.roomId }</div>
                 { body }
             </BaseDialog>
         );
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 543ee7bb..2ed6b204 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -107,7 +107,11 @@
     "Filter results": "Filter results",
     "Send Custom Event": "Send Custom Event",
     "Send Custom State Event": "Send Custom State Event",
+    "Send Account Data": "Send Account Data",
+    "Send Room Account Data": "Send Room Account Data",
+    "Explore Account Data": "Explore Account Data",
     "Explore Room State": "Explore Room State",
+    "Explore Room Account Data": "Explore Room Account Data",
     "Developer Tools": "Developer Tools",
     "You have successfully set a password!": "You have successfully set a password!",
     "You have successfully set a password and an email address!": "You have successfully set a password and an email address!",
diff --git a/src/skins/vector/css/vector-web/views/dialogs/_DevtoolsDialog.scss b/src/skins/vector/css/vector-web/views/dialogs/_DevtoolsDialog.scss
index 975ee8c1..46cdf10a 100644
--- a/src/skins/vector/css/vector-web/views/dialogs/_DevtoolsDialog.scss
+++ b/src/skins/vector/css/vector-web/views/dialogs/_DevtoolsDialog.scss
@@ -17,3 +17,106 @@ limitations under the License.
 .mx_DevTools_RoomStateExplorer_button, .mx_DevTools_RoomStateExplorer_query {
     margin-bottom: 10px;
 }
+
+.tgl {
+    display: none;
+
+    // add default box-sizing for this scope
+    &,
+    &:after,
+    &:before,
+    & *,
+    & *:after,
+    & *:before,
+    & + .tgl-btn {
+        box-sizing: border-box;
+        &::selection {
+            background: none;
+        }
+    }
+
+    + .tgl-btn {
+        outline: 0;
+        display: block;
+        width: 7em;
+        height: 2em;
+        position: relative;
+        cursor: pointer;
+        user-select: none;
+        &:after,
+        &:before {
+            position: relative;
+            display: block;
+            content: "";
+            width: 50%;
+            height: 100%;
+        }
+
+        &:after {
+            left: 0;
+        }
+
+        &:before {
+            display: none;
+        }
+    }
+
+    &:checked + .tgl-btn:after {
+        left: 50%;
+    }
+}
+
+.tgl-flip {
+    + .tgl-btn {
+        padding: 2px;
+        transition: all .2s ease;
+        font-family: sans-serif;
+        perspective: 100px;
+        &:after,
+        &:before {
+            display: inline-block;
+            transition: all .4s ease;
+            width: 100%;
+            text-align: center;
+            position: absolute;
+            line-height: 2em;
+            font-weight: bold;
+            color: #fff;
+            top: 0;
+            left: 0;
+            backface-visibility: hidden;
+            border-radius: 4px;
+        }
+
+        &:after {
+            content: attr(data-tg-on);
+            background: #02C66F;
+            transform: rotateY(-180deg);
+        }
+
+        &:before {
+            background: #FF3A19;
+            content: attr(data-tg-off);
+        }
+
+        &:active:before {
+            transform: rotateY(-20deg);
+        }
+    }
+
+    &:checked + .tgl-btn {
+        &:before {
+            transform: rotateY(180deg);
+        }
+
+        &:after {
+            transform: rotateY(0);
+            left: 0;
+            background: #7FC6A6;
+        }
+
+        &:active:after {
+            transform: rotateY(20deg);
+        }
+    }
+}

From 6da2f88dc5c99caac600434da076936e8cb8ef40 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 30 Oct 2017 13:32:31 +0000
Subject: [PATCH 05/21] Devtools styling tweaks

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
 .../views/dialogs/DevtoolsDialog.js           | 47 ++++++++-------
 src/i18n/strings/en_EN.json                   |  4 +-
 .../views/dialogs/_DevtoolsDialog.scss        | 58 ++++++++++++++++---
 3 files changed, 79 insertions(+), 30 deletions(-)

diff --git a/src/components/views/dialogs/DevtoolsDialog.js b/src/components/views/dialogs/DevtoolsDialog.js
index 2c854f99..a7155ad1 100644
--- a/src/components/views/dialogs/DevtoolsDialog.js
+++ b/src/components/views/dialogs/DevtoolsDialog.js
@@ -55,12 +55,12 @@ class GenericEditor extends DevtoolsComponent {
     }
 
     textInput(id, label) {
-        return <div className="mx_UserSettings_profileTableRow">
-            <div className="mx_UserSettings_profileLabelCell">
-                <label htmlFor="displayName">{ label }</label>
+        return <div className="mx_DevTools_inputRow">
+            <div className="mx_DevTools_inputLabelCell">
+                <label htmlFor={id}>{ label }</label>
             </div>
-            <div className="mx_UserSettings_profileInputCell">
-                <input id={id} onChange={this._onChange} value={this.state[id]} className="mx_EditableText" size="32" />
+            <div className="mx_DevTools_inputCell">
+                <input id={id} onChange={this._onChange} value={this.state[id]} size="32" />
             </div>
         </div>;
     }
@@ -148,8 +148,8 @@ class SendCustomEvent extends GenericEditor {
                 <button onClick={this.onBack}>{ _t('Back') }</button>
                 { !this.state.message && <button onClick={this._send}>{ _t('Send') }</button> }
                 { !this.state.message && !this.props.forceStateEvent && <div style={{float: "right"}}>
-                    <input id="isStateEvent" className="tgl tgl-flip" type="checkbox" onChange={this._onChange} checked={this.state.isStateEvent} />
-                    <label className="tgl-btn" data-tg-off="Event" data-tg-on="State Event" htmlFor="isStateEvent" />
+                    <input id="isStateEvent" className="mx_DevTools_tgl mx_DevTools_tgl-flip" type="checkbox" onChange={this._onChange} checked={this.state.isStateEvent} />
+                    <label className="mx_DevTools_tgl-btn" data-tg-off="Event" data-tg-on="State Event" htmlFor="isStateEvent" />
                 </div> }
             </div>
         </div>;
@@ -233,8 +233,8 @@ class SendAccountData extends GenericEditor {
                 <button onClick={this.onBack}>{ _t('Back') }</button>
                 { !this.state.message && <button onClick={this._send}>{ _t('Send') }</button> }
                 { !this.state.message && <div style={{float: "right"}}>
-                    <input id="isRoomAccountData" className="tgl tgl-flip" type="checkbox" onChange={this._onChange} checked={this.state.isRoomAccountData} disabled={this.props.forceMode} />
-                    <label className="tgl-btn" data-tg-off="Account Data" data-tg-on="Room Data" htmlFor="isRoomAccountData" />
+                    <input id="isRoomAccountData" className="mx_DevTools_tgl mx_DevTools_tgl-flip" type="checkbox" onChange={this._onChange} checked={this.state.isRoomAccountData} disabled={this.props.forceMode} />
+                    <label className="mx_DevTools_tgl-btn" data-tg-off="Account Data" data-tg-on="Room Data" htmlFor="isRoomAccountData" />
                 </div> }
             </div>
         </div>;
@@ -489,8 +489,8 @@ class AccountDataExplorer extends DevtoolsComponent {
             <div className="mx_Dialog_buttons">
                 <button onClick={this.onBack}>{ _t('Back') }</button>
                 { !this.state.message && <div style={{float: "right"}}>
-                    <input id="isRoomAccountData" className="tgl tgl-flip" type="checkbox" onChange={this._onChange} checked={this.state.isRoomAccountData} />
-                    <label className="tgl-btn" data-tg-off="Account Data" data-tg-on="Room Data" htmlFor="isRoomAccountData" />
+                    <input id="isRoomAccountData" className="mx_DevTools_tgl mx_DevTools_tgl-flip" type="checkbox" onChange={this._onChange} checked={this.state.isRoomAccountData} />
+                    <label className="mx_DevTools_tgl-btn" data-tg-off="Account Data" data-tg-on="Room Data" htmlFor="isRoomAccountData" />
                 </div> }
             </div>
         </div>;
@@ -557,19 +557,26 @@ export default class DevtoolsDialog extends React.Component {
 
         if (this.state.mode) {
             body = <div>
-                <div style={{float: 'left'}}>{ this.state.mode.getLabel() }</div>
-                <div style={{float: 'right'}}>Room ID: { this.props.roomId }</div>
-                <div style={{clear: 'both'}} />
+                <div className="mx_DevTools_label_left">{ this.state.mode.getLabel() }</div>
+                <div className="mx_DevTools_label_right">Room ID: { this.props.roomId }</div>
+                <div className="mx_DevTools_label_bottom" />
                 <this.state.mode onBack={this.onBack} />
             </div>;
         } else {
+            const classes = "mx_DevTools_RoomStateExplorer_button";
             body = <div>
-                <div className="mx_Dialog_content">
-                    <div>Room ID: { this.props.roomId }</div>
-                    { Entries.map((Entry) => {
-                        const label = Entry.getLabel();
-                        return <button key={label} onClick={this._setMode(Entry)}>{ _t(label) }</button>;
-                    }) }
+                <div>
+                    <div className="mx_DevTools_label_left">{ _t('Toolbox') }</div>
+                    <div className="mx_DevTools_label_right">Room ID: { this.props.roomId }</div>
+                    <div className="mx_DevTools_label_bottom" />
+
+                    <div className="mx_Dialog_content">
+                        { Entries.map((Entry) => {
+                            const label = Entry.getLabel();
+                            const onClick = this._setMode(Entry);
+                            return <button className={classes} key={label} onClick={onClick}>{ label }</button>;
+                        }) }
+                    </div>
                 </div>
                 <div className="mx_Dialog_buttons">
                     <button onClick={this.onCancel}>{ _t('Cancel') }</button>
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 2ed6b204..4702ae93 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -70,6 +70,7 @@
     "What's new?": "What's new?",
     "A new version of Riot is available.": "A new version of Riot is available.",
     "To return to your account in future you need to <u>set a password</u>": "To return to your account in future you need to <u>set a password</u>",
+    "Toolbox": "Toolbox",
     "Set Password": "Set Password",
     "Error encountered (%(errorDetail)s).": "Error encountered (%(errorDetail)s).",
     "Checking for an update...": "Checking for an update...",
@@ -106,12 +107,9 @@
     "Edit": "Edit",
     "Filter results": "Filter results",
     "Send Custom Event": "Send Custom Event",
-    "Send Custom State Event": "Send Custom State Event",
     "Send Account Data": "Send Account Data",
-    "Send Room Account Data": "Send Room Account Data",
     "Explore Account Data": "Explore Account Data",
     "Explore Room State": "Explore Room State",
-    "Explore Room Account Data": "Explore Room Account Data",
     "Developer Tools": "Developer Tools",
     "You have successfully set a password!": "You have successfully set a password!",
     "You have successfully set a password and an email address!": "You have successfully set a password and an email address!",
diff --git a/src/skins/vector/css/vector-web/views/dialogs/_DevtoolsDialog.scss b/src/skins/vector/css/vector-web/views/dialogs/_DevtoolsDialog.scss
index 46cdf10a..8918373e 100644
--- a/src/skins/vector/css/vector-web/views/dialogs/_DevtoolsDialog.scss
+++ b/src/skins/vector/css/vector-web/views/dialogs/_DevtoolsDialog.scss
@@ -18,7 +18,51 @@ limitations under the License.
     margin-bottom: 10px;
 }
 
-.tgl {
+.mx_DevTools_label_left {
+    float: left;
+}
+
+.mx_DevTools_label_right {
+    float: right;
+}
+
+.mx_DevTools_label_bottom {
+    clear: both;
+    border-bottom: 1px solid #e5e5e5;
+}
+
+.mx_DevTools_inputRow
+{
+    display: table-row;
+}
+
+.mx_DevTools_inputLabelCell
+{
+    padding-bottom: 21px;
+    display: table-cell;
+    font-weight: bold;
+    padding-right: 24px;
+}
+
+.mx_DevTools_inputCell {
+    display: table-cell;
+    padding-bottom: 21px;
+    width: 240px;
+}
+
+.mx_DevTools_inputCell input
+{
+    display: inline-block;
+    border: 0;
+    border-bottom: 1px solid $input-underline-color;
+    padding: 0;
+    width: 240px;
+    color: $input-fg-color;
+    font-family: 'Open Sans', Helvetica, Arial, Sans-Serif;
+    font-size: 16px;
+}
+
+.mx_DevTools_tgl {
     display: none;
 
     // add default box-sizing for this scope
@@ -28,14 +72,14 @@ limitations under the License.
     & *,
     & *:after,
     & *:before,
-    & + .tgl-btn {
+    & + .mx_DevTools_tgl-btn {
         box-sizing: border-box;
         &::selection {
             background: none;
         }
     }
 
-    + .tgl-btn {
+    + .mx_DevTools_tgl-btn {
         outline: 0;
         display: block;
         width: 7em;
@@ -61,13 +105,13 @@ limitations under the License.
         }
     }
 
-    &:checked + .tgl-btn:after {
+    &:checked + .mx_DevTools_tgl-btn:after {
         left: 50%;
     }
 }
 
-.tgl-flip {
-    + .tgl-btn {
+.mx_DevTools_tgl-flip {
+    + .mx_DevTools_tgl-btn {
         padding: 2px;
         transition: all .2s ease;
         font-family: sans-serif;
@@ -104,7 +148,7 @@ limitations under the License.
         }
     }
 
-    &:checked + .tgl-btn {
+    &:checked + .mx_DevTools_tgl-btn {
         &:before {
             transform: rotateY(180deg);
         }

From 71c8dca91ab4e9b65ed468939583c636e6e528a0 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 30 Oct 2017 15:12:03 +0000
Subject: [PATCH 06/21] Fix instanceof check, was checking against the Package
 rather than class

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
 src/components/structures/RightPanel.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js
index 0225f765..33275d3a 100644
--- a/src/components/structures/RightPanel.js
+++ b/src/components/structures/RightPanel.js
@@ -21,7 +21,7 @@ import PropTypes from 'prop-types';
 import { _t } from 'matrix-react-sdk/lib/languageHandler';
 import sdk from 'matrix-react-sdk';
 import dis from 'matrix-react-sdk/lib/dispatcher';
-import MatrixClient from 'matrix-js-sdk';
+import { MatrixClient } from 'matrix-js-sdk';
 import Analytics from 'matrix-react-sdk/lib/Analytics';
 import rate_limited_func from 'matrix-react-sdk/lib/ratelimitedfunc';
 import AccessibleButton from 'matrix-react-sdk/lib/components/views/elements/AccessibleButton';

From ba1ad84d59f78b7c33d4ee82b2133f22426b07dc Mon Sep 17 00:00:00 2001
From: Luke Barnard <lukeb@openmarket.com>
Date: Tue, 31 Oct 2017 10:55:42 +0000
Subject: [PATCH 07/21] Prevent group name looking clickable for non-members

---
 .../vector/css/matrix-react-sdk/structures/_GroupView.scss      | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss b/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss
index 1a92fc10..87b46c05 100644
--- a/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss
+++ b/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss
@@ -68,7 +68,7 @@ limitations under the License.
     box-shadow: none;
 }
 
-.mx_GroupView_header_name:hover div:not(.mx_GroupView_editable) {
+.mx_GroupView_header_isUserMember .mx_GroupView_header_name:hover div:not(.mx_GroupView_editable) {
     color: $accent-color;
     cursor: pointer;
 }

From 04d1a723073c0a517cebe4076f744ef7e86af66a Mon Sep 17 00:00:00 2001
From: Richard Lewis <richard@smetco.co.uk>
Date: Tue, 31 Oct 2017 18:02:04 +0000
Subject: [PATCH 08/21] Undo comitted changes to package-lock.json

---
 package-lock.json | 98 +++++++++++++++++++++++++----------------------
 1 file changed, 52 insertions(+), 46 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 67707dec..64e2e49a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "riot-web",
-  "version": "0.12.7",
+  "version": "0.12.2",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -260,16 +260,6 @@
       "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=",
       "dev": true
     },
-    "array-includes": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz",
-      "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=",
-      "dev": true,
-      "requires": {
-        "define-properties": "1.1.2",
-        "es-abstract": "1.9.0"
-      }
-    },
     "array-map": {
       "version": "0.0.0",
       "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz",
@@ -309,6 +299,16 @@
       "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=",
       "dev": true
     },
+    "array.prototype.find": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.0.4.tgz",
+      "integrity": "sha1-VWpcU2LAhkgyPdrrnenRS8GGTJA=",
+      "dev": true,
+      "requires": {
+        "define-properties": "1.1.2",
+        "es-abstract": "1.9.0"
+      }
+    },
     "arraybuffer.slice": {
       "version": "0.0.6",
       "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz",
@@ -3202,36 +3202,16 @@
       }
     },
     "eslint-plugin-react": {
-      "version": "7.4.0",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.4.0.tgz",
-      "integrity": "sha512-tvjU9u3VqmW2vVuYnE8Qptq+6ji4JltjOjJ9u7VAOxVYkUkyBZWRvNYKbDv5fN+L6wiA+4we9+qQahZ0m63XEA==",
+      "version": "6.10.3",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-6.10.3.tgz",
+      "integrity": "sha1-xUNb6wZ3ThLH2y9qut3L+QDNP3g=",
       "dev": true,
       "requires": {
-        "doctrine": "2.0.0",
+        "array.prototype.find": "2.0.4",
+        "doctrine": "1.2.3",
         "has": "1.0.1",
-        "jsx-ast-utils": "2.0.1",
-        "prop-types": "15.6.0"
-      },
-      "dependencies": {
-        "doctrine": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz",
-          "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=",
-          "dev": true,
-          "requires": {
-            "esutils": "2.0.2",
-            "isarray": "1.0.0"
-          }
-        },
-        "jsx-ast-utils": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz",
-          "integrity": "sha1-6AGxs5mF4g//yHtA43SAgOLcrH8=",
-          "dev": true,
-          "requires": {
-            "array-includes": "3.0.3"
-          }
-        }
+        "jsx-ast-utils": "1.4.1",
+        "object.assign": "4.0.4"
       }
     },
     "espree": {
@@ -4677,7 +4657,8 @@
         "tweetnacl": {
           "version": "0.14.5",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "uid-number": {
           "version": "0.0.6",
@@ -5715,6 +5696,12 @@
         "verror": "1.10.0"
       }
     },
+    "jsx-ast-utils": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz",
+      "integrity": "sha1-OGchPo3Xm/Ho8jAMDPwe+xgsDfE=",
+      "dev": true
+    },
     "karma": {
       "version": "1.7.1",
       "resolved": "https://registry.npmjs.org/karma/-/karma-1.7.1.tgz",
@@ -6121,9 +6108,9 @@
       "dev": true
     },
     "matrix-js-sdk": {
-      "version": "0.8.5",
-      "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-0.8.5.tgz",
-      "integrity": "sha1-1ZAVTx53ADVyZw+p28rH5APnbk8=",
+      "version": "0.8.2",
+      "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-0.8.2.tgz",
+      "integrity": "sha1-e7mrVoXrNCFLOFlMiDn++pY2ViE=",
       "requires": {
         "another-json": "0.2.0",
         "bluebird": "3.5.1",
@@ -6143,9 +6130,9 @@
       }
     },
     "matrix-react-sdk": {
-      "version": "0.10.7",
-      "resolved": "https://registry.npmjs.org/matrix-react-sdk/-/matrix-react-sdk-0.10.7.tgz",
-      "integrity": "sha1-pi7GrlMwbNRJi0eYYAU04ORBMcA=",
+      "version": "0.10.2",
+      "resolved": "https://registry.npmjs.org/matrix-react-sdk/-/matrix-react-sdk-0.10.2.tgz",
+      "integrity": "sha1-TNSwkN1P4Jsl4Yh5Z/XOE17U8q4=",
       "requires": {
         "babel-runtime": "6.26.0",
         "bluebird": "3.5.1",
@@ -6168,7 +6155,7 @@
         "isomorphic-fetch": "2.2.1",
         "linkifyjs": "2.1.5",
         "lodash": "4.17.4",
-        "matrix-js-sdk": "0.8.5",
+        "matrix-js-sdk": "0.8.2",
         "optimist": "0.6.1",
         "prop-types": "15.6.0",
         "react": "15.6.2",
@@ -6624,6 +6611,25 @@
       "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=",
       "dev": true
     },
+    "object.assign": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.0.4.tgz",
+      "integrity": "sha1-scnMBE7xuf5jYG/BQau7MuFHMMw=",
+      "dev": true,
+      "requires": {
+        "define-properties": "1.1.2",
+        "function-bind": "1.1.1",
+        "object-keys": "1.0.11"
+      },
+      "dependencies": {
+        "object-keys": {
+          "version": "1.0.11",
+          "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz",
+          "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=",
+          "dev": true
+        }
+      }
+    },
     "object.entries": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.0.4.tgz",

From 2163a54617c41ec4e56b4db5454daf343a70276c Mon Sep 17 00:00:00 2001
From: Luke Barnard <lukeb@openmarket.com>
Date: Wed, 1 Nov 2017 17:32:01 +0000
Subject: [PATCH 09/21] Implement simple GroupRoomInfo

See matrix-org/matrix-react-sdk#1563
---
 src/components/structures/RightPanel.js        | 18 +++++++++++++++---
 .../views/groups/_GroupRoomList.scss           | 15 ---------------
 .../views/rooms/_EntityTile.scss               |  2 +-
 3 files changed, 16 insertions(+), 19 deletions(-)

diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js
index 311c897f..76d9dd37 100644
--- a/src/components/structures/RightPanel.js
+++ b/src/components/structures/RightPanel.js
@@ -205,7 +205,6 @@ module.exports = React.createClass({
                 } else if (this.props.groupId) {
                     this.setState({
                         phase: this.Phase.GroupMemberList,
-                        groupId: payload.groupId,
                         member: payload.member,
                     });
                 }
@@ -213,13 +212,20 @@ module.exports = React.createClass({
         } else if (payload.action === "view_group") {
             this.setState({
                 phase: this.Phase.GroupMemberList,
-                groupId: payload.groupId,
                 member: null,
             });
+        } else if (payload.action === "view_group_room") {
+            this.setState({
+                phase: this.Phase.GroupRoomInfo,
+                groupRoom: payload.groupRoom,
+            });
+        } else if (payload.action === "view_group_room_list") {
+            this.setState({
+                phase: this.Phase.GroupRoomList,
+            });
         } else if (payload.action === "view_group_user") {
             this.setState({
                 phase: this.Phase.GroupMemberInfo,
-                groupId: payload.groupId,
                 member: payload.member,
             });
         } else if (payload.action === "view_room") {
@@ -242,6 +248,7 @@ module.exports = React.createClass({
         const GroupMemberList = sdk.getComponent('groups.GroupMemberList');
         const GroupMemberInfo = sdk.getComponent('groups.GroupMemberInfo');
         const GroupRoomList = sdk.getComponent('groups.GroupRoomList');
+        const GroupRoomInfo = sdk.getComponent('groups.GroupRoomInfo');
 
         const TintableSvg = sdk.getComponent("elements.TintableSvg");
 
@@ -340,6 +347,11 @@ module.exports = React.createClass({
                     groupMember={this.state.member}
                     groupId={this.props.groupId}
                     key={this.state.member.user_id} />;
+            } else if (this.state.phase == this.Phase.GroupRoomInfo) {
+                panel = <GroupRoomInfo
+                    groupRoom={this.state.groupRoom}
+                    groupId={this.props.groupId}
+                    key={this.state.groupRoom.roomId} />;
             } else if (this.state.phase == this.Phase.NotificationPanel) {
                 panel = <NotificationPanel />;
             } else if (this.state.phase == this.Phase.FilePanel) {
diff --git a/src/skins/vector/css/matrix-react-sdk/views/groups/_GroupRoomList.scss b/src/skins/vector/css/matrix-react-sdk/views/groups/_GroupRoomList.scss
index 91f0c347..fb41ebaa 100644
--- a/src/skins/vector/css/matrix-react-sdk/views/groups/_GroupRoomList.scss
+++ b/src/skins/vector/css/matrix-react-sdk/views/groups/_GroupRoomList.scss
@@ -19,18 +19,3 @@ limitations under the License.
     color: $primary-fg-color;
     cursor: pointer;
 }
-
-.mx_GroupRoomTile_delete {
-    opacity: 0.4;
-    position: absolute;
-    top: 6px;
-    right: 10px;
-    cursor: pointer;
-
-    display: none;
-}
-
-.mx_GroupRoomTile:hover > .mx_GroupRoomTile_delete {
-    display: initial;
-}
-
diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/_EntityTile.scss b/src/skins/vector/css/matrix-react-sdk/views/rooms/_EntityTile.scss
index 712e4bae..031894af 100644
--- a/src/skins/vector/css/matrix-react-sdk/views/rooms/_EntityTile.scss
+++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/_EntityTile.scss
@@ -57,7 +57,7 @@ limitations under the License.
     font-size: 14px;
     text-overflow: ellipsis;
     white-space: nowrap;
-    max-width: 135px;
+    max-width: 155px;
 }
 
 .mx_EntityTile_details {

From cd84d86fd13f7d239b91b42fab461fb86667cd66 Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Wed, 1 Nov 2017 19:45:59 +0000
Subject: [PATCH 10/21] CSS for room notification pills

---
 .../matrix-react-sdk/views/elements/_RichText.scss  | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/src/skins/vector/css/matrix-react-sdk/views/elements/_RichText.scss b/src/skins/vector/css/matrix-react-sdk/views/elements/_RichText.scss
index 0927cfbc..11dfee93 100644
--- a/src/skins/vector/css/matrix-react-sdk/views/elements/_RichText.scss
+++ b/src/skins/vector/css/matrix-react-sdk/views/elements/_RichText.scss
@@ -3,7 +3,8 @@
 // --Matthew
 
 .mx_UserPill,
-.mx_RoomPill {
+.mx_RoomPill,
+.mx_AtRoomPill {
     border-radius: 16px;
     display: inline-block;
     height: 20px;
@@ -24,9 +25,10 @@
     padding-right: 5px;
 }
 
-.mx_EventTile_highlight .mx_EventTile_content .markdown-body a.mx_UserPill_me {
-    color: $accent-fg-color;
-    background-color: $mention-user-pill-bg-color;
+.mx_EventTile_highlight .mx_EventTile_content .markdown-body a.mx_UserPill_me, .mx_AtRoomPill {
+    /* !important because otherwise @room pills in links get their colours clobbered by links styles :( */
+    color: $accent-fg-color !important;
+    background-color: $mention-user-pill-bg-color !important;
     padding-right: 5px;
 }
 
@@ -39,7 +41,8 @@
 }
 
 .mx_UserPill .mx_BaseAvatar,
-.mx_RoomPill .mx_BaseAvatar {
+.mx_RoomPill .mx_BaseAvatar,
+.mx_AtRoomPill .mx_BaseAvatar {
     position: relative;
     left: -3px;
     top: 2px;

From 65ea7a8460cfbe2bc93cd38b1f994a529efda19f Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Thu, 2 Nov 2017 10:37:25 +0000
Subject: [PATCH 11/21] Use more spercific selector instead of !important

---
 .../css/matrix-react-sdk/views/elements/_RichText.scss    | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/skins/vector/css/matrix-react-sdk/views/elements/_RichText.scss b/src/skins/vector/css/matrix-react-sdk/views/elements/_RichText.scss
index 11dfee93..8b7c8f06 100644
--- a/src/skins/vector/css/matrix-react-sdk/views/elements/_RichText.scss
+++ b/src/skins/vector/css/matrix-react-sdk/views/elements/_RichText.scss
@@ -25,10 +25,10 @@
     padding-right: 5px;
 }
 
-.mx_EventTile_highlight .mx_EventTile_content .markdown-body a.mx_UserPill_me, .mx_AtRoomPill {
-    /* !important because otherwise @room pills in links get their colours clobbered by links styles :( */
-    color: $accent-fg-color !important;
-    background-color: $mention-user-pill-bg-color !important;
+.mx_EventTile_highlight .mx_EventTile_content .markdown-body a.mx_UserPill_me,
+.mx_EventTile_content .mx_AtRoomPill {
+    color: $accent-fg-color;
+    background-color: $mention-user-pill-bg-color;
     padding-right: 5px;
 }
 

From c01ea566763733fb2525f21f15aa02afccb37dce Mon Sep 17 00:00:00 2001
From: Luke Barnard <lukeb@openmarket.com>
Date: Thu, 2 Nov 2017 13:40:07 +0000
Subject: [PATCH 12/21] Add toggle to alter visibility of room-group
 association

---
 src/components/structures/RightPanel.js       |  9 ++---
 .../views/elements/InlineSpinner.js           | 33 +++++++++++++++++++
 src/skins/vector/css/_components.scss         |  5 +--
 .../views/rooms/_MemberInfo.scss              | 10 ++++++
 .../views/elements/_InlineSpinner.scss        | 24 ++++++++++++++
 5 files changed, 75 insertions(+), 6 deletions(-)
 create mode 100644 src/components/views/elements/InlineSpinner.js
 create mode 100644 src/skins/vector/css/vector-web/views/elements/_InlineSpinner.scss

diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js
index 76d9dd37..dedd715f 100644
--- a/src/components/structures/RightPanel.js
+++ b/src/components/structures/RightPanel.js
@@ -90,6 +90,7 @@ module.exports = React.createClass({
         RoomMemberList: 'RoomMemberList',
         GroupMemberList: 'GroupMemberList',
         GroupRoomList: 'GroupRoomList',
+        GroupRoomInfo: 'GroupRoomInfo',
         FilePanel: 'FilePanel',
         NotificationPanel: 'NotificationPanel',
         RoomMemberInfo: 'RoomMemberInfo',
@@ -217,7 +218,7 @@ module.exports = React.createClass({
         } else if (payload.action === "view_group_room") {
             this.setState({
                 phase: this.Phase.GroupRoomInfo,
-                groupRoom: payload.groupRoom,
+                groupRoomId: payload.groupRoomId,
             });
         } else if (payload.action === "view_group_room_list") {
             this.setState({
@@ -312,7 +313,7 @@ module.exports = React.createClass({
                     analytics={['Right Panel', 'Group Member List Button', 'click']}
                 />,
                 <HeaderButton key="_roomsButton" title={_t('Rooms')} iconSrc="img/icons-room.svg"
-                    isHighlighted={this.state.phase === this.Phase.GroupRoomList}
+                    isHighlighted={[this.Phase.GroupRoomList, this.Phase.GroupRoomInfo].includes(this.state.phase)}
                     clickPhase={this.Phase.GroupRoomList}
                     analytics={['Right Panel', 'Group Room List Button', 'click']}
                 />,
@@ -349,9 +350,9 @@ module.exports = React.createClass({
                     key={this.state.member.user_id} />;
             } else if (this.state.phase == this.Phase.GroupRoomInfo) {
                 panel = <GroupRoomInfo
-                    groupRoom={this.state.groupRoom}
+                    groupRoomId={this.state.groupRoomId}
                     groupId={this.props.groupId}
-                    key={this.state.groupRoom.roomId} />;
+                    key={this.state.groupRoomId} />;
             } else if (this.state.phase == this.Phase.NotificationPanel) {
                 panel = <NotificationPanel />;
             } else if (this.state.phase == this.Phase.FilePanel) {
diff --git a/src/components/views/elements/InlineSpinner.js b/src/components/views/elements/InlineSpinner.js
new file mode 100644
index 00000000..adb916fc
--- /dev/null
+++ b/src/components/views/elements/InlineSpinner.js
@@ -0,0 +1,33 @@
+/*
+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.
+*/
+
+const React = require('react');
+
+module.exports = React.createClass({
+    displayName: 'InlineSpinner',
+
+    render: function() {
+        var w = this.props.w || 16;
+        var h = this.props.h || 16;
+        var imgClass = this.props.imgClassName || "";
+
+        return (
+            <div className="mx_InlineSpinner">
+                <img src="img/spinner.gif" width={w} height={h} className={imgClass}/>
+            </div>
+        );
+    }
+});
diff --git a/src/skins/vector/css/_components.scss b/src/skins/vector/css/_components.scss
index 82c2c186..c857a7c3 100644
--- a/src/skins/vector/css/_components.scss
+++ b/src/skins/vector/css/_components.scss
@@ -55,6 +55,8 @@
 @import "./matrix-react-sdk/views/rooms/_MemberInfo.scss";
 @import "./matrix-react-sdk/views/rooms/_MemberList.scss";
 @import "./matrix-react-sdk/views/rooms/_MessageComposer.scss";
+@import "./matrix-react-sdk/views/rooms/_PinnedEventTile.scss";
+@import "./matrix-react-sdk/views/rooms/_PinnedEventsPanel.scss";
 @import "./matrix-react-sdk/views/rooms/_PresenceLabel.scss";
 @import "./matrix-react-sdk/views/rooms/_RoomHeader.scss";
 @import "./matrix-react-sdk/views/rooms/_RoomList.scss";
@@ -68,8 +70,6 @@
 @import "./matrix-react-sdk/views/voip/_CallView.scss";
 @import "./matrix-react-sdk/views/voip/_IncomingCallbox.scss";
 @import "./matrix-react-sdk/views/voip/_VideoView.scss";
-@import "./matrix-react-sdk/views/rooms/_PinnedEventsPanel.scss";
-@import "./matrix-react-sdk/views/rooms/_PinnedEventTile.scss";
 @import "./vector-web/_fonts.scss";
 @import "./vector-web/structures/_CompatibilityPage.scss";
 @import "./vector-web/structures/_HomePage.scss";
@@ -86,6 +86,7 @@
 @import "./vector-web/views/dialogs/_SetPasswordDialog.scss";
 @import "./vector-web/views/directory/_NetworkDropdown.scss";
 @import "./vector-web/views/elements/_ImageView.scss";
+@import "./vector-web/views/elements/_InlineSpinner.scss";
 @import "./vector-web/views/elements/_Spinner.scss";
 @import "./vector-web/views/globals/_MatrixToolbar.scss";
 @import "./vector-web/views/messages/_DateSeparator.scss";
diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/_MemberInfo.scss b/src/skins/vector/css/matrix-react-sdk/views/rooms/_MemberInfo.scss
index 8920c6f6..567d6dba 100644
--- a/src/skins/vector/css/matrix-react-sdk/views/rooms/_MemberInfo.scss
+++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/_MemberInfo.scss
@@ -94,3 +94,13 @@ limitations under the License.
     cursor: pointer;
 }
 
+.mx_MemberInfo label {
+    font-size: 13px;
+}
+
+.mx_MemberInfo input[type="radio"] {
+    vertical-align: -2px;
+    margin-right: 5px;
+    margin-left: 8px;
+}
+
diff --git a/src/skins/vector/css/vector-web/views/elements/_InlineSpinner.scss b/src/skins/vector/css/vector-web/views/elements/_InlineSpinner.scss
new file mode 100644
index 00000000..612b6209
--- /dev/null
+++ b/src/skins/vector/css/vector-web/views/elements/_InlineSpinner.scss
@@ -0,0 +1,24 @@
+/*
+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.
+*/
+
+.mx_InlineSpinner {
+    display: inline;
+}
+
+.mx_InlineSpinner img {
+    margin: 0px 6px;
+    vertical-align: -3px;
+}

From 06ce4678761a6d8d0434f11fe16a6d3440a31adf Mon Sep 17 00:00:00 2001
From: Luke Barnard <lukeb@openmarket.com>
Date: Thu, 2 Nov 2017 15:05:08 +0000
Subject: [PATCH 13/21] Add CSS for group room visibility label alignment

---
 .../css/matrix-react-sdk/views/rooms/_MemberInfo.scss       | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/_MemberInfo.scss b/src/skins/vector/css/matrix-react-sdk/views/rooms/_MemberInfo.scss
index 567d6dba..5d47275e 100644
--- a/src/skins/vector/css/matrix-react-sdk/views/rooms/_MemberInfo.scss
+++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/_MemberInfo.scss
@@ -98,6 +98,12 @@ limitations under the License.
     font-size: 13px;
 }
 
+.mx_MemberInfo label .mx_MemberInfo_label_text {
+    display: inline-block;
+    max-width: 180px;
+    vertical-align: text-top;
+}
+
 .mx_MemberInfo input[type="radio"] {
     vertical-align: -2px;
     margin-right: 5px;

From ee71c72685287a95fa1b0e401b2f123ca1599549 Mon Sep 17 00:00:00 2001
From: Luke Barnard <lukeb@openmarket.com>
Date: Thu, 2 Nov 2017 15:13:55 +0000
Subject: [PATCH 14/21] Use margin to separate "perms" in the room directory

instead of a space.
---
 src/components/structures/RoomDirectory.js                     | 2 +-
 src/skins/vector/css/vector-web/structures/_RoomDirectory.scss | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js
index 323af86c..126ae404 100644
--- a/src/components/structures/RoomDirectory.js
+++ b/src/components/structures/RoomDirectory.js
@@ -409,7 +409,7 @@ module.exports = React.createClass({
 
             perms = null;
             if (guestRead || guestJoin) {
-                perms = <div className="mx_RoomDirectory_perms">{guestRead} {guestJoin}</div>;
+                perms = <div className="mx_RoomDirectory_perms">{guestRead}{guestJoin}</div>;
             }
 
             var topic = rooms[i].topic || '';
diff --git a/src/skins/vector/css/vector-web/structures/_RoomDirectory.scss b/src/skins/vector/css/vector-web/structures/_RoomDirectory.scss
index 91cf86c9..9cd3e728 100644
--- a/src/skins/vector/css/vector-web/structures/_RoomDirectory.scss
+++ b/src/skins/vector/css/vector-web/structures/_RoomDirectory.scss
@@ -100,6 +100,7 @@ limitations under the License.
     display: inline;
     padding-left: 5px;
     padding-right: 5px;
+    margin-right: 5px;
     height: 15px;
     border-radius: 11px;
     background-color: $plinth-bg-color;

From f6974407e30e358faa68e35eb6408125e60db1e9 Mon Sep 17 00:00:00 2001
From: Luke Barnard <lukeb@openmarket.com>
Date: Fri, 3 Nov 2017 11:22:29 +0000
Subject: [PATCH 15/21] CSS for Your Communities scrollbar

and also attempt to fix GroupTile flex weirdness.
---
 .../structures/_MyGroups.scss                 | 35 +++++++++++++++----
 1 file changed, 29 insertions(+), 6 deletions(-)

diff --git a/src/skins/vector/css/matrix-react-sdk/structures/_MyGroups.scss b/src/skins/vector/css/matrix-react-sdk/structures/_MyGroups.scss
index ddcf46fb..a9c7a5f0 100644
--- a/src/skins/vector/css/matrix-react-sdk/structures/_MyGroups.scss
+++ b/src/skins/vector/css/matrix-react-sdk/structures/_MyGroups.scss
@@ -18,6 +18,9 @@ limitations under the License.
     max-width: 960px;
     margin-left: auto;
     margin-right: auto;
+
+    display: flex;
+    flex-direction: column;
 }
 
 .mx_MyGroups .mx_RoomHeader_simpleHeader {
@@ -61,10 +64,21 @@ limitations under the License.
 /* Until the button is wired up */
 .mx_MyGroups_joinBox {
     visibility: hidden;
+    height: 0px;
+    margin: 0px;
 }
 
 .mx_MyGroups_content {
     margin-left: 2px;
+
+    flex: 1 0 0;
+
+    display: flex;
+    flex-direction: column;
+}
+
+.mx_MyGroups_content h3 {
+    margin-bottom: 10px;
 }
 
 .mx_MyGroups_placeholder {
@@ -75,19 +89,22 @@ limitations under the License.
     text-align: center;
 }
 
-.mx_MyGroups_joinedGroups {
+.mx_MyGroups_joinedGroups .gm-scroll-view {
+    border-top: 1px solid $primary-hairline-color;
+    overflow-x: hidden;
+
     display: flex;
     flex-direction: row;
     flex-flow: wrap;
-    justify-content: space-around;
+    align-content: flex-start;
 }
 
-.mx_MyGroups_joinedGroups .mx_GroupTile {
+.mx_MyGroups_joinedGroups .gm-scroll-view .mx_GroupTile {
     min-width: 300px;
-    flex: 1 0 25%;
+    max-width: 33%;
+    flex: 1 0 300px;
     height: 75px;
-    margin-bottom: 15px;
-    margin-right: 10px;
+    margin: 10px 0px;
     display: flex;
     align-items: flex-start;
     cursor: pointer;
@@ -100,6 +117,12 @@ limitations under the License.
     justify-content: center;
 }
 
+.mx_GroupTile_profile h3.mx_GroupTile_name,
+.mx_GroupTile_profile .mx_GroupTile_groupId,
+.mx_GroupTile_profile .mx_GroupTile_desc {
+    padding-right: 10px;
+}
+
 .mx_GroupTile_profile h3.mx_GroupTile_name {
     margin: 0px;
     font-size: 15px;

From 4d11e739b555c71177bb0a9b5379315ffd400d89 Mon Sep 17 00:00:00 2001
From: Luke Barnard <lukeb@openmarket.com>
Date: Fri, 3 Nov 2017 11:42:06 +0000
Subject: [PATCH 16/21] Comment 0px height and margin on joinBox

---
 .../vector/css/matrix-react-sdk/structures/_MyGroups.scss    | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/skins/vector/css/matrix-react-sdk/structures/_MyGroups.scss b/src/skins/vector/css/matrix-react-sdk/structures/_MyGroups.scss
index a9c7a5f0..d7cbda9a 100644
--- a/src/skins/vector/css/matrix-react-sdk/structures/_MyGroups.scss
+++ b/src/skins/vector/css/matrix-react-sdk/structures/_MyGroups.scss
@@ -64,6 +64,11 @@ limitations under the License.
 /* Until the button is wired up */
 .mx_MyGroups_joinBox {
     visibility: hidden;
+
+    /* When joinBox wraps onto its own row, it should take up zero height so
+       that there isn't an awkward gap between MyGroups_createBox and
+       MyGroups_content.
+    */
     height: 0px;
     margin: 0px;
 }

From c60ff5d28335b688ad152c316fbd46b8dc98216e Mon Sep 17 00:00:00 2001
From: Luke Barnard <lukeb@openmarket.com>
Date: Fri, 3 Nov 2017 12:15:33 +0000
Subject: [PATCH 17/21] Fix group invites such that they look similar to room
 invites

 - Remove CSS for GroupInviteTile - the component should be using RoomTile CSS
 - Added extra tiles to roomCount of RoomSubList header

Part of fixing https://github.com/vector-im/riot-web/issues/5226
---
 src/components/structures/RoomSubList.js      |  9 ++-
 .../views/groups/_GroupInviteTile.scss        | 74 -------------------
 src/skins/vector/css/themes/_base.scss        |  1 -
 3 files changed, 7 insertions(+), 77 deletions(-)
 delete mode 100644 src/skins/vector/css/matrix-react-sdk/views/groups/_GroupInviteTile.scss

diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js
index 8fb7562f..c3f51bc8 100644
--- a/src/components/structures/RoomSubList.js
+++ b/src/components/structures/RoomSubList.js
@@ -246,10 +246,14 @@ var RoomSubList = React.createClass({
     roomNotificationCount: function(truncateAt) {
         var self = this;
 
+        if (this.props.isInvite) {
+            return [0, true];
+        }
+
         return this.props.list.reduce(function(result, room, index) {
             if (truncateAt === undefined || index >= truncateAt) {
                 var roomNotifState = RoomNotifs.getRoomNotifsState(room.roomId);
-                var highlight = room.getUnreadNotificationCount('highlight') > 0 || self.props.isInvite;
+                var highlight = room.getUnreadNotificationCount('highlight') > 0;
                 var notificationCount = room.getUnreadNotificationCount();
 
                 const notifBadges = notificationCount > 0 && self._shouldShowNotifBadge(roomNotifState);
@@ -394,7 +398,8 @@ var RoomSubList = React.createClass({
         var subListNotifCount = subListNotifications[0];
         var subListNotifHighlight = subListNotifications[1];
 
-        var roomCount = this.props.list.length > 0 ? this.props.list.length : '';
+        var totalTiles = this.props.list.length + (this.props.extraTiles || []).length;
+        var roomCount = totalTiles > 0 ? totalTiles : '';
 
         var chevronClasses = classNames({
             'mx_RoomSubList_chevron': true,
diff --git a/src/skins/vector/css/matrix-react-sdk/views/groups/_GroupInviteTile.scss b/src/skins/vector/css/matrix-react-sdk/views/groups/_GroupInviteTile.scss
deleted file mode 100644
index 6b4034b9..00000000
--- a/src/skins/vector/css/matrix-react-sdk/views/groups/_GroupInviteTile.scss
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
-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.
-*/
-
-.mx_GroupInviteTile {
-    position: relative;
-    cursor: pointer;
-    font-size: 13px;
-    display: block;
-    height: 34px;
-}
-
-.mx_GroupInviteTile_nameContainer {
-    display: inline-block;
-    width: 180px;
-    height: 24px;
-}
-
-.mx_GroupInviteTile_avatarContainer {
-    display: inline-block;
-    padding-top: 5px;
-    padding-bottom: 5px;
-    padding-left: 16px;
-    padding-right: 6px;
-    width: 24px;
-    height: 24px;
-    vertical-align: middle;
-}
-
-.mx_GroupInviteTile_name {
-    display: inline-block;
-    position: relative;
-    width: 165px;
-    vertical-align: middle;
-    padding-left: 6px;
-    padding-right: 6px;
-    padding-top: 2px;
-    padding-bottom: 3px;
-    color: $roomtile-name-color;
-    white-space: nowrap;
-    overflow: hidden;
-    text-overflow: ellipsis;
-}
-
-.mx_GroupInviteTile_badge {
-    display: inline-block;
-    min-width: 15px;
-    height: 15px;
-    position: absolute;
-    right: 8px; /*gutter */
-    top: 9px;
-    border-radius: 8px;
-    color: $accent-fg-color;
-    background-color: $group-alert-color;
-    font-weight: 600;
-    font-size: 10px;
-    text-align: center;
-    padding-top: 1px;
-    padding-left: 4px;
-    padding-right: 4px;
-}
-
diff --git a/src/skins/vector/css/themes/_base.scss b/src/skins/vector/css/themes/_base.scss
index f1117a7a..1a0fa5b4 100644
--- a/src/skins/vector/css/themes/_base.scss
+++ b/src/skins/vector/css/themes/_base.scss
@@ -23,7 +23,6 @@ $mention-user-pill-bg-color: #ff0064;
 $other-user-pill-bg-color: rgba(0, 0, 0, 0.1);
 
 // groups
-$group-alert-color: #774f7e;
 $group-my-groups-placeholder-bg: #f7f7f7;
 $group-my-groups-placeholder-fg: #888;
 

From 3192c345be529bb839fea9eaa0367676cacde599 Mon Sep 17 00:00:00 2001
From: Luke Barnard <lukeb@openmarket.com>
Date: Fri, 3 Nov 2017 14:11:07 +0000
Subject: [PATCH 18/21] rethemedex

Needed, having removed _GroupInviteTile.scss
---
 src/skins/vector/css/_components.scss | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/skins/vector/css/_components.scss b/src/skins/vector/css/_components.scss
index c857a7c3..230b5ae4 100644
--- a/src/skins/vector/css/_components.scss
+++ b/src/skins/vector/css/_components.scss
@@ -35,7 +35,6 @@
 @import "./matrix-react-sdk/views/elements/_ProgressBar.scss";
 @import "./matrix-react-sdk/views/elements/_RichText.scss";
 @import "./matrix-react-sdk/views/elements/_RoleButton.scss";
-@import "./matrix-react-sdk/views/groups/_GroupInviteTile.scss";
 @import "./matrix-react-sdk/views/groups/_GroupRoomList.scss";
 @import "./matrix-react-sdk/views/login/_InteractiveAuthEntryComponents.scss";
 @import "./matrix-react-sdk/views/login/_ServerConfig.scss";

From 04c866ce9ccb3560a81fffcc3b127cf0efc756cf Mon Sep 17 00:00:00 2001
From: Luke Barnard <lukeb@openmarket.com>
Date: Fri, 3 Nov 2017 14:39:24 +0000
Subject: [PATCH 19/21] Fix #5359 - unbreakable topics not breaking

The room directory CSS has `word-break: break-word` applied to the entire mx_RoomDirectory, which is questionable. For now, add the same rule to RoomDetailList
---
 src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss b/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss
index 87b46c05..583ab2ce 100644
--- a/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss
+++ b/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss
@@ -204,6 +204,7 @@ limitations under the License.
     flex-grow: 1;
     border-top: 1px solid $primary-hairline-color;
     padding-top: 10px;
+    word-break: break-word;
 }
 
 .mx_GroupView .mx_RoomView_messageListWrapper {

From 67bc346ebbffe49e276a360a0e933b5193512448 Mon Sep 17 00:00:00 2001
From: Luke Barnard <lukeb@openmarket.com>
Date: Fri, 3 Nov 2017 15:39:24 +0000
Subject: [PATCH 20/21] Add CSS for CreateGroupDialog to give group ID input
 suffix and prefix style

---
 .../views/dialogs/_CreateGroupDialog.scss     | 27 +++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/src/skins/vector/css/matrix-react-sdk/views/dialogs/_CreateGroupDialog.scss b/src/skins/vector/css/matrix-react-sdk/views/dialogs/_CreateGroupDialog.scss
index ebe89027..ea4fd31a 100644
--- a/src/skins/vector/css/matrix-react-sdk/views/dialogs/_CreateGroupDialog.scss
+++ b/src/skins/vector/css/matrix-react-sdk/views/dialogs/_CreateGroupDialog.scss
@@ -33,3 +33,30 @@ limitations under the License.
     background-color: $primary-bg-color;
 }
 
+.mx_CreateGroupDialog_input.has_prefix.has_suffix {
+    border-radius: 0px;
+}
+
+.mx_CreateGroupDialog_input_group {
+    display: flex;
+}
+
+.mx_CreateGroupDialog_input_prefix,
+.mx_CreateGroupDialog_input_suffix {
+    height: 35px;
+    padding: 0px 5px;
+    line-height: 37px;
+    background-color: $input-border-color;
+    border: 1px solid $input-border-color;
+    text-align: center;
+}
+
+.mx_CreateGroupDialog_input_prefix {
+    border-right: 0px;
+    border-radius: 3px 0px 0px 3px;
+}
+
+.mx_CreateGroupDialog_input_suffix {
+    border-left: 0px;
+    border-radius: 0px 3px 3px 0px;
+}

From 290a501523376d9802b41f79c13c376c46bbeac8 Mon Sep 17 00:00:00 2001
From: Luke Barnard <lukeb@openmarket.com>
Date: Fri, 3 Nov 2017 16:02:25 +0000
Subject: [PATCH 21/21] Use prefixed class names to avoid collisions with other
 libraries

---
 .../views/dialogs/_CreateGroupDialog.scss              | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/skins/vector/css/matrix-react-sdk/views/dialogs/_CreateGroupDialog.scss b/src/skins/vector/css/matrix-react-sdk/views/dialogs/_CreateGroupDialog.scss
index ea4fd31a..500e12ee 100644
--- a/src/skins/vector/css/matrix-react-sdk/views/dialogs/_CreateGroupDialog.scss
+++ b/src/skins/vector/css/matrix-react-sdk/views/dialogs/_CreateGroupDialog.scss
@@ -33,7 +33,7 @@ limitations under the License.
     background-color: $primary-bg-color;
 }
 
-.mx_CreateGroupDialog_input.has_prefix.has_suffix {
+.mx_CreateGroupDialog_input_hasPrefixAndSuffix {
     border-radius: 0px;
 }
 
@@ -41,8 +41,8 @@ limitations under the License.
     display: flex;
 }
 
-.mx_CreateGroupDialog_input_prefix,
-.mx_CreateGroupDialog_input_suffix {
+.mx_CreateGroupDialog_prefix,
+.mx_CreateGroupDialog_suffix {
     height: 35px;
     padding: 0px 5px;
     line-height: 37px;
@@ -51,12 +51,12 @@ limitations under the License.
     text-align: center;
 }
 
-.mx_CreateGroupDialog_input_prefix {
+.mx_CreateGroupDialog_prefix {
     border-right: 0px;
     border-radius: 3px 0px 0px 3px;
 }
 
-.mx_CreateGroupDialog_input_suffix {
+.mx_CreateGroupDialog_suffix {
     border-left: 0px;
     border-radius: 0px 3px 3px 0px;
 }