mirror of
https://github.com/element-hq/element-web.git
synced 2024-12-16 18:11:51 +03:00
Initial implementation of loading animation
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
parent
1b00b304bb
commit
d421c3203f
3 changed files with 53 additions and 11 deletions
|
@ -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})
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue