First cut of app drawer tiled resizing

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2020-10-13 12:29:12 +01:00
parent 1a45d18b94
commit 7be5ff0fe6
3 changed files with 138 additions and 21 deletions

View file

@ -50,8 +50,8 @@ $MiniAppTileHeight: 200px;
} }
} }
.mx_AppsDrawer_hidden { .mx_AppsContainer_resizer {
display: none; margin-bottom: 8px;
} }
.mx_AppsContainer { .mx_AppsContainer {
@ -60,7 +60,9 @@ $MiniAppTileHeight: 200px;
align-items: stretch; align-items: stretch;
justify-content: center; justify-content: center;
height: 100%; height: 100%;
margin-bottom: 8px; width: 100%;
flex: 1;
min-height: 0;
.mx_AppTile:first-of-type { .mx_AppTile:first-of-type {
border-left-width: 8px; border-left-width: 8px;
@ -70,25 +72,52 @@ $MiniAppTileHeight: 200px;
border-right-width: 8px; border-right-width: 8px;
border-radius: 0 10px 10px 0; border-radius: 0 10px 10px 0;
} }
.mx_ResizeHandle_horizontal {
position: relative;
> div {
width: 0;
}
&:hover::before {
position: absolute;
left: 3px;
top: 50%;
transform: translate(0, -50%);
height: 64px; // to match width of the ones on roomlist
width: 4px;
border-radius: 4px;
content: ' ';
background-color: $primary-fg-color;
opacity: 0.8;
}
}
} }
.mx_AppsDrawer_minimised .mx_AppsContainer { .mx_AppsDrawer_has2 .mx_AppTile {
// override the re-resizable inline styles width: 50%;
height: inherit !important;
min-height: inherit !important;
}
.mx_AddWidget_button { &:nth-child(3) {
order: 2; flex-grow: 1;
cursor: pointer; width: 0;
padding: 0; }
margin: -3px auto 5px 0; }
color: $accent-color; .mx_AppsDrawer_has3 .mx_AppTile {
font-size: $font-12px; width: 33%;
&:nth-child(3) {
flex-grow: 1;
width: 0;
}
} }
.mx_AppTile { .mx_AppTile {
width: 50%; width: 50%;
min-width: 200px;
border: 8px solid $widget-menu-bar-bg-color; border: 8px solid $widget-menu-bar-bg-color;
border-left-width: 5px; border-left-width: 5px;
border-right-width: 5px; border-right-width: 5px;
@ -102,7 +131,7 @@ $MiniAppTileHeight: 200px;
} }
.mx_AppTileFullWidth { .mx_AppTileFullWidth {
width: 100%; width: 100% !important; // to override the inline style set by the resizer
margin: 0; margin: 0;
padding: 0; padding: 0;
border: 5px solid $widget-menu-bar-bg-color; border: 5px solid $widget-menu-bar-bg-color;

View file

@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, {useState} from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import {Resizable} from "re-resizable"; import {Resizable} from "re-resizable";
@ -31,6 +31,9 @@ import SettingsStore from "../../../settings/SettingsStore";
import {useLocalStorageState} from "../../../hooks/useLocalStorageState"; import {useLocalStorageState} from "../../../hooks/useLocalStorageState";
import ResizeNotifier from "../../../utils/ResizeNotifier"; import ResizeNotifier from "../../../utils/ResizeNotifier";
import WidgetStore from "../../../stores/WidgetStore"; import WidgetStore from "../../../stores/WidgetStore";
import ResizeHandle from "../elements/ResizeHandle";
import Resizer from "../../../resizer/resizer";
import PercentageDistributor from "../../../resizer/distributors/percentage";
export default class AppsDrawer extends React.Component { export default class AppsDrawer extends React.Component {
static propTypes = { static propTypes = {
@ -50,6 +53,9 @@ export default class AppsDrawer extends React.Component {
this.state = { this.state = {
apps: this._getApps(), apps: this._getApps(),
}; };
this._resizeContainer = null;
this.resizer = this._createResizer();
} }
componentDidMount() { componentDidMount() {
@ -62,6 +68,9 @@ export default class AppsDrawer extends React.Component {
ScalarMessaging.stopListening(); ScalarMessaging.stopListening();
WidgetStore.instance.off(this.props.room.roomId, this._updateApps); WidgetStore.instance.off(this.props.room.roomId, this._updateApps);
if (this.dispatcherRef) dis.unregister(this.dispatcherRef); if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
if (this._resizeContainer) {
this.resizer.detach();
}
} }
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event // TODO: [REACT-WARNING] Replace with appropriate lifecycle event
@ -71,6 +80,70 @@ export default class AppsDrawer extends React.Component {
this._updateApps(); this._updateApps();
} }
_createResizer() {
const classNames = {
handle: "mx_ResizeHandle",
vertical: "mx_ResizeHandle_vertical",
reverse: "mx_ResizeHandle_reverse",
};
const collapseConfig = {
onResizeStart: () => {
this._resizeContainer.classList.add("mx_AppsDrawer_resizing");
},
onResizeStop: () => {
this._resizeContainer.classList.remove("mx_AppsDrawer_resizing");
// persist to localStorage
localStorage.setItem(this._getStorageKey(), JSON.stringify([
this._getIdString(),
...this.state.apps.slice(1).map((_, i) => this.resizer.forHandleAt(i).size),
]));
},
};
// pass a truthy container for now, we won't call attach until we update it
const resizer = new Resizer({}, PercentageDistributor, collapseConfig);
resizer.setClassNames(classNames);
return resizer;
}
_collectResizer = (ref) => {
console.log("@@ _collectResizer");
if (this._resizeContainer) {
this.resizer.detach();
}
if (ref) {
this.resizer.container = ref;
this.resizer.attach();
}
this._resizeContainer = ref;
this._loadResizerPreferences();
};
_getStorageKey = () => `mx_apps_drawer-${this.props.room.roomId}`;
_getIdString = () => this.state.apps.map(app => app.id).join("~");
_loadResizerPreferences = () => { // TODO call this when changing pinned apps
console.log("@@ _loadResizerPreferences");
try {
const [idString, ...sizes] = JSON.parse(localStorage.getItem(this._getStorageKey()));
// format: [idString: string, ...percentages: string];
if (this._getIdString() !== idString) return;
sizes.forEach((size, i) => {
const distributor = this.resizer.forHandleAt(i);
distributor.size = size;
distributor.finish();
});
} catch (e) {
console.error(e);
this.state.apps.slice(1).forEach((_, i) => {
const distributor = this.resizer.forHandleAt(i);
distributor.item.clearSize();
distributor.finish();
});
}
};
onAction = (action) => { onAction = (action) => {
const hideWidgetKey = this.props.room.roomId + '_hide_widget_drawer'; const hideWidgetKey = this.props.room.roomId + '_hide_widget_drawer';
switch (action.action) { switch (action.action) {
@ -94,7 +167,7 @@ export default class AppsDrawer extends React.Component {
_updateApps = () => { _updateApps = () => {
this.setState({ this.setState({
apps: this._getApps(), apps: this._getApps(),
}); }, this._loadResizerPreferences);
}; };
_launchManageIntegrations() { _launchManageIntegrations() {
@ -147,6 +220,9 @@ export default class AppsDrawer extends React.Component {
mx_AppsDrawer: true, mx_AppsDrawer: true,
mx_AppsDrawer_fullWidth: apps.length < 2, mx_AppsDrawer_fullWidth: apps.length < 2,
mx_AppsDrawer_resizing: this.state.resizing, mx_AppsDrawer_resizing: this.state.resizing,
mx_AppsDrawer_has1: apps.length === 1,
mx_AppsDrawer_has2: apps.length === 2,
mx_AppsDrawer_has3: apps.length === 3,
}); });
return ( return (
@ -156,13 +232,21 @@ export default class AppsDrawer extends React.Component {
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"
className="mx_AppsContainer" className="mx_AppsContainer_resizer"
resizeNotifier={this.props.resizeNotifier} resizeNotifier={this.props.resizeNotifier}
setResizing={this.setResizing} setResizing={this.setResizing}
> >
{ apps } <div className="mx_AppsContainer" ref={this._collectResizer}>
{ spinner } { apps.map((app, i) => {
if (i < 1) return app;
return <React.Fragment key={app.key}>
<ResizeHandle reverse={i > apps.length / 2} />
{ app }
</React.Fragment>;
}) }
</div>
</PersistentVResizer> </PersistentVResizer>
{ spinner }
</div> </div>
); );
} }

View file

@ -48,6 +48,10 @@ interface IRoomWidgets {
export const MAX_PINNED = 3; export const MAX_PINNED = 3;
// TODO change order to be order that they were pinned
// TODO HARD cap at 3, truncating if needed
// TODO call finish more proactively to lock things in
// TODO consolidate WidgetEchoStore into this // TODO consolidate WidgetEchoStore into this
// TODO consolidate ActiveWidgetStore into this // TODO consolidate ActiveWidgetStore into this
export default class WidgetStore extends AsyncStoreWithClient<IState> { export default class WidgetStore extends AsyncStoreWithClient<IState> {