prevent reflow in app when accessing window dimensions
This commit is contained in:
parent
a57887cc61
commit
f156c2db15
14 changed files with 59 additions and 25 deletions
18
.eslintrc.js
18
.eslintrc.js
|
@ -30,6 +30,24 @@ module.exports = {
|
|||
|
||||
"quotes": "off",
|
||||
"no-extra-boolean-cast": "off",
|
||||
"no-restricted-properties": [
|
||||
"error",
|
||||
...buildRestrictedPropertiesOptions(
|
||||
["window.innerHeight", "window.innerWidth", "window.visualViewport"],
|
||||
"Use UIStore to access window dimensions instead",
|
||||
),
|
||||
],
|
||||
},
|
||||
}],
|
||||
};
|
||||
|
||||
function buildRestrictedPropertiesOptions(properties, message) {
|
||||
return properties.map(prop => {
|
||||
const [object, property] = prop.split(".");
|
||||
return {
|
||||
object,
|
||||
property,
|
||||
message,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import classNames from "classnames";
|
|||
import {Key} from "../../Keyboard";
|
||||
import {Writeable} from "../../@types/common";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
import UIStore from "../../stores/UIStore";
|
||||
|
||||
// Shamelessly ripped off Modal.js. There's probably a better way
|
||||
// of doing reusable widgets like dialog boxes & menus where we go and
|
||||
|
@ -410,12 +411,12 @@ export const aboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFace.None
|
|||
const buttonBottom = elementRect.bottom + window.pageYOffset;
|
||||
const buttonTop = elementRect.top + window.pageYOffset;
|
||||
// Align the right edge of the menu to the right edge of the button
|
||||
menuOptions.right = window.innerWidth - buttonRight;
|
||||
menuOptions.right = UIStore.instance.windowWidth - buttonRight;
|
||||
// Align the menu vertically on whichever side of the button has more space available.
|
||||
if (buttonBottom < window.innerHeight / 2) {
|
||||
if (buttonBottom < UIStore.instance.windowHeight / 2) {
|
||||
menuOptions.top = buttonBottom + vPadding;
|
||||
} else {
|
||||
menuOptions.bottom = (window.innerHeight - buttonTop) + vPadding;
|
||||
menuOptions.bottom = (UIStore.instance.windowHeight - buttonTop) + vPadding;
|
||||
}
|
||||
|
||||
return menuOptions;
|
||||
|
@ -430,12 +431,12 @@ export const alwaysAboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFac
|
|||
const buttonBottom = elementRect.bottom + window.pageYOffset;
|
||||
const buttonTop = elementRect.top + window.pageYOffset;
|
||||
// Align the right edge of the menu to the right edge of the button
|
||||
menuOptions.right = window.innerWidth - buttonRight;
|
||||
menuOptions.right = UIStore.instance.windowWidth - buttonRight;
|
||||
// Align the menu vertically on whichever side of the button has more space available.
|
||||
if (buttonBottom < window.innerHeight / 2) {
|
||||
if (buttonBottom < UIStore.instance.windowHeight / 2) {
|
||||
menuOptions.top = buttonBottom + vPadding;
|
||||
} else {
|
||||
menuOptions.bottom = (window.innerHeight - buttonTop) + vPadding;
|
||||
menuOptions.bottom = (UIStore.instance.windowHeight - buttonTop) + vPadding;
|
||||
}
|
||||
|
||||
return menuOptions;
|
||||
|
@ -451,7 +452,7 @@ export const alwaysAboveRightOf = (elementRect: DOMRect, chevronFace = ChevronFa
|
|||
// Align the left edge of the menu to the left edge of the button
|
||||
menuOptions.left = buttonLeft;
|
||||
// Align the menu vertically above the menu
|
||||
menuOptions.bottom = (window.innerHeight - buttonTop) + vPadding;
|
||||
menuOptions.bottom = (UIStore.instance.windowHeight - buttonTop) + vPadding;
|
||||
|
||||
return menuOptions;
|
||||
};
|
||||
|
|
|
@ -43,6 +43,7 @@ import {replaceableComponent} from "../../utils/replaceableComponent";
|
|||
import {mediaFromMxc} from "../../customisations/Media";
|
||||
import SpaceStore, {UPDATE_SELECTED_SPACE} from "../../stores/SpaceStore";
|
||||
import { getKeyBindingsManager, RoomListAction } from "../../KeyBindingsManager";
|
||||
import UIStore from "../../stores/UIStore";
|
||||
|
||||
interface IProps {
|
||||
isMinimized: boolean;
|
||||
|
@ -223,7 +224,8 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||
header.classList.add("mx_RoomSublist_headerContainer_stickyBottom");
|
||||
}
|
||||
|
||||
const offset = window.innerHeight - (list.parentElement.offsetTop + list.parentElement.offsetHeight);
|
||||
const offset = UIStore.instance.windowHeight -
|
||||
(list.parentElement.offsetTop + list.parentElement.offsetHeight);
|
||||
const newBottom = `${offset}px`;
|
||||
if (header.style.bottom !== newBottom) {
|
||||
header.style.bottom = newBottom;
|
||||
|
|
|
@ -27,6 +27,7 @@ import WidgetUtils, {IWidgetEvent} from "../../utils/WidgetUtils";
|
|||
import {useAccountData} from "../../hooks/useAccountData";
|
||||
import AppTile from "../views/elements/AppTile";
|
||||
import {useSettingValue} from "../../hooks/useSettings";
|
||||
import UIStore from "../../stores/UIStore";
|
||||
|
||||
const MIN_HEIGHT = 100;
|
||||
const MAX_HEIGHT = 500; // or 50% of the window height
|
||||
|
@ -63,7 +64,7 @@ const LeftPanelWidget: React.FC = () => {
|
|||
content = <Resizable
|
||||
size={{height} as any}
|
||||
minHeight={MIN_HEIGHT}
|
||||
maxHeight={Math.min(window.innerHeight / 2, MAX_HEIGHT)}
|
||||
maxHeight={Math.min(UIStore.instance.windowHeight / 2, MAX_HEIGHT)}
|
||||
onResizeStop={(e, dir, ref, d) => {
|
||||
setHeight(height + d.height);
|
||||
}}
|
||||
|
|
|
@ -1819,7 +1819,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
|
||||
handleResize = () => {
|
||||
const LHS_THRESHOLD = 1000;
|
||||
const width = UIStore.instance.windowWith;
|
||||
const width = UIStore.instance.windowWidth;
|
||||
|
||||
if (width <= LHS_THRESHOLD && !this.state.collapseLhs) {
|
||||
dis.dispatch({ action: 'hide_left_panel' });
|
||||
|
|
|
@ -83,6 +83,7 @@ import { objectHasDiff } from "../../utils/objects";
|
|||
import SpaceRoomView from "./SpaceRoomView";
|
||||
import { IOpts } from "../../createRoom";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
import UIStore from "../../stores/UIStore";
|
||||
|
||||
const DEBUG = false;
|
||||
let debuglog = function(msg: string) {};
|
||||
|
@ -1585,7 +1586,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
// a maxHeight on the underlying remote video tag.
|
||||
|
||||
// header + footer + status + give us at least 120px of scrollback at all times.
|
||||
let auxPanelMaxHeight = window.innerHeight -
|
||||
let auxPanelMaxHeight = UIStore.instance.windowHeight -
|
||||
(54 + // height of RoomHeader
|
||||
36 + // height of the status area
|
||||
51 + // minimum height of the message compmoser
|
||||
|
|
|
@ -38,13 +38,14 @@ import withValidation from "../elements/Validation";
|
|||
import { SettingLevel } from "../../../settings/SettingLevel";
|
||||
import TextInputDialog from "../dialogs/TextInputDialog";
|
||||
import QuestionDialog from "../dialogs/QuestionDialog";
|
||||
import UIStore from "../../../stores/UIStore";
|
||||
|
||||
export const ALL_ROOMS = Symbol("ALL_ROOMS");
|
||||
|
||||
const SETTING_NAME = "room_directory_servers";
|
||||
|
||||
const inPlaceOf = (elementRect: Pick<DOMRect, "right" | "top">) => ({
|
||||
right: window.innerWidth - elementRect.right,
|
||||
right: UIStore.instance.windowWidth - elementRect.right,
|
||||
top: elementRect.top,
|
||||
chevronOffset: 0,
|
||||
chevronFace: ChevronFace.None,
|
||||
|
|
|
@ -22,6 +22,7 @@ import React, {Component, CSSProperties} from 'react';
|
|||
import ReactDOM from 'react-dom';
|
||||
import classNames from 'classnames';
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import UIStore from "../../../stores/UIStore";
|
||||
|
||||
const MIN_TOOLTIP_HEIGHT = 25;
|
||||
|
||||
|
@ -97,15 +98,15 @@ export default class Tooltip extends React.Component<IProps> {
|
|||
// we need so that we're still centered.
|
||||
offset = Math.floor(parentBox.height - MIN_TOOLTIP_HEIGHT);
|
||||
}
|
||||
|
||||
const width = UIStore.instance.windowWidth;
|
||||
const baseTop = (parentBox.top - 2 + this.props.yOffset) + window.pageYOffset;
|
||||
const top = baseTop + offset;
|
||||
const right = window.innerWidth - parentBox.right - window.pageXOffset - 16;
|
||||
const right = width - parentBox.right - window.pageXOffset - 16;
|
||||
const left = parentBox.right + window.pageXOffset + 6;
|
||||
const horizontalCenter = parentBox.right - window.pageXOffset - (parentBox.width / 2);
|
||||
switch (this.props.alignment) {
|
||||
case Alignment.Natural:
|
||||
if (parentBox.right > window.innerWidth / 2) {
|
||||
if (parentBox.right > width / 2) {
|
||||
style.right = right;
|
||||
style.top = top;
|
||||
break;
|
||||
|
|
|
@ -36,6 +36,7 @@ import {toRightOf} from "../../structures/ContextMenu";
|
|||
import {copyPlaintext} from "../../../utils/strings";
|
||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import UIStore from "../../../stores/UIStore";
|
||||
|
||||
@replaceableComponent("views.messages.TextualBody")
|
||||
export default class TextualBody extends React.Component {
|
||||
|
@ -143,7 +144,7 @@ export default class TextualBody extends React.Component {
|
|||
_addCodeExpansionButton(div, pre) {
|
||||
// Calculate how many percent does the pre element take up.
|
||||
// If it's less than 30% we don't add the expansion button.
|
||||
const percentageOfViewport = pre.offsetHeight / window.innerHeight * 100;
|
||||
const percentageOfViewport = pre.offsetHeight / UIStore.instance.windowHeight * 100;
|
||||
if (percentageOfViewport < 30) return;
|
||||
|
||||
const button = document.createElement("span");
|
||||
|
|
|
@ -46,6 +46,7 @@ import WidgetContextMenu from "../context_menus/WidgetContextMenu";
|
|||
import {useRoomMemberCount} from "../../../hooks/useRoomMembers";
|
||||
import { Container, MAX_PINNED, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
|
||||
import RoomName from "../elements/RoomName";
|
||||
import UIStore from "../../../stores/UIStore";
|
||||
|
||||
interface IProps {
|
||||
room: Room;
|
||||
|
@ -116,8 +117,8 @@ const AppRow: React.FC<IAppRowProps> = ({ app, room }) => {
|
|||
const rect = handle.current.getBoundingClientRect();
|
||||
contextMenu = <WidgetContextMenu
|
||||
chevronFace={ChevronFace.None}
|
||||
right={window.innerWidth - rect.right}
|
||||
bottom={window.innerHeight - rect.top}
|
||||
right={UIStore.instance.windowWidth - rect.right}
|
||||
bottom={UIStore.instance.windowHeight - rect.top}
|
||||
onFinished={closeMenu}
|
||||
app={app}
|
||||
/>;
|
||||
|
|
|
@ -66,6 +66,7 @@ import { SetRightPanelPhasePayload } from "../../../dispatcher/payloads/SetRight
|
|||
import RoomAvatar from "../avatars/RoomAvatar";
|
||||
import RoomName from "../elements/RoomName";
|
||||
import {mediaFromMxc} from "../../../customisations/Media";
|
||||
import UIStore from "../../../stores/UIStore";
|
||||
|
||||
export interface IDevice {
|
||||
deviceId: string;
|
||||
|
@ -1448,8 +1449,8 @@ const UserInfoHeader: React.FC<{
|
|||
<MemberAvatar
|
||||
key={member.userId} // to instantly blank the avatar when UserInfo changes members
|
||||
member={member}
|
||||
width={2 * 0.3 * window.innerHeight} // 2x@30vh
|
||||
height={2 * 0.3 * window.innerHeight} // 2x@30vh
|
||||
width={2 * 0.3 * UIStore.instance.windowHeight} // 2x@30vh
|
||||
height={2 * 0.3 * UIStore.instance.windowHeight} // 2x@30vh
|
||||
resizeMethod="scale"
|
||||
fallbackUserId={member.userId}
|
||||
onClick={onMemberAvatarClick}
|
||||
|
|
|
@ -30,6 +30,7 @@ import { Action } from "../../../dispatcher/actions";
|
|||
import { ChevronFace, ContextMenuButton, useContextMenu } from "../../structures/ContextMenu";
|
||||
import WidgetContextMenu from "../context_menus/WidgetContextMenu";
|
||||
import { Container, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
|
||||
import UIStore from "../../../stores/UIStore";
|
||||
|
||||
interface IProps {
|
||||
room: Room;
|
||||
|
@ -65,7 +66,7 @@ const WidgetCard: React.FC<IProps> = ({ room, widgetId, onClose }) => {
|
|||
contextMenu = (
|
||||
<WidgetContextMenu
|
||||
chevronFace={ChevronFace.None}
|
||||
right={window.innerWidth - rect.right - 12}
|
||||
right={UIStore.instance.windowWidth - rect.right - 12}
|
||||
top={rect.bottom + 12}
|
||||
onFinished={closeMenu}
|
||||
app={app}
|
||||
|
|
|
@ -36,6 +36,7 @@ import {Container, WidgetLayoutStore} from "../../../stores/widgets/WidgetLayout
|
|||
import {clamp, percentageOf, percentageWithin} from "../../../utils/numbers";
|
||||
import {useStateCallback} from "../../../hooks/useStateCallback";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import UIStore from "../../../stores/UIStore";
|
||||
|
||||
@replaceableComponent("views.rooms.AppsDrawer")
|
||||
export default class AppsDrawer extends React.Component {
|
||||
|
@ -290,7 +291,7 @@ const PersistentVResizer = ({
|
|||
|
||||
// Arbitrary defaults to avoid NaN problems. 100 px or 3/4 of the visible window.
|
||||
if (!minHeight) minHeight = 100;
|
||||
if (!maxHeight) maxHeight = (window.innerHeight / 4) * 3;
|
||||
if (!maxHeight) maxHeight = (UIStore.instance.windowHeight / 4) * 3;
|
||||
|
||||
// Convert from percentage to height. Note that the default height is 280px.
|
||||
if (defaultHeight) {
|
||||
|
|
|
@ -15,6 +15,8 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import EventEmitter from "events";
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
import ResizeObserverEntry from 'resize-observer-polyfill/src/ResizeObserverEntry';
|
||||
|
||||
export enum UI_EVENTS {
|
||||
Resize = "resize"
|
||||
|
@ -28,13 +30,15 @@ export default class UIStore extends EventEmitter {
|
|||
|
||||
private resizeObserver: ResizeObserver;
|
||||
|
||||
public windowWith: number;
|
||||
public windowWidth: number;
|
||||
public windowHeight: number;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.windowWith = window.innerWidth;
|
||||
// eslint-disable-next-line no-restricted-properties
|
||||
this.windowWidth = window.innerWidth;
|
||||
// eslint-disable-next-line no-restricted-properties
|
||||
this.windowHeight = window.innerHeight;
|
||||
|
||||
this.resizeObserver = new ResizeObserver(this.resizeObserverCallback);
|
||||
|
@ -61,7 +65,7 @@ export default class UIStore extends EventEmitter {
|
|||
.find(entry => entry.target === document.body)
|
||||
.contentRect;
|
||||
|
||||
this.windowWith = width;
|
||||
this.windowWidth = width;
|
||||
this.windowHeight = height;
|
||||
|
||||
this.emit(UI_EVENTS.Resize, entries);
|
||||
|
|
Loading…
Reference in a new issue