Support delete for call links

Co-authored-by: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com>
This commit is contained in:
Jamie Kyle 2024-08-06 12:29:13 -07:00 committed by GitHub
parent 11fed7e7f8
commit 9a9f9495f1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
67 changed files with 853 additions and 345 deletions

View file

@ -0,0 +1,40 @@
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { action } from '@storybook/addon-actions';
import type { ComponentMeta } from '../storybook/types';
import type { CallLinkDetailsProps } from './CallLinkDetails';
import { CallLinkDetails } from './CallLinkDetails';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
import {
FAKE_CALL_LINK,
FAKE_CALL_LINK_WITH_ADMIN_KEY,
} from '../test-both/helpers/fakeCallLink';
import { getFakeCallLinkHistoryGroup } from '../test-both/helpers/getFakeCallHistoryGroup';
const i18n = setupI18n('en', enMessages);
export default {
title: 'Components/CallLinkDetails',
component: CallLinkDetails,
argTypes: {},
args: {
i18n,
callHistoryGroup: getFakeCallLinkHistoryGroup(),
callLink: FAKE_CALL_LINK_WITH_ADMIN_KEY,
onDeleteCallLink: action('onDeleteCallLink'),
onOpenCallLinkAddNameModal: action('onOpenCallLinkAddNameModal'),
onStartCallLinkLobby: action('onStartCallLinkLobby'),
onShareCallLinkViaSignal: action('onShareCallLinkViaSignal'),
onUpdateCallLinkRestrictions: action('onUpdateCallLinkRestrictions'),
},
} satisfies ComponentMeta<CallLinkDetailsProps>;
export function Admin(args: CallLinkDetailsProps): JSX.Element {
return <CallLinkDetails {...args} />;
}
export function NonAdmin(args: CallLinkDetailsProps): JSX.Element {
return <CallLinkDetails {...args} callLink={FAKE_CALL_LINK} />;
}

View file

