diff --git a/res/css/_common.scss b/res/css/_common.scss index 3d8b6659b38..7c8f7326a4c 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 e02c4c823be..81b5e3be99e 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 0981cbf113e..db06c11e21e 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 ac3491bc8ff..b5fdaeabef4 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 00000000000..3b0b982be2e --- /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 00000000000..749a5c7ecbf --- /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 00000000000..96d8fd3fb4f --- /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 c57c2f7ecf2..9a354e4d217 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 6a204775dc0..bb0ee29bb07 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 5d2c5900816..f56d7469f16 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 7f20d0c824e..699cc6ae829 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 615b90a631a..f43b6409cdc 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 c07741510ad..615f3eed2a9 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 495d3c438ff..aa5fda2f286 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 93149021044..c3e1848b11b 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 cbb0e17b427..69ae1344cbe 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 a7ebf40c3a8..5e7c2a725b7 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 4eb0177fef6..13dcb114f91 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 481a8c870bf..64a8ff67015 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 85821129a1e..d8f2f7c6559 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 b2e587e51ab..078886ba49c 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 20d9cdf75c7..6626da3ed72 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 aabfc1c9a48..ab701731d20 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 00000000000..bacdd6144d3 --- /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/contexts/RoomContext.ts b/src/contexts/RoomContext.ts index 605df72f0dd..8329335831e 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 752e4135fed..76e752fa7bb 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 c263317dc49..d83198fc56d 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 b1d6cac55e6..e2ea710e080 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 00000000000..47f16ddc834 --- /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 e57862a8243..f7a9fe9108d 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 f7d3bb5e09a..7c8265fd32c 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 ec4894719e5..9c1a558a866 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");