diff --git a/package.json b/package.json index 6b369e9c27..60840fbe92 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "cheerio": "^1.0.0-rc.9", "classnames": "^2.2.6", "commonmark": "^0.29.3", + "context-filter-polyfill": "^0.2.4", "counterpart": "^0.18.6", "diff-dom": "^4.2.2", "diff-match-patch": "^1.0.5", diff --git a/res/css/structures/_LeftPanel.scss b/res/css/structures/_LeftPanel.scss index d9db8b4fed..f5a27a2568 100644 --- a/res/css/structures/_LeftPanel.scss +++ b/res/css/structures/_LeftPanel.scss @@ -26,6 +26,7 @@ $roomListCollapsedWidth: 68px; // Create a row-based flexbox for the GroupFilterPanel and the room list display: flex; contain: content; + position: relative; .mx_LeftPanel_GroupFilterPanelContainer { flex-grow: 0; diff --git a/src/components/structures/BackdropPanel.tsx b/src/components/structures/BackdropPanel.tsx index daaae418c4..c20845c4ae 100644 --- a/src/components/structures/BackdropPanel.tsx +++ b/src/components/structures/BackdropPanel.tsx @@ -1,9 +1,10 @@ import React, { createRef } from "react"; +import "context-filter-polyfill"; interface IProps { width?: number; height?: number; - backgroundImage?: ImageBitmap; + backgroundImage?: CanvasImageSource; blur?: string; } @@ -31,20 +32,28 @@ export default class BackdropPanel extends React.PureComponent { this.canvasRef.current.width = width; this.canvasRef.current.height = height; - const destinationX = width - backgroundImage.width; - const destinationY = height - backgroundImage.height; + const imageWidth = (backgroundImage as ImageBitmap).width + || (backgroundImage as HTMLImageElement).naturalWidth; + const imageHeight = (backgroundImage as ImageBitmap).height + || (backgroundImage as HTMLImageElement).naturalHeight; + + const destinationX = width - imageWidth; + const destinationY = height - imageHeight; this.ctx.filter = `blur(${this.props.blur})`; this.ctx.drawImage( backgroundImage, Math.min(destinationX, 0), Math.min(destinationY, 0), - Math.max(width, backgroundImage.width), - Math.max(height, backgroundImage.height), + Math.max(width, imageWidth), + Math.max(height, imageHeight), ); } public render() { - return ; + return ; } } diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index f71817480e..4b4d593fce 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -47,7 +47,7 @@ import BackdropPanel from "./BackdropPanel"; interface IProps { isMinimized: boolean; resizeNotifier: ResizeNotifier; - backgroundImage?: ImageBitmap; + backgroundImage?: CanvasImageSource; } interface IState { diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index da6163e6a9..371de3679b 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -124,7 +124,7 @@ interface IState { usageLimitEventTs?: number; useCompactLayout: boolean; activeCalls: Array; - backgroundImage?: ImageBitmap; + backgroundImage?: CanvasImageSource; } /** diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx index 7e8ba2bd3b..eeddc23588 100644 --- a/src/components/views/spaces/SpacePanel.tsx +++ b/src/components/views/spaces/SpacePanel.tsx @@ -181,7 +181,7 @@ const InnerSpacePanel = React.memo(({ children, isPanelCo }); interface IProps { - backgroundImage?: ImageBitmap; + backgroundImage?: CanvasImageSource; } const SpacePanel = (props: IProps) => { diff --git a/src/stores/OwnProfileStore.ts b/src/stores/OwnProfileStore.ts index aa5dd822ad..3bbadad567 100644 --- a/src/stores/OwnProfileStore.ts +++ b/src/stores/OwnProfileStore.ts @@ -19,11 +19,12 @@ import { AsyncStoreWithClient } from "./AsyncStoreWithClient"; import defaultDispatcher from "../dispatcher/dispatcher"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { User } from "matrix-js-sdk/src/models/user"; -import { throttle } from "lodash"; +import { memoize, throttle } from "lodash"; import { MatrixClientPeg } from "../MatrixClientPeg"; import { _t } from "../languageHandler"; import {mediaFromMxc} from "../customisations/Media"; import SettingsStore from "../settings/SettingsStore"; +import { getDrawable } from "../utils/drawable"; interface IState { displayName?: string; @@ -138,7 +139,7 @@ export class OwnProfileStore extends AsyncStoreWithClient { await this.updateState({displayName: profileInfo.displayname, avatarUrl: profileInfo.avatar_url}); }; - public async getAvatarBitmap(avatarSize = 32): Promise { + public async getAvatarBitmap(avatarSize = 32): Promise { let avatarUrl = this.getHttpAvatarUrl(avatarSize); const settingBgMxc = SettingsStore.getValue("RoomList.backgroundImage"); if (settingBgMxc) { @@ -146,14 +147,14 @@ export class OwnProfileStore extends AsyncStoreWithClient { } if (avatarUrl) { - const response = await fetch(avatarUrl); - const blob = await response.blob(); - return await createImageBitmap(blob); + return await this.buildBitmap(avatarUrl); } else { return null; } } + private buildBitmap = memoize(getDrawable); + private onStateEvents = throttle(async (ev: MatrixEvent) => { const myUserId = MatrixClientPeg.get().getUserId(); if (ev.getType() === 'm.room.member' && ev.getSender() === myUserId && ev.getStateKey() === myUserId) { diff --git a/src/utils/drawable.ts b/src/utils/drawable.ts new file mode 100644 index 0000000000..b6e6af2e32 --- /dev/null +++ b/src/utils/drawable.ts @@ -0,0 +1,24 @@ +/** + * Fetch an image using the best available method based on browser compatibility + * @param url the URL of the image to fetch + * @returns a canvas drawable object + */ +export async function getDrawable(url: string): Promise { + if ('createImageBitmap' in window) { + const response = await fetch(url); + const blob = await response.blob(); + return await createImageBitmap(blob); + } else { + return new Promise((resolve, reject) => { + const img = document.createElement("img"); + img.crossOrigin = "anonymous"; + img.onload = function() { + resolve(img); + } + img.onerror = function(e) { + reject(e); + } + img.src = url; + }); + } +} diff --git a/yarn.lock b/yarn.lock index 32ca30a996..4584604cef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2652,6 +2652,11 @@ content-type@^1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== +context-filter-polyfill@^0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/context-filter-polyfill/-/context-filter-polyfill-0.2.4.tgz#ecf88d3197e7c3a47e9a7ae2d5167b703945a5d4" + integrity sha512-LDZ3WiTzo6kIeJM7j8kPSgZf+gbD1cV1GaLyYO8RWvAg25cO3zUo3d2KizO0w9hAezNwz7tTbuWKpPdvLWzKqQ== + convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442"