/* * Wire * Copyright (C) 2022 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, {useMemo, useEffect, useCallback, useRef} from 'react'; import {TabIndex} from '@wireapp/react-ui-kit/lib/types/enums'; import {amplify} from 'amplify'; import cx from 'classnames'; import {container} from 'tsyringe'; import {IconButton, IconButtonVariant, QUERY, useMatchMedia} from '@wireapp/react-ui-kit'; import {WebAppEvents} from '@wireapp/webapp-events'; import {ConversationVerificationBadges} from 'Components/Badge'; import {useCallAlertState} from 'Components/calling/useCallAlertState'; import * as Icon from 'Components/Icon'; import {LegalHoldDot} from 'Components/LegalHoldDot'; import {User} from 'src/script/entity/User'; import {useAppMainState, ViewType} from 'src/script/page/state'; import {ContentState} from 'src/script/page/useAppState'; import {useKoSubscribableChildren} from 'Util/ComponentUtil'; import {handleKeyDown} from 'Util/KeyboardUtil'; import {StringIdentifer, t} from 'Util/LocalizerUtil'; import {matchQualifiedIds} from 'Util/QualifiedId'; import {TIME_IN_MILLIS} from 'Util/TimeUtil'; import {CallState} from '../../calling/CallState'; import {ConversationFilter} from '../../conversation/ConversationFilter'; import {Conversation} from '../../entity/Conversation'; import {RightSidebarParams} from '../../page/AppMain'; import {PanelState} from '../../page/RightSidebar/RightSidebar'; import {TeamState} from '../../team/TeamState'; import {Shortcut} from '../../ui/Shortcut'; import {ShortcutType} from '../../ui/ShortcutType'; import {CallActions} from '../../view_model/CallingViewModel'; import {ViewModelRepositories} from '../../view_model/MainViewModel'; export interface TitleBarProps { callActions: CallActions; conversation: Conversation; openRightSidebar: (panelState: PanelState, params: RightSidebarParams, compareEntityId?: boolean) => void; repositories: ViewModelRepositories; selfUser: User; teamState: TeamState; isRightSidebarOpen?: boolean; callState?: CallState; isReadOnlyConversation?: boolean; } export const TitleBar: React.FC = ({ repositories, conversation, callActions, selfUser, openRightSidebar, isRightSidebarOpen = false, callState = container.resolve(CallState), teamState = container.resolve(TeamState), isReadOnlyConversation = false, }) => { const {calling: callingRepository} = repositories; const { is1to1, isRequest, isActiveParticipant, isGroup, hasExternal, hasDirectGuest, hasService, hasFederatedUsers, firstUserEntity, hasLegalHold, display_name: displayName, } = useKoSubscribableChildren(conversation, [ 'is1to1', 'isRequest', 'isActiveParticipant', 'isGroup', 'hasExternal', 'hasDirectGuest', 'hasService', 'hasFederatedUsers', 'firstUserEntity', 'hasLegalHold', 'display_name', ]); const {isActivatedAccount} = useKoSubscribableChildren(selfUser, ['isActivatedAccount']); const {joinedCall, activeCalls} = useKoSubscribableChildren(callState, ['joinedCall', 'activeCalls']); const {isVideoCallingEnabled} = useKoSubscribableChildren(teamState, ['isVideoCallingEnabled']); const currentFocusedElementRef = useRef(null); const badgeLabelCopy = useMemo(() => { if (is1to1 && isRequest) { return ''; } const translationKey = generateWarningBadgeKey({ hasExternal, hasFederated: hasFederatedUsers, hasGuest: hasDirectGuest, hasService, }); if (translationKey) { return t(translationKey); } return ''; }, [hasDirectGuest, hasExternal, hasFederatedUsers, hasService, is1to1, isRequest]); const hasCall = useMemo(() => { const hasEntities = !!joinedCall; return hasEntities && matchQualifiedIds(conversation.qualifiedId, joinedCall.conversation.qualifiedId); }, [conversation, joinedCall]); const showCallControls = ConversationFilter.showCallControls(conversation, hasCall); const supportsVideoCall = conversation.supportsVideoCall(callingRepository.supportsConferenceCalling); const conversationSubtitle = is1to1 && firstUserEntity?.isFederated ? firstUserEntity?.handle ?? '' : ''; const shortcut = Shortcut.getShortcutTooltip(ShortcutType.PEOPLE); const peopleTooltip = t('tooltipConversationPeople', shortcut); // To be changed when design chooses a breakpoint, the conditional can be integrated to the ui-kit directly const mdBreakpoint = useMatchMedia('max-width: 1000px'); const smBreakpoint = useMatchMedia(QUERY.tabletSMDown); const {close: closeRightSidebar} = useAppMainState(state => state.rightSidebar); const {setCurrentView: setView} = useAppMainState(state => state.responsiveView); const setLeftSidebar = () => { setView(ViewType.MOBILE_LEFT_SIDEBAR); closeRightSidebar(); }; const showDetails = useCallback( (addParticipants: boolean): void => { const panelId = addParticipants ? PanelState.ADD_PARTICIPANTS : PanelState.CONVERSATION_DETAILS; openRightSidebar(panelId, {entity: conversation}); }, [conversation, openRightSidebar], ); const showAddParticipant = useCallback(() => { if (is1to1) { return; } if (!isActiveParticipant) { return showDetails(false); } if (isGroup) { showDetails(true); } else { amplify.publish(WebAppEvents.CONVERSATION.CREATE_GROUP, 'conversation_details', firstUserEntity); } }, [firstUserEntity, isActiveParticipant, isGroup, showDetails, is1to1]); useEffect(() => { // TODO remove the titlebar for now to ensure that buttons are clickable in macOS wrappers window.setTimeout(() => document.querySelector('.titlebar')?.remove(), TIME_IN_MILLIS.SECOND); amplify.subscribe(WebAppEvents.SHORTCUT.PEOPLE, () => showDetails(false)); amplify.subscribe(WebAppEvents.SHORTCUT.ADD_PEOPLE, () => { if (isActivatedAccount) { showAddParticipant(); } }); return () => { amplify.unsubscribeAll(WebAppEvents.SHORTCUT.PEOPLE); amplify.unsubscribeAll(WebAppEvents.SHORTCUT.ADD_PEOPLE); }; }, [isActivatedAccount, showAddParticipant, showDetails]); const onClickCollectionButton = () => amplify.publish(WebAppEvents.CONTENT.SWITCH, ContentState.COLLECTION); const onClickDetails = () => showDetails(false); const onClickStartAudio = () => { callActions.startAudio(conversation); showStartedCallAlert(isGroup); if (smBreakpoint) { setLeftSidebar(); } }; useEffect(() => { if (!activeCalls.length && currentFocusedElementRef.current) { currentFocusedElementRef.current.focus(); currentFocusedElementRef.current = null; } }, [activeCalls.length]); const {showStartedCallAlert} = useCallAlertState(); return ( ); }; export function generateWarningBadgeKey({ hasFederated, hasExternal, hasGuest, hasService, }: { hasExternal?: boolean; hasFederated?: boolean; hasGuest?: boolean; hasService?: boolean; }): StringIdentifer { const baseKey = 'guestRoomConversationBadge'; const extras = []; if (hasGuest && !hasExternal && !hasService && !hasFederated) { return baseKey; } if (hasFederated) { extras.push('Federated'); } if (hasExternal) { extras.push('External'); } if (hasGuest) { extras.push('Guest'); } if (hasService) { extras.push('Service'); } if (!extras.length) { return ''; } return `${baseKey}${extras.join('And')}` as StringIdentifer; }