From 3f90803f2906e82e4c112420f27f6199cb605761 Mon Sep 17 00:00:00 2001 From: Avea-marina Date: Mon, 18 May 2026 22:15:54 +0300 Subject: [PATCH 1/3] feat(modules.profile): add cameraSettings --- .../PreJoin/components/UserTile/Controls.tsx | 7 +- .../PreJoin/components/UserTile/UserTile.tsx | 183 +---------------- .../components/UserTile/UserTileUI.tsx | 189 ++++++++++++++++++ packages/modules.profile/package.json | 3 + .../modules.profile/src/ui/CameraSettings.tsx | 79 ++++++++ packages/modules.profile/src/ui/Category.tsx | 15 ++ packages/modules.profile/src/ui/Content.tsx | 2 + packages/modules.profile/src/ui/Effects.tsx | 17 +- packages/modules.profile/src/ui/Header.tsx | 1 + packages/modules.profile/src/ui/Menu.tsx | 10 +- pnpm-lock.yaml | 49 +++-- 11 files changed, 335 insertions(+), 220 deletions(-) create mode 100644 packages/modules.calls/src/ui/PreJoin/components/UserTile/UserTileUI.tsx create mode 100644 packages/modules.profile/src/ui/CameraSettings.tsx create mode 100644 packages/modules.profile/src/ui/Category.tsx diff --git a/packages/modules.calls/src/ui/PreJoin/components/UserTile/Controls.tsx b/packages/modules.calls/src/ui/PreJoin/components/UserTile/Controls.tsx index c402e895f..18721cc8e 100644 --- a/packages/modules.calls/src/ui/PreJoin/components/UserTile/Controls.tsx +++ b/packages/modules.calls/src/ui/PreJoin/components/UserTile/Controls.tsx @@ -7,9 +7,10 @@ import { usePersistentUserChoices } from '../../../../hooks/usePersistentUserCho type ControlsProps = { audioTrack?: LocalAudioTrack; videoTrack?: LocalVideoTrack; + disableMicroToggle?: boolean; }; -export const Controls = ({ audioTrack, videoTrack }: ControlsProps) => { +export const Controls = ({ audioTrack, videoTrack, disableMicroToggle = false }: ControlsProps) => { const { userChoices: { audioEnabled, videoEnabled }, saveAudioInputEnabled, @@ -83,11 +84,11 @@ export const Controls = ({ audioTrack, videoTrack }: ControlsProps) => { ); return ( -
+
; - userId: string; - isCameraDeniedOrPrompted: boolean; - isMicrophoneDeniedOrPrompted: boolean; - isVideoInitiated: boolean; -}) => { - const isPermissionsBlocked = isCameraDeniedOrPrompted || isMicrophoneDeniedOrPrompted; - - const hintMessage = useMemo(() => { - if (isPermissionsBlocked) { - return null; // для блока разрешений показываем отдельный контент - } - if (isCameraDeniedOrPrompted) { - return isMicrophoneDeniedOrPrompted - ? 'Камера и микрофон не разрешены' - : 'Камера не разрешена'; - } - if (!videoEnabled) { - return 'Камера отключена'; - } - if (!isVideoInitiated) { - return 'Запуск камеры...'; - } - if (videoTrack && videoEnabled) { - return ''; - } - return 'Камера недоступна'; - }, [ - videoTrack, - videoEnabled, - isCameraDeniedOrPrompted, - isMicrophoneDeniedOrPrompted, - isVideoInitiated, - isPermissionsBlocked, - ]); - - const permissionsInstructions = useMemo(() => { - if (isSafari()) { - const origin = - typeof window !== 'undefined' - ? (window.location?.origin?.replace('https://', '') ?? '') - : ''; - return [ - `Нажмите на иконку ${origin} в адресной строке`, - 'Снимите запрет на использование камеры и микрофона', - ]; - } - return [ - 'Нажмите на значок настроек в адресной строке браузера', - 'Снимите запрет на использование камеры и микрофона', - ]; - }, []); - - const permissionsButtonLabel = useMemo(() => { - if (!isMicrophoneDeniedOrPrompted && !isCameraDeniedOrPrompted) { - return null; - } - if (isCameraDeniedOrPrompted && isMicrophoneDeniedOrPrompted) { - return 'Как разрешить камеру и микрофон'; - } - if (isMicrophoneDeniedOrPrompted) { - return 'Как разрешить микрофон'; - } - if (isCameraDeniedOrPrompted) { - return 'Как разрешить камеру'; - } - return null; - }, [isMicrophoneDeniedOrPrompted, isCameraDeniedOrPrompted]); - - const renderVideo = useMemo(() => { - if (!videoTrack || isCameraDeniedOrPrompted) { - return null; - } - - return ( -
- -
- ); - }, [videoTrack, facingMode, videoEl, videoEnabled, isCameraDeniedOrPrompted, isVideoInitiated]); - - const renderAvatar = useMemo(() => { - if (videoTrack && !videoTrack.isMuted && !isCameraDeniedOrPrompted) return null; - - return ( -
- - - - -
- ); - }, [videoTrack, userId, isCameraDeniedOrPrompted]); - - return ( -
-
- {renderVideo} - {renderAvatar} - - {/* Блок при отсутствии разрешений (по макету PreJoin) */} - {isPermissionsBlocked && ( -
-

- Хотите, чтобы другие участники услышали вас? -

-
    - {permissionsInstructions.map((instruction, index) => ( -
  1. - {index === 0 && !isSafari() && } - {instruction} -
  2. - ))} -
-

- Камеру или микрофон можно отключить в любой момент. -

- {permissionsButtonLabel && ( - - )} -
- )} - - {/* Сообщения о состоянии камеры (без блока разрешений) */} - {!isPermissionsBlocked && hintMessage && ( -
-

{hintMessage}

-
- )} -
- -
- -
-
- ); -}; +import { UserTileUI } from './UserTileUI'; interface UserTileProps { audioTrack?: LocalAudioTrack; videoTrack?: LocalVideoTrack; + disableMicroToggle?: boolean; } -export const UserTile = ({ audioTrack, videoTrack }: UserTileProps) => { +export const UserTile = ({ audioTrack, videoTrack, disableMicroToggle = false }: UserTileProps) => { const { data: user } = useCurrentUser(); const { userId } = user; @@ -313,6 +137,7 @@ export const UserTile = ({ audioTrack, videoTrack }: UserTileProps) => { isCameraDeniedOrPrompted={isCameraDeniedOrPrompted} isMicrophoneDeniedOrPrompted={isMicrophoneDeniedOrPrompted} isVideoInitiated={isVideoInitiated} + disableMicroToggle={disableMicroToggle} /> ); }; diff --git a/packages/modules.calls/src/ui/PreJoin/components/UserTile/UserTileUI.tsx b/packages/modules.calls/src/ui/PreJoin/components/UserTile/UserTileUI.tsx new file mode 100644 index 000000000..515c1c4d9 --- /dev/null +++ b/packages/modules.calls/src/ui/PreJoin/components/UserTile/UserTileUI.tsx @@ -0,0 +1,189 @@ +import { useMemo } from 'react'; +import { LocalAudioTrack, LocalVideoTrack } from 'livekit-client'; +import { isSafari } from '../../../../utils/livekit'; +import { openPermissionsDialog } from '../../../../store/permissions'; +import { Avatar, AvatarFallback, AvatarImage } from '@xipkg/avatar'; +import { Button } from '@xipkg/button'; +import { Settings } from '@xipkg/icons'; +import { SecureVideo } from '../../../shared'; +import { Controls } from './Controls'; + +export const UserTileUI = ({ + audioTrack, + videoTrack, + videoEnabled, + facingMode, + videoEl, + userId, + isCameraDeniedOrPrompted, + isMicrophoneDeniedOrPrompted, + isVideoInitiated, + disableMicroToggle = false, +}: { + audioTrack?: LocalAudioTrack; + videoTrack?: LocalVideoTrack; + videoEnabled: boolean; + facingMode: string; + videoEl: React.RefObject; + userId: string; + isCameraDeniedOrPrompted: boolean; + isMicrophoneDeniedOrPrompted: boolean; + isVideoInitiated: boolean; + disableMicroToggle?: boolean; +}) => { + const isPermissionsBlocked = isCameraDeniedOrPrompted || isMicrophoneDeniedOrPrompted; + + const hintMessage = useMemo(() => { + if (isPermissionsBlocked) { + return null; // для блока разрешений показываем отдельный контент + } + if (isCameraDeniedOrPrompted) { + return isMicrophoneDeniedOrPrompted + ? 'Камера и микрофон не разрешены' + : 'Камера не разрешена'; + } + if (!videoEnabled) { + return 'Камера отключена'; + } + if (!isVideoInitiated) { + return 'Запуск камеры...'; + } + if (videoTrack && videoEnabled) { + return ''; + } + return 'Камера недоступна'; + }, [ + videoTrack, + videoEnabled, + isCameraDeniedOrPrompted, + isMicrophoneDeniedOrPrompted, + isVideoInitiated, + isPermissionsBlocked, + ]); + + const permissionsInstructions = useMemo(() => { + if (isSafari()) { + const origin = + typeof window !== 'undefined' + ? (window.location?.origin?.replace('https://', '') ?? '') + : ''; + return [ + `Нажмите на иконку ${origin} в адресной строке`, + 'Снимите запрет на использование камеры и микрофона', + ]; + } + return [ + 'Нажмите на значок настроек в адресной строке браузера', + 'Снимите запрет на использование камеры и микрофона', + ]; + }, []); + + const permissionsButtonLabel = useMemo(() => { + if (!isMicrophoneDeniedOrPrompted && !isCameraDeniedOrPrompted) { + return null; + } + if (isCameraDeniedOrPrompted && isMicrophoneDeniedOrPrompted) { + return 'Как разрешить камеру и микрофон'; + } + if (isMicrophoneDeniedOrPrompted) { + return 'Как разрешить микрофон'; + } + if (isCameraDeniedOrPrompted) { + return 'Как разрешить камеру'; + } + return null; + }, [isMicrophoneDeniedOrPrompted, isCameraDeniedOrPrompted]); + + const renderVideo = useMemo(() => { + if (!videoTrack || isCameraDeniedOrPrompted) { + return null; + } + + return ( +
+ +
+ ); + }, [videoTrack, facingMode, videoEl, videoEnabled, isCameraDeniedOrPrompted, isVideoInitiated]); + + const renderAvatar = useMemo(() => { + if (videoTrack && !videoTrack.isMuted && !isCameraDeniedOrPrompted) return null; + + return ( +
+ + + + +
+ ); + }, [videoTrack, userId, isCameraDeniedOrPrompted]); + + return ( +
{}} + className="bg-gray-40 relative flex aspect-video h-full w-full items-center justify-center overflow-hidden rounded-[16px]" + > +
+ {renderVideo} + {renderAvatar} + + {/* Блок при отсутствии разрешений (по макету PreJoin) */} + {isPermissionsBlocked && ( +
+

+ Хотите, чтобы другие участники услышали вас? +

+
    + {permissionsInstructions.map((instruction, index) => ( +
  1. + {index === 0 && !isSafari() && } + {instruction} +
  2. + ))} +
+

+ Камеру или микрофон можно отключить в любой момент. +

+ {permissionsButtonLabel && ( + + )} +
+ )} + + {/* Сообщения о состоянии камеры (без блока разрешений) */} + {!isPermissionsBlocked && hintMessage && ( +
+

{hintMessage}

+
+ )} +
+ +
+ +
+
+ ); +}; diff --git a/packages/modules.profile/package.json b/packages/modules.profile/package.json index 61bd54482..698a5288a 100644 --- a/packages/modules.profile/package.json +++ b/packages/modules.profile/package.json @@ -11,6 +11,9 @@ "lint": "eslint ." }, "dependencies": { + "@livekit/components-core": "0.12.9", + "@livekit/components-react": "2.9.14", + "@livekit/components-styles": "1.1.6", "@xipkg/avatar": "3.3.0", "@xipkg/badge": "2.0.12", "@xipkg/button": "4.1.0", diff --git a/packages/modules.profile/src/ui/CameraSettings.tsx b/packages/modules.profile/src/ui/CameraSettings.tsx new file mode 100644 index 000000000..50d153298 --- /dev/null +++ b/packages/modules.profile/src/ui/CameraSettings.tsx @@ -0,0 +1,79 @@ +import { createLocalVideoTrack, LocalVideoTrack } from 'livekit-client'; +import { Camera } from '@xipkg/icons'; +import { ScrollArea } from '@xipkg/scrollarea'; +import { Category } from './Category'; +import { useMediaQuery } from '@xipkg/utils'; +import { MediaDeviceMenu } from '../../../modules.calls/src/ui/PreJoin/components/MediaDevices/MediaDeviceMenu'; +import { UserTile } from '../../../modules.calls/src/ui/PreJoin/components/UserTile'; +import { useEffect, useMemo, useState } from 'react'; +import { usePersistentUserChoices } from '../../../modules.calls/src/hooks/usePersistentUserChoices'; +import { usePermissionsStore } from '../../../modules.calls/src/store/permissions'; + +export const CameraSettings = () => { + const isMobile = useMediaQuery('(max-width: 719px)'); + const [videoTrack, setVideoTrack] = useState(); + const { + userChoices: { videoDeviceId }, + saveVideoInputEnabled, + saveVideoInputDeviceId, + } = usePersistentUserChoices(); + const cameraPermission = usePermissionsStore((s) => s.cameraPermission); + + const videoMenuKey = `videoinput-${cameraPermission}`; + + useEffect(() => { + const initCamera = async () => { + try { + const track = await createLocalVideoTrack({ + deviceId: { exact: videoDeviceId }, + }); + setVideoTrack(track); + } catch (error) { + console.error('Failed to start camera:', error); + } + }; + + initCamera(); + }, [videoDeviceId]); + + const handleVideoDeviceChange = useMemo( + () => async (_kind: MediaDeviceKind, deviceId: string) => { + try { + saveVideoInputDeviceId(deviceId); + if (videoTrack) { + await videoTrack.setDeviceId({ exact: deviceId }); + + const isActuallyEnabled = !videoTrack.isMuted; + saveVideoInputEnabled(isActuallyEnabled); + } + } catch (err) { + console.error('Failed to switch camera device', err); + } + }, + [videoTrack, saveVideoInputDeviceId, saveVideoInputEnabled], + ); + + return ( + <> + {!isMobile && ( +

Настройки видео

+ )} + +
+ } title="Камера"> + +
+ +
+
+
+
+ + ); +}; diff --git a/packages/modules.profile/src/ui/Category.tsx b/packages/modules.profile/src/ui/Category.tsx new file mode 100644 index 000000000..4380ce60d --- /dev/null +++ b/packages/modules.profile/src/ui/Category.tsx @@ -0,0 +1,15 @@ +type CategoryProps = { + icon: React.ReactNode; + title: string; + children: React.ReactNode; +}; + +export const Category = ({ icon, title, children }: CategoryProps) => ( +
+
+ {icon} + {title} +
+
{children}
+
+); diff --git a/packages/modules.profile/src/ui/Content.tsx b/packages/modules.profile/src/ui/Content.tsx index d8e96e6f4..0b758da16 100644 --- a/packages/modules.profile/src/ui/Content.tsx +++ b/packages/modules.profile/src/ui/Content.tsx @@ -5,6 +5,7 @@ import { PersonalData } from './PersonalData'; import { Notifications } from './Notifications'; import { Effects } from './Effects'; import { TechnicalReport } from './TechnicalReport'; +import { CameraSettings } from './CameraSettings'; type ComponentMapT = { [key: string]: ReactElement; @@ -17,6 +18,7 @@ const componentMap: ComponentMapT = { notifications: , effects: , report: , + cameraSettings: , }; type ContentPropsT = { diff --git a/packages/modules.profile/src/ui/Effects.tsx b/packages/modules.profile/src/ui/Effects.tsx index b9c38d847..44b88c643 100644 --- a/packages/modules.profile/src/ui/Effects.tsx +++ b/packages/modules.profile/src/ui/Effects.tsx @@ -4,6 +4,7 @@ import { Slider } from '@xipkg/slider'; import { Toggle } from '@xipkg/toggle'; import { useMediaQuery } from '@xipkg/utils'; import { useSoundEffectsStore, SOUND_DEFAULTS, type SoundKey } from 'common.ui'; +import { Category } from './Category'; type SoundItemProps = { label: string; @@ -92,22 +93,6 @@ const SoundItem = ({ ); }; -type CategoryProps = { - icon: React.ReactNode; - title: string; - children: React.ReactNode; -}; - -const Category = ({ icon, title, children }: CategoryProps) => ( -
-
- {icon} - {title} -
-
{children}
-
-); - export const Effects = () => { const isMobile = useMediaQuery('(max-width: 719px)'); diff --git a/packages/modules.profile/src/ui/Header.tsx b/packages/modules.profile/src/ui/Header.tsx index 434cd1bcd..a20c612c1 100644 --- a/packages/modules.profile/src/ui/Header.tsx +++ b/packages/modules.profile/src/ui/Header.tsx @@ -9,6 +9,7 @@ const menuLabels = [ 'Безопасность', 'Уведомления', 'Эффекты', + 'Настройки видео', 'Отчёт', ]; diff --git a/packages/modules.profile/src/ui/Menu.tsx b/packages/modules.profile/src/ui/Menu.tsx index 5a4406849..40388d11a 100644 --- a/packages/modules.profile/src/ui/Menu.tsx +++ b/packages/modules.profile/src/ui/Menu.tsx @@ -1,4 +1,4 @@ -import { Account, Exit, Key, Palette, Notification, File, Music } from '@xipkg/icons'; +import { Account, Exit, Key, Palette, Notification, File, Music, Camera } from '@xipkg/icons'; import { Dispatch, SetStateAction } from 'react'; import { useLocation, useNavigate, useSearch } from '@tanstack/react-router'; import { useAuth } from 'common.auth'; @@ -29,6 +29,10 @@ const options: ItemT[] = [ name: 'Эффекты', query: 'effects', }, + { + name: 'Настройки видео', + query: 'cameraSettings', + }, { name: 'Отчёт', query: 'report', @@ -70,6 +74,8 @@ const Item = ({ index, item, onMenuItemChange }: ItemPropsT) => { return ; case 'report': return ; + case 'cameraSettings': + return ; default: return null; } @@ -123,7 +129,7 @@ export const Menu = ({ setActiveContent, setActiveQuery, setShowContent }: MenuP }; return ( -
+
{options.map((item, index) => ( ))} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 656310047..68ab73b5e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3765,6 +3765,15 @@ importers: packages/modules.profile: dependencies: + '@livekit/components-core': + specifier: 0.12.9 + version: 0.12.9(livekit-client@2.15.6(@types/dom-mediacapture-record@1.0.22))(tslib@2.8.1) + '@livekit/components-react': + specifier: 2.9.14 + version: 2.9.14(@livekit/krisp-noise-filter@0.3.4(livekit-client@2.15.6(@types/dom-mediacapture-record@1.0.22)))(livekit-client@2.15.6(@types/dom-mediacapture-record@1.0.22))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tslib@2.8.1) + '@livekit/components-styles': + specifier: 1.1.6 + version: 1.1.6 '@xipkg/avatar': specifier: 3.3.0 version: 3.3.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) @@ -9699,8 +9708,8 @@ packages: peerDependencies: eslint: '>=7.0.0' - eslint-config-turbo@2.9.9: - resolution: {integrity: sha512-KhO8N8ot1s9wXbkNDVgMwvkpbfO+YgEbteikFu406aQoJgenzNh5ErI985CeFKV3e33ZGLZX5YAJ1pgnhOXCZA==} + eslint-config-turbo@2.9.12: + resolution: {integrity: sha512-QnAiTMzCz2mP2EGGVLx1j6mDsKRhMaY424IqJql3z1AQ06OSlXYl3aSIQEwQJEH5RgW/fHx9VK213BF3ZwO8Sw==} peerDependencies: eslint: '>6.6.0' turbo: '>2.0.0' @@ -9790,14 +9799,14 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - eslint-plugin-turbo@2.9.8: - resolution: {integrity: sha512-czP2GjPzR6G+BpVEKXNa7Vs45/Zz0QjUEyB+SSLNpVJTqBe331AlE9IxkC2Eh00H6a1hf/Q6BVwf+1WJupyVYA==} + eslint-plugin-turbo@2.9.12: + resolution: {integrity: sha512-Ilv1DTYyghdIdTUsW/VbjVTYKt1Hfs7X2C+rq/i4u7ZVofzcHZwk/3QwrV5UyOGADmxmWNyD+xcRS7+ZE5arQg==} peerDependencies: eslint: '>6.6.0' turbo: '>2.0.0' - eslint-plugin-turbo@2.9.9: - resolution: {integrity: sha512-CHjV79PUXv5D+eCcnk3IMfu15X+EnwDSOWcVM72gQ3Ib8J+pF6zrY7A0kOgdS2HexdaGDoqzSLiXYRyu42TaGQ==} + eslint-plugin-turbo@2.9.8: + resolution: {integrity: sha512-czP2GjPzR6G+BpVEKXNa7Vs45/Zz0QjUEyB+SSLNpVJTqBe331AlE9IxkC2Eh00H6a1hf/Q6BVwf+1WJupyVYA==} peerDependencies: eslint: '>6.6.0' turbo: '>2.0.0' @@ -15671,10 +15680,10 @@ snapshots: '@xipkg/eslint@3.2.0(eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1))(turbo@2.9.8)(typescript@5.7.3)': dependencies: '@typescript-eslint/parser': 8.59.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.7.3) - eslint-config-airbnb: 19.0.4(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.59.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.7.3))(eslint@9.39.4(jiti@2.6.1)))(eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.4(jiti@2.6.1)))(eslint-plugin-react-hooks@5.1.0(eslint@9.39.4(jiti@2.6.1)))(eslint-plugin-react@7.37.2(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)) + eslint-config-airbnb: 19.0.4(eslint-plugin-import@2.31.0)(eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.4(jiti@2.6.1)))(eslint-plugin-react-hooks@5.1.0(eslint@9.39.4(jiti@2.6.1)))(eslint-plugin-react@7.37.2(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)) eslint-config-next: 15.1.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.7.3) eslint-config-prettier: 9.1.0(eslint@9.39.4(jiti@2.6.1)) - eslint-config-turbo: 2.9.9(eslint@9.39.4(jiti@2.6.1))(turbo@2.9.8) + eslint-config-turbo: 2.9.12(eslint@9.39.4(jiti@2.6.1))(turbo@2.9.8) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.59.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.7.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-react: 7.37.2(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-react-hooks: 5.1.0(eslint@9.39.4(jiti@2.6.1)) @@ -16659,7 +16668,7 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.59.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.7.3))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)): + eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.31.0)(eslint@9.39.4(jiti@2.6.1)): dependencies: confusing-browser-globals: 1.0.11 eslint: 9.39.4(jiti@2.6.1) @@ -16668,10 +16677,10 @@ snapshots: object.entries: 1.1.9 semver: 6.3.1 - eslint-config-airbnb@19.0.4(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.59.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.7.3))(eslint@9.39.4(jiti@2.6.1)))(eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.4(jiti@2.6.1)))(eslint-plugin-react-hooks@5.1.0(eslint@9.39.4(jiti@2.6.1)))(eslint-plugin-react@7.37.2(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)): + eslint-config-airbnb@19.0.4(eslint-plugin-import@2.31.0)(eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.4(jiti@2.6.1)))(eslint-plugin-react-hooks@5.1.0(eslint@9.39.4(jiti@2.6.1)))(eslint-plugin-react@7.37.2(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)): dependencies: eslint: 9.39.4(jiti@2.6.1) - eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.59.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.7.3))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)) + eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.31.0)(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.59.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.7.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-react: 7.37.2(eslint@9.39.4(jiti@2.6.1)) @@ -16687,7 +16696,7 @@ snapshots: '@typescript-eslint/parser': 8.59.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.7.3) eslint: 9.39.4(jiti@2.6.1) eslint-import-resolver-node: 0.3.10 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.59.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.7.3))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.59.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.7.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.39.4(jiti@2.6.1)) @@ -16707,10 +16716,10 @@ snapshots: dependencies: eslint: 9.39.4(jiti@2.6.1) - eslint-config-turbo@2.9.9(eslint@9.39.4(jiti@2.6.1))(turbo@2.9.8): + eslint-config-turbo@2.9.12(eslint@9.39.4(jiti@2.6.1))(turbo@2.9.8): dependencies: eslint: 9.39.4(jiti@2.6.1) - eslint-plugin-turbo: 2.9.9(eslint@9.39.4(jiti@2.6.1))(turbo@2.9.8) + eslint-plugin-turbo: 2.9.12(eslint@9.39.4(jiti@2.6.1))(turbo@2.9.8) turbo: 2.9.8 eslint-import-resolver-node@0.3.10: @@ -16721,7 +16730,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.59.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.7.3))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@9.39.4(jiti@2.6.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3 @@ -16736,14 +16745,14 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.7.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.59.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.7.3))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.7.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.59.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.7.3) eslint: 9.39.4(jiti@2.6.1) eslint-import-resolver-node: 0.3.10 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.59.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.7.3))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.39.4(jiti@2.6.1)) transitivePeerDependencies: - supports-color @@ -16758,7 +16767,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.4(jiti@2.6.1) eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.59.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.7.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.59.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.7.3))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.59.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.7.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)) hasown: 2.0.3 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -16853,13 +16862,13 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 - eslint-plugin-turbo@2.9.8(eslint@9.39.4(jiti@2.6.1))(turbo@2.9.8): + eslint-plugin-turbo@2.9.12(eslint@9.39.4(jiti@2.6.1))(turbo@2.9.8): dependencies: dotenv: 16.0.3 eslint: 9.39.4(jiti@2.6.1) turbo: 2.9.8 - eslint-plugin-turbo@2.9.9(eslint@9.39.4(jiti@2.6.1))(turbo@2.9.8): + eslint-plugin-turbo@2.9.8(eslint@9.39.4(jiti@2.6.1))(turbo@2.9.8): dependencies: dotenv: 16.0.3 eslint: 9.39.4(jiti@2.6.1) From 6c29dbc343a9ad1cc0851fb1d9aeb2cffa8fc42c Mon Sep 17 00:00:00 2001 From: Avea-marina Date: Tue, 19 May 2026 00:41:27 +0300 Subject: [PATCH 2/3] fix(modules.profile): fix reexport --- packages/modules.calls/index.ts | 6 +- packages/modules.calls/src/index.ts | 5 +- packages/modules.calls/src/store/index.ts | 1 + .../MediaDevices/MediaDeviceMenu.tsx | 0 .../MediaDevices/MediaDeviceSelect.tsx | 0 .../MediaDevices/MediaDevices.tsx | 12 ++-- .../src/ui/MediaDevices/index.ts | 2 + .../modules.calls/src/ui/PreJoin/PreJoin.tsx | 4 +- .../PreJoin/components/MediaDevices/index.ts | 1 - .../src/ui/PreJoin/components/index.ts | 2 - .../components => }/UserTile/Controls.tsx | 4 +- .../components => }/UserTile/UserTile.tsx | 4 +- .../components => }/UserTile/UserTileUI.tsx | 6 +- .../components => }/UserTile/index.ts | 0 packages/modules.calls/src/ui/index.ts | 2 + packages/modules.profile/package.json | 1 + .../modules.profile/src/ui/CameraPreview.tsx | 71 +++++++++++++++++++ .../modules.profile/src/ui/CameraSettings.tsx | 14 ++-- pnpm-lock.yaml | 19 ++--- 19 files changed, 117 insertions(+), 37 deletions(-) rename packages/modules.calls/src/ui/{PreJoin/components => }/MediaDevices/MediaDeviceMenu.tsx (100%) rename packages/modules.calls/src/ui/{PreJoin/components => }/MediaDevices/MediaDeviceSelect.tsx (100%) rename packages/modules.calls/src/ui/{PreJoin/components => }/MediaDevices/MediaDevices.tsx (94%) create mode 100644 packages/modules.calls/src/ui/MediaDevices/index.ts delete mode 100644 packages/modules.calls/src/ui/PreJoin/components/MediaDevices/index.ts rename packages/modules.calls/src/ui/{PreJoin/components => }/UserTile/Controls.tsx (95%) rename packages/modules.calls/src/ui/{PreJoin/components => }/UserTile/UserTile.tsx (96%) rename packages/modules.calls/src/ui/{PreJoin/components => }/UserTile/UserTileUI.tsx (97%) rename packages/modules.calls/src/ui/{PreJoin/components => }/UserTile/index.ts (100%) create mode 100644 packages/modules.profile/src/ui/CameraPreview.tsx diff --git a/packages/modules.calls/index.ts b/packages/modules.calls/index.ts index 93ea958a3..56c22b389 100644 --- a/packages/modules.calls/index.ts +++ b/packages/modules.calls/index.ts @@ -1,6 +1,6 @@ export { Call, CompactView, useModeSync, ModeSyncProvider } from './src'; export { LiveKitProvider } from './src/providers/LiveKitProvider'; export { RoomProvider } from './src/providers/RoomProvider'; -export { useCallStore } from './src/store/callStore'; -export { useStartCall } from './src/hooks'; -export { useUmamiActivityHeartbeat } from './src'; +export { useCallStore, usePermissionsStore } from './src/store'; +export { useStartCall, usePersistentUserChoices } from './src/hooks'; +export { useUmamiActivityHeartbeat, MediaDeviceMenu, UserTile } from './src'; diff --git a/packages/modules.calls/src/index.ts b/packages/modules.calls/src/index.ts index 3347ceb90..4463116bb 100644 --- a/packages/modules.calls/src/index.ts +++ b/packages/modules.calls/src/index.ts @@ -1,4 +1,4 @@ -export { Call, CompactView } from './ui'; +export { Call, CompactView, MediaDeviceMenu, UserTile } from './ui'; export { RoomProvider, LiveKitProvider, ModeSyncProvider } from './providers'; export { useSize, @@ -12,7 +12,8 @@ export { useSpeakingParticipant, useUmamiActivityHeartbeat, useNoiseCancellation, + usePersistentUserChoices, type UseNoiseCancellationOptions, type UseNoiseCancellationResult, } from './hooks'; -export { useCallStore } from './store'; +export { useCallStore, usePermissionsStore } from './store'; diff --git a/packages/modules.calls/src/store/index.ts b/packages/modules.calls/src/store/index.ts index f5c596acc..8f573fb0a 100644 --- a/packages/modules.calls/src/store/index.ts +++ b/packages/modules.calls/src/store/index.ts @@ -1 +1,2 @@ export { useCallStore } from './callStore'; +export { usePermissionsStore } from './permissions'; diff --git a/packages/modules.calls/src/ui/PreJoin/components/MediaDevices/MediaDeviceMenu.tsx b/packages/modules.calls/src/ui/MediaDevices/MediaDeviceMenu.tsx similarity index 100% rename from packages/modules.calls/src/ui/PreJoin/components/MediaDevices/MediaDeviceMenu.tsx rename to packages/modules.calls/src/ui/MediaDevices/MediaDeviceMenu.tsx diff --git a/packages/modules.calls/src/ui/PreJoin/components/MediaDevices/MediaDeviceSelect.tsx b/packages/modules.calls/src/ui/MediaDevices/MediaDeviceSelect.tsx similarity index 100% rename from packages/modules.calls/src/ui/PreJoin/components/MediaDevices/MediaDeviceSelect.tsx rename to packages/modules.calls/src/ui/MediaDevices/MediaDeviceSelect.tsx diff --git a/packages/modules.calls/src/ui/PreJoin/components/MediaDevices/MediaDevices.tsx b/packages/modules.calls/src/ui/MediaDevices/MediaDevices.tsx similarity index 94% rename from packages/modules.calls/src/ui/PreJoin/components/MediaDevices/MediaDevices.tsx rename to packages/modules.calls/src/ui/MediaDevices/MediaDevices.tsx index 21d6898ad..1ea04cf8f 100644 --- a/packages/modules.calls/src/ui/PreJoin/components/MediaDevices/MediaDevices.tsx +++ b/packages/modules.calls/src/ui/MediaDevices/MediaDevices.tsx @@ -1,18 +1,18 @@ import { Button } from '@xipkg/button'; import { MediaDeviceMenu } from './MediaDeviceMenu'; -import { usePersistentUserChoices } from '../../../../hooks/usePersistentUserChoices'; +import { usePersistentUserChoices } from '../../hooks/usePersistentUserChoices'; import { useMemo } from 'react'; import { LocalAudioTrack, LocalVideoTrack } from 'livekit-client'; -import { useCallStore } from '../../../../store/callStore'; -import { useRoom } from '../../../../providers/RoomProvider'; -import { usePermissionsStore } from '../../../../store/permissions'; +import { useCallStore } from '../../store/callStore'; +import { useRoom } from '../../providers/RoomProvider'; +import { usePermissionsStore } from '../../store/permissions'; import { Alert, AlertIcon, AlertContainer, AlertDescription } from '@xipkg/alert'; import { InfoCircle } from '@xipkg/icons'; import { Label } from '@xipkg/label'; import { Toggle } from '@xipkg/toggle'; import { supportsBackgroundProcessors } from '@livekit/track-processors'; -import type { UseNoiseCancellationResult } from '../../../../hooks/useNoiseCancellation'; -import { NoiseCancellationSettings } from '../../../../ui/shared/NoiseCancellationSettings'; +import type { UseNoiseCancellationResult } from '../../hooks/useNoiseCancellation'; +import { NoiseCancellationSettings } from '../shared/NoiseCancellationSettings'; interface MediaDevicesProps { audioTrack?: LocalAudioTrack; diff --git a/packages/modules.calls/src/ui/MediaDevices/index.ts b/packages/modules.calls/src/ui/MediaDevices/index.ts new file mode 100644 index 000000000..4410d1b5e --- /dev/null +++ b/packages/modules.calls/src/ui/MediaDevices/index.ts @@ -0,0 +1,2 @@ +export { MediaDevices } from './MediaDevices'; +export { MediaDeviceMenu } from './MediaDeviceMenu'; diff --git a/packages/modules.calls/src/ui/PreJoin/PreJoin.tsx b/packages/modules.calls/src/ui/PreJoin/PreJoin.tsx index fb5872095..a864a8079 100644 --- a/packages/modules.calls/src/ui/PreJoin/PreJoin.tsx +++ b/packages/modules.calls/src/ui/PreJoin/PreJoin.tsx @@ -1,5 +1,7 @@ import { ScrollArea } from '@xipkg/scrollarea'; -import { Header, UserTile, MediaDevices } from './components'; +import { Header } from './components'; +import { UserTile } from '../UserTile'; +import { MediaDevices } from '../MediaDevices'; import { useMemo, useRef, useEffect, useCallback, useState } from 'react'; import { Track, diff --git a/packages/modules.calls/src/ui/PreJoin/components/MediaDevices/index.ts b/packages/modules.calls/src/ui/PreJoin/components/MediaDevices/index.ts deleted file mode 100644 index 7d39ff9f0..000000000 --- a/packages/modules.calls/src/ui/PreJoin/components/MediaDevices/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { MediaDevices } from './MediaDevices'; diff --git a/packages/modules.calls/src/ui/PreJoin/components/index.ts b/packages/modules.calls/src/ui/PreJoin/components/index.ts index 987c453b2..572b2d79d 100644 --- a/packages/modules.calls/src/ui/PreJoin/components/index.ts +++ b/packages/modules.calls/src/ui/PreJoin/components/index.ts @@ -1,3 +1 @@ export { Header } from './Header/Header'; -export { UserTile } from './UserTile/UserTile'; -export { MediaDevices } from './MediaDevices/MediaDevices'; diff --git a/packages/modules.calls/src/ui/PreJoin/components/UserTile/Controls.tsx b/packages/modules.calls/src/ui/UserTile/Controls.tsx similarity index 95% rename from packages/modules.calls/src/ui/PreJoin/components/UserTile/Controls.tsx rename to packages/modules.calls/src/ui/UserTile/Controls.tsx index 18721cc8e..477cd840a 100644 --- a/packages/modules.calls/src/ui/PreJoin/components/UserTile/Controls.tsx +++ b/packages/modules.calls/src/ui/UserTile/Controls.tsx @@ -1,8 +1,8 @@ import { LocalAudioTrack, LocalVideoTrack, Track } from 'livekit-client'; import { useCallback, useMemo } from 'react'; -import { DevicesBar } from '../../../shared/DevicesBar'; -import { usePersistentUserChoices } from '../../../../hooks/usePersistentUserChoices'; +import { DevicesBar } from '../shared/DevicesBar'; +import { usePersistentUserChoices } from '../../hooks/usePersistentUserChoices'; type ControlsProps = { audioTrack?: LocalAudioTrack; diff --git a/packages/modules.calls/src/ui/PreJoin/components/UserTile/UserTile.tsx b/packages/modules.calls/src/ui/UserTile/UserTile.tsx similarity index 96% rename from packages/modules.calls/src/ui/PreJoin/components/UserTile/UserTile.tsx rename to packages/modules.calls/src/ui/UserTile/UserTile.tsx index a5ab4fa2f..98de785b6 100644 --- a/packages/modules.calls/src/ui/PreJoin/components/UserTile/UserTile.tsx +++ b/packages/modules.calls/src/ui/UserTile/UserTile.tsx @@ -1,8 +1,8 @@ import { useMemo, useRef, useEffect, useState } from 'react'; import { facingModeFromLocalTrack, LocalVideoTrack, LocalAudioTrack } from 'livekit-client'; import { useCurrentUser } from 'common.services'; -import { usePersistentUserChoices } from '../../../../hooks/usePersistentUserChoices'; -import { useCannotUseDevice } from '../../../../hooks/useCannotUseDevice'; +import { usePersistentUserChoices } from '../../hooks/usePersistentUserChoices'; +import { useCannotUseDevice } from '../../hooks/useCannotUseDevice'; import { UserTileUI } from './UserTileUI'; interface UserTileProps { diff --git a/packages/modules.calls/src/ui/PreJoin/components/UserTile/UserTileUI.tsx b/packages/modules.calls/src/ui/UserTile/UserTileUI.tsx similarity index 97% rename from packages/modules.calls/src/ui/PreJoin/components/UserTile/UserTileUI.tsx rename to packages/modules.calls/src/ui/UserTile/UserTileUI.tsx index 515c1c4d9..03134e301 100644 --- a/packages/modules.calls/src/ui/PreJoin/components/UserTile/UserTileUI.tsx +++ b/packages/modules.calls/src/ui/UserTile/UserTileUI.tsx @@ -1,11 +1,11 @@ import { useMemo } from 'react'; import { LocalAudioTrack, LocalVideoTrack } from 'livekit-client'; -import { isSafari } from '../../../../utils/livekit'; -import { openPermissionsDialog } from '../../../../store/permissions'; +import { isSafari } from '../../utils/livekit'; +import { openPermissionsDialog } from '../../store/permissions'; import { Avatar, AvatarFallback, AvatarImage } from '@xipkg/avatar'; import { Button } from '@xipkg/button'; import { Settings } from '@xipkg/icons'; -import { SecureVideo } from '../../../shared'; +import { SecureVideo } from '../shared'; import { Controls } from './Controls'; export const UserTileUI = ({ diff --git a/packages/modules.calls/src/ui/PreJoin/components/UserTile/index.ts b/packages/modules.calls/src/ui/UserTile/index.ts similarity index 100% rename from packages/modules.calls/src/ui/PreJoin/components/UserTile/index.ts rename to packages/modules.calls/src/ui/UserTile/index.ts diff --git a/packages/modules.calls/src/ui/index.ts b/packages/modules.calls/src/ui/index.ts index 1de871deb..3548b6b8b 100644 --- a/packages/modules.calls/src/ui/index.ts +++ b/packages/modules.calls/src/ui/index.ts @@ -4,3 +4,5 @@ export { ChatButton } from './Bottom/ChatButton'; export { Chat } from './Chat/Chat'; export { RaiseHandButton } from './Bottom/RaiseHandButton'; export { RaisedHandIndicator } from './Participant/RaisedHandIndicator'; +export { MediaDeviceMenu } from './MediaDevices'; +export { UserTile } from './UserTile'; diff --git a/packages/modules.profile/package.json b/packages/modules.profile/package.json index 698a5288a..42dd8a4e4 100644 --- a/packages/modules.profile/package.json +++ b/packages/modules.profile/package.json @@ -35,6 +35,7 @@ "common.types": "workspace:*", "dayjs": "1.11.13", "features.avatar.editor": "workspace:*", + "modules.calls": "workspace:*", "sonner": "^2.0.7" }, "devDependencies": { diff --git a/packages/modules.profile/src/ui/CameraPreview.tsx b/packages/modules.profile/src/ui/CameraPreview.tsx new file mode 100644 index 000000000..b5c74e223 --- /dev/null +++ b/packages/modules.profile/src/ui/CameraPreview.tsx @@ -0,0 +1,71 @@ +import { createLocalVideoTrack, LocalAudioTrack, LocalVideoTrack } from 'livekit-client'; +import { MediaDeviceMenu } from '../../../modules.calls/src/ui/MediaDevices/MediaDeviceMenu'; +import { useEffect, useMemo, useState } from 'react'; +import { UserTile } from '../../../modules.calls/src/ui/UserTile/UserTile'; +import { usePersistentUserChoices } from '../../../modules.calls/src/hooks/usePersistentUserChoices'; +import { usePermissionsStore } from '../../../modules.calls/src/store/permissions'; + +interface CameraPreviewProps { + audioTrack?: LocalAudioTrack; + videoTrack?: LocalVideoTrack; +} + +export const CameraPreview = ({ audioTrack }: CameraPreviewProps) => { + const [videoTrack, setVideoTrack] = useState(); + const { + userChoices: { videoDeviceId }, + saveVideoInputEnabled, + saveVideoInputDeviceId, + } = usePersistentUserChoices(); + const cameraPermission = usePermissionsStore((s) => s.cameraPermission); + + const videoMenuKey = `videoinput-${cameraPermission}`; + + useEffect(() => { + const initCamera = async () => { + try { + const track = await createLocalVideoTrack({ + deviceId: { exact: videoDeviceId }, + }); + setVideoTrack(track); + } catch (error) { + console.error('Failed to start camera:', error); + } + }; + + initCamera(); + }, []); + + const handleVideoDeviceChange = useMemo( + () => async (_kind: MediaDeviceKind, deviceId: string) => { + try { + saveVideoInputDeviceId(deviceId); + if (videoTrack) { + await videoTrack.setDeviceId({ exact: deviceId }); + // Синхронизируем состояние после смены устройства + const isActuallyEnabled = !videoTrack.isMuted; + saveVideoInputEnabled(isActuallyEnabled); + } + } catch (err) { + console.error('Failed to switch camera device', err); + } + }, + [videoTrack, saveVideoInputDeviceId, saveVideoInputEnabled], + ); + + return ( + <> + +
+

Камера

+ +
+ + ); +}; diff --git a/packages/modules.profile/src/ui/CameraSettings.tsx b/packages/modules.profile/src/ui/CameraSettings.tsx index 50d153298..c73c549cd 100644 --- a/packages/modules.profile/src/ui/CameraSettings.tsx +++ b/packages/modules.profile/src/ui/CameraSettings.tsx @@ -3,11 +3,11 @@ import { Camera } from '@xipkg/icons'; import { ScrollArea } from '@xipkg/scrollarea'; import { Category } from './Category'; import { useMediaQuery } from '@xipkg/utils'; -import { MediaDeviceMenu } from '../../../modules.calls/src/ui/PreJoin/components/MediaDevices/MediaDeviceMenu'; -import { UserTile } from '../../../modules.calls/src/ui/PreJoin/components/UserTile'; -import { useEffect, useMemo, useState } from 'react'; -import { usePersistentUserChoices } from '../../../modules.calls/src/hooks/usePersistentUserChoices'; -import { usePermissionsStore } from '../../../modules.calls/src/store/permissions'; +import { MediaDeviceMenu } from 'modules.calls'; +import { UserTile } from 'modules.calls'; +import { useCallback, useEffect, useState } from 'react'; +import { usePersistentUserChoices } from 'modules.calls'; +import { usePermissionsStore } from 'modules.calls'; export const CameraSettings = () => { const isMobile = useMediaQuery('(max-width: 719px)'); @@ -36,8 +36,8 @@ export const CameraSettings = () => { initCamera(); }, [videoDeviceId]); - const handleVideoDeviceChange = useMemo( - () => async (_kind: MediaDeviceKind, deviceId: string) => { + const handleVideoDeviceChange = useCallback( + async (_kind: MediaDeviceKind, deviceId: string) => { try { saveVideoInputDeviceId(deviceId); if (videoTrack) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 68ab73b5e..93ffb8647 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3837,6 +3837,9 @@ importers: features.avatar.editor: specifier: workspace:* version: link:../features.avatar.editor + modules.calls: + specifier: workspace:* + version: link:../modules.calls react: specifier: '19' version: 19.2.5 @@ -9708,8 +9711,8 @@ packages: peerDependencies: eslint: '>=7.0.0' - eslint-config-turbo@2.9.12: - resolution: {integrity: sha512-QnAiTMzCz2mP2EGGVLx1j6mDsKRhMaY424IqJql3z1AQ06OSlXYl3aSIQEwQJEH5RgW/fHx9VK213BF3ZwO8Sw==} + eslint-config-turbo@2.9.14: + resolution: {integrity: sha512-8bAXNDwtmHV7CuSDX+FB9+TslZEP8qJoNWY9FQTEhyO42bRimcExxwOh1+K2H2JV2VFXJrkt1KxyJmy2xm8Ukw==} peerDependencies: eslint: '>6.6.0' turbo: '>2.0.0' @@ -9799,8 +9802,8 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - eslint-plugin-turbo@2.9.12: - resolution: {integrity: sha512-Ilv1DTYyghdIdTUsW/VbjVTYKt1Hfs7X2C+rq/i4u7ZVofzcHZwk/3QwrV5UyOGADmxmWNyD+xcRS7+ZE5arQg==} + eslint-plugin-turbo@2.9.14: + resolution: {integrity: sha512-ROTlsO1JBJLATxtDNd7t22vviSb0hD8fKnjOO0WRgtxJW3VBRNO3BLAC129GPZSvEvtMH/f71Y2TzrqGPjLpEw==} peerDependencies: eslint: '>6.6.0' turbo: '>2.0.0' @@ -15683,7 +15686,7 @@ snapshots: eslint-config-airbnb: 19.0.4(eslint-plugin-import@2.31.0)(eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.4(jiti@2.6.1)))(eslint-plugin-react-hooks@5.1.0(eslint@9.39.4(jiti@2.6.1)))(eslint-plugin-react@7.37.2(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)) eslint-config-next: 15.1.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.7.3) eslint-config-prettier: 9.1.0(eslint@9.39.4(jiti@2.6.1)) - eslint-config-turbo: 2.9.12(eslint@9.39.4(jiti@2.6.1))(turbo@2.9.8) + eslint-config-turbo: 2.9.14(eslint@9.39.4(jiti@2.6.1))(turbo@2.9.8) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.59.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.7.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-react: 7.37.2(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-react-hooks: 5.1.0(eslint@9.39.4(jiti@2.6.1)) @@ -16716,10 +16719,10 @@ snapshots: dependencies: eslint: 9.39.4(jiti@2.6.1) - eslint-config-turbo@2.9.12(eslint@9.39.4(jiti@2.6.1))(turbo@2.9.8): + eslint-config-turbo@2.9.14(eslint@9.39.4(jiti@2.6.1))(turbo@2.9.8): dependencies: eslint: 9.39.4(jiti@2.6.1) - eslint-plugin-turbo: 2.9.12(eslint@9.39.4(jiti@2.6.1))(turbo@2.9.8) + eslint-plugin-turbo: 2.9.14(eslint@9.39.4(jiti@2.6.1))(turbo@2.9.8) turbo: 2.9.8 eslint-import-resolver-node@0.3.10: @@ -16862,7 +16865,7 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 - eslint-plugin-turbo@2.9.12(eslint@9.39.4(jiti@2.6.1))(turbo@2.9.8): + eslint-plugin-turbo@2.9.14(eslint@9.39.4(jiti@2.6.1))(turbo@2.9.8): dependencies: dotenv: 16.0.3 eslint: 9.39.4(jiti@2.6.1) From 585339643b5827b8c096bcffd769efd46a1e5200 Mon Sep 17 00:00:00 2001 From: Avea-marina Date: Thu, 21 May 2026 20:50:06 +0300 Subject: [PATCH 3/3] fix(modules.profile): review fixes --- packages/modules.calls/src/ui/UserTile/UserTileUI.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/modules.calls/src/ui/UserTile/UserTileUI.tsx b/packages/modules.calls/src/ui/UserTile/UserTileUI.tsx index 03134e301..b316e4897 100644 --- a/packages/modules.calls/src/ui/UserTile/UserTileUI.tsx +++ b/packages/modules.calls/src/ui/UserTile/UserTileUI.tsx @@ -136,10 +136,7 @@ export const UserTileUI = ({ }, [videoTrack, userId, isCameraDeniedOrPrompted]); return ( -
{}} - className="bg-gray-40 relative flex aspect-video h-full w-full items-center justify-center overflow-hidden rounded-[16px]" - > +
{renderVideo} {renderAvatar}