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

Commit ef95644

Browse files
toger5jryans
andauthored
Render Jitsi (and other sticky widgets) in PiP container, so it can be dragged and the "jump to room functionality" is provided (#7450)
Co-authored-by: J. Ryan Stinnett <[email protected]>
1 parent 8b01b68 commit ef95644

File tree

13 files changed

+396
-342
lines changed

13 files changed

+396
-342
lines changed

res/css/_components.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,6 @@
304304
@import "./views/typography/_Heading.scss";
305305
@import "./views/verification/_VerificationShowSas.scss";
306306
@import "./views/voip/CallView/_CallViewButtons.scss";
307-
@import "./views/voip/_CallContainer.scss";
308307
@import "./views/voip/_CallPreview.scss";
309308
@import "./views/voip/_CallView.scss";
310309
@import "./views/voip/_CallViewForRoom.scss";
@@ -313,4 +312,5 @@
313312
@import "./views/voip/_DialPad.scss";
314313
@import "./views/voip/_DialPadContextMenu.scss";
315314
@import "./views/voip/_DialPadModal.scss";
315+
@import "./views/voip/_PiPContainer.scss";
316316
@import "./views/voip/_VideoFeed.scss";

res/css/views/voip/_CallView.scss

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,15 @@ limitations under the License.
2020
background-color: $dark-panel-bg-color;
2121
padding-left: 8px;
2222
padding-right: 8px;
23-
// XXX: CallContainer sets pointer-events: none - should probably be set back in a better place
23+
// XXX: PiPContainer sets pointer-events: none - should probably be set back in a better place
2424
pointer-events: initial;
2525
}
2626

2727
.mx_CallView_large {
2828
padding-bottom: 10px;
2929
margin: $container-gap-width;
30-
margin-right: calc($container-gap-width / 2); // The left side gap is fully handled by this margin. To prohibit bleeding on webkit browser.
30+
// The left side gap is fully handled by this margin. To prohibit bleeding on webkit browser.
31+
margin-right: calc($container-gap-width / 2);
3132
margin-bottom: 10px;
3233
display: flex;
3334
flex-direction: column;
@@ -46,7 +47,7 @@ limitations under the License.
4647
width: 320px;
4748
padding-bottom: 8px;
4849
background-color: $system;
49-
box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.20);
50+
box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.2);
5051
border-radius: 8px;
5152

5253
.mx_CallView_video_hold,
@@ -170,7 +171,7 @@ limitations under the License.
170171
background-position: center;
171172
filter: blur(20px);
172173
&::after {
173-
content: '';
174+
content: "";
174175
display: block;
175176
position: absolute;
176177
width: 100%;
@@ -194,10 +195,10 @@ limitations under the License.
194195
display: block;
195196
margin-left: auto;
196197
margin-right: auto;
197-
content: '';
198+
content: "";
198199
width: 40px;
199200
height: 40px;
200-
background-image: url('$(res)/img/voip/paused.svg');
201+
background-image: url("$(res)/img/voip/paused.svg");
201202
background-position: center;
202203
background-size: cover;
203204
}

res/css/views/voip/_CallContainer.scss renamed to res/css/views/voip/_PiPContainer.scss

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
.mx_CallContainer {
17+
.mx_PiPContainer {
1818
position: absolute;
1919
right: 20px;
2020
bottom: 72px;
@@ -25,8 +25,4 @@ limitations under the License.
2525
// sure the cursor hits the iframe for Jitsi which will be at a
2626
// different level.
2727
pointer-events: none;
28-
29-
.mx_AppTile_persistedWrapper div {
30-
min-width: 350px;
31-
}
3228
}

src/components/structures/LoggedInView.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import { DefaultTagID } from "../../stores/room-list/models";
4040
import { hideToast as hideServerLimitToast, showToast as showServerLimitToast } from "../../toasts/ServerLimitToast";
4141
import { Action } from "../../dispatcher/actions";
4242
import LeftPanel from "./LeftPanel";
43-
import CallContainer from '../views/voip/CallContainer';
43+
import PipContainer from '../views/voip/PipContainer';
4444
import { ViewRoomDeltaPayload } from "../../dispatcher/payloads/ViewRoomDeltaPayload";
4545
import RoomListStore from "../../stores/room-list/RoomListStore";
4646
import NonUrgentToastContainer from "./NonUrgentToastContainer";
@@ -674,7 +674,7 @@ class LoggedInView extends React.Component<IProps, IState> {
674674
</div>
675675
</div>
676676
</div>
677-
<CallContainer />
677+
<PipContainer />
678678
<NonUrgentToastContainer />
679679
<HostSignupContainer />
680680
{ audioFeedArraysForCalls }

src/components/views/elements/AppTile.tsx

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -508,8 +508,13 @@ export default class AppTile extends React.Component<IProps, IState> {
508508

509509
// Also wrap the PersistedElement in a div to fix the height, otherwise
510510
// AppTile's border is in the wrong place
511+
512+
// For persistent apps in PiP we want the zIndex to be higher then for other persistent apps (100)
513+
// otherwise there are issues that the PiP view is drawn UNDER another widget (Persistent app) when dragged around.
514+
const zIndexAboveOtherPersistentElements = 101;
515+
511516
appTileBody = <div className="mx_AppTile_persistedWrapper">
512-
<PersistedElement zIndex={this.props.miniMode ? 10 : 9}persistKey={this.persistKey}>
517+
<PersistedElement zIndex={this.props.miniMode ? zIndexAboveOtherPersistentElements : 9} persistKey={this.persistKey}>
513518
{ appTileBody }
514519
</PersistedElement>
515520
</div>;
@@ -545,15 +550,15 @@ export default class AppTile extends React.Component<IProps, IState> {
545550
if (!this.props.hideMaximiseButton) {
546551
const widgetIsMaximised = WidgetLayoutStore.instance.
547552
isInContainer(this.props.room, this.props.app, Container.Center);
553+
const className = classNames({
554+
"mx_AppTileMenuBar_iconButton": true,
555+
"mx_AppTileMenuBar_iconButton_minWidget": widgetIsMaximised,
556+
"mx_AppTileMenuBar_iconButton_maxWidget": !widgetIsMaximised,
557+
});
548558
maxMinButton = <AccessibleButton
549-
className={
550-
"mx_AppTileMenuBar_iconButton"
551-
+ (widgetIsMaximised
552-
? " mx_AppTileMenuBar_iconButton_minWidget"
553-
: " mx_AppTileMenuBar_iconButton_maxWidget")
554-
}
559+
className={className}
555560
title={
556-
widgetIsMaximised ? _t('Close'): _t('Maximise widget')
561+
widgetIsMaximised ? _t('Close') : _t('Maximise widget')
557562
}
558563
onClick={this.onMaxMinWidgetClick}
559564
/>;

src/components/views/elements/PersistedElement.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ export default class PersistedElement extends React.Component<IProps> {
184184
width: parentRect.width + 'px',
185185
height: parentRect.height + 'px',
186186
});
187-
}, 100, { trailing: true, leading: true });
187+
}, 16, { trailing: true, leading: true });
188188

