Skip to content

Commit 7d92dd8

Browse files
committed
♻️(frontend) decouple pip state and simplify pip options
Prevent PiP actions from affecting main panel state and reduce PiP-only noise.
1 parent a40c76b commit 7d92dd8

13 files changed

Lines changed: 139 additions & 145 deletions

File tree

src/frontend/src/features/pip/components/DocumentPiPPortal.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useMemo, useRef, useState } from 'react'
1+
import { type ReactNode, useEffect, useMemo, useRef, useState } from 'react'
22
import { createPortal } from 'react-dom'
33
import { useDocumentPiP } from '../hooks/useDocumentPiP'
44
import { UNSAFE_PortalProvider } from '@react-aria/overlays'
@@ -113,7 +113,7 @@ export const DocumentPiPPortal = ({
113113
height?: number
114114
children: React.ReactNode
115115
onClose?: () => void
116-
}) => {
116+
}): ReactNode => {
117117
const { openPiP, closePiP, pipWindow, isSupported } = useDocumentPiP({
118118
width,
119119
height,
@@ -189,5 +189,5 @@ export const DocumentPiPPortal = ({
189189
)
190190
}, [children, container])
191191

192-
return portal
192+
return portal as unknown as ReactNode
193193
}

src/frontend/src/features/pip/components/PipControlBar.tsx

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@ import { AudioDevicesControl } from '@/features/rooms/livekit/components/control
33
import { VideoDeviceControl } from '@/features/rooms/livekit/components/controls/Device/VideoDeviceControl'
44
import { ScreenShareToggle } from '@/features/rooms/livekit/components/controls/ScreenShareToggle'
55
import { LeaveButton } from '@/features/rooms/livekit/components/controls/LeaveButton'
6-
import { ReactionsToggle } from '@/features/rooms/livekit/components/controls/ReactionsToggle'
76
import { SubtitlesToggle } from '@/features/rooms/livekit/components/controls/SubtitlesToggle'
87
import { HandToggle } from '@/features/rooms/livekit/components/controls/HandToggle'
98
import { OptionsButton } from '@/features/rooms/livekit/components/controls/Options/OptionsButton'
109
import { StartMediaButton } from '@/features/rooms/livekit/components/controls/StartMediaButton'
11-
import { PipLateralMenu } from './controls/PipLateralMenu'
10+
import { ReactionsToggle } from '@/features/reactions/components/ReactionsToggle'
1211

1312
/**
1413
* Compact control bar for the Picture-in-Picture window.
@@ -31,9 +30,6 @@ export const PipControlBar = ({
3130
<LeaveButton />
3231
<StartMediaButton />
3332
</PipControlsCenter>
34-
<PipControlsRight>
35-
<PipLateralMenu />
36-
</PipControlsRight>
3733
</PipControls>
3834
)
3935

@@ -61,13 +57,3 @@ const PipControlsCenter = styled('div', {
6157
flex: '1 1 auto',
6258
},
6359
})
64-
65-
const PipControlsRight = styled('div', {
66-
base: {
67-
display: 'flex',
68-
justifyContent: 'flex-end',
69-
alignItems: 'center',
70-
position: 'absolute',
71-
right: '1.35rem',
72-
},
73-
})

src/frontend/src/features/pip/components/PipView.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,17 @@ import {
77
import { useTracks } from '@livekit/components-react'
88
import { Track } from 'livekit-client'
99
import { ParticipantTile } from '@/features/rooms/livekit/components/ParticipantTile'
10-
import { GridLayout } from '@/features/rooms/livekit/components/layout/GridLayout'
10+
import { GridLayout } from '@/features/layout/components/GridLayout'
1111
import { SidePanel } from '@/features/rooms/livekit/components/SidePanel'
12+
import { pipLayoutStore } from '../stores/pipLayoutStore'
1213
import { PipControlBar } from './PipControlBar'
1314

1415
const pickTrackForPip = (
1516
tracks: TrackReferenceOrPlaceholder[]
1617
): TrackReferenceOrPlaceholder | undefined => {
1718
// Prefer screen share when present; otherwise fallback to first available track.
1819
const screenShareTrack = tracks
19-
.filter(isTrackReference)
20+
.filter((track) => isTrackReference(track))
2021
.find((track) => track.publication.source === Track.Source.ScreenShare)
2122

2223
if (screenShareTrack) return screenShareTrack
@@ -60,7 +61,7 @@ export const PipView = () => {
6061
{/* Compact control bar for PiP; extend here when adding more actions. */}
6162
<PipControlBar showScreenShare={browserSupportsScreenSharing} />
6263
{/* Side panel (effects, settings, etc.) opens within PiP window. */}
63-
<SidePanel />
64+
<SidePanel store={pipLayoutStore} />
6465
</PipContainer>
6566
)
6667
}
Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
1+
import type { ReactNode } from 'react'
2+
13
import { DocumentPiPPortal } from './DocumentPiPPortal'
24
import { PipView } from './PipView'
35
import { useRoomPiP } from '../hooks/useRoomPiP'
46

57
/**
68
* Wrapper that mounts the PiP UI when room-level PiP state is enabled.
79
* Bridges Valtio-backed PiP state with DocumentPiPPortal and PipView rendering.
10+
* PiP panel state is decoupled via explicit pipLayoutStore injection.
811
*/
9-
export const RoomPiP = () => {
12+
export const RoomPiP = (): ReactNode => {
1013
const { isOpen, close } = useRoomPiP()
1114

12-
return (
13-
<DocumentPiPPortal isOpen={isOpen} onClose={close}>
14-
<PipView />
15-
</DocumentPiPPortal>
16-
)
15+
const portal = DocumentPiPPortal({
16+
isOpen,
17+
onClose: close,
18+
children: <PipView />,
19+
})
20+
return portal as ReactNode
1721
}

src/frontend/src/features/pip/components/controls/PipLateralMenu.tsx

Lines changed: 0 additions & 66 deletions
This file was deleted.

src/frontend/src/features/pip/components/controls/PipOptionsMenu.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { useEffect } from 'react'
22
import { RiMoreFill } from '@remixicon/react'
33
import { Box, Button } from '@/primitives'
44
import { css } from '@/styled-system/css'
5-
import { OptionsMenuItems } from '@/features/rooms/livekit/components/controls/Options/OptionsMenuItems'
5+
import { PipOptionsMenuItems } from './PipOptionsMenuItems'
66

77
type PipOptionsMenuProps = {
88
wrapperRef: React.RefObject<HTMLDivElement>
@@ -77,7 +77,7 @@ export const PipOptionsMenu = ({
7777
})}
7878
>
7979
<Box size="sm" type="popover" variant="dark">
80-
<OptionsMenuItems />
80+
<PipOptionsMenuItems />
8181
</Box>
8282
</div>
8383
)}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Menu as RACMenu, MenuSection } from 'react-aria-components'
2+
import { Separator } from '@/primitives/Separator'
3+
import { SettingsMenuItem } from '@/features/rooms/livekit/components/controls/Options/SettingsMenuItem'
4+
import { FeedbackMenuItem } from '@/features/rooms/livekit/components/controls/Options/FeedbackMenuItem'
5+
import { EffectsMenuItem } from '@/features/rooms/livekit/components/controls/Options/EffectsMenuItem'
6+
import { SupportMenuItem } from '@/features/rooms/livekit/components/controls/Options/SupportMenuItem'
7+
import { PictureInPictureMenuItem } from '@/features/rooms/livekit/components/controls/Options/PictureInPictureMenuItem'
8+
import { pipLayoutStore } from '@/features/pip/stores/pipLayoutStore'
9+
10+
/**
11+
* PiP options menu items: excludes transcript, screen recording, and full screen
12+
* (those features are not relevant in the PiP window context).
13+
*/
14+
export const PipOptionsMenuItems = () => (
15+
<RACMenu
16+
style={{
17+
minWidth: '150px',
18+
width: '300px',
19+
}}
20+
>
21+
<MenuSection>
22+
<PictureInPictureMenuItem />
23+
<EffectsMenuItem store={pipLayoutStore} />
24+
</MenuSection>
25+
<Separator />
26+
<MenuSection>
27+
<SupportMenuItem />
28+
<FeedbackMenuItem />
29+
<SettingsMenuItem />
30+
</MenuSection>
31+
</RACMenu>
32+
)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { proxy } from 'valtio'
2+
import type { PanelId, SubPanelId } from '@/features/rooms/livekit/types/panel'
3+
4+
type PipLayoutState = {
5+
activePanelId: PanelId | null
6+
activeSubPanelId: SubPanelId | null
7+
}
8+
9+
/**
10+
* Separate layout store for the PiP window.
11+
* Decouples PiP side panel state from the main view so opening Chat/Info/etc.
12+
* in PiP does not affect the main window and vice versa.
13+
*/
14+
export const pipLayoutStore = proxy<PipLayoutState>({
15+
activePanelId: null,
16+
activeSubPanelId: null,
17+
})

src/frontend/src/features/rooms/livekit/components/SidePanel.tsx

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
import { layoutStore } from '@/stores/layout'
21
import { css } from '@/styled-system/css'
32
import { Heading } from 'react-aria-components'
43
import { text } from '@/primitives/Text'
54
import { Button, Div } from '@/primitives'
65
import { RiArrowLeftLine, RiCloseLine } from '@remixicon/react'
76
import { useTranslation } from 'react-i18next'
87
import { ParticipantsList } from './controls/Participants/ParticipantsList'
9-
import { useSidePanel } from '../hooks/useSidePanel'
8+
import { type SidePanelStore, useSidePanel } from '../hooks/useSidePanel'
109
import { ReactNode } from 'react'
1110
import { Chat } from '../prefabs/Chat'
1211
import { Effects } from './effects/Effects'
@@ -144,7 +143,7 @@ const Panel = ({ isOpen, keepAlive = false, children }: PanelProps) => (
144143
{keepAlive || isOpen ? children : null}
145144
</div>
146145
)
147-
export const SidePanel = () => {
146+
export const SidePanel = ({ store }: { store?: SidePanelStore }) => {
148147
const {
149148
activePanelId,
150149
isParticipantsOpen,
@@ -156,7 +155,9 @@ export const SidePanel = () => {
156155
isInfoOpen,
157156
isSubPanelOpen,
158157
activeSubPanelId,
159-
} = useSidePanel()
158+
closePanel,
159+
goBack,
160+
} = useSidePanel(store)
160161
const { t } = useTranslation('rooms', { keyPrefix: 'sidePanel' })
161162
const title = t(`heading.${activeSubPanelId || activePanelId}`)
162163

@@ -166,18 +167,15 @@ export const SidePanel = () => {
166167
<StyledSidePanel
167168
title={title}
168169
ariaLabel={t('ariaLabel', { title })}
169-
onClose={() => {
170-
layoutStore.activePanelId = null
171-
layoutStore.activeSubPanelId = null
172-
}}
170+
onClose={closePanel}
173171
closeButtonTooltip={t('closeButton', {
174172
content: t(`content.${activeSubPanelId || activePanelId}`),
175173
})}
176174
isClosed={!isSidePanelOpen}
177175
isSubmenu={isSubPanelOpen}
178176
isReactionToolbarOpen={isReactionToolbarOpen}
179177
backButtonLabel={t('backToTools')}
180-
onBack={() => (layoutStore.activeSubPanelId = null)}
178+
onBack={goBack}
181179
>
182180
<Panel isOpen={isParticipantsOpen}>
183181
<ParticipantsList />

src/frontend/src/features/rooms/livekit/components/controls/Options/EffectsMenuItem.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import { RiImageCircleAiFill } from '@remixicon/react'
22
import { MenuItem } from 'react-aria-components'
33
import { useTranslation } from 'react-i18next'
44
import { menuRecipe } from '@/primitives/menuRecipe'
5-
import { useSidePanel } from '../../../hooks/useSidePanel'
5+
import { type SidePanelStore, useSidePanel } from '../../../hooks/useSidePanel'
66

7-
export const EffectsMenuItem = () => {
7+
export const EffectsMenuItem = ({ store }: { store?: SidePanelStore }) => {
88
const { t } = useTranslation('rooms', { keyPrefix: 'options.items' })
9-
const { toggleEffects } = useSidePanel()
9+
const { toggleEffects } = useSidePanel(store)
1010

1111
return (
1212
<MenuItem

0 commit comments

Comments
 (0)