Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Make widgets not reload (persistent) between center and top container #7575

Merged
merged 11 commits into from
Jan 24, 2022
52 changes: 36 additions & 16 deletions src/components/views/elements/AppTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import classNames from 'classnames';
import { MatrixCapabilities } from "matrix-widget-api";
import { Room } from "matrix-js-sdk/src/models/room";
import { logger } from "matrix-js-sdk/src/logger";
import { EventSubscription } from 'fbemitter';

import { MatrixClientPeg } from '../../../MatrixClientPeg';
import AccessibleButton from './AccessibleButton';
Expand All @@ -46,6 +47,7 @@ import { IApp } from "../../../stores/WidgetStore";
import { WidgetLayoutStore, Container } from "../../../stores/widgets/WidgetLayoutStore";
import { OwnProfileStore } from '../../../stores/OwnProfileStore';
import { UPDATE_EVENT } from '../../../stores/AsyncStore';
import RoomViewStore from '../../../stores/RoomViewStore';

interface IProps {
app: IApp;
Expand Down Expand Up @@ -116,6 +118,7 @@ export default class AppTile extends React.Component<IProps, IState> {
private persistKey: string;
private sgWidget: StopGapWidget;
private dispatcherRef: string;
private roomStoreToken: EventSubscription;

constructor(props: IProps) {
super(props);
Expand All @@ -134,9 +137,6 @@ export default class AppTile extends React.Component<IProps, IState> {
}

this.state = this.getNewState(props);
this.watchUserReady();

this.allowedWidgetsWatchRef = SettingsStore.watchSetting("allowedWidgets", null, this.onAllowedWidgetsChange);
}

private watchUserReady = () => {
Expand All @@ -162,6 +162,29 @@ export default class AppTile extends React.Component<IProps, IState> {
return !!currentlyAllowedWidgets[props.app.eventId];
};

private onWidgetLayoutChange = () => {
const room = MatrixClientPeg.get().getRoom(this.props.room.roomId);
const app = this.props.app;
const isActiveWidget = ActiveWidgetStore.instance.getWidgetPersistence(app.id);
const isVisibleOnScreen = WidgetLayoutStore.instance.isVisibleOnScreen(room, app.id);
if (!isVisibleOnScreen && !isActiveWidget) {
ActiveWidgetStore.instance.destroyPersistentWidget(app.id);
PersistedElement.destroyElement(this.persistKey);
this.sgWidget?.stop();
}
};

private onRoomViewStoreUpdate = () => {
if (this.props.room.roomId == RoomViewStore.getRoomId()) return;
const app = this.props.app;
const isActiveWidget = ActiveWidgetStore.instance.getWidgetPersistence(app.id);
if (!isActiveWidget) {
ActiveWidgetStore.instance.destroyPersistentWidget(app.id);
PersistedElement.destroyElement(this.persistKey);
this.sgWidget?.stop();
}
};

/**
* Set initial component state when the App wUrl (widget URL) is being updated.
* Component props *must* be passed (rather than relying on this.props).
Expand Down Expand Up @@ -194,7 +217,7 @@ export default class AppTile extends React.Component<IProps, IState> {
// Force the widget to be non-persistent (able to be deleted/forgotten)
ActiveWidgetStore.instance.destroyPersistentWidget(this.props.app.id);
PersistedElement.destroyElement(this.persistKey);
if (this.sgWidget) this.sgWidget.stop();
this.sgWidget?.stop();
}

this.setState({ hasPermissionToLoad });
Expand All @@ -217,7 +240,11 @@ export default class AppTile extends React.Component<IProps, IState> {
if (this.sgWidget && this.state.hasPermissionToLoad) {
this.startWidget();
}
this.watchUserReady();

WidgetLayoutStore.instance.on(WidgetLayoutStore.emissionForRoom(this.props.room), this.onWidgetLayoutChange);
this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate);
this.allowedWidgetsWatchRef = SettingsStore.watchSetting("allowedWidgets", null, this.onAllowedWidgetsChange);
// Widget action listeners
this.dispatcherRef = dis.register(this.onAction);
}
Expand All @@ -226,23 +253,15 @@ export default class AppTile extends React.Component<IProps, IState> {
// Widget action listeners
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);

// if it's not remaining on screen, get rid of the PersistedElement container
if (!ActiveWidgetStore.instance.getWidgetPersistence(this.props.app.id)) {
ActiveWidgetStore.instance.destroyPersistentWidget(this.props.app.id);
PersistedElement.destroyElement(this.persistKey);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the main change that fixes the issue. Instead of destroying it here we destroy it if the widget is not visible on screen anymore of if we switch room.
(this pr now fixes other bugs so i though it would be good to highlight this with a comment)


if (this.sgWidget) {
this.sgWidget.stop();
}

WidgetLayoutStore.instance.off(WidgetLayoutStore.emissionForRoom(this.props.room), this.onWidgetLayoutChange);
this.roomStoreToken?.remove();
SettingsStore.unwatchSetting(this.allowedWidgetsWatchRef);
OwnProfileStore.instance.removeListener(UPDATE_EVENT, this.onUserReady);
}

private resetWidget(newProps: IProps): void {
if (this.sgWidget) {
toger5 marked this conversation as resolved.
Show resolved Hide resolved
this.sgWidget.stop();
this.sgWidget?.stop();
}
try {
this.sgWidget = new StopGapWidget(newProps);
Expand Down Expand Up @@ -527,7 +546,7 @@ export default class AppTile extends React.Component<IProps, IState> {
// Also wrap the PersistedElement in a div to fix the height, otherwise
// AppTile's border is in the wrong place

// For persistent apps in PiP we want the zIndex to be higher then for other persistent apps (100)
// For persisted apps in PiP we want the zIndex to be higher then for other persisted apps (100)
// otherwise there are issues that the PiP view is drawn UNDER another widget (Persistent app) when dragged around.
const zIndexAboveOtherPersistentElements = 101;

Expand Down Expand Up @@ -564,6 +583,7 @@ export default class AppTile extends React.Component<IProps, IState> {
/>
);
}

let maxMinButton;
if (!this.props.hideMaximiseButton) {
const widgetIsMaximised = WidgetLayoutStore.instance.
Expand Down
2 changes: 1 addition & 1 deletion src/components/views/rooms/AppsDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ export default class AppsDrawer extends React.Component<IProps, IState> {
private onAction = (action: ActionPayload): void => {
const hideWidgetKey = this.props.room.roomId + '_hide_widget_drawer';
switch (action.action) {
case 'appsDrawer':
case "appsDrawer":
// Note: these booleans are awkward because localstorage is fundamentally
// string-based. We also do exact equality on the strings later on.
if (action.show) {
Expand Down
18 changes: 4 additions & 14 deletions src/components/views/voip/PipView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { replaceableComponent } from "../../../utils/replaceableComponent";
import PictureInPictureDragger from './PictureInPictureDragger';
import dis from '../../../dispatcher/dispatcher';
import { Action } from "../../../dispatcher/actions";
import { Container, WidgetLayoutStore } from '../../../stores/widgets/WidgetLayoutStore';
import { WidgetLayoutStore } from '../../../stores/widgets/WidgetLayoutStore';
import CallViewHeader from './CallView/CallViewHeader';
import ActiveWidgetStore, { ActiveWidgetStoreEvent } from '../../../stores/ActiveWidgetStore';
import { UPDATE_EVENT } from '../../../stores/AsyncStore';
Expand Down Expand Up @@ -242,9 +242,7 @@ export default class PipView extends React.Component<IProps, IState> {

let userIsPartOfTheRoom = false;
let fromAnotherRoom = false;
let notInRightPanel = false;
let notInCenterContainer = false;
let notInTopContainer = false;
let notVisible = false;
if (wId) {
const persistentWidgetInRoomId = ActiveWidgetStore.instance.getRoomId(wId);
const persistentWidgetInRoom = MatrixClientPeg.get().getRoom(persistentWidgetInRoomId);
Expand All @@ -254,24 +252,16 @@ export default class PipView extends React.Component<IProps, IState> {
if (!persistentWidgetInRoom) return null;

const wls = WidgetLayoutStore.instance;

notVisible = !wls.isVisibleOnScreen(persistentWidgetInRoom, wId);
userIsPartOfTheRoom = persistentWidgetInRoom.getMyMembership() == "join";
fromAnotherRoom = this.state.viewedRoomId !== persistentWidgetInRoomId;

notInRightPanel =
!(RightPanelStore.instance.currentCard.phase == RightPanelPhases.Widget &&
wId == RightPanelStore.instance.currentCard.state?.widgetId);
notInCenterContainer =
!wls.getContainerWidgets(persistentWidgetInRoom, Container.Center).some((app) => app.id == wId);
notInTopContainer =
!wls.getContainerWidgets(persistentWidgetInRoom, Container.Top).some(app => app.id == wId);
}

// The widget should only be shown as a persistent app (in a floating pip container) if it is not visible on screen
// either, because we are viewing a different room OR because it is in none of the possible containers of the room view.
const showWidgetInPip =
(fromAnotherRoom && userIsPartOfTheRoom) ||
(notInRightPanel && notInCenterContainer && notInTopContainer && userIsPartOfTheRoom);
(notVisible && userIsPartOfTheRoom);

this.setState({ showWidgetInPip });
}
Expand Down
36 changes: 28 additions & 8 deletions src/stores/widgets/WidgetLayoutStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import { SettingLevel } from "../../settings/SettingLevel";
import { arrayFastClone } from "../../utils/arrays";
import { UPDATE_EVENT } from "../AsyncStore";
import { compare } from "../../utils/strings";
import RightPanelStore from "../right-panel/RightPanelStore";
import { RightPanelPhases } from "../right-panel/RightPanelStorePhases";

export const WIDGET_LAYOUT_EVENT_TYPE = "io.element.widgets.layout";

Expand Down Expand Up @@ -351,6 +353,22 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
return this.getContainerWidgets(room, container).some(w => w.id === widget.id);
}

public isVisibleOnScreen(room: Room, widgetId: string) {
const wId = widgetId;
const inRightPanel =
(RightPanelStore.instance.currentCard.phase == RightPanelPhases.Widget &&
wId == RightPanelStore.instance.currentCard.state?.widgetId);
const inCenterContainer =
this.getContainerWidgets(room, Container.Center).some((app) => app.id == wId);
const inTopContainer =
this.getContainerWidgets(room, Container.Top).some(app => app.id == wId);

// The widget should only be shown as a persistent app (in a floating pip container) if it is not visible on screen
// either, because we are viewing a different room OR because it is in none of the possible containers of the room view.
const isVisibleOnScreen = (inRightPanel || inCenterContainer || inTopContainer);
return isVisibleOnScreen;
}

public canAddToContainer(room: Room, container: Container): boolean {
switch (container) {
case Container.Top: return this.getContainerWidgets(room, container).length < MAX_PINNED;
Expand Down Expand Up @@ -440,32 +458,34 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
public moveToContainer(room: Room, widget: IApp, toContainer: Container) {
const allWidgets = this.getAllWidgets(room);
if (!allWidgets.some(([w]) => w.id === widget.id)) return; // invalid
// Prepare other containers (potentially move widgets to obay the following rules)
// Prepare other containers (potentially move widgets to obey the following rules)
const newLayout = {};
switch (toContainer) {
case Container.Right:
// new "right" widget
break;
case Container.Center:
// new "center" widget => all other widgets go into "right"
for (const w of this.getContainerWidgets(room, Container.Top)) {
this.moveToContainer(room, w, Container.Right);
newLayout[w.id] = { container: Container.Right };
}
for (const w of this.getContainerWidgets(room, Container.Center)) {
this.moveToContainer(room, w, Container.Right);
newLayout[w.id] = { container: Container.Right };
}
break;
case Container.Top:
// new "top" widget => the center widget moves into "right"
if (this.hasMaximisedWidget(room)) {
this.moveToContainer(room, this.getContainerWidgets(room, Container.Center)[0], Container.Right);
const centerWidget = this.getContainerWidgets(room, Container.Center)[0];
newLayout[centerWidget.id] = { container: Container.Right };
}
break;
}

// move widgets into requested container.
this.updateUserLayout(room, {
[widget.id]: { container: toContainer },
});
newLayout[widget.id] = { container: toContainer };

// move widgets into requested containers.
this.updateUserLayout(room, newLayout);
}

public hasMaximisedWidget(room: Room) {
Expand Down