diff --git a/res/css/_common.scss b/res/css/_common.scss
index 3d8b6659b3..7c8f7326a4 100644
--- a/res/css/_common.scss
+++ b/res/css/_common.scss
@@ -32,6 +32,7 @@ $slider-selection-dot-size: 2.4em;
$container-border-width: 8px;
+$timeline-image-boarder-radius: 8px;
:root {
font-size: 10px;
diff --git a/res/css/_components.scss b/res/css/_components.scss
index e02c4c823b..81b5e3be99 100644
--- a/res/css/_components.scss
+++ b/res/css/_components.scss
@@ -252,6 +252,7 @@
@import "./views/settings/_E2eAdvancedPanel.scss";
@import "./views/settings/_EmailAddresses.scss";
@import "./views/settings/_FontScalingPanel.scss";
+@import "./views/settings/_ImageSizePanel.scss";
@import "./views/settings/_IntegrationManager.scss";
@import "./views/settings/_JoinRuleSettings.scss";
@import "./views/settings/_LayoutSwitcher.scss";
diff --git a/res/css/views/messages/_MImageBody.scss b/res/css/views/messages/_MImageBody.scss
index 0981cbf113..db06c11e21 100644
--- a/res/css/views/messages/_MImageBody.scss
+++ b/res/css/views/messages/_MImageBody.scss
@@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
+Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-$timelineImageBorderRadius: 4px;
+$timeline-image-boarder-radius: 8px;
.mx_MImageBody_thumbnail--blurhash {
position: absolute;
@@ -24,7 +25,7 @@ $timelineImageBorderRadius: 4px;
.mx_MImageBody_thumbnail {
object-fit: contain;
- border-radius: $timelineImageBorderRadius;
+ border-radius: $timeline-image-boarder-radius;
display: flex;
justify-content: center;
@@ -32,9 +33,10 @@ $timelineImageBorderRadius: 4px;
height: 100%;
width: 100%;
+ // this is needed so that the Blurhash can get have rounded corners without beeing the correct size during loading.
+ overflow: hidden;
.mx_Blurhash > canvas {
animation: mx--anim-pulse 1.75s infinite cubic-bezier(.4, 0, .6, 1);
- border-radius: $timelineImageBorderRadius;
}
.mx_no-image-placeholder {
diff --git a/res/css/views/messages/_MVideoBody.scss b/res/css/views/messages/_MVideoBody.scss
index ac3491bc8f..b5fdaeabef 100644
--- a/res/css/views/messages/_MVideoBody.scss
+++ b/res/css/views/messages/_MVideoBody.scss
@@ -1,5 +1,5 @@
/*
-Copyright 2020 The Matrix.org Foundation C.I.C.
+Copyright 2020 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -18,6 +18,6 @@ span.mx_MVideoBody {
video.mx_MVideoBody {
max-width: 100%;
height: auto;
- border-radius: 4px;
+ border-radius: $timeline-image-boarder-radius;
}
}
diff --git a/res/css/views/settings/_ImageSizePanel.scss b/res/css/views/settings/_ImageSizePanel.scss
new file mode 100644
index 0000000000..3b0b982be2
--- /dev/null
+++ b/res/css/views/settings/_ImageSizePanel.scss
@@ -0,0 +1,47 @@
+/*
+Copyright 2021 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+.mx_ImageSizePanel {
+ color: $primary-content;
+
+ .mx_ImageSizePanel_radios {
+ display: flex;
+ margin-top: 16px; // move away from header a bit
+
+ > label {
+ margin-right: 68px; // keep the boxes separate
+ cursor: pointer;
+ }
+
+ .mx_ImageSizePanel_size {
+ background-color: $quinary-content;
+ mask-repeat: no-repeat;
+ mask-size: 221px;
+ mask-position: center;
+ width: 221px;
+ height: 148px;
+ margin-bottom: 14px; // move radio button away from bottom edge a bit
+
+ &.mx_ImageSizePanel_sizeDefault {
+ mask: url("$(res)/img/element-icons/settings/img-size-normal.svg");
+ }
+
+ &.mx_ImageSizePanel_sizeLarge {
+ mask: url("$(res)/img/element-icons/settings/img-size-large.svg");
+ }
+ }
+ }
+}
diff --git a/res/img/element-icons/settings/img-size-large.svg b/res/img/element-icons/settings/img-size-large.svg
new file mode 100644
index 0000000000..749a5c7ecb
--- /dev/null
+++ b/res/img/element-icons/settings/img-size-large.svg
@@ -0,0 +1,15 @@
+
diff --git a/res/img/element-icons/settings/img-size-normal.svg b/res/img/element-icons/settings/img-size-normal.svg
new file mode 100644
index 0000000000..96d8fd3fb4
--- /dev/null
+++ b/res/img/element-icons/settings/img-size-normal.svg
@@ -0,0 +1,20 @@
+
diff --git a/src/components/structures/FilePanel.tsx b/src/components/structures/FilePanel.tsx
index c57c2f7ecf..9a354e4d21 100644
--- a/src/components/structures/FilePanel.tsx
+++ b/src/components/structures/FilePanel.tsx
@@ -36,7 +36,7 @@ import ResizeNotifier from '../../utils/ResizeNotifier';
import TimelinePanel from "./TimelinePanel";
import Spinner from "../views/elements/Spinner";
import { TileShape } from '../views/rooms/EventTile';
-import { Layout } from "../../settings/Layout";
+import { Layout } from "../../settings/enums/Layout";
import RoomContext, { TimelineRenderingType } from '../../contexts/RoomContext';
import { logger } from "matrix-js-sdk/src/logger";
diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx
index 6a204775dc..bb0ee29bb0 100644
--- a/src/components/structures/MessagePanel.tsx
+++ b/src/components/structures/MessagePanel.tsx
@@ -27,7 +27,7 @@ import { wantsDateSeparator } from '../../DateUtils';
import { MatrixClientPeg } from '../../MatrixClientPeg';
import SettingsStore from '../../settings/SettingsStore';
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
-import { Layout } from "../../settings/Layout";
+import { Layout } from "../../settings/enums/Layout";
import { _t } from "../../languageHandler";
import EventTile, { haveTileForEvent, IReadReceiptProps, TileShape } from "../views/rooms/EventTile";
import { hasText } from "../../TextForEvent";
diff --git a/src/components/structures/NotificationPanel.tsx b/src/components/structures/NotificationPanel.tsx
index 5d2c590081..f56d7469f1 100644
--- a/src/components/structures/NotificationPanel.tsx
+++ b/src/components/structures/NotificationPanel.tsx
@@ -23,7 +23,7 @@ import { replaceableComponent } from "../../utils/replaceableComponent";
import TimelinePanel from "./TimelinePanel";
import Spinner from "../views/elements/Spinner";
import { TileShape } from "../views/rooms/EventTile";
-import { Layout } from "../../settings/Layout";
+import { Layout } from "../../settings/enums/Layout";
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
import { logger } from "matrix-js-sdk/src/logger";
diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx
index 342a8c31f3..833d3d3bcc 100644
--- a/src/components/structures/RoomView.tsx
+++ b/src/components/structures/RoomView.tsx
@@ -44,7 +44,7 @@ import RoomViewStore from '../../stores/RoomViewStore';
import RoomScrollStateStore, { ScrollState } from '../../stores/RoomScrollStateStore';
import WidgetEchoStore from '../../stores/WidgetEchoStore';
import SettingsStore from "../../settings/SettingsStore";
-import { Layout } from "../../settings/Layout";
+import { Layout } from "../../settings/enums/Layout";
import AccessibleButton from "../views/elements/AccessibleButton";
import RightPanelStore from "../../stores/RightPanelStore";
import { haveTileForEvent } from "../views/rooms/EventTile";
diff --git a/src/components/structures/ThreadPanel.tsx b/src/components/structures/ThreadPanel.tsx
index 615b90a631..f43b6409cd 100644
--- a/src/components/structures/ThreadPanel.tsx
+++ b/src/components/structures/ThreadPanel.tsx
@@ -27,7 +27,7 @@ import { ContextMenuButton } from '../../accessibility/context_menu/ContextMenuB
import ContextMenu, { ChevronFace, useContextMenu } from './ContextMenu';
import RoomContext, { TimelineRenderingType } from '../../contexts/RoomContext';
import TimelinePanel from './TimelinePanel';
-import { Layout } from '../../settings/Layout';
+import { Layout } from '../../settings/enums/Layout';
import { useEventEmitter } from '../../hooks/useEventEmitter';
import AccessibleButton from '../views/elements/AccessibleButton';
import { TileShape } from '../views/rooms/EventTile';
diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx
index c07741510a..615f3eed2a 100644
--- a/src/components/structures/ThreadView.tsx
+++ b/src/components/structures/ThreadView.tsx
@@ -27,7 +27,7 @@ import ResizeNotifier from '../../utils/ResizeNotifier';
import { TileShape } from '../views/rooms/EventTile';
import MessageComposer from '../views/rooms/MessageComposer';
import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks';
-import { Layout } from '../../settings/Layout';
+import { Layout } from '../../settings/enums/Layout';
import TimelinePanel from './TimelinePanel';
import dis from "../../dispatcher/dispatcher";
import { ActionPayload } from '../../dispatcher/payloads';
diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx
index 495d3c438f..aa5fda2f28 100644
--- a/src/components/structures/TimelinePanel.tsx
+++ b/src/components/structures/TimelinePanel.tsx
@@ -25,7 +25,7 @@ import { EventType, RelationType } from 'matrix-js-sdk/src/@types/event';
import { SyncState } from 'matrix-js-sdk/src/sync.api';
import SettingsStore from "../../settings/SettingsStore";
-import { Layout } from "../../settings/Layout";
+import { Layout } from "../../settings/enums/Layout";
import { _t } from '../../languageHandler';
import { MatrixClientPeg } from "../../MatrixClientPeg";
import RoomContext from "../../contexts/RoomContext";
diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx
index 9314902104..c3e1848b11 100644
--- a/src/components/views/dialogs/ForwardDialog.tsx
+++ b/src/components/views/dialogs/ForwardDialog.tsx
@@ -25,7 +25,7 @@ import { _t } from "../../../languageHandler";
import dis from "../../../dispatcher/dispatcher";
import { useSettingValue, useFeatureEnabled } from "../../../hooks/useSettings";
import { UIFeature } from "../../../settings/UIFeature";
-import { Layout } from "../../../settings/Layout";
+import { Layout } from "../../../settings/enums/Layout";
import { IDialogProps } from "./IDialogProps";
import BaseDialog from "./BaseDialog";
import { avatarUrlForUser } from "../../../Avatar";
diff --git a/src/components/views/elements/EventListSummary.tsx b/src/components/views/elements/EventListSummary.tsx
index cbb0e17b42..69ae1344cb 100644
--- a/src/components/views/elements/EventListSummary.tsx
+++ b/src/components/views/elements/EventListSummary.tsx
@@ -22,7 +22,7 @@ import MemberAvatar from '../avatars/MemberAvatar';
import { _t } from '../../../languageHandler';
import { useStateToggle } from "../../../hooks/useStateToggle";
import AccessibleButton from "./AccessibleButton";
-import { Layout } from '../../../settings/Layout';
+import { Layout } from '../../../settings/enums/Layout';
interface IProps {
// An array of member events to summarise
diff --git a/src/components/views/elements/EventTilePreview.tsx b/src/components/views/elements/EventTilePreview.tsx
index a7ebf40c3a..5e7c2a725b 100644
--- a/src/components/views/elements/EventTilePreview.tsx
+++ b/src/components/views/elements/EventTilePreview.tsx
@@ -22,7 +22,7 @@ import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
import * as Avatar from '../../../Avatar';
import EventTile from '../rooms/EventTile';
import SettingsStore from "../../../settings/SettingsStore";
-import { Layout } from "../../../settings/Layout";
+import { Layout } from "../../../settings/enums/Layout";
import { UIFeature } from "../../../settings/UIFeature";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import Spinner from './Spinner';
diff --git a/src/components/views/elements/MemberEventListSummary.tsx b/src/components/views/elements/MemberEventListSummary.tsx
index 4eb0177fef..13dcb114f9 100644
--- a/src/components/views/elements/MemberEventListSummary.tsx
+++ b/src/components/views/elements/MemberEventListSummary.tsx
@@ -31,7 +31,7 @@ import { Action } from '../../../dispatcher/actions';
import { SetRightPanelPhasePayload } from '../../../dispatcher/payloads/SetRightPanelPhasePayload';
import { jsxJoin } from '../../../utils/ReactUtils';
import { EventType } from 'matrix-js-sdk/src/@types/event';
-import { Layout } from '../../../settings/Layout';
+import { Layout } from '../../../settings/enums/Layout';
const onPinnedMessagesClick = (): void => {
defaultDispatcher.dispatch({
diff --git a/src/components/views/elements/ReplyChain.tsx b/src/components/views/elements/ReplyChain.tsx
index 481a8c870b..64a8ff6701 100644
--- a/src/components/views/elements/ReplyChain.tsx
+++ b/src/components/views/elements/ReplyChain.tsx
@@ -23,7 +23,7 @@ import dis from '../../../dispatcher/dispatcher';
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
import { makeUserPermalink, RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
import SettingsStore from "../../../settings/SettingsStore";
-import { Layout } from "../../../settings/Layout";
+import { Layout } from "../../../settings/enums/Layout";
import escapeHtml from "escape-html";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { getUserNameColorClass } from "../../../utils/FormattingUtils";
diff --git a/src/components/views/messages/MImageBody.tsx b/src/components/views/messages/MImageBody.tsx
index 85821129a1..d8f2f7c655 100644
--- a/src/components/views/messages/MImageBody.tsx
+++ b/src/components/views/messages/MImageBody.tsx
@@ -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 {
private unmounted = true;
private image = createRef();
private timeout?: number;
+ private sizeWatcher: string;
constructor(props: IBodyProps) {
super(props);
@@ -317,12 +319,17 @@ export default class MImageBody extends React.Component {
}
}, 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 {
infoHeight = this.state.loadedImageDimensions.naturalHeight;
}
- // The maximum height of the thumbnail as it is rendered as an
- 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
+ // 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;
diff --git a/src/components/views/messages/MVideoBody.tsx b/src/components/views/messages/MVideoBody.tsx
index b2e587e51a..078886ba49 100644
--- a/src/components/views/messages/MVideoBody.tsx
+++ b/src/components/views/messages/MVideoBody.tsx
@@ -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 {
private videoRef = React.createRef();
+ private sizeWatcher: string;
constructor(props) {
super(props);
@@ -57,7 +59,22 @@ export default class MVideoBody extends React.PureComponent
};
}
- 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
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
}
}
- 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
}
}
+ 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
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) {
diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx
index 0745a6b191..0ed29d18b6 100644
--- a/src/components/views/rooms/EventTile.tsx
+++ b/src/components/views/rooms/EventTile.tsx
@@ -28,7 +28,7 @@ import { _t } from '../../../languageHandler';
import { hasText } from "../../../TextForEvent";
import * as sdk from "../../../index";
import dis from '../../../dispatcher/dispatcher';
-import { Layout } from "../../../settings/Layout";
+import { Layout } from "../../../settings/enums/Layout";
import { formatTime } from "../../../DateUtils";
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import { ALL_RULE_TYPES } from "../../../mjolnir/BanList";
diff --git a/src/components/views/settings/FontScalingPanel.tsx b/src/components/views/settings/FontScalingPanel.tsx
index aabfc1c9a4..ab701731d2 100644
--- a/src/components/views/settings/FontScalingPanel.tsx
+++ b/src/components/views/settings/FontScalingPanel.tsx
@@ -22,7 +22,7 @@ import SettingsStore from "../../../settings/SettingsStore";
import Slider from "../elements/Slider";
import { FontWatcher } from "../../../settings/watchers/FontWatcher";
import { IValidationResult, IFieldState } from '../elements/Validation';
-import { Layout } from "../../../settings/Layout";
+import { Layout } from "../../../settings/enums/Layout";
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import { SettingLevel } from "../../../settings/SettingLevel";
import { _t } from "../../../languageHandler";
diff --git a/src/components/views/settings/ImageSizePanel.tsx b/src/components/views/settings/ImageSizePanel.tsx
new file mode 100644
index 0000000000..bacdd6144d
--- /dev/null
+++ b/src/components/views/settings/ImageSizePanel.tsx
@@ -0,0 +1,79 @@
+/*
+Copyright 2021 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from "react";
+import SettingsStore from "../../../settings/SettingsStore";
+import StyledRadioButton from "../elements/StyledRadioButton";
+import { _t } from "../../../languageHandler";
+import { SettingLevel } from "../../../settings/SettingLevel";
+import { ImageSize } from "../../../settings/enums/ImageSize";
+
+interface IProps {
+ // none
+}
+
+interface IState {
+ size: ImageSize;
+}
+
+export default class ImageSizePanel extends React.Component {
+ constructor(props: IProps) {
+ super(props);
+
+ this.state = {
+ size: SettingsStore.getValue("Images.size"),
+ };
+ }
+
+ private onSizeChange = (ev: React.ChangeEvent): void => {
+ const newSize = ev.target.value as ImageSize;
+ this.setState({ size: newSize });
+
+ // noinspection JSIgnoredPromiseFromCall
+ SettingsStore.setValue("Images.size", null, SettingLevel.ACCOUNT, newSize);
+ };
+
+ public render(): JSX.Element {
+ return (
+
+
+ { _t("Image size in the timeline") }
+
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/src/components/views/settings/LayoutSwitcher.tsx b/src/components/views/settings/LayoutSwitcher.tsx
index ad8abd0033..22fcc734a1 100644
--- a/src/components/views/settings/LayoutSwitcher.tsx
+++ b/src/components/views/settings/LayoutSwitcher.tsx
@@ -22,7 +22,7 @@ import SettingsStore from "../../../settings/SettingsStore";
import EventTilePreview from "../elements/EventTilePreview";
import StyledRadioButton from "../elements/StyledRadioButton";
import { _t } from "../../../languageHandler";
-import { Layout } from "../../../settings/Layout";
+import { Layout } from "../../../settings/enums/Layout";
import { SettingLevel } from "../../../settings/SettingLevel";
interface IProps {
diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx
index ed0092989b..37007fbad7 100644
--- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx
+++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx
@@ -25,12 +25,13 @@ import SettingsFlag from '../../../elements/SettingsFlag';
import Field from '../../../elements/Field';
import { SettingLevel } from "../../../../../settings/SettingLevel";
import { UIFeature } from "../../../../../settings/UIFeature";
-import { Layout } from "../../../../../settings/Layout";
+import { Layout } from "../../../../../settings/enums/Layout";
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
import LayoutSwitcher from "../../LayoutSwitcher";
import FontScalingPanel from '../../FontScalingPanel';
import ThemeChoicePanel from '../../ThemeChoicePanel';
+import ImageSizePanel from "../../ImageSizePanel";
interface IProps {
}
@@ -188,6 +189,7 @@ export default class AppearanceUserSettingsTab extends React.Component
{ this.renderAdvancedSection() }
+
);
}
diff --git a/src/contexts/RoomContext.ts b/src/contexts/RoomContext.ts
index 605df72f0d..8329335831 100644
--- a/src/contexts/RoomContext.ts
+++ b/src/contexts/RoomContext.ts
@@ -17,7 +17,7 @@ limitations under the License.
import { createContext } from "react";
import { IRoomState } from "../components/structures/RoomView";
-import { Layout } from "../settings/Layout";
+import { Layout } from "../settings/enums/Layout";
export enum TimelineRenderingType {
Room = "Room",
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 752e4135fe..76e752fa7b 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1182,6 +1182,8 @@
"Size must be a number": "Size must be a number",
"Custom font size can only be between %(min)s pt and %(max)s pt": "Custom font size can only be between %(min)s pt and %(max)s pt",
"Use between %(min)s pt and %(max)s pt": "Use between %(min)s pt and %(max)s pt",
+ "Image size in the timeline": "Image size in the timeline",
+ "Large": "Large",
"Connecting to integration manager...": "Connecting to integration manager...",
"Cannot connect to integration manager": "Cannot connect to integration manager",
"The integration manager is offline or it cannot reach your homeserver.": "The integration manager is offline or it cannot reach your homeserver.",
diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx
index c263317dc4..d83198fc56 100644
--- a/src/settings/Settings.tsx
+++ b/src/settings/Settings.tsx
@@ -37,11 +37,12 @@ import { isMac } from '../Keyboard';
import UIFeatureController from "./controllers/UIFeatureController";
import { UIFeature } from "./UIFeature";
import { OrderedMultiController } from "./controllers/OrderedMultiController";
-import { Layout } from "./Layout";
+import { Layout } from "./enums/Layout";
import ReducedMotionController from './controllers/ReducedMotionController';
import IncompatibleController from "./controllers/IncompatibleController";
import PseudonymousAnalyticsController from './controllers/PseudonymousAnalyticsController';
import NewLayoutSwitcherController from './controllers/NewLayoutSwitcherController';
+import { ImageSize } from "./enums/ImageSize";
import { MetaSpace } from "../stores/spaces";
// These are just a bunch of helper arrays to avoid copy/pasting a bunch of times
@@ -737,6 +738,10 @@ export const SETTINGS: {[setting: string]: ISetting} = {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
default: Layout.Group,
},
+ "Images.size": {
+ supportedLevels: LEVELS_ACCOUNT_SETTINGS,
+ default: ImageSize.Normal,
+ },
"showChatEffects": {
supportedLevels: LEVELS_ROOM_SETTINGS_WITH_ROOM,
displayName: _td("Show chat effects (animations when receiving e.g. confetti)"),
diff --git a/src/settings/controllers/NewLayoutSwitcherController.ts b/src/settings/controllers/NewLayoutSwitcherController.ts
index b1d6cac55e..e2ea710e08 100644
--- a/src/settings/controllers/NewLayoutSwitcherController.ts
+++ b/src/settings/controllers/NewLayoutSwitcherController.ts
@@ -14,7 +14,7 @@ limitations under the License.
import SettingController from "./SettingController";
import { SettingLevel } from "../SettingLevel";
import SettingsStore from "../SettingsStore";
-import { Layout } from "../Layout";
+import { Layout } from "../enums/Layout";
export default class NewLayoutSwitcherController extends SettingController {
public onChange(level: SettingLevel, roomId: string, newValue: any) {
diff --git a/src/settings/enums/ImageSize.ts b/src/settings/enums/ImageSize.ts
new file mode 100644
index 0000000000..47f16ddc83
--- /dev/null
+++ b/src/settings/enums/ImageSize.ts
@@ -0,0 +1,33 @@
+/*
+Copyright 2021 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+const SIZE_LARGE = { w: 480, h: 360 };
+const SIZE_NORMAL = { w: 324, h: 220 };
+
+export enum ImageSize {
+ Normal = "normal",
+ Large = "large",
+}
+
+export function suggestedSize(size: ImageSize): { w: number, h: number } {
+ switch (size) {
+ case ImageSize.Large:
+ return SIZE_LARGE;
+ case ImageSize.Normal:
+ default:
+ return SIZE_NORMAL;
+ }
+}
diff --git a/src/settings/Layout.ts b/src/settings/enums/Layout.ts
similarity index 100%
rename from src/settings/Layout.ts
rename to src/settings/enums/Layout.ts
diff --git a/src/settings/handlers/DeviceSettingsHandler.ts b/src/settings/handlers/DeviceSettingsHandler.ts
index e57862a824..f7a9fe9108 100644
--- a/src/settings/handlers/DeviceSettingsHandler.ts
+++ b/src/settings/handlers/DeviceSettingsHandler.ts
@@ -20,7 +20,7 @@ import SettingsHandler from "./SettingsHandler";
import { MatrixClientPeg } from "../../MatrixClientPeg";
import { SettingLevel } from "../SettingLevel";
import { CallbackFn, WatchManager } from "../WatchManager";
-import { Layout } from "../Layout";
+import { Layout } from "../enums/Layout";
/**
* Gets and sets settings at the "device" level for the current device.
diff --git a/src/utils/exportUtils/HtmlExport.tsx b/src/utils/exportUtils/HtmlExport.tsx
index f7d3bb5e09..7c8265fd32 100644
--- a/src/utils/exportUtils/HtmlExport.tsx
+++ b/src/utils/exportUtils/HtmlExport.tsx
@@ -21,7 +21,7 @@ import { mediaFromMxc } from "../../customisations/Media";
import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { renderToStaticMarkup } from "react-dom/server";
-import { Layout } from "../../settings/Layout";
+import { Layout } from "../../settings/enums/Layout";
import { shouldFormContinuation } from "../../components/structures/MessagePanel";
import { formatFullDateNoDayNoTime, wantsDateSeparator } from "../../DateUtils";
import { RoomPermalinkCreator } from "../permalinks/Permalinks";
diff --git a/test/components/views/rooms/SendMessageComposer-test.tsx b/test/components/views/rooms/SendMessageComposer-test.tsx
index ec4894719e..9c1a558a86 100644
--- a/test/components/views/rooms/SendMessageComposer-test.tsx
+++ b/test/components/views/rooms/SendMessageComposer-test.tsx
@@ -35,7 +35,7 @@ import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import SpecPermalinkConstructor from "../../../../src/utils/permalinks/SpecPermalinkConstructor";
import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
import DocumentOffset from '../../../../src/editor/offset';
-import { Layout } from '../../../../src/settings/Layout';
+import { Layout } from '../../../../src/settings/enums/Layout';
jest.mock("../../../../src/stores/RoomViewStore");