Merge remote-tracking branch 'origin/release-v3.43.0'
# Conflicts: # CHANGELOG.md # package.json # src/components/views/messages/MessageActionBar.tsx
This commit is contained in:
commit
6b0156d876
680 changed files with 12433 additions and 5108 deletions
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
import React, { ReactNode } from "react";
|
||||
|
||||
import PlayPauseButton from "./PlayPauseButton";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { formatBytes } from "../../../utils/FormattingUtils";
|
||||
import DurationClock from "./DurationClock";
|
||||
import { _t } from "../../../languageHandler";
|
||||
|
@ -25,7 +24,6 @@ import SeekBar from "./SeekBar";
|
|||
import PlaybackClock from "./PlaybackClock";
|
||||
import AudioPlayerBase from "./AudioPlayerBase";
|
||||
|
||||
@replaceableComponent("views.audio_messages.AudioPlayer")
|
||||
export default class AudioPlayer extends AudioPlayerBase {
|
||||
protected renderFileSize(): string {
|
||||
const bytes = this.props.playback.sizeBytes;
|
||||
|
|
|
@ -19,7 +19,6 @@ import { logger } from "matrix-js-sdk/src/logger";
|
|||
|
||||
import { Playback, PlaybackState } from "../../../audio/Playback";
|
||||
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
|
@ -39,7 +38,6 @@ interface IState {
|
|||
error?: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.audio_messages.AudioPlayerBase")
|
||||
export default abstract class AudioPlayerBase<T extends IProps = IProps> extends React.PureComponent<T, IState> {
|
||||
protected seekRef: RefObject<SeekBar> = createRef();
|
||||
protected playPauseRef: RefObject<PlayPauseButton> = createRef();
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
import React, { HTMLProps } from "react";
|
||||
|
||||
import { formatSeconds } from "../../../DateUtils";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
export interface IProps extends Pick<HTMLProps<HTMLSpanElement>, "aria-live"> {
|
||||
seconds: number;
|
||||
|
@ -27,7 +26,6 @@ export interface IProps extends Pick<HTMLProps<HTMLSpanElement>, "aria-live"> {
|
|||
* Simply converts seconds into minutes and seconds. Note that hours will not be
|
||||
* displayed, making it possible to see "82:29".
|
||||
*/
|
||||
@replaceableComponent("views.audio_messages.Clock")
|
||||
export default class Clock extends React.Component<IProps> {
|
||||
public constructor(props) {
|
||||
super(props);
|
||||
|
|
|
@ -16,7 +16,6 @@ limitations under the License.
|
|||
|
||||
import React from "react";
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import Clock from "./Clock";
|
||||
import { Playback } from "../../../audio/Playback";
|
||||
|
||||
|
@ -31,7 +30,6 @@ interface IState {
|
|||
/**
|
||||
* A clock which shows a clip's maximum duration.
|
||||
*/
|
||||
@replaceableComponent("views.audio_messages.DurationClock")
|
||||
export default class DurationClock extends React.PureComponent<IProps, IState> {
|
||||
public constructor(props) {
|
||||
super(props);
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
import React from "react";
|
||||
|
||||
import { IRecordingUpdate, VoiceRecording } from "../../../audio/VoiceRecording";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import Clock from "./Clock";
|
||||
import { MarkedExecution } from "../../../utils/MarkedExecution";
|
||||
|
||||
|
@ -32,7 +31,6 @@ interface IState {
|
|||
/**
|
||||
* A clock for a live recording.
|
||||
*/
|
||||
@replaceableComponent("views.audio_messages.LiveRecordingClock")
|
||||
export default class LiveRecordingClock extends React.PureComponent<IProps, IState> {
|
||||
private seconds = 0;
|
||||
private scheduledUpdate = new MarkedExecution(
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
import React from "react";
|
||||
|
||||
import { IRecordingUpdate, RECORDING_PLAYBACK_SAMPLES, VoiceRecording } from "../../../audio/VoiceRecording";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { arrayFastResample, arraySeed } from "../../../utils/arrays";
|
||||
import Waveform from "./Waveform";
|
||||
import { MarkedExecution } from "../../../utils/MarkedExecution";
|
||||
|
@ -33,7 +32,6 @@ interface IState {
|
|||
/**
|
||||
* A waveform which shows the waveform of a live recording
|
||||
*/
|
||||
@replaceableComponent("views.audio_messages.LiveRecordingWaveform")
|
||||
export default class LiveRecordingWaveform extends React.PureComponent<IProps, IState> {
|
||||
public static defaultProps = {
|
||||
progress: 1,
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
import React, { ReactNode } from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { Playback, PlaybackState } from "../../../audio/Playback";
|
||||
|
@ -35,7 +34,6 @@ interface IProps extends Omit<React.ComponentProps<typeof AccessibleTooltipButto
|
|||
* Displays a play/pause button (activating the play/pause function of the recorder)
|
||||
* to be displayed in reference to a recording.
|
||||
*/
|
||||
@replaceableComponent("views.audio_messages.PlayPauseButton")
|
||||
export default class PlayPauseButton extends React.PureComponent<IProps> {
|
||||
public constructor(props) {
|
||||
super(props);
|
||||
|
|
|
@ -16,7 +16,6 @@ limitations under the License.
|
|||
|
||||
import React from "react";
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import Clock from "./Clock";
|
||||
import { Playback, PlaybackState } from "../../../audio/Playback";
|
||||
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
||||
|
@ -39,7 +38,6 @@ interface IState {
|
|||
/**
|
||||
* A clock for a playback of a recording.
|
||||
*/
|
||||
@replaceableComponent("views.audio_messages.PlaybackClock")
|
||||
export default class PlaybackClock extends React.PureComponent<IProps, IState> {
|
||||
public constructor(props) {
|
||||
super(props);
|
||||
|
|
|
@ -16,7 +16,6 @@ limitations under the License.
|
|||
|
||||
import React from "react";
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { arraySeed, arrayTrimFill } from "../../../utils/arrays";
|
||||
import Waveform from "./Waveform";
|
||||
import { Playback, PLAYBACK_WAVEFORM_SAMPLES } from "../../../audio/Playback";
|
||||
|
@ -34,7 +33,6 @@ interface IState {
|
|||
/**
|
||||
* A waveform which shows the waveform of a previously recorded recording
|
||||
*/
|
||||
@replaceableComponent("views.audio_messages.PlaybackWaveform")
|
||||
export default class PlaybackWaveform extends React.PureComponent<IProps, IState> {
|
||||
public constructor(props) {
|
||||
super(props);
|
||||
|
|
|
@ -18,7 +18,6 @@ import React, { ReactNode } from "react";
|
|||
|
||||
import PlayPauseButton from "./PlayPauseButton";
|
||||
import PlaybackClock from "./PlaybackClock";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import AudioPlayerBase, { IProps as IAudioPlayerBaseProps } from "./AudioPlayerBase";
|
||||
import SeekBar from "./SeekBar";
|
||||
import PlaybackWaveform from "./PlaybackWaveform";
|
||||
|
@ -30,7 +29,6 @@ interface IProps extends IAudioPlayerBaseProps {
|
|||
withWaveform?: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.audio_messages.RecordingPlayback")
|
||||
export default class RecordingPlayback extends AudioPlayerBase<IProps> {
|
||||
// This component is rendered in two ways: the composer and timeline. They have different
|
||||
// rendering properties (specifically the difference of a waveform or not).
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
import React, { ChangeEvent, CSSProperties, ReactNode } from "react";
|
||||
|
||||
import { Playback, PlaybackState } from "../../../audio/Playback";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { MarkedExecution } from "../../../utils/MarkedExecution";
|
||||
import { percentageOf } from "../../../utils/numbers";
|
||||
|
||||
|
@ -43,7 +42,6 @@ interface ISeekCSS extends CSSProperties {
|
|||
|
||||
const ARROW_SKIP_SECONDS = 5; // arbitrary
|
||||
|
||||
@replaceableComponent("views.audio_messages.SeekBar")
|
||||
export default class SeekBar extends React.PureComponent<IProps, IState> {
|
||||
// We use an animation frame request to avoid overly spamming prop updates, even if we aren't
|
||||
// really using anything demanding on the CSS front.
|
||||
|
|
|
@ -17,8 +17,6 @@ limitations under the License.
|
|||
import React, { CSSProperties } from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
interface WaveformCSSProperties extends CSSProperties {
|
||||
'--barHeight': number;
|
||||
}
|
||||
|
@ -39,7 +37,6 @@ interface IState {
|
|||
* For CSS purposes, a mx_Waveform_bar_100pct class is added when the bar should be
|
||||
* "filled", as a demonstration of the progress property.
|
||||
*/
|
||||
@replaceableComponent("views.audio_messages.Waveform")
|
||||
export default class Waveform extends React.PureComponent<IProps, IState> {
|
||||
public static defaultProps = {
|
||||
progress: 1,
|
||||
|
|
|
@ -16,9 +16,6 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("views.auth.AuthBody")
|
||||
export default class AuthBody extends React.PureComponent {
|
||||
public render(): React.ReactNode {
|
||||
return <div className="mx_AuthBody">
|
||||
|
|
|
@ -19,9 +19,7 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("views.auth.AuthFooter")
|
||||
export default class AuthFooter extends React.Component {
|
||||
public render(): React.ReactNode {
|
||||
return (
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import AuthHeaderLogo from "./AuthHeaderLogo";
|
||||
import LanguageSelector from "./LanguageSelector";
|
||||
|
||||
|
@ -25,7 +24,6 @@ interface IProps {
|
|||
disableLanguageSelector?: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.AuthHeader")
|
||||
export default class AuthHeader extends React.Component<IProps> {
|
||||
public render(): React.ReactNode {
|
||||
return (
|
||||
|
|
|
@ -16,9 +16,6 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("views.auth.AuthHeaderLogo")
|
||||
export default class AuthHeaderLogo extends React.PureComponent {
|
||||
public render(): React.ReactNode {
|
||||
return <div className="mx_AuthHeaderLogo">
|
||||
|
|
|
@ -18,10 +18,8 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import AuthFooter from "./AuthFooter";
|
||||
|
||||
@replaceableComponent("views.auth.AuthPage")
|
||||
export default class AuthPage extends React.PureComponent {
|
||||
public render(): React.ReactNode {
|
||||
return (
|
||||
|
|
|
@ -18,7 +18,6 @@ import React, { createRef } from 'react';
|
|||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
const DIV_ID = 'mx_recaptcha';
|
||||
|
||||
|
@ -35,7 +34,6 @@ interface ICaptchaFormState {
|
|||
/**
|
||||
* A pure UI component which displays a captcha form.
|
||||
*/
|
||||
@replaceableComponent("views.auth.CaptchaForm")
|
||||
export default class CaptchaForm extends React.Component<ICaptchaFormProps, ICaptchaFormState> {
|
||||
static defaultProps = {
|
||||
onCaptchaResponse: () => {},
|
||||
|
|
|
@ -16,9 +16,6 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("views.auth.CompleteSecurityBody")
|
||||
export default class CompleteSecurityBody extends React.PureComponent {
|
||||
public render(): React.ReactNode {
|
||||
return <div className="mx_CompleteSecurityBody">
|
||||
|
|
|
@ -19,7 +19,6 @@ import React from 'react';
|
|||
import { COUNTRIES, getEmojiFlag, PhoneNumberCountryDefinition } from '../../../phonenumber';
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import Dropdown from "../elements/Dropdown";
|
||||
|
||||
const COUNTRIES_BY_ISO2 = {};
|
||||
|
@ -53,7 +52,6 @@ interface IState {
|
|||
defaultCountry: PhoneNumberCountryDefinition;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.CountryDropdown")
|
||||
export default class CountryDropdown extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
|
|
@ -16,7 +16,6 @@ limitations under the License.
|
|||
|
||||
import React, { PureComponent, RefCallback, RefObject } from "react";
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import Field, { IInputProps } from "../elements/Field";
|
||||
import { _t, _td } from "../../../languageHandler";
|
||||
import withValidation, { IFieldState, IValidationResult } from "../elements/Validation";
|
||||
|
@ -39,7 +38,6 @@ interface IProps extends Omit<IInputProps, "onValidate"> {
|
|||
onValidate?(result: IValidationResult): void;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.EmailField")
|
||||
class EmailField extends PureComponent<IProps> {
|
||||
static defaultProps = {
|
||||
label: _td("Email"),
|
||||
|
|
|
@ -24,7 +24,6 @@ import { _t } from '../../../languageHandler';
|
|||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import Spinner from "../elements/Spinner";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { LocalisedPolicy, Policies } from '../../../Terms';
|
||||
import Field from '../elements/Field';
|
||||
import CaptchaForm from "./CaptchaForm";
|
||||
|
@ -93,7 +92,6 @@ interface IPasswordAuthEntryState {
|
|||
password: string;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.PasswordAuthEntry")
|
||||
export class PasswordAuthEntry extends React.Component<IAuthEntryProps, IPasswordAuthEntryState> {
|
||||
static LOGIN_TYPE = AuthType.Password;
|
||||
|
||||
|
@ -191,7 +189,6 @@ interface IRecaptchaAuthEntryProps extends IAuthEntryProps {
|
|||
}
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
@replaceableComponent("views.auth.RecaptchaAuthEntry")
|
||||
export class RecaptchaAuthEntry extends React.Component<IRecaptchaAuthEntryProps> {
|
||||
static LOGIN_TYPE = AuthType.Recaptcha;
|
||||
|
||||
|
@ -262,7 +259,6 @@ interface ITermsAuthEntryState {
|
|||
errorText?: string;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.TermsAuthEntry")
|
||||
export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITermsAuthEntryState> {
|
||||
static LOGIN_TYPE = AuthType.Terms;
|
||||
|
||||
|
@ -409,7 +405,6 @@ interface IEmailIdentityAuthEntryProps extends IAuthEntryProps {
|
|||
};
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.EmailIdentityAuthEntry")
|
||||
export class EmailIdentityAuthEntry extends React.Component<IEmailIdentityAuthEntryProps> {
|
||||
static LOGIN_TYPE = AuthType.Email;
|
||||
|
||||
|
@ -472,7 +467,6 @@ interface IMsisdnAuthEntryState {
|
|||
errorText: string;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.MsisdnAuthEntry")
|
||||
export class MsisdnAuthEntry extends React.Component<IMsisdnAuthEntryProps, IMsisdnAuthEntryState> {
|
||||
static LOGIN_TYPE = AuthType.Msisdn;
|
||||
|
||||
|
@ -623,7 +617,6 @@ interface ISSOAuthEntryState {
|
|||
attemptFailed: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.SSOAuthEntry")
|
||||
export class SSOAuthEntry extends React.Component<ISSOAuthEntryProps, ISSOAuthEntryState> {
|
||||
static LOGIN_TYPE = AuthType.Sso;
|
||||
static UNSTABLE_LOGIN_TYPE = AuthType.SsoUnstable;
|
||||
|
@ -743,7 +736,6 @@ export class SSOAuthEntry extends React.Component<ISSOAuthEntryProps, ISSOAuthEn
|
|||
}
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.FallbackAuthEntry")
|
||||
export class FallbackAuthEntry extends React.Component<IAuthEntryProps> {
|
||||
private popupWindow: Window;
|
||||
private fallbackButton = createRef<HTMLButtonElement>();
|
||||
|
|
|
@ -16,7 +16,6 @@ limitations under the License.
|
|||
|
||||
import React, { PureComponent, RefCallback, RefObject } from "react";
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import Field, { IInputProps } from "../elements/Field";
|
||||
import withValidation, { IFieldState, IValidationResult } from "../elements/Validation";
|
||||
import { _t, _td } from "../../../languageHandler";
|
||||
|
@ -35,7 +34,6 @@ interface IProps extends Omit<IInputProps, "onValidate"> {
|
|||
onValidate?(result: IValidationResult);
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.EmailField")
|
||||
class PassphraseConfirmField extends PureComponent<IProps> {
|
||||
static defaultProps = {
|
||||
label: _td("Confirm password"),
|
||||
|
|
|
@ -22,7 +22,6 @@ import SdkConfig from "../../../SdkConfig";
|
|||
import withValidation, { IFieldState, IValidationResult } from "../elements/Validation";
|
||||
import { _t, _td } from "../../../languageHandler";
|
||||
import Field, { IInputProps } from "../elements/Field";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps extends Omit<IInputProps, "onValidate"> {
|
||||
autoFocus?: boolean;
|
||||
|
@ -41,7 +40,6 @@ interface IProps extends Omit<IInputProps, "onValidate"> {
|
|||
onValidate?(result: IValidationResult);
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.PassphraseField")
|
||||
class PassphraseField extends PureComponent<IProps> {
|
||||
static defaultProps = {
|
||||
label: _td("Password"),
|
||||
|
|
|
@ -24,7 +24,6 @@ import AccessibleButton from "../elements/AccessibleButton";
|
|||
import withValidation, { IValidationResult } from "../elements/Validation";
|
||||
import Field from "../elements/Field";
|
||||
import CountryDropdown from "./CountryDropdown";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import EmailField from "./EmailField";
|
||||
|
||||
// For validating phone numbers without country codes
|
||||
|
@ -66,7 +65,6 @@ enum LoginField {
|
|||
* A pure UI component which displays a username/password form.
|
||||
* The email/username/phone fields are fully-controlled, the password field is not.
|
||||
*/
|
||||
@replaceableComponent("views.auth.PasswordLogin")
|
||||
export default class PasswordLogin extends React.PureComponent<IProps, IState> {
|
||||
static defaultProps = {
|
||||
onUsernameChanged: function() {},
|
||||
|
|
|
@ -31,7 +31,6 @@ import EmailField from "./EmailField";
|
|||
import PassphraseField from "./PassphraseField";
|
||||
import Field from '../elements/Field';
|
||||
import RegistrationEmailPromptDialog from '../dialogs/RegistrationEmailPromptDialog';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import CountryDropdown from "./CountryDropdown";
|
||||
import PassphraseConfirmField from "./PassphraseConfirmField";
|
||||
|
||||
|
@ -92,7 +91,6 @@ interface IState {
|
|||
/*
|
||||
* A pure UI component which displays a registration form.
|
||||
*/
|
||||
@replaceableComponent("views.auth.RegistrationForm")
|
||||
export default class RegistrationForm extends React.PureComponent<IProps, IState> {
|
||||
static defaultProps = {
|
||||
onValidationChange: logger.error,
|
||||
|
|
|
@ -17,14 +17,14 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
import classNames from "classnames";
|
||||
|
||||
import * as sdk from "../../../index";
|
||||
import SdkConfig from '../../../SdkConfig';
|
||||
import AuthPage from "./AuthPage";
|
||||
import { _td } from "../../../languageHandler";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { UIFeature } from "../../../settings/UIFeature";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import LanguageSelector from "./LanguageSelector";
|
||||
import EmbeddedPage from "../../structures/EmbeddedPage";
|
||||
import { MATRIX_LOGO_HTML } from "../../structures/static-page-vars";
|
||||
|
||||
// translatable strings for Welcome pages
|
||||
_td("Sign in with SSO");
|
||||
|
@ -33,12 +33,8 @@ interface IProps {
|
|||
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.Welcome")
|
||||
export default class Welcome extends React.PureComponent<IProps> {
|
||||
public render(): React.ReactNode {
|
||||
// FIXME: Using an import will result in wrench-element-tests failures
|
||||
const EmbeddedPage = sdk.getComponent("structures.EmbeddedPage");
|
||||
|
||||
const pagesConfig = SdkConfig.getObject("embedded_pages");
|
||||
let pageUrl = null;
|
||||
if (pagesConfig) {
|
||||
|
@ -59,6 +55,8 @@ export default class Welcome extends React.PureComponent<IProps> {
|
|||
replaceMap={{
|
||||
"$riot:ssoUrl": "#/start_sso",
|
||||
"$riot:casUrl": "#/start_cas",
|
||||
"$matrixLogo": MATRIX_LOGO_HTML,
|
||||
"[matrix]": MATRIX_LOGO_HTML,
|
||||
}}
|
||||
/>
|
||||
<LanguageSelector />
|
||||
|
|
|
@ -32,7 +32,6 @@ import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
|||
import { _t } from "../../../languageHandler";
|
||||
import TextWithTooltip from "../elements/TextWithTooltip";
|
||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { IOOBData } from "../../../stores/ThreepidInviteStore";
|
||||
import TooltipTarget from "../elements/TooltipTarget";
|
||||
|
||||
|
@ -78,7 +77,6 @@ function tooltipText(variant: Icon) {
|
|||
}
|
||||
}
|
||||
|
||||
@replaceableComponent("views.avatars.DecoratedRoomAvatar")
|
||||
export default class DecoratedRoomAvatar extends React.PureComponent<IProps, IState> {
|
||||
private _dmUser: User;
|
||||
private isUnmounted = false;
|
||||
|
|
|
@ -23,9 +23,8 @@ import { logger } from "matrix-js-sdk/src/logger";
|
|||
import dis from "../../../dispatcher/dispatcher";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import BaseAvatar from "./BaseAvatar";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { mediaFromMxc } from "../../../customisations/Media";
|
||||
import { CardContext } from '../right_panel/BaseCard';
|
||||
import { CardContext } from '../right_panel/context';
|
||||
import UserIdentifierCustomisations from '../../../customisations/UserIdentifier';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
|
@ -52,7 +51,6 @@ interface IState {
|
|||
imageUrl?: string;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.avatars.MemberAvatar")
|
||||
export default class MemberAvatar extends React.PureComponent<IProps, IState> {
|
||||
public static defaultProps = {
|
||||
width: 40,
|
||||
|
|
|
@ -28,7 +28,6 @@ import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
|||
import Modal from '../../../Modal';
|
||||
import * as Avatar from '../../../Avatar';
|
||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { mediaFromMxc } from "../../../customisations/Media";
|
||||
import { IOOBData } from '../../../stores/ThreepidInviteStore';
|
||||
|
||||
|
@ -52,7 +51,6 @@ interface IState {
|
|||
urls: string[];
|
||||
}
|
||||
|
||||
@replaceableComponent("views.avatars.RoomAvatar")
|
||||
export default class RoomAvatar extends React.Component<IProps, IState> {
|
||||
public static defaultProps = {
|
||||
width: 36,
|
||||
|
|
65
src/components/views/beacon/BeaconMarker.tsx
Normal file
65
src/components/views/beacon/BeaconMarker.tsx
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import maplibregl from 'maplibre-gl';
|
||||
import {
|
||||
Beacon,
|
||||
BeaconEvent,
|
||||
} from 'matrix-js-sdk/src/matrix';
|
||||
import { LocationAssetType } from 'matrix-js-sdk/src/@types/location';
|
||||
|
||||
import MatrixClientContext from '../../../contexts/MatrixClientContext';
|
||||
import { useEventEmitterState } from '../../../hooks/useEventEmitter';
|
||||
import SmartMarker from '../location/SmartMarker';
|
||||
|
||||
interface Props {
|
||||
map: maplibregl.Map;
|
||||
beacon: Beacon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a map SmartMarker with latest location from given beacon
|
||||
*/
|
||||
const BeaconMarker: React.FC<Props> = ({ map, beacon }) => {
|
||||
const latestLocationState = useEventEmitterState(
|
||||
beacon,
|
||||
BeaconEvent.LocationUpdate,
|
||||
() => beacon.latestLocationState,
|
||||
);
|
||||
const matrixClient = useContext(MatrixClientContext);
|
||||
const room = matrixClient.getRoom(beacon.roomId);
|
||||
|
||||
if (!latestLocationState || !beacon.isLive) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const geoUri = latestLocationState?.uri;
|
||||
|
||||
const markerRoomMember = beacon.beaconInfo.assetType === LocationAssetType.Self ?
|
||||
room.getMember(beacon.beaconInfoOwner) :
|
||||
undefined;
|
||||
|
||||
return <SmartMarker
|
||||
map={map}
|
||||
id={beacon.identifier}
|
||||
geoUri={geoUri}
|
||||
roomMember={markerRoomMember}
|
||||
useMemberColor
|
||||
/>;
|
||||
};
|
||||
|
||||
export default BeaconMarker;
|
84
src/components/views/beacon/BeaconStatus.tsx
Normal file
84
src/components/views/beacon/BeaconStatus.tsx
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { HTMLProps } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Beacon } from 'matrix-js-sdk/src/matrix';
|
||||
|
||||
import StyledLiveBeaconIcon from './StyledLiveBeaconIcon';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import LiveTimeRemaining from './LiveTimeRemaining';
|
||||
import { BeaconDisplayStatus } from './displayStatus';
|
||||
import { getBeaconExpiryTimestamp } from '../../../utils/beacon';
|
||||
import { formatTime } from '../../../DateUtils';
|
||||
|
||||
interface Props {
|
||||
displayStatus: BeaconDisplayStatus;
|
||||
displayLiveTimeRemaining?: boolean;
|
||||
beacon?: Beacon;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
const BeaconExpiryTime: React.FC<{ beacon: Beacon }> = ({ beacon }) => {
|
||||
const expiryTime = formatTime(new Date(getBeaconExpiryTimestamp(beacon)));
|
||||
return <span className='mx_BeaconStatus_expiryTime'>{ _t('Live until %(expiryTime)s', { expiryTime }) }</span>;
|
||||
};
|
||||
|
||||
const BeaconStatus: React.FC<Props & HTMLProps<HTMLDivElement>> =
|
||||
({
|
||||
beacon,
|
||||
displayStatus,
|
||||
displayLiveTimeRemaining,
|
||||
label,
|
||||
className,
|
||||
children,
|
||||
...rest
|
||||
}) => {
|
||||
const isIdle = displayStatus === BeaconDisplayStatus.Loading ||
|
||||
displayStatus === BeaconDisplayStatus.Stopped;
|
||||
|
||||
return <div
|
||||
{...rest}
|
||||
className={classNames('mx_BeaconStatus', `mx_BeaconStatus_${displayStatus}`, className)}
|
||||
>
|
||||
<StyledLiveBeaconIcon
|
||||
className='mx_BeaconStatus_icon'
|
||||
withError={displayStatus === BeaconDisplayStatus.Error}
|
||||
isIdle={isIdle}
|
||||
/>
|
||||
<div className='mx_BeaconStatus_description'>
|
||||
|
||||
{ displayStatus === BeaconDisplayStatus.Loading && <span>{ _t('Loading live location...') }</span> }
|
||||
{ displayStatus === BeaconDisplayStatus.Stopped && <span>{ _t('Live location ended') }</span> }
|
||||
|
||||
{ displayStatus === BeaconDisplayStatus.Error && <span>{ _t('Live location error') }</span> }
|
||||
|
||||
{ displayStatus === BeaconDisplayStatus.Active && beacon && <>
|
||||
<>
|
||||
{ label }
|
||||
{ displayLiveTimeRemaining ?
|
||||
<LiveTimeRemaining beacon={beacon} /> :
|
||||
<BeaconExpiryTime beacon={beacon} />
|
||||
}
|
||||
</>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
{ children }
|
||||
</div>;
|
||||
};
|
||||
|
||||
export default BeaconStatus;
|
116
src/components/views/beacon/BeaconViewDialog.tsx
Normal file
116
src/components/views/beacon/BeaconViewDialog.tsx
Normal file
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
||||
import {
|
||||
Beacon,
|
||||
Room,
|
||||
} from 'matrix-js-sdk/src/matrix';
|
||||
import maplibregl from 'maplibre-gl';
|
||||
|
||||
import { useLiveBeacons } from '../../../utils/beacon/useLiveBeacons';
|
||||
import MatrixClientContext from '../../../contexts/MatrixClientContext';
|
||||
import BaseDialog from "../dialogs/BaseDialog";
|
||||
import { IDialogProps } from "../dialogs/IDialogProps";
|
||||
import Map from '../location/Map';
|
||||
import ZoomButtons from '../location/ZoomButtons';
|
||||
import BeaconMarker from './BeaconMarker';
|
||||
import { Bounds, getBeaconBounds } from '../../../utils/beacon/bounds';
|
||||
import { getGeoUri } from '../../../utils/beacon';
|
||||
import { Icon as LocationIcon } from '../../../../res/img/element-icons/location.svg';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
|
||||
interface IProps extends IDialogProps {
|
||||
roomId: Room['roomId'];
|
||||
matrixClient: MatrixClient;
|
||||
// open the map centered on this beacon's location
|
||||
focusBeacon?: Beacon;
|
||||
}
|
||||
|
||||
const getBoundsCenter = (bounds: Bounds): string | undefined => {
|
||||
if (!bounds) {
|
||||
return;
|
||||
}
|
||||
return getGeoUri({
|
||||
latitude: (bounds.north + bounds.south) / 2,
|
||||
longitude: (bounds.east + bounds.west) / 2,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Dialog to view live beacons maximised
|
||||
*/
|
||||
const BeaconViewDialog: React.FC<IProps> = ({
|
||||
focusBeacon,
|
||||
roomId,
|
||||
matrixClient,
|
||||
onFinished,
|
||||
}) => {
|
||||
const liveBeacons = useLiveBeacons(roomId, matrixClient);
|
||||
|
||||
const bounds = getBeaconBounds(liveBeacons);
|
||||
const centerGeoUri = focusBeacon?.latestLocationState?.uri || getBoundsCenter(bounds);
|
||||
|
||||
return (
|
||||
<BaseDialog
|
||||
className='mx_BeaconViewDialog'
|
||||
onFinished={onFinished}
|
||||
fixedWidth={false}
|
||||
>
|
||||
<MatrixClientContext.Provider value={matrixClient}>
|
||||
{ !!bounds ? <Map
|
||||
id='mx_BeaconViewDialog'
|
||||
bounds={bounds}
|
||||
centerGeoUri={centerGeoUri}
|
||||
interactive
|
||||
className="mx_BeaconViewDialog_map"
|
||||
>
|
||||
{
|
||||
({ map }: { map: maplibregl.Map}) =>
|
||||
<>
|
||||
{ liveBeacons.map(beacon => <BeaconMarker
|
||||
key={beacon.identifier}
|
||||
map={map}
|
||||
beacon={beacon}
|
||||
/>) }
|
||||
<ZoomButtons map={map} />
|
||||
</>
|
||||
}
|
||||
</Map> :
|
||||
<div
|
||||
data-test-id='beacon-view-dialog-map-fallback'
|
||||
className='mx_BeaconViewDialog_map mx_BeaconViewDialog_mapFallback'
|
||||
>
|
||||
<LocationIcon className='mx_BeaconViewDialog_mapFallbackIcon' />
|
||||
<span className='mx_BeaconViewDialog_mapFallbackMessage'>{ _t('No live locations') }</span>
|
||||
<AccessibleButton
|
||||
kind='primary'
|
||||
onClick={onFinished}
|
||||
data-test-id='beacon-view-dialog-fallback-close'
|
||||
>
|
||||
{ _t('Close') }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
}
|
||||
</MatrixClientContext.Provider>
|
||||
</BaseDialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default BeaconViewDialog;
|
75
src/components/views/beacon/LiveTimeRemaining.tsx
Normal file
75
src/components/views/beacon/LiveTimeRemaining.tsx
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { BeaconEvent, Beacon } from 'matrix-js-sdk/src/matrix';
|
||||
|
||||
import { formatDuration } from '../../../DateUtils';
|
||||
import { useEventEmitterState } from '../../../hooks/useEventEmitter';
|
||||
import { useInterval } from '../../../hooks/useTimeout';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { getBeaconMsUntilExpiry } from '../../../utils/beacon';
|
||||
|
||||
const MINUTE_MS = 60000;
|
||||
const HOUR_MS = MINUTE_MS * 60;
|
||||
const getUpdateInterval = (ms: number) => {
|
||||
// every 10 mins when more than an hour
|
||||
if (ms > HOUR_MS) {
|
||||
return MINUTE_MS * 10;
|
||||
}
|
||||
// every minute when more than a minute
|
||||
if (ms > MINUTE_MS) {
|
||||
return MINUTE_MS;
|
||||
}
|
||||
// otherwise every second
|
||||
return 1000;
|
||||
};
|
||||
const useMsRemaining = (beacon: Beacon): number => {
|
||||
const beaconInfo = useEventEmitterState(
|
||||
beacon,
|
||||
BeaconEvent.Update,
|
||||
() => beacon.beaconInfo,
|
||||
);
|
||||
|
||||
const [msRemaining, setMsRemaining] = useState(() => getBeaconMsUntilExpiry(beaconInfo));
|
||||
|
||||
useEffect(() => {
|
||||
setMsRemaining(getBeaconMsUntilExpiry(beaconInfo));
|
||||
}, [beaconInfo]);
|
||||
|
||||
const updateMsRemaining = useCallback(() => {
|
||||
const ms = getBeaconMsUntilExpiry(beaconInfo);
|
||||
setMsRemaining(ms);
|
||||
}, [beaconInfo]);
|
||||
|
||||
useInterval(updateMsRemaining, getUpdateInterval(msRemaining));
|
||||
|
||||
return msRemaining;
|
||||
};
|
||||
|
||||
const LiveTimeRemaining: React.FC<{ beacon: Beacon }> = ({ beacon }) => {
|
||||
const msRemaining = useMsRemaining(beacon);
|
||||
|
||||
const timeRemaining = formatDuration(msRemaining);
|
||||
const liveTimeRemaining = _t(`%(timeRemaining)s left`, { timeRemaining });
|
||||
|
||||
return <span
|
||||
data-test-id='room-live-share-expiry'
|
||||
className="mx_LiveTimeRemaining"
|
||||
>{ liveTimeRemaining }</span>;
|
||||
};
|
||||
|
||||
export default LiveTimeRemaining;
|
89
src/components/views/beacon/OwnBeaconStatus.tsx
Normal file
89
src/components/views/beacon/OwnBeaconStatus.tsx
Normal file
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { Beacon } from 'matrix-js-sdk/src/matrix';
|
||||
import React, { HTMLProps } from 'react';
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { useOwnLiveBeacons } from '../../../utils/beacon';
|
||||
import BeaconStatus from './BeaconStatus';
|
||||
import { BeaconDisplayStatus } from './displayStatus';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
|
||||
interface Props {
|
||||
displayStatus: BeaconDisplayStatus;
|
||||
beacon?: Beacon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps BeaconStatus with more capabilities
|
||||
* for errors and actions available for users own live beacons
|
||||
*/
|
||||
const OwnBeaconStatus: React.FC<Props & HTMLProps<HTMLDivElement>> = ({
|
||||
beacon, displayStatus, className, ...rest
|
||||
}) => {
|
||||
const {
|
||||
hasWireError,
|
||||
hasStopSharingError,
|
||||
stoppingInProgress,
|
||||
onStopSharing,
|
||||
onResetWireError,
|
||||
} = useOwnLiveBeacons([beacon?.identifier]);
|
||||
|
||||
// combine display status with errors that only occur for user's own beacons
|
||||
const ownDisplayStatus = hasWireError || hasStopSharingError ?
|
||||
BeaconDisplayStatus.Error :
|
||||
displayStatus;
|
||||
|
||||
return <BeaconStatus
|
||||
className='mx_MBeaconBody_chin'
|
||||
beacon={beacon}
|
||||
displayStatus={ownDisplayStatus}
|
||||
label={_t('Live location enabled')}
|
||||
displayLiveTimeRemaining
|
||||
{...rest}
|
||||
>
|
||||
{ ownDisplayStatus === BeaconDisplayStatus.Active && <AccessibleButton
|
||||
data-test-id='beacon-status-stop-beacon'
|
||||
kind='link'
|
||||
onClick={onStopSharing}
|
||||
className='mx_OwnBeaconStatus_button mx_OwnBeaconStatus_destructiveButton'
|
||||
disabled={stoppingInProgress}
|
||||
>
|
||||
{ _t('Stop') }
|
||||
</AccessibleButton>
|
||||
}
|
||||
{ hasWireError && <AccessibleButton
|
||||
data-test-id='beacon-status-reset-wire-error'
|
||||
kind='link'
|
||||
onClick={onResetWireError}
|
||||
className='mx_OwnBeaconStatus_button mx_OwnBeaconStatus_destructiveButton'
|
||||
>
|
||||
{ _t('Retry') }
|
||||
</AccessibleButton>
|
||||
}
|
||||
{ hasStopSharingError && <AccessibleButton
|
||||
data-test-id='beacon-status-stop-beacon-retry'
|
||||
kind='link'
|
||||
onClick={onStopSharing}
|
||||
className='mx_OwnBeaconStatus_button mx_OwnBeaconStatus_destructiveButton'
|
||||
>
|
||||
{ _t('Retry') }
|
||||
</AccessibleButton> }
|
||||
</BeaconStatus>;
|
||||
};
|
||||
|
||||
export default OwnBeaconStatus;
|
|
@ -14,126 +14,19 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Room, Beacon } from 'matrix-js-sdk/src/matrix';
|
||||
import { Room } from 'matrix-js-sdk/src/matrix';
|
||||
|
||||
import { formatDuration } from '../../../DateUtils';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { useEventEmitterState } from '../../../hooks/useEventEmitter';
|
||||
import { useInterval } from '../../../hooks/useTimeout';
|
||||
import { OwnBeaconStore, OwnBeaconStoreEvent } from '../../../stores/OwnBeaconStore';
|
||||
import { getBeaconMsUntilExpiry, sortBeaconsByLatestExpiry } from '../../../utils/beacon';
|
||||
import { useOwnLiveBeacons } from '../../../utils/beacon';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import Spinner from '../elements/Spinner';
|
||||
import StyledLiveBeaconIcon from './StyledLiveBeaconIcon';
|
||||
import { Icon as CloseIcon } from '../../../../res/img/image-view/close.svg';
|
||||
|
||||
const MINUTE_MS = 60000;
|
||||
const HOUR_MS = MINUTE_MS * 60;
|
||||
|
||||
const getUpdateInterval = (ms: number) => {
|
||||
// every 10 mins when more than an hour
|
||||
if (ms > HOUR_MS) {
|
||||
return MINUTE_MS * 10;
|
||||
}
|
||||
// every minute when more than a minute
|
||||
if (ms > MINUTE_MS) {
|
||||
return MINUTE_MS;
|
||||
}
|
||||
// otherwise every second
|
||||
return 1000;
|
||||
};
|
||||
const useMsRemaining = (beacon: Beacon): number => {
|
||||
const [msRemaining, setMsRemaining] = useState(() => getBeaconMsUntilExpiry(beacon));
|
||||
|
||||
useEffect(() => {
|
||||
setMsRemaining(getBeaconMsUntilExpiry(beacon));
|
||||
}, [beacon]);
|
||||
|
||||
const updateMsRemaining = useCallback(() => {
|
||||
const ms = getBeaconMsUntilExpiry(beacon);
|
||||
setMsRemaining(ms);
|
||||
}, [beacon]);
|
||||
|
||||
useInterval(updateMsRemaining, getUpdateInterval(msRemaining));
|
||||
|
||||
return msRemaining;
|
||||
};
|
||||
|
||||
/**
|
||||
* It's technically possible to have multiple live beacons in one room
|
||||
* Select the latest expiry to display,
|
||||
* and kill all beacons on stop sharing
|
||||
*/
|
||||
type LiveBeaconsState = {
|
||||
beacon?: Beacon;
|
||||
onStopSharing?: () => void;
|
||||
onResetWireError?: () => void;
|
||||
stoppingInProgress?: boolean;
|
||||
hasStopSharingError?: boolean;
|
||||
hasWireError?: boolean;
|
||||
};
|
||||
const useLiveBeacons = (liveBeaconIds: string[], roomId: string): LiveBeaconsState => {
|
||||
const [stoppingInProgress, setStoppingInProgress] = useState(false);
|
||||
const [error, setError] = useState<Error>();
|
||||
|
||||
const hasWireError = useEventEmitterState(
|
||||
OwnBeaconStore.instance,
|
||||
OwnBeaconStoreEvent.WireError,
|
||||
() =>
|
||||
OwnBeaconStore.instance.hasWireErrors(roomId),
|
||||
);
|
||||
|
||||
// reset stopping in progress on change in live ids
|
||||
useEffect(() => {
|
||||
setStoppingInProgress(false);
|
||||
setError(undefined);
|
||||
}, [liveBeaconIds]);
|
||||
|
||||
// select the beacon with latest expiry to display expiry time
|
||||
const beacon = liveBeaconIds.map(beaconId => OwnBeaconStore.instance.getBeaconById(beaconId))
|
||||
.sort(sortBeaconsByLatestExpiry)
|
||||
.shift();
|
||||
|
||||
const onStopSharing = async () => {
|
||||
setStoppingInProgress(true);
|
||||
try {
|
||||
await Promise.all(liveBeaconIds.map(beaconId => OwnBeaconStore.instance.stopBeacon(beaconId)));
|
||||
} catch (error) {
|
||||
// only clear loading in case of error
|
||||
// to avoid flash of not-loading state
|
||||
// after beacons have been stopped but we wait for sync
|
||||
setError(error);
|
||||
setStoppingInProgress(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onResetWireError = () => {
|
||||
liveBeaconIds.map(beaconId => OwnBeaconStore.instance.resetWireError(beaconId));
|
||||
};
|
||||
|
||||
return {
|
||||
onStopSharing,
|
||||
onResetWireError,
|
||||
beacon,
|
||||
stoppingInProgress,
|
||||
hasWireError,
|
||||
hasStopSharingError: !!error,
|
||||
};
|
||||
};
|
||||
|
||||
const LiveTimeRemaining: React.FC<{ beacon: Beacon }> = ({ beacon }) => {
|
||||
const msRemaining = useMsRemaining(beacon);
|
||||
|
||||
const timeRemaining = formatDuration(msRemaining);
|
||||
const liveTimeRemaining = _t(`%(timeRemaining)s left`, { timeRemaining });
|
||||
|
||||
return <span
|
||||
data-test-id='room-live-share-expiry'
|
||||
className="mx_RoomLiveShareWarning_expiry"
|
||||
>{ liveTimeRemaining }</span>;
|
||||
};
|
||||
import LiveTimeRemaining from './LiveTimeRemaining';
|
||||
|
||||
const getLabel = (hasWireError: boolean, hasStopSharingError: boolean): string => {
|
||||
if (hasWireError) {
|
||||
|
@ -157,7 +50,7 @@ const RoomLiveShareWarningInner: React.FC<RoomLiveShareWarningInnerProps> = ({ l
|
|||
stoppingInProgress,
|
||||
hasStopSharingError,
|
||||
hasWireError,
|
||||
} = useLiveBeacons(liveBeaconIds, roomId);
|
||||
} = useOwnLiveBeacons(liveBeaconIds);
|
||||
|
||||
if (!beacon) {
|
||||
return null;
|
||||
|
@ -188,6 +81,7 @@ const RoomLiveShareWarningInner: React.FC<RoomLiveShareWarningInnerProps> = ({ l
|
|||
{ !stoppingInProgress && !hasError && <LiveTimeRemaining beacon={beacon} /> }
|
||||
|
||||
<AccessibleButton
|
||||
className='mx_RoomLiveShareWarning_stopButton'
|
||||
data-test-id='room-live-share-primary-button'
|
||||
onClick={onButtonClick}
|
||||
kind='danger'
|
||||
|
|
|
@ -22,11 +22,19 @@ import { Icon as LiveLocationIcon } from '../../../../res/img/location/live-loca
|
|||
interface Props extends React.SVGProps<SVGSVGElement> {
|
||||
// use error styling when true
|
||||
withError?: boolean;
|
||||
isIdle?: boolean;
|
||||
}
|
||||
const StyledLiveBeaconIcon: React.FC<Props> = ({ className, withError, ...props }) =>
|
||||
const StyledLiveBeaconIcon: React.FC<Props> = ({ className, withError, isIdle, ...props }) =>
|
||||
<LiveLocationIcon
|
||||
{...props}
|
||||
className={classNames('mx_StyledLiveBeaconIcon', className, { 'mx_StyledLiveBeaconIcon_error': withError })}
|
||||
className={classNames(
|
||||
'mx_StyledLiveBeaconIcon',
|
||||
className,
|
||||
{
|
||||
'mx_StyledLiveBeaconIcon_error': withError,
|
||||
'mx_StyledLiveBeaconIcon_idle': isIdle,
|
||||
|
||||
})}
|
||||
/>;
|
||||
|
||||
export default StyledLiveBeaconIcon;
|
||||
|
|
42
src/components/views/beacon/displayStatus.ts
Normal file
42
src/components/views/beacon/displayStatus.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { BeaconLocationState } from "matrix-js-sdk/src/content-helpers";
|
||||
|
||||
export enum BeaconDisplayStatus {
|
||||
Loading = 'Loading',
|
||||
Error = 'Error',
|
||||
Stopped = 'Stopped',
|
||||
Active = 'Active',
|
||||
}
|
||||
export const getBeaconDisplayStatus = (
|
||||
isLive: boolean,
|
||||
latestLocationState?: BeaconLocationState,
|
||||
error?: Error): BeaconDisplayStatus => {
|
||||
if (error) {
|
||||
return BeaconDisplayStatus.Error;
|
||||
}
|
||||
if (!isLive) {
|
||||
return BeaconDisplayStatus.Stopped;
|
||||
}
|
||||
|
||||
if (!latestLocationState) {
|
||||
return BeaconDisplayStatus.Loading;
|
||||
}
|
||||
if (latestLocationState) {
|
||||
return BeaconDisplayStatus.Active;
|
||||
}
|
||||
};
|
|
@ -21,13 +21,11 @@ import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import ContextMenu, { IProps as IContextMenuProps, MenuItem } from '../../structures/ContextMenu';
|
||||
import CallHandler from '../../../CallHandler';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps extends IContextMenuProps {
|
||||
call: MatrixCall;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.context_menus.CallContextMenu")
|
||||
export default class CallContextMenu extends React.Component<IProps> {
|
||||
static propTypes = {
|
||||
// js-sdk User object. Not required because it might not exist.
|
||||
|
|
|
@ -22,7 +22,6 @@ import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
|
|||
import ContextMenu, { IProps as IContextMenuProps } from '../../structures/ContextMenu';
|
||||
import Field from "../elements/Field";
|
||||
import DialPad from '../voip/DialPad';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps extends IContextMenuProps {
|
||||
call: MatrixCall;
|
||||
|
@ -32,7 +31,6 @@ interface IState {
|
|||
value: string;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.context_menus.DialpadContextMenu")
|
||||
export default class DialpadContextMenu extends React.Component<IProps, IState> {
|
||||
private numberEntryFieldRef: React.RefObject<Field> = createRef();
|
||||
|
||||
|
|
|
@ -16,8 +16,6 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps {
|
||||
element: React.ReactNode;
|
||||
// Function to be called when the parent window is resized
|
||||
|
@ -30,7 +28,6 @@ interface IProps {
|
|||
* This component can be used to display generic HTML content in a contextual
|
||||
* menu.
|
||||
*/
|
||||
@replaceableComponent("views.context_menus.GenericElementContextMenu")
|
||||
export default class GenericElementContextMenu extends React.Component<IProps> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
|
|
@ -16,13 +16,10 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps {
|
||||
message: string;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.context_menus.GenericTextContextMenu")
|
||||
export default class GenericTextContextMenu extends React.Component<IProps> {
|
||||
public render(): JSX.Element {
|
||||
return <div className="mx_Tooltip mx_Tooltip_visible" style={{ display: "block" }}>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||
Copyright 2015 - 2021 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2021 - 2022 Šimon Brandner <simon.bra.ag@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -15,12 +16,11 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ReactElement } from 'react';
|
||||
import React, { createRef } from 'react';
|
||||
import { EventStatus, MatrixEvent } from 'matrix-js-sdk/src/models/event';
|
||||
import { EventType, RelationType } from "matrix-js-sdk/src/@types/event";
|
||||
import { Relations } from 'matrix-js-sdk/src/models/relations';
|
||||
import { RoomMemberEvent } from "matrix-js-sdk/src/models/room-member";
|
||||
import { M_LOCATION } from 'matrix-js-sdk/src/@types/location';
|
||||
import { M_POLL_START } from "matrix-events-sdk";
|
||||
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
|
@ -30,79 +30,81 @@ import Modal from '../../../Modal';
|
|||
import Resend from '../../../Resend';
|
||||
import SettingsStore from '../../../settings/SettingsStore';
|
||||
import { isUrlPermitted } from '../../../HtmlUtils';
|
||||
import { isContentActionable } from '../../../utils/EventUtils';
|
||||
import { canEditContent, canForward, editEvent, isContentActionable, isLocationEvent } from '../../../utils/EventUtils';
|
||||
import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from './IconizedContextMenu';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { ReadPinsEventId } from "../right_panel/PinnedMessagesCard";
|
||||
import ForwardDialog from "../dialogs/ForwardDialog";
|
||||
import { ReadPinsEventId } from "../right_panel/types";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import ReportEventDialog from '../dialogs/ReportEventDialog';
|
||||
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
|
||||
import { ButtonEvent } from '../elements/AccessibleButton';
|
||||
import { copyPlaintext, getSelectedText } from '../../../utils/strings';
|
||||
import ContextMenu, { toRightOf } from '../../structures/ContextMenu';
|
||||
import ReactionPicker from '../emojipicker/ReactionPicker';
|
||||
import ViewSource from '../../structures/ViewSource';
|
||||
import { createRedactEventDialog } from '../dialogs/ConfirmRedactDialog';
|
||||
import ShareDialog from '../dialogs/ShareDialog';
|
||||
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
|
||||
import { ChevronFace, IPosition } from '../../structures/ContextMenu';
|
||||
import { IPosition, ChevronFace } from '../../structures/ContextMenu';
|
||||
import RoomContext, { TimelineRenderingType } from '../../../contexts/RoomContext';
|
||||
import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
|
||||
import EndPollDialog from '../dialogs/EndPollDialog';
|
||||
import { isPollEnded } from '../messages/MPollBody';
|
||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { GetRelationsForEvent, IEventTileOps } from "../rooms/EventTile";
|
||||
import { OpenForwardDialogPayload } from "../../../dispatcher/payloads/OpenForwardDialogPayload";
|
||||
import { OpenReportEventDialogPayload } from "../../../dispatcher/payloads/OpenReportEventDialogPayload";
|
||||
import { createMapSiteLink } from '../../../utils/location';
|
||||
|
||||
export function canCancel(status: EventStatus): boolean {
|
||||
return status === EventStatus.QUEUED || status === EventStatus.NOT_SENT || status === EventStatus.ENCRYPTING;
|
||||
}
|
||||
|
||||
export interface IEventTileOps {
|
||||
isWidgetHidden(): boolean;
|
||||
unhideWidget(): void;
|
||||
}
|
||||
|
||||
export interface IOperableEventTile {
|
||||
getEventTileOps(): IEventTileOps;
|
||||
}
|
||||
|
||||
interface IProps extends IPosition {
|
||||
chevronFace: ChevronFace;
|
||||
/* the MatrixEvent associated with the context menu */
|
||||
mxEvent: MatrixEvent;
|
||||
/* an optional EventTileOps implementation that can be used to unhide preview widgets */
|
||||
// An optional EventTileOps implementation that can be used to unhide preview widgets
|
||||
eventTileOps?: IEventTileOps;
|
||||
// Callback called when the menu is dismissed
|
||||
permalinkCreator?: RoomPermalinkCreator;
|
||||
/* an optional function to be called when the user clicks collapse thread, if not provided hide button */
|
||||
collapseReplyChain?(): void;
|
||||
/* callback called when the menu is dismissed */
|
||||
onFinished(): void;
|
||||
/* if the menu is inside a dialog, we sometimes need to close that dialog after click (forwarding) */
|
||||
// If the menu is inside a dialog, we sometimes need to close that dialog after click (forwarding)
|
||||
onCloseDialog?(): void;
|
||||
getRelationsForEvent?: (
|
||||
eventId: string,
|
||||
relationType: string,
|
||||
eventType: string
|
||||
) => Relations;
|
||||
// True if the menu is being used as a right click menu
|
||||
rightClick?: boolean;
|
||||
// The Relations model from the JS SDK for reactions to `mxEvent`
|
||||
reactions?: Relations;
|
||||
// A permalink to the event
|
||||
showPermalink?: boolean;
|
||||
|
||||
getRelationsForEvent?: GetRelationsForEvent;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
canRedact: boolean;
|
||||
canPin: boolean;
|
||||
reactionPickerDisplayed: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.context_menus.MessageContextMenu")
|
||||
export default class MessageContextMenu extends React.Component<IProps, IState> {
|
||||
static contextType = RoomContext;
|
||||
public context!: React.ContextType<typeof RoomContext>;
|
||||
|
||||
state = {
|
||||
canRedact: false,
|
||||
canPin: false,
|
||||
};
|
||||
private reactButtonRef = createRef<any>(); // XXX Ref to a functional component
|
||||
|
||||
componentDidMount() {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
canRedact: false,
|
||||
canPin: false,
|
||||
reactionPickerDisplayed: false,
|
||||
};
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
MatrixClientPeg.get().on(RoomMemberEvent.PowerLevel, this.checkPermissions);
|
||||
this.checkPermissions();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
public componentWillUnmount(): void {
|
||||
const cli = MatrixClientPeg.get();
|
||||
if (cli) {
|
||||
cli.removeListener(RoomMemberEvent.PowerLevel, this.checkPermissions);
|
||||
|
@ -155,9 +157,10 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
};
|
||||
|
||||
private onReportEventClick = (): void => {
|
||||
Modal.createTrackedDialog('Report Event', '', ReportEventDialog, {
|
||||
mxEvent: this.props.mxEvent,
|
||||
}, 'mx_Dialog_reportEvent');
|
||||
dis.dispatch<OpenReportEventDialogPayload>({
|
||||
action: Action.OpenReportEventDialog,
|
||||
event: this.props.mxEvent,
|
||||
});
|
||||
this.closeMenu();
|
||||
};
|
||||
|
||||
|
@ -178,8 +181,8 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
};
|
||||
|
||||
private onForwardClick = (): void => {
|
||||
Modal.createTrackedDialog('Forward Message', '', ForwardDialog, {
|
||||
matrixClient: MatrixClientPeg.get(),
|
||||
dis.dispatch<OpenForwardDialogPayload>({
|
||||
action: Action.OpenForwardDialog,
|
||||
event: this.props.mxEvent,
|
||||
permalinkCreator: this.props.permalinkCreator,
|
||||
});
|
||||
|
@ -234,11 +237,45 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
this.closeMenu();
|
||||
};
|
||||
|
||||
private onCopyPermalinkClick = (e: ButtonEvent): void => {
|
||||
e.preventDefault(); // So that we don't open the permalink
|
||||
copyPlaintext(this.getPermalink());
|
||||
this.closeMenu();
|
||||
};
|
||||
|
||||
private onCollapseReplyChainClick = (): void => {
|
||||
this.props.collapseReplyChain();
|
||||
this.closeMenu();
|
||||
};
|
||||
|
||||
private onCopyClick = (): void => {
|
||||
copyPlaintext(getSelectedText());
|
||||
this.closeMenu();
|
||||
};
|
||||
|
||||
private onEditClick = (): void => {
|
||||
editEvent(this.props.mxEvent, this.context.timelineRenderingType, this.props.getRelationsForEvent);
|
||||
this.closeMenu();
|
||||
};
|
||||
|
||||
private onReplyClick = (): void => {
|
||||
dis.dispatch({
|
||||
action: 'reply_to_event',
|
||||
event: this.props.mxEvent,
|
||||
context: this.context.timelineRenderingType,
|
||||
});
|
||||
this.closeMenu();
|
||||
};
|
||||
|
||||
private onReactClick = (): void => {
|
||||
this.setState({ reactionPickerDisplayed: true });
|
||||
};
|
||||
|
||||
private onCloseReactionPicker = (): void => {
|
||||
this.setState({ reactionPickerDisplayed: false });
|
||||
this.closeMenu();
|
||||
};
|
||||
|
||||
private onEndPollClick = (): void => {
|
||||
const matrixClient = MatrixClientPeg.get();
|
||||
Modal.createTrackedDialog('End Poll', '', EndPollDialog, {
|
||||
|
@ -259,11 +296,16 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
});
|
||||
}
|
||||
|
||||
private getPermalink(): string {
|
||||
if (!this.props.permalinkCreator) return;
|
||||
return this.props.permalinkCreator.forEvent(this.props.mxEvent.getId());
|
||||
}
|
||||
|
||||
private getUnsentReactions(): MatrixEvent[] {
|
||||
return this.getReactions(e => e.status === EventStatus.NOT_SENT);
|
||||
}
|
||||
|
||||
private viewInRoom = () => {
|
||||
private viewInRoom = (): void => {
|
||||
dis.dispatch<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
event_id: this.props.mxEvent.getId(),
|
||||
|
@ -274,39 +316,35 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
this.closeMenu();
|
||||
};
|
||||
|
||||
render() {
|
||||
public render(): JSX.Element {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const me = cli.getUserId();
|
||||
const mxEvent = this.props.mxEvent;
|
||||
const { mxEvent, rightClick, showPermalink, eventTileOps, reactions, collapseReplyChain } = this.props;
|
||||
const eventStatus = mxEvent.status;
|
||||
const unsentReactionsCount = this.getUnsentReactions().length;
|
||||
|
||||
let openInMapSiteButton: JSX.Element;
|
||||
let endPollButton: JSX.Element;
|
||||
let resendReactionsButton: JSX.Element;
|
||||
let redactButton: JSX.Element;
|
||||
let forwardButton: JSX.Element;
|
||||
let pinButton: JSX.Element;
|
||||
let unhidePreviewButton: JSX.Element;
|
||||
let externalURLButton: JSX.Element;
|
||||
let quoteButton: JSX.Element;
|
||||
let collapseReplyChain: JSX.Element;
|
||||
let redactItemList: JSX.Element;
|
||||
|
||||
const contentActionable = isContentActionable(mxEvent);
|
||||
const permalink = this.getPermalink();
|
||||
// status is SENT before remote-echo, null after
|
||||
const isSent = !eventStatus || eventStatus === EventStatus.SENT;
|
||||
if (!mxEvent.isRedacted()) {
|
||||
if (unsentReactionsCount !== 0) {
|
||||
resendReactionsButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconResend"
|
||||
label={_t('Resend %(unsentCount)s reaction(s)', { unsentCount: unsentReactionsCount })}
|
||||
onClick={this.onResendReactionsClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const { timelineRenderingType, canReact, canSendMessages } = this.context;
|
||||
const isThread = (
|
||||
timelineRenderingType === TimelineRenderingType.Thread ||
|
||||
timelineRenderingType === TimelineRenderingType.ThreadsList
|
||||
);
|
||||
const isThreadRootEvent = isThread && mxEvent?.getThread()?.rootEvent === mxEvent;
|
||||
|
||||
let resendReactionsButton: JSX.Element;
|
||||
if (!mxEvent.isRedacted() && unsentReactionsCount !== 0) {
|
||||
resendReactionsButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconResend"
|
||||
label={_t('Resend %(unsentCount)s reaction(s)', { unsentCount: unsentReactionsCount })}
|
||||
onClick={this.onResendReactionsClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let redactButton: JSX.Element;
|
||||
if (isSent && this.state.canRedact) {
|
||||
redactButton = (
|
||||
<IconizedContextMenuOption
|
||||
|
@ -317,6 +355,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
);
|
||||
}
|
||||
|
||||
let openInMapSiteButton: JSX.Element;
|
||||
if (this.canOpenInMapSite(mxEvent)) {
|
||||
const mapSiteLink = createMapSiteLink(mxEvent);
|
||||
openInMapSiteButton = (
|
||||
|
@ -336,26 +375,26 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
);
|
||||
}
|
||||
|
||||
if (isContentActionable(mxEvent)) {
|
||||
if (canForward(mxEvent)) {
|
||||
forwardButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconForward"
|
||||
label={_t("Forward")}
|
||||
onClick={this.onForwardClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
let forwardButton: JSX.Element;
|
||||
if (contentActionable && canForward(mxEvent)) {
|
||||
forwardButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconForward"
|
||||
label={_t("Forward")}
|
||||
onClick={this.onForwardClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.canPin) {
|
||||
pinButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconPin"
|
||||
label={this.isPinned() ? _t('Unpin') : _t('Pin')}
|
||||
onClick={this.onPinClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
let pinButton: JSX.Element;
|
||||
if (contentActionable && this.state.canPin) {
|
||||
pinButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconPin"
|
||||
label={this.isPinned() ? _t('Unpin') : _t('Pin')}
|
||||
onClick={this.onPinClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let viewSourceButton: JSX.Element;
|
||||
|
@ -369,40 +408,42 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
);
|
||||
}
|
||||
|
||||
if (this.props.eventTileOps) {
|
||||
if (this.props.eventTileOps.isWidgetHidden()) {
|
||||
unhidePreviewButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconUnhidePreview"
|
||||
label={_t("Show preview")}
|
||||
onClick={this.onUnhidePreviewClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
let unhidePreviewButton: JSX.Element;
|
||||
if (eventTileOps?.isWidgetHidden()) {
|
||||
unhidePreviewButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconUnhidePreview"
|
||||
label={_t("Show preview")}
|
||||
onClick={this.onUnhidePreviewClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let permalink: string | null = null;
|
||||
let permalinkButton: ReactElement | null = null;
|
||||
if (this.props.permalinkCreator) {
|
||||
permalink = this.props.permalinkCreator.forEvent(this.props.mxEvent.getId());
|
||||
}
|
||||
permalinkButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconPermalink"
|
||||
onClick={this.onPermalinkClick}
|
||||
label={_t('Share')}
|
||||
element="a"
|
||||
{
|
||||
// XXX: Typescript signature for AccessibleButton doesn't work properly for non-inputs like `a`
|
||||
...{
|
||||
href: permalink,
|
||||
target: "_blank",
|
||||
rel: "noreferrer noopener",
|
||||
let permalinkButton: JSX.Element;
|
||||
if (permalink) {
|
||||
permalinkButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName={showPermalink
|
||||
? "mx_MessageContextMenu_iconCopy"
|
||||
: "mx_MessageContextMenu_iconPermalink"
|
||||
}
|
||||
}
|
||||
/>
|
||||
);
|
||||
onClick={showPermalink ? this.onCopyPermalinkClick : this.onPermalinkClick}
|
||||
label={showPermalink ? _t('Copy link') : _t('Share')}
|
||||
element="a"
|
||||
{
|
||||
// XXX: Typescript signature for AccessibleButton doesn't work properly for non-inputs like `a`
|
||||
...{
|
||||
|
||||
href: permalink,
|
||||
target: "_blank",
|
||||
rel: "noreferrer noopener",
|
||||
}
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let endPollButton: JSX.Element;
|
||||
if (this.canEndPoll(mxEvent)) {
|
||||
endPollButton = (
|
||||
<IconizedContextMenuOption
|
||||
|
@ -413,7 +454,8 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
);
|
||||
}
|
||||
|
||||
if (this.props.eventTileOps) { // this event is rendered using TextualBody
|
||||
let quoteButton: JSX.Element;
|
||||
if (eventTileOps) { // this event is rendered using TextualBody
|
||||
quoteButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconQuote"
|
||||
|
@ -424,7 +466,9 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
}
|
||||
|
||||
// Bridges can provide a 'external_url' to link back to the source.
|
||||
if (typeof (mxEvent.getContent().external_url) === "string" &&
|
||||
let externalURLButton: JSX.Element;
|
||||
if (
|
||||
typeof (mxEvent.getContent().external_url) === "string" &&
|
||||
isUrlPermitted(mxEvent.getContent().external_url)
|
||||
) {
|
||||
externalURLButton = (
|
||||
|
@ -445,8 +489,9 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
);
|
||||
}
|
||||
|
||||
if (this.props.collapseReplyChain) {
|
||||
collapseReplyChain = (
|
||||
let collapseReplyChainButton: JSX.Element;
|
||||
if (collapseReplyChain) {
|
||||
collapseReplyChainButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconCollapse"
|
||||
label={_t("Collapse reply thread")}
|
||||
|
@ -466,20 +511,86 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
);
|
||||
}
|
||||
|
||||
const { timelineRenderingType } = this.context;
|
||||
const isThread = (
|
||||
timelineRenderingType === TimelineRenderingType.Thread ||
|
||||
timelineRenderingType === TimelineRenderingType.ThreadsList
|
||||
);
|
||||
const isThreadRootEvent = isThread && this.props.mxEvent.isThreadRoot;
|
||||
let copyButton: JSX.Element;
|
||||
if (rightClick && getSelectedText()) {
|
||||
copyButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconCopy"
|
||||
label={_t("Copy")}
|
||||
triggerOnMouseDown={true} // We use onMouseDown so that the selection isn't cleared when we click
|
||||
onClick={this.onCopyClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const commonItemsList = (
|
||||
<IconizedContextMenuOptionList>
|
||||
{ isThreadRootEvent && <IconizedContextMenuOption
|
||||
let editButton: JSX.Element;
|
||||
if (rightClick && canEditContent(mxEvent)) {
|
||||
editButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconEdit"
|
||||
label={_t("Edit")}
|
||||
onClick={this.onEditClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let replyButton: JSX.Element;
|
||||
if (rightClick && contentActionable && canSendMessages) {
|
||||
replyButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconReply"
|
||||
label={_t("Reply")}
|
||||
onClick={this.onReplyClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let reactButton;
|
||||
if (rightClick && contentActionable && canReact) {
|
||||
reactButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconReact"
|
||||
label={_t("React")}
|
||||
onClick={this.onReactClick}
|
||||
inputRef={this.reactButtonRef}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let viewInRoomButton: JSX.Element;
|
||||
if (isThreadRootEvent) {
|
||||
viewInRoomButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconViewInRoom"
|
||||
label={_t("View in room")}
|
||||
onClick={this.viewInRoom}
|
||||
/> }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let nativeItemsList: JSX.Element;
|
||||
if (copyButton) {
|
||||
nativeItemsList = (
|
||||
<IconizedContextMenuOptionList>
|
||||
{ copyButton }
|
||||
</IconizedContextMenuOptionList>
|
||||
);
|
||||
}
|
||||
|
||||
let quickItemsList: JSX.Element;
|
||||
if (editButton || replyButton || reactButton) {
|
||||
quickItemsList = (
|
||||
<IconizedContextMenuOptionList>
|
||||
{ reactButton }
|
||||
{ replyButton }
|
||||
{ editButton }
|
||||
</IconizedContextMenuOptionList>
|
||||
);
|
||||
}
|
||||
|
||||
const commonItemsList = (
|
||||
<IconizedContextMenuOptionList>
|
||||
{ viewInRoomButton }
|
||||
{ openInMapSiteButton }
|
||||
{ endPollButton }
|
||||
{ quoteButton }
|
||||
|
@ -491,10 +602,11 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
{ unhidePreviewButton }
|
||||
{ viewSourceButton }
|
||||
{ resendReactionsButton }
|
||||
{ collapseReplyChain }
|
||||
{ collapseReplyChainButton }
|
||||
</IconizedContextMenuOptionList>
|
||||
);
|
||||
|
||||
let redactItemList: JSX.Element;
|
||||
if (redactButton) {
|
||||
redactItemList = (
|
||||
<IconizedContextMenuOptionList red>
|
||||
|
@ -502,33 +614,40 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
</IconizedContextMenuOptionList>
|
||||
);
|
||||
}
|
||||
|
||||
let reactionPicker: JSX.Element;
|
||||
if (this.state.reactionPickerDisplayed) {
|
||||
const buttonRect = (this.reactButtonRef.current as HTMLElement)?.getBoundingClientRect();
|
||||
reactionPicker = (
|
||||
<ContextMenu
|
||||
{...toRightOf(buttonRect)}
|
||||
onFinished={this.closeMenu}
|
||||
managed={false}
|
||||
>
|
||||
<ReactionPicker
|
||||
mxEvent={mxEvent}
|
||||
onFinished={this.onCloseReactionPicker}
|
||||
reactions={reactions}
|
||||
/>
|
||||
</ContextMenu>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<IconizedContextMenu
|
||||
{...this.props}
|
||||
className="mx_MessageContextMenu"
|
||||
compact={true}
|
||||
>
|
||||
{ commonItemsList }
|
||||
{ redactItemList }
|
||||
</IconizedContextMenu>
|
||||
<React.Fragment>
|
||||
<IconizedContextMenu
|
||||
{...this.props}
|
||||
className="mx_MessageContextMenu"
|
||||
compact={true}
|
||||
>
|
||||
{ nativeItemsList }
|
||||
{ quickItemsList }
|
||||
{ commonItemsList }
|
||||
{ redactItemList }
|
||||
</IconizedContextMenu>
|
||||
{ reactionPicker }
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function canForward(event: MatrixEvent): boolean {
|
||||
return !(
|
||||
isLocationEvent(event) ||
|
||||
M_POLL_START.matches(event.getType())
|
||||
);
|
||||
}
|
||||
|
||||
function isLocationEvent(event: MatrixEvent): boolean {
|
||||
const eventType = event.getType();
|
||||
return (
|
||||
M_LOCATION.matches(eventType) ||
|
||||
(
|
||||
eventType === EventType.RoomMessage &&
|
||||
M_LOCATION.matches(event.getContent().msgtype)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ import Modal from "../../../Modal";
|
|||
import ExportDialog from "../dialogs/ExportDialog";
|
||||
import { useFeatureEnabled } from "../../../hooks/useSettings";
|
||||
import { usePinnedEvents } from "../right_panel/PinnedMessagesCard";
|
||||
import RoomViewStore from "../../../stores/RoomViewStore";
|
||||
import { RoomViewStore } from "../../../stores/RoomViewStore";
|
||||
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
|
||||
import { ROOM_NOTIFICATIONS_TAB } from "../dialogs/RoomSettingsDialog";
|
||||
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
|
||||
|
@ -328,7 +328,7 @@ const RoomContextMenu = ({ room, onFinished, ...props }: IProps) => {
|
|||
};
|
||||
|
||||
const ensureViewingRoom = (ev: ButtonEvent) => {
|
||||
if (RoomViewStore.getRoomId() === room.roomId) return;
|
||||
if (RoomViewStore.instance.getRoomId() === room.roomId) return;
|
||||
dis.dispatch<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
room_id: room.roomId,
|
||||
|
@ -373,7 +373,7 @@ const RoomContextMenu = ({ room, onFinished, ...props }: IProps) => {
|
|||
ev.stopPropagation();
|
||||
|
||||
Modal.createDialog(DevtoolsDialog, {
|
||||
roomId: RoomViewStore.getRoomId(),
|
||||
roomId: RoomViewStore.instance.getRoomId(),
|
||||
}, "mx_DevtoolsDialog_wrapper");
|
||||
onFinished();
|
||||
}}
|
||||
|
|
|
@ -22,7 +22,6 @@ import { IProps as IContextMenuProps } from "../../structures/ContextMenu";
|
|||
import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from "./IconizedContextMenu";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import {
|
||||
leaveSpace,
|
||||
shouldShowSpaceSettings,
|
||||
showCreateNewRoom,
|
||||
showCreateNewSubspace,
|
||||
|
@ -30,6 +29,7 @@ import {
|
|||
showSpacePreferences,
|
||||
showSpaceSettings,
|
||||
} from "../../../utils/space";
|
||||
import { leaveSpace } from "../../../utils/leave-behaviour";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import { ButtonEvent } from "../elements/AccessibleButton";
|
||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||
|
|
|
@ -19,7 +19,6 @@ import React from 'react';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { SettingLevel } from "../../../settings/SettingLevel";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
|
||||
interface IProps {
|
||||
|
@ -32,7 +31,6 @@ interface IProps {
|
|||
onFinished: (success: boolean) => void;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.AskInviteAnywayDialog")
|
||||
export default class AskInviteAnywayDialog extends React.Component<IProps> {
|
||||
private onInviteClicked = (): void => {
|
||||
this.props.onInviteAnyways();
|
||||
|
|
|
@ -25,7 +25,6 @@ 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 Heading from '../typography/Heading';
|
||||
import { IDialogProps } from "./IDialogProps";
|
||||
import { PosthogScreenTracker, ScreenName } from "../../../PosthogTrackers";
|
||||
|
@ -81,7 +80,6 @@ interface IProps extends IDialogProps {
|
|||
* Includes a div for the title, and a keypress handler which cancels the
|
||||
* dialog on escape.
|
||||
*/
|
||||
@replaceableComponent("views.dialogs.BaseDialog")
|
||||
export default class BaseDialog extends React.Component<IProps> {
|
||||
private matrixClient: MatrixClient;
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ import SettingsStore from "../../../settings/SettingsStore";
|
|||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import { UserTab } from "./UserSettingsDialog";
|
||||
import { UserTab } from "./UserTab";
|
||||
import GenericFeatureFeedbackDialog from "./GenericFeatureFeedbackDialog";
|
||||
|
||||
// XXX: Keep this around for re-use in future Betas
|
||||
|
|
|
@ -24,7 +24,6 @@ import Modal from '../../../Modal';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import sendBugReport, { downloadBugReport } from '../../../rageshake/submit-rageshake';
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import QuestionDialog from "./QuestionDialog";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import Field from '../elements/Field';
|
||||
|
@ -50,7 +49,6 @@ interface IState {
|
|||
downloadProgress: string;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.BugReportDialog")
|
||||
export default class BugReportDialog extends React.Component<IProps, IState> {
|
||||
private unmounted: boolean;
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import ConfirmRedactDialog from './ConfirmRedactDialog';
|
||||
import ErrorDialog from './ErrorDialog';
|
||||
import BaseDialog from "./BaseDialog";
|
||||
|
@ -45,7 +44,6 @@ interface IState {
|
|||
*
|
||||
* To avoid this, we keep the dialog open as long as /redact is in progress.
|
||||
*/
|
||||
@replaceableComponent("views.dialogs.ConfirmAndWaitRedactDialog")
|
||||
export default class ConfirmAndWaitRedactDialog extends React.PureComponent<IProps, IState> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
|
@ -20,7 +20,6 @@ import React from 'react';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import Modal from '../../../Modal';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import ErrorDialog from './ErrorDialog';
|
||||
import TextInputDialog from "./TextInputDialog";
|
||||
|
||||
|
@ -31,7 +30,6 @@ interface IProps {
|
|||
/*
|
||||
* A dialog for confirming a redaction.
|
||||
*/
|
||||
@replaceableComponent("views.dialogs.ConfirmRedactDialog")
|
||||
export default class ConfirmRedactDialog extends React.Component<IProps> {
|
||||
render() {
|
||||
return (
|
||||
|
|
|
@ -19,7 +19,6 @@ import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
|||
import classNames from "classnames";
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import MemberAvatar from '../avatars/MemberAvatar';
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import DialogButtons from "../elements/DialogButtons";
|
||||
|
@ -55,7 +54,6 @@ interface IState {
|
|||
* to make it obvious what is going to happen.
|
||||
* Also tweaks the style for 'dangerous' actions (albeit only with colour)
|
||||
*/
|
||||
@replaceableComponent("views.dialogs.ConfirmUserActionDialog")
|
||||
export default class ConfirmUserActionDialog extends React.Component<IProps, IState> {
|
||||
static defaultProps = {
|
||||
danger: false,
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import DialogButtons from "../elements/DialogButtons";
|
||||
|
||||
|
@ -25,7 +24,6 @@ interface IProps {
|
|||
onFinished: (success: boolean) => void;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.ConfirmWipeDeviceDialog")
|
||||
export default class ConfirmWipeDeviceDialog extends React.Component<IProps> {
|
||||
private onConfirm = (): void => {
|
||||
this.props.onFinished(true);
|
||||
|
|
|
@ -24,8 +24,7 @@ import SdkConfig from '../../../SdkConfig';
|
|||
import withValidation, { IFieldState } from '../elements/Validation';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import { IOpts, privateShouldBeEncrypted } from "../../../createRoom";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { IOpts } from "../../../createRoom";
|
||||
import Field from "../elements/Field";
|
||||
import RoomAliasField from "../elements/RoomAliasField";
|
||||
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
|
||||
|
@ -35,6 +34,7 @@ import SpaceStore from "../../../stores/spaces/SpaceStore";
|
|||
import JoinRuleDropdown from "../elements/JoinRuleDropdown";
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
import { privateShouldBeEncrypted } from "../../../utils/rooms";
|
||||
|
||||
interface IProps {
|
||||
type?: RoomType;
|
||||
|
@ -58,7 +58,6 @@ interface IState {
|
|||
canChangeEncryption: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.CreateRoomDialog")
|
||||
export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
||||
private readonly supportsRestricted: boolean;
|
||||
private nameField = createRef<Field>();
|
||||
|
@ -104,7 +103,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
|||
createOpts.preset = Preset.PublicChat;
|
||||
opts.guestAccess = false;
|
||||
const { alias } = this.state;
|
||||
createOpts.room_alias_name = alias.substr(1, alias.indexOf(":") - 1);
|
||||
createOpts.room_alias_name = alias.substring(1, alias.indexOf(":"));
|
||||
} else {
|
||||
// If we cannot change encryption we pass `true` for safety, the server should automatically do this for us.
|
||||
opts.encryption = this.state.canChangeEncryption ? this.state.isEncrypted : true;
|
||||
|
|
|
@ -21,13 +21,13 @@ import { logger } from "matrix-js-sdk/src/logger";
|
|||
|
||||
import Analytics from '../../../Analytics';
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import * as Lifecycle from '../../../Lifecycle';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import InteractiveAuth, { ERROR_USER_CANCELLED } from "../../structures/InteractiveAuth";
|
||||
import { DEFAULT_PHASE, PasswordAuthEntry, SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents";
|
||||
import StyledCheckbox from "../elements/StyledCheckbox";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
|
||||
interface IProps {
|
||||
onFinished: (success: boolean) => void;
|
||||
|
@ -46,7 +46,6 @@ interface IState {
|
|||
continueKind: string;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.DeactivateAccountDialog")
|
||||
export default class DeactivateAccountDialog extends React.Component<IProps, IState> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -124,7 +123,7 @@ export default class DeactivateAccountDialog extends React.Component<IProps, ISt
|
|||
MatrixClientPeg.get().deactivateAccount(auth, this.state.shouldErase).then(r => {
|
||||
// Deactivation worked - logout & close this dialog
|
||||
Analytics.trackEvent('Account', 'Deactivate Account');
|
||||
Lifecycle.onLoggedOut();
|
||||
defaultDispatcher.fire(Action.TriggerLogout);
|
||||
this.props.onFinished(true);
|
||||
}).catch(e => {
|
||||
logger.error(e);
|
||||
|
|
|
@ -28,7 +28,6 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
|
||||
interface IProps {
|
||||
|
@ -44,7 +43,6 @@ interface IState {
|
|||
onFinished: (success: boolean) => void;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.ErrorDialog")
|
||||
export default class ErrorDialog extends React.Component<IProps, IState> {
|
||||
public static defaultProps = {
|
||||
focus: true,
|
||||
|
|
|
@ -43,10 +43,10 @@ import QueryMatcher from "../../../autocomplete/QueryMatcher";
|
|||
import TruncatedList from "../elements/TruncatedList";
|
||||
import EntityTile from "../rooms/EntityTile";
|
||||
import BaseAvatar from "../avatars/BaseAvatar";
|
||||
import { roomContextDetailsText } from "../../../Rooms";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { ButtonEvent } from "../elements/AccessibleButton";
|
||||
import { roomContextDetailsText } from "../../../utils/i18n-helpers";
|
||||
|
||||
const AVATAR_SIZE = 30;
|
||||
|
||||
|
|
|
@ -32,7 +32,6 @@ import {
|
|||
IPostmessageResponseData,
|
||||
PostmessageAction,
|
||||
} from "./HostSignupDialogTypes";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { IConfigOptions } from "../../../IConfigOptions";
|
||||
import { SnakedObject } from "../../../utils/SnakedObject";
|
||||
|
||||
|
@ -46,7 +45,6 @@ interface IState {
|
|||
minimized: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.HostSignupDialog")
|
||||
export default class HostSignupDialog extends React.PureComponent<IProps, IState> {
|
||||
private iframeRef: React.RefObject<HTMLIFrameElement> = React.createRef();
|
||||
private readonly config: SnakedObject<IConfigOptions["host_signup"]>;
|
||||
|
|
|
@ -21,7 +21,6 @@ import { logger } from "matrix-js-sdk/src/logger";
|
|||
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { mediaFromMxc } from "../../../customisations/Media";
|
||||
import VerificationComplete from "../verification/VerificationComplete";
|
||||
import VerificationCancelled from "../verification/VerificationCancelled";
|
||||
|
@ -54,7 +53,6 @@ interface IState {
|
|||
sas: IGeneratedSas;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.IncomingSasDialog")
|
||||
export default class IncomingSasDialog extends React.Component<IProps, IState> {
|
||||
private showSasEvent: ISasEvent;
|
||||
|
||||
|
|
|
@ -19,8 +19,9 @@ import React, { ReactNode, KeyboardEvent } from 'react';
|
|||
import classNames from "classnames";
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import * as sdk from '../../../index';
|
||||
import { IDialogProps } from "./IDialogProps";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import DialogButtons from "../elements/DialogButtons";
|
||||
|
||||
interface IProps extends IDialogProps {
|
||||
title?: string;
|
||||
|
@ -44,9 +45,6 @@ export default class InfoDialog extends React.Component<IProps> {
|
|||
};
|
||||
|
||||
render() {
|
||||
// FIXME: Using a regular import will break the app
|
||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||
return (
|
||||
<BaseDialog
|
||||
className="mx_InfoDialog"
|
||||
|
|
|
@ -19,14 +19,12 @@ import React from 'react';
|
|||
import { _t } from "../../../languageHandler";
|
||||
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<IProps> {
|
||||
private onAcknowledgeClick = (): void => {
|
||||
this.props.onFinished();
|
||||
|
|
|
@ -18,14 +18,12 @@ import React from 'react';
|
|||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { IDialogProps } from "./IDialogProps";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import DialogButtons from "../elements/DialogButtons";
|
||||
|
||||
interface IProps extends IDialogProps {}
|
||||
|
||||
@replaceableComponent("views.dialogs.IntegrationsImpossibleDialog")
|
||||
export default class IntegrationsImpossibleDialog extends React.Component<IProps> {
|
||||
private onAcknowledgeClick = (): void => {
|
||||
this.props.onFinished();
|
||||
|
|
|
@ -24,7 +24,6 @@ import { _t } from '../../../languageHandler';
|
|||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import InteractiveAuth, { ERROR_USER_CANCELLED } from "../../structures/InteractiveAuth";
|
||||
import { SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import { IDialogProps } from "./IDialogProps";
|
||||
|
||||
|
@ -83,7 +82,6 @@ interface IState {
|
|||
uiaStagePhase: number | string;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.InteractiveAuthDialog")
|
||||
export default class InteractiveAuthDialog extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2019 - 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -19,7 +19,6 @@ import classNames from 'classnames';
|
|||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
|
||||
import { IInvite3PID } from "matrix-js-sdk/src/@types/requests";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { _t, _td } from "../../../languageHandler";
|
||||
|
@ -30,15 +29,8 @@ import SdkConfig from "../../../SdkConfig";
|
|||
import * as Email from "../../../email";
|
||||
import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from "../../../utils/IdentityServerUtils";
|
||||
import { abbreviateUrl } from "../../../utils/UrlUtils";
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
import IdentityAuthClient from "../../../IdentityAuthClient";
|
||||
import Modal from "../../../Modal";
|
||||
import { humanizeTime } from "../../../utils/humanize";
|
||||
import createRoom, {
|
||||
canEncryptToAllUsers,
|
||||
findDMForUser,
|
||||
privateShouldBeEncrypted,
|
||||
} from "../../../createRoom";
|
||||
import {
|
||||
IInviteResult,
|
||||
inviteMultipleToRoom,
|
||||
|
@ -49,9 +41,7 @@ import { DefaultTagID } from "../../../stores/room-list/models";
|
|||
import RoomListStore from "../../../stores/room-list/RoomListStore";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { UIFeature } from "../../../settings/UIFeature";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { mediaFromMxc } from "../../../customisations/Media";
|
||||
import { getAddressType } from "../../../UserAddress";
|
||||
import BaseAvatar from '../avatars/BaseAvatar';
|
||||
import AccessibleButton, { ButtonEvent } from '../elements/AccessibleButton';
|
||||
import { compare, selectText } from '../../../utils/strings';
|
||||
|
@ -66,9 +56,12 @@ import CallHandler from "../../../CallHandler";
|
|||
import UserIdentifierCustomisations from '../../../customisations/UserIdentifier';
|
||||
import CopyableText from "../elements/CopyableText";
|
||||
import { ScreenName } from '../../../PosthogTrackers';
|
||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
import { DirectoryMember, IDMUserTileProps, Member, startDm, ThreepidMember } from "../../../utils/direct-messages";
|
||||
import { AnyInviteKind, KIND_CALL_TRANSFER, KIND_DM, KIND_INVITE } from './InviteDialogTypes';
|
||||
import Modal from '../../../Modal';
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
|
||||
// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
|
||||
/* eslint-disable camelcase */
|
||||
|
@ -79,13 +72,6 @@ interface IRecentUser {
|
|||
lastActive: number;
|
||||
}
|
||||
|
||||
export const KIND_DM = "dm";
|
||||
export const KIND_INVITE = "invite";
|
||||
// NB. This dialog needs the 'mx_InviteDialog_transferWrapper' wrapper class to have the correct
|
||||
// padding on the bottom (because all modals have 24px padding on all sides), so this needs to
|
||||
// be passed when creating the modal
|
||||
export const KIND_CALL_TRANSFER = "call_transfer";
|
||||
|
||||
const INITIAL_ROOMS_SHOWN = 3; // Number of rooms to show at first
|
||||
const INCREMENT_ROOMS_SHOWN = 5; // Number of rooms to add when 'show more' is clicked
|
||||
|
||||
|
@ -94,90 +80,6 @@ enum TabId {
|
|||
DialPad = 'dialpad',
|
||||
}
|
||||
|
||||
// This is the interface that is expected by various components in the Invite Dialog and RoomInvite.
|
||||
// It is a bit awkward because it also matches the RoomMember class from the js-sdk with some extra support
|
||||
// for 3PIDs/email addresses.
|
||||
export abstract class Member {
|
||||
/**
|
||||
* The display name of this Member. For users this should be their profile's display
|
||||
* name or user ID if none set. For 3PIDs this should be the 3PID address (email).
|
||||
*/
|
||||
public abstract get name(): string;
|
||||
|
||||
/**
|
||||
* The ID of this Member. For users this should be their user ID. For 3PIDs this should
|
||||
* be the 3PID address (email).
|
||||
*/
|
||||
public abstract get userId(): string;
|
||||
|
||||
/**
|
||||
* Gets the MXC URL of this Member's avatar. For users this should be their profile's
|
||||
* avatar MXC URL or null if none set. For 3PIDs this should always be null.
|
||||
*/
|
||||
public abstract getMxcAvatarUrl(): string;
|
||||
}
|
||||
|
||||
class DirectoryMember extends Member {
|
||||
private readonly _userId: string;
|
||||
private readonly displayName?: string;
|
||||
private readonly avatarUrl?: string;
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
constructor(userDirResult: { user_id: string, display_name?: string, avatar_url?: string }) {
|
||||
super();
|
||||
this._userId = userDirResult.user_id;
|
||||
this.displayName = userDirResult.display_name;
|
||||
this.avatarUrl = userDirResult.avatar_url;
|
||||
}
|
||||
|
||||
// These next class members are for the Member interface
|
||||
get name(): string {
|
||||
return this.displayName || this._userId;
|
||||
}
|
||||
|
||||
get userId(): string {
|
||||
return this._userId;
|
||||
}
|
||||
|
||||
getMxcAvatarUrl(): string {
|
||||
return this.avatarUrl;
|
||||
}
|
||||
}
|
||||
|
||||
class ThreepidMember extends Member {
|
||||
private readonly id: string;
|
||||
|
||||
constructor(id: string) {
|
||||
super();
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
// This is a getter that would be falsey on all other implementations. Until we have
|
||||
// better type support in the react-sdk we can use this trick to determine the kind
|
||||
// of 3PID we're dealing with, if any.
|
||||
get isEmail(): boolean {
|
||||
return this.id.includes('@');
|
||||
}
|
||||
|
||||
// These next class members are for the Member interface
|
||||
get name(): string {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
get userId(): string {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
getMxcAvatarUrl(): string {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
interface IDMUserTileProps {
|
||||
member: Member;
|
||||
onRemove(member: Member): void;
|
||||
}
|
||||
|
||||
class DMUserTile extends React.PureComponent<IDMUserTileProps> {
|
||||
private onRemove = (e) => {
|
||||
// Stop the browser from highlighting text
|
||||
|
@ -355,7 +257,7 @@ interface IInviteDialogProps {
|
|||
|
||||
// The kind of invite being performed. Assumed to be KIND_DM if
|
||||
// not provided.
|
||||
kind: string;
|
||||
kind: AnyInviteKind;
|
||||
|
||||
// The room ID this dialog is for. Only required for KIND_INVITE.
|
||||
roomId: string;
|
||||
|
@ -387,7 +289,6 @@ interface IInviteDialogState {
|
|||
errorText: string;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.InviteDialog")
|
||||
export default class InviteDialog extends React.PureComponent<IInviteDialogProps, IInviteDialogState> {
|
||||
static defaultProps = {
|
||||
kind: KIND_DM,
|
||||
|
@ -663,72 +564,10 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
|
||||
private startDm = async () => {
|
||||
this.setState({ busy: true });
|
||||
const client = MatrixClientPeg.get();
|
||||
const targets = this.convertFilter();
|
||||
const targetIds = targets.map(t => t.userId);
|
||||
|
||||
// Check if there is already a DM with these people and reuse it if possible.
|
||||
let existingRoom: Room;
|
||||
if (targetIds.length === 1) {
|
||||
existingRoom = findDMForUser(client, targetIds[0]);
|
||||
} else {
|
||||
existingRoom = DMRoomMap.shared().getDMRoomForIdentifiers(targetIds);
|
||||
}
|
||||
if (existingRoom) {
|
||||
dis.dispatch<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
room_id: existingRoom.roomId,
|
||||
should_peek: false,
|
||||
joining: false,
|
||||
metricsTrigger: "MessageUser",
|
||||
});
|
||||
this.props.onFinished(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const createRoomOptions = { inlineErrors: true } as any; // XXX: Type out `createRoomOptions`
|
||||
|
||||
if (privateShouldBeEncrypted()) {
|
||||
// Check whether all users have uploaded device keys before.
|
||||
// If so, enable encryption in the new room.
|
||||
const has3PidMembers = targets.some(t => t instanceof ThreepidMember);
|
||||
if (!has3PidMembers) {
|
||||
const allHaveDeviceKeys = await canEncryptToAllUsers(client, targetIds);
|
||||
if (allHaveDeviceKeys) {
|
||||
createRoomOptions.encryption = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it's a traditional DM and create the room if required.
|
||||
// TODO: [Canonical DMs] Remove this check and instead just create the multi-person DM
|
||||
try {
|
||||
const isSelf = targetIds.length === 1 && targetIds[0] === client.getUserId();
|
||||
if (targetIds.length === 1 && !isSelf) {
|
||||
createRoomOptions.dmUserId = targetIds[0];
|
||||
}
|
||||
|
||||
if (targetIds.length > 1) {
|
||||
createRoomOptions.createOpts = targetIds.reduce(
|
||||
(roomOptions, address) => {
|
||||
const type = getAddressType(address);
|
||||
if (type === 'email') {
|
||||
const invite: IInvite3PID = {
|
||||
id_server: client.getIdentityServerUrl(true),
|
||||
medium: 'email',
|
||||
address,
|
||||
};
|
||||
roomOptions.invite_3pid.push(invite);
|
||||
} else if (type === 'mx-user-id') {
|
||||
roomOptions.invite.push(address);
|
||||
}
|
||||
return roomOptions;
|
||||
},
|
||||
{ invite: [], invite_3pid: [] },
|
||||
);
|
||||
}
|
||||
|
||||
await createRoom(createRoomOptions);
|
||||
const cli = MatrixClientPeg.get();
|
||||
const targets = this.convertFilter();
|
||||
await startDm(cli, targets);
|
||||
this.props.onFinished(true);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
|
@ -736,6 +575,8 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
busy: false,
|
||||
errorText: _t("We couldn't create your DM."),
|
||||
});
|
||||
} finally {
|
||||
this.setState({ busy: false });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
24
src/components/views/dialogs/InviteDialogTypes.ts
Normal file
24
src/components/views/dialogs/InviteDialogTypes.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export const KIND_DM = "dm";
|
||||
export const KIND_INVITE = "invite";
|
||||
// NB. This dialog needs the 'mx_InviteDialog_transferWrapper' wrapper class to have the correct
|
||||
// padding on the bottom (because all modals have 24px padding on all sides), so this needs to
|
||||
// be passed when creating the modal
|
||||
export const KIND_CALL_TRANSFER = "call_transfer";
|
||||
|
||||
export type AnyInviteKind = typeof KIND_INVITE | typeof KIND_DM | typeof KIND_CALL_TRANSFER;
|
|
@ -24,7 +24,6 @@ import dis from '../../../dispatcher/dispatcher';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import RestoreKeyBackupDialog from './security/RestoreKeyBackupDialog';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import QuestionDialog from "./QuestionDialog";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import Spinner from "../elements/Spinner";
|
||||
|
@ -41,7 +40,6 @@ interface IState {
|
|||
error?: string;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.LogoutDialog")
|
||||
export default class LogoutDialog extends React.Component<IProps, IState> {
|
||||
static defaultProps = {
|
||||
onFinished: function() {},
|
||||
|
|
|
@ -24,7 +24,6 @@ import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
|
|||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import * as FormattingUtils from '../../../utils/FormattingUtils';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import QuestionDialog from "./QuestionDialog";
|
||||
import { IDialogProps } from "./IDialogProps";
|
||||
|
||||
|
@ -33,7 +32,6 @@ interface IProps extends IDialogProps {
|
|||
device: DeviceInfo;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.ManualDeviceKeyVerificationDialog")
|
||||
export default class ManualDeviceKeyVerificationDialog extends React.Component<IProps> {
|
||||
private onLegacyFinished = (confirm: boolean): void => {
|
||||
if (confirm) {
|
||||
|
|
|
@ -25,7 +25,6 @@ import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
|||
import { _t } from '../../../languageHandler';
|
||||
import { wantsDateSeparator } from '../../../DateUtils';
|
||||
import SettingsStore from '../../../settings/SettingsStore';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import ScrollPanel from "../../structures/ScrollPanel";
|
||||
import Spinner from "../elements/Spinner";
|
||||
|
@ -48,7 +47,6 @@ interface IState {
|
|||
isTwelveHour: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.MessageEditHistoryDialog")
|
||||
export default class MessageEditHistoryDialog extends React.PureComponent<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
|
|
@ -39,7 +39,6 @@ import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
|||
import { OwnProfileStore } from "../../../stores/OwnProfileStore";
|
||||
import { arrayFastClone } from "../../../utils/arrays";
|
||||
import { ElementWidget } from "../../../stores/widgets/StopGapWidget";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { ELEMENT_CLIENT_ID } from "../../../identifiers";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
|
||||
|
@ -57,7 +56,6 @@ interface IState {
|
|||
|
||||
const MAX_BUTTONS = 3;
|
||||
|
||||
@replaceableComponent("views.dialogs.ModalWidgetDialog")
|
||||
export default class ModalWidgetDialog extends React.PureComponent<IProps, IState> {
|
||||
private readonly widget: Widget;
|
||||
private readonly possibleButtons: ModalButtonID[];
|
||||
|
|
|
@ -18,9 +18,10 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
import classNames from "classnames";
|
||||
|
||||
import * as sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { IDialogProps } from "./IDialogProps";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import DialogButtons from "../elements/DialogButtons";
|
||||
|
||||
interface IProps extends IDialogProps {
|
||||
title?: string;
|
||||
|
@ -58,10 +59,6 @@ export default class QuestionDialog extends React.Component<IProps> {
|
|||
};
|
||||
|
||||
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";
|
||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { ensureDMExists } from "../../../createRoom";
|
||||
|
@ -23,7 +24,6 @@ import { IDialogProps } from "./IDialogProps";
|
|||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import SdkConfig from '../../../SdkConfig';
|
||||
import Markdown from '../../../Markdown';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import StyledRadioButton from "../elements/StyledRadioButton";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
|
@ -89,7 +89,6 @@ type Moderation = {
|
|||
* a well-formed state event `m.room.moderation.moderated_by`
|
||||
* /`org.matrix.msc3215.room.moderation.moderated_by`?
|
||||
*/
|
||||
@replaceableComponent("views.dialogs.ReportEventDialog")
|
||||
export default class ReportEventDialog extends React.Component<IProps, IState> {
|
||||
// If the room supports moderation, the moderation information.
|
||||
private moderation?: Moderation;
|
||||
|
@ -215,7 +214,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
|||
try {
|
||||
const client = MatrixClientPeg.get();
|
||||
const ev = this.props.mxEvent;
|
||||
if (this.moderation && this.state.nature != NonStandardValue.Admin) {
|
||||
if (this.moderation && this.state.nature !== NonStandardValue.Admin) {
|
||||
const nature: Nature = this.state.nature;
|
||||
|
||||
// Report to moderators through to the dedicated bot,
|
||||
|
@ -235,6 +234,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
|||
}
|
||||
this.props.onFinished(true);
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
this.setState({
|
||||
busy: false,
|
||||
err: e.message,
|
||||
|
|
|
@ -30,7 +30,6 @@ import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
|||
import dis from "../../../dispatcher/dispatcher";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { UIFeature } from "../../../settings/UIFeature";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import { Action } from '../../../dispatcher/actions';
|
||||
|
||||
|
@ -51,7 +50,6 @@ interface IState {
|
|||
roomName: string;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.RoomSettingsDialog")
|
||||
export default class RoomSettingsDialog extends React.Component<IProps, IState> {
|
||||
private dispatcherRef: string;
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ import { Room } from "matrix-js-sdk/src/models/room";
|
|||
|
||||
import Modal from '../../../Modal';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { upgradeRoom } from "../../../utils/RoomUpgrade";
|
||||
import { IDialogProps } from "./IDialogProps";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
|
@ -35,7 +34,6 @@ interface IState {
|
|||
busy: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.RoomUpgradeDialog")
|
||||
export default class RoomUpgradeDialog extends React.Component<IProps, IState> {
|
||||
private targetVersion: string;
|
||||
|
||||
|
|
|
@ -23,7 +23,6 @@ import SdkConfig from "../../../SdkConfig";
|
|||
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import Modal from "../../../Modal";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { IDialogProps } from "./IDialogProps";
|
||||
import BugReportDialog from './BugReportDialog';
|
||||
import BaseDialog from "./BaseDialog";
|
||||
|
@ -50,7 +49,6 @@ interface IState {
|
|||
total?: number;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.RoomUpgradeWarningDialog")
|
||||
export default class RoomUpgradeWarningDialog extends React.Component<IProps, IState> {
|
||||
private readonly isPrivate: boolean;
|
||||
private readonly currentVersion: string;
|
||||
|
|
|
@ -29,12 +29,10 @@ import AccessibleButton from "../elements/AccessibleButton";
|
|||
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import { IDialogProps } from "./IDialogProps";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps extends IDialogProps {
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.ServerOfflineDialog")
|
||||
export default class ServerOfflineDialog extends React.PureComponent<IProps> {
|
||||
public componentDidMount() {
|
||||
EchoStore.instance.on(UPDATE_EVENT, this.onEchosUpdated);
|
||||
|
|
|
@ -27,7 +27,6 @@ import Field from "../elements/Field";
|
|||
import StyledRadioButton from "../elements/StyledRadioButton";
|
||||
import TextWithTooltip from "../elements/TextWithTooltip";
|
||||
import withValidation, { IFieldState } from "../elements/Validation";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps {
|
||||
title?: string;
|
||||
|
@ -40,7 +39,6 @@ interface IState {
|
|||
otherHomeserver: string;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.ServerPickerDialog")
|
||||
export default class ServerPickerDialog extends React.PureComponent<IProps, IState> {
|
||||
private readonly defaultServer: ValidatedServerConfig;
|
||||
private readonly fieldRef = createRef<Field>();
|
||||
|
|
|
@ -17,12 +17,10 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import DialogButtons from "../elements/DialogButtons";
|
||||
import { IDialogProps } from "./IDialogProps";
|
||||
|
||||
@replaceableComponent("views.dialogs.SeshatResetDialog")
|
||||
export default class SeshatResetDialog extends React.PureComponent<IDialogProps> {
|
||||
render() {
|
||||
return (
|
||||
|
|
|
@ -21,7 +21,6 @@ import React from 'react';
|
|||
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";
|
||||
|
@ -32,7 +31,6 @@ interface IProps extends IDialogProps {
|
|||
error: Error;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.SessionRestoreErrorDialog")
|
||||
export default class SessionRestoreErrorDialog extends React.Component<IProps> {
|
||||
private sendBugReport = (): void => {
|
||||
Modal.createTrackedDialog('Session Restore Error', 'Send Bug Report Dialog', BugReportDialog, {
|
||||
|
|
|
@ -22,7 +22,6 @@ 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";
|
||||
|
@ -44,7 +43,6 @@ interface IState {
|
|||
*
|
||||
* On success, `onFinished(true)` is called.
|
||||
*/
|
||||
@replaceableComponent("views.dialogs.SetEmailDialog")
|
||||
export default class SetEmailDialog extends React.Component<IProps, IState> {
|
||||
private addThreepid: AddThreepid;
|
||||
|
||||
|
|
|
@ -29,7 +29,6 @@ import StyledCheckbox from '../elements/StyledCheckbox';
|
|||
import { IDialogProps } from "./IDialogProps";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { UIFeature } from "../../../settings/UIFeature";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import CopyableText from "../elements/CopyableText";
|
||||
|
||||
|
@ -71,7 +70,6 @@ interface IState {
|
|||
permalinkCreator: RoomPermalinkCreator;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.ShareDialog")
|
||||
export default class ShareDialog extends React.PureComponent<IProps, IState> {
|
||||
protected closeCopiedTooltip: () => void;
|
||||
|
||||
|
|
|
@ -26,10 +26,7 @@ import { useSettingValue } from "../../../hooks/useSettings";
|
|||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { SettingLevel } from "../../../settings/SettingLevel";
|
||||
import RoomName from "../elements/RoomName";
|
||||
|
||||
export enum SpacePreferenceTab {
|
||||
Appearance = "SPACE_PREFERENCE_APPEARANCE_TAB",
|
||||
}
|
||||
import { SpacePreferenceTab } from "../../../dispatcher/payloads/OpenSpacePreferencesPayload";
|
||||
|
||||
interface IProps extends IDialogProps {
|
||||
space: Room;
|
||||
|
|
|
@ -53,19 +53,18 @@ import DMRoomMap from "../../../utils/DMRoomMap";
|
|||
import { mediaFromMxc } from "../../../customisations/Media";
|
||||
import BaseAvatar from "../avatars/BaseAvatar";
|
||||
import Spinner from "../elements/Spinner";
|
||||
import { roomContextDetailsText, spaceContextDetailsText } from "../../../Rooms";
|
||||
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import Modal from "../../../Modal";
|
||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||
import RoomViewStore from "../../../stores/RoomViewStore";
|
||||
import { RoomViewStore } from "../../../stores/RoomViewStore";
|
||||
import { showStartChatInviteDialog } from "../../../RoomInvite";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { SettingLevel } from "../../../settings/SettingLevel";
|
||||
import NotificationBadge from "../rooms/NotificationBadge";
|
||||
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
|
||||
import { BetaPill } from "../beta/BetaCard";
|
||||
import { UserTab } from "./UserSettingsDialog";
|
||||
import { UserTab } from "./UserTab";
|
||||
import BetaFeedbackDialog from "./BetaFeedbackDialog";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
|
@ -74,6 +73,7 @@ import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
|||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
import { PosthogAnalytics } from "../../../PosthogAnalytics";
|
||||
import { getCachedRoomIDForAlias } from "../../../RoomAliasCache";
|
||||
import { roomContextDetailsText, spaceContextDetailsText } from "../../../utils/i18n-helpers";
|
||||
|
||||
const MAX_RECENT_SEARCHES = 10;
|
||||
const SECTION_LIMIT = 50; // only show 50 results per section for performance reasons
|
||||
|
@ -559,7 +559,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", onFinished }) =>
|
|||
<h4>{ _t("Recently viewed") }</h4>
|
||||
<div>
|
||||
{ BreadcrumbsStore.instance.rooms
|
||||
.filter(r => r.roomId !== RoomViewStore.getRoomId())
|
||||
.filter(r => r.roomId !== RoomViewStore.instance.getRoomId())
|
||||
.map(room => (
|
||||
<TooltipOption
|
||||
id={`mx_SpotlightDialog_button_recentlyViewed_${room.roomId}`}
|
||||
|
|
|
@ -19,7 +19,6 @@ import React from 'react';
|
|||
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";
|
||||
|
@ -28,7 +27,6 @@ import AccessibleButton from '../elements/AccessibleButton';
|
|||
|
||||
interface IProps extends IDialogProps { }
|
||||
|
||||
@replaceableComponent("views.dialogs.StorageEvictedDialog")
|
||||
export default class StorageEvictedDialog extends React.Component<IProps> {
|
||||
private sendBugReport = (ev: React.MouseEvent): void => {
|
||||
ev.preventDefault();
|
||||
|
|
|
@ -22,7 +22,6 @@ import { logger } from "matrix-js-sdk/src/logger";
|
|||
import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
|
||||
import { dialogTermsInteractionCallback, TermsNotSignedError } from "../../../Terms";
|
||||
import * as ScalarMessaging from "../../../ScalarMessaging";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { IntegrationManagerInstance } from "../../../integrations/IntegrationManagerInstance";
|
||||
import ScalarAuthClient from "../../../ScalarAuthClient";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
|
@ -55,7 +54,6 @@ interface IState {
|
|||
currentScalarClient: ScalarAuthClient;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.TabbedIntegrationManagerDialog")
|
||||
export default class TabbedIntegrationManagerDialog extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
|
|
@ -19,7 +19,6 @@ import React from 'react';
|
|||
import { SERVICE_TYPES } from "matrix-js-sdk/src/service-types";
|
||||
|
||||
import { _t, pickBestLanguage } from '../../../languageHandler';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import DialogButtons from "../elements/DialogButtons";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
|
||||
|
@ -66,7 +65,6 @@ interface IState {
|
|||
agreedUrls: any;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.TermsDialog")
|
||||
export default class TermsDialog extends React.PureComponent<ITermsDialogProps, IState> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
|
@ -18,7 +18,6 @@ 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";
|
||||
|
@ -43,7 +42,6 @@ interface IState {
|
|||
valid: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.TextInputDialog")
|
||||
export default class TextInputDialog extends React.Component<IProps, IState> {
|
||||
private field = createRef<Field>();
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ import React from 'react';
|
|||
import filesize from "filesize";
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { getBlobSafeMimeType } from '../../../utils/blobs';
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import DialogButtons from "../elements/DialogButtons";
|
||||
|
@ -31,7 +30,6 @@ interface IProps {
|
|||
onFinished: (uploadConfirmed: boolean, uploadAll?: boolean) => void;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.UploadConfirmDialog")
|
||||
export default class UploadConfirmDialog extends React.Component<IProps> {
|
||||
private readonly objectUrl: string;
|
||||
private readonly mimeType: string;
|
||||
|
|
|
@ -19,7 +19,6 @@ import React from 'react';
|
|||
|
||||
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";
|
||||
|
@ -35,7 +34,6 @@ interface IProps extends IDialogProps {
|
|||
* them. This is named fairly generically but the only thing we check right now is
|
||||
* the size of the file.
|
||||
*/
|
||||
@replaceableComponent("views.dialogs.UploadFailureDialog")
|
||||
export default class UploadFailureDialog extends React.Component<IProps> {
|
||||
private onCancelClick = (): void => {
|
||||
this.props.onFinished(false);
|
||||
|
|
|
@ -31,25 +31,11 @@ import HelpUserSettingsTab from "../settings/tabs/user/HelpUserSettingsTab";
|
|||
import SdkConfig from "../../../SdkConfig";
|
||||
import MjolnirUserSettingsTab from "../settings/tabs/user/MjolnirUserSettingsTab";
|
||||
import { UIFeature } from "../../../settings/UIFeature";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import { IDialogProps } from "./IDialogProps";
|
||||
import SidebarUserSettingsTab from "../settings/tabs/user/SidebarUserSettingsTab";
|
||||
import KeyboardUserSettingsTab from "../settings/tabs/user/KeyboardUserSettingsTab";
|
||||
|
||||
export enum UserTab {
|
||||
General = "USER_GENERAL_TAB",
|
||||
Appearance = "USER_APPEARANCE_TAB",
|
||||
Notifications = "USER_NOTIFICATIONS_TAB",
|
||||
Preferences = "USER_PREFERENCES_TAB",
|
||||
Keyboard = "USER_KEYBOARD_TAB",
|
||||
Sidebar = "USER_SIDEBAR_TAB",
|
||||
Voice = "USER_VOICE_TAB",
|
||||
Security = "USER_SECURITY_TAB",
|
||||
Labs = "USER_LABS_TAB",
|
||||
Mjolnir = "USER_MJOLNIR_TAB",
|
||||
Help = "USER_HELP_TAB",
|
||||
}
|
||||
import { UserTab } from "./UserTab";
|
||||
|
||||
interface IProps extends IDialogProps {
|
||||
initialTabId?: UserTab;
|
||||
|
@ -59,7 +45,6 @@ interface IState {
|
|||
mjolnirEnabled: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.UserSettingsDialog")
|
||||
export default class UserSettingsDialog extends React.Component<IProps, IState> {
|
||||
private mjolnirWatcher: string;
|
||||
|
||||
|
|
29
src/components/views/dialogs/UserTab.ts
Normal file
29
src/components/views/dialogs/UserTab.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export enum UserTab {
|
||||
General = "USER_GENERAL_TAB",
|
||||
Appearance = "USER_APPEARANCE_TAB",
|
||||
Notifications = "USER_NOTIFICATIONS_TAB",
|
||||
Preferences = "USER_PREFERENCES_TAB",
|
||||
Keyboard = "USER_KEYBOARD_TAB",
|
||||
Sidebar = "USER_SIDEBAR_TAB",
|
||||
Voice = "USER_VOICE_TAB",
|
||||
Security = "USER_SECURITY_TAB",
|
||||
Labs = "USER_LABS_TAB",
|
||||
Mjolnir = "USER_MJOLNIR_TAB",
|
||||
Help = "USER_HELP_TAB",
|
||||
}
|
|
@ -20,7 +20,6 @@ import { User } from 'matrix-js-sdk/src/models/user';
|
|||
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import EncryptionPanel from "../right_panel/EncryptionPanel";
|
||||
|
||||
|
@ -35,7 +34,6 @@ interface IState {
|
|||
verificationRequest: VerificationRequest;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.VerificationRequestDialog")
|
||||
export default class VerificationRequestDialog extends React.Component<IProps, IState> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
|
@ -32,7 +32,6 @@ import StyledCheckbox from "../elements/StyledCheckbox";
|
|||
import DialogButtons from "../elements/DialogButtons";
|
||||
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
|
||||
import { CapabilityText } from "../../../widgets/CapabilityText";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps extends IDialogProps {
|
||||
requestedCapabilities: Set<Capability>;
|
||||
|
@ -50,7 +49,6 @@ interface IState {
|
|||
rememberSelection: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.WidgetCapabilitiesPromptDialog")
|
||||
export default class WidgetCapabilitiesPromptDialog extends React.PureComponent<IProps, IState> {
|
||||
private eventPermissionsMap = new Map<Capability, WidgetEventCapability>();
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@ import { logger } from "matrix-js-sdk/src/logger";
|
|||
import { _t } from "../../../languageHandler";
|
||||
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
|
||||
import { OIDCState, WidgetPermissionStore } from "../../../stores/widgets/WidgetPermissionStore";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { IDialogProps } from "./IDialogProps";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import DialogButtons from "../elements/DialogButtons";
|
||||
|
@ -37,7 +36,6 @@ interface IState {
|
|||
rememberSelection: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.WidgetOpenIDPermissionsDialog")
|
||||
export default class WidgetOpenIDPermissionsDialog extends React.PureComponent<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
|
|
@ -20,7 +20,6 @@ import React, { ChangeEvent, FormEvent } from 'react';
|
|||
import { ISecretStorageKeyInfo } from "matrix-js-sdk/src/crypto/api";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import * as sdk from '../../../../index';
|
||||
import { MatrixClientPeg } from '../../../../MatrixClientPeg';
|
||||
import Field from '../../elements/Field';
|
||||
import AccessibleButton from '../../elements/AccessibleButton';
|
||||
|
@ -28,6 +27,10 @@ import { _t } from '../../../../languageHandler';
|
|||
import { IDialogProps } from "../IDialogProps";
|
||||
import { accessSecretStorage } from "../../../../SecurityManager";
|
||||
import Modal from "../../../../Modal";
|
||||
import InteractiveAuthDialog from "../InteractiveAuthDialog";
|
||||
import DialogButtons from "../../elements/DialogButtons";
|
||||
import BaseDialog from "../BaseDialog";
|
||||
import { chromeFileInputFix } from "../../../../utils/BrowserWorkarounds";
|
||||
|
||||
// Maximum acceptable size of a key file. It's 59 characters including the spaces we encode,
|
||||
// so this should be plenty and allow for people putting extra whitespace in the file because
|
||||
|
@ -172,7 +175,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
|
|||
this.fileUpload.current.click();
|
||||
};
|
||||
|
||||
private onPassPhraseNext = async (ev: FormEvent<HTMLFormElement>) => {
|
||||
private onPassPhraseNext = async (ev: FormEvent<HTMLFormElement> | React.MouseEvent) => {
|
||||
ev.preventDefault();
|
||||
|
||||
if (this.state.passPhrase.length <= 0) return;
|
||||
|
@ -187,7 +190,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
|
|||
}
|
||||
};
|
||||
|
||||
private onRecoveryKeyNext = async (ev: FormEvent<HTMLFormElement>) => {
|
||||
private onRecoveryKeyNext = async (ev: FormEvent<HTMLFormElement> | React.MouseEvent) => {
|
||||
ev.preventDefault();
|
||||
|
||||
if (!this.state.recoveryKeyValid) return;
|
||||
|
@ -230,8 +233,6 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
|
|||
const cli = MatrixClientPeg.get();
|
||||
await cli.bootstrapCrossSigning({
|
||||
authUploadDeviceSigningKeys: async (makeRequest) => {
|
||||
// XXX: Making this an import breaks the app.
|
||||
const InteractiveAuthDialog = sdk.getComponent("views.dialogs.InteractiveAuthDialog");
|
||||
const { finished } = Modal.createTrackedDialog(
|
||||
'Cross-signing keys dialog', '', InteractiveAuthDialog,
|
||||
{
|
||||
|
@ -273,10 +274,6 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
|
|||
}
|
||||
|
||||
render() {
|
||||
// Caution: Making these an import will break tests.
|
||||
const BaseDialog = sdk.getComponent("views.dialogs.BaseDialog");
|
||||
const DialogButtons = sdk.getComponent("views.elements.DialogButtons");
|
||||
|
||||
const hasPassphrase = (
|
||||
this.props.keyInfo &&
|
||||
this.props.keyInfo.passphrase &&
|
||||
|
@ -315,7 +312,6 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
|
|||
/>
|
||||
</div>;
|
||||
} else if (hasPassphrase && !this.state.forceRecoveryKey) {
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
title = _t("Security Phrase");
|
||||
titleClass = ['mx_AccessSecretStorageDialog_titleWithIcon mx_AccessSecretStorageDialog_securePhraseTitle'];
|
||||
|
||||
|
@ -408,6 +404,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
|
|||
<input type="file"
|
||||
className="mx_AccessSecretStorageDialog_recoveryKeyEntry_fileInput"
|
||||
ref={this.fileUpload}
|
||||
onClick={chromeFileInputFix}
|
||||
onChange={this.onRecoveryKeyFileChange}
|
||||
/>
|
||||
<AccessibleButton kind="primary" onClick={this.onRecoveryKeyFileUploadClick}>
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
|
||||
import { _t } from "../../../../languageHandler";
|
||||
import { replaceableComponent } from "../../../../utils/replaceableComponent";
|
||||
import BaseDialog from "../BaseDialog";
|
||||
import DialogButtons from "../../elements/DialogButtons";
|
||||
|
||||
|
@ -25,7 +24,6 @@ interface IProps {
|
|||
onFinished: (success: boolean) => void;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.security.ConfirmDestroyCrossSigningDialog")
|
||||
export default class ConfirmDestroyCrossSigningDialog extends React.Component<IProps> {
|
||||
private onConfirm = (): void => {
|
||||
this.props.onFinished(true);
|
||||
|
|
|
@ -27,7 +27,6 @@ import DialogButtons from '../../elements/DialogButtons';
|
|||
import BaseDialog from '../BaseDialog';
|
||||
import Spinner from '../../elements/Spinner';
|
||||
import InteractiveAuthDialog from '../InteractiveAuthDialog';
|
||||
import { replaceableComponent } from "../../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps {
|
||||
accountPassword?: string;
|
||||
|
@ -46,7 +45,6 @@ interface IState {
|
|||
* cases, only a spinner is shown, but for more complex auth like SSO, the user
|
||||
* may need to complete some steps to proceed.
|
||||
*/
|
||||
@replaceableComponent("views.dialogs.security.CreateCrossSigningDialog")
|
||||
export default class CreateCrossSigningDialog extends React.PureComponent<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
|
|
@ -24,8 +24,11 @@ import { logger } from "matrix-js-sdk/src/logger";
|
|||
import { MatrixClientPeg } from '../../../../MatrixClientPeg';
|
||||
import { _t } from '../../../../languageHandler';
|
||||
import { accessSecretStorage } from '../../../../SecurityManager';
|
||||
import * as sdk from '../../../../index';
|
||||
import { IDialogProps } from "../IDialogProps";
|
||||
import Spinner from '../../elements/Spinner';
|
||||
import DialogButtons from "../../elements/DialogButtons";
|
||||
import AccessibleButton from "../../elements/AccessibleButton";
|
||||
import BaseDialog from "../BaseDialog";
|
||||
|
||||
enum RestoreType {
|
||||
Passphrase = "passphrase",
|
||||
|
@ -298,12 +301,6 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
|
|||
}
|
||||
|
||||
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");
|
||||
|
||||
const backupHasPassphrase = (
|
||||
this.state.backupInfo &&
|
||||
this.state.backupInfo.auth_data &&
|
||||
|
|
|
@ -20,7 +20,6 @@ import SetupEncryptionBody from '../../../structures/auth/SetupEncryptionBody';
|
|||
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) {
|
||||
|
@ -36,7 +35,6 @@ interface IState {
|
|||
icon: string;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.security.SetupEncryptionDialog")
|
||||
export default class SetupEncryptionDialog extends React.Component<IProps, IState> {
|
||||
private store: SetupEncryptionStore;
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ interface IProps extends React.InputHTMLAttributes<Element> {
|
|||
tabIndex?: number;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
triggerOnMouseDown?: boolean;
|
||||
onClick(e?: ButtonEvent): void | Promise<void>;
|
||||
}
|
||||
|
||||
|
@ -78,13 +79,18 @@ export default function AccessibleButton({
|
|||
className,
|
||||
onKeyDown,
|
||||
onKeyUp,
|
||||
triggerOnMouseDown,
|
||||
...restProps
|
||||
}: IProps) {
|
||||
const newProps: IAccessibleButtonProps = restProps;
|
||||
if (disabled) {
|
||||
newProps["aria-disabled"] = true;
|
||||
} else {
|
||||
newProps.onClick = onClick;
|
||||
if (triggerOnMouseDown) {
|
||||
newProps.onMouseDown = onClick;
|
||||
} else {
|
||||
newProps.onClick = onClick;
|
||||
}
|
||||
// We need to consume enter onKeyDown and space onKeyUp
|
||||
// otherwise we are risking also activating other keyboard focusable elements
|
||||
// that might receive focus as a result of the AccessibleButtonClick action
|
||||
|
|
|
@ -19,12 +19,11 @@ import React, { SyntheticEvent } from 'react';
|
|||
|
||||
import AccessibleButton from "./AccessibleButton";
|
||||
import Tooltip, { Alignment } from './Tooltip';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps extends React.ComponentProps<typeof AccessibleButton> {
|
||||
title: string;
|
||||
tooltip?: React.ReactNode;
|
||||
label?: React.ReactNode;
|
||||
label?: string;
|
||||
tooltipClassName?: string;
|
||||
forceHide?: boolean;
|
||||
yOffset?: number;
|
||||
|
@ -36,7 +35,6 @@ interface IState {
|
|||
hover: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.elements.AccessibleTooltipButton")
|
||||
export default class AccessibleTooltipButton extends React.PureComponent<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue