From c79596cfe60c360a7fbc04973e24abc394cb2e74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 5 May 2022 10:57:10 +0200 Subject: [PATCH] Add a way to maximize/pin widget from the PiP view (#7672) --- res/css/views/voip/_CallViewHeader.scss | 24 ++++--- src/components/views/voip/CallView.tsx | 8 +++ .../views/voip/CallView/CallViewHeader.tsx | 69 ++++++++++--------- src/components/views/voip/PipView.tsx | 55 ++++++++++++++- src/i18n/strings/en_EN.json | 2 +- 5 files changed, 112 insertions(+), 46 deletions(-) diff --git a/res/css/views/voip/_CallViewHeader.scss b/res/css/views/voip/_CallViewHeader.scss index 9340dfb040..6280da8cbb 100644 --- a/res/css/views/voip/_CallViewHeader.scss +++ b/res/css/views/voip/_CallViewHeader.scss @@ -45,6 +45,8 @@ limitations under the License. .mx_CallViewHeader_controls { margin-left: auto; + display: flex; + gap: 5px; } .mx_CallViewHeader_button { @@ -63,17 +65,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'); + } } } diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index bdcf7b38ad..296ebd79ae 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -270,6 +270,13 @@ export default class CallView extends React.Component { return { primary, sidebar }; } + private onMaximizeClick = (): void => { + dis.dispatch({ + action: 'video_fullscreen', + fullscreen: true, + }); + }; + private onMicMuteClick = async (): Promise => { const newVal = !this.state.micMuted; this.setState({ micMuted: await this.props.call.setMicrophoneMuted(newVal) }); @@ -614,6 +621,7 @@ export default class CallView extends React.Component { onPipMouseDown={onMouseDownOnHeader} pipMode={pipMode} callRooms={[callRoom, secCallRoom]} + onMaximize={this.onMaximizeClick} />
{ this.renderToast() } diff --git a/src/components/views/voip/CallView/CallViewHeader.tsx b/src/components/views/voip/CallView/CallViewHeader.tsx index 182ab2878a..fd585994d7 100644 --- a/src/components/views/voip/CallView/CallViewHeader.tsx +++ b/src/components/views/voip/CallView/CallViewHeader.tsx @@ -19,50 +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'; -import { ViewRoomPayload } from "../../../../dispatcher/payloads/ViewRoomPayload"; -interface CallViewHeaderProps { - pipMode: boolean; - callRooms?: Room[]; - onPipMouseDown: (event: React.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, - metricsTrigger: "WebFloatingCallWindow", - }); -}; - -type CallControlsProps = Pick & { - roomId: string; -}; -const CallViewHeaderControls: React.FC = ({ pipMode = false, roomId }) => { +const CallViewHeaderControls: React.FC = ({ onExpand, onPin, onMaximize }) => { return
- { !pipMode && } - { pipMode && } + { onExpand && onExpandClick(roomId)} + onClick={onExpand} title={_t("Return to call")} /> }
; }; -const SecondaryCallInfo: React.FC<{ callRoom: Room }> = ({ callRoom }) => { + +interface ISecondaryCallInfoProps { + callRoom: Room; +} + +const SecondaryCallInfo: React.FC = ({ callRoom }) => { return @@ -71,19 +60,31 @@ const SecondaryCallInfo: React.FC<{ callRoom: Room }> = ({ callRoom }) => { ; }; +interface CallViewHeaderProps { + pipMode: boolean; + callRooms?: Room[]; + onPipMouseDown: (event: React.MouseEvent) => void; + onExpand?: () => void; + onPin?: () => void; + onMaximize?: () => void; +} + const CallViewHeader: React.FC = ({ pipMode = false, callRooms = [], onPipMouseDown, + onExpand, + onPin, + onMaximize, }) => { const [callRoom, onHoldCallRoom] = callRooms; - const { roomId, name: callRoomName } = callRoom; + const callRoomName = callRoom.name; if (!pipMode) { return
{ _t("Call") } - +
; } return ( @@ -96,7 +97,7 @@ const CallViewHeader: React.FC = ({
{ callRoomName }
{ onHoldCallRoom && }
- +
); }; diff --git a/src/components/views/voip/PipView.tsx b/src/components/views/voip/PipView.tsx index db3ef0187d..613a542d70 100644 --- a/src/components/views/voip/PipView.tsx +++ b/src/components/views/voip/PipView.tsx @@ -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'; @@ -29,9 +30,10 @@ import { MatrixClientPeg } from '../../../MatrixClientPeg'; 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 WidgetStore, { IApp } from "../../../stores/WidgetStore"; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; const SHOW_CALL_IN_STATES = [ @@ -64,6 +66,16 @@ interface IState { moving: boolean; } +const getRoomAndAppForWidget = (widgetId: string, roomId: string): [Room, IApp] => { + if (!widgetId) return; + 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 @@ -232,6 +244,38 @@ export default class PipView extends React.Component { } }; + private onMaximize = (): void => { + const widgetId = this.state.persistentWidgetId; + const roomId = this.state.persistentRoomId; + + if (this.state.showWidgetInPip && widgetId && roomId) { + const [room, app] = getRoomAndAppForWidget(widgetId, roomId); + 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, this.state.persistentRoomId); + 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: this.state.persistentRoomId, + }); + }; + // Accepts a persistentWidgetId to be able to skip awaiting the setState for persistentWidgetId public updateShowWidgetInPip( persistentWidgetId = this.state.persistentWidgetId, @@ -276,7 +320,9 @@ export default class PipView extends React.Component { mx_CallView_pip: pipMode, mx_CallView_large: !pipMode, }); - const roomForWidget = MatrixClientPeg.get().getRoom(this.state.persistentRoomId); + const roomId = this.state.persistentRoomId; + const roomForWidget = MatrixClientPeg.get().getRoom(roomId); + const viewingCallRoom = this.state.viewedRoomId === roomId; pipContent = ({ onStartMoving, _onResize }) =>
@@ -284,10 +330,13 @@ export default class PipView extends React.Component { onPipMouseDown={(event) => { onStartMoving(event); this.onStartMoving.bind(this)(); }} pipMode={pipMode} callRooms={[roomForWidget]} + onExpand={!viewingCallRoom && this.onExpand} + onPin={viewingCallRoom && this.onPin} + onMaximize={viewingCallRoom && this.onMaximize} />
; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 67b2df8c50..a37af4d26c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1045,6 +1045,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", @@ -1128,7 +1129,6 @@ "Anchor": "Anchor", "Headphones": "Headphones", "Folder": "Folder", - "Pin": "Pin", "Your server isn't responding to some requests.": "Your server isn't responding to some requests.", "Decline (%(counter)s)": "Decline (%(counter)s)", "Accept to continue:": "Accept to continue:",