A hybrid panel system for React & Backbone
This commit is contained in:
parent
624adca360
commit
ebeb6a7a6d
14 changed files with 474 additions and 157 deletions
|
@ -12,6 +12,7 @@ export type PropsType = {
|
||||||
renderCompositionArea: () => JSX.Element;
|
renderCompositionArea: () => JSX.Element;
|
||||||
renderConversationHeader: () => JSX.Element;
|
renderConversationHeader: () => JSX.Element;
|
||||||
renderTimeline: () => JSX.Element;
|
renderTimeline: () => JSX.Element;
|
||||||
|
renderPanel: () => JSX.Element | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ConversationView({
|
export function ConversationView({
|
||||||
|
@ -20,6 +21,7 @@ export function ConversationView({
|
||||||
renderCompositionArea,
|
renderCompositionArea,
|
||||||
renderConversationHeader,
|
renderConversationHeader,
|
||||||
renderTimeline,
|
renderTimeline,
|
||||||
|
renderPanel,
|
||||||
}: PropsType): JSX.Element {
|
}: PropsType): JSX.Element {
|
||||||
const onDrop = React.useCallback(
|
const onDrop = React.useCallback(
|
||||||
(event: React.DragEvent<HTMLDivElement>) => {
|
(event: React.DragEvent<HTMLDivElement>) => {
|
||||||
|
@ -93,6 +95,7 @@ export function ConversationView({
|
||||||
{renderCompositionArea()}
|
{renderCompositionArea()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{renderPanel()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,7 +80,7 @@ const createProps = (
|
||||||
setDisappearingMessages: action('setDisappearingMessages'),
|
setDisappearingMessages: action('setDisappearingMessages'),
|
||||||
showAllMedia: action('showAllMedia'),
|
showAllMedia: action('showAllMedia'),
|
||||||
showContactModal: action('showContactModal'),
|
showContactModal: action('showContactModal'),
|
||||||
showChatColorEditor: action('showChatColorEditor'),
|
pushPanelForConversation: action('pushPanelForConversation'),
|
||||||
showGroupLinkManagement: action('showGroupLinkManagement'),
|
showGroupLinkManagement: action('showGroupLinkManagement'),
|
||||||
showGroupV2Permissions: action('showGroupV2Permissions'),
|
showGroupV2Permissions: action('showGroupV2Permissions'),
|
||||||
showConversationNotificationsSettings: action(
|
showConversationNotificationsSettings: action(
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { Button, ButtonIconType, ButtonVariant } from '../../Button';
|
||||||
import { Tooltip } from '../../Tooltip';
|
import { Tooltip } from '../../Tooltip';
|
||||||
import type {
|
import type {
|
||||||
ConversationType,
|
ConversationType,
|
||||||
|
PushPanelForConversationActionType,
|
||||||
ShowConversationType,
|
ShowConversationType,
|
||||||
} from '../../../state/ducks/conversations';
|
} from '../../../state/ducks/conversations';
|
||||||
import type { PreferredBadgeSelectorType } from '../../../state/selectors/badges';
|
import type { PreferredBadgeSelectorType } from '../../../state/selectors/badges';
|
||||||
|
@ -50,6 +51,7 @@ import type {
|
||||||
} from '../../../types/Avatar';
|
} from '../../../types/Avatar';
|
||||||
import { isConversationMuted } from '../../../util/isConversationMuted';
|
import { isConversationMuted } from '../../../util/isConversationMuted';
|
||||||
import { ConversationDetailsGroups } from './ConversationDetailsGroups';
|
import { ConversationDetailsGroups } from './ConversationDetailsGroups';
|
||||||
|
import { PanelType } from '../../../types/Panels';
|
||||||
|
|
||||||
enum ModalState {
|
enum ModalState {
|
||||||
NothingOpen,
|
NothingOpen,
|
||||||
|
@ -80,7 +82,6 @@ export type StateProps = {
|
||||||
pendingApprovalMemberships: ReadonlyArray<GroupV2RequestingMembership>;
|
pendingApprovalMemberships: ReadonlyArray<GroupV2RequestingMembership>;
|
||||||
pendingMemberships: ReadonlyArray<GroupV2PendingMembership>;
|
pendingMemberships: ReadonlyArray<GroupV2PendingMembership>;
|
||||||
showAllMedia: () => void;
|
showAllMedia: () => void;
|
||||||
showChatColorEditor: () => void;
|
|
||||||
showGroupLinkManagement: () => void;
|
showGroupLinkManagement: () => void;
|
||||||
showGroupV2Permissions: () => void;
|
showGroupV2Permissions: () => void;
|
||||||
showPendingInvites: () => void;
|
showPendingInvites: () => void;
|
||||||
|
@ -110,6 +111,7 @@ type ActionProps = {
|
||||||
loadRecentMediaItems: (id: string, limit: number) => void;
|
loadRecentMediaItems: (id: string, limit: number) => void;
|
||||||
onOutgoingAudioCallInConversation: (conversationId: string) => unknown;
|
onOutgoingAudioCallInConversation: (conversationId: string) => unknown;
|
||||||
onOutgoingVideoCallInConversation: (conversationId: string) => unknown;
|
onOutgoingVideoCallInConversation: (conversationId: string) => unknown;
|
||||||
|
pushPanelForConversation: PushPanelForConversationActionType;
|
||||||
replaceAvatar: ReplaceAvatarActionType;
|
replaceAvatar: ReplaceAvatarActionType;
|
||||||
saveAvatarToDisk: SaveAvatarToDiskActionType;
|
saveAvatarToDisk: SaveAvatarToDiskActionType;
|
||||||
searchInConversation: (id: string) => unknown;
|
searchInConversation: (id: string) => unknown;
|
||||||
|
@ -149,6 +151,7 @@ export function ConversationDetails({
|
||||||
onOutgoingVideoCallInConversation,
|
onOutgoingVideoCallInConversation,
|
||||||
pendingApprovalMemberships,
|
pendingApprovalMemberships,
|
||||||
pendingMemberships,
|
pendingMemberships,
|
||||||
|
pushPanelForConversation,
|
||||||
renderChooseGroupMembersModal,
|
renderChooseGroupMembersModal,
|
||||||
renderConfirmAdditionsModal,
|
renderConfirmAdditionsModal,
|
||||||
replaceAvatar,
|
replaceAvatar,
|
||||||
|
@ -157,7 +160,6 @@ export function ConversationDetails({
|
||||||
setDisappearingMessages,
|
setDisappearingMessages,
|
||||||
setMuteExpiration,
|
setMuteExpiration,
|
||||||
showAllMedia,
|
showAllMedia,
|
||||||
showChatColorEditor,
|
|
||||||
showContactModal,
|
showContactModal,
|
||||||
showConversationNotificationsSettings,
|
showConversationNotificationsSettings,
|
||||||
showConversation,
|
showConversation,
|
||||||
|
@ -426,7 +428,11 @@ export function ConversationDetails({
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label={i18n('showChatColorEditor')}
|
label={i18n('showChatColorEditor')}
|
||||||
onClick={showChatColorEditor}
|
onClick={() => {
|
||||||
|
pushPanelForConversation(conversation.id, {
|
||||||
|
type: PanelType.ChatColorEditor,
|
||||||
|
});
|
||||||
|
}}
|
||||||
right={
|
right={
|
||||||
<div
|
<div
|
||||||
className={`ConversationDetails__chat-color ConversationDetails__chat-color--${conversation.conversationColor}`}
|
className={`ConversationDetails__chat-color ConversationDetails__chat-color--${conversation.conversationColor}`}
|
||||||
|
|
|
@ -24,7 +24,6 @@ import { StagedLinkPreview } from './components/conversation/StagedLinkPreview';
|
||||||
import { DisappearingTimeDialog } from './components/DisappearingTimeDialog';
|
import { DisappearingTimeDialog } from './components/DisappearingTimeDialog';
|
||||||
|
|
||||||
// State
|
// State
|
||||||
import { createChatColorPicker } from './state/roots/createChatColorPicker';
|
|
||||||
import { createConversationDetails } from './state/roots/createConversationDetails';
|
import { createConversationDetails } from './state/roots/createConversationDetails';
|
||||||
import { createApp } from './state/roots/createApp';
|
import { createApp } from './state/roots/createApp';
|
||||||
import { createGroupLinkManagement } from './state/roots/createGroupLinkManagement';
|
import { createGroupLinkManagement } from './state/roots/createGroupLinkManagement';
|
||||||
|
@ -401,7 +400,6 @@ export const setup = (options: {
|
||||||
|
|
||||||
const Roots = {
|
const Roots = {
|
||||||
createApp,
|
createApp,
|
||||||
createChatColorPicker,
|
|
||||||
createConversationDetails,
|
createConversationDetails,
|
||||||
createGroupLinkManagement,
|
createGroupLinkManagement,
|
||||||
createGroupV2JoinModal,
|
createGroupV2JoinModal,
|
||||||
|
|
|
@ -117,6 +117,7 @@ import {
|
||||||
initiateMigrationToGroupV2 as doInitiateMigrationToGroupV2,
|
initiateMigrationToGroupV2 as doInitiateMigrationToGroupV2,
|
||||||
} from '../../groups';
|
} from '../../groups';
|
||||||
import { getMessageById } from '../../messages/getMessageById';
|
import { getMessageById } from '../../messages/getMessageById';
|
||||||
|
import type { PanelRenderType } from '../../types/Panels';
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
|
||||||
|
@ -392,8 +393,7 @@ export type ConversationsStateType = {
|
||||||
selectedMessage: string | undefined;
|
selectedMessage: string | undefined;
|
||||||
selectedMessageCounter: number;
|
selectedMessageCounter: number;
|
||||||
selectedMessageSource: SelectedMessageSource | undefined;
|
selectedMessageSource: SelectedMessageSource | undefined;
|
||||||
selectedConversationTitle?: string;
|
selectedConversationPanels: Array<PanelRenderType>;
|
||||||
selectedConversationPanelDepth: number;
|
|
||||||
showArchived: boolean;
|
showArchived: boolean;
|
||||||
composer?: ComposerStateType;
|
composer?: ComposerStateType;
|
||||||
contactSpoofingReview?: ContactSpoofingReviewStateType;
|
contactSpoofingReview?: ContactSpoofingReviewStateType;
|
||||||
|
@ -457,7 +457,8 @@ const DISCARD_MESSAGES = 'conversations/DISCARD_MESSAGES';
|
||||||
const REPLACE_AVATARS = 'conversations/REPLACE_AVATARS';
|
const REPLACE_AVATARS = 'conversations/REPLACE_AVATARS';
|
||||||
export const SELECTED_CONVERSATION_CHANGED =
|
export const SELECTED_CONVERSATION_CHANGED =
|
||||||
'conversations/SELECTED_CONVERSATION_CHANGED';
|
'conversations/SELECTED_CONVERSATION_CHANGED';
|
||||||
|
const PUSH_PANEL = 'conversations/PUSH_PANEL';
|
||||||
|
const POP_PANEL = 'conversations/POP_PANEL';
|
||||||
export const SET_VOICE_NOTE_PLAYBACK_RATE =
|
export const SET_VOICE_NOTE_PLAYBACK_RATE =
|
||||||
'conversations/SET_VOICE_NOTE_PLAYBACK_RATE';
|
'conversations/SET_VOICE_NOTE_PLAYBACK_RATE';
|
||||||
|
|
||||||
|
@ -678,14 +679,6 @@ export type SetIsNearBottomActionType = {
|
||||||
isNearBottom: boolean;
|
isNearBottom: boolean;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export type SetConversationHeaderTitleActionType = {
|
|
||||||
type: 'SET_CONVERSATION_HEADER_TITLE';
|
|
||||||
payload: { title?: string };
|
|
||||||
};
|
|
||||||
export type SetSelectedConversationPanelDepthActionType = {
|
|
||||||
type: 'SET_SELECTED_CONVERSATION_PANEL_DEPTH';
|
|
||||||
payload: { panelDepth: number };
|
|
||||||
};
|
|
||||||
export type ScrollToMessageActionType = {
|
export type ScrollToMessageActionType = {
|
||||||
type: 'SCROLL_TO_MESSAGE';
|
type: 'SCROLL_TO_MESSAGE';
|
||||||
payload: {
|
payload: {
|
||||||
|
@ -781,6 +774,14 @@ export type ToggleConversationInChooseMembersActionType = {
|
||||||
maxGroupSize: number;
|
maxGroupSize: number;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
type PushPanelActionType = {
|
||||||
|
type: typeof PUSH_PANEL;
|
||||||
|
payload: PanelRenderType;
|
||||||
|
};
|
||||||
|
type PopPanelActionType = {
|
||||||
|
type: typeof POP_PANEL;
|
||||||
|
payload: null;
|
||||||
|
};
|
||||||
|
|
||||||
type ReplaceAvatarsActionType = {
|
type ReplaceAvatarsActionType = {
|
||||||
type: typeof REPLACE_AVATARS;
|
type: typeof REPLACE_AVATARS;
|
||||||
|
@ -822,6 +823,8 @@ export type ConversationActionType =
|
||||||
| MessageSelectedActionType
|
| MessageSelectedActionType
|
||||||
| MessagesAddedActionType
|
| MessagesAddedActionType
|
||||||
| MessagesResetActionType
|
| MessagesResetActionType
|
||||||
|
| PopPanelActionType
|
||||||
|
| PushPanelActionType
|
||||||
| RemoveAllConversationsActionType
|
| RemoveAllConversationsActionType
|
||||||
| RepairNewestMessageActionType
|
| RepairNewestMessageActionType
|
||||||
| RepairOldestMessageActionType
|
| RepairOldestMessageActionType
|
||||||
|
@ -834,13 +837,11 @@ export type ConversationActionType =
|
||||||
| SetComposeGroupExpireTimerActionType
|
| SetComposeGroupExpireTimerActionType
|
||||||
| SetComposeGroupNameActionType
|
| SetComposeGroupNameActionType
|
||||||
| SetComposeSearchTermActionType
|
| SetComposeSearchTermActionType
|
||||||
| SetConversationHeaderTitleActionType
|
|
||||||
| SetIsFetchingUUIDActionType
|
| SetIsFetchingUUIDActionType
|
||||||
| SetIsNearBottomActionType
|
| SetIsNearBottomActionType
|
||||||
| SetMessageLoadingStateActionType
|
| SetMessageLoadingStateActionType
|
||||||
| SetPreJoinConversationActionType
|
| SetPreJoinConversationActionType
|
||||||
| SetRecentMediaItemsActionType
|
| SetRecentMediaItemsActionType
|
||||||
| SetSelectedConversationPanelDepthActionType
|
|
||||||
| ShowArchivedConversationsActionType
|
| ShowArchivedConversationsActionType
|
||||||
| ShowChooseGroupMembersActionType
|
| ShowChooseGroupMembersActionType
|
||||||
| ShowInboxActionType
|
| ShowInboxActionType
|
||||||
|
@ -885,14 +886,16 @@ export const actions = {
|
||||||
discardMessages,
|
discardMessages,
|
||||||
doubleCheckMissingQuoteReference,
|
doubleCheckMissingQuoteReference,
|
||||||
generateNewGroupLink,
|
generateNewGroupLink,
|
||||||
loadRecentMediaItems,
|
|
||||||
initiateMigrationToGroupV2,
|
initiateMigrationToGroupV2,
|
||||||
|
loadRecentMediaItems,
|
||||||
messageChanged,
|
messageChanged,
|
||||||
messageDeleted,
|
messageDeleted,
|
||||||
messageExpanded,
|
messageExpanded,
|
||||||
messagesAdded,
|
messagesAdded,
|
||||||
messagesReset,
|
messagesReset,
|
||||||
myProfileChanged,
|
myProfileChanged,
|
||||||
|
popPanelForConversation,
|
||||||
|
pushPanelForConversation,
|
||||||
removeAllConversations,
|
removeAllConversations,
|
||||||
removeCustomColorOnConversations,
|
removeCustomColorOnConversations,
|
||||||
removeMemberFromGroup,
|
removeMemberFromGroup,
|
||||||
|
@ -924,8 +927,6 @@ export const actions = {
|
||||||
setMuteExpiration,
|
setMuteExpiration,
|
||||||
setPinned,
|
setPinned,
|
||||||
setPreJoinConversation,
|
setPreJoinConversation,
|
||||||
setSelectedConversationHeaderTitle,
|
|
||||||
setSelectedConversationPanelDepth,
|
|
||||||
setVoiceNotePlaybackRate,
|
setVoiceNotePlaybackRate,
|
||||||
showArchivedConversations,
|
showArchivedConversations,
|
||||||
showChooseGroupMembers,
|
showChooseGroupMembers,
|
||||||
|
@ -2064,20 +2065,61 @@ function setIsFetchingUUID(
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function setSelectedConversationHeaderTitle(
|
|
||||||
title?: string
|
export type PushPanelForConversationActionType = (
|
||||||
): SetConversationHeaderTitleActionType {
|
conversationId: string,
|
||||||
|
panel: PanelRenderType
|
||||||
|
) => unknown;
|
||||||
|
|
||||||
|
function pushPanelForConversation(
|
||||||
|
conversationId: string,
|
||||||
|
panel: PanelRenderType
|
||||||
|
): PushPanelActionType {
|
||||||
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
|
if (!conversation) {
|
||||||
|
throw new Error(
|
||||||
|
`addPanelToConversation: No conversation found for conversation ${conversationId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
conversation.trigger('pushPanel', panel);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'SET_CONVERSATION_HEADER_TITLE',
|
type: PUSH_PANEL,
|
||||||
payload: { title },
|
payload: panel,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function setSelectedConversationPanelDepth(
|
|
||||||
panelDepth: number
|
function popPanelForConversation(
|
||||||
): SetSelectedConversationPanelDepthActionType {
|
conversationId: string
|
||||||
return {
|
): ThunkAction<void, RootStateType, unknown, PopPanelActionType> {
|
||||||
type: 'SET_SELECTED_CONVERSATION_PANEL_DEPTH',
|
return (dispatch, getState) => {
|
||||||
payload: { panelDepth },
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
|
if (!conversation) {
|
||||||
|
throw new Error(
|
||||||
|
`addPanelToConversation: No conversation found for conversation ${conversationId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { conversations } = getState();
|
||||||
|
const { selectedConversationPanels } = conversations;
|
||||||
|
|
||||||
|
if (!selectedConversationPanels.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const panel = [...selectedConversationPanels].pop();
|
||||||
|
|
||||||
|
if (!panel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
conversation.trigger('popPanel', panel);
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: POP_PANEL,
|
||||||
|
payload: null,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2869,8 +2911,7 @@ export function getEmptyState(): ConversationsStateType {
|
||||||
selectedMessageCounter: 0,
|
selectedMessageCounter: 0,
|
||||||
selectedMessageSource: undefined,
|
selectedMessageSource: undefined,
|
||||||
showArchived: false,
|
showArchived: false,
|
||||||
selectedConversationTitle: '',
|
selectedConversationPanels: [],
|
||||||
selectedConversationPanelDepth: 0,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3375,7 +3416,7 @@ export function reducer(
|
||||||
return {
|
return {
|
||||||
...omit(state, 'contactSpoofingReview'),
|
...omit(state, 'contactSpoofingReview'),
|
||||||
selectedConversationId,
|
selectedConversationId,
|
||||||
selectedConversationPanelDepth: 0,
|
selectedConversationPanels: [],
|
||||||
messagesLookup: omit(state.messagesLookup, messageIds),
|
messagesLookup: omit(state.messagesLookup, messageIds),
|
||||||
messagesByConversation: omit(state.messagesByConversation, [id]),
|
messagesByConversation: omit(state.messagesByConversation, [id]),
|
||||||
};
|
};
|
||||||
|
@ -3423,12 +3464,6 @@ export function reducer(
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (action.type === 'SET_SELECTED_CONVERSATION_PANEL_DEPTH') {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
selectedConversationPanelDepth: action.payload.panelDepth,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (action.type === 'MESSAGE_SELECTED') {
|
if (action.type === 'MESSAGE_SELECTED') {
|
||||||
const { messageId, conversationId } = action.payload;
|
const { messageId, conversationId } = action.payload;
|
||||||
|
|
||||||
|
@ -4180,10 +4215,24 @@ export function reducer(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action.type === 'SET_CONVERSATION_HEADER_TITLE') {
|
if (action.type === PUSH_PANEL) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
selectedConversationTitle: action.payload.title,
|
selectedConversationPanels: [
|
||||||
|
...state.selectedConversationPanels,
|
||||||
|
action.payload,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.type === POP_PANEL) {
|
||||||
|
const { selectedConversationPanels } = state;
|
||||||
|
const nextPanels = [...selectedConversationPanels];
|
||||||
|
nextPanels.pop();
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
selectedConversationPanels: nextPanels,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
|
|
||||||
import type { Store } from 'redux';
|
|
||||||
|
|
||||||
import type { SmartChatColorPickerProps } from '../smart/ChatColorPicker';
|
|
||||||
import { SmartChatColorPicker } from '../smart/ChatColorPicker';
|
|
||||||
|
|
||||||
export const createChatColorPicker = (
|
|
||||||
store: Store,
|
|
||||||
props: SmartChatColorPickerProps
|
|
||||||
): React.ReactElement => (
|
|
||||||
<Provider store={store}>
|
|
||||||
<SmartChatColorPicker {...props} />
|
|
||||||
</Provider>
|
|
||||||
);
|
|
|
@ -64,6 +64,9 @@ import * as log from '../../logging/log';
|
||||||
import { TimelineMessageLoadingState } from '../../util/timelineUtil';
|
import { TimelineMessageLoadingState } from '../../util/timelineUtil';
|
||||||
import { isSignalConversation } from '../../util/isSignalConversation';
|
import { isSignalConversation } from '../../util/isSignalConversation';
|
||||||
import { reduce } from '../../util/iterables';
|
import { reduce } from '../../util/iterables';
|
||||||
|
import { getConversationTitleForPanelType } from '../../util/getConversationTitleForPanelType';
|
||||||
|
import type { ReactPanelRenderType, PanelRenderType } from '../../types/Panels';
|
||||||
|
import { isPanelHandledByReact } from '../../types/Panels';
|
||||||
|
|
||||||
let placeholderContact: ConversationType;
|
let placeholderContact: ConversationType;
|
||||||
export const getPlaceholderContact = (): ConversationType => {
|
export const getPlaceholderContact = (): ConversationType => {
|
||||||
|
@ -1131,3 +1134,34 @@ export const getHideStoryConversationIds = createSelector(
|
||||||
conversationId => conversationLookup[conversationId].hideStory
|
conversationId => conversationLookup[conversationId].hideStory
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const getTopPanel = createSelector(
|
||||||
|
getConversations,
|
||||||
|
(conversations): PanelRenderType | undefined =>
|
||||||
|
conversations.selectedConversationPanels[
|
||||||
|
conversations.selectedConversationPanels.length - 1
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getTopPanelRenderableByReact = createSelector(
|
||||||
|
getConversations,
|
||||||
|
(conversations): ReactPanelRenderType | undefined => {
|
||||||
|
const topPanel =
|
||||||
|
conversations.selectedConversationPanels[
|
||||||
|
conversations.selectedConversationPanels.length - 1
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!isPanelHandledByReact(topPanel)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return topPanel;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getConversationTitle = createSelector(
|
||||||
|
getIntl,
|
||||||
|
getTopPanel,
|
||||||
|
(i18n, panel): string | undefined =>
|
||||||
|
getConversationTitleForPanelType(i18n, panel?.type)
|
||||||
|
);
|
||||||
|
|
|
@ -38,7 +38,6 @@ export type SmartConversationDetailsProps = {
|
||||||
addMembers: (conversationIds: ReadonlyArray<string>) => Promise<void>;
|
addMembers: (conversationIds: ReadonlyArray<string>) => Promise<void>;
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
showAllMedia: () => void;
|
showAllMedia: () => void;
|
||||||
showChatColorEditor: () => void;
|
|
||||||
showGroupLinkManagement: () => void;
|
showGroupLinkManagement: () => void;
|
||||||
showGroupV2Permissions: () => void;
|
showGroupV2Permissions: () => void;
|
||||||
showConversationNotificationsSettings: () => void;
|
showConversationNotificationsSettings: () => void;
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||||
import {
|
import {
|
||||||
getConversationSelector,
|
getConversationSelector,
|
||||||
|
getConversationTitle,
|
||||||
isMissingRequiredProfileSharing,
|
isMissingRequiredProfileSharing,
|
||||||
} from '../selectors/conversations';
|
} from '../selectors/conversations';
|
||||||
import { CallMode } from '../../types/Calling';
|
import { CallMode } from '../../types/Calling';
|
||||||
|
@ -108,14 +109,14 @@ const mapStateToProps = (state: StateType, ownProps: OwnProps) => {
|
||||||
'unblurredAvatarPath',
|
'unblurredAvatarPath',
|
||||||
]),
|
]),
|
||||||
badge: getPreferredBadgeSelector(state)(conversation.badges),
|
badge: getPreferredBadgeSelector(state)(conversation.badges),
|
||||||
conversationTitle: state.conversations.selectedConversationTitle,
|
conversationTitle: getConversationTitle(state),
|
||||||
hasStories,
|
hasStories,
|
||||||
isMissingMandatoryProfileSharing:
|
isMissingMandatoryProfileSharing:
|
||||||
isMissingRequiredProfileSharing(conversation),
|
isMissingRequiredProfileSharing(conversation),
|
||||||
isSMSOnly: isConversationSMSOnly(conversation),
|
isSMSOnly: isConversationSMSOnly(conversation),
|
||||||
isSignalConversation: isSignalConversation(conversation),
|
isSignalConversation: isSignalConversation(conversation),
|
||||||
i18n: getIntl(state),
|
i18n: getIntl(state),
|
||||||
showBackButton: state.conversations.selectedConversationPanelDepth > 0,
|
showBackButton: state.conversations.selectedConversationPanels.length > 0,
|
||||||
outgoingCallButtonStyle: getOutgoingCallButtonStyle(conversation, state),
|
outgoingCallButtonStyle: getOutgoingCallButtonStyle(conversation, state),
|
||||||
theme: getTheme(state),
|
theme: getTheme(state),
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,15 +3,19 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { mapDispatchToProps } from '../actions';
|
|
||||||
import { ConversationView } from '../../components/conversation/ConversationView';
|
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
import type { CompositionAreaPropsType } from './CompositionArea';
|
import type { CompositionAreaPropsType } from './CompositionArea';
|
||||||
import { SmartCompositionArea } from './CompositionArea';
|
|
||||||
import type { OwnProps as ConversationHeaderPropsType } from './ConversationHeader';
|
import type { OwnProps as ConversationHeaderPropsType } from './ConversationHeader';
|
||||||
import { SmartConversationHeader } from './ConversationHeader';
|
import type { StateType } from '../reducer';
|
||||||
import type { TimelinePropsType } from './Timeline';
|
import type { TimelinePropsType } from './Timeline';
|
||||||
|
import * as log from '../../logging/log';
|
||||||
|
import { ConversationView } from '../../components/conversation/ConversationView';
|
||||||
|
import { PanelType } from '../../types/Panels';
|
||||||
|
import { SmartChatColorPicker } from './ChatColorPicker';
|
||||||
|
import { SmartCompositionArea } from './CompositionArea';
|
||||||
|
import { SmartConversationHeader } from './ConversationHeader';
|
||||||
import { SmartTimeline } from './Timeline';
|
import { SmartTimeline } from './Timeline';
|
||||||
|
import { getTopPanelRenderableByReact } from '../selectors/conversations';
|
||||||
|
import { mapDispatchToProps } from '../actions';
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
|
@ -31,7 +35,7 @@ export type PropsType = {
|
||||||
timelineProps: TimelinePropsType;
|
timelineProps: TimelinePropsType;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (_state: StateType, props: PropsType) => {
|
const mapStateToProps = (state: StateType, props: PropsType) => {
|
||||||
const {
|
const {
|
||||||
compositionAreaProps,
|
compositionAreaProps,
|
||||||
conversationHeaderProps,
|
conversationHeaderProps,
|
||||||
|
@ -39,6 +43,8 @@ const mapStateToProps = (_state: StateType, props: PropsType) => {
|
||||||
timelineProps,
|
timelineProps,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const topPanel = getTopPanelRenderableByReact(state);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
conversationId,
|
conversationId,
|
||||||
renderCompositionArea: () => (
|
renderCompositionArea: () => (
|
||||||
|
@ -48,6 +54,24 @@ const mapStateToProps = (_state: StateType, props: PropsType) => {
|
||||||
<SmartConversationHeader {...conversationHeaderProps} />
|
<SmartConversationHeader {...conversationHeaderProps} />
|
||||||
),
|
),
|
||||||
renderTimeline: () => <SmartTimeline {...timelineProps} />,
|
renderTimeline: () => <SmartTimeline {...timelineProps} />,
|
||||||
|
renderPanel: () => {
|
||||||
|
if (!topPanel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (topPanel.type === PanelType.ChatColorEditor) {
|
||||||
|
return (
|
||||||
|
<div className="panel">
|
||||||
|
<SmartChatColorPicker conversationId={conversationId} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const unknownPanelType: never = topPanel.type;
|
||||||
|
log.warn(`renderPanel: Got unexpected panel type ${unknownPanelType}`);
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
54
ts/types/Panels.ts
Normal file
54
ts/types/Panels.ts
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import type { EmbeddedContactType } from './EmbeddedContact';
|
||||||
|
import type { UUIDStringType } from './UUID';
|
||||||
|
|
||||||
|
export enum PanelType {
|
||||||
|
AllMedia = 'AllMedia',
|
||||||
|
ChatColorEditor = 'ChatColorEditor',
|
||||||
|
ContactDetails = 'ContactDetails',
|
||||||
|
ConversationDetails = 'ConversationDetails',
|
||||||
|
GroupInvites = 'GroupInvites',
|
||||||
|
GroupLinkManagement = 'GroupLinkManagement',
|
||||||
|
GroupPermissions = 'GroupPermissions',
|
||||||
|
GroupV1Members = 'GroupV1Members',
|
||||||
|
MessageDetails = 'MessageDetails',
|
||||||
|
NotificationSettings = 'NotificationSettings',
|
||||||
|
StickerManager = 'StickerManager',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ReactPanelRenderType = { type: PanelType.ChatColorEditor };
|
||||||
|
|
||||||
|
export type BackbonePanelRenderType =
|
||||||
|
| { type: PanelType.AllMedia }
|
||||||
|
| {
|
||||||
|
type: PanelType.ContactDetails;
|
||||||
|
args: {
|
||||||
|
contact: EmbeddedContactType;
|
||||||
|
signalAccount?: {
|
||||||
|
phoneNumber: string;
|
||||||
|
uuid: UUIDStringType;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| { type: PanelType.ConversationDetails }
|
||||||
|
| { type: PanelType.GroupInvites }
|
||||||
|
| { type: PanelType.GroupLinkManagement }
|
||||||
|
| { type: PanelType.GroupPermissions }
|
||||||
|
| { type: PanelType.GroupV1Members }
|
||||||
|
| { type: PanelType.MessageDetails; args: { messageId: string } }
|
||||||
|
| { type: PanelType.NotificationSettings }
|
||||||
|
| { type: PanelType.StickerManager };
|
||||||
|
|
||||||
|
export type PanelRenderType = ReactPanelRenderType | BackbonePanelRenderType;
|
||||||
|
|
||||||
|
export function isPanelHandledByReact(
|
||||||
|
panel: PanelRenderType
|
||||||
|
): panel is ReactPanelRenderType {
|
||||||
|
if (!panel) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return panel.type === PanelType.ChatColorEditor;
|
||||||
|
}
|
59
ts/util/getConversationTitleForPanelType.ts
Normal file
59
ts/util/getConversationTitleForPanelType.ts
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import type { LocalizerType } from '../types/Util';
|
||||||
|
import * as log from '../logging/log';
|
||||||
|
import { PanelType } from '../types/Panels';
|
||||||
|
|
||||||
|
export function getConversationTitleForPanelType(
|
||||||
|
i18n: LocalizerType,
|
||||||
|
panelType: PanelType | undefined
|
||||||
|
): string | undefined {
|
||||||
|
if (!panelType) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (panelType === PanelType.AllMedia) {
|
||||||
|
return i18n('allMedia');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (panelType === PanelType.ChatColorEditor) {
|
||||||
|
return i18n('ChatColorPicker__menu-title');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (panelType === PanelType.ConversationDetails) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (panelType === PanelType.GroupInvites) {
|
||||||
|
return i18n('ConversationDetails--requests-and-invites');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (panelType === PanelType.GroupLinkManagement) {
|
||||||
|
return i18n('ConversationDetails--group-link');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (panelType === PanelType.GroupPermissions) {
|
||||||
|
return i18n('permissions');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (panelType === PanelType.NotificationSettings) {
|
||||||
|
return i18n('ConversationDetails--notifications');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
panelType === PanelType.ContactDetails ||
|
||||||
|
panelType === PanelType.GroupV1Members ||
|
||||||
|
panelType === PanelType.MessageDetails ||
|
||||||
|
panelType === PanelType.StickerManager
|
||||||
|
) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const unknownType: never = panelType;
|
||||||
|
log.warn(
|
||||||
|
`getConversationTitleForPanelType: Got unexpected type ${unknownType}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
|
@ -56,16 +56,18 @@ import { SECOND } from '../util/durations';
|
||||||
import { startConversation } from '../util/startConversation';
|
import { startConversation } from '../util/startConversation';
|
||||||
import { longRunningTaskWrapper } from '../util/longRunningTaskWrapper';
|
import { longRunningTaskWrapper } from '../util/longRunningTaskWrapper';
|
||||||
import { hasDraftAttachments } from '../util/hasDraftAttachments';
|
import { hasDraftAttachments } from '../util/hasDraftAttachments';
|
||||||
|
import type { BackbonePanelRenderType, PanelRenderType } from '../types/Panels';
|
||||||
|
import { PanelType, isPanelHandledByReact } from '../types/Panels';
|
||||||
|
|
||||||
type AttachmentOptions = {
|
type AttachmentOptions = {
|
||||||
messageId: string;
|
messageId: string;
|
||||||
attachment: AttachmentType;
|
attachment: AttachmentType;
|
||||||
};
|
};
|
||||||
|
|
||||||
type PanelType = { view: Backbone.View; headerTitle?: string };
|
|
||||||
|
|
||||||
const { Message } = window.Signal.Types;
|
const { Message } = window.Signal.Types;
|
||||||
|
|
||||||
|
type BackbonePanelType = { panelType: PanelType; view: Backbone.View };
|
||||||
|
|
||||||
const { getAbsoluteAttachmentPath, upgradeMessageSchema } =
|
const { getAbsoluteAttachmentPath, upgradeMessageSchema } =
|
||||||
window.Signal.Migrations;
|
window.Signal.Migrations;
|
||||||
|
|
||||||
|
@ -125,7 +127,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
private stickerPreviewModalView?: Backbone.View;
|
private stickerPreviewModalView?: Backbone.View;
|
||||||
|
|
||||||
// Panel support
|
// Panel support
|
||||||
private panels: Array<PanelType> = [];
|
private panels: Array<BackbonePanelType> = [];
|
||||||
private previousFocus?: HTMLElement;
|
private previousFocus?: HTMLElement;
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
@ -143,7 +145,9 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
|
|
||||||
// These are triggered by background.ts for keyboard handling
|
// These are triggered by background.ts for keyboard handling
|
||||||
this.listenTo(this.model, 'open-all-media', this.showAllMedia);
|
this.listenTo(this.model, 'open-all-media', this.showAllMedia);
|
||||||
this.listenTo(this.model, 'escape-pressed', this.resetPanel);
|
this.listenTo(this.model, 'escape-pressed', () => {
|
||||||
|
window.reduxActions.conversations.popPanelForConversation(this.model.id);
|
||||||
|
});
|
||||||
this.listenTo(this.model, 'show-message-details', this.showMessageDetail);
|
this.listenTo(this.model, 'show-message-details', this.showMessageDetail);
|
||||||
this.listenTo(this.model, 'delete-message', this.deleteMessage);
|
this.listenTo(this.model, 'delete-message', this.deleteMessage);
|
||||||
this.listenTo(this.model, 'remove-link-review', removeLinkPreview);
|
this.listenTo(this.model, 'remove-link-review', removeLinkPreview);
|
||||||
|
@ -157,6 +161,9 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
|
|
||||||
this.setupConversationView();
|
this.setupConversationView();
|
||||||
this.updateAttachmentsView();
|
this.updateAttachmentsView();
|
||||||
|
|
||||||
|
this.listenTo(this.model, 'pushPanel', this.pushPanel);
|
||||||
|
this.listenTo(this.model, 'popPanel', this.popPanel);
|
||||||
}
|
}
|
||||||
|
|
||||||
override events(): Record<string, string> {
|
override events(): Record<string, string> {
|
||||||
|
@ -212,7 +219,9 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
this.showGV1Members();
|
this.showGV1Members();
|
||||||
},
|
},
|
||||||
onGoBack: () => {
|
onGoBack: () => {
|
||||||
this.resetPanel();
|
window.reduxActions.conversations.popPanelForConversation(
|
||||||
|
this.model.id
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
onArchive: () => {
|
onArchive: () => {
|
||||||
|
@ -237,7 +246,6 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
showToast(ToastConversationUnarchived);
|
showToast(ToastConversationUnarchived);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
window.reduxActions.conversations.setSelectedConversationHeaderTitle();
|
|
||||||
|
|
||||||
// setupTimeline
|
// setupTimeline
|
||||||
|
|
||||||
|
@ -544,7 +552,6 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
const panel = this.panels[i];
|
const panel = this.panels[i];
|
||||||
panel.view.remove();
|
panel.view.remove();
|
||||||
}
|
}
|
||||||
window.reduxActions.conversations.setSelectedConversationPanelDepth(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
removeLinkPreview();
|
removeLinkPreview();
|
||||||
|
@ -624,6 +631,12 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
}
|
}
|
||||||
|
|
||||||
showAllMedia(): void {
|
showAllMedia(): void {
|
||||||
|
window.reduxActions.conversations.pushPanelForConversation(this.model.id, {
|
||||||
|
type: PanelType.AllMedia,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllMedia(): Backbone.View | undefined {
|
||||||
if (document.querySelectorAll('.module-media-gallery').length) {
|
if (document.querySelectorAll('.module-media-gallery').length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -807,19 +820,24 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const headerTitle = window.i18n('allMedia');
|
|
||||||
|
|
||||||
const update = async () => {
|
const update = async () => {
|
||||||
const props = await getProps();
|
const props = await getProps();
|
||||||
view.update(<MediaGallery i18n={window.i18n} {...props} />);
|
view.update(<MediaGallery i18n={window.i18n} {...props} />);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.addPanel({ view, headerTitle });
|
|
||||||
|
|
||||||
update();
|
update();
|
||||||
|
|
||||||
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
showGV1Members(): void {
|
showGV1Members(): void {
|
||||||
|
window.reduxActions.conversations.pushPanelForConversation(this.model.id, {
|
||||||
|
type: PanelType.GroupV1Members,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getGV1Members(): Backbone.View {
|
||||||
const { contactCollection, id } = this.model;
|
const { contactCollection, id } = this.model;
|
||||||
|
|
||||||
const memberships =
|
const memberships =
|
||||||
|
@ -855,8 +873,9 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addPanel({ view });
|
|
||||||
view.render();
|
view.render();
|
||||||
|
|
||||||
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteMessage(messageId: string): void {
|
deleteMessage(messageId: string): void {
|
||||||
|
@ -877,12 +896,20 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
} else {
|
} else {
|
||||||
this.model.decrementMessageCount();
|
this.model.decrementMessageCount();
|
||||||
}
|
}
|
||||||
this.resetPanel();
|
window.reduxActions.conversations.popPanelForConversation(
|
||||||
|
this.model.id
|
||||||
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
showGroupLinkManagement(): void {
|
showGroupLinkManagement(): void {
|
||||||
|
window.reduxActions.conversations.pushPanelForConversation(this.model.id, {
|
||||||
|
type: PanelType.GroupLinkManagement,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getGroupLinkManagement(): Backbone.View {
|
||||||
const view = new ReactWrapperView({
|
const view = new ReactWrapperView({
|
||||||
className: 'panel',
|
className: 'panel',
|
||||||
JSX: window.Signal.State.Roots.createGroupLinkManagement(
|
JSX: window.Signal.State.Roots.createGroupLinkManagement(
|
||||||
|
@ -892,13 +919,19 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
const headerTitle = window.i18n('ConversationDetails--group-link');
|
|
||||||
|
|
||||||
this.addPanel({ view, headerTitle });
|
|
||||||
view.render();
|
view.render();
|
||||||
|
|
||||||
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
showGroupV2Permissions(): void {
|
showGroupV2Permissions(): void {
|
||||||
|
window.reduxActions.conversations.pushPanelForConversation(this.model.id, {
|
||||||
|
type: PanelType.GroupPermissions,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getGroupV2Permissions(): Backbone.View {
|
||||||
const view = new ReactWrapperView({
|
const view = new ReactWrapperView({
|
||||||
className: 'panel',
|
className: 'panel',
|
||||||
JSX: window.Signal.State.Roots.createGroupV2Permissions(
|
JSX: window.Signal.State.Roots.createGroupV2Permissions(
|
||||||
|
@ -908,13 +941,19 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
const headerTitle = window.i18n('permissions');
|
|
||||||
|
|
||||||
this.addPanel({ view, headerTitle });
|
|
||||||
view.render();
|
view.render();
|
||||||
|
|
||||||
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
showPendingInvites(): void {
|
showPendingInvites(): void {
|
||||||
|
window.reduxActions.conversations.pushPanelForConversation(this.model.id, {
|
||||||
|
type: PanelType.GroupInvites,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getPendingInvites(): Backbone.View {
|
||||||
const view = new ReactWrapperView({
|
const view = new ReactWrapperView({
|
||||||
className: 'panel',
|
className: 'panel',
|
||||||
JSX: window.Signal.State.Roots.createPendingInvites(window.reduxStore, {
|
JSX: window.Signal.State.Roots.createPendingInvites(window.reduxStore, {
|
||||||
|
@ -922,15 +961,19 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
ourUuid: window.textsecure.storage.user.getCheckedUuid().toString(),
|
ourUuid: window.textsecure.storage.user.getCheckedUuid().toString(),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
const headerTitle = window.i18n(
|
|
||||||
'ConversationDetails--requests-and-invites'
|
|
||||||
);
|
|
||||||
|
|
||||||
this.addPanel({ view, headerTitle });
|
|
||||||
view.render();
|
view.render();
|
||||||
|
|
||||||
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
showConversationNotificationsSettings(): void {
|
showConversationNotificationsSettings(): void {
|
||||||
|
window.reduxActions.conversations.pushPanelForConversation(this.model.id, {
|
||||||
|
type: PanelType.NotificationSettings,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getConversationNotificationsSettings(): Backbone.View {
|
||||||
const view = new ReactWrapperView({
|
const view = new ReactWrapperView({
|
||||||
className: 'panel',
|
className: 'panel',
|
||||||
JSX: window.Signal.State.Roots.createConversationNotificationsSettings(
|
JSX: window.Signal.State.Roots.createConversationNotificationsSettings(
|
||||||
|
@ -940,26 +983,19 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
const headerTitle = window.i18n('ConversationDetails--notifications');
|
|
||||||
|
|
||||||
this.addPanel({ view, headerTitle });
|
|
||||||
view.render();
|
view.render();
|
||||||
}
|
|
||||||
|
|
||||||
showChatColorEditor(): void {
|
return view;
|
||||||
const view = new ReactWrapperView({
|
|
||||||
className: 'panel',
|
|
||||||
JSX: window.Signal.State.Roots.createChatColorPicker(window.reduxStore, {
|
|
||||||
conversationId: this.model.get('id'),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
const headerTitle = window.i18n('ChatColorPicker__menu-title');
|
|
||||||
|
|
||||||
this.addPanel({ view, headerTitle });
|
|
||||||
view.render();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
showConversationDetails(): void {
|
showConversationDetails(): void {
|
||||||
|
window.reduxActions.conversations.pushPanelForConversation(this.model.id, {
|
||||||
|
type: PanelType.ConversationDetails,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getConversationDetails(): Backbone.View {
|
||||||
// Run a getProfiles in case member's capabilities have changed
|
// Run a getProfiles in case member's capabilities have changed
|
||||||
// Redux should cover us on the return here so no need to await this.
|
// Redux should cover us on the return here so no need to await this.
|
||||||
if (this.model.throttledGetProfiles) {
|
if (this.model.throttledGetProfiles) {
|
||||||
|
@ -981,7 +1017,6 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
addMembers: this.model.addMembersV2.bind(this.model),
|
addMembers: this.model.addMembersV2.bind(this.model),
|
||||||
conversationId: this.model.get('id'),
|
conversationId: this.model.get('id'),
|
||||||
showAllMedia: this.showAllMedia.bind(this),
|
showAllMedia: this.showAllMedia.bind(this),
|
||||||
showChatColorEditor: this.showChatColorEditor.bind(this),
|
|
||||||
showGroupLinkManagement: this.showGroupLinkManagement.bind(this),
|
showGroupLinkManagement: this.showGroupLinkManagement.bind(this),
|
||||||
showGroupV2Permissions: this.showGroupV2Permissions.bind(this),
|
showGroupV2Permissions: this.showGroupV2Permissions.bind(this),
|
||||||
showConversationNotificationsSettings:
|
showConversationNotificationsSettings:
|
||||||
|
@ -1000,13 +1035,24 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
props
|
props
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
const headerTitle = '';
|
|
||||||
|
|
||||||
this.addPanel({ view, headerTitle });
|
|
||||||
view.render();
|
view.render();
|
||||||
|
|
||||||
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
showMessageDetail(messageId: string): void {
|
showMessageDetail(messageId: string): void {
|
||||||
|
window.reduxActions.conversations.pushPanelForConversation(this.model.id, {
|
||||||
|
type: PanelType.MessageDetails,
|
||||||
|
args: { messageId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getMessageDetail({
|
||||||
|
messageId,
|
||||||
|
}: {
|
||||||
|
messageId: string;
|
||||||
|
}): Backbone.View | undefined {
|
||||||
const message = window.MessageController.getById(messageId);
|
const message = window.MessageController.getById(messageId);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
throw new Error(`showMessageDetail: Message ${messageId} missing!`);
|
throw new Error(`showMessageDetail: Message ${messageId} missing!`);
|
||||||
|
@ -1025,7 +1071,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
|
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
this.stopListening(message, 'change', update);
|
this.stopListening(message, 'change', update);
|
||||||
this.resetPanel();
|
window.reduxActions.conversations.popPanelForConversation(this.model.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const view = new ReactWrapperView({
|
const view = new ReactWrapperView({
|
||||||
|
@ -1048,21 +1094,31 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
this.listenTo(message, 'expired', onClose);
|
this.listenTo(message, 'expired', onClose);
|
||||||
// We could listen to all involved contacts, but we'll call that overkill
|
// We could listen to all involved contacts, but we'll call that overkill
|
||||||
|
|
||||||
this.addPanel({ view });
|
|
||||||
view.render();
|
view.render();
|
||||||
|
|
||||||
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
showStickerManager(): void {
|
showStickerManager(): void {
|
||||||
|
window.reduxActions.conversations.pushPanelForConversation(this.model.id, {
|
||||||
|
type: PanelType.StickerManager,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getStickerManager(): Backbone.View {
|
||||||
const view = new ReactWrapperView({
|
const view = new ReactWrapperView({
|
||||||
className: ['sticker-manager-wrapper', 'panel'].join(' '),
|
className: ['sticker-manager-wrapper', 'panel'].join(' '),
|
||||||
JSX: window.Signal.State.Roots.createStickerManager(window.reduxStore),
|
JSX: window.Signal.State.Roots.createStickerManager(window.reduxStore),
|
||||||
onClose: () => {
|
onClose: () => {
|
||||||
this.resetPanel();
|
window.reduxActions.conversations.popPanelForConversation(
|
||||||
|
this.model.id
|
||||||
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addPanel({ view });
|
|
||||||
view.render();
|
view.render();
|
||||||
|
|
||||||
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
showContactDetail({
|
showContactDetail({
|
||||||
|
@ -1075,6 +1131,22 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
uuid: UUIDStringType;
|
uuid: UUIDStringType;
|
||||||
};
|
};
|
||||||
}): void {
|
}): void {
|
||||||
|
window.reduxActions.conversations.pushPanelForConversation(this.model.id, {
|
||||||
|
type: PanelType.ContactDetails,
|
||||||
|
args: { contact, signalAccount },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getContactDetail({
|
||||||
|
contact,
|
||||||
|
signalAccount,
|
||||||
|
}: {
|
||||||
|
contact: EmbeddedContactType;
|
||||||
|
signalAccount?: {
|
||||||
|
phoneNumber: string;
|
||||||
|
uuid: UUIDStringType;
|
||||||
|
};
|
||||||
|
}): Backbone.View {
|
||||||
const view = new ReactWrapperView({
|
const view = new ReactWrapperView({
|
||||||
className: 'contact-detail-pane panel',
|
className: 'contact-detail-pane panel',
|
||||||
JSX: (
|
JSX: (
|
||||||
|
@ -1090,11 +1162,13 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
onClose: () => {
|
onClose: () => {
|
||||||
this.resetPanel();
|
window.reduxActions.conversations.popPanelForConversation(
|
||||||
|
this.model.id
|
||||||
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addPanel({ view });
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
async openConversation(
|
async openConversation(
|
||||||
|
@ -1108,32 +1182,63 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
addPanel(panel: PanelType): void {
|
pushPanel(panel: PanelRenderType): void {
|
||||||
|
if (isPanelHandledByReact(panel)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.panels = this.panels || [];
|
this.panels = this.panels || [];
|
||||||
|
|
||||||
if (this.panels.length === 0) {
|
if (this.panels.length === 0) {
|
||||||
this.previousFocus = document.activeElement as HTMLElement;
|
this.previousFocus = document.activeElement as HTMLElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.panels.unshift(panel);
|
const { type } = panel as BackbonePanelRenderType;
|
||||||
panel.view.$el.insertAfter(this.$('.panel').last());
|
|
||||||
panel.view.$el.one('animationend', () => {
|
|
||||||
panel.view.$el.addClass('panel--static');
|
|
||||||
});
|
|
||||||
|
|
||||||
window.reduxActions.conversations.setSelectedConversationPanelDepth(
|
let view: Backbone.View | undefined;
|
||||||
this.panels.length
|
if (type === PanelType.AllMedia) {
|
||||||
);
|
view = this.getAllMedia();
|
||||||
window.reduxActions.conversations.setSelectedConversationHeaderTitle(
|
} else if (panel.type === PanelType.ContactDetails) {
|
||||||
panel.headerTitle
|
view = this.getContactDetail(panel.args);
|
||||||
);
|
} else if (type === PanelType.ConversationDetails) {
|
||||||
}
|
view = this.getConversationDetails();
|
||||||
resetPanel(): void {
|
} else if (type === PanelType.GroupInvites) {
|
||||||
if (!this.panels || !this.panels.length) {
|
view = this.getPendingInvites();
|
||||||
|
} else if (type === PanelType.GroupLinkManagement) {
|
||||||
|
view = this.getGroupLinkManagement();
|
||||||
|
} else if (type === PanelType.GroupPermissions) {
|
||||||
|
view = this.getGroupV2Permissions();
|
||||||
|
} else if (type === PanelType.GroupV1Members) {
|
||||||
|
view = this.getGV1Members();
|
||||||
|
} else if (type === PanelType.NotificationSettings) {
|
||||||
|
view = this.getConversationNotificationsSettings();
|
||||||
|
} else if (panel.type === PanelType.MessageDetails) {
|
||||||
|
view = this.getMessageDetail(panel.args);
|
||||||
|
} else if (type === PanelType.StickerManager) {
|
||||||
|
view = this.getStickerManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!view) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const panel = this.panels.shift();
|
this.panels.push({
|
||||||
|
panelType: type,
|
||||||
|
view,
|
||||||
|
});
|
||||||
|
|
||||||
|
view.$el.insertAfter(this.$('.panel').last());
|
||||||
|
view.$el.one('animationend', () => {
|
||||||
|
if (view) {
|
||||||
|
view.$el.addClass('panel--static');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
popPanel(poppedPanel: PanelRenderType): void {
|
||||||
|
if (!this.panels || !this.panels.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.panels.length === 0 &&
|
this.panels.length === 0 &&
|
||||||
|
@ -1144,36 +1249,42 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
this.previousFocus = undefined;
|
this.previousFocus = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const panel = this.panels[this.panels.length - 1];
|
||||||
|
|
||||||
|
if (!panel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPanelHandledByReact(poppedPanel)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.panels.pop();
|
||||||
|
|
||||||
|
if (panel.panelType !== poppedPanel.type) {
|
||||||
|
log.warn('popPanel: last panel was not of same type');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.panels.length > 0) {
|
if (this.panels.length > 0) {
|
||||||
this.panels[0].view.$el.fadeIn(250);
|
this.panels[this.panels.length - 1].view.$el.fadeIn(250);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (panel) {
|
let timeout: ReturnType<typeof setTimeout> | undefined;
|
||||||
let timeout: ReturnType<typeof setTimeout> | undefined;
|
const removePanel = () => {
|
||||||
const removePanel = () => {
|
if (!timeout) {
|
||||||
if (!timeout) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
timeout = undefined;
|
timeout = undefined;
|
||||||
|
|
||||||
panel.view.remove();
|
panel.view.remove();
|
||||||
};
|
};
|
||||||
panel.view.$el
|
panel.view.$el.addClass('panel--remove').one('transitionend', removePanel);
|
||||||
.addClass('panel--remove')
|
|
||||||
.one('transitionend', removePanel);
|
|
||||||
|
|
||||||
// Backup, in case things go wrong with the transitionend event
|
// Backup, in case things go wrong with the transitionend event
|
||||||
timeout = setTimeout(removePanel, SECOND);
|
timeout = setTimeout(removePanel, SECOND);
|
||||||
}
|
|
||||||
|
|
||||||
window.reduxActions.conversations.setSelectedConversationPanelDepth(
|
|
||||||
this.panels.length
|
|
||||||
);
|
|
||||||
window.reduxActions.conversations.setSelectedConversationHeaderTitle(
|
|
||||||
this.panels[0]?.headerTitle
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async clearAttachments(): Promise<void> {
|
async clearAttachments(): Promise<void> {
|
||||||
|
|
2
ts/window.d.ts
vendored
2
ts/window.d.ts
vendored
|
@ -37,7 +37,6 @@ import type { ConversationController } from './ConversationController';
|
||||||
import type { ReduxActions } from './state/types';
|
import type { ReduxActions } from './state/types';
|
||||||
import type { createStore } from './state/createStore';
|
import type { createStore } from './state/createStore';
|
||||||
import type { createApp } from './state/roots/createApp';
|
import type { createApp } from './state/roots/createApp';
|
||||||
import type { createChatColorPicker } from './state/roots/createChatColorPicker';
|
|
||||||
import type { createConversationDetails } from './state/roots/createConversationDetails';
|
import type { createConversationDetails } from './state/roots/createConversationDetails';
|
||||||
import type { createGroupLinkManagement } from './state/roots/createGroupLinkManagement';
|
import type { createGroupLinkManagement } from './state/roots/createGroupLinkManagement';
|
||||||
import type { createGroupV2JoinModal } from './state/roots/createGroupV2JoinModal';
|
import type { createGroupV2JoinModal } from './state/roots/createGroupV2JoinModal';
|
||||||
|
@ -167,7 +166,6 @@ export type SignalCoreType = {
|
||||||
createStore: typeof createStore;
|
createStore: typeof createStore;
|
||||||
Roots: {
|
Roots: {
|
||||||
createApp: typeof createApp;
|
createApp: typeof createApp;
|
||||||
createChatColorPicker: typeof createChatColorPicker;
|
|
||||||
createConversationDetails: typeof createConversationDetails;
|
createConversationDetails: typeof createConversationDetails;
|
||||||
createGroupLinkManagement: typeof createGroupLinkManagement;
|
createGroupLinkManagement: typeof createGroupLinkManagement;
|
||||||
createGroupV2JoinModal: typeof createGroupV2JoinModal;
|
createGroupV2JoinModal: typeof createGroupV2JoinModal;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue