Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into t3chguy/fix/17244

 Conflicts:
	src/components/views/spaces/SpaceCreateMenu.tsx
This commit is contained in:
Michael Telatynski 2021-06-15 12:24:26 +01:00
commit 83c30b2280
75 changed files with 1940 additions and 806 deletions

View file

@ -19,7 +19,7 @@ import React from 'react';
import classNames from 'classnames';
import AccessibleButton from "./AccessibleButton";
import Tooltip from './Tooltip';
import Tooltip, {Alignment} from './Tooltip';
import {replaceableComponent} from "../../../utils/replaceableComponent";
interface ITooltipProps extends React.ComponentProps<typeof AccessibleButton> {
@ -28,6 +28,7 @@ interface ITooltipProps extends React.ComponentProps<typeof AccessibleButton> {
tooltipClassName?: string;
forceHide?: boolean;
yOffset?: number;
alignment?: Alignment;
}
interface IState {
@ -66,13 +67,14 @@ export default class AccessibleTooltipButton extends React.PureComponent<IToolti
render() {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const {title, tooltip, children, tooltipClassName, forceHide, yOffset, ...props} = this.props;
const {title, tooltip, children, tooltipClassName, forceHide, yOffset, alignment, ...props} = this.props;
const tip = this.state.hover ? <Tooltip
className="mx_AccessibleTooltipButton_container"
tooltipClassName={classNames("mx_AccessibleTooltipButton_tooltip", tooltipClassName)}
label={tooltip || title}
yOffset={yOffset}
alignment={alignment}
/> : null;
return (
<AccessibleButton

View file

@ -47,9 +47,14 @@ export default class AppTile extends React.Component {
// The key used for PersistedElement
this._persistKey = getPersistKey(this.props.app.id);
this._sgWidget = new StopGapWidget(this.props);
this._sgWidget.on("preparing", this._onWidgetPrepared);
this._sgWidget.on("ready", this._onWidgetReady);
try {
this._sgWidget = new StopGapWidget(this.props);
this._sgWidget.on("preparing", this._onWidgetPrepared);
this._sgWidget.on("ready", this._onWidgetReady);
} catch (e) {
console.log("Failed to construct widget", e);
this._sgWidget = null;
}
this.iframe = null; // ref to the iframe (callback style)
this.state = this._getNewState(props);
@ -97,7 +102,7 @@ export default class AppTile extends React.Component {
// Force the widget to be non-persistent (able to be deleted/forgotten)
ActiveWidgetStore.destroyPersistentWidget(this.props.app.id);
PersistedElement.destroyElement(this._persistKey);
this._sgWidget.stop();
if (this._sgWidget) this._sgWidget.stop();
}
this.setState({ hasPermissionToLoad });
@ -117,7 +122,7 @@ export default class AppTile extends React.Component {
componentDidMount() {
// Only fetch IM token on mount if we're showing and have permission to load
if (this.state.hasPermissionToLoad) {
if (this._sgWidget && this.state.hasPermissionToLoad) {
this._startWidget();
}
@ -146,10 +151,15 @@ export default class AppTile extends React.Component {
if (this._sgWidget) {
this._sgWidget.stop();
}
this._sgWidget = new StopGapWidget(newProps);
this._sgWidget.on("preparing", this._onWidgetPrepared);
this._sgWidget.on("ready", this._onWidgetReady);
this._startWidget();
try {
this._sgWidget = new StopGapWidget(newProps);
this._sgWidget.on("preparing", this._onWidgetPrepared);
this._sgWidget.on("ready", this._onWidgetReady);
this._startWidget();
} catch (e) {
console.log("Failed to construct widget", e);
this._sgWidget = null;
}
}
_startWidget() {
@ -161,7 +171,7 @@ export default class AppTile extends React.Component {
_iframeRefChange = (ref) => {
this.iframe = ref;
if (ref) {
this._sgWidget.start(ref);
if (this._sgWidget) this._sgWidget.start(ref);
} else {
this._resetWidget(this.props);
}
@ -209,7 +219,7 @@ export default class AppTile extends React.Component {
// Delete the widget from the persisted store for good measure.
PersistedElement.destroyElement(this._persistKey);
this._sgWidget.stop({forceDestroy: true});
if (this._sgWidget) this._sgWidget.stop({forceDestroy: true});
}
_onWidgetPrepared = () => {
@ -340,7 +350,13 @@ export default class AppTile extends React.Component {
<Spinner message={_t("Loading...")} />
</div>
);
if (!this.state.hasPermissionToLoad) {
if (this._sgWidget === null) {
appTileBody = (
<div className={appTileBodyClass} style={appTileBodyStyles}>
<AppWarning errorMsg={_t("Error loading Widget")} />
</div>
);
} else if (!this.state.hasPermissionToLoad) {
// only possible for room widgets, can assert this.props.room here
const isEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
appTileBody = (
@ -364,7 +380,7 @@ export default class AppTile extends React.Component {
if (this.isMixedContent()) {
appTileBody = (
<div className={appTileBodyClass} style={appTileBodyStyles}>
<AppWarning errorMsg="Error - Mixed content" />
<AppWarning errorMsg={_t("Error - Mixed content")} />
</div>
);
} else {
@ -417,6 +433,8 @@ export default class AppTile extends React.Component {
onFinished={this._closeContextMenu}
showUnpin={!this.props.userWidget}
userWidget={this.props.userWidget}
onEditClick={this.props.onEditClick}
onDeleteClick={this.props.onDeleteClick}
/>
);
}

View file

@ -128,6 +128,7 @@ export default class EventTilePreview extends React.Component<IProps, IState> {
mxEvent={event}
layout={this.props.layout}
enableFlair={SettingsStore.getValue(UIFeature.Flair)}
as="div"
/>
</div>;
}

View file

@ -19,20 +19,20 @@ limitations under the License.
import React, { createRef } from 'react';
import { _t } from '../../../languageHandler';
import AccessibleTooltipButton from "./AccessibleTooltipButton";
import {Key} from "../../../Keyboard";
import { Key } from "../../../Keyboard";
import FocusLock from "react-focus-lock";
import MemberAvatar from "../avatars/MemberAvatar";
import {ContextMenuTooltipButton} from "../../../accessibility/context_menu/ContextMenuTooltipButton";
import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton";
import MessageContextMenu from "../context_menus/MessageContextMenu";
import {aboveLeftOf, ContextMenu} from '../../structures/ContextMenu';
import { aboveLeftOf, ContextMenu } from '../../structures/ContextMenu';
import MessageTimestamp from "../messages/MessageTimestamp";
import SettingsStore from "../../../settings/SettingsStore";
import {formatFullDate} from "../../../DateUtils";
import { formatFullDate } from "../../../DateUtils";
import dis from '../../../dispatcher/dispatcher';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks"
import {MatrixEvent} from "matrix-js-sdk/src/models/event";
import {normalizeWheelEvent} from "../../../utils/Mouse";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { normalizeWheelEvent } from "../../../utils/Mouse";
// Max scale to keep gaps around the image
const MAX_SCALE = 0.95;
@ -95,8 +95,6 @@ export default class ImageView extends React.Component<IProps, IState> {
private initX = 0;
private initY = 0;
private lastX = 0;
private lastY = 0;
private previousX = 0;
private previousY = 0;
@ -105,23 +103,35 @@ export default class ImageView extends React.Component<IProps, IState> {
// needs to be passive in order to work with Chromium
this.focusLock.current.addEventListener('wheel', this.onWheel, { passive: false });
// We want to recalculate zoom whenever the window's size changes
window.addEventListener("resize", this.calculateZoom);
window.addEventListener("resize", this.recalculateZoom);
// After the image loads for the first time we want to calculate the zoom
this.image.current.addEventListener("load", this.calculateZoom);
this.image.current.addEventListener("load", this.recalculateZoom);
}
componentWillUnmount() {
this.focusLock.current.removeEventListener('wheel', this.onWheel);
window.removeEventListener("resize", this.calculateZoom);
this.image.current.removeEventListener("load", this.calculateZoom);
window.removeEventListener("resize", this.recalculateZoom);
this.image.current.removeEventListener("load", this.recalculateZoom);
}
private calculateZoom = () => {
private recalculateZoom = () => {
this.setZoomAndRotation();
}
private setZoomAndRotation = (inputRotation?: number) => {
const image = this.image.current;
const imageWrapper = this.imageWrapper.current;
const zoomX = imageWrapper.clientWidth / image.naturalWidth;
const zoomY = imageWrapper.clientHeight / image.naturalHeight;
const rotation = inputRotation || this.state.rotation;
const imageIsNotFlipped = rotation % 180 === 0;
// If the image is rotated take it into account
const width = imageIsNotFlipped ? image.naturalWidth : image.naturalHeight;
const height = imageIsNotFlipped ? image.naturalHeight : image.naturalWidth;
const zoomX = imageWrapper.clientWidth / width;
const zoomY = imageWrapper.clientHeight / height;
// If the image is smaller in both dimensions set its the zoom to 1 to
// display it in its original size
@ -130,6 +140,7 @@ export default class ImageView extends React.Component<IProps, IState> {
zoom: 1,
minZoom: 1,
maxZoom: 1,
rotation: rotation,
});
return;
}
@ -138,10 +149,14 @@ export default class ImageView extends React.Component<IProps, IState> {
// image by default
const minZoom = Math.min(zoomX, zoomY) * MAX_SCALE;
if (this.state.zoom <= this.state.minZoom) this.setState({zoom: minZoom});
// If zoom is smaller than minZoom don't go below that value
const zoom = (this.state.zoom <= this.state.minZoom) ? minZoom : this.state.zoom;
this.setState({
minZoom: minZoom,
maxZoom: 1,
rotation: rotation,
zoom: zoom,
});
}
@ -157,7 +172,7 @@ export default class ImageView extends React.Component<IProps, IState> {
return;
}
if (newZoom >= this.state.maxZoom) {
this.setState({zoom: this.state.maxZoom});
this.setState({ zoom: this.state.maxZoom });
return;
}
@ -170,7 +185,7 @@ export default class ImageView extends React.Component<IProps, IState> {
ev.stopPropagation();
ev.preventDefault();
const {deltaY} = normalizeWheelEvent(ev);
const { deltaY } = normalizeWheelEvent(ev);
this.zoom(-(deltaY * ZOOM_COEFFICIENT));
};
@ -192,14 +207,12 @@ export default class ImageView extends React.Component<IProps, IState> {
private onRotateCounterClockwiseClick = () => {
const cur = this.state.rotation;
const rotationDegrees = cur - 90;
this.setState({ rotation: rotationDegrees });
this.setZoomAndRotation(cur - 90);
};
private onRotateClockwiseClick = () => {
const cur = this.state.rotation;
const rotationDegrees = cur + 90;
this.setState({ rotation: rotationDegrees });
this.setZoomAndRotation(cur + 90);
};
private onDownloadClick = () => {
@ -246,15 +259,15 @@ 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.setState({ zoom: this.state.maxZoom });
return;
}
this.setState({moving: true});
this.setState({ moving: true });
this.previousX = this.state.translationX;
this.previousY = this.state.translationY;
this.initX = ev.pageX - this.lastX;
this.initY = ev.pageY - this.lastY;
this.initX = ev.pageX - this.state.translationX;
this.initY = ev.pageY - this.state.translationY;
};
private onMoving = (ev: React.MouseEvent) => {
@ -263,11 +276,9 @@ export default class ImageView extends React.Component<IProps, IState> {
if (!this.state.moving) return;
this.lastX = ev.pageX - this.initX;
this.lastY = ev.pageY - this.initY;
this.setState({
translationX: this.lastX,
translationY: this.lastY,
translationX: ev.pageX - this.initX,
translationY: ev.pageY - this.initY,
});
};
@ -283,8 +294,10 @@ export default class ImageView extends React.Component<IProps, IState> {
translationX: 0,
translationY: 0,
});
this.initX = 0;
this.initY = 0;
}
this.setState({moving: false});
this.setState({ moving: false });
};
private renderContextMenu() {
@ -355,7 +368,7 @@ export default class ImageView extends React.Component<IProps, IState> {
const senderName = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender();
const sender = (
<div className="mx_ImageView_info_sender">
{senderName}
{ senderName }
</div>
);
const messageTimestamp = (
@ -382,10 +395,10 @@ export default class ImageView extends React.Component<IProps, IState> {
info = (
<div className="mx_ImageView_info_wrapper">
{avatar}
{ avatar }
<div className="mx_ImageView_info">
{sender}
{messageTimestamp}
{ sender }
{ messageTimestamp }
</div>
</div>
);
@ -425,7 +438,7 @@ export default class ImageView extends React.Component<IProps, IState> {
<AccessibleTooltipButton
className="mx_ImageView_button mx_ImageView_button_zoomIn"
title={_t("Zoom in")}
onClick={ this.onZoomInClick }>
onClick={this.onZoomInClick}>
</AccessibleTooltipButton>
);
}
@ -441,7 +454,7 @@ export default class ImageView extends React.Component<IProps, IState> {
ref={this.focusLock}
>
<div className="mx_ImageView_panel">
{info}
{ info }
<div className="mx_ImageView_toolbar">
<AccessibleTooltipButton
className="mx_ImageView_button mx_ImageView_button_rotateCCW"
@ -453,20 +466,20 @@ export default class ImageView extends React.Component<IProps, IState> {
title={_t("Rotate Right")}
onClick={this.onRotateClockwiseClick}>
</AccessibleTooltipButton>
{zoomOutButton}
{zoomInButton}
{ zoomOutButton }
{ zoomInButton }
<AccessibleTooltipButton
className="mx_ImageView_button mx_ImageView_button_download"
title={_t("Download")}
onClick={ this.onDownloadClick }>
</AccessibleTooltipButton>
{contextMenuButton}
{ contextMenuButton }
<AccessibleTooltipButton
className="mx_ImageView_button mx_ImageView_button_close"
title={_t("Close")}
onClick={ this.props.onFinished }>
</AccessibleTooltipButton>
{this.renderContextMenu()}
{ this.renderContextMenu() }
</div>
</div>
<div