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

Commit

Permalink
Make widgets not reload (persistent) between center and top container (
Browse files Browse the repository at this point in the history
  • Loading branch information
toger5 authored Jan 24, 2022
1 parent e28d2a2 commit 9d9b77d
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 81 deletions.
60 changes: 38 additions & 22 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';
import WidgetUtils from '../../../utils/WidgetUtils';

interface IProps {
Expand Down Expand Up @@ -117,6 +119,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 @@ -135,9 +138,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 @@ -163,6 +163,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?.stopMessaging();
}
};

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?.stopMessaging();
}
};

/**
* 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 @@ -195,7 +218,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?.stopMessaging();
}

this.setState({ hasPermissionToLoad });
Expand All @@ -218,7 +241,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 @@ -227,24 +254,14 @@ 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);
}

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) {
this.sgWidget.stop();
}
this.sgWidget?.stopMessaging();
try {
this.sgWidget = new StopGapWidget(newProps);
this.sgWidget.on("preparing", this.onWidgetPreparing);
Expand All @@ -266,9 +283,7 @@ export default class AppTile extends React.Component<IProps, IState> {
this.iframe = ref;
if (ref) {
try {
if (this.sgWidget) {
this.sgWidget.start(ref);
}
this.sgWidget?.startMessaging(ref);
} catch (e) {
logger.error("Failed to start widget", e);
}
Expand Down Expand Up @@ -321,7 +336,7 @@ export default class AppTile extends React.Component<IProps, IState> {
PersistedElement.destroyElement(this.persistKey);
ActiveWidgetStore.instance.destroyPersistentWidget(this.props.app.id);

if (this.sgWidget) this.sgWidget.stop({ forceDestroy: true });
this.sgWidget?.stopMessaging({ forceDestroy: true });
}

private onWidgetPreparing = (): void => {
Expand Down Expand Up @@ -532,7 +547,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 @@ -569,6 +584,7 @@ export default class AppTile extends React.Component<IProps, IState> {
/>
);
}

let maxMinButton;
if (!this.props.hideMaximiseButton) {
const widgetIsMaximised = WidgetLayoutStore.instance.
Expand Down
4 changes: 2 additions & 2 deletions 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 Expand Up @@ -279,7 +279,7 @@ export default class AppsDrawer extends React.Component<IProps, IState> {
drawer = <PersistentVResizer
room={this.props.room}
minHeight={100}
maxHeight={(this.props.maxHeight || !widgetIsMaxmised) ? this.props.maxHeight - 50 : undefined}
maxHeight={this.props.maxHeight - 50}
handleClass="mx_AppsContainer_resizerHandle"
handleWrapperClass="mx_AppsContainer_resizerHandleContainer"
className="mx_AppsContainer_resizer"
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
57 changes: 25 additions & 32 deletions src/stores/right-panel/RightPanelStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import { EventSubscription } from 'fbemitter';
import { logger } from "matrix-js-sdk/src/logger";

import defaultDispatcher from '../../dispatcher/dispatcher';
import { pendingVerificationRequestForUser } from '../../verification';
import SettingsStore from "../../settings/SettingsStore";
import { RightPanelPhases } from "./RightPanelStorePhases";
import { ActionPayload } from "../../dispatcher/payloads";
import { Action } from '../../dispatcher/actions';
import { SettingLevel } from "../../settings/SettingLevel";
import { UPDATE_EVENT } from '../AsyncStore';
import { ReadyWatchingStore } from '../ReadyWatchingStore';
Expand All @@ -32,7 +32,7 @@ import {
IRightPanelForRoom,
} from './RightPanelStoreIPanelState';
import { MatrixClientPeg } from "../../MatrixClientPeg";
// import RoomViewStore from '../RoomViewStore';
import RoomViewStore from '../RoomViewStore';

const GROUP_PHASES = [
RightPanelPhases.GroupMemberList,
Expand All @@ -41,12 +41,6 @@ const GROUP_PHASES = [
RightPanelPhases.GroupMemberInfo,
];

const MEMBER_INFO_PHASES = [
RightPanelPhases.RoomMemberInfo,
RightPanelPhases.Room3pidMemberInfo,
RightPanelPhases.EncryptionPanel,
];

/**
* A class for tracking the state of the right panel between layouts and
* sessions. This state includes a history for each room. Each history element
Expand All @@ -68,16 +62,18 @@ export default class RightPanelStore extends ReadyWatchingStore {
[roomId: string]: IRightPanelForRoom;
} = {};

private roomStoreToken: EventSubscription;

private constructor() {
super(defaultDispatcher);
this.dispatcherRefRightPanelStore = defaultDispatcher.register(this.onDispatch);
}

protected async onReady(): Promise<any> {
this.isReady = true;
// TODO RightPanelStore (will be addressed when dropping groups): This should be used instead of the onDispatch callback when groups are removed.
// RoomViewStore.on(UPDATE_EVENT, this.onRoomViewStoreUpdate);
this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate);
MatrixClientPeg.get().on("crypto.verification.request", this.onVerificationRequestUpdate);
this.viewedRoomId = RoomViewStore.getRoomId();
this.loadCacheFromSettings();
this.emitAndUpdateSettings();
}
Expand All @@ -91,8 +87,7 @@ export default class RightPanelStore extends ReadyWatchingStore {
protected async onNotReady(): Promise<any> {
this.isReady = false;
MatrixClientPeg.get().off("crypto.verification.request", this.onVerificationRequestUpdate);
// TODO RightPanelStore (will be addressed when dropping groups): User this instead of the dispatcher.
// RoomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate);
this.roomStoreToken.remove();
}

// Getters
Expand Down Expand Up @@ -373,45 +368,43 @@ export default class RightPanelStore extends ReadyWatchingStore {
};

onRoomViewStoreUpdate = () => {
// TODO: use this function instead of the onDispatch (the whole onDispatch can get removed!) as soon groups are removed
// this.viewedRoomId = RoomViewStore.getRoomId();
// this.isViewingRoom = true; // Is viewing room will of course be removed when removing groups
// // load values from byRoomCache with the viewedRoomId.
// this.loadCacheFromSettings();
// TODO: only use this function instead of the onDispatch (the whole onDispatch can get removed!) as soon groups are removed
this.viewedRoomId = RoomViewStore.getRoomId();
this.isViewingRoom = true; // Is viewing room will of course be removed when removing groups
// load values from byRoomCache with the viewedRoomId.
this.loadCacheFromSettings();
this.emitAndUpdateSettings();
};

onDispatch = (payload: ActionPayload) => {
switch (payload.action) {
case 'view_group':
case Action.ViewRoom: {
case 'view_group': {
if (payload.room_id === this.viewedRoomId) break; // skip this transition, probably a permalink

// Put group in the same/similar view to what was open from the previously viewed room
// Is contradictory to the new "per room" philosophy but it is the legacy behavior for groups.
if ((this.isViewingRoom ? Action.ViewRoom : "view_group") != payload.action) {
if (payload.action == Action.ViewRoom && MEMBER_INFO_PHASES.includes(this.currentCard?.phase)) {
// switch from group to room
this.setRightPanelCache({ phase: RightPanelPhases.RoomMemberList, state: {} });
} else if (
payload.action == "view_group" &&
this.currentCard?.phase === RightPanelPhases.GroupMemberInfo
) {
// switch from room to group
this.setRightPanelCache({ phase: RightPanelPhases.GroupMemberList, state: {} });
}

if (
this.currentCard?.phase === RightPanelPhases.GroupMemberInfo
) {
// switch from room to group
this.setRightPanelCache({ phase: RightPanelPhases.GroupMemberList, state: {} });
}

// Update the current room here, so that all the other functions dont need to be room dependant.
// The right panel store always will return the state for the current room.
this.viewedRoomId = payload.room_id;
this.isViewingRoom = payload.action == Action.ViewRoom;
this.isViewingRoom = false;
// load values from byRoomCache with the viewedRoomId.
if (this.isReady) {
// we need the client to be ready to get the events form the ids of the settings
// the loading will be done in the onReady function (to catch up with the changes done here before it was ready)
// all the logic in this case is not necessary anymore as soon as groups are dropped and we use: onRoomViewStoreUpdate
this.loadCacheFromSettings();
this.emitAndUpdateSettings();

// DO NOT EMIT. Emitting breaks iframe refs by triggering a render
// for the room view and calling the iframe ref changed function
// this.emitAndUpdateSettings();
}
break;
}
Expand Down
14 changes: 11 additions & 3 deletions src/stores/widgets/StopGapWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,11 @@ export class StopGapWidget extends EventEmitter {
});
}
};

public start(iframe: HTMLIFrameElement) {
/**
* This starts the messaging for the widget if it is not in the state `started` yet.
* @param iframe the iframe the widget should use
*/
public startMessaging(iframe: HTMLIFrameElement): any {
if (this.started) return;
const allowedCapabilities = this.appTileProps.whitelistCapabilities || [];
const driver = new StopGapWidgetDriver(allowedCapabilities, this.mockWidget, this.kind, this.roomId);
Expand Down Expand Up @@ -407,7 +410,12 @@ export class StopGapWidget extends EventEmitter {
}
}

public stop(opts = { forceDestroy: false }) {
/**
* Stops the widget messaging for if it is started. Skips stopping if it is an active
* widget.
* @param opts
*/
public stopMessaging(opts = { forceDestroy: false }) {
if (!opts?.forceDestroy && ActiveWidgetStore.instance.getPersistentWidgetId() === this.mockWidget.id) {
logger.log("Skipping destroy - persistent widget");
return;
Expand Down
Loading

0 comments on commit 9d9b77d

Please sign in to comment.