Switch widget resizing to re-resizable and add persistence
parent
ae65ed5c2e
commit
cca5ccd79d
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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} />;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
};
|
||||
|
|
|
@ -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];
|
||||
};
|
Loading…
Reference in New Issue