@ -1,6 +1,6 @@
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import React, { useState } from 'react';
import type { CallHistoryGroup } from '../types/CallDisposition';
import type { LocalizerType } from '../types/I18N';
import { CallHistoryGroupPanelSection } from './conversation/conversation-details/CallHistoryGroupPanelSection';
@ -17,8 +17,9 @@ import { Avatar, AvatarSize } from './Avatar';
import { Button, ButtonSize, ButtonVariant } from './Button';
import { copyCallLink } from '../util/copyLinksWithToast';
import { getColorForCallLink } from '../util/getColorForCallLink';
import { isCallLinkAdmin } from '../util/callLinks';
import { isCallLinkAdmin } from '../types/CallLink';
import { CallLinkRestrictionsSelect } from './CallLinkRestrictionsSelect';
import { ConfirmationDialog } from './ConfirmationDialog';
function toUrlWithoutProtocol(url: URL): string {
return `${url.hostname}${url.pathname}${url.search}${url.hash}`;
@ -28,6 +29,7 @@ export type CallLinkDetailsProps = Readonly<{
callHistoryGroup: CallHistoryGroup;
callLink: CallLinkType;
i18n: LocalizerType;
onDeleteCallLink: () => void;
onOpenCallLinkAddNameModal: () => void;
onStartCallLinkLobby: () => void;
onShareCallLinkViaSignal: () => void;
@ -38,11 +40,14 @@ export function CallLinkDetails({
callHistoryGroup,
callLink,
i18n,
onDeleteCallLink,
onOpenCallLinkAddNameModal,
onStartCallLinkLobby,
onShareCallLinkViaSignal,
onUpdateCallLinkRestrictions,
}: CallLinkDetailsProps): JSX.Element {
const [isDeleteCallLinkModalOpen, setIsDeleteCallLinkModalOpen] =
useState(false);
const webUrl = linkCallRoute.toWebUrl({
key: callLink.rootKey,
});
@ -144,6 +149,43 @@ export function CallLinkDetails({
onClick={onShareCallLinkViaSignal}
/>
</PanelSection>
{isCallLinkAdmin(callLink) && (
<PanelSection>
<PanelRow
className="CallLinkDetails__DeleteLink"
icon={
<ConversationDetailsIcon
ariaLabel={i18n('icu:CallLinkDetails__DeleteLink')}
icon={IconType.trash}
/>
}
label={i18n('icu:CallLinkDetails__DeleteLink')}
onClick={() => {
setIsDeleteCallLinkModalOpen(true);
}}
/>
</PanelSection>
)}
{isDeleteCallLinkModalOpen && (
<ConfirmationDialog
i18n={i18n}
dialogName="CallLinkDetails__DeleteLinkModal"
title={i18n('icu:CallLinkDetails__DeleteLinkModal__Title')}
cancelText={i18n('icu:CallLinkDetails__DeleteLinkModal__Cancel')}
actions={[
{
text: i18n('icu:CallLinkDetails__DeleteLinkModal__Delete'),
style: 'affirmative',
action: onDeleteCallLink,
},
]}
onClose={() => {
setIsDeleteCallLinkModalOpen(false);
}}
>
{i18n('icu:CallLinkDetails__DeleteLinkModal__Body')}
</ConfirmationDialog>
)}
</div>
);
}

View file

@ -8,12 +8,12 @@ import type { PropsType } from './CallManager';
import { CallManager } from './CallManager';
import {
CallEndedReason,
CallMode,
CallState,
CallViewMode,
GroupCallConnectionState,
GroupCallJoinState,
} from '../types/Calling';
import { CallMode } from '../types/CallDisposition';
import type {
ActiveGroupCallType,
GroupCallRemoteParticipantType,

View file

@ -20,11 +20,11 @@ import type {
} from '../types/Calling';
import {
CallEndedReason,
CallMode,
CallState,
GroupCallConnectionState,
GroupCallJoinState,
} from '../types/Calling';
import { CallMode } from '../types/CallDisposition';
import type { ConversationType } from '../state/ducks/conversations';
import type {
AcceptCallType,

View file

@ -3,7 +3,7 @@
import React from 'react';
import type { LocalizerType } from '../types/Util';
import { CallMode } from '../types/Calling';
import { CallMode } from '../types/CallDisposition';
export type PropsType = {
callMode: CallMode;

View file

@ -12,12 +12,12 @@ import type {
GroupCallRemoteParticipantType,
} from '../types/Calling';
import {
CallMode,
CallViewMode,
CallState,
GroupCallConnectionState,
GroupCallJoinState,
} from '../types/Calling';
import { CallMode } from '../types/CallDisposition';
import { generateAci } from '../types/ServiceId';
import type { ConversationType } from '../state/ducks/conversations';
import { AvatarColors } from '../types/Colors';

View file

@ -33,12 +33,12 @@ import type {
} from '../types/Calling';
import {
CALLING_REACTIONS_LIFETIME,
CallMode,
CallViewMode,
CallState,
GroupCallConnectionState,
GroupCallJoinState,
} from '../types/Calling';
import { CallMode } from '../types/CallDisposition';
import type { ServiceIdString } from '../types/ServiceId';
import { AvatarColors } from '../types/Colors';
import type { ConversationType } from '../state/ducks/conversations';

View file

@ -19,7 +19,7 @@ import {
getDefaultConversationWithServiceId,
} from '../test-both/helpers/getDefaultConversation';
import { CallingToastProvider } from './CallingToast';
import { CallMode } from '../types/Calling';
import { CallMode } from '../types/CallDisposition';
import { getDefaultCallLinkConversation } from '../test-both/helpers/fakeCallLink';
const i18n = setupI18n('en', enMessages);

View file

@ -19,7 +19,7 @@ import {
CallingLobbyJoinButton,
CallingLobbyJoinButtonVariant,
} from './CallingLobbyJoinButton';
import { CallMode } from '../types/Calling';
import { CallMode } from '../types/CallDisposition';
import type { CallingConversationType } from '../types/Calling';
import type { LocalizerType } from '../types/Util';
import { useIsOnline } from '../hooks/useIsOnline';

View file

@ -11,12 +11,12 @@ import type { PropsType } from './CallingPip';
import { CallingPip } from './CallingPip';
import type { ActiveDirectCallType } from '../types/Calling';
import {
CallMode,
CallViewMode,
CallState,
GroupCallConnectionState,
GroupCallJoinState,
} from '../types/Calling';
import { CallMode } from '../types/CallDisposition';
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
import { fakeGetGroupCallVideoFrameSource } from '../test-both/helpers/fakeGetGroupCallVideoFrameSource';
import { setupI18n } from '../util/setupI18n';

View file

@ -14,7 +14,8 @@ import type {
GroupCallRemoteParticipantType,
GroupCallVideoRequest,
} from '../types/Calling';
import { CallMode, GroupCallJoinState } from '../types/Calling';
import { GroupCallJoinState } from '../types/Calling';
import { CallMode } from '../types/CallDisposition';
import { AvatarColors } from '../types/Colors';
import type { SetRendererCanvasType } from '../state/ducks/calling';
import { useGetCallingFrameBuffer } from '../calling/useGetCallingFrameBuffer';

View file

@ -3,7 +3,7 @@
import React, { useEffect, useMemo, useRef } from 'react';
import type { ActiveCallType } from '../types/Calling';
import { CallMode } from '../types/Calling';
import { CallMode } from '../types/CallDisposition';
import type { ConversationType } from '../state/ducks/conversations';
import type { LocalizerType } from '../types/Util';
import { CallingToastProvider, useCallingToasts } from './CallingToast';

View file

@ -28,6 +28,7 @@ import {
DirectCallStatus,
GroupCallStatus,
isSameCallHistoryGroup,
CallMode,
} from '../types/CallDisposition';
import { formatDateTimeShort, isMoreRecentThan } from '../util/timestamp';
import type { ConversationType } from '../state/ducks/conversations';
@ -47,7 +48,6 @@ import { CallsNewCallButton } from './CallsNewCall';
import { Tooltip, TooltipPlacement } from './Tooltip';
import { Theme } from '../util/theme';
import type { CallingConversationType } from '../types/Calling';
import { CallMode } from '../types/Calling';
import type { CallLinkType } from '../types/CallLink';
import {
callLinkToConversation,

View file

@ -62,7 +62,8 @@ type CallsTabProps = Readonly<{
preferredLeftPaneWidth: number;
renderCallLinkDetails: (
roomId: string,
callHistoryGroup: CallHistoryGroup
callHistoryGroup: CallHistoryGroup,
onClose: () => void
) => JSX.Element;
renderConversationDetails: (
conversationId: string,
@ -152,6 +153,10 @@ export function CallsTab({
[updateSelectedView]
);
const onCloseSelectedView = useCallback(() => {
updateSelectedView(null);
}, [updateSelectedView]);
useEscapeHandling(
sidebarView === CallsTabSidebarView.NewCallView
? () => {
@ -328,7 +333,8 @@ export function CallsTab({
{selectedView.type === 'callLink' &&
renderCallLinkDetails(
selectedView.roomId,
selectedView.callHistoryGroup
selectedView.callHistoryGroup,
onCloseSelectedView
)}
</div>
)}

View file

@ -6,7 +6,7 @@ import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import type { PropsType } from './IncomingCallBar';
import { IncomingCallBar } from './IncomingCallBar';
import { CallMode } from '../types/Calling';
import { CallMode } from '../types/CallDisposition';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';

View file

@ -11,7 +11,7 @@ import { getParticipantName } from '../util/callingGetParticipantName';
import { ContactName } from './conversation/ContactName';
import type { LocalizerType } from '../types/Util';
import { AvatarColors } from '../types/Colors';
import { CallMode } from '../types/Calling';
import { CallMode } from '../types/CallDisposition';
import type { ConversationType } from '../state/ducks/conversations';
import type { AcceptCallType, DeclineCallType } from '../state/ducks/calling';
import { missingCaseError } from '../util/missingCaseError';

View file

@ -6,7 +6,13 @@ import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import { setupI18n } from '../../util/setupI18n';
import enMessages from '../../../_locales/en/messages.json';
import { CallMode } from '../../types/Calling';
import {
CallMode,
CallType,
CallDirection,
GroupCallStatus,
DirectCallStatus,
} from '../../types/CallDisposition';
import { generateAci } from '../../types/ServiceId';
import { CallingNotification, type PropsType } from './CallingNotification';
import {
@ -14,12 +20,6 @@ import {
getDefaultGroup,
} from '../../test-both/helpers/getDefaultConversation';
import type { CallStatus } from '../../types/CallDisposition';
import {
CallType,
CallDirection,
GroupCallStatus,
DirectCallStatus,
} from '../../types/CallDisposition';
import type { ConversationType } from '../../state/ducks/conversations';
const i18n = setupI18n('en', enMessages);

View file

@ -10,7 +10,13 @@ import { SystemMessage, SystemMessageKind } from './SystemMessage';
import { Button, ButtonSize, ButtonVariant } from '../Button';
import { MessageTimestamp } from './MessageTimestamp';
import type { LocalizerType } from '../../types/Util';
import { CallMode } from '../../types/Calling';
import {
CallMode,
CallDirection,
CallType,
DirectCallStatus,
GroupCallStatus,
} from '../../types/CallDisposition';
import type { CallingNotificationType } from '../../util/callingNotification';
import {
getCallingIcon,
@ -19,12 +25,6 @@ import {
import { missingCaseError } from '../../util/missingCaseError';
import { Tooltip, TooltipPlacement } from '../Tooltip';
import * as log from '../../logging/log';
import {
CallDirection,
CallType,
DirectCallStatus,
GroupCallStatus,
} from '../../types/CallDisposition';
import {
type ContextMenuTriggerType,
MessageContextMenu,

View file

@ -11,7 +11,7 @@ import enMessages from '../../../_locales/en/messages.json';
import type { PropsType as TimelineItemProps } from './TimelineItem';
import { TimelineItem } from './TimelineItem';
import { UniversalTimerNotification } from './UniversalTimerNotification';
import { CallMode } from '../../types/Calling';
import { CallMode } from '../../types/CallDisposition';
import { AvatarColors } from '../../types/Colors';
import { getDefaultConversation } from '../../test-both/helpers/getDefaultConversation';
import { WidthBreakpoint } from '../_util';

View file

@ -19,12 +19,7 @@ import { makeFakeLookupConversationWithoutServiceId } from '../../../test-both/h
import { ThemeType } from '../../../types/Util';
import { DurationInSeconds } from '../../../util/durations';
import { NavTab } from '../../../state/ducks/nav';
import { CallMode } from '../../../types/Calling';
import {
CallDirection,
CallType,
DirectCallStatus,
} from '../../../types/CallDisposition';
import { getFakeCallHistoryGroup } from '../../../test-both/helpers/getFakeCallHistoryGroup';
const i18n = setupI18n('en', enMessages);
@ -224,30 +219,15 @@ export const _11 = (): JSX.Element => (
<ConversationDetails {...createProps()} isGroup={false} />
);
function mins(n: number) {
return DurationInSeconds.toMillis(DurationInSeconds.fromMinutes(n));
}
export function WithCallHistoryGroup(): JSX.Element {
const props = createProps();
return (
<ConversationDetails
{...props}
callHistoryGroup={{
peerId: props.conversation?.serviceId ?? '',
mode: CallMode.Direct,
type: CallType.Video,
direction: CallDirection.Incoming,
status: DirectCallStatus.Accepted,
timestamp: Date.now(),
children: [
{ callId: '123', timestamp: Date.now() },
{ callId: '122', timestamp: Date.now() - mins(30) },
{ callId: '121', timestamp: Date.now() - mins(45) },
{ callId: '121', timestamp: Date.now() - mins(60) },
],
}}
callHistoryGroup={getFakeCallHistoryGroup({
peerId: props.conversation?.serviceId,
})}
selectedNavTab={NavTab.Calls}
/>
);