From 0d29d15a464eab3a66278c963026033ca8be9f1d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 18 Jan 2021 19:27:11 -0700 Subject: [PATCH] Support room-defined height as well Much like widget widths, it is acceptable for us to forget what everyone's height was previously at. --- src/components/views/rooms/AppsDrawer.js | 26 ++++++++++-- src/hooks/useStateCallback.ts | 28 +++++++++++++ src/stores/widgets/WidgetLayoutStore.ts | 50 +++++++++++++++++++----- src/utils/numbers.ts | 8 ++++ 4 files changed, 98 insertions(+), 14 deletions(-) create mode 100644 src/hooks/useStateCallback.ts diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index 5982c52d98..5efcefc4bc 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -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 (
{ - 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 (initialValue: T, callback: (v: T) => void): [T, Dispatch>] => { + const [value, setValue] = useState(initialValue); + const interceptSetValue = (newVal: T) => { + setValue(newVal); + callback(newVal); + }; + return [value, interceptSetValue]; +}; diff --git a/src/stores/widgets/WidgetLayoutStore.ts b/src/stores/widgets/WidgetLayoutStore.ts index 7d22f6729e..12051d35bc 100644 --- a/src/stores/widgets/WidgetLayoutStore.ts +++ b/src/stores/widgets/WidgetLayoutStore.ts @@ -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,9 +251,11 @@ export class WidgetLayoutStore extends ReadyWatchingStore { widths.push(100); // we'll figure this out later } - 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)); + 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) { @@ -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, }); } } diff --git a/src/utils/numbers.ts b/src/utils/numbers.ts index e26db0d5aa..c8b9e7248f 100644 --- a/src/utils/numbers.ts +++ b/src/utils/numbers.ts @@ -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; +}