Convert cases of mxcUrlToHttp to new media customisation

This commit is contained in:
Travis Ralston 2021-03-03 19:06:46 -07:00
parent 53935782bc
commit 1ac12479ca
33 changed files with 178 additions and 121 deletions

View file

@ -36,6 +36,7 @@ import {MatrixClientPeg} from './MatrixClientPeg';
import {tryTransformPermalinkToLocalHref} from "./utils/permalinks/Permalinks"; import {tryTransformPermalinkToLocalHref} from "./utils/permalinks/Permalinks";
import {SHORTCODE_TO_EMOJI, getEmojiFromUnicode} from "./emoji"; import {SHORTCODE_TO_EMOJI, getEmojiFromUnicode} from "./emoji";
import ReplyThread from "./components/views/elements/ReplyThread"; import ReplyThread from "./components/views/elements/ReplyThread";
import {mediaFromMxc} from "./customisations/Media";
linkifyMatrix(linkify); linkifyMatrix(linkify);
@ -181,11 +182,9 @@ const transformTags: IExtendedSanitizeOptions["transformTags"] = { // custom to
if (!attribs.src || !attribs.src.startsWith('mxc://') || !SettingsStore.getValue("showImages")) { if (!attribs.src || !attribs.src.startsWith('mxc://') || !SettingsStore.getValue("showImages")) {
return { tagName, attribs: {}}; return { tagName, attribs: {}};
} }
attribs.src = MatrixClientPeg.get().mxcUrlToHttp( const width = Number(attribs.width) || 800;
attribs.src, const height = Number(attribs.height) || 600;
attribs.width || 800, attribs.src = mediaFromMxc(attribs.src).getThumbnailOfSourceHttp(width, height);
attribs.height || 600,
);
return { tagName, attribs }; return { tagName, attribs };
}, },
'code': function(tagName: string, attribs: sanitizeHtml.Attributes) { 'code': function(tagName: string, attribs: sanitizeHtml.Attributes) {

View file

@ -36,6 +36,7 @@ import {SettingLevel} from "./settings/SettingLevel";
import {isPushNotifyDisabled} from "./settings/controllers/NotificationControllers"; import {isPushNotifyDisabled} from "./settings/controllers/NotificationControllers";
import RoomViewStore from "./stores/RoomViewStore"; import RoomViewStore from "./stores/RoomViewStore";
import UserActivity from "./UserActivity"; import UserActivity from "./UserActivity";
import {mediaFromMxc} from "./customisations/Media";
/* /*
* Dispatches: * Dispatches:
@ -150,7 +151,7 @@ export const Notifier = {
// Ideally in here we could use MSC1310 to detect the type of file, and reject it. // Ideally in here we could use MSC1310 to detect the type of file, and reject it.
return { return {
url: MatrixClientPeg.get().mxcUrlToHttp(content.url), url: mediaFromMxc(content.url).srcHttp,
name: content.name, name: content.name,
type: content.type, type: content.type,
size: content.size, size: content.size,

View file

@ -27,6 +27,7 @@ import {sortBy} from "lodash";
import {makeGroupPermalink} from "../utils/permalinks/Permalinks"; import {makeGroupPermalink} from "../utils/permalinks/Permalinks";
import {ICompletion, ISelectionRange} from "./Autocompleter"; import {ICompletion, ISelectionRange} from "./Autocompleter";
import FlairStore from "../stores/FlairStore"; import FlairStore from "../stores/FlairStore";
import {mediaFromMxc} from "../customisations/Media";
const COMMUNITY_REGEX = /\B\+\S*/g; const COMMUNITY_REGEX = /\B\+\S*/g;
@ -95,7 +96,7 @@ export default class CommunityProvider extends AutocompleteProvider {
name={name || groupId} name={name || groupId}
width={24} width={24}
height={24} height={24}
url={avatarUrl ? cli.mxcUrlToHttp(avatarUrl, 24, 24) : null} /> url={avatarUrl ? mediaFromMxc(avatarUrl).getSquareThumbnailHttp(24) : null} />
</PillCompletion> </PillCompletion>
), ),
range, range,

View file

@ -39,6 +39,7 @@ import {Group} from "matrix-js-sdk";
import {allSettled, sleep} from "../../utils/promise"; import {allSettled, sleep} from "../../utils/promise";
import RightPanelStore from "../../stores/RightPanelStore"; import RightPanelStore from "../../stores/RightPanelStore";
import AutoHideScrollbar from "./AutoHideScrollbar"; import AutoHideScrollbar from "./AutoHideScrollbar";
import {mediaFromMxc} from "../../customisations/Media";
import {replaceableComponent} from "../../utils/replaceableComponent"; import {replaceableComponent} from "../../utils/replaceableComponent";
const LONG_DESC_PLACEHOLDER = _td( const LONG_DESC_PLACEHOLDER = _td(
@ -368,8 +369,7 @@ class FeaturedUser extends React.Component {
const permalink = makeUserPermalink(this.props.summaryInfo.user_id); const permalink = makeUserPermalink(this.props.summaryInfo.user_id);
const userNameNode = <a href={permalink} onClick={this.onClick}>{ name }</a>; const userNameNode = <a href={permalink} onClick={this.onClick}>{ name }</a>;
const httpUrl = MatrixClientPeg.get() const httpUrl = mediaFromMxc(this.props.summaryInfo.avatar_url).getSquareThumbnailHttp(64);
.mxcUrlToHttp(this.props.summaryInfo.avatar_url, 64, 64);
const deleteButton = this.props.editing ? const deleteButton = this.props.editing ?
<img <img
@ -981,10 +981,9 @@ export default class GroupView extends React.Component {
<Spinner /> <Spinner />
</div>; </div>;
} }
const httpInviterAvatar = this.state.inviterProfile ? const httpInviterAvatar = this.state.inviterProfile
this._matrixClient.mxcUrlToHttp( ? mediaFromMxc(this.state.inviterProfile.avatarUrl).getSquareThumbnailHttp(36)
this.state.inviterProfile.avatarUrl, 36, 36, : null;
) : null;
const inviter = group.inviter || {}; const inviter = group.inviter || {};
let inviterName = inviter.userId; let inviterName = inviter.userId;

View file

@ -41,6 +41,7 @@ import RoomListNumResults from "../views/rooms/RoomListNumResults";
import LeftPanelWidget from "./LeftPanelWidget"; import LeftPanelWidget from "./LeftPanelWidget";
import SpacePanel from "../views/spaces/SpacePanel"; import SpacePanel from "../views/spaces/SpacePanel";
import {replaceableComponent} from "../../utils/replaceableComponent"; import {replaceableComponent} from "../../utils/replaceableComponent";
import {mediaFromMxc} from "../../customisations/Media";
interface IProps { interface IProps {
isMinimized: boolean; isMinimized: boolean;
@ -121,7 +122,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
let avatarUrl = OwnProfileStore.instance.getHttpAvatarUrl(avatarSize); let avatarUrl = OwnProfileStore.instance.getHttpAvatarUrl(avatarSize);
const settingBgMxc = SettingsStore.getValue("RoomList.backgroundImage"); const settingBgMxc = SettingsStore.getValue("RoomList.backgroundImage");
if (settingBgMxc) { if (settingBgMxc) {
avatarUrl = MatrixClientPeg.get().mxcUrlToHttp(settingBgMxc, avatarSize, avatarSize); avatarUrl = mediaFromMxc(settingBgMxc).getSquareThumbnailHttp(avatarSize);
} }
const avatarUrlProp = `url(${avatarUrl})`; const avatarUrlProp = `url(${avatarUrl})`;

View file

@ -34,6 +34,7 @@ import {EnhancedMap} from "../../utils/maps";
import StyledCheckbox from "../views/elements/StyledCheckbox"; import StyledCheckbox from "../views/elements/StyledCheckbox";
import AutoHideScrollbar from "./AutoHideScrollbar"; import AutoHideScrollbar from "./AutoHideScrollbar";
import BaseAvatar from "../views/avatars/BaseAvatar"; import BaseAvatar from "../views/avatars/BaseAvatar";
import {mediaFromMxc} from "../../customisations/Media";
interface IProps { interface IProps {
space: Room; space: Room;
@ -158,12 +159,7 @@ const SubSpace: React.FC<ISubspaceProps> = ({
let url: string; let url: string;
if (space.avatar_url) { if (space.avatar_url) {
url = MatrixClientPeg.get().mxcUrlToHttp( url = mediaFromMxc(space.avatar_url).getSquareThumbnailHttp(Math.floor(24 * window.devicePixelRatio));
space.avatar_url,
Math.floor(24 * window.devicePixelRatio),
Math.floor(24 * window.devicePixelRatio),
"crop",
);
} }
return <div className="mx_SpaceRoomDirectory_subspace"> return <div className="mx_SpaceRoomDirectory_subspace">
@ -265,12 +261,7 @@ const RoomTile = ({ room, event, editing, queueAction, onPreviewClick, onJoinCli
let url: string; let url: string;
if (room.avatar_url) { if (room.avatar_url) {
url = cli.mxcUrlToHttp( url = mediaFromMxc(room.avatar_url).getSquareThumbnailHttp(Math.floor(32 * window.devicePixelRatio));
room.avatar_url,
Math.floor(32 * window.devicePixelRatio),
Math.floor(32 * window.devicePixelRatio),
"crop",
);
} }
const content = <React.Fragment> const content = <React.Fragment>

View file

@ -1,5 +1,5 @@
/* /*
Copyright 2017 Vector Creations Ltd Copyright 2017, 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -18,6 +18,8 @@ import React from 'react';
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {MatrixClientPeg} from '../../../MatrixClientPeg';
import BaseAvatar from './BaseAvatar'; import BaseAvatar from './BaseAvatar';
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromMxc} from "../../../customisations/Media";
import {ResizeMode} from "../../../customisations/models/ResizeMode";
export interface IProps { export interface IProps {
groupId?: string; groupId?: string;
@ -25,7 +27,7 @@ export interface IProps {
groupAvatarUrl?: string; groupAvatarUrl?: string;
width?: number; width?: number;
height?: number; height?: number;
resizeMethod?: string; resizeMethod?: ResizeMode;
onClick?: React.MouseEventHandler; onClick?: React.MouseEventHandler;
} }
@ -38,8 +40,7 @@ export default class GroupAvatar extends React.Component<IProps> {
}; };
getGroupAvatarUrl() { getGroupAvatarUrl() {
return MatrixClientPeg.get().mxcUrlToHttp( return mediaFromMxc(this.props.groupAvatarUrl).getThumbnailOfSourceHttp(
this.props.groupAvatarUrl,
this.props.width, this.props.width,
this.props.height, this.props.height,
this.props.resizeMethod, this.props.resizeMethod,

View file

@ -21,6 +21,7 @@ import * as sdk from '../../../index';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import { GroupMemberType } from '../../../groups'; import { GroupMemberType } from '../../../groups';
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromMxc} from "../../../customisations/Media";
/* /*
* A dialog for confirming an operation on another user. * A dialog for confirming an operation on another user.
@ -108,8 +109,9 @@ export default class ConfirmUserActionDialog extends React.Component {
name = this.props.member.name; name = this.props.member.name;
userId = this.props.member.userId; userId = this.props.member.userId;
} else { } else {
const httpAvatarUrl = this.props.groupMember.avatarUrl ? const httpAvatarUrl = this.props.groupMember.avatarUrl
this.props.matrixClient.mxcUrlToHttp(this.props.groupMember.avatarUrl, 48, 48) : null; ? mediaFromMxc(this.props.groupMember.avatarUrl).getSquareThumbnailHttp(48)
: null;
name = this.props.groupMember.displayname || this.props.groupMember.userId; name = this.props.groupMember.displayname || this.props.groupMember.userId;
userId = this.props.groupMember.userId; userId = this.props.groupMember.userId;
avatar = <BaseAvatar name={name} url={httpAvatarUrl} width={48} height={48} />; avatar = <BaseAvatar name={name} url={httpAvatarUrl} width={48} height={48} />;

View file

@ -24,6 +24,7 @@ import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore"; import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore";
import FlairStore from "../../../stores/FlairStore"; import FlairStore from "../../../stores/FlairStore";
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromMxc} from "../../../customisations/Media";
interface IProps extends IDialogProps { interface IProps extends IDialogProps {
communityId: string; communityId: string;
@ -118,7 +119,7 @@ export default class EditCommunityPrototypeDialog extends React.PureComponent<IP
let preview = <img src={this.state.avatarPreview} className="mx_EditCommunityPrototypeDialog_avatar" />; let preview = <img src={this.state.avatarPreview} className="mx_EditCommunityPrototypeDialog_avatar" />;
if (!this.state.avatarPreview) { if (!this.state.avatarPreview) {
if (this.state.currentAvatarUrl) { if (this.state.currentAvatarUrl) {
const url = MatrixClientPeg.get().mxcUrlToHttp(this.state.currentAvatarUrl); const url = mediaFromMxc(this.state.currentAvatarUrl).srcHttp;
preview = <img src={url} className="mx_EditCommunityPrototypeDialog_avatar" />; preview = <img src={url} className="mx_EditCommunityPrototypeDialog_avatar" />;
} else { } else {
preview = <div className="mx_EditCommunityPrototypeDialog_placeholderAvatar" /> preview = <div className="mx_EditCommunityPrototypeDialog_placeholderAvatar" />

View file

@ -20,6 +20,7 @@ import {MatrixClientPeg} from '../../../MatrixClientPeg';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromMxc} from "../../../customisations/Media";
const PHASE_START = 0; const PHASE_START = 0;
const PHASE_SHOW_SAS = 1; const PHASE_SHOW_SAS = 1;
@ -123,22 +124,21 @@ export default class IncomingSasDialog extends React.Component {
const Spinner = sdk.getComponent("views.elements.Spinner"); const Spinner = sdk.getComponent("views.elements.Spinner");
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar"); const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
const isSelf = this.props.verifier.userId == MatrixClientPeg.get().getUserId(); const isSelf = this.props.verifier.userId === MatrixClientPeg.get().getUserId();
let profile; let profile;
if (this.state.opponentProfile) { const oppProfile = this.state.opponentProfile;
if (oppProfile) {
const url = oppProfile.avatar_url
? mediaFromMxc(oppProfile.avatar_url).getSquareThumbnailHttp(Math.floor(48 * window.devicePixelRatio))
: null;
profile = <div className="mx_IncomingSasDialog_opponentProfile"> profile = <div className="mx_IncomingSasDialog_opponentProfile">
<BaseAvatar name={this.state.opponentProfile.displayname} <BaseAvatar name={oppProfile.displayname}
idName={this.props.verifier.userId} idName={this.props.verifier.userId}
url={MatrixClientPeg.get().mxcUrlToHttp( url={url}
this.state.opponentProfile.avatar_url,
Math.floor(48 * window.devicePixelRatio),
Math.floor(48 * window.devicePixelRatio),
'crop',
)}
width={48} height={48} resizeMethod='crop' width={48} height={48} resizeMethod='crop'
/> />
<h2>{this.state.opponentProfile.displayname}</h2> <h2>{oppProfile.displayname}</h2>
</div>; </div>;
} else if (this.state.opponentProfileError) { } else if (this.state.opponentProfileError) {
profile = <div> profile = <div>

View file

@ -23,6 +23,7 @@ import {MatrixClientPeg} from "../../../MatrixClientPeg";
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import { UserAddressType } from '../../../UserAddress.js'; import { UserAddressType } from '../../../UserAddress.js';
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromMxc} from "../../../customisations/Media";
@replaceableComponent("views.elements.AddressTile") @replaceableComponent("views.elements.AddressTile")
export default class AddressTile extends React.Component { export default class AddressTile extends React.Component {
@ -47,9 +48,7 @@ export default class AddressTile extends React.Component {
const isMatrixAddress = ['mx-user-id', 'mx-room-id'].includes(address.addressType); const isMatrixAddress = ['mx-user-id', 'mx-room-id'].includes(address.addressType);
if (isMatrixAddress && address.avatarMxc) { if (isMatrixAddress && address.avatarMxc) {
imgUrls.push(MatrixClientPeg.get().mxcUrlToHttp( imgUrls.push(mediaFromMxc(address.avatarMxc).getSquareThumbnailHttp(25));
address.avatarMxc, 25, 25, 'crop',
));
} else if (address.addressType === 'email') { } else if (address.addressType === 'email') {
imgUrls.push(require("../../../../res/img/icon-email-user.svg")); imgUrls.push(require("../../../../res/img/icon-email-user.svg"));
} }

View file

@ -20,6 +20,7 @@ import FlairStore from '../../../stores/FlairStore';
import dis from '../../../dispatcher/dispatcher'; import dis from '../../../dispatcher/dispatcher';
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromMxc} from "../../../customisations/Media";
class FlairAvatar extends React.Component { class FlairAvatar extends React.Component {
@ -39,8 +40,7 @@ class FlairAvatar extends React.Component {
} }
render() { render() {
const httpUrl = this.context.mxcUrlToHttp( const httpUrl = mediaFromMxc(this.props.groupProfile.avatarUrl).getSquareThumbnailHttp(16);
this.props.groupProfile.avatarUrl, 16, 16, 'scale', false);
const tooltip = this.props.groupProfile.name ? const tooltip = this.props.groupProfile.name ?
`${this.props.groupProfile.name} (${this.props.groupProfile.groupId})`: `${this.props.groupProfile.name} (${this.props.groupProfile.groupId})`:
this.props.groupProfile.groupId; this.props.groupProfile.groupId;

View file

@ -26,6 +26,7 @@ import FlairStore from "../../../stores/FlairStore";
import {getPrimaryPermalinkEntity, parseAppLocalLink} from "../../../utils/permalinks/Permalinks"; import {getPrimaryPermalinkEntity, parseAppLocalLink} from "../../../utils/permalinks/Permalinks";
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {Action} from "../../../dispatcher/actions"; import {Action} from "../../../dispatcher/actions";
import {mediaFromMxc} from "../../../customisations/Media";
import Tooltip from './Tooltip'; import Tooltip from './Tooltip';
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
@ -259,7 +260,7 @@ class Pill extends React.Component {
linkText = groupId; linkText = groupId;
if (this.props.shouldShowPillAvatar) { if (this.props.shouldShowPillAvatar) {
avatar = <BaseAvatar name={name || groupId} width={16} height={16} aria-hidden="true" avatar = <BaseAvatar name={name || groupId} width={16} height={16} aria-hidden="true"
url={avatarUrl ? cli.mxcUrlToHttp(avatarUrl, 16, 16) : null} />; url={avatarUrl ? mediaFromMxc(avatarUrl).getSquareThumbnailHttp(16) : null} />;
} }
pillClass = 'mx_GroupPill'; pillClass = 'mx_GroupPill';
} }

View file

@ -24,6 +24,7 @@ import AccessibleButton from "./AccessibleButton";
import {_t} from "../../../languageHandler"; import {_t} from "../../../languageHandler";
import {IdentityProviderBrand, IIdentityProvider, ISSOFlow} from "../../../Login"; import {IdentityProviderBrand, IIdentityProvider, ISSOFlow} from "../../../Login";
import AccessibleTooltipButton from "./AccessibleTooltipButton"; import AccessibleTooltipButton from "./AccessibleTooltipButton";
import {mediaFromMxc} from "../../../customisations/Media";
interface ISSOButtonProps extends Omit<IProps, "flow"> { interface ISSOButtonProps extends Omit<IProps, "flow"> {
idp: IIdentityProvider; idp: IIdentityProvider;
@ -72,7 +73,7 @@ const SSOButton: React.FC<ISSOButtonProps> = ({
brandClass = `mx_SSOButton_brand_${brandName}`; brandClass = `mx_SSOButton_brand_${brandName}`;
icon = <img src={brandIcon} height="24" width="24" alt={brandName} />; icon = <img src={brandIcon} height="24" width="24" alt={brandName} />;
} else if (typeof idp?.icon === "string" && idp.icon.startsWith("mxc://")) { } else if (typeof idp?.icon === "string" && idp.icon.startsWith("mxc://")) {
const src = matrixClient.mxcUrlToHttp(idp.icon, 24, 24, "crop", true); const src = mediaFromMxc(idp.icon).getSquareThumbnailHttp(24);
icon = <img src={src} height="24" width="24" alt={idp.name} />; icon = <img src={src} height="24" width="24" alt={idp.name} />;
} }

View file

@ -30,6 +30,7 @@ import GroupFilterOrderStore from '../../../stores/GroupFilterOrderStore';
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import AccessibleButton from "./AccessibleButton"; import AccessibleButton from "./AccessibleButton";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import {mediaFromMxc} from "../../../customisations/Media";
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
// A class for a child of GroupFilterPanel (possibly wrapped in a DNDTagTile) that represents // A class for a child of GroupFilterPanel (possibly wrapped in a DNDTagTile) that represents
@ -130,11 +131,11 @@ export default class TagTile extends React.Component {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar'); const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const profile = this.state.profile || {}; const profile = this.state.profile || {};
const name = profile.name || this.props.tag; const name = profile.name || this.props.tag;
const avatarHeight = 32; const avatarSize = 32;
const httpUrl = profile.avatarUrl ? this.context.mxcUrlToHttp( const httpUrl = profile.avatarUrl
profile.avatarUrl, avatarHeight, avatarHeight, "crop", ? mediaFromMxc(profile.avatarUrl).getSquareThumbnailHttp(avatarSize)
) : null; : null;
const isPrototype = SettingsStore.getValue("feature_communities_v2_prototypes"); const isPrototype = SettingsStore.getValue("feature_communities_v2_prototypes");
const className = classNames({ const className = classNames({
@ -180,8 +181,8 @@ export default class TagTile extends React.Component {
name={name} name={name}
idName={this.props.tag} idName={this.props.tag}
url={httpUrl} url={httpUrl}
width={avatarHeight} width={avatarSize}
height={avatarHeight} height={avatarSize}
/> />
{contextButton} {contextButton}
{badgeElement} {badgeElement}

View file

@ -27,6 +27,7 @@ import {ContextMenu, ContextMenuButton, toRightOf} from "../../structures/Contex
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {RovingTabIndexWrapper} from "../../../accessibility/RovingTabIndex"; import {RovingTabIndexWrapper} from "../../../accessibility/RovingTabIndex";
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromMxc} from "../../../customisations/Media";
// XXX this class copies a lot from RoomTile.js // XXX this class copies a lot from RoomTile.js
@replaceableComponent("views.groups.GroupInviteTile") @replaceableComponent("views.groups.GroupInviteTile")
@ -117,8 +118,9 @@ export default class GroupInviteTile extends React.Component {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar'); const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const groupName = this.props.group.name || this.props.group.groupId; const groupName = this.props.group.name || this.props.group.groupId;
const httpAvatarUrl = this.props.group.avatarUrl ? const httpAvatarUrl = this.props.group.avatarUrl
this.context.mxcUrlToHttp(this.props.group.avatarUrl, 24, 24) : null; ? mediaFromMxc(this.props.group.avatarUrl).getSquareThumbnailHttp(24)
: null;
const av = <BaseAvatar name={groupName} width={24} height={24} url={httpAvatarUrl} />; const av = <BaseAvatar name={groupName} width={24} height={24} url={httpAvatarUrl} />;

View file

@ -23,6 +23,7 @@ import dis from '../../../dispatcher/dispatcher';
import { GroupMemberType } from '../../../groups'; import { GroupMemberType } from '../../../groups';
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromMxc} from "../../../customisations/Media";
@replaceableComponent("views.groups.GroupMemberTile") @replaceableComponent("views.groups.GroupMemberTile")
export default class GroupMemberTile extends React.Component { export default class GroupMemberTile extends React.Component {
@ -46,10 +47,9 @@ export default class GroupMemberTile extends React.Component {
const EntityTile = sdk.getComponent('rooms.EntityTile'); const EntityTile = sdk.getComponent('rooms.EntityTile');
const name = this.props.member.displayname || this.props.member.userId; const name = this.props.member.displayname || this.props.member.userId;
const avatarUrl = this.context.mxcUrlToHttp( const avatarUrl = this.props.member.avatarUrl
this.props.member.avatarUrl, ? mediaFromMxc(this.props.member.avatarUrl).getSquareThumbnailHttp(36)
36, 36, 'crop', : null;
);
const av = ( const av = (
<BaseAvatar <BaseAvatar

View file

@ -25,6 +25,7 @@ import GroupStore from '../../../stores/GroupStore';
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromMxc} from "../../../customisations/Media";
@replaceableComponent("views.groups.GroupRoomInfo") @replaceableComponent("views.groups.GroupRoomInfo")
export default class GroupRoomInfo extends React.Component { export default class GroupRoomInfo extends React.Component {
@ -204,10 +205,8 @@ export default class GroupRoomInfo extends React.Component {
const avatarUrl = this.state.groupRoom.avatarUrl; const avatarUrl = this.state.groupRoom.avatarUrl;
let avatarElement; let avatarElement;
if (avatarUrl) { if (avatarUrl) {
const httpUrl = this.context.mxcUrlToHttp(avatarUrl, 800, 800); const httpUrl = mediaFromMxc(avatarUrl).getSquareThumbnailHttp(800);
avatarElement = (<div className="mx_MemberInfo_avatar"> avatarElement = <div className="mx_MemberInfo_avatar"><img src={httpUrl} /></div>;
<img src={httpUrl} />
</div>);
} }
const groupRoomName = this.state.groupRoom.displayname; const groupRoomName = this.state.groupRoom.displayname;

View file

@ -21,6 +21,7 @@ import dis from '../../../dispatcher/dispatcher';
import { GroupRoomType } from '../../../groups'; import { GroupRoomType } from '../../../groups';
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromMxc} from "../../../customisations/Media";
@replaceableComponent("views.groups.GroupRoomTile") @replaceableComponent("views.groups.GroupRoomTile")
class GroupRoomTile extends React.Component { class GroupRoomTile extends React.Component {
@ -42,10 +43,9 @@ class GroupRoomTile extends React.Component {
render() { render() {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar'); const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const avatarUrl = this.context.mxcUrlToHttp( const avatarUrl = this.props.groupRoom.avatarUrl
this.props.groupRoom.avatarUrl, ? mediaFromMxc(this.props.groupRoom.avatarUrl).getSquareThumbnailHttp(36)
36, 36, 'crop', : null;
);
const av = ( const av = (
<BaseAvatar name={this.props.groupRoom.displayname} <BaseAvatar name={this.props.groupRoom.displayname}

View file

@ -22,6 +22,7 @@ import dis from '../../../dispatcher/dispatcher';
import FlairStore from '../../../stores/FlairStore'; import FlairStore from '../../../stores/FlairStore';
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromMxc} from "../../../customisations/Media";
function nop() {} function nop() {}
@ -73,8 +74,9 @@ class GroupTile extends React.Component {
const descElement = this.props.showDescription ? const descElement = this.props.showDescription ?
<div className="mx_GroupTile_desc">{ profile.shortDescription }</div> : <div className="mx_GroupTile_desc">{ profile.shortDescription }</div> :
<div />; <div />;
const httpUrl = profile.avatarUrl ? this.context.mxcUrlToHttp( const httpUrl = profile.avatarUrl
profile.avatarUrl, avatarHeight, avatarHeight, "crop") : null; ? mediaFromMxc(profile.avatarUrl).getSquareThumbnailHttp(avatarHeight)
: null;
let avatarElement = ( let avatarElement = (
<div className="mx_GroupTile_avatar"> <div className="mx_GroupTile_avatar">

View file

@ -22,6 +22,7 @@ import { decryptFile } from '../../../utils/DecryptFile';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import InlineSpinner from '../elements/InlineSpinner'; import InlineSpinner from '../elements/InlineSpinner';
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromContent} from "../../../customisations/Media";
@replaceableComponent("views.messages.MAudioBody") @replaceableComponent("views.messages.MAudioBody")
export default class MAudioBody extends React.Component { export default class MAudioBody extends React.Component {
@ -41,11 +42,11 @@ export default class MAudioBody extends React.Component {
} }
_getContentUrl() { _getContentUrl() {
const content = this.props.mxEvent.getContent(); const media = mediaFromContent(this.props.mxEvent.getContent());
if (content.file !== undefined) { if (media.isEncrypted) {
return this.state.decryptedUrl; return this.state.decryptedUrl;
} else { } else {
return MatrixClientPeg.get().mxcUrlToHttp(content.url); return media.srcHttp;
} }
} }

View file

@ -27,6 +27,7 @@ import request from 'browser-request';
import Modal from '../../../Modal'; import Modal from '../../../Modal';
import AccessibleButton from "../elements/AccessibleButton"; import AccessibleButton from "../elements/AccessibleButton";
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromContent} from "../../../customisations/Media";
// A cached tinted copy of require("../../../../res/img/download.svg") // A cached tinted copy of require("../../../../res/img/download.svg")
@ -178,8 +179,8 @@ export default class MFileBody extends React.Component {
} }
_getContentUrl() { _getContentUrl() {
const content = this.props.mxEvent.getContent(); const media = mediaFromContent(this.props.mxEvent.getContent());
return MatrixClientPeg.get().mxcUrlToHttp(content.url); return media.srcHttp;
} }
componentDidMount() { componentDidMount() {

View file

@ -28,6 +28,7 @@ import SettingsStore from "../../../settings/SettingsStore";
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import InlineSpinner from '../elements/InlineSpinner'; import InlineSpinner from '../elements/InlineSpinner';
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromContent} from "../../../customisations/Media";
@replaceableComponent("views.messages.MImageBody") @replaceableComponent("views.messages.MImageBody")
export default class MImageBody extends React.Component { export default class MImageBody extends React.Component {
@ -167,16 +168,16 @@ export default class MImageBody extends React.Component {
} }
_getContentUrl() { _getContentUrl() {
const content = this.props.mxEvent.getContent(); const media = mediaFromContent(this.props.mxEvent.getContent());
if (content.file !== undefined) { if (media.isEncrypted) {
return this.state.decryptedUrl; return this.state.decryptedUrl;
} else { } else {
return this.context.mxcUrlToHttp(content.url); return media.srcHttp;
} }
} }
_getThumbUrl() { _getThumbUrl() {
// FIXME: the dharma skin lets images grow as wide as you like, rather than capped to 800x600. // FIXME: we let images grow as wide as you like, rather than capped to 800x600.
// So either we need to support custom timeline widths here, or reimpose the cap, otherwise the // So either we need to support custom timeline widths here, or reimpose the cap, otherwise the
// thumbnail resolution will be unnecessarily reduced. // thumbnail resolution will be unnecessarily reduced.
// custom timeline widths seems preferable. // custom timeline widths seems preferable.
@ -185,21 +186,19 @@ export default class MImageBody extends React.Component {
const thumbHeight = Math.round(600 * pixelRatio); const thumbHeight = Math.round(600 * pixelRatio);
const content = this.props.mxEvent.getContent(); const content = this.props.mxEvent.getContent();
if (content.file !== undefined) { const media = mediaFromContent(content);
if (media.isEncrypted) {
// Don't use the thumbnail for clients wishing to autoplay gifs. // Don't use the thumbnail for clients wishing to autoplay gifs.
if (this.state.decryptedThumbnailUrl) { if (this.state.decryptedThumbnailUrl) {
return this.state.decryptedThumbnailUrl; return this.state.decryptedThumbnailUrl;
} }
return this.state.decryptedUrl; return this.state.decryptedUrl;
} else if (content.info && content.info.mimetype === "image/svg+xml" && content.info.thumbnail_url) { } else if (content.info && content.info.mimetype === "image/svg+xml" && media.hasThumbnail) {
// special case to return clientside sender-generated thumbnails for SVGs, if any, // special case to return clientside sender-generated thumbnails for SVGs, if any,
// given we deliberately don't thumbnail them serverside to prevent // given we deliberately don't thumbnail them serverside to prevent
// billion lol attacks and similar // billion lol attacks and similar
return this.context.mxcUrlToHttp( return media.getThumbnailHttp(thumbWidth, thumbHeight, 'scale');
content.info.thumbnail_url,
thumbWidth,
thumbHeight,
);
} else { } else {
// we try to download the correct resolution // we try to download the correct resolution
// for hi-res images (like retina screenshots). // for hi-res images (like retina screenshots).
@ -218,7 +217,7 @@ export default class MImageBody extends React.Component {
pixelRatio === 1.0 || pixelRatio === 1.0 ||
(!info || !info.w || !info.h || !info.size) (!info || !info.w || !info.h || !info.size)
) { ) {
return this.context.mxcUrlToHttp(content.url, thumbWidth, thumbHeight); return media.getThumbnailOfSourceHttp(thumbWidth, thumbHeight);
} else { } else {
// we should only request thumbnails if the image is bigger than 800x600 // we should only request thumbnails if the image is bigger than 800x600
// (or 1600x1200 on retina) otherwise the image in the timeline will just // (or 1600x1200 on retina) otherwise the image in the timeline will just
@ -233,24 +232,17 @@ export default class MImageBody extends React.Component {
info.w > thumbWidth || info.w > thumbWidth ||
info.h > thumbHeight info.h > thumbHeight
); );
const isLargeFileSize = info.size > 1*1024*1024; const isLargeFileSize = info.size > 1*1024*1024; // 1mb
if (isLargeFileSize && isLargerThanThumbnail) { if (isLargeFileSize && isLargerThanThumbnail) {
// image is too large physically and bytewise to clutter our timeline so // image is too large physically and bytewise to clutter our timeline so
// we ask for a thumbnail, despite knowing that it will be max 800x600 // we ask for a thumbnail, despite knowing that it will be max 800x600
// despite us being retina (as synapse doesn't do 1600x1200 thumbs yet). // despite us being retina (as synapse doesn't do 1600x1200 thumbs yet).
return this.context.mxcUrlToHttp( return media.getThumbnailOfSourceHttp(thumbWidth, thumbHeight);
content.url,
thumbWidth,
thumbHeight,
);
} else { } else {
// download the original image otherwise, so we can scale it client side // download the original image otherwise, so we can scale it client side
// to take pixelRatio into account. // to take pixelRatio into account.
// ( no width/height means we want the original image) return media.srcHttp;
return this.context.mxcUrlToHttp(
content.url,
);
} }
} }
} }

View file

@ -23,6 +23,7 @@ import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import InlineSpinner from '../elements/InlineSpinner'; import InlineSpinner from '../elements/InlineSpinner';
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromContent} from "../../../customisations/Media";
interface IProps { interface IProps {
/* the MatrixEvent to show */ /* the MatrixEvent to show */
@ -76,11 +77,11 @@ export default class MVideoBody extends React.PureComponent<IProps, IState> {
} }
private getContentUrl(): string|null { private getContentUrl(): string|null {
const content = this.props.mxEvent.getContent(); const media = mediaFromContent(this.props.mxEvent.getContent());
if (content.file !== undefined) { if (media.isEncrypted) {
return this.state.decryptedUrl; return this.state.decryptedUrl;
} else { } else {
return MatrixClientPeg.get().mxcUrlToHttp(content.url); return media.srcHttp;
} }
} }
@ -91,10 +92,11 @@ export default class MVideoBody extends React.PureComponent<IProps, IState> {
private getThumbUrl(): string|null { private getThumbUrl(): string|null {
const content = this.props.mxEvent.getContent(); const content = this.props.mxEvent.getContent();
if (content.file !== undefined) { const media = mediaFromContent(content);
if (media.isEncrypted) {
return this.state.decryptedThumbnailUrl; return this.state.decryptedThumbnailUrl;
} else if (content.info && content.info.thumbnail_url) { } else if (media.hasThumbnail) {
return MatrixClientPeg.get().mxcUrlToHttp(content.info.thumbnail_url); return media.thumbnailHttp;
} else { } else {
return null; return null;
} }

View file

@ -24,6 +24,7 @@ import * as sdk from '../../../index';
import Modal from '../../../Modal'; import Modal from '../../../Modal';
import AccessibleButton from '../elements/AccessibleButton'; import AccessibleButton from '../elements/AccessibleButton';
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromMxc} from "../../../customisations/Media";
@replaceableComponent("views.messages.RoomAvatarEvent") @replaceableComponent("views.messages.RoomAvatarEvent")
export default class RoomAvatarEvent extends React.Component { export default class RoomAvatarEvent extends React.Component {
@ -35,7 +36,7 @@ export default class RoomAvatarEvent extends React.Component {
onAvatarClick = () => { onAvatarClick = () => {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const ev = this.props.mxEvent; const ev = this.props.mxEvent;
const httpUrl = cli.mxcUrlToHttp(ev.getContent().url); const httpUrl = mediaFromMxc(ev.getContent().url).srcHttp;
const room = cli.getRoom(this.props.mxEvent.getRoomId()); const room = cli.getRoom(this.props.mxEvent.getRoomId());
const text = _t('%(senderDisplayName)s changed the avatar for %(roomName)s', { const text = _t('%(senderDisplayName)s changed the avatar for %(roomName)s', {

View file

@ -63,6 +63,7 @@ import { EventType } from "matrix-js-sdk/src/@types/event";
import { SetRightPanelPhasePayload } from "../../../dispatcher/payloads/SetRightPanelPhasePayload"; import { SetRightPanelPhasePayload } from "../../../dispatcher/payloads/SetRightPanelPhasePayload";
import RoomAvatar from "../avatars/RoomAvatar"; import RoomAvatar from "../avatars/RoomAvatar";
import RoomName from "../elements/RoomName"; import RoomName from "../elements/RoomName";
import {mediaFromMxc} from "../../../customisations/Media";
interface IDevice { interface IDevice {
deviceId: string; deviceId: string;
@ -1408,7 +1409,7 @@ const UserInfoHeader: React.FC<{
const avatarUrl = member.getMxcAvatarUrl ? member.getMxcAvatarUrl() : member.avatarUrl; const avatarUrl = member.getMxcAvatarUrl ? member.getMxcAvatarUrl() : member.avatarUrl;
if (!avatarUrl) return; if (!avatarUrl) return;
const httpUrl = cli.mxcUrlToHttp(avatarUrl); const httpUrl = mediaFromMxc(avatarUrl).srcHttp;
const params = { const params = {
src: httpUrl, src: httpUrl,
name: member.name, name: member.name,

View file

@ -21,6 +21,7 @@ import {MatrixClientPeg} from "../../../MatrixClientPeg";
import Field from "../elements/Field"; import Field from "../elements/Field";
import * as sdk from "../../../index"; import * as sdk from "../../../index";
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromMxc} from "../../../customisations/Media";
// TODO: Merge with ProfileSettings? // TODO: Merge with ProfileSettings?
@replaceableComponent("views.room_settings.RoomProfileSettings") @replaceableComponent("views.room_settings.RoomProfileSettings")
@ -38,7 +39,7 @@ export default class RoomProfileSettings extends React.Component {
const avatarEvent = room.currentState.getStateEvents("m.room.avatar", ""); const avatarEvent = room.currentState.getStateEvents("m.room.avatar", "");
let avatarUrl = avatarEvent && avatarEvent.getContent() ? avatarEvent.getContent()["url"] : null; let avatarUrl = avatarEvent && avatarEvent.getContent() ? avatarEvent.getContent()["url"] : null;
if (avatarUrl) avatarUrl = client.mxcUrlToHttp(avatarUrl, 96, 96, 'crop', false); if (avatarUrl) avatarUrl = mediaFromMxc(avatarUrl).getSquareThumbnailHttp(96);
const topicEvent = room.currentState.getStateEvents("m.room.topic", ""); const topicEvent = room.currentState.getStateEvents("m.room.topic", "");
const topic = topicEvent && topicEvent.getContent() ? topicEvent.getContent()['topic'] : ''; const topic = topicEvent && topicEvent.getContent() ? topicEvent.getContent()['topic'] : '';
@ -112,7 +113,7 @@ export default class RoomProfileSettings extends React.Component {
if (this.state.avatarFile) { if (this.state.avatarFile) {
const uri = await client.uploadContent(this.state.avatarFile); const uri = await client.uploadContent(this.state.avatarFile);
await client.sendStateEvent(this.props.roomId, 'm.room.avatar', {url: uri}, ''); await client.sendStateEvent(this.props.roomId, 'm.room.avatar', {url: uri}, '');
newState.avatarUrl = client.mxcUrlToHttp(uri, 96, 96, 'crop', false); newState.avatarUrl = mediaFromMxc(uri).getSquareThumbnailHttp(96);
newState.originalAvatarUrl = newState.avatarUrl; newState.originalAvatarUrl = newState.avatarUrl;
newState.avatarFile = null; newState.avatarFile = null;
} else if (this.state.originalAvatarUrl !== this.state.avatarUrl) { } else if (this.state.originalAvatarUrl !== this.state.avatarUrl) {

View file

@ -26,6 +26,7 @@ import Modal from "../../../Modal";
import * as ImageUtils from "../../../ImageUtils"; import * as ImageUtils from "../../../ImageUtils";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromMxc} from "../../../customisations/Media";
@replaceableComponent("views.rooms.LinkPreviewWidget") @replaceableComponent("views.rooms.LinkPreviewWidget")
export default class LinkPreviewWidget extends React.Component { export default class LinkPreviewWidget extends React.Component {
@ -83,7 +84,7 @@ export default class LinkPreviewWidget extends React.Component {
let src = p["og:image"]; let src = p["og:image"];
if (src && src.startsWith("mxc://")) { if (src && src.startsWith("mxc://")) {
src = MatrixClientPeg.get().mxcUrlToHttp(src); src = mediaFromMxc(src).srcHttp;
} }
const params = { const params = {
@ -109,9 +110,11 @@ export default class LinkPreviewWidget extends React.Component {
if (!SettingsStore.getValue("showImages")) { if (!SettingsStore.getValue("showImages")) {
image = null; // Don't render a button to show the image, just hide it outright image = null; // Don't render a button to show the image, just hide it outright
} }
const imageMaxWidth = 100; const imageMaxHeight = 100; const imageMaxWidth = 100;
const imageMaxHeight = 100;
if (image && image.startsWith("mxc://")) { if (image && image.startsWith("mxc://")) {
image = MatrixClientPeg.get().mxcUrlToHttp(image, imageMaxWidth, imageMaxHeight); // We deliberately don't want a square here, so use the source HTTP thumbnail function
image = mediaFromMxc(image).getThumbnailOfSourceHttp(imageMaxWidth, imageMaxHeight, 'scale');
} }
let thumbHeight = imageMaxHeight; let thumbHeight = imageMaxHeight;

View file

@ -21,6 +21,7 @@ import * as sdk from '../../../index';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import Spinner from '../elements/Spinner'; import Spinner from '../elements/Spinner';
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromMxc} from "../../../customisations/Media";
@replaceableComponent("views.settings.ChangeAvatar") @replaceableComponent("views.settings.ChangeAvatar")
export default class ChangeAvatar extends React.Component { export default class ChangeAvatar extends React.Component {
@ -117,7 +118,7 @@ export default class ChangeAvatar extends React.Component {
httpPromise.then(function() { httpPromise.then(function() {
self.setState({ self.setState({
phase: ChangeAvatar.Phases.Display, phase: ChangeAvatar.Phases.Display,
avatarUrl: MatrixClientPeg.get().mxcUrlToHttp(newUrl), avatarUrl: mediaFromMxc(newUrl).srcHttp,
}); });
}, function(error) { }, function(error) {
self.setState({ self.setState({

View file

@ -24,6 +24,7 @@ import {OwnProfileStore} from "../../../stores/OwnProfileStore";
import Modal from "../../../Modal"; import Modal from "../../../Modal";
import ErrorDialog from "../dialogs/ErrorDialog"; import ErrorDialog from "../dialogs/ErrorDialog";
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromMxc} from "../../../customisations/Media";
@replaceableComponent("views.settings.ProfileSettings") @replaceableComponent("views.settings.ProfileSettings")
export default class ProfileSettings extends React.Component { export default class ProfileSettings extends React.Component {
@ -32,7 +33,7 @@ export default class ProfileSettings extends React.Component {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
let avatarUrl = OwnProfileStore.instance.avatarMxc; let avatarUrl = OwnProfileStore.instance.avatarMxc;
if (avatarUrl) avatarUrl = client.mxcUrlToHttp(avatarUrl, 96, 96, 'crop', false); if (avatarUrl) avatarUrl = mediaFromMxc(avatarUrl).getSquareThumbnailHttp(96);
this.state = { this.state = {
userId: client.getUserId(), userId: client.getUserId(),
originalDisplayName: OwnProfileStore.instance.displayName, originalDisplayName: OwnProfileStore.instance.displayName,
@ -97,7 +98,7 @@ export default class ProfileSettings extends React.Component {
` (${this.state.avatarFile.size}) bytes`); ` (${this.state.avatarFile.size}) bytes`);
const uri = await client.uploadContent(this.state.avatarFile); const uri = await client.uploadContent(this.state.avatarFile);
await client.setAvatarUrl(uri); await client.setAvatarUrl(uri);
newState.avatarUrl = client.mxcUrlToHttp(uri, 96, 96, 'crop', false); newState.avatarUrl = mediaFromMxc(uri).getSquareThumbnailHttp(96);
newState.originalAvatarUrl = newState.avatarUrl; newState.originalAvatarUrl = newState.avatarUrl;
newState.avatarFile = null; newState.avatarFile = null;
} else if (this.state.originalAvatarUrl !== this.state.avatarUrl) { } else if (this.state.originalAvatarUrl !== this.state.avatarUrl) {

View file

@ -16,6 +16,7 @@
import {MatrixClientPeg} from "../MatrixClientPeg"; import {MatrixClientPeg} from "../MatrixClientPeg";
import {IMediaEventContent, IPreparedMedia, prepEventContentAsMedia} from "./models/IMediaEventContent"; import {IMediaEventContent, IPreparedMedia, prepEventContentAsMedia} from "./models/IMediaEventContent";
import {ResizeMode} from "./models/ResizeMode";
// Populate this class with the details of your customisations when copying it. // Populate this class with the details of your customisations when copying it.
@ -33,6 +34,13 @@ export class Media {
constructor(private prepared: IPreparedMedia) { constructor(private prepared: IPreparedMedia) {
} }
/**
* True if the media appears to be encrypted. Actual file contents may vary.
*/
public get isEncrypted(): boolean {
return !!this.prepared.file;
}
/** /**
* The MXC URI of the source media. * The MXC URI of the source media.
*/ */
@ -62,6 +70,15 @@ export class Media {
return MatrixClientPeg.get().mxcUrlToHttp(this.srcMxc); return MatrixClientPeg.get().mxcUrlToHttp(this.srcMxc);
} }
/**
* The HTTP URL for the thumbnail media (without any specified width, height, etc). Null/undefined
* if no thumbnail media recorded.
*/
public get thumbnailHttp(): string | undefined | null {
if (!this.hasThumbnail) return null;
return MatrixClientPeg.get().mxcUrlToHttp(this.thumbnailMxc);
}
/** /**
* Gets the HTTP URL for the thumbnail media with the requested characteristics, if a thumbnail * Gets the HTTP URL for the thumbnail media with the requested characteristics, if a thumbnail
* is recorded for this media. Returns null/undefined otherwise. * is recorded for this media. Returns null/undefined otherwise.
@ -70,7 +87,7 @@ export class Media {
* @param {"scale"|"crop"} mode The desired thumbnailing mode. Defaults to scale. * @param {"scale"|"crop"} mode The desired thumbnailing mode. Defaults to scale.
* @returns {string} The HTTP URL which points to the thumbnail. * @returns {string} The HTTP URL which points to the thumbnail.
*/ */
public getThumbnailHttp(width: number, height: number, mode: 'scale' | 'crop' = "scale"): string | null | undefined { public getThumbnailHttp(width: number, height: number, mode: ResizeMode = "scale"): string | null | undefined {
if (!this.hasThumbnail) return null; if (!this.hasThumbnail) return null;
return MatrixClientPeg.get().mxcUrlToHttp(this.thumbnailMxc, width, height, mode); return MatrixClientPeg.get().mxcUrlToHttp(this.thumbnailMxc, width, height, mode);
} }
@ -82,10 +99,23 @@ export class Media {
* @param {"scale"|"crop"} mode The desired thumbnailing mode. Defaults to scale. * @param {"scale"|"crop"} mode The desired thumbnailing mode. Defaults to scale.
* @returns {string} The HTTP URL which points to the thumbnail. * @returns {string} The HTTP URL which points to the thumbnail.
*/ */
public getThumbnailOfSourceHttp(width: number, height: number, mode: 'scale' | 'crop' = "scale"): string { public getThumbnailOfSourceHttp(width: number, height: number, mode: ResizeMode = "scale"): string {
return MatrixClientPeg.get().mxcUrlToHttp(this.srcMxc, width, height, mode); return MatrixClientPeg.get().mxcUrlToHttp(this.srcMxc, width, height, mode);
} }
/**
* Creates a square thumbnail of the media. If the media has a thumbnail recorded, that MXC will
* be used, otherwise the source media will be used.
* @param {number} dim The desired width and height.
* @returns {string} An HTTP URL for the thumbnail.
*/
public getSquareThumbnailHttp(dim: number): string {
if (this.hasThumbnail) {
return this.getThumbnailHttp(dim, dim, 'crop');
}
return this.getThumbnailOfSourceHttp(dim, dim, 'crop');
}
/** /**
* Downloads the source media. * Downloads the source media.
* @returns {Promise<Response>} Resolves to the server's response for chaining. * @returns {Promise<Response>} Resolves to the server's response for chaining.
@ -102,7 +132,7 @@ export class Media {
* @param {"scale"|"crop"} mode The desired thumbnailing mode. Defaults to scale. * @param {"scale"|"crop"} mode The desired thumbnailing mode. Defaults to scale.
* @returns {Promise<Response>} Resolves to the server's response for chaining. * @returns {Promise<Response>} Resolves to the server's response for chaining.
*/ */
public downloadThumbnail(width: number, height: number, mode: 'scale' | 'crop' = "scale"): Promise<Response> { public downloadThumbnail(width: number, height: number, mode: ResizeMode = "scale"): Promise<Response> {
if (!this.hasThumbnail) throw new Error("Cannot download non-existent thumbnail"); if (!this.hasThumbnail) throw new Error("Cannot download non-existent thumbnail");
return fetch(this.getThumbnailHttp(width, height, mode)); return fetch(this.getThumbnailHttp(width, height, mode));
} }
@ -114,7 +144,7 @@ export class Media {
* @param {"scale"|"crop"} mode The desired thumbnailing mode. Defaults to scale. * @param {"scale"|"crop"} mode The desired thumbnailing mode. Defaults to scale.
* @returns {Promise<Response>} Resolves to the server's response for chaining. * @returns {Promise<Response>} Resolves to the server's response for chaining.
*/ */
public downloadThumbnailOfSource(width: number, height: number, mode: 'scale' | 'crop' = "scale"): Promise<Response> { public downloadThumbnailOfSource(width: number, height: number, mode: ResizeMode = "scale"): Promise<Response> {
return fetch(this.getThumbnailOfSourceHttp(width, height, mode)); return fetch(this.getThumbnailOfSourceHttp(width, height, mode));
} }
} }

View file

@ -0,0 +1,17 @@
/*
* 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.
*/
export type ResizeMode = "scale" | "crop";

View file

@ -22,6 +22,7 @@ import { User } from "matrix-js-sdk/src/models/user";
import { throttle } from "lodash"; import { throttle } from "lodash";
import { MatrixClientPeg } from "../MatrixClientPeg"; import { MatrixClientPeg } from "../MatrixClientPeg";
import { _t } from "../languageHandler"; import { _t } from "../languageHandler";
import {mediaFromMxc} from "../customisations/Media";
interface IState { interface IState {
displayName?: string; displayName?: string;
@ -72,8 +73,12 @@ export class OwnProfileStore extends AsyncStoreWithClient<IState> {
*/ */
public getHttpAvatarUrl(size = 0): string { public getHttpAvatarUrl(size = 0): string {
if (!this.avatarMxc) return null; if (!this.avatarMxc) return null;
const adjustedSize = size > 1 ? size : undefined; // don't let negatives or zero through const media = mediaFromMxc(this.avatarMxc);
return this.matrixClient.mxcUrlToHttp(this.avatarMxc, adjustedSize, adjustedSize); if (!size || size <= 0) {
return media.srcHttp;
} else {
return media.getSquareThumbnailHttp(size);
}
} }
protected async onNotReady() { protected async onNotReady() {