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 {IntegrationManagers} from "../../../integrations/IntegrationManagers";
import SettingsStore from "../../../settings/SettingsStore";
import {useLocalStorageState} from "../../../hooks/useLocalStorageState";
import ResizeNotifier from "../../../utils/ResizeNotifier";
import ResizeHandle from "../elements/ResizeHandle";
import Resizer from "../../../resizer/resizer";
import PercentageDistributor from "../../../resizer/distributors/percentage";
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 {
static propTypes = {
@ -237,7 +238,7 @@ export default class AppsDrawer extends React.Component {
return (
<div className={classes}>
<PersistentVResizer
id={"apps-drawer_" + this.props.room.roomId}
room={this.props.room}
minHeight={100}
maxHeight={this.props.maxHeight ? this.props.maxHeight - 50 : undefined}
handleClass="mx_AppsContainer_resizerHandle"
@ -261,7 +262,7 @@ export default class AppsDrawer extends React.Component {
}
const PersistentVResizer = ({
id,
room,
minHeight,
maxHeight,
className,
@ -270,7 +271,24 @@ const PersistentVResizer = ({
resizeNotifier,
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
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.
}
interface IWidgetLayouts {
[widgetId: string]: IStoredLayout;
}
interface ILayoutStateEvent {
// TODO: [Deferred] Forced layout (fixed with no changes)
// The widget layouts.
widgets: {
[widgetId: string]: IStoredLayout;
};
widgets: IWidgetLayouts;
}
interface ILayoutSettings extends ILayoutStateEvent {
@ -79,8 +81,11 @@ interface ILayoutSettings extends ILayoutStateEvent {
// Dev note: "Pinned" widgets are ones in the top container.
const MAX_PINNED = 3;
const MIN_WIDGET_WIDTH_PCT = 10; // Don't make anything smaller than 10% width
const MIN_WIDGET_HEIGHT_PCT = 20;
// These two are whole percentages and don't really mean anything. Later values will decide
// 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 {
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)
const widths: number[] = [];
let maxHeight = 0;
let maxHeight = null; // null == default
let doAutobalance = true;
for (let i = 0; i < topWidgets.length; i++) {
const widget = topWidgets[i];
@ -246,10 +251,12 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
widths.push(100); // we'll figure this out later
}
if (widgetLayout?.height || userWidgetLayout?.height) {
const defRoomHeight = defaultNumber(widgetLayout?.height, MIN_WIDGET_HEIGHT_PCT);
const h = defaultNumber(userWidgetLayout?.height, defRoomHeight);
maxHeight = Math.max(maxHeight, clamp(h, MIN_WIDGET_HEIGHT_PCT, 100));
}
}
let remainingWidth = 100;
for (const width of widths) {
remainingWidth -= width;
@ -330,12 +337,35 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
localLayout[w.id] = {
width: numbers[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, "");
SettingsStore.setValue("Widgets.layout", room.roomId, SettingLevel.ROOM_ACCOUNT, {
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 {
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;
}