You've already forked matrix-react-sdk
							
							
				mirror of
				https://github.com/matrix-org/matrix-react-sdk.git
				synced 2025-11-03 00:33:22 +03:00 
			
		
		
		
	Merge pull request #5138 from matrix-org/t3chguy/dpsah/6785
Allow persistent resizing of the widget app drawer
This commit is contained in:
		@@ -15,18 +15,39 @@ See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
the tile title bar is 5 (top border) + 12 (title, buttons) + 5 (bottom padding) px = 22px
 | 
			
		||||
the body is assumed to be 300px (assumed by at least the sticker pickerm, perhaps elsewhere),
 | 
			
		||||
so the body height would be 300px - 22px (room for title bar) = 278px
 | 
			
		||||
BUT! the sticker picker also assumes it's a little less high than that because the iframe
 | 
			
		||||
for the sticker picker doesn't have any padding or margin on it's bottom.
 | 
			
		||||
so subtracking another 5px, which brings us at 273px.
 | 
			
		||||
*/
 | 
			
		||||
$AppsDrawerBodyHeight: 273px;
 | 
			
		||||
$MiniAppTileHeight: 114px;
 | 
			
		||||
 | 
			
		||||
.mx_AppsDrawer {
 | 
			
		||||
    margin: 5px;
 | 
			
		||||
    margin: 5px 5px 5px 18px;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
 | 
			
		||||
    .mx_AppsContainer_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_AppsContainer_resizerHandle {
 | 
			
		||||
            opacity: 0.8;
 | 
			
		||||
            background: $primary-fg-color;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_AppsDrawer_hidden {
 | 
			
		||||
@@ -36,15 +57,23 @@ $AppsDrawerBodyHeight: 273px;
 | 
			
		||||
.mx_AppsContainer {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    align-items: stretch;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    margin-bottom: 8px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_AppsDrawer_minimised .mx_AppsContainer {
 | 
			
		||||
    // override the re-resizable inline styles
 | 
			
		||||
    height: inherit !important;
 | 
			
		||||
    min-height: inherit !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_AddWidget_button {
 | 
			
		||||
    order: 2;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    margin: 5px auto 5px auto;
 | 
			
		||||
    margin: -3px auto 5px 0;
 | 
			
		||||
    color: $accent-color;
 | 
			
		||||
    font-size: $font-12px;
 | 
			
		||||
}
 | 
			
		||||
@@ -65,40 +94,52 @@ $AppsDrawerBodyHeight: 273px;
 | 
			
		||||
.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 {
 | 
			
		||||
    max-width: 960px;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    border: 5px solid $widget-menu-bar-bg-color;
 | 
			
		||||
    border-radius: 8px;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_AppTile_mini {
 | 
			
		||||
    max-width: 960px;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    height: $MiniAppTileHeight;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_AppTile_persistedWrapper {
 | 
			
		||||
    height: $AppsDrawerBodyHeight;
 | 
			
		||||
.mx_AppTile.mx_AppTile_minimised,
 | 
			
		||||
.mx_AppTileFullWidth.mx_AppTile_minimised,
 | 
			
		||||
.mx_AppTile_mini.mx_AppTile_minimised {
 | 
			
		||||
    height: 14px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_AppTile .mx_AppTile_persistedWrapper,
 | 
			
		||||
.mx_AppTileFullWidth .mx_AppTile_persistedWrapper,
 | 
			
		||||
.mx_AppTile_mini .mx_AppTile_persistedWrapper {
 | 
			
		||||
    height: 114px;
 | 
			
		||||
    min-width: 300px;
 | 
			
		||||
    flex: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_AppTile_persistedWrapper div {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_AppTileMenuBar {
 | 
			
		||||
@@ -110,6 +151,7 @@ $AppsDrawerBodyHeight: 273px;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_AppTileMenuBar_expanded {
 | 
			
		||||
@@ -172,7 +214,7 @@ $AppsDrawerBodyHeight: 273px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_AppTileBody {
 | 
			
		||||
    height: $AppsDrawerBodyHeight;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
@@ -183,6 +225,13 @@ $AppsDrawerBodyHeight: 273px;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_AppTile .mx_AppTileBody,
 | 
			
		||||
.mx_AppTileFullWidth .mx_AppTileBody,
 | 
			
		||||
.mx_AppTile_mini .mx_AppTileBody_mini {
 | 
			
		||||
    height: inherit;
 | 
			
		||||
    flex: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_AppTileBody_mini iframe {
 | 
			
		||||
    border: none;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
@@ -191,7 +240,7 @@ $AppsDrawerBodyHeight: 273px;
 | 
			
		||||
 | 
			
		||||
.mx_AppTileBody iframe {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: $AppsDrawerBodyHeight;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    border: none;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
@@ -331,7 +380,7 @@ form.mx_Custom_Widget_Form div {
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    height: $AppsDrawerBodyHeight;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_AppLoading .mx_Spinner {
 | 
			
		||||
@@ -358,3 +407,16 @@ form.mx_Custom_Widget_Form div {
 | 
			
		||||
.mx_AppLoading iframe {
 | 
			
		||||
    display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_AppsDrawer_minimised .mx_AppsContainer_resizerHandle {
 | 
			
		||||
    display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Avoid apptile iframes capturing mouse event focus when resizing */
 | 
			
		||||
.mx_AppsDrawer_resizing iframe {
 | 
			
		||||
    pointer-events: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_AppsDrawer_resizing .mx_AppTile_persistedWrapper {
 | 
			
		||||
    z-index: 1;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -1322,7 +1322,7 @@ export default class GroupView extends React.Component {
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <GroupHeaderButtons />
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <MainSplit panel={rightPanel}>
 | 
			
		||||
                    <MainSplit panel={rightPanel} resizeNotifier={this.props.resizeNotifier}>
 | 
			
		||||
                        <AutoHideScrollbar className="mx_GroupView_body">
 | 
			
		||||
                            { this._getMembershipSection() }
 | 
			
		||||
                            { this._getGroupSection() }
 | 
			
		||||
 
 | 
			
		||||
@@ -257,6 +257,12 @@ class LoggedInView extends React.Component<IProps, IState> {
 | 
			
		||||
                window.localStorage.setItem("mx_lhs_size", '' + size);
 | 
			
		||||
                this.props.resizeNotifier.notifyLeftHandleResized();
 | 
			
		||||
            },
 | 
			
		||||
            onResizeStart: () => {
 | 
			
		||||
                this.props.resizeNotifier.startResizing();
 | 
			
		||||
            },
 | 
			
		||||
            onResizeStop: () => {
 | 
			
		||||
                this.props.resizeNotifier.stopResizing();
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
        const resizer = new Resizer(
 | 
			
		||||
            this._resizeContainer.current,
 | 
			
		||||
@@ -650,12 +656,13 @@ class LoggedInView extends React.Component<IProps, IState> {
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case PageTypes.UserView:
 | 
			
		||||
                pageElement = <UserView userId={this.props.currentUserId} />;
 | 
			
		||||
                pageElement = <UserView userId={this.props.currentUserId} resizeNotifier={this.props.resizeNotifier} />;
 | 
			
		||||
                break;
 | 
			
		||||
            case PageTypes.GroupView:
 | 
			
		||||
                pageElement = <GroupView
 | 
			
		||||
                    groupId={this.props.currentGroupId}
 | 
			
		||||
                    isNew={this.props.currentGroupIsNew}
 | 
			
		||||
                    resizeNotifier={this.props.resizeNotifier}
 | 
			
		||||
                />;
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -19,9 +19,18 @@ import React from 'react';
 | 
			
		||||
import { Resizable } from 're-resizable';
 | 
			
		||||
 | 
			
		||||
export default class MainSplit extends React.Component {
 | 
			
		||||
    _onResized = (event, direction, refToElement, delta) => {
 | 
			
		||||
    _onResizeStart = () => {
 | 
			
		||||
        this.props.resizeNotifier.startResizing();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    _onResize = () => {
 | 
			
		||||
        this.props.resizeNotifier.notifyRightHandleResized();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    _onResizeStop = (event, direction, refToElement, delta) => {
 | 
			
		||||
        this.props.resizeNotifier.stopResizing();
 | 
			
		||||
        window.localStorage.setItem("mx_rhs_size", this._loadSidePanelSize().width + delta.width);
 | 
			
		||||
    }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    _loadSidePanelSize() {
 | 
			
		||||
        let rhsSize = parseInt(window.localStorage.getItem("mx_rhs_size"), 10);
 | 
			
		||||
@@ -58,7 +67,9 @@ export default class MainSplit extends React.Component {
 | 
			
		||||
                    bottomLeft: false,
 | 
			
		||||
                    topLeft: false,
 | 
			
		||||
                }}
 | 
			
		||||
                onResizeStop={this._onResized}
 | 
			
		||||
                onResizeStart={this._onResizeStart}
 | 
			
		||||
                onResize={this._onResize}
 | 
			
		||||
                onResizeStop={this._onResizeStop}
 | 
			
		||||
                className="mx_RightPanel_ResizeWrapper"
 | 
			
		||||
                handleClasses={{left: "mx_RightPanel_ResizeHandle"}}
 | 
			
		||||
            >
 | 
			
		||||
 
 | 
			
		||||
@@ -1545,9 +1545,9 @@ export default class RoomView extends React.Component {
 | 
			
		||||
 | 
			
		||||
        // 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
 | 
			
		||||
@@ -1884,7 +1884,8 @@ export default class RoomView extends React.Component {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const auxPanel = (
 | 
			
		||||
            <AuxPanel room={this.state.room}
 | 
			
		||||
            <AuxPanel
 | 
			
		||||
                room={this.state.room}
 | 
			
		||||
                fullHeight={false}
 | 
			
		||||
                userId={this.context.credentials.userId}
 | 
			
		||||
                conferenceHandler={this.props.ConferenceHandler}
 | 
			
		||||
@@ -1892,7 +1893,10 @@ export default class RoomView extends React.Component {
 | 
			
		||||
                displayConfCallNotification={this.state.displayConfCallNotification}
 | 
			
		||||
                maxHeight={this.state.auxPanelMaxHeight}
 | 
			
		||||
                showApps={this.state.showApps}
 | 
			
		||||
              hideAppsDrawer={false} >
 | 
			
		||||
                hideAppsDrawer={false}
 | 
			
		||||
                onResize={this.onResize}
 | 
			
		||||
                resizeNotifier={this.props.resizeNotifier}
 | 
			
		||||
            >
 | 
			
		||||
                { aux }
 | 
			
		||||
            </AuxPanel>
 | 
			
		||||
        );
 | 
			
		||||
@@ -2090,10 +2094,7 @@ export default class RoomView extends React.Component {
 | 
			
		||||
                            onLeaveClick={(myMembership === "join") ? this.onLeaveClick : null}
 | 
			
		||||
                            e2eStatus={this.state.e2eStatus}
 | 
			
		||||
                        />
 | 
			
		||||
                        <MainSplit
 | 
			
		||||
                            panel={rightPanel}
 | 
			
		||||
                            resizeNotifier={this.props.resizeNotifier}
 | 
			
		||||
                        >
 | 
			
		||||
                        <MainSplit panel={rightPanel} resizeNotifier={this.props.resizeNotifier}>
 | 
			
		||||
                            <div className={fadableSectionClasses}>
 | 
			
		||||
                                {auxPanel}
 | 
			
		||||
                                <div className={timelineClasses}>
 | 
			
		||||
 
 | 
			
		||||
@@ -163,7 +163,7 @@ export default class ScrollPanel extends React.Component {
 | 
			
		||||
        this._pendingFillRequests = {b: null, f: null};
 | 
			
		||||
 | 
			
		||||
        if (this.props.resizeNotifier) {
 | 
			
		||||
            this.props.resizeNotifier.on("middlePanelResized", this.onResize);
 | 
			
		||||
            this.props.resizeNotifier.on("middlePanelResizedNoisy", this.onResize);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.resetScrollState();
 | 
			
		||||
@@ -193,11 +193,12 @@ export default class ScrollPanel extends React.Component {
 | 
			
		||||
        this.unmounted = true;
 | 
			
		||||
 | 
			
		||||
        if (this.props.resizeNotifier) {
 | 
			
		||||
            this.props.resizeNotifier.removeListener("middlePanelResized", this.onResize);
 | 
			
		||||
            this.props.resizeNotifier.removeListener("middlePanelResizedNoisy", this.onResize);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    onScroll = ev => {
 | 
			
		||||
        if (this.props.resizeNotifier.isResizing) return; // skip scroll events caused by resizing
 | 
			
		||||
        debuglog("onScroll", this._getScrollNode().scrollTop);
 | 
			
		||||
        this._scrollTimeout.restart();
 | 
			
		||||
        this._saveScrollState();
 | 
			
		||||
@@ -207,6 +208,7 @@ export default class ScrollPanel extends React.Component {
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    onResize = () => {
 | 
			
		||||
        debuglog("onResize");
 | 
			
		||||
        this.checkScroll();
 | 
			
		||||
        // update preventShrinkingState if present
 | 
			
		||||
        if (this.preventShrinkingState) {
 | 
			
		||||
@@ -236,7 +238,6 @@ export default class ScrollPanel extends React.Component {
 | 
			
		||||
        // when scrolled all the way down. E.g. Chrome 72 on debian.
 | 
			
		||||
        // so check difference <= 1;
 | 
			
		||||
        return Math.abs(sn.scrollHeight - (sn.scrollTop + sn.clientHeight)) <= 1;
 | 
			
		||||
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // returns the vertical height in the given direction that can be removed from
 | 
			
		||||
 
 | 
			
		||||
@@ -80,7 +80,9 @@ export default class UserView extends React.Component {
 | 
			
		||||
            const RightPanel = sdk.getComponent('structures.RightPanel');
 | 
			
		||||
            const MainSplit = sdk.getComponent('structures.MainSplit');
 | 
			
		||||
            const panel = <RightPanel user={this.state.member} />;
 | 
			
		||||
            return (<MainSplit panel={panel}><HomePage /></MainSplit>);
 | 
			
		||||
            return (<MainSplit panel={panel} resizeNotifier={this.props.resizeNotifier}>
 | 
			
		||||
                <HomePage />
 | 
			
		||||
            </MainSplit>);
 | 
			
		||||
        } else {
 | 
			
		||||
            return (<div />);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -804,14 +804,16 @@ export default class AppTile extends React.Component {
 | 
			
		||||
        const showMinimiseButton = this.props.showMinimise && this.props.show;
 | 
			
		||||
        const showMaximiseButton = this.props.showMinimise && !this.props.show;
 | 
			
		||||
 | 
			
		||||
        let appTileClass;
 | 
			
		||||
        let appTileClasses;
 | 
			
		||||
        if (this.props.miniMode) {
 | 
			
		||||
            appTileClass = 'mx_AppTile_mini';
 | 
			
		||||
            appTileClasses = {mx_AppTile_mini: true};
 | 
			
		||||
        } else if (this.props.fullWidth) {
 | 
			
		||||
            appTileClass = 'mx_AppTileFullWidth';
 | 
			
		||||
            appTileClasses = {mx_AppTileFullWidth: true};
 | 
			
		||||
        } else {
 | 
			
		||||
            appTileClass = 'mx_AppTile';
 | 
			
		||||
            appTileClasses = {mx_AppTile: true};
 | 
			
		||||
        }
 | 
			
		||||
        appTileClasses.mx_AppTile_minimised = !this.props.show;
 | 
			
		||||
        appTileClasses = classNames(appTileClasses);
 | 
			
		||||
 | 
			
		||||
        const menuBarClasses = classNames({
 | 
			
		||||
            mx_AppTileMenuBar: true,
 | 
			
		||||
@@ -843,7 +845,7 @@ export default class AppTile extends React.Component {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return <React.Fragment>
 | 
			
		||||
            <div className={appTileClass} id={this.props.app.id}>
 | 
			
		||||
            <div className={appTileClasses} id={this.props.app.id}>
 | 
			
		||||
                { this.props.showMenubar &&
 | 
			
		||||
                <div ref={this._menu_bar} className={menuBarClasses} onClick={this.onClickMenuBar}>
 | 
			
		||||
                    <span className="mx_AppTileMenuBarTitle" style={{pointerEvents: (this.props.handleMinimisePointerEvents ? 'all' : false)}}>
 | 
			
		||||
 
 | 
			
		||||
@@ -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} />;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import React, {useState} from 'react';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
 | 
			
		||||
import AppTile from '../elements/AppTile';
 | 
			
		||||
@@ -29,6 +29,10 @@ import WidgetEchoStore from "../../../stores/WidgetEchoStore";
 | 
			
		||||
import AccessibleButton from '../elements/AccessibleButton';
 | 
			
		||||
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
 | 
			
		||||
import SettingsStore from "../../../settings/SettingsStore";
 | 
			
		||||
import classNames from 'classnames';
 | 
			
		||||
import {Resizable} from "re-resizable";
 | 
			
		||||
import {useLocalStorageState} from "../../../hooks/useLocalStorageState";
 | 
			
		||||
import ResizeNotifier from "../../../utils/ResizeNotifier";
 | 
			
		||||
 | 
			
		||||
// The maximum number of widgets that can be added in a room
 | 
			
		||||
const MAX_WIDGETS = 2;
 | 
			
		||||
@@ -37,6 +41,7 @@ export default class AppsDrawer extends React.Component {
 | 
			
		||||
    static propTypes = {
 | 
			
		||||
        userId: PropTypes.string.isRequired,
 | 
			
		||||
        room: PropTypes.object.isRequired,
 | 
			
		||||
        resizeNotifier: PropTypes.instanceOf(ResizeNotifier).isRequired,
 | 
			
		||||
        showApps: PropTypes.bool, // Should apps be rendered
 | 
			
		||||
        hide: PropTypes.bool, // If rendered, should apps drawer be visible
 | 
			
		||||
    };
 | 
			
		||||
@@ -161,7 +166,7 @@ export default class AppsDrawer extends React.Component {
 | 
			
		||||
            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}
 | 
			
		||||
@@ -172,8 +177,8 @@ export default class AppsDrawer extends React.Component {
 | 
			
		||||
            />);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (apps.length == 0) {
 | 
			
		||||
            return <div></div>;
 | 
			
		||||
        if (apps.length === 0) {
 | 
			
		||||
            return <div />;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let addWidget;
 | 
			
		||||
@@ -202,14 +207,68 @@ export default class AppsDrawer extends React.Component {
 | 
			
		||||
            spinner = <Loader />;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const classes = classNames({
 | 
			
		||||
            "mx_AppsDrawer": true,
 | 
			
		||||
            "mx_AppsDrawer_hidden": this.props.hide,
 | 
			
		||||
            "mx_AppsDrawer_fullWidth": apps.length < 2,
 | 
			
		||||
            "mx_AppsDrawer_minimised": !this.props.showApps,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return (
 | 
			
		||||
            <div className={'mx_AppsDrawer' + (this.props.hide ? ' mx_AppsDrawer_hidden' : '')}>
 | 
			
		||||
                <div id='apps' className='mx_AppsContainer'>
 | 
			
		||||
            <div className={classes}>
 | 
			
		||||
                <PersistentVResizer
 | 
			
		||||
                    id={"apps-drawer_" + this.props.room.roomId}
 | 
			
		||||
                    minHeight={100}
 | 
			
		||||
                    maxHeight={this.props.maxHeight ? this.props.maxHeight - 50 : undefined}
 | 
			
		||||
                    handleClass="mx_AppsContainer_resizerHandle"
 | 
			
		||||
                    className="mx_AppsContainer"
 | 
			
		||||
                    resizeNotifier={this.props.resizeNotifier}
 | 
			
		||||
                >
 | 
			
		||||
                    { apps }
 | 
			
		||||
                    { spinner }
 | 
			
		||||
                </div>
 | 
			
		||||
                </PersistentVResizer>
 | 
			
		||||
                { this._canUserModify() && addWidget }
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const PersistentVResizer = ({
 | 
			
		||||
    id,
 | 
			
		||||
    minHeight,
 | 
			
		||||
    maxHeight,
 | 
			
		||||
    className,
 | 
			
		||||
    handleWrapperClass,
 | 
			
		||||
    handleClass,
 | 
			
		||||
    resizeNotifier,
 | 
			
		||||
    children,
 | 
			
		||||
}) => {
 | 
			
		||||
    const [height, setHeight] = useLocalStorageState("pvr_" + id, 100);
 | 
			
		||||
    const [resizing, setResizing] = useState(false);
 | 
			
		||||
 | 
			
		||||
    return <Resizable
 | 
			
		||||
        size={{height: Math.min(height, maxHeight)}}
 | 
			
		||||
        minHeight={minHeight}
 | 
			
		||||
        maxHeight={maxHeight}
 | 
			
		||||
        onResizeStart={() => {
 | 
			
		||||
            if (!resizing) setResizing(true);
 | 
			
		||||
            resizeNotifier.startResizing();
 | 
			
		||||
        }}
 | 
			
		||||
        onResize={() => {
 | 
			
		||||
            resizeNotifier.notifyTimelineHeightChanged();
 | 
			
		||||
        }}
 | 
			
		||||
        onResizeStop={(e, dir, ref, d) => {
 | 
			
		||||
            setHeight(height + d.height);
 | 
			
		||||
            if (resizing) setResizing(false);
 | 
			
		||||
            resizeNotifier.stopResizing();
 | 
			
		||||
        }}
 | 
			
		||||
        handleWrapperClass={handleWrapperClass}
 | 
			
		||||
        handleClasses={{bottom: handleClass}}
 | 
			
		||||
        className={classNames(className, {
 | 
			
		||||
            mx_AppsDrawer_resizing: resizing,
 | 
			
		||||
        })}
 | 
			
		||||
        enable={{bottom: true}}
 | 
			
		||||
    >
 | 
			
		||||
        { children }
 | 
			
		||||
    </Resizable>;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -204,6 +204,7 @@ export default class AuxPanel extends React.Component {
 | 
			
		||||
            maxHeight={this.props.maxHeight}
 | 
			
		||||
            showApps={this.props.showApps}
 | 
			
		||||
            hide={this.props.hideAppsDrawer}
 | 
			
		||||
            resizeNotifier={this.props.resizeNotifier}
 | 
			
		||||
        />;
 | 
			
		||||
 | 
			
		||||
        let stateViews = null;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										44
									
								
								src/hooks/useLocalStorageState.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/hooks/useLocalStorageState.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2020 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 {Dispatch, SetStateAction, useCallback, useEffect, useState} from "react";
 | 
			
		||||
 | 
			
		||||
const getValue = <T>(key: string, initialValue: T): T => {
 | 
			
		||||
    try {
 | 
			
		||||
        const item = window.localStorage.getItem(key);
 | 
			
		||||
        return item ? JSON.parse(item) : initialValue;
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        return initialValue;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Hook behaving like useState but persisting the value to localStorage. Returns same as useState
 | 
			
		||||
export const useLocalStorageState = <T>(key: string, initialValue: T) => {
 | 
			
		||||
    const lsKey = "mx_" + key;
 | 
			
		||||
 | 
			
		||||
    const [value, setValue] = useState<T>(getValue(lsKey, initialValue));
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        setValue(getValue(lsKey, initialValue));
 | 
			
		||||
    }, [lsKey, initialValue]);
 | 
			
		||||
 | 
			
		||||
    const _setValue: Dispatch<SetStateAction<T>> = useCallback((v: T) => {
 | 
			
		||||
        window.localStorage.setItem(lsKey, JSON.stringify(v));
 | 
			
		||||
        setValue(v);
 | 
			
		||||
    }, [lsKey]);
 | 
			
		||||
 | 
			
		||||
    return [value, _setValue];
 | 
			
		||||
};
 | 
			
		||||
@@ -105,6 +105,9 @@ export default class Resizer {
 | 
			
		||||
        if (this.classNames.resizing) {
 | 
			
		||||
            this.container.classList.add(this.classNames.resizing);
 | 
			
		||||
        }
 | 
			
		||||
        if (this.config.onResizeStart) {
 | 
			
		||||
            this.config.onResizeStart();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const {sizer, distributor} = this._createSizerAndDistributor(resizeHandle);
 | 
			
		||||
        distributor.start();
 | 
			
		||||
@@ -119,6 +122,9 @@ export default class Resizer {
 | 
			
		||||
            if (this.classNames.resizing) {
 | 
			
		||||
                this.container.classList.remove(this.classNames.resizing);
 | 
			
		||||
            }
 | 
			
		||||
            if (this.config.onResizeStop) {
 | 
			
		||||
                this.config.onResizeStop();
 | 
			
		||||
            }
 | 
			
		||||
            distributor.finish();
 | 
			
		||||
            body.removeEventListener("mouseup", finishResize, false);
 | 
			
		||||
            document.removeEventListener("mouseleave", finishResize, false);
 | 
			
		||||
 
 | 
			
		||||
@@ -56,6 +56,18 @@ export default class Sizer {
 | 
			
		||||
        return this.vertical ? this.container.offsetTop : this.container.offsetLeft;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** @return {number} container offset to document */
 | 
			
		||||
    _getPageOffset() {
 | 
			
		||||
        let element = this.container;
 | 
			
		||||
        let offset = 0;
 | 
			
		||||
        while (element) {
 | 
			
		||||
            const pos = this.vertical ? element.offsetTop : element.offsetLeft;
 | 
			
		||||
            offset = offset + pos;
 | 
			
		||||
            element = element.offsetParent;
 | 
			
		||||
        }
 | 
			
		||||
        return offset;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setItemSize(item, size) {
 | 
			
		||||
        if (this.vertical) {
 | 
			
		||||
            item.style.height = `${Math.round(size)}px`;
 | 
			
		||||
@@ -80,9 +92,9 @@ export default class Sizer {
 | 
			
		||||
    offsetFromEvent(event) {
 | 
			
		||||
        const pos = this.vertical ? event.pageY : event.pageX;
 | 
			
		||||
        if (this.reverse) {
 | 
			
		||||
            return (this._getOffset() + this.getTotalSize()) - pos;
 | 
			
		||||
            return (this._getPageOffset() + this.getTotalSize()) - pos;
 | 
			
		||||
        } else {
 | 
			
		||||
            return pos - this._getOffset();
 | 
			
		||||
            return pos - this._getPageOffset();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,19 @@ export default class ResizeNotifier extends EventEmitter {
 | 
			
		||||
        // with default options, will call fn once at first call, and then every x ms
 | 
			
		||||
        // if there was another call in that timespan
 | 
			
		||||
        this._throttledMiddlePanel = throttle(() => this.emit("middlePanelResized"), 200);
 | 
			
		||||
        this._isResizing = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get isResizing() {
 | 
			
		||||
        return this._isResizing;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    startResizing() {
 | 
			
		||||
        this._isResizing = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    stopResizing() {
 | 
			
		||||
        this._isResizing = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _noisyMiddlePanel() {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user