Add maximise widget functionality (#7098)

Co-authored-by: J. Ryan Stinnett <jryans@gmail.com>
This commit is contained in:
Timo 2021-11-16 15:43:18 +01:00 committed by GitHub
parent 2f4f3f2a8c
commit 556cfc7ed8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 418 additions and 233 deletions

View file

@ -40,7 +40,7 @@ import WidgetAvatar from "../avatars/WidgetAvatar";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { Room } from "matrix-js-sdk/src/models/room";
import { IApp } from "../../../stores/WidgetStore";
import { WidgetLayoutStore, Container } from "../../../stores/widgets/WidgetLayoutStore";
interface IProps {
app: IApp;
// If room is not specified then it is an account level widget
@ -400,6 +400,14 @@ export default class AppTile extends React.Component<IProps, IState> {
{ target: '_blank', href: this.sgWidget.popoutUrl, rel: 'noreferrer noopener' }).click();
};
private onMaxMinWidgetClick = (): void => {
const targetContainer =
WidgetLayoutStore.instance.isInContainer(this.props.room, this.props.app, Container.Center)
? Container.Right
: Container.Center;
WidgetLayoutStore.instance.moveToContainer(this.props.room, this.props.app, targetContainer);
};
private onContextMenuClick = (): void => {
this.setState({ menuDisplayed: true });
};
@ -522,6 +530,23 @@ export default class AppTile extends React.Component<IProps, IState> {
/>
);
}
let maxMinButton;
if (SettingsStore.getValue("feature_maximised_widgets")) {
const widgetIsMaximised = WidgetLayoutStore.instance.
isInContainer(this.props.room, this.props.app, Container.Center);
maxMinButton = <AccessibleButton
className={
"mx_AppTileMenuBar_iconButton"
+ (widgetIsMaximised
? " mx_AppTileMenuBar_iconButton_minWidget"
: " mx_AppTileMenuBar_iconButton_maxWidget")
}
title={
widgetIsMaximised ? _t('Close'): _t('Maximise widget')
}
onClick={this.onMaxMinWidgetClick}
/>;
}
return <React.Fragment>
<div className={appTileClasses} id={this.props.app.id}>
@ -531,6 +556,7 @@ export default class AppTile extends React.Component<IProps, IState> {
{ this.props.showTitle && this.getTileTitle() }
</span>
<span className="mx_AppTileMenuBarWidgets">
{ maxMinButton }
{ (this.props.showPopout && !this.state.requiresClient) && <AccessibleButton
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_popout"
title={_t('Popout widget')}

View file

@ -138,14 +138,28 @@ const AppRow: React.FC<IAppRowProps> = ({ app, room }) => {
mx_RoomSummaryCard_Button_pinned: isPinned,
});
const isMaximised = WidgetLayoutStore.instance.isInContainer(room, app, Container.Center);
const toggleMaximised = isMaximised
? () => { WidgetLayoutStore.instance.moveToContainer(room, app, Container.Right); }
: () => { WidgetLayoutStore.instance.moveToContainer(room, app, Container.Center); };
const maximiseTitle = isMaximised ? _t("Close") : _t("Maximise widget");
let openTitle = "";
if (isPinned) {
openTitle = _t("Unpin this widget to view it in this panel");
} else if (isMaximised) {
openTitle =_t("Close this widget to view it in this panel");
}
return <div className={classes} ref={handle}>
<AccessibleTooltipButton
className="mx_RoomSummaryCard_icon_app"
onClick={onOpenWidgetClick}
// only show a tooltip if the widget is pinned
title={isPinned ? _t("Unpin a widget to view it in this panel") : ""}
forceHide={!isPinned}
disabled={isPinned}
title={openTitle}
forceHide={!(isPinned || isMaximised)}
disabled={isPinned || isMaximised}
yOffset={-48}
>
<WidgetAvatar app={app} />
@ -154,7 +168,10 @@ const AppRow: React.FC<IAppRowProps> = ({ app, room }) => {
</AccessibleTooltipButton>
<ContextMenuTooltipButton
className="mx_RoomSummaryCard_app_options"
className={classNames({
"mx_RoomSummaryCard_app_options": true,
"mx_RoomSummaryCard_maximised_widget": SettingsStore.getValue("feature_maximised_widgets"),
})}
isExpanded={menuDisplayed}
onClick={openMenu}
title={_t("Options")}
@ -168,6 +185,13 @@ const AppRow: React.FC<IAppRowProps> = ({ app, room }) => {
disabled={cannotPin}
yOffset={-24}
/>
{ SettingsStore.getValue("feature_maximised_widgets") &&
<AccessibleTooltipButton
className={isMaximised ? "mx_RoomSummaryCard_app_minimise" : "mx_RoomSummaryCard_app_maximise"}
onClick={toggleMaximised}
title={maximiseTitle}
yOffset={-24}
/> }
{ contextMenu }
</div>;

View file

@ -47,7 +47,8 @@ interface IProps {
}
interface IState {
apps: IApp[];
// @ts-ignore - TS wants a string key, but we know better
apps: {[id: Container]: IApp[]};
resizingVertical: boolean; // true when changing the height of the apps drawer
resizingHorizontal: boolean; // true when chagning the distribution of the width between widgets
resizing: boolean;
@ -118,7 +119,7 @@ export default class AppsDrawer extends React.Component<IProps, IState> {
this.resizeContainer.classList.remove("mx_AppsDrawer_resizing");
WidgetLayoutStore.instance.setResizerDistributions(
this.props.room, Container.Top,
this.state.apps.slice(1).map((_, i) => this.resizer.forHandleAt(i).size),
this.topApps().slice(1).map((_, i) => this.resizer.forHandleAt(i).size),
);
this.setState({ resizingHorizontal: false });
},
@ -148,7 +149,7 @@ export default class AppsDrawer extends React.Component<IProps, IState> {
if (prevProps.userId !== this.props.userId || prevProps.room !== this.props.room) {
// Room has changed, update apps
this.updateApps();
} else if (this.getAppsHash(this.state.apps) !== this.getAppsHash(prevState.apps)) {
} else if (this.getAppsHash(this.topApps()) !== this.getAppsHash(prevState.apps[Container.Top])) {
this.loadResizerPreferences();
}
}
@ -163,7 +164,7 @@ export default class AppsDrawer extends React.Component<IProps, IState> {
private loadResizerPreferences = (): void => {
const distributions = WidgetLayoutStore.instance.getResizerDistributions(this.props.room, Container.Top);
if (this.state.apps && (this.state.apps.length - 1) === distributions.length) {
if (this.state.apps && (this.topApps().length - 1) === distributions.length) {
distributions.forEach((size, i) => {
const distributor = this.resizer.forHandleAt(i);
if (distributor) {
@ -200,8 +201,16 @@ export default class AppsDrawer extends React.Component<IProps, IState> {
break;
}
};
private getApps = (): IApp[] => WidgetLayoutStore.instance.getContainerWidgets(this.props.room, Container.Top);
// @ts-ignore - TS wants a string key, but we know better
private getApps = (): { [id: Container]: IApp[] } => {
// @ts-ignore
const appsDict: { [id: Container]: IApp[] } = {};
appsDict[Container.Top] = WidgetLayoutStore.instance.getContainerWidgets(this.props.room, Container.Top);
appsDict[Container.Center] = WidgetLayoutStore.instance.getContainerWidgets(this.props.room, Container.Center);
return appsDict;
};
private topApps = (): IApp[] => this.state.apps[Container.Top];
private centerApps = (): IApp[] => this.state.apps[Container.Center];
private updateApps = (): void => {
this.setState({
@ -211,8 +220,9 @@ export default class AppsDrawer extends React.Component<IProps, IState> {
public render(): JSX.Element {
if (!this.props.showApps) return <div />;
const apps = this.state.apps.map((app, index, arr) => {
const widgetIsMaxmised: boolean = this.centerApps().length > 0;
const appsToDisplay = widgetIsMaxmised ? this.centerApps() : this.topApps();
const apps = appsToDisplay.map((app, index, arr) => {
return (<AppTile
key={app.id}
app={app}
@ -242,33 +252,42 @@ export default class AppsDrawer extends React.Component<IProps, IState> {
const classes = classNames({
mx_AppsDrawer: true,
mx_AppsDrawer_maximise: widgetIsMaxmised,
mx_AppsDrawer_fullWidth: apps.length < 2,
mx_AppsDrawer_resizing: this.state.resizing,
mx_AppsDrawer_2apps: apps.length === 2,
mx_AppsDrawer_3apps: apps.length === 3,
});
const appConatiners =
<div className="mx_AppsContainer" ref={this.collectResizer}>
{ 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>;
let drawer;
if (widgetIsMaxmised) {
drawer = appConatiners;
} else {
drawer = <PersistentVResizer
room={this.props.room}
minHeight={100}
maxHeight={(this.props.maxHeight || !widgetIsMaxmised) ? this.props.maxHeight - 50 : undefined}
handleClass="mx_AppsContainer_resizerHandle"
handleWrapperClass="mx_AppsContainer_resizerHandleContainer"
className="mx_AppsContainer_resizer"
resizeNotifier={this.props.resizeNotifier}>
{ appConatiners }
</PersistentVResizer>;
}
return (
<div className={classes}>
<PersistentVResizer
room={this.props.room}
minHeight={100}
maxHeight={this.props.maxHeight ? this.props.maxHeight - 50 : undefined}
handleClass="mx_AppsContainer_resizerHandle"
handleWrapperClass="mx_AppsContainer_resizerHandleContainer"
className="mx_AppsContainer_resizer"
resizeNotifier={this.props.resizeNotifier}
>
<div className="mx_AppsContainer" ref={this.collectResizer}>
{ 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>
{ drawer }
{ spinner }
</div>
);