Convert inputs on Export/Import Room Key dialogs to be real Fields (#9350)
* Convert inputs on Export/Import Room Key dialogs to be real Fields Fixes https://github.com/vector-im/element-web/issues/18517 * Correctly label the second field * Appease the linter
This commit is contained in:
parent
99488b84ec
commit
1032334b20
3 changed files with 61 additions and 51 deletions
|
@ -49,3 +49,8 @@ export type KeysWithObjectShape<Input> = {
|
||||||
? (Input[P] extends Array<unknown> ? never : P)
|
? (Input[P] extends Array<unknown> ? never : P)
|
||||||
: never;
|
: never;
|
||||||
}[keyof Input];
|
}[keyof Input];
|
||||||
|
|
||||||
|
export type KeysStartingWith<Input extends object, Str extends string> = {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
[P in keyof Input]: P extends `${Str}${infer _X}` ? P : never; // we don't use _X
|
||||||
|
}[keyof Input];
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -15,7 +16,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import FileSaver from 'file-saver';
|
import FileSaver from 'file-saver';
|
||||||
import React, { createRef } from 'react';
|
import React from 'react';
|
||||||
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
|
@ -23,6 +24,8 @@ import { _t } from '../../../../languageHandler';
|
||||||
import * as MegolmExportEncryption from '../../../../utils/MegolmExportEncryption';
|
import * as MegolmExportEncryption from '../../../../utils/MegolmExportEncryption';
|
||||||
import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps";
|
import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps";
|
||||||
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
||||||
|
import Field from "../../../../components/views/elements/Field";
|
||||||
|
import { KeysStartingWith } from "../../../../@types/common";
|
||||||
|
|
||||||
enum Phase {
|
enum Phase {
|
||||||
Edit = "edit",
|
Edit = "edit",
|
||||||
|
@ -36,12 +39,14 @@ interface IProps extends IDialogProps {
|
||||||
interface IState {
|
interface IState {
|
||||||
phase: Phase;
|
phase: Phase;
|
||||||
errStr: string;
|
errStr: string;
|
||||||
|
passphrase1: string;
|
||||||
|
passphrase2: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AnyPassphrase = KeysStartingWith<IState, "passphrase">;
|
||||||
|
|
||||||
export default class ExportE2eKeysDialog extends React.Component<IProps, IState> {
|
export default class ExportE2eKeysDialog extends React.Component<IProps, IState> {
|
||||||
private unmounted = false;
|
private unmounted = false;
|
||||||
private passphrase1 = createRef<HTMLInputElement>();
|
|
||||||
private passphrase2 = createRef<HTMLInputElement>();
|
|
||||||
|
|
||||||
constructor(props: IProps) {
|
constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -49,6 +54,8 @@ export default class ExportE2eKeysDialog extends React.Component<IProps, IState>
|
||||||
this.state = {
|
this.state = {
|
||||||
phase: Phase.Edit,
|
phase: Phase.Edit,
|
||||||
errStr: null,
|
errStr: null,
|
||||||
|
passphrase1: "",
|
||||||
|
passphrase2: "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,8 +66,8 @@ export default class ExportE2eKeysDialog extends React.Component<IProps, IState>
|
||||||
private onPassphraseFormSubmit = (ev: React.FormEvent): boolean => {
|
private onPassphraseFormSubmit = (ev: React.FormEvent): boolean => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
const passphrase = this.passphrase1.current.value;
|
const passphrase = this.state.passphrase1;
|
||||||
if (passphrase !== this.passphrase2.current.value) {
|
if (passphrase !== this.state.passphrase2) {
|
||||||
this.setState({ errStr: _t('Passphrases must match') });
|
this.setState({ errStr: _t('Passphrases must match') });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -112,6 +119,12 @@ export default class ExportE2eKeysDialog extends React.Component<IProps, IState>
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private onPassphraseChange = (ev: React.ChangeEvent<HTMLInputElement>, phrase: AnyPassphrase) => {
|
||||||
|
this.setState({
|
||||||
|
[phrase]: ev.target.value,
|
||||||
|
} as Pick<IState, AnyPassphrase>);
|
||||||
|
};
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
const disableForm = (this.state.phase === Phase.Exporting);
|
const disableForm = (this.state.phase === Phase.Exporting);
|
||||||
|
|
||||||
|
@ -146,39 +159,28 @@ export default class ExportE2eKeysDialog extends React.Component<IProps, IState>
|
||||||
</div>
|
</div>
|
||||||
<div className='mx_E2eKeysDialog_inputTable'>
|
<div className='mx_E2eKeysDialog_inputTable'>
|
||||||
<div className='mx_E2eKeysDialog_inputRow'>
|
<div className='mx_E2eKeysDialog_inputRow'>
|
||||||
<div className='mx_E2eKeysDialog_inputLabel'>
|
<Field
|
||||||
<label htmlFor='passphrase1'>
|
label={_t("Enter passphrase")}
|
||||||
{ _t("Enter passphrase") }
|
value={this.state.passphrase1}
|
||||||
</label>
|
onChange={e => this.onPassphraseChange(e, "passphrase1")}
|
||||||
</div>
|
|
||||||
<div className='mx_E2eKeysDialog_inputCell'>
|
|
||||||
<input
|
|
||||||
ref={this.passphrase1}
|
|
||||||
id='passphrase1'
|
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
size={64}
|
size={64}
|
||||||
type='password'
|
type="password"
|
||||||
disabled={disableForm}
|
disabled={disableForm}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div className='mx_E2eKeysDialog_inputRow'>
|
<div className='mx_E2eKeysDialog_inputRow'>
|
||||||
<div className='mx_E2eKeysDialog_inputLabel'>
|
<Field
|
||||||
<label htmlFor='passphrase2'>
|
label={_t("Confirm passphrase")}
|
||||||
{ _t("Confirm passphrase") }
|
value={this.state.passphrase2}
|
||||||
</label>
|
onChange={e => this.onPassphraseChange(e, "passphrase2")}
|
||||||
</div>
|
|
||||||
<div className='mx_E2eKeysDialog_inputCell'>
|
|
||||||
<input ref={this.passphrase2}
|
|
||||||
id='passphrase2'
|
|
||||||
size={64}
|
size={64}
|
||||||
type='password'
|
type="password"
|
||||||
disabled={disableForm}
|
disabled={disableForm}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div className='mx_Dialog_buttons'>
|
<div className='mx_Dialog_buttons'>
|
||||||
<input
|
<input
|
||||||
className='mx_Dialog_primary'
|
className='mx_Dialog_primary'
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -22,6 +23,7 @@ import * as MegolmExportEncryption from '../../../../utils/MegolmExportEncryptio
|
||||||
import { _t } from '../../../../languageHandler';
|
import { _t } from '../../../../languageHandler';
|
||||||
import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps";
|
import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps";
|
||||||
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
||||||
|
import Field from "../../../../components/views/elements/Field";
|
||||||
|
|
||||||
function readFileAsArrayBuffer(file: File): Promise<ArrayBuffer> {
|
function readFileAsArrayBuffer(file: File): Promise<ArrayBuffer> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
@ -48,12 +50,12 @@ interface IState {
|
||||||
enableSubmit: boolean;
|
enableSubmit: boolean;
|
||||||
phase: Phase;
|
phase: Phase;
|
||||||
errStr: string;
|
errStr: string;
|
||||||
|
passphrase: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ImportE2eKeysDialog extends React.Component<IProps, IState> {
|
export default class ImportE2eKeysDialog extends React.Component<IProps, IState> {
|
||||||
private unmounted = false;
|
private unmounted = false;
|
||||||
private file = createRef<HTMLInputElement>();
|
private file = createRef<HTMLInputElement>();
|
||||||
private passphrase = createRef<HTMLInputElement>();
|
|
||||||
|
|
||||||
constructor(props: IProps) {
|
constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -62,6 +64,7 @@ export default class ImportE2eKeysDialog extends React.Component<IProps, IState>
|
||||||
enableSubmit: false,
|
enableSubmit: false,
|
||||||
phase: Phase.Edit,
|
phase: Phase.Edit,
|
||||||
errStr: null,
|
errStr: null,
|
||||||
|
passphrase: "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,16 +72,22 @@ export default class ImportE2eKeysDialog extends React.Component<IProps, IState>
|
||||||
this.unmounted = true;
|
this.unmounted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private onFormChange = (ev: React.FormEvent): void => {
|
private onFormChange = (): void => {
|
||||||
const files = this.file.current.files || [];
|
const files = this.file.current.files || [];
|
||||||
this.setState({
|
this.setState({
|
||||||
enableSubmit: (this.passphrase.current.value !== "" && files.length > 0),
|
enableSubmit: (this.state.passphrase !== "" && files.length > 0),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private onPassphraseChange = (ev: React.ChangeEvent<HTMLInputElement>): void => {
|
||||||
|
this.setState({ passphrase: ev.target.value });
|
||||||
|
this.onFormChange(); // update general form state too
|
||||||
|
};
|
||||||
|
|
||||||
private onFormSubmit = (ev: React.FormEvent): boolean => {
|
private onFormSubmit = (ev: React.FormEvent): boolean => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
this.startImport(this.file.current.files[0], this.passphrase.current.value);
|
// noinspection JSIgnoredPromiseFromCall
|
||||||
|
this.startImport(this.file.current.files[0], this.state.passphrase);
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -161,20 +170,14 @@ export default class ImportE2eKeysDialog extends React.Component<IProps, IState>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='mx_E2eKeysDialog_inputRow'>
|
<div className='mx_E2eKeysDialog_inputRow'>
|
||||||
<div className='mx_E2eKeysDialog_inputLabel'>
|
<Field
|
||||||
<label htmlFor='passphrase'>
|
label={_t("Enter passphrase")}
|
||||||
{ _t("Enter passphrase") }
|
value={this.state.passphrase}
|
||||||
</label>
|
onChange={this.onPassphraseChange}
|
||||||
</div>
|
|
||||||
<div className='mx_E2eKeysDialog_inputCell'>
|
|
||||||
<input
|
|
||||||
ref={this.passphrase}
|
|
||||||
id='passphrase'
|
|
||||||
size={64}
|
size={64}
|
||||||
type='password'
|
type="password"
|
||||||
onChange={this.onFormChange}
|
disabled={disableForm}
|
||||||
disabled={disableForm} />
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue