Merge pull request #6092 from matrix-org/gsouquet/window-dimensions-reflow

This commit is contained in:
Germain 2021-05-25 12:00:08 +01:00 committed by GitHub
commit 1751b4ba43
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 155 additions and 75 deletions

View file

@ -30,6 +30,24 @@ module.exports = {
"quotes": "off", "quotes": "off",
"no-extra-boolean-cast": "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,
};
});
}

View file

@ -685,7 +685,9 @@ export default class CountlyAnalytics {
} }
private getOrientation = (): Orientation => { private getOrientation = (): Orientation => {
return window.innerWidth > window.innerHeight ? Orientation.Landscape : Orientation.Portrait; return window.matchMedia("(orientation: landscape)").matches
? Orientation.Landscape
: Orientation.Portrait
}; };
private reportOrientation = () => { private reportOrientation = () => {

View file

@ -23,6 +23,7 @@ import classNames from "classnames";
import {Key} from "../../Keyboard"; import {Key} from "../../Keyboard";
import {Writeable} from "../../@types/common"; import {Writeable} from "../../@types/common";
import {replaceableComponent} from "../../utils/replaceableComponent"; import {replaceableComponent} from "../../utils/replaceableComponent";
import UIStore from "../../stores/UIStore";
// Shamelessly ripped off Modal.js. There's probably a better way // Shamelessly ripped off Modal.js. There's probably a better way
// of doing reusable widgets like dialog boxes & menus where we go and // 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 buttonBottom = elementRect.bottom + window.pageYOffset;
const buttonTop = elementRect.top + window.pageYOffset; const buttonTop = elementRect.top + window.pageYOffset;
// Align the right edge of the menu to the right edge of the button // 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. // 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; menuOptions.top = buttonBottom + vPadding;
} else { } else {
menuOptions.bottom = (window.innerHeight - buttonTop) + vPadding; menuOptions.bottom = (UIStore.instance.windowHeight - buttonTop) + vPadding;
} }
return menuOptions; return menuOptions;
@ -430,12 +431,12 @@ export const alwaysAboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFac
const buttonBottom = elementRect.bottom + window.pageYOffset; const buttonBottom = elementRect.bottom + window.pageYOffset;
const buttonTop = elementRect.top + window.pageYOffset; const buttonTop = elementRect.top + window.pageYOffset;
// Align the right edge of the menu to the right edge of the button // 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. // 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; menuOptions.top = buttonBottom + vPadding;
} else { } else {
menuOptions.bottom = (window.innerHeight - buttonTop) + vPadding; menuOptions.bottom = (UIStore.instance.windowHeight - buttonTop) + vPadding;
} }
return menuOptions; 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 // Align the left edge of the menu to the left edge of the button
menuOptions.left = buttonLeft; menuOptions.left = buttonLeft;
// Align the menu vertically above the menu // Align the menu vertically above the menu
menuOptions.bottom = (window.innerHeight - buttonTop) + vPadding; menuOptions.bottom = (UIStore.instance.windowHeight - buttonTop) + vPadding;
return menuOptions; return menuOptions;
}; };

View file

