Init create/admin call links flow
This commit is contained in:
parent
53b8f5f152
commit
f19f0fb47d
31 changed files with 1256 additions and 149 deletions
32
ts/components/CallLinkEditModal.stories.tsx
Normal file
32
ts/components/CallLinkEditModal.stories.tsx
Normal file
|
@ -0,0 +1,32 @@
|
|||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { setupI18n } from '../util/setupI18n';
|
||||
import enMessages from '../../_locales/en/messages.json';
|
||||
import type { CallLinkEditModalProps } from './CallLinkEditModal';
|
||||
import { CallLinkEditModal } from './CallLinkEditModal';
|
||||
import type { ComponentMeta } from '../storybook/types';
|
||||
import { FAKE_CALL_LINK_WITH_ADMIN_KEY } from '../test-both/helpers/fakeCallLink';
|
||||
|
||||
const i18n = setupI18n('en', enMessages);
|
||||
|
||||
export default {
|
||||
title: 'Components/CallLinkEditModal',
|
||||
component: CallLinkEditModal,
|
||||
args: {
|
||||
i18n,
|
||||
callLink: FAKE_CALL_LINK_WITH_ADMIN_KEY,
|
||||
onClose: action('onClose'),
|
||||
onCopyCallLink: action('onCopyCallLink'),
|
||||
onUpdateCallLinkName: action('onUpdateCallLinkName'),
|
||||
onUpdateCallLinkRestrictions: action('onUpdateCallLinkRestrictions'),
|
||||
onShareCallLinkViaSignal: action('onShareCallLinkViaSignal'),
|
||||
onStartCallLinkLobby: action('onStartCallLinkLobby'),
|
||||
},
|
||||
} satisfies ComponentMeta<CallLinkEditModalProps>;
|
||||
|
||||
export function Basic(args: CallLinkEditModalProps): JSX.Element {
|
||||
return <CallLinkEditModal {...args} />;
|
||||
}
|
214
ts/components/CallLinkEditModal.tsx
Normal file
214
ts/components/CallLinkEditModal.tsx
Normal file
|
@ -0,0 +1,214 @@
|
|||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { v4 as generateUuid } from 'uuid';
|
||||
import { Modal } from './Modal';
|
||||
import type { LocalizerType } from '../types/I18N';
|
||||
import {
|
||||
CallLinkRestrictions,
|
||||
toCallLinkRestrictions,
|
||||
type CallLinkType,
|
||||
} from '../types/CallLink';
|
||||
import { Input } from './Input';
|
||||
import { Select } from './Select';
|
||||
import { linkCallRoute } from '../util/signalRoutes';
|
||||
import { Button, ButtonSize, ButtonVariant } from './Button';
|
||||
import { Avatar, AvatarSize } from './Avatar';
|
||||
import { formatUrlWithoutProtocol } from '../util/url';
|
||||
|
||||
export type CallLinkEditModalProps = {
|
||||
i18n: LocalizerType;
|
||||
callLink: CallLinkType;
|
||||
onClose: () => void;
|
||||
onCopyCallLink: () => void;
|
||||
onUpdateCallLinkName: (name: string) => void;
|
||||
onUpdateCallLinkRestrictions: (restrictions: CallLinkRestrictions) => void;
|
||||
onShareCallLinkViaSignal: () => void;
|
||||
onStartCallLinkLobby: () => void;
|
||||
};
|
||||
|
||||
export function CallLinkEditModal({
|
||||
i18n,
|
||||
callLink,
|
||||
onClose,
|
||||
onCopyCallLink,
|
||||
onUpdateCallLinkName,
|
||||
onUpdateCallLinkRestrictions,
|
||||
onShareCallLinkViaSignal,
|
||||
onStartCallLinkLobby,
|
||||
}: CallLinkEditModalProps): JSX.Element {
|
||||
const { name: savedName, restrictions: savedRestrictions } = callLink;
|
||||
|
||||
const [nameId] = useState(() => generateUuid());
|
||||
const [restrictionsId] = useState(() => generateUuid());
|
||||
|
||||
const [nameInput, setNameInput] = useState(savedName);
|
||||
const [restrictionsInput, setRestrictionsInput] = useState(savedRestrictions);
|
||||
|
||||
// We only want to use the default name "Signal Call" as a value if the user
|
||||
// modified the input and then chose that name. Doesn't revert when saved.
|
||||
const [nameTouched, setNameTouched] = useState(false);
|
||||
|
||||
const callLinkWebUrl = useMemo(() => {
|
||||
return formatUrlWithoutProtocol(
|
||||
linkCallRoute.toWebUrl({ key: callLink.rootKey })
|
||||
);
|
||||
}, [callLink.rootKey]);
|
||||
|
||||
const onSaveName = useCallback(
|
||||
(newName: string) => {
|
||||
if (!nameTouched) {
|
||||
return;
|
||||
}
|
||||
if (newName === savedName) {
|
||||
return;
|
||||
}
|
||||
onUpdateCallLinkName(newName);
|
||||
},
|
||||
[nameTouched, savedName, onUpdateCallLinkName]
|
||||
);
|
||||
|
||||
const onSaveRestrictions = useCallback(
|
||||
(newRestrictions: CallLinkRestrictions) => {
|
||||
if (newRestrictions === savedRestrictions) {
|
||||
return;
|
||||
}
|
||||
onUpdateCallLinkRestrictions(newRestrictions);
|
||||
},
|
||||
[savedRestrictions, onUpdateCallLinkRestrictions]
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
i18n={i18n}
|
||||
modalName="CallLinkEditModal"
|
||||
moduleClassName="CallLinkEditModal"
|
||||
title={i18n('icu:CallLinkEditModal__Title')}
|
||||
hasXButton
|
||||
onClose={() => {
|
||||
// Save the modal in case the user hits escape
|
||||
onSaveName(nameInput);
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
<div className="CallLinkEditModal__Header">
|
||||
<Avatar
|
||||
i18n={i18n}
|
||||
badge={undefined}
|
||||
conversationType="callLink"
|
||||
size={AvatarSize.SIXTY_FOUR}
|
||||
acceptedMessageRequest
|
||||
isMe={false}
|
||||
sharedGroupNames={[]}
|
||||
title={callLink.name ?? i18n('icu:calling__call-link-default-title')}
|
||||
/>
|
||||
<div className="CallLinkEditModal__Header__Details">
|
||||
<label htmlFor={nameId} className="CallLinkEditModal__SrOnly">
|
||||
{i18n('icu:CallLinkEditModal__InputLabel--Name--SrOnly')}
|
||||
</label>
|
||||
<Input
|
||||
moduleClassName="CallLinkEditModal__Input--Name"
|
||||
i18n={i18n}
|
||||
value={
|
||||
nameInput === '' && !nameTouched
|
||||
? i18n('icu:calling__call-link-default-title')
|
||||
: nameInput
|
||||
}
|
||||
maxByteCount={120}
|
||||
onChange={value => {
|
||||
setNameTouched(true);
|
||||
setNameInput(value);
|
||||
}}
|
||||
onBlur={() => {
|
||||
onSaveName(nameInput);
|
||||
}}
|
||||
onEnter={() => {
|
||||
onSaveName(nameInput);
|
||||
}}
|
||||
placeholder={i18n('icu:calling__call-link-default-title')}
|
||||
/>
|
||||
|
||||
<div className="CallLinkEditModal__CallLinkAndJoinButton">
|
||||
<button
|
||||
className="CallLinkEditModal__CopyUrlTextButton"
|
||||
type="button"
|
||||
onClick={onCopyCallLink}
|
||||
aria-label={i18n('icu:CallLinkDetails__CopyLink')}
|
||||
>
|
||||
{callLinkWebUrl}
|
||||
</button>
|
||||
<Button
|
||||
onClick={onStartCallLinkLobby}
|
||||
size={ButtonSize.Small}
|
||||
variant={ButtonVariant.SecondaryAffirmative}
|
||||
className="CallLinkEditModal__JoinButton"
|
||||
>
|
||||
{i18n('icu:CallLinkEditModal__JoinButtonLabel')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="CallLinkEditModal__ApproveAllMembers__Row"
|
||||
// For testing, to easily check the restrictions saved
|
||||
data-restrictions={savedRestrictions}
|
||||
>
|
||||
<label
|
||||
htmlFor={restrictionsId}
|
||||
className="CallLinkEditModal__ApproveAllMembers__Label"
|
||||
>
|
||||
{i18n('icu:CallLinkEditModal__InputLabel--ApproveAllMembers')}
|
||||
</label>
|
||||
<Select
|
||||
id={restrictionsId}
|
||||
value={restrictionsInput}
|
||||
options={[
|
||||
{
|
||||
value: CallLinkRestrictions.None,
|
||||
text: i18n(
|
||||
'icu:CallLinkEditModal__ApproveAllMembers__Option--Off'
|
||||
),
|
||||
},
|
||||
{
|
||||
value: CallLinkRestrictions.AdminApproval,
|
||||
text: i18n(
|
||||
'icu:CallLinkEditModal__ApproveAllMembers__Option--On'
|
||||
),
|
||||
},
|
||||
]}
|
||||
onChange={value => {
|
||||
const newRestrictions = toCallLinkRestrictions(value);
|
||||
setRestrictionsInput(newRestrictions);
|
||||
onSaveRestrictions(newRestrictions);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="CallLinkEditModal__ActionButton"
|
||||
onClick={onCopyCallLink}
|
||||
>
|
||||
<i
|
||||
role="presentation"
|
||||
className="CallLinkEditModal__ActionButton__Icon CallLinkEditModal__ActionButton__Icon--Copy"
|
||||
/>
|
||||
{i18n('icu:CallLinkDetails__CopyLink')}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="CallLinkEditModal__ActionButton"
|
||||
onClick={onShareCallLinkViaSignal}
|
||||
>
|
||||
<i
|
||||
role="presentation"
|
||||
className="CallLinkEditModal__ActionButton__Icon CallLinkEditModal__ActionButton__Icon--Share"
|
||||
/>
|
||||
{i18n('icu:CallLinkDetails__ShareLinkViaSignal')}
|
||||
</button>
|
||||
</Modal>
|
||||
);
|
||||
}
|
|
@ -127,6 +127,7 @@ const defaultPendingState: SearchState = {
|
|||
|
||||
type CallsListProps = Readonly<{
|
||||
activeCall: ActiveCallStateType | undefined;
|
||||
canCreateCallLinks: boolean;
|
||||
getCallHistoryGroupsCount: (
|
||||
options: CallHistoryFilterOptions
|
||||
) => Promise<number>;
|
||||
|
@ -142,6 +143,7 @@ type CallsListProps = Readonly<{
|
|||
hangUpActiveCall: (reason: string) => void;
|
||||
i18n: LocalizerType;
|
||||
selectedCallHistoryGroup: CallHistoryGroup | null;
|
||||
onCreateCallLink: () => void;
|
||||
onOutgoingAudioCallInConversation: (conversationId: string) => void;
|
||||
onOutgoingVideoCallInConversation: (conversationId: string) => void;
|
||||
onChangeCallsTabSelectedView: (selectedView: CallsTabSelectedView) => void;
|
||||
|
@ -157,10 +159,6 @@ const INACTIVE_CALL_LINK_PEEK_INTERVAL = 5 * MINUTE;
|
|||
const PEEK_BATCH_COUNT = 10;
|
||||
const PEEK_QUEUE_INTERVAL = 30 * SECOND;
|
||||
|
||||
function rowHeight() {
|
||||
return CALL_LIST_ITEM_ROW_HEIGHT;
|
||||
}
|
||||
|
||||
function isSameOptions(
|
||||
a: CallHistoryFilterOptions,
|
||||
b: CallHistoryFilterOptions
|
||||
|
@ -168,8 +166,12 @@ function isSameOptions(
|
|||
return a.query === b.query && a.status === b.status;
|
||||
}
|
||||
|
||||
type SpecialRows = 'CreateCallLink' | 'EmptyState';
|
||||
type Row = CallHistoryGroup | SpecialRows;
|
||||
|
||||
export function CallsList({
|
||||
activeCall,
|
||||
canCreateCallLinks,
|
||||
getCallHistoryGroupsCount,
|
||||
getCallHistoryGroups,
|
||||
callHistoryEdition,
|
||||
|
@ -180,6 +182,7 @@ export function CallsList({
|
|||
hangUpActiveCall,
|
||||
i18n,
|
||||
selectedCallHistoryGroup,
|
||||
onCreateCallLink,
|
||||
onOutgoingAudioCallInConversation,
|
||||
onOutgoingVideoCallInConversation,
|
||||
onChangeCallsTabSelectedView,
|
||||
|
@ -190,7 +193,7 @@ export function CallsList({
|
|||
const infiniteLoaderRef = useRef<InfiniteLoader>(null);
|
||||
const listRef = useRef<List>(null);
|
||||
const [queryInput, setQueryInput] = useState('');
|
||||
const [status, setStatus] = useState(CallHistoryFilterStatus.All);
|
||||
const [statusInput, setStatusInput] = useState(CallHistoryFilterStatus.All);
|
||||
const [searchState, setSearchState] = useState(defaultInitState);
|
||||
const [isLeaveCallDialogVisible, setIsLeaveCallDialogVisible] =
|
||||
useState(false);
|
||||
|
@ -200,6 +203,27 @@ export function CallsList({
|
|||
const getCallHistoryGroupsCountRef = useRef(getCallHistoryGroupsCount);
|
||||
const getCallHistoryGroupsRef = useRef(getCallHistoryGroups);
|
||||
|
||||
const searchStateQuery = searchState.options?.query ?? '';
|
||||
const searchStateStatus =
|
||||
searchState.options?.status ?? CallHistoryFilterStatus.All;
|
||||
const searchFiltering =
|
||||
searchStateQuery !== '' ||
|
||||
searchStateStatus !== CallHistoryFilterStatus.All;
|
||||
const searchPending = searchState.state === 'pending';
|
||||
|
||||
const rows = useMemo(() => {
|
||||
let results: ReadonlyArray<Row> = searchState.results?.items ?? [];
|
||||
if (results.length === 0) {
|
||||
results = ['EmptyState'];
|
||||
}
|
||||
if (!searchFiltering && canCreateCallLinks) {
|
||||
results = ['CreateCallLink', ...results];
|
||||
}
|
||||
return results;
|
||||
}, [searchState.results?.items, searchFiltering, canCreateCallLinks]);
|
||||
|
||||
const rowCount = rows.length;
|
||||
|
||||
const searchStateItemsRef = useRef<ReadonlyArray<CallHistoryGroup> | null>(
|
||||
null
|
||||
);
|
||||
|
@ -208,17 +232,14 @@ export function CallsList({
|
|||
new Map()
|
||||
);
|
||||
const inactiveCallLinksPeekedAtRef = useRef<Map<string, number>>(new Map());
|
||||
|
||||
const peekQueueTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||
function clearPeekQueueTimer() {
|
||||
if (peekQueueTimerRef.current != null) {
|
||||
clearInterval(peekQueueTimerRef.current);
|
||||
peekQueueTimerRef.current = null;
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearPeekQueueTimer();
|
||||
if (peekQueueTimerRef.current != null) {
|
||||
clearInterval(peekQueueTimerRef.current);
|
||||
peekQueueTimerRef.current = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
@ -489,7 +510,7 @@ export function CallsList({
|
|||
async function search() {
|
||||
const options: CallHistoryFilterOptions = {
|
||||
query: queryInput.toLowerCase().normalize().trim(),
|
||||
status,
|
||||
status: statusInput,
|
||||
};
|
||||
|
||||
let timer = setTimeout(() => {
|
||||
|
@ -560,7 +581,7 @@ export function CallsList({
|
|||
return () => {
|
||||
controller.abort();
|
||||
};
|
||||
}, [queryInput, status, callHistoryEdition, enqueueCallPeeks]);
|
||||
}, [queryInput, statusInput, callHistoryEdition, enqueueCallPeeks]);
|
||||
|
||||
const loadMoreRows = useCallback(
|
||||
async (props: IndexRange) => {
|
||||
|
@ -625,9 +646,72 @@ export function CallsList({
|
|||
[searchState]
|
||||
);
|
||||
|
||||
const rowHeight = useCallback(
|
||||
({ index }: Index) => {
|
||||
const item = rows.at(index) ?? null;
|
||||
|
||||
if (item === 'EmptyState') {
|
||||
// arbitary large number so the empty state can be as big as it wants,
|
||||
// scrolling should always be locked when the list is empty
|
||||
return 9999;
|
||||
}
|
||||
|
||||
return CALL_LIST_ITEM_ROW_HEIGHT;
|
||||
},
|
||||
[rows]
|
||||
);
|
||||
|
||||
const rowRenderer = useCallback(
|
||||
({ key, index, style }: ListRowProps) => {
|
||||
const item = searchState.results?.items.at(index) ?? null;
|
||||
const item = rows.at(index) ?? null;
|
||||
|
||||
if (item === 'CreateCallLink') {
|
||||
return (
|
||||
<div key={key} style={style}>
|
||||
<ListTile
|
||||
moduleClassName="CallsList__ItemTile"
|
||||
title={
|
||||
<span className="CallsList__ItemTitle">
|
||||
{i18n('icu:CallsList__CreateCallLink')}
|
||||
</span>
|
||||
}
|
||||
leading={
|
||||
<Avatar
|
||||
acceptedMessageRequest
|
||||
conversationType="callLink"
|
||||
i18n={i18n}
|
||||
isMe={false}
|
||||
title=""
|
||||
sharedGroupNames={[]}
|
||||
size={AvatarSize.THIRTY_SIX}
|
||||
badge={undefined}
|
||||
className="CallsList__ItemAvatar"
|
||||
/>
|
||||
}
|
||||
onClick={onCreateCallLink}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (item === 'EmptyState') {
|
||||
return (
|
||||
<div key={key} className="CallsList__EmptyState" style={style}>
|
||||
{searchStateQuery === '' ? (
|
||||
i18n('icu:CallsList__EmptyState--noQuery')
|
||||
) : (
|
||||
<I18n
|
||||
i18n={i18n}
|
||||
id="icu:CallsList__EmptyState--hasQuery"
|
||||
components={{
|
||||
query: <UserText text={searchStateQuery} />,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const conversation = getConversationForItem(item);
|
||||
const activeCallConversationId = activeCall?.conversationId;
|
||||
|
||||
|
@ -647,11 +731,7 @@ export function CallsList({
|
|||
);
|
||||
const isActiveVisible = Boolean(isCallButtonVisible && item && isActive);
|
||||
|
||||
if (
|
||||
searchState.state === 'pending' ||
|
||||
item == null ||
|
||||
conversation == null
|
||||
) {
|
||||
if (searchPending || item == null || conversation == null) {
|
||||
return (
|
||||
<div key={key} style={style}>
|
||||
<ListTile
|
||||
|
@ -697,6 +777,7 @@ export function CallsList({
|
|||
<div
|
||||
key={key}
|
||||
style={style}
|
||||
data-type={item.type}
|
||||
className={classNames('CallsList__Item', {
|
||||
'CallsList__Item--selected': isSelected,
|
||||
'CallsList__Item--missed': wasMissed,
|
||||
|
@ -792,13 +873,16 @@ export function CallsList({
|
|||
},
|
||||
[
|
||||
activeCall,
|
||||
searchState,
|
||||
rows,
|
||||
searchStateQuery,
|
||||
searchPending,
|
||||
getCallLink,
|
||||
getConversationForItem,
|
||||
getIsCallActive,
|
||||
getIsInCall,
|
||||
selectedCallHistoryGroup,
|
||||
onChangeCallsTabSelectedView,
|
||||
onCreateCallLink,
|
||||
onOutgoingAudioCallInConversation,
|
||||
onOutgoingVideoCallInConversation,
|
||||
startCallLinkLobbyByRoomId,
|
||||
|
@ -819,18 +903,13 @@ export function CallsList({
|
|||
}, []);
|
||||
|
||||
const handleStatusToggle = useCallback(() => {
|
||||
setStatus(prevStatus => {
|
||||
setStatusInput(prevStatus => {
|
||||
return prevStatus === CallHistoryFilterStatus.All
|
||||
? CallHistoryFilterStatus.Missed
|
||||
: CallHistoryFilterStatus.All;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const filteringByMissed = status === CallHistoryFilterStatus.Missed;
|
||||
|
||||
const hasEmptyResults = searchState.results?.count === 0;
|
||||
const currentQuery = searchState.options?.query ?? '';
|
||||
|
||||
return (
|
||||
<>
|
||||
{isLeaveCallDialogVisible && (
|
||||
|
@ -874,10 +953,11 @@ export function CallsList({
|
|||
>
|
||||
<button
|
||||
className={classNames('CallsList__ToggleFilterByMissed', {
|
||||
'CallsList__ToggleFilterByMissed--pressed': filteringByMissed,
|
||||
'CallsList__ToggleFilterByMissed--pressed':
|
||||
statusInput === CallHistoryFilterStatus.Missed,
|
||||
})}
|
||||
type="button"
|
||||
aria-pressed={filteringByMissed}
|
||||
aria-pressed={statusInput === CallHistoryFilterStatus.Missed}
|
||||
aria-roledescription={i18n(
|
||||
'icu:CallsList__ToggleFilterByMissed__RoleDescription'
|
||||
)}
|
||||
|
@ -890,22 +970,6 @@ export function CallsList({
|
|||
</Tooltip>
|
||||
</NavSidebarSearchHeader>
|
||||
|
||||
{hasEmptyResults && (
|
||||
<p className="CallsList__EmptyState">
|
||||
{currentQuery === '' ? (
|
||||
i18n('icu:CallsList__EmptyState--noQuery')
|
||||
) : (
|
||||
<I18n
|
||||
i18n={i18n}
|
||||
id="icu:CallsList__EmptyState--hasQuery"
|
||||
components={{
|
||||
query: <UserText text={currentQuery} />,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<SizeObserver>
|
||||
{(ref, size) => {
|
||||
return (
|
||||
|
@ -915,7 +979,7 @@ export function CallsList({
|
|||
ref={infiniteLoaderRef}
|
||||
isRowLoaded={isRowLoaded}
|
||||
loadMoreRows={loadMoreRows}
|
||||
rowCount={searchState.results?.count}
|
||||
rowCount={rowCount}
|
||||
minimumBatchSize={100}
|
||||
threshold={30}
|
||||
>
|
||||
|
@ -923,13 +987,14 @@ export function CallsList({
|
|||
return (
|
||||
<List
|
||||
className={classNames('CallsList__List', {
|
||||
'CallsList__List--loading':
|
||||
searchState.state === 'pending',
|
||||
'CallsList__List--disableScrolling':
|
||||
searchState.results == null ||
|
||||
searchState.results.count === 0,
|
||||
})}
|
||||
ref={refMerger(listRef, registerChild)}
|
||||
width={size.width}
|
||||
height={size.height}
|
||||
rowCount={searchState.results?.count ?? 0}
|
||||
rowCount={rowCount}
|
||||
rowHeight={rowHeight}
|
||||
rowRenderer={rowRenderer}
|
||||
onRowsRendered={onRowsRendered}
|
||||
|
|
|
@ -41,6 +41,7 @@ type CallsTabProps = Readonly<{
|
|||
pagination: CallHistoryPagination
|
||||
) => Promise<Array<CallHistoryGroup>>;
|
||||
callHistoryEdition: number;
|
||||
canCreateCallLinks: boolean;
|
||||
getAdhocCall: (roomId: string) => CallStateType | undefined;
|
||||
getCall: (id: string) => CallStateType | undefined;
|
||||
getCallLink: (id: string) => CallLinkType | undefined;
|
||||
|
@ -53,6 +54,7 @@ type CallsTabProps = Readonly<{
|
|||
onClearCallHistory: () => void;
|
||||
onMarkCallHistoryRead: (conversationId: string, callId: string) => void;
|
||||
onToggleNavTabsCollapse: (navTabsCollapsed: boolean) => void;
|
||||
onCreateCallLink: () => void;
|
||||
onOutgoingAudioCallInConversation: (conversationId: string) => void;
|
||||
onOutgoingVideoCallInConversation: (conversationId: string) => void;
|
||||
peekNotConnectedGroupCall: (options: PeekNotConnectedGroupCallType) => void;
|
||||
|
@ -93,6 +95,7 @@ export function CallsTab({
|
|||
getCallHistoryGroupsCount,
|
||||
getCallHistoryGroups,
|
||||
callHistoryEdition,
|
||||
canCreateCallLinks,
|
||||
getAdhocCall,
|
||||
getCall,
|
||||
getCallLink,
|
||||
|
@ -105,6 +108,7 @@ export function CallsTab({
|
|||
onClearCallHistory,
|
||||
onMarkCallHistoryRead,
|
||||
onToggleNavTabsCollapse,
|
||||
onCreateCallLink,
|
||||
onOutgoingAudioCallInConversation,
|
||||
onOutgoingVideoCallInConversation,
|
||||
peekNotConnectedGroupCall,
|
||||
|
@ -257,6 +261,7 @@ export function CallsTab({
|
|||
<CallsList
|
||||
key={CallsTabSidebarView.CallsListView}
|
||||
activeCall={activeCall}
|
||||
canCreateCallLinks={canCreateCallLinks}
|
||||
getCallHistoryGroupsCount={getCallHistoryGroupsCount}
|
||||
getCallHistoryGroups={getCallHistoryGroups}
|
||||
callHistoryEdition={callHistoryEdition}
|
||||
|
@ -268,6 +273,7 @@ export function CallsTab({
|
|||
i18n={i18n}
|
||||
selectedCallHistoryGroup={selectedView?.callHistoryGroup ?? null}
|
||||
onChangeCallsTabSelectedView={updateSelectedView}
|
||||
onCreateCallLink={onCreateCallLink}
|
||||
onOutgoingAudioCallInConversation={
|
||||
handleOutgoingAudioCallInConversation
|
||||
}
|
||||
|
|
|
@ -29,6 +29,9 @@ export type PropsType = {
|
|||
// AddUserToAnotherGroupModal
|
||||
addUserToAnotherGroupModalContactId: string | undefined;
|
||||
renderAddUserToAnotherGroup: () => JSX.Element;
|
||||
// CallLinkEditModal
|
||||
callLinkEditModalRoomId: string | null;
|
||||
renderCallLinkEditModal: () => JSX.Element;
|
||||
// ContactModal
|
||||
contactModalState: ContactModalStateType | undefined;
|
||||
renderContactModal: () => JSX.Element;
|
||||
|
@ -102,6 +105,9 @@ export function GlobalModalContainer({
|
|||
// AddUserToAnotherGroupModal
|
||||
addUserToAnotherGroupModalContactId,
|
||||
renderAddUserToAnotherGroup,
|
||||
// CallLinkEditModal
|
||||
callLinkEditModalRoomId,
|
||||
renderCallLinkEditModal,
|
||||
// ContactModal
|
||||
contactModalState,
|
||||
renderContactModal,
|
||||
|
@ -164,7 +170,8 @@ export function GlobalModalContainer({
|
|||
// We want the following dialogs to show in this order:
|
||||
// 1. Errors
|
||||
// 2. Safety Number Changes
|
||||
// 3. The Rest (in no particular order, but they're ordered alphabetically)
|
||||
// 3. Forward Modal, so other modals can open it
|
||||
// 4. The Rest (in no particular order, but they're ordered alphabetically)
|
||||
|
||||
// Errors
|
||||
if (errorModalProps) {
|
||||
|
@ -176,12 +183,21 @@ export function GlobalModalContainer({
|
|||
return renderSendAnywayDialog();
|
||||
}
|
||||
|
||||
// Forward Modal
|
||||
if (forwardMessagesProps) {
|
||||
return renderForwardMessagesModal();
|
||||
}
|
||||
|
||||
// The Rest
|
||||
|
||||
if (addUserToAnotherGroupModalContactId) {
|
||||
return renderAddUserToAnotherGroup();
|
||||
}
|
||||
|
||||
if (callLinkEditModalRoomId) {
|
||||
return renderCallLinkEditModal();
|
||||
}
|
||||
|
||||
if (editHistoryMessages) {
|
||||
return renderEditHistoryMessagesModal();
|
||||
}
|
||||
|
@ -194,10 +210,6 @@ export function GlobalModalContainer({
|
|||
return renderDeleteMessagesModal();
|
||||
}
|
||||
|
||||
if (forwardMessagesProps) {
|
||||
return renderForwardMessagesModal();
|
||||
}
|
||||
|
||||
if (messageRequestActionsConfirmationProps) {
|
||||
return renderMessageRequestActionsConfirmation();
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ export type PropsType = {
|
|||
maxLengthCount?: number;
|
||||
moduleClassName?: string;
|
||||
onChange: (value: string) => unknown;
|
||||
onBlur?: () => unknown;
|
||||
onEnter?: () => unknown;
|
||||
placeholder: string;
|
||||
value?: string;
|
||||
|
@ -76,6 +77,7 @@ export const Input = forwardRef<
|
|||
maxLengthCount = 0,
|
||||
moduleClassName,
|
||||
onChange,
|
||||
onBlur,
|
||||
onEnter,
|
||||
placeholder,
|
||||
value = '',
|
||||
|
@ -214,6 +216,7 @@ export const Input = forwardRef<
|
|||
id,
|
||||
spellCheck: !disableSpellcheck,
|
||||
onChange: handleChange,
|
||||
onBlur,
|
||||
onKeyDown: handleKeyDown,
|
||||
onPaste: handlePaste,
|
||||
placeholder,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue