Merge pull request #6092 from matrix-org/gsouquet/window-dimensions-reflow
This commit is contained in:
commit
1751b4ba43
20 changed files with 155 additions and 75 deletions
18
.eslintrc.js
18
.eslintrc.js
|
@ -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,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -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 = () => {
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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);
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -18,6 +18,7 @@ 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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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}
|
||||||
/>;
|
/>;
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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
73
src/stores/UIStore.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue