Merge branch 'develop' into gsouquet/fix-18144

This commit is contained in:
Germain Souquet 2021-07-26 09:25:31 +02:00
commit 468887415a
119 changed files with 830 additions and 460 deletions

View file

@ -234,6 +234,9 @@ $SpaceRoomViewInnerWidth: 428px;
} }
.mx_SpaceRoomView_landing { .mx_SpaceRoomView_landing {
display: flex;
flex-direction: column;
> .mx_BaseAvatar_image, > .mx_BaseAvatar_image,
> .mx_BaseAvatar > .mx_BaseAvatar_image { > .mx_BaseAvatar > .mx_BaseAvatar_image {
border-radius: 12px; border-radius: 12px;
@ -340,6 +343,7 @@ $SpaceRoomViewInnerWidth: 428px;
.mx_SearchBox { .mx_SearchBox {
margin: 0 0 20px; margin: 0 0 20px;
flex: 0;
} }
.mx_SpaceFeedbackPrompt { .mx_SpaceFeedbackPrompt {
@ -350,6 +354,11 @@ $SpaceRoomViewInnerWidth: 428px;
display: none; display: none;
} }
} }
.mx_SpaceRoomDirectory_list {
// we don't want this container to get forced into the flexbox layout
display: contents;
}
} }
.mx_SpaceRoomView_privateScope { .mx_SpaceRoomView_privateScope {

View file

@ -27,7 +27,6 @@ limitations under the License.
// https://bugzilla.mozilla.org/show_bug.cgi?id=255139 // https://bugzilla.mozilla.org/show_bug.cgi?id=255139
display: inline-block; display: inline-block;
user-select: none; user-select: none;
line-height: 1;
} }
.mx_BaseAvatar_initial { .mx_BaseAvatar_initial {

View file

@ -16,10 +16,6 @@ limitations under the License.
$timelineImageBorderRadius: 4px; $timelineImageBorderRadius: 4px;
.mx_MImageBody {
display: block;
}
.mx_MImageBody_thumbnail { .mx_MImageBody_thumbnail {
object-fit: contain; object-fit: contain;
border-radius: $timelineImageBorderRadius; border-radius: $timelineImageBorderRadius;
@ -28,7 +24,7 @@ $timelineImageBorderRadius: 4px;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
> canvas { > div > canvas {
border-radius: $timelineImageBorderRadius; border-radius: $timelineImageBorderRadius;
} }
} }

View file

@ -156,12 +156,24 @@ limitations under the License.
position: absolute; position: absolute;
top: 0; top: 0;
line-height: 1; line-height: 1;
z-index: 9;
img { img {
box-shadow: 0 0 0 3px $eventbubble-avatar-outline; box-shadow: 0 0 0 3px $eventbubble-avatar-outline;
border-radius: 50%; border-radius: 50%;
} }
} }
&.mx_EventTile_noSender {
.mx_EventTile_avatar {
top: -19px;
}
}
.mx_BaseAvatar,
.mx_EventTile_avatar {
line-height: 1;
}
&[data-has-reply=true] { &[data-has-reply=true] {
> .mx_EventTile_line { > .mx_EventTile_line {
flex-direction: column; flex-direction: column;

View file

@ -116,6 +116,11 @@ $irc-line-height: $font-18px;
.mx_EditMessageComposer_buttons { .mx_EditMessageComposer_buttons {
position: relative; position: relative;
} }
.mx_ReactionsRow {
padding-left: 0;
padding-right: 0;
}
} }
.mx_EventTile_emote { .mx_EventTile_emote {

View file

@ -51,10 +51,15 @@ export async function startAnyRegistrationFlow(options) {
description: _t("Use your account or create a new one to continue."), description: _t("Use your account or create a new one to continue."),
button: _t("Create Account"), button: _t("Create Account"),
extraButtons: [ extraButtons: [
<button key="start_login" onClick={() => { <button
modal.close(); key="start_login"
dis.dispatch({ action: 'start_login', screenAfterLogin: options.screen_after }); onClick={() => {
}}>{ _t('Sign In') }</button>, modal.close();
dis.dispatch({ action: 'start_login', screenAfterLogin: options.screen_after });
}}
>
{ _t('Sign In') }
</button>,
], ],
onFinished: (proceed) => { onFinished: (proceed) => {
if (proceed) { if (proceed) {

View file

@ -269,7 +269,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
<details> <details>
<summary>{ _t("Advanced") }</summary> <summary>{ _t("Advanced") }</summary>
<AccessibleButton kind='primary' onClick={this._onSkipPassPhraseClick} > <AccessibleButton kind='primary' onClick={this._onSkipPassPhraseClick}>
{ _t("Set up with a Security Key") } { _t("Set up with a Security Key") }
</AccessibleButton> </AccessibleButton>
</details> </details>

View file

@ -474,7 +474,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
outlined outlined
> >
<div className="mx_CreateSecretStorageDialog_optionTitle"> <div className="mx_CreateSecretStorageDialog_optionTitle">
<span className="mx_CreateSecretStorageDialog_optionIcon mx_CreateSecretStorageDialog_optionIcon_secureBackup"></span> <span className="mx_CreateSecretStorageDialog_optionIcon mx_CreateSecretStorageDialog_optionIcon_secureBackup" />
{ _t("Generate a Security Key") } { _t("Generate a Security Key") }
</div> </div>
<div>{ _t("Well generate a Security Key for you to store somewhere safe, like a password manager or a safe.") }</div> <div>{ _t("Well generate a Security Key for you to store somewhere safe, like a password manager or a safe.") }</div>
@ -493,7 +493,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
outlined outlined
> >
<div className="mx_CreateSecretStorageDialog_optionTitle"> <div className="mx_CreateSecretStorageDialog_optionTitle">
<span className="mx_CreateSecretStorageDialog_optionIcon mx_CreateSecretStorageDialog_optionIcon_securePhrase"></span> <span className="mx_CreateSecretStorageDialog_optionIcon mx_CreateSecretStorageDialog_optionIcon_securePhrase" />
{ _t("Enter a Security Phrase") } { _t("Enter a Security Phrase") }
</div> </div>
<div>{ _t("Use a secret phrase only you know, and optionally save a Security Key to use for backup.") }</div> <div>{ _t("Use a secret phrase only you know, and optionally save a Security Key to use for backup.") }</div>
@ -701,7 +701,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
<code ref={this._collectRecoveryKeyNode}>{ this._recoveryKey.encodedPrivateKey }</code> <code ref={this._collectRecoveryKeyNode}>{ this._recoveryKey.encodedPrivateKey }</code>
</div> </div>
<div className="mx_CreateSecretStorageDialog_recoveryKeyButtons"> <div className="mx_CreateSecretStorageDialog_recoveryKeyButtons">
<AccessibleButton kind='primary' className="mx_Dialog_primary" <AccessibleButton kind='primary'
className="mx_Dialog_primary"
onClick={this._onDownloadClick} onClick={this._onDownloadClick}
disabled={this.state.phase === PHASE_STORING} disabled={this.state.phase === PHASE_STORING}
> >

View file

@ -148,8 +148,12 @@ export default class ExportE2eKeysDialog extends React.Component {
</label> </label>
</div> </div>
<div className='mx_E2eKeysDialog_inputCell'> <div className='mx_E2eKeysDialog_inputCell'>
<input ref={this._passphrase1} id='passphrase1' <input
autoFocus={true} size='64' type='password' ref={this._passphrase1}
id='passphrase1'
autoFocus={true}
size='64'
type='password'
disabled={disableForm} disabled={disableForm}
/> />
</div> </div>
@ -161,8 +165,10 @@ export default class ExportE2eKeysDialog extends React.Component {
</label> </label>
</div> </div>
<div className='mx_E2eKeysDialog_inputCell'> <div className='mx_E2eKeysDialog_inputCell'>
<input ref={this._passphrase2} id='passphrase2' <input ref={this._passphrase2}
size='64' type='password' id='passphrase2'
size='64'
type='password'
disabled={disableForm} disabled={disableForm}
/> />
</div> </div>

View file

@ -174,7 +174,10 @@ export default class ImportE2eKeysDialog extends React.Component {
</div> </div>
</div> </div>
<div className='mx_Dialog_buttons'> <div className='mx_Dialog_buttons'>
<input className='mx_Dialog_primary' type='submit' value={_t('Import')} <input
className='mx_Dialog_primary'
type='submit'
value={_t('Import')}
disabled={!this.state.enableSubmit || disableForm} disabled={!this.state.enableSubmit || disableForm}
/> />
<button onClick={this._onCancelClick} disabled={disableForm}> <button onClick={this._onCancelClick} disabled={disableForm}>

View file

@ -120,8 +120,7 @@ export default class EmbeddedPage extends React.PureComponent {
const content = <div className={`${className}_body`} const content = <div className={`${className}_body`}
dangerouslySetInnerHTML={{ __html: this.state.page }} dangerouslySetInnerHTML={{ __html: this.state.page }}
> />;
</div>;
if (this.props.scrollbar) { if (this.props.scrollbar) {
return <AutoHideScrollbar className={classes}> return <AutoHideScrollbar className={classes}>

View file

@ -222,7 +222,7 @@ class FeaturedRoom extends React.Component {
let roomNameNode = null; let roomNameNode = null;
if (permalink) { if (permalink) {
roomNameNode = <a href={permalink} onClick={this.onClick} >{ roomName }</a>; roomNameNode = <a href={permalink} onClick={this.onClick}>{ roomName }</a>;
} else { } else {
roomNameNode = <span>{ roomName }</span>; roomNameNode = <span>{ roomName }</span>;
} }
@ -1185,10 +1185,13 @@ export default class GroupView extends React.Component {
avatarImage = <Spinner />; avatarImage = <Spinner />;
} else { } else {
const GroupAvatar = sdk.getComponent('avatars.GroupAvatar'); const GroupAvatar = sdk.getComponent('avatars.GroupAvatar');
avatarImage = <GroupAvatar groupId={this.props.groupId} avatarImage = <GroupAvatar
groupId={this.props.groupId}
groupName={this.state.profileForm.name} groupName={this.state.profileForm.name}
groupAvatarUrl={this.state.profileForm.avatar_url} groupAvatarUrl={this.state.profileForm.avatar_url}
width={28} height={28} resizeMethod='crop' width={28}
height={28}
resizeMethod='crop'
/>; />;
} }
@ -1199,9 +1202,12 @@ export default class GroupView extends React.Component {
</label> </label>
<div className="mx_GroupView_avatarPicker_edit"> <div className="mx_GroupView_avatarPicker_edit">
<label htmlFor="avatarInput" className="mx_GroupView_avatarPicker_label"> <label htmlFor="avatarInput" className="mx_GroupView_avatarPicker_label">
<img src={require("../../../res/img/camera.svg")} <img
alt={_t("Upload avatar")} title={_t("Upload avatar")} src={require("../../../res/img/camera.svg")}
width="17" height="15" /> alt={_t("Upload avatar")}
title={_t("Upload avatar")}
width="17"
height="15" />
</label> </label>
<input id="avatarInput" className="mx_GroupView_uploadInput" type="file" onChange={this._onAvatarSelected} /> <input id="avatarInput" className="mx_GroupView_uploadInput" type="file" onChange={this._onAvatarSelected} />
</div> </div>
@ -1238,7 +1244,8 @@ export default class GroupView extends React.Component {
groupAvatarUrl={groupAvatarUrl} groupAvatarUrl={groupAvatarUrl}
groupName={groupName} groupName={groupName}
onClick={onGroupHeaderItemClick} onClick={onGroupHeaderItemClick}
width={28} height={28} width={28}
height={28}
/>; />;
if (summary.profile && summary.profile.name) { if (summary.profile && summary.profile.name) {
nameNode = <div onClick={onGroupHeaderItemClick}> nameNode = <div onClick={onGroupHeaderItemClick}>
@ -1269,28 +1276,32 @@ export default class GroupView extends React.Component {
key="_cancelButton" key="_cancelButton"
onClick={this._onCancelClick} onClick={this._onCancelClick}
> >
<img src={require("../../../res/img/cancel.svg")} className="mx_filterFlipColor" <img
width="18" height="18" alt={_t("Cancel")} /> src={require("../../../res/img/cancel.svg")}
className="mx_filterFlipColor"
width="18"
height="18"
alt={_t("Cancel")} />
</AccessibleButton>, </AccessibleButton>,
); );
} else { } else {
if (summary.user && summary.user.membership === 'join') { if (summary.user && summary.user.membership === 'join') {
rightButtons.push( rightButtons.push(
<AccessibleButton className="mx_GroupHeader_button mx_GroupHeader_editButton" <AccessibleButton
className="mx_GroupHeader_button mx_GroupHeader_editButton"
key="_editButton" key="_editButton"
onClick={this._onEditClick} onClick={this._onEditClick}
title={_t("Community Settings")} title={_t("Community Settings")}
> />,
</AccessibleButton>,
); );
} }
rightButtons.push( rightButtons.push(
<AccessibleButton className="mx_GroupHeader_button mx_GroupHeader_shareButton" <AccessibleButton
className="mx_GroupHeader_button mx_GroupHeader_shareButton"
key="_shareButton" key="_shareButton"
onClick={this._onShareClick} onClick={this._onShareClick}
title={_t('Share Community')} title={_t('Share Community')}
> />,
</AccessibleButton>,
); );
} }

View file

@ -109,8 +109,7 @@ export default class MyGroups extends React.Component {
<SimpleRoomHeader title={_t("Communities")} icon={require("../../../res/img/icons-groups.svg")} /> <SimpleRoomHeader title={_t("Communities")} icon={require("../../../res/img/icons-groups.svg")} />
<div className='mx_MyGroups_header'> <div className='mx_MyGroups_header'>
<div className="mx_MyGroups_headerCard"> <div className="mx_MyGroups_headerCard">
<AccessibleButton className='mx_MyGroups_headerCard_button' onClick={this._onCreateGroupClick}> <AccessibleButton className='mx_MyGroups_headerCard_button' onClick={this._onCreateGroupClick} />
</AccessibleButton>
<div className="mx_MyGroups_headerCard_content"> <div className="mx_MyGroups_headerCard_content">
<div className="mx_MyGroups_headerCard_header"> <div className="mx_MyGroups_headerCard_header">
{ _t('Create a new community') } { _t('Create a new community') }

View file

@ -266,8 +266,12 @@ export default class RoomStatusBar extends React.PureComponent {
<div className="mx_RoomStatusBar"> <div className="mx_RoomStatusBar">
<div role="alert"> <div role="alert">
<div className="mx_RoomStatusBar_connectionLostBar"> <div className="mx_RoomStatusBar_connectionLostBar">
<img src={require("../../../res/img/feather-customised/warning-triangle.svg")} width="24" <img
height="24" title="/!\ " alt="/!\ " /> src={require("../../../res/img/feather-customised/warning-triangle.svg")}
width="24"
height="24"
title="/!\ "
alt="/!\ " />
<div> <div>
<div className="mx_RoomStatusBar_connectionLostBar_title"> <div className="mx_RoomStatusBar_connectionLostBar_title">
{ _t('Connectivity to the server has been lost.') } { _t('Connectivity to the server has been lost.') }

View file

@ -1740,7 +1740,8 @@ export default class RoomView extends React.Component<IProps, IState> {
onJoinClick={this.onJoinButtonClicked} onJoinClick={this.onJoinButtonClicked}
onForgetClick={this.onForgetClick} onForgetClick={this.onForgetClick}
onRejectClick={this.onRejectThreepidInviteButtonClicked} onRejectClick={this.onRejectThreepidInviteButtonClicked}
canPreview={false} error={this.state.roomLoadError} canPreview={false}
error={this.state.roomLoadError}
roomAlias={roomAlias} roomAlias={roomAlias}
joining={this.state.joining} joining={this.state.joining}
inviterName={inviterName} inviterName={inviterName}

View file

@ -136,8 +136,8 @@ export default class SearchBox extends React.Component {
key="button" key="button"
tabIndex={-1} tabIndex={-1}
className="mx_SearchBox_closeButton" className="mx_SearchBox_closeButton"
onClick={() => {this._clearSearch("button"); }}> onClick={() => {this._clearSearch("button"); }}
</AccessibleButton>) : undefined; />) : undefined;
// show a shorter placeholder when blurred, if requested // show a shorter placeholder when blurred, if requested
// this is used for the room filter field that has // this is used for the room filter field that has

View file

@ -100,12 +100,14 @@ export const SpaceFeedbackPrompt = ({ onClick }: { onClick?: () => void }) => {
<hr /> <hr />
<div> <div>
<span className="mx_SpaceFeedbackPrompt_text">{ _t("Spaces are a beta feature.") }</span> <span className="mx_SpaceFeedbackPrompt_text">{ _t("Spaces are a beta feature.") }</span>
<AccessibleButton kind="link" onClick={() => { <AccessibleButton
if (onClick) onClick(); kind="link"
Modal.createTrackedDialog("Beta Feedback", "feature_spaces", BetaFeedbackDialog, { onClick={() => {
if (onClick) onClick();
Modal.createTrackedDialog("Beta Feedback", "feature_spaces", BetaFeedbackDialog, {
featureId: "feature_spaces", featureId: "feature_spaces",
}); });
}}> }}>
{ _t("Feedback") } { _t("Feedback") }
</AccessibleButton> </AccessibleButton>
</div> </div>
@ -551,9 +553,7 @@ const SpaceAddExistingRooms = ({ space, onFinished }) => {
onFinished={onFinished} onFinished={onFinished}
/> />
<div className="mx_SpaceRoomView_buttons"> <div className="mx_SpaceRoomView_buttons" />
</div>
<SpaceFeedbackPrompt /> <SpaceFeedbackPrompt />
</div>; </div>;
}; };

View file

@ -315,7 +315,10 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
{ _t("An email has been sent to %(emailAddress)s. Once you've followed the " + { _t("An email has been sent to %(emailAddress)s. Once you've followed the " +
"link it contains, click below.", { emailAddress: this.state.email }) } "link it contains, click below.", { emailAddress: this.state.email }) }
<br /> <br />
<input className="mx_Login_submit" type="button" onClick={this.onVerify} <input
className="mx_Login_submit"
type="button"
onClick={this.onVerify}
value={_t('I have verified my email address')} /> value={_t('I have verified my email address')} />
</div>; </div>;
} }
@ -328,7 +331,10 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
"push notifications. To re-enable notifications, sign in again on each " + "push notifications. To re-enable notifications, sign in again on each " +
"device.", "device.",
) }</p> ) }</p>
<input className="mx_Login_submit" type="button" onClick={this.props.onComplete} <input
className="mx_Login_submit"
type="button"
onClick={this.props.onComplete}
value={_t('Return to login screen')} /> value={_t('Return to login screen')} />
</div>; </div>;
} }

View file

@ -463,7 +463,9 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
"Either use HTTPS or <a>enable unsafe scripts</a>.", {}, "Either use HTTPS or <a>enable unsafe scripts</a>.", {},
{ {
'a': (sub) => { 'a': (sub) => {
return <a target="_blank" rel="noreferrer noopener" return <a
target="_blank"
rel="noreferrer noopener"
href="https://www.google.com/search?&q=enable%20unsafe%20scripts" href="https://www.google.com/search?&q=enable%20unsafe%20scripts"
> >
{ sub } { sub }

View file

@ -557,12 +557,16 @@ export default class Registration extends React.Component<IProps, IState> {
loggedInUserId: this.state.differentLoggedInUserId, loggedInUserId: this.state.differentLoggedInUserId,
}, },
) }</p> ) }</p>
<p><AccessibleButton element="span" className="mx_linkButton" onClick={async event => { <p><AccessibleButton
const sessionLoaded = await this.onLoginClickWithCheck(event); element="span"
if (sessionLoaded) { className="mx_linkButton"
dis.dispatch({ action: "view_welcome_page" }); onClick={async event => {
} const sessionLoaded = await this.onLoginClickWithCheck(event);
}}> if (sessionLoaded) {
dis.dispatch({ action: "view_welcome_page" });
}
}}
>
{ _t("Continue with previous account") } { _t("Continue with previous account") }
</AccessibleButton></p> </AccessibleButton></p>
</div>; </div>;

View file

@ -14,9 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { Playback, PlaybackState } from "../../../voice/Playback";
import React, { createRef, ReactNode, RefObject } from "react"; import React, { createRef, ReactNode, RefObject } from "react";
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
import PlayPauseButton from "./PlayPauseButton"; import PlayPauseButton from "./PlayPauseButton";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import { formatBytes } from "../../../utils/FormattingUtils"; import { formatBytes } from "../../../utils/FormattingUtils";
@ -25,47 +23,13 @@ import { Key } from "../../../Keyboard";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import SeekBar from "./SeekBar"; import SeekBar from "./SeekBar";
import PlaybackClock from "./PlaybackClock"; import PlaybackClock from "./PlaybackClock";
import AudioPlayerBase from "./AudioPlayerBase";
interface IProps {
// Playback instance to render. Cannot change during component lifecycle: create
// an all-new component instead.
playback: Playback;
mediaName: string;
}
interface IState {
playbackPhase: PlaybackState;
error?: boolean;
}
@replaceableComponent("views.audio_messages.AudioPlayer") @replaceableComponent("views.audio_messages.AudioPlayer")
export default class AudioPlayer extends React.PureComponent<IProps, IState> { export default class AudioPlayer extends AudioPlayerBase {
private playPauseRef: RefObject<PlayPauseButton> = createRef(); private playPauseRef: RefObject<PlayPauseButton> = createRef();
private seekRef: RefObject<SeekBar> = createRef(); private seekRef: RefObject<SeekBar> = createRef();
constructor(props: IProps) {
super(props);
this.state = {
playbackPhase: PlaybackState.Decoding, // default assumption
};
// We don't need to de-register: the class handles this for us internally
this.props.playback.on(UPDATE_EVENT, this.onPlaybackUpdate);
// Don't wait for the promise to complete - it will emit a progress update when it
// is done, and it's not meant to take long anyhow.
this.props.playback.prepare().catch(e => {
console.error("Error processing audio file:", e);
this.setState({ error: true });
});
}
private onPlaybackUpdate = (ev: PlaybackState) => {
this.setState({ playbackPhase: ev });
};
private onKeyDown = (ev: React.KeyboardEvent) => { private onKeyDown = (ev: React.KeyboardEvent) => {
// stopPropagation() prevents the FocusComposer catch-all from triggering, // stopPropagation() prevents the FocusComposer catch-all from triggering,
// but we need to do it on key down instead of press (even though the user // but we need to do it on key down instead of press (even though the user
@ -91,10 +55,10 @@ export default class AudioPlayer extends React.PureComponent<IProps, IState> {
return `(${formatBytes(bytes)})`; return `(${formatBytes(bytes)})`;
} }
public render(): ReactNode { protected renderComponent(): ReactNode {
// tabIndex=0 to ensure that the whole component becomes a tab stop, where we handle keyboard // tabIndex=0 to ensure that the whole component becomes a tab stop, where we handle keyboard
// events for accessibility // events for accessibility
return <> return (
<div className='mx_MediaBody mx_AudioPlayer_container' tabIndex={0} onKeyDown={this.onKeyDown}> <div className='mx_MediaBody mx_AudioPlayer_container' tabIndex={0} onKeyDown={this.onKeyDown}>
<div className='mx_AudioPlayer_primaryContainer'> <div className='mx_AudioPlayer_primaryContainer'>
<PlayPauseButton <PlayPauseButton
@ -124,7 +88,6 @@ export default class AudioPlayer extends React.PureComponent<IProps, IState> {
<PlaybackClock playback={this.props.playback} defaultDisplaySeconds={0} /> <PlaybackClock playback={this.props.playback} defaultDisplaySeconds={0} />
</div> </div>
</div> </div>
{ this.state.error && <div className="text-warning">{ _t("Error downloading audio") }</div> } );
</>;
} }
} }

View file

@ -0,0 +1,70 @@
/*
Copyright 2021 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 { Playback, PlaybackState } from "../../../audio/Playback";
import { TileShape } from "../rooms/EventTile";
import React, { ReactNode } from "react";
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { _t } from "../../../languageHandler";
interface IProps {
// Playback instance to render. Cannot change during component lifecycle: create
// an all-new component instead.
playback: Playback;
mediaName?: string;
tileShape?: TileShape;
}
interface IState {
playbackPhase: PlaybackState;
error?: boolean;
}
@replaceableComponent("views.audio_messages.AudioPlayerBase")
export default abstract class AudioPlayerBase extends React.PureComponent<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
playbackPhase: PlaybackState.Decoding, // default assumption
};
// We don't need to de-register: the class handles this for us internally
this.props.playback.on(UPDATE_EVENT, this.onPlaybackUpdate);
// Don't wait for the promise to complete - it will emit a progress update when it
// is done, and it's not meant to take long anyhow.
this.props.playback.prepare().catch(e => {
console.error("Error processing audio file:", e);
this.setState({ error: true });
});
}
private onPlaybackUpdate = (ev: PlaybackState) => {
this.setState({ playbackPhase: ev });
};
protected abstract renderComponent(): ReactNode;
public render(): ReactNode {
return <>
{ this.renderComponent() }
{ this.state.error && <div className="text-warning">{ _t("Error downloading audio") }</div> }
</>;
}
}

View file

@ -17,7 +17,7 @@ limitations under the License.
import React from "react"; import React from "react";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import Clock from "./Clock"; import Clock from "./Clock";
import { Playback } from "../../../voice/Playback"; import { Playback } from "../../../audio/Playback";
interface IProps { interface IProps {
playback: Playback; playback: Playback;

View file

@ -15,7 +15,7 @@ limitations under the License.
*/ */
import React from "react"; import React from "react";
import { IRecordingUpdate, VoiceRecording } from "../../../voice/VoiceRecording"; import { IRecordingUpdate, VoiceRecording } from "../../../audio/VoiceRecording";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import Clock from "./Clock"; import Clock from "./Clock";
import { MarkedExecution } from "../../../utils/MarkedExecution"; import { MarkedExecution } from "../../../utils/MarkedExecution";

View file

@ -15,7 +15,7 @@ limitations under the License.
*/ */
import React from "react"; import React from "react";
import { IRecordingUpdate, RECORDING_PLAYBACK_SAMPLES, VoiceRecording } from "../../../voice/VoiceRecording"; import { IRecordingUpdate, RECORDING_PLAYBACK_SAMPLES, VoiceRecording } from "../../../audio/VoiceRecording";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import { arrayFastResample } from "../../../utils/arrays"; import { arrayFastResample } from "../../../utils/arrays";
import { percentageOf } from "../../../utils/numbers"; import { percentageOf } from "../../../utils/numbers";

View file

@ -18,7 +18,7 @@ import React, { ReactNode } from "react";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import { Playback, PlaybackState } from "../../../voice/Playback"; import { Playback, PlaybackState } from "../../../audio/Playback";
import classNames from "classnames"; import classNames from "classnames";
// omitted props are handled by render function // omitted props are handled by render function

View file

@ -17,7 +17,7 @@ limitations under the License.
import React from "react"; import React from "react";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import Clock from "./Clock"; import Clock from "./Clock";
import { Playback, PlaybackState } from "../../../voice/Playback"; import { Playback, PlaybackState } from "../../../audio/Playback";
import { UPDATE_EVENT } from "../../../stores/AsyncStore"; import { UPDATE_EVENT } from "../../../stores/AsyncStore";
interface IProps { interface IProps {

View file

@ -18,7 +18,7 @@ import React from "react";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import { arraySeed, arrayTrimFill } from "../../../utils/arrays"; import { arraySeed, arrayTrimFill } from "../../../utils/arrays";
import Waveform from "./Waveform"; import Waveform from "./Waveform";
import { Playback, PLAYBACK_WAVEFORM_SAMPLES } from "../../../voice/Playback"; import { Playback, PLAYBACK_WAVEFORM_SAMPLES } from "../../../audio/Playback";
import { percentageOf } from "../../../utils/numbers"; import { percentageOf } from "../../../utils/numbers";
interface IProps { interface IProps {

View file

@ -14,68 +14,30 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { Playback, PlaybackState } from "../../../voice/Playback";
import React, { ReactNode } from "react"; import React, { ReactNode } from "react";
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
import PlayPauseButton from "./PlayPauseButton"; import PlayPauseButton from "./PlayPauseButton";
import PlaybackClock from "./PlaybackClock"; import PlaybackClock from "./PlaybackClock";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import { TileShape } from "../rooms/EventTile"; import { TileShape } from "../rooms/EventTile";
import PlaybackWaveform from "./PlaybackWaveform"; import PlaybackWaveform from "./PlaybackWaveform";
import { _t } from "../../../languageHandler"; import AudioPlayerBase from "./AudioPlayerBase";
interface IProps {
// Playback instance to render. Cannot change during component lifecycle: create
// an all-new component instead.
playback: Playback;
tileShape?: TileShape;
}
interface IState {
playbackPhase: PlaybackState;
error?: boolean;
}
@replaceableComponent("views.audio_messages.RecordingPlayback") @replaceableComponent("views.audio_messages.RecordingPlayback")
export default class RecordingPlayback extends React.PureComponent<IProps, IState> { export default class RecordingPlayback extends AudioPlayerBase {
constructor(props: IProps) {
super(props);
this.state = {
playbackPhase: PlaybackState.Decoding, // default assumption
};
// We don't need to de-register: the class handles this for us internally
this.props.playback.on(UPDATE_EVENT, this.onPlaybackUpdate);
// Don't wait for the promise to complete - it will emit a progress update when it
// is done, and it's not meant to take long anyhow.
this.props.playback.prepare().catch(e => {
console.error("Error processing audio file:", e);
this.setState({ error: true });
});
}
private get isWaveformable(): boolean { private get isWaveformable(): boolean {
return this.props.tileShape !== TileShape.Notif return this.props.tileShape !== TileShape.Notif
&& this.props.tileShape !== TileShape.FileGrid && this.props.tileShape !== TileShape.FileGrid
&& this.props.tileShape !== TileShape.Pinned; && this.props.tileShape !== TileShape.Pinned;
} }
private onPlaybackUpdate = (ev: PlaybackState) => { protected renderComponent(): ReactNode {
this.setState({ playbackPhase: ev });
};
public render(): ReactNode {
const shapeClass = !this.isWaveformable ? 'mx_VoiceMessagePrimaryContainer_noWaveform' : ''; const shapeClass = !this.isWaveformable ? 'mx_VoiceMessagePrimaryContainer_noWaveform' : '';
return <> return (
<div className={'mx_MediaBody mx_VoiceMessagePrimaryContainer ' + shapeClass}> <div className={'mx_MediaBody mx_VoiceMessagePrimaryContainer ' + shapeClass}>
<PlayPauseButton playback={this.props.playback} playbackPhase={this.state.playbackPhase} /> <PlayPauseButton playback={this.props.playback} playbackPhase={this.state.playbackPhase} />
<PlaybackClock playback={this.props.playback} /> <PlaybackClock playback={this.props.playback} />
{ this.isWaveformable && <PlaybackWaveform playback={this.props.playback} /> } { this.isWaveformable && <PlaybackWaveform playback={this.props.playback} /> }
</div> </div>
{ this.state.error && <div className="text-warning">{ _t("Error downloading audio") }</div> } );
</>;
} }
} }

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { Playback, PlaybackState } from "../../../voice/Playback"; import { Playback, PlaybackState } from "../../../audio/Playback";
import React, { ChangeEvent, CSSProperties, ReactNode } from "react"; import React, { ChangeEvent, CSSProperties, ReactNode } from "react";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import { MarkedExecution } from "../../../utils/MarkedExecution"; import { MarkedExecution } from "../../../utils/MarkedExecution";

View file

@ -54,9 +54,13 @@ export default class Waveform extends React.PureComponent<IProps, IState> {
'mx_Waveform_bar': true, 'mx_Waveform_bar': true,
'mx_Waveform_bar_100pct': isCompleteBar, 'mx_Waveform_bar_100pct': isCompleteBar,
}); });
return <span key={i} style={{ return <span
"--barHeight": h, key={i}
} as WaveformCSSProperties} className={classes} />; style={{
"--barHeight": h,
} as WaveformCSSProperties}
className={classes}
/>;
}) } }) }
</div>; </div>;
} }

View file

@ -416,8 +416,10 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
let submitButton; let submitButton;
if (this.props.showContinue !== false) { if (this.props.showContinue !== false) {
// XXX: button classes // XXX: button classes
submitButton = <button className="mx_InteractiveAuthEntryComponents_termsSubmit mx_GeneralButton" submitButton = <button
onClick={this.trySubmit} disabled={!allChecked}>{ _t("Accept") }</button>; className="mx_InteractiveAuthEntryComponents_termsSubmit mx_GeneralButton"
onClick={this.trySubmit}
disabled={!allChecked}>{ _t("Accept") }</button>;
} }
return ( return (
@ -616,7 +618,9 @@ export class MsisdnAuthEntry extends React.Component<IMsisdnAuthEntryProps, IMsi
aria-label={_t("Code")} aria-label={_t("Code")}
/> />
<br /> <br />
<input type="submit" value={_t("Submit")} <input
type="submit"
value={_t("Submit")}
className={submitClasses} className={submitClasses}
disabled={!enableSubmit} disabled={!enableSubmit}
/> />

View file

@ -187,7 +187,8 @@ const BaseAvatar = (props: IProps) => {
width: toPx(width), width: toPx(width),
height: toPx(height), height: toPx(height),
}} }}
title={title} alt={_t("Avatar")} title={title}
alt={_t("Avatar")}
inputRef={inputRef} inputRef={inputRef}
{...otherProps} /> {...otherProps} />
); );
@ -201,7 +202,8 @@ const BaseAvatar = (props: IProps) => {
width: toPx(width), width: toPx(width),
height: toPx(height), height: toPx(height),
}} }}
title={title} alt="" title={title}
alt=""
ref={inputRef} ref={inputRef}
{...otherProps} /> {...otherProps} />
); );

