From 2f9233222d54b0a78a041794f6c9095d84efd934 Mon Sep 17 00:00:00 2001 From: Jori Lallo Date: Mon, 28 Mar 2016 21:54:16 -0700 Subject: [PATCH] Implemented drag and drop file uploads --- package.json | 1 + src/Actions/index.js | 5 ++ .../MarkdownEditor/MarkdownEditor.js | 86 +++++++++++++++++-- src/Components/MarkdownEditor/codemirror.css | 6 ++ src/Constants.js | 2 +- src/Reducers/index.js | 9 ++ src/Views/Dashboard/Dashboard.js | 26 +++--- 7 files changed, 118 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index e212cff1a..88ad47add 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "normalize.css": "^3.0.3", "react": "^0.14.7", "react-codemirror": "^0.2.5", + "react-dropzone": "^3.3.2", "react-medium-editor": "^1.6.2", "react-redux": "^4.4.0", "react-router": "^2.0.0", diff --git a/src/Actions/index.js b/src/Actions/index.js index 9859f028d..cbf7928bf 100644 --- a/src/Actions/index.js +++ b/src/Actions/index.js @@ -7,6 +7,7 @@ import keyMirror from 'fbjs/lib/keyMirror'; export const UPDATE_TEXT = 'UPDATE_TEXT'; export const TOGGLE_EDITORS = 'TOGGLE_EDITORS'; export const ADD_REVISION = 'ADD_REVISION'; +export const REPLACE_TEXT= 'REPLACE_TEXT'; /* * Other Constants @@ -32,3 +33,7 @@ export function toggleEditors(toggledEditor) { export function addRevision(createdAt) { return { type: ADD_REVISION, createdAt }; } + +export function replaceText(originalText, replacedText) { + return { type: REPLACE_TEXT, originalText, replacedText }; +} \ No newline at end of file diff --git a/src/Components/MarkdownEditor/MarkdownEditor.js b/src/Components/MarkdownEditor/MarkdownEditor.js index cdac8d1a7..a1d2a0bd3 100644 --- a/src/Components/MarkdownEditor/MarkdownEditor.js +++ b/src/Components/MarkdownEditor/MarkdownEditor.js @@ -3,14 +3,22 @@ import Codemirror from 'react-codemirror'; import 'codemirror/mode/gfm/gfm'; import 'codemirror/mode/javascript/javascript'; import 'codemirror/addon/edit/continuelist'; +import Dropzone from 'react-dropzone'; import styles from './MarkdownEditor.scss'; import './codemirror.css'; +import { client } from '../../Utils/ApiClient'; + class MarkdownAtlas extends React.Component { static propTypes = { text: React.PropTypes.string, onChange: React.PropTypes.func, + replaceText: React.PropTypes.func, + } + + getEditorInstance = () => { + return this.refs.editor.getCodeMirror(); } onChange = (newText) => { @@ -19,6 +27,63 @@ class MarkdownAtlas extends React.Component { } } + componentDidMount = () => { + console.log(this.props); + } + + onDropAccepted = (files) => { + const file = files[0]; + const editor = this.getEditorInstance(); + + const cursorPosition = editor.getCursor(); + const insertOnNewLine = cursorPosition.ch !== 0; + let newCursorPositionLine; + + // Lets set up the upload text + const pendingUploadTag = `![${file.name}](Uploading...)`; + if (insertOnNewLine) { + editor.replaceSelection('\n' + pendingUploadTag + '\n'); + newCursorPositionLine = cursorPosition.line + 3; + } else { + editor.replaceSelection(pendingUploadTag + '\n'); + newCursorPositionLine = cursorPosition.line + 2; + } + editor.setCursor(newCursorPositionLine, 0); + + client.post('/v0/user/s3', { + kind: file.type, + size: file.size, + filename: file.name, + }) + .then(data => { + // Upload using FormData API + let formData = new FormData(); + + for (let key in data.form) { + formData.append(key, data.form[key]); + } + + if (file.blob) { + formData.append('file', file.file); + } else { + formData.append('file', file); + } + + fetch(data.upload_url, { + method: 'post', + body: formData + }) + .then(s3Response => { + this.props.replaceText(pendingUploadTag, `![${file.name}](${data.asset.url})`); + editor.setCursor(newCursorPositionLine, 0); + }) + .catch(err => { + this.props.replaceText(pendingUploadTag, ''); + editor.setCursor(newCursorPositionLine, 0); + }); + }); + } + render = () => { // https://github.com/jbt/markdown-editor/blob/master/index.html const options = { @@ -39,12 +104,21 @@ class MarkdownAtlas extends React.Component { // - Emojify // - return ( -
- +
+ + +
); } diff --git a/src/Components/MarkdownEditor/codemirror.css b/src/Components/MarkdownEditor/codemirror.css index f076bb891..3fb8a6929 100644 --- a/src/Components/MarkdownEditor/codemirror.css +++ b/src/Components/MarkdownEditor/codemirror.css @@ -10,6 +10,7 @@ @import url(https://fonts.googleapis.com/css?family=Cousine:400,700,700italic,400italic); +/* Custom styling */ .cm-s-atlas.CodeMirror { background: #ffffff; color: #202020; @@ -21,6 +22,11 @@ background: #90CAF9; } +/* Disable ondrag cursor for file uploads */ +.cm-s-atlas div.CodeMirror-dragcursors { + visibility: hidden; +} + .cm-s-atlas .CodeMirror-line::selection, .cm-s-atlas .CodeMirror-line > span::selection, .cm-s-atlas .CodeMirror-line > span > span::selection { diff --git a/src/Constants.js b/src/Constants.js index 4b9e2dbbc..197b82f6d 100644 --- a/src/Constants.js +++ b/src/Constants.js @@ -11,7 +11,7 @@ const keys = keyMirror({ // Constant values const constants = { API_USER_AGENT: `${name}/${version}`, - API_BASE_URL: 'http://localhost:3000/api', + API_BASE_URL: 'http://localhost:8000/api', LOGIN_PATH: '/login', LOGIN_SUCCESS_PATH: '/dashboard', }; diff --git a/src/Reducers/index.js b/src/Reducers/index.js index 6472f0f37..634dbdbc9 100644 --- a/src/Reducers/index.js +++ b/src/Reducers/index.js @@ -6,6 +6,7 @@ import { TOGGLE_EDITORS, TOGGLE_HISTORY_SIDEBAR, ADD_REVISION, + REPLACE_TEXT, ActiveEditors, } from '../Actions'; @@ -89,6 +90,14 @@ const text = (state = textDefaultState, action) => { return state; } } + case REPLACE_TEXT: { + const newText = state.text.replace(action.originalText, action.replacedText); + + return { + ...state, + text: newText, + }; + } default: return state; } diff --git a/src/Views/Dashboard/Dashboard.js b/src/Views/Dashboard/Dashboard.js index 57e76ed0c..a09d87872 100644 --- a/src/Views/Dashboard/Dashboard.js +++ b/src/Views/Dashboard/Dashboard.js @@ -5,7 +5,12 @@ import MarkdownEditor from '../../Components/MarkdownEditor'; import TextEditor from '../../Components/TextEditor'; import { toMarkdown } from '../../Utils/Markdown'; -import { updateText } from '../../Actions'; +import { + updateText, + replaceText, +} from '../../Actions'; + +import Constants from '../../Constants'; import styles from './Dashboard.scss'; @@ -14,28 +19,26 @@ class Dashboard extends Component { editMarkdown: React.PropTypes.func.isRequired, editText: React.PropTypes.func.isRequired, text: React.PropTypes.string, + replaceText: React.PropTypes.func.isRequired, activeEditors: React.PropTypes.arrayOf(React.PropTypes.string), showHistorySidebar: React.PropTypes.bool.isRequired, } - // componentDidMount = () => { - // client.get('/user') - // .then(data => { - // this.setState({ user: data }); - // }); - // } - render() { const activeEditors = this.props.activeEditors; return ( -
+
{ activeEditors.includes('MARKDOWN') ? (
1 ? styles.panel : styles.fullscreen} ${styles.markdown}`} > - +
) : null } @@ -72,6 +75,9 @@ const mapDispatchToProps = (dispatch) => { const text = toMarkdown(html); dispatch(updateText(text, 'text')); }, + replaceText: (originalText, replacedText) => { + dispatch(replaceText(originalText, replacedText)); + }, }; };