Switch widget resizing to re-resizable and add persistence

pull/21833/head
Michael Telatynski 2020-08-21 16:29:07 +01:00
parent ae65ed5c2e
commit cca5ccd79d
6 changed files with 124 additions and 69 deletions

View File

@ -15,16 +15,43 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
/*
Size settings
*/
$AppsDrawerMinHeight: 50px;
$AppsDrawerDefaultHeight: 300px;
$MiniAppTileHeight: 114px;
.mx_AppsDrawer {
margin: 5px;
display: block;
margin: 5px 5px 5px 18px;
position: relative;
display: flex;
flex-direction: column;
overflow: hidden;
.mx_RoomSublist_resizerHandles {
flex: 0 0 4px;
}
.mx_RoomSublist_resizerHandle {
cursor: ns-resize;
border-radius: 3px;
// Override styles from library
width: unset !important;
height: 4px !important;
// This is positioned directly below frame
position: absolute;
bottom: -8px !important; // override from library
// Together, these make the bar 64px wide
// These are also overridden from the library
left: calc(50% - 32px) !important;
right: calc(50% - 32px) !important;
}
&:hover {
.mx_RoomSublist_resizerHandle {
opacity: 0.8;
background: $primary-fg-color;
}
}
}
.mx_AppsDrawer_hidden {
@ -36,13 +63,13 @@ $MiniAppTileHeight: 114px;
flex-direction: row;
align-items: stretch;
justify-content: center;
min-height: $AppsDrawerMinHeight;
height: $AppsDrawerDefaultHeight;
height: 100%;
}
.mx_AppsDrawer_minimised .mx_AppsContainer {
min-height: inherit;
height: inherit;
// override the re-resizable inline styles
height: inherit !important;
min-height: inherit !important;
}
.mx_AddWidget_button {
@ -70,15 +97,14 @@ $MiniAppTileHeight: 114px;
.mx_AppTile {
max-width: 960px;
width: 50%;
margin-right: 5px;
border: 5px solid $widget-menu-bar-bg-color;
border-radius: 4px;
display: flex;
flex-direction: column;
}
.mx_AppTile:last-child {
margin-right: 1px;
& + .mx_AppTile {
margin-left: 5px;
}
}
.mx_AppTileFullWidth {
@ -105,7 +131,7 @@ $MiniAppTileHeight: 114px;
.mx_AppTile.mx_AppTile_minimised,
.mx_AppTileFullWidth.mx_AppTile_minimised,
.mx_AppTile_mini.mx_AppTile_minimised {
min-height: inherit;
height: 14px;
}
.mx_AppTile .mx_AppTile_persistedWrapper,
@ -117,7 +143,6 @@ $MiniAppTileHeight: 114px;
.mx_AppTile_persistedWrapper div {
width: 100%;
height: 100%;
min-width: 300px;
}
.mx_AppTileMenuBar {
@ -402,7 +427,7 @@ form.mx_Custom_Widget_Form div {
margin-right: auto;
}
.mx_AppsDrawer_minimised .mx_ResizeHandle {
.mx_AppsDrawer_minimised .mx_RoomSublist_resizerHandle {
display: none;
}

View File

@ -36,6 +36,10 @@ limitations under the License.
}
}
.mx_AppTile_persistedWrapper div {
min-width: 300px;
}
.mx_IncomingCallBox {
min-width: 250px;
background-color: $primary-bg-color;

View File

@ -1547,9 +1547,9 @@ export default createReactClass({
// header + footer + status + give us at least 120px of scrollback at all times.
let auxPanelMaxHeight = window.innerHeight -
(83 + // height of RoomHeader
(54 + // height of RoomHeader
36 + // height of the status area
72 + // minimum height of the message compmoser
51 + // minimum height of the message compmoser
120); // amount of desired scrollback
// XXX: this is a bit of a hack and might possibly cause the video to push out the page anyway

View File

@ -17,7 +17,7 @@ limitations under the License.
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import {throttle} from "lodash";
import ResizeObserver from 'resize-observer-polyfill';
import dis from '../../../dispatcher/dispatcher';
@ -156,7 +156,7 @@ export default class PersistedElement extends React.Component {
child.style.display = visible ? 'block' : 'none';
}
updateChildPosition(child, parent) {
updateChildPosition = throttle((child, parent) => {
if (!child || !parent) return;
const parentRect = parent.getBoundingClientRect();
@ -167,9 +167,9 @@ export default class PersistedElement extends React.Component {
width: parentRect.width + 'px',
height: parentRect.height + 'px',
});
}
}, 100, {trailing: true, leading: true});
render() {
return <div ref={this.collectChildContainer}></div>;
return <div ref={this.collectChildContainer} />;
}
}

View File

@ -31,8 +31,8 @@ import AccessibleButton from '../elements/AccessibleButton';
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
import SettingsStore from "../../../settings/SettingsStore";
import classNames from 'classnames';
import ResizeHandle from '../elements/ResizeHandle';
import {Resizer, FixedDistributor} from '../../../resizer';
import {Resizable} from "re-resizable";
import {useLocalStorageState} from "../../../hooks/useLocalStorage";
// The maximum number of widgets that can be added in a room
const MAX_WIDGETS = 2;
@ -63,7 +63,6 @@ export default createReactClass({
MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents);
WidgetEchoStore.on('update', this._updateApps);
this.dispatcherRef = dis.register(this.onAction);
this._createResizer();
},
componentWillUnmount: function() {
@ -73,10 +72,6 @@ export default createReactClass({
}
WidgetEchoStore.removeListener('update', this._updateApps);
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
if (this.resizer) {
this.resizer.detach();
this.resizer = null;
}
},
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
@ -162,30 +157,6 @@ export default createReactClass({
this._launchManageIntegrations();
},
_createResizer: function() {
if (!this.resizeContainer) {
return;
}
const classNames = {
handle: "mx_ResizeHandle",
vertical: "mx_ResizeHandle_vertical",
resizing: "mx_AppsDrawer_resizing",
};
const resizer = new Resizer(
this.resizeContainer,
FixedDistributor,
{},
);
resizer.setClassNames(classNames);
resizer.attach();
this.resizer = resizer;
},
_setResizeContainerRef: function(div) {
this.resizeContainer = div;
},
render: function() {
const apps = this.state.apps.map((app, index, arr) => {
const capWhitelist = WidgetUtils.getCapWhitelistForAppTypeInRoomId(app.type, this.props.room.roomId);
@ -193,7 +164,7 @@ export default createReactClass({
return (<AppTile
key={app.id}
app={app}
fullWidth={arr.length<2 ? true : false}
fullWidth={arr.length < 2}
room={this.props.room}
userId={this.props.userId}
show={this.props.showApps}
@ -204,8 +175,8 @@ export default createReactClass({
/>);
});
if (apps.length == 0) {
return <div></div>;
if (apps.length === 0) {
return <div />;
}
let addWidget;
@ -223,13 +194,6 @@ export default createReactClass({
</AccessibleButton>;
}
const containerStyle = {
maxHeight: Math.max(this.props.maxHeight - 50, 300),
};
if (!this.props.showApps && this.resizer) {
this.resizer.forHandleAt(0).item.clearSize();
}
let spinner;
if (
apps.length === 0 && WidgetEchoStore.roomHasPendingWidgets(
@ -249,14 +213,39 @@ export default createReactClass({
});
return (
<div className={classes} ref={this._setResizeContainerRef}>
<div id='apps' className='mx_AppsContainer' style={containerStyle}>
<div className={classes}>
<PersistentVResizer
id={"apps-drawer_" + this.props.room.roomId}
minHeight={100}
maxHeight={this.props.maxHeight - 50}
handleWrapperClass="mx_RoomSublist_resizerHandles"
handleClass="mx_RoomSublist_resizerHandle"
className="mx_AppsContainer"
>
{ apps }
{ spinner }
</div>
<ResizeHandle vertical={true} />
</PersistentVResizer>
{ this._canUserModify() && addWidget }
</div>
);
},
});
const PersistentVResizer = ({id, minHeight, maxHeight, className, handleWrapperClass, handleClass, children}) => {
const [height, setHeight] = useLocalStorageState("pvr_" + id, 100);
return <Resizable
size={{height: Math.min(height, maxHeight)}}
minHeight={minHeight}
maxHeight={maxHeight}
onResizeStop={(e, dir, ref, d) => {
setHeight(height + d.height);
}}
handleWrapperClass={handleWrapperClass}
handleClasses={{bottom: handleClass}}
className={className}
enable={{bottom: true}}
>
{ children }
</Resizable>;
};

View File

@ -0,0 +1,37 @@
/*
Copyright 2019 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 {useEffect, useRef, useState} from "react";
// Hook behaving like useState but persisting the value to localStorage. Returns same as useState
export const useLocalStorageState = (key: string, initialValue: boolean) => {
const lsKey = useRef("useLocalStorageState_" + key).current;
const [value, setValue] = useState(() => {
try {
const item = window.localStorage.getItem(lsKey);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
return initialValue;
}
});
useEffect(() => {
window.localStorage.setItem(lsKey, JSON.stringify(value));
}, [lsKey, value]);
return [value, setValue];
};