From dbd102541e72952f12f020267ede7fd5b92c3e8e Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Sat, 3 Jul 2021 11:37:06 +0100 Subject: [PATCH 01/14] Migrate E2eSetup to TypeScript --- .../structures/auth/{E2eSetup.js => E2eSetup.tsx} | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) rename src/components/structures/auth/{E2eSetup.js => E2eSetup.tsx} (84%) diff --git a/src/components/structures/auth/E2eSetup.js b/src/components/structures/auth/E2eSetup.tsx similarity index 84% rename from src/components/structures/auth/E2eSetup.js rename to src/components/structures/auth/E2eSetup.tsx index 9b627449bc..93cb92664f 100644 --- a/src/components/structures/auth/E2eSetup.js +++ b/src/components/structures/auth/E2eSetup.tsx @@ -15,20 +15,19 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import AuthPage from '../../views/auth/AuthPage'; import CompleteSecurityBody from '../../views/auth/CompleteSecurityBody'; import CreateCrossSigningDialog from '../../views/dialogs/security/CreateCrossSigningDialog'; import { replaceableComponent } from "../../../utils/replaceableComponent"; -@replaceableComponent("structures.auth.E2eSetup") -export default class E2eSetup extends React.Component { - static propTypes = { - onFinished: PropTypes.func.isRequired, - accountPassword: PropTypes.string, - tokenLogin: PropTypes.bool, - }; +interface IProps { + onFinished: () => void; + accountPassword?: string; + tokenLogin?: boolean; +} +@replaceableComponent("structures.auth.E2eSetup") +export default class E2eSetup extends React.Component { render() { return ( From 298a33867635086ba878a13534892da991b74ba7 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Sat, 3 Jul 2021 11:38:51 +0100 Subject: [PATCH 02/14] Migrate CompleteSecurity to TypeScript --- ...mpleteSecurity.js => CompleteSecurity.tsx} | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) rename src/components/structures/auth/{CompleteSecurity.js => CompleteSecurity.tsx} (87%) diff --git a/src/components/structures/auth/CompleteSecurity.js b/src/components/structures/auth/CompleteSecurity.tsx similarity index 87% rename from src/components/structures/auth/CompleteSecurity.js rename to src/components/structures/auth/CompleteSecurity.tsx index d691f6034b..2f37e60450 100644 --- a/src/components/structures/auth/CompleteSecurity.js +++ b/src/components/structures/auth/CompleteSecurity.tsx @@ -15,39 +15,42 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import { _t } from '../../../languageHandler'; import * as sdk from '../../../index'; import { SetupEncryptionStore, Phase } from '../../../stores/SetupEncryptionStore'; import SetupEncryptionBody from "./SetupEncryptionBody"; import { replaceableComponent } from "../../../utils/replaceableComponent"; -@replaceableComponent("structures.auth.CompleteSecurity") -export default class CompleteSecurity extends React.Component { - static propTypes = { - onFinished: PropTypes.func.isRequired, - }; +interface IProps { + onFinished: () => void; +} - constructor() { - super(); +interface IState { + phase: Phase; +} + +@replaceableComponent("structures.auth.CompleteSecurity") +export default class CompleteSecurity extends React.Component { + constructor(props: IProps) { + super(props); const store = SetupEncryptionStore.sharedInstance(); - store.on("update", this._onStoreUpdate); + store.on("update", this.onStoreUpdate); store.start(); this.state = { phase: store.phase }; } - _onStoreUpdate = () => { + private onStoreUpdate = (): void => { const store = SetupEncryptionStore.sharedInstance(); this.setState({ phase: store.phase }); }; - componentWillUnmount() { + public componentWillUnmount(): void { const store = SetupEncryptionStore.sharedInstance(); - store.off("update", this._onStoreUpdate); + store.off("update", this.onStoreUpdate); store.stop(); } - render() { + public render() { const AuthPage = sdk.getComponent("auth.AuthPage"); const CompleteSecurityBody = sdk.getComponent("auth.CompleteSecurityBody"); const { phase } = this.state; From f4ec197513899d1117818919780b5377f21a39ce Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Sat, 3 Jul 2021 11:55:10 +0100 Subject: [PATCH 03/14] Migrate ForgotPassword to TypeScript --- .../{ForgotPassword.js => ForgotPassword.tsx} | 113 +++++++++++------- 1 file changed, 68 insertions(+), 45 deletions(-) rename src/components/structures/auth/{ForgotPassword.js => ForgotPassword.tsx} (81%) diff --git a/src/components/structures/auth/ForgotPassword.js b/src/components/structures/auth/ForgotPassword.tsx similarity index 81% rename from src/components/structures/auth/ForgotPassword.js rename to src/components/structures/auth/ForgotPassword.tsx index 9f2ac9deed..432f69fd1b 100644 --- a/src/components/structures/auth/ForgotPassword.js +++ b/src/components/structures/auth/ForgotPassword.tsx @@ -17,7 +17,6 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import { _t, _td } from '../../../languageHandler'; import * as sdk from '../../../index'; import Modal from "../../../Modal"; @@ -31,27 +30,50 @@ import PassphraseField from '../../views/auth/PassphraseField'; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { PASSWORD_MIN_SCORE } from '../../views/auth/RegistrationForm'; -// Phases -// Show the forgot password inputs -const PHASE_FORGOT = 1; -// Email is in the process of being sent -const PHASE_SENDING_EMAIL = 2; -// Email has been sent -const PHASE_EMAIL_SENT = 3; -// User has clicked the link in email and completed reset -const PHASE_DONE = 4; +import { IValidationResult } from "../../views/elements/Validation"; + +enum Phase { + // Show the forgot password inputs + Forgot = 1, + // Email is in the process of being sent + SendingEmail = 2, + // Email has been sent + EmailSent = 3, + // User has clicked the link in email and completed reset + Done = 4, +} + +interface IProps { + serverConfig: ValidatedServerConfig; + onServerConfigChange: () => void; + onLoginClick?: () => void; + onComplete: () => void; +} + +interface IState { + phase: Phase; + email: string; + password: string; + password2: string; + errorText: string; + + // We perform liveliness checks later, but for now suppress the errors. + // We also track the server dead errors independently of the regular errors so + // that we can render it differently, and override any other error the user may + // be seeing. + serverIsAlive: boolean; + serverErrorIsFatal: boolean; + serverDeadError: string; + + passwordFieldValid: boolean; +} @replaceableComponent("structures.auth.ForgotPassword") -export default class ForgotPassword extends React.Component { - static propTypes = { - serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired, - onServerConfigChange: PropTypes.func.isRequired, - onLoginClick: PropTypes.func, - onComplete: PropTypes.func.isRequired, - }; +export default class ForgotPassword extends React.Component { + private reset: PasswordReset; state = { - phase: PHASE_FORGOT, + phase: Phase.Forgot, email: "", password: "", password2: "", @@ -64,30 +86,31 @@ export default class ForgotPassword extends React.Component { serverIsAlive: true, serverErrorIsFatal: false, serverDeadError: "", + passwordFieldValid: false, }; - constructor(props) { + constructor(props: IProps) { super(props); CountlyAnalytics.instance.track("onboarding_forgot_password_begin"); } - componentDidMount() { + public componentDidMount() { this.reset = null; - this._checkServerLiveliness(this.props.serverConfig); + this.checkServerLiveliness(this.props.serverConfig); } // TODO: [REACT-WARNING] Replace with appropriate lifecycle event // eslint-disable-next-line camelcase - UNSAFE_componentWillReceiveProps(newProps) { + public UNSAFE_componentWillReceiveProps(newProps: IProps): void { if (newProps.serverConfig.hsUrl === this.props.serverConfig.hsUrl && newProps.serverConfig.isUrl === this.props.serverConfig.isUrl) return; // Do a liveliness check on the new URLs - this._checkServerLiveliness(newProps.serverConfig); + this.checkServerLiveliness(newProps.serverConfig); } - async _checkServerLiveliness(serverConfig) { + private async checkServerLiveliness(serverConfig): Promise { try { await AutoDiscoveryUtils.validateServerConfigWithStaticUrls( serverConfig.hsUrl, @@ -98,28 +121,28 @@ export default class ForgotPassword extends React.Component { serverIsAlive: true, }); } catch (e) { - this.setState(AutoDiscoveryUtils.authComponentStateForError(e, "forgot_password")); + this.setState(AutoDiscoveryUtils.authComponentStateForError(e, "forgot_password") as IState); } } - submitPasswordReset(email, password) { + public submitPasswordReset(email: string, password: string): void { this.setState({ - phase: PHASE_SENDING_EMAIL, + phase: Phase.SendingEmail, }); this.reset = new PasswordReset(this.props.serverConfig.hsUrl, this.props.serverConfig.isUrl); this.reset.resetPassword(email, password).then(() => { this.setState({ - phase: PHASE_EMAIL_SENT, + phase: Phase.EmailSent, }); }, (err) => { this.showErrorDialog(_t('Failed to send email') + ": " + err.message); this.setState({ - phase: PHASE_FORGOT, + phase: Phase.Forgot, }); }); } - onVerify = async ev => { + private onVerify = async (ev: React.MouseEvent): Promise => { ev.preventDefault(); if (!this.reset) { console.error("onVerify called before submitPasswordReset!"); @@ -127,17 +150,17 @@ export default class ForgotPassword extends React.Component { } try { await this.reset.checkEmailLinkClicked(); - this.setState({ phase: PHASE_DONE }); + this.setState({ phase: Phase.Done }); } catch (err) { this.showErrorDialog(err.message); } }; - onSubmitForm = async ev => { + private onSubmitForm = async (ev: React.FormEvent): Promise => { ev.preventDefault(); // refresh the server errors, just in case the server came back online - await this._checkServerLiveliness(this.props.serverConfig); + await this.checkServerLiveliness(this.props.serverConfig); await this['password_field'].validate({ allowEmpty: false }); @@ -172,27 +195,27 @@ export default class ForgotPassword extends React.Component { } }; - onInputChanged = (stateKey, ev) => { + private onInputChanged = (stateKey: string, ev: React.FormEvent) => { this.setState({ - [stateKey]: ev.target.value, - }); + [stateKey]: ev.currentTarget.value, + } as any); }; - onLoginClick = ev => { + private onLoginClick = (ev: React.MouseEvent): void => { ev.preventDefault(); ev.stopPropagation(); this.props.onLoginClick(); }; - showErrorDialog(body, title) { + public showErrorDialog(description: string, title?: string) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createTrackedDialog('Forgot Password Error', '', ErrorDialog, { - title: title, - description: body, + title, + description, }); } - onPasswordValidate(result) { + private onPasswordValidate(result: IValidationResult) { this.setState({ passwordFieldValid: result.valid, }); @@ -316,16 +339,16 @@ export default class ForgotPassword extends React.Component { let resetPasswordJsx; switch (this.state.phase) { - case PHASE_FORGOT: + case Phase.Forgot: resetPasswordJsx = this.renderForgot(); break; - case PHASE_SENDING_EMAIL: + case Phase.SendingEmail: resetPasswordJsx = this.renderSendingEmail(); break; - case PHASE_EMAIL_SENT: + case Phase.EmailSent: resetPasswordJsx = this.renderEmailSent(); break; - case PHASE_DONE: + case Phase.Done: resetPasswordJsx = this.renderDone(); break; } From ef2848664fdf548c1eb167d3c08f54ebdd64f052 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Sat, 3 Jul 2021 12:03:00 +0100 Subject: [PATCH 04/14] Migrate InlineTermsAgreement to TypeScript --- ...sAgreement.js => InlineTermsAgreement.tsx} | 51 +++++++++++-------- 1 file changed, 30 insertions(+), 21 deletions(-) rename src/components/views/terms/{InlineTermsAgreement.js => InlineTermsAgreement.tsx} (81%) diff --git a/src/components/views/terms/InlineTermsAgreement.js b/src/components/views/terms/InlineTermsAgreement.tsx similarity index 81% rename from src/components/views/terms/InlineTermsAgreement.js rename to src/components/views/terms/InlineTermsAgreement.tsx index e3b4df4712..62594746ed 100644 --- a/src/components/views/terms/InlineTermsAgreement.js +++ b/src/components/views/terms/InlineTermsAgreement.tsx @@ -15,39 +15,48 @@ limitations under the License. */ import React from "react"; -import PropTypes from "prop-types"; import { _t, pickBestLanguage } from "../../../languageHandler"; import * as sdk from "../../.."; import { objectClone } from "../../../utils/objects"; import StyledCheckbox from "../elements/StyledCheckbox"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +interface IProps { + policiesAndServicePairs: any[]; + onFinished: (string) => void; + agreedUrls: string[], // array of URLs the user has accepted + introElement: Node, +} + +interface IState { + policies: Policy[], + busy: boolean; +} + +interface Policy { + checked: boolean; + url: string; + name: string; +} + @replaceableComponent("views.terms.InlineTermsAgreement") -export default class InlineTermsAgreement extends React.Component { - static propTypes = { - policiesAndServicePairs: PropTypes.array.isRequired, // array of service/policy pairs - agreedUrls: PropTypes.array.isRequired, // array of URLs the user has accepted - onFinished: PropTypes.func.isRequired, // takes an argument of accepted URLs - introElement: PropTypes.node, - }; - - constructor() { - super(); - +export default class InlineTermsAgreement extends React.Component { + constructor(props: IProps) { + super(props); this.state = { policies: [], busy: false, }; } - componentDidMount() { + public componentDidMount(): void { // Build all the terms the user needs to accept const policies = []; // { checked, url, name } for (const servicePolicies of this.props.policiesAndServicePairs) { const availablePolicies = Object.values(servicePolicies.policies); for (const policy of availablePolicies) { const language = pickBestLanguage(Object.keys(policy).filter(p => p !== 'version')); - const renderablePolicy = { + const renderablePolicy: Policy = { checked: false, url: policy[language].url, name: policy[language].name, @@ -59,13 +68,13 @@ export default class InlineTermsAgreement extends React.Component { this.setState({ policies }); } - _togglePolicy = (index) => { + private togglePolicy = (index: number): void => { const policies = objectClone(this.state.policies); policies[index].checked = !policies[index].checked; this.setState({ policies }); }; - _onContinue = () => { + private onContinue = (): void => { const hasUnchecked = !!this.state.policies.some(p => !p.checked); if (hasUnchecked) return; @@ -73,7 +82,7 @@ export default class InlineTermsAgreement extends React.Component { this.props.onFinished(this.state.policies.map(p => p.url)); }; - _renderCheckboxes() { + private renderCheckboxes(): React.ReactNode[] { const rendered = []; for (let i = 0; i < this.state.policies.length; i++) { const policy = this.state.policies[i]; @@ -93,7 +102,7 @@ export default class InlineTermsAgreement extends React.Component {
{introText}
- this._togglePolicy(i)} checked={policy.checked}> + this.togglePolicy(i)} checked={policy.checked}> {_t("Accept")}
@@ -103,16 +112,16 @@ export default class InlineTermsAgreement extends React.Component { return rendered; } - render() { + public render(): React.ReactNode { const AccessibleButton = sdk.getComponent("views.elements.AccessibleButton"); const hasUnchecked = !!this.state.policies.some(p => !p.checked); return (
{this.props.introElement} - {this._renderCheckboxes()} + {this.renderCheckboxes()} From ef4d58c0d9e7f81406a930e1059828eac4854e8d Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Sat, 3 Jul 2021 12:08:56 +0100 Subject: [PATCH 05/14] Migrate SetIntegrationManager to TypeScript --- ...onManager.js => SetIntegrationManager.tsx} | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) rename src/components/views/settings/{SetIntegrationManager.js => SetIntegrationManager.tsx} (89%) diff --git a/src/components/views/settings/SetIntegrationManager.js b/src/components/views/settings/SetIntegrationManager.tsx similarity index 89% rename from src/components/views/settings/SetIntegrationManager.js rename to src/components/views/settings/SetIntegrationManager.tsx index 6636c87df8..ada78e2848 100644 --- a/src/components/views/settings/SetIntegrationManager.js +++ b/src/components/views/settings/SetIntegrationManager.tsx @@ -17,15 +17,25 @@ limitations under the License. import React from 'react'; import { _t } from "../../../languageHandler"; import { IntegrationManagers } from "../../../integrations/IntegrationManagers"; +import { IntegrationManagerInstance } from "../../../integrations/IntegrationManagerInstance"; import * as sdk from '../../../index'; import SettingsStore from "../../../settings/SettingsStore"; import { SettingLevel } from "../../../settings/SettingLevel"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +interface IProps { + +} + +interface IState { + currentManager: IntegrationManagerInstance; + provisioningEnabled: boolean; +} + @replaceableComponent("views.settings.SetIntegrationManager") -export default class SetIntegrationManager extends React.Component { - constructor() { - super(); +export default class SetIntegrationManager extends React.Component { + constructor(props: IProps) { + super(props); const currentManager = IntegrationManagers.sharedInstance().getPrimaryManager(); @@ -35,7 +45,7 @@ export default class SetIntegrationManager extends React.Component { }; } - onProvisioningToggled = () => { + private onProvisioningToggled = (): void => { const current = this.state.provisioningEnabled; SettingsStore.setValue("integrationProvisioning", null, SettingLevel.ACCOUNT, !current).catch(err => { console.error("Error changing integration manager provisioning"); @@ -46,7 +56,7 @@ export default class SetIntegrationManager extends React.Component { this.setState({ provisioningEnabled: !current }); }; - render() { + public render(): React.ReactNode { const ToggleSwitch = sdk.getComponent("views.elements.ToggleSwitch"); const currentManager = this.state.currentManager; From e7743e2a57bdff3de4752f4fa0c8cc5ce94588cb Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Sat, 3 Jul 2021 12:17:38 +0100 Subject: [PATCH 06/14] Migrate RoomScrollStateStore to TypeScript --- src/@types/global.d.ts | 2 + src/components/structures/RoomView.tsx | 4 +- src/stores/RoomScrollStateStore.js | 50 ------------------------ src/stores/RoomScrollStateStore.ts | 53 ++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 52 deletions(-) delete mode 100644 src/stores/RoomScrollStateStore.js create mode 100644 src/stores/RoomScrollStateStore.ts diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index f75c17aaf4..d257ee4c5c 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -46,6 +46,7 @@ import { VoiceRecordingStore } from "../stores/VoiceRecordingStore"; import PerformanceMonitor from "../performance"; import UIStore from "../stores/UIStore"; import { SetupEncryptionStore } from "../stores/SetupEncryptionStore"; +import { RoomScrollStateStore } from "../stores/RoomScrollStateStore"; declare global { interface Window { @@ -87,6 +88,7 @@ declare global { mxPerformanceEntryNames: any; mxUIStore: UIStore; mxSetupEncryptionStore?: SetupEncryptionStore; + mxRoomScrollStateStore?: RoomScrollStateStore; } interface Document { diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 2c8fc08dac..0985719302 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -42,7 +42,7 @@ import eventSearch, { searchPagination } from '../../Searching'; import MainSplit from './MainSplit'; import RightPanel from './RightPanel'; import RoomViewStore from '../../stores/RoomViewStore'; -import RoomScrollStateStore from '../../stores/RoomScrollStateStore'; +import RoomScrollStateStore, { ScrollState } from '../../stores/RoomScrollStateStore'; import WidgetEchoStore from '../../stores/WidgetEchoStore'; import SettingsStore from "../../settings/SettingsStore"; import { Layout } from "../../settings/Layout"; @@ -1577,7 +1577,7 @@ export default class RoomView extends React.Component { // get the current scroll position of the room, so that it can be // restored when we switch back to it. // - private getScrollState() { + private getScrollState(): ScrollState { const messagePanel = this.messagePanel; if (!messagePanel) return null; diff --git a/src/stores/RoomScrollStateStore.js b/src/stores/RoomScrollStateStore.js deleted file mode 100644 index 07848283d1..0000000000 --- a/src/stores/RoomScrollStateStore.js +++ /dev/null @@ -1,50 +0,0 @@ -/* -Copyright 2017 New Vector Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -/** - * Stores where the user has scrolled to in each room - */ -class RoomScrollStateStore { - constructor() { - // A map from room id to scroll state. - // - // If there is no special scroll state (ie, we are following the live - // timeline), the scroll state is null. Otherwise, it is an object with - // the following properties: - // - // focussedEvent: the ID of the 'focussed' event. Typically this is - // the last event fully visible in the viewport, though if we - // have done an explicit scroll to an explicit event, it will be - // that event. - // - // pixelOffset: the number of pixels the window is scrolled down - // from the focussedEvent. - this._scrollStateMap = {}; - } - - getScrollState(roomId) { - return this._scrollStateMap[roomId]; - } - - setScrollState(roomId, scrollState) { - this._scrollStateMap[roomId] = scrollState; - } -} - -if (global.mx_RoomScrollStateStore === undefined) { - global.mx_RoomScrollStateStore = new RoomScrollStateStore(); -} -export default global.mx_RoomScrollStateStore; diff --git a/src/stores/RoomScrollStateStore.ts b/src/stores/RoomScrollStateStore.ts new file mode 100644 index 0000000000..1d8d4eb8fd --- /dev/null +++ b/src/stores/RoomScrollStateStore.ts @@ -0,0 +1,53 @@ +/* +Copyright 2017 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +export interface ScrollState { + focussedEvent: string; + pixelOffset: number; +} + +/** + * Stores where the user has scrolled to in each room + */ +export class RoomScrollStateStore { + // A map from room id to scroll state. + // + // If there is no special scroll state (ie, we are following the live + // timeline), the scroll state is null. Otherwise, it is an object with + // the following properties: + // + // focussedEvent: the ID of the 'focussed' event. Typically this is + // the last event fully visible in the viewport, though if we + // have done an explicit scroll to an explicit event, it will be + // that event. + // + // pixelOffset: the number of pixels the window is scrolled down + // from the focussedEvent. + private scrollStateMap = new Map(); + + public getScrollState(roomId: string): ScrollState { + return this.scrollStateMap.get(roomId); + } + + setScrollState(roomId: string, scrollState: ScrollState): void { + this.scrollStateMap.set(roomId, scrollState); + } +} + +if (window.mxRoomScrollStateStore === undefined) { + window.mxRoomScrollStateStore = new RoomScrollStateStore(); +} +export default window.mxRoomScrollStateStore; From d5cebf5e8d8f22230c109aa0809c58e44e8f337f Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Sat, 3 Jul 2021 12:22:58 +0100 Subject: [PATCH 07/14] Migrate index to TypeScript --- src/notifications/{index.js => index.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/notifications/{index.js => index.ts} (100%) diff --git a/src/notifications/index.js b/src/notifications/index.ts similarity index 100% rename from src/notifications/index.js rename to src/notifications/index.ts From 66dde68377b5d124c902da2c6da7e7b7166dadd5 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Sat, 3 Jul 2021 12:29:13 +0100 Subject: [PATCH 08/14] Migrate VectorPushRulesDefinitions to TypeScript --- ...tions.js => VectorPushRulesDefinitions.ts} | 47 ++++++++++++++----- 1 file changed, 34 insertions(+), 13 deletions(-) rename src/notifications/{VectorPushRulesDefinitions.js => VectorPushRulesDefinitions.ts} (91%) diff --git a/src/notifications/VectorPushRulesDefinitions.js b/src/notifications/VectorPushRulesDefinitions.ts similarity index 91% rename from src/notifications/VectorPushRulesDefinitions.js rename to src/notifications/VectorPushRulesDefinitions.ts index df41f23ec5..38dd88e6c6 100644 --- a/src/notifications/VectorPushRulesDefinitions.js +++ b/src/notifications/VectorPushRulesDefinitions.ts @@ -20,15 +20,25 @@ import { StandardActions } from "./StandardActions"; import { PushRuleVectorState } from "./PushRuleVectorState"; import { NotificationUtils } from "./NotificationUtils"; +interface IProps { + kind: Kind; + description: string; + vectorStateToActions: Action; +} + class VectorPushRuleDefinition { - constructor(opts) { + private kind: Kind; + private description: string; + private vectorStateToActions: Action; + + constructor(opts: IProps) { this.kind = opts.kind; this.description = opts.description; this.vectorStateToActions = opts.vectorStateToActions; } // Translate the rule actions and its enabled value into vector state - ruleToVectorState(rule) { + public ruleToVectorState(rule): VectorPushRuleDefinition { let enabled = false; if (rule) { enabled = rule.enabled; @@ -63,13 +73,24 @@ class VectorPushRuleDefinition { } } +enum Kind { + Override = "override", + Underride = "underride", +} + +interface Action { + on: StandardActions; + loud: StandardActions; + off: StandardActions; +} + /** * The descriptions of rules managed by the Vector UI. */ export const VectorPushRulesDefinitions = { // Messages containing user's display name ".m.rule.contains_display_name": new VectorPushRuleDefinition({ - kind: "override", + kind: Kind.Override, description: _td("Messages containing my display name"), // passed through _t() translation in src/components/views/settings/Notifications.js vectorStateToActions: { // The actions for each vector state, or null to disable the rule. on: StandardActions.ACTION_NOTIFY, @@ -80,7 +101,7 @@ export const VectorPushRulesDefinitions = { // Messages containing user's username (localpart/MXID) ".m.rule.contains_user_name": new VectorPushRuleDefinition({ - kind: "override", + kind: Kind.Override, description: _td("Messages containing my username"), // passed through _t() translation in src/components/views/settings/Notifications.js vectorStateToActions: { // The actions for each vector state, or null to disable the rule. on: StandardActions.ACTION_NOTIFY, @@ -91,7 +112,7 @@ export const VectorPushRulesDefinitions = { // Messages containing @room ".m.rule.roomnotif": new VectorPushRuleDefinition({ - kind: "override", + kind: Kind.Override, description: _td("Messages containing @room"), // passed through _t() translation in src/components/views/settings/Notifications.js vectorStateToActions: { // The actions for each vector state, or null to disable the rule. on: StandardActions.ACTION_NOTIFY, @@ -102,7 +123,7 @@ export const VectorPushRulesDefinitions = { // Messages just sent to the user in a 1:1 room ".m.rule.room_one_to_one": new VectorPushRuleDefinition({ - kind: "underride", + kind: Kind.Underride, description: _td("Messages in one-to-one chats"), // passed through _t() translation in src/components/views/settings/Notifications.js vectorStateToActions: { on: StandardActions.ACTION_NOTIFY, @@ -113,7 +134,7 @@ export const VectorPushRulesDefinitions = { // Encrypted messages just sent to the user in a 1:1 room ".m.rule.encrypted_room_one_to_one": new VectorPushRuleDefinition({ - kind: "underride", + kind: Kind.Underride, description: _td("Encrypted messages in one-to-one chats"), // passed through _t() translation in src/components/views/settings/Notifications.js vectorStateToActions: { on: StandardActions.ACTION_NOTIFY, @@ -126,7 +147,7 @@ export const VectorPushRulesDefinitions = { // 1:1 room messages are catched by the .m.rule.room_one_to_one rule if any defined // By opposition, all other room messages are from group chat rooms. ".m.rule.message": new VectorPushRuleDefinition({ - kind: "underride", + kind: Kind.Underride, description: _td("Messages in group chats"), // passed through _t() translation in src/components/views/settings/Notifications.js vectorStateToActions: { on: StandardActions.ACTION_NOTIFY, @@ -139,7 +160,7 @@ export const VectorPushRulesDefinitions = { // Encrypted 1:1 room messages are catched by the .m.rule.encrypted_room_one_to_one rule if any defined // By opposition, all other room messages are from group chat rooms. ".m.rule.encrypted": new VectorPushRuleDefinition({ - kind: "underride", + kind: Kind.Underride, description: _td("Encrypted messages in group chats"), // passed through _t() translation in src/components/views/settings/Notifications.js vectorStateToActions: { on: StandardActions.ACTION_NOTIFY, @@ -150,7 +171,7 @@ export const VectorPushRulesDefinitions = { // Invitation for the user ".m.rule.invite_for_me": new VectorPushRuleDefinition({ - kind: "underride", + kind: Kind.Underride, description: _td("When I'm invited to a room"), // passed through _t() translation in src/components/views/settings/Notifications.js vectorStateToActions: { on: StandardActions.ACTION_NOTIFY, @@ -161,7 +182,7 @@ export const VectorPushRulesDefinitions = { // Incoming call ".m.rule.call": new VectorPushRuleDefinition({ - kind: "underride", + kind: Kind.Underride, description: _td("Call invitation"), // passed through _t() translation in src/components/views/settings/Notifications.js vectorStateToActions: { on: StandardActions.ACTION_NOTIFY, @@ -172,7 +193,7 @@ export const VectorPushRulesDefinitions = { // Notifications from bots ".m.rule.suppress_notices": new VectorPushRuleDefinition({ - kind: "override", + kind: Kind.Override, description: _td("Messages sent by bot"), // passed through _t() translation in src/components/views/settings/Notifications.js vectorStateToActions: { // .m.rule.suppress_notices is a "negative" rule, we have to invert its enabled value for vector UI @@ -184,7 +205,7 @@ export const VectorPushRulesDefinitions = { // Room upgrades (tombstones) ".m.rule.tombstone": new VectorPushRuleDefinition({ - kind: "override", + kind: Kind.Override, description: _td("When rooms are upgraded"), // passed through _t() translation in src/components/views/settings/Notifications.js vectorStateToActions: { // The actions for each vector state, or null to disable the rule. on: StandardActions.ACTION_NOTIFY, From b18691a1cba4a251ac7f6990ad7b76b8b507318c Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Sat, 3 Jul 2021 12:31:10 +0100 Subject: [PATCH 09/14] Migrate VerificationCancelled to TypeScript --- ...cationCancelled.js => VerificationCancelled.tsx} | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) rename src/components/views/verification/{VerificationCancelled.js => VerificationCancelled.tsx} (90%) diff --git a/src/components/views/verification/VerificationCancelled.js b/src/components/views/verification/VerificationCancelled.tsx similarity index 90% rename from src/components/views/verification/VerificationCancelled.js rename to src/components/views/verification/VerificationCancelled.tsx index 0d868edaaa..aa34b22382 100644 --- a/src/components/views/verification/VerificationCancelled.js +++ b/src/components/views/verification/VerificationCancelled.tsx @@ -15,18 +15,17 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; import { replaceableComponent } from "../../../utils/replaceableComponent"; -@replaceableComponent("views.verification.VerificationCancelled") -export default class VerificationCancelled extends React.Component { - static propTypes = { - onDone: PropTypes.func.isRequired, - } +interface IProps { + onDone: () => void; +} - render() { +@replaceableComponent("views.verification.VerificationCancelled") +export default class VerificationCancelled extends React.Component { + public render(): React.ReactNode { const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); return

{_t( From a1135783bc16c056139e910d70ed7eae4cbd8c35 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Sat, 3 Jul 2021 12:32:08 +0100 Subject: [PATCH 10/14] Migrate VerificationComplete to TypeScript --- ...ficationComplete.js => VerificationComplete.tsx} | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) rename src/components/views/verification/{VerificationComplete.js => VerificationComplete.tsx} (91%) diff --git a/src/components/views/verification/VerificationComplete.js b/src/components/views/verification/VerificationComplete.tsx similarity index 91% rename from src/components/views/verification/VerificationComplete.js rename to src/components/views/verification/VerificationComplete.tsx index c382a6dde4..7da601fc93 100644 --- a/src/components/views/verification/VerificationComplete.js +++ b/src/components/views/verification/VerificationComplete.tsx @@ -15,18 +15,17 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; import { replaceableComponent } from "../../../utils/replaceableComponent"; -@replaceableComponent("views.verification.VerificationComplete") -export default class VerificationComplete extends React.Component { - static propTypes = { - onDone: PropTypes.func.isRequired, - } +interface IProps { + onDone: () => void; +} - render() { +@replaceableComponent("views.verification.VerificationComplete") +export default class VerificationComplete extends React.Component { + public render(): React.ReactNode { const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); return

{_t("Verified!")}

From 103caffb5b193c45b10e111674bc7651d57b0edc Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Sat, 3 Jul 2021 12:35:14 +0100 Subject: [PATCH 11/14] Delete unused VerificationQREmojiOPtions --- .../VerificationQREmojiOptions.js | 68 ------------------- 1 file changed, 68 deletions(-) delete mode 100644 src/components/views/verification/VerificationQREmojiOptions.js diff --git a/src/components/views/verification/VerificationQREmojiOptions.js b/src/components/views/verification/VerificationQREmojiOptions.js deleted file mode 100644 index 785d383035..0000000000 --- a/src/components/views/verification/VerificationQREmojiOptions.js +++ /dev/null @@ -1,68 +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 AccessibleButton from "../elements/AccessibleButton"; -import { replaceableComponent } from "../../../utils/replaceableComponent"; -import VerificationQRCode from "../elements/crypto/VerificationQRCode"; -import Spinner from "../elements/Spinner"; -import { SCAN_QR_CODE_METHOD } from "matrix-js-sdk/src/crypto/verification/QRCode"; - -@replaceableComponent("views.verification.VerificationQREmojiOptions") -export default class VerificationQREmojiOptions extends React.Component { - static propTypes = { - request: PropTypes.object.isRequired, - onCancel: PropTypes.func.isRequired, - onStartEmoji: PropTypes.func.isRequired, - }; - - render() { - const { request } = this.props; - const showQR = request.otherPartySupportsMethod(SCAN_QR_CODE_METHOD); - - let qrCode; - if (showQR) { - qrCode = ; - } else { - qrCode =
; - } - - return ( -
- {_t("Verify this session by completing one of the following:")} -
-
-

{_t("Scan this unique code")}

- {qrCode} -
-
{_t("or")}
-
-

{_t("Compare unique emoji")}

- {_t("Compare a unique set of emoji if you don't have a camera on either device")} - - {_t("Start")} - -
-
- - {_t("Cancel")} - -
- ); - } -} From b5ff7eb7d2b49977a738c6c49c2b0990f9323e3e Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Sat, 3 Jul 2021 12:43:18 +0100 Subject: [PATCH 12/14] Migrate VerificationShowSas to TypeScript --- ...tionShowSas.js => VerificationShowSas.tsx} | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) rename src/components/views/verification/{VerificationShowSas.js => VerificationShowSas.tsx} (91%) diff --git a/src/components/views/verification/VerificationShowSas.js b/src/components/views/verification/VerificationShowSas.tsx similarity index 91% rename from src/components/views/verification/VerificationShowSas.js rename to src/components/views/verification/VerificationShowSas.tsx index 3f154b9f01..72dd5f0669 100644 --- a/src/components/views/verification/VerificationShowSas.js +++ b/src/components/views/verification/VerificationShowSas.tsx @@ -15,7 +15,8 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; +import { SAS } from "matrix-js-sdk/src/crypto/verification/SAS"; +import { DeviceInfo } from "matrix-js-sdk/src//crypto/deviceinfo"; import { _t, _td } from '../../../languageHandler'; import { PendingActionSpinner } from "../right_panel/EncryptionInfo"; import AccessibleButton from "../elements/AccessibleButton"; @@ -23,24 +24,29 @@ import DialogButtons from "../elements/DialogButtons"; import { fixupColorFonts } from '../../../utils/FontManager'; import { replaceableComponent } from "../../../utils/replaceableComponent"; +interface IProps { + pending?: boolean; + displayName?: string // required if pending is true + device?: DeviceInfo; + onDone: () => void; + onCancel: () => void; + sas: SAS.sas; + isSelf?: boolean; + inDialog?: boolean; // whether this component is being shown in a dialog and to use DialogButtons +} + +interface IState { + pending: boolean; + cancelling?: boolean; +} + function capFirst(s) { return s.charAt(0).toUpperCase() + s.slice(1); } @replaceableComponent("views.verification.VerificationShowSas") -export default class VerificationShowSas extends React.Component { - static propTypes = { - pending: PropTypes.bool, - displayName: PropTypes.string, // required if pending is true - device: PropTypes.object, - onDone: PropTypes.func.isRequired, - onCancel: PropTypes.func.isRequired, - sas: PropTypes.object.isRequired, - isSelf: PropTypes.bool, - inDialog: PropTypes.bool, // whether this component is being shown in a dialog and to use DialogButtons - }; - - constructor(props) { +export default class VerificationShowSas extends React.Component { + constructor(props: IProps) { super(props); this.state = { @@ -48,19 +54,19 @@ export default class VerificationShowSas extends React.Component { }; } - componentWillMount() { + public componentWillMount(): void { // As this component is also used before login (during complete security), // also make sure we have a working emoji font to display the SAS emojis here. // This is also done from LoggedInView. fixupColorFonts(); } - onMatchClick = () => { + private onMatchClick = (): void => { this.setState({ pending: true }); this.props.onDone(); }; - onDontMatchClick = () => { + private onDontMatchClick = (): void => { this.setState({ cancelling: true }); this.props.onCancel(); }; From 8d3476776856b5d26a1d8e063ee57eabaa23bffd Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 5 Jul 2021 09:18:16 +0200 Subject: [PATCH 13/14] Fix i18n after deprecation --- src/i18n/strings/en_EN.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 618d5763fa..7b9cfe30e2 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -923,12 +923,6 @@ "You've successfully verified this user.": "You've successfully verified this user.", "Secure messages with this user are end-to-end encrypted and not able to be read by third parties.": "Secure messages with this user are end-to-end encrypted and not able to be read by third parties.", "Got It": "Got It", - "Verify this session by completing one of the following:": "Verify this session by completing one of the following:", - "Scan this unique code": "Scan this unique code", - "or": "or", - "Compare unique emoji": "Compare unique emoji", - "Compare a unique set of emoji if you don't have a camera on either device": "Compare a unique set of emoji if you don't have a camera on either device", - "Start": "Start", "Confirm the emoji below are displayed on both sessions, in the same order:": "Confirm the emoji below are displayed on both sessions, in the same order:", "Verify this user by confirming the following emoji appear on their screen.": "Verify this user by confirming the following emoji appear on their screen.", "Verify this session by confirming the following number appears on its screen.": "Verify this session by confirming the following number appears on its screen.", @@ -1828,6 +1822,12 @@ "Edit devices": "Edit devices", "Security": "Security", "The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what %(brand)s supports. Try with a different client.": "The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what %(brand)s supports. Try with a different client.", + "Scan this unique code": "Scan this unique code", + "Compare unique emoji": "Compare unique emoji", + "Compare a unique set of emoji if you don't have a camera on either device": "Compare a unique set of emoji if you don't have a camera on either device", + "Start": "Start", + "or": "or", + "Verify this session by completing one of the following:": "Verify this session by completing one of the following:", "Verify by scanning": "Verify by scanning", "Ask %(displayName)s to scan your code:": "Ask %(displayName)s to scan your code:", "If you can't scan the code above, verify by comparing unique emoji.": "If you can't scan the code above, verify by comparing unique emoji.", From 925d434d536d60e58ac5d7a11ee7beaac2582844 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 5 Jul 2021 09:19:19 +0200 Subject: [PATCH 14/14] Linting fix in TypeScript interface --- src/components/views/terms/InlineTermsAgreement.tsx | 6 +++--- src/components/views/verification/VerificationShowSas.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/views/terms/InlineTermsAgreement.tsx b/src/components/views/terms/InlineTermsAgreement.tsx index 62594746ed..306dfea6b9 100644 --- a/src/components/views/terms/InlineTermsAgreement.tsx +++ b/src/components/views/terms/InlineTermsAgreement.tsx @@ -24,12 +24,12 @@ import { replaceableComponent } from "../../../utils/replaceableComponent"; interface IProps { policiesAndServicePairs: any[]; onFinished: (string) => void; - agreedUrls: string[], // array of URLs the user has accepted - introElement: Node, + agreedUrls: string[]; // array of URLs the user has accepted + introElement: Node; } interface IState { - policies: Policy[], + policies: Policy[]; busy: boolean; } diff --git a/src/components/views/verification/VerificationShowSas.tsx b/src/components/views/verification/VerificationShowSas.tsx index 72dd5f0669..aaf0ca4848 100644 --- a/src/components/views/verification/VerificationShowSas.tsx +++ b/src/components/views/verification/VerificationShowSas.tsx @@ -26,7 +26,7 @@ import { replaceableComponent } from "../../../utils/replaceableComponent"; interface IProps { pending?: boolean; - displayName?: string // required if pending is true + displayName?: string; // required if pending is true device?: DeviceInfo; onDone: () => void; onCancel: () => void;