Merge branch 'develop' into gsouquet/threaded-messaging-2349

This commit is contained in:
Germain Souquet 2021-08-19 10:44:32 +01:00
commit ffc7326b0c
43 changed files with 926 additions and 229 deletions

View file

@ -29,11 +29,13 @@ import BaseDialog from "./BaseDialog";
import Field from '../elements/Field';
import Spinner from "../elements/Spinner";
import DialogButtons from "../elements/DialogButtons";
import { sendSentryReport } from "../../../sentry";
interface IProps {
onFinished: (success: boolean) => void;
initialText?: string;
label?: string;
error?: Error;
}
interface IState {
@ -113,6 +115,8 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
});
}
});
sendSentryReport(this.state.text, this.state.issueUrl, this.props.error);
};
private onDownload = async (): Promise<void> => {
@ -200,8 +204,8 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
{ _t(
"Debug logs contain application usage data including your " +
"username, the IDs or aliases of the rooms or groups you " +
"have visited and the usernames of other users. They do " +
"not contain messages.",
"have visited, which UI elements you last interacted with, " +
"and the usernames of other users. They do not contain messages.",
) }
</p>
<p><b>

View file

@ -218,6 +218,7 @@ export default class AppTile extends React.Component {
// Delete the widget from the persisted store for good measure.
PersistedElement.destroyElement(this._persistKey);
ActiveWidgetStore.destroyPersistentWidget(this.props.app.id);
if (this._sgWidget) this._sgWidget.stop({ forceDestroy: true });
}
@ -307,7 +308,6 @@ export default class AppTile extends React.Component {
if (this.iframe) {
// Reload iframe
this.iframe.src = this._sgWidget.embedUrl;
this.setState({});
}
});
}
@ -333,7 +333,7 @@ export default class AppTile extends React.Component {
// this would only be for content hosted on the same origin as the element client: anything
// hosted on the same origin as the client will get the same access as if you clicked
// a link to it.
const sandboxFlags = "allow-forms allow-popups allow-popups-to-escape-sandbox "+
const sandboxFlags = "allow-forms allow-popups allow-popups-to-escape-sandbox " +
"allow-same-origin allow-scripts allow-presentation";
// Additional iframe feature pemissions
@ -443,25 +443,25 @@ export default class AppTile extends React.Component {
return <React.Fragment>
<div className={appTileClasses} id={this.props.app.id}>
{ this.props.showMenubar &&
<div className="mx_AppTileMenuBar">
<span className="mx_AppTileMenuBarTitle" style={{ pointerEvents: (this.props.handleMinimisePointerEvents ? 'all' : false) }}>
{ this.props.showTitle && this._getTileTitle() }
</span>
<span className="mx_AppTileMenuBarWidgets">
{ this.props.showPopout && <AccessibleButton
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_popout"
title={_t('Popout widget')}
onClick={this._onPopoutWidgetClick}
/> }
<ContextMenuButton
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_menu"
label={_t("Options")}
isExpanded={this.state.menuDisplayed}
inputRef={this._contextMenuButton}
onClick={this._onContextMenuClick}
/>
</span>
</div> }
<div className="mx_AppTileMenuBar">
<span className="mx_AppTileMenuBarTitle" style={{ pointerEvents: (this.props.handleMinimisePointerEvents ? 'all' : false) }}>
{ this.props.showTitle && this._getTileTitle() }
</span>
<span className="mx_AppTileMenuBarWidgets">
{ this.props.showPopout && <AccessibleButton
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_popout"
title={_t('Popout widget')}
onClick={this._onPopoutWidgetClick}
/> }
<ContextMenuButton
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_menu"
label={_t("Options")}
isExpanded={this.state.menuDisplayed}
inputRef={this._contextMenuButton}
onClick={this._onContextMenuClick}
/>
</span>
</div> }
{ appTileBody }
</div>

View file

@ -71,6 +71,7 @@ export default class ErrorBoundary extends React.PureComponent<{}, IState> {
private onBugReport = (): void => {
Modal.createTrackedDialog('Bug Report Dialog', '', BugReportDialog, {
label: 'react-soft-crash',
error: this.state.error,
});
};
@ -93,8 +94,9 @@ export default class ErrorBoundary extends React.PureComponent<{}, IState> {
"If you've submitted a bug via GitHub, debug logs can help " +
"us track down the problem. Debug logs contain application " +
"usage data including your username, the IDs or aliases of " +
"the rooms or groups you have visited and the usernames of " +
"other users. They do not contain messages.",
"the rooms or groups you have visited, which UI elements you " +
"last interacted with, and the usernames of other users. " +
"They do not contain messages.",
) }</p>
<AccessibleButton onClick={this.onBugReport} kind='primary'>
{ _t("Submit debug logs") }

View file

@ -27,7 +27,7 @@ import classNames from 'classnames';
import AccessibleTooltipButton from '../elements/AccessibleTooltipButton';
import { formatCallTime } from "../../../DateUtils";
const MAX_NON_NARROW_WIDTH = 400 / 70 * 100;
const MAX_NON_NARROW_WIDTH = 450 / 70 * 100;
interface IProps {
mxEvent: MatrixEvent;

View file

@ -178,7 +178,7 @@ export default class MFileBody extends React.Component<IProps, IState> {
private onPlaceholderClick = async () => {
const mediaHelper = this.props.mediaEventHelper;
if (mediaHelper.media.isEncrypted) {
if (mediaHelper?.media.isEncrypted) {
await this.decryptFile();
this.downloadFile(this.fileName, this.linkText);
} else {
@ -192,7 +192,7 @@ export default class MFileBody extends React.Component<IProps, IState> {
};
public render() {
const isEncrypted = this.props.mediaEventHelper.media.isEncrypted;
const isEncrypted = this.props.mediaEventHelper?.media.isEncrypted;
const contentUrl = this.getContentUrl();
const fileSize = this.content.info ? this.content.info.size : null;
const fileType = this.content.info ? this.content.info.mimetype : "application/octet-stream";

View file

@ -47,6 +47,7 @@ interface IState {
};
hover: boolean;
showImage: boolean;
placeholder: 'no-image' | 'blurhash';
}
@replaceableComponent("views.messages.MImageBody")
@ -68,6 +69,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
loadedImageDimensions: null,
hover: false,
showImage: SettingsStore.getValue("showImages"),
placeholder: 'no-image',
};
}
@ -277,6 +279,17 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
this.downloadImage();
this.setState({ showImage: true });
} // else don't download anything because we don't want to display anything.
// Add a 150ms timer for blurhash to first appear.
if (this.media.isEncrypted) {
setTimeout(() => {
if (!this.state.imgLoaded || !this.state.imgError) {
this.setState({
placeholder: 'blurhash',
});
}
}, 150);
}
}
componentWillUnmount() {
@ -434,7 +447,14 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
// Overidden by MStickerBody
protected getPlaceholder(width: number, height: number): JSX.Element {
const blurhash = this.props.mxEvent.getContent().info[BLURHASH_FIELD];
if (blurhash) return <Blurhash className="mx_Blurhash" hash={blurhash} width={width} height={height} />;
if (blurhash) {
if (this.state.placeholder === 'no-image') {
return <div className="mx_no-image-placeholder" style={{ width: width, height: height }} />;
} else if (this.state.placeholder === 'blurhash') {
return <Blurhash className="mx_Blurhash" hash={blurhash} width={width} height={height} />;
}
}
return (
<InlineSpinner w={32} h={32} />
);

View file

@ -51,6 +51,7 @@ export default class TileErrorBoundary extends React.Component<IProps, IState> {
private onBugReport = (): void => {
Modal.createTrackedDialog('Bug Report Dialog', '', BugReportDialog, {
label: 'react-soft-crash-tile',
error: this.state.error,
});
};

View file

@ -907,13 +907,14 @@ export default class EventTile extends React.Component<IProps, IState> {
render() {
const msgtype = this.props.mxEvent.getContent().msgtype;
const eventType = this.props.mxEvent.getType() as EventType;
const { tileHandler, isBubbleMessage, isInfoMessage } = getEventDisplayInfo(this.props.mxEvent);
// This shouldn't happen: the caller should check we support this type
// before trying to instantiate us
if (!tileHandler) {
const { mxEvent } = this.props;
console.warn(`Event type not supported: type:${mxEvent.getType()} isState:${mxEvent.isState()}`);
console.warn(`Event type not supported: type:${eventType} isState:${mxEvent.isState()}`);
return <div className="mx_EventTile mx_EventTile_info mx_MNoticeBody">
<div className="mx_EventTile_line">
{ _t('This event could not be displayed') }
@ -937,7 +938,10 @@ export default class EventTile extends React.Component<IProps, IState> {
mx_EventTile_sending: !isEditing && isSending,
mx_EventTile_highlight: this.props.tileShape === TileShape.Notif ? false : this.shouldHighlight(),
mx_EventTile_selected: this.props.isSelectedEvent,
mx_EventTile_continuation: this.props.tileShape ? '' : this.props.continuation,
mx_EventTile_continuation: (
(this.props.tileShape ? '' : this.props.continuation) ||
eventType === EventType.CallInvite
),
mx_EventTile_last: this.props.last,
mx_EventTile_lastInSection: this.props.lastInSection,
mx_EventTile_contextual: this.props.contextual,
@ -985,7 +989,7 @@ export default class EventTile extends React.Component<IProps, IState> {
needsSenderProfile = true;
} else if (
(this.props.continuation && this.props.tileShape !== TileShape.FileGrid) ||
this.props.mxEvent.getType() === EventType.CallInvite
eventType === EventType.CallInvite
) {
// no avatar or sender profile for continuation messages and call tiles
avatarSize = 0;

View file

@ -268,7 +268,8 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
"If you've submitted a bug via GitHub, debug logs can help " +
"us track down the problem. Debug logs contain application " +
"usage data including your username, the IDs or aliases of " +
"the rooms or groups you have visited and the usernames of " +
"the rooms or groups you have visited, which UI elements you " +
"last interacted with, and the usernames of " +
"other users. They do not contain messages.",
) }
<div className='mx_HelpUserSettingsTab_debugButton'>

View file

@ -14,7 +14,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { ComponentProps, Dispatch, ReactNode, SetStateAction, useEffect, useState } from "react";
import React, {
ComponentProps,
Dispatch,
ReactNode,
SetStateAction,
useEffect,
useLayoutEffect,
useRef,
useState,
} from "react";
import { DragDropContext, Draggable, Droppable } from "react-beautiful-dnd";
import classNames from "classnames";
import { Room } from "matrix-js-sdk/src/models/room";
@ -43,6 +52,7 @@ import IconizedContextMenu, {
} from "../context_menus/IconizedContextMenu";
import SettingsStore from "../../../settings/SettingsStore";
import { SettingLevel } from "../../../settings/SettingLevel";
import UIStore from "../../../stores/UIStore";
const useSpaces = (): [Room[], Room[], Room | null] => {
const invites = useEventEmitterState<Room[]>(SpaceStore.instance, UPDATE_INVITED_SPACES, () => {
@ -206,6 +216,11 @@ const InnerSpacePanel = React.memo<IInnerSpacePanelProps>(({ children, isPanelCo
const SpacePanel = () => {
const [isPanelCollapsed, setPanelCollapsed] = useState(true);
const ref = useRef<HTMLUListElement>();
useLayoutEffect(() => {
UIStore.instance.trackElementDimensions("SpacePanel", ref.current);
return () => UIStore.instance.stopTrackingElementDimensions("SpacePanel");
}, []);
const onKeyDown = (ev: React.KeyboardEvent) => {
let handled = true;
@ -280,6 +295,7 @@ const SpacePanel = () => {
onKeyDown={onKeyDownHandler}
role="tree"
aria-label={_t("Spaces")}
ref={ref}
>
<Droppable droppableId="top-level-spaces">
{ (provided, snapshot) => (

View file

@ -72,7 +72,7 @@ export default class AudioFeed extends React.Component<IProps, IState> {
}
};
private playMedia() {
private async playMedia() {
const element = this.element.current;
if (!element) return;
this.onAudioOutputChanged(MediaDeviceHandler.getAudioOutput());
@ -90,7 +90,7 @@ export default class AudioFeed extends React.Component<IProps, IState> {
// should serialise the ones that need to be serialised but then be able to interrupt
// them with another load() which will cancel the pending one, but since we don't call
// load() explicitly, it shouldn't be a problem. - Dave
element.play();
await element.load();
} catch (e) {
logger.info("Failed to play media element with feed", this.props.feed, e);
}

View file

@ -32,7 +32,7 @@ export default class AudioFeedArrayForCall extends React.Component<IProps, IStat
super(props);
this.state = {
feeds: [],
feeds: this.props.call.getRemoteFeeds(),
};
}

View file

@ -112,7 +112,7 @@ export default class VideoFeed extends React.PureComponent<IProps, IState> {
}
}
private playMedia() {
private async playMedia() {
const element = this.element;
if (!element) return;
// We play audio in AudioFeed, not here
@ -129,7 +129,7 @@ export default class VideoFeed extends React.PureComponent<IProps, IState> {
// should serialise the ones that need to be serialised but then be able to interrupt
// them with another load() which will cancel the pending one, but since we don't call
// load() explicitly, it shouldn't be a problem. - Dave
element.play();
await element.play();
} catch (e) {
logger.info("Failed to play media element with feed", this.props.feed, e);
}