Merge pull request #6659 from matrix-org/palid/fix/backdrop-blur
Optimize background image from avatar on left panel
This commit is contained in:
commit
6205cbbdec
13 changed files with 85 additions and 257 deletions
|
@ -14,153 +14,31 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { createRef } from "react";
|
||||
import "context-filter-polyfill";
|
||||
|
||||
import UIStore from "../../stores/UIStore";
|
||||
import React, { CSSProperties } from "react";
|
||||
|
||||
interface IProps {
|
||||
backgroundImage?: CanvasImageSource;
|
||||
backgroundImage?: string;
|
||||
blurMultiplier?: number;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
// Left Panel image
|
||||
lpImage?: string;
|
||||
// Left-left panel image
|
||||
llpImage?: string;
|
||||
}
|
||||
export const BackdropPanel: React.FC<IProps> = ({ backgroundImage, blurMultiplier }) => {
|
||||
if (!backgroundImage) return null;
|
||||
|
||||
export default class BackdropPanel extends React.PureComponent<IProps, IState> {
|
||||
private leftLeftPanelRef = createRef<HTMLCanvasElement>();
|
||||
private leftPanelRef = createRef<HTMLCanvasElement>();
|
||||
|
||||
private sizes = {
|
||||
leftLeftPanelWidth: 0,
|
||||
leftPanelWidth: 0,
|
||||
height: 0,
|
||||
};
|
||||
private style = getComputedStyle(document.documentElement);
|
||||
|
||||
public state: IState = {};
|
||||
|
||||
public componentDidMount() {
|
||||
UIStore.instance.on("SpacePanel", this.onResize);
|
||||
UIStore.instance.on("GroupFilterPanelContainer", this.onResize);
|
||||
this.onResize();
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
UIStore.instance.off("SpacePanel", this.onResize);
|
||||
UIStore.instance.on("GroupFilterPanelContainer", this.onResize);
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps: IProps) {
|
||||
if (prevProps.backgroundImage !== this.props.backgroundImage) {
|
||||
this.setState({});
|
||||
this.onResize();
|
||||
const styles: CSSProperties = {};
|
||||
if (blurMultiplier) {
|
||||
const rootStyle = getComputedStyle(document.documentElement);
|
||||
const blurValue = rootStyle.getPropertyValue('--lp-background-blur');
|
||||
const pixelsValue = blurValue.replace('px', '');
|
||||
const parsed = parseInt(pixelsValue, 10);
|
||||
if (!isNaN(parsed)) {
|
||||
styles.filter = `blur(${parsed * blurMultiplier}px)`;
|
||||
}
|
||||
}
|
||||
|
||||
private onResize = () => {
|
||||
if (this.props.backgroundImage) {
|
||||
const groupFilterPanelDimensions = UIStore.instance.getElementDimensions("GroupFilterPanelContainer");
|
||||
const spacePanelDimensions = UIStore.instance.getElementDimensions("SpacePanel");
|
||||
const roomListDimensions = UIStore.instance.getElementDimensions("LeftPanel");
|
||||
this.sizes = {
|
||||
leftLeftPanelWidth: spacePanelDimensions?.width ?? groupFilterPanelDimensions?.width ?? 0,
|
||||
leftPanelWidth: roomListDimensions?.width ?? 0,
|
||||
height: UIStore.instance.windowHeight,
|
||||
};
|
||||
this.refreshBackdropImage();
|
||||
}
|
||||
};
|
||||
|
||||
private refreshBackdropImage = (): void => {
|
||||
const leftLeftPanelContext = this.leftLeftPanelRef.current.getContext("2d");
|
||||
const leftPanelContext = this.leftPanelRef.current.getContext("2d");
|
||||
const { leftLeftPanelWidth, leftPanelWidth, height } = this.sizes;
|
||||
const width = leftLeftPanelWidth + leftPanelWidth;
|
||||
const { backgroundImage } = this.props;
|
||||
|
||||
const imageWidth = (backgroundImage as ImageBitmap).width;
|
||||
const imageHeight = (backgroundImage as ImageBitmap).height;
|
||||
|
||||
const contentRatio = imageWidth / imageHeight;
|
||||
const containerRatio = width / height;
|
||||
let resultHeight;
|
||||
let resultWidth;
|
||||
if (contentRatio > containerRatio) {
|
||||
resultHeight = height;
|
||||
resultWidth = height * contentRatio;
|
||||
} else {
|
||||
resultWidth = width;
|
||||
resultHeight = width / contentRatio;
|
||||
}
|
||||
|
||||
// This value has been chosen to be as close with rendering as the css-only
|
||||
// backdrop-filter: blur effect was, mostly takes effect for vertical pictures.
|
||||
const x = width * 0.1;
|
||||
const y = (height - resultHeight) / 2;
|
||||
|
||||
this.leftLeftPanelRef.current.width = leftLeftPanelWidth;
|
||||
this.leftLeftPanelRef.current.height = height;
|
||||
this.leftPanelRef.current.width = (window.screen.width * 0.5);
|
||||
this.leftPanelRef.current.height = height;
|
||||
|
||||
const spacesBlur = this.style.getPropertyValue('--llp-background-blur');
|
||||
const roomListBlur = this.style.getPropertyValue('--lp-background-blur');
|
||||
|
||||
leftLeftPanelContext.filter = `blur(${spacesBlur})`;
|
||||
leftPanelContext.filter = `blur(${roomListBlur})`;
|
||||
leftLeftPanelContext.drawImage(
|
||||
backgroundImage,
|
||||
0, 0,
|
||||
imageWidth, imageHeight,
|
||||
x,
|
||||
y,
|
||||
resultWidth,
|
||||
resultHeight,
|
||||
);
|
||||
leftPanelContext.drawImage(
|
||||
backgroundImage,
|
||||
0, 0,
|
||||
imageWidth, imageHeight,
|
||||
x - leftLeftPanelWidth,
|
||||
y,
|
||||
resultWidth,
|
||||
resultHeight,
|
||||
);
|
||||
this.setState({
|
||||
lpImage: this.leftPanelRef.current.toDataURL('image/jpeg', 1),
|
||||
llpImage: this.leftLeftPanelRef.current.toDataURL('image/jpeg', 1),
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
public render() {
|
||||
if (!this.props.backgroundImage) return null;
|
||||
return <div className="mx_BackdropPanel">
|
||||
{ this.state?.llpImage !== 'data:,' && <img
|
||||
className="mx_BackdropPanel--canvas"
|
||||
src={this.state.llpImage} /> }
|
||||
|
||||
{ this.state?.lpImage !== 'data:,' && <img
|
||||
className="mx_BackdropPanel--canvas"
|
||||
src={this.state.lpImage} /> }
|
||||
<canvas
|
||||
ref={this.leftLeftPanelRef}
|
||||
className="mx_BackdropPanel--canvas"
|
||||
style={{
|
||||
display: this.state.lpImage ? 'none' : 'block',
|
||||
}}
|
||||
/>
|
||||
<canvas
|
||||
style={{
|
||||
display: this.state.lpImage ? 'none' : 'block',
|
||||
}}
|
||||
ref={this.leftPanelRef}
|
||||
className="mx_BackdropPanel--canvas"
|
||||
/>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
return <div className="mx_BackdropPanel">
|
||||
<img
|
||||
style={styles}
|
||||
className="mx_BackdropPanel--image"
|
||||
src={backgroundImage} />
|
||||
</div>;
|
||||
};
|
||||
export default BackdropPanel;
|
||||
|
|
|
@ -19,8 +19,6 @@ import { createRef } from "react";
|
|||
import classNames from "classnames";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
|
||||
import GroupFilterPanel from "./GroupFilterPanel";
|
||||
import CustomRoomTagPanel from "./CustomRoomTagPanel";
|
||||
import dis from "../../dispatcher/dispatcher";
|
||||
import { _t } from "../../languageHandler";
|
||||
import RoomList from "../views/rooms/RoomList";
|
||||
|
@ -33,7 +31,6 @@ import RoomBreadcrumbs from "../views/rooms/RoomBreadcrumbs";
|
|||
import { BreadcrumbsStore } from "../../stores/BreadcrumbsStore";
|
||||
import { UPDATE_EVENT } from "../../stores/AsyncStore";
|
||||
import ResizeNotifier from "../../utils/ResizeNotifier";
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../stores/room-list/RoomListStore";
|
||||
import IndicatorScrollbar from "../structures/IndicatorScrollbar";
|
||||
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
|
||||
|
@ -51,7 +48,6 @@ interface IProps {
|
|||
|
||||
interface IState {
|
||||
showBreadcrumbs: boolean;
|
||||
showGroupFilterPanel: boolean;
|
||||
activeSpace?: Room;
|
||||
}
|
||||
|
||||
|
@ -68,9 +64,6 @@ const cssClasses = [
|
|||
export default class LeftPanel extends React.Component<IProps, IState> {
|
||||
private ref: React.RefObject<HTMLDivElement> = createRef();
|
||||
private listContainerRef: React.RefObject<HTMLDivElement> = createRef();
|
||||
private groupFilterPanelWatcherRef: string;
|
||||
private groupFilterPanelContainer = createRef<HTMLDivElement>();
|
||||
private bgImageWatcherRef: string;
|
||||
private focusedElement = null;
|
||||
private isDoingStickyHeaders = false;
|
||||
|
||||
|
@ -79,25 +72,17 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||
|
||||
this.state = {
|
||||
showBreadcrumbs: BreadcrumbsStore.instance.visible,
|
||||
showGroupFilterPanel: SettingsStore.getValue('TagPanel.enableTagPanel'),
|
||||
activeSpace: SpaceStore.instance.activeSpace,
|
||||
};
|
||||
|
||||
BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
||||
RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
||||
SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.updateActiveSpace);
|
||||
this.groupFilterPanelWatcherRef = SettingsStore.watchSetting("TagPanel.enableTagPanel", null, () => {
|
||||
this.setState({ showGroupFilterPanel: SettingsStore.getValue("TagPanel.enableTagPanel") });
|
||||
});
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
UIStore.instance.trackElementDimensions("LeftPanel", this.ref.current);
|
||||
UIStore.instance.trackElementDimensions("ListContainer", this.listContainerRef.current);
|
||||
if (this.groupFilterPanelContainer.current) {
|
||||
const componentName = "GroupFilterPanelContainer";
|
||||
UIStore.instance.trackElementDimensions(componentName, this.groupFilterPanelContainer.current);
|
||||
}
|
||||
UIStore.instance.on("ListContainer", this.refreshStickyHeaders);
|
||||
// Using the passive option to not block the main thread
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#improving_scrolling_performance_with_passive_listeners
|
||||
|
@ -105,7 +90,6 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
SettingsStore.unwatchSetting(this.groupFilterPanelWatcherRef);
|
||||
BreadcrumbsStore.instance.off(UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
||||
RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
||||
SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.updateActiveSpace);
|
||||
|
@ -422,16 +406,6 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
public render(): React.ReactNode {
|
||||
let leftLeftPanel;
|
||||
if (this.state.showGroupFilterPanel) {
|
||||
leftLeftPanel = (
|
||||
<div className="mx_LeftPanel_GroupFilterPanelContainer" ref={this.groupFilterPanelContainer}>
|
||||
<GroupFilterPanel />
|
||||
{ SettingsStore.getValue("feature_custom_tags") ? <CustomRoomTagPanel /> : null }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const roomList = <RoomList
|
||||
onKeyDown={this.onKeyDown}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
|
@ -455,7 +429,6 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||
|
||||
return (
|
||||
<div className={containerClasses} ref={this.ref}>
|
||||
{ leftLeftPanel }
|
||||
<aside className="mx_LeftPanel_roomListContainer">
|
||||
{ this.renderHeader() }
|
||||
{ this.renderSearchDialExplore() }
|
||||
|
|
|
@ -68,6 +68,8 @@ import GroupView from "./GroupView";
|
|||
import BackdropPanel from "./BackdropPanel";
|
||||
import SpaceStore from "../../stores/SpaceStore";
|
||||
import classNames from 'classnames';
|
||||
import GroupFilterPanel from './GroupFilterPanel';
|
||||
import CustomRoomTagPanel from './CustomRoomTagPanel';
|
||||
|
||||
// We need to fetch each pinned message individually (if we don't already have it)
|
||||
// so each pinned message may trigger a request. Limit the number per room for sanity.
|
||||
|
@ -131,7 +133,7 @@ interface IState {
|
|||
usageLimitEventTs?: number;
|
||||
useCompactLayout: boolean;
|
||||
activeCalls: Array<MatrixCall>;
|
||||
backgroundImage?: CanvasImageSource;
|
||||
backgroundImage?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -222,7 +224,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
|
||||
private refreshBackgroundImage = async (): Promise<void> => {
|
||||
this.setState({
|
||||
backgroundImage: await OwnProfileStore.instance.getAvatarBitmap(),
|
||||
backgroundImage: OwnProfileStore.instance.getHttpAvatarUrl(),
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -289,7 +291,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
if (isNaN(lhsSize)) {
|
||||
lhsSize = 350;
|
||||
}
|
||||
this.resizer.forHandleAt(0).resize(lhsSize);
|
||||
this.resizer.forHandleWithId('lp-resizer').resize(lhsSize);
|
||||
}
|
||||
|
||||
private onAccountData = (event: MatrixEvent) => {
|
||||
|
@ -653,16 +655,37 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
>
|
||||
<ToastContainer />
|
||||
<div className={bodyClasses}>
|
||||
<div ref={this._resizeContainer} className='mx_LeftPanel_wrapper'>
|
||||
<div className='mx_LeftPanel_wrapper'>
|
||||
{ SettingsStore.getValue('TagPanel.enableTagPanel') &&
|
||||
(<div className="mx_GroupFilterPanelContainer">
|
||||
<BackdropPanel
|
||||
blurMultiplier={0.5}
|
||||
backgroundImage={this.state.backgroundImage}
|
||||
/>
|
||||
<GroupFilterPanel />
|
||||
{ SettingsStore.getValue("feature_custom_tags") ? <CustomRoomTagPanel /> : null }
|
||||
</div>)
|
||||
}
|
||||
{ SpaceStore.spacesEnabled ? <>
|
||||
<BackdropPanel
|
||||
blurMultiplier={0.5}
|
||||
backgroundImage={this.state.backgroundImage}
|
||||
/>
|
||||
<SpacePanel />
|
||||
</> : null }
|
||||
<BackdropPanel
|
||||
backgroundImage={this.state.backgroundImage}
|
||||
/>
|
||||
{ SpaceStore.spacesEnabled ? <SpacePanel /> : null }
|
||||
<LeftPanel
|
||||
isMinimized={this.props.collapseLhs || false}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
/>
|
||||
<ResizeHandle />
|
||||
<div
|
||||
ref={this._resizeContainer}
|
||||
className="mx_LeftPanel_wrapper--user"
|
||||
>
|
||||
<LeftPanel
|
||||
isMinimized={this.props.collapseLhs || false}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
/>
|
||||
<ResizeHandle id="lp-resizer" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx_RoomView_wrapper">
|
||||
{ pageElement }
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue