diff --git a/js/modules/signal.js b/js/modules/signal.js index bd791f1bf..8cafdcf88 100644 --- a/js/modules/signal.js +++ b/js/modules/signal.js @@ -67,9 +67,6 @@ const { const { createCompositionArea, } = require('../../ts/state/roots/createCompositionArea'); -const { - createContactModal, -} = require('../../ts/state/roots/createContactModal'); const { createConversationDetails, } = require('../../ts/state/roots/createConversationDetails'); @@ -363,7 +360,6 @@ exports.setup = (options = {}) => { createApp, createChatColorPicker, createCompositionArea, - createContactModal, createConversationDetails, createConversationHeader, createForwardMessageModal, diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss index fd0447136..198132543 100644 --- a/stylesheets/_modules.scss +++ b/stylesheets/_modules.scss @@ -8812,213 +8812,6 @@ button.module-image__border-overlay:focus { } } -// Module: Group Contact Details - -$contact-modal-padding: 18px; -.module-contact-modal { - @include font-body-2; - - min-width: 280px; - padding: $contact-modal-padding; - border-radius: 8px; - overflow: hidden; - @include popper-shadow(); - - position: relative; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - - @include light-theme() { - background: $color-white; - color: $color-gray-90; - } - - @include dark-theme() { - background: $color-gray-75; - color: $color-gray-05; - } - - &__overlay { - background: $color-black-alpha-40; - position: fixed; - left: 0; - top: 0; - width: 100vw; - height: 100vh; - display: flex; - justify-content: center; - align-items: center; - z-index: 5; - } -} - -.module-contact-modal__name { - @include font-title-2; - - margin-top: 6px; -} - -.module-contact-modal__info { - text-align: center; - max-width: 248px; - margin-top: 8px; - - @include light-theme { - color: $color-gray-60; - } - @include dark-theme { - color: $color-gray-25; - } -} - -.module-contact-modal__button-container { - display: flex; - flex-direction: column; - align-items: flex-start; - margin: 12px 0 15px -$contact-modal-padding; - width: calc(100% + (#{$contact-modal-padding} * 2)); -} - -.module-contact-modal__button { - @include button-reset; - - display: flex; - align-items: center; - padding: 7px $contact-modal-padding; - width: 100%; - - &:last-child { - margin-bottom: 0; - } - - &:hover { - background-color: $color-gray-15; - - @include dark-theme { - background-color: $color-gray-60; - } - } - - &:focus { - @include keyboard-mode { - background-color: $color-gray-15; - } - - @include dark-keyboard-mode { - background-color: $color-gray-60; - } - } -} - -.module-contact-modal__bubble-icon { - display: flex; - justify-content: center; - align-items: center; - margin-right: 10px; - width: 20px; -} - -.module-contact-modal__send-message__bubble-icon { - height: 16px; - width: 18px; - - @include light-theme { - @include color-svg( - '../images/icons/v2/message-outline-24.svg', - $color-gray-75 - ); - } - - @include dark-theme { - @include color-svg( - '../images/icons/v2/message-outline-24.svg', - $color-gray-15 - ); - } -} - -.module-contact-modal__safety-number__bubble-icon { - height: 18px; - width: 17px; - - @include light-theme { - @include color-svg( - '../images/icons/v2/safety-number-outline-24.svg', - $color-gray-75 - ); - } - - @include dark-theme { - @include color-svg( - '../images/icons/v2/safety-number-outline-24.svg', - $color-gray-15 - ); - } -} - -.module-contact-modal__make-admin__bubble-icon { - height: 16px; - width: 18px; - - @include light-theme { - @include color-svg( - '../images/icons/v2/group-outline-24.svg', - $color-gray-75 - ); - } - - @include dark-theme { - @include color-svg( - '../images/icons/v2/group-outline-24.svg', - $color-gray-15 - ); - } -} - -.module-contact-modal__remove-from-group__bubble-icon { - height: 16px; - width: 16px; - - @include light-theme { - @include color-svg( - '../images/icons/v2/leave-group-outline-16.svg', - $color-gray-75 - ); - } - - @include dark-theme { - @include color-svg( - '../images/icons/v2/leave-group-outline-16.svg', - $color-gray-15 - ); - } -} - -.module-contact-modal__close-button { - @include button-reset; - - position: absolute; - top: 10px; - right: 12px; - - width: 24px; - height: 24px; - - @include color-svg('../images/icons/v2/x-24.svg', $color-gray-75); - - @include dark-theme() { - @include color-svg('../images/icons/v2/x-24.svg', $color-gray-25); - } - - &:focus { - @include keyboard-mode { - background-color: $color-ultramarine; - } - } -} - .module-background-color { &__default { background-color: $color-black-alpha-40; diff --git a/stylesheets/components/ContactModal.scss b/stylesheets/components/ContactModal.scss new file mode 100644 index 000000000..c5cdf8786 --- /dev/null +++ b/stylesheets/components/ContactModal.scss @@ -0,0 +1,148 @@ +// Copyright 2021 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +.ContactModal { + align-items: center; + display: flex; + flex-direction: column; + justify-content: center; + + &__name { + @include font-title-2; + margin-top: 6px; + } + + &__info { + text-align: center; + max-width: 248px; + margin-top: 8px; + } + + &__button-container { + display: flex; + flex-direction: column; + align-items: flex-start; + margin-top: 12px; + width: 100%; + } + + &__button { + @include button-reset; + + display: flex; + align-items: center; + padding: 7px 16px; + width: 100%; + + &:last-child { + margin-bottom: 0; + } + + &:hover { + background-color: $color-gray-02; + + @include dark-theme { + background-color: $color-gray-80; + } + } + + &:focus { + @include keyboard-mode { + background-color: $color-gray-02; + } + + @include dark-keyboard-mode { + background-color: $color-gray-80; + } + } + } + + &__bubble-icon { + display: flex; + justify-content: center; + align-items: center; + margin-right: 10px; + width: 20px; + } + + &__send-message__bubble-icon { + height: 16px; + width: 18px; + + @include light-theme { + @include color-svg( + '../images/icons/v2/message-outline-24.svg', + $color-gray-75 + ); + } + + @include dark-theme { + @include color-svg( + '../images/icons/v2/message-outline-24.svg', + $color-gray-15 + ); + } + } + + &__safety-number__bubble-icon { + height: 18px; + width: 17px; + + @include light-theme { + @include color-svg( + '../images/icons/v2/safety-number-outline-24.svg', + $color-gray-75 + ); + } + + @include dark-theme { + @include color-svg( + '../images/icons/v2/safety-number-outline-24.svg', + $color-gray-15 + ); + } + } + + &__make-admin__bubble-icon { + height: 16px; + width: 18px; + + @include light-theme { + @include color-svg( + '../images/icons/v2/group-outline-24.svg', + $color-gray-75 + ); + } + + @include dark-theme { + @include color-svg( + '../images/icons/v2/group-outline-24.svg', + $color-gray-15 + ); + } + } + + &__remove-from-group__bubble-icon { + height: 16px; + width: 16px; + + @include light-theme { + @include color-svg( + '../images/icons/v2/leave-group-outline-16.svg', + $color-gray-75 + ); + } + + @include dark-theme { + @include color-svg( + '../images/icons/v2/leave-group-outline-16.svg', + $color-gray-15 + ); + } + } +} + +.module-Modal.ContactModal__modal .ContactModal__modal__body { + padding-left: 0; + padding-right: 0; +} diff --git a/stylesheets/components/Modal.scss b/stylesheets/components/Modal.scss index d5ced0711..e5ab57989 100644 --- a/stylesheets/components/Modal.scss +++ b/stylesheets/components/Modal.scss @@ -87,7 +87,7 @@ padding: 0 16px 16px 16px; border-top: 1px solid transparent; // If there's a header, just the body scrolls - overflow-y: scroll; // scroll so that the padding is always there + overflow-y: overlay; overflow-x: auto; &--scrolled { @@ -105,7 +105,7 @@ &--no-header { padding: 16px; // If there's no header, the whole thing scrolls - overflow-y: scroll; // scroll so that the padding is always there + overflow-y: overlay; overflow-x: auto; } diff --git a/stylesheets/manifest.scss b/stylesheets/manifest.scss index 177535239..0d6ea0e92 100644 --- a/stylesheets/manifest.scss +++ b/stylesheets/manifest.scss @@ -45,6 +45,7 @@ @import './components/ChatColorPicker.scss'; @import './components/Checkbox.scss'; @import './components/CompositionArea.scss'; +@import './components/ContactModal.scss'; @import './components/ContactName.scss'; @import './components/ContactPill.scss'; @import './components/ContactPills.scss'; diff --git a/ts/components/GlobalModalContainer.tsx b/ts/components/GlobalModalContainer.tsx index c85b43745..4af93c8b9 100644 --- a/ts/components/GlobalModalContainer.tsx +++ b/ts/components/GlobalModalContainer.tsx @@ -1,17 +1,29 @@ // Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only +import { ContactModalStateType } from '../state/ducks/globalModals'; + type PropsType = { + // ContactModal + contactModalState?: ContactModalStateType; + renderContactModal: () => JSX.Element; // ProfileEditor isProfileEditorVisible: boolean; renderProfileEditor: () => JSX.Element; }; export const GlobalModalContainer = ({ + // ContactModal + contactModalState, + renderContactModal, // ProfileEditor isProfileEditorVisible, renderProfileEditor, }: PropsType): JSX.Element | null => { + if (contactModalState) { + return renderContactModal(); + } + if (isProfileEditorVisible) { return renderProfileEditor(); } diff --git a/ts/components/conversation/ContactModal.stories.tsx b/ts/components/conversation/ContactModal.stories.tsx index c0ef57733..b02a1d746 100644 --- a/ts/components/conversation/ContactModal.stories.tsx +++ b/ts/components/conversation/ContactModal.stories.tsx @@ -28,15 +28,17 @@ const defaultContact: ConversationType = getDefaultConversation({ const createProps = (overrideProps: Partial = {}): PropsType => ({ areWeAdmin: boolean('areWeAdmin', overrideProps.areWeAdmin || false), contact: overrideProps.contact || defaultContact, + hideContactModal: action('hideContactModal'), i18n, isAdmin: boolean('isAdmin', overrideProps.isAdmin || false), isMember: boolean('isMember', overrideProps.isMember || true), - onClose: action('onClose'), - openConversation: action('openConversation'), - removeMember: action('removeMember'), - showSafetyNumber: action('showSafetyNumber'), + openConversationInternal: action('openConversationInternal'), + removeMemberFromGroup: action('removeMemberFromGroup'), + showSafetyNumberInConversation: action('showSafetyNumberInConversation'), toggleAdmin: action('toggleAdmin'), - updateSharedGroups: action('updateSharedGroups'), + updateConversationModelSharedGroups: action( + 'updateConversationModelSharedGroups' + ), }); story.add('As non-admin', () => { diff --git a/ts/components/conversation/ContactModal.tsx b/ts/components/conversation/ContactModal.tsx index 1ae8507dc..58916794e 100644 --- a/ts/components/conversation/ContactModal.tsx +++ b/ts/components/conversation/ContactModal.tsx @@ -1,103 +1,73 @@ // Copyright 2020-2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import React, { ReactPortal, useEffect, useRef, useState } from 'react'; -import { createPortal } from 'react-dom'; +import React, { useEffect, useState } from 'react'; import { About } from './About'; import { Avatar } from '../Avatar'; import { AvatarLightbox } from '../AvatarLightbox'; import { ConversationType } from '../../state/ducks/conversations'; +import { Modal } from '../Modal'; import { LocalizerType } from '../../types/Util'; import { SharedGroupNames } from '../SharedGroupNames'; +import { ConfirmationDialog } from '../ConfirmationDialog'; -export type PropsType = { +export type PropsDataType = { areWeAdmin: boolean; contact?: ConversationType; + conversationId?: string; readonly i18n: LocalizerType; isAdmin: boolean; isMember: boolean; - onClose: () => void; - openConversation: (conversationId: string) => void; - removeMember: (conversationId: string) => void; - showSafetyNumber: (conversationId: string) => void; - toggleAdmin: (conversationId: string) => void; - updateSharedGroups: () => void; }; +type PropsActionType = { + hideContactModal: () => void; + openConversationInternal: ( + options: Readonly<{ + conversationId: string; + messageId?: string; + switchToAssociatedView?: boolean; + }> + ) => void; + removeMemberFromGroup: (conversationId: string, contactId: string) => void; + showSafetyNumberInConversation: (conversationId: string) => void; + toggleAdmin: (conversationId: string, contactId: string) => void; + updateConversationModelSharedGroups: (conversationId: string) => void; +}; + +export type PropsType = PropsDataType & PropsActionType; + export const ContactModal = ({ areWeAdmin, contact, + conversationId, + hideContactModal, i18n, isAdmin, isMember, - onClose, - openConversation, - removeMember, - showSafetyNumber, + openConversationInternal, + removeMemberFromGroup, + showSafetyNumberInConversation, toggleAdmin, - updateSharedGroups, -}: PropsType): ReactPortal | null => { + updateConversationModelSharedGroups, +}: PropsType): JSX.Element => { if (!contact) { throw new Error('Contact modal opened without a matching contact'); } - const [root, setRoot] = useState(null); - const overlayRef = useRef(null); - const closeButtonRef = useRef(null); - const [showingAvatar, setShowingAvatar] = useState(false); + const [confirmToggleAdmin, setConfirmToggleAdmin] = useState(false); useEffect(() => { - const div = document.createElement('div'); - document.body.appendChild(div); - setRoot(div); - - return () => { - document.body.removeChild(div); - setRoot(null); - }; - }, []); - - useEffect(() => { - // Kick off the expensive hydration of the current sharedGroupNames - updateSharedGroups(); - }, [updateSharedGroups]); - - useEffect(() => { - if (root !== null && closeButtonRef.current) { - closeButtonRef.current.focus(); + if (conversationId) { + // Kick off the expensive hydration of the current sharedGroupNames + updateConversationModelSharedGroups(conversationId); } - }, [root]); + }, [conversationId, updateConversationModelSharedGroups]); - useEffect(() => { - const handler = (event: KeyboardEvent) => { - if (event.key === 'Escape') { - event.preventDefault(); - event.stopPropagation(); - - onClose(); - } - }; - document.addEventListener('keyup', handler); - - return () => { - document.removeEventListener('keyup', handler); - }; - }, [onClose]); - - const onClickOverlay = (e: React.MouseEvent) => { - if (e.target === overlayRef.current) { - e.preventDefault(); - e.stopPropagation(); - - onClose(); - } - }; - - let content: JSX.Element; if (showingAvatar) { - content = ( + return ( setShowingAvatar(false)} /> ); - } else { - content = ( -
- {!contact.isMe && ( )} - {!contact.isMe && areWeAdmin && isMember && ( + {!contact.isMe && areWeAdmin && isMember && conversationId && ( <> )}
+ {confirmToggleAdmin && conversationId && ( + toggleAdmin(conversationId, contact.id), + text: isAdmin + ? i18n('ContactModal--rm-admin') + : i18n('ContactModal--make-admin'), + }, + ]} + i18n={i18n} + onClose={() => setConfirmToggleAdmin(false)} + > + {isAdmin + ? i18n('ContactModal--rm-admin-info', [contact.title]) + : i18n('ContactModal--make-admin-info', [contact.title])} + + )} - ); - } - - return root - ? createPortal( -
{ - overlayRef.current = ref; - }} - role="presentation" - className="module-contact-modal__overlay" - onClick={onClickOverlay} - > - {content} -
, - root - ) - : null; + + ); }; diff --git a/ts/components/conversation/conversation-details/ConversationDetails.tsx b/ts/components/conversation/conversation-details/ConversationDetails.tsx index 6743a8437..4882f3efc 100644 --- a/ts/components/conversation/conversation-details/ConversationDetails.tsx +++ b/ts/components/conversation/conversation-details/ConversationDetails.tsx @@ -60,7 +60,6 @@ export type StateProps = { pendingMemberships: ReadonlyArray; setDisappearingMessages: (seconds: number) => void; showAllMedia: () => void; - showContactModal: (conversationId: string) => void; showGroupChatColorEditor: () => void; showGroupLinkManagement: () => void; showGroupV2Permissions: () => void; @@ -86,6 +85,7 @@ type ActionProps = { deleteAvatarFromDisk: DeleteAvatarFromDiskActionType; replaceAvatar: ReplaceAvatarActionType; saveAvatarToDisk: SaveAvatarToDiskActionType; + showContactModal: (contactId: string, conversationId: string) => void; }; export type Props = StateProps & ActionProps; @@ -329,6 +329,7 @@ export const ConversationDetails: React.ComponentType = ({ ): Props => ({ canAddNewMembers: isBoolean(overrideProps.canAddNewMembers) ? overrideProps.canAddNewMembers : false, + conversationId: '123', i18n, memberships: overrideProps.memberships || [], showContactModal: action('showContactModal'), diff --git a/ts/components/conversation/conversation-details/ConversationDetailsMembershipList.tsx b/ts/components/conversation/conversation-details/ConversationDetailsMembershipList.tsx index 05703a13c..2502cc6cf 100644 --- a/ts/components/conversation/conversation-details/ConversationDetailsMembershipList.tsx +++ b/ts/components/conversation/conversation-details/ConversationDetailsMembershipList.tsx @@ -19,10 +19,11 @@ export type GroupV2Membership = { export type Props = { canAddNewMembers: boolean; + conversationId: string; i18n: LocalizerType; maxShownMemberCount?: number; memberships: Array; - showContactModal: (conversationId: string) => void; + showContactModal: (contactId: string, conversationId: string) => void; startAddingNewMembers?: () => void; }; @@ -67,6 +68,7 @@ function sortMemberships( export const ConversationDetailsMembershipList: React.ComponentType = ({ canAddNewMembers, + conversationId, i18n, maxShownMemberCount = 5, memberships, @@ -101,7 +103,7 @@ export const ConversationDetailsMembershipList: React.ComponentType = ({ {sortedMemberships.slice(0, membersToShow).map(({ isAdmin, member }) => ( showContactModal(member.id)} + onClick={() => showContactModal(member.id, conversationId)} icon={ { + return dispatch => { + const conversationModel = window.ConversationController.get(conversationId); + if (conversationModel) { + const idForLogging = conversationModel.idForLogging(); + longRunningTaskWrapper({ + name: 'removeMemberFromGroup', + idForLogging, + task: () => conversationModel.removeFromGroupV2(contactId), + }); + } + dispatch({ + type: 'NOOP', + payload: null, + }); + }; +} + +function toggleAdmin( + conversationId: string, + contactId: string +): ThunkAction { + return dispatch => { + const conversationModel = window.ConversationController.get(conversationId); + if (conversationModel) { + conversationModel.toggleAdmin(contactId); + } + dispatch({ + type: 'NOOP', + payload: null, + }); + }; +} + +function updateConversationModelSharedGroups( + conversationId: string +): ThunkAction { + return dispatch => { + const conversation = window.ConversationController.get(conversationId); + if (conversation && conversation.throttledUpdateSharedGroups) { + conversation.throttledUpdateSharedGroups(); + } + dispatch({ + type: 'NOOP', + payload: null, + }); + }; +} + +function showSafetyNumberInConversation( + conversationId: string +): ThunkAction { + return dispatch => { + window.Whisper.events.trigger( + 'showSafetyNumberInConversation', + conversationId + ); + dispatch({ + type: 'NOOP', + payload: null, + }); + }; +} + function showInbox(): ShowInboxActionType { return { type: 'SHOW_INBOX', diff --git a/ts/state/ducks/globalModals.ts b/ts/state/ducks/globalModals.ts index 451d80581..4a2bb6522 100644 --- a/ts/state/ducks/globalModals.ts +++ b/ts/state/ducks/globalModals.ts @@ -4,16 +4,33 @@ // State export type GlobalModalsStateType = { + readonly contactModalState?: ContactModalStateType; readonly isProfileEditorVisible: boolean; readonly profileEditorHasError: boolean; }; // Actions +const HIDE_CONTACT_MODAL = 'globalModals/HIDE_CONTACT_MODAL'; +const SHOW_CONTACT_MODAL = 'globalModals/SHOW_CONTACT_MODAL'; const TOGGLE_PROFILE_EDITOR = 'globalModals/TOGGLE_PROFILE_EDITOR'; export const TOGGLE_PROFILE_EDITOR_ERROR = 'globalModals/TOGGLE_PROFILE_EDITOR_ERROR'; +export type ContactModalStateType = { + contactId: string; + conversationId?: string; +}; + +type HideContactModalActionType = { + type: typeof HIDE_CONTACT_MODAL; +}; + +type ShowContactModalActionType = { + type: typeof SHOW_CONTACT_MODAL; + payload: ContactModalStateType; +}; + type ToggleProfileEditorActionType = { type: typeof TOGGLE_PROFILE_EDITOR; }; @@ -23,16 +40,39 @@ export type ToggleProfileEditorErrorActionType = { }; export type GlobalModalsActionType = + | HideContactModalActionType + | ShowContactModalActionType | ToggleProfileEditorActionType | ToggleProfileEditorErrorActionType; // Action Creators export const actions = { + hideContactModal, + showContactModal, toggleProfileEditor, toggleProfileEditorHasError, }; +function hideContactModal(): HideContactModalActionType { + return { + type: HIDE_CONTACT_MODAL, + }; +} + +function showContactModal( + contactId: string, + conversationId?: string +): ShowContactModalActionType { + return { + type: SHOW_CONTACT_MODAL, + payload: { + contactId, + conversationId, + }, + }; +} + function toggleProfileEditor(): ToggleProfileEditorActionType { return { type: TOGGLE_PROFILE_EDITOR }; } @@ -68,5 +108,19 @@ export function reducer( }; } + if (action.type === SHOW_CONTACT_MODAL) { + return { + ...state, + contactModalState: action.payload, + }; + } + + if (action.type === HIDE_CONTACT_MODAL) { + return { + ...state, + contactModalState: undefined, + }; + } + return state; } diff --git a/ts/state/roots/createContactModal.tsx b/ts/state/roots/createContactModal.tsx deleted file mode 100644 index 91cb9aa6d..000000000 --- a/ts/state/roots/createContactModal.tsx +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2020 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only - -import React from 'react'; -import { Provider } from 'react-redux'; - -import { Store } from 'redux'; - -import { - SmartContactModal, - SmartContactModalProps, -} from '../smart/ContactModal'; - -export const createContactModal = ( - store: Store, - props: SmartContactModalProps -): React.ReactElement => ( - - - -); diff --git a/ts/state/smart/ContactModal.tsx b/ts/state/smart/ContactModal.tsx index a6df2d5fd..97fcbfd11 100644 --- a/ts/state/smart/ContactModal.tsx +++ b/ts/state/smart/ContactModal.tsx @@ -5,33 +5,18 @@ import { connect } from 'react-redux'; import { mapDispatchToProps } from '../actions'; import { ContactModal, - PropsType, + PropsDataType, } from '../../components/conversation/ContactModal'; import { StateType } from '../reducer'; import { getIntl } from '../selectors/user'; import { getConversationSelector } from '../selectors/conversations'; -export type SmartContactModalProps = { - contactId: string; - currentConversationId: string; - readonly onClose: () => unknown; - readonly openConversation: (conversationId: string) => void; - readonly removeMember: (conversationId: string) => void; - readonly showSafetyNumber: (conversationId: string) => void; - readonly toggleAdmin: (conversationId: string) => void; - readonly updateSharedGroups: () => void; -}; +const mapStateToProps = (state: StateType): PropsDataType => { + const { contactId, conversationId } = + state.globalModals.contactModalState || {}; -const mapStateToProps = ( - state: StateType, - props: SmartContactModalProps -): PropsType => { - const { contactId, currentConversationId } = props; - - const currentConversation = getConversationSelector(state)( - currentConversationId - ); + const currentConversation = getConversationSelector(state)(conversationId); const contact = getConversationSelector(state)(contactId); const areWeAdmin = @@ -51,9 +36,9 @@ const mapStateToProps = ( } return { - ...props, areWeAdmin, contact, + conversationId, i18n: getIntl(state), isAdmin, isMember, diff --git a/ts/state/smart/ConversationDetails.tsx b/ts/state/smart/ConversationDetails.tsx index 856bb3660..af6e8e175 100644 --- a/ts/state/smart/ConversationDetails.tsx +++ b/ts/state/smart/ConversationDetails.tsx @@ -25,7 +25,6 @@ export type SmartConversationDetailsProps = { loadRecentMediaItems: (limit: number) => void; setDisappearingMessages: (seconds: number) => void; showAllMedia: () => void; - showContactModal: (conversationId: string) => void; showGroupChatColorEditor: () => void; showGroupLinkManagement: () => void; showGroupV2Permissions: () => void; diff --git a/ts/state/smart/GlobalModalContainer.tsx b/ts/state/smart/GlobalModalContainer.tsx index c148585f0..18be838f8 100644 --- a/ts/state/smart/GlobalModalContainer.tsx +++ b/ts/state/smart/GlobalModalContainer.tsx @@ -7,6 +7,7 @@ import { mapDispatchToProps } from '../actions'; import { GlobalModalContainer } from '../../components/GlobalModalContainer'; import { StateType } from '../reducer'; import { SmartProfileEditorModal } from './ProfileEditorModal'; +import { SmartContactModal } from './ContactModal'; const FilteredSmartProfileEditorModal = SmartProfileEditorModal; @@ -14,9 +15,14 @@ function renderProfileEditor(): JSX.Element { return ; } +function renderContactModal(): JSX.Element { + return ; +} + const mapStateToProps = (state: StateType) => { return { ...state.globalModals, + renderContactModal, renderProfileEditor, }; }; diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json index 6be041ac4..2272702c9 100644 --- a/ts/util/lint/exceptions.json +++ b/ts/util/lint/exceptions.json @@ -12717,20 +12717,6 @@ "reasonCategory": "usageTrusted", "updated": "2021-07-30T16:57:33.618Z" }, - { - "rule": "React-useRef", - "path": "ts/components/conversation/ContactModal.tsx", - "line": " const overlayRef = useRef(null);", - "reasonCategory": "usageTrusted", - "updated": "2021-08-03T21:17:38.615Z" - }, - { - "rule": "React-useRef", - "path": "ts/components/conversation/ContactModal.tsx", - "line": " const closeButtonRef = useRef(null);", - "reasonCategory": "usageTrusted", - "updated": "2021-08-03T21:17:38.615Z" - }, { "rule": "React-createRef", "path": "ts/components/conversation/ConversationHeader.js", diff --git a/ts/views/conversation_view.ts b/ts/views/conversation_view.ts index e19ca7f5f..8ea21d6b9 100644 --- a/ts/views/conversation_view.ts +++ b/ts/views/conversation_view.ts @@ -646,22 +646,6 @@ export class ConversationView extends window.Backbone.View { return this; } - getMuteExpirationLabel(): string | undefined { - const muteExpiresAt = this.model.get('muteExpiresAt'); - if (!this.model.isMuted()) { - return; - } - - const today = window.moment(Date.now()); - const expires = window.moment(muteExpiresAt); - - if (today.isSame(expires, 'day')) { - return expires.format('hh:mm A'); - } - - return expires.format('M/D/YY, hh:mm A'); - } - setMuteExpiration(ms = 0): void { this.model.setMuteExpiration( ms >= Number.MAX_SAFE_INTEGER ? ms : Date.now() + ms @@ -3298,81 +3282,7 @@ export class ConversationView extends window.Backbone.View { } showContactModal(contactId: string): void { - if (this.contactModalView) { - this.contactModalView.remove(); - this.contactModalView = undefined; - } - - this.previousFocus = document.activeElement as HTMLElement; - - const hideContactModal = () => { - if (this.contactModalView) { - this.contactModalView.remove(); - this.contactModalView = undefined; - if (this.previousFocus && this.previousFocus.focus) { - this.previousFocus.focus(); - this.previousFocus = undefined; - } - } - }; - - this.contactModalView = new Whisper.ReactWrapperView({ - JSX: window.Signal.State.Roots.createContactModal(window.reduxStore, { - contactId, - currentConversationId: this.model.id, - onClose: hideContactModal, - openConversation: (conversationId: string) => { - hideContactModal(); - this.openConversation(conversationId); - }, - removeMember: (conversationId: string) => { - hideContactModal(); - this.model.removeFromGroupV2(conversationId); - }, - showSafetyNumber: (conversationId: string) => { - hideContactModal(); - this.showSafetyNumber(conversationId); - }, - toggleAdmin: (conversationId: string) => { - hideContactModal(); - - const isAdmin = this.model.isAdmin(conversationId); - const conversationModel = window.ConversationController.get( - conversationId - ); - - if (!conversationModel) { - log.info( - 'conversation_view/toggleAdmin: Could not find conversation to toggle admin privileges' - ); - return; - } - - window.showConfirmationDialog({ - cancelText: window.i18n('cancel'), - message: isAdmin - ? window.i18n('ContactModal--rm-admin-info', [ - conversationModel.getTitle(), - ]) - : window.i18n('ContactModal--make-admin-info', [ - conversationModel.getTitle(), - ]), - okText: isAdmin - ? window.i18n('ContactModal--rm-admin') - : window.i18n('ContactModal--make-admin'), - resolve: () => this.model.toggleAdmin(conversationId), - }); - }, - updateSharedGroups: () => { - const conversation = window.ConversationController.get(contactId); - if (conversation && conversation.throttledUpdateSharedGroups) { - conversation.throttledUpdateSharedGroups(); - } - }, - }), - }); - - this.contactModalView.render(); + window.reduxActions.globalModals.showContactModal(contactId, this.model.id); } showGroupLinkManagement(): void { diff --git a/ts/views/inbox_view.ts b/ts/views/inbox_view.ts index bcb9acbbb..85fa94ee9 100644 --- a/ts/views/inbox_view.ts +++ b/ts/views/inbox_view.ts @@ -23,6 +23,9 @@ const ConversationStack = Whisper.View.extend({ model: conversation, }); this.listenTo(conversation, 'unload', () => this.onUnload(conversation)); + this.listenTo(conversation, 'showSafetyNumber', () => + view.showSafetyNumber() + ); view.$el.appendTo(this.el); if (this.lastConversation && this.lastConversation !== conversation) { @@ -119,6 +122,13 @@ Whisper.InboxView = Whisper.View.extend({ this.focusConversation(); }); + window.Whisper.events.on('showSafetyNumberInConversation', id => { + const conversation = window.ConversationController.get(id); + if (conversation) { + conversation.trigger('showSafetyNumber'); + } + }); + window.Whisper.events.on('loadingProgress', count => { const view = this.appLoadingScreen; if (view) { diff --git a/ts/window.d.ts b/ts/window.d.ts index 4934ee75a..ad8292c0a 100644 --- a/ts/window.d.ts +++ b/ts/window.d.ts @@ -43,7 +43,6 @@ import { createStore } from './state/createStore'; import { createApp } from './state/roots/createApp'; import { createChatColorPicker } from './state/roots/createChatColorPicker'; import { createCompositionArea } from './state/roots/createCompositionArea'; -import { createContactModal } from './state/roots/createContactModal'; import { createConversationDetails } from './state/roots/createConversationDetails'; import { createConversationHeader } from './state/roots/createConversationHeader'; import { createForwardMessageModal } from './state/roots/createForwardMessageModal'; @@ -424,7 +423,6 @@ declare global { createApp: typeof createApp; createChatColorPicker: typeof createChatColorPicker; createCompositionArea: typeof createCompositionArea; - createContactModal: typeof createContactModal; createConversationDetails: typeof createConversationDetails; createConversationHeader: typeof createConversationHeader; createForwardMessageModal: typeof createForwardMessageModal;