First cut of app drawer tiled resizing
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
1a45d18b94
commit
7be5ff0fe6
3 changed files with 138 additions and 21 deletions
|
@ -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;
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
Loading…
Reference in a new issue