Convert `ReactWrapperView` to TypeScript

This commit is contained in:
Evan Hahn 2022-06-03 16:33:39 +00:00 committed by GitHub
parent bb9a270bfd
commit 63189f3f91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 325 additions and 364 deletions

View File

@ -26,23 +26,14 @@ const { ChatColorPicker } = require('../../ts/components/ChatColorPicker');
const { const {
ConfirmationDialog, ConfirmationDialog,
} = require('../../ts/components/ConfirmationDialog'); } = require('../../ts/components/ConfirmationDialog');
const {
ContactDetail,
} = require('../../ts/components/conversation/ContactDetail');
const { const {
ContactModal, ContactModal,
} = require('../../ts/components/conversation/ContactModal'); } = require('../../ts/components/conversation/ContactModal');
const { Emojify } = require('../../ts/components/conversation/Emojify'); const { Emojify } = require('../../ts/components/conversation/Emojify');
const { ErrorModal } = require('../../ts/components/ErrorModal');
const { Lightbox } = require('../../ts/components/Lightbox');
const {
MediaGallery,
} = require('../../ts/components/conversation/media-gallery/MediaGallery');
const { const {
MessageDetail, MessageDetail,
} = require('../../ts/components/conversation/MessageDetail'); } = require('../../ts/components/conversation/MessageDetail');
const { Quote } = require('../../ts/components/conversation/Quote'); const { Quote } = require('../../ts/components/conversation/Quote');
const { ProgressModal } = require('../../ts/components/ProgressModal');
const { const {
StagedLinkPreview, StagedLinkPreview,
} = require('../../ts/components/conversation/StagedLinkPreview'); } = require('../../ts/components/conversation/StagedLinkPreview');
@ -52,7 +43,6 @@ const {
const { const {
SystemTraySettingsCheckboxes, SystemTraySettingsCheckboxes,
} = require('../../ts/components/conversation/SystemTraySettingsCheckboxes'); } = require('../../ts/components/conversation/SystemTraySettingsCheckboxes');
const { WhatsNewLink } = require('../../ts/components/WhatsNewLink');
// State // State
const { const {
@ -320,19 +310,13 @@ exports.setup = (options = {}) => {
AttachmentList, AttachmentList,
ChatColorPicker, ChatColorPicker,
ConfirmationDialog, ConfirmationDialog,
ContactDetail,
ContactModal, ContactModal,
Emojify, Emojify,
ErrorModal,
Lightbox,
MediaGallery,
MessageDetail, MessageDetail,
Quote, Quote,
ProgressModal,
StagedLinkPreview, StagedLinkPreview,
DisappearingTimeDialog, DisappearingTimeDialog,
SystemTraySettingsCheckboxes, SystemTraySettingsCheckboxes,
WhatsNewLink,
}; };
const Roots = { const Roots = {

View File

@ -1,86 +0,0 @@
// Copyright 2018-2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* global Backbone: false */
/* global i18n: false */
/* global React: false */
/* global ReactDOM: false */
// eslint-disable-next-line func-names
(function () {
window.Whisper = window.Whisper || {};
window.Whisper.ReactWrapperView = Backbone.View.extend({
className: 'react-wrapper',
initialize(options) {
const {
Component,
JSX,
props,
onClose,
tagName,
className,
onInitialRender,
elCallback,
} = options;
this.render();
if (elCallback) {
elCallback(this.el);
}
this.tagName = tagName;
this.className = className;
this.JSX = JSX;
this.Component = Component;
this.onClose = onClose;
this.onInitialRender = onInitialRender;
this.update(props);
this.hasRendered = false;
},
update(propsOrJSX, cb) {
const reactElement = this.JSX
? propsOrJSX || this.JSX
: React.createElement(this.Component, this.augmentProps(propsOrJSX));
ReactDOM.render(reactElement, this.el, () => {
if (cb) {
try {
cb();
} catch (error) {
window.SignalContext.log.error(
'ReactWrapperView.update error:',
error && error.stack ? error.stack : error
);
}
}
if (this.hasRendered) {
return;
}
this.hasRendered = true;
if (this.onInitialRender) {
this.onInitialRender();
}
});
},
augmentProps(props) {
return {
...props,
close: () => {
this.remove();
},
i18n,
};
},
remove() {
if (this.onClose) {
this.onClose();
}
ReactDOM.unmountComponentAtNode(this.el);
Backbone.View.prototype.remove.call(this);
},
});
})();

View File

@ -134,6 +134,7 @@ import type { UUID } from './types/UUID';
import * as log from './logging/log'; import * as log from './logging/log';
import { loadRecentEmojis } from './util/loadRecentEmojis'; import { loadRecentEmojis } from './util/loadRecentEmojis';
import { deleteAllLogs } from './util/deleteAllLogs'; import { deleteAllLogs } from './util/deleteAllLogs';
import { ReactWrapperView } from './views/ReactWrapperView';
import { ToastCaptchaFailed } from './components/ToastCaptchaFailed'; import { ToastCaptchaFailed } from './components/ToastCaptchaFailed';
import { ToastCaptchaSolved } from './components/ToastCaptchaSolved'; import { ToastCaptchaSolved } from './components/ToastCaptchaSolved';
import { ToastConversationArchived } from './components/ToastConversationArchived'; import { ToastConversationArchived } from './components/ToastConversationArchived';
@ -1114,7 +1115,7 @@ export async function startApp(): Promise<void> {
window.showKeyboardShortcuts = () => { window.showKeyboardShortcuts = () => {
if (!shortcutGuideView) { if (!shortcutGuideView) {
shortcutGuideView = new window.Whisper.ReactWrapperView({ shortcutGuideView = new ReactWrapperView({
className: 'shortcut-guide-wrapper', className: 'shortcut-guide-wrapper',
JSX: window.Signal.State.Roots.createShortcutGuideModal( JSX: window.Signal.State.Roots.createShortcutGuideModal(
window.reduxStore, window.reduxStore,

View File

@ -1,4 +1,4 @@
// Copyright 2018-2021 Signal Messenger, LLC // Copyright 2018-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
@ -16,7 +16,7 @@ import { Avatar, AvatarSize } from './Avatar';
import type { ConversationType } from '../state/ducks/conversations'; import type { ConversationType } from '../state/ducks/conversations';
import { IMAGE_PNG, isImage, isVideo } from '../types/MIME'; import { IMAGE_PNG, isImage, isVideo } from '../types/MIME';
import type { LocalizerType } from '../types/Util'; import type { LocalizerType } from '../types/Util';
import type { MediaItemType, MessageAttributesType } from '../types/MediaItem'; import type { MediaItemType, MediaItemMessageType } from '../types/MediaItem';
import { formatDuration } from '../util/formatDuration'; import { formatDuration } from '../util/formatDuration';
import { useRestoreFocus } from '../hooks/useRestoreFocus'; import { useRestoreFocus } from '../hooks/useRestoreFocus';
import * as log from '../logging/log'; import * as log from '../logging/log';
@ -31,7 +31,7 @@ export type PropsType = {
onForward?: (messageId: string) => void; onForward?: (messageId: string) => void;
onSave?: (options: { onSave?: (options: {
attachment: AttachmentType; attachment: AttachmentType;
message: MessageAttributesType; message: MediaItemMessageType;
index: number; index: number;
}) => void; }) => void;
selectedIndex?: number; selectedIndex?: number;
@ -676,7 +676,7 @@ function LightboxHeader({
}: { }: {
getConversation: (id: string) => ConversationType; getConversation: (id: string) => ConversationType;
i18n: LocalizerType; i18n: LocalizerType;
message: MessageAttributesType; message: MediaItemMessageType;
}): JSX.Element { }): JSX.Element {
const conversation = getConversation(message.conversationId); const conversation = getConversation(message.conversationId);

View File

@ -1,11 +1,11 @@
// Copyright 2018-2021 Signal Messenger, LLC // Copyright 2018-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import type { MessageAttributesType } from '../../../../model-types.d';
import type { AttachmentType } from '../../../../types/Attachment'; import type { AttachmentType } from '../../../../types/Attachment';
import type { Message } from './Message';
export type ItemClickEvent = { export type ItemClickEvent = {
message: Message; message: Pick<MessageAttributesType, 'sent_at'>;
attachment: AttachmentType; attachment: AttachmentType;
type: 'media' | 'documents'; type: 'media' | 'documents';
}; };

View File

@ -1,14 +0,0 @@
// Copyright 2018-2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { AttachmentType } from '../../../../types/Attachment';
export type Message = {
id: string;
attachments: Array<AttachmentType>;
// Assuming this is for the API
// eslint-disable-next-line camelcase
received_at: number;
// eslint-disable-next-line camelcase
received_at_ms: number;
};

View File

@ -1,6 +1,7 @@
// Copyright 2021 Signal Messenger, LLC // Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react';
import { import {
applyNewAvatar, applyNewAvatar,
decryptGroupDescription, decryptGroupDescription,
@ -23,6 +24,8 @@ import type { PreJoinConversationType } from '../state/ducks/conversations';
import { SignalService as Proto } from '../protobuf'; import { SignalService as Proto } from '../protobuf';
import * as log from '../logging/log'; import * as log from '../logging/log';
import { showToast } from '../util/showToast'; import { showToast } from '../util/showToast';
import { ReactWrapperView } from '../views/ReactWrapperView';
import { ErrorModal } from '../components/ErrorModal';
import { ToastAlreadyGroupMember } from '../components/ToastAlreadyGroupMember'; import { ToastAlreadyGroupMember } from '../components/ToastAlreadyGroupMember';
import { ToastAlreadyRequestedToJoin } from '../components/ToastAlreadyRequestedToJoin'; import { ToastAlreadyRequestedToJoin } from '../components/ToastAlreadyRequestedToJoin';
import { HTTPError } from '../textsecure/Errors'; import { HTTPError } from '../textsecure/Errors';
@ -373,14 +376,13 @@ export async function joinViaLink(hash: string): Promise<void> {
log.info(`joinViaLink/${logId}: Showing modal`); log.info(`joinViaLink/${logId}: Showing modal`);
let groupV2InfoDialog: Backbone.View | undefined = let groupV2InfoDialog: Backbone.View | undefined = new ReactWrapperView({
new window.Whisper.ReactWrapperView({ className: 'group-v2-join-dialog-wrapper',
className: 'group-v2-join-dialog-wrapper', JSX: window.Signal.State.Roots.createGroupV2JoinModal(window.reduxStore, {
JSX: window.Signal.State.Roots.createGroupV2JoinModal(window.reduxStore, { join,
join, onClose: closeDialog,
onClose: closeDialog, }),
}), });
});
// We declare a new function here so we can await but not block // We declare a new function here so we can await but not block
const fetchAvatar = async () => { const fetchAvatar = async () => {
@ -427,15 +429,17 @@ export async function joinViaLink(hash: string): Promise<void> {
} }
function showErrorDialog(description: string, title: string) { function showErrorDialog(description: string, title: string) {
const errorView = new window.Whisper.ReactWrapperView({ const errorView = new ReactWrapperView({
className: 'error-modal-wrapper', className: 'error-modal-wrapper',
Component: window.Signal.Components.ErrorModal, JSX: (
props: { <ErrorModal
title, i18n={window.i18n}
description, title={title}
onClose: () => { description={description}
errorView.remove(); onClose={() => {
}, errorView.remove();
}, }}
/>
),
}); });
} }

View File

@ -1,27 +1,26 @@
// Copyright 2021 Signal Messenger, LLC // Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import type { MessageAttributesType } from '../model-types.d';
import type { AttachmentType } from './Attachment'; import type { AttachmentType } from './Attachment';
import type { MIMEType } from './MIME'; import type { MIMEType } from './MIME';
export type MessageAttributesType = { export type MediaItemMessageType = Pick<
attachments: Array<AttachmentType>; MessageAttributesType,
conversationId: string; | 'attachments'
id: string; | 'conversationId'
// eslint-disable-next-line camelcase | 'id'
received_at: number; | 'received_at'
// eslint-disable-next-line camelcase | 'received_at_ms'
received_at_ms: number; | 'sent_at'
// eslint-disable-next-line camelcase >;
sent_at: number;
};
export type MediaItemType = { export type MediaItemType = {
attachment: AttachmentType; attachment: AttachmentType;
contentType?: MIMEType; contentType?: MIMEType;
index: number; index: number;
loop?: boolean; loop?: boolean;
message: MessageAttributesType; message: MediaItemMessageType;
objectURL?: string; objectURL?: string;
thumbnailObjectUrl?: string; thumbnailObjectUrl?: string;
}; };

View File

@ -1,8 +1,9 @@
// Copyright 2020-2021 Signal Messenger, LLC // Copyright 2020-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { webFrame } from 'electron'; import { webFrame } from 'electron';
import type { AudioDevice } from 'ringrtc'; import type { AudioDevice } from 'ringrtc';
import * as React from 'react';
import type { ZoomFactorType } from '../types/Storage.d'; import type { ZoomFactorType } from '../types/Storage.d';
import type { import type {
@ -15,6 +16,9 @@ import * as Stickers from '../types/Stickers';
import type { SystemTraySetting } from '../types/SystemTraySetting'; import type { SystemTraySetting } from '../types/SystemTraySetting';
import { parseSystemTraySetting } from '../types/SystemTraySetting'; import { parseSystemTraySetting } from '../types/SystemTraySetting';
import { ReactWrapperView } from '../views/ReactWrapperView';
import { ErrorModal } from '../components/ErrorModal';
import type { ConversationType } from '../state/ducks/conversations'; import type { ConversationType } from '../state/ducks/conversations';
import { calling } from '../services/calling'; import { calling } from '../services/calling';
import { getConversationsWithCustomColorSelector } from '../state/selectors/conversations'; import { getConversationsWithCustomColorSelector } from '../state/selectors/conversations';
@ -406,7 +410,7 @@ export function createIPCEvents(
}, },
}; };
const stickerPreviewModalView = new window.Whisper.ReactWrapperView({ const stickerPreviewModalView = new ReactWrapperView({
className: 'sticker-preview-modal-wrapper', className: 'sticker-preview-modal-wrapper',
JSX: window.Signal.State.Roots.createStickerPreviewModal( JSX: window.Signal.State.Roots.createStickerPreviewModal(
window.reduxStore, window.reduxStore,
@ -419,14 +423,16 @@ export function createIPCEvents(
'showStickerPack: Ran into an error!', 'showStickerPack: Ran into an error!',
error && error.stack ? error.stack : error error && error.stack ? error.stack : error
); );
const errorView = new window.Whisper.ReactWrapperView({ const errorView = new ReactWrapperView({
className: 'error-modal-wrapper', className: 'error-modal-wrapper',
Component: window.Signal.Components.ErrorModal, JSX: (
props: { <ErrorModal
onClose: () => { i18n={window.i18n}
errorView.remove(); onClose={() => {
}, errorView.remove();
}, }}
/>
),
}); });
} }
}, },
@ -447,16 +453,18 @@ export function createIPCEvents(
'showGroupViaLink: Ran into an error!', 'showGroupViaLink: Ran into an error!',
error && error.stack ? error.stack : error error && error.stack ? error.stack : error
); );
const errorView = new window.Whisper.ReactWrapperView({ const errorView = new ReactWrapperView({
className: 'error-modal-wrapper', className: 'error-modal-wrapper',
Component: window.Signal.Components.ErrorModal, JSX: (
props: { <ErrorModal
title: window.i18n('GroupV2--join--general-join-failure--title'), i18n={window.i18n}
description: window.i18n('GroupV2--join--general-join-failure'), title={window.i18n('GroupV2--join--general-join-failure--title')}
onClose: () => { description={window.i18n('GroupV2--join--general-join-failure')}
errorView.remove(); onClose={() => {
}, errorView.remove();
}, }}
/>
),
}); });
} }
window.isShowingModal = false; window.isShowingModal = false;
@ -513,14 +521,16 @@ export function createIPCEvents(
} }
function showUnknownSgnlLinkModal(): void { function showUnknownSgnlLinkModal(): void {
const errorView = new window.Whisper.ReactWrapperView({ const errorView = new ReactWrapperView({
className: 'error-modal-wrapper', className: 'error-modal-wrapper',
Component: window.Signal.Components.ErrorModal, JSX: (
props: { <ErrorModal
description: window.i18n('unknown-sgnl-link'), i18n={window.i18n}
onClose: () => { description={window.i18n('unknown-sgnl-link')}
errorView.remove(); onClose={() => {
}, errorView.remove();
}, }}
/>
),
}); });
} }

View File

@ -1,8 +1,10 @@
// Copyright 2021 Signal Messenger, LLC // Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import type { Message } from '../components/conversation/media-gallery/types/Message'; import type { MessageAttributesType } from '../model-types.d';
export function getMessageTimestamp(message: Message): number { export function getMessageTimestamp(
message: Pick<MessageAttributesType, 'received_at' | 'received_at_ms'>
): number {
return message.received_at_ms || message.received_at; return message.received_at_ms || message.received_at;
} }

View File

@ -7967,7 +7967,7 @@
}, },
{ {
"rule": "jQuery-$(", "rule": "jQuery-$(",
"path": "ts/util/createIPCEvents.ts", "path": "ts/util/createIPCEvents.tsx",
"line": " if ($('.dark-overlay').length) {", "line": " if ($('.dark-overlay').length) {",
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2021-08-18T18:22:55.307Z", "updated": "2021-08-18T18:22:55.307Z",
@ -7975,7 +7975,7 @@
}, },
{ {
"rule": "jQuery-$(", "rule": "jQuery-$(",
"path": "ts/util/createIPCEvents.ts", "path": "ts/util/createIPCEvents.tsx",
"line": " $(document.body).prepend('<div class=\"dark-overlay\"></div>');", "line": " $(document.body).prepend('<div class=\"dark-overlay\"></div>');",
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2021-08-18T18:22:55.307Z", "updated": "2021-08-18T18:22:55.307Z",
@ -7983,7 +7983,7 @@
}, },
{ {
"rule": "jQuery-$(", "rule": "jQuery-$(",
"path": "ts/util/createIPCEvents.ts", "path": "ts/util/createIPCEvents.tsx",
"line": " $('.dark-overlay').on('click', () => $('.dark-overlay').remove());", "line": " $('.dark-overlay').on('click', () => $('.dark-overlay').remove());",
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2021-08-18T18:22:55.307Z", "updated": "2021-08-18T18:22:55.307Z",
@ -7991,7 +7991,7 @@
}, },
{ {
"rule": "jQuery-$(", "rule": "jQuery-$(",
"path": "ts/util/createIPCEvents.ts", "path": "ts/util/createIPCEvents.tsx",
"line": " removeDarkOverlay: () => $('.dark-overlay').remove(),", "line": " removeDarkOverlay: () => $('.dark-overlay').remove(),",
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2021-08-18T18:22:55.307Z", "updated": "2021-08-18T18:22:55.307Z",
@ -7999,7 +7999,7 @@
}, },
{ {
"rule": "jQuery-prepend(", "rule": "jQuery-prepend(",
"path": "ts/util/createIPCEvents.ts", "path": "ts/util/createIPCEvents.tsx",
"line": " $(document.body).prepend('<div class=\"dark-overlay\"></div>');", "line": " $(document.body).prepend('<div class=\"dark-overlay\"></div>');",
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2021-08-18T18:22:55.307Z", "updated": "2021-08-18T18:22:55.307Z",
@ -8021,84 +8021,84 @@
}, },
{ {
"rule": "jQuery-$(", "rule": "jQuery-$(",
"path": "ts/views/inbox_view.ts", "path": "ts/views/inbox_view.tsx",
"line": " template: () => $('#app-loading-screen').html(),", "line": " template: () => $('#app-loading-screen').html(),",
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z" "updated": "2021-09-15T21:07:50.995Z"
}, },
{ {
"rule": "jQuery-$(", "rule": "jQuery-$(",
"path": "ts/views/inbox_view.ts", "path": "ts/views/inbox_view.tsx",
"line": " this.$('.message').text(message);", "line": " this.$('.message').text(message);",
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z" "updated": "2021-09-15T21:07:50.995Z"
}, },
{ {
"rule": "jQuery-$(", "rule": "jQuery-$(",
"path": "ts/views/inbox_view.ts", "path": "ts/views/inbox_view.tsx",
"line": " template: () => $('#two-column').html(),", "line": " template: () => $('#two-column').html(),",
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z" "updated": "2021-09-15T21:07:50.995Z"
}, },
{ {
"rule": "jQuery-$(", "rule": "jQuery-$(",
"path": "ts/views/inbox_view.ts", "path": "ts/views/inbox_view.tsx",
"line": " el: this.$('.conversation-stack'),", "line": " el: this.$('.conversation-stack'),",
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z" "updated": "2021-09-15T21:07:50.995Z"
}, },
{ {
"rule": "jQuery-$(", "rule": "jQuery-$(",
"path": "ts/views/inbox_view.ts", "path": "ts/views/inbox_view.tsx",
"line": " this.$('.no-conversation-open').toggle(!isAnyConversationOpen);", "line": " this.$('.no-conversation-open').toggle(!isAnyConversationOpen);",
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2021-10-08T17:40:22.770Z" "updated": "2021-10-08T17:40:22.770Z"
}, },
{ {
"rule": "jQuery-$(", "rule": "jQuery-$(",
"path": "ts/views/inbox_view.ts", "path": "ts/views/inbox_view.tsx",
"line": " this.$('.left-pane-placeholder').replaceWith(this.leftPaneView.el);", "line": " this.$('.left-pane-placeholder').replaceWith(this.leftPaneView.el);",
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2021-10-08T17:40:22.770Z" "updated": "2021-10-08T17:40:22.770Z"
}, },
{ {
"rule": "jQuery-$(", "rule": "jQuery-$(",
"path": "ts/views/inbox_view.ts", "path": "ts/views/inbox_view.tsx",
"line": " this.$('.whats-new-placeholder').append(this.whatsNewLink.el);", "line": " this.$('.whats-new-placeholder').append(this.whatsNewLink.el);",
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2021-10-22T20:58:48.103Z" "updated": "2021-10-22T20:58:48.103Z"
}, },
{ {
"rule": "jQuery-append(", "rule": "jQuery-append(",
"path": "ts/views/inbox_view.ts", "path": "ts/views/inbox_view.tsx",
"line": " this.$('.whats-new-placeholder').append(this.whatsNewLink.el);", "line": " this.$('.whats-new-placeholder').append(this.whatsNewLink.el);",
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2021-10-22T20:58:48.103Z" "updated": "2021-10-22T20:58:48.103Z"
}, },
{ {
"rule": "jQuery-appendTo(", "rule": "jQuery-appendTo(",
"path": "ts/views/inbox_view.ts", "path": "ts/views/inbox_view.tsx",
"line": " view.$el.appendTo(this.el);", "line": " view.$el.appendTo(this.el);",
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z" "updated": "2021-09-15T21:07:50.995Z"
}, },
{ {
"rule": "jQuery-html(", "rule": "jQuery-html(",
"path": "ts/views/inbox_view.ts", "path": "ts/views/inbox_view.tsx",
"line": " template: () => $('#app-loading-screen').html(),", "line": " template: () => $('#app-loading-screen').html(),",
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z" "updated": "2021-09-15T21:07:50.995Z"
}, },
{ {
"rule": "jQuery-html(", "rule": "jQuery-html(",
"path": "ts/views/inbox_view.ts", "path": "ts/views/inbox_view.tsx",
"line": " template: () => $('#two-column').html(),", "line": " template: () => $('#two-column').html(),",
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z" "updated": "2021-09-15T21:07:50.995Z"
}, },
{ {
"rule": "jQuery-prependTo(", "rule": "jQuery-prependTo(",
"path": "ts/views/inbox_view.ts", "path": "ts/views/inbox_view.tsx",
"line": " this.appLoadingScreen.$el.prependTo(this.el);", "line": " this.appLoadingScreen.$el.prependTo(this.el);",
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z" "updated": "2021-09-15T21:07:50.995Z"

View File

@ -1,6 +1,10 @@
// Copyright 2021-2022 Signal Messenger, LLC // Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react';
import { ReactWrapperView } from '../views/ReactWrapperView';
import { ErrorModal } from '../components/ErrorModal';
import { ProgressModal } from '../components/ProgressModal';
import * as log from '../logging/log'; import * as log from '../logging/log';
import { clearTimeoutIfNecessary } from './clearTimeoutIfNecessary'; import { clearTimeoutIfNecessary } from './clearTimeoutIfNecessary';
@ -26,9 +30,9 @@ export async function longRunningTaskWrapper<T>({
// Note: this component uses a portal to render itself into the top-level DOM. No // Note: this component uses a portal to render itself into the top-level DOM. No
// need to attach it to the DOM here. // need to attach it to the DOM here.
progressView = new window.Whisper.ReactWrapperView({ progressView = new ReactWrapperView({
className: 'progress-modal-wrapper', className: 'progress-modal-wrapper',
Component: window.Signal.Components.ProgressModal, JSX: <ProgressModal i18n={window.i18n} />,
}); });
spinnerStart = Date.now(); spinnerStart = Date.now();
}, TWO_SECONDS); }, TWO_SECONDS);
@ -73,14 +77,16 @@ export async function longRunningTaskWrapper<T>({
// Note: this component uses a portal to render itself into the top-level DOM. No // Note: this component uses a portal to render itself into the top-level DOM. No
// need to attach it to the DOM here. // need to attach it to the DOM here.
const errorView: Backbone.View = new window.Whisper.ReactWrapperView({ const errorView: Backbone.View = new ReactWrapperView({
className: 'error-modal-wrapper', className: 'error-modal-wrapper',
Component: window.Signal.Components.ErrorModal, JSX: (
props: { <ErrorModal
onClose: (): void => { i18n={window.i18n}
errorView.remove(); onClose={() => {
}, errorView.remove();
}, }}
/>
),
}); });
} }

View File

@ -0,0 +1,49 @@
// Copyright 2018-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { ReactElement } from 'react';
import * as ReactDOM from 'react-dom';
import * as Backbone from 'backbone';
export class ReactWrapperView extends Backbone.View {
private readonly onClose?: () => unknown;
private JSX: ReactElement;
constructor({
className,
onClose,
JSX,
}: Readonly<{
className?: string;
onClose?: () => unknown;
JSX: ReactElement;
}>) {
super();
this.className = className ?? 'react-wrapper';
this.JSX = JSX;
this.onClose = onClose;
this.render();
}
update(JSX: ReactElement): void {
this.JSX = JSX;
this.render();
}
override render(): this {
this.el.className = this.className;
ReactDOM.render(this.JSX, this.el);
return this;
}
override remove(): this {
if (this.onClose) {
this.onClose();
}
ReactDOM.unmountComponentAtNode(this.el);
Backbone.View.prototype.remove.call(this);
return this;
}
}

View File

@ -3,6 +3,9 @@
/* eslint-disable camelcase */ /* eslint-disable camelcase */
import type * as Backbone from 'backbone';
import type { ComponentProps } from 'react';
import * as React from 'react';
import { debounce, flatten, omit, throttle } from 'lodash'; import { debounce, flatten, omit, throttle } from 'lodash';
import { render } from 'mustache'; import { render } from 'mustache';
@ -23,10 +26,7 @@ import type {
QuotedMessageType, QuotedMessageType,
} from '../model-types.d'; } from '../model-types.d';
import type { LinkPreviewType } from '../types/message/LinkPreviews'; import type { LinkPreviewType } from '../types/message/LinkPreviews';
import type { import type { MediaItemType, MediaItemMessageType } from '../types/MediaItem';
MediaItemType,
MessageAttributesType as MediaItemMessageType,
} from '../types/MediaItem';
import type { MessageModel } from '../models/messages'; import type { MessageModel } from '../models/messages';
import { getMessageById } from '../messages/getMessageById'; import { getMessageById } from '../messages/getMessageById';
import { getContactId } from '../messages/helpers'; import { getContactId } from '../messages/helpers';
@ -43,6 +43,7 @@ import {
} from '../util/whatTypeOfConversation'; } from '../util/whatTypeOfConversation';
import { findAndFormatContact } from '../util/findAndFormatContact'; import { findAndFormatContact } from '../util/findAndFormatContact';
import * as Bytes from '../Bytes'; import * as Bytes from '../Bytes';
import { getPreferredBadgeSelector } from '../state/selectors/badges';
import { import {
canReply, canReply,
getAttachmentsForMessage, getAttachmentsForMessage,
@ -55,6 +56,9 @@ import {
getMessagesByConversation, getMessagesByConversation,
} from '../state/selectors/conversations'; } from '../state/selectors/conversations';
import { getActiveCallState } from '../state/selectors/calling'; import { getActiveCallState } from '../state/selectors/calling';
import { getTheme } from '../state/selectors/user';
import { ReactWrapperView } from './ReactWrapperView';
import { Lightbox } from '../components/Lightbox';
import { ConversationDetailsMembershipList } from '../components/conversation/conversation-details/ConversationDetailsMembershipList'; import { ConversationDetailsMembershipList } from '../components/conversation/conversation-details/ConversationDetailsMembershipList';
import { showSafetyNumberChangeDialog } from '../shims/showSafetyNumberChangeDialog'; import { showSafetyNumberChangeDialog } from '../shims/showSafetyNumberChangeDialog';
import type { import type {
@ -65,7 +69,6 @@ import type {
import * as LinkPreview from '../types/LinkPreview'; import * as LinkPreview from '../types/LinkPreview';
import * as VisualAttachment from '../types/VisualAttachment'; import * as VisualAttachment from '../types/VisualAttachment';
import * as log from '../logging/log'; import * as log from '../logging/log';
import type { AnyViewClass, BasicReactWrapperViewClass } from '../window.d';
import type { EmbeddedContactType } from '../types/EmbeddedContact'; import type { EmbeddedContactType } from '../types/EmbeddedContact';
import { createConversationView } from '../state/roots/createConversationView'; import { createConversationView } from '../state/roots/createConversationView';
import { AttachmentToastType } from '../types/AttachmentToastType'; import { AttachmentToastType } from '../types/AttachmentToastType';
@ -116,18 +119,20 @@ import { RecordingState } from '../state/ducks/audioRecorder';
import { UUIDKind } from '../types/UUID'; import { UUIDKind } from '../types/UUID';
import type { UUIDStringType } from '../types/UUID'; import type { UUIDStringType } from '../types/UUID';
import { retryDeleteForEveryone } from '../util/retryDeleteForEveryone'; import { retryDeleteForEveryone } from '../util/retryDeleteForEveryone';
import { ContactDetail } from '../components/conversation/ContactDetail';
import { MediaGallery } from '../components/conversation/media-gallery/MediaGallery';
import type { ItemClickEvent } from '../components/conversation/media-gallery/types/ItemClickEvent';
type AttachmentOptions = { type AttachmentOptions = {
messageId: string; messageId: string;
attachment: AttachmentType; attachment: AttachmentType;
}; };
type PanelType = { view: Backbone.View; headerTitle?: string };
const FIVE_MINUTES = 1000 * 60 * 5; const FIVE_MINUTES = 1000 * 60 * 5;
const LINK_PREVIEW_TIMEOUT = 60 * 1000; const LINK_PREVIEW_TIMEOUT = 60 * 1000;
window.Whisper = window.Whisper || {};
const { Whisper } = window;
const { Message } = window.Signal.Types; const { Message } = window.Signal.Types;
const { const {
@ -247,14 +252,14 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
// Sub-views // Sub-views
private contactModalView?: Backbone.View; private contactModalView?: Backbone.View;
private conversationView?: BasicReactWrapperViewClass; private conversationView?: Backbone.View;
private forwardMessageModal?: Backbone.View; private forwardMessageModal?: Backbone.View;
private lightboxView?: BasicReactWrapperViewClass; private lightboxView?: ReactWrapperView;
private migrationDialog?: Backbone.View; private migrationDialog?: Backbone.View;
private stickerPreviewModalView?: Backbone.View; private stickerPreviewModalView?: Backbone.View;
// Panel support // Panel support
private panels: Array<AnyViewClass> = []; private panels: Array<PanelType> = [];
private previousFocus?: HTMLElement; private previousFocus?: HTMLElement;
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -676,7 +681,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
timelineProps, timelineProps,
}); });
this.conversationView = new Whisper.ReactWrapperView({ JSX }); this.conversationView = new ReactWrapperView({ JSX });
this.$('.ConversationView__template').append(this.conversationView.el); this.$('.ConversationView__template').append(this.conversationView.el);
} }
@ -988,7 +993,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
(item: GroupV2PendingMemberType) => item.uuid (item: GroupV2PendingMemberType) => item.uuid
); );
this.migrationDialog = new Whisper.ReactWrapperView({ this.migrationDialog = new ReactWrapperView({
className: 'group-v1-migration-wrapper', className: 'group-v1-migration-wrapper',
JSX: window.Signal.State.Roots.createGroupV1MigrationModal( JSX: window.Signal.State.Roots.createGroupV1MigrationModal(
window.reduxStore, window.reduxStore,
@ -1106,7 +1111,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
if (this.panels && this.panels.length) { if (this.panels && this.panels.length) {
for (let i = 0, max = this.panels.length; i < max; i += 1) { for (let i = 0, max = this.panels.length; i < max; i += 1) {
const panel = this.panels[i]; const panel = this.panels[i];
panel.remove(); panel.view.remove();
} }
window.reduxActions.conversations.setSelectedConversationPanelDepth(0); window.reduxActions.conversations.setSelectedConversationPanelDepth(0);
} }
@ -1334,7 +1339,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
} }
}; };
this.forwardMessageModal = new Whisper.ReactWrapperView({ this.forwardMessageModal = new ReactWrapperView({
JSX: window.Signal.State.Roots.createForwardMessageModal( JSX: window.Signal.State.Roots.createForwardMessageModal(
window.reduxStore, window.reduxStore,
{ {
@ -1623,20 +1628,24 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
).filter(isNotNil); ).filter(isNotNil);
// Unlike visual media, only one non-image attachment is supported // Unlike visual media, only one non-image attachment is supported
const documents = rawDocuments const documents: Array<MediaItemType> = [];
.filter(message => rawDocuments.forEach(message => {
Boolean(message.attachments && message.attachments.length) const attachments = message.attachments || [];
) const attachment = attachments[0];
.map(message => { if (!attachment) {
const attachments = message.attachments || []; return;
const attachment = attachments[0]; }
return {
contentType: attachment.contentType, documents.push({
index: 0, contentType: attachment.contentType,
attachment, index: 0,
message, attachment,
}; // We do this cast because we know there attachments (see the checks above).
message: message as MessageAttributesType & {
attachments: Array<AttachmentType>;
},
}); });
});
const saveAttachment = async ({ const saveAttachment = async ({
attachment, attachment,
@ -1666,11 +1675,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
message, message,
attachment, attachment,
type, type,
}: { }: ItemClickEvent) => {
message: MessageAttributesType;
attachment: AttachmentType;
type: 'documents' | 'media';
}) => {
switch (type) { switch (type) {
case 'documents': { case 'documents': {
saveAttachment({ message, attachment }); saveAttachment({ message, attachment });
@ -1719,20 +1724,22 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
} }
}); });
const view = new Whisper.ReactWrapperView({ const view = new ReactWrapperView({
className: 'panel', className: 'panel',
Component: window.Signal.Components.MediaGallery, // We present an empty panel briefly, while we wait for props to load.
JSX: <></>,
onClose: () => { onClose: () => {
unsubscribe(); unsubscribe();
}, },
}); });
view.headerTitle = window.i18n('allMedia'); const headerTitle = window.i18n('allMedia');
const update = async () => { const update = async () => {
view.update(await getProps()); const props = await getProps();
view.update(<MediaGallery i18n={window.i18n} {...props} />);
}; };
this.listenBack(view); this.addPanel({ view, headerTitle });
update(); update();
} }
@ -1758,7 +1765,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
} }
showGV1Members(): void { showGV1Members(): void {
const { contactCollection } = this.model; const { contactCollection, id } = this.model;
const memberships = const memberships =
contactCollection?.map((conversation: ConversationModel) => { contactCollection?.map((conversation: ConversationModel) => {
@ -1768,19 +1775,29 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
}; };
}) || []; }) || [];
const view = new Whisper.ReactWrapperView({ const reduxState = window.reduxStore.getState();
const getPreferredBadge = getPreferredBadgeSelector(reduxState);
const theme = getTheme(reduxState);
const view = new ReactWrapperView({
className: 'group-member-list panel', className: 'group-member-list panel',
Component: ConversationDetailsMembershipList, JSX: (
props: { <ConversationDetailsMembershipList
canAddNewMembers: false, canAddNewMembers={false}
i18n: window.i18n, conversationId={id}
maxShownMemberCount: 32, i18n={window.i18n}
memberships, getPreferredBadge={getPreferredBadge}
showContactModal: this.showContactModal.bind(this), maxShownMemberCount={32}
}, memberships={memberships}
showContactModal={contactId => {
this.showContactModal(contactId);
}}
theme={theme}
/>
),
}); });
this.listenBack(view); this.addPanel({ view });
view.render(); view.render();
} }
@ -1913,14 +1930,18 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
this.listenTo(message, 'expired', closeLightbox); this.listenTo(message, 'expired', closeLightbox);
this.listenTo(message, 'change', () => { this.listenTo(message, 'change', () => {
if (this.lightboxView) { if (this.lightboxView) {
this.lightboxView.update(getProps()); this.lightboxView.update(<Lightbox {...getProps()} />);
} }
}); });
const getProps = () => { const getProps = (): ComponentProps<typeof Lightbox> => {
const { path, contentType } = tempAttachment; const { path, contentType } = tempAttachment;
return { return {
close: () => {
this.lightboxView?.remove();
},
i18n: window.i18n,
media: [ media: [
{ {
attachment: tempAttachment, attachment: tempAttachment,
@ -1928,7 +1949,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
contentType, contentType,
index: 0, index: 0,
message: { message: {
attachments: message.get('attachments'), attachments: message.get('attachments') || [],
id: message.get('id'), id: message.get('id'),
conversationId: message.get('conversationId'), conversationId: message.get('conversationId'),
received_at: message.get('received_at'), received_at: message.get('received_at'),
@ -1946,10 +1967,9 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
this.lightboxView = undefined; this.lightboxView = undefined;
} }
this.lightboxView = new Whisper.ReactWrapperView({ this.lightboxView = new ReactWrapperView({
className: 'lightbox-wrapper', className: 'lightbox-wrapper',
Component: window.Signal.Components.Lightbox, JSX: <Lightbox {...getProps()} />,
props: getProps(),
onClose: closeLightbox, onClose: closeLightbox,
}); });
@ -2025,7 +2045,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
}, },
}; };
this.stickerPreviewModalView = new Whisper.ReactWrapperView({ this.stickerPreviewModalView = new ReactWrapperView({
className: 'sticker-preview-modal-wrapper', className: 'sticker-preview-modal-wrapper',
JSX: window.Signal.State.Roots.createStickerPreviewModal( JSX: window.Signal.State.Roots.createStickerPreviewModal(
window.reduxStore, window.reduxStore,
@ -2074,16 +2094,25 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
this.lightboxView = undefined; this.lightboxView = undefined;
} }
this.lightboxView = new Whisper.ReactWrapperView({ this.lightboxView = new ReactWrapperView({
className: 'lightbox-wrapper', className: 'lightbox-wrapper',
Component: window.Signal.Components.Lightbox, JSX: (
props: { <Lightbox
getConversation: getConversationSelector(window.reduxStore.getState()), close={() => {
media, this.lightboxView?.remove();
onForward: this.showForwardMessageModal.bind(this), }}
onSave, i18n={window.i18n}
selectedIndex: selectedIndex >= 0 ? selectedIndex : 0, getConversation={getConversationSelector(
}, window.reduxStore.getState()
)}
media={media}
onForward={messageId => {
this.showForwardMessageModal(messageId);
}}
onSave={onSave}
selectedIndex={selectedIndex >= 0 ? selectedIndex : 0}
/>
),
onClose: () => window.Signal.Backbone.Views.Lightbox.hide(), onClose: () => window.Signal.Backbone.Views.Lightbox.hide(),
}); });
@ -2177,7 +2206,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
} }
showGroupLinkManagement(): void { showGroupLinkManagement(): void {
const view = new Whisper.ReactWrapperView({ const view = new ReactWrapperView({
className: 'panel', className: 'panel',
JSX: window.Signal.State.Roots.createGroupLinkManagement( JSX: window.Signal.State.Roots.createGroupLinkManagement(
window.reduxStore, window.reduxStore,
@ -2191,14 +2220,14 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
} }
), ),
}); });
view.headerTitle = window.i18n('ConversationDetails--group-link'); const headerTitle = window.i18n('ConversationDetails--group-link');
this.listenBack(view); this.addPanel({ view, headerTitle });
view.render(); view.render();
} }
showGroupV2Permissions(): void { showGroupV2Permissions(): void {
const view = new Whisper.ReactWrapperView({ const view = new ReactWrapperView({
className: 'panel', className: 'panel',
JSX: window.Signal.State.Roots.createGroupV2Permissions( JSX: window.Signal.State.Roots.createGroupV2Permissions(
window.reduxStore, window.reduxStore,
@ -2212,14 +2241,14 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
} }
), ),
}); });
view.headerTitle = window.i18n('permissions'); const headerTitle = window.i18n('permissions');
this.listenBack(view); this.addPanel({ view, headerTitle });
view.render(); view.render();
} }
showPendingInvites(): void { showPendingInvites(): void {
const view = new Whisper.ReactWrapperView({ const view = new ReactWrapperView({
className: 'panel', className: 'panel',
JSX: window.Signal.State.Roots.createPendingInvites(window.reduxStore, { JSX: window.Signal.State.Roots.createPendingInvites(window.reduxStore, {
conversationId: this.model.id, conversationId: this.model.id,
@ -2232,14 +2261,16 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
}, },
}), }),
}); });
view.headerTitle = window.i18n('ConversationDetails--requests-and-invites'); const headerTitle = window.i18n(
'ConversationDetails--requests-and-invites'
);
this.listenBack(view); this.addPanel({ view, headerTitle });
view.render(); view.render();
} }
showConversationNotificationsSettings(): void { showConversationNotificationsSettings(): void {
const view = new Whisper.ReactWrapperView({ const view = new ReactWrapperView({
className: 'panel', className: 'panel',
JSX: window.Signal.State.Roots.createConversationNotificationsSettings( JSX: window.Signal.State.Roots.createConversationNotificationsSettings(
window.reduxStore, window.reduxStore,
@ -2251,23 +2282,22 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
} }
), ),
}); });
view.headerTitle = window.i18n('ConversationDetails--notifications'); const headerTitle = window.i18n('ConversationDetails--notifications');
this.listenBack(view); this.addPanel({ view, headerTitle });
view.render(); view.render();
} }
showChatColorEditor(): void { showChatColorEditor(): void {
const view = new Whisper.ReactWrapperView({ const view = new ReactWrapperView({
className: 'panel', className: 'panel',
JSX: window.Signal.State.Roots.createChatColorPicker(window.reduxStore, { JSX: window.Signal.State.Roots.createChatColorPicker(window.reduxStore, {
conversationId: this.model.get('id'), conversationId: this.model.get('id'),
}), }),
}); });
const headerTitle = window.i18n('ChatColorPicker__menu-title');
view.headerTitle = window.i18n('ChatColorPicker__menu-title'); this.addPanel({ view, headerTitle });
this.listenBack(view);
view.render(); view.render();
} }
@ -2331,16 +2361,16 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
this.onOutgoingVideoCallInConversation.bind(this), this.onOutgoingVideoCallInConversation.bind(this),
}; };
const view = new Whisper.ReactWrapperView({ const view = new ReactWrapperView({
className: 'conversation-details-pane panel', className: 'conversation-details-pane panel',
JSX: window.Signal.State.Roots.createConversationDetails( JSX: window.Signal.State.Roots.createConversationDetails(
window.reduxStore, window.reduxStore,
props props
), ),
}); });
view.headerTitle = ''; const headerTitle = '';
this.listenBack(view); this.addPanel({ view, headerTitle });
view.render(); view.render();
} }
@ -2366,7 +2396,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
this.resetPanel(); this.resetPanel();
}; };
const view = new Whisper.ReactWrapperView({ const view = new ReactWrapperView({
className: 'panel message-detail-wrapper', className: 'panel message-detail-wrapper',
JSX: window.Signal.State.Roots.createMessageDetail( JSX: window.Signal.State.Roots.createMessageDetail(
window.reduxStore, window.reduxStore,
@ -2386,12 +2416,12 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
this.listenTo(message, 'expired', onClose); this.listenTo(message, 'expired', onClose);
// We could listen to all involved contacts, but we'll call that overkill // We could listen to all involved contacts, but we'll call that overkill
this.listenBack(view); this.addPanel({ view });
view.render(); view.render();
} }
showStickerManager(): void { showStickerManager(): void {
const view = new Whisper.ReactWrapperView({ const view = new ReactWrapperView({
className: ['sticker-manager-wrapper', 'panel'].join(' '), className: ['sticker-manager-wrapper', 'panel'].join(' '),
JSX: window.Signal.State.Roots.createStickerManager(window.reduxStore), JSX: window.Signal.State.Roots.createStickerManager(window.reduxStore),
onClose: () => { onClose: () => {
@ -2399,7 +2429,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
}, },
}); });
this.listenBack(view); this.addPanel({ view });
view.render(); view.render();
} }
@ -2413,27 +2443,29 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
uuid: UUIDStringType; uuid: UUIDStringType;
}; };
}): void { }): void {
const view = new Whisper.ReactWrapperView({ const view = new ReactWrapperView({
Component: window.Signal.Components.ContactDetail,
className: 'contact-detail-pane panel', className: 'contact-detail-pane panel',
props: { JSX: (
contact, <ContactDetail
hasSignalAccount: Boolean(signalAccount), i18n={window.i18n}
onSendMessage: () => { contact={contact}
if (signalAccount) { hasSignalAccount={Boolean(signalAccount)}
this.startConversation( onSendMessage={() => {
signalAccount.phoneNumber, if (signalAccount) {
signalAccount.uuid this.startConversation(
); signalAccount.phoneNumber,
} signalAccount.uuid
}, );
}, }
}}
/>
),
onClose: () => { onClose: () => {
this.resetPanel(); this.resetPanel();
}, },
}); });
this.listenBack(view); this.addPanel({ view });
} }
startConversation(e164: string, uuid: UUIDStringType): void { startConversation(e164: string, uuid: UUIDStringType): void {
@ -2460,24 +2492,24 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
); );
} }
listenBack(view: AnyViewClass): void { addPanel(panel: PanelType): void {
this.panels = this.panels || []; this.panels = this.panels || [];
if (this.panels.length === 0) { if (this.panels.length === 0) {
this.previousFocus = document.activeElement as HTMLElement; this.previousFocus = document.activeElement as HTMLElement;
} }
this.panels.unshift(view); this.panels.unshift(panel);
view.$el.insertAfter(this.$('.panel').last()); panel.view.$el.insertAfter(this.$('.panel').last());
view.$el.one('animationend', () => { panel.view.$el.one('animationend', () => {
view.$el.addClass('panel--static'); panel.view.$el.addClass('panel--static');
}); });
window.reduxActions.conversations.setSelectedConversationPanelDepth( window.reduxActions.conversations.setSelectedConversationPanelDepth(
this.panels.length this.panels.length
); );
window.reduxActions.conversations.setSelectedConversationHeaderTitle( window.reduxActions.conversations.setSelectedConversationHeaderTitle(
view.headerTitle panel.headerTitle
); );
} }
resetPanel(): void { resetPanel(): void {
@ -2485,7 +2517,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
return; return;
} }
const view = this.panels.shift(); const panel = this.panels.shift();
if ( if (
this.panels.length === 0 && this.panels.length === 0 &&
@ -2497,12 +2529,12 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
} }
if (this.panels.length > 0) { if (this.panels.length > 0) {
this.panels[0].$el.fadeIn(250); this.panels[0].view.$el.fadeIn(250);
} }
if (view) { if (panel) {
view.$el.addClass('panel--remove').one('transitionend', () => { panel.view.$el.addClass('panel--remove').one('transitionend', () => {
view.remove(); panel.view.remove();
if (this.panels.length === 0) { if (this.panels.length === 0) {
// Make sure poppers are positioned properly // Make sure poppers are positioned properly

View File

@ -1,11 +1,14 @@
// Copyright 2014-2021 Signal Messenger, LLC // Copyright 2014-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react';
import * as Backbone from 'backbone'; import * as Backbone from 'backbone';
import * as log from '../logging/log'; import * as log from '../logging/log';
import type { ConversationModel } from '../models/conversations'; import type { ConversationModel } from '../models/conversations';
import { ReactWrapperView } from './ReactWrapperView';
import { showToast } from '../util/showToast'; import { showToast } from '../util/showToast';
import { strictAssert } from '../util/assert'; import { strictAssert } from '../util/assert';
import { WhatsNewLink } from '../components/WhatsNewLink';
import { ToastStickerPackInstallFailed } from '../components/ToastStickerPackInstallFailed'; import { ToastStickerPackInstallFailed } from '../components/ToastStickerPackInstallFailed';
window.Whisper = window.Whisper || {}; window.Whisper = window.Whisper || {};
@ -163,12 +166,13 @@ Whisper.InboxView = Whisper.View.extend({
return; return;
} }
const { showWhatsNewModal } = window.reduxActions.globalModals; const { showWhatsNewModal } = window.reduxActions.globalModals;
this.whatsNewLink = new Whisper.ReactWrapperView({ this.whatsNewLink = new ReactWrapperView({
Component: window.Signal.Components.WhatsNewLink, JSX: (
props: { <WhatsNewLink
i18n: window.i18n, i18n={window.i18n}
showWhatsNewModal, showWhatsNewModal={showWhatsNewModal}
}, />
),
}); });
this.$('.whats-new-placeholder').append(this.whatsNewLink.el); this.$('.whats-new-placeholder').append(this.whatsNewLink.el);
}, },
@ -176,7 +180,7 @@ Whisper.InboxView = Whisper.View.extend({
if (this.leftPaneView) { if (this.leftPaneView) {
return; return;
} }
this.leftPaneView = new Whisper.ReactWrapperView({ this.leftPaneView = new ReactWrapperView({
className: 'left-pane-wrapper', className: 'left-pane-wrapper',
JSX: window.Signal.State.Roots.createLeftPane(window.reduxStore), JSX: window.Signal.State.Roots.createLeftPane(window.reduxStore),
}); });

34
ts/window.d.ts vendored
View File

@ -74,17 +74,11 @@ import { BatcherType } from './util/batcher';
import { AttachmentList } from './components/conversation/AttachmentList'; import { AttachmentList } from './components/conversation/AttachmentList';
import { ChatColorPicker } from './components/ChatColorPicker'; import { ChatColorPicker } from './components/ChatColorPicker';
import { ConfirmationDialog } from './components/ConfirmationDialog'; import { ConfirmationDialog } from './components/ConfirmationDialog';
import { ContactDetail } from './components/conversation/ContactDetail';
import { ContactModal } from './components/conversation/ContactModal'; import { ContactModal } from './components/conversation/ContactModal';
import { ErrorModal } from './components/ErrorModal';
import { Lightbox } from './components/Lightbox';
import { MediaGallery } from './components/conversation/media-gallery/MediaGallery';
import { MessageDetail } from './components/conversation/MessageDetail'; import { MessageDetail } from './components/conversation/MessageDetail';
import { ProgressModal } from './components/ProgressModal';
import { Quote } from './components/conversation/Quote'; import { Quote } from './components/conversation/Quote';
import { StagedLinkPreview } from './components/conversation/StagedLinkPreview'; import { StagedLinkPreview } from './components/conversation/StagedLinkPreview';
import { DisappearingTimeDialog } from './components/DisappearingTimeDialog'; import { DisappearingTimeDialog } from './components/DisappearingTimeDialog';
import { WhatsNewLink } from './components/WhatsNewLink';
import { DownloadedAttachmentType } from './types/Attachment'; import { DownloadedAttachmentType } from './types/Attachment';
import { ElectronLocaleType } from './util/mapToSupportLocale'; import { ElectronLocaleType } from './util/mapToSupportLocale';
import { SignalProtocolStore } from './SignalProtocolStore'; import { SignalProtocolStore } from './SignalProtocolStore';
@ -370,17 +364,11 @@ declare global {
AttachmentList: typeof AttachmentList; AttachmentList: typeof AttachmentList;
ChatColorPicker: typeof ChatColorPicker; ChatColorPicker: typeof ChatColorPicker;
ConfirmationDialog: typeof ConfirmationDialog; ConfirmationDialog: typeof ConfirmationDialog;
ContactDetail: typeof ContactDetail;
ContactModal: typeof ContactModal; ContactModal: typeof ContactModal;
DisappearingTimeDialog: typeof DisappearingTimeDialog; DisappearingTimeDialog: typeof DisappearingTimeDialog;
ErrorModal: typeof ErrorModal;
Lightbox: typeof Lightbox;
MediaGallery: typeof MediaGallery;
MessageDetail: typeof MessageDetail; MessageDetail: typeof MessageDetail;
ProgressModal: typeof ProgressModal;
Quote: typeof Quote; Quote: typeof Quote;
StagedLinkPreview: typeof StagedLinkPreview; StagedLinkPreview: typeof StagedLinkPreview;
WhatsNewLink: typeof WhatsNewLink;
}; };
OS: typeof OS; OS: typeof OS;
State: { State: {
@ -506,17 +494,6 @@ export class CanvasVideoRenderer {
constructor(canvas: Ref<HTMLCanvasElement>); constructor(canvas: Ref<HTMLCanvasElement>);
} }
export class AnyViewClass extends window.Backbone.View<any> {
public headerTitle?: string;
static show(view: typeof AnyViewClass, element: Element): void;
constructor(options?: any);
}
export class BasicReactWrapperViewClass extends AnyViewClass {
public update(options: any): void;
}
export type WhisperType = { export type WhisperType = {
Conversation: typeof ConversationModel; Conversation: typeof ConversationModel;
ConversationCollection: typeof ConversationModelCollectionType; ConversationCollection: typeof ConversationModelCollectionType;
@ -530,18 +507,11 @@ export type WhisperType = {
// Backbone views // Backbone views
// Modernized
ConversationView: typeof ConversationView; ConversationView: typeof ConversationView;
// Note: we can no longer use 'View.extend' once we've moved to Typescript's preferred // Note: we can no longer use 'View.extend' once we've moved to Typescript's preferred
// 'extend View' syntax. Thus, we'll need to typescriptify most of it at once. // 'extend View' syntax. Thus, we'll need to typescriptify most of it at once.
ClearDataView: typeof AnyViewClass; InboxView: typeof Backbone.View;
ConversationLoadingScreen: typeof AnyViewClass; View: typeof Backbone.View;
GroupMemberList: typeof AnyViewClass;
InboxView: typeof AnyViewClass;
KeyVerificationPanelView: typeof AnyViewClass;
ReactWrapperView: typeof BasicReactWrapperViewClass;
SafetyNumberChangeDialogView: typeof AnyViewClass;
View: typeof AnyViewClass;
}; };