Removes some Backbone views

This commit is contained in:
Josh Perez 2021-06-17 17:15:09 -04:00 committed by GitHub
parent 93bc094342
commit 94d116c621
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 160 additions and 656 deletions

View file

@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect } from 'react';
import classNames from 'classnames';
import { AppViewType } from '../state/ducks/app';
@ -10,12 +10,16 @@ import { ThemeType } from '../types/Util';
export type PropsType = {
appView: AppViewType;
hasInitialLoadCompleted: boolean;
renderCallManager: () => JSX.Element;
renderGlobalModalContainer: () => JSX.Element;
theme: ThemeType;
};
export const App = ({
appView,
hasInitialLoadCompleted,
renderCallManager,
renderGlobalModalContainer,
theme,
}: PropsType): JSX.Element => {
let contents;
@ -28,6 +32,20 @@ export const App = ({
contents = <Inbox hasInitialLoadCompleted={hasInitialLoadCompleted} />;
}
// This is here so that themes are properly applied to anything that is
// created in a portal and exists outside of the <App /> container.
useEffect(() => {
document.body.classList.remove('light-theme');
document.body.classList.remove('dark-theme');
if (theme === ThemeType.dark) {
document.body.classList.add('dark-theme');
}
if (theme === ThemeType.light) {
document.body.classList.add('light-theme');
}
}, [theme]);
return (
<div
className={classNames({
@ -36,6 +54,8 @@ export const App = ({
'dark-theme': theme === ThemeType.dark,
})}
>
{renderGlobalModalContainer()}
{renderCallManager()}
{contents}
</div>
);

View file

@ -19,14 +19,13 @@ export type GroupV2Membership = {
export type Props = {
canAddNewMembers: boolean;
i18n: LocalizerType;
maxShownMemberCount?: number;
memberships: Array<GroupV2Membership>;
showContactModal: (conversationId: string) => void;
startAddingNewMembers: () => void;
i18n: LocalizerType;
startAddingNewMembers?: () => void;
};
const MAX_MEMBER_COUNT = 5;
const collator = new Intl.Collator(undefined, { sensitivity: 'base' });
function sortConversationTitles(
left: GroupV2Membership,
@ -68,18 +67,20 @@ function sortMemberships(
export const ConversationDetailsMembershipList: React.ComponentType<Props> = ({
canAddNewMembers,
i18n,
maxShownMemberCount = 5,
memberships,
showContactModal,
startAddingNewMembers,
i18n,
}) => {
const [showAllMembers, setShowAllMembers] = React.useState<boolean>(false);
const sortedMemberships = sortMemberships(memberships);
const shouldHideRestMembers = sortedMemberships.length - MAX_MEMBER_COUNT > 1;
const shouldHideRestMembers =
sortedMemberships.length - maxShownMemberCount > 1;
const membersToShow =
shouldHideRestMembers && !showAllMembers
? MAX_MEMBER_COUNT
? maxShownMemberCount
: sortedMemberships.length;
return (
@ -94,7 +95,7 @@ export const ConversationDetailsMembershipList: React.ComponentType<Props> = ({
<div className="module-conversation-details-membership-list__add-members-icon" />
}
label={i18n('ConversationDetailsMembershipList--add-members')}
onClick={startAddingNewMembers}
onClick={() => startAddingNewMembers?.()}
/>
)}
{sortedMemberships.slice(0, membersToShow).map(({ isAdmin, member }) => (

View file

@ -5405,23 +5405,3 @@ const sortConversationTitles = (
) => {
return collator.compare(left.getTitle(), right.getTitle());
};
// We need a custom collection here to get the sorting we need
window.Whisper.GroupConversationCollection = window.Backbone.Collection.extend({
model: window.Whisper.GroupMemberConversation,
initialize() {
this.collator = new Intl.Collator(undefined, { sensitivity: 'base' });
},
comparator(left: WhatIsThis, right: WhatIsThis) {
if (left.isAdmin && !right.isAdmin) {
return -1;
}
if (!left.isAdmin && right.isAdmin) {
return 1;
}
return sortConversationTitles(left, right, this.collator);
},
});

View file

@ -17,8 +17,8 @@ type ConfirmationDialogViewProps = {
resolve: () => void;
};
let confirmationDialogViewNode: HTMLElement | null = null;
let confirmationDialogPreviousFocus: HTMLElement | null = null;
let confirmationDialogViewNode: HTMLElement | undefined;
let confirmationDialogPreviousFocus: HTMLElement | undefined;
function removeConfirmationDialog() {
if (!confirmationDialogViewNode) {
@ -34,7 +34,7 @@ function removeConfirmationDialog() {
) {
confirmationDialogPreviousFocus.focus();
}
confirmationDialogViewNode = null;
confirmationDialogViewNode = undefined;
}
function showConfirmationDialog(options: ConfirmationDialogViewProps) {

View file

@ -0,0 +1,67 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// This file is here temporarily while we're switching off of Backbone into
// React. In the future, and in React-land, please just import and use
// the component directly. This is the thin API layer to bridge the gap
// while we convert things over. Please delete this file once all usages are
// ported over.
import React from 'react';
import { unmountComponentAtNode, render } from 'react-dom';
import { ConversationModel } from '../models/conversations';
import { SafetyNumberChangeDialog } from '../components/SafetyNumberChangeDialog';
export type SafetyNumberChangeViewProps = {
confirmText?: string;
contacts: Array<ConversationModel>;
reject: () => void;
resolve: () => void;
};
let dialogContainerNode: HTMLElement | undefined;
function removeDialog() {
if (!dialogContainerNode) {
return;
}
unmountComponentAtNode(dialogContainerNode);
document.body.removeChild(dialogContainerNode);
dialogContainerNode = undefined;
}
export function showSafetyNumberChangeDialog(
options: SafetyNumberChangeViewProps
): void {
if (dialogContainerNode) {
removeDialog();
}
dialogContainerNode = document.createElement('div');
document.body.appendChild(dialogContainerNode);
render(
<SafetyNumberChangeDialog
confirmText={options.confirmText}
contacts={options.contacts.map(contact => contact.format())}
i18n={window.i18n}
onCancel={() => {
options.reject();
removeDialog();
}}
onConfirm={() => {
options.resolve();
removeDialog();
}}
renderSafetyNumber={props => {
return window.Signal.State.Roots.createSafetyNumberViewer(
window.reduxStore,
props
);
}}
/>,
dialogContainerNode
);
}

View file

@ -1,15 +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 { SmartCallManager } from '../smart/CallManager';
export const createCallManager = (store: Store): React.ReactElement => (
<Provider store={store}>
<SmartCallManager />
</Provider>
);

View file

@ -1,17 +0,0 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { Provider } from 'react-redux';
import { Store } from 'redux';
import { SmartGlobalModalContainer } from '../smart/GlobalModalContainer';
export const createGlobalModalContainer = (
store: Store
): React.ReactElement => (
<Provider store={store}>
<SmartGlobalModalContainer />
</Provider>
);

View file

@ -1,21 +0,0 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { connect } from 'react-redux';
import { App } from '../../components/App';
import { StateType } from '../reducer';
import { getIntl, getTheme } from '../selectors/user';
import { mapDispatchToProps } from '../actions';
const mapStateToProps = (state: StateType) => {
return {
...state.app,
i18n: getIntl(state),
theme: getTheme(state),
};
};
const smart = connect(mapStateToProps, mapDispatchToProps);
export const SmartApp = smart(App);

25
ts/state/smart/App.tsx Normal file
View file

@ -0,0 +1,25 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { connect } from 'react-redux';
import { App, PropsType } from '../../components/App';
import { SmartCallManager } from './CallManager';
import { SmartGlobalModalContainer } from './GlobalModalContainer';
import { StateType } from '../reducer';
import { getTheme } from '../selectors/user';
import { mapDispatchToProps } from '../actions';
const mapStateToProps = (state: StateType): PropsType => {
return {
...state.app,
renderCallManager: () => <SmartCallManager />,
renderGlobalModalContainer: () => <SmartGlobalModalContainer />,
theme: getTheme(state),
};
};
const smart = connect(mapStateToProps, mapDispatchToProps);
export const SmartApp = smart(App);

View file

@ -252,22 +252,6 @@
"updated": "2020-08-21T11:29:29.636Z",
"reasonDetail": "Interacting with already-existing DOM nodes"
},
{
"rule": "jQuery-$(",
"path": "js/views/banner_view.js",
"line": " template: () => $('#banner').html(),",
"reasonCategory": "usageTrusted",
"updated": "2021-02-26T18:44:56.450Z",
"reasonDetail": "Static selector, read-only access"
},
{
"rule": "jQuery-html(",
"path": "js/views/banner_view.js",
"line": " template: () => $('#banner').html(),",
"reasonCategory": "usageTrusted",
"updated": "2021-02-26T18:44:56.450Z",
"reasonDetail": "Static selector, read-only access"
},
{
"rule": "jQuery-$(",
"path": "js/views/clear_data_view.js",
@ -284,30 +268,6 @@
"updated": "2021-02-26T18:44:56.450Z",
"reasonDetail": "Static selector, read-only access"
},
{
"rule": "jQuery-$(",
"path": "js/views/contact_list_view.js",
"line": " template: () => $('#contact').html(),",
"reasonCategory": "usageTrusted",
"updated": "2021-02-26T18:44:56.450Z",
"reasonDetail": "Static selector, read-only access"
},
{
"rule": "jQuery-append(",
"path": "js/views/contact_list_view.js",
"line": " this.$el.append(this.contactView.el);",
"reasonCategory": "usageTrusted",
"updated": "2019-07-31T00:19:18.696Z",
"reasonDetail": "Known DOM elements"
},
{
"rule": "jQuery-html(",
"path": "js/views/contact_list_view.js",
"line": " template: () => $('#contact').html(),",
"reasonCategory": "usageTrusted",
"updated": "2021-02-26T18:44:56.450Z",
"reasonDetail": "Static selector, read-only access"
},
{
"rule": "jQuery-$(",
"path": "js/views/debug_log_view.js",
@ -420,38 +380,6 @@
"updated": "2021-02-26T18:44:56.450Z",
"reasonDetail": "Static selector, read-only access"
},
{
"rule": "jQuery-$(",
"path": "js/views/group_member_list_view.js",
"line": " this.$('.container').append(this.member_list_view.el);",
"reasonCategory": "usageTrusted",
"updated": "2021-02-26T18:44:56.450Z",
"reasonDetail": "Static selector, adding sub-view to DOM"
},
{
"rule": "jQuery-$(",
"path": "js/views/group_member_list_view.js",
"line": " template: () => $('#group-member-list').html(),",
"reasonCategory": "usageTrusted",
"updated": "2021-02-26T18:44:56.450Z",
"reasonDetail": "Static selector, read-only access"
},
{
"rule": "jQuery-append(",
"path": "js/views/group_member_list_view.js",
"line": " this.$('.container').append(this.member_list_view.el);",
"reasonCategory": "usageTrusted",
"updated": "2021-02-26T18:44:56.450Z",
"reasonDetail": "Static selector, adding sub-view to DOM"
},
{
"rule": "jQuery-html(",
"path": "js/views/group_member_list_view.js",
"line": " template: () => $('#group-member-list').html(),",
"reasonCategory": "usageTrusted",
"updated": "2021-02-26T18:44:56.450Z",
"reasonDetail": "Static selector, read-only access"
},
{
"rule": "jQuery-$(",
"path": "js/views/identicon_svg_view.js",
@ -476,14 +404,6 @@
"updated": "2021-02-26T18:44:56.450Z",
"reasonDetail": "Static selector, read-only access"
},
{
"rule": "jQuery-$(",
"path": "js/views/inbox_view.js",
"line": " this.$('.call-manager-placeholder').append(this.callManagerView.el);",
"reasonCategory": "usageTrusted",
"updated": "2021-02-26T18:44:56.450Z",
"reasonDetail": "Adding sub-view to DOM"
},
{
"rule": "jQuery-$(",
"path": "js/views/inbox_view.js",
@ -500,14 +420,6 @@
"updated": "2021-02-26T18:44:56.450Z",
"reasonDetail": "Providing reference to DOM for sub-view"
},
{
"rule": "jQuery-$(",
"path": "js/views/inbox_view.js",
"line": " this.$('#header, .gutter').addClass('inactive');",
"reasonCategory": "usageTrusted",
"updated": "2021-02-26T18:44:56.450Z",
"reasonDetail": "Static selector, adding or removing classes"
},
{
"rule": "jQuery-$(",
"path": "js/views/inbox_view.js",
@ -516,22 +428,6 @@
"updated": "2021-02-26T18:44:56.450Z",
"reasonDetail": "Static selector, adding or removing classes"
},
{
"rule": "jQuery-$(",
"path": "js/views/inbox_view.js",
"line": " this.$('.conversation-stack').addClass('inactive');",
"reasonCategory": "usageTrusted",
"updated": "2021-02-26T18:44:56.450Z",
"reasonDetail": "Static selector, adding or removing classes"
},
{
"rule": "jQuery-$(",
"path": "js/views/inbox_view.js",
"line": " this.$('#header, .gutter').removeClass('inactive');",
"reasonCategory": "usageTrusted",
"updated": "2021-02-26T18:44:56.450Z",
"reasonDetail": "Static selector, adding or removing classes"
},
{
"rule": "jQuery-$(",
"path": "js/views/inbox_view.js",
@ -583,18 +479,9 @@
{
"rule": "jQuery-$(",
"path": "js/views/inbox_view.js",
"line": " this.$('.conversation:first .menu').trigger('close');",
"line": " this.$('#header, .gutter').addClass('inactive');",
"reasonCategory": "usageTrusted",
"updated": "2021-02-26T18:44:56.450Z",
"reasonDetail": "Static selector, trigging DOM event"
},
{
"rule": "jQuery-append(",
"path": "js/views/inbox_view.js",
"line": " this.$('.call-manager-placeholder').append(this.callManagerView.el);",
"reasonCategory": "usageTrusted",
"updated": "2021-02-26T18:44:56.450Z",
"reasonDetail": "Adding sub-view to DOM"
"updated": "2021-06-15T23:46:51.629Z"
},
{
"rule": "jQuery-append(",
@ -812,22 +699,6 @@
"updated": "2021-02-26T18:44:56.450Z",
"reasonDetail": "Static selector, read-only access"
},
{
"rule": "jQuery-append(",
"path": "js/views/list_view.js",
"line": " this.$el.append(view.render().el);",
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T18:13:29.628Z",
"reasonDetail": "Interacting with already-existing DOM nodes"
},
{
"rule": "jQuery-html(",
"path": "js/views/list_view.js",
"line": " this.$el.html('');",
"reasonCategory": "usageTrusted",
"updated": "2018-09-15T00:38:04.183Z",
"reasonDetail": "Hard-coded value"
},
{
"rule": "jQuery-$(",
"path": "js/views/phone-input-view.js",
@ -932,36 +803,6 @@
"updated": "2021-02-26T18:44:56.450Z",
"reasonDetail": "Static selector, read-only access"
},
{
"rule": "jQuery-$(",
"path": "js/views/safety_number_change_dialog_view.js",
"line": " this.$('.safety-number-change-dialog-wrapper').append(dialog.el);",
"reasonCategory": "usageTrusted",
"updated": "2020-06-23T06:48:06.829Z"
},
{
"rule": "jQuery-$(",
"path": "js/views/safety_number_change_dialog_view.js",
"line": " template: () => $('#safety-number-change-dialog').html(),",
"reasonCategory": "usageTrusted",
"updated": "2021-02-26T18:44:56.450Z",
"reasonDetail": "Static selector, read-only access"
},
{
"rule": "jQuery-append(",
"path": "js/views/safety_number_change_dialog_view.js",
"line": " this.$('.safety-number-change-dialog-wrapper').append(dialog.el);",
"reasonCategory": "usageTrusted",
"updated": "2020-06-23T06:48:06.829Z"
},
{
"rule": "jQuery-html(",
"path": "js/views/safety_number_change_dialog_view.js",
"line": " template: () => $('#safety-number-change-dialog').html(),",
"reasonCategory": "usageTrusted",
"updated": "2021-02-26T18:44:56.450Z",
"reasonDetail": "Static selector, read-only access"
},
{
"rule": "jQuery-$(",
"path": "js/views/settings_view.js",
@ -14348,4 +14189,4 @@
"updated": "2021-03-18T21:41:28.361Z",
"reasonDetail": "A generic hook. Typically not to be used with non-DOM values."
}
]
]

View file

@ -21,7 +21,6 @@ import { GroupNameCollisionsWithIdsByTitle } from '../util/groupMemberNameCollis
import {
isDirectConversation,
isGroupV1,
isGroupV2,
isMe,
} from '../util/whatTypeOfConversation';
import {
@ -31,6 +30,8 @@ import {
isOutgoing,
isTapToView,
} from '../state/selectors/message';
import { ConversationDetailsMembershipList } from '../components/conversation/conversation-details/ConversationDetailsMembershipList';
import { showSafetyNumberChangeDialog } from '../shims/showSafetyNumberChangeDialog';
type GetLinkPreviewImageResult = {
data: ArrayBuffer;
@ -367,7 +368,6 @@ Whisper.ConversationView = Whisper.View.extend({
// Events on Conversation model
this.listenTo(this.model, 'destroy', this.stopListening);
this.listenTo(this.model, 'change:verified', this.onVerifiedChange);
this.listenTo(this.model, 'newmessage', this.lazyUpdateVerified);
// These are triggered by InboxView
@ -573,8 +573,8 @@ Whisper.ConversationView = Whisper.View.extend({
onShowAllMedia: () => {
this.showAllMedia();
},
onShowGroupMembers: async () => {
await this.showMembers();
onShowGroupMembers: () => {
this.showGV1Members();
},
onGoBack: () => {
this.resetPanel();
@ -1446,9 +1446,6 @@ Whisper.ConversationView = Whisper.View.extend({
if (this.captureAudioView) {
this.captureAudioView.remove();
}
if (this.banner) {
this.banner.remove();
}
if (this.lastSeenIndicator) {
this.lastSeenIndicator.remove();
}
@ -2132,55 +2129,6 @@ Whisper.ConversationView = Whisper.View.extend({
return Promise.all(untrusted.map((contact: any) => contact.setApproved()));
},
openSafetyNumberScreens(unverified: any) {
if (unverified.length === 1) {
this.showSafetyNumber(unverified.at(0).id);
return;
}
this.showMembers(null, unverified, { needVerify: true });
},
onVerifiedChange() {
const { model }: { model: ConversationModel } = this;
if (model.isUnverified()) {
const unverified = model.getUnverified();
let message;
if (!unverified.length) {
return;
}
if (unverified.length > 1) {
message = window.i18n('multipleNoLongerVerified');
} else {
message = window.i18n('noLongerVerified', [
unverified.at(0).getTitle(),
]);
}
// Need to re-add, since unverified set may have changed
if (this.banner) {
this.banner.remove();
this.banner = null;
}
this.banner = new Whisper.BannerView({
message,
onDismiss: () => {
this.markAllAsVerifiedDefault(unverified);
},
onClick: () => {
this.openSafetyNumberScreens(unverified);
},
});
const container = this.$('.discussion-container');
container.append(this.banner.el);
} else if (this.banner) {
this.banner.remove();
this.banner = null;
}
},
toggleMicrophone() {
this.compositionApi.current.setShowMic(!this.hasFiles());
},
@ -2314,7 +2262,6 @@ Whisper.ConversationView = Whisper.View.extend({
this.statusFetch = statusPromise.then(() =>
// eslint-disable-next-line more/no-then
model.updateVerified().then(() => {
this.onVerifiedChange();
this.statusFetch = null;
})
);
@ -2736,35 +2683,32 @@ Whisper.ConversationView = Whisper.View.extend({
this.compositionApi.current.resetEmojiResults(false);
},
async showMembers(
_e: unknown,
providedMembers: void | Backbone.Collection<ConversationModel>,
options: any = {}
) {
showGV1Members() {
const { model }: { model: ConversationModel } = this;
window._.defaults(options, { needVerify: false });
const { contactCollection } = model;
let contactCollection = providedMembers || model.contactCollection;
const memberships =
contactCollection?.map((conversation: ConversationModel) => {
return {
isAdmin: false,
member: conversation.format(),
};
}) || [];
if (!providedMembers && isGroupV2(model.attributes)) {
contactCollection = new Whisper.GroupConversationCollection(
this.model.get('membersV2').map(({ conversationId, role }: any) => ({
conversation: window.ConversationController.get(conversationId),
isAdmin:
role === window.textsecure.protobuf.Member.Role.ADMINISTRATOR,
}))
);
}
const view = new Whisper.GroupMemberList({
model: contactCollection,
// we pass this in to allow nested panels
listenBack: this.listenBack.bind(this),
needVerify: options.needVerify,
conversation: model,
const view = new Whisper.ReactWrapperView({
className: 'group-member-list panel',
Component: ConversationDetailsMembershipList,
props: {
canAddNewMembers: false,
i18n: window.i18n,
maxShownMemberCount: 32,
memberships,
showContactModal: this.showContactModal.bind(this),
},
});
this.listenBack(view);
view.render();
},
forceSend({
@ -3695,7 +3639,7 @@ Whisper.ConversationView = Whisper.View.extend({
confirmText?: string
) {
return new Promise(resolve => {
const dialog = new Whisper.SafetyNumberChangeDialogView({
showSafetyNumberChangeDialog({
confirmText,
contacts,
reject: () => {
@ -3705,8 +3649,6 @@ Whisper.ConversationView = Whisper.View.extend({
resolve(true);
},
});
this.$el.prepend(dialog.el);
});
},

5
ts/window.d.ts vendored
View file

@ -48,14 +48,12 @@ import { ConversationController } from './ConversationController';
import { ReduxActions } from './state/types';
import { createStore } from './state/createStore';
import { createApp } from './state/roots/createApp';
import { createCallManager } from './state/roots/createCallManager';
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';
import { createGlobalModalContainer } from './state/roots/createGlobalModalContainer';
import { createGroupLinkManagement } from './state/roots/createGroupLinkManagement';
import { createGroupV1MigrationModal } from './state/roots/createGroupV1MigrationModal';
import { createGroupV2JoinModal } from './state/roots/createGroupV2JoinModal';
@ -480,14 +478,12 @@ declare global {
createStore: typeof createStore;
Roots: {
createApp: typeof createApp;
createCallManager: typeof createCallManager;
createChatColorPicker: typeof createChatColorPicker;
createCompositionArea: typeof createCompositionArea;
createContactModal: typeof createContactModal;
createConversationDetails: typeof createConversationDetails;
createConversationHeader: typeof createConversationHeader;
createForwardMessageModal: typeof createForwardMessageModal;
createGlobalModalContainer: typeof createGlobalModalContainer;
createGroupLinkManagement: typeof createGroupLinkManagement;
createGroupV1MigrationModal: typeof createGroupV1MigrationModal;
createGroupV2JoinModal: typeof createGroupV2JoinModal;
@ -658,7 +654,6 @@ export type WhisperType = {
reject: Function
) => void;
};
GroupConversationCollection: typeof ConversationModelCollectionType;
ConversationCollection: typeof ConversationModelCollectionType;
ConversationCollectionType: ConversationModelCollectionType;
Conversation: typeof ConversationModel;