Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into t3chguy/fix/18091
Conflicts: src/components/structures/SpaceRoomView.tsx
This commit is contained in:
commit
2114e854f9
131 changed files with 929 additions and 505 deletions
res/css
structures
views
src
Registration.js
async-components/views/dialogs/security
audio
ManagedPlayback.tsPlayback.tsPlaybackClock.tsPlaybackManager.tsRecorderWorklet.tsVoiceRecording.tscompat.tsconsts.ts
components
structures
EmbeddedPage.jsFilePanel.tsxGroupView.jsMyGroups.jsNotificationPanel.tsxRoomStatusBar.jsRoomView.tsxSearchBox.jsSpaceRoomView.tsx
auth
views
audio_messages
AudioPlayer.tsxAudioPlayerBase.tsxDurationClock.tsxLiveRecordingClock.tsxLiveRecordingWaveform.tsxPlayPauseButton.tsxPlaybackClock.tsxPlaybackWaveform.tsxRecordingPlayback.tsxSeekBar.tsxWaveform.tsx
auth
avatars
context_menus
dialogs
AddressPickerDialog.tsxBaseDialog.jsBetaFeedbackDialog.tsxBugReportDialog.tsxCommunityPrototypeInviteDialog.tsxConfirmRedactDialog.tsxConfirmUserActionDialog.tsxCreateCommunityPrototypeDialog.tsxCreateGroupDialog.tsxCreateRoomDialog.tsxCryptoStoreTooNewDialog.tsxDevtoolsDialog.tsxEditCommunityPrototypeDialog.tsxForwardDialog.tsxIncomingSasDialog.jsInfoDialog.tsxInviteDialog.tsxSessionRestoreErrorDialog.jsStorageEvictedDialog.js
security
elements
AddressTile.tsxDesktopCapturerSourcePicker.tsxImageView.tsxMiniAvatarUploader.tsxPill.jsPowerSelector.jsTooltip.tsx
emojipicker
groups
messages
CallEvent.tsxMAudioBody.tsxMImageBody.tsxMVideoBody.tsxMVoiceMessageBody.tsxRoomAvatarEvent.jsTextualBody.tsx
right_panel
rooms
|
@ -45,9 +45,14 @@ limitations under the License.
|
|||
|
||||
/* Overrides for the attachment body tiles */
|
||||
|
||||
.mx_FilePanel .mx_EventTile {
|
||||
.mx_FilePanel .mx_EventTile:not([data-layout=bubble]) {
|
||||
word-break: break-word;
|
||||
margin-top: 32px;
|
||||
margin-top: 10px;
|
||||
padding-top: 0;
|
||||
|
||||
.mx_EventTile_line {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_FilePanel .mx_EventTile .mx_MImageBody {
|
||||
|
|
|
@ -84,7 +84,7 @@ limitations under the License.
|
|||
display: inline;
|
||||
}
|
||||
|
||||
.mx_NotificationPanel .mx_EventTile_senderDetails {
|
||||
.mx_NotificationPanel .mx_EventTile:not([data-layout=bubble]) .mx_EventTile_senderDetails {
|
||||
padding-left: 36px; // align with the room name
|
||||
position: relative;
|
||||
|
||||
|
@ -105,7 +105,7 @@ limitations under the License.
|
|||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.mx_NotificationPanel .mx_EventTile_line {
|
||||
.mx_NotificationPanel .mx_EventTile:not([data-layout=bubble]) .mx_EventTile_line {
|
||||
margin-right: 0px;
|
||||
padding-left: 36px; // align with the room name
|
||||
padding-top: 0px;
|
||||
|
|
|
@ -234,6 +234,9 @@ $SpaceRoomViewInnerWidth: 428px;
|
|||
}
|
||||
|
||||
.mx_SpaceRoomView_landing {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> .mx_BaseAvatar_image,
|
||||
> .mx_BaseAvatar > .mx_BaseAvatar_image {
|
||||
border-radius: 12px;
|
||||
|
@ -340,6 +343,7 @@ $SpaceRoomViewInnerWidth: 428px;
|
|||
|
||||
.mx_SearchBox {
|
||||
margin: 0 0 20px;
|
||||
flex: 0;
|
||||
}
|
||||
|
||||
.mx_SpaceFeedbackPrompt {
|
||||
|
@ -350,6 +354,11 @@ $SpaceRoomViewInnerWidth: 428px;
|
|||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_SpaceRoomDirectory_list {
|
||||
// we don't want this container to get forced into the flexbox layout
|
||||
display: contents;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_SpaceRoomView_privateScope {
|
||||
|
|
|
@ -27,7 +27,6 @@ limitations under the License.
|
|||
// https://bugzilla.mozilla.org/show_bug.cgi?id=255139
|
||||
display: inline-block;
|
||||
user-select: none;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.mx_BaseAvatar_initial {
|
||||
|
|
|
@ -36,6 +36,10 @@ limitations under the License.
|
|||
flex-shrink: 0;
|
||||
overflow-y: auto;
|
||||
|
||||
.mx_EventTile[data-layout=bubble] {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
div {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ limitations under the License.
|
|||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
margin-bottom: 8px;
|
||||
padding-left: 10px;
|
||||
padding: 0 10px;
|
||||
border-left: 2px solid $button-bg-color;
|
||||
border-radius: 2px;
|
||||
|
||||
|
|
|
@ -16,10 +16,6 @@ limitations under the License.
|
|||
|
||||
$timelineImageBorderRadius: 4px;
|
||||
|
||||
.mx_MImageBody {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mx_MImageBody_thumbnail {
|
||||
object-fit: contain;
|
||||
border-radius: $timelineImageBorderRadius;
|
||||
|
@ -28,7 +24,7 @@ $timelineImageBorderRadius: 4px;
|
|||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
> canvas {
|
||||
> div > canvas {
|
||||
border-radius: $timelineImageBorderRadius;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,8 @@ limitations under the License.
|
|||
padding-top: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:hover,
|
||||
&.mx_EventTile_selected {
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
|
@ -97,6 +98,11 @@ limitations under the License.
|
|||
.mx_SenderProfile {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mx_ReplyTile .mx_SenderProfile {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mx_ReactionsRow {
|
||||
float: right;
|
||||
clear: right;
|
||||
|
@ -150,12 +156,24 @@ limitations under the License.
|
|||
position: absolute;
|
||||
top: 0;
|
||||
line-height: 1;
|
||||
z-index: 9;
|
||||
img {
|
||||
box-shadow: 0 0 0 3px $eventbubble-avatar-outline;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
&.mx_EventTile_noSender {
|
||||
.mx_EventTile_avatar {
|
||||
top: -19px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_BaseAvatar,
|
||||
.mx_EventTile_avatar {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
&[data-has-reply=true] {
|
||||
> .mx_EventTile_line {
|
||||
flex-direction: column;
|
||||
|
@ -215,6 +233,7 @@ limitations under the License.
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 5px 0;
|
||||
|
||||
.mx_EventTile_avatar {
|
||||
position: static;
|
||||
|
@ -281,7 +300,7 @@ limitations under the License.
|
|||
& + .mx_EventListSummary {
|
||||
.mx_EventTile {
|
||||
margin-top: 0;
|
||||
padding: 0;
|
||||
padding: 2px 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -132,7 +132,8 @@ $hover-select-border: 4px;
|
|||
}
|
||||
}
|
||||
|
||||
&.mx_EventTile_info .mx_EventTile_line {
|
||||
&.mx_EventTile_info .mx_EventTile_line,
|
||||
& ~ .mx_EventListSummary .mx_EventTile_avatar ~ .mx_EventTile_line {
|
||||
padding-left: calc($left-gutter + 18px);
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ $left-gutter: 64px;
|
|||
|
||||
> .mx_EventTile_avatar {
|
||||
position: absolute;
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
.mx_MessageTimestamp {
|
||||
|
|
|
@ -116,6 +116,11 @@ $irc-line-height: $font-18px;
|
|||
.mx_EditMessageComposer_buttons {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mx_ReactionsRow {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_EventTile_emote {
|
||||
|
|
|
@ -34,7 +34,7 @@ limitations under the License.
|
|||
.mx_LinkPreviewWidget_caption {
|
||||
margin-left: 15px;
|
||||
flex: 1 1 auto;
|
||||
overflow-x: hidden; // cause it to wrap rather than clip
|
||||
overflow: hidden; // cause it to wrap rather than clip
|
||||
}
|
||||
|
||||
.mx_LinkPreviewWidget_title {
|
||||
|
|
|
@ -51,10 +51,15 @@ export async function startAnyRegistrationFlow(options) {
|
|||
description: _t("Use your account or create a new one to continue."),
|
||||
button: _t("Create Account"),
|
||||
extraButtons: [
|
||||
<button key="start_login" onClick={() => {
|
||||
modal.close();
|
||||
dis.dispatch({ action: 'start_login', screenAfterLogin: options.screen_after });
|
||||
}}>{ _t('Sign In') }</button>,
|
||||
<button
|
||||
key="start_login"
|
||||
onClick={() => {
|
||||
modal.close();
|
||||
dis.dispatch({ action: 'start_login', screenAfterLogin: options.screen_after });
|
||||
}}
|
||||
>
|
||||
{ _t('Sign In') }
|
||||
</button>,
|
||||
],
|
||||
onFinished: (proceed) => {
|
||||
if (proceed) {
|
||||
|
|
|
@ -269,7 +269,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
|||
|
||||
<details>
|
||||
<summary>{ _t("Advanced") }</summary>
|
||||
<AccessibleButton kind='primary' onClick={this._onSkipPassPhraseClick} >
|
||||
<AccessibleButton kind='primary' onClick={this._onSkipPassPhraseClick}>
|
||||
{ _t("Set up with a Security Key") }
|
||||
</AccessibleButton>
|
||||
</details>
|
||||
|
|
|
@ -474,7 +474,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
outlined
|
||||
>
|
||||
<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") }
|
||||
</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
|
||||
>
|
||||
<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") }
|
||||
</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>
|
||||
</div>
|
||||
<div className="mx_CreateSecretStorageDialog_recoveryKeyButtons">
|
||||
<AccessibleButton kind='primary' className="mx_Dialog_primary"
|
||||
<AccessibleButton kind='primary'
|
||||
className="mx_Dialog_primary"
|
||||
onClick={this._onDownloadClick}
|
||||
disabled={this.state.phase === PHASE_STORING}
|
||||
>
|
||||
|
|
|
@ -148,8 +148,12 @@ export default class ExportE2eKeysDialog extends React.Component {
|
|||
</label>
|
||||
</div>
|
||||
<div className='mx_E2eKeysDialog_inputCell'>
|
||||
<input ref={this._passphrase1} id='passphrase1'
|
||||
autoFocus={true} size='64' type='password'
|
||||
<input
|
||||
ref={this._passphrase1}
|
||||
id='passphrase1'
|
||||
autoFocus={true}
|
||||
size='64'
|
||||
type='password'
|
||||
disabled={disableForm}
|
||||
/>
|
||||
</div>
|
||||
|
@ -161,8 +165,10 @@ export default class ExportE2eKeysDialog extends React.Component {
|
|||
</label>
|
||||
</div>
|
||||
<div className='mx_E2eKeysDialog_inputCell'>
|
||||
<input ref={this._passphrase2} id='passphrase2'
|
||||
size='64' type='password'
|
||||
<input ref={this._passphrase2}
|
||||
id='passphrase2'
|
||||
size='64'
|
||||
type='password'
|
||||
disabled={disableForm}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -174,7 +174,10 @@ export default class ImportE2eKeysDialog extends React.Component {
|
|||
</div>
|
||||
</div>
|
||||
<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}
|
||||
/>
|
||||
<button onClick={this._onCancelClick} disabled={disableForm}>
|
||||
|
|
|
@ -120,8 +120,7 @@ export default class EmbeddedPage extends React.PureComponent {
|
|||
|
||||
const content = <div className={`${className}_body`}
|
||||
dangerouslySetInnerHTML={{ __html: this.state.page }}
|
||||
>
|
||||
</div>;
|
||||
/>;
|
||||
|
||||
if (this.props.scrollbar) {
|
||||
return <AutoHideScrollbar className={classes}>
|
||||
|
|
|
@ -36,6 +36,7 @@ import ResizeNotifier from '../../utils/ResizeNotifier';
|
|||
import TimelinePanel from "./TimelinePanel";
|
||||
import Spinner from "../views/elements/Spinner";
|
||||
import { TileShape } from '../views/rooms/EventTile';
|
||||
import { Layout } from "../../settings/Layout";
|
||||
|
||||
interface IProps {
|
||||
roomId: string;
|
||||
|
@ -267,6 +268,7 @@ class FilePanel extends React.Component<IProps, IState> {
|
|||
tileShape={TileShape.FileGrid}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
empty={emptyState}
|
||||
layout={Layout.Group}
|
||||
/>
|
||||
</BaseCard>
|
||||
);
|
||||
|
|
|
@ -222,7 +222,7 @@ class FeaturedRoom extends React.Component {
|
|||
|
||||
let roomNameNode = null;
|
||||
if (permalink) {
|
||||
roomNameNode = <a href={permalink} onClick={this.onClick} >{ roomName }</a>;
|
||||
roomNameNode = <a href={permalink} onClick={this.onClick}>{ roomName }</a>;
|
||||
} else {
|
||||
roomNameNode = <span>{ roomName }</span>;
|
||||
}
|
||||
|
@ -1185,10 +1185,13 @@ export default class GroupView extends React.Component {
|
|||
avatarImage = <Spinner />;
|
||||
} else {
|
||||
const GroupAvatar = sdk.getComponent('avatars.GroupAvatar');
|
||||
avatarImage = <GroupAvatar groupId={this.props.groupId}
|
||||
avatarImage = <GroupAvatar
|
||||
groupId={this.props.groupId}
|
||||
groupName={this.state.profileForm.name}
|
||||
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>
|
||||
<div className="mx_GroupView_avatarPicker_edit">
|
||||
<label htmlFor="avatarInput" className="mx_GroupView_avatarPicker_label">
|
||||
<img src={require("../../../res/img/camera.svg")}
|
||||
alt={_t("Upload avatar")} title={_t("Upload avatar")}
|
||||
width="17" height="15" />
|
||||
<img
|
||||
src={require("../../../res/img/camera.svg")}
|
||||
alt={_t("Upload avatar")}
|
||||
title={_t("Upload avatar")}
|
||||
width="17"
|
||||
height="15" />
|
||||
</label>
|
||||
<input id="avatarInput" className="mx_GroupView_uploadInput" type="file" onChange={this._onAvatarSelected} />
|
||||
</div>
|
||||
|
@ -1238,7 +1244,8 @@ export default class GroupView extends React.Component {
|
|||
groupAvatarUrl={groupAvatarUrl}
|
||||
groupName={groupName}
|
||||
onClick={onGroupHeaderItemClick}
|
||||
width={28} height={28}
|
||||
width={28}
|
||||
height={28}
|
||||
/>;
|
||||
if (summary.profile && summary.profile.name) {
|
||||
nameNode = <div onClick={onGroupHeaderItemClick}>
|
||||
|
@ -1269,28 +1276,32 @@ export default class GroupView extends React.Component {
|
|||
key="_cancelButton"
|
||||
onClick={this._onCancelClick}
|
||||
>
|
||||
<img src={require("../../../res/img/cancel.svg")} className="mx_filterFlipColor"
|
||||
width="18" height="18" alt={_t("Cancel")} />
|
||||
<img
|
||||
src={require("../../../res/img/cancel.svg")}
|
||||
className="mx_filterFlipColor"
|
||||
width="18"
|
||||
height="18"
|
||||
alt={_t("Cancel")} />
|
||||
</AccessibleButton>,
|
||||
);
|
||||
} else {
|
||||
if (summary.user && summary.user.membership === 'join') {
|
||||
rightButtons.push(
|
||||
<AccessibleButton className="mx_GroupHeader_button mx_GroupHeader_editButton"
|
||||
<AccessibleButton
|
||||
className="mx_GroupHeader_button mx_GroupHeader_editButton"
|
||||
key="_editButton"
|
||||
onClick={this._onEditClick}
|
||||
title={_t("Community Settings")}
|
||||
>
|
||||
</AccessibleButton>,
|
||||
/>,
|
||||
);
|
||||
}
|
||||
rightButtons.push(
|
||||
<AccessibleButton className="mx_GroupHeader_button mx_GroupHeader_shareButton"
|
||||
<AccessibleButton
|
||||
className="mx_GroupHeader_button mx_GroupHeader_shareButton"
|
||||
key="_shareButton"
|
||||
onClick={this._onShareClick}
|
||||
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")} />
|
||||
<div className='mx_MyGroups_header'>
|
||||
<div className="mx_MyGroups_headerCard">
|
||||
<AccessibleButton className='mx_MyGroups_headerCard_button' onClick={this._onCreateGroupClick}>
|
||||
</AccessibleButton>
|
||||
<AccessibleButton className='mx_MyGroups_headerCard_button' onClick={this._onCreateGroupClick} />
|
||||
<div className="mx_MyGroups_headerCard_content">
|
||||
<div className="mx_MyGroups_headerCard_header">
|
||||
{ _t('Create a new community') }
|
||||
|
|
|
@ -23,6 +23,7 @@ import { replaceableComponent } from "../../utils/replaceableComponent";
|
|||
import TimelinePanel from "./TimelinePanel";
|
||||
import Spinner from "../views/elements/Spinner";
|
||||
import { TileShape } from "../views/rooms/EventTile";
|
||||
import { Layout } from "../../settings/Layout";
|
||||
|
||||
interface IProps {
|
||||
onClose(): void;
|
||||
|
@ -52,6 +53,7 @@ export default class NotificationPanel extends React.PureComponent<IProps> {
|
|||
tileShape={TileShape.Notif}
|
||||
empty={emptyState}
|
||||
alwaysShowTimestamps={true}
|
||||
layout={Layout.Group}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
|
|
|
@ -266,8 +266,12 @@ export default class RoomStatusBar extends React.PureComponent {
|
|||
<div className="mx_RoomStatusBar">
|
||||
<div role="alert">
|
||||
<div className="mx_RoomStatusBar_connectionLostBar">
|
||||
<img src={require("../../../res/img/feather-customised/warning-triangle.svg")} width="24"
|
||||
height="24" title="/!\ " alt="/!\ " />
|
||||
<img
|
||||
src={require("../../../res/img/feather-customised/warning-triangle.svg")}
|
||||
width="24"
|
||||
height="24"
|
||||
title="/!\ "
|
||||
alt="/!\ " />
|
||||
<div>
|
||||
<div className="mx_RoomStatusBar_connectionLostBar_title">
|
||||
{ _t('Connectivity to the server has been lost.') }
|
||||
|
|
|
@ -1740,7 +1740,8 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
onJoinClick={this.onJoinButtonClicked}
|
||||
onForgetClick={this.onForgetClick}
|
||||
onRejectClick={this.onRejectThreepidInviteButtonClicked}
|
||||
canPreview={false} error={this.state.roomLoadError}
|
||||
canPreview={false}
|
||||
error={this.state.roomLoadError}
|
||||
roomAlias={roomAlias}
|
||||
joining={this.state.joining}
|
||||
inviterName={inviterName}
|
||||
|
|
|
@ -136,8 +136,8 @@ export default class SearchBox extends React.Component {
|
|||
key="button"
|
||||
tabIndex={-1}
|
||||
className="mx_SearchBox_closeButton"
|
||||
onClick={() => {this._clearSearch("button"); }}>
|
||||
</AccessibleButton>) : undefined;
|
||||
onClick={() => {this._clearSearch("button"); }}
|
||||
/>) : undefined;
|
||||
|
||||
// show a shorter placeholder when blurred, if requested
|
||||
// this is used for the room filter field that has
|
||||
|
|
|
@ -111,12 +111,14 @@ export const SpaceFeedbackPrompt = ({ onClick }: { onClick?: () => void }) => {
|
|||
<hr />
|
||||
<div>
|
||||
<span className="mx_SpaceFeedbackPrompt_text">{ _t("Spaces are a beta feature.") }</span>
|
||||
<AccessibleButton kind="link" onClick={() => {
|
||||
if (onClick) onClick();
|
||||
Modal.createTrackedDialog("Beta Feedback", "feature_spaces", BetaFeedbackDialog, {
|
||||
<AccessibleButton
|
||||
kind="link"
|
||||
onClick={() => {
|
||||
if (onClick) onClick();
|
||||
Modal.createTrackedDialog("Beta Feedback", "feature_spaces", BetaFeedbackDialog, {
|
||||
featureId: "feature_spaces",
|
||||
});
|
||||
}}>
|
||||
});
|
||||
}}>
|
||||
{ _t("Feedback") }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
|
@ -581,7 +583,6 @@ const SpaceAddExistingRooms = ({ space, onFinished }) => {
|
|||
spacesRenderer={defaultSpacesRenderer}
|
||||
dmsRenderer={defaultDmsRenderer}
|
||||
/>
|
||||
|
||||
<SpaceFeedbackPrompt />
|
||||
</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 " +
|
||||
"link it contains, click below.", { emailAddress: this.state.email }) }
|
||||
<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')} />
|
||||
</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 " +
|
||||
"device.",
|
||||
) }</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')} />
|
||||
</div>;
|
||||
}
|
||||
|
|
|
@ -463,7 +463,9 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
|||
"Either use HTTPS or <a>enable unsafe scripts</a>.", {},
|
||||
{
|
||||
'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"
|
||||
>
|
||||
{ sub }
|
||||
|
|
|
@ -557,12 +557,16 @@ export default class Registration extends React.Component<IProps, IState> {
|
|||
loggedInUserId: this.state.differentLoggedInUserId,
|
||||
},
|
||||
) }</p>
|
||||
<p><AccessibleButton element="span" className="mx_linkButton" onClick={async event => {
|
||||
const sessionLoaded = await this.onLoginClickWithCheck(event);
|
||||
if (sessionLoaded) {
|
||||
dis.dispatch({ action: "view_welcome_page" });
|
||||
}
|
||||
}}>
|
||||
<p><AccessibleButton
|
||||
element="span"
|
||||
className="mx_linkButton"
|
||||
onClick={async event => {
|
||||
const sessionLoaded = await this.onLoginClickWithCheck(event);
|
||||
if (sessionLoaded) {
|
||||
dis.dispatch({ action: "view_welcome_page" });
|
||||
}
|
||||
}}
|
||||
>
|
||||
{ _t("Continue with previous account") }
|
||||
</AccessibleButton></p>
|
||||
</div>;
|
||||
|
|
|
@ -14,9 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { Playback, PlaybackState } from "../../../voice/Playback";
|
||||
import React, { createRef, ReactNode, RefObject } from "react";
|
||||
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
||||
import PlayPauseButton from "./PlayPauseButton";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { formatBytes } from "../../../utils/FormattingUtils";
|
||||
|
@ -25,47 +23,13 @@ import { Key } from "../../../Keyboard";
|
|||
import { _t } from "../../../languageHandler";
|
||||
import SeekBar from "./SeekBar";
|
||||
import PlaybackClock from "./PlaybackClock";
|
||||
|
||||
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;
|
||||
}
|
||||
import AudioPlayerBase from "./AudioPlayerBase";
|
||||
|
||||
@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 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) => {
|
||||
// stopPropagation() prevents the FocusComposer catch-all from triggering,
|
||||
// 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)})`;
|
||||
}
|
||||
|
||||
public render(): ReactNode {
|
||||
protected renderComponent(): ReactNode {
|
||||
// tabIndex=0 to ensure that the whole component becomes a tab stop, where we handle keyboard
|
||||
// events for accessibility
|
||||
return <>
|
||||
return (
|
||||
<div className='mx_MediaBody mx_AudioPlayer_container' tabIndex={0} onKeyDown={this.onKeyDown}>
|
||||
<div className='mx_AudioPlayer_primaryContainer'>
|
||||
<PlayPauseButton
|
||||
|
@ -124,7 +88,6 @@ export default class AudioPlayer extends React.PureComponent<IProps, IState> {
|
|||
<PlaybackClock playback={this.props.playback} defaultDisplaySeconds={0} />
|
||||
</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 { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import Clock from "./Clock";
|
||||
import { Playback } from "../../../voice/Playback";
|
||||
import { Playback } from "../../../audio/Playback";
|
||||
|
||||
interface IProps {
|
||||
playback: Playback;
|
||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
import { IRecordingUpdate, VoiceRecording } from "../../../voice/VoiceRecording";
|
||||
import { IRecordingUpdate, VoiceRecording } from "../../../audio/VoiceRecording";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import Clock from "./Clock";
|
||||
import { MarkedExecution } from "../../../utils/MarkedExecution";
|
||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
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 { arrayFastResample } from "../../../utils/arrays";
|
||||
import { percentageOf } from "../../../utils/numbers";
|
||||
|
|
|
@ -18,7 +18,7 @@ import React, { ReactNode } from "react";
|
|||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { Playback, PlaybackState } from "../../../voice/Playback";
|
||||
import { Playback, PlaybackState } from "../../../audio/Playback";
|
||||
import classNames from "classnames";
|
||||
|
||||
// omitted props are handled by render function
|
||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||
import React from "react";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import Clock from "./Clock";
|
||||
import { Playback, PlaybackState } from "../../../voice/Playback";
|
||||
import { Playback, PlaybackState } from "../../../audio/Playback";
|
||||
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
||||
|
||||
interface IProps {
|
||||
|
|
|
@ -18,7 +18,7 @@ import React from "react";
|
|||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { arraySeed, arrayTrimFill } from "../../../utils/arrays";
|
||||
import Waveform from "./Waveform";
|
||||
import { Playback, PLAYBACK_WAVEFORM_SAMPLES } from "../../../voice/Playback";
|
||||
import { Playback, PLAYBACK_WAVEFORM_SAMPLES } from "../../../audio/Playback";
|
||||
import { percentageOf } from "../../../utils/numbers";
|
||||
|
||||
interface IProps {
|
||||
|
|
|
@ -14,68 +14,30 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { Playback, PlaybackState } from "../../../voice/Playback";
|
||||
import React, { ReactNode } from "react";
|
||||
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
||||
import PlayPauseButton from "./PlayPauseButton";
|
||||
import PlaybackClock from "./PlaybackClock";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { TileShape } from "../rooms/EventTile";
|
||||
import PlaybackWaveform from "./PlaybackWaveform";
|
||||
import { _t } from "../../../languageHandler";
|
||||
|
||||
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;
|
||||
}
|
||||
import AudioPlayerBase from "./AudioPlayerBase";
|
||||
|
||||
@replaceableComponent("views.audio_messages.RecordingPlayback")
|
||||
export default class RecordingPlayback 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 });
|
||||
});
|
||||
}
|
||||
|
||||
export default class RecordingPlayback extends AudioPlayerBase {
|
||||
private get isWaveformable(): boolean {
|
||||
return this.props.tileShape !== TileShape.Notif
|
||||
&& this.props.tileShape !== TileShape.FileGrid
|
||||
&& this.props.tileShape !== TileShape.Pinned;
|
||||
}
|
||||
|
||||
private onPlaybackUpdate = (ev: PlaybackState) => {
|
||||
this.setState({ playbackPhase: ev });
|
||||
};
|
||||
|
||||
public render(): ReactNode {
|
||||
protected renderComponent(): ReactNode {
|
||||
const shapeClass = !this.isWaveformable ? 'mx_VoiceMessagePrimaryContainer_noWaveform' : '';
|
||||
return <>
|
||||
return (
|
||||
<div className={'mx_MediaBody mx_VoiceMessagePrimaryContainer ' + shapeClass}>
|
||||
<PlayPauseButton playback={this.props.playback} playbackPhase={this.state.playbackPhase} />
|
||||
<PlaybackClock playback={this.props.playback} />
|
||||
{ this.isWaveformable && <PlaybackWaveform playback={this.props.playback} /> }
|
||||
</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.
|
||||
*/
|
||||
|
||||
import { Playback, PlaybackState } from "../../../voice/Playback";
|
||||
import { Playback, PlaybackState } from "../../../audio/Playback";
|
||||
import React, { ChangeEvent, CSSProperties, ReactNode } from "react";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
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_100pct': isCompleteBar,
|
||||
});
|
||||
return <span key={i} style={{
|
||||
"--barHeight": h,
|
||||
} as WaveformCSSProperties} className={classes} />;
|
||||
return <span
|
||||
key={i}
|
||||
style={{
|
||||
"--barHeight": h,
|
||||
} as WaveformCSSProperties}
|
||||
className={classes}
|
||||
/>;
|
||||
}) }
|
||||
</div>;
|
||||
}
|
||||
|
|
|
@ -416,8 +416,10 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
|
|||
let submitButton;
|
||||
if (this.props.showContinue !== false) {
|
||||
// XXX: button classes
|
||||
submitButton = <button className="mx_InteractiveAuthEntryComponents_termsSubmit mx_GeneralButton"
|
||||
onClick={this.trySubmit} disabled={!allChecked}>{ _t("Accept") }</button>;
|
||||
submitButton = <button
|
||||
className="mx_InteractiveAuthEntryComponents_termsSubmit mx_GeneralButton"
|
||||
onClick={this.trySubmit}
|
||||
disabled={!allChecked}>{ _t("Accept") }</button>;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -616,7 +618,9 @@ export class MsisdnAuthEntry extends React.Component<IMsisdnAuthEntryProps, IMsi
|
|||
aria-label={_t("Code")}
|
||||
/>
|
||||
<br />
|
||||
<input type="submit" value={_t("Submit")}
|
||||
<input
|
||||
type="submit"
|
||||
value={_t("Submit")}
|
||||
className={submitClasses}
|
||||
disabled={!enableSubmit}
|
||||
/>
|
||||
|
|
|
@ -187,7 +187,8 @@ const BaseAvatar = (props: IProps) => {
|
|||
width: toPx(width),
|
||||
height: toPx(height),
|
||||
}}
|
||||
title={title} alt={_t("Avatar")}
|
||||
title={title}
|
||||
alt={_t("Avatar")}
|
||||
inputRef={inputRef}
|
||||
{...otherProps} />
|
||||
);
|
||||
|
@ -201,7 +202,8 @@ const BaseAvatar = (props: IProps) => {
|
|||
width: toPx(width),
|
||||
height: toPx(height),
|
||||
}}
|
||||
title={title} alt=""
|
||||
title={title}
|
||||
alt=""
|
||||
ref={inputRef}
|
||||
{...otherProps} />
|
||||
);
|
||||
|
|
|
@ -102,8 +102,12 @@ export default class MemberAvatar extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
return (
|
||||
<BaseAvatar {...otherProps} name={this.state.name} title={this.state.title}
|
||||
idName={userId} url={this.state.imageUrl} onClick={onClick} />
|
||||
<BaseAvatar {...otherProps}
|
||||
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} />
|
||||
</div>
|
||||
<div className="mx_DialPadContextMenu_header">
|
||||
<Field className="mx_DialPadContextMenu_dialled"
|
||||
value={this.state.value} autoFocus={true}
|
||||
<Field
|
||||
className="mx_DialPadContextMenu_dialled"
|
||||
value={this.state.value}
|
||||
autoFocus={true}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -109,8 +109,10 @@ export default class StatusMessageContextMenu extends React.Component {
|
|||
</AccessibleButton>;
|
||||
}
|
||||
} else {
|
||||
actionButton = <AccessibleButton className="mx_StatusMessageContextMenu_submit"
|
||||
disabled={!this.state.message} onClick={this._onSubmit}
|
||||
actionButton = <AccessibleButton
|
||||
className="mx_StatusMessageContextMenu_submit"
|
||||
disabled={!this.state.message}
|
||||
onClick={this._onSubmit}
|
||||
>
|
||||
<span>{ _t("Set status") }</span>
|
||||
</AccessibleButton>;
|
||||
|
@ -121,12 +123,19 @@ export default class StatusMessageContextMenu extends React.Component {
|
|||
spinner = <Spinner w="24" h="24" />;
|
||||
}
|
||||
|
||||
const form = <form className="mx_StatusMessageContextMenu_form"
|
||||
autoComplete="off" onSubmit={this._onSubmit}
|
||||
const form = <form
|
||||
className="mx_StatusMessageContextMenu_form"
|
||||
autoComplete="off"
|
||||
onSubmit={this._onSubmit}
|
||||
>
|
||||
<input type="text" className="mx_StatusMessageContextMenu_message"
|
||||
key="message" placeholder={_t("Set a new status...")}
|
||||
autoFocus={true} maxLength="60" value={this.state.message}
|
||||
<input
|
||||
type="text"
|
||||
className="mx_StatusMessageContextMenu_message"
|
||||
key="message"
|
||||
placeholder={_t("Set a new status...")}
|
||||
autoFocus={true}
|
||||
maxLength="60"
|
||||
value={this.state.message}
|
||||
onChange={this._onStatusChange}
|
||||
/>
|
||||
<div className="mx_StatusMessageContextMenu_actionContainer">
|
||||
|
|
|
@ -76,7 +76,8 @@ const WidgetContextMenu: React.FC<IProps> = ({
|
|||
onFinished();
|
||||
};
|
||||
streamAudioStreamButton = <IconizedContextMenuOption
|
||||
onClick={onStreamAudioClick} label={_t("Start audio stream")}
|
||||
onClick={onStreamAudioClick}
|
||||
label={_t("Start audio stream")}
|
||||
/>;
|
||||
}
|
||||
|
||||
|
|
|
@ -665,8 +665,8 @@ export default class AddressPickerDialog extends React.Component<IProps, IState>
|
|||
onChange={this.onQueryChanged}
|
||||
placeholder={this.getPlaceholder()}
|
||||
defaultValue={this.props.value}
|
||||
autoFocus={this.props.focus}>
|
||||
</textarea>,
|
||||
autoFocus={this.props.focus}
|
||||
/>,
|
||||
);
|
||||
|
||||
const filteredSuggestedList = this.getFilteredSuggestions();
|
||||
|
@ -727,8 +727,12 @@ export default class AddressPickerDialog extends React.Component<IProps, IState>
|
|||
}
|
||||
|
||||
return (
|
||||
<BaseDialog className="mx_AddressPickerDialog" onKeyDown={this.onKeyDown}
|
||||
onFinished={this.props.onFinished} title={this.props.title}>
|
||||
<BaseDialog
|
||||
className="mx_AddressPickerDialog"
|
||||
onKeyDown={this.onKeyDown}
|
||||
onFinished={this.props.onFinished}
|
||||
title={this.props.title}
|
||||
>
|
||||
{ inputLabel }
|
||||
<div className="mx_Dialog_content">
|
||||
<div className="mx_AddressPickerDialog_inputContainer">{ query }</div>
|
||||
|
|
|
@ -118,9 +118,7 @@ export default class BaseDialog extends React.Component {
|
|||
|
||||
let headerImage;
|
||||
if (this.props.headerImage) {
|
||||
headerImage = <img className="mx_Dialog_titleImage" src={this.props.headerImage}
|
||||
alt=""
|
||||
/>;
|
||||
headerImage = <img className="mx_Dialog_titleImage" src={this.props.headerImage} alt="" />;
|
||||
}
|
||||
|
||||
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.") }
|
||||
|
||||
<AccessibleButton kind="link" onClick={() => {
|
||||
onFinished(false);
|
||||
defaultDispatcher.dispatch({
|
||||
<AccessibleButton
|
||||
kind="link"
|
||||
onClick={() => {
|
||||
onFinished(false);
|
||||
defaultDispatcher.dispatch({
|
||||
action: Action.ViewUserSettings,
|
||||
initialTabId: UserTab.Labs,
|
||||
});
|
||||
}}>
|
||||
});
|
||||
}}
|
||||
>
|
||||
{ _t("To leave the beta, visit your settings.") }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
|
|
|
@ -188,7 +188,9 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
return (
|
||||
<BaseDialog className="mx_BugReportDialog" onFinished={this.onCancel}
|
||||
<BaseDialog
|
||||
className="mx_BugReportDialog"
|
||||
onFinished={this.onCancel}
|
||||
title={_t('Submit debug logs')}
|
||||
contentId='mx_Dialog_content'
|
||||
>
|
||||
|
|
|
@ -205,9 +205,12 @@ export default class CommunityPrototypeInviteDialog extends React.PureComponent<
|
|||
people.push((
|
||||
<AccessibleButton
|
||||
onClick={this.onShowMorePeople}
|
||||
kind="link" key="more"
|
||||
kind="link"
|
||||
key="more"
|
||||
className="mx_CommunityPrototypeInviteDialog_morePeople"
|
||||
>{ _t("Show more") }</AccessibleButton>
|
||||
>
|
||||
{ _t("Show more") }
|
||||
</AccessibleButton>
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -240,10 +243,13 @@ export default class CommunityPrototypeInviteDialog extends React.PureComponent<
|
|||
{ peopleIntro }
|
||||
{ people }
|
||||
<AccessibleButton
|
||||
kind="primary" onClick={this.onSubmit}
|
||||
kind="primary"
|
||||
onClick={this.onSubmit}
|
||||
disabled={this.state.busy}
|
||||
className="mx_CommunityPrototypeInviteDialog_primaryButton"
|
||||
>{ buttonText }</AccessibleButton>
|
||||
>
|
||||
{ buttonText }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</form>
|
||||
</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.")}
|
||||
placeholder={_t("Reason (optional)")}
|
||||
focus
|
||||
button={_t("Remove")}>
|
||||
</TextInputDialog>
|
||||
button={_t("Remove")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,7 +104,9 @@ export default class ConfirmUserActionDialog extends React.Component<IProps> {
|
|||
}
|
||||
|
||||
return (
|
||||
<BaseDialog className="mx_ConfirmUserActionDialog" onFinished={this.props.onFinished}
|
||||
<BaseDialog
|
||||
className="mx_ConfirmUserActionDialog"
|
||||
onFinished={this.props.onFinished}
|
||||
title={this.props.title}
|
||||
contentId='mx_Dialog_content'
|
||||
>
|
||||
|
|
|
@ -204,8 +204,10 @@ export default class CreateCommunityPrototypeDialog extends React.PureComponent<
|
|||
</div>
|
||||
<div className="mx_CreateCommunityPrototypeDialog_colAvatar">
|
||||
<input
|
||||
type="file" style={{ display: "none" }}
|
||||
ref={this.avatarUploadRef} accept="image/*"
|
||||
type="file"
|
||||
style={{ display: "none" }}
|
||||
ref={this.avatarUploadRef}
|
||||
accept="image/*"
|
||||
onChange={this.onAvatarChanged}
|
||||
/>
|
||||
<AccessibleButton
|
||||
|
|
|
@ -123,7 +123,9 @@ export default class CreateGroupDialog extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
return (
|
||||
<BaseDialog className="mx_CreateGroupDialog" onFinished={this.props.onFinished}
|
||||
<BaseDialog
|
||||
className="mx_CreateGroupDialog"
|
||||
onFinished={this.props.onFinished}
|
||||
title={_t('Create Community')}
|
||||
>
|
||||
<form onSubmit={this.onFormSubmit}>
|
||||
|
@ -133,8 +135,11 @@ export default class CreateGroupDialog extends React.Component<IProps, IState> {
|
|||
<label htmlFor="groupname">{ _t('Community Name') }</label>
|
||||
</div>
|
||||
<div>
|
||||
<input id="groupname" className="mx_CreateGroupDialog_input"
|
||||
autoFocus={true} size={64}
|
||||
<input
|
||||
id="groupname"
|
||||
className="mx_CreateGroupDialog_input"
|
||||
autoFocus={true}
|
||||
size={64}
|
||||
placeholder={_t('Example')}
|
||||
onChange={this.onGroupNameChange}
|
||||
value={this.state.groupName}
|
||||
|
|
|
@ -93,13 +93,18 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
|||
const opts: IOpts = {};
|
||||
const createOpts: IOpts["createOpts"] = opts.createOpts = {};
|
||||
createOpts.name = this.state.name;
|
||||
|
||||
if (this.state.joinRule === JoinRule.Public) {
|
||||
createOpts.visibility = Visibility.Public;
|
||||
createOpts.preset = Preset.PublicChat;
|
||||
opts.guestAccess = false;
|
||||
const { alias } = this.state;
|
||||
createOpts.room_alias_name = alias.substr(1, alias.indexOf(":") - 1);
|
||||
} else {
|
||||
// If we cannot change encryption we pass `true` for safety, the server should automatically do this for us.
|
||||
opts.encryption = this.state.canChangeEncryption ? this.state.isEncrypted : true;
|
||||
}
|
||||
|
||||
if (this.state.topic) {
|
||||
createOpts.topic = this.state.topic;
|
||||
}
|
||||
|
@ -107,16 +112,6 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
|||
createOpts.creation_content = { 'm.federate': false };
|
||||
}
|
||||
|
||||
if (this.state.joinRule !== JoinRule.Public) {
|
||||
if (this.state.canChangeEncryption) {
|
||||
opts.encryption = this.state.isEncrypted;
|
||||
} else {
|
||||
// the server should automatically do this for us, but for safety
|
||||
// we'll demand it too.
|
||||
opts.encryption = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (CommunityPrototypeStore.instance.getSelectedCommunityId()) {
|
||||
opts.associatedWithCommunity = CommunityPrototypeStore.instance.getSelectedCommunityId();
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ const CryptoStoreTooNewDialog: React.FC<IProps> = (props: IProps) => {
|
|||
hasCancel={false}
|
||||
onPrimaryButtonClick={props.onFinished}
|
||||
>
|
||||
<button onClick={_onLogoutClicked} >
|
||||
<button onClick={_onLogoutClicked}>
|
||||
{ _t('Sign out') }
|
||||
</button>
|
||||
</DialogButtons>
|
||||
|
|
|
@ -182,14 +182,23 @@ export class SendCustomEvent extends GenericEditor<ISendCustomEventProps, ISendC
|
|||
|
||||
<br />
|
||||
|
||||
<Field id="evContent" label={_t("Event Content")} type="text" className="mx_DevTools_textarea"
|
||||
autoComplete="off" value={this.state.evContent} onChange={this.onChange} element="textarea" />
|
||||
<Field
|
||||
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 className="mx_Dialog_buttons">
|
||||
<button onClick={this.onBack}>{ _t('Back') }</button>
|
||||
{ !this.state.message && <button onClick={this.send}>{ _t('Send') }</button> }
|
||||
{ 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"
|
||||
checked={this.state.isStateEvent}
|
||||
onChange={this.onChange}
|
||||
|
@ -282,14 +291,24 @@ class SendAccountData extends GenericEditor<ISendAccountDataProps, ISendAccountD
|
|||
{ this.textInput('eventType', _t('Event Type')) }
|
||||
<br />
|
||||
|
||||
<Field id="evContent" label={_t("Event Content")} type="text" className="mx_DevTools_textarea"
|
||||
autoComplete="off" value={this.state.evContent} onChange={this.onChange} element="textarea" />
|
||||
<Field
|
||||
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 className="mx_Dialog_buttons">
|
||||
<button onClick={this.onBack}>{ _t('Back') }</button>
|
||||
{ !this.state.message && <button onClick={this.send}>{ _t('Send') }</button> }
|
||||
{ !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"
|
||||
checked={this.state.isRoomAccountData}
|
||||
disabled={this.props.forceMode}
|
||||
|
@ -371,11 +390,18 @@ class FilteredList extends React.PureComponent<IFilteredListProps, IFilteredList
|
|||
|
||||
render() {
|
||||
return <div>
|
||||
<Field label={_t('Filter results')} autoFocus={true} size={64}
|
||||
type="text" autoComplete="off" value={this.props.query} onChange={this.onQuery}
|
||||
<Field
|
||||
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"
|
||||
// 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}
|
||||
getChildCount={this.getChildCount}
|
||||
|
@ -459,11 +485,16 @@ class RoomStateExplorer extends React.PureComponent<IExplorerProps, IRoomStateEx
|
|||
render() {
|
||||
if (this.state.event) {
|
||||
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(),
|
||||
evContent: JSON.stringify(this.state.event.getContent(), null, '\t'),
|
||||
stateKey: this.state.event.getStateKey(),
|
||||
}} />;
|
||||
}}
|
||||
/>;
|
||||
}
|
||||
|
||||
return <div className="mx_ViewSource">
|
||||
|
@ -594,7 +625,9 @@ class AccountDataExplorer extends React.PureComponent<IExplorerProps, IAccountDa
|
|||
inputs={{
|
||||
eventType: this.state.event.getType(),
|
||||
evContent: JSON.stringify(this.state.event.getContent(), null, '\t'),
|
||||
}} forceMode={true} />;
|
||||
}}
|
||||
forceMode={true}
|
||||
/>;
|
||||
}
|
||||
|
||||
return <div className="mx_ViewSource">
|
||||
|
@ -631,7 +664,9 @@ class AccountDataExplorer extends React.PureComponent<IExplorerProps, IAccountDa
|
|||
<div className="mx_Dialog_buttons">
|
||||
<button onClick={this.onBack}>{ _t('Back') }</button>
|
||||
<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"
|
||||
checked={this.state.isRoomAccountData}
|
||||
onChange={this.onChange}
|
||||
|
@ -1021,8 +1056,13 @@ class SettingsExplorer extends React.PureComponent<IExplorerProps, ISettingsExpl
|
|||
<div>
|
||||
<div className="mx_Dialog_content mx_DevTools_SettingsExplorer">
|
||||
<Field
|
||||
label={_t('Filter results')} autoFocus={true} size={64}
|
||||
type="text" autoComplete="off" value={this.state.query} onChange={this.onQueryChange}
|
||||
label={_t('Filter results')}
|
||||
autoFocus={true}
|
||||
size={64}
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
value={this.state.query}
|
||||
onChange={this.onQueryChange}
|
||||
className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query"
|
||||
/>
|
||||
<table>
|
||||
|
@ -1040,7 +1080,9 @@ class SettingsExplorer extends React.PureComponent<IExplorerProps, ISettingsExpl
|
|||
<a href="" onClick={(e) => this.onViewClick(e, i)}>
|
||||
<code>{ i }</code>
|
||||
</a>
|
||||
<a href="" onClick={(e) => this.onEditClick(e, i)}
|
||||
<a
|
||||
href=""
|
||||
onClick={(e) => this.onEditClick(e, i)}
|
||||
className='mx_DevTools_SettingsExplorer_edit'
|
||||
>
|
||||
✏
|
||||
|
@ -1104,18 +1146,26 @@ class SettingsExplorer extends React.PureComponent<IExplorerProps, ISettingsExpl
|
|||
|
||||
<div>
|
||||
<Field
|
||||
id="valExpl" label={_t("Values at explicit levels")} type="text"
|
||||
className="mx_DevTools_textarea" element="textarea"
|
||||
autoComplete="off" value={this.state.explicitValues}
|
||||
id="valExpl"
|
||||
label={_t("Values at explicit levels")}
|
||||
type="text"
|
||||
className="mx_DevTools_textarea"
|
||||
element="textarea"
|
||||
autoComplete="off"
|
||||
value={this.state.explicitValues}
|
||||
onChange={this.onExplValuesEdit}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Field
|
||||
id="valExpl" label={_t("Values at explicit levels in this room")} type="text"
|
||||
className="mx_DevTools_textarea" element="textarea"
|
||||
autoComplete="off" value={this.state.explicitRoomValues}
|
||||
id="valExpl"
|
||||
label={_t("Values at explicit levels in this room")}
|
||||
type="text"
|
||||
className="mx_DevTools_textarea"
|
||||
element="textarea"
|
||||
autoComplete="off"
|
||||
value={this.state.explicitRoomValues}
|
||||
onChange={this.onExplRoomValuesEdit}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -144,8 +144,10 @@ export default class EditCommunityPrototypeDialog extends React.PureComponent<IP
|
|||
</div>
|
||||
<div className="mx_EditCommunityPrototypeDialog_rowAvatar">
|
||||
<input
|
||||
type="file" style={{ display: "none" }}
|
||||
ref={this.avatarUploadRef} accept="image/*"
|
||||
type="file"
|
||||
style={{ display: "none" }}
|
||||
ref={this.avatarUploadRef}
|
||||
accept="image/*"
|
||||
onChange={this.onAvatarChanged}
|
||||
/>
|
||||
<AccessibleButton
|
||||
|
|
|
@ -106,12 +106,12 @@ const Entry: React.FC<IEntryProps> = ({ room, event, matrixClient: cli, onFinish
|
|||
className = "mx_ForwardList_sending";
|
||||
disabled = true;
|
||||
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) {
|
||||
className = "mx_ForwardList_sent";
|
||||
disabled = true;
|
||||
title = _t("Sent");
|
||||
icon = <div className="mx_ForwardList_sendIcon" aria-label={title}></div>;
|
||||
icon = <div className="mx_ForwardList_sendIcon" aria-label={title} />;
|
||||
} else {
|
||||
className = "mx_ForwardList_sendFailed";
|
||||
disabled = true;
|
||||
|
@ -204,10 +204,16 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
|
|||
function overflowTile(overflowCount, totalCount) {
|
||||
const text = _t("and %(count)s others...", { count: overflowCount });
|
||||
return (
|
||||
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={
|
||||
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
||||
} name={text} presenceState="online" suppressOnHover={true}
|
||||
onClick={() => setTruncateAt(totalCount)} />
|
||||
<EntityTile
|
||||
className="mx_EntityTile_ellipsis"
|
||||
avatarJsx={
|
||||
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
||||
}
|
||||
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)
|
||||
: null;
|
||||
profile = <div className="mx_IncomingSasDialog_opponentProfile">
|
||||
<BaseAvatar name={oppProfile.displayname}
|
||||
<BaseAvatar
|
||||
name={oppProfile.displayname}
|
||||
idName={this.props.verifier.userId}
|
||||
url={url}
|
||||
width={48} height={48} resizeMethod='crop'
|
||||
width={48}
|
||||
height={48}
|
||||
resizeMethod='crop'
|
||||
/>
|
||||
<h2>{ oppProfile.displayname }</h2>
|
||||
</div>;
|
||||
} else if (this.state.opponentProfileError) {
|
||||
profile = <div>
|
||||
<BaseAvatar name={this.props.verifier.userId.slice(1)}
|
||||
<BaseAvatar
|
||||
name={this.props.verifier.userId.slice(1)}
|
||||
idName={this.props.verifier.userId}
|
||||
width={48} height={48}
|
||||
width={48}
|
||||
height={48}
|
||||
/>
|
||||
<h2>{ this.props.verifier.userId }</h2>
|
||||
</div>;
|
||||
|
|
|
@ -62,8 +62,7 @@ export default class InfoDialog extends React.Component<IProps> {
|
|||
{ this.props.button !== false && <DialogButtons primaryButton={this.props.button || _t('OK')}
|
||||
onPrimaryButtonClick={this.onFinished}
|
||||
hasCancel={false}
|
||||
>
|
||||
</DialogButtons> }
|
||||
/> }
|
||||
</BaseDialog>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -196,7 +196,9 @@ class DMUserTile extends React.PureComponent<IDMUserTileProps> {
|
|||
? <img
|
||||
className='mx_InviteDialog_userTile_avatar mx_InviteDialog_userTile_threepidAvatar'
|
||||
src={require("../../../../res/img/icon-email-pill-avatar.svg")}
|
||||
width={avatarSize} height={avatarSize} />
|
||||
width={avatarSize}
|
||||
height={avatarSize}
|
||||
/>
|
||||
: <BaseAvatar
|
||||
className='mx_InviteDialog_userTile_avatar'
|
||||
url={this.props.member.getMxcAvatarUrl()
|
||||
|
@ -214,8 +216,11 @@ class DMUserTile extends React.PureComponent<IDMUserTileProps> {
|
|||
className='mx_InviteDialog_userTile_remove'
|
||||
onClick={this.onRemove}
|
||||
>
|
||||
<img src={require("../../../../res/img/icon-pill-remove.svg")}
|
||||
alt={_t('Remove')} width={8} height={8}
|
||||
<img
|
||||
src={require("../../../../res/img/icon-pill-remove.svg")}
|
||||
alt={_t('Remove')}
|
||||
width={8}
|
||||
height={8}
|
||||
/>
|
||||
</AccessibleButton>
|
||||
);
|
||||
|
@ -297,7 +302,9 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
|
|||
const avatar = (this.props.member as ThreepidMember).isEmail
|
||||
? <img
|
||||
src={require("../../../../res/img/icon-email-pill-avatar.svg")}
|
||||
width={avatarSize} height={avatarSize} />
|
||||
width={avatarSize}
|
||||
height={avatarSize}
|
||||
/>
|
||||
: <BaseAvatar
|
||||
url={this.props.member.getMxcAvatarUrl()
|
||||
? mediaFromMxc(this.props.member.getMxcAvatarUrl()).getSquareThumbnailHttp(avatarSize)
|
||||
|
@ -1458,7 +1465,8 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
<p className='mx_InviteDialog_helpText'>
|
||||
<img
|
||||
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.") }
|
||||
</p>;
|
||||
}
|
||||
|
@ -1534,14 +1542,18 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
// Only show the backspace button if the field has content
|
||||
let dialPadField;
|
||||
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}
|
||||
autoFocus={true}
|
||||
onChange={this.onDialChange}
|
||||
postfixComponent={backspaceButton}
|
||||
/>;
|
||||
} else {
|
||||
dialPadField = <Field className="mx_InviteDialog_dialPadField" id="dialpad_number"
|
||||
dialPadField = <Field
|
||||
className="mx_InviteDialog_dialPadField"
|
||||
id="dialpad_number"
|
||||
value={this.state.dialPadValue}
|
||||
autoFocus={true}
|
||||
onChange={this.onDialChange}
|
||||
|
@ -1552,14 +1564,19 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
<form onSubmit={this.onDialFormSubmit}>
|
||||
{ dialPadField }
|
||||
</form>
|
||||
<Dialpad hasDial={false}
|
||||
onDigitPress={this.onDigitPress} onDeletePress={this.onDeletePress}
|
||||
<Dialpad
|
||||
hasDial={false}
|
||||
onDigitPress={this.onDigitPress}
|
||||
onDeletePress={this.onDeletePress}
|
||||
/>
|
||||
</div>;
|
||||
tabs.push(new Tab(TabId.DialPad, _td("Dial pad"), 'mx_InviteDialog_dialPadIcon', dialPadSection));
|
||||
dialogContent = <React.Fragment>
|
||||
<TabbedView tabs={tabs} initialTabId={this.state.currentTabId}
|
||||
tabLocation={TabLocation.TOP} onChange={this.onTabChange}
|
||||
<TabbedView
|
||||
tabs={tabs}
|
||||
initialTabId={this.state.currentTabId}
|
||||
tabLocation={TabLocation.TOP}
|
||||
onChange={this.onTabChange}
|
||||
/>
|
||||
{ consultConnectSection }
|
||||
</React.Fragment>;
|
||||
|
|
|
@ -85,7 +85,9 @@ export default class SessionRestoreErrorDialog extends React.Component {
|
|||
}
|
||||
|
||||
return (
|
||||
<BaseDialog className="mx_ErrorDialog" onFinished={this.props.onFinished}
|
||||
<BaseDialog
|
||||
className="mx_ErrorDialog"
|
||||
onFinished={this.props.onFinished}
|
||||
title={_t('Unable to restore session')}
|
||||
contentId='mx_Dialog_content'
|
||||
hasCancel={false}
|
||||
|
|
|
@ -54,7 +54,9 @@ export default class StorageEvictedDialog extends React.Component {
|
|||
}
|
||||
|
||||
return (
|
||||
<BaseDialog className="mx_ErrorDialog" onFinished={this.props.onFinished}
|
||||
<BaseDialog
|
||||
className="mx_ErrorDialog"
|
||||
onFinished={this.props.onFinished}
|
||||
title={_t('Missing session data')}
|
||||
contentId='mx_Dialog_content'
|
||||
hasCancel={false}
|
||||
|
|
|
@ -287,7 +287,8 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
|
|||
<div className="mx_AccessSecretStorageDialog_reset">
|
||||
{ _t("Forgotten or lost all recovery methods? <a>Reset all</a>", null, {
|
||||
a: (sub) => <a
|
||||
href="" onClick={this.onResetAllClick}
|
||||
href=""
|
||||
onClick={this.onResetAllClick}
|
||||
className="mx_AccessSecretStorageDialog_reset_link">{ sub }</a>,
|
||||
}) }
|
||||
</div>
|
||||
|
|
|
@ -399,7 +399,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
|
|||
|
||||
let keyStatus;
|
||||
if (this.state.recoveryKey.length === 0) {
|
||||
keyStatus = <div className="mx_RestoreKeyBackupDialog_keyStatus"></div>;
|
||||
keyStatus = <div className="mx_RestoreKeyBackupDialog_keyStatus" />;
|
||||
} else if (this.state.recoveryKeyValid) {
|
||||
keyStatus = <div className="mx_RestoreKeyBackupDialog_keyStatus">
|
||||
{ "\uD83D\uDC4D " }{ _t("This looks like a valid Security Key!") }
|
||||
|
|
|
@ -122,7 +122,7 @@ export default class AddressTile extends React.Component<IProps> {
|
|||
let dismiss;
|
||||
if (this.props.canDismiss) {
|
||||
dismiss = (
|
||||
<div className="mx_AddressTile_dismiss" onClick={this.props.onDismissed} >
|
||||
<div className="mx_AddressTile_dismiss" onClick={this.props.onDismissed}>
|
||||
<img src={require("../../../../res/img/icon-address-delete.svg")} width="9" height="9" />
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -51,7 +51,8 @@ export class ExistingSource extends React.Component<DesktopCapturerSourceIProps>
|
|||
<AccessibleButton
|
||||
className="mx_desktopCapturerSourcePicker_stream_button"
|
||||
title={this.props.source.name}
|
||||
onClick={this.onClick} >
|
||||
onClick={this.onClick}
|
||||
>
|
||||
<img
|
||||
className="mx_desktopCapturerSourcePicker_stream_thumbnail"
|
||||
src={this.props.source.thumbnailURL}
|
||||
|
|
|
@ -160,41 +160,80 @@ export default class ImageView extends React.Component<IProps, IState> {
|
|||
});
|
||||
};
|
||||
|
||||
private zoom(delta: number) {
|
||||
const newZoom = this.state.zoom + delta;
|
||||
private zoomDelta(delta: number, anchorX?: number, anchorY?: number) {
|
||||
this.zoom(this.state.zoom + delta, anchorX, anchorY);
|
||||
}
|
||||
|
||||
private zoom(zoomLevel: number, anchorX?: number, anchorY?: number) {
|
||||
const oldZoom = this.state.zoom;
|
||||
const newZoom = Math.min(zoomLevel, this.state.maxZoom);
|
||||
|
||||
if (newZoom <= this.state.minZoom) {
|
||||
// Zoom out fully
|
||||
this.setState({
|
||||
zoom: this.state.minZoom,
|
||||
translationX: 0,
|
||||
translationY: 0,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (newZoom >= this.state.maxZoom) {
|
||||
this.setState({ zoom: this.state.maxZoom });
|
||||
return;
|
||||
}
|
||||
} else if (typeof anchorX !== "number" && typeof anchorY !== "number") {
|
||||
// Zoom relative to the center of the view
|
||||
this.setState({
|
||||
zoom: newZoom,
|
||||
translationX: this.state.translationX * newZoom / oldZoom,
|
||||
translationY: this.state.translationY * newZoom / oldZoom,
|
||||
});
|
||||
} else {
|
||||
// Zoom relative to the given point on the image.
|
||||
// First we need to figure out the offset of the anchor point
|
||||
// relative to the center of the image, accounting for rotation.
|
||||
let offsetX;
|
||||
let offsetY;
|
||||
// The modulo operator can return negative values for some
|
||||
// rotations, so we have to do some extra work to normalize it
|
||||
switch (((this.state.rotation % 360) + 360) % 360) {
|
||||
case 0:
|
||||
offsetX = this.image.current.clientWidth / 2 - anchorX;
|
||||
offsetY = this.image.current.clientHeight / 2 - anchorY;
|
||||
break;
|
||||
case 90:
|
||||
offsetX = anchorY - this.image.current.clientHeight / 2;
|
||||
offsetY = this.image.current.clientWidth / 2 - anchorX;
|
||||
break;
|
||||
case 180:
|
||||
offsetX = anchorX - this.image.current.clientWidth / 2;
|
||||
offsetY = anchorY - this.image.current.clientHeight / 2;
|
||||
break;
|
||||
case 270:
|
||||
offsetX = this.image.current.clientHeight / 2 - anchorY;
|
||||
offsetY = anchorX - this.image.current.clientWidth / 2;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
zoom: newZoom,
|
||||
});
|
||||
// Apply the zoom and offset
|
||||
this.setState({
|
||||
zoom: newZoom,
|
||||
translationX: this.state.translationX + (newZoom - oldZoom) * offsetX,
|
||||
translationY: this.state.translationY + (newZoom - oldZoom) * offsetY,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private onWheel = (ev: WheelEvent) => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
if (ev.target === this.image.current) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
|
||||
const { deltaY } = normalizeWheelEvent(ev);
|
||||
this.zoom(-(deltaY * ZOOM_COEFFICIENT));
|
||||
const { deltaY } = normalizeWheelEvent(ev);
|
||||
// Zoom in on the point on the image targeted by the cursor
|
||||
this.zoomDelta(-deltaY * ZOOM_COEFFICIENT, ev.offsetX, ev.offsetY);
|
||||
}
|
||||
};
|
||||
|
||||
private onZoomInClick = () => {
|
||||
this.zoom(ZOOM_STEP);
|
||||
this.zoomDelta(ZOOM_STEP);
|
||||
};
|
||||
|
||||
private onZoomOutClick = () => {
|
||||
this.zoom(-ZOOM_STEP);
|
||||
this.zoomDelta(-ZOOM_STEP);
|
||||
};
|
||||
|
||||
private onKeyDown = (ev: KeyboardEvent) => {
|
||||
|
@ -259,7 +298,7 @@ export default class ImageView extends React.Component<IProps, IState> {
|
|||
|
||||
// Zoom in if we are completely zoomed out
|
||||
if (this.state.zoom === this.state.minZoom) {
|
||||
this.setState({ zoom: this.state.maxZoom });
|
||||
this.zoom(this.state.maxZoom, ev.nativeEvent.offsetX, ev.nativeEvent.offsetY);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -289,11 +328,7 @@ export default class ImageView extends React.Component<IProps, IState> {
|
|||
Math.abs(this.state.translationX - this.previousX) < ZOOM_DISTANCE &&
|
||||
Math.abs(this.state.translationY - this.previousY) < ZOOM_DISTANCE
|
||||
) {
|
||||
this.setState({
|
||||
zoom: this.state.minZoom,
|
||||
translationX: 0,
|
||||
translationY: 0,
|
||||
});
|
||||
this.zoom(this.state.minZoom);
|
||||
this.initX = 0;
|
||||
this.initY = 0;
|
||||
}
|
||||
|
@ -384,7 +419,8 @@ export default class ImageView extends React.Component<IProps, IState> {
|
|||
const avatar = (
|
||||
<MemberAvatar
|
||||
member={mxEvent.sender}
|
||||
width={32} height={32}
|
||||
width={32}
|
||||
height={32}
|
||||
viewUserOnClick={true}
|
||||
/>
|
||||
);
|
||||
|
@ -403,7 +439,7 @@ export default class ImageView extends React.Component<IProps, IState> {
|
|||
// an empty div here, since the panel uses space-between
|
||||
// and we want the same placement of elements
|
||||
info = (
|
||||
<div></div>
|
||||
<div />
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -427,15 +463,15 @@ export default class ImageView extends React.Component<IProps, IState> {
|
|||
<AccessibleTooltipButton
|
||||
className="mx_ImageView_button mx_ImageView_button_zoomOut"
|
||||
title={_t("Zoom out")}
|
||||
onClick={this.onZoomOutClick}>
|
||||
</AccessibleTooltipButton>
|
||||
onClick={this.onZoomOutClick}
|
||||
/>
|
||||
);
|
||||
zoomInButton = (
|
||||
<AccessibleTooltipButton
|
||||
className="mx_ImageView_button mx_ImageView_button_zoomIn"
|
||||
title={_t("Zoom in")}
|
||||
onClick={this.onZoomInClick}>
|
||||
</AccessibleTooltipButton>
|
||||
onClick={this.onZoomInClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -457,24 +493,24 @@ export default class ImageView extends React.Component<IProps, IState> {
|
|||
<AccessibleTooltipButton
|
||||
className="mx_ImageView_button mx_ImageView_button_rotateCCW"
|
||||
title={_t("Rotate Left")}
|
||||
onClick={this.onRotateCounterClockwiseClick}>
|
||||
</AccessibleTooltipButton>
|
||||
onClick={this.onRotateCounterClockwiseClick}
|
||||
/>
|
||||
<AccessibleTooltipButton
|
||||
className="mx_ImageView_button mx_ImageView_button_rotateCW"
|
||||
title={_t("Rotate Right")}
|
||||
onClick={this.onRotateClockwiseClick}>
|
||||
</AccessibleTooltipButton>
|
||||
onClick={this.onRotateClockwiseClick}
|
||||
/>
|
||||
<AccessibleTooltipButton
|
||||
className="mx_ImageView_button mx_ImageView_button_download"
|
||||
title={_t("Download")}
|
||||
onClick={this.onDownloadClick}>
|
||||
</AccessibleTooltipButton>
|
||||
onClick={this.onDownloadClick}
|
||||
/>
|
||||
{ contextMenuButton }
|
||||
<AccessibleTooltipButton
|
||||
className="mx_ImageView_button mx_ImageView_button_close"
|
||||
title={_t("Close")}
|
||||
onClick={this.props.onFinished}>
|
||||
</AccessibleTooltipButton>
|
||||
onClick={this.props.onFinished}
|
||||
/>
|
||||
{ this.renderContextMenu() }
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -92,7 +92,7 @@ const MiniAvatarUploader: React.FC<IProps> = ({ hasAvatar, hasAvatarLabel, noAva
|
|||
<div className="mx_MiniAvatarUploader_indicator">
|
||||
{ busy ?
|
||||
<Spinner w={20} h={20} /> :
|
||||
<div className="mx_MiniAvatarUploader_cameraIcon"></div> }
|
||||
<div className="mx_MiniAvatarUploader_cameraIcon" /> }
|
||||
</div>
|
||||
|
||||
<div className={classNames("mx_Tooltip", {
|
||||
|
|
|
@ -258,7 +258,10 @@ class Pill extends React.Component {
|
|||
linkText = groupId;
|
||||
if (this.props.shouldShowPillAvatar) {
|
||||
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} />;
|
||||
}
|
||||
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;
|
||||
if (this.state.custom) {
|
||||
picker = (
|
||||
<Field type="number"
|
||||
label={label} max={this.props.maxValue}
|
||||
<Field
|
||||
type="number"
|
||||
label={label}
|
||||
max={this.props.maxValue}
|
||||
onBlur={this.onCustomBlur}
|
||||
onKeyDown={this.onCustomKeyDown}
|
||||
onChange={this.onCustomChange}
|
||||
|
@ -157,9 +159,12 @@ export default class PowerSelector extends React.Component {
|
|||
});
|
||||
|
||||
picker = (
|
||||
<Field element="select"
|
||||
label={label} onChange={this.onSelectChange}
|
||||
value={String(this.state.selectValue)} disabled={this.props.disabled}
|
||||
<Field
|
||||
element="select"
|
||||
label={label}
|
||||
onChange={this.onSelectChange}
|
||||
value={String(this.state.selectValue)}
|
||||
disabled={this.props.disabled}
|
||||
>
|
||||
{ options }
|
||||
</Field>
|
||||
|
|
|
@ -166,8 +166,7 @@ export default class Tooltip extends React.Component<IProps> {
|
|||
public render() {
|
||||
// Render a placeholder
|
||||
return (
|
||||
<div className={this.props.className}>
|
||||
</div>
|
||||
<div className={this.props.className} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -101,14 +101,16 @@ class Category extends React.PureComponent<IProps> {
|
|||
{ name }
|
||||
</h2>
|
||||
<LazyRenderList
|
||||
element="ul" className="mx_EmojiPicker_list"
|
||||
itemHeight={EMOJI_HEIGHT} items={rows}
|
||||
element="ul"
|
||||
className="mx_EmojiPicker_list"
|
||||
itemHeight={EMOJI_HEIGHT}
|
||||
items={rows}
|
||||
scrollTop={localScrollTop}
|
||||
height={localHeight}
|
||||
overflowItems={OVERFLOW_ROWS}
|
||||
overflowMargin={0}
|
||||
renderItem={this.renderEmojiRow}>
|
||||
</LazyRenderList>
|
||||
renderItem={this.renderEmojiRow}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -86,10 +86,16 @@ export default class GroupMemberList extends React.Component {
|
|||
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
|
||||
const text = _t("and %(count)s others...", { count: overflowCount });
|
||||
return (
|
||||
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={
|
||||
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
||||
} name={text} presenceState="online" suppressOnHover={true}
|
||||
onClick={this._showFullMemberList} />
|
||||
<EntityTile
|
||||
className="mx_EntityTile_ellipsis"
|
||||
avatarJsx={
|
||||
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
||||
}
|
||||
name={text}
|
||||
presenceState="online"
|
||||
suppressOnHover={true}
|
||||
onClick={this._showFullMemberList}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -152,7 +158,9 @@ export default class GroupMemberList extends React.Component {
|
|||
);
|
||||
});
|
||||
|
||||
return <TruncatedList className="mx_MemberList_wrapper" truncateAt={this.state.truncateAt}
|
||||
return <TruncatedList
|
||||
className="mx_MemberList_wrapper"
|
||||
truncateAt={this.state.truncateAt}
|
||||
createOverflowElement={this._createOverflowTile}
|
||||
>
|
||||
{ memberTiles }
|
||||
|
|
|
@ -56,14 +56,19 @@ export default class GroupMemberTile extends React.Component {
|
|||
aria-hidden="true"
|
||||
name={this.props.member.displayname || this.props.member.userId}
|
||||
idName={this.props.member.userId}
|
||||
width={36} height={36}
|
||||
width={36}
|
||||
height={36}
|
||||
url={avatarUrl}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<EntityTile name={name} avatarJsx={av} onClick={this.onClick}
|
||||
suppressOnHover={true} presenceState="online"
|
||||
<EntityTile
|
||||
name={name}
|
||||
avatarJsx={av}
|
||||
onClick={this.onClick}
|
||||
suppressOnHover={true}
|
||||
presenceState="online"
|
||||
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 text = _t("and %(count)s others...", { count: overflowCount });
|
||||
return (
|
||||
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={
|
||||
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
||||
} name={text} presenceState="online" suppressOnHover={true}
|
||||
onClick={this._showFullRoomList} />
|
||||
<EntityTile
|
||||
className="mx_EntityTile_ellipsis"
|
||||
avatarJsx={
|
||||
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
||||
}
|
||||
name={text}
|
||||
presenceState="online"
|
||||
suppressOnHover={true}
|
||||
onClick={this._showFullRoomList}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -142,7 +148,8 @@ export default class GroupRoomList extends React.Component {
|
|||
}
|
||||
const inputBox = (
|
||||
<input
|
||||
className="mx_GroupRoomList_query mx_textinput" id="mx_GroupRoomList_query"
|
||||
className="mx_GroupRoomList_query mx_textinput"
|
||||
id="mx_GroupRoomList_query"
|
||||
type="text"
|
||||
onChange={this.onSearchQueryChanged}
|
||||
value={this.state.searchQuery}
|
||||
|
@ -156,8 +163,11 @@ export default class GroupRoomList extends React.Component {
|
|||
<div className="mx_GroupRoomList" role="tabpanel">
|
||||
{ inviteButton }
|
||||
<AutoHideScrollbar className="mx_GroupRoomList_joined mx_GroupRoomList_outerWrapper">
|
||||
<TruncatedList className="mx_GroupRoomList_wrapper" truncateAt={this.state.truncateAt}
|
||||
createOverflowElement={this._createOverflowTile}>
|
||||
<TruncatedList
|
||||
className="mx_GroupRoomList_wrapper"
|
||||
truncateAt={this.state.truncateAt}
|
||||
createOverflowElement={this._createOverflowTile}
|
||||
>
|
||||
{ this.makeGroupRoomTiles(this.state.searchQuery) }
|
||||
</TruncatedList>
|
||||
</AutoHideScrollbar>
|
||||
|
|
|
@ -48,8 +48,10 @@ class GroupRoomTile extends React.Component {
|
|||
: null;
|
||||
|
||||
const av = (
|
||||
<BaseAvatar name={this.props.groupRoom.displayname}
|
||||
width={36} height={36}
|
||||
<BaseAvatar
|
||||
name={this.props.groupRoom.displayname}
|
||||
width={36}
|
||||
height={36}
|
||||
url={avatarUrl}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -206,7 +206,7 @@ export default class CallEvent extends React.Component<IProps, IState> {
|
|||
{ sender }
|
||||
</div>
|
||||
<div className="mx_CallEvent_type">
|
||||
<div className="mx_CallEvent_type_icon"></div>
|
||||
<div className="mx_CallEvent_type_icon" />
|
||||
{ callType }
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -16,14 +16,14 @@ limitations under the License.
|
|||
|
||||
import React from "react";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { Playback } from "../../../voice/Playback";
|
||||
import { Playback } from "../../../audio/Playback";
|
||||
import InlineSpinner from '../elements/InlineSpinner';
|
||||
import { _t } from "../../../languageHandler";
|
||||
import AudioPlayer from "../audio_messages/AudioPlayer";
|
||||
import { IMediaEventContent } from "../../../customisations/models/IMediaEventContent";
|
||||
import MFileBody from "./MFileBody";
|
||||
import { IBodyProps } from "./IBodyProps";
|
||||
import { PlaybackManager } from "../../../voice/PlaybackManager";
|
||||
import { PlaybackManager } from "../../../audio/PlaybackManager";
|
||||
|
||||
interface IState {
|
||||
error?: Error;
|
||||
|
@ -76,7 +76,6 @@ export default class MAudioBody extends React.PureComponent<IBodyProps, IState>
|
|||
|
||||
public render() {
|
||||
if (this.state.error) {
|
||||
// TODO: @@TR: Verify error state
|
||||
return (
|
||||
<span className="mx_MAudioBody">
|
||||
<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) {
|
||||
// TODO: @@TR: Verify loading/decrypting state
|
||||
return (
|
||||
<span className="mx_MAudioBody">
|
||||
<InlineSpinner />
|
||||
|
|
|
@ -306,7 +306,10 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
|||
imageElement = <HiddenImagePlaceholder />;
|
||||
} else {
|
||||
imageElement = (
|
||||
<img style={{ display: 'none' }} src={thumbUrl} ref={this.image}
|
||||
<img
|
||||
style={{ display: 'none' }}
|
||||
src={thumbUrl}
|
||||
ref={this.image}
|
||||
alt={content.body}
|
||||
onError={this.onImageError}
|
||||
onLoad={this.onImageLoad}
|
||||
|
@ -340,8 +343,11 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
|||
// which has the same width as the timeline
|
||||
// mx_MImageBody_thumbnail resizes img to exactly container size
|
||||
img = (
|
||||
<img className="mx_MImageBody_thumbnail" src={thumbUrl} ref={this.image}
|
||||
style={{ maxWidth: maxWidth + "px" }}
|
||||
<img
|
||||
className="mx_MImageBody_thumbnail"
|
||||
src={thumbUrl}
|
||||
ref={this.image}
|
||||
style={{ maxWidth: `min(100%, ${maxWidth}px)` }}
|
||||
alt={content.body}
|
||||
onError={this.onImageError}
|
||||
onLoad={this.onImageLoad}
|
||||
|
@ -360,12 +366,15 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
|||
}
|
||||
|
||||
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 &&
|
||||
<div className="mx_MImageBody_thumbnail" style={{
|
||||
// Constrain width here so that spinner appears central to the loaded thumbnail
|
||||
maxWidth: infoWidth + "px",
|
||||
}}>
|
||||
<div
|
||||
className="mx_MImageBody_thumbnail"
|
||||
style={{
|
||||
// Constrain width here so that spinner appears central to the loaded thumbnail
|
||||
maxWidth: `min(100%, ${infoWidth}px)`,
|
||||
}}
|
||||
>
|
||||
{ placeholder }
|
||||
</div>
|
||||
}
|
||||
|
@ -416,10 +425,10 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
|||
|
||||
if (this.state.error !== null) {
|
||||
return (
|
||||
<span className="mx_MImageBody">
|
||||
<div className="mx_MImageBody">
|
||||
<img src={require("../../../../res/img/warning.svg")} width="16" height="16" />
|
||||
{ _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 fileBody = this.getFileBody();
|
||||
|
||||
return <span className="mx_MImageBody">
|
||||
return <div className="mx_MImageBody">
|
||||
{ thumbnail }
|
||||
{ fileBody }
|
||||
</span>;
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -452,7 +461,7 @@ export class HiddenImagePlaceholder extends React.PureComponent<PlaceholderIProp
|
|||
let className = 'mx_HiddenImagePlaceholder';
|
||||
if (this.props.hover) className += ' mx_HiddenImagePlaceholder_hover';
|
||||
return (
|
||||
<div className={className} style={{ maxWidth: maxWidth }}>
|
||||
<div className={className} style={{ maxWidth: `min(100%, ${maxWidth}px)` }}>
|
||||
<div className='mx_HiddenImagePlaceholder_button'>
|
||||
<span className='mx_HiddenImagePlaceholder_eye' />
|
||||
<span>{ _t("Show image") }</span>
|
||||
|
|
|
@ -267,8 +267,7 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
|
|||
width={width}
|
||||
poster={poster}
|
||||
onPlay={this.videoOnPlay}
|
||||
>
|
||||
</video>
|
||||
/>
|
||||
{ this.props.tileShape && <MFileBody {...this.props} showGenericPlaceholder={false} /> }
|
||||
</span>
|
||||
);
|
||||
|
|
|
@ -27,7 +27,6 @@ export default class MVoiceMessageBody extends MAudioBody {
|
|||
// A voice message is an audio file but rendered in a special way.
|
||||
public render() {
|
||||
if (this.state.error) {
|
||||
// TODO: @@TR: Verify error state
|
||||
return (
|
||||
<span className="mx_MVoiceMessageBody">
|
||||
<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) {
|
||||
// TODO: @@TR: Verify loading/decrypting state
|
||||
return (
|
||||
<span className="mx_MVoiceMessageBody">
|
||||
<InlineSpinner />
|
||||
|
|
|
@ -78,8 +78,11 @@ export default class RoomAvatarEvent extends React.Component {
|
|||
{ senderDisplayName: senderDisplayName },
|
||||
{
|
||||
'img': () =>
|
||||
<AccessibleButton key="avatar" className="mx_RoomAvatarEvent_avatar"
|
||||
onClick={this.onAvatarClick}>
|
||||
<AccessibleButton
|
||||
key="avatar"
|
||||
className="mx_RoomAvatarEvent_avatar"
|
||||
onClick={this.onAvatarClick}
|
||||
>
|
||||
<RoomAvatar width={14} height={14} oobData={oobData} />
|
||||
</AccessibleButton>,
|
||||
})
|
||||
|
|
|
@ -514,7 +514,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
|
|||
switch (content.msgtype) {
|
||||
case MsgType.Emote:
|
||||
return (
|
||||
<span className="mx_MEmoteBody mx_EventTile_content">
|
||||
<div className="mx_MEmoteBody mx_EventTile_content">
|
||||
*
|
||||
<span
|
||||
className="mx_MEmoteBody_sender"
|
||||
|
@ -525,21 +525,21 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
|
|||
|
||||
{ body }
|
||||
{ widgets }
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
case MsgType.Notice:
|
||||
return (
|
||||
<span className="mx_MNoticeBody mx_EventTile_content">
|
||||
<div className="mx_MNoticeBody mx_EventTile_content">
|
||||
{ body }
|
||||
{ widgets }
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
default: // including "m.text"
|
||||
return (
|
||||
<span className="mx_MTextBody mx_EventTile_content">
|
||||
<div className="mx_MTextBody mx_EventTile_content">
|
||||
{ body }
|
||||
{ widgets }
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -205,7 +205,7 @@ function DeviceItem({ userId, device }: {userId: string, device: IDevice}) {
|
|||
|
||||
if (isVerified) {
|
||||
return (
|
||||
<div className={classes} title={device.deviceId} >
|
||||
<div className={classes} title={device.deviceId}>
|
||||
<div className={iconClasses} />
|
||||
<div className="mx_UserInfo_device_name">{ deviceName }</div>
|
||||
<div className="mx_UserInfo_device_trusted">{ trustedLabel }</div>
|
||||
|
@ -1353,13 +1353,16 @@ const BasicUserInfo: React.FC<{
|
|||
if (hasCrossSigningKeys !== undefined) {
|
||||
// Note: mx_UserInfo_verifyButton is for the end-to-end tests
|
||||
verifyButton = (
|
||||
<AccessibleButton className="mx_UserInfo_field mx_UserInfo_verifyButton" onClick={() => {
|
||||
if (hasCrossSigningKeys) {
|
||||
verifyUser(member as User);
|
||||
} else {
|
||||
legacyVerifyUser(member as User);
|
||||
}
|
||||
}}>
|
||||
<AccessibleButton
|
||||
className="mx_UserInfo_field mx_UserInfo_verifyButton"
|
||||
onClick={() => {
|
||||
if (hasCrossSigningKeys) {
|
||||
verifyUser(member as User);
|
||||
} else {
|
||||
legacyVerifyUser(member as User);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{ _t("Verify") }
|
||||
</AccessibleButton>
|
||||
);
|
||||
|
@ -1374,12 +1377,15 @@ const BasicUserInfo: React.FC<{
|
|||
let editDevices;
|
||||
if (member.userId == cli.getUserId()) {
|
||||
editDevices = (<p>
|
||||
<AccessibleButton className="mx_UserInfo_field" onClick={() => {
|
||||
dis.dispatch({
|
||||
<AccessibleButton
|
||||
className="mx_UserInfo_field"
|
||||
onClick={() => {
|
||||
dis.dispatch({
|
||||
action: Action.ViewUserSettings,
|
||||
initialTabId: UserTab.Security,
|
||||
});
|
||||
}}>
|
||||
});
|
||||
}}
|
||||
>
|
||||
{ _t("Edit devices") }
|
||||
</AccessibleButton>
|
||||
</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)
|
||||
avatars.unshift(
|
||||
<ReadReceiptMarker key={userId} member={receipt.roomMember}
|
||||
<ReadReceiptMarker
|
||||
key={userId}
|
||||
member={receipt.roomMember}
|
||||
fallbackUserId={userId}
|
||||
leftOffset={left} hidden={hidden}
|
||||
leftOffset={left}
|
||||
hidden={hidden}
|
||||
readReceiptInfo={readReceiptInfo}
|
||||
checkUnmounting={this.props.checkUnmounting}
|
||||
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_bad: isEncryptionFailure,
|
||||
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.
|
||||
|
@ -949,8 +953,10 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
}
|
||||
avatar = (
|
||||
<div className="mx_EventTile_avatar">
|
||||
<MemberAvatar member={member}
|
||||
width={avatarSize} height={avatarSize}
|
||||
<MemberAvatar
|
||||
member={member}
|
||||
width={avatarSize}
|
||||
height={avatarSize}
|
||||
viewUserOnClick={true}
|
||||
/>
|
||||
</div>
|
||||
|
@ -1142,6 +1148,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
{ ircTimestamp }
|
||||
{ sender }
|
||||
{ ircPadlock }
|
||||
{ avatar }
|
||||
<div className="mx_EventTile_line" key="mx_EventTile_line">
|
||||
{ groupTimestamp }
|
||||
{ groupPadlock }
|
||||
|
@ -1159,10 +1166,10 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
/>
|
||||
{ keyRequestInfo }
|
||||
{ actionBar }
|
||||
{ this.props.layout === Layout.IRC && (reactionsRow) }
|
||||
</div>
|
||||
{ reactionsRow }
|
||||
{ this.props.layout !== Layout.IRC && (reactionsRow) }
|
||||
{ msgOption }
|
||||
{ avatar }
|
||||
</>)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -28,10 +28,11 @@ export default (props) => {
|
|||
badge = (<div className="mx_JumpToBottomButton_badge">{ props.numUnreadMessages }</div>);
|
||||
}
|
||||
return (<div className={className}>
|
||||
<AccessibleButton className="mx_JumpToBottomButton_scrollDown"
|
||||
<AccessibleButton
|
||||
className="mx_JumpToBottomButton_scrollDown"
|
||||
title={_t("Scroll to most recent messages")}
|
||||
onClick={props.onScrollToBottomClick}>
|
||||
</AccessibleButton>
|
||||
onClick={props.onScrollToBottomClick}
|
||||
/>
|
||||
{ badge }
|
||||
</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.
|
||||
const text = _t("and %(count)s others...", { count: overflowCount });
|
||||
return (
|
||||
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={
|
||||
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
||||
} name={text} presenceState="online" suppressOnHover={true}
|
||||
onClick={onClick} />
|
||||
<EntityTile
|
||||
className="mx_EntityTile_ellipsis"
|
||||
avatarJsx={
|
||||
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
||||
}
|
||||
name={text}
|
||||
presenceState="online"
|
||||
suppressOnHover={true}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -465,8 +471,12 @@ export default class MemberList extends React.Component<IProps, IState> {
|
|||
return <MemberTile key={m.userId} member={m} ref={m.userId} showPresence={this.showPresence} />;
|
||||
} else {
|
||||
// Is a 3pid invite
|
||||
return <EntityTile key={m.getStateKey()} name={m.getContent().display_name} suppressOnHover={true}
|
||||
onClick={() => this.onPending3pidInviteClick(m)} />;
|
||||
return <EntityTile
|
||||
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 VoiceRecordComposerTile from "./VoiceRecordComposerTile";
|
||||
import { VoiceRecordingStore } from "../../../stores/VoiceRecordingStore";
|
||||
import { RecordingState } from "../../../voice/VoiceRecording";
|
||||
import { RecordingState } from "../../../audio/VoiceRecording";
|
||||
import Tooltip, { Alignment } from "../elements/Tooltip";
|
||||
import ResizeNotifier from "../../../utils/ResizeNotifier";
|
||||
import { E2EStatus } from '../../../utils/ShieldUtils';
|
||||
|
@ -98,9 +98,7 @@ const EmojiButton = ({ addEmoji }) => {
|
|||
isExpanded={menuDisplayed}
|
||||
title={_t('Emoji picker')}
|
||||
inputRef={button}
|
||||
>
|
||||
|
||||
</ContextMenuTooltipButton>
|
||||
/>
|
||||
|
||||
{ contextMenu }
|
||||
</React.Fragment>;
|
||||
|
@ -439,7 +437,8 @@ export default class MessageComposer extends React.Component<IProps, IState> {
|
|||
if (secondsLeft) {
|
||||
recordingTooltip = <Tooltip
|
||||
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 displayName = member?.rawDisplayName || dmPartner;
|
||||
body = <React.Fragment>
|
||||
<RoomAvatar room={room} width={AVATAR_SIZE} height={AVATAR_SIZE} onClick={() => {
|
||||
defaultDispatcher.dispatch<ViewUserPayload>({
|
||||
<RoomAvatar
|
||||
room={room}
|
||||
width={AVATAR_SIZE}
|
||||
height={AVATAR_SIZE}
|
||||
onClick={() => {
|
||||
defaultDispatcher.dispatch<ViewUserPayload>({
|
||||
action: Action.ViewUser,
|
||||
// XXX: We should be using a real member object and not assuming what the receiver wants.
|
||||
member: member || { userId: dmPartner } as User,
|
||||
});
|
||||
}} />
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
<h2>{ room.name }</h2>
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue