Add option to change the size of images/videos in the timeline (#7017)

Co-authored-by: Šimon Brandner <simon.bra.ag@gmail.com>
Co-authored-by: J. Ryan Stinnett <jryans@gmail.com>
Co-authored-by: Timo K <toger5@hotmail.de>
This commit is contained in:
Travis Ralston 2021-11-17 08:19:30 -07:00 committed by GitHub
parent 816136de97
commit 3c06e7f7a0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 293 additions and 45 deletions

View file

@ -35,6 +35,7 @@ import classNames from 'classnames';
import { CSSTransition, SwitchTransition } from 'react-transition-group';
import { logger } from "matrix-js-sdk/src/logger";
import { ImageSize, suggestedSize as suggestedImageSize } from "../../../settings/enums/ImageSize";
interface IState {
decryptedUrl?: string;
@ -58,6 +59,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
private unmounted = true;
private image = createRef<HTMLImageElement>();
private timeout?: number;
private sizeWatcher: string;
constructor(props: IBodyProps) {
super(props);
@ -317,12 +319,17 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
}
}, 150);
}
this.sizeWatcher = SettingsStore.watchSetting("Images.size", null, () => {
this.forceUpdate(); // we don't really have a reliable thing to update, so just update the whole thing
});
}
componentWillUnmount() {
this.unmounted = true;
this.context.removeListener('sync', this.onClientSync);
this.clearBlurhashTimeout();
SettingsStore.unwatchSetting(this.sizeWatcher);
}
protected messageContent(
@ -367,11 +374,25 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
infoHeight = this.state.loadedImageDimensions.naturalHeight;
}
// The maximum height of the thumbnail as it is rendered as an <img>
const maxHeight = forcedHeight || Math.min((this.props.maxImageHeight || 600), infoHeight);
// The maximum width of the thumbnail, as dictated by its natural
// maximum height.
const maxWidth = infoWidth * maxHeight / infoHeight;
// The maximum size of the thumbnail as it is rendered as an <img>
// check for any height constraints
const imageSize = SettingsStore.getValue("Images.size") as ImageSize;
const suggestedAndPossibleWidth = Math.min(suggestedImageSize(imageSize).w, infoWidth);
const aspectRatio = infoWidth / infoHeight;
let maxWidth;
let maxHeight;
const maxHeightConstraint = forcedHeight || this.props.maxImageHeight || undefined;
if (maxHeightConstraint && maxHeightConstraint * aspectRatio < suggestedAndPossibleWidth) {
// width is dictated by the maximum height that was defined by the props or the function param `forcedHeight`
maxWidth = maxHeightConstraint * aspectRatio;
// there is no need to check for infoHeight here since this is done with `maxHeightConstraint * aspectRatio < suggestedAndPossibleWidth`
maxHeight = maxHeightConstraint;
} else {
// height is dictated by suggestedWidth (based on the Image.size setting)
maxWidth = suggestedAndPossibleWidth;
maxHeight = suggestedAndPossibleWidth / aspectRatio;
}
let img = null;
let placeholder = null;

View file

@ -28,6 +28,7 @@ import { IBodyProps } from "./IBodyProps";
import MFileBody from "./MFileBody";
import { logger } from "matrix-js-sdk/src/logger";
import { ImageSize, suggestedSize as suggestedVideoSize } from "../../../settings/enums/ImageSize";
interface IState {
decryptedUrl?: string;
@ -42,6 +43,7 @@ interface IState {
@replaceableComponent("views.messages.MVideoBody")
export default class MVideoBody extends React.PureComponent<IBodyProps, IState> {
private videoRef = React.createRef<HTMLVideoElement>();
private sizeWatcher: string;
constructor(props) {
super(props);
@ -57,7 +59,22 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
};
}
thumbScale(fullWidth: number, fullHeight: number, thumbWidth = 480, thumbHeight = 360) {
private get suggestedDimensions(): { w: number, h: number } {
return suggestedVideoSize(SettingsStore.getValue("Images.size") as ImageSize);
}
private thumbScale(
fullWidth: number,
fullHeight: number,
thumbWidth?: number,
thumbHeight?: number,
): number {
if (!thumbWidth || !thumbHeight) {
const dims = this.suggestedDimensions;
thumbWidth = dims.w;
thumbHeight = dims.h;
}
if (!fullWidth || !fullHeight) {
// Cannot calculate thumbnail height for image: missing w/h in metadata. We can't even
// log this because it's spammy
@ -68,14 +85,8 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
return 1;
}
const widthMulti = thumbWidth / fullWidth;
const heightMulti = thumbHeight / fullHeight;
if (widthMulti < heightMulti) {
// width is the dominant dimension so scaling will be fixed on that
return widthMulti;
} else {
// height is the dominant dimension so scaling will be fixed on that
return heightMulti;
}
// always scale the videos based on their width.
return widthMulti;
}
private getContentUrl(): string|null {
@ -152,12 +163,16 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
}
}
async componentDidMount() {
const autoplay = SettingsStore.getValue("autoplayVideo") as boolean;
public async componentDidMount() {
this.sizeWatcher = SettingsStore.watchSetting("Images.size", null, () => {
this.forceUpdate(); // we don't really have a reliable thing to update, so just update the whole thing
});
this.loadBlurhash();
if (this.props.mediaEventHelper.media.isEncrypted && this.state.decryptedUrl === null) {
try {
const autoplay = SettingsStore.getValue("autoplayVideo") as boolean;
const thumbnailUrl = await this.props.mediaEventHelper.thumbnailUrl.value;
if (autoplay) {
logger.log("Preloading video");
@ -189,6 +204,10 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
}
}
public componentWillUnmount() {
SettingsStore.unwatchSetting(this.sizeWatcher);
}
private videoOnPlay = async () => {
if (this.hasContentUrl() || this.state.fetchingData || this.state.error) {
// We have the file, we are fetching the file, or there is an error.
@ -249,8 +268,9 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
const contentUrl = this.getContentUrl();
const thumbUrl = this.getThumbUrl();
let height = null;
let width = null;
const defaultDims = this.suggestedDimensions;
let height = defaultDims.h;
let width = defaultDims.w;
let poster = null;
let preload = "metadata";
if (content.info) {