189189
public render(): JSX.Element {
190190
return <div ref={this.collectChildContainer} />;

src/components/views/elements/PersistentApp.tsx

Lines changed: 29 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -16,141 +16,79 @@ limitations under the License.
1616
*/
1717

1818
import React from 'react';
19-
import { EventSubscription } from 'fbemitter';
2019
import { Room } from "matrix-js-sdk/src/models/room";
2120

2221
import RoomViewStore from '../../../stores/RoomViewStore';
23-
import ActiveWidgetStore, { ActiveWidgetStoreEvent } from '../../../stores/ActiveWidgetStore';
22+
import ActiveWidgetStore from '../../../stores/ActiveWidgetStore';
2423
import WidgetUtils from '../../../utils/WidgetUtils';
2524
import { MatrixClientPeg } from '../../../MatrixClientPeg';
2625
import { replaceableComponent } from "../../../utils/replaceableComponent";
2726
import AppTile from "./AppTile";
28-
import { Container, WidgetLayoutStore } from '../../../stores/widgets/WidgetLayoutStore';
29-
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
30-
import RightPanelStore from '../../../stores/right-panel/RightPanelStore';
31-
import { UPDATE_EVENT } from '../../../stores/AsyncStore';
3227

3328
interface IProps {
34-
// none
29+
persistentWidgetId: string;
30+
pointerEvents?: string;
3531
}
3632

3733
interface IState {
3834
roomId: string;
39-
persistentWidgetId: string;
40-
rightPanelPhase?: RightPanelPhases;
4135
}
4236

4337
@replaceableComponent("views.elements.PersistentApp")
4438
export default class PersistentApp extends React.Component<IProps, IState> {
45-
private roomStoreToken: EventSubscription;
46-
4739
constructor(props: IProps) {
4840
super(props);
4941

5042
this.state = {
5143
roomId: RoomViewStore.getRoomId(),
52-
persistentWidgetId: ActiveWidgetStore.instance.getPersistentWidgetId(),
53-
rightPanelPhase: RightPanelStore.instance.currentCard.phase,
5444
};
5545
}
5646

5747
public componentDidMount(): void {
58-
this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate);
59-
ActiveWidgetStore.instance.on(ActiveWidgetStoreEvent.Update, this.onActiveWidgetStoreUpdate);
60-
RightPanelStore.instance.on(UPDATE_EVENT, this.onRightPanelStoreUpdate);
6148
MatrixClientPeg.get().on("Room.myMembership", this.onMyMembership);
6249
}
6350

6451
public componentWillUnmount(): void {
65-
if (this.roomStoreToken) {
66-
this.roomStoreToken.remove();
67-
}
68-
ActiveWidgetStore.instance.removeListener(ActiveWidgetStoreEvent.Update, this.onActiveWidgetStoreUpdate);
69-
RightPanelStore.instance.off(UPDATE_EVENT, this.onRightPanelStoreUpdate);
70-
if (MatrixClientPeg.get()) {
71-
MatrixClientPeg.get().removeListener("Room.myMembership", this.onMyMembership);
72-
}
52+
MatrixClientPeg.get().off("Room.myMembership", this.onMyMembership);
7353
}
7454

75-
private onRoomViewStoreUpdate = (): void => {
76-
if (RoomViewStore.getRoomId() === this.state.roomId) return;
77-
this.setState({
78-
roomId: RoomViewStore.getRoomId(),
79-
});
80-
};
81-
82-
private onRightPanelStoreUpdate = () => {
83-
this.setState({
84-
rightPanelPhase: RightPanelStore.instance.currentCard.phase,
85-
});
86-
};
87-
88-
private onActiveWidgetStoreUpdate = (): void => {
89-
this.setState({
90-
persistentWidgetId: ActiveWidgetStore.instance.getPersistentWidgetId(),
91-
});
92-
};
93-
9455
private onMyMembership = async (room: Room, membership: string): Promise<void> => {
95-
const persistentWidgetInRoomId = ActiveWidgetStore.instance.getRoomId(this.state.persistentWidgetId);
56+
const persistentWidgetInRoomId = ActiveWidgetStore.instance.getRoomId(this.props.persistentWidgetId);
9657
if (membership !== "join") {
9758
// we're not in the room anymore - delete
98-
if (room .roomId === persistentWidgetInRoomId) {
99-
ActiveWidgetStore.instance.destroyPersistentWidget(this.state.persistentWidgetId);
59+
if (room.roomId === persistentWidgetInRoomId) {
60+
ActiveWidgetStore.instance.destroyPersistentWidget(this.props.persistentWidgetId);
10061
}
10162
}
10263
};
10364

10465
public render(): JSX.Element {
105-
const wId = this.state.persistentWidgetId;
66+
const wId = this.props.persistentWidgetId;
10667
if (wId) {
10768
const persistentWidgetInRoomId = ActiveWidgetStore.instance.getRoomId(wId);
108-
10969
const persistentWidgetInRoom = MatrixClientPeg.get().getRoom(persistentWidgetInRoomId);
11070

111-
// Sanity check the room - the widget may have been destroyed between render cycles, and
112-
// thus no room is associated anymore.
113-
if (!persistentWidgetInRoom) return null;
114-
115-
const wls = WidgetLayoutStore.instance;
116-
117-
const userIsPartOfTheRoom = persistentWidgetInRoom.getMyMembership() == "join";
118-
const fromAnotherRoom = this.state.roomId !== persistentWidgetInRoomId;
119-
120-
const notInRightPanel =
121-
!(this.state.rightPanelPhase == RightPanelPhases.Widget &&
122-
wId == RightPanelStore.instance.currentCard.state?.widgetId);
123-
const notInCenterContainer =
124-
!wls.getContainerWidgets(persistentWidgetInRoom, Container.Center).some((app) => app.id == wId);
125-
const notInTopContainer =
126-
!wls.getContainerWidgets(persistentWidgetInRoom, Container.Top).some(app => app.id == wId);
127-
if (
128-
// the widget should only be shown as a persistent app (in a floating pip container) if it is not visible on screen
129-
// either, because we are viewing a different room OR because it is in none of the possible containers of the room view.
130-
(fromAnotherRoom && userIsPartOfTheRoom) ||
131-
(notInRightPanel && notInCenterContainer && notInTopContainer && userIsPartOfTheRoom)
132-
) {
133-
// get the widget data
134-
const appEvent = WidgetUtils.getRoomWidgets(persistentWidgetInRoom).find((ev) => {
135-
return ev.getStateKey() === ActiveWidgetStore.instance.getPersistentWidgetId();
136-
});
137-
const app = WidgetUtils.makeAppConfig(
138-
appEvent.getStateKey(), appEvent.getContent(), appEvent.getSender(),
139-
persistentWidgetInRoomId, appEvent.getId(),
140-
);
141-
return <AppTile
142-
key={app.id}
143-
app={app}
144-
fullWidth={true}
145-
room={persistentWidgetInRoom}
146-
userId={MatrixClientPeg.get().credentials.userId}
147-
creatorUserId={app.creatorUserId}
148-
widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)}
149-
waitForIframeLoad={app.waitForIframeLoad}
150-
miniMode={true}
151-
showMenubar={false}
152-
/>;
153-
}
71+
// get the widget data
72+
const appEvent = WidgetUtils.getRoomWidgets(persistentWidgetInRoom).find((ev) => {
73+
return ev.getStateKey() === ActiveWidgetStore.instance.getPersistentWidgetId();
74+
});
75+
const app = WidgetUtils.makeAppConfig(
76+
appEvent.getStateKey(), appEvent.getContent(), appEvent.getSender(),
77+
persistentWidgetInRoomId, appEvent.getId(),
78+
);
79+
return <AppTile
80+
key={app.id}
81+
app={app}
82+
fullWidth={true}
83+
room={persistentWidgetInRoom}
84+
userId={MatrixClientPeg.get().credentials.userId}
85+
creatorUserId={app.creatorUserId}
86+
widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)}
87+
waitForIframeLoad={app.waitForIframeLoad}
88+
miniMode={true}
89+
showMenubar={false}
90+
pointerEvents={this.props.pointerEvents}
91+
/>;
15492
}
15593
return null;
15694
}

src/components/views/rooms/Stickerpicker.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
135135
// Close the sticker picker when the window resizes
136136
window.addEventListener('resize', this.onResize);
137137

138-
this.dispatcherRef = dis.register(this.onWidgetAction);
138+
this.dispatcherRef = dis.register(this.onAction);
139139

140140
// Track updates to widget state in account data
141141
MatrixClientPeg.get().on('accountData', this.updateWidget);
@@ -198,7 +198,7 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
198198
});
199199
};
200200

201-
private onWidgetAction = (payload: ActionPayload): void => {
201+
private onAction = (payload: ActionPayload): void => {
202202
switch (payload.action) {
203203
case "user_widget_updated":
204204
this.forceUpdate();

0 commit comments

Comments
 (0)