From af2eed2228dbf7552c94fe10e970d8a4af88640d Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 19 May 2020 13:17:34 +0100
Subject: [PATCH 01/84] Fix room alias lookup vs peeking race condition
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
src/components/structures/RoomView.js | 24 ++++++++++++++++++++----
src/stores/RoomViewStore.js | 12 ++----------
2 files changed, 22 insertions(+), 14 deletions(-)
diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js
index 635597db74..8e343fe08f 100644
--- a/src/components/structures/RoomView.js
+++ b/src/components/structures/RoomView.js
@@ -164,6 +164,8 @@ export default createReactClass({
canReact: false,
canReply: false,
+
+ matrixClientIsReady: this.context && this.context.isInitialSyncComplete(),
};
},
@@ -232,7 +234,8 @@ export default createReactClass({
initialEventId: RoomViewStore.getInitialEventId(),
isInitialEventHighlighted: RoomViewStore.isInitialEventHighlighted(),
forwardingEvent: RoomViewStore.getForwardingEvent(),
- shouldPeek: RoomViewStore.shouldPeek(),
+ // we should only peek once we have a ready client
+ shouldPeek: this.state.matrixClientIsReady && RoomViewStore.shouldPeek(),
showingPinned: SettingsStore.getValue("PinnedEvents.isOpen", roomId),
showReadReceipts: SettingsStore.getValue("showReadReceipts", roomId),
};
@@ -681,6 +684,16 @@ export default createReactClass({
});
}
break;
+ case 'sync_state':
+ if (!this.state.matrixClientIsReady) {
+ this.setState({
+ matrixClientIsReady: this.context && this.context.isInitialSyncComplete(),
+ }, () => {
+ // send another "initial" RVS update to trigger peeking if needed
+ this._onRoomViewStoreUpdate(true);
+ });
+ }
+ break;
}
},
@@ -1663,14 +1676,16 @@ export default createReactClass({
const ErrorBoundary = sdk.getComponent("elements.ErrorBoundary");
if (!this.state.room) {
- const loading = this.state.roomLoading || this.state.peekLoading;
+ const loading = this.state.matrixClientIsReady || this.state.roomLoading || this.state.peekLoading;
if (loading) {
+ // Assume preview loading if we don't have a ready client or a room ID (still resolving the alias)
+ const previewLoading = !this.state.matrixClientIsReady || !this.state.roomId || this.state.peekLoading;
return (
-
Date: Fri, 22 May 2020 11:18:14 +0100
Subject: [PATCH 02/84] Fix typo and improve error context
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
src/components/structures/RoomView.js | 2 +-
src/stores/RoomViewStore.js | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js
index 8e343fe08f..39cd497098 100644
--- a/src/components/structures/RoomView.js
+++ b/src/components/structures/RoomView.js
@@ -1676,7 +1676,7 @@ export default createReactClass({
const ErrorBoundary = sdk.getComponent("elements.ErrorBoundary");
if (!this.state.room) {
- const loading = this.state.matrixClientIsReady || this.state.roomLoading || this.state.peekLoading;
+ const loading = !this.state.matrixClientIsReady || this.state.roomLoading || this.state.peekLoading;
if (loading) {
// Assume preview loading if we don't have a ready client or a room ID (still resolving the alias)
const previewLoading = !this.state.matrixClientIsReady || !this.state.roomId || this.state.peekLoading;
diff --git a/src/stores/RoomViewStore.js b/src/stores/RoomViewStore.js
index 1958b9539f..a38445c89b 100644
--- a/src/stores/RoomViewStore.js
+++ b/src/stores/RoomViewStore.js
@@ -215,7 +215,7 @@ class RoomViewStore extends Store {
storeRoomAliasInCache(payload.room_alias, result.room_id);
roomId = result.room_id;
} catch (err) {
- console.error(err);
+ console.error("RVS failed to get room id for alias: ", err);
dis.dispatch({
action: 'view_room_error',
room_id: null,
From dc37469808d23000f3a7c9b6f184b07a31677379 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 22 May 2020 12:47:40 +0100
Subject: [PATCH 03/84] Convert ToastContainer and ToastStore to Typescript
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
package.json | 1 +
src/@types/global.d.ts | 3 +
src/components/structures/MatrixChat.tsx | 2 +-
.../{ToastContainer.js => ToastContainer.tsx} | 16 ++--
src/stores/ToastStore.js | 73 ------------------
src/stores/ToastStore.ts | 77 +++++++++++++++++++
yarn.lock | 5 ++
7 files changed, 98 insertions(+), 79 deletions(-)
rename src/components/structures/{ToastContainer.js => ToastContainer.tsx} (87%)
delete mode 100644 src/stores/ToastStore.js
create mode 100644 src/stores/ToastStore.ts
diff --git a/package.json b/package.json
index 193fb86218..440fe3beac 100644
--- a/package.json
+++ b/package.json
@@ -120,6 +120,7 @@
"@types/classnames": "^2.2.10",
"@types/flux": "^3.1.9",
"@types/modernizr": "^3.5.3",
+ "@types/node": "^12.12.41",
"@types/qrcode": "^1.3.4",
"@types/react": "16.9",
"@types/zxcvbn": "^4.4.0",
diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts
index e6e339d067..6c62000143 100644
--- a/src/@types/global.d.ts
+++ b/src/@types/global.d.ts
@@ -15,6 +15,7 @@ limitations under the License.
*/
import * as ModernizrStatic from "modernizr";
+import ToastStore from "../stores/ToastStore";
declare global {
interface Window {
@@ -22,6 +23,8 @@ declare global {
Olm: {
init: () => Promise;
};
+
+ mx_ToastStore: ToastStore;
}
// workaround for https://github.com/microsoft/TypeScript/issues/30933
diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx
index 48dc72f4fa..89db30c7b4 100644
--- a/src/components/structures/MatrixChat.tsx
+++ b/src/components/structures/MatrixChat.tsx
@@ -1559,7 +1559,7 @@ export default class MatrixChat extends React.PureComponent {
icon: "verification",
props: {request},
component: sdk.getComponent("toasts.VerificationRequestToast"),
- priority: ToastStore.PRIORITY_REALTIME,
+ priority: 95,
});
}
});
diff --git a/src/components/structures/ToastContainer.js b/src/components/structures/ToastContainer.tsx
similarity index 87%
rename from src/components/structures/ToastContainer.js
rename to src/components/structures/ToastContainer.tsx
index 283fbdd96a..9440aa3463 100644
--- a/src/components/structures/ToastContainer.js
+++ b/src/components/structures/ToastContainer.tsx
@@ -16,13 +16,19 @@ limitations under the License.
import * as React from "react";
import { _t } from '../../languageHandler';
-import ToastStore from "../../stores/ToastStore";
+import ToastStore, {IToast} from "../../stores/ToastStore";
import classNames from "classnames";
-export default class ToastContainer extends React.Component {
- constructor() {
- super();
- this.state = {toasts: ToastStore.sharedInstance().getToasts()};
+interface IState {
+ toasts: IToast[];
+}
+
+export default class ToastContainer extends React.Component<{}, IState> {
+ constructor(props, context) {
+ super(props, context);
+ this.state = {
+ toasts: ToastStore.sharedInstance().getToasts(),
+ };
// Start listening here rather than in componentDidMount because
// toasts may dismiss themselves in their didMount if they find
diff --git a/src/stores/ToastStore.js b/src/stores/ToastStore.js
deleted file mode 100644
index 8901736739..0000000000
--- a/src/stores/ToastStore.js
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
-Copyright 2020 The Matrix.org Foundation C.I.C.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-import EventEmitter from 'events';
-
-/**
- * Holds the active toasts
- */
-export default class ToastStore extends EventEmitter {
- static PRIORITY_REALTIME = 0;
- static PRIORITY_DEFAULT = 1;
- static PRIORITY_LOW = 2;
-
- static sharedInstance() {
- if (!global.mx_ToastStore) global.mx_ToastStore = new ToastStore();
- return global.mx_ToastStore;
- }
-
- constructor() {
- super();
- this._dispatcherRef = null;
- this._toasts = [];
- }
-
- reset() {
- this._toasts = [];
- }
-
- /**
- * Add or replace a toast
- * If a toast with the same toastKey already exists, the given toast will replace it
- * Toasts are always added underneath any toasts of the same priority, so existing
- * toasts stay at the top unless a higher priority one arrives (better to not change the
- * toast unless necessary).
- *
- * @param {boject} newToast The new toast
- */
- addOrReplaceToast(newToast) {
- if (newToast.priority === undefined) newToast.priority = ToastStore.PRIORITY_DEFAULT;
-
- const oldIndex = this._toasts.findIndex(t => t.key === newToast.key);
- if (oldIndex === -1) {
- let newIndex = this._toasts.length;
- while (newIndex > 0 && this._toasts[newIndex - 1].priority > newToast.priority) --newIndex;
- this._toasts.splice(newIndex, 0, newToast);
- } else {
- this._toasts[oldIndex] = newToast;
- }
- this.emit('update');
- }
-
- dismissToast(key) {
- this._toasts = this._toasts.filter(t => t.key !== key);
- this.emit('update');
- }
-
- getToasts() {
- return this._toasts;
- }
-}
diff --git a/src/stores/ToastStore.ts b/src/stores/ToastStore.ts
new file mode 100644
index 0000000000..4f6d2963c5
--- /dev/null
+++ b/src/stores/ToastStore.ts
@@ -0,0 +1,77 @@
+/*
+Copyright 2020 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import EventEmitter from "events";
+import React, {JSXElementConstructor} from "react";
+
+export interface IToast> {
+ key: string;
+ // higher priority number will be shown on top of lower priority
+ priority: number;
+ title: string;
+ icon?: string;
+ component: C;
+ props?: React.ComponentProps;
+}
+
+/**
+ * Holds the active toasts
+ */
+export default class ToastStore extends EventEmitter {
+ private toasts: IToast[] = [];
+
+ static sharedInstance() {
+ if (!window.mx_ToastStore) window.mx_ToastStore = new ToastStore();
+ return window.mx_ToastStore;
+ }
+
+ reset() {
+ this.toasts = [];
+ }
+
+ /**
+ * Add or replace a toast
+ * If a toast with the same toastKey already exists, the given toast will replace it
+ * Toasts are always added underneath any toasts of the same priority, so existing
+ * toasts stay at the top unless a higher priority one arrives (better to not change the
+ * toast unless necessary).
+ *
+ * @param {object} newToast The new toast
+ */
+ addOrReplaceToast>(newToast: IToast) {
+ const oldIndex = this.toasts.findIndex(t => t.key === newToast.key);
+ if (oldIndex === -1) {
+ let newIndex = this.toasts.length;
+ while (newIndex > 0 && this.toasts[newIndex - 1].priority > newToast.priority) --newIndex;
+ this.toasts.splice(newIndex, 0, newToast);
+ } else {
+ this.toasts[oldIndex] = newToast;
+ }
+ this.emit('update');
+ }
+
+ dismissToast(key) {
+ const length = this.toasts.length;
+ this.toasts = this.toasts.filter(t => t.key !== key);
+ if (length !== this.toasts.length) {
+ this.emit('update');
+ }
+ }
+
+ getToasts() {
+ return this.toasts;
+ }
+}
diff --git a/yarn.lock b/yarn.lock
index 6cdc771c5b..340d0b4454 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1280,6 +1280,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.0.tgz#390ea202539c61c8fa6ba4428b57e05bc36dc47b"
integrity sha512-uM4mnmsIIPK/yeO+42F2RQhGUIs39K2RFmugcJANppXe6J1nvH87PvzPZYpza7Xhhs8Yn9yIAVdLZ84z61+0xQ==
+"@types/node@^12.12.41":
+ version "12.12.42"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.42.tgz#d0d1149336bd07540dd1ea576692829d575dec34"
+ integrity sha512-R/9QdYFLL9dE9l5cWWzWIZByVGFd7lk7JVOJ7KD+E1SJ4gni7XJRLz9QTjyYQiHIqEAgku9VgxdLjMlhhUaAFg==
+
"@types/prop-types@*":
version "15.7.3"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
From 3bf5e003a154ff54704f5873ff81d51688d60052 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 22 May 2020 12:54:03 +0100
Subject: [PATCH 04/84] Convert DeviceListener to Typescript
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
src/@types/global.d.ts | 2 +
src/{DeviceListener.js => DeviceListener.ts} | 91 +++++++++-----------
2 files changed, 45 insertions(+), 48 deletions(-)
rename src/{DeviceListener.js => DeviceListener.ts} (81%)
diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts
index 6c62000143..bcf08b26bb 100644
--- a/src/@types/global.d.ts
+++ b/src/@types/global.d.ts
@@ -16,6 +16,7 @@ limitations under the License.
import * as ModernizrStatic from "modernizr";
import ToastStore from "../stores/ToastStore";
+import DeviceListener from "../DeviceListener";
declare global {
interface Window {
@@ -25,6 +26,7 @@ declare global {
};
mx_ToastStore: ToastStore;
+ mx_DeviceListener: DeviceListener;
}
// workaround for https://github.com/microsoft/TypeScript/issues/30933
diff --git a/src/DeviceListener.js b/src/DeviceListener.ts
similarity index 81%
rename from src/DeviceListener.js
rename to src/DeviceListener.ts
index 27caba971e..ce1f781dad 100644
--- a/src/DeviceListener.js
+++ b/src/DeviceListener.ts
@@ -29,28 +29,23 @@ function toastKey(deviceId) {
}
export default class DeviceListener {
+ // device IDs for which the user has dismissed the verify toast ('Later')
+ private dismissed = new Set();
+ // has the user dismissed any of the various nag toasts to setup encryption on this device?
+ private dismissedThisDeviceToast = false;
+ // cache of the key backup info
+ private keyBackupInfo: object = null;
+ private keyBackupFetchedAt: number = null;
+ // We keep a list of our own device IDs so we can batch ones that were already
+ // there the last time the app launched into a single toast, but display new
+ // ones in their own toasts.
+ private ourDeviceIdsAtStart: Set = null;
+ // The set of device IDs we're currently displaying toasts for
+ private displayingToastsForDeviceIds = new Set();
+
static sharedInstance() {
- if (!global.mx_DeviceListener) global.mx_DeviceListener = new DeviceListener();
- return global.mx_DeviceListener;
- }
-
- constructor() {
- // device IDs for which the user has dismissed the verify toast ('Later')
- this._dismissed = new Set();
- // has the user dismissed any of the various nag toasts to setup encryption on this device?
- this._dismissedThisDeviceToast = false;
-
- // cache of the key backup info
- this._keyBackupInfo = null;
- this._keyBackupFetchedAt = null;
-
- // We keep a list of our own device IDs so we can batch ones that were already
- // there the last time the app launched into a single toast, but display new
- // ones in their own toasts.
- this._ourDeviceIdsAtStart = null;
-
- // The set of device IDs we're currently displaying toasts for
- this._displayingToastsForDeviceIds = new Set();
+ if (!window.mx_DeviceListener) window.mx_DeviceListener = new DeviceListener();
+ return window.mx_DeviceListener;
}
start() {
@@ -74,12 +69,12 @@ export default class DeviceListener {
MatrixClientPeg.get().removeListener('accountData', this._onAccountData);
MatrixClientPeg.get().removeListener('sync', this._onSync);
}
- this._dismissed.clear();
- this._dismissedThisDeviceToast = false;
- this._keyBackupInfo = null;
- this._keyBackupFetchedAt = null;
- this._ourDeviceIdsAtStart = null;
- this._displayingToastsForDeviceIds = new Set();
+ this.dismissed.clear();
+ this.dismissedThisDeviceToast = false;
+ this.keyBackupInfo = null;
+ this.keyBackupFetchedAt = null;
+ this.ourDeviceIdsAtStart = null;
+ this.displayingToastsForDeviceIds = new Set();
}
/**
@@ -87,29 +82,29 @@ export default class DeviceListener {
*
* @param {String[]} deviceIds List of device IDs to dismiss notifications for
*/
- async dismissUnverifiedSessions(deviceIds) {
+ async dismissUnverifiedSessions(deviceIds: string[]) {
for (const d of deviceIds) {
- this._dismissed.add(d);
+ this.dismissed.add(d);
}
this._recheck();
}
dismissEncryptionSetup() {
- this._dismissedThisDeviceToast = true;
+ this.dismissedThisDeviceToast = true;
this._recheck();
}
_ensureDeviceIdsAtStartPopulated() {
- if (this._ourDeviceIdsAtStart === null) {
+ if (this.ourDeviceIdsAtStart === null) {
const cli = MatrixClientPeg.get();
- this._ourDeviceIdsAtStart = new Set(
+ this.ourDeviceIdsAtStart = new Set(
cli.getStoredDevicesForUser(cli.getUserId()).map(d => d.deviceId),
);
}
}
- _onWillUpdateDevices = async (users, initialFetch) => {
+ _onWillUpdateDevices = async (users: string[], initialFetch?: boolean) => {
// If we didn't know about *any* devices before (ie. it's fresh login),
// then they are all pre-existing devices, so ignore this and set the
// devicesAtStart list to the devices that we see after the fetch.
@@ -122,17 +117,17 @@ export default class DeviceListener {
// before we download any new ones.
}
- _onDevicesUpdated = (users) => {
+ _onDevicesUpdated = (users: string[]) => {
if (!users.includes(MatrixClientPeg.get().getUserId())) return;
this._recheck();
}
- _onDeviceVerificationChanged = (userId) => {
+ _onDeviceVerificationChanged = (userId: string) => {
if (userId !== MatrixClientPeg.get().getUserId()) return;
this._recheck();
}
- _onUserTrustStatusChanged = (userId, trustLevel) => {
+ _onUserTrustStatusChanged = (userId: string) => {
if (userId !== MatrixClientPeg.get().getUserId()) return;
this._recheck();
}
@@ -163,11 +158,11 @@ export default class DeviceListener {
// & cache the result
async _getKeyBackupInfo() {
const now = (new Date()).getTime();
- if (!this._keyBackupInfo || this._keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL) {
- this._keyBackupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
- this._keyBackupFetchedAt = now;
+ if (!this.keyBackupInfo || this.keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL) {
+ this.keyBackupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
+ this.keyBackupFetchedAt = now;
}
- return this._keyBackupInfo;
+ return this.keyBackupInfo;
}
async _recheck() {
@@ -186,7 +181,7 @@ export default class DeviceListener {
const crossSigningReady = await cli.isCrossSigningReady();
- if (this._dismissedThisDeviceToast) {
+ if (this.dismissedThisDeviceToast) {
ToastStore.sharedInstance().dismissToast(THIS_DEVICE_TOAST_KEY);
} else {
if (!crossSigningReady) {
@@ -239,20 +234,20 @@ export default class DeviceListener {
// (technically could just be a boolean: we don't actually
// need to remember the device IDs, but for the sake of
// symmetry...).
- const oldUnverifiedDeviceIds = new Set();
+ const oldUnverifiedDeviceIds = new Set();
// Unverified devices that have appeared since then
- const newUnverifiedDeviceIds = new Set();
+ const newUnverifiedDeviceIds = new Set();
// as long as cross-signing isn't ready,
// you can't see or dismiss any device toasts
if (crossSigningReady) {
const devices = cli.getStoredDevicesForUser(cli.getUserId());
for (const device of devices) {
- if (device.deviceId == cli.deviceId) continue;
+ if (device.deviceId === cli.deviceId) continue;
const deviceTrust = await cli.checkDeviceTrust(cli.getUserId(), device.deviceId);
- if (!deviceTrust.isCrossSigningVerified() && !this._dismissed.has(device.deviceId)) {
- if (this._ourDeviceIdsAtStart.has(device.deviceId)) {
+ if (!deviceTrust.isCrossSigningVerified() && !this.dismissed.has(device.deviceId)) {
+ if (this.ourDeviceIdsAtStart.has(device.deviceId)) {
oldUnverifiedDeviceIds.add(device.deviceId);
} else {
newUnverifiedDeviceIds.add(device.deviceId);
@@ -289,12 +284,12 @@ export default class DeviceListener {
}
// ...and hide any we don't need any more
- for (const deviceId of this._displayingToastsForDeviceIds) {
+ for (const deviceId of this.displayingToastsForDeviceIds) {
if (!newUnverifiedDeviceIds.has(deviceId)) {
ToastStore.sharedInstance().dismissToast(toastKey(deviceId));
}
}
- this._displayingToastsForDeviceIds = newUnverifiedDeviceIds;
+ this.displayingToastsForDeviceIds = newUnverifiedDeviceIds;
}
}
From b21e5ba10b313ebc492b2241d808f917814a5706 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 22 May 2020 12:57:48 +0100
Subject: [PATCH 05/84] Set new granular priorities
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
src/DeviceListener.ts | 6 +++++-
src/components/structures/MatrixChat.tsx | 2 +-
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/src/DeviceListener.ts b/src/DeviceListener.ts
index ce1f781dad..1ecaca9b40 100644
--- a/src/DeviceListener.ts
+++ b/src/DeviceListener.ts
@@ -197,6 +197,7 @@ export default class DeviceListener {
icon: "verification_warning",
props: {kind: 'verify_this_session'},
component: sdk.getComponent("toasts.SetupEncryptionToast"),
+ priority: 95,
});
} else {
const backupInfo = await this._getKeyBackupInfo();
@@ -208,6 +209,7 @@ export default class DeviceListener {
icon: "verification_warning",
props: {kind: 'upgrade_encryption'},
component: sdk.getComponent("toasts.SetupEncryptionToast"),
+ priority: 40,
});
} else {
// No cross-signing or key backup on account (set up encryption)
@@ -217,6 +219,7 @@ export default class DeviceListener {
icon: "verification_warning",
props: {kind: 'set_up_encryption'},
component: sdk.getComponent("toasts.SetupEncryptionToast"),
+ priority: 40,
});
}
}
@@ -262,11 +265,11 @@ export default class DeviceListener {
key: OTHER_DEVICES_TOAST_KEY,
title: _t("Review where you’re logged in"),
icon: "verification_warning",
- priority: ToastStore.PRIORITY_LOW,
props: {
deviceIds: oldUnverifiedDeviceIds,
},
component: sdk.getComponent("toasts.BulkUnverifiedSessionsToast"),
+ priority: 50,
});
} else {
ToastStore.sharedInstance().dismissToast(OTHER_DEVICES_TOAST_KEY);
@@ -280,6 +283,7 @@ export default class DeviceListener {
icon: "verification_warning",
props: { deviceId },
component: sdk.getComponent("toasts.UnverifiedSessionToast"),
+ priority: 80,
});
}
diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx
index 89db30c7b4..e6a56c683f 100644
--- a/src/components/structures/MatrixChat.tsx
+++ b/src/components/structures/MatrixChat.tsx
@@ -1559,7 +1559,7 @@ export default class MatrixChat extends React.PureComponent {
icon: "verification",
props: {request},
component: sdk.getComponent("toasts.VerificationRequestToast"),
- priority: 95,
+ priority: 90,
});
}
});
From 14cee413603ce4fc36bdf1ac2f2b0551fe6b4516 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 22 May 2020 13:29:53 +0100
Subject: [PATCH 06/84] Convert things to Typescript and re-use a generic
component
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
src/DeviceListener.ts | 111 ++++++------------
.../toasts/BulkUnverifiedSessionsToast.js | 56 ---------
src/components/views/toasts/GenericToast.tsx | 42 +++++++
.../views/toasts/SetupEncryptionToast.js | 88 --------------
.../views/toasts/UnverifiedSessionToast.js | 66 -----------
...tToast.js => VerificationRequestToast.tsx} | 48 +++++---
src/toasts/BulkUnverifiedSessionsToast.ts | 58 +++++++++
src/toasts/SetupEncryptionToast.ts | 106 +++++++++++++++++
src/toasts/UnverifiedSessionToast.ts | 70 +++++++++++
9 files changed, 342 insertions(+), 303 deletions(-)
delete mode 100644 src/components/views/toasts/BulkUnverifiedSessionsToast.js
create mode 100644 src/components/views/toasts/GenericToast.tsx
delete mode 100644 src/components/views/toasts/SetupEncryptionToast.js
delete mode 100644 src/components/views/toasts/UnverifiedSessionToast.js
rename src/components/views/toasts/{VerificationRequestToast.js => VerificationRequestToast.tsx} (86%)
create mode 100644 src/toasts/BulkUnverifiedSessionsToast.ts
create mode 100644 src/toasts/SetupEncryptionToast.ts
create mode 100644 src/toasts/UnverifiedSessionToast.ts
diff --git a/src/DeviceListener.ts b/src/DeviceListener.ts
index 1ecaca9b40..ca51b5ac1c 100644
--- a/src/DeviceListener.ts
+++ b/src/DeviceListener.ts
@@ -14,19 +14,24 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import { MatrixClientPeg } from './MatrixClientPeg';
+import {MatrixClientPeg} from './MatrixClientPeg';
import SettingsStore from './settings/SettingsStore';
-import * as sdk from './index';
-import { _t } from './languageHandler';
-import ToastStore from './stores/ToastStore';
+import {
+ hideToast as hideBulkUnverifiedSessionsToast,
+ showToast as showBulkUnverifiedSessionsToast
+} from "./toasts/BulkUnverifiedSessionsToast";
+import {
+ hideToast as hideSetupEncryptionToast,
+ Kind as SetupKind,
+ Kind,
+ showToast as showSetupEncryptionToast
+} from "./toasts/SetupEncryptionToast";
+import {
+ hideToast as hideUnverifiedSessionsToast,
+ showToast as showUnverifiedSessionsToast
+} from "./toasts/UnverifiedSessionToast";
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
-const THIS_DEVICE_TOAST_KEY = 'setupencryption';
-const OTHER_DEVICES_TOAST_KEY = 'reviewsessions';
-
-function toastKey(deviceId) {
- return "unverified_session_" + deviceId;
-}
export default class DeviceListener {
// device IDs for which the user has dismissed the verify toast ('Later')
@@ -82,7 +87,7 @@ export default class DeviceListener {
*
* @param {String[]} deviceIds List of device IDs to dismiss notifications for
*/
- async dismissUnverifiedSessions(deviceIds: string[]) {
+ async dismissUnverifiedSessions(deviceIds: Iterable) {
for (const d of deviceIds) {
this.dismissed.add(d);
}
@@ -181,51 +186,25 @@ export default class DeviceListener {
const crossSigningReady = await cli.isCrossSigningReady();
- if (this.dismissedThisDeviceToast) {
- ToastStore.sharedInstance().dismissToast(THIS_DEVICE_TOAST_KEY);
+ if (this.dismissedThisDeviceToast || crossSigningReady) {
+ hideSetupEncryptionToast();
} else {
- if (!crossSigningReady) {
- // make sure our keys are finished downlaoding
- await cli.downloadKeys([cli.getUserId()]);
- // cross signing isn't enabled - nag to enable it
- // There are 3 different toasts for:
- if (cli.getStoredCrossSigningForUser(cli.getUserId())) {
- // Cross-signing on account but this device doesn't trust the master key (verify this session)
- ToastStore.sharedInstance().addOrReplaceToast({
- key: THIS_DEVICE_TOAST_KEY,
- title: _t("Verify this session"),
- icon: "verification_warning",
- props: {kind: 'verify_this_session'},
- component: sdk.getComponent("toasts.SetupEncryptionToast"),
- priority: 95,
- });
- } else {
- const backupInfo = await this._getKeyBackupInfo();
- if (backupInfo) {
- // No cross-signing on account but key backup available (upgrade encryption)
- ToastStore.sharedInstance().addOrReplaceToast({
- key: THIS_DEVICE_TOAST_KEY,
- title: _t("Encryption upgrade available"),
- icon: "verification_warning",
- props: {kind: 'upgrade_encryption'},
- component: sdk.getComponent("toasts.SetupEncryptionToast"),
- priority: 40,
- });
- } else {
- // No cross-signing or key backup on account (set up encryption)
- ToastStore.sharedInstance().addOrReplaceToast({
- key: THIS_DEVICE_TOAST_KEY,
- title: _t("Set up encryption"),
- icon: "verification_warning",
- props: {kind: 'set_up_encryption'},
- component: sdk.getComponent("toasts.SetupEncryptionToast"),
- priority: 40,
- });
- }
- }
+ // make sure our keys are finished downloading
+ await cli.downloadKeys([cli.getUserId()]);
+ // cross signing isn't enabled - nag to enable it
+ // There are 3 different toasts for:
+ if (cli.getStoredCrossSigningForUser(cli.getUserId())) {
+ // Cross-signing on account but this device doesn't trust the master key (verify this session)
+ showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION);
} else {
- // cross-signing is ready, and we don't need to upgrade encryption
- ToastStore.sharedInstance().dismissToast(THIS_DEVICE_TOAST_KEY);
+ const backupInfo = await this._getKeyBackupInfo();
+ if (backupInfo) {
+ // No cross-signing on account but key backup available (upgrade encryption)
+ showSetupEncryptionToast(Kind.UPGRADE_ENCRYPTION);
+ } else {
+ // No cross-signing or key backup on account (set up encryption)
+ showSetupEncryptionToast(Kind.SET_UP_ENCRYPTION);
+ }
}
}
@@ -261,36 +240,20 @@ export default class DeviceListener {
// Display or hide the batch toast for old unverified sessions
if (oldUnverifiedDeviceIds.size > 0) {
- ToastStore.sharedInstance().addOrReplaceToast({
- key: OTHER_DEVICES_TOAST_KEY,
- title: _t("Review where you’re logged in"),
- icon: "verification_warning",
- props: {
- deviceIds: oldUnverifiedDeviceIds,
- },
- component: sdk.getComponent("toasts.BulkUnverifiedSessionsToast"),
- priority: 50,
- });
+ showBulkUnverifiedSessionsToast(oldUnverifiedDeviceIds);
} else {
- ToastStore.sharedInstance().dismissToast(OTHER_DEVICES_TOAST_KEY);
+ hideBulkUnverifiedSessionsToast();
}
// Show toasts for new unverified devices if they aren't already there
for (const deviceId of newUnverifiedDeviceIds) {
- ToastStore.sharedInstance().addOrReplaceToast({
- key: toastKey(deviceId),
- title: _t("New login. Was this you?"),
- icon: "verification_warning",
- props: { deviceId },
- component: sdk.getComponent("toasts.UnverifiedSessionToast"),
- priority: 80,
- });
+ showUnverifiedSessionsToast(deviceId);
}
// ...and hide any we don't need any more
for (const deviceId of this.displayingToastsForDeviceIds) {
if (!newUnverifiedDeviceIds.has(deviceId)) {
- ToastStore.sharedInstance().dismissToast(toastKey(deviceId));
+ hideUnverifiedSessionsToast(deviceId);
}
}
diff --git a/src/components/views/toasts/BulkUnverifiedSessionsToast.js b/src/components/views/toasts/BulkUnverifiedSessionsToast.js
deleted file mode 100644
index 99ff529c35..0000000000
--- a/src/components/views/toasts/BulkUnverifiedSessionsToast.js
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
-Copyright 2020 The Matrix.org Foundation C.I.C.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import { _t } from '../../../languageHandler';
-import dis from "../../../dispatcher/dispatcher";
-import { MatrixClientPeg } from '../../../MatrixClientPeg';
-import DeviceListener from '../../../DeviceListener';
-import FormButton from '../elements/FormButton';
-import { replaceableComponent } from '../../../utils/replaceableComponent';
-
-@replaceableComponent("views.toasts.BulkUnverifiedSessionsToast")
-export default class BulkUnverifiedSessionsToast extends React.PureComponent {
- static propTypes = {
- deviceIds: PropTypes.array,
- }
-
- _onLaterClick = () => {
- DeviceListener.sharedInstance().dismissUnverifiedSessions(this.props.deviceIds);
- };
-
- _onReviewClick = async () => {
- DeviceListener.sharedInstance().dismissUnverifiedSessions(this.props.deviceIds);
-
- dis.dispatch({
- action: 'view_user_info',
- userId: MatrixClientPeg.get().getUserId(),
- });
- };
-
- render() {
- return (
-
- {_t("Verify all your sessions to ensure your account & messages are safe")}
-
-
-
-
-
-
);
- }
-}
diff --git a/src/components/views/toasts/GenericToast.tsx b/src/components/views/toasts/GenericToast.tsx
new file mode 100644
index 0000000000..9d69330857
--- /dev/null
+++ b/src/components/views/toasts/GenericToast.tsx
@@ -0,0 +1,42 @@
+/*
+Copyright 2020 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React, {ReactChild} from "react";
+
+import FormButton from "../elements/FormButton";
+
+interface IProps {
+ description: ReactChild;
+ acceptLabel: string;
+ rejectLabel?: string;
+
+ onAccept();
+ onReject?();
+}
+
+const GenericToast: React.FC = ({description, acceptLabel, rejectLabel, onAccept, onReject}) => {
+ return
+
+ { description }
+
+
+ {onReject && rejectLabel && }
+
+
+
;
+};
+
+export default GenericToast;
diff --git a/src/components/views/toasts/SetupEncryptionToast.js b/src/components/views/toasts/SetupEncryptionToast.js
deleted file mode 100644
index b5510e85b6..0000000000
--- a/src/components/views/toasts/SetupEncryptionToast.js
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
-Copyright 2020 The Matrix.org Foundation C.I.C.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import Modal from '../../../Modal';
-import * as sdk from "../../../index";
-import { _t } from '../../../languageHandler';
-import DeviceListener from '../../../DeviceListener';
-import SetupEncryptionDialog from "../dialogs/SetupEncryptionDialog";
-import { accessSecretStorage } from '../../../CrossSigningManager';
-
-export default class SetupEncryptionToast extends React.PureComponent {
- static propTypes = {
- toastKey: PropTypes.string.isRequired,
- kind: PropTypes.oneOf([
- 'set_up_encryption',
- 'verify_this_session',
- 'upgrade_encryption',
- ]).isRequired,
- };
-
- _onLaterClick = () => {
- DeviceListener.sharedInstance().dismissEncryptionSetup();
- };
-
- _onSetupClick = async () => {
- if (this.props.kind === "verify_this_session") {
- Modal.createTrackedDialog('Verify session', 'Verify session', SetupEncryptionDialog,
- {}, null, /* priority = */ false, /* static = */ true);
- } else {
- const Spinner = sdk.getComponent("elements.Spinner");
- const modal = Modal.createDialog(
- Spinner, null, 'mx_Dialog_spinner', /* priority */ false, /* static */ true,
- );
- try {
- await accessSecretStorage();
- } finally {
- modal.close();
- }
- }
- };
-
- getDescription() {
- switch (this.props.kind) {
- case 'set_up_encryption':
- case 'upgrade_encryption':
- return _t('Verify yourself & others to keep your chats safe');
- case 'verify_this_session':
- return _t('Other users may not trust it');
- }
- }
-
- getSetupCaption() {
- switch (this.props.kind) {
- case 'set_up_encryption':
- return _t('Set up');
- case 'upgrade_encryption':
- return _t('Upgrade');
- case 'verify_this_session':
- return _t('Verify');
- }
- }
-
- render() {
- const FormButton = sdk.getComponent("elements.FormButton");
- return (
-
{this.getDescription()}
-
-
-
-
-
);
- }
-}
diff --git a/src/components/views/toasts/UnverifiedSessionToast.js b/src/components/views/toasts/UnverifiedSessionToast.js
deleted file mode 100644
index 38cd9f20df..0000000000
--- a/src/components/views/toasts/UnverifiedSessionToast.js
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
-Copyright 2020 The Matrix.org Foundation C.I.C.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import { _t } from '../../../languageHandler';
-import { MatrixClientPeg } from '../../../MatrixClientPeg';
-import Modal from '../../../Modal';
-import DeviceListener from '../../../DeviceListener';
-import NewSessionReviewDialog from '../dialogs/NewSessionReviewDialog';
-import FormButton from '../elements/FormButton';
-import { replaceableComponent } from '../../../utils/replaceableComponent';
-
-@replaceableComponent("views.toasts.UnverifiedSessionToast")
-export default class UnverifiedSessionToast extends React.PureComponent {
- static propTypes = {
- deviceId: PropTypes.string,
- }
-
- _onLaterClick = () => {
- DeviceListener.sharedInstance().dismissUnverifiedSessions([this.props.deviceId]);
- };
-
- _onReviewClick = async () => {
- const cli = MatrixClientPeg.get();
- Modal.createTrackedDialog('New Session Review', 'Starting dialog', NewSessionReviewDialog, {
- userId: cli.getUserId(),
- device: cli.getStoredDevice(cli.getUserId(), this.props.deviceId),
- onFinished: (r) => {
- if (!r) {
- /* This'll come back false if the user clicks "this wasn't me" and saw a warning dialog */
- DeviceListener.sharedInstance().dismissUnverifiedSessions([this.props.deviceId]);
- }
- },
- }, null, /* priority = */ false, /* static = */ true);
- };
-
- render() {
- const cli = MatrixClientPeg.get();
- const device = cli.getStoredDevice(cli.getUserId(), this.props.deviceId);
-
- return (
-
- {_t(
- "Verify the new login accessing your account: %(name)s", { name: device.getDisplayName()})}
-
-
-
-
-
-
);
- }
-}
diff --git a/src/components/views/toasts/VerificationRequestToast.js b/src/components/views/toasts/VerificationRequestToast.tsx
similarity index 86%
rename from src/components/views/toasts/VerificationRequestToast.js
rename to src/components/views/toasts/VerificationRequestToast.tsx
index 421dd7bea1..38e7e31989 100644
--- a/src/components/views/toasts/VerificationRequestToast.js
+++ b/src/components/views/toasts/VerificationRequestToast.tsx
@@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from 'react';
-import PropTypes from 'prop-types';
+import React from "react";
+
import * as sdk from "../../../index";
import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
@@ -24,8 +24,23 @@ import {userLabelForEventRoom} from "../../../utils/KeyVerificationStateObserver
import dis from "../../../dispatcher/dispatcher";
import ToastStore from "../../../stores/ToastStore";
import Modal from "../../../Modal";
+import GenericToast from "./GenericToast";
+import {VerificationRequest} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
+import {DeviceInfo} from "matrix-js-sdk/src/crypto/deviceinfo";
+
+interface IProps {
+ toastKey: string;
+ request: VerificationRequest;
+}
+
+interface IState {
+ counter: number;
+ device?: DeviceInfo;
+}
+
+export default class VerificationRequestToast extends React.PureComponent {
+ private intervalHandle: NodeJS.Timeout;
-export default class VerificationRequestToast extends React.PureComponent {
constructor(props) {
super(props);
this.state = {counter: Math.ceil(props.request.timeout / 1000)};
@@ -34,7 +49,7 @@ export default class VerificationRequestToast extends React.PureComponent {
async componentDidMount() {
const {request} = this.props;
if (request.timeout && request.timeout > 0) {
- this._intervalHandle = setInterval(() => {
+ this.intervalHandle = setInterval(() => {
let {counter} = this.state;
counter = Math.max(0, counter - 1);
this.setState({counter});
@@ -56,7 +71,7 @@ export default class VerificationRequestToast extends React.PureComponent {
}
componentWillUnmount() {
- clearInterval(this._intervalHandle);
+ clearInterval(this.intervalHandle);
const {request} = this.props;
request.off("change", this._checkRequestIsPending);
}
@@ -110,7 +125,6 @@ export default class VerificationRequestToast extends React.PureComponent {
};
render() {
- const FormButton = sdk.getComponent("elements.FormButton");
const {request} = this.props;
let nameLabel;
if (request.isSelfVerification) {
@@ -133,20 +147,16 @@ export default class VerificationRequestToast extends React.PureComponent {
}
}
}
- const declineLabel = this.state.counter == 0 ?
+ const declineLabel = this.state.counter === 0 ?
_t("Decline") :
_t("Decline (%(counter)s)", {counter: this.state.counter});
- return (
-
{nameLabel}
-
-
-
-
-
);
+
+ return ;
}
}
-
-VerificationRequestToast.propTypes = {
- request: PropTypes.object.isRequired,
- toastKey: PropTypes.string.isRequired,
-};
diff --git a/src/toasts/BulkUnverifiedSessionsToast.ts b/src/toasts/BulkUnverifiedSessionsToast.ts
new file mode 100644
index 0000000000..41717e0804
--- /dev/null
+++ b/src/toasts/BulkUnverifiedSessionsToast.ts
@@ -0,0 +1,58 @@
+/*
+Copyright 2020 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { _t } from '../languageHandler';
+import dis from "../dispatcher/dispatcher";
+import { MatrixClientPeg } from '../MatrixClientPeg';
+import DeviceListener from '../DeviceListener';
+import GenericToast from "../components/views/toasts/GenericToast";
+import ToastStore from "../stores/ToastStore";
+
+const TOAST_KEY = "reviewsessions";
+
+export const showToast = (deviceIds: Set) => {
+ const onAccept = () => {
+ DeviceListener.sharedInstance().dismissUnverifiedSessions(deviceIds);
+
+ dis.dispatch({
+ action: 'view_user_info',
+ userId: MatrixClientPeg.get().getUserId(),
+ });
+ };
+
+ const onReject = () => {
+ DeviceListener.sharedInstance().dismissUnverifiedSessions(deviceIds);
+ };
+
+ ToastStore.sharedInstance().addOrReplaceToast({
+ key: TOAST_KEY,
+ title: _t("Review where you’re logged in"),
+ icon: "verification_warning",
+ props: {
+ description: _t("Verify all your sessions to ensure your account & messages are safe"),
+ acceptLabel: _t("Review"),
+ onAccept,
+ rejectLabel: _t("Later"),
+ onReject,
+ },
+ component: GenericToast,
+ priority: 50,
+ });
+};
+
+export const hideToast = () => {
+ ToastStore.sharedInstance().dismissToast(TOAST_KEY);
+};
diff --git a/src/toasts/SetupEncryptionToast.ts b/src/toasts/SetupEncryptionToast.ts
new file mode 100644
index 0000000000..d35bbf1c88
--- /dev/null
+++ b/src/toasts/SetupEncryptionToast.ts
@@ -0,0 +1,106 @@
+/*
+Copyright 2020 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import Modal from "../Modal";
+import * as sdk from "../index";
+import { _t } from "../languageHandler";
+import DeviceListener from "../DeviceListener";
+import SetupEncryptionDialog from "../components/views/dialogs/SetupEncryptionDialog";
+import { accessSecretStorage } from "../CrossSigningManager";
+import ToastStore from "../stores/ToastStore";
+import GenericToast from "../components/views/toasts/GenericToast";
+
+const TOAST_KEY = "setupencryption";
+
+const getTitle = (kind: Kind) => {
+ switch (kind) {
+ case Kind.SET_UP_ENCRYPTION:
+ return _t("Set up encryption");
+ case Kind.UPGRADE_ENCRYPTION:
+ return _t("Encryption upgrade available");
+ case Kind.VERIFY_THIS_SESSION:
+ return _t("Verify this session");
+ }
+};
+
+const getSetupCaption = (kind: Kind) => {
+ switch (kind) {
+ case Kind.SET_UP_ENCRYPTION:
+ return _t("Set up");
+ case Kind.UPGRADE_ENCRYPTION:
+ return _t("Upgrade");
+ case Kind.VERIFY_THIS_SESSION:
+ return _t("Verify");
+ }
+};
+
+const getDescription = (kind: Kind) => {
+ switch (kind) {
+ case Kind.SET_UP_ENCRYPTION:
+ case Kind.UPGRADE_ENCRYPTION:
+ return _t("Verify yourself & others to keep your chats safe");
+ case Kind.VERIFY_THIS_SESSION:
+ return _t("Other users may not trust it");
+ }
+};
+
+export enum Kind {
+ SET_UP_ENCRYPTION = "set_up_encryption",
+ UPGRADE_ENCRYPTION = "upgrade_encryption",
+ VERIFY_THIS_SESSION = "verify_this_session",
+}
+
+const onReject = () => {
+ DeviceListener.sharedInstance().dismissEncryptionSetup();
+};
+
+export const showToast = (kind: Kind) => {
+ const onAccept = async () => {
+ if (kind === Kind.VERIFY_THIS_SESSION) {
+ Modal.createTrackedDialog("Verify session", "Verify session", SetupEncryptionDialog,
+ {}, null, /* priority = */ false, /* static = */ true);
+ } else {
+ const Spinner = sdk.getComponent("elements.Spinner");
+ const modal = Modal.createDialog(
+ Spinner, null, "mx_Dialog_spinner", /* priority */ false, /* static */ true,
+ );
+ try {
+ await accessSecretStorage();
+ } finally {
+ modal.close();
+ }
+ }
+ };
+
+ ToastStore.sharedInstance().addOrReplaceToast({
+ key: TOAST_KEY,
+ title: getTitle(kind),
+ icon: "verification_warning",
+ props: {
+ description: getDescription(kind),
+ acceptLabel: getSetupCaption(kind),
+ onAccept,
+ rejectLabel: _t("Later"),
+ onReject,
+ },
+ component: GenericToast,
+ priority: kind === Kind.VERIFY_THIS_SESSION ? 95 : 40,
+ });
+};
+
+export const hideToast = () => {
+ ToastStore.sharedInstance().dismissToast(TOAST_KEY);
+};
diff --git a/src/toasts/UnverifiedSessionToast.ts b/src/toasts/UnverifiedSessionToast.ts
new file mode 100644
index 0000000000..635356b9db
--- /dev/null
+++ b/src/toasts/UnverifiedSessionToast.ts
@@ -0,0 +1,70 @@
+/*
+Copyright 2020 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { _t } from '../languageHandler';
+import { MatrixClientPeg } from '../MatrixClientPeg';
+import Modal from '../Modal';
+import DeviceListener from '../DeviceListener';
+import NewSessionReviewDialog from '../components/views/dialogs/NewSessionReviewDialog';
+import ToastStore from "../stores/ToastStore";
+import GenericToast from "../components/views/toasts/GenericToast";
+
+function toastKey(deviceId: string) {
+ return "unverified_session_" + deviceId;
+}
+
+export const showToast = (deviceId: string) => {
+ const cli = MatrixClientPeg.get();
+
+ const onAccept = () => {
+ Modal.createTrackedDialog('New Session Review', 'Starting dialog', NewSessionReviewDialog, {
+ userId: cli.getUserId(),
+ device: cli.getStoredDevice(cli.getUserId(), deviceId),
+ onFinished: (r) => {
+ if (!r) {
+ /* This'll come back false if the user clicks "this wasn't me" and saw a warning dialog */
+ DeviceListener.sharedInstance().dismissUnverifiedSessions([deviceId]);
+ }
+ },
+ }, null, /* priority = */ false, /* static = */ true);
+ };
+
+ const onReject = () => {
+ DeviceListener.sharedInstance().dismissUnverifiedSessions([deviceId]);
+ };
+
+ const device = cli.getStoredDevice(cli.getUserId(), deviceId);
+
+ ToastStore.sharedInstance().addOrReplaceToast({
+ key: toastKey(deviceId),
+ title: _t("New login. Was this you?"),
+ icon: "verification_warning",
+ props: {
+ description: _t(
+ "Verify the new login accessing your account: %(name)s", { name: device.getDisplayName()}),
+ acceptLabel: _t("Verify"),
+ onAccept,
+ rejectLabel: _t("Later"),
+ onReject,
+ },
+ component: GenericToast,
+ priority: 80,
+ });
+};
+
+export const hideToast = (deviceId: string) => {
+ ToastStore.sharedInstance().dismissToast(deviceId);
+};
From a822ab49d5e9dbc2ead9d3a828b211bce3d4e053 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 22 May 2020 13:40:30 +0100
Subject: [PATCH 07/84] i18n
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
src/i18n/strings/en_EN.json | 28 ++++++++++++++--------------
1 file changed, 14 insertions(+), 14 deletions(-)
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 96ccf1589d..8ac05bf429 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -102,11 +102,6 @@
"%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(time)s",
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s",
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s",
- "Verify this session": "Verify this session",
- "Encryption upgrade available": "Encryption upgrade available",
- "Set up encryption": "Set up encryption",
- "Review where you’re logged in": "Review where you’re logged in",
- "New login. Was this you?": "New login. Was this you?",
"Who would you like to add to this community?": "Who would you like to add to this community?",
"Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID",
"Invite new community members": "Invite new community members",
@@ -396,6 +391,20 @@
"Common names and surnames are easy to guess": "Common names and surnames are easy to guess",
"Straight rows of keys are easy to guess": "Straight rows of keys are easy to guess",
"Short keyboard patterns are easy to guess": "Short keyboard patterns are easy to guess",
+ "Review where you’re logged in": "Review where you’re logged in",
+ "Verify all your sessions to ensure your account & messages are safe": "Verify all your sessions to ensure your account & messages are safe",
+ "Review": "Review",
+ "Later": "Later",
+ "Set up encryption": "Set up encryption",
+ "Encryption upgrade available": "Encryption upgrade available",
+ "Verify this session": "Verify this session",
+ "Set up": "Set up",
+ "Upgrade": "Upgrade",
+ "Verify": "Verify",
+ "Verify yourself & others to keep your chats safe": "Verify yourself & others to keep your chats safe",
+ "Other users may not trust it": "Other users may not trust it",
+ "New login. Was this you?": "New login. Was this you?",
+ "Verify the new login accessing your account: %(name)s": "Verify the new login accessing your account: %(name)s",
"There was an error joining the room": "There was an error joining the room",
"Sorry, your homeserver is too old to participate in this room.": "Sorry, your homeserver is too old to participate in this room.",
"Please contact your homeserver administrator.": "Please contact your homeserver administrator.",
@@ -570,15 +579,6 @@
"Headphones": "Headphones",
"Folder": "Folder",
"Pin": "Pin",
- "Verify all your sessions to ensure your account & messages are safe": "Verify all your sessions to ensure your account & messages are safe",
- "Later": "Later",
- "Review": "Review",
- "Verify yourself & others to keep your chats safe": "Verify yourself & others to keep your chats safe",
- "Other users may not trust it": "Other users may not trust it",
- "Set up": "Set up",
- "Upgrade": "Upgrade",
- "Verify": "Verify",
- "Verify the new login accessing your account: %(name)s": "Verify the new login accessing your account: %(name)s",
"From %(deviceName)s (%(deviceId)s)": "From %(deviceName)s (%(deviceId)s)",
"Decline (%(counter)s)": "Decline (%(counter)s)",
"Accept to continue:": "Accept to continue:",
From c464abaa498df44ea6d0366c64eec6be7afa4556 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 22 May 2020 14:28:01 +0100
Subject: [PATCH 08/84] Iterate toast count indicator more logically
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
src/components/structures/ToastContainer.tsx | 14 +++++++++++---
src/stores/ToastStore.ts | 14 ++++++++++++++
2 files changed, 25 insertions(+), 3 deletions(-)
diff --git a/src/components/structures/ToastContainer.tsx b/src/components/structures/ToastContainer.tsx
index 9440aa3463..c7b8e911d3 100644
--- a/src/components/structures/ToastContainer.tsx
+++ b/src/components/structures/ToastContainer.tsx
@@ -15,12 +15,12 @@ limitations under the License.
*/
import * as React from "react";
-import { _t } from '../../languageHandler';
import ToastStore, {IToast} from "../../stores/ToastStore";
import classNames from "classnames";
interface IState {
toasts: IToast[];
+ countSeen: number;
}
export default class ToastContainer extends React.Component<{}, IState> {
@@ -28,6 +28,7 @@ export default class ToastContainer extends React.Component<{}, IState> {
super(props, context);
this.state = {
toasts: ToastStore.sharedInstance().getToasts(),
+ countSeen: 0,
};
// Start listening here rather than in componentDidMount because
@@ -42,7 +43,10 @@ export default class ToastContainer extends React.Component<{}, IState> {
}
_onToastStoreUpdate = () => {
- this.setState({toasts: ToastStore.sharedInstance().getToasts()});
+ this.setState({
+ toasts: ToastStore.sharedInstance().getToasts(),
+ countSeen: ToastStore.sharedInstance().getCountSeen(),
+ });
};
render() {
@@ -56,7 +60,11 @@ export default class ToastContainer extends React.Component<{}, IState> {
"mx_Toast_hasIcon": icon,
[`mx_Toast_icon_${icon}`]: icon,
});
- const countIndicator = isStacked ? _t(" (1/%(totalCount)s)", {totalCount}) : null;
+
+ let countIndicator;
+ if (isStacked || this.state.countSeen > 0) {
+ countIndicator = ` (${this.state.countSeen + 1}/${this.state.countSeen + totalCount})`;
+ }
const toastProps = Object.assign({}, props, {
key,
diff --git a/src/stores/ToastStore.ts b/src/stores/ToastStore.ts
index 4f6d2963c5..b6b6f19872 100644
--- a/src/stores/ToastStore.ts
+++ b/src/stores/ToastStore.ts
@@ -32,6 +32,9 @@ export interface IToast[] = [];
+ // The count of toasts which have been seen & dealt with in this stack
+ // where the count resets when the stack of toasts clears.
+ private countSeen: number = 0;
static sharedInstance() {
if (!window.mx_ToastStore) window.mx_ToastStore = new ToastStore();
@@ -40,6 +43,7 @@ export default class ToastStore extends EventEmitter {
reset() {
this.toasts = [];
+ this.countSeen = 0;
}
/**
@@ -67,6 +71,12 @@ export default class ToastStore extends EventEmitter {
const length = this.toasts.length;
this.toasts = this.toasts.filter(t => t.key !== key);
if (length !== this.toasts.length) {
+ if (this.toasts.length === 0) {
+ this.countSeen = 0;
+ } else {
+ this.countSeen++;
+ }
+
this.emit('update');
}
}
@@ -74,4 +84,8 @@ export default class ToastStore extends EventEmitter {
getToasts() {
return this.toasts;
}
+
+ getCountSeen() {
+ return this.countSeen;
+ }
}
From 4e67e46863877a50e040dced1447970daa967cc5 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 22 May 2020 14:29:30 +0100
Subject: [PATCH 09/84] fix countSeen
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
src/components/structures/ToastContainer.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/structures/ToastContainer.tsx b/src/components/structures/ToastContainer.tsx
index c7b8e911d3..c88c830d59 100644
--- a/src/components/structures/ToastContainer.tsx
+++ b/src/components/structures/ToastContainer.tsx
@@ -28,7 +28,7 @@ export default class ToastContainer extends React.Component<{}, IState> {
super(props, context);
this.state = {
toasts: ToastStore.sharedInstance().getToasts(),
- countSeen: 0,
+ countSeen: ToastStore.sharedInstance().getCountSeen(),
};
// Start listening here rather than in componentDidMount because
From 49c0748990b00be04520bfb85b2a79589b32cb34 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 22 May 2020 14:32:41 +0100
Subject: [PATCH 10/84] delint and i18n
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
src/i18n/strings/en_EN.json | 1 -
src/stores/ToastStore.ts | 2 +-
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 8ac05bf429..812ce826fc 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -2083,7 +2083,6 @@
"Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.",
"Tried to load a specific point in this room's timeline, but was unable to find it.": "Tried to load a specific point in this room's timeline, but was unable to find it.",
"Failed to load timeline position": "Failed to load timeline position",
- " (1/%(totalCount)s)": " (1/%(totalCount)s)",
"Guest": "Guest",
"Your profile": "Your profile",
"Uploading %(filename)s and %(count)s others|other": "Uploading %(filename)s and %(count)s others",
diff --git a/src/stores/ToastStore.ts b/src/stores/ToastStore.ts
index b6b6f19872..89b4dc2dc1 100644
--- a/src/stores/ToastStore.ts
+++ b/src/stores/ToastStore.ts
@@ -34,7 +34,7 @@ export default class ToastStore extends EventEmitter {
private toasts: IToast[] = [];
// The count of toasts which have been seen & dealt with in this stack
// where the count resets when the stack of toasts clears.
- private countSeen: number = 0;
+ private countSeen = 0;
static sharedInstance() {
if (!window.mx_ToastStore) window.mx_ToastStore = new ToastStore();
From 08fadb092c930cc25323bba29ec8f5979f4dd9e2 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 22 May 2020 21:43:15 +0100
Subject: [PATCH 11/84] Remove redundant component
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
.../settings/EnableNotificationsButton.js | 75 -------------------
1 file changed, 75 deletions(-)
delete mode 100644 src/components/views/settings/EnableNotificationsButton.js
diff --git a/src/components/views/settings/EnableNotificationsButton.js b/src/components/views/settings/EnableNotificationsButton.js
deleted file mode 100644
index e4b348dfbd..0000000000
--- a/src/components/views/settings/EnableNotificationsButton.js
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
-Copyright 2015, 2016 OpenMarket Ltd
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-import React from "react";
-import createReactClass from 'create-react-class';
-import Notifier from "../../../Notifier";
-import dis from "../../../dispatcher/dispatcher";
-import { _t } from '../../../languageHandler';
-
-export default createReactClass({
- displayName: 'EnableNotificationsButton',
-
- componentDidMount: function() {
- this.dispatcherRef = dis.register(this.onAction);
- },
-
- componentWillUnmount: function() {
- dis.unregister(this.dispatcherRef);
- },
-
- onAction: function(payload) {
- if (payload.action !== "notifier_enabled") {
- return;
- }
- this.forceUpdate();
- },
-
- enabled: function() {
- return Notifier.isEnabled();
- },
-
- onClick: function() {
- const self = this;
- if (!Notifier.supportsDesktopNotifications()) {
- return;
- }
- if (!Notifier.isEnabled()) {
- Notifier.setEnabled(true, function() {
- self.forceUpdate();
- });
- } else {
- Notifier.setEnabled(false);
- }
- this.forceUpdate();
- },
-
- render: function() {
- if (this.enabled()) {
- return (
-
- );
- } else {
- return (
-
- );
- }
- },
-});
From a977b8c4ca12570919a1cf5deceaf71652648510 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 22 May 2020 21:55:43 +0100
Subject: [PATCH 12/84] Fix lifecycle to reset things before it starts them
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
src/Lifecycle.js | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/Lifecycle.js b/src/Lifecycle.js
index 22c5d48317..5fb54ede7f 100644
--- a/src/Lifecycle.js
+++ b/src/Lifecycle.js
@@ -575,10 +575,12 @@ async function startMatrixClient(startSyncing=true) {
// to work).
dis.dispatch({action: 'will_start_client'}, true);
+ // reset things first just in case
+ TypingStore.sharedInstance().reset();
+ ToastStore.sharedInstance().reset();
+
Notifier.start();
UserActivity.sharedInstance().start();
- TypingStore.sharedInstance().reset(); // just in case
- ToastStore.sharedInstance().reset();
DMRoomMap.makeShared().start();
IntegrationManagers.sharedInstance().startWatching();
ActiveWidgetStore.start();
From 3732d1f5a5d0ad2fe22c73db924d612651967c79 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 22 May 2020 21:56:25 +0100
Subject: [PATCH 13/84] Migrate Desktop Notifications MatrixToolbar to a Toast
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
src/Notifier.js | 15 +++---
src/components/structures/LoggedInView.tsx | 7 +--
src/components/structures/MatrixChat.tsx | 6 ---
src/components/views/globals/MatrixToolbar.js | 45 -----------------
src/toasts/DesktopNotificationsToast.ts | 50 +++++++++++++++++++
5 files changed, 60 insertions(+), 63 deletions(-)
delete mode 100644 src/components/views/globals/MatrixToolbar.js
create mode 100644 src/toasts/DesktopNotificationsToast.ts
diff --git a/src/Notifier.js b/src/Notifier.js
index 2ffa92452b..cc804904e2 100644
--- a/src/Notifier.js
+++ b/src/Notifier.js
@@ -26,6 +26,10 @@ import * as sdk from './index';
import { _t } from './languageHandler';
import Modal from './Modal';
import SettingsStore, {SettingLevel} from "./settings/SettingsStore";
+import {
+ showToast as showNotificationsToast,
+ hideToast as hideNotificationsToast,
+} from "./toasts/DesktopNotificationsToast";
/*
* Dispatches:
@@ -184,6 +188,10 @@ const Notifier = {
MatrixClientPeg.get().on("sync", this.boundOnSyncStateChange);
this.toolbarHidden = false;
this.isSyncing = false;
+
+ if (this.shouldShowToolbar()) {
+ showNotificationsToast();
+ }
},
stop: function() {
@@ -278,12 +286,7 @@ const Notifier = {
Analytics.trackEvent('Notifier', 'Set Toolbar Hidden', hidden);
- // XXX: why are we dispatching this here?
- // this is nothing to do with notifier_enabled
- dis.dispatch({
- action: "notifier_enabled",
- value: this.isEnabled(),
- });
+ hideNotificationsToast();
// update the info to localStorage for persistent settings
if (persistent && global.localStorage) {
diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx
index 148d10fe8d..0d3eda759e 100644
--- a/src/components/structures/LoggedInView.tsx
+++ b/src/components/structures/LoggedInView.tsx
@@ -68,7 +68,6 @@ interface IProps {
showCookieBar: boolean;
hasNewVersion: boolean;
userHasGeneratedPassword: boolean;
- showNotifierToolbar: boolean;
page_type: string;
autoJoin: boolean;
thirdPartyInvite?: object;
@@ -184,8 +183,7 @@ class LoggedInView extends React.PureComponent {
if (
(prevProps.showCookieBar !== this.props.showCookieBar) ||
(prevProps.hasNewVersion !== this.props.hasNewVersion) ||
- (prevState.userHasGeneratedPassword !== this.state.userHasGeneratedPassword) ||
- (prevProps.showNotifierToolbar !== this.props.showNotifierToolbar)
+ (prevState.userHasGeneratedPassword !== this.state.userHasGeneratedPassword)
) {
this.props.resizeNotifier.notifyBannersChanged();
}
@@ -599,7 +597,6 @@ class LoggedInView extends React.PureComponent {
const GroupView = sdk.getComponent('structures.GroupView');
const MyGroups = sdk.getComponent('structures.MyGroups');
const ToastContainer = sdk.getComponent('structures.ToastContainer');
- const MatrixToolbar = sdk.getComponent('globals.MatrixToolbar');
const CookieBar = sdk.getComponent('globals.CookieBar');
const NewVersionBar = sdk.getComponent('globals.NewVersionBar');
const UpdateCheckBar = sdk.getComponent('globals.UpdateCheckBar');
@@ -680,8 +677,6 @@ class LoggedInView extends React.PureComponent {
topBar = ;
} else if (this.state.userHasGeneratedPassword) {
topBar = ;
- } else if (this.props.showNotifierToolbar) {
- topBar = ;
}
let bodyClasses = 'mx_MatrixChat';
diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx
index e6a56c683f..120497e5ef 100644
--- a/src/components/structures/MatrixChat.tsx
+++ b/src/components/structures/MatrixChat.tsx
@@ -184,7 +184,6 @@ interface IState {
hideToSRUsers: boolean;
syncError?: Error;
resizeNotifier: ResizeNotifier;
- showNotifierToolbar: boolean;
serverConfig?: ValidatedServerConfig;
ready: boolean;
thirdPartyInvite?: object;
@@ -238,7 +237,6 @@ export default class MatrixChat extends React.PureComponent {
syncError: null, // If the current syncing status is ERROR, the error object, otherwise null.
resizeNotifier: new ResizeNotifier(),
- showNotifierToolbar: false,
ready: false,
};
@@ -686,9 +684,6 @@ export default class MatrixChat extends React.PureComponent {
dis.dispatch({action: 'view_my_groups'});
}
break;
- case 'notifier_enabled':
- this.setState({showNotifierToolbar: Notifier.shouldShowToolbar()});
- break;
case 'hide_left_panel':
this.setState({
collapseLhs: true,
@@ -1381,7 +1376,6 @@ export default class MatrixChat extends React.PureComponent {
dis.dispatch({action: 'focus_composer'});
this.setState({
ready: true,
- showNotifierToolbar: Notifier.shouldShowToolbar(),
});
});
cli.on('Call.incoming', function(call) {
diff --git a/src/components/views/globals/MatrixToolbar.js b/src/components/views/globals/MatrixToolbar.js
deleted file mode 100644
index 758e4d62aa..0000000000
--- a/src/components/views/globals/MatrixToolbar.js
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
-Copyright 2015, 2016 OpenMarket Ltd
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-import React from 'react';
-import createReactClass from 'create-react-class';
-import { _t } from '../../../languageHandler';
-import Notifier from '../../../Notifier';
-import AccessibleButton from '../../../components/views/elements/AccessibleButton';
-
-export default createReactClass({
- displayName: 'MatrixToolbar',
-
- hideToolbar: function() {
- Notifier.setToolbarHidden(true);
- },
-
- onClick: function() {
- Notifier.setEnabled(true);
- },
-
- render: function() {
- return (
-
- );
- },
-});
diff --git a/src/toasts/DesktopNotificationsToast.ts b/src/toasts/DesktopNotificationsToast.ts
new file mode 100644
index 0000000000..02f0730759
--- /dev/null
+++ b/src/toasts/DesktopNotificationsToast.ts
@@ -0,0 +1,50 @@
+/*
+Copyright 2020 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { _t } from "../languageHandler";
+import Notifier from "../Notifier";
+import GenericToast from "../components/views/toasts/GenericToast";
+import ToastStore from "../stores/ToastStore";
+
+const onAccept = () => {
+ Notifier.setEnabled(true);
+};
+
+const onReject = () => {
+ Notifier.setToolbarHidden(true);
+};
+
+const TOAST_KEY = "desktopnotifications";
+
+export const showToast = () => {
+ ToastStore.sharedInstance().addOrReplaceToast({
+ key: TOAST_KEY,
+ title: _t("Notifications"),
+ props: {
+ description: _t("You are not receiving desktop notifications"),
+ acceptLabel: _t("Enable them now"),
+ onAccept,
+ rejectLabel: _t("Close"),
+ onReject,
+ },
+ component: GenericToast,
+ priority: 20,
+ });
+};
+
+export const hideToast = () => {
+ ToastStore.sharedInstance().dismissToast(TOAST_KEY);
+};
From 89292ca47be3fc0a714024a708452452e9938ee6 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 22 May 2020 22:03:16 +0100
Subject: [PATCH 14/84] Fix toast priority sorting to put the highest priority
into slot[0]
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
src/stores/ToastStore.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/stores/ToastStore.ts b/src/stores/ToastStore.ts
index 4f6d2963c5..23317a0ad3 100644
--- a/src/stores/ToastStore.ts
+++ b/src/stores/ToastStore.ts
@@ -55,7 +55,7 @@ export default class ToastStore extends EventEmitter {
const oldIndex = this.toasts.findIndex(t => t.key === newToast.key);
if (oldIndex === -1) {
let newIndex = this.toasts.length;
- while (newIndex > 0 && this.toasts[newIndex - 1].priority > newToast.priority) --newIndex;
+ while (newIndex > 0 && this.toasts[newIndex - 1].priority < newToast.priority) --newIndex;
this.toasts.splice(newIndex, 0, newToast);
} else {
this.toasts[oldIndex] = newToast;
From c91f8c2631799d92cc582dbe6eebcdec3e0a2cb4 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 22 May 2020 22:04:21 +0100
Subject: [PATCH 15/84] Migrate Analytics Banner to a Toast
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
src/components/structures/LoggedInView.tsx | 9 --
src/components/structures/MatrixChat.tsx | 28 ++----
src/components/views/globals/CookieBar.js | 103 ---------------------
src/i18n/strings/en_EN.json | 14 +--
src/toasts/AnalyticsToast.tsx | 75 +++++++++++++++
5 files changed, 92 insertions(+), 137 deletions(-)
delete mode 100644 src/components/views/globals/CookieBar.js
create mode 100644 src/toasts/AnalyticsToast.tsx
diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx
index 0d3eda759e..88b64f0cbc 100644
--- a/src/components/structures/LoggedInView.tsx
+++ b/src/components/structures/LoggedInView.tsx
@@ -65,7 +65,6 @@ interface IProps {
initialEventPixelOffset: number;
leftDisabled: boolean;
rightDisabled: boolean;
- showCookieBar: boolean;
hasNewVersion: boolean;
userHasGeneratedPassword: boolean;
page_type: string;
@@ -181,7 +180,6 @@ class LoggedInView extends React.PureComponent {
componentDidUpdate(prevProps, prevState) {
// attempt to guess when a banner was opened or closed
if (
- (prevProps.showCookieBar !== this.props.showCookieBar) ||
(prevProps.hasNewVersion !== this.props.hasNewVersion) ||
(prevState.userHasGeneratedPassword !== this.state.userHasGeneratedPassword)
) {
@@ -597,7 +595,6 @@ class LoggedInView extends React.PureComponent {
const GroupView = sdk.getComponent('structures.GroupView');
const MyGroups = sdk.getComponent('structures.MyGroups');
const ToastContainer = sdk.getComponent('structures.ToastContainer');
- const CookieBar = sdk.getComponent('globals.CookieBar');
const NewVersionBar = sdk.getComponent('globals.NewVersionBar');
const UpdateCheckBar = sdk.getComponent('globals.UpdateCheckBar');
const PasswordNagBar = sdk.getComponent('globals.PasswordNagBar');
@@ -663,12 +660,6 @@ class LoggedInView extends React.PureComponent {
adminContact={usageLimitEvent.getContent().admin_contact}
limitType={usageLimitEvent.getContent().limit_type}
/>;
- } else if (this.props.showCookieBar &&
- this.props.config.piwik &&
- navigator.doNotTrack !== "1"
- ) {
- const policyUrl = this.props.config.piwik.policyUrl || null;
- topBar = ;
} else if (this.props.hasNewVersion) {
topBar = {
newVersionReleaseNotes: null,
checkingForUpdate: null,
- showCookieBar: false,
-
hideToSRUsers: false,
syncError: null, // If the current syncing status is ERROR, the error object, otherwise null.
@@ -337,12 +338,6 @@ export default class MatrixChat extends React.PureComponent {
});
}
- if (SettingsStore.getValue("showCookieBar")) {
- this.setState({
- showCookieBar: true,
- });
- }
-
if (SettingsStore.getValue("analyticsOptIn")) {
Analytics.enable();
}
@@ -756,19 +751,13 @@ export default class MatrixChat extends React.PureComponent {
case 'accept_cookies':
SettingsStore.setValue("analyticsOptIn", null, SettingLevel.DEVICE, true);
SettingsStore.setValue("showCookieBar", null, SettingLevel.DEVICE, false);
-
- this.setState({
- showCookieBar: false,
- });
+ hideAnalyticsToast();
Analytics.enable();
break;
case 'reject_cookies':
SettingsStore.setValue("analyticsOptIn", null, SettingLevel.DEVICE, false);
SettingsStore.setValue("showCookieBar", null, SettingLevel.DEVICE, false);
-
- this.setState({
- showCookieBar: false,
- });
+ hideAnalyticsToast();
break;
}
};
@@ -1246,6 +1235,10 @@ export default class MatrixChat extends React.PureComponent {
}
StorageManager.tryPersistStorage();
+
+ if (SettingsStore.getValue("showCookieBar") && this.props.config.piwik && navigator.doNotTrack !== "1") {
+ showAnalyticsToast(this.props.config.piwik && this.props.config.piwik.policyUrl);
+ }
}
private showScreenAfterLogin() {
@@ -2031,7 +2024,6 @@ export default class MatrixChat extends React.PureComponent {
onCloseAllSettings={this.onCloseAllSettings}
onRegistered={this.onRegistered}
currentRoomId={this.state.currentRoomId}
- showCookieBar={this.state.showCookieBar}
/>
);
} else {
diff --git a/src/components/views/globals/CookieBar.js b/src/components/views/globals/CookieBar.js
deleted file mode 100644
index bf264686d0..0000000000
--- a/src/components/views/globals/CookieBar.js
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
-Copyright 2018 New Vector Ltd.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import dis from '../../../dispatcher/dispatcher';
-import { _t } from '../../../languageHandler';
-import * as sdk from '../../../index';
-import Analytics from '../../../Analytics';
-
-export default class CookieBar extends React.Component {
- static propTypes = {
- policyUrl: PropTypes.string,
- }
-
- constructor() {
- super();
- }
-
- onUsageDataClicked(e) {
- e.stopPropagation();
- e.preventDefault();
- Analytics.showDetailsModal();
- }
-
- onAccept() {
- dis.dispatch({
- action: 'accept_cookies',
- });
- }
-
- onReject() {
- dis.dispatch({
- action: 'reject_cookies',
- });
- }
-
- render() {
- const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
- const toolbarClasses = "mx_MatrixToolbar";
- return (
-
-
-
- { this.props.policyUrl ? _t(
- "Please help improve Riot.im by sending anonymous usage data. " +
- "This will use a cookie " +
- "(please see our Cookie Policy).",
- {},
- {
- 'UsageDataLink': (sub) =>
- { sub }
- ,
- // XXX: We need to link to the page that explains our cookies
- 'PolicyLink': (sub) =>
- { sub }
-
- ,
- },
- ) : _t(
- "Please help improve Riot.im by sending anonymous usage data. " +
- "This will use a cookie.",
- {},
- {
- 'UsageDataLink': (sub) =>
- { sub }
- ,
- },
- ) }
-
-
- { _t("Yes, I want to help!") }
-
-
-
-
-
- );
- }
-}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 8ac05bf429..a79ee97109 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -391,10 +391,17 @@
"Common names and surnames are easy to guess": "Common names and surnames are easy to guess",
"Straight rows of keys are easy to guess": "Straight rows of keys are easy to guess",
"Short keyboard patterns are easy to guess": "Short keyboard patterns are easy to guess",
+ "Notifications": "Notifications",
+ "Send anonymous usage data which helps us improve Riot. This will use a cookie.": "Send anonymous usage data which helps us improve Riot. This will use a cookie.",
+ "I want to help": "I want to help",
+ "No": "No",
"Review where you’re logged in": "Review where you’re logged in",
"Verify all your sessions to ensure your account & messages are safe": "Verify all your sessions to ensure your account & messages are safe",
"Review": "Review",
"Later": "Later",
+ "You are not receiving desktop notifications": "You are not receiving desktop notifications",
+ "Enable them now": "Enable them now",
+ "Close": "Close",
"Set up encryption": "Set up encryption",
"Encryption upgrade available": "Encryption upgrade available",
"Verify this session": "Verify this session",
@@ -643,8 +650,6 @@
"Last seen": "Last seen",
"Failed to set display name": "Failed to set display name",
"Individually verify each session used by a user to mark it as trusted, not trusting cross-signed devices.": "Individually verify each session used by a user to mark it as trusted, not trusting cross-signed devices.",
- "Disable Notifications": "Disable Notifications",
- "Enable Notifications": "Enable Notifications",
"Securely cache encrypted messages locally for them to appear in search results, using ": "Securely cache encrypted messages locally for them to appear in search results, using ",
" to store messages from ": " to store messages from ",
"rooms.": "rooms.",
@@ -815,7 +820,6 @@
"Ban list rules - %(roomName)s": "Ban list rules - %(roomName)s",
"Server rules": "Server rules",
"User rules": "User rules",
- "Close": "Close",
"You have not ignored anyone.": "You have not ignored anyone.",
"You are currently ignoring:": "You are currently ignoring:",
"You are not subscribed to any lists": "You are not subscribed to any lists",
@@ -836,7 +840,6 @@
"If this isn't what you want, please use a different tool to ignore users.": "If this isn't what you want, please use a different tool to ignore users.",
"Room ID or alias of ban list": "Room ID or alias of ban list",
"Subscribe": "Subscribe",
- "Notifications": "Notifications",
"Start automatically after system login": "Start automatically after system login",
"Always show the window menu bar": "Always show the window menu bar",
"Show tray icon and minimize window to it on close": "Show tray icon and minimize window to it on close",
@@ -1287,7 +1290,6 @@
"Verify by emoji": "Verify by emoji",
"Almost there! Is your other session showing the same shield?": "Almost there! Is your other session showing the same shield?",
"Almost there! Is %(displayName)s showing the same shield?": "Almost there! Is %(displayName)s showing the same shield?",
- "No": "No",
"Yes": "Yes",
"Verify all users in a room to ensure it's secure.": "Verify all users in a room to ensure it's secure.",
"In encrypted rooms, verify all users to ensure it’s secure.": "In encrypted rooms, verify all users to ensure it’s secure.",
@@ -1384,8 +1386,6 @@
"Please help improve Riot.im by sending anonymous usage data. This will use a cookie (please see our Cookie Policy).": "Please help improve Riot.im by sending anonymous usage data. This will use a cookie (please see our Cookie Policy).",
"Please help improve Riot.im by sending anonymous usage data. This will use a cookie.": "Please help improve Riot.im by sending anonymous usage data. This will use a cookie.",
"Yes, I want to help!": "Yes, I want to help!",
- "You are not receiving desktop notifications": "You are not receiving desktop notifications",
- "Enable them now": "Enable them now",
"What's New": "What's New",
"Update": "Update",
"What's new?": "What's new?",
diff --git a/src/toasts/AnalyticsToast.tsx b/src/toasts/AnalyticsToast.tsx
new file mode 100644
index 0000000000..5b53a903fe
--- /dev/null
+++ b/src/toasts/AnalyticsToast.tsx
@@ -0,0 +1,75 @@
+/*
+Copyright 2020 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from "react";
+
+import { _t } from "../languageHandler";
+import dis from "../dispatcher/dispatcher";
+import Analytics from "../Analytics";
+import AccessibleButton from "../components/views/elements/AccessibleButton";
+import GenericToast from "../components/views/toasts/GenericToast";
+import ToastStore from "../stores/ToastStore";
+
+const onAccept = () => {
+ dis.dispatch({
+ action: 'accept_cookies',
+ });
+};
+
+const onReject = () => {
+ dis.dispatch({
+ action: "reject_cookies",
+ });
+};
+
+const onUsageDataClicked = () => {
+ Analytics.showDetailsModal();
+};
+
+const TOAST_KEY = "analytics";
+
+export const showToast = (policyUrl?: string) => {
+ ToastStore.sharedInstance().addOrReplaceToast({
+ key: TOAST_KEY,
+ title: _t("Notifications"),
+ props: {
+ description: _t(
+ "Send anonymous usage data which helps us improve Riot. " +
+ "This will use a cookie.",
+ {},
+ {
+ "UsageDataLink": (sub) => (
+ { sub }
+ ),
+ // XXX: We need to link to the page that explains our cookies
+ "PolicyLink": (sub) => policyUrl ? (
+ { sub }
+ ) : sub,
+ },
+ ),
+ acceptLabel: _t("I want to help"),
+ onAccept,
+ rejectLabel: _t("No"),
+ onReject,
+ },
+ component: GenericToast,
+ priority: 10,
+ });
+};
+
+export const hideToast = () => {
+ ToastStore.sharedInstance().dismissToast(TOAST_KEY);
+};
From 5d0040b8b36d1474db807748d1191844b424a569 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 22 May 2020 22:15:22 +0100
Subject: [PATCH 16/84] Migrate Password Nag Bar Banner to a Toast
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
src/components/structures/LoggedInView.tsx | 22 ++++----
.../views/globals/PasswordNagBar.js | 53 -------------------
src/toasts/SetPasswordToast.ts | 47 ++++++++++++++++
3 files changed, 58 insertions(+), 64 deletions(-)
delete mode 100644 src/components/views/globals/PasswordNagBar.js
create mode 100644 src/toasts/SetPasswordToast.ts
diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx
index 88b64f0cbc..131e6a3867 100644
--- a/src/components/structures/LoggedInView.tsx
+++ b/src/components/structures/LoggedInView.tsx
@@ -43,6 +43,11 @@ import ResizeNotifier from "../../utils/ResizeNotifier";
import PlatformPeg from "../../PlatformPeg";
import { RoomListStoreTempProxy } from "../../stores/room-list/RoomListStoreTempProxy";
import { DefaultTagID } from "../../stores/room-list/models";
+import {
+ showToast as showSetPasswordToast,
+ hideToast as hideSetPasswordToast
+} from "../../toasts/SetPasswordToast";
+
// We need to fetch each pinned message individually (if we don't already have it)
// so each pinned message may trigger a request. Limit the number per room for sanity.
// NB. this is just for server notices rather than pinned messages in general.
@@ -66,7 +71,6 @@ interface IProps {
leftDisabled: boolean;
rightDisabled: boolean;
hasNewVersion: boolean;
- userHasGeneratedPassword: boolean;
page_type: string;
autoJoin: boolean;
thirdPartyInvite?: object;
@@ -96,7 +100,6 @@ interface IState {
syncErrorData: any;
useCompactLayout: boolean;
serverNoticeEvents: MatrixEvent[];
- userHasGeneratedPassword: boolean;
}
/**
@@ -139,7 +142,6 @@ class LoggedInView extends React.PureComponent {
this.state = {
mouseDown: undefined,
syncErrorData: undefined,
- userHasGeneratedPassword: false,
// use compact timeline view
useCompactLayout: SettingsStore.getValue('useCompactLayout'),
// any currently active server notice events
@@ -180,8 +182,7 @@ class LoggedInView extends React.PureComponent {
componentDidUpdate(prevProps, prevState) {
// attempt to guess when a banner was opened or closed
if (
- (prevProps.hasNewVersion !== this.props.hasNewVersion) ||
- (prevState.userHasGeneratedPassword !== this.state.userHasGeneratedPassword)
+ (prevProps.hasNewVersion !== this.props.hasNewVersion)
) {
this.props.resizeNotifier.notifyBannersChanged();
}
@@ -216,9 +217,11 @@ class LoggedInView extends React.PureComponent {
};
_setStateFromSessionStore = () => {
- this.setState({
- userHasGeneratedPassword: Boolean(this._sessionStore.getCachedPassword()),
- });
+ if (this._sessionStore.getCachedPassword()) {
+ showSetPasswordToast();
+ } else {
+ hideSetPasswordToast();
+ }
};
_createResizer() {
@@ -597,7 +600,6 @@ class LoggedInView extends React.PureComponent {
const ToastContainer = sdk.getComponent('structures.ToastContainer');
const NewVersionBar = sdk.getComponent('globals.NewVersionBar');
const UpdateCheckBar = sdk.getComponent('globals.UpdateCheckBar');
- const PasswordNagBar = sdk.getComponent('globals.PasswordNagBar');
const ServerLimitBar = sdk.getComponent('globals.ServerLimitBar');
let pageElement;
@@ -666,8 +668,6 @@ class LoggedInView extends React.PureComponent {
/>;
} else if (this.props.checkingForUpdate) {
topBar = ;
- } else if (this.state.userHasGeneratedPassword) {
- topBar = ;
}
let bodyClasses = 'mx_MatrixChat';
diff --git a/src/components/views/globals/PasswordNagBar.js b/src/components/views/globals/PasswordNagBar.js
deleted file mode 100644
index 74735ca5ea..0000000000
--- a/src/components/views/globals/PasswordNagBar.js
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
-Copyright 2017 Vector Creations Ltd
-Copyright 2018 New Vector Ltd
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-import React from 'react';
-import createReactClass from 'create-react-class';
-import * as sdk from '../../../index';
-import Modal from '../../../Modal';
-import { _t } from '../../../languageHandler';
-
-export default createReactClass({
- onUpdateClicked: function() {
- const SetPasswordDialog = sdk.getComponent('dialogs.SetPasswordDialog');
- Modal.createTrackedDialog('Set Password Dialog', 'Password Nag Bar', SetPasswordDialog);
- },
-
- render: function() {
- const toolbarClasses = "mx_MatrixToolbar mx_MatrixToolbar_clickable";
- return (
-
-
-
- { _t(
- "To return to your account in future you need to set a password",
- {},
- { 'u': (sub) => { sub } },
- ) }
-
-
-
- );
- },
-});
diff --git a/src/toasts/SetPasswordToast.ts b/src/toasts/SetPasswordToast.ts
new file mode 100644
index 0000000000..88cc317978
--- /dev/null
+++ b/src/toasts/SetPasswordToast.ts
@@ -0,0 +1,47 @@
+/*
+Copyright 2020 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { _t } from "../languageHandler";
+import Modal from "../Modal";
+import SetPasswordDialog from "../components/views/dialogs/SetPasswordDialog";
+import GenericToast from "../components/views/toasts/GenericToast";
+import ToastStore from "../stores/ToastStore";
+
+const onAccept = () => {
+ Modal.createTrackedDialog('Set Password Dialog', 'Password Nag Bar', SetPasswordDialog);
+};
+
+const TOAST_KEY = "setpassword";
+
+export const showToast = () => {
+ ToastStore.sharedInstance().addOrReplaceToast({
+ key: TOAST_KEY,
+ title: _t("Set password"),
+ props: {
+ description: _t("To return to your account in future you need to set a password"),
+ acceptLabel: _t("Set Password"),
+ onAccept,
+ rejectLabel: _t("Later"),
+ onReject: hideToast, // it'll return on reload
+ },
+ component: GenericToast,
+ priority: 60,
+ });
+};
+
+export const hideToast = () => {
+ ToastStore.sharedInstance().dismissToast(TOAST_KEY);
+};
From ccf9e6512341bb7395b1eba3841b0ce475379da5 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 22 May 2020 22:27:19 +0100
Subject: [PATCH 17/84] Migrate Server Limit Bar Banner to a Toast
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
src/components/structures/LoggedInView.tsx | 56 ++++++-----
.../views/globals/ServerLimitBar.js | 99 -------------------
src/toasts/ServerLimitToast.tsx | 50 ++++++++++
3 files changed, 80 insertions(+), 125 deletions(-)
delete mode 100644 src/components/views/globals/ServerLimitBar.js
create mode 100644 src/toasts/ServerLimitToast.tsx
diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx
index 131e6a3867..edb2482aa3 100644
--- a/src/components/structures/LoggedInView.tsx
+++ b/src/components/structures/LoggedInView.tsx
@@ -47,6 +47,10 @@ import {
showToast as showSetPasswordToast,
hideToast as hideSetPasswordToast
} from "../../toasts/SetPasswordToast";
+import {
+ showToast as showServerLimitToast,
+ hideToast as hideServerLimitToast
+} from "../../toasts/ServerLimitToast";
// We need to fetch each pinned message individually (if we don't already have it)
// so each pinned message may trigger a request. Limit the number per room for sanity.
@@ -99,7 +103,6 @@ interface IState {
};
syncErrorData: any;
useCompactLayout: boolean;
- serverNoticeEvents: MatrixEvent[];
}
/**
@@ -144,8 +147,6 @@ class LoggedInView extends React.PureComponent {
syncErrorData: undefined,
// use compact timeline view
useCompactLayout: SettingsStore.getValue('useCompactLayout'),
- // any currently active server notice events
- serverNoticeEvents: [],
};
// stash the MatrixClient in case we log out before we are unmounted
@@ -293,6 +294,8 @@ class LoggedInView extends React.PureComponent {
if (oldSyncState === 'PREPARED' && syncState === 'SYNCING') {
this._updateServerNoticeEvents();
+ } else {
+ this._calculateServerLimitToast(data);
}
};
@@ -303,11 +306,24 @@ class LoggedInView extends React.PureComponent {
}
};
+ _calculateServerLimitToast(syncErrorData, usageLimitEventContent?) {
+ const error = syncErrorData && syncErrorData.error && syncErrorData.error.errcode === "M_RESOURCE_LIMIT_EXCEEDED";
+ if (error) {
+ usageLimitEventContent = syncErrorData.error.data;
+ }
+
+ if (usageLimitEventContent) {
+ showServerLimitToast(usageLimitEventContent.limit_type, usageLimitEventContent.admin_contact, error);
+ } else {
+ hideServerLimitToast();
+ }
+ }
+
_updateServerNoticeEvents = async () => {
const roomLists = RoomListStoreTempProxy.getRoomLists();
if (!roomLists[DefaultTagID.ServerNotice]) return [];
- const pinnedEvents = [];
+ const events = [];
for (const room of roomLists[DefaultTagID.ServerNotice]) {
const pinStateEvent = room.currentState.getStateEvents("m.room.pinned_events", "");
@@ -317,12 +333,18 @@ class LoggedInView extends React.PureComponent {
for (const eventId of pinnedEventIds) {
const timeline = await this._matrixClient.getEventTimeline(room.getUnfilteredTimelineSet(), eventId, 0);
const event = timeline.getEvents().find(ev => ev.getId() === eventId);
- if (event) pinnedEvents.push(event);
+ if (event) events.push(event);
}
}
- this.setState({
- serverNoticeEvents: pinnedEvents,
+
+ const usageLimitEvent = events.find((e) => {
+ return (
+ e && e.getType() === 'm.room.message' &&
+ e.getContent()['server_notice_type'] === 'm.server_notice.usage_limit_reached'
+ );
});
+
+ this._calculateServerLimitToast(this.state.syncErrorData, usageLimitEvent && usageLimitEvent.getContent());
};
_onPaste = (ev) => {
@@ -600,7 +622,6 @@ class LoggedInView extends React.PureComponent {
const ToastContainer = sdk.getComponent('structures.ToastContainer');
const NewVersionBar = sdk.getComponent('globals.NewVersionBar');
const UpdateCheckBar = sdk.getComponent('globals.UpdateCheckBar');
- const ServerLimitBar = sdk.getComponent('globals.ServerLimitBar');
let pageElement;
@@ -644,25 +665,8 @@ class LoggedInView extends React.PureComponent {
break;
}
- const usageLimitEvent = this.state.serverNoticeEvents.find((e) => {
- return (
- e && e.getType() === 'm.room.message' &&
- e.getContent()['server_notice_type'] === 'm.server_notice.usage_limit_reached'
- );
- });
-
let topBar;
- if (this.state.syncErrorData && this.state.syncErrorData.error.errcode === 'M_RESOURCE_LIMIT_EXCEEDED') {
- topBar = ;
- } else if (usageLimitEvent) {
- topBar = ;
- } else if (this.props.hasNewVersion) {
+ if (this.props.hasNewVersion) {
topBar = ;
diff --git a/src/components/views/globals/ServerLimitBar.js b/src/components/views/globals/ServerLimitBar.js
deleted file mode 100644
index 7d414a2826..0000000000
--- a/src/components/views/globals/ServerLimitBar.js
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
-Copyright 2018 New Vector Ltd
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import createReactClass from 'create-react-class';
-import classNames from 'classnames';
-import { _td } from '../../../languageHandler';
-import { messageForResourceLimitError } from '../../../utils/ErrorUtils';
-
-export default createReactClass({
- propTypes: {
- // 'hard' if the logged in user has been locked out, 'soft' if they haven't
- kind: PropTypes.string,
- adminContact: PropTypes.string,
- // The type of limit that has been hit.
- limitType: PropTypes.string.isRequired,
- },
-
- getDefaultProps: function() {
- return {
- kind: 'hard',
- };
- },
-
- render: function() {
- const toolbarClasses = {
- 'mx_MatrixToolbar': true,
- };
-
- let adminContact;
- let limitError;
- if (this.props.kind === 'hard') {
- toolbarClasses['mx_MatrixToolbar_error'] = true;
-
- adminContact = messageForResourceLimitError(
- this.props.limitType,
- this.props.adminContact,
- {
- '': _td("Please contact your service administrator to continue using the service."),
- },
- );
- limitError = messageForResourceLimitError(
- this.props.limitType,
- this.props.adminContact,
- {
- 'monthly_active_user': _td("This homeserver has hit its Monthly Active User limit."),
- '': _td("This homeserver has exceeded one of its resource limits."),
- },
- );
- } else {
- toolbarClasses['mx_MatrixToolbar_info'] = true;
- adminContact = messageForResourceLimitError(
- this.props.limitType,
- this.props.adminContact,
- {
- '': _td("Please contact your service administrator to get this limit increased."),
- },
- );
- limitError = messageForResourceLimitError(
- this.props.limitType,
- this.props.adminContact,
- {
- 'monthly_active_user': _td(
- "This homeserver has hit its Monthly Active User limit so " +
- "some users will not be able to log in.",
- ),
- '': _td(
- "This homeserver has exceeded one of its resource limits so " +
- "some users will not be able to log in.",
- ),
- },
- {'b': sub => {sub}},
- );
- }
- return (
-
-
- {limitError}
- {' '}
- {adminContact}
-
-
- );
- },
-});
diff --git a/src/toasts/ServerLimitToast.tsx b/src/toasts/ServerLimitToast.tsx
new file mode 100644
index 0000000000..f2de9e3499
--- /dev/null
+++ b/src/toasts/ServerLimitToast.tsx
@@ -0,0 +1,50 @@
+/*
+Copyright 2020 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from "react";
+
+import { _t, _td } from "../languageHandler";
+import GenericToast from "../components/views/toasts/GenericToast";
+import ToastStore from "../stores/ToastStore";
+import {messageForResourceLimitError} from "../utils/ErrorUtils";
+
+const TOAST_KEY = "serverlimit";
+
+export const showToast = (limitType: string, adminContact?: string, syncError?: boolean) => {
+ const errorText = messageForResourceLimitError(limitType, adminContact, {
+ 'monthly_active_user': _td("Your homeserver has exceeded its user limit."),
+ '': _td("Your homeserver has exceeded one of its resource limits."),
+ });
+ const contactText = messageForResourceLimitError(limitType, adminContact, {
+ '': _td("Contact your server admin."),
+ });
+
+ ToastStore.sharedInstance().addOrReplaceToast({
+ key: TOAST_KEY,
+ title: _t("Notifications"),
+ props: {
+ description: {errorText} {contactText},
+ acceptLabel: _t("Ok"),
+ onAccept: hideToast,
+ },
+ component: GenericToast,
+ priority: 20,
+ });
+};
+
+export const hideToast = () => {
+ ToastStore.sharedInstance().dismissToast(TOAST_KEY);
+};
From 29cfb47a83f5fc016f941a8387efb8f5a3e7463f Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 22 May 2020 22:29:09 +0100
Subject: [PATCH 18/84] fix copy
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
src/toasts/AnalyticsToast.tsx | 2 +-
src/toasts/ServerLimitToast.tsx | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/toasts/AnalyticsToast.tsx b/src/toasts/AnalyticsToast.tsx
index 5b53a903fe..b186a65d9d 100644
--- a/src/toasts/AnalyticsToast.tsx
+++ b/src/toasts/AnalyticsToast.tsx
@@ -44,7 +44,7 @@ const TOAST_KEY = "analytics";
export const showToast = (policyUrl?: string) => {
ToastStore.sharedInstance().addOrReplaceToast({
key: TOAST_KEY,
- title: _t("Notifications"),
+ title: _t("Help us improve Riot"),
props: {
description: _t(
"Send anonymous usage data which helps us improve Riot. " +
diff --git a/src/toasts/ServerLimitToast.tsx b/src/toasts/ServerLimitToast.tsx
index f2de9e3499..d35140be3d 100644
--- a/src/toasts/ServerLimitToast.tsx
+++ b/src/toasts/ServerLimitToast.tsx
@@ -34,14 +34,14 @@ export const showToast = (limitType: string, adminContact?: string, syncError?:
ToastStore.sharedInstance().addOrReplaceToast({
key: TOAST_KEY,
- title: _t("Notifications"),
+ title: _t("Warning"),
props: {
description: {errorText} {contactText},
acceptLabel: _t("Ok"),
onAccept: hideToast,
},
component: GenericToast,
- priority: 20,
+ priority: 70,
});
};
From 9f060d113259d3cf22d5e9ee4f6a98eb91c0d5d7 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 22 May 2020 22:34:32 +0100
Subject: [PATCH 19/84] i18n
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
src/i18n/strings/en_EN.json | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index a79ee97109..72c19587c8 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -391,7 +391,7 @@
"Common names and surnames are easy to guess": "Common names and surnames are easy to guess",
"Straight rows of keys are easy to guess": "Straight rows of keys are easy to guess",
"Short keyboard patterns are easy to guess": "Short keyboard patterns are easy to guess",
- "Notifications": "Notifications",
+ "Help us improve Riot": "Help us improve Riot",
"Send anonymous usage data which helps us improve Riot. This will use a cookie.": "Send anonymous usage data which helps us improve Riot. This will use a cookie.",
"I want to help": "I want to help",
"No": "No",
@@ -399,9 +399,18 @@
"Verify all your sessions to ensure your account & messages are safe": "Verify all your sessions to ensure your account & messages are safe",
"Review": "Review",
"Later": "Later",
+ "Notifications": "Notifications",
"You are not receiving desktop notifications": "You are not receiving desktop notifications",
"Enable them now": "Enable them now",
"Close": "Close",
+ "Your homeserver has exceeded its user limit.": "Your homeserver has exceeded its user limit.",
+ "Your homeserver has exceeded one of its resource limits.": "Your homeserver has exceeded one of its resource limits.",
+ "Contact your server admin.": "Contact your server admin.",
+ "Warning": "Warning",
+ "Ok": "Ok",
+ "Set password": "Set password",
+ "To return to your account in future you need to set a password": "To return to your account in future you need to set a password",
+ "Set Password": "Set Password",
"Set up encryption": "Set up encryption",
"Encryption upgrade available": "Encryption upgrade available",
"Verify this session": "Verify this session",
@@ -781,7 +790,6 @@
"Account management": "Account management",
"Deactivating your account is a permanent action - be careful!": "Deactivating your account is a permanent action - be careful!",
"Deactivate Account": "Deactivate Account",
- "Warning": "Warning",
"General": "General",
"Discovery": "Discovery",
"Deactivate account": "Deactivate account",
@@ -1383,18 +1391,10 @@
"Something went wrong when trying to get your communities.": "Something went wrong when trying to get your communities.",
"Display your community flair in rooms configured to show it.": "Display your community flair in rooms configured to show it.",
"You're not currently a member of any communities.": "You're not currently a member of any communities.",
- "Please help improve Riot.im by sending anonymous usage data. This will use a cookie (please see our Cookie Policy).": "Please help improve Riot.im by sending anonymous usage data. This will use a cookie (please see our Cookie Policy).",
- "Please help improve Riot.im by sending anonymous usage data. This will use a cookie.": "Please help improve Riot.im by sending anonymous usage data. This will use a cookie.",
- "Yes, I want to help!": "Yes, I want to help!",
"What's New": "What's New",
"Update": "Update",
"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 set a password": "To return to your account in future you need to set a password",
- "Set Password": "Set Password",
- "Please contact your service administrator to get this limit increased.": "Please contact your service administrator to get this limit increased.",
- "This homeserver has hit its Monthly Active User limit so some users will not be able to log in.": "This homeserver has hit its Monthly Active User limit so some users will not be able to log in.",
- "This homeserver has exceeded one of its resource limits so some users will not be able to log in.": "This homeserver has exceeded one of its resource limits so some users will not be able to log in.",
"Error encountered (%(errorDetail)s).": "Error encountered (%(errorDetail)s).",
"Checking for an update...": "Checking for an update...",
"No update available.": "No update available.",
From 891ba1bbe3c34bf5651846d425c07d969fec7cb7 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 22 May 2020 23:08:45 +0100
Subject: [PATCH 20/84] Replace New Version Bar with a Toast
discards the `new_version` dispatch
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
res/css/views/globals/_MatrixToolbar.scss | 4 -
src/components/structures/LoggedInView.tsx | 11 +-
src/components/structures/MatrixChat.tsx | 21 ----
src/components/views/globals/NewVersionBar.js | 108 ------------------
src/i18n/strings/en_EN.json | 10 +-
src/toasts/UpdateToast.tsx | 90 +++++++++++++++
6 files changed, 99 insertions(+), 145 deletions(-)
delete mode 100644 src/components/views/globals/NewVersionBar.js
create mode 100644 src/toasts/UpdateToast.tsx
diff --git a/res/css/views/globals/_MatrixToolbar.scss b/res/css/views/globals/_MatrixToolbar.scss
index 5fdf572f99..07b92a7235 100644
--- a/res/css/views/globals/_MatrixToolbar.scss
+++ b/res/css/views/globals/_MatrixToolbar.scss
@@ -67,7 +67,3 @@ limitations under the License.
.mx_MatrixToolbar_action {
margin-right: 16px;
}
-
-.mx_MatrixToolbar_changelog {
- white-space: pre;
-}
diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx
index edb2482aa3..2a17233ec6 100644
--- a/src/components/structures/LoggedInView.tsx
+++ b/src/components/structures/LoggedInView.tsx
@@ -74,7 +74,6 @@ interface IProps {
initialEventPixelOffset: number;
leftDisabled: boolean;
rightDisabled: boolean;
- hasNewVersion: boolean;
page_type: string;
autoJoin: boolean;
thirdPartyInvite?: object;
@@ -96,6 +95,7 @@ interface IProps {
newVersion?: string;
newVersionReleaseNotes?: string;
}
+
interface IState {
mouseDown?: {
x: number;
@@ -183,7 +183,7 @@ class LoggedInView extends React.PureComponent {
componentDidUpdate(prevProps, prevState) {
// attempt to guess when a banner was opened or closed
if (
- (prevProps.hasNewVersion !== this.props.hasNewVersion)
+ (prevProps.checkingForUpdate !== this.props.checkingForUpdate)
) {
this.props.resizeNotifier.notifyBannersChanged();
}
@@ -620,7 +620,6 @@ class LoggedInView extends React.PureComponent {
const GroupView = sdk.getComponent('structures.GroupView');
const MyGroups = sdk.getComponent('structures.MyGroups');
const ToastContainer = sdk.getComponent('structures.ToastContainer');
- const NewVersionBar = sdk.getComponent('globals.NewVersionBar');
const UpdateCheckBar = sdk.getComponent('globals.UpdateCheckBar');
let pageElement;
@@ -666,11 +665,7 @@ class LoggedInView extends React.PureComponent {
}
let topBar;
- if (this.props.hasNewVersion) {
- topBar = ;
- } else if (this.props.checkingForUpdate) {
+ if (this.props.checkingForUpdate) {
topBar = ;
}
diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx
index c69911fe04..f4d31708fe 100644
--- a/src/components/structures/MatrixChat.tsx
+++ b/src/components/structures/MatrixChat.tsx
@@ -173,10 +173,6 @@ interface IState {
leftDisabled: boolean;
middleDisabled: boolean;
// the right panel's disabled state is tracked in its store.
- version?: string;
- newVersion?: string;
- hasNewVersion: boolean;
- newVersionReleaseNotes?: string;
checkingForUpdate?: string; // updateCheckStatusEnum
// Parameters used in the registration dance with the IS
register_client_secret?: string;
@@ -230,7 +226,6 @@ export default class MatrixChat extends React.PureComponent {
leftDisabled: false,
middleDisabled: false,
- hasNewVersion: false,
newVersionReleaseNotes: null,
checkingForUpdate: null,
@@ -726,12 +721,6 @@ export default class MatrixChat extends React.PureComponent {
case 'client_started':
this.onClientStarted();
break;
- case 'new_version':
- this.onVersion(
- payload.currentVersion, payload.newVersion,
- payload.releaseNotes,
- );
- break;
case 'check_updates':
this.setState({ checkingForUpdate: payload.value });
break;
@@ -1820,16 +1809,6 @@ export default class MatrixChat extends React.PureComponent {
this.showScreen("settings");
};
- onVersion(current: string, latest: string, releaseNotes?: string) {
- this.setState({
- version: current,
- newVersion: latest,
- hasNewVersion: current !== latest,
- newVersionReleaseNotes: releaseNotes,
- checkingForUpdate: null,
- });
- }
-
onSendEvent(roomId: string, event: MatrixEvent) {
const cli = MatrixClientPeg.get();
if (!cli) {
diff --git a/src/components/views/globals/NewVersionBar.js b/src/components/views/globals/NewVersionBar.js
deleted file mode 100644
index dedccdc6b6..0000000000
--- a/src/components/views/globals/NewVersionBar.js
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
-Copyright 2015, 2016 OpenMarket Ltd
-Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import createReactClass from 'create-react-class';
-import * as sdk from '../../../index';
-import Modal from '../../../Modal';
-import PlatformPeg from '../../../PlatformPeg';
-import { _t } from '../../../languageHandler';
-
-/**
- * Check a version string is compatible with the Changelog
- * dialog ([vectorversion]-react-[react-sdk-version]-js-[js-sdk-version])
- */
-function checkVersion(ver) {
- const parts = ver.split('-');
- return parts.length == 5 && parts[1] == 'react' && parts[3] == 'js';
-}
-
-export default createReactClass({
- propTypes: {
- version: PropTypes.string.isRequired,
- newVersion: PropTypes.string.isRequired,
- releaseNotes: PropTypes.string,
- },
-
- displayReleaseNotes: function(releaseNotes) {
- const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog');
- Modal.createTrackedDialog('Display release notes', '', QuestionDialog, {
- title: _t("What's New"),
- description:
{releaseNotes}
,
- button: _t("Update"),
- onFinished: (update) => {
- if (update && PlatformPeg.get()) {
- PlatformPeg.get().installUpdate();
- }
- },
- });
- },
-
- displayChangelog: function() {
- const ChangelogDialog = sdk.getComponent('dialogs.ChangelogDialog');
- Modal.createTrackedDialog('Display Changelog', '', ChangelogDialog, {
- version: this.props.version,
- newVersion: this.props.newVersion,
- onFinished: (update) => {
- if (update && PlatformPeg.get()) {
- PlatformPeg.get().installUpdate();
- }
- },
- });
- },
-
- onUpdateClicked: function() {
- PlatformPeg.get().installUpdate();
- },
-
- render: function() {
- let action_button;
- // If we have release notes to display, we display them. Otherwise,
- // we display the Changelog Dialog which takes two versions and
- // automatically tells you what's changed (provided the versions
- // are in the right format)
- if (this.props.releaseNotes) {
- action_button = (
-
- );
- } else if (checkVersion(this.props.version) && checkVersion(this.props.newVersion)) {
- action_button = (
-
- );
- } else if (PlatformPeg.get()) {
- action_button = (
-
- );
- }
- return (
-
-
-
- {_t("A new version of Riot is available.")}
-
- {action_button}
-
- );
- },
-});
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 72c19587c8..0170aee17a 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -421,6 +421,12 @@
"Other users may not trust it": "Other users may not trust it",
"New login. Was this you?": "New login. Was this you?",
"Verify the new login accessing your account: %(name)s": "Verify the new login accessing your account: %(name)s",
+ "What's New": "What's New",
+ "Update": "Update",
+ "What's new?": "What's new?",
+ "Restart": "Restart",
+ "Upgrade your Riot": "Upgrade your Riot",
+ "A new version of Riot is available!": "A new version of Riot is available!",
"There was an error joining the room": "There was an error joining the room",
"Sorry, your homeserver is too old to participate in this room.": "Sorry, your homeserver is too old to participate in this room.",
"Please contact your homeserver administrator.": "Please contact your homeserver administrator.",
@@ -1391,10 +1397,6 @@
"Something went wrong when trying to get your communities.": "Something went wrong when trying to get your communities.",
"Display your community flair in rooms configured to show it.": "Display your community flair in rooms configured to show it.",
"You're not currently a member of any communities.": "You're not currently a member of any communities.",
- "What's New": "What's New",
- "Update": "Update",
- "What's new?": "What's new?",
- "A new version of Riot is available.": "A new version of Riot is available.",
"Error encountered (%(errorDetail)s).": "Error encountered (%(errorDetail)s).",
"Checking for an update...": "Checking for an update...",
"No update available.": "No update available.",
diff --git a/src/toasts/UpdateToast.tsx b/src/toasts/UpdateToast.tsx
new file mode 100644
index 0000000000..3d4b55a4ff
--- /dev/null
+++ b/src/toasts/UpdateToast.tsx
@@ -0,0 +1,90 @@
+/*
+Copyright 2020 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from "react";
+
+import { _t } from "../languageHandler";
+import GenericToast from "../components/views/toasts/GenericToast";
+import ToastStore from "../stores/ToastStore";
+import QuestionDialog from "../components/views/dialogs/QuestionDialog";
+import ChangelogDialog from "../components/views/dialogs/ChangelogDialog";
+import PlatformPeg from "../PlatformPeg";
+import Modal from "../Modal";
+
+const TOAST_KEY = "update";
+
+/*
+ * Check a version string is compatible with the Changelog
+ * dialog ([riot-version]-react-[react-sdk-version]-js-[js-sdk-version])
+ */
+function checkVersion(ver) {
+ const parts = ver.split('-');
+ return parts.length === 5 && parts[1] === 'react' && parts[3] === 'js';
+}
+
+function installUpdate() {
+ PlatformPeg.get().installUpdate();
+}
+
+export const showToast = (version: string, newVersion: string, releaseNotes?: string) => {
+ let onAccept;
+ let acceptLabel = _t("What's new?");
+ if (releaseNotes) {
+ onAccept = () => {
+ Modal.createTrackedDialog('Display release notes', '', QuestionDialog, {
+ title: _t("What's New"),
+ description:
{releaseNotes}
,
+ button: _t("Update"),
+ onFinished: (update) => {
+ if (update && PlatformPeg.get()) {
+ PlatformPeg.get().installUpdate();
+ }
+ },
+ });
+ };
+ } else if (checkVersion(version) && checkVersion(newVersion)) {
+ onAccept = () => {
+ Modal.createTrackedDialog('Display Changelog', '', ChangelogDialog, {
+ version,
+ newVersion,
+ onFinished: (update) => {
+ if (update && PlatformPeg.get()) {
+ PlatformPeg.get().installUpdate();
+ }
+ },
+ });
+ };
+ } else {
+ onAccept = installUpdate;
+ acceptLabel = _t("Restart");
+ }
+
+ ToastStore.sharedInstance().addOrReplaceToast({
+ key: TOAST_KEY,
+ title: _t("Upgrade your Riot"),
+ props: {
+ description: _t("A new version of Riot is available!"),
+ acceptLabel,
+ onAccept,
+ },
+ component: GenericToast,
+ priority: 20,
+ });
+};
+
+export const hideToast = () => {
+ ToastStore.sharedInstance().dismissToast(TOAST_KEY);
+};
From 41934268087fa0b9be828f1714eb4a5e3ab6f28a Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 22 May 2020 23:14:33 +0100
Subject: [PATCH 21/84] delint
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
src/components/structures/LoggedInView.tsx | 3 ---
src/components/structures/MatrixChat.tsx | 1 -
2 files changed, 4 deletions(-)
diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx
index 2a17233ec6..df21768da5 100644
--- a/src/components/structures/LoggedInView.tsx
+++ b/src/components/structures/LoggedInView.tsx
@@ -91,9 +91,6 @@ interface IProps {
currentUserId?: string;
currentGroupId?: string;
currentGroupIsNew?: boolean;
- version?: string;
- newVersion?: string;
- newVersionReleaseNotes?: string;
}
interface IState {
diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx
index f4d31708fe..e6db42af1d 100644
--- a/src/components/structures/MatrixChat.tsx
+++ b/src/components/structures/MatrixChat.tsx
@@ -226,7 +226,6 @@ export default class MatrixChat extends React.PureComponent {
leftDisabled: false,
middleDisabled: false,
- newVersionReleaseNotes: null,
checkingForUpdate: null,
hideToSRUsers: false,
From 3d2b56aecd38ea399f2666b688c4007438384411 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 22 May 2020 23:23:23 +0100
Subject: [PATCH 22/84] i18n
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
src/i18n/strings/en_EN.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 0170aee17a..97ada284b0 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -421,9 +421,9 @@
"Other users may not trust it": "Other users may not trust it",
"New login. Was this you?": "New login. Was this you?",
"Verify the new login accessing your account: %(name)s": "Verify the new login accessing your account: %(name)s",
+ "What's new?": "What's new?",
"What's New": "What's New",
"Update": "Update",
- "What's new?": "What's new?",
"Restart": "Restart",
"Upgrade your Riot": "Upgrade your Riot",
"A new version of Riot is available!": "A new version of Riot is available!",
From d29ba5b597db302a457bea0574603d93474c842d Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Sat, 23 May 2020 09:02:35 +0100
Subject: [PATCH 23/84] fix priority sorting to highest first
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
src/stores/ToastStore.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/stores/ToastStore.ts b/src/stores/ToastStore.ts
index 4f6d2963c5..23317a0ad3 100644
--- a/src/stores/ToastStore.ts
+++ b/src/stores/ToastStore.ts
@@ -55,7 +55,7 @@ export default class ToastStore extends EventEmitter {
const oldIndex = this.toasts.findIndex(t => t.key === newToast.key);
if (oldIndex === -1) {
let newIndex = this.toasts.length;
- while (newIndex > 0 && this.toasts[newIndex - 1].priority > newToast.priority) --newIndex;
+ while (newIndex > 0 && this.toasts[newIndex - 1].priority < newToast.priority) --newIndex;
this.toasts.splice(newIndex, 0, newToast);
} else {
this.toasts[oldIndex] = newToast;
From 1f52b5e2036ef0c6fb1192f9db9df41a63f562bd Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 25 May 2020 10:59:31 +0100
Subject: [PATCH 24/84] Update Crypto Store Too New copy
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
src/MatrixClientPeg.js | 4 +---
src/components/views/dialogs/CryptoStoreTooNewDialog.js | 6 ++----
src/i18n/strings/en_EN.json | 2 +-
3 files changed, 4 insertions(+), 8 deletions(-)
diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js
index 21f05b9759..704f1052fc 100644
--- a/src/MatrixClientPeg.js
+++ b/src/MatrixClientPeg.js
@@ -158,9 +158,7 @@ class _MatrixClientPeg {
// The js-sdk found a crypto DB too new for it to use
const CryptoStoreTooNewDialog =
sdk.getComponent("views.dialogs.CryptoStoreTooNewDialog");
- Modal.createDialog(CryptoStoreTooNewDialog, {
- host: window.location.host,
- });
+ Modal.createDialog(CryptoStoreTooNewDialog);
}
// this can happen for a number of reasons, the most likely being
// that the olm library was missing. It's not fatal.
diff --git a/src/components/views/dialogs/CryptoStoreTooNewDialog.js b/src/components/views/dialogs/CryptoStoreTooNewDialog.js
index 081e84696c..4694619601 100644
--- a/src/components/views/dialogs/CryptoStoreTooNewDialog.js
+++ b/src/components/views/dialogs/CryptoStoreTooNewDialog.js
@@ -42,11 +42,9 @@ export default (props) => {
};
const description =
- _t("You've previously used a newer version of Riot on %(host)s. " +
+ _t("You've previously used a newer version of Riot with this session. " +
"To use this version again with end to end encryption, you will " +
- "need to sign out and back in again. ",
- {host: props.host},
- );
+ "need to sign out and back in again.");
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 96ccf1589d..67ee31e71e 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1597,7 +1597,7 @@
"Create Room": "Create Room",
"Sign out": "Sign out",
"To avoid losing your chat history, you must export your room keys before logging out. You will need to go back to the newer version of Riot to do this": "To avoid losing your chat history, you must export your room keys before logging out. You will need to go back to the newer version of Riot to do this",
- "You've previously used a newer version of Riot on %(host)s. To use this version again with end to end encryption, you will need to sign out and back in again. ": "You've previously used a newer version of Riot on %(host)s. To use this version again with end to end encryption, you will need to sign out and back in again. ",
+ "You've previously used a newer version of Riot with this session. To use this version again with end to end encryption, you will need to sign out and back in again.": "You've previously used a newer version of Riot with this session. To use this version again with end to end encryption, you will need to sign out and back in again.",
"Incompatible Database": "Incompatible Database",
"Continue With Encryption Disabled": "Continue With Encryption Disabled",
"Confirm your account deactivation by using Single Sign On to prove your identity.": "Confirm your account deactivation by using Single Sign On to prove your identity.",
From 63f78b0808f89dc0fbffd70cc3d176d45d1a57b3 Mon Sep 17 00:00:00 2001
From: Jorik Schellekens
Date: Mon, 25 May 2020 13:40:05 +0100
Subject: [PATCH 25/84] Move tooltip to ts
---
package.json | 2 +
.../views/elements/{Field.js => Field.tsx} | 159 ++++++++++--------
.../elements/{Tooltip.js => Tooltip.tsx} | 86 +++++-----
.../views/settings/account/PhoneNumbers.js | 2 +-
src/dispatcher/actions.ts | 5 +
src/dispatcher/payloads/ViewUserPayload.ts | 17 ++
yarn.lock | 12 ++
7 files changed, 170 insertions(+), 113 deletions(-)
rename src/components/views/elements/{Field.js => Field.tsx} (67%)
rename src/components/views/elements/{Tooltip.js => Tooltip.tsx} (71%)
diff --git a/package.json b/package.json
index 7c008d5ccc..620957dd04 100644
--- a/package.json
+++ b/package.json
@@ -118,9 +118,11 @@
"@peculiar/webcrypto": "^1.0.22",
"@types/classnames": "^2.2.10",
"@types/flux": "^3.1.9",
+ "@types/lodash": "^4.14.152",
"@types/modernizr": "^3.5.3",
"@types/qrcode": "^1.3.4",
"@types/react": "16.9",
+ "@types/react-dom": "^16.9.8",
"@types/zxcvbn": "^4.4.0",
"babel-eslint": "^10.0.3",
"babel-jest": "^24.9.0",
diff --git a/src/components/views/elements/Field.js b/src/components/views/elements/Field.tsx
similarity index 67%
rename from src/components/views/elements/Field.js
rename to src/components/views/elements/Field.tsx
index 2ebb90da26..100a6ebf56 100644
--- a/src/components/views/elements/Field.js
+++ b/src/components/views/elements/Field.tsx
@@ -15,10 +15,9 @@ limitations under the License.
*/
import React from 'react';
-import PropTypes from 'prop-types';
import classNames from 'classnames';
import * as sdk from '../../../index';
-import { debounce } from 'lodash';
+import { debounce, Cancelable } from 'lodash';
// Invoke validation from user input (when typing, etc.) at most once every N ms.
const VALIDATION_THROTTLE_MS = 200;
@@ -29,51 +28,88 @@ function getId() {
return `${BASE_ID}_${count++}`;
}
-export default class Field extends React.PureComponent {
- static propTypes = {
- // The field's ID, which binds the input and label together. Immutable.
- id: PropTypes.string,
- // The element to create. Defaults to "input".
- // To define options for a select, use
- element: PropTypes.oneOf(["input", "select", "textarea"]),
- // The field's type (when used as an ). Defaults to "text".
- type: PropTypes.string,
- // id of a