Merge branch 'develop' into gsouquet/message-bubbles-4635

This commit is contained in:
Germain Souquet 2021-07-14 17:47:22 +02:00
commit 51c5094ca4
9 changed files with 47 additions and 21 deletions

View file

@ -49,4 +49,8 @@ limitations under the License.
padding-right: 6px; // with the fixed width this ends up as a visual 8px most of the time, as intended. padding-right: 6px; // with the fixed width this ends up as a visual 8px most of the time, as intended.
padding-left: 8px; // isolate from recording circle / play control padding-left: 8px; // isolate from recording circle / play control
} }
&.mx_VoiceMessagePrimaryContainer_noWaveform {
max-width: 162px; // with all the padding this results in 185px wide
}
} }

View file

@ -17,15 +17,18 @@ limitations under the License.
import { Playback, PlaybackState } from "../../../voice/Playback"; import { Playback, PlaybackState } from "../../../voice/Playback";
import React, { ReactNode } from "react"; import React, { ReactNode } from "react";
import { UPDATE_EVENT } from "../../../stores/AsyncStore"; import { UPDATE_EVENT } from "../../../stores/AsyncStore";
import PlaybackWaveform from "./PlaybackWaveform";
import PlayPauseButton from "./PlayPauseButton"; import PlayPauseButton from "./PlayPauseButton";
import PlaybackClock from "./PlaybackClock"; import PlaybackClock from "./PlaybackClock";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import { TileShape } from "../rooms/EventTile";
import PlaybackWaveform from "./PlaybackWaveform";
interface IProps { interface IProps {
// Playback instance to render. Cannot change during component lifecycle: create // Playback instance to render. Cannot change during component lifecycle: create
// an all-new component instead. // an all-new component instead.
playback: Playback; playback: Playback;
tileShape?: TileShape;
} }
interface IState { interface IState {
@ -50,15 +53,22 @@ export default class RecordingPlayback extends React.PureComponent<IProps, IStat
this.props.playback.prepare(); this.props.playback.prepare();
} }
private get isWaveformable(): boolean {
return this.props.tileShape !== TileShape.Notif
&& this.props.tileShape !== TileShape.FileGrid
&& this.props.tileShape !== TileShape.Pinned;
}
private onPlaybackUpdate = (ev: PlaybackState) => { private onPlaybackUpdate = (ev: PlaybackState) => {
this.setState({ playbackPhase: ev }); this.setState({ playbackPhase: ev });
}; };
public render(): ReactNode { public render(): ReactNode {
return <div className='mx_MediaBody mx_VoiceMessagePrimaryContainer'> const shapeClass = !this.isWaveformable ? 'mx_VoiceMessagePrimaryContainer_noWaveform' : '';
return <div className={'mx_MediaBody mx_VoiceMessagePrimaryContainer ' + shapeClass}>
<PlayPauseButton playback={this.props.playback} playbackPhase={this.state.playbackPhase} /> <PlayPauseButton playback={this.props.playback} playbackPhase={this.state.playbackPhase} />
<PlaybackClock playback={this.props.playback} /> <PlaybackClock playback={this.props.playback} />
<PlaybackWaveform playback={this.props.playback} /> { this.isWaveformable && <PlaybackWaveform playback={this.props.playback} /> }
</div>; </div>;
} }
} }

View file

