From 97b54600cdae3e1d6b59aecf2bc6afad66be76d5 Mon Sep 17 00:00:00 2001 From: Daria Oldenburg Date: Thu, 19 Dec 2024 16:16:17 +0100 Subject: [PATCH] WIP --- src/i18n/en-US.json | 3 + .../calling/FullscreenVideoCall.styles.ts | 28 --- .../calling/FullscreenVideoCall.tsx | 109 ++-------- .../components/calling/GroupVideoGrid.tsx | 16 +- src/script/components/calling/Pagination.tsx | 92 -------- .../calling/Pagination/Pagination.styles.ts | 48 +++++ .../calling/Pagination/Pagination.tsx | 159 ++++++++++++++ .../useSyncCurrentRange.test.ts | 0 .../{ => Pagination}/useSyncCurrentRange.ts | 0 .../EmojisBar/EmojisBar.styles.ts | 64 ++++++ .../VideoControls/EmojisBar/EmojisBar.tsx | 71 +++++++ .../VideoControls/VideoControls.styles.ts | 25 +++ .../calling/VideoControls/VideoControls.tsx | 198 ++++++++++++++---- src/script/hooks/useToggleState.ts | 1 + src/style/foundation/video-calling.less | 42 ---- 15 files changed, 561 insertions(+), 295 deletions(-) delete mode 100644 src/script/components/calling/Pagination.tsx create mode 100644 src/script/components/calling/Pagination/Pagination.styles.ts create mode 100644 src/script/components/calling/Pagination/Pagination.tsx rename src/script/components/calling/{ => Pagination}/useSyncCurrentRange.test.ts (100%) rename src/script/components/calling/{ => Pagination}/useSyncCurrentRange.ts (100%) create mode 100644 src/script/components/calling/VideoControls/EmojisBar/EmojisBar.styles.ts create mode 100644 src/script/components/calling/VideoControls/EmojisBar/EmojisBar.tsx diff --git a/src/i18n/en-US.json b/src/i18n/en-US.json index debf4f212a5..059add7426e 100644 --- a/src/i18n/en-US.json +++ b/src/i18n/en-US.json @@ -331,6 +331,9 @@ "callReactionButtonsAriaLabel": "Emoji selection bar", "callReactions": "Reactions", "callReactionsAriaLabel": "Emoji {{emoji}} from {{from}}", + "callMenuMoreInteractions": "More settings", + "callMenuMoreButtonAriaLabel": "Select setting: {{setting}}", + "callMenuMoreButtonsAriaLabel": "More settings selection bar", "callStateCbr": "Constant Bit Rate", "callStateConnecting": "Connectingโ€ฆ", "callStateIncoming": "Callingโ€ฆ", diff --git a/src/script/components/calling/FullscreenVideoCall.styles.ts b/src/script/components/calling/FullscreenVideoCall.styles.ts index 3784beb76ae..ee957bdae56 100644 --- a/src/script/components/calling/FullscreenVideoCall.styles.ts +++ b/src/script/components/calling/FullscreenVideoCall.styles.ts @@ -41,28 +41,6 @@ export const videoControlInActiveStyles = css` } `; -export const paginationButtonStyles: CSSObject = { - ['& svg > path']: { - fill: 'var(--main-color)', - }, - ['&:focus-visible']: { - ['& svg > path']: { - fill: 'var(--accent-color)', - }, - outline: '1px solid var(--accent-color-focus)', - }, - ['&:not([disabled]):hover svg > path']: { - fill: 'var(--accent-color)', - }, - ['&:disabled svg > path']: { - fill: 'var(--disabled-call-button-svg)', - }, - display: 'flex', - alignItems: 'center', - cursor: 'pointer', - height: '100%', -}; - export const videoTopBarStyles: CSSObject = { display: 'grid', gridTemplateColumns: '1fr auto 1fr', @@ -78,9 +56,3 @@ export const headerActionsWrapperStyles: CSSObject = { marginLeft: 'auto', padding: '0 6px', }; - -export const paginationWrapperStyles: CSSObject = { - display: 'flex', - alignItems: 'center', - gap: '10px', -}; diff --git a/src/script/components/calling/FullscreenVideoCall.tsx b/src/script/components/calling/FullscreenVideoCall.tsx index d3c395a613d..e2405fdccc7 100644 --- a/src/script/components/calling/FullscreenVideoCall.tsx +++ b/src/script/components/calling/FullscreenVideoCall.tsx @@ -23,7 +23,7 @@ import {DefaultConversationRoleName} from '@wireapp/api-client/lib/conversation/ import {TabIndex} from '@wireapp/react-ui-kit/lib/types/enums'; import {container} from 'tsyringe'; -import {Checkbox, CheckboxLabel, IconButton, IconButtonVariant} from '@wireapp/react-ui-kit'; +import {Checkbox, CheckboxLabel, IconButton, IconButtonVariant, QUERY} from '@wireapp/react-ui-kit'; import {useCallAlertState} from 'Components/calling/useCallAlertState'; import {ConversationClassifiedBar} from 'Components/ClassifiedBar/ClassifiedBar'; @@ -43,15 +43,12 @@ import {CallingParticipantList} from './CallingCell/CallIngParticipantList'; import {Duration} from './Duration'; import { videoControlInActiveStyles, - paginationButtonStyles, classifiedBarStyles, headerActionsWrapperStyles, - paginationWrapperStyles, videoTopBarStyles, } from './FullscreenVideoCall.styles'; import {GroupVideoGrid} from './GroupVideoGrid'; -import {Pagination} from './Pagination'; -import {useSyncCurrentRange} from './useSyncCurrentRange'; +import {Pagination} from './Pagination/Pagination'; import {VideoControls} from './VideoControls/VideoControls'; import type {Call} from '../../calling/Call'; @@ -92,8 +89,6 @@ export interface FullscreenVideoCallProps { const LOCAL_STORAGE_KEY_FOR_SCREEN_SHARING_CONFIRM_MODAL = 'DO_NOT_ASK_AGAIN_FOR_SCREEN_SHARING_CONFIRM_MODAL'; -const DEFAULT_VISIBLE_DOTS = 5; - const FullscreenVideoCall: React.FC = ({ call, canShareScreen, @@ -194,47 +189,10 @@ const FullscreenVideoCall: React.FC = ({ cameraStatus: t(selfSharesCamera ? 'cameraStatusOn' : 'cameraStatusOff'), }); - const isModerator = selfUser && roles[selfUser.id] === DefaultConversationRoleName.WIRE_ADMIN; - - const [currentStart, setCurrentStart] = useState(0); - const visibleDots = DEFAULT_VISIBLE_DOTS > totalPages ? totalPages : DEFAULT_VISIBLE_DOTS; - - useSyncCurrentRange({ - currentStart, - currentPage, - totalPages, - visibleDots, - setCurrentStart, - }); - - const handlePreviousPage = () => { - if (currentPage === 0) { - return; - } - - const previousPage = currentPage - 1; - - // previousPage !== 0 --> jest niepotrzebne prawdopodnie - if (previousPage === currentStart && previousPage !== 0) { - setCurrentStart(currentStart => currentStart - 1); - } - - changePage(previousPage, call); - }; - - const handleNextPage = () => { - if (currentPage === totalPages - 1) { - return; - } - - const nextPage = currentPage + 1; + const isMobile = useActiveWindowMatchMedia(QUERY.mobile); + const isPaginationVisible = !maximizedParticipant && activeCallViewTab === CallViewTab.ALL && totalPages > 1; - if (nextPage === currentStart + visibleDots - 1 && nextPage !== totalPages - 1) { - setCurrentStart(currentStart => currentStart + 1); - } - - changePage(nextPage, call); - }; + const isModerator = selfUser && roles[selfUser.id] === DefaultConversationRoleName.WIRE_ADMIN; return (
@@ -282,49 +240,12 @@ const FullscreenVideoCall: React.FC = ({ )}
- {!maximizedParticipant && activeCallViewTab === CallViewTab.ALL && totalPages > 1 && ( -
- - - - -
+ {!isMobile && isPaginationVisible && ( + changePage(newPage, call)} + /> )} {isDetachedCallingFeatureEnabled() && viewMode !== CallingViewMode.DETACHED_WINDOW && ( @@ -369,6 +290,14 @@ const FullscreenVideoCall: React.FC = ({ )}
+ {isMobile && isPaginationVisible && ( + changePage(newPage, call)} + /> + )} + {!isChoosingScreen && (
{emojis.map(({id, emoji, left, from}) => ( diff --git a/src/script/components/calling/GroupVideoGrid.tsx b/src/script/components/calling/GroupVideoGrid.tsx index 67517848925..fbaf0f44a51 100644 --- a/src/script/components/calling/GroupVideoGrid.tsx +++ b/src/script/components/calling/GroupVideoGrid.tsx @@ -194,11 +194,21 @@ const GroupVideoGrid: React.FunctionComponent = ({ const PARTICIPANTS_LIMITS = { TABLET: {SHORT: 2, MEDIUM: 4, TALL: 8}, DESKTOP: {SHORT: 3, MEDIUM: 6, TALL: 9}, - MOBILE: {SHORT: 1, MEDIUM: 2, TALL: 4}, + MOBILE: {WITH_THUMBNAIL: 2, SHORT: 1, MEDIUM: 2, TALL: 4}, }; - const setParticipantsForDevice = (limits: {SHORT: number; MEDIUM: number; TALL: number}) => { + const setParticipantsForDevice = (limits: { + WITH_THUMBNAIL?: number; + SHORT: number; + MEDIUM: number; + TALL: number; + }) => { if (isShort) { + // TODO: czy ten komentarz jest poprawny? + // Special case: use different layout for 2 participants when in short mode + if (grid.thumbnail && limits.WITH_THUMBNAIL) { + return call.setNumberOfParticipantsInOnePage(limits.WITH_THUMBNAIL); + } return call.setNumberOfParticipantsInOnePage(limits.SHORT); } if (isMedium) { @@ -218,7 +228,7 @@ const GroupVideoGrid: React.FunctionComponent = ({ if (isMobile) { setParticipantsForDevice(PARTICIPANTS_LIMITS.MOBILE); } - }, [call, isTablet, isDesktop, isMobile, isShort, isMedium, isTall]); + }, [call, grid.thumbnail, isTablet, isDesktop, isMobile, isShort, isMedium, isTall]); const {isMuted: selfIsMuted} = useKoSubscribableChildren(selfParticipant, ['isMuted']); diff --git a/src/script/components/calling/Pagination.tsx b/src/script/components/calling/Pagination.tsx deleted file mode 100644 index f7f095224fb..00000000000 --- a/src/script/components/calling/Pagination.tsx +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Wire - * Copyright (C) 2021 Wire Swiss GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - * - */ - -import React from 'react'; - -import {CSSObject} from '@emotion/react'; - -export interface PaginationProps { - currentPage: number; - totalPages: number; - currentStart: number; - visibleDots: number; - wrapperStyles?: CSSObject; -} - -const paginationItemStyles: CSSObject = { - ':last-child': { - marginRight: 4, - }, - borderRadius: '50%', - marginLeft: 4, -}; - -const Pagination: React.FC = ({ - totalPages, - currentPage, - currentStart, - visibleDots, - wrapperStyles = {}, -}) => { - const visibleRange = Array.from({length: visibleDots}, (_, index) => currentStart + index); - - return ( -
- {visibleRange.map((page, index) => { - const isCurrentPage = currentPage === page; - const isFirstOrLastInTheRange = index === 0 || index === visibleRange.length - 1; - const isLastPage = page === totalPages - 1; - const isFirstPage = page === 0; - - const isSmaller = isFirstOrLastInTheRange && !(isFirstPage || isLastPage); - - return ( -
- ); - })} -
- ); -}; - -export {Pagination}; diff --git a/src/script/components/calling/Pagination/Pagination.styles.ts b/src/script/components/calling/Pagination/Pagination.styles.ts new file mode 100644 index 00000000000..5306cc9cc1c --- /dev/null +++ b/src/script/components/calling/Pagination/Pagination.styles.ts @@ -0,0 +1,48 @@ +/* + * Wire + * Copyright (C) 2020 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {CSSObject} from '@emotion/react'; + +export const paginationWrapperStyles: CSSObject = { + display: 'flex', + alignItems: 'center', + gap: '10px', +}; + +export const paginationButtonStyles: CSSObject = { + ['& svg > path']: { + fill: 'var(--main-color)', + }, + ['&:focus-visible']: { + ['& svg > path']: { + fill: 'var(--accent-color)', + }, + outline: '1px solid var(--accent-color-focus)', + }, + ['&:not([disabled]):hover svg > path']: { + fill: 'var(--accent-color)', + }, + ['&:disabled svg > path']: { + fill: 'var(--disabled-call-button-svg)', + }, + display: 'flex', + alignItems: 'center', + cursor: 'pointer', + height: '100%', +}; diff --git a/src/script/components/calling/Pagination/Pagination.tsx b/src/script/components/calling/Pagination/Pagination.tsx new file mode 100644 index 00000000000..a0d2017749b --- /dev/null +++ b/src/script/components/calling/Pagination/Pagination.tsx @@ -0,0 +1,159 @@ +/* + * Wire + * Copyright (C) 2021 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import React, {useState} from 'react'; + +import {CSSObject} from '@emotion/react'; + +import * as Icon from 'Components/Icon'; +import {handleKeyDown} from 'Util/KeyboardUtil'; + +import {paginationButtonStyles, paginationWrapperStyles} from './Pagination.styles'; + +export interface PaginationProps { + currentPage: number; + totalPages: number; + onChangePage: (newPage: number) => void; + wrapperStyles?: CSSObject; +} + +const paginationItemStyles: CSSObject = { + ':last-child': { + marginRight: 4, + }, + borderRadius: '50%', + marginLeft: 4, +}; + +const DEFAULT_VISIBLE_DOTS = 5; + +const Pagination: React.FC = ({totalPages, currentPage, onChangePage, wrapperStyles = {}}) => { + const [currentStart, setCurrentStart] = useState(0); + const visibleDots = DEFAULT_VISIBLE_DOTS > totalPages ? totalPages : DEFAULT_VISIBLE_DOTS; + + const handlePreviousPage = () => { + if (currentPage === 0) { + return; + } + + const previousPage = currentPage - 1; + + // previousPage !== 0 --> jest niepotrzebne prawdopodnie + if (previousPage === currentStart && previousPage !== 0) { + setCurrentStart(currentStart => currentStart - 1); + } + + onChangePage(previousPage); + }; + + const handleNextPage = () => { + if (currentPage === totalPages - 1) { + return; + } + + const nextPage = currentPage + 1; + + if (nextPage === currentStart + visibleDots - 1 && nextPage !== totalPages - 1) { + setCurrentStart(currentStart => currentStart + 1); + } + + onChangePage(nextPage); + }; + + const visibleRange = Array.from({length: visibleDots}, (_, index) => currentStart + index); + + return ( +
+ +
+ {visibleRange.map((page, index) => { + const isCurrentPage = currentPage === page; + const isFirstOrLastInTheRange = index === 0 || index === visibleRange.length - 1; + const isLastPage = page === totalPages - 1; + const isFirstPage = page === 0; + + const isSmaller = isFirstOrLastInTheRange && !(isFirstPage || isLastPage); + + return ( +
+ ); + })} +
+ +
+ ); +}; + +export {Pagination}; diff --git a/src/script/components/calling/useSyncCurrentRange.test.ts b/src/script/components/calling/Pagination/useSyncCurrentRange.test.ts similarity index 100% rename from src/script/components/calling/useSyncCurrentRange.test.ts rename to src/script/components/calling/Pagination/useSyncCurrentRange.test.ts diff --git a/src/script/components/calling/useSyncCurrentRange.ts b/src/script/components/calling/Pagination/useSyncCurrentRange.ts similarity index 100% rename from src/script/components/calling/useSyncCurrentRange.ts rename to src/script/components/calling/Pagination/useSyncCurrentRange.ts diff --git a/src/script/components/calling/VideoControls/EmojisBar/EmojisBar.styles.ts b/src/script/components/calling/VideoControls/EmojisBar/EmojisBar.styles.ts new file mode 100644 index 00000000000..da22f0ad4d7 --- /dev/null +++ b/src/script/components/calling/VideoControls/EmojisBar/EmojisBar.styles.ts @@ -0,0 +1,64 @@ +/* + * Wire + * Copyright (C) 2020 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {CSSObject} from '@emotion/react'; + +export const wrapperStyles: CSSObject = { + position: 'absolute', + bottom: '130%', + left: '50%', + display: 'grid', + padding: '0.4rem', + borderRadius: '1rem', + backgroundColor: 'var(--app-bg-secondary)', + boxShadow: '0px 7px 15px 0 #0000004d', + gap: '0.5rem', + gridTemplateColumns: 'repeat(3, 1fr)', + transform: 'translateX(-50%)', + + '&::after': { + position: 'absolute', + bottom: '-0.5rem', + left: '50%', + width: 0, + height: 0, + borderTop: '0.5rem solid var(--app-bg-secondary)', + borderRight: '0.5rem solid transparent', + borderLeft: '0.5rem solid transparent', + content: '""', + transform: 'translateX(-50%)', + }, +}; + +export const buttonStyles: CSSObject = { + backgroundColor: 'transparent', + border: 0, + padding: '0.5rem', + borderRadius: '1rem', + fontSize: '1.5rem', + + '&:disabled': { + cursor: 'not-allowed', + opacity: 0.5, + }, + + '&:hover': { + backgroundColor: 'var(--inactive-call-button-hover-bg)', + }, +}; diff --git a/src/script/components/calling/VideoControls/EmojisBar/EmojisBar.tsx b/src/script/components/calling/VideoControls/EmojisBar/EmojisBar.tsx new file mode 100644 index 00000000000..90d7e8a051f --- /dev/null +++ b/src/script/components/calling/VideoControls/EmojisBar/EmojisBar.tsx @@ -0,0 +1,71 @@ +/* + * Wire + * Copyright (C) 2021 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import React, {useState} from 'react'; + +import {CallingRepository} from 'src/script/calling/CallingRepository'; +import {t} from 'Util/LocalizerUtil'; + +import {buttonStyles, wrapperStyles} from './EmojisBar.styles'; + +const EMOJIS_LIST = ['๐Ÿ‘', '๐ŸŽ‰', 'โค๏ธ', '๐Ÿ˜‚', '๐Ÿ˜ฎ', '๐Ÿ‘', '๐Ÿค”', '๐Ÿ˜ข', '๐Ÿ‘Ž']; + +export interface EmojisBarProps { + onEmojiClick: (emoji: string) => void; +} + +export const EmojisBar: React.FC = ({onEmojiClick}) => { + const [disabledEmojis, setDisabledEmojis] = useState([]); + + const handleEmojiClick = (selectedEmoji: string) => { + setDisabledEmojis(prev => [...prev, selectedEmoji]); + + onEmojiClick(selectedEmoji); + + setTimeout(() => { + setDisabledEmojis(prev => [...prev].filter(emoji => emoji !== selectedEmoji)); + }, CallingRepository.EMOJI_TIME_OUT_DURATION); + }; + + return ( +
+ {EMOJIS_LIST.map(emoji => { + const isDisabled = disabledEmojis.includes(emoji); + return ( + + ); + })} +
+ ); +}; diff --git a/src/script/components/calling/VideoControls/VideoControls.styles.ts b/src/script/components/calling/VideoControls/VideoControls.styles.ts index 9fc94be6e1c..ef7b2ed191b 100644 --- a/src/script/components/calling/VideoControls/VideoControls.styles.ts +++ b/src/script/components/calling/VideoControls/VideoControls.styles.ts @@ -20,6 +20,7 @@ import {css, CSSObject} from '@emotion/react'; export const videoControlsWrapperStyles: CSSObject = { + position: 'relative', display: 'flex', alignItems: 'center', padding: '8px 16px', @@ -53,6 +54,30 @@ export const hangUpVideoControlStyles: CSSObject = { }, }; +export const menuMoreInteractionsVideoControlStyles: CSSObject = { + display: 'flex', + flexDirection: 'column', + width: 'max-content', + position: 'absolute', + top: '-10px', + right: 0, + transform: 'translateY(-100%)', + padding: '8px 0', + borderRadius: '12px', + backgroundColor: 'var(--app-bg-secondary)', + boxShadow: '0px 0px 12px 0px #00000040', +}; + +export const menuMoreInteractionsButtonStyles: CSSObject = { + display: 'flex', + alignItems: 'center', + padding: '8px', +}; + +export const menuMoreInteractionsIconStyles: CSSObject = { + marginRight: '8px', +}; + export const videoControlActiveStyles = css` background-color: var(--main-color); border: 1px solid var(--main-color); diff --git a/src/script/components/calling/VideoControls/VideoControls.tsx b/src/script/components/calling/VideoControls/VideoControls.tsx index 42c3795788d..58941b19371 100644 --- a/src/script/components/calling/VideoControls/VideoControls.tsx +++ b/src/script/components/calling/VideoControls/VideoControls.tsx @@ -24,13 +24,12 @@ import classNames from 'classnames'; import {container} from 'tsyringe'; import {CALL_TYPE} from '@wireapp/avs'; -import {EmojiIcon, GridIcon, QUERY, Select} from '@wireapp/react-ui-kit'; +import {EmojiIcon, GridIcon, MoreIcon, QUERY, Select} from '@wireapp/react-ui-kit'; import * as Icon from 'Components/Icon'; import {useActiveWindowMatchMedia} from 'Hooks/useActiveWindowMatchMedia'; import {useToggleState} from 'Hooks/useToggleState'; import {Call} from 'src/script/calling/Call'; -import {CallingRepository} from 'src/script/calling/CallingRepository'; import {CallingViewMode, CallState} from 'src/script/calling/CallState'; import {Participant} from 'src/script/calling/Participant'; import {Config} from 'src/script/Config'; @@ -40,13 +39,18 @@ import {isMediaDevice} from 'src/script/guards/MediaDevice'; import {ElectronDesktopCapturerSource, MediaDevicesHandler} from 'src/script/media/MediaDevicesHandler'; import {MediaDeviceType} from 'src/script/media/MediaDeviceType'; import {TeamState} from 'src/script/team/TeamState'; +import {ContextMenuEntry, showContextMenu} from 'src/script/ui/ContextMenu'; import {CallViewTab} from 'src/script/view_model/CallingViewModel'; import {useKoSubscribableChildren} from 'Util/ComponentUtil'; import {handleKeyDown, isEscapeKey} from 'Util/KeyboardUtil'; import {t} from 'Util/LocalizerUtil'; +import {EmojisBar} from './EmojisBar/EmojisBar'; import { hangUpVideoControlStyles, + menuMoreInteractionsButtonStyles, + menuMoreInteractionsIconStyles, + menuMoreInteractionsVideoControlStyles, minimizeVideoControlStyles, shareScreenVideoControlStyles, videoControlActiveStyles, @@ -62,8 +66,6 @@ enum BlurredBackgroundStatus { ON = 'bluron', } -const EMOJIS_LIST = ['๐Ÿ‘', '๐ŸŽ‰', 'โค๏ธ', '๐Ÿ˜‚', '๐Ÿ˜ฎ', '๐Ÿ‘', '๐Ÿค”', '๐Ÿ˜ข', '๐Ÿ‘Ž']; - export interface VideoControlsProps { activeCallViewTab: string; call: Call; @@ -125,7 +127,6 @@ export const VideoControls: React.FC = ({ const {participants} = useKoSubscribableChildren(call, ['participants']); const [showEmojisBar, setShowEmojisBar] = useState(false); - const [disabledEmojis, setDisabledEmojis] = useState([]); const {viewMode} = useKoSubscribableChildren(callState, ['viewMode']); @@ -148,6 +149,7 @@ export const VideoControls: React.FC = ({ ]); const isMobile = useActiveWindowMatchMedia(QUERY.mobile); + const isDesktop = useActiveWindowMatchMedia(QUERY.desktop); const [audioOptionsOpen, setAudioOptionsOpen] = useState(false); const [videoOptionsOpen, setVideoOptionsOpen] = useState(false); @@ -296,18 +298,139 @@ export const VideoControls: React.FC = ({ } }; - const onEmojiClick = (selectedEmoji: string) => { - setDisabledEmojis(prev => [...prev, selectedEmoji]); - - sendEmoji(selectedEmoji, call); - - setTimeout(() => { - setDisabledEmojis(prev => [...prev].filter(emoji => emoji !== selectedEmoji)); - }, CallingRepository.EMOJI_TIME_OUT_DURATION); + const handleEmojiClick = (selectedEmoji: string) => sendEmoji(selectedEmoji, call); + + const onMoreInteractionsMenuClick = (event: React.MouseEvent) => { + const mobileEntires: ContextMenuEntry[] = isMobile + ? [ + { + click: () => { + setAudioOptionsOpen(prev => !prev); + }, + label: 'Audio Settings', + icon: Icon.MicOnIcon, + }, + { + click: () => { + setVideoOptionsOpen(prev => !prev); + }, + label: 'Video Settings', + // label: t('userAvailabilityAvailable'), + icon: Icon.CameraIcon, + }, + ] + : []; + + showContextMenu({ + event: event.nativeEvent, + entries: [ + ...mobileEntires, + { + label: ' Raise hand', + }, + { + click: toggleCallView, + label: 'Change view', + // icon: GridIcon, + }, + { + click: () => setShowEmojisBar(prev => !prev), + label: showEmojisBar ? 'Close reactions' : 'Add reaction', + // icon: EmojiIcon, + }, + { + click: toggleParticipantsList, + label: 'See participants', + icon: Icon.PeopleIcon, + }, + ], + identifier: 'more-interactions-menu', + }); }; return (
    +
    + {isMobile && audioOptionsOpen && ( + { + updateVideoOptions(String(selectedOption?.value)); + setVideoOptionsOpen(false); + }} + id="select-camera" + dataUieName="select-camera" + controlShouldRenderValue={false} + isClearable={false} + backspaceRemovesValue={false} + hideSelectedOptions={false} + options={videoOptions} + onMenuClose={() => setVideoOptionsOpen(false)} + menuPlacement="top" + menuIsOpen={videoOptionsOpen} + // wrapperCSS={{marginBottom: 0}} + menuCSS={{width: '100vw', minWidth: 'initial'}} + /> + )} + {!isDesktop && isCallViewOpen && ( +