Add Element call related functionality to new room header (#12091)
* New room header - add chat button during call - close lobby button in lobby - join button if session exists - allow to toggle call <-> timeline during call with call button Compound style for join button in call notify toast. Signed-off-by: Timo K <toger5@hotmail.de> * dont show start call, join button in video rooms. Signed-off-by: Timo K <toger5@hotmail.de> * Make active call check based on participant count Not based on available call object Signed-off-by: Timo K <toger5@hotmail.de> * fix room header tests Signed-off-by: Timo K <toger5@hotmail.de> * fix room header tests Signed-off-by: Timo K <toger5@hotmail.de> * remove chat button test for displaying. Chat button display logic is now part of the RoomHeader. Signed-off-by: Timo K <toger5@hotmail.de> * remove duplicate notification Tread icon Signed-off-by: Timo K <toger5@hotmail.de> * remove obsolete jest snapshot Signed-off-by: Timo K <toger5@hotmail.de> * Update src/components/views/rooms/RoomHeader.tsx Co-authored-by: Robin <robin@robin.town> * update isECWidget logic Signed-off-by: Timo K <toger5@hotmail.de> * remove dead code Signed-off-by: Timo K <toger5@hotmail.de> * refactor call options Add menu to choose if there are multiple options Signed-off-by: Timo K <toger5@hotmail.de> * join ec when clicking join button (dont start jitsi) Use icon buttons don't show call icon when join button is visible Signed-off-by: Timo K <toger5@hotmail.de> * refactor isViewingCall Signed-off-by: Timo K <toger5@hotmail.de> * fix room header tests Signed-off-by: Timo K <toger5@hotmail.de> * fix header snapshot Signed-off-by: Timo K <toger5@hotmail.de> * sonar proposals Signed-off-by: Timo K <toger5@hotmail.de> * fix event shiftKey may be undefined Signed-off-by: Timo K <toger5@hotmail.de> * more lobby time before timeout only await sticky promise on becoming sticky. Signed-off-by: Timo K <toger5@hotmail.de> * don't allow starting new calls if there is an ongoing other call. Signed-off-by: Timo K <toger5@hotmail.de> * review Signed-off-by: Timo K <toger5@hotmail.de> * fix translation typo Signed-off-by: Timo K <toger5@hotmail.de> --------- Signed-off-by: Timo K <toger5@hotmail.de> Co-authored-by: Robin <robin@robin.town>
This commit is contained in:
parent
31449d6f80
commit
73b16239a5
14 changed files with 286 additions and 164 deletions
|
@ -24,18 +24,37 @@ import { useEventEmitter, useEventEmitterState } from "../useEventEmitter";
|
|||
import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../LegacyCallHandler";
|
||||
import { useWidgets } from "../../components/views/right_panel/RoomSummaryCard";
|
||||
import { WidgetType } from "../../widgets/WidgetType";
|
||||
import { useCall } from "../useCall";
|
||||
import { useCall, useConnectionState, useParticipantCount } from "../useCall";
|
||||
import { useRoomMemberCount } from "../useRoomMembers";
|
||||
import { ElementCall } from "../../models/Call";
|
||||
import { Call, ConnectionState, ElementCall } from "../../models/Call";
|
||||
import { placeCall } from "../../utils/room/placeCall";
|
||||
import { Container, WidgetLayoutStore } from "../../stores/widgets/WidgetLayoutStore";
|
||||
import { useRoomState } from "../useRoomState";
|
||||
import { _t } from "../../languageHandler";
|
||||
import { isManagedHybridWidget } from "../../widgets/ManagedHybrid";
|
||||
import { IApp } from "../../stores/WidgetStore";
|
||||
import { SdkContextClass } from "../../contexts/SDKContext";
|
||||
import { UPDATE_EVENT } from "../../stores/AsyncStore";
|
||||
import defaultDispatcher from "../../dispatcher/dispatcher";
|
||||
import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import { CallStore, CallStoreEvent } from "../../stores/CallStore";
|
||||
|
||||
export type PlatformCallType = "element_call" | "jitsi_or_element_call" | "legacy_or_jitsi";
|
||||
|
||||
export enum PlatformCallType {
|
||||
ElementCall,
|
||||
JitsiCall,
|
||||
LegacyCall,
|
||||
}
|
||||
export const getPlatformCallTypeLabel = (platformCallType: PlatformCallType): string => {
|
||||
switch (platformCallType) {
|
||||
case PlatformCallType.ElementCall:
|
||||
return _t("voip|element_call");
|
||||
case PlatformCallType.JitsiCall:
|
||||
return _t("voip|jitsi_call");
|
||||
case PlatformCallType.LegacyCall:
|
||||
return _t("voip|legacy_call");
|
||||
}
|
||||
};
|
||||
const enum State {
|
||||
NoCall,
|
||||
NoOneHere,
|
||||
|
@ -53,9 +72,14 @@ export const useRoomCall = (
|
|||
room: Room,
|
||||
): {
|
||||
voiceCallDisabledReason: string | null;
|
||||
voiceCallClick(evt: React.MouseEvent): void;
|
||||
voiceCallClick(evt: React.MouseEvent | undefined, selectedType: PlatformCallType): void;
|
||||
videoCallDisabledReason: string | null;
|
||||
videoCallClick(evt: React.MouseEvent): void;
|
||||
videoCallClick(evt: React.MouseEvent | undefined, selectedType: PlatformCallType): void;
|
||||
toggleCallMaximized: () => void;
|
||||
isViewingCall: boolean;
|
||||
isConnectedToCall: boolean;
|
||||
hasActiveCallSession: boolean;
|
||||
callOptions: PlatformCallType[];
|
||||
} => {
|
||||
const groupCallsEnabled = useFeatureEnabled("feature_group_calls");
|
||||
const useElementCallExclusively = useMemo(() => {
|
||||
|
@ -75,69 +99,83 @@ export const useRoomCall = (
|
|||
const hasManagedHybridWidget = !!managedHybridWidget;
|
||||
|
||||
const groupCall = useCall(room.roomId);
|
||||
const isConnectedToCall = useConnectionState(groupCall) === ConnectionState.Connected;
|
||||
const hasGroupCall = groupCall !== null;
|
||||
const hasActiveCallSession = useParticipantCount(groupCall) > 0;
|
||||
const isViewingCall = useEventEmitterState(SdkContextClass.instance.roomViewStore, UPDATE_EVENT, () =>
|
||||
SdkContextClass.instance.roomViewStore.isViewingCall(),
|
||||
);
|
||||
|
||||
const memberCount = useRoomMemberCount(room);
|
||||
|
||||
const [mayEditWidgets, mayCreateElementCalls] = useRoomState(room, () => [
|
||||
room.currentState.mayClientSendStateEvent("im.vector.modular.widgets", room.client),
|
||||
room.currentState.mayClientSendStateEvent(ElementCall.CALL_EVENT_TYPE.name, room.client),
|
||||
room.currentState.mayClientSendStateEvent(ElementCall.MEMBER_EVENT_TYPE.name, room.client),
|
||||
]);
|
||||
|
||||
const callType = useMemo((): PlatformCallType => {
|
||||
// The options provided to the RoomHeader.
|
||||
// If there are multiple options, the user will be prompted to choose.
|
||||
const callOptions = useMemo((): PlatformCallType[] => {
|
||||
const options = [];
|
||||
if (memberCount <= 2) {
|
||||
options.push(PlatformCallType.LegacyCall);
|
||||
} else if (mayEditWidgets || hasJitsiWidget) {
|
||||
options.push(PlatformCallType.JitsiCall);
|
||||
}
|
||||
if (groupCallsEnabled) {
|
||||
if (hasGroupCall) {
|
||||
return "jitsi_or_element_call";
|
||||
if (hasGroupCall || mayCreateElementCalls) {
|
||||
options.push(PlatformCallType.ElementCall);
|
||||
}
|
||||
if (mayCreateElementCalls && hasJitsiWidget) {
|
||||
return "jitsi_or_element_call";
|
||||
if (useElementCallExclusively && !hasJitsiWidget) {
|
||||
return [PlatformCallType.ElementCall];
|
||||
}
|
||||
if (useElementCallExclusively) {
|
||||
return "element_call";
|
||||
}
|
||||
if (memberCount <= 2) {
|
||||
return "legacy_or_jitsi";
|
||||
}
|
||||
if (mayCreateElementCalls) {
|
||||
return "element_call";
|
||||
if (hasGroupCall && WidgetType.CALL.matches(groupCall.widget.type)) {
|
||||
// only allow joining joining the ongoing Element call if there is one.
|
||||
return [PlatformCallType.ElementCall];
|
||||
}
|
||||
}
|
||||
return "legacy_or_jitsi";
|
||||
return options;
|
||||
}, [
|
||||
memberCount,
|
||||
mayEditWidgets,
|
||||
hasJitsiWidget,
|
||||
groupCallsEnabled,
|
||||
hasGroupCall,
|
||||
mayCreateElementCalls,
|
||||
hasJitsiWidget,
|
||||
useElementCallExclusively,
|
||||
memberCount,
|
||||
groupCall?.widget.type,
|
||||
]);
|
||||
|
||||
let widget: IApp | undefined;
|
||||
if (callType === "legacy_or_jitsi") {
|
||||
if (callOptions.includes(PlatformCallType.JitsiCall) || callOptions.includes(PlatformCallType.LegacyCall)) {
|
||||
widget = jitsiWidget ?? managedHybridWidget;
|
||||
} else if (callType === "element_call") {
|
||||
}
|
||||
if (callOptions.includes(PlatformCallType.ElementCall)) {
|
||||
widget = groupCall?.widget;
|
||||
} else {
|
||||
widget = groupCall?.widget ?? jitsiWidget;
|
||||
}
|
||||
|
||||
const updateWidgetState = useCallback((): void => {
|
||||
setCanPinWidget(WidgetLayoutStore.instance.canAddToContainer(room, Container.Top));
|
||||
setWidgetPinned(!!widget && WidgetLayoutStore.instance.isInContainer(room, widget, Container.Top));
|
||||
}, [room, widget]);
|
||||
useEventEmitter(WidgetLayoutStore.instance, WidgetLayoutStore.emissionForRoom(room), updateWidgetState);
|
||||
useEffect(() => {
|
||||
updateWidgetState();
|
||||
}, [room, jitsiWidget, groupCall, updateWidgetState]);
|
||||
const [activeCalls, setActiveCalls] = useState<Call[]>(Array.from(CallStore.instance.activeCalls));
|
||||
useEventEmitter(CallStore.instance, CallStoreEvent.ActiveCalls, () => {
|
||||
setActiveCalls(Array.from(CallStore.instance.activeCalls));
|
||||
});
|
||||
const [canPinWidget, setCanPinWidget] = useState(false);
|
||||
const [widgetPinned, setWidgetPinned] = useState(false);
|
||||
// We only want to prompt to pin the widget if it's not element call based.
|
||||
const isECWidget = WidgetType.CALL.matches(widget?.type ?? "");
|
||||
const promptPinWidget = !isECWidget && canPinWidget && !widgetPinned;
|
||||
|
||||
const updateWidgetState = useCallback((): void => {
|
||||
setCanPinWidget(WidgetLayoutStore.instance.canAddToContainer(room, Container.Top));
|
||||
setWidgetPinned(!!widget && WidgetLayoutStore.instance.isInContainer(room, widget, Container.Top));
|
||||
}, [room, widget]);
|
||||
|
||||
useEventEmitter(WidgetLayoutStore.instance, WidgetLayoutStore.emissionForRoom(room), updateWidgetState);
|
||||
useEffect(() => {
|
||||
updateWidgetState();
|
||||
}, [room, jitsiWidget, groupCall, updateWidgetState]);
|
||||
|
||||
const state = useMemo((): State => {
|
||||
if (activeCalls.find((call) => call.roomId != room.roomId)) {
|
||||
return State.Ongoing;
|
||||
}
|
||||
if (hasGroupCall || hasJitsiWidget || hasManagedHybridWidget) {
|
||||
return promptPinWidget ? State.Unpinned : State.Ongoing;
|
||||
}
|
||||
|
@ -152,9 +190,9 @@ export const useRoomCall = (
|
|||
if (!mayCreateElementCalls && !mayEditWidgets) {
|
||||
return State.NoPermission;
|
||||
}
|
||||
|
||||
return State.NoCall;
|
||||
}, [
|
||||
activeCalls,
|
||||
hasGroupCall,
|
||||
hasJitsiWidget,
|
||||
hasLegacyCall,
|
||||
|
@ -163,29 +201,30 @@ export const useRoomCall = (
|
|||
mayEditWidgets,
|
||||
memberCount,
|
||||
promptPinWidget,
|
||||
room.roomId,
|
||||
]);
|
||||
|
||||
const voiceCallClick = useCallback(
|
||||
(evt: React.MouseEvent): void => {
|
||||
evt.stopPropagation();
|
||||
(evt: React.MouseEvent | undefined, callPlatformType: PlatformCallType): void => {
|
||||
evt?.stopPropagation();
|
||||
if (widget && promptPinWidget) {
|
||||
WidgetLayoutStore.instance.moveToContainer(room, widget, Container.Top);
|
||||
} else {
|
||||
placeCall(room, CallType.Voice, callType, evt.shiftKey);
|
||||
placeCall(room, CallType.Voice, callPlatformType, evt?.shiftKey ?? false);
|
||||
}
|
||||
},
|
||||
[promptPinWidget, room, widget, callType],
|
||||
[promptPinWidget, room, widget],
|
||||
);
|
||||
const videoCallClick = useCallback(
|
||||
(evt: React.MouseEvent): void => {
|
||||
evt.stopPropagation();
|
||||
(evt: React.MouseEvent | undefined, callPlatformType: PlatformCallType): void => {
|
||||
evt?.stopPropagation();
|
||||
if (widget && promptPinWidget) {
|
||||
WidgetLayoutStore.instance.moveToContainer(room, widget, Container.Top);
|
||||
} else {
|
||||
placeCall(room, CallType.Video, callType, evt.shiftKey);
|
||||
placeCall(room, CallType.Video, callPlatformType, evt?.shiftKey ?? false);
|
||||
}
|
||||
},
|
||||
[widget, promptPinWidget, room, callType],
|
||||
[widget, promptPinWidget, room],
|
||||
);
|
||||
|
||||
let voiceCallDisabledReason: string | null;
|
||||
|
@ -208,6 +247,14 @@ export const useRoomCall = (
|
|||
voiceCallDisabledReason = null;
|
||||
videoCallDisabledReason = null;
|
||||
}
|
||||
const toggleCallMaximized = useCallback(() => {
|
||||
defaultDispatcher.dispatch<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
room_id: room.roomId,
|
||||
metricsTrigger: undefined,
|
||||
view_call: !isViewingCall,
|
||||
});
|
||||
}, [isViewingCall, room.roomId]);
|
||||
|
||||
/**
|
||||
* We've gone through all the steps
|
||||
|
@ -217,5 +264,10 @@ export const useRoomCall = (
|
|||
voiceCallClick,
|
||||
videoCallDisabledReason,
|
||||
videoCallClick,
|
||||
toggleCallMaximized: toggleCallMaximized,
|
||||
isViewingCall: isViewingCall,
|
||||
isConnectedToCall: isConnectedToCall,
|
||||
hasActiveCallSession: hasActiveCallSession,
|
||||
callOptions,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -36,21 +36,22 @@ export const useCallForWidget = (widgetId: string, roomId: string): Call | null
|
|||
return call?.widget.id === widgetId ? call : null;
|
||||
};
|
||||
|
||||
export const useConnectionState = (call: Call): ConnectionState =>
|
||||
export const useConnectionState = (call: Call | null): ConnectionState =>
|
||||
useTypedEventEmitterState(
|
||||
call,
|
||||
call ?? undefined,
|
||||
CallEvent.ConnectionState,
|
||||
useCallback((state) => state ?? call.connectionState, [call]),
|
||||
useCallback((state) => state ?? call?.connectionState ?? ConnectionState.Disconnected, [call]),
|
||||
);
|
||||
|
||||
export const useParticipants = (call: Call): Map<RoomMember, Set<string>> =>
|
||||
useTypedEventEmitterState(
|
||||
call,
|
||||
export const useParticipants = (call: Call | null): Map<RoomMember, Set<string>> => {
|
||||
return useTypedEventEmitterState(
|
||||
call ?? undefined,
|
||||
CallEvent.Participants,
|
||||
useCallback((state) => state ?? call.participants, [call]),
|
||||
useCallback((state) => state ?? call?.participants ?? [], [call]),
|
||||
);
|
||||
};
|
||||
|
||||
export const useParticipantCount = (call: Call): number => {
|
||||
export const useParticipantCount = (call: Call | null): number => {
|
||||
const participants = useParticipants(call);
|
||||
|
||||
return useMemo(() => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue