2017-08-27 06:33:29 +08:00
|
|
|
import React, {Component} from 'react';
|
|
|
|
import PropTypes from 'prop-types'
|
2016-09-27 07:59:43 +08:00
|
|
|
import ReactDOM from 'react-dom';
|
|
|
|
import fs from 'fs';
|
|
|
|
import path from 'path';
|
2017-06-29 13:45:15 +08:00
|
|
|
import {Actions, AttachmentStore} from 'nylas-exports'
|
2017-03-24 04:34:24 +08:00
|
|
|
import {ImageAttachmentItem} from 'nylas-component-kit'
|
2016-09-27 07:59:43 +08:00
|
|
|
|
|
|
|
export default class InlineImageUploadContainer extends Component {
|
|
|
|
static displayName = 'InlineImageUploadContainer';
|
|
|
|
|
|
|
|
static supportsPreviewWithinEditor = false;
|
|
|
|
|
|
|
|
static propTypes = {
|
|
|
|
draft: PropTypes.object.isRequired,
|
2017-06-29 08:22:18 +08:00
|
|
|
fileId: PropTypes.string.isRequired,
|
2016-10-21 08:47:56 +08:00
|
|
|
session: PropTypes.object,
|
2016-09-27 07:59:43 +08:00
|
|
|
isPreview: PropTypes.bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
_onGoEdit = () => {
|
2016-10-21 08:47:56 +08:00
|
|
|
if (!this.props.session) {
|
|
|
|
console.warn("InlineImage editor cannot be activated, `session` prop not present. (isPreview?)")
|
|
|
|
return;
|
|
|
|
}
|
2016-09-27 07:59:43 +08:00
|
|
|
// This is just a fun temporary hack because I was jealous of Apple Mail.
|
|
|
|
//
|
|
|
|
const el = ReactDOM.findDOMNode(this);
|
|
|
|
const rect = el.getBoundingClientRect();
|
|
|
|
|
|
|
|
const editorEl = document.createElement('div');
|
|
|
|
editorEl.style.position = 'absolute';
|
|
|
|
editorEl.style.left = `${rect.left}px`;
|
|
|
|
editorEl.style.top = `${rect.top}px`;
|
|
|
|
editorEl.style.width = `${rect.width}px`;
|
|
|
|
editorEl.style.height = `${rect.height}px`;
|
|
|
|
editorEl.style.zIndex = 2000;
|
|
|
|
|
|
|
|
const editorCanvas = document.createElement('canvas');
|
|
|
|
editorCanvas.width = rect.width * window.devicePixelRatio;
|
|
|
|
editorCanvas.height = rect.height * window.devicePixelRatio;
|
|
|
|
editorCanvas.style.width = `${rect.width}px`;
|
|
|
|
editorCanvas.style.height = `${rect.height}px`;
|
|
|
|
editorEl.appendChild(editorCanvas);
|
|
|
|
|
|
|
|
const editorCtx = editorCanvas.getContext("2d");
|
2016-11-09 03:54:19 +08:00
|
|
|
editorCtx.drawImage(el.querySelector('.file-preview img'), 0, 0, editorCanvas.width, editorCanvas.height);
|
2016-09-27 07:59:43 +08:00
|
|
|
editorCtx.strokeStyle = "#df4b26";
|
|
|
|
editorCtx.lineJoin = "round";
|
|
|
|
editorCtx.lineWidth = 3 * window.devicePixelRatio;
|
|
|
|
|
|
|
|
let penDown = false;
|
|
|
|
let penXY = null;
|
|
|
|
editorCanvas.addEventListener('mousedown', (event) => {
|
|
|
|
penDown = true;
|
|
|
|
penXY = {
|
|
|
|
x: event.offsetX,
|
|
|
|
y: event.offsetY,
|
|
|
|
}
|
|
|
|
});
|
|
|
|
editorCanvas.addEventListener('mousemove', (event) => {
|
|
|
|
if (penDown) {
|
|
|
|
const nextPenXY = {
|
|
|
|
x: event.offsetX,
|
|
|
|
y: event.offsetY,
|
|
|
|
}
|
|
|
|
editorCtx.beginPath();
|
|
|
|
editorCtx.moveTo(penXY.x * window.devicePixelRatio, penXY.y * window.devicePixelRatio);
|
|
|
|
editorCtx.lineTo(nextPenXY.x * window.devicePixelRatio, nextPenXY.y * window.devicePixelRatio);
|
|
|
|
editorCtx.closePath();
|
|
|
|
editorCtx.stroke();
|
|
|
|
penXY = nextPenXY;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
editorCanvas.addEventListener('mouseup', () => {
|
|
|
|
penDown = false;
|
|
|
|
penXY = null;
|
|
|
|
});
|
|
|
|
|
|
|
|
const backgroundEl = document.createElement('div');
|
|
|
|
backgroundEl.style.background = 'rgba(0,0,0,0.4)';
|
|
|
|
backgroundEl.style.position = 'absolute';
|
|
|
|
backgroundEl.style.top = '0px';
|
|
|
|
backgroundEl.style.left = '0px';
|
|
|
|
backgroundEl.style.right = '0px';
|
|
|
|
backgroundEl.style.bottom = '0px';
|
|
|
|
backgroundEl.style.zIndex = 1999;
|
|
|
|
backgroundEl.addEventListener('click', () => {
|
|
|
|
editorCanvas.toBlob((blob) => {
|
|
|
|
const reader = new FileReader();
|
|
|
|
reader.addEventListener('loadend', () => {
|
2017-06-29 08:22:18 +08:00
|
|
|
const {draft, session, fileId} = this.props;
|
2016-09-27 07:59:43 +08:00
|
|
|
const buffer = new Buffer(new Uint8Array(reader.result));
|
2017-06-29 08:22:18 +08:00
|
|
|
const file = draft.files.find(u =>
|
|
|
|
u.id === fileId
|
2016-09-27 07:59:43 +08:00
|
|
|
);
|
|
|
|
|
2017-06-29 13:45:15 +08:00
|
|
|
const filepath = AttachmentStore.pathForFile(file);
|
2017-06-29 08:22:18 +08:00
|
|
|
const nextFileName = `edited-${Date.now()}.png`;
|
|
|
|
const nextFilePath = path.join(path.dirname(filepath), nextFileName);
|
|
|
|
|
|
|
|
fs.writeFile(nextFilePath, buffer, (err) => {
|
2016-09-27 07:59:43 +08:00
|
|
|
if (err) {
|
|
|
|
NylasEnv.showErrorDialog(err.toString())
|
|
|
|
return;
|
|
|
|
}
|
2016-11-09 03:54:19 +08:00
|
|
|
const img = el.querySelector('.file-preview img');
|
2016-09-27 07:59:43 +08:00
|
|
|
img.style.width = `${rect.width}px`;
|
|
|
|
img.style.height = `${rect.height}px`;
|
|
|
|
img.src = `${img.src}?${Date.now()}`;
|
|
|
|
|
2017-08-01 13:21:00 +08:00
|
|
|
fs.unlink(filepath, () => {});
|
2016-09-27 07:59:43 +08:00
|
|
|
|
2017-06-29 08:22:18 +08:00
|
|
|
const nextFiles = [].concat(draft.files);
|
|
|
|
nextFiles.forEach((f) => {
|
|
|
|
if (f.id === file.id) {
|
|
|
|
f.filename = nextFileName;
|
2016-09-27 07:59:43 +08:00
|
|
|
}
|
|
|
|
});
|
2017-06-29 08:22:18 +08:00
|
|
|
session.changes.add({files: nextFiles});
|
2016-09-27 07:59:43 +08:00
|
|
|
});
|
|
|
|
});
|
|
|
|
reader.readAsArrayBuffer(blob);
|
|
|
|
});
|
|
|
|
document.body.removeChild(editorEl);
|
|
|
|
document.body.removeChild(backgroundEl);
|
|
|
|
});
|
|
|
|
document.body.appendChild(backgroundEl);
|
|
|
|
document.body.appendChild(editorEl);
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
2017-06-29 08:22:18 +08:00
|
|
|
const {draft, fileId, isPreview} = this.props;
|
|
|
|
const file = draft.files.find(u => fileId === u.id);
|
2016-09-27 07:59:43 +08:00
|
|
|
|
2017-06-29 08:22:18 +08:00
|
|
|
if (!file) {
|
2016-09-27 07:59:43 +08:00
|
|
|
return (
|
2016-10-18 08:59:33 +08:00
|
|
|
<span />
|
2016-09-27 07:59:43 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
if (isPreview) {
|
|
|
|
return (
|
2017-06-29 08:22:18 +08:00
|
|
|
<img src={`cid:${file.id}`} alt={file.name} />
|
2016-09-27 07:59:43 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div
|
2017-06-29 08:22:18 +08:00
|
|
|
data-src={`cid:${file.id}`}
|
2016-09-27 07:59:43 +08:00
|
|
|
className="inline-image-upload-container"
|
|
|
|
onDoubleClick={this._onGoEdit}
|
|
|
|
>
|
:art:(attachments): DRY and cleanup attachment components code
Summary:
The attachment components were the only React Components which used
inheritance between components, which is an anti-pattern in react. I
deleted these components in favor of new purely functional/dumb
components exposed via the component-kit: Attachment Item and
ImageAttachmentItem. These are defined in the same file to reuse some
smaller components between them, like the progress-bar, etc.
The attachments pacakage still remains, and only registers a single component to
a new are called MessageAttachments. This InjectedComponent role is
shared by the Composer and MessageItem, and is the only reason this
exists as an injected component in a separate package.
MessageAttachments renders all image and non image attachments for a
message or draft, and binds the appropriate actions for removal, downloading, etc.
The composer still used FileUpload and ImageUpload components for rendering
uploads in the Composer (i.e. when you add an attachment (these are
different from files because they aren't saved until the draft is
sent)). These 2 components were pretty much copied and pasted from the
ones in the attachments package, with subtle differences-- I got rid of
these as well in favor of the new AttachmentItem and ImageAttachmentItem
Also convert more coffee to ES6!
Test Plan: Unit tests
Reviewers: bengotow, evan
Reviewed By: evan
Differential Revision: https://phab.nylas.com/D3381
2016-11-01 08:26:07 +08:00
|
|
|
<ImageAttachmentItem
|
|
|
|
className="file-upload"
|
|
|
|
draggable={false}
|
2017-06-29 13:45:15 +08:00
|
|
|
filePath={AttachmentStore.pathForFile(file)}
|
2017-06-29 08:22:18 +08:00
|
|
|
displayName={file.filename}
|
|
|
|
onRemoveAttachment={() => Actions.removeAttachment(draft.headerMessageId, file)}
|
:art:(attachments): DRY and cleanup attachment components code
Summary:
The attachment components were the only React Components which used
inheritance between components, which is an anti-pattern in react. I
deleted these components in favor of new purely functional/dumb
components exposed via the component-kit: Attachment Item and
ImageAttachmentItem. These are defined in the same file to reuse some
smaller components between them, like the progress-bar, etc.
The attachments pacakage still remains, and only registers a single component to
a new are called MessageAttachments. This InjectedComponent role is
shared by the Composer and MessageItem, and is the only reason this
exists as an injected component in a separate package.
MessageAttachments renders all image and non image attachments for a
message or draft, and binds the appropriate actions for removal, downloading, etc.
The composer still used FileUpload and ImageUpload components for rendering
uploads in the Composer (i.e. when you add an attachment (these are
different from files because they aren't saved until the draft is
sent)). These 2 components were pretty much copied and pasted from the
ones in the attachments package, with subtle differences-- I got rid of
these as well in favor of the new AttachmentItem and ImageAttachmentItem
Also convert more coffee to ES6!
Test Plan: Unit tests
Reviewers: bengotow, evan
Reviewed By: evan
Differential Revision: https://phab.nylas.com/D3381
2016-11-01 08:26:07 +08:00
|
|
|
/>
|
2016-09-27 07:59:43 +08:00
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|