diff --git a/background.html b/background.html
index 49f52b134..b5fb39499 100644
--- a/background.html
+++ b/background.html
@@ -53,7 +53,6 @@
-
diff --git a/background_test.html b/background_test.html
index 19e98cc31..871fb44a2 100644
--- a/background_test.html
+++ b/background_test.html
@@ -52,7 +52,6 @@
-
diff --git a/js/modules/signal.js b/js/modules/signal.js
index fc782f966..e0b35e016 100644
--- a/js/modules/signal.js
+++ b/js/modules/signal.js
@@ -1,7 +1,6 @@
// The idea with this file is to make it webpackable for the style guide
const { bindActionCreators } = require('redux');
-const Backbone = require('../../ts/backbone');
const Crypto = require('./crypto');
const Data = require('./data');
const Database = require('./database');
@@ -363,7 +362,6 @@ exports.setup = (options = {}) => {
return {
AttachmentDownloads,
- Backbone,
Components,
Crypto,
Data,
diff --git a/package.json b/package.json
index e9f82bb49..bcbb8111d 100644
--- a/package.json
+++ b/package.json
@@ -109,7 +109,6 @@
"react-autosize-textarea": "^7.0.0",
"react-contextmenu": "2.11.0",
"react-dom": "16.8.3",
- "react-dropzone": "^11.0.2",
"react-emoji": "^0.5.0",
"react-emoji-render": "^1.2.4",
"react-h5-audio-player": "^3.2.0",
diff --git a/test/index.html b/test/index.html
index ee8c252b8..c8610c097 100644
--- a/test/index.html
+++ b/test/index.html
@@ -40,7 +40,6 @@
-
diff --git a/ts/backbone/index.ts b/ts/backbone/index.ts
deleted file mode 100644
index 26dc5f620..000000000
--- a/ts/backbone/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import * as Views from './views';
-
-export { Views };
diff --git a/ts/backbone/views/Lightbox.ts b/ts/backbone/views/Lightbox.ts
deleted file mode 100644
index 04da21003..000000000
--- a/ts/backbone/views/Lightbox.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-export const show = (element: HTMLElement): void => {
- const container: HTMLDivElement | null = document.querySelector(
- '.lightbox-container'
- );
- if (!container) {
- throw new TypeError("'.lightbox-container' is required");
- }
- // tslint:disable-next-line:no-inner-html
- container.innerHTML = '';
- container.style.display = 'block';
- container.appendChild(element);
-};
-
-export const hide = (): void => {
- const container: HTMLDivElement | null = document.querySelector(
- '.lightbox-container'
- );
- if (!container) {
- return;
- }
- // tslint:disable-next-line:no-inner-html
- container.innerHTML = '';
- container.style.display = 'none';
-};
diff --git a/ts/backbone/views/index.ts b/ts/backbone/views/index.ts
deleted file mode 100644
index e3cf49917..000000000
--- a/ts/backbone/views/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import * as Lightbox from './Lightbox';
-
-export { Lightbox };
diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx
index eac5884c7..6101ba6cb 100644
--- a/ts/components/conversation/Message.tsx
+++ b/ts/components/conversation/Message.tsx
@@ -36,7 +36,6 @@ import { isFileDangerous } from '../../util/isFileDangerous';
import { ColorType, LocalizerType } from '../../types/Util';
import { ContextMenu, ContextMenuTrigger, MenuItem } from 'react-contextmenu';
import { SessionIcon, SessionIconSize, SessionIconType } from '../session/icon';
-import { ReplyingToMessageProps } from '../session/conversation/SessionCompositionBox';
import _ from 'lodash';
import { MessageModel } from '../../../js/models/messages';
diff --git a/ts/components/session/conversation/SessionCompositionBox.tsx b/ts/components/session/conversation/SessionCompositionBox.tsx
index 075885077..e8c226ab3 100644
--- a/ts/components/session/conversation/SessionCompositionBox.tsx
+++ b/ts/components/session/conversation/SessionCompositionBox.tsx
@@ -9,7 +9,6 @@ import TextareaAutosize from 'react-autosize-textarea';
import { SessionIconButton, SessionIconSize, SessionIconType } from '../icon';
import { SessionEmojiPanel } from './SessionEmojiPanel';
import { SessionRecording } from './SessionRecording';
-import * as GoogleChrome from '../../../util/GoogleChrome';
import { SignalService } from '../../../protobuf';
@@ -20,6 +19,7 @@ import { SessionQuotedMessageComposition } from './SessionQuotedMessageCompositi
import { Flex } from '../Flex';
import { AttachmentList } from '../../conversation/AttachmentList';
import { ToastUtils } from '../../../session/utils';
+import { AttachmentUtil } from '../../../util';
export interface ReplyingToMessageProps {
convoId: string;
@@ -30,7 +30,7 @@ export interface ReplyingToMessageProps {
attachments?: Array;
}
-interface StagedAttachmentType extends AttachmentType {
+export interface StagedAttachmentType extends AttachmentType {
file: File;
}
@@ -45,11 +45,14 @@ interface Props {
onLoadVoiceNoteView: any;
onExitVoiceNoteView: any;
- dropZoneFiles: FileList;
quotedMessageProps?: ReplyingToMessageProps;
removeQuotedMessage: () => void;
textarea: React.RefObject;
+ stagedAttachments: Array;
+ clearAttachments: () => any;
+ removeAttachment: (toRemove: AttachmentType) => void;
+ onChoseAttachments: (newAttachments: FileList) => void;
}
interface State {
@@ -58,7 +61,6 @@ interface State {
mediaSetting: boolean | null;
showEmojiPanel: boolean;
- stagedAttachments: Array;
voiceRecording?: Blob;
}
@@ -71,7 +73,6 @@ export class SessionCompositionBox extends React.Component {
super(props);
this.state = {
message: '',
- stagedAttachments: [],
voiceRecording: undefined,
showRecordingView: false,
mediaSetting: null,
@@ -101,8 +102,6 @@ export class SessionCompositionBox extends React.Component {
// Attachments
this.onChoseAttachment = this.onChoseAttachment.bind(this);
this.onChooseAttachment = this.onChooseAttachment.bind(this);
- this.clearAttachments = this.clearAttachments.bind(this);
- this.removeAttachment = this.removeAttachment.bind(this);
// On Sending
this.onSendMessage = this.onSendMessage.bind(this);
@@ -267,7 +266,7 @@ export class SessionCompositionBox extends React.Component {
}
private renderAttachmentsStaged() {
- const { stagedAttachments } = this.state;
+ const { stagedAttachments } = this.props;
if (stagedAttachments && stagedAttachments.length) {
return (
{
// tslint:disable-next-line: no-empty
onClickAttachment={() => {}}
onAddAttachment={this.onChooseAttachment}
- onCloseAttachment={this.removeAttachment}
- onClose={this.clearAttachments}
+ onCloseAttachment={this.props.removeAttachment}
+ onClose={this.props.clearAttachments}
/>
);
}
@@ -293,11 +292,7 @@ export class SessionCompositionBox extends React.Component {
if (!attachmentsFileList || attachmentsFileList.length === 0) {
return;
}
-
- // tslint:disable-next-line: prefer-for-of
- for (let i = 0; i < attachmentsFileList.length; i++) {
- await this.maybeAddAttachment(attachmentsFileList[i]);
- }
+ this.props.onChoseAttachments(attachmentsFileList);
}
private async onKeyDown(event: any) {
@@ -331,7 +326,7 @@ export class SessionCompositionBox extends React.Component {
ToastUtils.pushMessageBodyTooLong();
return;
}
- if (msgLen === 0 && this.state.stagedAttachments?.length === 0) {
+ if (msgLen === 0 && this.props.stagedAttachments?.length === 0) {
ToastUtils.pushMessageBodyMissing();
return;
}
@@ -361,13 +356,13 @@ export class SessionCompositionBox extends React.Component {
// Message sending sucess
this.props.onMessageSuccess();
- // Empty stagedAttachments
// Empty composition box
this.setState({
message: '',
showEmojiPanel: false,
});
- this.clearAttachments();
+ // Empty stagedAttachments
+ this.props.clearAttachments();
} catch (e) {
// Message sending failed
window.log.error(e);
@@ -377,11 +372,11 @@ export class SessionCompositionBox extends React.Component {
// this function is called right before sending a message, to gather really files bejind attachments.
private async getFiles() {
- const { stagedAttachments } = this.state;
+ const { stagedAttachments } = this.props;
const files = await Promise.all(
- stagedAttachments.map(attachment => this.getFile(attachment))
+ stagedAttachments.map(attachment => AttachmentUtil.getFile(attachment))
);
- this.clearAttachments();
+ this.props.clearAttachments();
return files;
}
@@ -488,316 +483,4 @@ export class SessionCompositionBox extends React.Component {
// Focus the textarea when user clicks anywhere in the composition box
this.textarea.current?.focus();
}
-
- // tslint:disable: max-func-body-length cyclomatic-complexity
- private async maybeAddAttachment(file: any) {
- if (!file) {
- return;
- }
-
- const fileName = file.name;
- const contentType = file.type;
-
- const { stagedAttachments } = this.state;
-
- if (window.Signal.Util.isFileDangerous(fileName)) {
- ToastUtils.pushDangerousFileError();
- return;
- }
-
- if (stagedAttachments.length >= 32) {
- ToastUtils.pushMaximumAttachmentsError();
- return;
- }
-
- const haveNonImage = _.some(
- stagedAttachments,
- attachment => !MIME.isImage(attachment.contentType)
- );
- // You can't add another attachment if you already have a non-image staged
- if (haveNonImage) {
- ToastUtils.pushMultipleNonImageError();
- return;
- }
-
- // You can't add a non-image attachment if you already have attachments staged
- if (!MIME.isImage(contentType) && stagedAttachments.length > 0) {
- ToastUtils.pushCannotMixError();
- return;
- }
- const { VisualAttachment } = window.Signal.Types;
-
- const renderVideoPreview = async () => {
- const objectUrl = URL.createObjectURL(file);
- try {
- const type = 'image/png';
-
- const thumbnail = await VisualAttachment.makeVideoScreenshot({
- objectUrl,
- contentType: type,
- logger: window.log,
- });
- const data = await VisualAttachment.blobToArrayBuffer(thumbnail);
- const url = window.Signal.Util.arrayBufferToObjectURL({
- data,
- type,
- });
- this.addAttachment({
- file,
- size: file.size,
- fileName,
- contentType,
- videoUrl: objectUrl,
- url,
- isVoiceMessage: false,
- });
- } catch (error) {
- URL.revokeObjectURL(objectUrl);
- }
- };
-
- const renderImagePreview = async () => {
- if (!MIME.isJPEG(contentType)) {
- const urlImage = URL.createObjectURL(file);
- if (!urlImage) {
- throw new Error('Failed to create object url for image!');
- }
- this.addAttachment({
- file,
- size: file.size,
- fileName,
- contentType,
- url: urlImage,
- isVoiceMessage: false,
- });
- return;
- }
-
- const url = await window.autoOrientImage(file);
- this.addAttachment({
- file,
- size: file.size,
- fileName,
- contentType,
- url,
- isVoiceMessage: false,
- });
- };
-
- try {
- const blob = await this.autoScale({
- contentType,
- file,
- });
- let limitKb = 10000;
- const blobType =
- file.type === 'image/gif' ? 'gif' : contentType.split('/')[0];
-
- switch (blobType) {
- case 'image':
- limitKb = 6000;
- break;
- case 'gif':
- limitKb = 10000;
- break;
- case 'audio':
- limitKb = 10000;
- break;
- case 'video':
- limitKb = 10000;
- break;
- default:
- limitKb = 10000;
- }
- // if ((blob.file.size / 1024).toFixed(4) >= limitKb) {
- // const units = ['kB', 'MB', 'GB'];
- // let u = -1;
- // let limit = limitKb * 1000;
- // do {
- // limit /= 1000;
- // u += 1;
- // } while (limit >= 1000 && u < units.length - 1);
- // // this.showFileSizeError(limit, units[u]);
- // return;
- // }
- } catch (error) {
- window.log.error(
- 'Error ensuring that image is properly sized:',
- error && error.stack ? error.stack : error
- );
-
- ToastUtils.pushLoadAttachmentFailure();
- return;
- }
-
- try {
- if (GoogleChrome.isImageTypeSupported(contentType)) {
- await renderImagePreview();
- } else if (GoogleChrome.isVideoTypeSupported(contentType)) {
- await renderVideoPreview();
- } else {
- this.addAttachment({
- file,
- size: file.size,
- contentType,
- fileName,
- url: '',
- isVoiceMessage: false,
- });
- }
- } catch (e) {
- window.log.error(
- `Was unable to generate thumbnail for file type ${contentType}`,
- e && e.stack ? e.stack : e
- );
- this.addAttachment({
- file,
- size: file.size,
- contentType,
- fileName,
- isVoiceMessage: false,
- url: '',
- });
- }
- }
-
- private addAttachment(attachment: StagedAttachmentType) {
- const { stagedAttachments } = this.state;
- if (attachment.isVoiceMessage && stagedAttachments.length > 0) {
- throw new Error('A voice note cannot be sent with other attachments');
- }
- this.setState(
- {
- stagedAttachments: [...stagedAttachments, { ...attachment }],
- },
- this.focusCompositionBox
- );
- }
-
- private async autoScale(
- attachment: T
- ): Promise {
- const { contentType, file } = attachment;
- if (contentType.split('/')[0] !== 'image' || contentType === 'image/tiff') {
- // nothing to do
- return Promise.resolve(attachment);
- }
-
- return new Promise((resolve, reject) => {
- const url = URL.createObjectURL(file);
- const img = document.createElement('img');
- img.onerror = reject;
- img.onload = () => {
- URL.revokeObjectURL(url);
-
- const maxSize = 6000 * 1024;
- const maxHeight = 4096;
- const maxWidth = 4096;
- if (
- img.naturalWidth <= maxWidth &&
- img.naturalHeight <= maxHeight &&
- file.size <= maxSize
- ) {
- resolve(attachment);
- return;
- }
-
- const gifMaxSize = 25000 * 1024;
- if (file.type === 'image/gif' && file.size <= gifMaxSize) {
- resolve(attachment);
- return;
- }
-
- if (file.type === 'image/gif') {
- reject(new Error('GIF is too large'));
- return;
- }
-
- const canvas = window.loadImage.scale(img, {
- canvas: true,
- maxWidth,
- maxHeight,
- });
-
- let quality = 0.95;
- let i = 4;
- let blob;
- do {
- i -= 1;
- blob = window.dataURLToBlobSync(
- canvas.toDataURL('image/jpeg', quality)
- );
- quality = (quality * maxSize) / blob.size;
- // NOTE: During testing with a large image, we observed the
- // `quality` value being > 1. Should we clamp it to [0.5, 1.0]?
- // See: https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob#Syntax
- if (quality < 0.5) {
- quality = 0.5;
- }
- } while (i > 0 && blob.size > maxSize);
-
- resolve({
- ...attachment,
- file: blob,
- });
- };
- img.src = url;
- });
- }
-
- private clearAttachments() {
- this.state.stagedAttachments.forEach(attachment => {
- if (attachment.url) {
- URL.revokeObjectURL(attachment.url);
- }
- if (attachment.videoUrl) {
- URL.revokeObjectURL(attachment.videoUrl);
- }
- });
- this.setState({ stagedAttachments: [] });
- }
-
- private removeAttachment(attachment: AttachmentType) {
- const { stagedAttachments } = this.state;
- const updatedStagedAttachments = (stagedAttachments || []).filter(
- m => m.fileName !== attachment.fileName
- );
-
- this.setState({ stagedAttachments: updatedStagedAttachments });
- }
-
- private async getFile(attachment: any) {
- if (!attachment) {
- return Promise.resolve();
- }
-
- const attachmentFlags = attachment.isVoiceMessage
- ? SignalService.AttachmentPointer.Flags.VOICE_MESSAGE
- : null;
-
- const scaled = await this.autoScale(attachment);
- const fileRead = await this.readFile(scaled);
- return {
- ...fileRead,
- url: undefined,
- flags: attachmentFlags || null,
- };
- }
-
- private async readFile(attachment: any): Promise