From e55cae697d4f8c3e943e51511e6445982ed379b0 Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Wed, 9 Jun 2021 10:06:23 +1000 Subject: [PATCH] Refactor confirm modal to redux. --- ts/components/MainViewController.tsx | 3 +- ts/components/OnionStatusDialog.tsx | 2 + .../AdminLeaveClosedGroupDialog.tsx | 67 ++--- .../conversation/ModeratorsAddDialog.tsx | 259 +++++++++++++----- ts/components/session/ActionsPanel.tsx | 92 ++++--- .../session/LeftPaneMessageSection.tsx | 2 +- .../session/SessionClosableOverlay.tsx | 3 - ts/components/session/SessionConfirm.tsx | 65 ++++- ts/components/session/SessionToggle.tsx | 19 +- .../conversation/SessionConversation.tsx | 32 ++- .../session/menu/ConversationHeaderMenu.tsx | 9 +- .../menu/ConversationListItemContextMenu.tsx | 9 +- ts/components/session/menu/Menu.tsx | 117 +++++++- .../settings/SessionSettingListItem.tsx | 7 +- .../session/settings/SessionSettings.tsx | 16 +- ts/interactions/message.ts | 176 ++++++++---- ts/models/conversation.ts | 42 +-- .../{closedGroups.tsx => closedGroups.ts} | 82 ++++-- ts/state/actions.ts | 2 + ts/state/ducks/modalDialog.tsx | 21 ++ ts/state/ducks/onion.tsx | 9 - ts/state/reducer.ts | 7 +- ts/state/smart/SessionConversation.tsx | 1 + ts/window.d.ts | 6 + 24 files changed, 745 insertions(+), 303 deletions(-) rename ts/receiver/{closedGroups.tsx => closedGroups.ts} (94%) create mode 100644 ts/state/ducks/modalDialog.tsx diff --git a/ts/components/MainViewController.tsx b/ts/components/MainViewController.tsx index 25d898b2b..9e2bcbad0 100644 --- a/ts/components/MainViewController.tsx +++ b/ts/components/MainViewController.tsx @@ -39,7 +39,6 @@ export class MessageView extends React.Component { async function createClosedGroup( groupName: string, groupMembers: Array, - setModal: () => void ): Promise { // Validate groupName and groupMembers length if (groupName.length === 0) { @@ -64,7 +63,7 @@ async function createClosedGroup( const groupMemberIds = groupMembers.map(m => m.id); - await createClosedGroupV2(groupName, groupMemberIds, setModal); + await createClosedGroupV2(groupName, groupMemberIds); return true; } diff --git a/ts/components/OnionStatusDialog.tsx b/ts/components/OnionStatusDialog.tsx index b640f586d..9261be1b2 100644 --- a/ts/components/OnionStatusDialog.tsx +++ b/ts/components/OnionStatusDialog.tsx @@ -34,6 +34,8 @@ export type StatusLightType = { const OnionPathModalInner = (props: any) => { const onionNodes = useSelector((state: StateType) => state.onionPaths.snodePath); + const confirmModalState = useSelector((state: StateType) => state); + console.log('onion path: ', confirmModalState); const onionPath = onionNodes.path; // including the device and destination in calculation const glowDuration = onionPath.length + 2; diff --git a/ts/components/conversation/AdminLeaveClosedGroupDialog.tsx b/ts/components/conversation/AdminLeaveClosedGroupDialog.tsx index 9ec417771..fd0f09ee2 100644 --- a/ts/components/conversation/AdminLeaveClosedGroupDialog.tsx +++ b/ts/components/conversation/AdminLeaveClosedGroupDialog.tsx @@ -2,52 +2,53 @@ import React from 'react'; import { SessionModal } from '../session/SessionModal'; import { SessionButton, SessionButtonColor } from '../session/SessionButton'; -import { DefaultTheme } from 'styled-components'; +import { DefaultTheme, useTheme } from 'styled-components'; +import { SessionWrapperModal } from '../session/SessionWrapperModal'; interface Props { groupName: string; - onSubmit: any; + onSubmit: () => any; onClose: any; theme: DefaultTheme; } -class AdminLeaveClosedGroupDialogInner extends React.Component { - constructor(props: any) { - super(props); +const AdminLeaveClosedGroupDialogInner = (props: Props) => { - this.closeDialog = this.closeDialog.bind(this); - this.onClickOK = this.onClickOK.bind(this); - } + const { groupName, theme, onSubmit, onClose } = props; - public render() { - const titleText = `${window.i18n('leaveGroup')} ${this.props.groupName}`; - const warningAsAdmin = `${window.i18n('leaveGroupConfirmationAdmin')}`; - const okText = window.i18n('leaveAndRemoveForEveryone'); - - return ( - -
-

{warningAsAdmin}

- -
- -
- - ); - } + const titleText = `${window.i18n('leaveGroup')} ${groupName}`; + const warningAsAdmin = `${window.i18n('leaveGroupConfirmationAdmin')}`; + const okText = window.i18n('leaveAndRemoveForEveryone'); + const cancelText = window.i18n('cancel'); - private onClickOK() { - this.props.onSubmit(); - this.closeDialog(); + const onClickOK = () => { + void onSubmit(); + closeDialog(); } - private closeDialog() { - this.props.onClose(); + const closeDialog = () => { + onClose(); } + + return ( + + +
+

{warningAsAdmin}

+ +
+ + +
+ + ); } export const AdminLeaveClosedGroupDialog = AdminLeaveClosedGroupDialogInner; diff --git a/ts/components/conversation/ModeratorsAddDialog.tsx b/ts/components/conversation/ModeratorsAddDialog.tsx index e201d2cac..dcb1076ef 100644 --- a/ts/components/conversation/ModeratorsAddDialog.tsx +++ b/ts/components/conversation/ModeratorsAddDialog.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { SessionButton, SessionButtonColor, SessionButtonType } from '../session/SessionButton'; import { PubKey } from '../../session/types'; import { ToastUtils } from '../../session/utils'; @@ -8,6 +8,7 @@ import { SessionSpinner } from '../session/SessionSpinner'; import { Flex } from '../basic/Flex'; import { ConversationModel } from '../../models/conversation'; import { ApiV2 } from '../../opengroup/opengroupV2'; +import { processDecrypted } from '../../receiver/dataMessage'; interface Props { convo: ConversationModel; onClose: any; @@ -20,33 +21,144 @@ interface State { firstLoading: boolean; } -export class AddModeratorsDialog extends React.Component { - private channelAPI: any; - - constructor(props: Props) { - super(props); - - this.addAsModerator = this.addAsModerator.bind(this); - this.onPubkeyBoxChanges = this.onPubkeyBoxChanges.bind(this); - - this.state = { - inputBoxValue: '', - addingInProgress: false, - firstLoading: true, - }; - } - - public async componentDidMount() { - if (this.props.convo.isOpenGroupV1()) { - this.channelAPI = await this.props.convo.getPublicSendData(); +// export class AddModeratorsDialog extends React.Component { +// private channelAPI: any; + +// constructor(props: Props) { +// super(props); + +// this.addAsModerator = this.addAsModerator.bind(this); +// this.onPubkeyBoxChanges = this.onPubkeyBoxChanges.bind(this); + +// this.state = { +// inputBoxValue: '', +// addingInProgress: false, +// firstLoading: true, +// }; +// } + +// public async componentDidMount() { +// if (this.props.convo.isOpenGroupV1()) { +// this.channelAPI = await this.props.convo.getPublicSendData(); +// } + +// this.setState({ firstLoading: false }); +// } + +// public async addAsModerator() { +// // if we don't have valid data entered by the user +// const pubkey = PubKey.from(this.state.inputBoxValue); +// if (!pubkey) { +// window.log.info('invalid pubkey for adding as moderator:', this.state.inputBoxValue); +// ToastUtils.pushInvalidPubKey(); +// return; +// } + +// window.log.info(`asked to add moderator: ${pubkey.key}`); + +// try { +// this.setState({ +// addingInProgress: true, +// }); +// let isAdded: any; +// if (this.props.convo.isOpenGroupV1()) { +// isAdded = await this.channelAPI.serverAPI.addModerator([pubkey.key]); +// } else { +// // this is a v2 opengroup +// const roomInfos = this.props.convo.toOpenGroupV2(); +// isAdded = await ApiV2.addModerator(pubkey, roomInfos); +// } +// if (!isAdded) { +// window.log.warn('failed to add moderators:', isAdded); + +// ToastUtils.pushUserNeedsToHaveJoined(); +// } else { +// window.log.info(`${pubkey.key} added as moderator...`); +// ToastUtils.pushUserAddedToModerators(); + +// // clear input box +// this.setState({ +// inputBoxValue: '', +// }); +// } +// } catch (e) { +// window.log.error('Got error while adding moderator:', e); +// } finally { +// this.setState({ +// addingInProgress: false, +// }); +// } +// } + +// public render() { +// const { i18n } = window; +// const { addingInProgress, inputBoxValue, firstLoading } = this.state; +// const chatName = this.props.convo.get('name'); + +// const title = `${i18n('addModerators')}: ${chatName}`; + +// const renderContent = !firstLoading; + +// return ( +// this.props.onClose()} theme={this.props.theme}> +// +// {renderContent && ( +// <> +//

Add Moderator:

+// +// +// +// )} +// +//
+//
+// ); +// } + +// private onPubkeyBoxChanges(e: any) { +// const val = e.target.value; +// this.setState({ inputBoxValue: val }); +// } +// } + + +export const AddModeratorsDialog = (props: any) => { + const { convo, onClose, theme } = props; + + const [inputBoxValue, setInputBoxValue] = useState(''); + const [addingInProgress, setAddingInProgress] = useState(false); + const [firstLoading, setFirstLoading] = useState(true); + + let channelAPI: any; + + useEffect(() => { + async function getPublicSendData { + if (props.convo.isOpenGroupV1()) { + channelAPI = await convo.getPublicSendData(); + } + setFirstLoading(false); } + }, []) + - this.setState({ firstLoading: false }); - } - public async addAsModerator() { + const addAsModerator = async () => { // if we don't have valid data entered by the user - const pubkey = PubKey.from(this.state.inputBoxValue); + const pubkey = PubKey.from(inputBoxValue); if (!pubkey) { window.log.info('invalid pubkey for adding as moderator:', this.state.inputBoxValue); ToastUtils.pushInvalidPubKey(); @@ -56,15 +168,13 @@ export class AddModeratorsDialog extends React.Component { window.log.info(`asked to add moderator: ${pubkey.key}`); try { - this.setState({ - addingInProgress: true, - }); + setAddingInProgress(true); let isAdded: any; - if (this.props.convo.isOpenGroupV1()) { - isAdded = await this.channelAPI.serverAPI.addModerator([pubkey.key]); + if (convo.isOpenGroupV1()) { + isAdded = await channelAPI.serverAPI.addModerator([pubkey.key]); } else { // this is a v2 opengroup - const roomInfos = this.props.convo.toOpenGroupV2(); + const roomInfos = props.convo.toOpenGroupV2(); isAdded = await ApiV2.addModerator(pubkey, roomInfos); } if (!isAdded) { @@ -76,60 +186,57 @@ export class AddModeratorsDialog extends React.Component { ToastUtils.pushUserAddedToModerators(); // clear input box - this.setState({ - inputBoxValue: '', - }); + setInputBoxValue(''); } } catch (e) { window.log.error('Got error while adding moderator:', e); } finally { - this.setState({ - addingInProgress: false, - }); + setAddingInProgress(false); } - } - public render() { - const { i18n } = window; - const { addingInProgress, inputBoxValue, firstLoading } = this.state; - const chatName = this.props.convo.get('name'); - - const title = `${i18n('addModerators')}: ${chatName}`; - - const renderContent = !firstLoading; - - return ( - this.props.onClose()} theme={this.props.theme}> - - {renderContent && ( - <> -

Add Moderator:

- - - - )} - -
-
- ); } - private onPubkeyBoxChanges(e: any) { + const { i18n } = window; + // const { addingInProgress, inputBoxValue, firstLoading } = this.state; + const chatName = props.convo.get('name'); + + const title = `${i18n('addModerators')}: ${chatName}`; + + const renderContent = !firstLoading; + + const onPubkeyBoxChanges = (e: any) => { const val = e.target.value; - this.setState({ inputBoxValue: val }); + setInputBoxValue(val); } + + + return ( + onClose()} theme={theme}> + + {renderContent && ( + <> +

Add Moderator:

+ + + + )} + +
+
+ ); + } diff --git a/ts/components/session/ActionsPanel.tsx b/ts/components/session/ActionsPanel.tsx index 57e203c0c..3f9c856e8 100644 --- a/ts/components/session/ActionsPanel.tsx +++ b/ts/components/session/ActionsPanel.tsx @@ -33,9 +33,13 @@ import { OpenGroupManagerV2 } from '../../opengroup/opengroupV2/OpenGroupManager import { loadDefaultRooms } from '../../opengroup/opengroupV2/ApiUtil'; import { forceRefreshRandomSnodePool } from '../../session/snode_api/snodePool'; import { SwarmPolling } from '../../session/snode_api/swarmPolling'; -import _ from 'lodash'; +import _, { divide } from 'lodash'; import { ActionPanelOnionStatusLight, OnionPathModal } from '../OnionStatusDialog'; import { EditProfileDialog } from '../EditProfileDialog'; +import { StateType } from '../../state/reducer'; +import { updateConfirmModal } from "../../state/ducks/modalDialog"; +import { useInView } from 'react-intersection-observer'; +import { SessionConfirm } from './SessionConfirm'; // tslint:disable-next-line: no-import-side-effect no-submodule-imports @@ -72,6 +76,11 @@ const Section = (props: { /* tslint:disable:no-void-expression */ if (type === SectionType.Profile) { // window.showEditProfileDialog(); + console.log("edit profile"); + + // window.inboxStore?.dispatch(updateConfirmModal({ title: "title test" })); + + // dispatch(updateConfirmModal({ title: "title test" })); // setModal( setModal(null)}>); setModal(); @@ -134,21 +143,21 @@ const Section = (props: { return ( <> {type === SectionType.PathIndicator ? - - : - - } + + : + + } ); }; @@ -260,24 +269,45 @@ export const ActionsPanel = () => { void forceRefreshRandomSnodePool(); }, DAYS * 1); + const formatLog = (s: any ) => { + console.log("@@@@:: ", s); + } + + + // const confirmModalState = useSelector((state: StateType) => state); + + const confirmModalState = useSelector((state: StateType) => state.confirmModal); + + console.log('@@@ confirm modal state', confirmModalState); + + // formatLog(confirmModalState.modalState.title); + + formatLog(confirmModalState); + + // formatLog(confirmModalState2); + return ( <> {modal ? modal : null} -
-
-
-
-
- - - -
-
-
+ {/* { confirmModalState && confirmModalState.title ?
{confirmModalState.title}
: null} */} + { confirmModalState ? : null} +
+
+
+
+
+ + + +
+
+
); }; diff --git a/ts/components/session/LeftPaneMessageSection.tsx b/ts/components/session/LeftPaneMessageSection.tsx index 92b5c7707..c20d6eac3 100644 --- a/ts/components/session/LeftPaneMessageSection.tsx +++ b/ts/components/session/LeftPaneMessageSection.tsx @@ -447,7 +447,7 @@ export class LeftPaneMessageSection extends React.Component { return; } this.setState({ loading: true }, async () => { - const groupCreated = await MainViewController.createClosedGroup(groupName, groupMembers, setModal); + const groupCreated = await MainViewController.createClosedGroup(groupName, groupMembers); if (groupCreated) { this.handleToggleOverlay(undefined); diff --git a/ts/components/session/SessionClosableOverlay.tsx b/ts/components/session/SessionClosableOverlay.tsx index 9fc9bf7f2..2be4db21e 100644 --- a/ts/components/session/SessionClosableOverlay.tsx +++ b/ts/components/session/SessionClosableOverlay.tsx @@ -1,16 +1,13 @@ import React from 'react'; import { SessionIconButton, SessionIconSize, SessionIconType } from './icon'; -import { SessionToggle } from './SessionToggle'; import { SessionIdEditable } from './SessionIdEditable'; import { UserSearchDropdown } from './UserSearchDropdown'; import { ContactType, SessionMemberListItem } from './SessionMemberListItem'; import { ConversationType } from '../../state/ducks/conversations'; import { SessionButton, SessionButtonColor, SessionButtonType } from './SessionButton'; import { SessionSpinner } from './SessionSpinner'; -import { PillDivider } from './PillDivider'; import { DefaultTheme } from 'styled-components'; -import { UserUtils } from '../../session/utils'; import { ConversationTypeEnum } from '../../models/conversation'; import { SessionJoinableRooms } from './SessionJoinableDefaultRooms'; diff --git a/ts/components/session/SessionConfirm.tsx b/ts/components/session/SessionConfirm.tsx index 03e2496ae..466f591bd 100644 --- a/ts/components/session/SessionConfirm.tsx +++ b/ts/components/session/SessionConfirm.tsx @@ -5,15 +5,18 @@ import { SessionHtmlRenderer } from './SessionHTMLRenderer'; import { SessionIcon, SessionIconSize, SessionIconType } from './icon'; import { DefaultTheme, useTheme, withTheme } from 'styled-components'; import { SessionWrapperModal } from './SessionWrapperModal'; +import { useDispatch } from 'react-redux'; +import { updateConfirmModal } from '../../state/ducks/modalDialog'; +import { update } from 'lodash'; -type Props = { - message: string; +export interface SessionConfirmDialogProps { + message?: string; messageSub?: string; - title: string; + title?: string; onOk?: any; onClose?: any; - onClickOk?: any; - onClickClose?: any; + onClickOk?: () => any; + onClickClose?: () => any; okText?: string; cancelText?: string; hideCancel?: boolean; @@ -22,20 +25,25 @@ type Props = { sessionIcon?: SessionIconType; iconSize?: SessionIconSize; theme?: DefaultTheme; + closeAfterClickOk?: boolean; + shouldShowConfirm?: () => boolean | undefined; }; -const SessionConfirmInner = (props: Props) => { +const SessionConfirmInner = (props: SessionConfirmDialogProps) => { const { title = '', - message, + message = '', messageSub = '', okTheme = SessionButtonColor.Primary, closeTheme = SessionButtonColor.Primary, onClickOk, onClickClose, + closeAfterClickOk = true, hideCancel = false, sessionIcon, iconSize, + shouldShowConfirm, + // updateConfirmModal } = props; const okText = props.okText || window.i18n('ok'); @@ -46,8 +54,43 @@ const SessionConfirmInner = (props: Props) => { const messageSubText = messageSub ? 'session-confirm-main-message' : 'subtle'; - return ( + /** + * Calls close function after the ok button is clicked. If no close method specified, closes the modal + */ + const onClickOkWithClose = () => { + if (onClickOk) { + onClickOk(); + } + + if (onClickClose) { + onClickClose(); + } + } + + + const onClickOkHandler = () => { + if (onClickOk) { + onClickOk(); + } + + + window.inboxStore?.dispatch(updateConfirmModal(null)); + } + if (shouldShowConfirm && !shouldShowConfirm()) { + return null; + } + + const onClickCancelHandler = () => { + if (onClickClose) { + onClickClose(); + } + + window.inboxStore?.dispatch(updateConfirmModal(null)); + } + + + return ( {
- + {/* */} + {!hideCancel && ( - + // + )}
diff --git a/ts/components/session/SessionToggle.tsx b/ts/components/session/SessionToggle.tsx index b9d79625e..f0f0d26f6 100644 --- a/ts/components/session/SessionToggle.tsx +++ b/ts/components/session/SessionToggle.tsx @@ -1,5 +1,6 @@ import React from 'react'; import classNames from 'classnames'; +import { updateConfirmModal } from '../../state/ducks/modalDialog'; interface Props { active: boolean; @@ -14,6 +15,8 @@ interface Props { // okTheme: 'danger', // } confirmationDialogParams?: any | undefined; + + updateConfirmModal?: any; } interface State { @@ -62,15 +65,25 @@ export class SessionToggle extends React.PureComponent { if ( this.props.confirmationDialogParams && + this.props.updateConfirmModal && this.props.confirmationDialogParams.shouldShowConfirm() ) { // If item needs a confirmation dialog to turn ON, render it - window.confirmationDialog({ - resolve: () => { + const closeConfirmModal = () => { + this.props.updateConfirmModal(null); + } + + this.props.updateConfirmModal({ + onClickOk: () => { stateManager(event); + closeConfirmModal(); + }, + onClickClose: () => { + this.props.updateConfirmModal(null); }, ...this.props.confirmationDialogParams, - }); + updateConfirmModal, + }) return; } diff --git a/ts/components/session/conversation/SessionConversation.tsx b/ts/components/session/conversation/SessionConversation.tsx index f1f0bf848..48053eb5e 100644 --- a/ts/components/session/conversation/SessionConversation.tsx +++ b/ts/components/session/conversation/SessionConversation.tsx @@ -1,6 +1,8 @@ // tslint:disable: no-backbone-get-set-outside-model import React from 'react'; +import { useDispatch } from "react-redux"; +import { updateConfirmModal } from '../../../state/ducks/modalDialog'; import classNames from 'classnames'; @@ -12,7 +14,7 @@ import { AttachmentUtil, GoogleChrome } from '../../../util'; import { ConversationHeaderWithDetails } from '../../conversation/ConversationHeader'; import { SessionRightPanelWithDetails } from './SessionRightPanel'; import { SessionTheme } from '../../../state/ducks/SessionTheme'; -import { DefaultTheme } from 'styled-components'; +import { DefaultTheme, useTheme } from 'styled-components'; import { SessionMessagesList } from './SessionMessagesList'; import { LightboxGallery, MediaItemType } from '../../LightboxGallery'; import { Message } from '../../conversation/media-gallery/types/Message'; @@ -31,6 +33,9 @@ import autoBind from 'auto-bind'; import { getDecryptedMediaUrl } from '../../../session/crypto/DecryptedAttachmentsManager'; import { deleteOpenGroupMessages } from '../../../interactions/conversation'; import { ConversationTypeEnum } from '../../../models/conversation'; +import { SessionButtonColor } from '../SessionButton'; + + interface State { // Message sending progress @@ -359,12 +364,10 @@ export class SessionConversation extends React.Component { onSetDisappearingMessages: conversation.updateExpirationTimer, onDeleteMessages: conversation.deleteMessages, onDeleteSelectedMessages: this.deleteSelectedMessages, - onChangeNickname: conversation.changeNickname, onClearNickname: conversation.clearNickname, onCloseOverlay: () => { this.setState({ selectedMessages: [] }); }, - onDeleteContact: conversation.deleteContact, onGoBack: () => { this.setState({ @@ -451,8 +454,8 @@ export class SessionConversation extends React.Component { const isAdmin = conversation.isMediumGroup() ? true : conversation.isPublic() - ? conversation.isAdmin(ourPrimary) - : false; + ? conversation.isAdmin(ourPrimary) + : false; return { id: conversation.id, @@ -507,7 +510,6 @@ export class SessionConversation extends React.Component { onInviteContacts: () => { window.Whisper.events.trigger('inviteContacts', conversation); }, - onDeleteContact: conversation.deleteContact, onLeaveGroup: () => { window.Whisper.events.trigger('leaveClosedGroup', conversation); }, @@ -661,14 +663,22 @@ export class SessionConversation extends React.Component { } const okText = window.i18n(isServerDeletable ? 'deleteForEveryone' : 'delete'); + if (askUserForConfirmation) { - window.confirmationDialog({ + const onClickClose = () => { + this.props.actions.updateConfirmModal(null); + } + + this.props.actions.updateConfirmModal({ title, message: warningMessage, okText, - okTheme: 'danger', - resolve: doDelete, - }); + okTheme: SessionButtonColor.Danger, + onClickOk: doDelete, + onClickClose, + closeAfterClick: true + }) + } else { void doDelete(); } @@ -684,7 +694,7 @@ export class SessionConversation extends React.Component { public selectMessage(messageId: string) { const selectedMessages = this.state.selectedMessages.includes(messageId) ? // Add to array if not selected. Else remove. - this.state.selectedMessages.filter(id => id !== messageId) + this.state.selectedMessages.filter(id => id !== messageId) : [...this.state.selectedMessages, messageId]; this.setState({ selectedMessages }); diff --git a/ts/components/session/menu/ConversationHeaderMenu.tsx b/ts/components/session/menu/ConversationHeaderMenu.tsx index 84c80f045..d524b5163 100644 --- a/ts/components/session/menu/ConversationHeaderMenu.tsx +++ b/ts/components/session/menu/ConversationHeaderMenu.tsx @@ -29,6 +29,7 @@ export type PropsConversationHeaderMenu = { timerOptions: Array; isPrivate: boolean; isBlocked: boolean; + theme: any; hasNickname?: boolean; onDeleteMessages?: () => void; @@ -62,6 +63,7 @@ export const ConversationHeaderMenu = (props: PropsConversationHeaderMenu) => { isPrivate, left, hasNickname, + theme, onClearNickname, onChangeNickname, @@ -104,11 +106,11 @@ export const ConversationHeaderMenu = (props: PropsConversationHeaderMenu) => { {getMarkAllReadMenuItem(onMarkAllRead, window.i18n)} {getChangeNicknameMenuItem(isMe, onChangeNickname, isGroup, window.i18n, id, setModal)} {getClearNicknameMenuItem(isMe, hasNickname, onClearNickname, isGroup, window.i18n)} - {getDeleteMessagesMenuItem(isPublic, onDeleteMessages, window.i18n)} + {getDeleteMessagesMenuItem(isPublic, onDeleteMessages, window.i18n, id)} {getAddModeratorsMenuItem(isAdmin, isKickedFromGroup, onAddModerators, window.i18n)} {getRemoveModeratorsMenuItem(isAdmin, isKickedFromGroup, onRemoveModerators, window.i18n)} {getUpdateGroupNameMenuItem(isAdmin, isKickedFromGroup, left, onUpdateGroupName, window.i18n)} - {getLeaveGroupMenuItem(isKickedFromGroup, left, isGroup, isPublic, onLeaveGroup, window.i18n)} + {getLeaveGroupMenuItem(isKickedFromGroup, left, isGroup, isPublic, onLeaveGroup, window.i18n, id, setModal, theme)} {/* TODO: add delete group */} {getInviteContactMenuItem(isGroup, isPublic, onInviteContacts, window.i18n)} {getDeleteContactMenuItem( @@ -118,7 +120,8 @@ export const ConversationHeaderMenu = (props: PropsConversationHeaderMenu) => { left, isKickedFromGroup, onDeleteContact, - window.i18n + window.i18n, + id )} diff --git a/ts/components/session/menu/ConversationListItemContextMenu.tsx b/ts/components/session/menu/ConversationListItemContextMenu.tsx index e5db0080b..321eb3c7b 100644 --- a/ts/components/session/menu/ConversationListItemContextMenu.tsx +++ b/ts/components/session/menu/ConversationListItemContextMenu.tsx @@ -24,6 +24,7 @@ export type PropsContextConversationItem = { hasNickname?: boolean; isKickedFromGroup?: boolean; left?: boolean; + theme?: any onDeleteMessages?: () => void; onDeleteContact?: () => void; @@ -58,6 +59,7 @@ export const ConversationListItemContextMenu = (props: PropsContextConversationI onInviteContacts, onLeaveGroup, onChangeNickname, + theme } = props; const isGroup = type === 'group'; @@ -82,7 +84,7 @@ export const ConversationListItemContextMenu = (props: PropsContextConversationI {getChangeNicknameMenuItem(isMe, onChangeNickname, isGroup, window.i18n, id, setModal)} {getClearNicknameMenuItem(isMe, hasNickname, onClearNickname, isGroup, window.i18n)} - {getDeleteMessagesMenuItem(isPublic, onDeleteMessages, window.i18n)} + {getDeleteMessagesMenuItem(isPublic, onDeleteMessages, window.i18n, id)} {getInviteContactMenuItem(isGroup, isPublic, onInviteContacts, window.i18n)} {getDeleteContactMenuItem( isMe, @@ -91,9 +93,10 @@ export const ConversationListItemContextMenu = (props: PropsContextConversationI left, isKickedFromGroup, onDeleteContact, - window.i18n + window.i18n, + id )} - {getLeaveGroupMenuItem(isKickedFromGroup, left, isGroup, isPublic, onLeaveGroup, window.i18n)} + {getLeaveGroupMenuItem(isKickedFromGroup, left, isGroup, isPublic, onLeaveGroup, window.i18n, id, setModal, theme)} ); diff --git a/ts/components/session/menu/Menu.tsx b/ts/components/session/menu/Menu.tsx index fab18049b..248f7fec7 100644 --- a/ts/components/session/menu/Menu.tsx +++ b/ts/components/session/menu/Menu.tsx @@ -3,6 +3,13 @@ import { LocalizerType } from '../../../types/Util'; import { TimerOption } from '../../conversation/ConversationHeader'; import { Item, Submenu } from 'react-contexify'; import { SessionNicknameDialog } from '../SessionNicknameDialog'; +import { useDispatch, useSelector } from 'react-redux'; +import { updateConfirmModal } from '../../../state/ducks/modalDialog'; +import { ConversationController } from '../../../session/conversations'; +import { useTheme } from 'styled-components'; +import { UserUtils } from '../../../session/utils'; +import { AdminLeaveClosedGroupDialog } from '../../conversation/AdminLeaveClosedGroupDialog'; +import { getTheme } from '../../../state/selectors/theme'; function showTimerOptions( isPublic: boolean, @@ -95,7 +102,8 @@ export function getDeleteContactMenuItem( isLeft: boolean | undefined, isKickedFromGroup: boolean | undefined, action: any, - i18n: LocalizerType + i18n: LocalizerType, + id: string ): JSX.Element | null { if ( showDeleteContact( @@ -106,10 +114,31 @@ export function getDeleteContactMenuItem( Boolean(isKickedFromGroup) ) ) { + let menuItemText: string; if (isPublic) { - return {i18n('leaveGroup')}; + menuItemText = i18n('leaveGroup'); + } else { + menuItemText = i18n('delete'); + } + + const dispatch = useDispatch(); + const onClickClose = () => { + dispatch(updateConfirmModal(null)); } - return {i18n('delete')}; + + const showConfirmationModal = () => { + dispatch(updateConfirmModal({ + title: menuItemText, + message: isGroup ? i18n('leaveGroupConfirmation'): i18n('deleteContactConfirmation'), + onClickClose, + onClickOk: () => { + void ConversationController.getInstance().deleteContact(id); + onClickClose(); + } + })) + } + + return {menuItemText}; } return null; } @@ -120,12 +149,57 @@ export function getLeaveGroupMenuItem( isGroup: boolean | undefined, isPublic: boolean | undefined, action: any, - i18n: LocalizerType + i18n: LocalizerType, + id: string, + setModal: any, + theme: any ): JSX.Element | null { if ( showLeaveGroup(Boolean(isKickedFromGroup), Boolean(left), Boolean(isGroup), Boolean(isPublic)) ) { - return {i18n('leaveGroup')}; + const dispatch = useDispatch(); + const conversation = ConversationController.getInstance().get(id); + + const onClickClose = () => { + dispatch(updateConfirmModal(null)); + } + + const openConfirmationModal = () => { + if (!conversation.isGroup()) { + throw new Error('showLeaveGroupDialog() called with a non group convo.'); + } + + const title = window.i18n('leaveGroup'); + const message = window.i18n('leaveGroupConfirmation'); + const ourPK = UserUtils.getOurPubKeyStrFromCache(); + const isAdmin = (conversation.get('groupAdmins') || []).includes(ourPK); + const isClosedGroup = conversation.get('is_medium_group') || false; + + // if this is not a closed group, or we are not admin, we can just show a confirmation dialog + if (!isClosedGroup || (isClosedGroup && !isAdmin)) { + dispatch(updateConfirmModal({ + title, + message, + onClickOk: () => { + conversation.leaveClosedGroup(); + onClickClose(); + }, + onClickClose + })); + } else { + setModal( + {setModal(null)}} + theme={theme} + > + ) + } + + } + + return {i18n('leaveGroup')}; } return null; } @@ -283,19 +357,13 @@ export function getChangeNicknameMenuItem( setModal(null); } - // const onClickOk = () => { - // console.log("@@ onclickok clicked"); - // } - const onClickCustom = () => { setModal(); - // setModal(null); } return ( <> {i18n('changeNickname')} - {/* {i18n('changeNickname')} */} ); } @@ -305,10 +373,33 @@ export function getChangeNicknameMenuItem( export function getDeleteMessagesMenuItem( isPublic: boolean | undefined, action: any, - i18n: LocalizerType + i18n: LocalizerType, + id: string ): JSX.Element | null { if (showDeleteMessages(Boolean(isPublic))) { - return {i18n('deleteMessages')}; + + const dispatch = useDispatch(); + const conversation = ConversationController.getInstance().get(id); + + const onClickClose = () => { + dispatch(updateConfirmModal(null)); + } + + const onClickOk = () => { + conversation.destroyMessages(); + onClickClose(); + } + + const openConfirmationModal = () => { + dispatch(updateConfirmModal({ + title: window.i18n('deleteMessages'), + message: window.i18n('deleteConversationConfirmation'), + onClickOk, + onClickClose, + })) + } + + return {i18n('deleteMessages')}; } return null; } diff --git a/ts/components/session/settings/SessionSettingListItem.tsx b/ts/components/session/settings/SessionSettingListItem.tsx index c638027f0..711dd43f0 100644 --- a/ts/components/session/settings/SessionSettingListItem.tsx +++ b/ts/components/session/settings/SessionSettingListItem.tsx @@ -7,6 +7,7 @@ import { SessionToggle } from '../SessionToggle'; import { SessionButton } from '../SessionButton'; import { SessionSettingType } from './SessionSettings'; import { SessionRadioGroup } from '../SessionRadioGroup'; +import { SessionConfirmDialogProps } from '../SessionConfirm'; interface Props { title?: string; @@ -17,7 +18,10 @@ interface Props { onClick?: any; onSliderChange?: any; content: any; - confirmationDialogParams?: any; + confirmationDialogParams?: SessionConfirmDialogProps; + + // for updating modal in redux + updateConfirmModal?: any } interface State { @@ -61,6 +65,7 @@ export class SessionSettingListItem extends React.Component { active={Boolean(value)} onClick={this.handleClick} confirmationDialogParams={this.props.confirmationDialogParams} + updateConfirmModal={this.props.updateConfirmModal} />
)} diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx index 57f3f7561..ed1825062 100644 --- a/ts/components/session/settings/SessionSettings.tsx +++ b/ts/components/session/settings/SessionSettings.tsx @@ -12,6 +12,8 @@ import { getConversationLookup, getConversations } from '../../../state/selector import { connect } from 'react-redux'; import { getPasswordHash } from '../../../../ts/data/data'; import { PasswordAction, SessionPasswordModal } from '../SessionPasswordModal'; +import { SessionConfirmDialogProps } from '../SessionConfirm'; +import { mapDispatchToProps } from '../../../state/actions'; export enum SessionSettingCategory { Appearance = 'appearance', @@ -34,6 +36,7 @@ export interface SettingsViewProps { // pass the conversation as props, so our render is called everytime they change. // we have to do this to make the list refresh on unblock() conversations?: ConversationLookupType; + updateConfirmModal?: any; } interface State { @@ -44,6 +47,10 @@ interface State { modal: JSX.Element | null; } +interface ConfirmationDialogParams extends SessionConfirmDialogProps { + shouldShowConfirm: () => boolean | undefined; +} + interface LocalSettingType { category: SessionSettingCategory; description: string | undefined; @@ -56,7 +63,7 @@ interface LocalSettingType { type: SessionSettingType | undefined; setFn: any; onClick: any; - confirmationDialogParams: any | undefined; + confirmationDialogParams: ConfirmationDialogParams | undefined; } class SettingsViewInner extends React.Component { @@ -147,6 +154,7 @@ class SettingsViewInner extends React.Component { onSliderChange={sliderFn} content={content} confirmationDialogParams={setting.confirmationDialogParams} + updateConfirmModal={this.props.updateConfirmModal} /> )} @@ -345,7 +353,7 @@ class SettingsViewInner extends React.Component { shouldShowConfirm: () => !window.getSettingValue('link-preview-setting'), title: window.i18n('linkPreviewsTitle'), message: window.i18n('linkPreviewsConfirmMessage'), - okTheme: 'danger', + okTheme: SessionButtonColor.Danger, }, }, { @@ -614,11 +622,13 @@ class SettingsViewInner extends React.Component { } } + const mapStateToProps = (state: StateType) => { return { conversations: getConversationLookup(state), }; }; -const smart = connect(mapStateToProps); + +const smart = connect(mapStateToProps, mapDispatchToProps); export const SmartSettingsView = smart(SettingsViewInner); diff --git a/ts/interactions/message.ts b/ts/interactions/message.ts index bbf7d3f9e..16bfb74a3 100644 --- a/ts/interactions/message.ts +++ b/ts/interactions/message.ts @@ -10,6 +10,9 @@ import { PubKey } from '../session/types'; import { ToastUtils } from '../session/utils'; import { openConversationExternal } from '../state/ducks/conversations'; +import { useDispatch } from 'react-redux'; +import { updateConfirmModal } from '../state/ducks/modalDialog'; + export function banUser(userToBan: string, conversation?: ConversationModel) { let pubKeyToBan: PubKey; try { @@ -19,37 +22,46 @@ export function banUser(userToBan: string, conversation?: ConversationModel) { ToastUtils.pushUserBanFailure(); return; } - window.confirmationDialog({ - title: window.i18n('banUser'), - message: window.i18n('banUserConfirm'), - resolve: async () => { - if (!conversation) { - window.log.info('cannot ban user, the corresponding conversation was not found.'); - return; - } - let success = false; - if (isOpenGroupV2(conversation.id)) { - const roomInfos = await getV2OpenGroupRoom(conversation.id); - if (!roomInfos) { - window.log.warn('banUser room not found'); - } else { - success = await ApiV2.banUser(pubKeyToBan, _.pick(roomInfos, 'serverUrl', 'roomId')); - } - } else { - const channelAPI = await conversation.getPublicSendData(); - if (!channelAPI) { - window.log.info('cannot ban user, the corresponding channelAPI was not found.'); - return; - } - success = await channelAPI.banUser(userToBan); - } - if (success) { - ToastUtils.pushUserBanSuccess(); - } else { - ToastUtils.pushUserBanFailure(); - } - }, - }); + + const dispatch = useDispatch(); + const onClickClose = () => { + dispatch(updateConfirmModal(null)) + }; + + // const confirmationModalProps = { + // title: window.i18n('banUser'), + // message: window.i18n('banUserConfirm'), + // onClickClose, + // // onClickOk: async () => { + // // if (!conversation) { + // // window.log.info('cannot ban user, the corresponding conversation was not found.'); + // // return; + // // } + // // let success = false; + // // if (isOpenGroupV2(conversation.id)) { + // // const roomInfos = await getV2OpenGroupRoom(conversation.id); + // // if (!roomInfos) { + // // window.log.warn('banUser room not found'); + // // } else { + // // success = await ApiV2.banUser(pubKeyToBan, _.pick(roomInfos, 'serverUrl', 'roomId')); + // // } + // // } else { + // // const channelAPI = await conversation.getPublicSendData(); + // // if (!channelAPI) { + // // window.log.info('cannot ban user, the corresponding channelAPI was not found.'); + // // return; + // // } + // // success = await channelAPI.banUser(userToBan); + // // } + // // if (success) { + // // ToastUtils.pushUserBanSuccess(); + // // } else { + // // ToastUtils.pushUserBanFailure(); + // // } + // // }, + // } + + // dispatch(updateConfirmModal(confirmationModalProps)); } /** @@ -70,31 +82,65 @@ export function unbanUser(userToUnBan: string, conversation?: ConversationModel) ToastUtils.pushUserBanFailure(); return; } - window.confirmationDialog({ - title: window.i18n('unbanUser'), - message: window.i18n('unbanUserConfirm'), - resolve: async () => { - if (!conversation) { - // double check here. the convo might have been removed since the dialog was opened - window.log.info('cannot unban user, the corresponding conversation was not found.'); - return; - } - let success = false; - if (isOpenGroupV2(conversation.id)) { - const roomInfos = await getV2OpenGroupRoom(conversation.id); - if (!roomInfos) { - window.log.warn('unbanUser room not found'); - } else { - success = await ApiV2.unbanUser(pubKeyToUnban, _.pick(roomInfos, 'serverUrl', 'roomId')); - } - } - if (success) { - ToastUtils.pushUserUnbanSuccess(); + + + const dispatch = useDispatch(); + const onClickClose = () => dispatch(updateConfirmModal(null)); + + const onClickOk = async () => { + if (!conversation) { + // double check here. the convo might have been removed since the dialog was opened + window.log.info('cannot unban user, the corresponding conversation was not found.'); + return; + } + let success = false; + if (isOpenGroupV2(conversation.id)) { + const roomInfos = await getV2OpenGroupRoom(conversation.id); + if (!roomInfos) { + window.log.warn('unbanUser room not found'); } else { - ToastUtils.pushUserUnbanFailure(); + success = await ApiV2.unbanUser(pubKeyToUnban, _.pick(roomInfos, 'serverUrl', 'roomId')); } - }, - }); + } + if (success) { + ToastUtils.pushUserUnbanSuccess(); + } else { + ToastUtils.pushUserUnbanFailure(); + } + } + + dispatch(updateConfirmModal({ + title: window.i18n('unbanUser'), + message: window.i18n('unbanUserConfirm'), + onClickOk, + onClickClose, + })); + + // window.confirmationDialog({ + // title: window.i18n('unbanUser'), + // message: window.i18n('unbanUserConfirm'), + // resolve: async () => { + // if (!conversation) { + // // double check here. the convo might have been removed since the dialog was opened + // window.log.info('cannot unban user, the corresponding conversation was not found.'); + // return; + // } + // let success = false; + // if (isOpenGroupV2(conversation.id)) { + // const roomInfos = await getV2OpenGroupRoom(conversation.id); + // if (!roomInfos) { + // window.log.warn('unbanUser room not found'); + // } else { + // success = await ApiV2.unbanUser(pubKeyToUnban, _.pick(roomInfos, 'serverUrl', 'roomId')); + // } + // } + // if (success) { + // ToastUtils.pushUserUnbanSuccess(); + // } else { + // ToastUtils.pushUserUnbanFailure(); + // } + // }, + // }); } export function copyBodyToClipboard(body?: string) { @@ -242,11 +288,27 @@ async function acceptOpenGroupInvitationV1(serverAddress: string) { } const acceptOpenGroupInvitationV2 = (completeUrl: string, roomName?: string) => { - window.confirmationDialog({ + + const dispatch = useDispatch(); + + const onClickClose = () => { + dispatch(updateConfirmModal(null)) + }; + + dispatch(updateConfirmModal({ title: window.i18n('joinOpenGroupAfterInvitationConfirmationTitle', roomName), message: window.i18n('joinOpenGroupAfterInvitationConfirmationDesc', roomName), - resolve: () => joinOpenGroupV2WithUIEvents(completeUrl, true), - }); + onClickOk: () => joinOpenGroupV2WithUIEvents(completeUrl, true), + onClickClose + })) + + // window.confirmationDialog({ + // title: window.i18n('joinOpenGroupAfterInvitationConfirmationTitle', roomName), + // message: window.i18n('joinOpenGroupAfterInvitationConfirmationDesc', roomName), + // resolve: () => joinOpenGroupV2WithUIEvents(completeUrl, true), + // }); + + // this function does not throw, and will showToasts if anything happens }; diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts index c8c740efd..8f12aba19 100644 --- a/ts/models/conversation.ts +++ b/ts/models/conversation.ts @@ -42,6 +42,8 @@ import { ConversationInteraction } from '../interactions'; import { OpenGroupVisibleMessage } from '../session/messages/outgoing/visibleMessage/OpenGroupVisibleMessage'; import { OpenGroupRequestCommonType } from '../opengroup/opengroupV2/ApiUtil'; import { getOpenGroupV2FromConversationId } from '../opengroup/utils/OpenGroupUtils'; +import { useDispatch } from 'react-redux'; +import { updateConfirmModal } from '../state/ducks/modalDialog'; export enum ConversationTypeEnum { GROUP = 'group', @@ -421,7 +423,6 @@ export class ConversationModel extends Backbone.Model { onBlockContact: this.block, onUnblockContact: this.unblock, onCopyPublicKey: this.copyPublicKey, - onDeleteContact: this.deleteContact, onClearNickname: this.clearNickname, onDeleteMessages: this.deleteMessages, onLeaveGroup: () => { @@ -1309,23 +1310,23 @@ export class ConversationModel extends Backbone.Model { void this.setNickname(''); }; - public deleteContact() { - let title = window.i18n('delete'); - let message = window.i18n('deleteContactConfirmation'); + // public deleteContact() { + // let title = window.i18n('delete'); + // let message = window.i18n('deleteContactConfirmation'); - if (this.isGroup()) { - title = window.i18n('leaveGroup'); - message = window.i18n('leaveGroupConfirmation'); - } + // if (this.isGroup()) { + // title = window.i18n('leaveGroup'); + // message = window.i18n('leaveGroupConfirmation'); + // } - window.confirmationDialog({ - title, - message, - resolve: () => { - void ConversationController.getInstance().deleteContact(this.id); - }, - }); - } + // window.confirmationDialog({ + // title, + // message, + // resolve: () => { + // void ConversationController.getInstance().deleteContact(this.id); + // }, + // }); + // } public async removeMessage(messageId: any) { await dataRemoveMessage(messageId); @@ -1351,7 +1352,14 @@ export class ConversationModel extends Backbone.Model { }; } - window.confirmationDialog(params); + // window.confirmationDialog(params); + + const dispatch = useDispatch() + dispatch(updateConfirmModal({ + title: window.i18n('deleteMessages'), + message: window.i18n('deleteConversationConfirmation'), + onClickOk: () => this.destroyMessages(), + })) } public async destroyMessages() { diff --git a/ts/receiver/closedGroups.tsx b/ts/receiver/closedGroups.ts similarity index 94% rename from ts/receiver/closedGroups.tsx rename to ts/receiver/closedGroups.ts index e757ce684..ff5743e44 100644 --- a/ts/receiver/closedGroups.tsx +++ b/ts/receiver/closedGroups.ts @@ -1,4 +1,3 @@ -import React from 'react'; import { SignalService } from '../protobuf'; import { removeFromCache } from './cache'; import { EnvelopePlus } from './types'; @@ -36,7 +35,10 @@ import { queueAllCachedFromSource } from './receiver'; import { actions as conversationActions } from '../state/ducks/conversations'; import { SwarmPolling } from '../session/snode_api/swarmPolling'; import { MessageModel } from '../models/message'; -import { SessionConfirm } from '../components/session/SessionConfirm'; + +import { useSelector, useDispatch } from "react-redux"; +import { updateConfirmModal } from "../state/ducks/modalDialog"; + export const distributingClosedGroupEncryptionKeyPairs = new Map(); @@ -838,7 +840,7 @@ async function handleClosedGroupEncryptionKeyPairRequest( return removeFromCache(envelope); } -export async function createClosedGroup(groupName: string, members: Array, setModal: any) { +export async function createClosedGroup(groupName: string, members: Array) { const setOfMembers = new Set(members); const ourNumber = UserUtils.getOurPubKeyFromCache(); @@ -894,7 +896,6 @@ export async function createClosedGroup(groupName: string, members: Array, encryptionKeyPair: ECKeyPair, dbMessage: MessageModel, - setModal: any, isRetry: boolean = false, ): Promise { const promises = createInvitePromises( @@ -945,35 +945,34 @@ async function sendToGroupMembers( const inviteResults = await Promise.all(promises); const allInvitesSent = _.every(inviteResults, Boolean); + // const dispatch = useDispatch(); + + // window.inboxStore?.dispatch(updateConfirmModal({ + // title: 'hi' + // })) + + console.log('@@@@', inviteResults); + if (allInvitesSent) { + // if (true) { if (isRetry) { const invitesTitle = inviteResults.length > 1 ? window.i18n('closedGroupInviteSuccessTitlePlural') : window.i18n('closedGroupInviteSuccessTitle'); - // setModal() - - setModal( - ) - ) - - // window.confirmationDialog({ - // title: invitesTitle, - // message: window.i18n('closedGroupInviteSuccessMessage'), - // }); + window.inboxStore?.dispatch(updateConfirmModal({ + title: invitesTitle, + message: window.i18n('closedGroupInviteSuccessMessage'), + hideCancel: true + })); } return allInvitesSent; } else { // Confirmation dialog that recursively calls sendToGroupMembers on resolve - window.confirmationDialog({ + + window.inboxStore?.dispatch(updateConfirmModal({ title: inviteResults.length > 1 ? window.i18n('closedGroupInviteFailTitlePlural') @@ -983,7 +982,7 @@ async function sendToGroupMembers( ? window.i18n('closedGroupInviteFailMessagePlural') : window.i18n('closedGroupInviteFailMessage'), okText: window.i18n('closedGroupInviteOkText'), - resolve: async () => { + onClickOk: async () => { const membersToResend: Array = new Array(); inviteResults.forEach((result, index) => { const member = listOfMembers[index]; @@ -1001,12 +1000,45 @@ async function sendToGroupMembers( admins, encryptionKeyPair, dbMessage, - setModal, isRetrySend ); } }, - }); + })); + + // window.confirmationDialog({ + // title: + // inviteResults.length > 1 + // ? window.i18n('closedGroupInviteFailTitlePlural') + // : window.i18n('closedGroupInviteFailTitle'), + // message: + // inviteResults.length > 1 + // ? window.i18n('closedGroupInviteFailMessagePlural') + // : window.i18n('closedGroupInviteFailMessage'), + // okText: window.i18n('closedGroupInviteOkText'), + // resolve: async () => { + // const membersToResend: Array = new Array(); + // inviteResults.forEach((result, index) => { + // const member = listOfMembers[index]; + // // group invite must always contain the admin member. + // if (result !== true || admins.includes(member)) { + // membersToResend.push(member); + // } + // }); + // if (membersToResend.length > 0) { + // const isRetrySend = true; + // await sendToGroupMembers( + // membersToResend, + // groupPublicKey, + // groupName, + // admins, + // encryptionKeyPair, + // dbMessage, + // isRetrySend + // ); + // } + // }, + // }); } return allInvitesSent; } diff --git a/ts/state/actions.ts b/ts/state/actions.ts index 38f141d45..60ea47a73 100644 --- a/ts/state/actions.ts +++ b/ts/state/actions.ts @@ -5,6 +5,7 @@ import { actions as conversations } from './ducks/conversations'; import { actions as user } from './ducks/user'; import { actions as sections } from './ducks/section'; import { actions as theme } from './ducks/theme'; +import { actions as modalDialog } from './ducks/modalDialog'; export function mapDispatchToProps(dispatch: Dispatch): Object { return { @@ -15,6 +16,7 @@ export function mapDispatchToProps(dispatch: Dispatch): Object { ...user, ...theme, ...sections, + ...modalDialog }, dispatch ), diff --git a/ts/state/ducks/modalDialog.tsx b/ts/state/ducks/modalDialog.tsx new file mode 100644 index 000000000..2acd8891a --- /dev/null +++ b/ts/state/ducks/modalDialog.tsx @@ -0,0 +1,21 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { SessionConfirmDialogProps } from '../../components/session/SessionConfirm'; + +export type ConfirmModalState = SessionConfirmDialogProps | null; + +const initialState: ConfirmModalState = null as ConfirmModalState; + +const confirmModalSlice = createSlice({ + name: 'confirmModal', + initialState, + reducers: { + updateConfirmModal(state, action: PayloadAction) { + state = action.payload; + return action.payload; + } + } +}) + +export const { actions, reducer } = confirmModalSlice; +export const { updateConfirmModal } = actions; +export const confirmModalReducer = reducer; \ No newline at end of file diff --git a/ts/state/ducks/onion.tsx b/ts/state/ducks/onion.tsx index 06ad4c978..3a5e8a3cf 100644 --- a/ts/state/ducks/onion.tsx +++ b/ts/state/ducks/onion.tsx @@ -1,17 +1,10 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import _, { forEach } from 'underscore'; import { SnodePath, Snode } from '../../session/onions/index'; export type OnionState = { - // nodes: Array; - // path: SnodePath; snodePath: SnodePath; }; -// const initialState: OnionState = { -// // nodes: new Array(), -// nodes: new Array(), -// }; const initialState = { snodePath: { @@ -32,8 +25,6 @@ const onionSlice = createSlice({ let isEqual = JSON.stringify(state, null, 2) == JSON.stringify(newPayload, null, 2); return isEqual ? state : newPayload; - - return newPayload; }, }, }); diff --git a/ts/state/reducer.ts b/ts/state/reducer.ts index 7aecd3f55..c16ac79d3 100644 --- a/ts/state/reducer.ts +++ b/ts/state/reducer.ts @@ -6,8 +6,8 @@ import { reducer as user, UserStateType } from './ducks/user'; import { reducer as theme, ThemeStateType } from './ducks/theme'; import { reducer as section, SectionStateType } from './ducks/section'; import { defaultRoomReducer as defaultRooms, DefaultRoomsState } from './ducks/defaultRooms'; - import { defaultOnionReducer as onionPaths, OnionState } from './ducks/onion'; +import { confirmModalReducer as confirmModal, ConfirmModalState } from "./ducks/modalDialog"; export type StateType = { search: SearchStateType; @@ -19,6 +19,9 @@ export type StateType = { defaultRooms: DefaultRoomsState; onionPaths: OnionState; + + confirmModal: ConfirmModalState + // modalState: ConfirmModalState }; export const reducers = { @@ -31,8 +34,8 @@ export const reducers = { theme, section, defaultRooms, - onionPaths, + confirmModal }; // Making this work would require that our reducer signature supported AnyAction, not diff --git a/ts/state/smart/SessionConversation.tsx b/ts/state/smart/SessionConversation.tsx index b05332ee0..e39ce97a6 100644 --- a/ts/state/smart/SessionConversation.tsx +++ b/ts/state/smart/SessionConversation.tsx @@ -17,6 +17,7 @@ const mapStateToProps = (state: StateType) => { theme: getTheme(state), messages: getMessagesOfSelectedConversation(state), ourNumber: getOurNumber(state), + confirmModal: (state: StateType) => {state.confirmModal} }; }; diff --git a/ts/window.d.ts b/ts/window.d.ts index 3309f1a17..0c1afa41a 100644 --- a/ts/window.d.ts +++ b/ts/window.d.ts @@ -96,5 +96,11 @@ declare global { darkTheme: DefaultTheme; LokiPushNotificationServer: any; LokiPushNotificationServerApi: any; + + confirmationDialog: any; } + + // window.confirmationDialog = () => { + // console.log("confirmation dialog stub called"); + // } }