@ -1,7 +1,6 @@
/* /*
Copyright 2017 New Vector Ltd Copyright 2017 - 2021 The Matrix.org Foundation C.I.C.
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -32,6 +31,7 @@ import sanitizeHtml from "sanitize-html";
import { UIFeature } from "../../../settings/UIFeature"; import { UIFeature } from "../../../settings/UIFeature";
import { PERMITTED_URL_SCHEMES } from "../../../HtmlUtils"; import { PERMITTED_URL_SCHEMES } from "../../../HtmlUtils";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import { TileShape } from "../rooms/EventTile";
// This component does no cycle detection, simply because the only way to make such a cycle would be to // This component does no cycle detection, simply because the only way to make such a cycle would be to
// craft event_id's, using a homeserver that generates predictable event IDs; even then the impact would // craft event_id's, using a homeserver that generates predictable event IDs; even then the impact would
@ -384,7 +384,7 @@ export default class ReplyThread extends React.Component {
{ dateSep } { dateSep }
<EventTile <EventTile
mxEvent={ev} mxEvent={ev}
tileShape="reply" tileShape={TileShape.Reply}
onHeightChanged={this.props.onHeightChanged} onHeightChanged={this.props.onHeightChanged}
permalinkCreator={this.props.permalinkCreator} permalinkCreator={this.props.permalinkCreator}
isRedacted={ev.isRedacted()} isRedacted={ev.isRedacted()}

View file

@ -1,5 +1,5 @@
/* /*
Copyright 2015, 2016, 2018, 2021 The Matrix.org Foundation C.I.C. Copyright 2015 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -24,6 +24,7 @@ import AccessibleButton from "../elements/AccessibleButton";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import { mediaFromContent } from "../../../customisations/Media"; import { mediaFromContent } from "../../../customisations/Media";
import ErrorDialog from "../dialogs/ErrorDialog"; import ErrorDialog from "../dialogs/ErrorDialog";
import { TileShape } from "../rooms/EventTile";
let downloadIconUrl; // cached copy of the download.svg asset for the sandboxed iframe later on let downloadIconUrl; // cached copy of the download.svg asset for the sandboxed iframe later on
@ -306,7 +307,7 @@ export default class MFileBody extends React.Component {
// If the attachment is not encrypted then we check whether we // If the attachment is not encrypted then we check whether we
// are being displayed in the room timeline or in a list of // are being displayed in the room timeline or in a list of
// files in the right hand side of the screen. // files in the right hand side of the screen.
if (this.props.tileShape === "file_grid") { if (this.props.tileShape === TileShape.FileGrid) {
return ( return (
<span className="mx_MFileBody"> <span className="mx_MFileBody">
{placeholder} {placeholder}

View file

@ -25,9 +25,11 @@ import { mediaFromContent } from "../../../customisations/Media";
import { decryptFile } from "../../../utils/DecryptFile"; import { decryptFile } from "../../../utils/DecryptFile";
import RecordingPlayback from "../audio_messages/RecordingPlayback"; import RecordingPlayback from "../audio_messages/RecordingPlayback";
import { IMediaEventContent } from "../../../customisations/models/IMediaEventContent"; import { IMediaEventContent } from "../../../customisations/models/IMediaEventContent";
import { TileShape } from "../rooms/EventTile";
interface IProps { interface IProps {
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
tileShape?: TileShape;
} }
interface IState { interface IState {
@ -103,7 +105,7 @@ export default class MVoiceMessageBody extends React.PureComponent<IProps, IStat
// At this point we should have a playable state // At this point we should have a playable state
return ( return (
<span className="mx_MVoiceMessageBody"> <span className="mx_MVoiceMessageBody">
<RecordingPlayback playback={this.state.playback} /> <RecordingPlayback playback={this.state.playback} tileShape={this.props.tileShape} />
<MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} showGenericPlaceholder={false} /> <MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} showGenericPlaceholder={false} />
</span> </span>
); );

View file

@ -42,7 +42,7 @@ export default class MessageEvent extends React.Component {
onHeightChanged: PropTypes.func, onHeightChanged: PropTypes.func,
/* the shape of the tile, used */ /* the shape of the tile, used */
tileShape: PropTypes.string, tileShape: PropTypes.string, // TODO: Use TileShape enum
/* the maximum image height to use, if the event is an image */ /* the maximum image height to use, if the event is an image */
maxImageHeight: PropTypes.number, maxImageHeight: PropTypes.number,

View file

