mirror of
https://github.com/element-hq/element-web
synced 2024-11-22 09:15:41 +03:00
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:
parent
816136de97
commit
3c06e7f7a0
35 changed files with 293 additions and 45 deletions
|
@ -32,6 +32,7 @@ $slider-selection-dot-size: 2.4em;
|
|||
|
||||
$container-border-width: 8px;
|
||||
|
||||
$timeline-image-boarder-radius: 8px;
|
||||
:root {
|
||||
font-size: 10px;
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
47
res/css/views/settings/_ImageSizePanel.scss
Normal file
47
res/css/views/settings/_ImageSizePanel.scss
Normal file
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
15
res/img/element-icons/settings/img-size-large.svg
Normal file
15
res/img/element-icons/settings/img-size-large.svg
Normal file
|
@ -0,0 +1,15 @@
|
|||
<svg width="221" height="148" viewBox="0 0 221 148" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<mask id="mountains">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="white" />
|
||||
<path d="M109.918 67.966L80.2282 114.975C79.0024 116.916 80.3971 119.447 82.6927 119.447H142.073C144.369 119.447 145.763 116.916 144.538 114.975L114.847 67.966C113.704 66.1548 111.062 66.1548 109.918 67.966Z" fill="black"/>
|
||||
<path d="M141.995 82.4526L121.1 114.956C119.853 116.896 121.246 119.447 123.552 119.447H165.342C167.648 119.447 169.041 116.896 167.794 114.956L146.899 82.4526C145.751 80.6677 143.142 80.6677 141.995 82.4526Z" fill="black"/>
|
||||
<circle cx="161.936" cy="61.1488" r="11.6596" fill="black"/>
|
||||
</mask>
|
||||
</defs>
|
||||
<rect x="0.5" y="0.5" width="220" height="147" rx="7.5" stroke="#E3E8F0"/>
|
||||
<rect x="57" y="14" width="10" height="10" rx="1.5" fill="#E3E8F0"/>
|
||||
<rect x="70" y="14" width="60" height="10" rx="1.5" fill="#E3E8F0"/>
|
||||
<path d="M0 8C0 3.58173 3.58172 0 8 0H45C45.5523 0 46 0.447715 46 1V147C46 147.552 45.5523 148 45 148H8C3.58172 148 0 144.418 0 140V8Z" fill="#E3E8F0"/>
|
||||
<rect x="57" y="32" width="137" height="102.021" rx="5.82979" fill="#E3E8F0" mask="url(#mountains)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
20
res/img/element-icons/settings/img-size-normal.svg
Normal file
20
res/img/element-icons/settings/img-size-normal.svg
Normal file
|
@ -0,0 +1,20 @@
|
|||
<svg width="221" height="148" viewBox="0 0 221 148" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<mask id="mountains">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="white" />
|
||||
<path d="M75.1545 94.3387L64.9688 110.466C64.5483 111.132 65.0268 112 65.8143 112H86.1857C86.9732 112 87.4517 111.132 87.0312 110.466L76.8455 94.3387C76.4531 93.7173 75.5469 93.7173 75.1545 94.3387Z" fill="black"/>
|
||||
<path d="M86.1588 99.3085L78.9905 110.459C78.5627 111.125 79.0405 112 79.8317 112H94.1683C94.9595 112 95.4373 111.125 95.0095 110.459L87.8412 99.3085C87.4475 98.6962 86.5525 98.6962 86.1588 99.3085Z" fill="black"/>
|
||||
<circle cx="93" cy="92" r="4" fill="black"/>
|
||||
</mask>
|
||||
</defs>
|
||||
<rect x="0.5" y="0.5" width="220" height="147" rx="7.5" stroke="#E3E8F0"/>
|
||||
<rect x="57" y="14" width="10" height="10" rx="1.5" fill="#E3E8F0"/>
|
||||
<rect x="57" y="64" width="10" height="10" rx="1.5" fill="#E3E8F0"/>
|
||||
<rect x="57" y="125" width="10" height="10" rx="1.5" fill="#E3E8F0"/>
|
||||
<rect x="70" y="14" width="60" height="10" rx="1.5" fill="#E3E8F0"/>
|
||||
<rect x="70" y="64" width="60" height="10" rx="1.5" fill="#E3E8F0"/>
|
||||
<rect x="70" y="125" width="60" height="10" rx="1.5" fill="#E3E8F0"/>
|
||||
<path d="M57 32.5C57 31.6716 57.6716 31 58.5 31H208.5C209.328 31 210 31.6716 210 32.5V54.5C210 55.3284 209.328 56 208.5 56H58.5C57.6716 56 57 55.3284 57 54.5V32.5Z" fill="#E3E8F0"/>
|
||||
<path d="M0 8C0 3.58173 3.58172 0 8 0H45C45.5523 0 46 0.447715 46 1V147C46 147.552 45.5523 148 45 148H8C3.58172 148 0 144.418 0 140V8Z" fill="#E3E8F0"/>
|
||||
<rect x="57" y="82" width="47" height="35" rx="2" fill="#E3E8F0" mask="url(#mountains)" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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<SetRightPanelPhasePayload>({
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
79
src/components/views/settings/ImageSizePanel.tsx
Normal file
79
src/components/views/settings/ImageSizePanel.tsx
Normal file
|
@ -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<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
size: SettingsStore.getValue("Images.size"),
|
||||
};
|
||||
}
|
||||
|
||||
private onSizeChange = (ev: React.ChangeEvent<HTMLInputElement>): 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 (
|
||||
<div className="mx_SettingsTab_section mx_ImageSizePanel">
|
||||
<span className="mx_SettingsTab_subheading">
|
||||
{ _t("Image size in the timeline") }
|
||||
</span>
|
||||
|
||||
<div className="mx_ImageSizePanel_radios">
|
||||
<label>
|
||||
<div className="mx_ImageSizePanel_size mx_ImageSizePanel_sizeDefault" />
|
||||
<StyledRadioButton
|
||||
name="image_size"
|
||||
value={ImageSize.Normal}
|
||||
checked={this.state.size === ImageSize.Normal}
|
||||
onChange={this.onSizeChange}
|
||||
>{ _t("Default") }</StyledRadioButton>
|
||||
</label>
|
||||
<label>
|
||||
<div className="mx_ImageSizePanel_size mx_ImageSizePanel_sizeLarge" />
|
||||
<StyledRadioButton
|
||||
name="image_size"
|
||||
value={ImageSize.Large}
|
||||
checked={this.state.size === ImageSize.Large}
|
||||
onChange={this.onSizeChange}
|
||||
>{ _t("Large") }</StyledRadioButton>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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<IProps, I
|
|||
{ layoutSection }
|
||||
<FontScalingPanel />
|
||||
{ this.renderAdvancedSection() }
|
||||
<ImageSizePanel />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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)"),
|
||||
|
|
|
@ -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) {
|
||||
|
|
33
src/settings/enums/ImageSize.ts
Normal file
33
src/settings/enums/ImageSize.ts
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
Loading…
Reference in a new issue