diff --git a/ts/state/smart/ConversationPanel.tsx b/ts/state/smart/ConversationPanel.tsx new file mode 100644 index 0000000000..a573b2ab35 --- /dev/null +++ b/ts/state/smart/ConversationPanel.tsx @@ -0,0 +1,116 @@ +// Copyright 2023 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import React, { useEffect, useMemo, useRef } from 'react'; +import classNames from 'classnames'; +import { useSelector } from 'react-redux'; +import type { PanelRenderType } from '../../types/Panels'; +import type { StateType } from '../reducer'; +import * as log from '../../logging/log'; +import { ContactDetail } from '../../components/conversation/ContactDetail'; +import { PanelType } from '../../types/Panels'; +import { SmartAllMedia } from './AllMedia'; +import { SmartChatColorPicker } from './ChatColorPicker'; +import { SmartConversationDetails } from './ConversationDetails'; +import { SmartConversationNotificationsSettings } from './ConversationNotificationsSettings'; +import { SmartGV1Members } from './GV1Members'; +import { SmartGroupLinkManagement } from './GroupLinkManagement'; +import { SmartGroupV2Permissions } from './GroupV2Permissions'; +import { SmartMessageDetail } from './MessageDetail'; +import { SmartPendingInvites } from './PendingInvites'; +import { SmartStickerManager } from './StickerManager'; +import { getIntl } from '../selectors/user'; +import { getTopPanel } from '../selectors/conversations'; +import { useConversationsActions } from '../ducks/conversations'; +import { focusableSelectors } from '../../util/focusableSelectors'; + +export function ConversationPanel({ + conversationId, +}: { + conversationId: string; +}): JSX.Element | null { + const i18n = useSelector(getIntl); + const { startConversation } = useConversationsActions(); + const topPanel = useSelector( + getTopPanel + ); + + const selectors = useMemo(() => focusableSelectors.join(','), []); + const panelRef = useRef(null); + useEffect(() => { + const panelNode = panelRef.current; + if (!panelNode) { + return; + } + + const elements = panelNode.querySelectorAll(selectors); + if (!elements.length) { + return; + } + elements[0]?.focus(); + }, [selectors, topPanel]); + + if (!topPanel) { + return null; + } + + let panelChild: JSX.Element; + let panelClassName = ''; + + if (topPanel.type === PanelType.AllMedia) { + panelChild = ; + } else if (topPanel.type === PanelType.ChatColorEditor) { + panelChild = ; + } else if (topPanel.type === PanelType.ContactDetails) { + const { contact, signalAccount } = topPanel.args; + + panelChild = ( + { + if (signalAccount) { + startConversation(signalAccount.phoneNumber, signalAccount.uuid); + } + }} + /> + ); + } else if (topPanel.type === PanelType.ConversationDetails) { + panelClassName = 'conversation-details-pane'; + panelChild = ; + } else if (topPanel.type === PanelType.GroupInvites) { + panelChild = ( + + ); + } else if (topPanel.type === PanelType.GroupLinkManagement) { + panelChild = ; + } else if (topPanel.type === PanelType.GroupPermissions) { + panelChild = ; + } else if (topPanel.type === PanelType.GroupV1Members) { + panelClassName = 'group-member-list'; + panelChild = ; + } else if (topPanel.type === PanelType.MessageDetails) { + panelClassName = 'message-detail-wrapper'; + panelChild = ; + } else if (topPanel.type === PanelType.NotificationSettings) { + panelChild = ( + + ); + } else if (topPanel.type === PanelType.StickerManager) { + panelClassName = 'sticker-manager-wrapper'; + panelChild = ; + } else { + log.warn('renderPanel: Got unexpected panel', topPanel); + return null; + } + + return ( +
+ {panelChild} +
+ ); +} diff --git a/ts/state/smart/ConversationView.tsx b/ts/state/smart/ConversationView.tsx index 08f99f7529..8b479e4cfa 100644 --- a/ts/state/smart/ConversationView.tsx +++ b/ts/state/smart/ConversationView.tsx @@ -3,30 +3,15 @@ import React from 'react'; import { useSelector } from 'react-redux'; -import type { PanelRenderType } from '../../types/Panels'; import type { StateType } from '../reducer'; -import * as log from '../../logging/log'; -import { ContactDetail } from '../../components/conversation/ContactDetail'; +import { ConversationPanel } from './ConversationPanel'; import { ConversationView } from '../../components/conversation/ConversationView'; -import { PanelType } from '../../types/Panels'; -import { SmartAllMedia } from './AllMedia'; -import { SmartChatColorPicker } from './ChatColorPicker'; import { SmartCompositionArea } from './CompositionArea'; -import { SmartConversationDetails } from './ConversationDetails'; import { SmartConversationHeader } from './ConversationHeader'; -import { SmartConversationNotificationsSettings } from './ConversationNotificationsSettings'; -import { SmartGV1Members } from './GV1Members'; -import { SmartGroupLinkManagement } from './GroupLinkManagement'; -import { SmartGroupV2Permissions } from './GroupV2Permissions'; -import { SmartMessageDetail } from './MessageDetail'; -import { SmartPendingInvites } from './PendingInvites'; -import { SmartStickerManager } from './StickerManager'; import { SmartTimeline } from './Timeline'; -import { getIntl } from '../selectors/user'; import { getSelectedConversationId, getSelectedMessageIds, - getTopPanel, } from '../selectors/conversations'; import { useComposerActions } from '../ducks/composer'; import { useConversationsActions } from '../ducks/conversations'; @@ -38,15 +23,11 @@ export function SmartConversationView(): JSX.Element { throw new Error('SmartConversationView: No selected conversation'); } - const topPanel = useSelector( - getTopPanel - ); - const { startConversation, toggleSelectMode } = useConversationsActions(); + const { toggleSelectMode } = useConversationsActions(); const selectedMessageIds = useSelector(getSelectedMessageIds); const isSelectMode = selectedMessageIds != null; const { processAttachments } = useComposerActions(); - const i18n = useSelector(getIntl); const hasOpenModal = useSelector((state: StateType) => { return ( @@ -72,122 +53,7 @@ export function SmartConversationView(): JSX.Element { renderTimeline={() => ( )} - renderPanel={() => { - if (!topPanel) { - return; - } - - if (topPanel.type === PanelType.AllMedia) { - return ( -
- -
- ); - } - - if (topPanel.type === PanelType.ChatColorEditor) { - return ( -
- -
- ); - } - - if (topPanel.type === PanelType.ContactDetails) { - const { contact, signalAccount } = topPanel.args; - - return ( -
- { - if (signalAccount) { - startConversation( - signalAccount.phoneNumber, - signalAccount.uuid - ); - } - }} - /> -
- ); - } - - if (topPanel.type === PanelType.ConversationDetails) { - return ( -
- -
- ); - } - - if (topPanel.type === PanelType.GroupInvites) { - return ( -
- -
- ); - } - - if (topPanel.type === PanelType.GroupLinkManagement) { - return ( -
- -
- ); - } - - if (topPanel.type === PanelType.GroupPermissions) { - return ( -
- -
- ); - } - - if (topPanel.type === PanelType.GroupV1Members) { - return ( -
- -
- ); - } - - if (topPanel.type === PanelType.MessageDetails) { - return ( -
- -
- ); - } - - if (topPanel.type === PanelType.NotificationSettings) { - return ( -
- -
- ); - } - - if (topPanel.type === PanelType.StickerManager) { - return ( -
- -
- ); - } - - log.warn('renderPanel: Got unexpected panel', topPanel); - - return undefined; - }} + renderPanel={() => } /> ); } diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json index 4cdaeae769..5291b32386 100644 --- a/ts/util/lint/exceptions.json +++ b/ts/util/lint/exceptions.json @@ -2560,6 +2560,13 @@ "updated": "2023-06-02T00:37:19.861Z", "reasonDetail": "Reading from innerHTML, not setting it" }, + { + "rule": "React-useRef", + "path": "ts/state/smart/ConversationPanel.tsx", + "line": " const panelRef = useRef(null);", + "reasonCategory": "usageTrusted", + "updated": "2023-06-15T19:55:51.367Z" + }, { "rule": "React-useRef", "path": "ts/state/smart/InstallScreen.tsx",