Skip to content

Commit

Permalink
feat: calling popout version 2 (#17733)
Browse files Browse the repository at this point in the history
* feat: put fullscreen ui into detached calling cell

* runfix: make sure less styles are properly overwritten with emotion

* runfix: make screenshare work on browser

* feat: do not allow in-app fullscreen anymore

* runfix: remove labels from full ui buttons

* runfix: align margins

* feat: add useToggleState hook

* feat: select call view mode

* feat: align icons styles and labels

* runfix: align changes after rebasing

* tmp

* Revert "fix: remove unused component (#17681)"

This reverts commit 2689225.

* feat: add participants label

* feat: toggle participants list

* runfix: mic off icon

* runfix: call tab view styles

* runfix: put fullscreen into the webapp temp

* runfix: icon styles on dark mode

* tmp

* runfix: active window match media

* runfix: screenshare button

* runfix: revert extra handling of separate window media devices

* runfix: screen sharing on desktop

* chore: self-review

* runfix: rename variable

* test: align tests

* test: align tests

* chore: hide detached window on desktop

* feat: move call pagination to top bar

* runfix: apply theme change during fullscreen call

* Revert "fix: revert minimized calling cell (#17696)"

This reverts commit 62ec5a5.

* runfix: last overflowed participant list item not visible

* chore: add debug util for adding a participant to the active call

* runfix: default call avatar size

* chore: remove debug util to enable the feature
  • Loading branch information
PatrykBuniX committed Jul 26, 2024
1 parent 8293531 commit 9590d75
Show file tree
Hide file tree
Showing 48 changed files with 1,998 additions and 1,323 deletions.
10 changes: 10 additions & 0 deletions src/i18n/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -1522,7 +1522,16 @@
"verify.subhead": "Enter the verification code we sent to{newline}{email}",
"videoCallOverlayCamera": "Camera",
"videoCallOverlayConversations": "Conversations",
"videoCallOverlayOpenFullScreen": "Open the call in full screen",
"videoCallOverlayCloseFullScreen": "Go back to minimized view",
"videoCallOverlayParticipantsListLabel": "Participants ({{count}})",
"videoCallOverlayShowParticipantsList": "Show participants list",
"videoCallOverlayHideParticipantsList": "Hide participants list",
"videoCallOverlayFitVideoLabel": "Double-click to view full screen",
"videoCallOverlayViewModeLabel": "View mode",
"videoCallOverlayChangeViewMode": "Change view mode",
"videoCallOverlayViewModeAll": "Show all participants",
"videoCallOverlayViewModeSpeakers": "Show active speakers only",
"videoCallOverlayFitVideoLabelGoBack": "Double-click to show all participants",
"videoCallOverlayHangUp": "Hang Up",
"videoCallOverlayMicrophone": "Microphone",
Expand All @@ -1538,6 +1547,7 @@
"videoCallvideoInputCamera": "Camera",
"videoSpeakersTabAll": "All ({{count}})",
"videoSpeakersTabSpeakers": "Speakers",
"viewingInAnotherWindow": "Viewing in another window",
"warningCallIssues": "This version of {{brandName}} can not participate in the call. Please use",
"warningCallQualityPoor": "Poor connection",
"warningCallUnsupportedIncoming": "{{user}} is calling. Your browser doesn’t support calls.",
Expand Down
16 changes: 11 additions & 5 deletions src/script/calling/CallState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,14 @@ export enum MuteState {
}

export enum CallingViewMode {
FULL_SCREEN_GRID = 'full-screen-grid',
DETACHED_WINDOW = 'detached_window',
MINIMIZED = 'minimized',
DETACHED_WINDOW = 'detached-window',
}

export enum DesktopScreenShareMenu {
NONE = 'none',
MAIN_WINDOW = 'main_window',
DETACHED_WINDOW = 'detached_window',
}

type Emoji = {emoji: string; id: string; left: number; from: string};
Expand All @@ -60,9 +65,10 @@ export class CallState {
public readonly activeCalls: ko.PureComputed<Call[]>;
public readonly joinedCall: ko.PureComputed<Call | undefined>;
public readonly activeCallViewTab = ko.observable(CallViewTab.ALL);
readonly isChoosingScreen: ko.PureComputed<boolean>;
readonly hasAvailableScreensToShare: ko.PureComputed<boolean>;
readonly isSpeakersViewActive: ko.PureComputed<boolean>;
public readonly viewMode = ko.observable<CallingViewMode>(CallingViewMode.MINIMIZED);
public readonly desktopScreenShareMenu = ko.observable<DesktopScreenShareMenu>(DesktopScreenShareMenu.NONE);

constructor() {
this.joinedCall = ko.pureComputed(() => this.calls().find(call => call.state() === CALL_STATE.MEDIA_ESTAB));
Expand All @@ -72,7 +78,7 @@ export class CallState {
call => call.state() === CALL_STATE.INCOMING && call.reason() !== CALL_REASON.ANSWERED_ELSEWHERE,
),
);
this.isChoosingScreen = ko.pureComputed(
this.hasAvailableScreensToShare = ko.pureComputed(
() => this.selectableScreens().length > 0 || this.selectableWindows().length > 0,
);

Expand All @@ -84,7 +90,7 @@ export class CallState {
});
this.isSpeakersViewActive = ko.pureComputed(() => this.activeCallViewTab() === CallViewTab.SPEAKERS);

this.isChoosingScreen = ko.pureComputed(
this.hasAvailableScreensToShare = ko.pureComputed(
() => this.selectableScreens().length > 0 || this.selectableWindows().length > 0,
);
}
Expand Down
4 changes: 3 additions & 1 deletion src/script/calling/CallingRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ import {createUuid} from 'Util/uuid';

import {Call, SerializedConversationId} from './Call';
import {callingSubscriptions} from './callingSubscriptionsHandler';
import {CallState, MuteState} from './CallState';
import {CallingViewMode, CallState, MuteState} from './CallState';
import {CALL_MESSAGE_TYPE} from './enum/CallMessageType';
import {LEAVE_CALL_REASON} from './enum/LeaveCallReason';
import {ClientId, Participant, UserId} from './Participant';
Expand Down Expand Up @@ -838,6 +838,7 @@ export class CallingRepository {
}

async startCall(conversation: Conversation, callType: CALL_TYPE): Promise<void | Call> {
this.callState.viewMode(CallingViewMode.MINIMIZED);
if (!this.selfUser || !this.selfClientId) {
this.logger.warn(
`Calling repository is not initialized correctly \n ${JSON.stringify({
Expand Down Expand Up @@ -977,6 +978,7 @@ export class CallingRepository {
};

async answerCall(call: Call, callType?: CALL_TYPE): Promise<void> {
this.callState.viewMode(CallingViewMode.MINIMIZED);
const {conversation} = call;
try {
callType ??= call.getSelfParticipant().sharesCamera() ? call.initialType : CALL_TYPE.NORMAL;
Expand Down
15 changes: 9 additions & 6 deletions src/script/components/AppContainer/AppContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {container} from 'tsyringe';
import {StyledApp, THEME_ID} from '@wireapp/react-ui-kit';

import {DetachedCallingCell} from 'Components/calling/DetachedCallingCell';
import {useDetachedCallingFeatureState} from 'Components/calling/DetachedCallingCell/DetachedCallingFeature.state';
import {PrimaryModalComponent} from 'Components/Modals/PrimaryModal/PrimaryModal';
import {SIGN_OUT_REASON} from 'src/script/auth/SignOutReason';
import {useAppSoftLock} from 'src/script/hooks/useAppSoftLock';
Expand Down Expand Up @@ -83,6 +84,7 @@ export const AppContainer: FC<AppProps> = ({config, clientType}) => {
const {repository: repositories} = app;

const {softLockEnabled} = useAppSoftLock(repositories.calling, repositories.notification);
const {isSupported: isDetachedCallingFeatureEnabled} = useDetachedCallingFeatureState();

if (hasOtherInstance) {
app.redirectToLogin(SIGN_OUT_REASON.MULTIPLE_TABS);
Expand All @@ -100,12 +102,13 @@ export const AppContainer: FC<AppProps> = ({config, clientType}) => {
<PrimaryModalComponent />
</StyledApp>

<DetachedCallingCell
callActions={mainView.calling.callActions}
callingRepository={app.repository.calling}
pushToTalkKey={repositories.properties.getPreference(PROPERTIES_TYPE.CALL.PUSH_TO_TALK_KEY)}
hasAccessToCamera={mainView.calling.hasAccessToCamera()}
/>
{isDetachedCallingFeatureEnabled() && (
<DetachedCallingCell
callingRepository={app.repository.calling}
mediaRepository={app.repository.media}
toggleScreenshare={mainView.calling.callActions.toggleScreenshare}
/>
)}
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function DefaultAvatarImageSmall({diameter}: {diameter: number}) {

export function DefaultAvatarImageLarge({diameter}: {diameter: number}) {
return (
<svg width={diameter} height={diameter} fill="none" xmlns="http://www.w3.org/2000/svg">
<svg width={diameter} height={diameter} viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M100.381 200c55.228 0 100-44.772 100-100s-44.772-100-100-100c-55.229 0-100 44.772-100 100s44.771 100 100 100Z"
fill="#fff"
Expand Down
31 changes: 12 additions & 19 deletions src/script/components/Conversation/Conversation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import {showDetailViewModal} from 'Components/Modals/DetailViewModal';
import {PrimaryModal} from 'Components/Modals/PrimaryModal';
import {showWarningModal} from 'Components/Modals/utils/showWarningModal';
import {TitleBar} from 'Components/TitleBar';
import {CallingViewMode, CallState} from 'src/script/calling/CallState';
import {CallState} from 'src/script/calling/CallState';
import {Config} from 'src/script/Config';
import {PROPERTIES_TYPE} from 'src/script/properties/PropertiesType';
import {useKoSubscribableChildren} from 'Util/ComponentUtil';
Expand Down Expand Up @@ -117,9 +117,7 @@ export const Conversation = ({

const inTeam = teamState.isInTeam(selfUser);

const {activeCalls, viewMode} = useKoSubscribableChildren(callState, ['activeCalls', 'viewMode']);

const isCallWindowDetached = viewMode === CallingViewMode.DETACHED_WINDOW;
const {activeCalls} = useKoSubscribableChildren(callState, ['activeCalls']);

const [isMsgElementsFocusable, setMsgElementsFocusable] = useState(true);

Expand Down Expand Up @@ -495,22 +493,17 @@ export const Conversation = ({
return null;
}

if (isCallWindowDetached) {
return null;
}

return (
<div className="calling-cell" key={conversation.id}>
<CallingCell
classifiedDomains={classifiedDomains}
call={call}
callActions={callingViewModel.callActions}
callingRepository={callingRepository}
pushToTalkKey={callingViewModel.propertiesRepository.getPreference(
PROPERTIES_TYPE.CALL.PUSH_TO_TALK_KEY,
)}
/>
</div>
<CallingCell
key={conversation.id}
classifiedDomains={classifiedDomains}
call={call}
callActions={callingViewModel.callActions}
callingRepository={callingRepository}
pushToTalkKey={callingViewModel.propertiesRepository.getPreference(
PROPERTIES_TYPE.CALL.PUSH_TO_TALK_KEY,
)}
/>
);
})}

Expand Down
28 changes: 24 additions & 4 deletions src/script/components/DetachedWindow/DetachedWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ import {useEffect, useMemo} from 'react';
import createCache from '@emotion/cache';
import {CacheProvider} from '@emotion/react';
import weakMemoize from '@emotion/weak-memoize';
import {amplify} from 'amplify';
import {createPortal} from 'react-dom';

import {StyledApp, THEME_ID} from '@wireapp/react-ui-kit';
import {WebAppEvents} from '@wireapp/webapp-events';

import {useActiveWindow} from 'src/script/hooks/useActiveWindow';
import {calculateChildWindowPosition} from 'Util/DOM/caculateChildWindowPosition';
Expand All @@ -37,14 +39,22 @@ interface DetachedWindowProps {
height?: number;
onClose: () => void;
name: string;
onNewWindowOpened?: (newWindow: Window) => void;
}

const memoizedCreateCacheWithContainer = weakMemoize((container: HTMLHeadElement) => {
const newCache = createCache({container, key: 'detached-window'});
return newCache;
});

export const DetachedWindow = ({children, name, onClose, width = 600, height = 600}: DetachedWindowProps) => {
export const DetachedWindow = ({
children,
name,
onClose,
width = 600,
height = 600,
onNewWindowOpened,
}: DetachedWindowProps) => {
const newWindow = useMemo(() => {
const {top, left} = calculateChildWindowPosition(height, width);

Expand Down Expand Up @@ -88,12 +98,18 @@ export const DetachedWindow = ({children, name, onClose, width = 600, height = 6
newWindow.addEventListener('beforeunload', onClose);
window.addEventListener('pagehide', onPageHide);

amplify.subscribe(WebAppEvents.PROPERTIES.UPDATE.INTERFACE.THEME, () => {
newWindow.document.body.className = window.document.body.className;
});

onNewWindowOpened?.(newWindow);

return () => {
newWindow.close();
newWindow.removeEventListener('beforeunload', onClose);
window.removeEventListener('pagehide', onPageHide);
};
}, [height, name, width, onClose, newWindow]);
}, [height, name, width, onClose, newWindow, onNewWindowOpened]);

return !newWindow
? null
Expand All @@ -113,8 +129,12 @@ export const DetachedWindow = ({children, name, onClose, width = 600, height = 6
* @param target the target document object
*/
const copyStyles = (source: Document, target: Document) => {
source.head.querySelectorAll('link, style').forEach(htmlElement => {
target.head.appendChild(htmlElement.cloneNode(true));
const targetHead = target.head;

const elements = source.head.querySelectorAll('link, style');

elements.forEach(htmlElement => {
targetHead.insertBefore(htmlElement.cloneNode(true), targetHead.firstChild);
});

target.body.className = source.body.className;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,7 @@ export const callParticipantListItemWrapper = (isLast = false): CSSObject => ({
'&:hover, &:focus, &:focus-visible': {
backgroundColor: 'var(--disabled-call-button-bg)',
},

...(isLast && {
borderRadius: '0 0 8px 8px',
}),
borderBottom: isLast ? 'none' : '1px solid var(--border-color)',
});

const commonIconStyles = {
Expand Down
Loading

0 comments on commit 9590d75

Please sign in to comment.