@ -43,6 +43,7 @@ import {replaceableComponent} from "../../utils/replaceableComponent";
import {mediaFromMxc} from "../../customisations/Media"; import {mediaFromMxc} from "../../customisations/Media";
import SpaceStore, {UPDATE_SELECTED_SPACE} from "../../stores/SpaceStore"; import SpaceStore, {UPDATE_SELECTED_SPACE} from "../../stores/SpaceStore";
import { getKeyBindingsManager, RoomListAction } from "../../KeyBindingsManager"; import { getKeyBindingsManager, RoomListAction } from "../../KeyBindingsManager";
import UIStore from "../../stores/UIStore";
interface IProps { interface IProps {
isMinimized: boolean; isMinimized: boolean;
@ -90,10 +91,6 @@ export default class LeftPanel extends React.Component<IProps, IState> {
this.groupFilterPanelWatcherRef = SettingsStore.watchSetting("TagPanel.enableTagPanel", null, () => { this.groupFilterPanelWatcherRef = SettingsStore.watchSetting("TagPanel.enableTagPanel", null, () => {
this.setState({showGroupFilterPanel: SettingsStore.getValue("TagPanel.enableTagPanel")}); this.setState({showGroupFilterPanel: SettingsStore.getValue("TagPanel.enableTagPanel")});
}); });
// We watch the middle panel because we don't actually get resized, the middle panel does.
// We listen to the noisy channel to avoid choppy reaction times.
this.props.resizeNotifier.on("middlePanelResizedNoisy", this.onResize);
} }
public componentWillUnmount() { public componentWillUnmount() {
@ -103,7 +100,6 @@ export default class LeftPanel extends React.Component<IProps, IState> {
RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate); RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate);
OwnProfileStore.instance.off(UPDATE_EVENT, this.onBackgroundImageUpdate); OwnProfileStore.instance.off(UPDATE_EVENT, this.onBackgroundImageUpdate);
SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.updateActiveSpace); SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.updateActiveSpace);
this.props.resizeNotifier.off("middlePanelResizedNoisy", this.onResize);
} }
private updateActiveSpace = (activeSpace: Room) => { private updateActiveSpace = (activeSpace: Room) => {
@ -228,7 +224,8 @@ export default class LeftPanel extends React.Component<IProps, IState> {
header.classList.add("mx_RoomSublist_headerContainer_stickyBottom"); 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`; const newBottom = `${offset}px`;
if (header.style.bottom !== newBottom) { if (header.style.bottom !== newBottom) {
header.style.bottom = newBottom; header.style.bottom = newBottom;
@ -281,11 +278,6 @@ export default class LeftPanel extends React.Component<IProps, IState> {
this.handleStickyHeaders(list); this.handleStickyHeaders(list);
}; };
private onResize = () => {
if (!this.listContainerRef.current) return; // ignore: no headers to sticky
this.handleStickyHeaders(this.listContainerRef.current);
};
private onFocus = (ev: React.FocusEvent) => { private onFocus = (ev: React.FocusEvent) => {
this.focusedElement = ev.target; this.focusedElement = ev.target;
}; };
@ -420,7 +412,6 @@ export default class LeftPanel extends React.Component<IProps, IState> {
onFocus={this.onFocus} onFocus={this.onFocus}
onBlur={this.onBlur} onBlur={this.onBlur}
isMinimized={this.props.isMinimized} isMinimized={this.props.isMinimized}
onResize={this.onResize}
activeSpace={this.state.activeSpace} activeSpace={this.state.activeSpace}
/>; />;
@ -454,7 +445,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
{roomList} {roomList}
</div> </div>
</div> </div>
{ !this.props.isMinimized && <LeftPanelWidget onResize={this.onResize} /> } { !this.props.isMinimized && <LeftPanelWidget /> }
</aside> </aside>
</div> </div>
); );

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, {useContext, useEffect, useMemo} from "react"; import React, {useContext, useMemo} from "react";
import {Resizable} from "re-resizable"; import {Resizable} from "re-resizable";
import classNames from "classnames"; import classNames from "classnames";
@ -27,16 +27,13 @@ import WidgetUtils, {IWidgetEvent} from "../../utils/WidgetUtils";
import {useAccountData} from "../../hooks/useAccountData"; import {useAccountData} from "../../hooks/useAccountData";
import AppTile from "../views/elements/AppTile"; import AppTile from "../views/elements/AppTile";
import {useSettingValue} from "../../hooks/useSettings"; import {useSettingValue} from "../../hooks/useSettings";
import UIStore from "../../stores/UIStore";
interface IProps {
onResize(): void;
}
const MIN_HEIGHT = 100; const MIN_HEIGHT = 100;
const MAX_HEIGHT = 500; // or 50% of the window height const MAX_HEIGHT = 500; // or 50% of the window height
const INITIAL_HEIGHT = 280; const INITIAL_HEIGHT = 280;
const LeftPanelWidget: React.FC<IProps> = ({ onResize }) => { const LeftPanelWidget: React.FC = () => {
const cli = useContext(MatrixClientContext); const cli = useContext(MatrixClientContext);
const mWidgetsEvent = useAccountData<Record<string, IWidgetEvent>>(cli, "m.widgets"); const mWidgetsEvent = useAccountData<Record<string, IWidgetEvent>>(cli, "m.widgets");
@ -56,7 +53,6 @@ const LeftPanelWidget: React.FC<IProps> = ({ onResize }) => {
const [height, setHeight] = useLocalStorageState("left-panel-widget-height", INITIAL_HEIGHT); const [height, setHeight] = useLocalStorageState("left-panel-widget-height", INITIAL_HEIGHT);
const [expanded, setExpanded] = useLocalStorageState("left-panel-widget-expanded", true); const [expanded, setExpanded] = useLocalStorageState("left-panel-widget-expanded", true);
useEffect(onResize, [expanded, onResize]);
const [onFocus, isActive, ref] = useRovingTabIndex(); const [onFocus, isActive, ref] = useRovingTabIndex();
const tabIndex = isActive ? 0 : -1; const tabIndex = isActive ? 0 : -1;
@ -68,8 +64,7 @@ const LeftPanelWidget: React.FC<IProps> = ({ onResize }) => {
content = <Resizable content = <Resizable
size={{height} as any} size={{height} as any}
minHeight={MIN_HEIGHT} minHeight={MIN_HEIGHT}
maxHeight={Math.min(window.innerHeight / 2, MAX_HEIGHT)} maxHeight={Math.min(UIStore.instance.windowHeight / 2, MAX_HEIGHT)}
onResize={onResize}
onResizeStop={(e, dir, ref, d) => { onResizeStop={(e, dir, ref, d) => {
setHeight(height + d.height); setHeight(height + d.height);
}} }}

View file

@ -87,6 +87,7 @@ import defaultDispatcher from "../../dispatcher/dispatcher";
import SecurityCustomisations from "../../customisations/Security"; import SecurityCustomisations from "../../customisations/Security";
import PerformanceMonitor, { PerformanceEntryNames } from "../../performance"; import PerformanceMonitor, { PerformanceEntryNames } from "../../performance";
import UIStore, { UI_EVENTS } from "../../stores/UIStore";
/** constants for MatrixChat.state.view */ /** constants for MatrixChat.state.view */
export enum Views { export enum Views {
@ -225,7 +226,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
firstSyncPromise: IDeferred<void>; firstSyncPromise: IDeferred<void>;
private screenAfterLogin?: IScreen; private screenAfterLogin?: IScreen;
private windowWidth: number;
private pageChanging: boolean; private pageChanging: boolean;
private tokenLogin?: boolean; private tokenLogin?: boolean;
private accountPassword?: string; private accountPassword?: string;
@ -277,9 +277,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
} }
} }
this.windowWidth = 10000; UIStore.instance.on(UI_EVENTS.Resize, this.handleResize);
this.handleResize();
window.addEventListener('resize', this.handleResize);
this.pageChanging = false; this.pageChanging = false;
@ -436,7 +434,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
dis.unregister(this.dispatcherRef); dis.unregister(this.dispatcherRef);
this.themeWatcher.stop(); this.themeWatcher.stop();
this.fontWatcher.stop(); this.fontWatcher.stop();
window.removeEventListener('resize', this.handleResize); UIStore.destroy();
this.state.resizeNotifier.removeListener("middlePanelResized", this.dispatchTimelineResize); this.state.resizeNotifier.removeListener("middlePanelResized", this.dispatchTimelineResize);
if (this.accountPasswordTimer !== null) clearTimeout(this.accountPasswordTimer); if (this.accountPasswordTimer !== null) clearTimeout(this.accountPasswordTimer);
@ -1820,18 +1818,17 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
} }
handleResize = () => { handleResize = () => {
const hideLhsThreshold = 1000; const LHS_THRESHOLD = 1000;
const showLhsThreshold = 1000; const width = UIStore.instance.windowWidth;
if (this.windowWidth > hideLhsThreshold && window.innerWidth <= hideLhsThreshold) { if (width <= LHS_THRESHOLD && !this.state.collapseLhs) {
dis.dispatch({ action: 'hide_left_panel' }); dis.dispatch({ action: 'hide_left_panel' });
} }
if (this.windowWidth <= showLhsThreshold && window.innerWidth > showLhsThreshold) { if (width > LHS_THRESHOLD && this.state.collapseLhs) {
dis.dispatch({ action: 'show_left_panel' }); dis.dispatch({ action: 'show_left_panel' });
} }
this.state.resizeNotifier.notifyWindowResized(); this.state.resizeNotifier.notifyWindowResized();
this.windowWidth = window.innerWidth;
}; };
private dispatchTimelineResize() { private dispatchTimelineResize() {

View file

@ -83,6 +83,7 @@ import { objectHasDiff } from "../../utils/objects";
import SpaceRoomView from "./SpaceRoomView"; import SpaceRoomView from "./SpaceRoomView";
import { IOpts } from "../../createRoom"; import { IOpts } from "../../createRoom";
import {replaceableComponent} from "../../utils/replaceableComponent"; import {replaceableComponent} from "../../utils/replaceableComponent";
import UIStore from "../../stores/UIStore";
const DEBUG = false; const DEBUG = false;
let debuglog = function(msg: string) {}; let debuglog = function(msg: string) {};
@ -1586,7 +1587,7 @@ export default class RoomView extends React.Component<IProps, IState> {
// a maxHeight on the underlying remote video tag. // a maxHeight on the underlying remote video tag.
// header + footer + status + give us at least 120px of scrollback at all times. // 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 (54 + // height of RoomHeader
36 + // height of the status area 36 + // height of the status area
51 + // minimum height of the message compmoser 51 + // minimum height of the message compmoser

View file

@ -38,13 +38,14 @@ import withValidation from "../elements/Validation";
import { SettingLevel } from "../../../settings/SettingLevel"; import { SettingLevel } from "../../../settings/SettingLevel";
import TextInputDialog from "../dialogs/TextInputDialog"; import TextInputDialog from "../dialogs/TextInputDialog";
import QuestionDialog from "../dialogs/QuestionDialog"; import QuestionDialog from "../dialogs/QuestionDialog";
import UIStore from "../../../stores/UIStore";
export const ALL_ROOMS = Symbol("ALL_ROOMS"); export const ALL_ROOMS = Symbol("ALL_ROOMS");
const SETTING_NAME = "room_directory_servers"; const SETTING_NAME = "room_directory_servers";
const inPlaceOf = (elementRect: Pick<DOMRect, "right" | "top">) => ({ const inPlaceOf = (elementRect: Pick<DOMRect, "right" | "top">) => ({
right: window.innerWidth - elementRect.right, right: UIStore.instance.windowWidth - elementRect.right,
top: elementRect.top, top: elementRect.top,
chevronOffset: 0, chevronOffset: 0,
chevronFace: ChevronFace.None, chevronFace: ChevronFace.None,

View file

@ -17,7 +17,8 @@
import React, { FunctionComponent, useEffect, useRef } from 'react'; import React, { FunctionComponent, useEffect, useRef } from 'react';
import dis from '../../../dispatcher/dispatcher'; import dis from '../../../dispatcher/dispatcher';
import ICanvasEffect from '../../../effects/ICanvasEffect'; import ICanvasEffect from '../../../effects/ICanvasEffect';
import {CHAT_EFFECTS} from '../../../effects' import { CHAT_EFFECTS } from '../../../effects'
import UIStore, { UI_EVENTS } from "../../../stores/UIStore";
interface IProps { interface IProps {
roomWidth: number; roomWidth: number;
@ -45,8 +46,8 @@ const EffectsOverlay: FunctionComponent<IProps> = ({ roomWidth }) => {
useEffect(() => { useEffect(() => {
const resize = () => { const resize = () => {
if (canvasRef.current) { if (canvasRef.current && canvasRef.current?.height !== UIStore.instance.windowHeight) {
canvasRef.current.height = window.innerHeight; canvasRef.current.height = UIStore.instance.windowHeight;
} }
}; };
const onAction = (payload: { action: string }) => { const onAction = (payload: { action: string }) => {
@ -58,12 +59,12 @@ const EffectsOverlay: FunctionComponent<IProps> = ({ roomWidth }) => {
} }
const dispatcherRef = dis.register(onAction); const dispatcherRef = dis.register(onAction);
const canvas = canvasRef.current; const canvas = canvasRef.current;
canvas.height = window.innerHeight; canvas.height = UIStore.instance.windowHeight;
window.addEventListener('resize', resize, true); UIStore.instance.on(UI_EVENTS.Resize, resize);
return () => { return () => {
dis.unregister(dispatcherRef); dis.unregister(dispatcherRef);
window.removeEventListener('resize', resize); UIStore.instance.off(UI_EVENTS.Resize, resize);
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
const currentEffects = effectsRef.current; // this is not a react node ref, warning can be safely ignored const currentEffects = effectsRef.current; // this is not a react node ref, warning can be safely ignored
for (const effect in currentEffects) { for (const effect in currentEffects) {

View file

@ -22,6 +22,7 @@ import React, {Component, CSSProperties} from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import classNames from 'classnames'; import classNames from 'classnames';
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import UIStore from "../../../stores/UIStore";
const MIN_TOOLTIP_HEIGHT = 25; 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. // we need so that we're still centered.
offset = Math.floor(parentBox.height - MIN_TOOLTIP_HEIGHT); offset = Math.floor(parentBox.height - MIN_TOOLTIP_HEIGHT);
} }
const width = UIStore.instance.windowWidth;
const baseTop = (parentBox.top - 2 + this.props.yOffset) + window.pageYOffset; const baseTop = (parentBox.top - 2 + this.props.yOffset) + window.pageYOffset;
const top = baseTop + offset; 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 left = parentBox.right + window.pageXOffset + 6;
const horizontalCenter = parentBox.right - window.pageXOffset - (parentBox.width / 2); const horizontalCenter = parentBox.right - window.pageXOffset - (parentBox.width / 2);
switch (this.props.alignment) { switch (this.props.alignment) {
case Alignment.Natural: case Alignment.Natural:
if (parentBox.right > window.innerWidth / 2) { if (parentBox.right > width / 2) {
style.right = right; style.right = right;
style.top = top; style.top = top;
break; break;

View file

@ -36,6 +36,7 @@ import {toRightOf} from "../../structures/ContextMenu";
import {copyPlaintext} from "../../../utils/strings"; import {copyPlaintext} from "../../../utils/strings";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import UIStore from "../../../stores/UIStore";
@replaceableComponent("views.messages.TextualBody") @replaceableComponent("views.messages.TextualBody")
export default class TextualBody extends React.Component { export default class TextualBody extends React.Component {
@ -143,7 +144,7 @@ export default class TextualBody extends React.Component {
_addCodeExpansionButton(div, pre) { _addCodeExpansionButton(div, pre) {
// Calculate how many percent does the pre element take up. // Calculate how many percent does the pre element take up.
// If it's less than 30% we don't add the expansion button. // 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; if (percentageOfViewport < 30) return;
const button = document.createElement("span"); const button = document.createElement("span");

View file

@ -46,6 +46,7 @@ import WidgetContextMenu from "../context_menus/WidgetContextMenu";
import {useRoomMemberCount} from "../../../hooks/useRoomMembers"; import {useRoomMemberCount} from "../../../hooks/useRoomMembers";
import { Container, MAX_PINNED, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore"; import { Container, MAX_PINNED, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
import RoomName from "../elements/RoomName"; import RoomName from "../elements/RoomName";
import UIStore from "../../../stores/UIStore";
interface IProps { interface IProps {
room: Room; room: Room;
@ -116,8 +117,8 @@ const AppRow: React.FC<IAppRowProps> = ({ app, room }) => {
const rect = handle.current.getBoundingClientRect(); const rect = handle.current.getBoundingClientRect();
contextMenu = <WidgetContextMenu contextMenu = <WidgetContextMenu
chevronFace={ChevronFace.None} chevronFace={ChevronFace.None}
right={window.innerWidth - rect.right} right={UIStore.instance.windowWidth - rect.right}
bottom={window.innerHeight - rect.top} bottom={UIStore.instance.windowHeight - rect.top}
onFinished={closeMenu} onFinished={closeMenu}
app={app} app={app}
/>; />;

View file

@ -66,6 +66,7 @@ import { SetRightPanelPhasePayload } from "../../../dispatcher/payloads/SetRight
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"; import {mediaFromMxc} from "../../../customisations/Media";
import UIStore from "../../../stores/UIStore";
export interface IDevice { export interface IDevice {
deviceId: string; deviceId: string;
@ -1448,8 +1449,8 @@ const UserInfoHeader: React.FC<{
<MemberAvatar <MemberAvatar
key={member.userId} // to instantly blank the avatar when UserInfo changes members key={member.userId} // to instantly blank the avatar when UserInfo changes members
member={member} member={member}
width={2 * 0.3 * window.innerHeight} // 2x@30vh width={2 * 0.3 * UIStore.instance.windowHeight} // 2x@30vh
height={2 * 0.3 * window.innerHeight} // 2x@30vh height={2 * 0.3 * UIStore.instance.windowHeight} // 2x@30vh
resizeMethod="scale" resizeMethod="scale"
fallbackUserId={member.userId} fallbackUserId={member.userId}
onClick={onMemberAvatarClick} onClick={onMemberAvatarClick}

View file

@ -30,6 +30,7 @@ import { Action } from "../../../dispatcher/actions";
import { ChevronFace, ContextMenuButton, useContextMenu } from "../../structures/ContextMenu"; import { ChevronFace, ContextMenuButton, useContextMenu } from "../../structures/ContextMenu";
import WidgetContextMenu from "../context_menus/WidgetContextMenu"; import WidgetContextMenu from "../context_menus/WidgetContextMenu";
import { Container, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore"; import { Container, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
import UIStore from "../../../stores/UIStore";
interface IProps { interface IProps {
room: Room; room: Room;
@ -65,7 +66,7 @@ const WidgetCard: React.FC<IProps> = ({ room, widgetId, onClose }) => {
contextMenu = ( contextMenu = (
<WidgetContextMenu <WidgetContextMenu
chevronFace={ChevronFace.None} chevronFace={ChevronFace.None}
right={window.innerWidth - rect.right - 12} right={UIStore.instance.windowWidth - rect.right - 12}
top={rect.bottom + 12} top={rect.bottom + 12}
onFinished={closeMenu} onFinished={closeMenu}
app={app} app={app}

View file

@ -36,6 +36,7 @@ import {Container, WidgetLayoutStore} from "../../../stores/widgets/WidgetLayout
import {clamp, percentageOf, percentageWithin} from "../../../utils/numbers"; import {clamp, percentageOf, percentageWithin} from "../../../utils/numbers";
import {useStateCallback} from "../../../hooks/useStateCallback"; import {useStateCallback} from "../../../hooks/useStateCallback";
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import UIStore from "../../../stores/UIStore";
@replaceableComponent("views.rooms.AppsDrawer") @replaceableComponent("views.rooms.AppsDrawer")
export default class AppsDrawer extends React.Component { 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. // Arbitrary defaults to avoid NaN problems. 100 px or 3/4 of the visible window.
if (!minHeight) minHeight = 100; 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. // Convert from percentage to height. Note that the default height is 280px.
if (defaultHeight) { if (defaultHeight) {

View file

@ -55,7 +55,6 @@ interface IProps {
onKeyDown: (ev: React.KeyboardEvent) => void; onKeyDown: (ev: React.KeyboardEvent) => void;
onFocus: (ev: React.FocusEvent) => void; onFocus: (ev: React.FocusEvent) => void;
onBlur: (ev: React.FocusEvent) => void; onBlur: (ev: React.FocusEvent) => void;
onResize: () => void;
resizeNotifier: ResizeNotifier; resizeNotifier: ResizeNotifier;
isMinimized: boolean; isMinimized: boolean;
activeSpace: Room; activeSpace: Room;
@ -404,9 +403,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
const newSublists = objectWithOnly(newLists, newListIds); const newSublists = objectWithOnly(newLists, newListIds);
const sublists = objectShallowClone(newSublists, (k, v) => arrayFastClone(v)); const sublists = objectShallowClone(newSublists, (k, v) => arrayFastClone(v));
this.setState({sublists, isNameFiltering}, () => { this.setState({sublists, isNameFiltering});
this.props.onResize();
});
} }
}; };
@ -537,7 +534,6 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
addRoomLabel={aesthetics.addRoomLabel ? _t(aesthetics.addRoomLabel) : aesthetics.addRoomLabel} addRoomLabel={aesthetics.addRoomLabel ? _t(aesthetics.addRoomLabel) : aesthetics.addRoomLabel}
addRoomContextMenu={aesthetics.addRoomContextMenu} addRoomContextMenu={aesthetics.addRoomContextMenu}
isMinimized={this.props.isMinimized} isMinimized={this.props.isMinimized}
onResize={this.props.onResize}
showSkeleton={showSkeleton} showSkeleton={showSkeleton}
extraTiles={extraTiles} extraTiles={extraTiles}
resizeNotifier={this.props.resizeNotifier} resizeNotifier={this.props.resizeNotifier}

View file

@ -74,7 +74,6 @@ interface IProps {
addRoomLabel: string; addRoomLabel: string;
isMinimized: boolean; isMinimized: boolean;
tagId: TagID; tagId: TagID;
onResize: () => void;
showSkeleton?: boolean; showSkeleton?: boolean;
alwaysVisible?: boolean; alwaysVisible?: boolean;
resizeNotifier: ResizeNotifier; resizeNotifier: ResizeNotifier;
@ -473,7 +472,6 @@ export default class RoomSublist extends React.Component<IProps, IState> {
private toggleCollapsed = () => { private toggleCollapsed = () => {
this.layout.isCollapsed = this.state.isExpanded; this.layout.isCollapsed = this.state.isExpanded;
this.setState({isExpanded: !this.layout.isCollapsed}); this.setState({isExpanded: !this.layout.isCollapsed});
setImmediate(() => this.props.onResize()); // needs to happen when the DOM is updated
}; };
private onHeaderKeyDown = (ev: React.KeyboardEvent) => { private onHeaderKeyDown = (ev: React.KeyboardEvent) => {

View file

@ -40,7 +40,7 @@ const STICKERPICKER_Z_INDEX = 3500;
const PERSISTED_ELEMENT_KEY = "stickerPicker"; const PERSISTED_ELEMENT_KEY = "stickerPicker";
@replaceableComponent("views.rooms.Stickerpicker") @replaceableComponent("views.rooms.Stickerpicker")
export default class Stickerpicker extends React.Component { export default class Stickerpicker extends React.PureComponent {
static currentWidget; static currentWidget;
constructor(props) { constructor(props) {
@ -341,22 +341,28 @@ export default class Stickerpicker extends React.Component {
* @param {Event} ev Event that triggered the function call * @param {Event} ev Event that triggered the function call
*/ */
_onHideStickersClick(ev) { _onHideStickersClick(ev) {
if (this.state.showStickers) {
this.setState({showStickers: false}); this.setState({showStickers: false});
} }
}
/** /**
* Called when the window is resized * Called when the window is resized
*/ */
_onResize() { _onResize() {
if (this.state.showStickers) {
this.setState({showStickers: false}); this.setState({showStickers: false});
} }
}
/** /**
* The stickers picker was hidden * The stickers picker was hidden
*/ */
_onFinished() { _onFinished() {
if (this.state.showStickers) {
this.setState({showStickers: false}); this.setState({showStickers: false});
} }
}
/** /**
* Launch the integration manager on the stickers integration page * Launch the integration manager on the stickers integration page

73
src/stores/UIStore.ts Normal file
View file

@ -0,0 +1,73 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import EventEmitter from "events";
import ResizeObserver from 'resize-observer-polyfill';
import ResizeObserverEntry from 'resize-observer-polyfill/src/ResizeObserverEntry';
export enum UI_EVENTS {
Resize = "resize"
}
export type ResizeObserverCallbackFunction = (entries: ResizeObserverEntry[]) => void;
export default class UIStore extends EventEmitter {
private static _instance: UIStore = null;
private resizeObserver: ResizeObserver;
public windowWidth: number;
public windowHeight: number;
constructor() {
super();
// 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);
this.resizeObserver.observe(document.body);
}
public static get instance(): UIStore {
if (!UIStore._instance) {
UIStore._instance = new UIStore();
}
return UIStore._instance;
}
public static destroy(): void {
if (UIStore._instance) {
UIStore._instance.resizeObserver.disconnect();
UIStore._instance.removeAllListeners();
UIStore._instance = null;
}
}
private resizeObserverCallback = (entries: ResizeObserverEntry[]) => {
const { width, height } = entries
.find(entry => entry.target === document.body)
.contentRect;
this.windowWidth = width;
this.windowHeight = height;
this.emit(UI_EVENTS.Resize, entries);
}
}

View file

@ -74,12 +74,6 @@ export default class ResizeNotifier extends EventEmitter {
// can be called in quick succession // can be called in quick succession
notifyWindowResized() { notifyWindowResized() {
// no need to throttle this one,
// also it could make scrollbars appear for
// a split second when the room list manual layout is now
// taller than the available space
this.emit("leftPanelResized");
this._updateMiddlePanel(); this._updateMiddlePanel();
} }
} }