@ -192,6 +192,7 @@ export enum TileShape {
FileGrid = "file_grid", FileGrid = "file_grid",
Reply = "reply", Reply = "reply",
ReplyPreview = "reply_preview", ReplyPreview = "reply_preview",
Pinned = "pinned",
} }
interface IProps { interface IProps {
@ -907,7 +908,7 @@ export default class EventTile extends React.Component<IProps, IState> {
mx_EventTile_12hr: this.props.isTwelveHour, mx_EventTile_12hr: this.props.isTwelveHour,
// Note: we keep the `sending` state class for tests, not for our styles // Note: we keep the `sending` state class for tests, not for our styles
mx_EventTile_sending: !isEditing && isSending, mx_EventTile_sending: !isEditing && isSending,
mx_EventTile_highlight: this.props.tileShape === 'notif' ? false : this.shouldHighlight(), mx_EventTile_highlight: this.props.tileShape === TileShape.Notif ? false : this.shouldHighlight(),
mx_EventTile_selected: this.props.isSelectedEvent, mx_EventTile_selected: this.props.isSelectedEvent,
mx_EventTile_continuation: this.props.tileShape ? '' : this.props.continuation, mx_EventTile_continuation: this.props.tileShape ? '' : this.props.continuation,
mx_EventTile_last: this.props.last, mx_EventTile_last: this.props.last,
@ -940,7 +941,7 @@ export default class EventTile extends React.Component<IProps, IState> {
let avatarSize; let avatarSize;
let needsSenderProfile; let needsSenderProfile;
if (this.props.tileShape === "notif") { if (this.props.tileShape === TileShape.Notif) {
avatarSize = 24; avatarSize = 24;
needsSenderProfile = true; needsSenderProfile = true;
} else if (tileHandler === 'messages.RoomCreate' || isBubbleMessage) { } else if (tileHandler === 'messages.RoomCreate' || isBubbleMessage) {
@ -954,7 +955,7 @@ export default class EventTile extends React.Component<IProps, IState> {
} else if (this.props.layout == Layout.IRC) { } else if (this.props.layout == Layout.IRC) {
avatarSize = 14; avatarSize = 14;
needsSenderProfile = true; needsSenderProfile = true;
} else if (this.props.continuation && this.props.tileShape !== "file_grid") { } else if (this.props.continuation && this.props.tileShape !== TileShape.FileGrid) {
// no avatar or sender profile for continuation messages // no avatar or sender profile for continuation messages
avatarSize = 0; avatarSize = 0;
needsSenderProfile = false; needsSenderProfile = false;
@ -984,7 +985,11 @@ export default class EventTile extends React.Component<IProps, IState> {
} }
if (needsSenderProfile && this.props.hideSender !== true) { if (needsSenderProfile && this.props.hideSender !== true) {
if (!this.props.tileShape || this.props.tileShape === 'reply' || this.props.tileShape === 'reply_preview') { if (
!this.props.tileShape
|| this.props.tileShape === TileShape.Reply
|| this.props.tileShape === TileShape.ReplyPreview
) {
sender = <SenderProfile onClick={this.onSenderProfileClick} sender = <SenderProfile onClick={this.onSenderProfileClick}
mxEvent={this.props.mxEvent} mxEvent={this.props.mxEvent}
enableFlair={this.props.enableFlair} enableFlair={this.props.enableFlair}
@ -1074,7 +1079,7 @@ export default class EventTile extends React.Component<IProps, IState> {
} }
switch (this.props.tileShape) { switch (this.props.tileShape) {
case 'notif': { case TileShape.Notif: {
const room = this.context.getRoom(this.props.mxEvent.getRoomId()); const room = this.context.getRoom(this.props.mxEvent.getRoomId());
return React.createElement(this.props.as || "li", { return React.createElement(this.props.as || "li", {
"className": classes, "className": classes,
@ -1102,11 +1107,12 @@ export default class EventTile extends React.Component<IProps, IState> {
highlightLink={this.props.highlightLink} highlightLink={this.props.highlightLink}
showUrlPreview={this.props.showUrlPreview} showUrlPreview={this.props.showUrlPreview}
onHeightChanged={this.props.onHeightChanged} onHeightChanged={this.props.onHeightChanged}
tileShape={this.props.tileShape}
/> />
</div>, </div>,
]); ]);
} }
case 'file_grid': { case TileShape.FileGrid: {
return React.createElement(this.props.as || "li", { return React.createElement(this.props.as || "li", {
"className": classes, "className": classes,
"aria-live": ariaLive, "aria-live": ariaLive,
@ -1137,10 +1143,10 @@ export default class EventTile extends React.Component<IProps, IState> {
]); ]);
} }
case 'reply': case TileShape.Reply:
case 'reply_preview': { case TileShape.ReplyPreview: {
let thread; let thread;
if (this.props.tileShape === 'reply_preview') { if (this.props.tileShape === TileShape.ReplyPreview) {
thread = ReplyThread.makeThread( thread = ReplyThread.makeThread(
this.props.mxEvent, this.props.mxEvent,
this.props.onHeightChanged, this.props.onHeightChanged,

View file

@ -29,6 +29,7 @@ import { replaceableComponent } from "../../../utils/replaceableComponent";
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { getUserNameColorClass } from "../../../utils/FormattingUtils"; import { getUserNameColorClass } from "../../../utils/FormattingUtils";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { TileShape } from "./EventTile";
interface IProps { interface IProps {
room: Room; room: Room;
@ -87,6 +88,7 @@ export default class PinnedEventTile extends React.Component<IProps> {
className="mx_PinnedEventTile_body" className="mx_PinnedEventTile_body"
maxImageHeight={150} maxImageHeight={150}
onHeightChanged={() => {}} // we need to give this, apparently onHeightChanged={() => {}} // we need to give this, apparently
tileShape={TileShape.Pinned}
/> />
</div> </div>

View file

@ -1,5 +1,5 @@
/* /*
Copyright 2017 New Vector Ltd Copyright 2017 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -24,6 +24,7 @@ import PropTypes from "prop-types";
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
import { UIFeature } from "../../../settings/UIFeature"; import { UIFeature } from "../../../settings/UIFeature";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import { TileShape } from "./EventTile";
function cancelQuoting() { function cancelQuoting() {
dis.dispatch({ dis.dispatch({
@ -90,7 +91,7 @@ export default class ReplyPreview extends React.Component {
<div className="mx_ReplyPreview_clear" /> <div className="mx_ReplyPreview_clear" />
<EventTile <EventTile
alwaysShowTimestamps={true} alwaysShowTimestamps={true}
tileShape="reply_preview" tileShape={TileShape.ReplyPreview}
mxEvent={this.state.event} mxEvent={this.state.event}
permalinkCreator={this.props.permalinkCreator} permalinkCreator={this.props.permalinkCreator}
isTwelveHour={SettingsStore.getValue("showTwelveHourTimestamps")} isTwelveHour={SettingsStore.getValue("showTwelveHourTimestamps")}