Merge branch 'develop' into gsouquet/fix-18144
This commit is contained in:
commit
468887415a
119 changed files with 830 additions and 460 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
key="start_login"
|
||||||
|
onClick={() => {
|
||||||
modal.close();
|
modal.close();
|
||||||
dis.dispatch({ action: 'start_login', screenAfterLogin: options.screen_after });
|
dis.dispatch({ action: 'start_login', screenAfterLogin: options.screen_after });
|
||||||
}}>{ _t('Sign In') }</button>,
|
}}
|
||||||
|
>
|
||||||
|
{ _t('Sign In') }
|
||||||
|
</button>,
|
||||||
],
|
],
|
||||||
onFinished: (proceed) => {
|
onFinished: (proceed) => {
|
||||||
if (proceed) {
|
if (proceed) {
|
||||||
|
|
|
@ -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("We’ll generate a Security Key for you to store somewhere safe, like a password manager or a safe.") }</div>
|
<div>{ _t("We’ll 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}
|
||||||
>
|
>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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}>
|
||||||
|
|
|
@ -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}>
|
||||||
|
|
|
@ -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>,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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') }
|
||||||
|
|
|
@ -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.') }
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -100,7 +100,9 @@ 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
|
||||||
|
kind="link"
|
||||||
|
onClick={() => {
|
||||||
if (onClick) onClick();
|
if (onClick) onClick();
|
||||||
Modal.createTrackedDialog("Beta Feedback", "feature_spaces", BetaFeedbackDialog, {
|
Modal.createTrackedDialog("Beta Feedback", "feature_spaces", BetaFeedbackDialog, {
|
||||||
featureId: "feature_spaces",
|
featureId: "feature_spaces",
|
||||||
|
@ -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>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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
|
||||||
|
element="span"
|
||||||
|
className="mx_linkButton"
|
||||||
|
onClick={async event => {
|
||||||
const sessionLoaded = await this.onLoginClickWithCheck(event);
|
const sessionLoaded = await this.onLoginClickWithCheck(event);
|
||||||
if (sessionLoaded) {
|
if (sessionLoaded) {
|
||||||
dis.dispatch({ action: "view_welcome_page" });
|
dis.dispatch({ action: "view_welcome_page" });
|
||||||
}
|
}
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{ _t("Continue with previous account") }
|
{ _t("Continue with previous account") }
|
||||||
</AccessibleButton></p>
|
</AccessibleButton></p>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
@ -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> }
|
);
|
||||||
</>;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
70
src/components/views/audio_messages/AudioPlayerBase.tsx
Normal file
70
src/components/views/audio_messages/AudioPlayerBase.tsx
Normal 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> }
|
||||||
|
</>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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> }
|
);
|
||||||
</>;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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
|
||||||
|
key={i}
|
||||||
|
style={{
|
||||||
"--barHeight": h,
|
"--barHeight": h,
|
||||||
} as WaveformCSSProperties} className={classes} />;
|
} as WaveformCSSProperties}
|
||||||
|
className={classes}
|
||||||
|
/>;
|
||||||
}) }
|
}) }
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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} />
|
||||||
);
|
);
|
||||||
|
|
|
@ -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} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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")}
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
className="mx_EntityTile_ellipsis"
|
||||||
|
avatarJsx={
|
||||||
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
||||||
} name={text} presenceState="online" suppressOnHover={true}
|
}
|
||||||
onClick={() => setTruncateAt(totalCount)} />
|
name={text}
|
||||||
|
presenceState="online"
|
||||||
|
suppressOnHover={true}
|
||||||
|
onClick={() => setTruncateAt(totalCount)}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -71,13 +71,16 @@ const BetaFeedbackDialog: React.FC<IProps> = ({ featureId, onFinished }) => {
|
||||||
|
|
||||||
{ _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
|
||||||
|
kind="link"
|
||||||
|
onClick={() => {
|
||||||
onFinished(false);
|
onFinished(false);
|
||||||
defaultDispatcher.dispatch({
|
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>
|
||||||
|
|
|
@ -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'
|
||||||
>
|
>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'
|
||||||
>
|
>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
className="mx_EntityTile_ellipsis"
|
||||||
|
avatarJsx={
|
||||||
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
||||||
} name={text} presenceState="online" suppressOnHover={true}
|
}
|
||||||
onClick={() => setTruncateAt(totalCount)} />
|
name={text}
|
||||||
|
presenceState="online"
|
||||||
|
suppressOnHover={true}
|
||||||
|
onClick={() => setTruncateAt(totalCount)}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>;
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>;
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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!") }
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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", {
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
className="mx_EntityTile_ellipsis"
|
||||||
|
avatarJsx={
|
||||||
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
||||||
} name={text} presenceState="online" suppressOnHover={true}
|
}
|
||||||
onClick={this._showFullMemberList} />
|
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 }
|
||||||
|
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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
|
||||||
|
className="mx_EntityTile_ellipsis"
|
||||||
|
avatarJsx={
|
||||||
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
||||||
} name={text} presenceState="online" suppressOnHover={true}
|
}
|
||||||
onClick={this._showFullRoomList} />
|
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>
|
||||||
|
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 />
|
||||||
|
|
|
@ -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}
|
||||||
|
@ -362,10 +368,13 @@ 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
|
||||||
|
className="mx_MImageBody_thumbnail"
|
||||||
|
style={{
|
||||||
// Constrain width here so that spinner appears central to the loaded thumbnail
|
// Constrain width here so that spinner appears central to the loaded thumbnail
|
||||||
maxWidth: `min(100%, ${infoWidth}px)`,
|
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>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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 />
|
||||||
|
|
|
@ -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>,
|
||||||
})
|
})
|
||||||
|
|
|
@ -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
|
||||||
|
className="mx_UserInfo_field mx_UserInfo_verifyButton"
|
||||||
|
onClick={() => {
|
||||||
if (hasCrossSigningKeys) {
|
if (hasCrossSigningKeys) {
|
||||||
verifyUser(member as User);
|
verifyUser(member as User);
|
||||||
} else {
|
} else {
|
||||||
legacyVerifyUser(member as User);
|
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
|
||||||
|
className="mx_UserInfo_field"
|
||||||
|
onClick={() => {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: Action.ViewUserSettings,
|
action: Action.ViewUserSettings,
|
||||||
initialTabId: UserTab.Security,
|
initialTabId: UserTab.Security,
|
||||||
});
|
});
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{ _t("Edit devices") }
|
{ _t("Edit devices") }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</p>);
|
</p>);
|
||||||
|
|
|
@ -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 }
|
||||||
</>)
|
</>)
|
||||||
);
|
);
|
||||||
|
|
|
@ -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>);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
|
||||||
|
className="mx_EntityTile_ellipsis"
|
||||||
|
avatarJsx={
|
||||||
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
||||||
} name={text} presenceState="online" suppressOnHover={true}
|
}
|
||||||
onClick={onClick} />
|
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)}
|
||||||
|
/>;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
room={room}
|
||||||
|
width={AVATAR_SIZE}
|
||||||
|
height={AVATAR_SIZE}
|
||||||
|
onClick={() => {
|
||||||
defaultDispatcher.dispatch<ViewUserPayload>({
|
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>
|
||||||
|
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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")}>
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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 = () => {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -35,8 +35,10 @@ 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"
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -148,13 +148,22 @@ export default class ChangeAvatar extends React.Component {
|
||||||
if (this.props.room && !this.avatarSet) {
|
if (this.props.room && !this.avatarSet) {
|
||||||
const RoomAvatar = sdk.getComponent('avatars.RoomAvatar');
|
const RoomAvatar = sdk.getComponent('avatars.RoomAvatar');
|
||||||
avatarImg = <RoomAvatar
|
avatarImg = <RoomAvatar
|
||||||
room={this.props.room} width={this.props.width} height={this.props.height} resizeMethod='crop'
|
room={this.props.room}
|
||||||
|
width={this.props.width}
|
||||||
|
height={this.props.height}
|
||||||
|
resizeMethod='crop'
|
||||||
/>;
|
/>;
|
||||||
} else {
|
} else {
|
||||||
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
|
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
|
||||||
// XXX: FIXME: once we track in the JS what our own displayname is(!) then use it here rather than ?
|
// XXX: FIXME: once we track in the JS what our own displayname is(!) then use it here rather than ?
|
||||||
avatarImg = <BaseAvatar width={this.props.width} height={this.props.height} resizeMethod='crop'
|
avatarImg = <BaseAvatar
|
||||||
name='?' idName={MatrixClientPeg.get().getUserIdLocalpart()} url={this.state.avatarUrl} />;
|
width={this.props.width}
|
||||||
|
height={this.props.height}
|
||||||
|
resizeMethod='crop'
|
||||||
|
name='?'
|
||||||
|
idName={MatrixClientPeg.get().getUserIdLocalpart()}
|
||||||
|
url={this.state.avatarUrl}
|
||||||
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
let uploadSection;
|
let uploadSection;
|
||||||
|
|
|
@ -178,8 +178,11 @@ export default class EventIndexPanel extends React.Component<{}, IState> {
|
||||||
"appear in search results.",
|
"appear in search results.",
|
||||||
) }</div>
|
) }</div>
|
||||||
<div>
|
<div>
|
||||||
<AccessibleButton kind="primary" disabled={this.state.enabling}
|
<AccessibleButton
|
||||||
onClick={this.onEnable}>
|
kind="primary"
|
||||||
|
disabled={this.state.enabling}
|
||||||
|
onClick={this.onEnable}
|
||||||
|
>
|
||||||
{ _t("Enable") }
|
{ _t("Enable") }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
{ this.state.enabling ? <InlineSpinner /> : <div /> }
|
{ this.state.enabling ? <InlineSpinner /> : <div /> }
|
||||||
|
@ -203,8 +206,10 @@ export default class EventIndexPanel extends React.Component<{}, IState> {
|
||||||
brand,
|
brand,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
nativeLink: sub => <a href={nativeLink}
|
nativeLink: sub => <a
|
||||||
target="_blank" rel="noreferrer noopener"
|
href={nativeLink}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
>{ sub }</a>,
|
>{ sub }</a>,
|
||||||
},
|
},
|
||||||
) }</div>
|
) }</div>
|
||||||
|
@ -219,8 +224,10 @@ export default class EventIndexPanel extends React.Component<{}, IState> {
|
||||||
brand,
|
brand,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desktopLink: sub => <a href="https://element.io/get-started"
|
desktopLink: sub => <a
|
||||||
target="_blank" rel="noreferrer noopener"
|
href="https://element.io/get-started"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
>{ sub }</a>,
|
>{ sub }</a>,
|
||||||
},
|
},
|
||||||
) }</div>
|
) }</div>
|
||||||
|
|
|
@ -172,7 +172,8 @@ export default class ProfileSettings extends React.Component {
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
ref={this._avatarUpload} className="mx_ProfileSettings_avatarUpload"
|
ref={this._avatarUpload}
|
||||||
|
className="mx_ProfileSettings_avatarUpload"
|
||||||
onChange={this._onAvatarChanged}
|
onChange={this._onAvatarChanged}
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
/>
|
/>
|
||||||
|
@ -181,7 +182,8 @@ export default class ProfileSettings extends React.Component {
|
||||||
<span className="mx_SettingsTab_subheading">{ _t("Profile") }</span>
|
<span className="mx_SettingsTab_subheading">{ _t("Profile") }</span>
|
||||||
<Field
|
<Field
|
||||||
label={_t("Display Name")}
|
label={_t("Display Name")}
|
||||||
type="text" value={this.state.displayName}
|
type="text"
|
||||||
|
value={this.state.displayName}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
onChange={this._onDisplayNameChanged}
|
onChange={this._onDisplayNameChanged}
|
||||||
/>
|
/>
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue