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

Add a way to maximize/pin widget from the PiP view #7672

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions res/css/views/voip/_CallViewHeader.scss
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ limitations under the License.

.mx_CallViewHeader_controls {
margin-left: auto;
display: flex;
gap: 5px;
}

.mx_CallViewHeader_button {
Expand All @@ -61,17 +63,23 @@ limitations under the License.
mask-size: contain;
mask-position: center;
}
}

.mx_CallViewHeader_button_fullscreen {
&::before {
mask-image: url('$(res)/img/element-icons/call/fullscreen.svg');
&.mx_CallViewHeader_button_fullscreen {
&::before {
mask-image: url('$(res)/img/element-icons/call/fullscreen.svg');
}
}
}

.mx_CallViewHeader_button_expand {
&::before {
mask-image: url('$(res)/img/element-icons/call/expand.svg');
&.mx_CallViewHeader_button_pin {
&::before {
mask-image: url('$(res)/img/element-icons/room/pin-upright.svg');
}
}

&.mx_CallViewHeader_button_expand {
&::before {
mask-image: url('$(res)/img/element-icons/call/expand.svg');
}
}
}

Expand Down
8 changes: 8 additions & 0 deletions src/components/views/voip/CallView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,13 @@ export default class CallView extends React.Component<IProps, IState> {
return { primary, secondary };
}

private onMaximizeClick = (): void => {
dis.dispatch({
action: 'video_fullscreen',
fullscreen: true,
});
};

private onMicMuteClick = async (): Promise<void> => {
const newVal = !this.state.micMuted;
this.setState({ micMuted: await this.props.call.setMicrophoneMuted(newVal) });
Expand Down Expand Up @@ -617,6 +624,7 @@ export default class CallView extends React.Component<IProps, IState> {
onPipMouseDown={this.props.onMouseDownOnHeader}
pipMode={this.props.pipMode}
callRooms={[callRoom, secCallRoom]}
onMaximize={this.onMaximizeClick}
/>
{ contentView }
</div>;
Expand Down
67 changes: 35 additions & 32 deletions src/components/views/voip/CallView/CallViewHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,48 +19,39 @@ import React from 'react';

import { _t } from '../../../../languageHandler';
import RoomAvatar from '../../avatars/RoomAvatar';
import dis from '../../../../dispatcher/dispatcher';
import { Action } from '../../../../dispatcher/actions';
import AccessibleTooltipButton from '../../elements/AccessibleTooltipButton';

interface CallViewHeaderProps {
pipMode: boolean;
callRooms?: Room[];
onPipMouseDown: (event: React.MouseEvent<Element, MouseEvent>) => void;
interface CallControlsProps {
onExpand?: () => void;
onPin?: () => void;
onMaximize?: () => void;
}

const onFullscreenClick = () => {
dis.dispatch({
action: 'video_fullscreen',
fullscreen: true,
});
};

const onExpandClick = (roomId: string) => {
dis.dispatch({
action: Action.ViewRoom,
room_id: roomId,
});
};

type CallControlsProps = Pick<CallViewHeaderProps, 'pipMode'> & {
roomId: string;
};
const CallViewHeaderControls: React.FC<CallControlsProps> = ({ pipMode = false, roomId }) => {
const CallViewHeaderControls: React.FC<CallControlsProps> = ({ onExpand, onPin, onMaximize }) => {
return <div className="mx_CallViewHeader_controls">
{ !pipMode && <AccessibleTooltipButton
{ onMaximize && <AccessibleTooltipButton
className="mx_CallViewHeader_button mx_CallViewHeader_button_fullscreen"
onClick={onFullscreenClick}
onClick={onMaximize}
title={_t("Fill Screen")}
/> }
{ pipMode && <AccessibleTooltipButton
{ onPin && <AccessibleTooltipButton
className="mx_CallViewHeader_button mx_CallViewHeader_button_pin"
onClick={onPin}
title={_t("Pin")}
/> }
{ onExpand && <AccessibleTooltipButton
className="mx_CallViewHeader_button mx_CallViewHeader_button_expand"
onClick={() => onExpandClick(roomId)}
onClick={onExpand}
title={_t("Return to call")}
/> }
</div>;
};
const SecondaryCallInfo: React.FC<{ callRoom: Room }> = ({ callRoom }) => {

interface ISecondaryCallInfoProps {
callRoom: Room;
}

const SecondaryCallInfo: React.FC<ISecondaryCallInfoProps> = ({ callRoom }) => {
return <span className="mx_CallViewHeader_secondaryCallInfo">
<RoomAvatar room={callRoom} height={16} width={16} />
<span className="mx_CallView_secondaryCall_roomName">
Expand All @@ -69,19 +60,31 @@ const SecondaryCallInfo: React.FC<{ callRoom: Room }> = ({ callRoom }) => {
</span>;
};

interface CallViewHeaderProps {
pipMode: boolean;
callRooms?: Room[];
onPipMouseDown: (event: React.MouseEvent<Element, MouseEvent>) => void;
onExpand?: () => void;
onPin?: () => void;
onMaximize?: () => void;
}

const CallViewHeader: React.FC<CallViewHeaderProps> = ({
pipMode = false,
callRooms = [],
onPipMouseDown,
onExpand,
onPin,
onMaximize,
}) => {
const [callRoom, onHoldCallRoom] = callRooms;
const { roomId, name: callRoomName } = callRoom;
const callRoomName = callRoom.name;

if (!pipMode) {
return <div className="mx_CallViewHeader">
<div className="mx_CallViewHeader_icon" />
<span className="mx_CallViewHeader_text">{ _t("Call") }</span>
<CallViewHeaderControls roomId={roomId} pipMode={pipMode} />
<CallViewHeaderControls onMaximize={onMaximize} />
</div>;
}
return (
Expand All @@ -94,7 +97,7 @@ const CallViewHeader: React.FC<CallViewHeaderProps> = ({
<div className="mx_CallViewHeader_roomName">{ callRoomName }</div>
{ onHoldCallRoom && <SecondaryCallInfo callRoom={onHoldCallRoom} /> }
</div>
<CallViewHeaderControls roomId={roomId} pipMode={pipMode} />
<CallViewHeaderControls onExpand={onExpand} onPin={onPin} onMaximize={onMaximize} />
</div>
);
};
Expand Down
50 changes: 49 additions & 1 deletion src/components/views/voip/PipView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { CallEvent, CallState, MatrixCall } from 'matrix-js-sdk/src/webrtc/call'
import { EventSubscription } from 'fbemitter';
import { logger } from "matrix-js-sdk/src/logger";
import classNames from 'classnames';
import { Room } from "matrix-js-sdk/src/models/room";

import CallView from "./CallView";
import RoomViewStore from '../../../stores/RoomViewStore';
Expand All @@ -30,12 +31,13 @@ import { replaceableComponent } from "../../../utils/replaceableComponent";
import PictureInPictureDragger from './PictureInPictureDragger';
import dis from '../../../dispatcher/dispatcher';
import { Action } from "../../../dispatcher/actions";
import { WidgetLayoutStore } from '../../../stores/widgets/WidgetLayoutStore';
import { Container, WidgetLayoutStore } from '../../../stores/widgets/WidgetLayoutStore';
import CallViewHeader from './CallView/CallViewHeader';
import ActiveWidgetStore, { ActiveWidgetStoreEvent } from '../../../stores/ActiveWidgetStore';
import { UPDATE_EVENT } from '../../../stores/AsyncStore';
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
import RightPanelStore from '../../../stores/right-panel/RightPanelStore';
import WidgetStore, { IApp } from "../../../stores/WidgetStore";

const SHOW_CALL_IN_STATES = [
CallState.Connected,
Expand Down Expand Up @@ -67,6 +69,17 @@ interface IState {
moving: boolean;
}

const getRoomAndAppForWidget = (widgetId: string): [Room, IApp] => {
if (!widgetId) return;
const roomId = ActiveWidgetStore.instance.getRoomId(widgetId);
if (!roomId) return;

const room = MatrixClientPeg.get().getRoom(roomId);
const app = WidgetStore.instance.getApps(roomId).find((app) => app.id === widgetId);

return [room, app];
};

// Splits a list of calls into one 'primary' one and a list
// (which should be a single element) of other calls.
// The primary will be the one not on hold, or an arbitrary one
Expand Down Expand Up @@ -234,6 +247,37 @@ export default class PipView extends React.Component<IProps, IState> {
}
};

private onMaximize = (): void => {
const widgetId = this.state.persistentWidgetId;

if (this.state.showWidgetInPip && widgetId) {
const [room, app] = getRoomAndAppForWidget(widgetId);
WidgetLayoutStore.instance.moveToContainer(room, app, Container.Center);
} else {
dis.dispatch({
action: 'video_fullscreen',
fullscreen: true,
});
}
};

private onPin = (): void => {
if (!this.state.showWidgetInPip) return;

const [room, app] = getRoomAndAppForWidget(this.state.persistentWidgetId);
WidgetLayoutStore.instance.moveToContainer(room, app, Container.Top);
};

private onExpand = (): void => {
const widgetId = this.state.persistentWidgetId;
if (!widgetId || !this.state.showWidgetInPip) return;

dis.dispatch({
action: Action.ViewRoom,
room_id: ActiveWidgetStore.instance.getRoomId(widgetId),
});
};

// Accepts a persistentWidgetId to be able to skip awaiting the setState for persistentWidgetId
public updateShowWidgetInPip(persistentWidgetId = this.state.persistentWidgetId) {
let userIsPartOfTheRoom = false;
Expand Down Expand Up @@ -285,13 +329,17 @@ export default class PipView extends React.Component<IProps, IState> {
});
const roomId = ActiveWidgetStore.instance.getRoomId(this.state.persistentWidgetId);
const roomForWidget = MatrixClientPeg.get().getRoom(roomId);
const viewingCallRoom = this.state.viewedRoomId === roomId;

pipContent = ({ onStartMoving, _onResize }) =>
<div className={pipViewClasses}>
<CallViewHeader
onPipMouseDown={(event) => { onStartMoving(event); this.onStartMoving.bind(this)(); }}
pipMode={pipMode}
callRooms={[roomForWidget]}
onExpand={!viewingCallRoom && this.onExpand}
onPin={viewingCallRoom && this.onPin}
onMaximize={viewingCallRoom && this.onMaximize}
/>
<PersistentApp
persistentWidgetId={this.state.persistentWidgetId}
Expand Down
2 changes: 1 addition & 1 deletion src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -1012,6 +1012,7 @@
"More": "More",
"Hangup": "Hangup",
"Fill Screen": "Fill Screen",
"Pin": "Pin",
"Return to call": "Return to call",
"%(name)s on hold": "%(name)s on hold",
"Call": "Call",
Expand Down Expand Up @@ -1095,7 +1096,6 @@
"Anchor": "Anchor",
"Headphones": "Headphones",
"Folder": "Folder",
"Pin": "Pin",
"Your server isn't responding to some <a>requests</a>.": "Your server isn't responding to some <a>requests</a>.",
"Decline (%(counter)s)": "Decline (%(counter)s)",
"Accept <policyLink /> to continue:": "Accept <policyLink /> to continue:",
Expand Down