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;
|
||||
renderConversationHeader: () => JSX.Element;
|
||||
renderTimeline: () => JSX.Element;
|
||||
renderPanel: () => JSX.Element | undefined;
|
||||
};
|
||||
|
||||
export function ConversationView({
|
||||
|
@ -20,6 +21,7 @@ export function ConversationView({
|
|||
renderCompositionArea,
|
||||
renderConversationHeader,
|
||||
renderTimeline,
|
||||
renderPanel,
|
||||
}: PropsType): JSX.Element {
|
||||
const onDrop = React.useCallback(
|
||||
(event: React.DragEvent<HTMLDivElement>) => {
|
||||
|
@ -93,6 +95,7 @@ export function ConversationView({
|
|||
{renderCompositionArea()}
|
||||
</div>
|
||||
</div>
|
||||
{renderPanel()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@ const createProps = (
|
|||
setDisappearingMessages: action('setDisappearingMessages'),
|
||||
showAllMedia: action('showAllMedia'),
|
||||
showContactModal: action('showContactModal'),
|
||||
showChatColorEditor: action('showChatColorEditor'),
|
||||
pushPanelForConversation: action('pushPanelForConversation'),
|
||||
showGroupLinkManagement: action('showGroupLinkManagement'),
|
||||
showGroupV2Permissions: action('showGroupV2Permissions'),
|
||||
showConversationNotificationsSettings: action(
|
||||
|
|
|
@ -8,6 +8,7 @@ import { Button, ButtonIconType, ButtonVariant } from '../../Button';
|
|||
import { Tooltip } from '../../Tooltip';
|
||||
import type {
|
||||
ConversationType,
|
||||
PushPanelForConversationActionType,
|
||||
ShowConversationType,
|
||||
} from '../../../state/ducks/conversations';
|
||||
import type { PreferredBadgeSelectorType } from '../../../state/selectors/badges';
|
||||
|
@ -50,6 +51,7 @@ import type {
|
|||
} from '../../../types/Avatar';
|
||||
import { isConversationMuted } from '../../../util/isConversationMuted';
|
||||
import { ConversationDetailsGroups } from './ConversationDetailsGroups';
|
||||
import { PanelType } from '../../../types/Panels';
|
||||
|
||||
enum ModalState {
|
||||
NothingOpen,
|
||||
|
@ -80,7 +82,6 @@ export type StateProps = {
|
|||
pendingApprovalMemberships: ReadonlyArray<GroupV2RequestingMembership>;
|
||||
pendingMemberships: ReadonlyArray<GroupV2PendingMembership>;
|
||||
showAllMedia: () => void;
|
||||
showChatColorEditor: () => void;
|
||||
showGroupLinkManagement: () => void;
|
||||
showGroupV2Permissions: () => void;
|
||||
showPendingInvites: () => void;
|
||||
|
@ -110,6 +111,7 @@ type ActionProps = {
|
|||
loadRecentMediaItems: (id: string, limit: number) => void;
|
||||
onOutgoingAudioCallInConversation: (conversationId: string) => unknown;
|
||||
onOutgoingVideoCallInConversation: (conversationId: string) => unknown;
|
||||
pushPanelForConversation: PushPanelForConversationActionType;
|
||||
replaceAvatar: ReplaceAvatarActionType;
|
||||
saveAvatarToDisk: SaveAvatarToDiskActionType;
|
||||
searchInConversation: (id: string) => unknown;
|
||||
|
@ -149,6 +151,7 @@ export function ConversationDetails({
|
|||
onOutgoingVideoCallInConversation,
|
||||
pendingApprovalMemberships,
|
||||
pendingMemberships,
|
||||
pushPanelForConversation,
|
||||
renderChooseGroupMembersModal,
|
||||
renderConfirmAdditionsModal,
|
||||
replaceAvatar,
|
||||
|
@ -157,7 +160,6 @@ export function ConversationDetails({
|
|||
setDisappearingMessages,
|
||||
setMuteExpiration,
|
||||
showAllMedia,
|
||||
showChatColorEditor,
|
||||
showContactModal,
|
||||
showConversationNotificationsSettings,
|
||||
showConversation,
|
||||
|
@ -426,7 +428,11 @@ export function ConversationDetails({
|
|||
/>
|
||||
}
|
||||
label={i18n('showChatColorEditor')}
|
||||
onClick={showChatColorEditor}
|
||||
onClick={() => {
|
||||
pushPanelForConversation(conversation.id, {
|
||||
type: PanelType.ChatColorEditor,
|
||||
});
|
||||
}}
|
||||
right={
|
||||
<div
|
||||
className={`ConversationDetails__chat-color ConversationDetails__chat-color--${conversation.conversationColor}`}
|
||||
|
|
|
@ -24,7 +24,6 @@ import { StagedLinkPreview } from './components/conversation/StagedLinkPreview';
|
|||
import { DisappearingTimeDialog } from './components/DisappearingTimeDialog';
|
||||
|
||||
// State
|
||||
import { createChatColorPicker } from './state/roots/createChatColorPicker';
|
||||
import { createConversationDetails } from './state/roots/createConversationDetails';
|
||||
import { createApp } from './state/roots/createApp';
|
||||
import { createGroupLinkManagement } from './state/roots/createGroupLinkManagement';
|
||||
|
@ -401,7 +400,6 @@ export const setup = (options: {
|
|||
|
||||
const Roots = {
|
||||
createApp,
|
||||
createChatColorPicker,
|
||||
createConversationDetails,
|
||||
createGroupLinkManagement,
|
||||
createGroupV2JoinModal,
|
||||
|
|
|
@ -117,6 +117,7 @@ import {
|
|||
initiateMigrationToGroupV2 as doInitiateMigrationToGroupV2,
|
||||
} from '../../groups';
|
||||
import { getMessageById } from '../../messages/getMessageById';
|
||||
import type { PanelRenderType } from '../../types/Panels';
|
||||
|
||||
// State
|
||||
|
||||
|
@ -392,8 +393,7 @@ export type ConversationsStateType = {
|
|||
selectedMessage: string | undefined;
|
||||
selectedMessageCounter: number;
|
||||
selectedMessageSource: SelectedMessageSource | undefined;
|
||||
selectedConversationTitle?: string;
|
||||
selectedConversationPanelDepth: number;
|
||||
selectedConversationPanels: Array<PanelRenderType>;
|
||||
showArchived: boolean;
|
||||
composer?: ComposerStateType;
|
||||
contactSpoofingReview?: ContactSpoofingReviewStateType;
|
||||
|
@ -457,7 +457,8 @@ const DISCARD_MESSAGES = 'conversations/DISCARD_MESSAGES';
|
|||
const REPLACE_AVATARS = 'conversations/REPLACE_AVATARS';
|
||||
export const 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 =
|
||||
'conversations/SET_VOICE_NOTE_PLAYBACK_RATE';
|
||||
|
||||
|
@ -678,14 +679,6 @@ export type SetIsNearBottomActionType = {
|
|||
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 = {
|
||||
type: 'SCROLL_TO_MESSAGE';
|
||||
payload: {
|
||||
|
@ -781,6 +774,14 @@ export type ToggleConversationInChooseMembersActionType = {
|
|||
maxGroupSize: number;
|
||||
};
|
||||
};
|
||||
type PushPanelActionType = {
|
||||
type: typeof PUSH_PANEL;
|
||||
payload: PanelRenderType;
|
||||
};
|
||||
type PopPanelActionType = {
|
||||
type: typeof POP_PANEL;
|
||||
payload: null;
|
||||
};
|
||||
|
||||
type ReplaceAvatarsActionType = {
|
||||
type: typeof REPLACE_AVATARS;
|
||||
|
@ -822,6 +823,8 @@ export type ConversationActionType =
|
|||
| MessageSelectedActionType
|
||||
| MessagesAddedActionType
|
||||
| MessagesResetActionType
|
||||
| PopPanelActionType
|
||||
| PushPanelActionType
|
||||
| RemoveAllConversationsActionType
|
||||
| RepairNewestMessageActionType
|
||||
| RepairOldestMessageActionType
|
||||
|
@ -834,13 +837,11 @@ export type ConversationActionType =
|
|||
| SetComposeGroupExpireTimerActionType
|
||||
| SetComposeGroupNameActionType
|
||||
| SetComposeSearchTermActionType
|
||||
| SetConversationHeaderTitleActionType
|
||||
| SetIsFetchingUUIDActionType
|
||||
| SetIsNearBottomActionType
|
||||
| SetMessageLoadingStateActionType
|
||||
| SetPreJoinConversationActionType
|
||||
| SetRecentMediaItemsActionType
|
||||
| SetSelectedConversationPanelDepthActionType
|
||||
| ShowArchivedConversationsActionType
|
||||
| ShowChooseGroupMembersActionType
|
||||
| ShowInboxActionType
|
||||
|
@ -885,14 +886,16 @@ export const actions = {
|
|||
discardMessages,
|
||||
doubleCheckMissingQuoteReference,
|
||||
generateNewGroupLink,
|
||||
loadRecentMediaItems,
|
||||
initiateMigrationToGroupV2,
|
||||
loadRecentMediaItems,
|
||||
messageChanged,
|
||||
messageDeleted,
|
||||
messageExpanded,
|
||||
messagesAdded,
|
||||
messagesReset,
|
||||
myProfileChanged,
|
||||
popPanelForConversation,
|
||||
pushPanelForConversation,
|
||||
removeAllConversations,
|
||||
removeCustomColorOnConversations,
|
||||
removeMemberFromGroup,
|
||||
|
@ -924,8 +927,6 @@ export const actions = {
|
|||
setMuteExpiration,
|
||||
setPinned,
|
||||
setPreJoinConversation,
|
||||
setSelectedConversationHeaderTitle,
|
||||
setSelectedConversationPanelDepth,
|
||||
setVoiceNotePlaybackRate,
|
||||
showArchivedConversations,
|
||||
showChooseGroupMembers,
|
||||
|
@ -2064,20 +2065,61 @@ function setIsFetchingUUID(
|
|||
},
|
||||
};
|
||||
}
|
||||
function setSelectedConversationHeaderTitle(
|
||||
title?: string
|
||||
): SetConversationHeaderTitleActionType {
|
||||
|
||||
export type PushPanelForConversationActionType = (
|
||||
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 {
|
||||
type: 'SET_CONVERSATION_HEADER_TITLE',
|
||||
payload: { title },
|
||||
type: PUSH_PANEL,
|
||||
payload: panel,
|
||||
};
|
||||
}
|
||||
function setSelectedConversationPanelDepth(
|
||||
panelDepth: number
|
||||
): SetSelectedConversationPanelDepthActionType {
|
||||
return {
|
||||
type: 'SET_SELECTED_CONVERSATION_PANEL_DEPTH',
|
||||
payload: { panelDepth },
|
||||
|
||||
function popPanelForConversation(
|
||||
conversationId: string
|
||||
): ThunkAction<void, RootStateType, unknown, PopPanelActionType> {
|
||||
return (dispatch, getState) => {
|
||||
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,
|
||||
selectedMessageSource: undefined,
|
||||
showArchived: false,
|
||||
selectedConversationTitle: '',
|
||||
selectedConversationPanelDepth: 0,
|
||||
selectedConversationPanels: [],
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -3375,7 +3416,7 @@ export function reducer(
|
|||
return {
|
||||
...omit(state, 'contactSpoofingReview'),
|
||||
selectedConversationId,
|
||||
selectedConversationPanelDepth: 0,
|
||||
selectedConversationPanels: [],
|
||||
messagesLookup: omit(state.messagesLookup, messageIds),
|
||||
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') {
|
||||
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 {
|
||||
...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 { isSignalConversation } from '../../util/isSignalConversation';
|
||||
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;
|
||||
export const getPlaceholderContact = (): ConversationType => {
|
||||
|
@ -1131,3 +1134,34 @@ export const getHideStoryConversationIds = createSelector(
|
|||
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>;
|
||||
conversationId: string;
|
||||
showAllMedia: () => void;
|
||||
showChatColorEditor: () => void;
|
||||
showGroupLinkManagement: () => void;
|
||||
showGroupV2Permissions: () => void;
|
||||
showConversationNotificationsSettings: () => void;
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||
import {
|
||||
getConversationSelector,
|
||||
getConversationTitle,
|
||||
isMissingRequiredProfileSharing,
|
||||
} from '../selectors/conversations';
|
||||
import { CallMode } from '../../types/Calling';
|
||||
|
@ -108,14 +109,14 @@ const mapStateToProps = (state: StateType, ownProps: OwnProps) => {
|
|||
'unblurredAvatarPath',
|
||||
]),
|
||||
badge: getPreferredBadgeSelector(state)(conversation.badges),
|
||||
conversationTitle: state.conversations.selectedConversationTitle,
|
||||
conversationTitle: getConversationTitle(state),
|
||||
hasStories,
|
||||
isMissingMandatoryProfileSharing:
|
||||
isMissingRequiredProfileSharing(conversation),
|
||||
isSMSOnly: isConversationSMSOnly(conversation),
|
||||
isSignalConversation: isSignalConversation(conversation),
|
||||
i18n: getIntl(state),
|
||||
showBackButton: state.conversations.selectedConversationPanelDepth > 0,
|
||||
showBackButton: state.conversations.selectedConversationPanels.length > 0,
|
||||
outgoingCallButtonStyle: getOutgoingCallButtonStyle(conversation, state),
|
||||
theme: getTheme(state),
|
||||
};
|
||||
|
|
|
@ -3,15 +3,19 @@
|
|||
|
||||
import React from 'react';
|
||||
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 { SmartCompositionArea } from './CompositionArea';
|
||||
import type { OwnProps as ConversationHeaderPropsType } from './ConversationHeader';
|
||||
import { SmartConversationHeader } from './ConversationHeader';
|
||||
import type { StateType } from '../reducer';
|
||||
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 { getTopPanelRenderableByReact } from '../selectors/conversations';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
|
||||
export type PropsType = {
|
||||
conversationId: string;
|
||||
|
@ -31,7 +35,7 @@ export type PropsType = {
|
|||
timelineProps: TimelinePropsType;
|
||||
};
|
||||
|
||||
const mapStateToProps = (_state: StateType, props: PropsType) => {
|
||||
const mapStateToProps = (state: StateType, props: PropsType) => {
|
||||
const {
|
||||
compositionAreaProps,
|
||||
conversationHeaderProps,
|
||||
|
@ -39,6 +43,8 @@ const mapStateToProps = (_state: StateType, props: PropsType) => {
|
|||
timelineProps,
|
||||
} = props;
|
||||
|
||||
const topPanel = getTopPanelRenderableByReact(state);
|
||||
|
||||
return {
|
||||
conversationId,
|
||||
renderCompositionArea: () => (
|
||||
|
@ -48,6 +54,24 @@ const mapStateToProps = (_state: StateType, props: PropsType) => {
|
|||
<SmartConversationHeader {...conversationHeaderProps} />
|
||||
),
|
||||
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 { longRunningTaskWrapper } from '../util/longRunningTaskWrapper';
|
||||
import { hasDraftAttachments } from '../util/hasDraftAttachments';
|
||||
import type { BackbonePanelRenderType, PanelRenderType } from '../types/Panels';
|
||||
import { PanelType, isPanelHandledByReact } from '../types/Panels';
|
||||
|
||||
type AttachmentOptions = {
|
||||
messageId: string;
|
||||
attachment: AttachmentType;
|
||||
};
|
||||
|
||||
type PanelType = { view: Backbone.View; headerTitle?: string };
|
||||
|
||||
const { Message } = window.Signal.Types;
|
||||
|
||||
type BackbonePanelType = { panelType: PanelType; view: Backbone.View };
|
||||
|
||||
const { getAbsoluteAttachmentPath, upgradeMessageSchema } =
|
||||
window.Signal.Migrations;
|
||||
|
||||
|
@ -125,7 +127,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
private stickerPreviewModalView?: Backbone.View;
|
||||
|
||||
// Panel support
|
||||
private panels: Array<PanelType> = [];
|
||||
private panels: Array<BackbonePanelType> = [];
|
||||
private previousFocus?: HTMLElement;
|
||||
|
||||
// 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
|
||||
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, 'delete-message', this.deleteMessage);
|
||||
this.listenTo(this.model, 'remove-link-review', removeLinkPreview);
|
||||
|
@ -157,6 +161,9 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
|
||||
this.setupConversationView();
|
||||
this.updateAttachmentsView();
|
||||
|
||||
this.listenTo(this.model, 'pushPanel', this.pushPanel);
|
||||
this.listenTo(this.model, 'popPanel', this.popPanel);
|
||||
}
|
||||
|
||||
override events(): Record<string, string> {
|
||||
|
@ -212,7 +219,9 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
this.showGV1Members();
|
||||
},
|
||||
onGoBack: () => {
|
||||
this.resetPanel();
|
||||
window.reduxActions.conversations.popPanelForConversation(
|
||||
this.model.id
|
||||
);
|
||||
},
|
||||
|
||||
onArchive: () => {
|
||||
|
@ -237,7 +246,6 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
showToast(ToastConversationUnarchived);
|
||||
},
|
||||
};
|
||||
window.reduxActions.conversations.setSelectedConversationHeaderTitle();
|
||||
|
||||
// setupTimeline
|
||||
|
||||
|
@ -544,7 +552,6 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
const panel = this.panels[i];
|
||||
panel.view.remove();
|
||||
}
|
||||
window.reduxActions.conversations.setSelectedConversationPanelDepth(0);
|
||||
}
|
||||
|
||||
removeLinkPreview();
|
||||
|
@ -624,6 +631,12 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
}
|
||||
|
||||
showAllMedia(): void {
|
||||
window.reduxActions.conversations.pushPanelForConversation(this.model.id, {
|
||||
type: PanelType.AllMedia,
|
||||
});
|
||||
}
|
||||
|
||||
getAllMedia(): Backbone.View | undefined {
|
||||
if (document.querySelectorAll('.module-media-gallery').length) {
|
||||
return;
|
||||
}
|
||||
|
@ -807,19 +820,24 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
unsubscribe();
|
||||
},
|
||||
});
|
||||
const headerTitle = window.i18n('allMedia');
|
||||
|
||||
const update = async () => {
|
||||
const props = await getProps();
|
||||
view.update(<MediaGallery i18n={window.i18n} {...props} />);
|
||||
};
|
||||
|
||||
this.addPanel({ view, headerTitle });
|
||||
|
||||
update();
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
showGV1Members(): void {
|
||||
window.reduxActions.conversations.pushPanelForConversation(this.model.id, {
|
||||
type: PanelType.GroupV1Members,
|
||||
});
|
||||
}
|
||||
|
||||
getGV1Members(): Backbone.View {
|
||||
const { contactCollection, id } = this.model;
|
||||
|
||||
const memberships =
|
||||
|
@ -855,8 +873,9 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
),
|
||||
});
|
||||
|
||||
this.addPanel({ view });
|
||||
view.render();
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
deleteMessage(messageId: string): void {
|
||||
|
@ -877,12 +896,20 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
} else {
|
||||
this.model.decrementMessageCount();
|
||||
}
|
||||
this.resetPanel();
|
||||
window.reduxActions.conversations.popPanelForConversation(
|
||||
this.model.id
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
showGroupLinkManagement(): void {
|
||||
window.reduxActions.conversations.pushPanelForConversation(this.model.id, {
|
||||
type: PanelType.GroupLinkManagement,
|
||||
});
|
||||
}
|
||||
|
||||
getGroupLinkManagement(): Backbone.View {
|
||||
const view = new ReactWrapperView({
|
||||
className: 'panel',
|
||||
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();
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
showGroupV2Permissions(): void {
|
||||
window.reduxActions.conversations.pushPanelForConversation(this.model.id, {
|
||||
type: PanelType.GroupPermissions,
|
||||
});
|
||||
}
|
||||
|
||||
getGroupV2Permissions(): Backbone.View {
|
||||
const view = new ReactWrapperView({
|
||||
className: 'panel',
|
||||
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();
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
showPendingInvites(): void {
|
||||
window.reduxActions.conversations.pushPanelForConversation(this.model.id, {
|
||||
type: PanelType.GroupInvites,
|
||||
});
|
||||
}
|
||||
|
||||
getPendingInvites(): Backbone.View {
|
||||
const view = new ReactWrapperView({
|
||||
className: 'panel',
|
||||
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(),
|
||||
}),
|
||||
});
|
||||
const headerTitle = window.i18n(
|
||||
'ConversationDetails--requests-and-invites'
|
||||
);
|
||||
|
||||
this.addPanel({ view, headerTitle });
|
||||
view.render();
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
showConversationNotificationsSettings(): void {
|
||||
window.reduxActions.conversations.pushPanelForConversation(this.model.id, {
|
||||
type: PanelType.NotificationSettings,
|
||||
});
|
||||
}
|
||||
|
||||
getConversationNotificationsSettings(): Backbone.View {
|
||||
const view = new ReactWrapperView({
|
||||
className: 'panel',
|
||||
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();
|
||||
}
|
||||
|
||||
showChatColorEditor(): void {
|
||||
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();
|
||||
return view;
|
||||
}
|
||||
|
||||
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
|
||||
// Redux should cover us on the return here so no need to await this.
|
||||
if (this.model.throttledGetProfiles) {
|
||||
|
@ -981,7 +1017,6 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
addMembers: this.model.addMembersV2.bind(this.model),
|
||||
conversationId: this.model.get('id'),
|
||||
showAllMedia: this.showAllMedia.bind(this),
|
||||
showChatColorEditor: this.showChatColorEditor.bind(this),
|
||||
showGroupLinkManagement: this.showGroupLinkManagement.bind(this),
|
||||
showGroupV2Permissions: this.showGroupV2Permissions.bind(this),
|
||||
showConversationNotificationsSettings:
|
||||
|
@ -1000,13 +1035,24 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
props
|
||||
),
|
||||
});
|
||||
const headerTitle = '';
|
||||
|
||||
this.addPanel({ view, headerTitle });
|
||||
view.render();
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
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);
|
||||
if (!message) {
|
||||
throw new Error(`showMessageDetail: Message ${messageId} missing!`);
|
||||
|
@ -1025,7 +1071,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
|
||||
const onClose = () => {
|
||||
this.stopListening(message, 'change', update);
|
||||
this.resetPanel();
|
||||
window.reduxActions.conversations.popPanelForConversation(this.model.id);
|
||||
};
|
||||
|
||||
const view = new ReactWrapperView({
|
||||
|
@ -1048,21 +1094,31 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
this.listenTo(message, 'expired', onClose);
|
||||
// We could listen to all involved contacts, but we'll call that overkill
|
||||
|
||||
this.addPanel({ view });
|
||||
view.render();
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
showStickerManager(): void {
|
||||
window.reduxActions.conversations.pushPanelForConversation(this.model.id, {
|
||||
type: PanelType.StickerManager,
|
||||
});
|
||||
}
|
||||
|
||||
getStickerManager(): Backbone.View {
|
||||
const view = new ReactWrapperView({
|
||||
className: ['sticker-manager-wrapper', 'panel'].join(' '),
|
||||
JSX: window.Signal.State.Roots.createStickerManager(window.reduxStore),
|
||||
onClose: () => {
|
||||
this.resetPanel();
|
||||
window.reduxActions.conversations.popPanelForConversation(
|
||||
this.model.id
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
this.addPanel({ view });
|
||||
view.render();
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
showContactDetail({
|
||||
|
@ -1075,6 +1131,22 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
uuid: UUIDStringType;
|
||||
};
|
||||
}): 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({
|
||||
className: 'contact-detail-pane panel',
|
||||
JSX: (
|
||||
|
@ -1090,11 +1162,13 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
/>
|
||||
),
|
||||
onClose: () => {
|
||||
this.resetPanel();
|
||||
window.reduxActions.conversations.popPanelForConversation(
|
||||
this.model.id
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
this.addPanel({ view });
|
||||
return view;
|
||||
}
|
||||
|
||||
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 || [];
|
||||
|
||||
if (this.panels.length === 0) {
|
||||
this.previousFocus = document.activeElement as HTMLElement;
|
||||
}
|
||||
|
||||
this.panels.unshift(panel);
|
||||
panel.view.$el.insertAfter(this.$('.panel').last());
|
||||
panel.view.$el.one('animationend', () => {
|
||||
panel.view.$el.addClass('panel--static');
|
||||
});
|
||||
const { type } = panel as BackbonePanelRenderType;
|
||||
|
||||
window.reduxActions.conversations.setSelectedConversationPanelDepth(
|
||||
this.panels.length
|
||||
);
|
||||
window.reduxActions.conversations.setSelectedConversationHeaderTitle(
|
||||
panel.headerTitle
|
||||
);
|
||||
}
|
||||
resetPanel(): void {
|
||||
if (!this.panels || !this.panels.length) {
|
||||
let view: Backbone.View | undefined;
|
||||
if (type === PanelType.AllMedia) {
|
||||
view = this.getAllMedia();
|
||||
} else if (panel.type === PanelType.ContactDetails) {
|
||||
view = this.getContactDetail(panel.args);
|
||||
} else if (type === PanelType.ConversationDetails) {
|
||||
view = this.getConversationDetails();
|
||||
} else if (type === PanelType.GroupInvites) {
|
||||
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;
|
||||
}
|
||||
|
||||
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 (
|
||||
this.panels.length === 0 &&
|
||||
|
@ -1144,36 +1249,42 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
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) {
|
||||
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;
|
||||
const removePanel = () => {
|
||||
if (!timeout) {
|
||||
return;
|
||||
}
|
||||
let timeout: ReturnType<typeof setTimeout> | undefined;
|
||||
const removePanel = () => {
|
||||
if (!timeout) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(timeout);
|
||||
timeout = undefined;
|
||||
clearTimeout(timeout);
|
||||
timeout = undefined;
|
||||
|
||||
panel.view.remove();
|
||||
};
|
||||
panel.view.$el
|
||||
.addClass('panel--remove')
|
||||
.one('transitionend', removePanel);
|
||||
panel.view.remove();
|
||||
};
|
||||
panel.view.$el.addClass('panel--remove').one('transitionend', removePanel);
|
||||
|
||||
// Backup, in case things go wrong with the transitionend event
|
||||
timeout = setTimeout(removePanel, SECOND);
|
||||
}
|
||||
|
||||
window.reduxActions.conversations.setSelectedConversationPanelDepth(
|
||||
this.panels.length
|
||||
);
|
||||
window.reduxActions.conversations.setSelectedConversationHeaderTitle(
|
||||
this.panels[0]?.headerTitle
|
||||
);
|
||||
// Backup, in case things go wrong with the transitionend event
|
||||
timeout = setTimeout(removePanel, SECOND);
|
||||
}
|
||||
|
||||
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 { createStore } from './state/createStore';
|
||||
import type { createApp } from './state/roots/createApp';
|
||||
import type { createChatColorPicker } from './state/roots/createChatColorPicker';
|
||||
import type { createConversationDetails } from './state/roots/createConversationDetails';
|
||||
import type { createGroupLinkManagement } from './state/roots/createGroupLinkManagement';
|
||||
import type { createGroupV2JoinModal } from './state/roots/createGroupV2JoinModal';
|
||||
|
@ -167,7 +166,6 @@ export type SignalCoreType = {
|
|||
createStore: typeof createStore;
|
||||
Roots: {
|
||||
createApp: typeof createApp;
|
||||
createChatColorPicker: typeof createChatColorPicker;
|
||||
createConversationDetails: typeof createConversationDetails;
|
||||
createGroupLinkManagement: typeof createGroupLinkManagement;
|
||||
createGroupV2JoinModal: typeof createGroupV2JoinModal;
|
||||
|
|
Loading…
Reference in a new issue