Support room-defined height as well

Much like widget widths, it is acceptable for us to forget what everyone's height was previously at.
This commit is contained in:
Travis Ralston 2021-01-18 19:27:11 -07:00
parent cfb583d193
commit 0d29d15a46
4 changed files with 98 additions and 14 deletions

View file

@ -28,12 +28,13 @@ import WidgetUtils from '../../../utils/WidgetUtils';
import WidgetEchoStore from "../../../stores/WidgetEchoStore"; import WidgetEchoStore from "../../../stores/WidgetEchoStore";
import {IntegrationManagers} from "../../../integrations/IntegrationManagers"; import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import {useLocalStorageState} from "../../../hooks/useLocalStorageState";
import ResizeNotifier from "../../../utils/ResizeNotifier"; import ResizeNotifier from "../../../utils/ResizeNotifier";
import ResizeHandle from "../elements/ResizeHandle"; import ResizeHandle from "../elements/ResizeHandle";
import Resizer from "../../../resizer/resizer"; import Resizer from "../../../resizer/resizer";
import PercentageDistributor from "../../../resizer/distributors/percentage"; import PercentageDistributor from "../../../resizer/distributors/percentage";
import {Container, WidgetLayoutStore} from "../../../stores/widgets/WidgetLayoutStore"; import {Container, WidgetLayoutStore} from "../../../stores/widgets/WidgetLayoutStore";
import {clamp, percentageOf, percentageWithin} from "../../../utils/numbers";
import {useStateCallback} from "../../../hooks/useStateCallback";
export default class AppsDrawer extends React.Component { export default class AppsDrawer extends React.Component {
static propTypes = { static propTypes = {
@ -237,7 +238,7 @@ export default class AppsDrawer extends React.Component {
return ( return (
<div className={classes}> <div className={classes}>
<PersistentVResizer <PersistentVResizer
id={"apps-drawer_" + this.props.room.roomId} room={this.props.room}
minHeight={100} minHeight={100}
maxHeight={this.props.maxHeight ? this.props.maxHeight - 50 : undefined} maxHeight={this.props.maxHeight ? this.props.maxHeight - 50 : undefined}
handleClass="mx_AppsContainer_resizerHandle" handleClass="mx_AppsContainer_resizerHandle"
@ -261,7 +262,7 @@ export default class AppsDrawer extends React.Component {
} }
const PersistentVResizer = ({ const PersistentVResizer = ({
id, room,
minHeight, minHeight,
maxHeight, maxHeight,
className, className,
@ -270,7 +271,24 @@ const PersistentVResizer = ({
resizeNotifier, resizeNotifier,
children, children,
}) => { }) => {
const [height, setHeight] = useLocalStorageState("pvr_" + id, 280); // old fixed height was 273px let defaultHeight = WidgetLayoutStore.instance.getContainerHeight(room, Container.Top);
// 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;
// Convert from percentage to height. Note that the default height is 280px.
if (defaultHeight) {
defaultHeight = clamp(defaultHeight, 0, 100);
defaultHeight = percentageWithin(defaultHeight / 100, minHeight, maxHeight);
} else {
defaultHeight = 280;
}
const [height, setHeight] = useStateCallback(defaultHeight, newHeight => {
newHeight = percentageOf(newHeight, minHeight, maxHeight) * 100;
WidgetLayoutStore.instance.setContainerHeight(room, Container.Top, newHeight)
});
return <Resizable return <Resizable
size={{height: Math.min(height, maxHeight)}} size={{height: Math.min(height, maxHeight)}}

View file

@ -0,0 +1,28 @@
/*
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 {Dispatch, SetStateAction, useState} from "react";
// Hook to simplify interactions with a store-backed state values
// Returns value and method to change the state value
export const useStateCallback = <T>(initialValue: T, callback: (v: T) => void): [T, Dispatch<SetStateAction<T>>] => {
const [value, setValue] = useState(initialValue);
const interceptSetValue = (newVal: T) => {
setValue(newVal);
callback(newVal);
};
return [value, interceptSetValue];
};

View file

@ -63,13 +63,15 @@ interface IStoredLayout {
// TODO: [Deferred] Maximizing (fullscreen) widgets by default. // TODO: [Deferred] Maximizing (fullscreen) widgets by default.
} }
interface IWidgetLayouts {
[widgetId: string]: IStoredLayout;
}
interface ILayoutStateEvent { interface ILayoutStateEvent {
// TODO: [Deferred] Forced layout (fixed with no changes) // TODO: [Deferred] Forced layout (fixed with no changes)
// The widget layouts. // The widget layouts.
widgets: { widgets: IWidgetLayouts;
[widgetId: string]: IStoredLayout;
};
} }
interface ILayoutSettings extends ILayoutStateEvent { interface ILayoutSettings extends ILayoutStateEvent {
@ -79,8 +81,11 @@ interface ILayoutSettings extends ILayoutStateEvent {
// Dev note: "Pinned" widgets are ones in the top container. // Dev note: "Pinned" widgets are ones in the top container.
const MAX_PINNED = 3; const MAX_PINNED = 3;
const MIN_WIDGET_WIDTH_PCT = 10; // Don't make anything smaller than 10% width // These two are whole percentages and don't really mean anything. Later values will decide
const MIN_WIDGET_HEIGHT_PCT = 20; // minimum, but these help determine proportions during our calculations here. In fact, these
// values should be *smaller* than the actual minimums imposed by later components.
const MIN_WIDGET_WIDTH_PCT = 10; // 10%
const MIN_WIDGET_HEIGHT_PCT = 2; // 2%
export class WidgetLayoutStore extends ReadyWatchingStore { export class WidgetLayoutStore extends ReadyWatchingStore {
private static internalInstance: WidgetLayoutStore; private static internalInstance: WidgetLayoutStore;
@ -230,7 +235,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
// Determine width distribution and height of the top container now (the only relevant one) // Determine width distribution and height of the top container now (the only relevant one)
const widths: number[] = []; const widths: number[] = [];
let maxHeight = 0; let maxHeight = null; // null == default
let doAutobalance = true; let doAutobalance = true;
for (let i = 0; i < topWidgets.length; i++) { for (let i = 0; i < topWidgets.length; i++) {
const widget = topWidgets[i]; const widget = topWidgets[i];
@ -246,9 +251,11 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
widths.push(100); // we'll figure this out later widths.push(100); // we'll figure this out later
} }
const defRoomHeight = defaultNumber(widgetLayout?.height, MIN_WIDGET_HEIGHT_PCT); if (widgetLayout?.height || userWidgetLayout?.height) {
const h = defaultNumber(userWidgetLayout?.height, defRoomHeight); const defRoomHeight = defaultNumber(widgetLayout?.height, MIN_WIDGET_HEIGHT_PCT);
maxHeight = Math.max(maxHeight, clamp(h, MIN_WIDGET_HEIGHT_PCT, 100)); const h = defaultNumber(userWidgetLayout?.height, defRoomHeight);
maxHeight = Math.max(maxHeight, clamp(h, MIN_WIDGET_HEIGHT_PCT, 100));
}
} }
let remainingWidth = 100; let remainingWidth = 100;
for (const width of widths) { for (const width of widths) {
@ -330,12 +337,35 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
localLayout[w.id] = { localLayout[w.id] = {
width: numbers[i], width: numbers[i],
index: i, index: i,
height: this.byRoom[room.roomId]?.[container]?.height || MIN_WIDGET_HEIGHT_PCT,
}; };
}); });
this.updateUserLayout(room, localLayout);
}
public getContainerHeight(room: Room, container: Container): number {
return this.byRoom[room.roomId]?.[container]?.height; // let the default get returned if needed
}
public setContainerHeight(room: Room, container: Container, height: number) {
const widgets = this.getContainerWidgets(room, container);
const widths = this.byRoom[room.roomId]?.[container]?.distributions;
const localLayout = {};
widgets.forEach((w, i) => {
localLayout[w.id] = {
width: widths[i],
index: i,
height: height,
};
});
this.updateUserLayout(room, localLayout);
}
private updateUserLayout(room: Room, newLayout: IWidgetLayouts) {
const layoutEv = room.currentState.getStateEvents(WIDGET_LAYOUT_EVENT_TYPE, ""); const layoutEv = room.currentState.getStateEvents(WIDGET_LAYOUT_EVENT_TYPE, "");
SettingsStore.setValue("Widgets.layout", room.roomId, SettingLevel.ROOM_ACCOUNT, { SettingsStore.setValue("Widgets.layout", room.roomId, SettingLevel.ROOM_ACCOUNT, {
overrides: layoutEv?.getId(), overrides: layoutEv?.getId(),
widgets: localLayout, widgets: newLayout,
}); });
} }
} }

View file

@ -32,3 +32,11 @@ export function clamp(i: number, min: number, max: number): number {
export function sum(...i: number[]): number { export function sum(...i: number[]): number {
return [...i].reduce((p, c) => c + p, 0); return [...i].reduce((p, c) => c + p, 0);
} }
export function percentageWithin(pct: number, min: number, max: number): number {
return (pct * (max - min)) + min;
}
export function percentageOf(val: number, min: number, max: number): number {
return (val - min) / max;
}