View file

@ -102,8 +102,12 @@ export default class MemberAvatar extends React.Component<IProps, IState> {
} }
return ( return (
<BaseAvatar {...otherProps} name={this.state.name} title={this.state.title} <BaseAvatar {...otherProps}
idName={userId} url={this.state.imageUrl} onClick={onClick} /> name={this.state.name}
title={this.state.title}
idName={userId}
url={this.state.imageUrl}
onClick={onClick} />
); );
} }
} }

View file

@ -60,8 +60,10 @@ export default class DialpadContextMenu extends React.Component<IProps, IState>
<AccessibleButton className="mx_DialPadContextMenu_cancel" onClick={this.onCancelClick} /> <AccessibleButton className="mx_DialPadContextMenu_cancel" onClick={this.onCancelClick} />
</div> </div>
<div className="mx_DialPadContextMenu_header"> <div className="mx_DialPadContextMenu_header">
<Field className="mx_DialPadContextMenu_dialled" <Field
value={this.state.value} autoFocus={true} className="mx_DialPadContextMenu_dialled"
value={this.state.value}
autoFocus={true}
onChange={this.onChange} onChange={this.onChange}
/> />
</div> </div>

View file

@ -109,8 +109,10 @@ export default class StatusMessageContextMenu extends React.Component {
</AccessibleButton>; </AccessibleButton>;
} }
} else { } else {
actionButton = <AccessibleButton className="mx_StatusMessageContextMenu_submit" actionButton = <AccessibleButton
disabled={!this.state.message} onClick={this._onSubmit} className="mx_StatusMessageContextMenu_submit"
disabled={!this.state.message}
onClick={this._onSubmit}
> >
<span>{ _t("Set status") }</span> <span>{ _t("Set status") }</span>
</AccessibleButton>; </AccessibleButton>;
@ -121,12 +123,19 @@ export default class StatusMessageContextMenu extends React.Component {
spinner = <Spinner w="24" h="24" />; spinner = <Spinner w="24" h="24" />;
} }
const form = <form className="mx_StatusMessageContextMenu_form" const form = <form
autoComplete="off" onSubmit={this._onSubmit} className="mx_StatusMessageContextMenu_form"
autoComplete="off"
onSubmit={this._onSubmit}
> >
<input type="text" className="mx_StatusMessageContextMenu_message" <input
key="message" placeholder={_t("Set a new status...")} type="text"
autoFocus={true} maxLength="60" value={this.state.message} className="mx_StatusMessageContextMenu_message"
key="message"
placeholder={_t("Set a new status...")}
autoFocus={true}
maxLength="60"
value={this.state.message}
onChange={this._onStatusChange} onChange={this._onStatusChange}
/> />
<div className="mx_StatusMessageContextMenu_actionContainer"> <div className="mx_StatusMessageContextMenu_actionContainer">

