From 8cacf1ff9743fd807b8169c8aa8c09a3c9da4b2e Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 21 Jul 2020 10:14:12 +0100
Subject: [PATCH 1/7] Convert RoomViewStore and ActiveRoomObserver to
 typescript

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
 src/@types/global.d.ts                        |   3 +
 ...eRoomObserver.js => ActiveRoomObserver.ts} |  52 ++++----
 .../{RoomViewStore.js => RoomViewStore.tsx}   | 122 ++++++++++--------
 3 files changed, 95 insertions(+), 82 deletions(-)
 rename src/{ActiveRoomObserver.js => ActiveRoomObserver.ts} (54%)
 rename src/stores/{RoomViewStore.js => RoomViewStore.tsx} (83%)

diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts
index f556ff8b5c..4ab9e5c8b3 100644
--- a/src/@types/global.d.ts
+++ b/src/@types/global.d.ts
@@ -24,6 +24,7 @@ import { RoomListStoreClass } from "../stores/room-list/RoomListStore";
 import { PlatformPeg } from "../PlatformPeg";
 import RoomListLayoutStore from "../stores/room-list/RoomListLayoutStore";
 import {IntegrationManagers} from "../integrations/IntegrationManagers";
+import {ActiveRoomObserver} from "../ActiveRoomObserver";
 
 declare global {
     interface Window {
@@ -39,6 +40,8 @@ declare global {
         mx_RebrandListener: RebrandListener;
         mx_RoomListStore: RoomListStoreClass;
         mx_RoomListLayoutStore: RoomListLayoutStore;
+        mx_ActiveRoomObserver: ActiveRoomObserver;
+
         mxPlatformPeg: PlatformPeg;
         mxIntegrationManagers: typeof IntegrationManagers;
     }
diff --git a/src/ActiveRoomObserver.js b/src/ActiveRoomObserver.ts
similarity index 54%
rename from src/ActiveRoomObserver.js
rename to src/ActiveRoomObserver.ts
index b7695d401d..4ff4951915 100644
--- a/src/ActiveRoomObserver.js
+++ b/src/ActiveRoomObserver.ts
@@ -16,6 +16,8 @@ limitations under the License.
 
 import RoomViewStore from './stores/RoomViewStore';
 
+type Listener = (isActive: boolean) => void;
+
 /**
  * Consumes changes from the RoomViewStore and notifies specific things
  * about when the active room changes. Unlike listening for RoomViewStore
@@ -25,57 +27,57 @@ import RoomViewStore from './stores/RoomViewStore';
  * TODO: If we introduce an observer for something else, factor out
  * the adding / removing of listeners & emitting into a common class.
  */
-class ActiveRoomObserver {
-    constructor() {
-        this._listeners = {}; // key=roomId, value=function(isActive:boolean)
+export class ActiveRoomObserver {
+    private listeners: {[key: string]: Listener[]} = {};
+    private _activeRoomId = RoomViewStore.getRoomId(); // TODO
+    private readonly roomStoreToken: string;
 
-        this._activeRoomId = RoomViewStore.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));
+    constructor() {
+        // TODO: We could self-destruct when the last listener goes away, or at least stop listening.
+        this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate);
     }
 
-    get activeRoomId(): string {
+    public get activeRoomId(): string {
         return this._activeRoomId;
     }
 
-    addListener(roomId, listener) {
-        if (!this._listeners[roomId]) this._listeners[roomId] = [];
-        this._listeners[roomId].push(listener);
+    public addListener(roomId, listener) {
+        if (!this.listeners[roomId]) this.listeners[roomId] = [];
+        this.listeners[roomId].push(listener);
     }
 
-    removeListener(roomId, listener) {
-        if (this._listeners[roomId]) {
-            const i = this._listeners[roomId].indexOf(listener);
+    public removeListener(roomId, listener) {
+        if (this.listeners[roomId]) {
+            const i = this.listeners[roomId].indexOf(listener);
             if (i > -1) {
-                this._listeners[roomId].splice(i, 1);
+                this.listeners[roomId].splice(i, 1);
             }
         } else {
             console.warn("Unregistering unrecognised listener (roomId=" + roomId + ")");
         }
     }
 
-    _emit(roomId, isActive: boolean) {
-        if (!this._listeners[roomId]) return;
+    private emit(roomId, isActive: boolean) {
+        if (!this.listeners[roomId]) return;
 
-        for (const l of this._listeners[roomId]) {
+        for (const l of this.listeners[roomId]) {
             l.call(null, isActive);
         }
     }
 
-    _onRoomViewStoreUpdate() {
+    private onRoomViewStoreUpdate = () => {
         // emit for the old room ID
-        if (this._activeRoomId) this._emit(this._activeRoomId, false);
+        if (this._activeRoomId) this.emit(this._activeRoomId, false);
 
         // update our cache
         this._activeRoomId = RoomViewStore.getRoomId();
 
         // and emit for the new one
-        if (this._activeRoomId) this._emit(this._activeRoomId, true);
-    }
+        if (this._activeRoomId) this.emit(this._activeRoomId, true);
+    };
 }
 
-if (global.mx_ActiveRoomObserver === undefined) {
-    global.mx_ActiveRoomObserver = new ActiveRoomObserver();
+if (window.mx_ActiveRoomObserver === undefined) {
+    window.mx_ActiveRoomObserver = new ActiveRoomObserver();
 }
-export default global.mx_ActiveRoomObserver;
+export default window.mx_ActiveRoomObserver;
diff --git a/src/stores/RoomViewStore.js b/src/stores/RoomViewStore.tsx
similarity index 83%
rename from src/stores/RoomViewStore.js
rename to src/stores/RoomViewStore.tsx
index 6e5007895c..e634433933 100644
--- a/src/stores/RoomViewStore.js
+++ b/src/stores/RoomViewStore.tsx
@@ -15,13 +15,17 @@ 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/dispatcher';
+
+import React from "react";
 import {Store} from 'flux/utils';
+
+import dis from '../dispatcher/dispatcher';
 import {MatrixClientPeg} from '../MatrixClientPeg';
 import * as sdk from '../index';
 import Modal from '../Modal';
 import { _t } from '../languageHandler';
 import { getCachedRoomIDForAlias, storeRoomAliasInCache } from '../RoomAliasCache';
+import {ActionPayload} from "../dispatcher/payloads";
 
 const INITIAL_STATE = {
     // Whether we're joining the currently viewed room (see isJoining())
@@ -33,6 +37,7 @@ const INITIAL_STATE = {
 
     // The event to scroll to when the room is first viewed
     initialEventId: null,
+    initialEventPixelOffset: null,
     // Whether to highlight the initial event
     isInitialEventHighlighted: false,
 
@@ -46,6 +51,10 @@ const INITIAL_STATE = {
     forwardingEvent: null,
 
     quotingEvent: null,
+
+    replyingToEvent: null,
+
+    shouldPeek: false,
 };
 
 /**
@@ -53,21 +62,20 @@ const INITIAL_STATE = {
 *  with a subset of the js-sdk.
  *  ```
  */
-class RoomViewStore extends Store {
+class RoomViewStore extends Store<ActionPayload> {
+    private state = INITIAL_STATE; // initialize state
+
     constructor() {
         super(dis);
-
-        // Initialise state
-        this._state = INITIAL_STATE;
     }
 
-    _setState(newState) {
+    setState(newState: Partial<typeof INITIAL_STATE>) {
         // If values haven't changed, there's nothing to do.
         // This only tries a shallow comparison, so unchanged objects will slip
         // through, but that's probably okay for now.
         let stateChanged = false;
         for (const key of Object.keys(newState)) {
-            if (this._state[key] !== newState[key]) {
+            if (this.state[key] !== newState[key]) {
                 stateChanged = true;
                 break;
             }
@@ -76,7 +84,7 @@ class RoomViewStore extends Store {
             return;
         }
 
-        this._state = Object.assign(this._state, newState);
+        this.state = Object.assign(this.state, newState);
         this.__emitChange();
     }
 
@@ -89,59 +97,59 @@ class RoomViewStore extends Store {
             //      - event_offset: 100
             //      - highlighted:  true
             case 'view_room':
-                this._viewRoom(payload);
+                this.viewRoom(payload);
                 break;
             case 'view_my_groups':
             case 'view_group':
-                this._setState({
+                this.setState({
                     roomId: null,
                     roomAlias: null,
                 });
                 break;
             case 'view_room_error':
-                this._viewRoomError(payload);
+                this.viewRoomError(payload);
                 break;
             case 'will_join':
-                this._setState({
+                this.setState({
                     joining: true,
                 });
                 break;
             case 'cancel_join':
-                this._setState({
+                this.setState({
                     joining: false,
                 });
                 break;
             // join_room:
             //      - opts: options for joinRoom
             case 'join_room':
-                this._joinRoom(payload);
+                this.joinRoom(payload);
                 break;
             case 'join_room_error':
-                this._joinRoomError(payload);
+                this.joinRoomError(payload);
                 break;
             case 'join_room_ready':
-                this._setState({ shouldPeek: false });
+                this.setState({ shouldPeek: false });
                 break;
             case 'on_client_not_viable':
             case 'on_logged_out':
                 this.reset();
                 break;
             case 'forward_event':
-                this._setState({
+                this.setState({
                     forwardingEvent: payload.event,
                 });
                 break;
             case 'reply_to_event':
                 // If currently viewed room does not match the room in which we wish to reply then change rooms
                 // this can happen when performing a search across all rooms
-                if (payload.event && payload.event.getRoomId() !== this._state.roomId) {
+                if (payload.event && payload.event.getRoomId() !== this.state.roomId) {
                     dis.dispatch({
                         action: 'view_room',
                         room_id: payload.event.getRoomId(),
                         replyingToEvent: payload.event,
                     });
                 } else {
-                    this._setState({
+                    this.setState({
                         replyingToEvent: payload.event,
                     });
                 }
@@ -149,14 +157,14 @@ class RoomViewStore extends Store {
             case 'open_room_settings': {
                 const RoomSettingsDialog = sdk.getComponent("dialogs.RoomSettingsDialog");
                 Modal.createTrackedDialog('Room settings', '', RoomSettingsDialog, {
-                    roomId: payload.room_id || this._state.roomId,
+                    roomId: payload.room_id || this.state.roomId,
                 }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
                 break;
             }
         }
     }
 
-    async _viewRoom(payload) {
+    private async viewRoom(payload) {
         if (payload.room_id) {
             const newState = {
                 roomId: payload.room_id,
@@ -181,18 +189,18 @@ class RoomViewStore extends Store {
                 newState.replyingToEvent = payload.replyingToEvent;
             }
 
-            if (this._state.forwardingEvent) {
+            if (this.state.forwardingEvent) {
                 dis.dispatch({
                     action: 'send_event',
                     room_id: newState.roomId,
-                    event: this._state.forwardingEvent,
+                    event: this.state.forwardingEvent,
                 });
             }
 
-            this._setState(newState);
+            this.setState(newState);
 
             if (payload.auto_join) {
-                this._joinRoom(payload);
+                this.joinRoom(payload);
             }
         } else if (payload.room_alias) {
             // Try the room alias to room ID navigation cache first to avoid
@@ -201,7 +209,7 @@ class RoomViewStore extends Store {
             if (!roomId) {
                 // Room alias cache miss, so let's ask the homeserver. Resolve the alias
                 // and then do a second dispatch with the room ID acquired.
-                this._setState({
+                this.setState({
                     roomId: null,
                     initialEventId: null,
                     initialEventPixelOffset: null,
@@ -238,8 +246,8 @@ class RoomViewStore extends Store {
         }
     }
 
-    _viewRoomError(payload) {
-        this._setState({
+    private viewRoomError(payload) {
+        this.setState({
             roomId: payload.room_id,
             roomAlias: payload.room_alias,
             roomLoading: false,
@@ -247,12 +255,12 @@ class RoomViewStore extends Store {
         });
     }
 
-    _joinRoom(payload) {
-        this._setState({
+    private joinRoom(payload) {
+        this.setState({
             joining: true,
         });
         MatrixClientPeg.get().joinRoom(
-            this._state.roomAlias || this._state.roomId, payload.opts,
+            this.state.roomAlias || this.state.roomId, payload.opts,
         ).then(() => {
             // We do *not* clear the 'joining' flag because the Room object and/or our 'joined' member event may not
             // have come down the sync stream yet, and that's the point at which we'd consider the user joined to the
@@ -282,45 +290,45 @@ class RoomViewStore extends Store {
         });
     }
 
-    _joinRoomError(payload) {
-        this._setState({
+    private joinRoomError(payload) {
+        this.setState({
             joining: false,
             joinError: payload.err,
         });
     }
 
-    reset() {
-        this._state = Object.assign({}, INITIAL_STATE);
+    public reset() {
+        this.state = Object.assign({}, INITIAL_STATE);
     }
 
     // The room ID of the room currently being viewed
-    getRoomId() {
-        return this._state.roomId;
+    public getRoomId() {
+        return this.state.roomId;
     }
 
     // The event to scroll to when the room is first viewed
-    getInitialEventId() {
-        return this._state.initialEventId;
+    public getInitialEventId() {
+        return this.state.initialEventId;
     }
 
     // Whether to highlight the initial event
-    isInitialEventHighlighted() {
-        return this._state.isInitialEventHighlighted;
+    public isInitialEventHighlighted() {
+        return this.state.isInitialEventHighlighted;
     }
 
     // The room alias of the room (or null if not originally specified in view_room)
-    getRoomAlias() {
-        return this._state.roomAlias;
+    public getRoomAlias() {
+        return this.state.roomAlias;
     }
 
     // Whether the current room is loading (true whilst resolving an alias)
-    isRoomLoading() {
-        return this._state.roomLoading;
+    public isRoomLoading() {
+        return this.state.roomLoading;
     }
 
     // Any error that has occurred during loading
-    getRoomLoadError() {
-        return this._state.roomLoadError;
+    public getRoomLoadError() {
+        return this.state.roomLoadError;
     }
 
     // True if we're expecting the user to be joined to the room currently being
@@ -346,27 +354,27 @@ class RoomViewStore extends Store {
     //         // show join prompt
     //     }
     // }
-    isJoining() {
-        return this._state.joining;
+    public isJoining() {
+        return this.state.joining;
     }
 
     // Any error that has occurred during joining
-    getJoinError() {
-        return this._state.joinError;
+    public getJoinError() {
+        return this.state.joinError;
     }
 
     // The mxEvent if one is about to be forwarded
-    getForwardingEvent() {
-        return this._state.forwardingEvent;
+    public getForwardingEvent() {
+        return this.state.forwardingEvent;
     }
 
     // The mxEvent if one is currently being replied to/quoted
-    getQuotingEvent() {
-        return this._state.replyingToEvent;
+    public getQuotingEvent() {
+        return this.state.replyingToEvent;
     }
 
-    shouldPeek() {
-        return this._state.shouldPeek;
+    public shouldPeek() {
+        return this.state.shouldPeek;
     }
 }
 

From 9aff56afcc958cfc7f14052617fe33ec3cd89845 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 21 Jul 2020 10:15:05 +0100
Subject: [PATCH 2/7] add more types

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
 src/stores/RoomViewStore.tsx | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx
index e634433933..cb4ed39a1d 100644
--- a/src/stores/RoomViewStore.tsx
+++ b/src/stores/RoomViewStore.tsx
@@ -164,7 +164,7 @@ class RoomViewStore extends Store<ActionPayload> {
         }
     }
 
-    private async viewRoom(payload) {
+    private async viewRoom(payload: ActionPayload) {
         if (payload.room_id) {
             const newState = {
                 roomId: payload.room_id,
@@ -246,7 +246,7 @@ class RoomViewStore extends Store<ActionPayload> {
         }
     }
 
-    private viewRoomError(payload) {
+    private viewRoomError(payload: ActionPayload) {
         this.setState({
             roomId: payload.room_id,
             roomAlias: payload.room_alias,
@@ -255,7 +255,7 @@ class RoomViewStore extends Store<ActionPayload> {
         });
     }
 
-    private joinRoom(payload) {
+    private joinRoom(payload: ActionPayload) {
         this.setState({
             joining: true,
         });
@@ -290,7 +290,7 @@ class RoomViewStore extends Store<ActionPayload> {
         });
     }
 
-    private joinRoomError(payload) {
+    private joinRoomError(payload: ActionPayload) {
         this.setState({
             joining: false,
             joinError: payload.err,

From 0db66313e2435d5ced590cef2a1f9ee70094861e Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 21 Jul 2020 10:20:30 +0100
Subject: [PATCH 3/7] Add more causes to blank out active room

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
 src/stores/RoomViewStore.tsx | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx
index cb4ed39a1d..ea1f3e04c9 100644
--- a/src/stores/RoomViewStore.tsx
+++ b/src/stores/RoomViewStore.tsx
@@ -99,6 +99,9 @@ class RoomViewStore extends Store<ActionPayload> {
             case 'view_room':
                 this.viewRoom(payload);
                 break;
+            case 'view_create_group':
+            case 'view_welcome_page':
+            case 'view_home_page':
             case 'view_my_groups':
             case 'view_group':
                 this.setState({

From a69560d0de78c71ebac66c000c54a5587a13936f Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 21 Jul 2020 10:22:03 +0100
Subject: [PATCH 4/7] add comment

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
 src/stores/RoomViewStore.tsx | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx
index ea1f3e04c9..6a6468d5d0 100644
--- a/src/stores/RoomViewStore.tsx
+++ b/src/stores/RoomViewStore.tsx
@@ -99,6 +99,7 @@ class RoomViewStore extends Store<ActionPayload> {
             case 'view_room':
                 this.viewRoom(payload);
                 break;
+            // for these events blank out the roomId as we are no longer in the RoomView
             case 'view_create_group':
             case 'view_welcome_page':
             case 'view_home_page':

From 6f34c365370b38014bf55f0b9d3ee531f4c1df75 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 21 Jul 2020 17:56:50 +0100
Subject: [PATCH 5/7] tidy up

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
 src/@types/global.d.ts    | 2 +-
 src/ActiveRoomObserver.ts | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts
index e719da92c2..5d72061be5 100644
--- a/src/@types/global.d.ts
+++ b/src/@types/global.d.ts
@@ -41,7 +41,7 @@ declare global {
         mxRebrandListener: RebrandListener;
         mxRoomListStore: RoomListStoreClass;
         mxRoomListLayoutStore: RoomListLayoutStore;
-        mx_ActiveRoomObserver: ActiveRoomObserver;
+        mxActiveRoomObserver: ActiveRoomObserver;
         mxPlatformPeg: PlatformPeg;
         mxIntegrationManagers: typeof IntegrationManagers;
         singletonModalManager: ModalManager;
diff --git a/src/ActiveRoomObserver.ts b/src/ActiveRoomObserver.ts
index 4ff4951915..cc3f29dc42 100644
--- a/src/ActiveRoomObserver.ts
+++ b/src/ActiveRoomObserver.ts
@@ -77,7 +77,7 @@ export class ActiveRoomObserver {
     };
 }
 
-if (window.mx_ActiveRoomObserver === undefined) {
-    window.mx_ActiveRoomObserver = new ActiveRoomObserver();
+if (window.mxActiveRoomObserver === undefined) {
+    window.mxActiveRoomObserver = new ActiveRoomObserver();
 }
-export default window.mx_ActiveRoomObserver;
+export default window.mxActiveRoomObserver;

From 7faf94cd8dd8c12a2d8798e8a9246e10d0e070e3 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 21 Jul 2020 17:57:08 +0100
Subject: [PATCH 6/7] remove stale comment

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

diff --git a/src/ActiveRoomObserver.ts b/src/ActiveRoomObserver.ts
index cc3f29dc42..1126dc9496 100644
--- a/src/ActiveRoomObserver.ts
+++ b/src/ActiveRoomObserver.ts
@@ -29,7 +29,7 @@ type Listener = (isActive: boolean) => void;
  */
 export class ActiveRoomObserver {
     private listeners: {[key: string]: Listener[]} = {};
-    private _activeRoomId = RoomViewStore.getRoomId(); // TODO
+    private _activeRoomId = RoomViewStore.getRoomId();
     private readonly roomStoreToken: string;
 
     constructor() {

From 9ce96bc28db8a5f078c7808c08d970c46d683248 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 4 Aug 2020 21:35:42 +0100
Subject: [PATCH 7/7] delint

---
 src/stores/RoomViewStore.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx
index bfcbaf9029..a0f0fb8f68 100644
--- a/src/stores/RoomViewStore.tsx
+++ b/src/stores/RoomViewStore.tsx
@@ -285,7 +285,7 @@ class RoomViewStore extends Store<ActionPayload> {
                     {_t("Please contact your homeserver administrator.")}
                 </div>;
             } else if (err.httpStatus === 404) {
-                const invitingUserId = this.getInvitingUserId(this._state.roomId);
+                const invitingUserId = this.getInvitingUserId(this.state.roomId);
                 // only provide a better error message for invites
                 if (invitingUserId) {
                     // if the inviting user is on the same HS, there can only be one cause: they left.