Merge pull request #6743 from SimonBrandner/task/dialogs-ts
						commit
						b13fdb698c
					
				|  | @ -30,6 +30,8 @@ const HEARTBEAT_INTERVAL = 5_000; // ms | |||
| const SESSION_UPDATE_INTERVAL = 60; // seconds
 | ||||
| const MAX_PENDING_EVENTS = 1000; | ||||
| 
 | ||||
| export type Rating = 1 | 2 | 3 | 4 | 5; | ||||
| 
 | ||||
| enum Orientation { | ||||
|     Landscape = "landscape", | ||||
|     Portrait = "portrait", | ||||
|  | @ -451,7 +453,7 @@ export default class CountlyAnalytics { | |||
|         window.removeEventListener("scroll", this.onUserActivity); | ||||
|     } | ||||
| 
 | ||||
|     public reportFeedback(rating: 1 | 2 | 3 | 4 | 5, comment: string) { | ||||
|     public reportFeedback(rating: Rating, comment: string) { | ||||
|         this.track<IStarRatingEvent>("[CLY]_star_rating", { rating, comment }, null, {}, true); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -18,15 +18,54 @@ limitations under the License. | |||
| 
 | ||||
| import React from 'react'; | ||||
| import FocusLock from 'react-focus-lock'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import classNames from 'classnames'; | ||||
| 
 | ||||
| import { Key } from '../../../Keyboard'; | ||||
| import AccessibleButton from '../elements/AccessibleButton'; | ||||
| import AccessibleButton, { ButtonEvent } from '../elements/AccessibleButton'; | ||||
| import { MatrixClientPeg } from '../../../MatrixClientPeg'; | ||||
| import { _t } from "../../../languageHandler"; | ||||
| import MatrixClientContext from "../../../contexts/MatrixClientContext"; | ||||
| import { replaceableComponent } from "../../../utils/replaceableComponent"; | ||||
| import { MatrixClient } from "matrix-js-sdk/src/client"; | ||||
| import { IDialogProps } from "./IDialogProps"; | ||||
| 
 | ||||
| interface IProps extends IDialogProps { | ||||
|     // Whether the dialog should have a 'close' button that will
 | ||||
|     // cause the dialog to be cancelled. This should only be set
 | ||||
|     // to false if there is nothing the app can sensibly do if the
 | ||||
|     // dialog is cancelled, eg. "We can't restore your session and
 | ||||
|     // the app cannot work". Default: true.
 | ||||
|     hasCancel?: boolean; | ||||
| 
 | ||||
|     // called when a key is pressed
 | ||||
|     onKeyDown?: (e: KeyboardEvent | React.KeyboardEvent) => void; | ||||
| 
 | ||||
|     // CSS class to apply to dialog div
 | ||||
|     className?: string; | ||||
| 
 | ||||
|     // if true, dialog container is 60% of the viewport width. Otherwise,
 | ||||
|     // the container will have no fixed size, allowing its contents to
 | ||||
|     // determine its size. Default: true.
 | ||||
|     fixedWidth?: boolean; | ||||
| 
 | ||||
|     // Title for the dialog.
 | ||||
|     title?: JSX.Element | string; | ||||
| 
 | ||||
|     // Path to an icon to put in the header
 | ||||
|     headerImage?: string; | ||||
| 
 | ||||
|     // children should be the content of the dialog
 | ||||
|     children?: React.ReactNode; | ||||
| 
 | ||||
|     // Id of content element
 | ||||
|     // If provided, this is used to add a aria-describedby attribute
 | ||||
|     contentId?: string; | ||||
| 
 | ||||
|     // optional additional class for the title element (basically anything that can be passed to classnames)
 | ||||
|     titleClass?: string | string[]; | ||||
| 
 | ||||
|     headerButton?: JSX.Element; | ||||
| } | ||||
| 
 | ||||
| /* | ||||
|  * Basic container for modal dialogs. | ||||
|  | @ -35,54 +74,10 @@ import { replaceableComponent } from "../../../utils/replaceableComponent"; | |||
|  * dialog on escape. | ||||
|  */ | ||||
| @replaceableComponent("views.dialogs.BaseDialog") | ||||
| export default class BaseDialog extends React.Component { | ||||
|     static propTypes = { | ||||
|         // onFinished callback to call when Escape is pressed
 | ||||
|         // Take a boolean which is true if the dialog was dismissed
 | ||||
|         // with a positive / confirm action or false if it was
 | ||||
|         // cancelled (BaseDialog itself only calls this with false).
 | ||||
|         onFinished: PropTypes.func.isRequired, | ||||
| export default class BaseDialog extends React.Component<IProps> { | ||||
|     private matrixClient: MatrixClient; | ||||
| 
 | ||||
|         // Whether the dialog should have a 'close' button that will
 | ||||
|         // cause the dialog to be cancelled. This should only be set
 | ||||
|         // to false if there is nothing the app can sensibly do if the
 | ||||
|         // dialog is cancelled, eg. "We can't restore your session and
 | ||||
|         // the app cannot work". Default: true.
 | ||||
|         hasCancel: PropTypes.bool, | ||||
| 
 | ||||
|         // called when a key is pressed
 | ||||
|         onKeyDown: PropTypes.func, | ||||
| 
 | ||||
|         // CSS class to apply to dialog div
 | ||||
|         className: PropTypes.string, | ||||
| 
 | ||||
|         // if true, dialog container is 60% of the viewport width. Otherwise,
 | ||||
|         // the container will have no fixed size, allowing its contents to
 | ||||
|         // determine its size. Default: true.
 | ||||
|         fixedWidth: PropTypes.bool, | ||||
| 
 | ||||
|         // Title for the dialog.
 | ||||
|         title: PropTypes.node.isRequired, | ||||
| 
 | ||||
|         // Path to an icon to put in the header
 | ||||
|         headerImage: PropTypes.string, | ||||
| 
 | ||||
|         // children should be the content of the dialog
 | ||||
|         children: PropTypes.node, | ||||
| 
 | ||||
|         // Id of content element
 | ||||
|         // If provided, this is used to add a aria-describedby attribute
 | ||||
|         contentId: PropTypes.string, | ||||
| 
 | ||||
|         // optional additional class for the title element (basically anything that can be passed to classnames)
 | ||||
|         titleClass: PropTypes.oneOfType([ | ||||
|             PropTypes.string, | ||||
|             PropTypes.object, | ||||
|             PropTypes.arrayOf(PropTypes.string), | ||||
|         ]), | ||||
|     }; | ||||
| 
 | ||||
|     static defaultProps = { | ||||
|     public static defaultProps = { | ||||
|         hasCancel: true, | ||||
|         fixedWidth: true, | ||||
|     }; | ||||
|  | @ -90,10 +85,10 @@ export default class BaseDialog extends React.Component { | |||
|     constructor(props) { | ||||
|         super(props); | ||||
| 
 | ||||
|         this._matrixClient = MatrixClientPeg.get(); | ||||
|         this.matrixClient = MatrixClientPeg.get(); | ||||
|     } | ||||
| 
 | ||||
|     _onKeyDown = (e) => { | ||||
|     private onKeyDown = (e: KeyboardEvent | React.KeyboardEvent): void => { | ||||
|         if (this.props.onKeyDown) { | ||||
|             this.props.onKeyDown(e); | ||||
|         } | ||||
|  | @ -104,15 +99,15 @@ export default class BaseDialog extends React.Component { | |||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     _onCancelClick = (e) => { | ||||
|     private onCancelClick = (e: ButtonEvent): void => { | ||||
|         this.props.onFinished(false); | ||||
|     }; | ||||
| 
 | ||||
|     render() { | ||||
|     public render(): JSX.Element { | ||||
|         let cancelButton; | ||||
|         if (this.props.hasCancel) { | ||||
|             cancelButton = ( | ||||
|                 <AccessibleButton onClick={this._onCancelClick} className="mx_Dialog_cancelButton" aria-label={_t("Close dialog")} /> | ||||
|                 <AccessibleButton onClick={this.onCancelClick} className="mx_Dialog_cancelButton" aria-label={_t("Close dialog")} /> | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|  | @ -122,11 +117,11 @@ export default class BaseDialog extends React.Component { | |||
|         } | ||||
| 
 | ||||
|         return ( | ||||
|             <MatrixClientContext.Provider value={this._matrixClient}> | ||||
|             <MatrixClientContext.Provider value={this.matrixClient}> | ||||
|                 <FocusLock | ||||
|                     returnFocus={true} | ||||
|                     lockProps={{ | ||||
|                         onKeyDown: this._onKeyDown, | ||||
|                         onKeyDown: this.onKeyDown, | ||||
|                         role: "dialog", | ||||
|                         ["aria-labelledby"]: "mx_BaseDialog_title", | ||||
|                         // This should point to a node describing the dialog.
 | ||||
|  | @ -19,30 +19,33 @@ import QuestionDialog from './QuestionDialog'; | |||
| import { _t } from '../../../languageHandler'; | ||||
| import Field from "../elements/Field"; | ||||
| import AccessibleButton from "../elements/AccessibleButton"; | ||||
| import CountlyAnalytics from "../../../CountlyAnalytics"; | ||||
| import CountlyAnalytics, { Rating } from "../../../CountlyAnalytics"; | ||||
| import SdkConfig from "../../../SdkConfig"; | ||||
| import Modal from "../../../Modal"; | ||||
| import BugReportDialog from "./BugReportDialog"; | ||||
| import InfoDialog from "./InfoDialog"; | ||||
| import StyledRadioGroup from "../elements/StyledRadioGroup"; | ||||
| import { IDialogProps } from "./IDialogProps"; | ||||
| 
 | ||||
| const existingIssuesUrl = "https://github.com/vector-im/element-web/issues" + | ||||
|     "?q=is%3Aopen+is%3Aissue+sort%3Areactions-%2B1-desc"; | ||||
| const newIssueUrl = "https://github.com/vector-im/element-web/issues/new/choose"; | ||||
| 
 | ||||
| export default (props) => { | ||||
|     const [rating, setRating] = useState(""); | ||||
|     const [comment, setComment] = useState(""); | ||||
| interface IProps extends IDialogProps {} | ||||
| 
 | ||||
|     const onDebugLogsLinkClick = () => { | ||||
| const FeedbackDialog: React.FC<IProps> = (props: IProps) => { | ||||
|     const [rating, setRating] = useState<Rating>(); | ||||
|     const [comment, setComment] = useState<string>(""); | ||||
| 
 | ||||
|     const onDebugLogsLinkClick = (): void => { | ||||
|         props.onFinished(); | ||||
|         Modal.createTrackedDialog('Bug Report Dialog', '', BugReportDialog, {}); | ||||
|     }; | ||||
| 
 | ||||
|     const hasFeedback = CountlyAnalytics.instance.canEnable(); | ||||
|     const onFinished = (sendFeedback) => { | ||||
|     const onFinished = (sendFeedback: boolean): void => { | ||||
|         if (hasFeedback && sendFeedback) { | ||||
|             CountlyAnalytics.instance.reportFeedback(parseInt(rating, 10), comment); | ||||
|             CountlyAnalytics.instance.reportFeedback(rating, comment); | ||||
|             Modal.createTrackedDialog('Feedback sent', '', InfoDialog, { | ||||
|                 title: _t('Feedback sent'), | ||||
|                 description: _t('Thank you!'), | ||||
|  | @ -65,8 +68,8 @@ export default (props) => { | |||
| 
 | ||||
|                 <StyledRadioGroup | ||||
|                     name="feedbackRating" | ||||
|                     value={rating} | ||||
|                     onChange={setRating} | ||||
|                     value={String(rating)} | ||||
|                     onChange={(r) => setRating(parseInt(r, 10) as Rating)} | ||||
|                     definitions={[ | ||||
|                         { value: "1", label: "😠" }, | ||||
|                         { value: "2", label: "😞" }, | ||||
|  | @ -138,7 +141,9 @@ export default (props) => { | |||
|             { countlyFeedbackSection } | ||||
|         </React.Fragment>} | ||||
|         button={hasFeedback ? _t("Send feedback") : _t("Go back")} | ||||
|         buttonDisabled={hasFeedback && rating === ""} | ||||
|         buttonDisabled={hasFeedback && !rating} | ||||
|         onFinished={onFinished} | ||||
|     />); | ||||
| }; | ||||
| 
 | ||||
| export default FeedbackDialog; | ||||
|  | @ -15,12 +15,20 @@ limitations under the License. | |||
| */ | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { MatrixClientPeg } from '../../../MatrixClientPeg'; | ||||
| import * as sdk from '../../../index'; | ||||
| import { _t } from '../../../languageHandler'; | ||||
| import { replaceableComponent } from "../../../utils/replaceableComponent"; | ||||
| import { mediaFromMxc } from "../../../customisations/Media"; | ||||
| import VerificationComplete from "../verification/VerificationComplete"; | ||||
| import VerificationCancelled from "../verification/VerificationCancelled"; | ||||
| import BaseAvatar from "../avatars/BaseAvatar"; | ||||
| import Spinner from "../elements/Spinner"; | ||||
| import VerificationShowSas from "../verification/VerificationShowSas"; | ||||
| import BaseDialog from "./BaseDialog"; | ||||
| import DialogButtons from "../elements/DialogButtons"; | ||||
| import { IDialogProps } from "./IDialogProps"; | ||||
| import { IGeneratedSas, ISasEvent } from "matrix-js-sdk/src/crypto/verification/SAS"; | ||||
| import { VerificationBase } from "matrix-js-sdk/src/crypto/verification/Base"; | ||||
| 
 | ||||
| import { logger } from "matrix-js-sdk/src/logger"; | ||||
| 
 | ||||
|  | @ -30,41 +38,56 @@ const PHASE_WAIT_FOR_PARTNER_TO_CONFIRM = 2; | |||
| const PHASE_VERIFIED = 3; | ||||
| const PHASE_CANCELLED = 4; | ||||
| 
 | ||||
| @replaceableComponent("views.dialogs.IncomingSasDialog") | ||||
| export default class IncomingSasDialog extends React.Component { | ||||
|     static propTypes = { | ||||
|         verifier: PropTypes.object.isRequired, | ||||
|     }; | ||||
| interface IProps extends IDialogProps { | ||||
|     verifier: VerificationBase; // TODO types
 | ||||
| } | ||||
| 
 | ||||
|     constructor(props) { | ||||
| interface IState { | ||||
|     phase: number; | ||||
|     sasVerified: boolean; | ||||
|     opponentProfile: { | ||||
|         // eslint-disable-next-line camelcase
 | ||||
|         avatar_url?: string; | ||||
|         displayname?: string; | ||||
|     }; | ||||
|     opponentProfileError: Error; | ||||
|     sas: IGeneratedSas; | ||||
| } | ||||
| 
 | ||||
| @replaceableComponent("views.dialogs.IncomingSasDialog") | ||||
| export default class IncomingSasDialog extends React.Component<IProps, IState> { | ||||
|     private showSasEvent: ISasEvent; | ||||
| 
 | ||||
|     constructor(props: IProps) { | ||||
|         super(props); | ||||
| 
 | ||||
|         let phase = PHASE_START; | ||||
|         if (this.props.verifier.cancelled) { | ||||
|         if (this.props.verifier.hasBeenCancelled) { | ||||
|             logger.log("Verifier was cancelled in the background."); | ||||
|             phase = PHASE_CANCELLED; | ||||
|         } | ||||
| 
 | ||||
|         this._showSasEvent = null; | ||||
|         this.showSasEvent = null; | ||||
|         this.state = { | ||||
|             phase: phase, | ||||
|             sasVerified: false, | ||||
|             opponentProfile: null, | ||||
|             opponentProfileError: null, | ||||
|             sas: null, | ||||
|         }; | ||||
|         this.props.verifier.on('show_sas', this._onVerifierShowSas); | ||||
|         this.props.verifier.on('cancel', this._onVerifierCancel); | ||||
|         this._fetchOpponentProfile(); | ||||
|         this.props.verifier.on('show_sas', this.onVerifierShowSas); | ||||
|         this.props.verifier.on('cancel', this.onVerifierCancel); | ||||
|         this.fetchOpponentProfile(); | ||||
|     } | ||||
| 
 | ||||
|     componentWillUnmount() { | ||||
|     public componentWillUnmount(): void { | ||||
|         if (this.state.phase !== PHASE_CANCELLED && this.state.phase !== PHASE_VERIFIED) { | ||||
|             this.props.verifier.cancel('User cancel'); | ||||
|             this.props.verifier.cancel(new Error('User cancel')); | ||||
|         } | ||||
|         this.props.verifier.removeListener('show_sas', this._onVerifierShowSas); | ||||
|         this.props.verifier.removeListener('show_sas', this.onVerifierShowSas); | ||||
|     } | ||||
| 
 | ||||
|     async _fetchOpponentProfile() { | ||||
|     private async fetchOpponentProfile(): Promise<void> { | ||||
|         try { | ||||
|             const prof = await MatrixClientPeg.get().getProfileInfo( | ||||
|                 this.props.verifier.userId, | ||||
|  | @ -79,53 +102,49 @@ export default class IncomingSasDialog extends React.Component { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     _onFinished = () => { | ||||
|     private onFinished = (): void => { | ||||
|         this.props.onFinished(this.state.phase === PHASE_VERIFIED); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _onCancelClick = () => { | ||||
|     private onCancelClick = (): void => { | ||||
|         this.props.onFinished(this.state.phase === PHASE_VERIFIED); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _onContinueClick = () => { | ||||
|     private onContinueClick = (): void => { | ||||
|         this.setState({ phase: PHASE_WAIT_FOR_PARTNER_TO_CONFIRM }); | ||||
|         this.props.verifier.verify().then(() => { | ||||
|             this.setState({ phase: PHASE_VERIFIED }); | ||||
|         }).catch((e) => { | ||||
|             logger.log("Verification failed", e); | ||||
|         }); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _onVerifierShowSas = (e) => { | ||||
|         this._showSasEvent = e; | ||||
|     private onVerifierShowSas = (e: ISasEvent): void => { | ||||
|         this.showSasEvent = e; | ||||
|         this.setState({ | ||||
|             phase: PHASE_SHOW_SAS, | ||||
|             sas: e.sas, | ||||
|         }); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _onVerifierCancel = (e) => { | ||||
|     private onVerifierCancel = (): void => { | ||||
|         this.setState({ | ||||
|             phase: PHASE_CANCELLED, | ||||
|         }); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _onSasMatchesClick = () => { | ||||
|         this._showSasEvent.confirm(); | ||||
|     private onSasMatchesClick = (): void => { | ||||
|         this.showSasEvent.confirm(); | ||||
|         this.setState({ | ||||
|             phase: PHASE_WAIT_FOR_PARTNER_TO_CONFIRM, | ||||
|         }); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _onVerifiedDoneClick = () => { | ||||
|     private onVerifiedDoneClick = (): void => { | ||||
|         this.props.onFinished(true); | ||||
|     } | ||||
| 
 | ||||
|     _renderPhaseStart() { | ||||
|         const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); | ||||
|         const Spinner = sdk.getComponent("views.elements.Spinner"); | ||||
|         const BaseAvatar = sdk.getComponent("avatars.BaseAvatar"); | ||||
|     }; | ||||
| 
 | ||||
|     private renderPhaseStart(): JSX.Element { | ||||
|         const isSelf = this.props.verifier.userId === MatrixClientPeg.get().getUserId(); | ||||
| 
 | ||||
|         let profile; | ||||
|  | @ -192,27 +211,24 @@ export default class IncomingSasDialog extends React.Component { | |||
|                 <DialogButtons | ||||
|                     primaryButton={_t('Continue')} | ||||
|                     hasCancel={true} | ||||
|                     onPrimaryButtonClick={this._onContinueClick} | ||||
|                     onCancel={this._onCancelClick} | ||||
|                     onPrimaryButtonClick={this.onContinueClick} | ||||
|                     onCancel={this.onCancelClick} | ||||
|                 /> | ||||
|             </div> | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     _renderPhaseShowSas() { | ||||
|         const VerificationShowSas = sdk.getComponent('views.verification.VerificationShowSas'); | ||||
|     private renderPhaseShowSas(): JSX.Element { | ||||
|         return <VerificationShowSas | ||||
|             sas={this._showSasEvent.sas} | ||||
|             onCancel={this._onCancelClick} | ||||
|             onDone={this._onSasMatchesClick} | ||||
|             sas={this.showSasEvent.sas} | ||||
|             onCancel={this.onCancelClick} | ||||
|             onDone={this.onSasMatchesClick} | ||||
|             isSelf={this.props.verifier.userId === MatrixClientPeg.get().getUserId()} | ||||
|             inDialog={true} | ||||
|         />; | ||||
|     } | ||||
| 
 | ||||
|     _renderPhaseWaitForPartnerToConfirm() { | ||||
|         const Spinner = sdk.getComponent("views.elements.Spinner"); | ||||
| 
 | ||||
|     private renderPhaseWaitForPartnerToConfirm(): JSX.Element { | ||||
|         return ( | ||||
|             <div> | ||||
|                 <Spinner /> | ||||
|  | @ -221,41 +237,38 @@ export default class IncomingSasDialog extends React.Component { | |||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     _renderPhaseVerified() { | ||||
|         const VerificationComplete = sdk.getComponent('views.verification.VerificationComplete'); | ||||
|         return <VerificationComplete onDone={this._onVerifiedDoneClick} />; | ||||
|     private renderPhaseVerified(): JSX.Element { | ||||
|         return <VerificationComplete onDone={this.onVerifiedDoneClick} />; | ||||
|     } | ||||
| 
 | ||||
|     _renderPhaseCancelled() { | ||||
|         const VerificationCancelled = sdk.getComponent('views.verification.VerificationCancelled'); | ||||
|         return <VerificationCancelled onDone={this._onCancelClick} />; | ||||
|     private renderPhaseCancelled(): JSX.Element { | ||||
|         return <VerificationCancelled onDone={this.onCancelClick} />; | ||||
|     } | ||||
| 
 | ||||
|     render() { | ||||
|     public render(): JSX.Element { | ||||
|         let body; | ||||
|         switch (this.state.phase) { | ||||
|             case PHASE_START: | ||||
|                 body = this._renderPhaseStart(); | ||||
|                 body = this.renderPhaseStart(); | ||||
|                 break; | ||||
|             case PHASE_SHOW_SAS: | ||||
|                 body = this._renderPhaseShowSas(); | ||||
|                 body = this.renderPhaseShowSas(); | ||||
|                 break; | ||||
|             case PHASE_WAIT_FOR_PARTNER_TO_CONFIRM: | ||||
|                 body = this._renderPhaseWaitForPartnerToConfirm(); | ||||
|                 body = this.renderPhaseWaitForPartnerToConfirm(); | ||||
|                 break; | ||||
|             case PHASE_VERIFIED: | ||||
|                 body = this._renderPhaseVerified(); | ||||
|                 body = this.renderPhaseVerified(); | ||||
|                 break; | ||||
|             case PHASE_CANCELLED: | ||||
|                 body = this._renderPhaseCancelled(); | ||||
|                 body = this.renderPhaseCancelled(); | ||||
|                 break; | ||||
|         } | ||||
| 
 | ||||
|         const BaseDialog = sdk.getComponent("dialogs.BaseDialog"); | ||||
|         return ( | ||||
|             <BaseDialog | ||||
|                 title={_t("Incoming Verification Request")} | ||||
|                 onFinished={this._onFinished} | ||||
|                 onFinished={this.onFinished} | ||||
|                 fixedWidth={false} | ||||
|             > | ||||
|                 { body } | ||||
|  | @ -15,32 +15,28 @@ limitations under the License. | |||
| */ | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { _t } from "../../../languageHandler"; | ||||
| import * as sdk from "../../../index"; | ||||
| import dis from '../../../dispatcher/dispatcher'; | ||||
| import { Action } from "../../../dispatcher/actions"; | ||||
| import { replaceableComponent } from "../../../utils/replaceableComponent"; | ||||
| import BaseDialog from "./BaseDialog"; | ||||
| import DialogButtons from "../elements/DialogButtons"; | ||||
| import { IDialogProps } from "./IDialogProps"; | ||||
| 
 | ||||
| interface IProps extends IDialogProps {} | ||||
| 
 | ||||
| @replaceableComponent("views.dialogs.IntegrationsDisabledDialog") | ||||
| export default class IntegrationsDisabledDialog extends React.Component { | ||||
|     static propTypes = { | ||||
|         onFinished: PropTypes.func.isRequired, | ||||
|     }; | ||||
| 
 | ||||
|     _onAcknowledgeClick = () => { | ||||
| export default class IntegrationsDisabledDialog extends React.Component<IProps> { | ||||
|     private onAcknowledgeClick = (): void => { | ||||
|         this.props.onFinished(); | ||||
|     }; | ||||
| 
 | ||||
|     _onOpenSettingsClick = () => { | ||||
|     private onOpenSettingsClick = (): void => { | ||||
|         this.props.onFinished(); | ||||
|         dis.fire(Action.ViewUserSettings); | ||||
|     }; | ||||
| 
 | ||||
|     render() { | ||||
|         const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); | ||||
|         const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); | ||||
| 
 | ||||
|     public render(): JSX.Element { | ||||
|         return ( | ||||
|             <BaseDialog | ||||
|                 className='mx_IntegrationsDisabledDialog' | ||||
|  | @ -53,9 +49,9 @@ export default class IntegrationsDisabledDialog extends React.Component { | |||
|                 </div> | ||||
|                 <DialogButtons | ||||
|                     primaryButton={_t("Settings")} | ||||
|                     onPrimaryButtonClick={this._onOpenSettingsClick} | ||||
|                     onPrimaryButtonClick={this.onOpenSettingsClick} | ||||
|                     cancelButton={_t("OK")} | ||||
|                     onCancel={this._onAcknowledgeClick} | ||||
|                     onCancel={this.onAcknowledgeClick} | ||||
|                 /> | ||||
|             </BaseDialog> | ||||
|         ); | ||||
|  | @ -15,23 +15,21 @@ limitations under the License. | |||
| */ | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { _t } from "../../../languageHandler"; | ||||
| import SdkConfig from "../../../SdkConfig"; | ||||
| import * as sdk from "../../../index"; | ||||
| import { replaceableComponent } from "../../../utils/replaceableComponent"; | ||||
| import { IDialogProps } from "./IDialogProps"; | ||||
| 
 | ||||
| interface IProps extends IDialogProps {} | ||||
| 
 | ||||
| @replaceableComponent("views.dialogs.IntegrationsImpossibleDialog") | ||||
| export default class IntegrationsImpossibleDialog extends React.Component { | ||||
|     static propTypes = { | ||||
|         onFinished: PropTypes.func.isRequired, | ||||
|     }; | ||||
| 
 | ||||
|     _onAcknowledgeClick = () => { | ||||
| export default class IntegrationsImpossibleDialog extends React.Component<IProps> { | ||||
|     private onAcknowledgeClick = (): void => { | ||||
|         this.props.onFinished(); | ||||
|     }; | ||||
| 
 | ||||
|     render() { | ||||
|     public render(): JSX.Element { | ||||
|         const brand = SdkConfig.get().brand; | ||||
|         const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); | ||||
|         const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); | ||||
|  | @ -54,7 +52,7 @@ export default class IntegrationsImpossibleDialog extends React.Component { | |||
|                 </div> | ||||
|                 <DialogButtons | ||||
|                     primaryButton={_t("OK")} | ||||
|                     onPrimaryButtonClick={this._onAcknowledgeClick} | ||||
|                     onPrimaryButtonClick={this.onAcknowledgeClick} | ||||
|                     hasCancel={false} | ||||
|                 /> | ||||
|             </BaseDialog> | ||||
|  | @ -17,69 +17,88 @@ limitations under the License. | |||
| */ | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| 
 | ||||
| import * as sdk from '../../../index'; | ||||
| import { _t } from '../../../languageHandler'; | ||||
| 
 | ||||
| import AccessibleButton from '../elements/AccessibleButton'; | ||||
| import { ERROR_USER_CANCELLED } from "../../structures/InteractiveAuth"; | ||||
| import InteractiveAuth, { ERROR_USER_CANCELLED } from "../../structures/InteractiveAuth"; | ||||
| import { SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents"; | ||||
| import { replaceableComponent } from "../../../utils/replaceableComponent"; | ||||
| import { MatrixClient } from "matrix-js-sdk/src/client"; | ||||
| import BaseDialog from "./BaseDialog"; | ||||
| import { IAuthData } from "matrix-js-sdk/src/interactive-auth"; | ||||
| import { IDialogProps } from "./IDialogProps"; | ||||
| 
 | ||||
| interface IDialogAesthetics { | ||||
|     [x: string]: { | ||||
|         [x: number]: { | ||||
|             title: string; | ||||
|             body: string; | ||||
|             continueText: string; | ||||
|             continueKind: string; | ||||
|         }; | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| interface IProps extends IDialogProps { | ||||
|     // matrix client to use for UI auth requests
 | ||||
|     matrixClient: MatrixClient; | ||||
| 
 | ||||
|     // response from initial request. If not supplied, will do a request on
 | ||||
|     // mount.
 | ||||
|     authData?: IAuthData; | ||||
| 
 | ||||
|     // callback
 | ||||
|     makeRequest: (auth: IAuthData) => Promise<IAuthData>; | ||||
| 
 | ||||
|     // Optional title and body to show when not showing a particular stage
 | ||||
|     title?: string; | ||||
|     body?: string; | ||||
| 
 | ||||
|     // Optional title and body pairs for particular stages and phases within
 | ||||
|     // those stages. Object structure/example is:
 | ||||
|     // {
 | ||||
|     //     "org.example.stage_type": {
 | ||||
|     //         1: {
 | ||||
|     //             "body": "This is a body for phase 1" of org.example.stage_type,
 | ||||
|     //             "title": "Title for phase 1 of org.example.stage_type"
 | ||||
|     //         },
 | ||||
|     //         2: {
 | ||||
|     //             "body": "This is a body for phase 2 of org.example.stage_type",
 | ||||
|     //             "title": "Title for phase 2 of org.example.stage_type"
 | ||||
|     //             "continueText": "Confirm identity with Example Auth",
 | ||||
|     //             "continueKind": "danger"
 | ||||
|     //         }
 | ||||
|     //     }
 | ||||
|     // }
 | ||||
|     //
 | ||||
|     // Default is defined in _getDefaultDialogAesthetics()
 | ||||
|     aestheticsForStagePhases?: IDialogAesthetics; | ||||
| } | ||||
| 
 | ||||
| interface IState { | ||||
|     authError: Error; | ||||
| 
 | ||||
|     // See _onUpdateStagePhase()
 | ||||
|     uiaStage: number | string; | ||||
|     uiaStagePhase: number | string; | ||||
| } | ||||
| 
 | ||||
| @replaceableComponent("views.dialogs.InteractiveAuthDialog") | ||||
| export default class InteractiveAuthDialog extends React.Component { | ||||
|     static propTypes = { | ||||
|         // matrix client to use for UI auth requests
 | ||||
|         matrixClient: PropTypes.object.isRequired, | ||||
| export default class InteractiveAuthDialog extends React.Component<IProps, IState> { | ||||
|     constructor(props: IProps) { | ||||
|         super(props); | ||||
| 
 | ||||
|         // response from initial request. If not supplied, will do a request on
 | ||||
|         // mount.
 | ||||
|         authData: PropTypes.shape({ | ||||
|             flows: PropTypes.array, | ||||
|             params: PropTypes.object, | ||||
|             session: PropTypes.string, | ||||
|         }), | ||||
|         this.state = { | ||||
|             authError: null, | ||||
| 
 | ||||
|         // callback
 | ||||
|         makeRequest: PropTypes.func.isRequired, | ||||
|             // See _onUpdateStagePhase()
 | ||||
|             uiaStage: null, | ||||
|             uiaStagePhase: null, | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|         onFinished: PropTypes.func.isRequired, | ||||
| 
 | ||||
|         // Optional title and body to show when not showing a particular stage
 | ||||
|         title: PropTypes.string, | ||||
|         body: PropTypes.string, | ||||
| 
 | ||||
|         // Optional title and body pairs for particular stages and phases within
 | ||||
|         // those stages. Object structure/example is:
 | ||||
|         // {
 | ||||
|         //     "org.example.stage_type": {
 | ||||
|         //         1: {
 | ||||
|         //             "body": "This is a body for phase 1" of org.example.stage_type,
 | ||||
|         //             "title": "Title for phase 1 of org.example.stage_type"
 | ||||
|         //         },
 | ||||
|         //         2: {
 | ||||
|         //             "body": "This is a body for phase 2 of org.example.stage_type",
 | ||||
|         //             "title": "Title for phase 2 of org.example.stage_type"
 | ||||
|         //             "continueText": "Confirm identity with Example Auth",
 | ||||
|         //             "continueKind": "danger"
 | ||||
|         //         }
 | ||||
|         //     }
 | ||||
|         // }
 | ||||
|         //
 | ||||
|         // Default is defined in _getDefaultDialogAesthetics()
 | ||||
|         aestheticsForStagePhases: PropTypes.object, | ||||
|     }; | ||||
| 
 | ||||
|     state = { | ||||
|         authError: null, | ||||
| 
 | ||||
|         // See _onUpdateStagePhase()
 | ||||
|         uiaStage: null, | ||||
|         uiaStagePhase: null, | ||||
|     }; | ||||
| 
 | ||||
|     _getDefaultDialogAesthetics() { | ||||
|     private getDefaultDialogAesthetics(): IDialogAesthetics { | ||||
|         const ssoAesthetics = { | ||||
|             [SSOAuthEntry.PHASE_PREAUTH]: { | ||||
|                 title: _t("Use Single Sign On to continue"), | ||||
|  | @ -101,7 +120,7 @@ export default class InteractiveAuthDialog extends React.Component { | |||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     _onAuthFinished = (success, result) => { | ||||
|     private onAuthFinished = (success: boolean, result: Error): void => { | ||||
|         if (success) { | ||||
|             this.props.onFinished(true, result); | ||||
|         } else { | ||||
|  | @ -115,19 +134,16 @@ export default class InteractiveAuthDialog extends React.Component { | |||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     _onUpdateStagePhase = (newStage, newPhase) => { | ||||
|     private onUpdateStagePhase = (newStage: string | number, newPhase: string | number): void => { | ||||
|         // We copy the stage and stage phase params into state for title selection in render()
 | ||||
|         this.setState({ uiaStage: newStage, uiaStagePhase: newPhase }); | ||||
|     }; | ||||
| 
 | ||||
|     _onDismissClick = () => { | ||||
|     private onDismissClick = (): void => { | ||||
|         this.props.onFinished(false); | ||||
|     }; | ||||
| 
 | ||||
|     render() { | ||||
|         const InteractiveAuth = sdk.getComponent("structures.InteractiveAuth"); | ||||
|         const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); | ||||
| 
 | ||||
|     public render(): JSX.Element { | ||||
|         // Let's pick a title, body, and other params text that we'll show to the user. The order
 | ||||
|         // is most specific first, so stagePhase > our props > defaults.
 | ||||
| 
 | ||||
|  | @ -135,7 +151,7 @@ export default class InteractiveAuthDialog extends React.Component { | |||
|         let body = this.state.authError ? null : this.props.body; | ||||
|         let continueText = null; | ||||
|         let continueKind = null; | ||||
|         const dialogAesthetics = this.props.aestheticsForStagePhases || this._getDefaultDialogAesthetics(); | ||||
|         const dialogAesthetics = this.props.aestheticsForStagePhases || this.getDefaultDialogAesthetics(); | ||||
|         if (!this.state.authError && dialogAesthetics) { | ||||
|             if (dialogAesthetics[this.state.uiaStage]) { | ||||
|                 const aesthetics = dialogAesthetics[this.state.uiaStage][this.state.uiaStagePhase]; | ||||
|  | @ -152,9 +168,9 @@ export default class InteractiveAuthDialog extends React.Component { | |||
|                 <div id='mx_Dialog_content'> | ||||
|                     <div role="alert">{ this.state.authError.message || this.state.authError.toString() }</div> | ||||
|                     <br /> | ||||
|                     <AccessibleButton onClick={this._onDismissClick} | ||||
|                     <AccessibleButton onClick={this.onDismissClick} | ||||
|                         className="mx_GeneralButton" | ||||
|                         autoFocus="true" | ||||
|                         autoFocus={true} | ||||
|                     > | ||||
|                         { _t("Dismiss") } | ||||
|                     </AccessibleButton> | ||||
|  | @ -165,12 +181,11 @@ export default class InteractiveAuthDialog extends React.Component { | |||
|                 <div id='mx_Dialog_content'> | ||||
|                     { body } | ||||
|                     <InteractiveAuth | ||||
|                         ref={this._collectInteractiveAuth} | ||||
|                         matrixClient={this.props.matrixClient} | ||||
|                         authData={this.props.authData} | ||||
|                         makeRequest={this.props.makeRequest} | ||||
|                         onAuthFinished={this._onAuthFinished} | ||||
|                         onStagePhaseChange={this._onUpdateStagePhase} | ||||
|                         onAuthFinished={this.onAuthFinished} | ||||
|                         onStagePhaseChange={this.onUpdateStagePhase} | ||||
|                         continueText={continueText} | ||||
|                         continueKind={continueKind} | ||||
|                     /> | ||||
|  | @ -15,20 +15,29 @@ limitations under the License. | |||
| */ | ||||
| 
 | ||||
| import React, { useState, useCallback, useRef } from 'react'; | ||||
| import * as sdk from '../../../index'; | ||||
| import { _t } from '../../../languageHandler'; | ||||
| import SdkConfig from '../../../SdkConfig'; | ||||
| import BaseDialog from "./BaseDialog"; | ||||
| import DialogButtons from "../elements/DialogButtons"; | ||||
| import Spinner from "../elements/Spinner"; | ||||
| import { IDialogProps } from "./IDialogProps"; | ||||
| 
 | ||||
| export default function KeySignatureUploadFailedDialog({ | ||||
| interface IProps extends IDialogProps { | ||||
|     failures: Record<string, Record<string, { | ||||
|         errcode: string; | ||||
|         error: string; | ||||
|     }>>; | ||||
|     source: string; | ||||
|     continuation: () => void; | ||||
| } | ||||
| 
 | ||||
| const KeySignatureUploadFailedDialog: React.FC<IProps> = ({ | ||||
|     failures, | ||||
|     source, | ||||
|     continuation, | ||||
|     onFinished, | ||||
| }) { | ||||
| }) => { | ||||
|     const RETRIES = 2; | ||||
|     const BaseDialog = sdk.getComponent('dialogs.BaseDialog'); | ||||
|     const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); | ||||
|     const Spinner = sdk.getComponent('elements.Spinner'); | ||||
|     const [retry, setRetry] = useState(RETRIES); | ||||
|     const [cancelled, setCancelled] = useState(false); | ||||
|     const [retrying, setRetrying] = useState(false); | ||||
|  | @ -107,4 +116,6 @@ export default function KeySignatureUploadFailedDialog({ | |||
|             { body } | ||||
|         </BaseDialog> | ||||
|     ); | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export default KeySignatureUploadFailedDialog; | ||||
|  | @ -19,8 +19,13 @@ import React from 'react'; | |||
| import QuestionDialog from './QuestionDialog'; | ||||
| import { _t } from '../../../languageHandler'; | ||||
| import SdkConfig from '../../../SdkConfig'; | ||||
| import { IDialogProps } from "./IDialogProps"; | ||||
| 
 | ||||
| export default (props) => { | ||||
| interface IProps extends IDialogProps { | ||||
|     host: string; | ||||
| } | ||||
| 
 | ||||
| const LazyLoadingDisabledDialog: React.FC<IProps> = (props) => { | ||||
|     const brand = SdkConfig.get().brand; | ||||
|     const description1 = _t( | ||||
|         "You've previously used %(brand)s on %(host)s with lazy loading of members enabled. " + | ||||
|  | @ -49,3 +54,5 @@ export default (props) => { | |||
|         onFinished={props.onFinished} | ||||
|     />); | ||||
| }; | ||||
| 
 | ||||
| export default LazyLoadingDisabledDialog; | ||||
|  | @ -19,8 +19,11 @@ import React from 'react'; | |||
| import QuestionDialog from './QuestionDialog'; | ||||
| import { _t } from '../../../languageHandler'; | ||||
| import SdkConfig from '../../../SdkConfig'; | ||||
| import { IDialogProps } from "./IDialogProps"; | ||||
| 
 | ||||
| export default (props) => { | ||||
| interface IProps extends IDialogProps {} | ||||
| 
 | ||||
| const LazyLoadingResyncDialog: React.FC<IProps> = (props) => { | ||||
|     const brand = SdkConfig.get().brand; | ||||
|     const description = | ||||
|         _t( | ||||
|  | @ -38,3 +41,5 @@ export default (props) => { | |||
|         onFinished={props.onFinished} | ||||
|     />); | ||||
| }; | ||||
| 
 | ||||
| export default LazyLoadingResyncDialog; | ||||
|  | @ -19,37 +19,31 @@ limitations under the License. | |||
| */ | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { MatrixClientPeg } from '../../../MatrixClientPeg'; | ||||
| import * as sdk from '../../../index'; | ||||
| import * as FormattingUtils from '../../../utils/FormattingUtils'; | ||||
| import { _t } from '../../../languageHandler'; | ||||
| import { replaceableComponent } from "../../../utils/replaceableComponent"; | ||||
| import QuestionDialog from "./QuestionDialog"; | ||||
| import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo"; | ||||
| import { IDialogProps } from "./IDialogProps"; | ||||
| 
 | ||||
| interface IProps extends IDialogProps { | ||||
|     userId: string; | ||||
|     device: DeviceInfo; | ||||
| } | ||||
| 
 | ||||
| @replaceableComponent("views.dialogs.ManualDeviceKeyVerificationDialog") | ||||
| export default class ManualDeviceKeyVerificationDialog extends React.Component { | ||||
|     static propTypes = { | ||||
|         userId: PropTypes.string.isRequired, | ||||
|         device: PropTypes.object.isRequired, | ||||
|         onFinished: PropTypes.func.isRequired, | ||||
|     }; | ||||
| 
 | ||||
|     _onCancelClick = () => { | ||||
|         this.props.onFinished(false); | ||||
|     } | ||||
| 
 | ||||
|     _onLegacyFinished = (confirm) => { | ||||
| export default class ManualDeviceKeyVerificationDialog extends React.Component<IProps> { | ||||
|     private onLegacyFinished = (confirm: boolean): void => { | ||||
|         if (confirm) { | ||||
|             MatrixClientPeg.get().setDeviceVerified( | ||||
|                 this.props.userId, this.props.device.deviceId, true, | ||||
|             ); | ||||
|         } | ||||
|         this.props.onFinished(confirm); | ||||
|     } | ||||
| 
 | ||||
|     render() { | ||||
|         const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); | ||||
|     }; | ||||
| 
 | ||||
|     public render(): JSX.Element { | ||||
|         let text; | ||||
|         if (MatrixClientPeg.get().getUserId() === this.props.userId) { | ||||
|             text = _t("Confirm by comparing the following with the User Settings in your other session:"); | ||||
|  | @ -81,7 +75,7 @@ export default class ManualDeviceKeyVerificationDialog extends React.Component { | |||
|                 title={_t("Verify session")} | ||||
|                 description={body} | ||||
|                 button={_t("Verify session")} | ||||
|                 onFinished={this._onLegacyFinished} | ||||
|                 onFinished={this.onLegacyFinished} | ||||
|             /> | ||||
|         ); | ||||
|     } | ||||
|  | @ -15,21 +15,39 @@ limitations under the License. | |||
| */ | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { MatrixClientPeg } from "../../../MatrixClientPeg"; | ||||
| import { _t } from '../../../languageHandler'; | ||||
| import * as sdk from "../../../index"; | ||||
| import { wantsDateSeparator } from '../../../DateUtils'; | ||||
| import SettingsStore from '../../../settings/SettingsStore'; | ||||
| import { replaceableComponent } from "../../../utils/replaceableComponent"; | ||||
| import { MatrixEvent } from "matrix-js-sdk/src/models/event"; | ||||
| import BaseDialog from "./BaseDialog"; | ||||
| import ScrollPanel from "../../structures/ScrollPanel"; | ||||
| import Spinner from "../elements/Spinner"; | ||||
| import EditHistoryMessage from "../messages/EditHistoryMessage"; | ||||
| import DateSeparator from "../messages/DateSeparator"; | ||||
| import { IDialogProps } from "./IDialogProps"; | ||||
| import { EventType, RelationType } from "matrix-js-sdk/src/@types/event"; | ||||
| import { defer } from "matrix-js-sdk/src/utils"; | ||||
| 
 | ||||
| interface IProps extends IDialogProps { | ||||
|     mxEvent: MatrixEvent; | ||||
| } | ||||
| 
 | ||||
| interface IState { | ||||
|     originalEvent: MatrixEvent; | ||||
|     error: { | ||||
|         errcode: string; | ||||
|     }; | ||||
|     events: MatrixEvent[]; | ||||
|     nextBatch: string; | ||||
|     isLoading: boolean; | ||||
|     isTwelveHour: boolean; | ||||
| } | ||||
| 
 | ||||
| @replaceableComponent("views.dialogs.MessageEditHistoryDialog") | ||||
| export default class MessageEditHistoryDialog extends React.PureComponent { | ||||
|     static propTypes = { | ||||
|         mxEvent: PropTypes.object.isRequired, | ||||
|     }; | ||||
| 
 | ||||
|     constructor(props) { | ||||
| export default class MessageEditHistoryDialog extends React.PureComponent<IProps, IState> { | ||||
|     constructor(props: IProps) { | ||||
|         super(props); | ||||
|         this.state = { | ||||
|             originalEvent: null, | ||||
|  | @ -41,7 +59,7 @@ export default class MessageEditHistoryDialog extends React.PureComponent { | |||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     loadMoreEdits = async (backwards) => { | ||||
|     private loadMoreEdits = async (backwards?: boolean): Promise<boolean> => { | ||||
|         if (backwards || (!this.state.nextBatch && !this.state.isLoading)) { | ||||
|             // bail out on backwards as we only paginate in one direction
 | ||||
|             return false; | ||||
|  | @ -50,13 +68,13 @@ export default class MessageEditHistoryDialog extends React.PureComponent { | |||
|         const roomId = this.props.mxEvent.getRoomId(); | ||||
|         const eventId = this.props.mxEvent.getId(); | ||||
|         const client = MatrixClientPeg.get(); | ||||
| 
 | ||||
|         const { resolve, reject, promise } = defer<boolean>(); | ||||
|         let result; | ||||
|         let resolve; | ||||
|         let reject; | ||||
|         const promise = new Promise((_resolve, _reject) => {resolve = _resolve; reject = _reject;}); | ||||
| 
 | ||||
|         try { | ||||
|             result = await client.relations( | ||||
|                 roomId, eventId, "m.replace", "m.room.message", opts); | ||||
|                 roomId, eventId, RelationType.Replace, EventType.RoomMessage, opts); | ||||
|         } catch (error) { | ||||
|             // log if the server returned an error
 | ||||
|             if (error.errcode) { | ||||
|  | @ -67,7 +85,7 @@ export default class MessageEditHistoryDialog extends React.PureComponent { | |||
|         } | ||||
| 
 | ||||
|         const newEvents = result.events; | ||||
|         this._locallyRedactEventsIfNeeded(newEvents); | ||||
|         this.locallyRedactEventsIfNeeded(newEvents); | ||||
|         this.setState({ | ||||
|             originalEvent: this.state.originalEvent || result.originalEvent, | ||||
|             events: this.state.events.concat(newEvents), | ||||
|  | @ -78,9 +96,9 @@ export default class MessageEditHistoryDialog extends React.PureComponent { | |||
|             resolve(hasMoreResults); | ||||
|         }); | ||||
|         return promise; | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _locallyRedactEventsIfNeeded(newEvents) { | ||||
|     private locallyRedactEventsIfNeeded(newEvents: MatrixEvent[]): void { | ||||
|         const roomId = this.props.mxEvent.getRoomId(); | ||||
|         const client = MatrixClientPeg.get(); | ||||
|         const room = client.getRoom(roomId); | ||||
|  | @ -95,13 +113,11 @@ export default class MessageEditHistoryDialog extends React.PureComponent { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     componentDidMount() { | ||||
|     public componentDidMount(): void { | ||||
|         this.loadMoreEdits(); | ||||
|     } | ||||
| 
 | ||||
|     _renderEdits() { | ||||
|         const EditHistoryMessage = sdk.getComponent('messages.EditHistoryMessage'); | ||||
|         const DateSeparator = sdk.getComponent('messages.DateSeparator'); | ||||
|     private renderEdits(): JSX.Element[] { | ||||
|         const nodes = []; | ||||
|         let lastEvent; | ||||
|         let allEvents = this.state.events; | ||||
|  | @ -128,7 +144,7 @@ export default class MessageEditHistoryDialog extends React.PureComponent { | |||
|         return nodes; | ||||
|     } | ||||
| 
 | ||||
|     render() { | ||||
|     public render(): JSX.Element { | ||||
|         let content; | ||||
|         if (this.state.error) { | ||||
|             const { error } = this.state; | ||||
|  | @ -149,20 +165,17 @@ export default class MessageEditHistoryDialog extends React.PureComponent { | |||
|                 </p>); | ||||
|             } | ||||
|         } else if (this.state.isLoading) { | ||||
|             const Spinner = sdk.getComponent("elements.Spinner"); | ||||
|             content = <Spinner />; | ||||
|         } else { | ||||
|             const ScrollPanel = sdk.getComponent("structures.ScrollPanel"); | ||||
|             content = (<ScrollPanel | ||||
|                 className="mx_MessageEditHistoryDialog_scrollPanel" | ||||
|                 onFillRequest={this.loadMoreEdits} | ||||
|                 stickyBottom={false} | ||||
|                 startAtBottom={false} | ||||
|             > | ||||
|                 <ul className="mx_MessageEditHistoryDialog_edits">{ this._renderEdits() }</ul> | ||||
|                 <ul className="mx_MessageEditHistoryDialog_edits">{ this.renderEdits() }</ul> | ||||
|             </ScrollPanel>); | ||||
|         } | ||||
|         const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); | ||||
|         return ( | ||||
|             <BaseDialog | ||||
|                 className='mx_MessageEditHistoryDialog' | ||||
|  | @ -16,29 +16,30 @@ limitations under the License. | |||
| */ | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import classNames from "classnames"; | ||||
| 
 | ||||
| import * as sdk from '../../../index'; | ||||
| import { _t } from '../../../languageHandler'; | ||||
| import { IDialogProps } from "./IDialogProps"; | ||||
| 
 | ||||
| export default class QuestionDialog extends React.Component { | ||||
|     static propTypes = { | ||||
|         title: PropTypes.string, | ||||
|         description: PropTypes.node, | ||||
|         extraButtons: PropTypes.node, | ||||
|         button: PropTypes.string, | ||||
|         buttonDisabled: PropTypes.bool, | ||||
|         danger: PropTypes.bool, | ||||
|         focus: PropTypes.bool, | ||||
|         onFinished: PropTypes.func.isRequired, | ||||
|         headerImage: PropTypes.string, | ||||
|         quitOnly: PropTypes.bool, // quitOnly doesn't show the cancel button just the quit [x].
 | ||||
|         fixedWidth: PropTypes.bool, | ||||
|         className: PropTypes.string, | ||||
|     }; | ||||
| interface IProps extends IDialogProps { | ||||
|     title?: string; | ||||
|     description?: React.ReactNode; | ||||
|     extraButtons?: React.ReactNode; | ||||
|     button?: string; | ||||
|     buttonDisabled?: boolean; | ||||
|     danger?: boolean; | ||||
|     focus?: boolean; | ||||
|     headerImage?: string; | ||||
|     quitOnly?: boolean; // quitOnly doesn't show the cancel button just the quit [x].
 | ||||
|     fixedWidth?: boolean; | ||||
|     className?: string; | ||||
|     hasCancelButton?: boolean; | ||||
|     cancelButton?: React.ReactNode; | ||||
| } | ||||
| 
 | ||||
|     static defaultProps = { | ||||
| export default class QuestionDialog extends React.Component<IProps> { | ||||
|     public static defaultProps: Partial<IProps> = { | ||||
|         title: "", | ||||
|         description: "", | ||||
|         extraButtons: null, | ||||
|  | @ -48,17 +49,19 @@ export default class QuestionDialog extends React.Component { | |||
|         quitOnly: false, | ||||
|     }; | ||||
| 
 | ||||
|     onOk = () => { | ||||
|     private onOk = (): void => { | ||||
|         this.props.onFinished(true); | ||||
|     }; | ||||
| 
 | ||||
|     onCancel = () => { | ||||
|     private onCancel = (): void => { | ||||
|         this.props.onFinished(false); | ||||
|     }; | ||||
| 
 | ||||
|     render() { | ||||
|     public render(): JSX.Element { | ||||
|         // Converting these to imports breaks wrench tests
 | ||||
|         const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); | ||||
|         const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); | ||||
| 
 | ||||
|         let primaryButtonClass = ""; | ||||
|         if (this.props.danger) { | ||||
|             primaryButtonClass = "danger"; | ||||
|  | @ -17,27 +17,27 @@ limitations under the License. | |||
| */ | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import * as sdk from '../../../index'; | ||||
| import SdkConfig from '../../../SdkConfig'; | ||||
| import Modal from '../../../Modal'; | ||||
| import { _t } from '../../../languageHandler'; | ||||
| import { replaceableComponent } from "../../../utils/replaceableComponent"; | ||||
| import QuestionDialog from "./QuestionDialog"; | ||||
| import BugReportDialog from "./BugReportDialog"; | ||||
| import BaseDialog from "./BaseDialog"; | ||||
| import DialogButtons from "../elements/DialogButtons"; | ||||
| import { IDialogProps } from "./IDialogProps"; | ||||
| 
 | ||||
| interface IProps extends IDialogProps { | ||||
|     error: string; | ||||
| } | ||||
| 
 | ||||
| @replaceableComponent("views.dialogs.SessionRestoreErrorDialog") | ||||
| export default class SessionRestoreErrorDialog extends React.Component { | ||||
|     static propTypes = { | ||||
|         error: PropTypes.string.isRequired, | ||||
|         onFinished: PropTypes.func.isRequired, | ||||
|     }; | ||||
| 
 | ||||
|     _sendBugReport = () => { | ||||
|         const BugReportDialog = sdk.getComponent("dialogs.BugReportDialog"); | ||||
| export default class SessionRestoreErrorDialog extends React.Component<IProps> { | ||||
|     private sendBugReport = (): void => { | ||||
|         Modal.createTrackedDialog('Session Restore Error', 'Send Bug Report Dialog', BugReportDialog, {}); | ||||
|     }; | ||||
| 
 | ||||
|     _onClearStorageClick = () => { | ||||
|         const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); | ||||
|     private onClearStorageClick = (): void => { | ||||
|         Modal.createTrackedDialog('Session Restore Confirm Logout', '', QuestionDialog, { | ||||
|             title: _t("Sign out"), | ||||
|             description: | ||||
|  | @ -48,19 +48,17 @@ export default class SessionRestoreErrorDialog extends React.Component { | |||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     _onRefreshClick = () => { | ||||
|     private onRefreshClick = (): void => { | ||||
|         // Is this likely to help? Probably not, but giving only one button
 | ||||
|         // that clears your storage seems awful.
 | ||||
|         window.location.reload(true); | ||||
|         window.location.reload(); | ||||
|     }; | ||||
| 
 | ||||
|     render() { | ||||
|     public render(): JSX.Element { | ||||
|         const brand = SdkConfig.get().brand; | ||||
|         const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); | ||||
|         const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); | ||||
| 
 | ||||
|         const clearStorageButton = ( | ||||
|             <button onClick={this._onClearStorageClick} className="danger"> | ||||
|             <button onClick={this.onClearStorageClick} className="danger"> | ||||
|                 { _t("Clear Storage and Sign Out") } | ||||
|             </button> | ||||
|         ); | ||||
|  | @ -68,7 +66,7 @@ export default class SessionRestoreErrorDialog extends React.Component { | |||
|         let dialogButtons; | ||||
|         if (SdkConfig.get().bug_report_endpoint_url) { | ||||
|             dialogButtons = <DialogButtons primaryButton={_t("Send Logs")} | ||||
|                 onPrimaryButtonClick={this._sendBugReport} | ||||
|                 onPrimaryButtonClick={this.sendBugReport} | ||||
|                 focus={true} | ||||
|                 hasCancel={false} | ||||
|             > | ||||
|  | @ -76,7 +74,7 @@ export default class SessionRestoreErrorDialog extends React.Component { | |||
|             </DialogButtons>; | ||||
|         } else { | ||||
|             dialogButtons = <DialogButtons primaryButton={_t("Refresh")} | ||||
|                 onPrimaryButtonClick={this._onRefreshClick} | ||||
|                 onPrimaryButtonClick={this.onRefreshClick} | ||||
|                 focus={true} | ||||
|                 hasCancel={false} | ||||
|             > | ||||
|  | @ -16,13 +16,26 @@ limitations under the License. | |||
| */ | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import * as sdk from '../../../index'; | ||||
| import * as Email from '../../../email'; | ||||
| import AddThreepid from '../../../AddThreepid'; | ||||
| import { _t } from '../../../languageHandler'; | ||||
| import Modal from '../../../Modal'; | ||||
| import { replaceableComponent } from "../../../utils/replaceableComponent"; | ||||
| import Spinner from "../elements/Spinner"; | ||||
| import ErrorDialog from "./ErrorDialog"; | ||||
| import QuestionDialog from "./QuestionDialog"; | ||||
| import BaseDialog from "./BaseDialog"; | ||||
| import EditableText from "../elements/EditableText"; | ||||
| import { IDialogProps } from "./IDialogProps"; | ||||
| 
 | ||||
| interface IProps extends IDialogProps { | ||||
|     title: string; | ||||
| } | ||||
| 
 | ||||
| interface IState { | ||||
|     emailAddress: string; | ||||
|     emailBusy: boolean; | ||||
| } | ||||
| 
 | ||||
| /* | ||||
|  * Prompt the user to set an email address. | ||||
|  | @ -30,26 +43,25 @@ import { replaceableComponent } from "../../../utils/replaceableComponent"; | |||
|  * On success, `onFinished(true)` is called. | ||||
|  */ | ||||
| @replaceableComponent("views.dialogs.SetEmailDialog") | ||||
| export default class SetEmailDialog extends React.Component { | ||||
|     static propTypes = { | ||||
|         onFinished: PropTypes.func.isRequired, | ||||
|     }; | ||||
| export default class SetEmailDialog extends React.Component<IProps, IState> { | ||||
|     private addThreepid: AddThreepid; | ||||
| 
 | ||||
|     state = { | ||||
|         emailAddress: '', | ||||
|         emailBusy: false, | ||||
|     }; | ||||
|     constructor(props: IProps) { | ||||
|         super(props); | ||||
| 
 | ||||
|     onEmailAddressChanged = value => { | ||||
|         this.state = { | ||||
|             emailAddress: '', | ||||
|             emailBusy: false, | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     private onEmailAddressChanged = (value: string): void => { | ||||
|         this.setState({ | ||||
|             emailAddress: value, | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     onSubmit = () => { | ||||
|         const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); | ||||
|         const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); | ||||
| 
 | ||||
|     private onSubmit = (): void => { | ||||
|         const emailAddress = this.state.emailAddress; | ||||
|         if (!Email.looksValid(emailAddress)) { | ||||
|             Modal.createTrackedDialog('Invalid Email Address', '', ErrorDialog, { | ||||
|  | @ -58,8 +70,8 @@ export default class SetEmailDialog extends React.Component { | |||
|             }); | ||||
|             return; | ||||
|         } | ||||
|         this._addThreepid = new AddThreepid(); | ||||
|         this._addThreepid.addEmailAddress(emailAddress).then(() => { | ||||
|         this.addThreepid = new AddThreepid(); | ||||
|         this.addThreepid.addEmailAddress(emailAddress).then(() => { | ||||
|             Modal.createTrackedDialog('Verification Pending', '', QuestionDialog, { | ||||
|                 title: _t("Verification Pending"), | ||||
|                 description: _t( | ||||
|  | @ -80,11 +92,11 @@ export default class SetEmailDialog extends React.Component { | |||
|         this.setState({ emailBusy: true }); | ||||
|     }; | ||||
| 
 | ||||
|     onCancelled = () => { | ||||
|     private onCancelled = (): void => { | ||||
|         this.props.onFinished(false); | ||||
|     }; | ||||
| 
 | ||||
|     onEmailDialogFinished = ok => { | ||||
|     private onEmailDialogFinished = (ok: boolean): void => { | ||||
|         if (ok) { | ||||
|             this.verifyEmailAddress(); | ||||
|         } else { | ||||
|  | @ -92,13 +104,12 @@ export default class SetEmailDialog extends React.Component { | |||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     verifyEmailAddress() { | ||||
|         this._addThreepid.checkEmailLinkClicked().then(() => { | ||||
|     private verifyEmailAddress(): void { | ||||
|         this.addThreepid.checkEmailLinkClicked().then(() => { | ||||
|             this.props.onFinished(true); | ||||
|         }, (err) => { | ||||
|             this.setState({ emailBusy: false }); | ||||
|             if (err.errcode == 'M_THREEPID_AUTH_FAILED') { | ||||
|                 const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); | ||||
|                 const message = _t("Unable to verify email address.") + " " + | ||||
|                     _t("Please check your email and click on the link it contains. Once this is done, click continue."); | ||||
|                 Modal.createTrackedDialog('Verification Pending', '3pid Auth Failed', QuestionDialog, { | ||||
|  | @ -108,7 +119,6 @@ export default class SetEmailDialog extends React.Component { | |||
|                     onFinished: this.onEmailDialogFinished, | ||||
|                 }); | ||||
|             } else { | ||||
|                 const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); | ||||
|                 console.error("Unable to verify email address: " + err); | ||||
|                 Modal.createTrackedDialog('Unable to verify email address', '', ErrorDialog, { | ||||
|                     title: _t("Unable to verify email address."), | ||||
|  | @ -118,15 +128,10 @@ export default class SetEmailDialog extends React.Component { | |||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     render() { | ||||
|         const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); | ||||
|         const Spinner = sdk.getComponent('elements.Spinner'); | ||||
|         const EditableText = sdk.getComponent('elements.EditableText'); | ||||
| 
 | ||||
|     public render(): JSX.Element { | ||||
|         const emailInput = this.state.emailBusy ? <Spinner /> : <EditableText | ||||
|             initialValue={this.state.emailAddress} | ||||
|             className="mx_SetEmailDialog_email_input" | ||||
|             autoFocus="true" | ||||
|             placeholder={_t("Email address")} | ||||
|             placeholderClassName="mx_SetEmailDialog_email_input_placeholder" | ||||
|             blurToCancel={false} | ||||
|  | @ -17,11 +17,12 @@ limitations under the License. | |||
| import React from 'react'; | ||||
| import { _t } from "../../../languageHandler"; | ||||
| import { CommandCategories, Commands } from "../../../SlashCommands"; | ||||
| import * as sdk from "../../../index"; | ||||
| import { IDialogProps } from "./IDialogProps"; | ||||
| import InfoDialog from "./InfoDialog"; | ||||
| 
 | ||||
| export default ({ onFinished }) => { | ||||
|     const InfoDialog = sdk.getComponent('dialogs.InfoDialog'); | ||||
| interface IProps extends IDialogProps {} | ||||
| 
 | ||||
| const SlashCommandHelpDialog: React.FC<IProps> = ({ onFinished }) => { | ||||
|     const categories = {}; | ||||
|     Commands.forEach(cmd => { | ||||
|         if (!cmd.isEnabled()) return; | ||||
|  | @ -62,3 +63,5 @@ export default ({ onFinished }) => { | |||
|         hasCloseButton={true} | ||||
|         onFinished={onFinished} />; | ||||
| }; | ||||
| 
 | ||||
| export default SlashCommandHelpDialog; | ||||
|  | @ -15,40 +15,36 @@ limitations under the License. | |||
| */ | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import * as sdk from '../../../index'; | ||||
| import SdkConfig from '../../../SdkConfig'; | ||||
| import Modal from '../../../Modal'; | ||||
| import { _t } from '../../../languageHandler'; | ||||
| import { replaceableComponent } from "../../../utils/replaceableComponent"; | ||||
| import BaseDialog from "./BaseDialog"; | ||||
| import DialogButtons from "../elements/DialogButtons"; | ||||
| import BugReportDialog from "./BugReportDialog"; | ||||
| import { IDialogProps } from "./IDialogProps"; | ||||
| 
 | ||||
| interface IProps extends IDialogProps { } | ||||
| 
 | ||||
| @replaceableComponent("views.dialogs.StorageEvictedDialog") | ||||
| export default class StorageEvictedDialog extends React.Component { | ||||
|     static propTypes = { | ||||
|         onFinished: PropTypes.func.isRequired, | ||||
|     }; | ||||
| 
 | ||||
|     _sendBugReport = ev => { | ||||
| export default class StorageEvictedDialog extends React.Component<IProps> { | ||||
|     private sendBugReport = (ev: React.MouseEvent): void => { | ||||
|         ev.preventDefault(); | ||||
|         const BugReportDialog = sdk.getComponent("dialogs.BugReportDialog"); | ||||
|         Modal.createTrackedDialog('Storage evicted', 'Send Bug Report Dialog', BugReportDialog, {}); | ||||
|     }; | ||||
| 
 | ||||
|     _onSignOutClick = () => { | ||||
|     private onSignOutClick = (): void => { | ||||
|         this.props.onFinished(true); | ||||
|     }; | ||||
| 
 | ||||
|     render() { | ||||
|         const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); | ||||
|         const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); | ||||
| 
 | ||||
|     public render(): JSX.Element { | ||||
|         let logRequest; | ||||
|         if (SdkConfig.get().bug_report_endpoint_url) { | ||||
|             logRequest = _t( | ||||
|                 "To help us prevent this in future, please <a>send us logs</a>.", | ||||
|                 {}, | ||||
|                 { | ||||
|                     a: text => <a href="#" onClick={this._sendBugReport}>{ text }</a>, | ||||
|                     a: text => <a href="#" onClick={this.sendBugReport}>{ text }</a>, | ||||
|                 }, | ||||
|             ); | ||||
|         } | ||||
|  | @ -73,7 +69,7 @@ export default class StorageEvictedDialog extends React.Component { | |||
|                     ) } { logRequest }</p> | ||||
|                 </div> | ||||
|                 <DialogButtons primaryButton={_t("Sign out")} | ||||
|                     onPrimaryButtonClick={this._onSignOutClick} | ||||
|                     onPrimaryButtonClick={this.onSignOutClick} | ||||
|                     focus={true} | ||||
|                     hasCancel={false} | ||||
|                 /> | ||||
|  | @ -15,42 +15,47 @@ limitations under the License. | |||
| */ | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { IntegrationManagers } from "../../../integrations/IntegrationManagers"; | ||||
| import { Room } from "matrix-js-sdk/src/models/room"; | ||||
| import * as sdk from '../../../index'; | ||||
| import { dialogTermsInteractionCallback, TermsNotSignedError } from "../../../Terms"; | ||||
| import classNames from 'classnames'; | ||||
| import * as ScalarMessaging from "../../../ScalarMessaging"; | ||||
| import { replaceableComponent } from "../../../utils/replaceableComponent"; | ||||
| import { IntegrationManagerInstance } from "../../../integrations/IntegrationManagerInstance"; | ||||
| import ScalarAuthClient from "../../../ScalarAuthClient"; | ||||
| import AccessibleButton from "../elements/AccessibleButton"; | ||||
| import IntegrationManager from "../settings/IntegrationManager"; | ||||
| import { IDialogProps } from "./IDialogProps"; | ||||
| 
 | ||||
| interface IProps extends IDialogProps { | ||||
|     /** | ||||
|      * Optional room where the integration manager should be open to | ||||
|      */ | ||||
|     room?: Room; | ||||
| 
 | ||||
|     /** | ||||
|      * Optional screen to open on the integration manager | ||||
|      */ | ||||
|     screen?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Optional integration ID to open in the integration manager | ||||
|      */ | ||||
|     integrationId?: string; | ||||
| } | ||||
| 
 | ||||
| interface IState { | ||||
|     managers: IntegrationManagerInstance[]; | ||||
|     busy: boolean; | ||||
|     currentIndex: number; | ||||
|     currentConnected: boolean; | ||||
|     currentLoading: boolean; | ||||
|     currentScalarClient: ScalarAuthClient; | ||||
| } | ||||
| 
 | ||||
| @replaceableComponent("views.dialogs.TabbedIntegrationManagerDialog") | ||||
| export default class TabbedIntegrationManagerDialog extends React.Component { | ||||
|     static propTypes = { | ||||
|         /** | ||||
|          * Called with: | ||||
|          *     * success {bool} True if the user accepted any douments, false if cancelled | ||||
|          *     * agreedUrls {string[]} List of agreed URLs | ||||
|          */ | ||||
|         onFinished: PropTypes.func.isRequired, | ||||
| 
 | ||||
|         /** | ||||
|          * Optional room where the integration manager should be open to | ||||
|          */ | ||||
|         room: PropTypes.instanceOf(Room), | ||||
| 
 | ||||
|         /** | ||||
|          * Optional screen to open on the integration manager | ||||
|          */ | ||||
|         screen: PropTypes.string, | ||||
| 
 | ||||
|         /** | ||||
|          * Optional integration ID to open in the integration manager | ||||
|          */ | ||||
|         integrationId: PropTypes.string, | ||||
|     }; | ||||
| 
 | ||||
|     constructor(props) { | ||||
| export default class TabbedIntegrationManagerDialog extends React.Component<IProps, IState> { | ||||
|     constructor(props: IProps) { | ||||
|         super(props); | ||||
| 
 | ||||
|         this.state = { | ||||
|  | @ -63,11 +68,11 @@ export default class TabbedIntegrationManagerDialog extends React.Component { | |||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     componentDidMount() { | ||||
|     public componentDidMount(): void { | ||||
|         this.openManager(0, true); | ||||
|     } | ||||
| 
 | ||||
|     openManager = async (i, force = false) => { | ||||
|     private openManager = async (i: number, force = false): Promise<void> => { | ||||
|         if (i === this.state.currentIndex && !force) return; | ||||
| 
 | ||||
|         const manager = this.state.managers[i]; | ||||
|  | @ -120,8 +125,7 @@ export default class TabbedIntegrationManagerDialog extends React.Component { | |||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     _renderTabs() { | ||||
|         const AccessibleButton = sdk.getComponent("views.elements.AccessibleButton"); | ||||
|     private renderTabs(): JSX.Element[] { | ||||
|         return this.state.managers.map((m, i) => { | ||||
|             const classes = classNames({ | ||||
|                 'mx_TabbedIntegrationManagerDialog_tab': true, | ||||
|  | @ -140,8 +144,7 @@ export default class TabbedIntegrationManagerDialog extends React.Component { | |||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     _renderTab() { | ||||
|         const IntegrationManager = sdk.getComponent("views.settings.IntegrationManager"); | ||||
|     public renderTab(): JSX.Element { | ||||
|         let uiUrl = null; | ||||
|         if (this.state.currentScalarClient) { | ||||
|             uiUrl = this.state.currentScalarClient.getScalarInterfaceUrlForRoom( | ||||
|  | @ -151,7 +154,6 @@ export default class TabbedIntegrationManagerDialog extends React.Component { | |||
|             ); | ||||
|         } | ||||
|         return <IntegrationManager | ||||
|             configured={true} | ||||
|             loading={this.state.currentLoading} | ||||
|             connected={this.state.currentConnected} | ||||
|             url={uiUrl} | ||||
|  | @ -159,14 +161,14 @@ export default class TabbedIntegrationManagerDialog extends React.Component { | |||
|         />; | ||||
|     } | ||||
| 
 | ||||
|     render() { | ||||
|     public render(): JSX.Element { | ||||
|         return ( | ||||
|             <div className='mx_TabbedIntegrationManagerDialog_container'> | ||||
|                 <div className='mx_TabbedIntegrationManagerDialog_tabs'> | ||||
|                     { this._renderTabs() } | ||||
|                     { this.renderTabs() } | ||||
|                 </div> | ||||
|                 <div className='mx_TabbedIntegrationManagerDialog_currentManager'> | ||||
|                     { this._renderTab() } | ||||
|                     { this.renderTab() } | ||||
|                 </div> | ||||
|             </div> | ||||
|         ); | ||||
|  | @ -14,33 +14,39 @@ See the License for the specific language governing permissions and | |||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| import React, { createRef } from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import * as sdk from '../../../index'; | ||||
| import React, { ChangeEvent, createRef } from 'react'; | ||||
| import Field from "../elements/Field"; | ||||
| import { _t, _td } from '../../../languageHandler'; | ||||
| import { replaceableComponent } from "../../../utils/replaceableComponent"; | ||||
| import { IFieldState, IValidationResult } from "../elements/Validation"; | ||||
| import BaseDialog from "./BaseDialog"; | ||||
| import DialogButtons from "../elements/DialogButtons"; | ||||
| import { IDialogProps } from "./IDialogProps"; | ||||
| 
 | ||||
| interface IProps extends IDialogProps { | ||||
|     title?: string; | ||||
|     description?: React.ReactNode; | ||||
|     value?: string; | ||||
|     placeholder?: string; | ||||
|     button?: string; | ||||
|     busyMessage?: string; // pass _td string
 | ||||
|     focus?: boolean; | ||||
|     hasCancel?: boolean; | ||||
|     validator?: (fieldState: IFieldState) => IValidationResult; // result of withValidation
 | ||||
|     fixedWidth?: boolean; | ||||
| } | ||||
| 
 | ||||
| interface IState { | ||||
|     value: string; | ||||
|     busy: boolean; | ||||
|     valid: boolean; | ||||
| } | ||||
| 
 | ||||
| @replaceableComponent("views.dialogs.TextInputDialog") | ||||
| export default class TextInputDialog extends React.Component { | ||||
|     static propTypes = { | ||||
|         title: PropTypes.string, | ||||
|         description: PropTypes.oneOfType([ | ||||
|             PropTypes.element, | ||||
|             PropTypes.string, | ||||
|         ]), | ||||
|         value: PropTypes.string, | ||||
|         placeholder: PropTypes.string, | ||||
|         button: PropTypes.string, | ||||
|         busyMessage: PropTypes.string, // pass _td string
 | ||||
|         focus: PropTypes.bool, | ||||
|         onFinished: PropTypes.func.isRequired, | ||||
|         hasCancel: PropTypes.bool, | ||||
|         validator: PropTypes.func, // result of withValidation
 | ||||
|         fixedWidth: PropTypes.bool, | ||||
|     }; | ||||
| export default class TextInputDialog extends React.Component<IProps, IState> { | ||||
|     private field = createRef<Field>(); | ||||
| 
 | ||||
|     static defaultProps = { | ||||
|     public static defaultProps = { | ||||
|         title: "", | ||||
|         value: "", | ||||
|         description: "", | ||||
|  | @ -49,11 +55,9 @@ export default class TextInputDialog extends React.Component { | |||
|         hasCancel: true, | ||||
|     }; | ||||
| 
 | ||||
|     constructor(props) { | ||||
|     constructor(props: IProps) { | ||||
|         super(props); | ||||
| 
 | ||||
|         this._field = createRef(); | ||||
| 
 | ||||
|         this.state = { | ||||
|             value: this.props.value, | ||||
|             busy: false, | ||||
|  | @ -61,23 +65,23 @@ export default class TextInputDialog extends React.Component { | |||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     componentDidMount() { | ||||
|     public componentDidMount(): void { | ||||
|         if (this.props.focus) { | ||||
|             // Set the cursor at the end of the text input
 | ||||
|             // this._field.current.value = this.props.value;
 | ||||
|             this._field.current.focus(); | ||||
|             this.field.current.focus(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     onOk = async ev => { | ||||
|     private onOk = async (ev: React.FormEvent): Promise<void> => { | ||||
|         ev.preventDefault(); | ||||
|         if (this.props.validator) { | ||||
|             this.setState({ busy: true }); | ||||
|             await this._field.current.validate({ allowEmpty: false }); | ||||
|             await this.field.current.validate({ allowEmpty: false }); | ||||
| 
 | ||||
|             if (!this._field.current.state.valid) { | ||||
|                 this._field.current.focus(); | ||||
|                 this._field.current.validate({ allowEmpty: false, focused: true }); | ||||
|             if (!this.field.current.state.valid) { | ||||
|                 this.field.current.focus(); | ||||
|                 this.field.current.validate({ allowEmpty: false, focused: true }); | ||||
|                 this.setState({ busy: false }); | ||||
|                 return; | ||||
|             } | ||||
|  | @ -85,17 +89,17 @@ export default class TextInputDialog extends React.Component { | |||
|         this.props.onFinished(true, this.state.value); | ||||
|     }; | ||||
| 
 | ||||
|     onCancel = () => { | ||||
|     private onCancel = (): void => { | ||||
|         this.props.onFinished(false); | ||||
|     }; | ||||
| 
 | ||||
|     onChange = ev => { | ||||
|     private onChange = (ev: ChangeEvent<HTMLInputElement>): void => { | ||||
|         this.setState({ | ||||
|             value: ev.target.value, | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     onValidate = async fieldState => { | ||||
|     private onValidate = async (fieldState: IFieldState): Promise<IValidationResult> => { | ||||
|         const result = await this.props.validator(fieldState); | ||||
|         this.setState({ | ||||
|             valid: result.valid, | ||||
|  | @ -103,9 +107,7 @@ export default class TextInputDialog extends React.Component { | |||
|         return result; | ||||
|     }; | ||||
| 
 | ||||
|     render() { | ||||
|         const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); | ||||
|         const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); | ||||
|     public render(): JSX.Element { | ||||
|         return ( | ||||
|             <BaseDialog | ||||
|                 className="mx_TextInputDialog" | ||||
|  | @ -121,13 +123,12 @@ export default class TextInputDialog extends React.Component { | |||
|                         <div> | ||||
|                             <Field | ||||
|                                 className="mx_TextInputDialog_input" | ||||
|                                 ref={this._field} | ||||
|                                 ref={this.field} | ||||
|                                 type="text" | ||||
|                                 label={this.props.placeholder} | ||||
|                                 value={this.state.value} | ||||
|                                 onChange={this.onChange} | ||||
|                                 onValidate={this.props.validator ? this.onValidate : undefined} | ||||
|                                 size="64" | ||||
|                             /> | ||||
|                         </div> | ||||
|                     </div> | ||||
|  | @ -17,11 +17,18 @@ limitations under the License. | |||
| import filesize from 'filesize'; | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import * as sdk from '../../../index'; | ||||
| import { _t } from '../../../languageHandler'; | ||||
| import ContentMessages from '../../../ContentMessages'; | ||||
| import { replaceableComponent } from "../../../utils/replaceableComponent"; | ||||
| import BaseDialog from "./BaseDialog"; | ||||
| import DialogButtons from "../elements/DialogButtons"; | ||||
| import { IDialogProps } from "./IDialogProps"; | ||||
| 
 | ||||
| interface IProps extends IDialogProps { | ||||
|     badFiles: File[]; | ||||
|     totalFiles: number; | ||||
|     contentMessages: ContentMessages; | ||||
| } | ||||
| 
 | ||||
| /* | ||||
|  * Tells the user about files we know cannot be uploaded before we even try uploading | ||||
|  | @ -29,26 +36,16 @@ import { replaceableComponent } from "../../../utils/replaceableComponent"; | |||
|  * the size of the file. | ||||
|  */ | ||||
| @replaceableComponent("views.dialogs.UploadFailureDialog") | ||||
| export default class UploadFailureDialog extends React.Component { | ||||
|     static propTypes = { | ||||
|         badFiles: PropTypes.arrayOf(PropTypes.object).isRequired, | ||||
|         totalFiles: PropTypes.number.isRequired, | ||||
|         contentMessages: PropTypes.instanceOf(ContentMessages).isRequired, | ||||
|         onFinished: PropTypes.func.isRequired, | ||||
|     } | ||||
| 
 | ||||
|     _onCancelClick = () => { | ||||
| export default class UploadFailureDialog extends React.Component<IProps> { | ||||
|     private onCancelClick = (): void => { | ||||
|         this.props.onFinished(false); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _onUploadClick = () => { | ||||
|     private onUploadClick = (): void => { | ||||
|         this.props.onFinished(true); | ||||
|     } | ||||
| 
 | ||||
|     render() { | ||||
|         const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); | ||||
|         const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); | ||||
|     }; | ||||
| 
 | ||||
|     public render(): JSX.Element { | ||||
|         let message; | ||||
|         let preview; | ||||
|         let buttons; | ||||
|  | @ -65,7 +62,7 @@ export default class UploadFailureDialog extends React.Component { | |||
|             ); | ||||
|             buttons = <DialogButtons primaryButton={_t('OK')} | ||||
|                 hasCancel={false} | ||||
|                 onPrimaryButtonClick={this._onCancelClick} | ||||
|                 onPrimaryButtonClick={this.onCancelClick} | ||||
|                 focus={true} | ||||
|             />; | ||||
|         } else if (this.props.totalFiles === this.props.badFiles.length) { | ||||
|  | @ -80,7 +77,7 @@ export default class UploadFailureDialog extends React.Component { | |||
|             ); | ||||
|             buttons = <DialogButtons primaryButton={_t('OK')} | ||||
|                 hasCancel={false} | ||||
|                 onPrimaryButtonClick={this._onCancelClick} | ||||
|                 onPrimaryButtonClick={this.onCancelClick} | ||||
|                 focus={true} | ||||
|             />; | ||||
|         } else { | ||||
|  | @ -96,17 +93,17 @@ export default class UploadFailureDialog extends React.Component { | |||
|             const howManyOthers = this.props.totalFiles - this.props.badFiles.length; | ||||
|             buttons = <DialogButtons | ||||
|                 primaryButton={_t('Upload %(count)s other files', { count: howManyOthers })} | ||||
|                 onPrimaryButtonClick={this._onUploadClick} | ||||
|                 onPrimaryButtonClick={this.onUploadClick} | ||||
|                 hasCancel={true} | ||||
|                 cancelButton={_t("Cancel All")} | ||||
|                 onCancel={this._onCancelClick} | ||||
|                 onCancel={this.onCancelClick} | ||||
|                 focus={true} | ||||
|             />; | ||||
|         } | ||||
| 
 | ||||
|         return ( | ||||
|             <BaseDialog className='mx_UploadFailureDialog' | ||||
|                 onFinished={this._onCancelClick} | ||||
|                 onFinished={this.onCancelClick} | ||||
|                 title={_t("Upload Error")} | ||||
|                 contentId='mx_Dialog_content' | ||||
|             > | ||||
|  | @ -47,15 +47,15 @@ export default class WidgetOpenIDPermissionsDialog extends React.PureComponent<I | |||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     private onAllow = () => { | ||||
|     private onAllow = (): void => { | ||||
|         this.onPermissionSelection(true); | ||||
|     }; | ||||
| 
 | ||||
|     private onDeny = () => { | ||||
|     private onDeny = (): void => { | ||||
|         this.onPermissionSelection(false); | ||||
|     }; | ||||
| 
 | ||||
|     private onPermissionSelection(allowed: boolean) { | ||||
|     private onPermissionSelection(allowed: boolean): void { | ||||
|         if (this.state.rememberSelection) { | ||||
|             logger.log(`Remembering ${this.props.widget.id} as allowed=${allowed} for OpenID`); | ||||
| 
 | ||||
|  | @ -68,11 +68,11 @@ export default class WidgetOpenIDPermissionsDialog extends React.PureComponent<I | |||
|         this.props.onFinished(allowed); | ||||
|     } | ||||
| 
 | ||||
|     private onRememberSelectionChange = (newVal: boolean) => { | ||||
|     private onRememberSelectionChange = (newVal: boolean): void => { | ||||
|         this.setState({ rememberSelection: newVal }); | ||||
|     }; | ||||
| 
 | ||||
|     public render() { | ||||
|     public render(): JSX.Element { | ||||
|         return ( | ||||
|             <BaseDialog | ||||
|                 className='mx_WidgetOpenIDPermissionsDialog' | ||||
|  |  | |||
|  | @ -16,32 +16,64 @@ limitations under the License. | |||
| */ | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import * as sdk from '../../../../index'; | ||||
| import { MatrixClientPeg } from '../../../../MatrixClientPeg'; | ||||
| import { MatrixClient } from 'matrix-js-sdk/src/client'; | ||||
| import { _t } from '../../../../languageHandler'; | ||||
| import { accessSecretStorage } from '../../../../SecurityManager'; | ||||
| 
 | ||||
| import { IKeyBackupInfo, IKeyBackupRestoreResult } from "matrix-js-sdk/src/crypto/keybackup"; | ||||
| import { ISecretStorageKeyInfo } from "matrix-js-sdk/src/crypto/api"; | ||||
| import * as sdk from '../../../../index'; | ||||
| import { IDialogProps } from "../IDialogProps"; | ||||
| import { logger } from "matrix-js-sdk/src/logger"; | ||||
| 
 | ||||
| const RESTORE_TYPE_PASSPHRASE = 0; | ||||
| const RESTORE_TYPE_RECOVERYKEY = 1; | ||||
| const RESTORE_TYPE_SECRET_STORAGE = 2; | ||||
| enum RestoreType { | ||||
|     Passphrase = "passphrase", | ||||
|     RecoveryKey = "recovery_key", | ||||
|     SecretStorage = "secret_storage" | ||||
| } | ||||
| 
 | ||||
| enum ProgressState { | ||||
|     PreFetch = "prefetch", | ||||
|     Fetch = "fetch", | ||||
|     LoadKeys = "load_keys", | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| interface IProps extends IDialogProps { | ||||
|     // if false, will close the dialog as soon as the restore completes succesfully
 | ||||
|     // default: true
 | ||||
|     showSummary?: boolean; | ||||
|     // If specified, gather the key from the user but then call the function with the backup
 | ||||
|     // key rather than actually (necessarily) restoring the backup.
 | ||||
|     keyCallback?: (key: Uint8Array) => void; | ||||
| } | ||||
| 
 | ||||
| interface IState { | ||||
|     backupInfo: IKeyBackupInfo; | ||||
|     backupKeyStored: Record<string, ISecretStorageKeyInfo>; | ||||
|     loading: boolean; | ||||
|     loadError: string; | ||||
|     restoreError: { | ||||
|         errcode: string; | ||||
|     }; | ||||
|     recoveryKey: string; | ||||
|     recoverInfo: IKeyBackupRestoreResult; | ||||
|     recoveryKeyValid: boolean; | ||||
|     forceRecoveryKey: boolean; | ||||
|     passPhrase: string; | ||||
|     restoreType: RestoreType; | ||||
|     progress: { | ||||
|         stage: ProgressState; | ||||
|         total?: number; | ||||
|         successes?: number; | ||||
|         failures?: number; | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| /* | ||||
|  * Dialog for restoring e2e keys from a backup and the user's recovery key | ||||
|  */ | ||||
| export default class RestoreKeyBackupDialog extends React.PureComponent { | ||||
|     static propTypes = { | ||||
|         // if false, will close the dialog as soon as the restore completes succesfully
 | ||||
|         // default: true
 | ||||
|         showSummary: PropTypes.bool, | ||||
|         // If specified, gather the key from the user but then call the function with the backup
 | ||||
|         // key rather than actually (necessarily) restoring the backup.
 | ||||
|         keyCallback: PropTypes.func, | ||||
|     }; | ||||
| 
 | ||||
| export default class RestoreKeyBackupDialog extends React.PureComponent<IProps, IState> { | ||||
|     static defaultProps = { | ||||
|         showSummary: true, | ||||
|     }; | ||||
|  | @ -60,58 +92,58 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { | |||
|             forceRecoveryKey: false, | ||||
|             passPhrase: '', | ||||
|             restoreType: null, | ||||
|             progress: { stage: "prefetch" }, | ||||
|             progress: { stage: ProgressState.PreFetch }, | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     componentDidMount() { | ||||
|         this._loadBackupStatus(); | ||||
|     public componentDidMount(): void { | ||||
|         this.loadBackupStatus(); | ||||
|     } | ||||
| 
 | ||||
|     _onCancel = () => { | ||||
|     private onCancel = (): void => { | ||||
|         this.props.onFinished(false); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _onDone = () => { | ||||
|     private onDone = (): void => { | ||||
|         this.props.onFinished(true); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _onUseRecoveryKeyClick = () => { | ||||
|     private onUseRecoveryKeyClick = (): void => { | ||||
|         this.setState({ | ||||
|             forceRecoveryKey: true, | ||||
|         }); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _progressCallback = (data) => { | ||||
|     private progressCallback = (data): void => { | ||||
|         this.setState({ | ||||
|             progress: data, | ||||
|         }); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _onResetRecoveryClick = () => { | ||||
|     private onResetRecoveryClick = (): void => { | ||||
|         this.props.onFinished(false); | ||||
|         accessSecretStorage(() => {}, /* forceReset = */ true); | ||||
|     } | ||||
|         accessSecretStorage(async () => {}, /* forceReset = */ true); | ||||
|     }; | ||||
| 
 | ||||
|     _onRecoveryKeyChange = (e) => { | ||||
|     private onRecoveryKeyChange = (e): void => { | ||||
|         this.setState({ | ||||
|             recoveryKey: e.target.value, | ||||
|             recoveryKeyValid: MatrixClientPeg.get().isValidRecoveryKey(e.target.value), | ||||
|         }); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _onPassPhraseNext = async () => { | ||||
|     private onPassPhraseNext = async (): Promise<void> => { | ||||
|         this.setState({ | ||||
|             loading: true, | ||||
|             restoreError: null, | ||||
|             restoreType: RESTORE_TYPE_PASSPHRASE, | ||||
|             restoreType: RestoreType.Passphrase, | ||||
|         }); | ||||
|         try { | ||||
|             // We do still restore the key backup: we must ensure that the key backup key
 | ||||
|             // is the right one and restoring it is currently the only way we can do this.
 | ||||
|             const recoverInfo = await MatrixClientPeg.get().restoreKeyBackupWithPassword( | ||||
|                 this.state.passPhrase, undefined, undefined, this.state.backupInfo, | ||||
|                 { progressCallback: this._progressCallback }, | ||||
|                 { progressCallback: this.progressCallback }, | ||||
|             ); | ||||
|             if (this.props.keyCallback) { | ||||
|                 const key = await MatrixClientPeg.get().keyBackupKeyFromPassword( | ||||
|  | @ -135,20 +167,20 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { | |||
|                 restoreError: e, | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _onRecoveryKeyNext = async () => { | ||||
|     private onRecoveryKeyNext = async (): Promise<void> => { | ||||
|         if (!this.state.recoveryKeyValid) return; | ||||
| 
 | ||||
|         this.setState({ | ||||
|             loading: true, | ||||
|             restoreError: null, | ||||
|             restoreType: RESTORE_TYPE_RECOVERYKEY, | ||||
|             restoreType: RestoreType.RecoveryKey, | ||||
|         }); | ||||
|         try { | ||||
|             const recoverInfo = await MatrixClientPeg.get().restoreKeyBackupWithRecoveryKey( | ||||
|                 this.state.recoveryKey, undefined, undefined, this.state.backupInfo, | ||||
|                 { progressCallback: this._progressCallback }, | ||||
|                 { progressCallback: this.progressCallback }, | ||||
|             ); | ||||
|             if (this.props.keyCallback) { | ||||
|                 const key = MatrixClientPeg.get().keyBackupKeyFromRecoveryKey(this.state.recoveryKey); | ||||
|  | @ -169,31 +201,30 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { | |||
|                 restoreError: e, | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _onPassPhraseChange = (e) => { | ||||
|     private onPassPhraseChange = (e): void => { | ||||
|         this.setState({ | ||||
|             passPhrase: e.target.value, | ||||
|         }); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     async _restoreWithSecretStorage() { | ||||
|     private async restoreWithSecretStorage(): Promise<void> { | ||||
|         this.setState({ | ||||
|             loading: true, | ||||
|             restoreError: null, | ||||
|             restoreType: RESTORE_TYPE_SECRET_STORAGE, | ||||
|             restoreType: RestoreType.SecretStorage, | ||||
|         }); | ||||
|         try { | ||||
|             // `accessSecretStorage` may prompt for storage access as needed.
 | ||||
|             const recoverInfo = await accessSecretStorage(async () => { | ||||
|                 return MatrixClientPeg.get().restoreKeyBackupWithSecretStorage( | ||||
|             await accessSecretStorage(async () => { | ||||
|                 await MatrixClientPeg.get().restoreKeyBackupWithSecretStorage( | ||||
|                     this.state.backupInfo, undefined, undefined, | ||||
|                     { progressCallback: this._progressCallback }, | ||||
|                     { progressCallback: this.progressCallback }, | ||||
|                 ); | ||||
|             }); | ||||
|             this.setState({ | ||||
|                 loading: false, | ||||
|                 recoverInfo, | ||||
|             }); | ||||
|         } catch (e) { | ||||
|             logger.log("Error restoring backup", e); | ||||
|  | @ -204,14 +235,14 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     async _restoreWithCachedKey(backupInfo) { | ||||
|     private async restoreWithCachedKey(backupInfo): Promise<boolean> { | ||||
|         if (!backupInfo) return false; | ||||
|         try { | ||||
|             const recoverInfo = await MatrixClientPeg.get().restoreKeyBackupWithCache( | ||||
|                 undefined, /* targetRoomId */ | ||||
|                 undefined, /* targetSessionId */ | ||||
|                 backupInfo, | ||||
|                 { progressCallback: this._progressCallback }, | ||||
|                 { progressCallback: this.progressCallback }, | ||||
|             ); | ||||
|             this.setState({ | ||||
|                 recoverInfo, | ||||
|  | @ -223,7 +254,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     async _loadBackupStatus() { | ||||
|     private async loadBackupStatus(): Promise<void> { | ||||
|         this.setState({ | ||||
|             loading: true, | ||||
|             loadError: null, | ||||
|  | @ -238,7 +269,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { | |||
|                 backupKeyStored, | ||||
|             }); | ||||
| 
 | ||||
|             const gotCache = await this._restoreWithCachedKey(backupInfo); | ||||
|             const gotCache = await this.restoreWithCachedKey(backupInfo); | ||||
|             if (gotCache) { | ||||
|                 logger.log("RestoreKeyBackupDialog: found cached backup key"); | ||||
|                 this.setState({ | ||||
|  | @ -249,7 +280,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { | |||
| 
 | ||||
|             // If the backup key is stored, we can proceed directly to restore.
 | ||||
|             if (backupKeyStored) { | ||||
|                 return this._restoreWithSecretStorage(); | ||||
|                 return this.restoreWithSecretStorage(); | ||||
|             } | ||||
| 
 | ||||
|             this.setState({ | ||||
|  | @ -265,7 +296,10 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     render() { | ||||
|     public render(): JSX.Element { | ||||
|         // FIXME: Making these into imports will break tests
 | ||||
|         const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); | ||||
|         const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); | ||||
|         const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); | ||||
|         const Spinner = sdk.getComponent("elements.Spinner"); | ||||
| 
 | ||||
|  | @ -281,12 +315,12 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { | |||
|         if (this.state.loading) { | ||||
|             title = _t("Restoring keys from backup"); | ||||
|             let details; | ||||
|             if (this.state.progress.stage === "fetch") { | ||||
|             if (this.state.progress.stage === ProgressState.Fetch) { | ||||
|                 details = _t("Fetching keys from server..."); | ||||
|             } else if (this.state.progress.stage === "load_keys") { | ||||
|             } else if (this.state.progress.stage === ProgressState.LoadKeys) { | ||||
|                 const { total, successes, failures } = this.state.progress; | ||||
|                 details = _t("%(completed)s of %(total)s keys restored", { total, completed: successes + failures }); | ||||
|             } else if (this.state.progress.stage === "prefetch") { | ||||
|             } else if (this.state.progress.stage === ProgressState.PreFetch) { | ||||
|                 details = _t("Fetching keys from server..."); | ||||
|             } | ||||
|             content = <div> | ||||
|  | @ -298,7 +332,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { | |||
|             content = _t("Unable to load backup status"); | ||||
|         } else if (this.state.restoreError) { | ||||
|             if (this.state.restoreError.errcode === MatrixClient.RESTORE_BACKUP_ERROR_BAD_KEY) { | ||||
|                 if (this.state.restoreType === RESTORE_TYPE_RECOVERYKEY) { | ||||
|                 if (this.state.restoreType === RestoreType.RecoveryKey) { | ||||
|                     title = _t("Security Key mismatch"); | ||||
|                     content = <div> | ||||
|                         <p>{ _t( | ||||
|  | @ -323,7 +357,6 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { | |||
|             title = _t("Error"); | ||||
|             content = _t("No backup found!"); | ||||
|         } else if (this.state.recoverInfo) { | ||||
|             const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); | ||||
|             title = _t("Keys restored"); | ||||
|             let failedToDecrypt; | ||||
|             if (this.state.recoverInfo.total > this.state.recoverInfo.imported) { | ||||
|  | @ -336,14 +369,12 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { | |||
|                 <p>{ _t("Successfully restored %(sessionCount)s keys", { sessionCount: this.state.recoverInfo.imported }) }</p> | ||||
|                 { failedToDecrypt } | ||||
|                 <DialogButtons primaryButton={_t('OK')} | ||||
|                     onPrimaryButtonClick={this._onDone} | ||||
|                     onPrimaryButtonClick={this.onDone} | ||||
|                     hasCancel={false} | ||||
|                     focus={true} | ||||
|                 /> | ||||
|             </div>; | ||||
|         } else if (backupHasPassphrase && !this.state.forceRecoveryKey) { | ||||
|             const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); | ||||
|             const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); | ||||
|             title = _t("Enter Security Phrase"); | ||||
|             content = <div> | ||||
|                 <p>{ _t( | ||||
|  | @ -359,16 +390,16 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { | |||
|                 <form className="mx_RestoreKeyBackupDialog_primaryContainer"> | ||||
|                     <input type="password" | ||||
|                         className="mx_RestoreKeyBackupDialog_passPhraseInput" | ||||
|                         onChange={this._onPassPhraseChange} | ||||
|                         onChange={this.onPassPhraseChange} | ||||
|                         value={this.state.passPhrase} | ||||
|                         autoFocus={true} | ||||
|                     /> | ||||
|                     <DialogButtons | ||||
|                         primaryButton={_t('Next')} | ||||
|                         onPrimaryButtonClick={this._onPassPhraseNext} | ||||
|                         onPrimaryButtonClick={this.onPassPhraseNext} | ||||
|                         primaryIsSubmit={true} | ||||
|                         hasCancel={true} | ||||
|                         onCancel={this._onCancel} | ||||
|                         onCancel={this.onCancel} | ||||
|                         focus={false} | ||||
|                     /> | ||||
|                 </form> | ||||
|  | @ -381,14 +412,14 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { | |||
|                         button1: s => <AccessibleButton | ||||
|                             className="mx_linkButton" | ||||
|                             element="span" | ||||
|                             onClick={this._onUseRecoveryKeyClick} | ||||
|                             onClick={this.onUseRecoveryKeyClick} | ||||
|                         > | ||||
|                             { s } | ||||
|                         </AccessibleButton>, | ||||
|                         button2: s => <AccessibleButton | ||||
|                             className="mx_linkButton" | ||||
|                             element="span" | ||||
|                             onClick={this._onResetRecoveryClick} | ||||
|                             onClick={this.onResetRecoveryClick} | ||||
|                         > | ||||
|                             { s } | ||||
|                         </AccessibleButton>, | ||||
|  | @ -396,8 +427,6 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { | |||
|             </div>; | ||||
|         } else { | ||||
|             title = _t("Enter Security Key"); | ||||
|             const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); | ||||
|             const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); | ||||
| 
 | ||||
|             let keyStatus; | ||||
|             if (this.state.recoveryKey.length === 0) { | ||||
|  | @ -425,15 +454,15 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { | |||
| 
 | ||||
|                 <div className="mx_RestoreKeyBackupDialog_primaryContainer"> | ||||
|                     <input className="mx_RestoreKeyBackupDialog_recoveryKeyInput" | ||||
|                         onChange={this._onRecoveryKeyChange} | ||||
|                         onChange={this.onRecoveryKeyChange} | ||||
|                         value={this.state.recoveryKey} | ||||
|                         autoFocus={true} | ||||
|                     /> | ||||
|                     { keyStatus } | ||||
|                     <DialogButtons primaryButton={_t('Next')} | ||||
|                         onPrimaryButtonClick={this._onRecoveryKeyNext} | ||||
|                         onPrimaryButtonClick={this.onRecoveryKeyNext} | ||||
|                         hasCancel={true} | ||||
|                         onCancel={this._onCancel} | ||||
|                         onCancel={this.onCancel} | ||||
|                         focus={false} | ||||
|                         primaryDisabled={!this.state.recoveryKeyValid} | ||||
|                     /> | ||||
|  | @ -445,7 +474,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { | |||
|                     { | ||||
|                         button: s => <AccessibleButton className="mx_linkButton" | ||||
|                             element="span" | ||||
|                             onClick={this._onResetRecoveryClick} | ||||
|                             onClick={this.onResetRecoveryClick} | ||||
|                         > | ||||
|                             { s } | ||||
|                         </AccessibleButton>, | ||||
|  | @ -20,6 +20,7 @@ import BaseDialog from '../BaseDialog'; | |||
| import { _t } from '../../../../languageHandler'; | ||||
| import { SetupEncryptionStore, Phase } from '../../../../stores/SetupEncryptionStore'; | ||||
| import { replaceableComponent } from "../../../../utils/replaceableComponent"; | ||||
| import { IDialogProps } from "../IDialogProps"; | ||||
| 
 | ||||
| function iconFromPhase(phase: Phase) { | ||||
|     if (phase === Phase.Done) { | ||||
|  | @ -29,12 +30,9 @@ function iconFromPhase(phase: Phase) { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| interface IProps { | ||||
|     onFinished: (success: boolean) => void; | ||||
| } | ||||
| 
 | ||||
| interface IProps extends IDialogProps {} | ||||
| interface IState { | ||||
|     icon: Phase; | ||||
|     icon: string; | ||||
| } | ||||
| 
 | ||||
| @replaceableComponent("views.dialogs.security.SetupEncryptionDialog") | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Germain
						Germain