2021-11-01 23:44:42 -06:00
|
|
|
/*
|
2024-09-09 14:57:16 +01:00
|
|
|
Copyright 2024 New Vector Ltd.
|
2021-11-01 23:44:42 -06:00
|
|
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
|
|
|
|
|
2025-01-06 11:18:54 +00:00
|
|
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
2024-09-09 14:57:16 +01:00
|
|
|
Please see LICENSE files in the repository root for full details.
|
2021-11-01 23:44:42 -06:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import React, { FormEvent } from "react";
|
2023-08-09 08:18:41 +01:00
|
|
|
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
2021-12-09 09:10:23 +00:00
|
|
|
import FocusLock from "react-focus-lock";
|
|
|
|
|
|
2021-11-01 23:44:42 -06:00
|
|
|
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
|
|
|
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
|
|
|
|
import { _t } from "../../../languageHandler";
|
|
|
|
|
import AccessibleButton from "../elements/AccessibleButton";
|
2022-02-28 17:05:52 +01:00
|
|
|
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
|
|
|
|
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
2021-11-01 23:44:42 -06:00
|
|
|
|
|
|
|
|
export interface IScrollableBaseState {
|
|
|
|
|
canSubmit: boolean;
|
|
|
|
|
title: string;
|
|
|
|
|
actionLabel: string;
|
2023-08-21 14:09:17 +02:00
|
|
|
cancelLabel?: string;
|
2021-11-01 23:44:42 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Scrollable dialog base from Compound (Web Components).
|
|
|
|
|
*/
|
|
|
|
|
export default abstract class ScrollableBaseModal<
|
2023-02-28 10:31:48 +00:00
|
|
|
TProps extends { onFinished?: (...args: any[]) => void },
|
2021-11-01 23:44:42 -06:00
|
|
|
TState extends IScrollableBaseState,
|
|
|
|
|
> extends React.PureComponent<TProps, TState> {
|
|
|
|
|
protected constructor(props: TProps) {
|
|
|
|
|
super(props);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected get matrixClient(): MatrixClient {
|
2023-07-06 14:59:47 +01:00
|
|
|
// XXX: The contract on MatrixClientContext says it is only available within a LoggedInView subtree,
|
|
|
|
|
// given that modals function outside the MatrixChat React tree this simulates that. We don't want to
|
|
|
|
|
// use safeGet as it throwing would mean we cannot use modals whilst the user isn't logged in.
|
|
|
|
|
// The longer term solution is to move our ModalManager into the React tree to inherit contexts properly.
|
|
|
|
|
return MatrixClientPeg.get()!;
|
2021-11-01 23:44:42 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private onKeyDown = (e: KeyboardEvent | React.KeyboardEvent): void => {
|
2022-02-28 17:05:52 +01:00
|
|
|
const action = getKeyBindingsManager().getAccessibilityAction(e);
|
|
|
|
|
switch (action) {
|
|
|
|
|
case KeyBindingAction.Escape:
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
this.cancel();
|
|
|
|
|
break;
|
2021-11-01 23:44:42 -06:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2023-01-12 13:25:14 +00:00
|
|
|
private onCancel = (): void => {
|
2021-11-01 23:44:42 -06:00
|
|
|
this.cancel();
|
|
|
|
|
};
|
|
|
|
|
|
2023-01-12 13:25:14 +00:00
|
|
|
private onSubmit = (e: MouseEvent | FormEvent): void => {
|
2021-11-01 23:44:42 -06:00
|
|
|
e.stopPropagation();
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
if (!this.state.canSubmit) return; // pretend the submit button was disabled
|
|
|
|
|
this.submit();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
protected abstract cancel(): void;
|
|
|
|
|
protected abstract submit(): void;
|
|
|
|
|
protected abstract renderContent(): React.ReactNode;
|
|
|
|
|
|
2023-02-13 17:01:43 +00:00
|
|
|
public render(): React.ReactNode {
|
2021-11-01 23:44:42 -06:00
|
|
|
return (
|
|
|
|
|
<MatrixClientContext.Provider value={this.matrixClient}>
|
|
|
|
|
<FocusLock
|
|
|
|
|
returnFocus={true}
|
|
|
|
|
lockProps={{
|
|
|
|
|
onKeyDown: this.onKeyDown,
|
|
|
|
|
role: "dialog",
|
|
|
|
|
["aria-labelledby"]: "mx_CompoundDialog_title",
|
|
|
|
|
|
|
|
|
|
// Like BaseDialog, we'll just point this at the whole content
|
|
|
|
|
["aria-describedby"]: "mx_CompoundDialog_content",
|
|
|
|
|
}}
|
|
|
|
|
className="mx_CompoundDialog mx_ScrollableBaseDialog"
|
|
|
|
|
>
|
|
|
|
|
<div className="mx_CompoundDialog_header">
|
|
|
|
|
<h1>{this.state.title}</h1>
|
|
|
|
|
</div>
|
2024-03-13 09:38:32 -04:00
|
|
|
<AccessibleButton
|
|
|
|
|
onClick={this.onCancel}
|
|
|
|
|
className="mx_CompoundDialog_cancelButton"
|
|
|
|
|
aria-label={_t("dialog_close_label")}
|
|
|
|
|
/>
|
2023-01-06 17:24:13 +01:00
|
|
|
<form onSubmit={this.onSubmit} className="mx_CompoundDialog_form">
|
2021-11-01 23:44:42 -06:00
|
|
|
<div className="mx_CompoundDialog_content">{this.renderContent()}</div>
|
|
|
|
|
<div className="mx_CompoundDialog_footer">
|
|
|
|
|
<AccessibleButton onClick={this.onCancel} kind="primary_outline">
|
2023-08-23 11:57:22 +01:00
|
|
|
{this.state.cancelLabel ?? _t("action|cancel")}
|
2021-11-01 23:44:42 -06:00
|
|
|
</AccessibleButton>
|
|
|
|
|
<AccessibleButton
|
|
|
|
|
onClick={this.onSubmit}
|
|
|
|
|
kind="primary"
|
|
|
|
|
disabled={!this.state.canSubmit}
|
|
|
|
|
type="submit"
|
|
|
|
|
element="button"
|
|
|
|
|
className="mx_Dialog_nonDialogButton"
|
|
|
|
|
>
|
|
|
|
|
{this.state.actionLabel}
|
|
|
|
|
</AccessibleButton>
|
|
|
|
|
</div>
|
|
|
|
|
</form>
|
|
|
|
|
</FocusLock>
|
|
|
|
|
</MatrixClientContext.Provider>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|