View file

@ -76,7 +76,8 @@ const WidgetContextMenu: React.FC<IProps> = ({
onFinished(); onFinished();
}; };
streamAudioStreamButton = <IconizedContextMenuOption streamAudioStreamButton = <IconizedContextMenuOption
onClick={onStreamAudioClick} label={_t("Start audio stream")} onClick={onStreamAudioClick}
label={_t("Start audio stream")}
/>; />;
} }

View file

@ -209,10 +209,16 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
function overflowTile(overflowCount, totalCount) { function overflowTile(overflowCount, totalCount) {
const text = _t("and %(count)s others...", { count: overflowCount }); const text = _t("and %(count)s others...", { count: overflowCount });
return ( return (
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={ <EntityTile
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} /> className="mx_EntityTile_ellipsis"
} name={text} presenceState="online" suppressOnHover={true} avatarJsx={
onClick={() => setTruncateAt(totalCount)} /> <BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
}
name={text}
presenceState="online"
suppressOnHover={true}
onClick={() => setTruncateAt(totalCount)}
/>
); );
} }

View file

@ -665,8 +665,8 @@ export default class AddressPickerDialog extends React.Component<IProps, IState>
onChange={this.onQueryChanged} onChange={this.onQueryChanged}
placeholder={this.getPlaceholder()} placeholder={this.getPlaceholder()}
defaultValue={this.props.value} defaultValue={this.props.value}
autoFocus={this.props.focus}> autoFocus={this.props.focus}
</textarea>, />,
); );
const filteredSuggestedList = this.getFilteredSuggestions(); const filteredSuggestedList = this.getFilteredSuggestions();
@ -727,8 +727,12 @@ export default class AddressPickerDialog extends React.Component<IProps, IState>
} }
return ( return (
<BaseDialog className="mx_AddressPickerDialog" onKeyDown={this.onKeyDown} <BaseDialog
onFinished={this.props.onFinished} title={this.props.title}> className="mx_AddressPickerDialog"
onKeyDown={this.onKeyDown}
onFinished={this.props.onFinished}
title={this.props.title}
>
{ inputLabel } { inputLabel }
<div className="mx_Dialog_content"> <div className="mx_Dialog_content">
<div className="mx_AddressPickerDialog_inputContainer">{ query }</div> <div className="mx_AddressPickerDialog_inputContainer">{ query }</div>

View file

@ -118,9 +118,7 @@ export default class BaseDialog extends React.Component {
let headerImage; let headerImage;
if (this.props.headerImage) { if (this.props.headerImage) {
headerImage = <img className="mx_Dialog_titleImage" src={this.props.headerImage} headerImage = <img className="mx_Dialog_titleImage" src={this.props.headerImage} alt="" />;
alt=""
/>;
} }
return ( return (

View file

@ -71,13 +71,16 @@ const BetaFeedbackDialog: React.FC<IProps> = ({ featureId, onFinished }) => {
&nbsp; &nbsp;
{ _t("Your platform and username will be noted to help us use your feedback as much as we can.") } { _t("Your platform and username will be noted to help us use your feedback as much as we can.") }
<AccessibleButton kind="link" onClick={() => { <AccessibleButton
onFinished(false); kind="link"
defaultDispatcher.dispatch({ onClick={() => {
onFinished(false);
defaultDispatcher.dispatch({
action: Action.ViewUserSettings, action: Action.ViewUserSettings,
initialTabId: UserTab.Labs, initialTabId: UserTab.Labs,
}); });
}}> }}
>
{ _t("To leave the beta, visit your settings.") } { _t("To leave the beta, visit your settings.") }
</AccessibleButton> </AccessibleButton>
</div> </div>

View file

@ -188,7 +188,9 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
} }
return ( return (
<BaseDialog className="mx_BugReportDialog" onFinished={this.onCancel} <BaseDialog
className="mx_BugReportDialog"
onFinished={this.onCancel}
title={_t('Submit debug logs')} title={_t('Submit debug logs')}
contentId='mx_Dialog_content' contentId='mx_Dialog_content'
> >

View file

@ -205,9 +205,12 @@ export default class CommunityPrototypeInviteDialog extends React.PureComponent<
people.push(( people.push((
<AccessibleButton <AccessibleButton
onClick={this.onShowMorePeople} onClick={this.onShowMorePeople}
kind="link" key="more" kind="link"
key="more"
className="mx_CommunityPrototypeInviteDialog_morePeople" className="mx_CommunityPrototypeInviteDialog_morePeople"
>{ _t("Show more") }</AccessibleButton> >
{ _t("Show more") }
</AccessibleButton>
)); ));
} }
} }
@ -240,10 +243,13 @@ export default class CommunityPrototypeInviteDialog extends React.PureComponent<
{ peopleIntro } { peopleIntro }
{ people } { people }
<AccessibleButton <AccessibleButton
kind="primary" onClick={this.onSubmit} kind="primary"
onClick={this.onSubmit}
disabled={this.state.busy} disabled={this.state.busy}
className="mx_CommunityPrototypeInviteDialog_primaryButton" className="mx_CommunityPrototypeInviteDialog_primaryButton"
>{ buttonText }</AccessibleButton> >
{ buttonText }
</AccessibleButton>
</div> </div>
</form> </form>
</BaseDialog> </BaseDialog>

View file

@ -37,8 +37,8 @@ export default class ConfirmRedactDialog extends React.Component<IProps> {
"Note that if you delete a room name or topic change, it could undo the change.")} "Note that if you delete a room name or topic change, it could undo the change.")}
placeholder={_t("Reason (optional)")} placeholder={_t("Reason (optional)")}
focus focus
button={_t("Remove")}> button={_t("Remove")}
</TextInputDialog> />
); );
} }
} }

View file

@ -104,7 +104,9 @@ export default class ConfirmUserActionDialog extends React.Component<IProps> {
} }
return ( return (
<BaseDialog className="mx_ConfirmUserActionDialog" onFinished={this.props.onFinished} <BaseDialog
className="mx_ConfirmUserActionDialog"
onFinished={this.props.onFinished}
title={this.props.title} title={this.props.title}
contentId='mx_Dialog_content' contentId='mx_Dialog_content'
> >

View file

@ -204,8 +204,10 @@ export default class CreateCommunityPrototypeDialog extends React.PureComponent<
</div> </div>
<div className="mx_CreateCommunityPrototypeDialog_colAvatar"> <div className="mx_CreateCommunityPrototypeDialog_colAvatar">
<input <input
type="file" style={{ display: "none" }} type="file"
ref={this.avatarUploadRef} accept="image/*" style={{ display: "none" }}
ref={this.avatarUploadRef}
accept="image/*"
onChange={this.onAvatarChanged} onChange={this.onAvatarChanged}
/> />
<AccessibleButton <AccessibleButton

View file

@ -123,7 +123,9 @@ export default class CreateGroupDialog extends React.Component<IProps, IState> {
} }
return ( return (
<BaseDialog className="mx_CreateGroupDialog" onFinished={this.props.onFinished} <BaseDialog
className="mx_CreateGroupDialog"
onFinished={this.props.onFinished}
title={_t('Create Community')} title={_t('Create Community')}
> >
<form onSubmit={this.onFormSubmit}> <form onSubmit={this.onFormSubmit}>
@ -133,8 +135,11 @@ export default class CreateGroupDialog extends React.Component<IProps, IState> {
<label htmlFor="groupname">{ _t('Community Name') }</label> <label htmlFor="groupname">{ _t('Community Name') }</label>
</div> </div>
<div> <div>
<input id="groupname" className="mx_CreateGroupDialog_input" <input
autoFocus={true} size={64} id="groupname"
className="mx_CreateGroupDialog_input"
autoFocus={true}
size={64}
placeholder={_t('Example')} placeholder={_t('Example')}
onChange={this.onGroupNameChange} onChange={this.onGroupNameChange}
value={this.state.groupName} value={this.state.groupName}

View file

@ -72,7 +72,7 @@ const CryptoStoreTooNewDialog: React.FC<IProps> = (props: IProps) => {
hasCancel={false} hasCancel={false}
onPrimaryButtonClick={props.onFinished} onPrimaryButtonClick={props.onFinished}
> >
<button onClick={_onLogoutClicked} > <button onClick={_onLogoutClicked}>
{ _t('Sign out') } { _t('Sign out') }
</button> </button>
</DialogButtons> </DialogButtons>

View file

@ -182,14 +182,23 @@ export class SendCustomEvent extends GenericEditor<ISendCustomEventProps, ISendC
<br /> <br />
<Field id="evContent" label={_t("Event Content")} type="text" className="mx_DevTools_textarea" <Field
autoComplete="off" value={this.state.evContent} onChange={this.onChange} element="textarea" /> id="evContent"
label={_t("Event Content")}
type="text"
className="mx_DevTools_textarea"
autoComplete="off"
value={this.state.evContent}
onChange={this.onChange}
element="textarea" />
</div> </div>
<div className="mx_Dialog_buttons"> <div className="mx_Dialog_buttons">
<button onClick={this.onBack}>{ _t('Back') }</button> <button onClick={this.onBack}>{ _t('Back') }</button>
{ !this.state.message && <button onClick={this.send}>{ _t('Send') }</button> } { !this.state.message && <button onClick={this.send}>{ _t('Send') }</button> }
{ showTglFlip && <div style={{ float: "right" }}> { showTglFlip && <div style={{ float: "right" }}>
<input id="isStateEvent" className="mx_DevTools_tgl mx_DevTools_tgl-flip" <input
id="isStateEvent"
className="mx_DevTools_tgl mx_DevTools_tgl-flip"
type="checkbox" type="checkbox"
checked={this.state.isStateEvent} checked={this.state.isStateEvent}
onChange={this.onChange} onChange={this.onChange}
@ -282,14 +291,24 @@ class SendAccountData extends GenericEditor<ISendAccountDataProps, ISendAccountD
{ this.textInput('eventType', _t('Event Type')) } { this.textInput('eventType', _t('Event Type')) }
<br /> <br />
<Field id="evContent" label={_t("Event Content")} type="text" className="mx_DevTools_textarea" <Field
autoComplete="off" value={this.state.evContent} onChange={this.onChange} element="textarea" /> id="evContent"
label={_t("Event Content")}
type="text"
className="mx_DevTools_textarea"
autoComplete="off"
value={this.state.evContent}
onChange={this.onChange}
element="textarea"
/>
</div> </div>
<div className="mx_Dialog_buttons"> <div className="mx_Dialog_buttons">
<button onClick={this.onBack}>{ _t('Back') }</button> <button onClick={this.onBack}>{ _t('Back') }</button>
{ !this.state.message && <button onClick={this.send}>{ _t('Send') }</button> } { !this.state.message && <button onClick={this.send}>{ _t('Send') }</button> }
{ !this.state.message && <div style={{ float: "right" }}> { !this.state.message && <div style={{ float: "right" }}>
<input id="isRoomAccountData" className="mx_DevTools_tgl mx_DevTools_tgl-flip" <input
id="isRoomAccountData"
className="mx_DevTools_tgl mx_DevTools_tgl-flip"
type="checkbox" type="checkbox"
checked={this.state.isRoomAccountData} checked={this.state.isRoomAccountData}
disabled={this.props.forceMode} disabled={this.props.forceMode}
@ -371,11 +390,18 @@ class FilteredList extends React.PureComponent<IFilteredListProps, IFilteredList
render() { render() {
return <div> return <div>
<Field label={_t('Filter results')} autoFocus={true} size={64} <Field
type="text" autoComplete="off" value={this.props.query} onChange={this.onQuery} label={_t('Filter results')}
autoFocus={true}
size={64}
type="text"
autoComplete="off"
value={this.props.query}
onChange={this.onQuery}
className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query" className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query"
// force re-render so that autoFocus is applied when this component is re-used // force re-render so that autoFocus is applied when this component is re-used
key={this.props.children[0] ? this.props.children[0].key : ''} /> key={this.props.children[0] ? this.props.children[0].key : ''}
/>
<TruncatedList getChildren={this.getChildren} <TruncatedList getChildren={this.getChildren}
getChildCount={this.getChildCount} getChildCount={this.getChildCount}
@ -459,11 +485,16 @@ class RoomStateExplorer extends React.PureComponent<IExplorerProps, IRoomStateEx
render() { render() {
if (this.state.event) { if (this.state.event) {
if (this.state.editing) { if (this.state.editing) {
return <SendCustomEvent room={this.props.room} forceStateEvent={true} onBack={this.onBack} inputs={{ return <SendCustomEvent
room={this.props.room}
forceStateEvent={true}
onBack={this.onBack}
inputs={{
eventType: this.state.event.getType(), eventType: this.state.event.getType(),
evContent: JSON.stringify(this.state.event.getContent(), null, '\t'), evContent: JSON.stringify(this.state.event.getContent(), null, '\t'),
stateKey: this.state.event.getStateKey(), stateKey: this.state.event.getStateKey(),
}} />; }}
/>;
} }
return <div className="mx_ViewSource"> return <div className="mx_ViewSource">
@ -594,7 +625,9 @@ class AccountDataExplorer extends React.PureComponent<IExplorerProps, IAccountDa
inputs={{ inputs={{
eventType: this.state.event.getType(), eventType: this.state.event.getType(),
evContent: JSON.stringify(this.state.event.getContent(), null, '\t'), evContent: JSON.stringify(this.state.event.getContent(), null, '\t'),
}} forceMode={true} />; }}
forceMode={true}
/>;
} }
return <div className="mx_ViewSource"> return <div className="mx_ViewSource">
@ -631,7 +664,9 @@ class AccountDataExplorer extends React.PureComponent<IExplorerProps, IAccountDa
<div className="mx_Dialog_buttons"> <div className="mx_Dialog_buttons">
<button onClick={this.onBack}>{ _t('Back') }</button> <button onClick={this.onBack}>{ _t('Back') }</button>
<div style={{ float: "right" }}> <div style={{ float: "right" }}>
<input id="isRoomAccountData" className="mx_DevTools_tgl mx_DevTools_tgl-flip" <input
id="isRoomAccountData"
className="mx_DevTools_tgl mx_DevTools_tgl-flip"
type="checkbox" type="checkbox"
checked={this.state.isRoomAccountData} checked={this.state.isRoomAccountData}
onChange={this.onChange} onChange={this.onChange}
@ -1021,8 +1056,13 @@ class SettingsExplorer extends React.PureComponent<IExplorerProps, ISettingsExpl
<div> <div>
<div className="mx_Dialog_content mx_DevTools_SettingsExplorer"> <div className="mx_Dialog_content mx_DevTools_SettingsExplorer">
<Field <Field
label={_t('Filter results')} autoFocus={true} size={64} label={_t('Filter results')}
type="text" autoComplete="off" value={this.state.query} onChange={this.onQueryChange} autoFocus={true}
size={64}
type="text"
autoComplete="off"
value={this.state.query}
onChange={this.onQueryChange}
className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query" className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query"
/> />
<table> <table>
@ -1040,7 +1080,9 @@ class SettingsExplorer extends React.PureComponent<IExplorerProps, ISettingsExpl
<a href="" onClick={(e) => this.onViewClick(e, i)}> <a href="" onClick={(e) => this.onViewClick(e, i)}>
<code>{ i }</code> <code>{ i }</code>
</a> </a>
<a href="" onClick={(e) => this.onEditClick(e, i)} <a
href=""
onClick={(e) => this.onEditClick(e, i)}
className='mx_DevTools_SettingsExplorer_edit' className='mx_DevTools_SettingsExplorer_edit'
> >
@ -1104,18 +1146,26 @@ class SettingsExplorer extends React.PureComponent<IExplorerProps, ISettingsExpl
<div> <div>
<Field <Field
id="valExpl" label={_t("Values at explicit levels")} type="text" id="valExpl"
className="mx_DevTools_textarea" element="textarea" label={_t("Values at explicit levels")}
autoComplete="off" value={this.state.explicitValues} type="text"
className="mx_DevTools_textarea"
element="textarea"
autoComplete="off"
value={this.state.explicitValues}
onChange={this.onExplValuesEdit} onChange={this.onExplValuesEdit}
/> />
</div> </div>
<div> <div>
<Field <Field
id="valExpl" label={_t("Values at explicit levels in this room")} type="text" id="valExpl"
className="mx_DevTools_textarea" element="textarea" label={_t("Values at explicit levels in this room")}
autoComplete="off" value={this.state.explicitRoomValues} type="text"
className="mx_DevTools_textarea"
element="textarea"
autoComplete="off"
value={this.state.explicitRoomValues}
onChange={this.onExplRoomValuesEdit} onChange={this.onExplRoomValuesEdit}
/> />
</div> </div>

View file

@ -144,8 +144,10 @@ export default class EditCommunityPrototypeDialog extends React.PureComponent<IP
</div> </div>
<div className="mx_EditCommunityPrototypeDialog_rowAvatar"> <div className="mx_EditCommunityPrototypeDialog_rowAvatar">
<input <input
type="file" style={{ display: "none" }} type="file"
ref={this.avatarUploadRef} accept="image/*" style={{ display: "none" }}
ref={this.avatarUploadRef}
accept="image/*"
onChange={this.onAvatarChanged} onChange={this.onAvatarChanged}
/> />
<AccessibleButton <AccessibleButton

View file

@ -106,12 +106,12 @@ const Entry: React.FC<IEntryProps> = ({ room, event, matrixClient: cli, onFinish
className = "mx_ForwardList_sending"; className = "mx_ForwardList_sending";
disabled = true; disabled = true;
title = _t("Sending"); title = _t("Sending");
icon = <div className="mx_ForwardList_sendIcon" aria-label={title}></div>; icon = <div className="mx_ForwardList_sendIcon" aria-label={title} />;
} else if (sendState === SendState.Sent) { } else if (sendState === SendState.Sent) {
className = "mx_ForwardList_sent"; className = "mx_ForwardList_sent";
disabled = true; disabled = true;
title = _t("Sent"); title = _t("Sent");
icon = <div className="mx_ForwardList_sendIcon" aria-label={title}></div>; icon = <div className="mx_ForwardList_sendIcon" aria-label={title} />;
} else { } else {
className = "mx_ForwardList_sendFailed"; className = "mx_ForwardList_sendFailed";
disabled = true; disabled = true;
@ -204,10 +204,16 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
function overflowTile(overflowCount, totalCount) { function overflowTile(overflowCount, totalCount) {
const text = _t("and %(count)s others...", { count: overflowCount }); const text = _t("and %(count)s others...", { count: overflowCount });
return ( return (
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={ <EntityTile
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} /> className="mx_EntityTile_ellipsis"
} name={text} presenceState="online" suppressOnHover={true} avatarJsx={
onClick={() => setTruncateAt(totalCount)} /> <BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
}
name={text}
presenceState="online"
suppressOnHover={true}
onClick={() => setTruncateAt(totalCount)}
/>
); );
} }

View file

@ -133,18 +133,23 @@ export default class IncomingSasDialog extends React.Component {
? mediaFromMxc(oppProfile.avatar_url).getSquareThumbnailHttp(48) ? mediaFromMxc(oppProfile.avatar_url).getSquareThumbnailHttp(48)
: null; : null;
profile = <div className="mx_IncomingSasDialog_opponentProfile"> profile = <div className="mx_IncomingSasDialog_opponentProfile">
<BaseAvatar name={oppProfile.displayname} <BaseAvatar
name={oppProfile.displayname}
idName={this.props.verifier.userId} idName={this.props.verifier.userId}
url={url} url={url}
width={48} height={48} resizeMethod='crop' width={48}
height={48}
resizeMethod='crop'
/> />
<h2>{ oppProfile.displayname }</h2> <h2>{ oppProfile.displayname }</h2>
</div>; </div>;
} else if (this.state.opponentProfileError) { } else if (this.state.opponentProfileError) {
profile = <div> profile = <div>
<BaseAvatar name={this.props.verifier.userId.slice(1)} <BaseAvatar
name={this.props.verifier.userId.slice(1)}
idName={this.props.verifier.userId} idName={this.props.verifier.userId}
width={48} height={48} width={48}
height={48}
/> />
<h2>{ this.props.verifier.userId }</h2> <h2>{ this.props.verifier.userId }</h2>
</div>; </div>;

View file

@ -62,8 +62,7 @@ export default class InfoDialog extends React.Component<IProps> {
{ this.props.button !== false && <DialogButtons primaryButton={this.props.button || _t('OK')} { this.props.button !== false && <DialogButtons primaryButton={this.props.button || _t('OK')}
onPrimaryButtonClick={this.onFinished} onPrimaryButtonClick={this.onFinished}
hasCancel={false} hasCancel={false}
> /> }
</DialogButtons> }
</BaseDialog> </BaseDialog>
); );
} }

View file

@ -196,7 +196,9 @@ class DMUserTile extends React.PureComponent<IDMUserTileProps> {
? <img ? <img
className='mx_InviteDialog_userTile_avatar mx_InviteDialog_userTile_threepidAvatar' className='mx_InviteDialog_userTile_avatar mx_InviteDialog_userTile_threepidAvatar'
src={require("../../../../res/img/icon-email-pill-avatar.svg")} src={require("../../../../res/img/icon-email-pill-avatar.svg")}
width={avatarSize} height={avatarSize} /> width={avatarSize}
height={avatarSize}
/>
: <BaseAvatar : <BaseAvatar
className='mx_InviteDialog_userTile_avatar' className='mx_InviteDialog_userTile_avatar'
url={this.props.member.getMxcAvatarUrl() url={this.props.member.getMxcAvatarUrl()
@ -214,8 +216,11 @@ class DMUserTile extends React.PureComponent<IDMUserTileProps> {
className='mx_InviteDialog_userTile_remove' className='mx_InviteDialog_userTile_remove'
onClick={this.onRemove} onClick={this.onRemove}
> >
<img src={require("../../../../res/img/icon-pill-remove.svg")} <img
alt={_t('Remove')} width={8} height={8} src={require("../../../../res/img/icon-pill-remove.svg")}
alt={_t('Remove')}
width={8}
height={8}
/> />
</AccessibleButton> </AccessibleButton>
); );
@ -297,7 +302,9 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
const avatar = (this.props.member as ThreepidMember).isEmail const avatar = (this.props.member as ThreepidMember).isEmail
? <img ? <img
src={require("../../../../res/img/icon-email-pill-avatar.svg")} src={require("../../../../res/img/icon-email-pill-avatar.svg")}
width={avatarSize} height={avatarSize} /> width={avatarSize}
height={avatarSize}
/>
: <BaseAvatar : <BaseAvatar
url={this.props.member.getMxcAvatarUrl() url={this.props.member.getMxcAvatarUrl()
? mediaFromMxc(this.props.member.getMxcAvatarUrl()).getSquareThumbnailHttp(avatarSize) ? mediaFromMxc(this.props.member.getMxcAvatarUrl()).getSquareThumbnailHttp(avatarSize)
@ -1458,7 +1465,8 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
<p className='mx_InviteDialog_helpText'> <p className='mx_InviteDialog_helpText'>
<img <img
src={require("../../../../res/img/element-icons/info.svg")} src={require("../../../../res/img/element-icons/info.svg")}
width={14} height={14} /> width={14}
height={14} />
{ " " + _t("Invited people will be able to read old messages.") } { " " + _t("Invited people will be able to read old messages.") }
</p>; </p>;
} }
@ -1534,14 +1542,18 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
// Only show the backspace button if the field has content // Only show the backspace button if the field has content
let dialPadField; let dialPadField;
if (this.state.dialPadValue.length !== 0) { if (this.state.dialPadValue.length !== 0) {
dialPadField = <Field className="mx_InviteDialog_dialPadField" id="dialpad_number" dialPadField = <Field
className="mx_InviteDialog_dialPadField"
id="dialpad_number"
value={this.state.dialPadValue} value={this.state.dialPadValue}
autoFocus={true} autoFocus={true}
onChange={this.onDialChange} onChange={this.onDialChange}
postfixComponent={backspaceButton} postfixComponent={backspaceButton}
/>; />;
} else { } else {
dialPadField = <Field className="mx_InviteDialog_dialPadField" id="dialpad_number" dialPadField = <Field
className="mx_InviteDialog_dialPadField"
id="dialpad_number"
value={this.state.dialPadValue} value={this.state.dialPadValue}
autoFocus={true} autoFocus={true}
onChange={this.onDialChange} onChange={this.onDialChange}
@ -1552,14 +1564,19 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
<form onSubmit={this.onDialFormSubmit}> <form onSubmit={this.onDialFormSubmit}>
{ dialPadField } { dialPadField }
</form> </form>
<Dialpad hasDial={false} <Dialpad
onDigitPress={this.onDigitPress} onDeletePress={this.onDeletePress} hasDial={false}
onDigitPress={this.onDigitPress}
onDeletePress={this.onDeletePress}
/> />
</div>; </div>;
tabs.push(new Tab(TabId.DialPad, _td("Dial pad"), 'mx_InviteDialog_dialPadIcon', dialPadSection)); tabs.push(new Tab(TabId.DialPad, _td("Dial pad"), 'mx_InviteDialog_dialPadIcon', dialPadSection));
dialogContent = <React.Fragment> dialogContent = <React.Fragment>
<TabbedView tabs={tabs} initialTabId={this.state.currentTabId} <TabbedView
tabLocation={TabLocation.TOP} onChange={this.onTabChange} tabs={tabs}
initialTabId={this.state.currentTabId}
tabLocation={TabLocation.TOP}
onChange={this.onTabChange}
/> />
{ consultConnectSection } { consultConnectSection }
</React.Fragment>; </React.Fragment>;

View file

@ -85,7 +85,9 @@ export default class SessionRestoreErrorDialog extends React.Component {
} }
return ( return (
<BaseDialog className="mx_ErrorDialog" onFinished={this.props.onFinished} <BaseDialog
className="mx_ErrorDialog"
onFinished={this.props.onFinished}
title={_t('Unable to restore session')} title={_t('Unable to restore session')}
contentId='mx_Dialog_content' contentId='mx_Dialog_content'
hasCancel={false} hasCancel={false}

View file

@ -54,7 +54,9 @@ export default class StorageEvictedDialog extends React.Component {
} }
return ( return (
<BaseDialog className="mx_ErrorDialog" onFinished={this.props.onFinished} <BaseDialog
className="mx_ErrorDialog"
onFinished={this.props.onFinished}
title={_t('Missing session data')} title={_t('Missing session data')}
contentId='mx_Dialog_content' contentId='mx_Dialog_content'
hasCancel={false} hasCancel={false}

View file

@ -287,7 +287,8 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
<div className="mx_AccessSecretStorageDialog_reset"> <div className="mx_AccessSecretStorageDialog_reset">
{ _t("Forgotten or lost all recovery methods? <a>Reset all</a>", null, { { _t("Forgotten or lost all recovery methods? <a>Reset all</a>", null, {
a: (sub) => <a a: (sub) => <a
href="" onClick={this.onResetAllClick} href=""
onClick={this.onResetAllClick}
className="mx_AccessSecretStorageDialog_reset_link">{ sub }</a>, className="mx_AccessSecretStorageDialog_reset_link">{ sub }</a>,
}) } }) }
</div> </div>

View file

@ -399,7 +399,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
let keyStatus; let keyStatus;
if (this.state.recoveryKey.length === 0) { if (this.state.recoveryKey.length === 0) {
keyStatus = <div className="mx_RestoreKeyBackupDialog_keyStatus"></div>; keyStatus = <div className="mx_RestoreKeyBackupDialog_keyStatus" />;
} else if (this.state.recoveryKeyValid) { } else if (this.state.recoveryKeyValid) {
keyStatus = <div className="mx_RestoreKeyBackupDialog_keyStatus"> keyStatus = <div className="mx_RestoreKeyBackupDialog_keyStatus">
{ "\uD83D\uDC4D " }{ _t("This looks like a valid Security Key!") } { "\uD83D\uDC4D " }{ _t("This looks like a valid Security Key!") }

View file

@ -122,7 +122,7 @@ export default class AddressTile extends React.Component<IProps> {
let dismiss; let dismiss;
if (this.props.canDismiss) { if (this.props.canDismiss) {
dismiss = ( dismiss = (
<div className="mx_AddressTile_dismiss" onClick={this.props.onDismissed} > <div className="mx_AddressTile_dismiss" onClick={this.props.onDismissed}>
<img src={require("../../../../res/img/icon-address-delete.svg")} width="9" height="9" /> <img src={require("../../../../res/img/icon-address-delete.svg")} width="9" height="9" />
</div> </div>
); );

View file

@ -51,7 +51,8 @@ export class ExistingSource extends React.Component<DesktopCapturerSourceIProps>
<AccessibleButton <AccessibleButton
className="mx_desktopCapturerSourcePicker_stream_button" className="mx_desktopCapturerSourcePicker_stream_button"
title={this.props.source.name} title={this.props.source.name}
onClick={this.onClick} > onClick={this.onClick}
>
<img <img
className="mx_desktopCapturerSourcePicker_stream_thumbnail" className="mx_desktopCapturerSourcePicker_stream_thumbnail"
src={this.props.source.thumbnailURL} src={this.props.source.thumbnailURL}

View file

@ -419,7 +419,8 @@ export default class ImageView extends React.Component<IProps, IState> {
const avatar = ( const avatar = (
<MemberAvatar <MemberAvatar
member={mxEvent.sender} member={mxEvent.sender}
width={32} height={32} width={32}
height={32}
viewUserOnClick={true} viewUserOnClick={true}
/> />
); );
@ -438,7 +439,7 @@ export default class ImageView extends React.Component<IProps, IState> {
// an empty div here, since the panel uses space-between // an empty div here, since the panel uses space-between
// and we want the same placement of elements // and we want the same placement of elements
info = ( info = (
<div></div> <div />
); );
} }
@ -462,15 +463,15 @@ export default class ImageView extends React.Component<IProps, IState> {
<AccessibleTooltipButton <AccessibleTooltipButton
className="mx_ImageView_button mx_ImageView_button_zoomOut" className="mx_ImageView_button mx_ImageView_button_zoomOut"
title={_t("Zoom out")} title={_t("Zoom out")}
onClick={this.onZoomOutClick}> onClick={this.onZoomOutClick}
</AccessibleTooltipButton> />
); );
zoomInButton = ( zoomInButton = (
<AccessibleTooltipButton <AccessibleTooltipButton
className="mx_ImageView_button mx_ImageView_button_zoomIn" className="mx_ImageView_button mx_ImageView_button_zoomIn"
title={_t("Zoom in")} title={_t("Zoom in")}
onClick={this.onZoomInClick}> onClick={this.onZoomInClick}
</AccessibleTooltipButton> />
); );
} }
@ -492,24 +493,24 @@ export default class ImageView extends React.Component<IProps, IState> {
<AccessibleTooltipButton <AccessibleTooltipButton
className="mx_ImageView_button mx_ImageView_button_rotateCCW" className="mx_ImageView_button mx_ImageView_button_rotateCCW"
title={_t("Rotate Left")} title={_t("Rotate Left")}
onClick={this.onRotateCounterClockwiseClick}> onClick={this.onRotateCounterClockwiseClick}
</AccessibleTooltipButton> />
<AccessibleTooltipButton <AccessibleTooltipButton
className="mx_ImageView_button mx_ImageView_button_rotateCW" className="mx_ImageView_button mx_ImageView_button_rotateCW"
title={_t("Rotate Right")} title={_t("Rotate Right")}
onClick={this.onRotateClockwiseClick}> onClick={this.onRotateClockwiseClick}
</AccessibleTooltipButton> />
<AccessibleTooltipButton <AccessibleTooltipButton
className="mx_ImageView_button mx_ImageView_button_download" className="mx_ImageView_button mx_ImageView_button_download"
title={_t("Download")} title={_t("Download")}
onClick={this.onDownloadClick}> onClick={this.onDownloadClick}
</AccessibleTooltipButton> />
{ contextMenuButton } { contextMenuButton }
<AccessibleTooltipButton <AccessibleTooltipButton
className="mx_ImageView_button mx_ImageView_button_close" className="mx_ImageView_button mx_ImageView_button_close"
title={_t("Close")} title={_t("Close")}
onClick={this.props.onFinished}> onClick={this.props.onFinished}
</AccessibleTooltipButton> />
{ this.renderContextMenu() } { this.renderContextMenu() }
</div> </div>
</div> </div>

View file

@ -92,7 +92,7 @@ const MiniAvatarUploader: React.FC<IProps> = ({ hasAvatar, hasAvatarLabel, noAva
<div className="mx_MiniAvatarUploader_indicator"> <div className="mx_MiniAvatarUploader_indicator">
{ busy ? { busy ?
<Spinner w={20} h={20} /> : <Spinner w={20} h={20} /> :
<div className="mx_MiniAvatarUploader_cameraIcon"></div> } <div className="mx_MiniAvatarUploader_cameraIcon" /> }
</div> </div>
<div className={classNames("mx_Tooltip", { <div className={classNames("mx_Tooltip", {

View file

@ -258,7 +258,10 @@ class Pill extends React.Component {
linkText = groupId; linkText = groupId;
if (this.props.shouldShowPillAvatar) { if (this.props.shouldShowPillAvatar) {
avatar = <BaseAvatar avatar = <BaseAvatar
name={name || groupId} width={16} height={16} aria-hidden="true" name={name || groupId}
width={16}
height={16}
aria-hidden="true"
url={avatarUrl ? mediaFromMxc(avatarUrl).getSquareThumbnailHttp(16) : null} />; url={avatarUrl ? mediaFromMxc(avatarUrl).getSquareThumbnailHttp(16) : null} />;
} }
pillClass = 'mx_GroupPill'; pillClass = 'mx_GroupPill';

View file

@ -134,8 +134,10 @@ export default class PowerSelector extends React.Component {
const label = typeof this.props.label === "undefined" ? _t("Power level") : this.props.label; const label = typeof this.props.label === "undefined" ? _t("Power level") : this.props.label;
if (this.state.custom) { if (this.state.custom) {
picker = ( picker = (
<Field type="number" <Field
label={label} max={this.props.maxValue} type="number"
label={label}
max={this.props.maxValue}
onBlur={this.onCustomBlur} onBlur={this.onCustomBlur}
onKeyDown={this.onCustomKeyDown} onKeyDown={this.onCustomKeyDown}
onChange={this.onCustomChange} onChange={this.onCustomChange}
@ -157,9 +159,12 @@ export default class PowerSelector extends React.Component {
}); });
picker = ( picker = (
<Field element="select" <Field
label={label} onChange={this.onSelectChange} element="select"
value={String(this.state.selectValue)} disabled={this.props.disabled} label={label}
onChange={this.onSelectChange}
value={String(this.state.selectValue)}
disabled={this.props.disabled}
> >
{ options } { options }
</Field> </Field>

View file

@ -166,8 +166,7 @@ export default class Tooltip extends React.Component<IProps> {
public render() { public render() {
// Render a placeholder // Render a placeholder
return ( return (
<div className={this.props.className}> <div className={this.props.className} />
</div>
); );
} }
} }

View file

@ -101,14 +101,16 @@ class Category extends React.PureComponent<IProps> {
{ name } { name }
</h2> </h2>
<LazyRenderList <LazyRenderList
element="ul" className="mx_EmojiPicker_list" element="ul"
itemHeight={EMOJI_HEIGHT} items={rows} className="mx_EmojiPicker_list"
itemHeight={EMOJI_HEIGHT}
items={rows}
scrollTop={localScrollTop} scrollTop={localScrollTop}
height={localHeight} height={localHeight}
overflowItems={OVERFLOW_ROWS} overflowItems={OVERFLOW_ROWS}
overflowMargin={0} overflowMargin={0}
renderItem={this.renderEmojiRow}> renderItem={this.renderEmojiRow}
</LazyRenderList> />
</section> </section>
); );
} }

View file

@ -86,10 +86,16 @@ export default class GroupMemberList extends React.Component {
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar"); const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
const text = _t("and %(count)s others...", { count: overflowCount }); const text = _t("and %(count)s others...", { count: overflowCount });
return ( return (
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={ <EntityTile
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} /> className="mx_EntityTile_ellipsis"
} name={text} presenceState="online" suppressOnHover={true} avatarJsx={
onClick={this._showFullMemberList} /> <BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
}
name={text}
presenceState="online"
suppressOnHover={true}
onClick={this._showFullMemberList}
/>
); );
}; };
@ -152,7 +158,9 @@ export default class GroupMemberList extends React.Component {
); );
}); });
return <TruncatedList className="mx_MemberList_wrapper" truncateAt={this.state.truncateAt} return <TruncatedList
className="mx_MemberList_wrapper"
truncateAt={this.state.truncateAt}
createOverflowElement={this._createOverflowTile} createOverflowElement={this._createOverflowTile}
> >
{ memberTiles } { memberTiles }

View file

@ -56,14 +56,19 @@ export default class GroupMemberTile extends React.Component {
aria-hidden="true" aria-hidden="true"
name={this.props.member.displayname || this.props.member.userId} name={this.props.member.displayname || this.props.member.userId}
idName={this.props.member.userId} idName={this.props.member.userId}
width={36} height={36} width={36}
height={36}
url={avatarUrl} url={avatarUrl}
/> />
); );
return ( return (
<EntityTile name={name} avatarJsx={av} onClick={this.onClick} <EntityTile
suppressOnHover={true} presenceState="online" name={name}
avatarJsx={av}
onClick={this.onClick}
suppressOnHover={true}
presenceState="online"
powerStatus={this.props.member.isPrivileged ? EntityTile.POWER_STATUS_ADMIN : null} powerStatus={this.props.member.isPrivileged ? EntityTile.POWER_STATUS_ADMIN : null}
/> />
); );

View file

@ -76,10 +76,16 @@ export default class GroupRoomList extends React.Component {
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar"); const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
const text = _t("and %(count)s others...", { count: overflowCount }); const text = _t("and %(count)s others...", { count: overflowCount });
return ( return (
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={ <EntityTile
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} /> className="mx_EntityTile_ellipsis"
} name={text} presenceState="online" suppressOnHover={true} avatarJsx={
onClick={this._showFullRoomList} /> <BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
}
name={text}
presenceState="online"
suppressOnHover={true}
onClick={this._showFullRoomList}
/>
); );
}; };
@ -142,7 +148,8 @@ export default class GroupRoomList extends React.Component {
} }
const inputBox = ( const inputBox = (
<input <input
className="mx_GroupRoomList_query mx_textinput" id="mx_GroupRoomList_query" className="mx_GroupRoomList_query mx_textinput"
id="mx_GroupRoomList_query"
type="text" type="text"
onChange={this.onSearchQueryChanged} onChange={this.onSearchQueryChanged}
value={this.state.searchQuery} value={this.state.searchQuery}
@ -156,8 +163,11 @@ export default class GroupRoomList extends React.Component {
<div className="mx_GroupRoomList" role="tabpanel"> <div className="mx_GroupRoomList" role="tabpanel">
{ inviteButton } { inviteButton }
<AutoHideScrollbar className="mx_GroupRoomList_joined mx_GroupRoomList_outerWrapper"> <AutoHideScrollbar className="mx_GroupRoomList_joined mx_GroupRoomList_outerWrapper">
<TruncatedList className="mx_GroupRoomList_wrapper" truncateAt={this.state.truncateAt} <TruncatedList
createOverflowElement={this._createOverflowTile}> className="mx_GroupRoomList_wrapper"
truncateAt={this.state.truncateAt}
createOverflowElement={this._createOverflowTile}
>
{ this.makeGroupRoomTiles(this.state.searchQuery) } { this.makeGroupRoomTiles(this.state.searchQuery) }
</TruncatedList> </TruncatedList>
</AutoHideScrollbar> </AutoHideScrollbar>

View file

@ -48,8 +48,10 @@ class GroupRoomTile extends React.Component {
: null; : null;
const av = ( const av = (
<BaseAvatar name={this.props.groupRoom.displayname} <BaseAvatar
width={36} height={36} name={this.props.groupRoom.displayname}
width={36}
height={36}
url={avatarUrl} url={avatarUrl}
/> />
); );

View file

@ -206,7 +206,7 @@ export default class CallEvent extends React.Component<IProps, IState> {
{ sender } { sender }
</div> </div>
<div className="mx_CallEvent_type"> <div className="mx_CallEvent_type">
<div className="mx_CallEvent_type_icon"></div> <div className="mx_CallEvent_type_icon" />
{ callType } { callType }
</div> </div>
</div> </div>

View file

@ -16,14 +16,14 @@ limitations under the License.
import React from "react"; import React from "react";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import { Playback } from "../../../voice/Playback"; import { Playback } from "../../../audio/Playback";
import InlineSpinner from '../elements/InlineSpinner'; import InlineSpinner from '../elements/InlineSpinner';
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import AudioPlayer from "../audio_messages/AudioPlayer"; import AudioPlayer from "../audio_messages/AudioPlayer";
import { IMediaEventContent } from "../../../customisations/models/IMediaEventContent"; import { IMediaEventContent } from "../../../customisations/models/IMediaEventContent";
import MFileBody from "./MFileBody"; import MFileBody from "./MFileBody";
import { IBodyProps } from "./IBodyProps"; import { IBodyProps } from "./IBodyProps";
import { PlaybackManager } from "../../../voice/PlaybackManager"; import { PlaybackManager } from "../../../audio/PlaybackManager";
interface IState { interface IState {
error?: Error; error?: Error;
@ -76,7 +76,6 @@ export default class MAudioBody extends React.PureComponent<IBodyProps, IState>
public render() { public render() {
if (this.state.error) { if (this.state.error) {
// TODO: @@TR: Verify error state
return ( return (
<span className="mx_MAudioBody"> <span className="mx_MAudioBody">
<img src={require("../../../../res/img/warning.svg")} width="16" height="16" /> <img src={require("../../../../res/img/warning.svg")} width="16" height="16" />
@ -86,7 +85,6 @@ export default class MAudioBody extends React.PureComponent<IBodyProps, IState>
} }
if (!this.state.playback) { if (!this.state.playback) {
// TODO: @@TR: Verify loading/decrypting state
return ( return (
<span className="mx_MAudioBody"> <span className="mx_MAudioBody">
<InlineSpinner /> <InlineSpinner />

View file

@ -306,7 +306,10 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
imageElement = <HiddenImagePlaceholder />; imageElement = <HiddenImagePlaceholder />;
} else { } else {
imageElement = ( imageElement = (
<img style={{ display: 'none' }} src={thumbUrl} ref={this.image} <img
style={{ display: 'none' }}
src={thumbUrl}
ref={this.image}
alt={content.body} alt={content.body}
onError={this.onImageError} onError={this.onImageError}
onLoad={this.onImageLoad} onLoad={this.onImageLoad}
@ -340,7 +343,10 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
// which has the same width as the timeline // which has the same width as the timeline
// mx_MImageBody_thumbnail resizes img to exactly container size // mx_MImageBody_thumbnail resizes img to exactly container size
img = ( img = (
<img className="mx_MImageBody_thumbnail" src={thumbUrl} ref={this.image} <img
className="mx_MImageBody_thumbnail"
src={thumbUrl}
ref={this.image}
style={{ maxWidth: `min(100%, ${maxWidth}px)` }} style={{ maxWidth: `min(100%, ${maxWidth}px)` }}
alt={content.body} alt={content.body}
onError={this.onImageError} onError={this.onImageError}
@ -360,12 +366,15 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
} }
const thumbnail = ( const thumbnail = (
<div className="mx_MImageBody_thumbnail_container" style={{ maxHeight: maxHeight + "px", maxWidth: maxWidth + "px" }} > <div className="mx_MImageBody_thumbnail_container" style={{ maxHeight: maxHeight + "px", maxWidth: maxWidth + "px" }}>
{ showPlaceholder && { showPlaceholder &&
<div className="mx_MImageBody_thumbnail" style={{ <div
// Constrain width here so that spinner appears central to the loaded thumbnail className="mx_MImageBody_thumbnail"
maxWidth: `min(100%, ${infoWidth}px)`, style={{
}}> // Constrain width here so that spinner appears central to the loaded thumbnail
maxWidth: `min(100%, ${infoWidth}px)`,
}}
>
{ placeholder } { placeholder }
</div> </div>
} }
@ -416,10 +425,10 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
if (this.state.error !== null) { if (this.state.error !== null) {
return ( return (
<span className="mx_MImageBody"> <div className="mx_MImageBody">
<img src={require("../../../../res/img/warning.svg")} width="16" height="16" /> <img src={require("../../../../res/img/warning.svg")} width="16" height="16" />
{ _t("Error decrypting image") } { _t("Error decrypting image") }
</span> </div>
); );
} }
@ -434,10 +443,10 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
const thumbnail = this.messageContent(contentUrl, thumbUrl, content); const thumbnail = this.messageContent(contentUrl, thumbUrl, content);
const fileBody = this.getFileBody(); const fileBody = this.getFileBody();
return <span className="mx_MImageBody"> return <div className="mx_MImageBody">
{ thumbnail } { thumbnail }
{ fileBody } { fileBody }
</span>; </div>;
} }
} }

View file

@ -267,8 +267,7 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
width={width} width={width}
poster={poster} poster={poster}
onPlay={this.videoOnPlay} onPlay={this.videoOnPlay}
> />
</video>
{ this.props.tileShape && <MFileBody {...this.props} showGenericPlaceholder={false} /> } { this.props.tileShape && <MFileBody {...this.props} showGenericPlaceholder={false} /> }
</span> </span>
); );

View file

@ -27,7 +27,6 @@ export default class MVoiceMessageBody extends MAudioBody {
// A voice message is an audio file but rendered in a special way. // A voice message is an audio file but rendered in a special way.
public render() { public render() {
if (this.state.error) { if (this.state.error) {
// TODO: @@TR: Verify error state
return ( return (
<span className="mx_MVoiceMessageBody"> <span className="mx_MVoiceMessageBody">
<img src={require("../../../../res/img/warning.svg")} width="16" height="16" /> <img src={require("../../../../res/img/warning.svg")} width="16" height="16" />
@ -37,7 +36,6 @@ export default class MVoiceMessageBody extends MAudioBody {
} }
if (!this.state.playback) { if (!this.state.playback) {
// TODO: @@TR: Verify loading/decrypting state
return ( return (
<span className="mx_MVoiceMessageBody"> <span className="mx_MVoiceMessageBody">
<InlineSpinner /> <InlineSpinner />

View file

@ -78,8 +78,11 @@ export default class RoomAvatarEvent extends React.Component {
{ senderDisplayName: senderDisplayName }, { senderDisplayName: senderDisplayName },
{ {
'img': () => 'img': () =>
<AccessibleButton key="avatar" className="mx_RoomAvatarEvent_avatar" <AccessibleButton
onClick={this.onAvatarClick}> key="avatar"
className="mx_RoomAvatarEvent_avatar"
onClick={this.onAvatarClick}
>
<RoomAvatar width={14} height={14} oobData={oobData} /> <RoomAvatar width={14} height={14} oobData={oobData} />
</AccessibleButton>, </AccessibleButton>,
}) })

View file

@ -205,7 +205,7 @@ function DeviceItem({ userId, device }: {userId: string, device: IDevice}) {
if (isVerified) { if (isVerified) {
return ( return (
<div className={classes} title={device.deviceId} > <div className={classes} title={device.deviceId}>
<div className={iconClasses} /> <div className={iconClasses} />
<div className="mx_UserInfo_device_name">{ deviceName }</div> <div className="mx_UserInfo_device_name">{ deviceName }</div>
<div className="mx_UserInfo_device_trusted">{ trustedLabel }</div> <div className="mx_UserInfo_device_trusted">{ trustedLabel }</div>
@ -1353,13 +1353,16 @@ const BasicUserInfo: React.FC<{
if (hasCrossSigningKeys !== undefined) { if (hasCrossSigningKeys !== undefined) {
// Note: mx_UserInfo_verifyButton is for the end-to-end tests // Note: mx_UserInfo_verifyButton is for the end-to-end tests
verifyButton = ( verifyButton = (
<AccessibleButton className="mx_UserInfo_field mx_UserInfo_verifyButton" onClick={() => { <AccessibleButton
if (hasCrossSigningKeys) { className="mx_UserInfo_field mx_UserInfo_verifyButton"
verifyUser(member as User); onClick={() => {
} else { if (hasCrossSigningKeys) {
legacyVerifyUser(member as User); verifyUser(member as User);
} } else {
}}> legacyVerifyUser(member as User);
}
}}
>
{ _t("Verify") } { _t("Verify") }
</AccessibleButton> </AccessibleButton>
); );
@ -1374,12 +1377,15 @@ const BasicUserInfo: React.FC<{
let editDevices; let editDevices;
if (member.userId == cli.getUserId()) { if (member.userId == cli.getUserId()) {
editDevices = (<p> editDevices = (<p>
<AccessibleButton className="mx_UserInfo_field" onClick={() => { <AccessibleButton
dis.dispatch({ className="mx_UserInfo_field"
onClick={() => {
dis.dispatch({
action: Action.ViewUserSettings, action: Action.ViewUserSettings,
initialTabId: UserTab.Security, initialTabId: UserTab.Security,
}); });
}}> }}
>
{ _t("Edit devices") } { _t("Edit devices") }
</AccessibleButton> </AccessibleButton>
</p>); </p>);

View file

@ -711,9 +711,12 @@ export default class EventTile extends React.Component<IProps, IState> {
// add to the start so the most recent is on the end (ie. ends up rightmost) // add to the start so the most recent is on the end (ie. ends up rightmost)
avatars.unshift( avatars.unshift(
<ReadReceiptMarker key={userId} member={receipt.roomMember} <ReadReceiptMarker
key={userId}
member={receipt.roomMember}
fallbackUserId={userId} fallbackUserId={userId}
leftOffset={left} hidden={hidden} leftOffset={left}
hidden={hidden}
readReceiptInfo={readReceiptInfo} readReceiptInfo={readReceiptInfo}
checkUnmounting={this.props.checkUnmounting} checkUnmounting={this.props.checkUnmounting}
suppressAnimation={this.suppressReadReceiptAnimation} suppressAnimation={this.suppressReadReceiptAnimation}
@ -893,6 +896,7 @@ export default class EventTile extends React.Component<IProps, IState> {
mx_EventTile_unknown: !isBubbleMessage && this.state.verified === E2E_STATE.UNKNOWN, mx_EventTile_unknown: !isBubbleMessage && this.state.verified === E2E_STATE.UNKNOWN,
mx_EventTile_bad: isEncryptionFailure, mx_EventTile_bad: isEncryptionFailure,
mx_EventTile_emote: msgtype === 'm.emote', mx_EventTile_emote: msgtype === 'm.emote',
mx_EventTile_noSender: this.props.hideSender,
}); });
// If the tile is in the Sending state, don't speak the message. // If the tile is in the Sending state, don't speak the message.
@ -949,8 +953,10 @@ export default class EventTile extends React.Component<IProps, IState> {
} }
avatar = ( avatar = (
<div className="mx_EventTile_avatar"> <div className="mx_EventTile_avatar">
<MemberAvatar member={member} <MemberAvatar
width={avatarSize} height={avatarSize} member={member}
width={avatarSize}
height={avatarSize}
viewUserOnClick={true} viewUserOnClick={true}
/> />
</div> </div>
@ -1160,8 +1166,9 @@ export default class EventTile extends React.Component<IProps, IState> {
/> />
{ keyRequestInfo } { keyRequestInfo }
{ actionBar } { actionBar }
{ this.props.layout === Layout.IRC && (reactionsRow) }
</div> </div>
{ reactionsRow } { this.props.layout !== Layout.IRC && (reactionsRow) }
{ msgOption } { msgOption }
</>) </>)
); );

View file

@ -28,10 +28,11 @@ export default (props) => {
badge = (<div className="mx_JumpToBottomButton_badge">{ props.numUnreadMessages }</div>); badge = (<div className="mx_JumpToBottomButton_badge">{ props.numUnreadMessages }</div>);
} }
return (<div className={className}> return (<div className={className}>
<AccessibleButton className="mx_JumpToBottomButton_scrollDown" <AccessibleButton
className="mx_JumpToBottomButton_scrollDown"
title={_t("Scroll to most recent messages")} title={_t("Scroll to most recent messages")}
onClick={props.onScrollToBottomClick}> onClick={props.onScrollToBottomClick}
</AccessibleButton> />
{ badge } { badge }
</div>); </div>);
}; };

View file

@ -306,10 +306,16 @@ export default class MemberList extends React.Component<IProps, IState> {
// For now we'll pretend this is any entity. It should probably be a separate tile. // For now we'll pretend this is any entity. It should probably be a separate tile.
const text = _t("and %(count)s others...", { count: overflowCount }); const text = _t("and %(count)s others...", { count: overflowCount });
return ( return (
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={ <EntityTile
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} /> className="mx_EntityTile_ellipsis"
} name={text} presenceState="online" suppressOnHover={true} avatarJsx={
onClick={onClick} /> <BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
}
name={text}
presenceState="online"
suppressOnHover={true}
onClick={onClick}
/>
); );
}; };
@ -465,8 +471,12 @@ export default class MemberList extends React.Component<IProps, IState> {
return <MemberTile key={m.userId} member={m} ref={m.userId} showPresence={this.showPresence} />; return <MemberTile key={m.userId} member={m} ref={m.userId} showPresence={this.showPresence} />;
} else { } else {
// Is a 3pid invite // Is a 3pid invite
return <EntityTile key={m.getStateKey()} name={m.getContent().display_name} suppressOnHover={true} return <EntityTile
onClick={() => this.onPending3pidInviteClick(m)} />; key={m.getStateKey()}
name={m.getContent().display_name}
suppressOnHover={true}
onClick={() => this.onPending3pidInviteClick(m)}
/>;
} }
}); });
} }

View file

@ -35,7 +35,7 @@ import { UPDATE_EVENT } from "../../../stores/AsyncStore";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import VoiceRecordComposerTile from "./VoiceRecordComposerTile"; import VoiceRecordComposerTile from "./VoiceRecordComposerTile";
import { VoiceRecordingStore } from "../../../stores/VoiceRecordingStore"; import { VoiceRecordingStore } from "../../../stores/VoiceRecordingStore";
import { RecordingState } from "../../../voice/VoiceRecording"; import { RecordingState } from "../../../audio/VoiceRecording";
import Tooltip, { Alignment } from "../elements/Tooltip"; import Tooltip, { Alignment } from "../elements/Tooltip";
import ResizeNotifier from "../../../utils/ResizeNotifier"; import ResizeNotifier from "../../../utils/ResizeNotifier";
import { E2EStatus } from '../../../utils/ShieldUtils'; import { E2EStatus } from '../../../utils/ShieldUtils';
@ -98,9 +98,7 @@ const EmojiButton = ({ addEmoji }) => {
isExpanded={menuDisplayed} isExpanded={menuDisplayed}
title={_t('Emoji picker')} title={_t('Emoji picker')}
inputRef={button} inputRef={button}
> />
</ContextMenuTooltipButton>
{ contextMenu } { contextMenu }
</React.Fragment>; </React.Fragment>;
@ -439,7 +437,8 @@ export default class MessageComposer extends React.Component<IProps, IState> {
if (secondsLeft) { if (secondsLeft) {
recordingTooltip = <Tooltip recordingTooltip = <Tooltip
label={_t("%(seconds)ss left", { seconds: secondsLeft })} label={_t("%(seconds)ss left", { seconds: secondsLeft })}
alignment={Alignment.Top} yOffset={-50} alignment={Alignment.Top}
yOffset={-50}
/>; />;
} }

View file

@ -58,13 +58,18 @@ const NewRoomIntro = () => {
const member = room?.getMember(dmPartner); const member = room?.getMember(dmPartner);
const displayName = member?.rawDisplayName || dmPartner; const displayName = member?.rawDisplayName || dmPartner;
body = <React.Fragment> body = <React.Fragment>
<RoomAvatar room={room} width={AVATAR_SIZE} height={AVATAR_SIZE} onClick={() => { <RoomAvatar
defaultDispatcher.dispatch<ViewUserPayload>({ room={room}
width={AVATAR_SIZE}
height={AVATAR_SIZE}
onClick={() => {
defaultDispatcher.dispatch<ViewUserPayload>({
action: Action.ViewUser, action: Action.ViewUser,
// XXX: We should be using a real member object and not assuming what the receiver wants. // XXX: We should be using a real member object and not assuming what the receiver wants.
member: member || { userId: dmPartner } as User, member: member || { userId: dmPartner } as User,
}); });
}} /> }}
/>
<h2>{ room.name }</h2> <h2>{ room.name }</h2>

View file

@ -192,7 +192,9 @@ export default class ReadReceiptMarker extends React.PureComponent {
member={this.props.member} member={this.props.member}
fallbackUserId={this.props.fallbackUserId} fallbackUserId={this.props.fallbackUserId}
aria-hidden="true" aria-hidden="true"
width={14} height={14} resizeMethod="crop" width={14}
height={14}
resizeMethod="crop"
style={style} style={style}
title={title} title={title}
onClick={this.props.onClick} onClick={this.props.onClick}

View file

@ -105,7 +105,9 @@ export default class RoomBreadcrumbs extends React.PureComponent<IProps, IState>
// NOTE: The CSSTransition timeout MUST match the timeout in our CSS! // NOTE: The CSSTransition timeout MUST match the timeout in our CSS!
return ( return (
<CSSTransition <CSSTransition
appear={true} in={this.state.doAnimation} timeout={640} appear={true}
in={this.state.doAnimation}
timeout={640}
classNames='mx_RoomBreadcrumbs' classNames='mx_RoomBreadcrumbs'
> >
<Toolbar className='mx_RoomBreadcrumbs' aria-label={_t("Recently visited rooms")}> <Toolbar className='mx_RoomBreadcrumbs' aria-label={_t("Recently visited rooms")}>

View file

@ -105,8 +105,12 @@ export default class RoomDetailRow extends React.Component {
return <tr key={room.roomId} onClick={this.onClick} onMouseDown={this.props.onMouseDown}> return <tr key={room.roomId} onClick={this.onClick} onMouseDown={this.props.onMouseDown}>
<td className="mx_RoomDirectory_roomAvatar"> <td className="mx_RoomDirectory_roomAvatar">
<BaseAvatar width={24} height={24} resizeMethod='crop' <BaseAvatar
name={name} idName={name} width={24}
height={24}
resizeMethod='crop'
name={name}
idName={name}
url={avatarUrl} /> url={avatarUrl} />
</td> </td>
<td className="mx_RoomDirectory_roomDescription"> <td className="mx_RoomDirectory_roomDescription">

View file

@ -428,7 +428,9 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
groupId={g.groupId} groupId={g.groupId}
groupName={g.name} groupName={g.name}
groupAvatarUrl={g.avatarUrl} groupAvatarUrl={g.avatarUrl}
width={32} height={32} resizeMethod='crop' width={32}
height={32}
resizeMethod='crop'
/> />
); );
const openGroup = () => { const openGroup = () => {

View file

@ -536,8 +536,10 @@ export default class RoomPreviewBar extends React.Component {
"If you think you're seeing this message in error, please " + "If you think you're seeing this message in error, please " +
"<issueLink>submit a bug report</issueLink>.", "<issueLink>submit a bug report</issueLink>.",
{ errcode: this.props.error.errcode }, { errcode: this.props.error.errcode },
{ issueLink: label => <a href="https://github.com/vector-im/element-web/issues/new/choose" { issueLink: label => <a
target="_blank" rel="noreferrer noopener">{ label }</a> }, href="https://github.com/vector-im/element-web/issues/new/choose"
target="_blank"
rel="noreferrer noopener">{ label }</a> },
), ),
]; ];
break; break;

View file

@ -35,13 +35,15 @@ export default class SimpleRoomHeader extends React.Component {
let icon; let icon;
if (this.props.icon) { if (this.props.icon) {
icon = <img icon = <img
className="mx_RoomHeader_icon" src={this.props.icon} className="mx_RoomHeader_icon"
width="25" height="25" src={this.props.icon}
width="25"
height="25"
/>; />;
} }
return ( return (
<div className="mx_RoomHeader mx_RoomHeader_wrapper" > <div className="mx_RoomHeader mx_RoomHeader_wrapper">
<div className="mx_RoomHeader_simpleHeader"> <div className="mx_RoomHeader_simpleHeader">
{ icon } { icon }
{ this.props.title } { this.props.title }

View file

@ -403,8 +403,7 @@ export default class Stickerpicker extends React.PureComponent {
onClick={this._onHideStickersClick} onClick={this._onHideStickersClick}
active={this.state.showStickers.toString()} active={this.state.showStickers.toString()}
title={_t("Hide Stickers")} title={_t("Hide Stickers")}
> />;
</AccessibleButton>;
const GenericElementContextMenu = sdk.getComponent('context_menus.GenericElementContextMenu'); const GenericElementContextMenu = sdk.getComponent('context_menus.GenericElementContextMenu');
stickerPicker = <ContextMenu stickerPicker = <ContextMenu
@ -431,8 +430,7 @@ export default class Stickerpicker extends React.PureComponent {
className="mx_MessageComposer_button mx_MessageComposer_stickers" className="mx_MessageComposer_button mx_MessageComposer_stickers"
onClick={this._onShowStickersClick} onClick={this._onShowStickersClick}
title={_t("Show Stickers")} title={_t("Show Stickers")}
> />;
</AccessibleTooltipButton>;
} }
return <React.Fragment> return <React.Fragment>
{ stickersButton } { stickersButton }

View file

@ -32,14 +32,16 @@ export default class TopUnreadMessagesBar extends React.Component {
render() { render() {
return ( return (
<div className="mx_TopUnreadMessagesBar"> <div className="mx_TopUnreadMessagesBar">
<AccessibleButton className="mx_TopUnreadMessagesBar_scrollUp" <AccessibleButton
className="mx_TopUnreadMessagesBar_scrollUp"
title={_t('Jump to first unread message.')} title={_t('Jump to first unread message.')}
onClick={this.props.onScrollUpClick}> onClick={this.props.onScrollUpClick}
</AccessibleButton> />
<AccessibleButton className="mx_TopUnreadMessagesBar_markAsRead" <AccessibleButton
className="mx_TopUnreadMessagesBar_markAsRead"
title={_t('Mark all as read')} title={_t('Mark all as read')}
onClick={this.props.onCloseClick}> onClick={this.props.onCloseClick}
</AccessibleButton> />
</div> </div>
); );
} }

View file

@ -20,7 +20,7 @@ import React, { ReactNode } from "react";
import { import {
RecordingState, RecordingState,
VoiceRecording, VoiceRecording,
} from "../../../voice/VoiceRecording"; } from "../../../audio/VoiceRecording";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../MatrixClientPeg";
import classNames from "classnames"; import classNames from "classnames";
@ -189,7 +189,6 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
if (!this.state.recorder) return null; // no recorder means we're not recording: no waveform if (!this.state.recorder) return null; // no recorder means we're not recording: no waveform
if (this.state.recordingPhase !== RecordingState.Started) { if (this.state.recordingPhase !== RecordingState.Started) {
// TODO: @@ TR: Should we disable this during upload? What does a failed upload look like?
return <RecordingPlayback playback={this.state.recorder.getPlayback()} />; return <RecordingPlayback playback={this.state.recorder.getPlayback()} />;
} }

View file

@ -124,7 +124,7 @@ export default class BridgeTile extends React.PureComponent<IProps> {
url={avatarUrl} url={avatarUrl}
/>; />;
} else { } else {
networkIcon = <div className="noProtocolIcon"></div>; networkIcon = <div className="noProtocolIcon" />;
} }
let networkItem = null; let networkItem = null;
if (network) { if (network) {

Some files were not shown because too many files have changed in this diff Show more