Initial implementation of loading animation

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
Šimon Brandner 2021-07-22 21:26:15 +02:00
parent 1b00b304bb
commit d421c3203f
No known key found for this signature in database
GPG key ID: 55C211A1226CB17D
3 changed files with 53 additions and 11 deletions

View file

@ -34,6 +34,7 @@ import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { normalizeWheelEvent } from "../../../utils/Mouse"; import { normalizeWheelEvent } from "../../../utils/Mouse";
import { IDialogProps } from '../dialogs/IDialogProps'; import { IDialogProps } from '../dialogs/IDialogProps';
import UIStore from '../../../stores/UIStore';
// Max scale to keep gaps around the image // Max scale to keep gaps around the image
const MAX_SCALE = 0.95; const MAX_SCALE = 0.95;
@ -56,8 +57,15 @@ interface IProps extends IDialogProps {
// redactions, senders, timestamps etc. Other descriptors are taken from the explicit // redactions, senders, timestamps etc. Other descriptors are taken from the explicit
// properties above, which let us use lightboxes to display images which aren't associated // properties above, which let us use lightboxes to display images which aren't associated
// with events. // with events.
mxEvent: MatrixEvent; mxEvent?: MatrixEvent;
permalinkCreator: RoomPermalinkCreator; permalinkCreator?: RoomPermalinkCreator;
thumbnailInfo?: {
positionX: number;
positionY: number;
width: number;
height: number;
};
} }
interface IState { interface IState {
@ -75,13 +83,16 @@ interface IState {
export default class ImageView extends React.Component<IProps, IState> { export default class ImageView extends React.Component<IProps, IState> {
constructor(props) { constructor(props) {
super(props); super(props);
const { thumbnailInfo, width } = this.props;
this.state = { this.state = {
zoom: 0, zoom: thumbnailInfo?.width / width ?? 0,
minZoom: MAX_SCALE, minZoom: MAX_SCALE,
maxZoom: MAX_SCALE, maxZoom: MAX_SCALE,
rotation: 0, rotation: 0,
translationX: 0, translationX: thumbnailInfo?.positionX + (thumbnailInfo?.width / 2) - (UIStore.instance.windowWidth / 2),
translationY: 0, translationY: thumbnailInfo?.positionY + (thumbnailInfo?.height / 2) - (UIStore.instance.windowHeight / 2),
moving: false, moving: false,
contextMenuDisplayed: false, contextMenuDisplayed: false,
}; };
@ -105,15 +116,23 @@ export default class ImageView extends React.Component<IProps, IState> {
// We want to recalculate zoom whenever the window's size changes // We want to recalculate zoom whenever the window's size changes
window.addEventListener("resize", this.recalculateZoom); window.addEventListener("resize", this.recalculateZoom);
// After the image loads for the first time we want to calculate the zoom // After the image loads for the first time we want to calculate the zoom
this.image.current.addEventListener("load", this.recalculateZoom); this.image.current.addEventListener("load", this.imageLoaded);
} }
componentWillUnmount() { componentWillUnmount() {
this.focusLock.current.removeEventListener('wheel', this.onWheel); this.focusLock.current.removeEventListener('wheel', this.onWheel);
window.removeEventListener("resize", this.recalculateZoom); window.removeEventListener("resize", this.recalculateZoom);
this.image.current.removeEventListener("load", this.recalculateZoom); this.image.current.removeEventListener("load", this.imageLoaded);
} }
private imageLoaded =() => {
this.setZoomAndRotation();
this.setState({
translationX: 0,
translationY: 0,
});
};
private recalculateZoom = () => { private recalculateZoom = () => {
this.setZoomAndRotation(); this.setZoomAndRotation();
}; };
@ -380,7 +399,7 @@ export default class ImageView extends React.Component<IProps, IState> {
// image causing it translate in the wrong direction. // image causing it translate in the wrong direction.
const style = { const style = {
cursor: cursor, cursor: cursor,
transition: this.state.moving ? null : "transform 200ms ease 0s", transition: this.state.moving ? null : "transform 250ms ease 0s",
transform: `translateX(${translatePixelsX}) transform: `translateX(${translatePixelsX})
translateY(${translatePixelsY}) translateY(${translatePixelsY})
scale(${zoom}) scale(${zoom})

View file

@ -112,6 +112,17 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
params.fileSize = content.info.size; params.fileSize = content.info.size;
} }
if (this.image.current) {
const clientRect = this.image.current.getBoundingClientRect();
params.thumbnailInfo = {
width: clientRect.width,
height: clientRect.height,
positionX: clientRect.x,
positionY: clientRect.y,
};
}
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", null, true); Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", null, true);
} }
}; };

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { createRef } from 'react'; import React, { ComponentProps, createRef } from 'react';
import { AllHtmlEntities } from 'html-entities'; import { AllHtmlEntities } from 'html-entities';
import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
import { IPreviewUrlResponse } from 'matrix-js-sdk/src/client'; import { IPreviewUrlResponse } from 'matrix-js-sdk/src/client';
@ -36,6 +36,7 @@ interface IProps {
@replaceableComponent("views.rooms.LinkPreviewWidget") @replaceableComponent("views.rooms.LinkPreviewWidget")
export default class LinkPreviewWidget extends React.Component<IProps> { export default class LinkPreviewWidget extends React.Component<IProps> {
private readonly description = createRef<HTMLDivElement>(); private readonly description = createRef<HTMLDivElement>();
private image = createRef<HTMLImageElement>();
componentDidMount() { componentDidMount() {
if (this.description.current) { if (this.description.current) {
@ -59,7 +60,7 @@ export default class LinkPreviewWidget extends React.Component<IProps> {
src = mediaFromMxc(src).srcHttp; src = mediaFromMxc(src).srcHttp;
} }
const params = { const params: Omit<ComponentProps<typeof ImageView>, "onFinished"> = {
src: src, src: src,
width: p["og:image:width"], width: p["og:image:width"],
height: p["og:image:height"], height: p["og:image:height"],
@ -68,6 +69,17 @@ export default class LinkPreviewWidget extends React.Component<IProps> {
link: this.props.link, link: this.props.link,
}; };
if (this.image.current) {
const clientRect = this.image.current.getBoundingClientRect();
params.thumbnailInfo = {
width: clientRect.width,
height: clientRect.height,
positionX: clientRect.x,
positionY: clientRect.y,
};
}
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", null, true); Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", null, true);
}; };
@ -100,7 +112,7 @@ export default class LinkPreviewWidget extends React.Component<IProps> {
let img; let img;
if (image) { if (image) {
img = <div className="mx_LinkPreviewWidget_image" style={{ height: thumbHeight }}> img = <div className="mx_LinkPreviewWidget_image" style={{ height: thumbHeight }}>
<img style={{ maxWidth: imageMaxWidth, maxHeight: imageMaxHeight }} src={image} onClick={this.onImageClick} /> <img ref={this.image} style={{ maxWidth: imageMaxWidth, maxHeight: imageMaxHeight }} src={image} onClick={this.onImageClick} />
</div>; </div>;
} }