Implemented drag and drop file uploads

This commit is contained in:
Jori Lallo
2016-03-28 21:54:16 -07:00
parent bc7ae72210
commit 2f9233222d
7 changed files with 118 additions and 17 deletions

View File

@@ -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",

View File

@@ -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 };
}

View File

@@ -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 (
<div className={ styles.container }>
<Codemirror
value={this.props.text}
onChange={this.onChange}
options={options}
/>
<div>
<Dropzone
onDropAccepted={this.onDropAccepted}
disableClick={true}
multiple={false}
accept={'image/*'}
className={styles.container}
>
<Codemirror
value={this.props.text}
onChange={this.onChange}
options={options}
ref="editor"
/>
</Dropzone>
</div>
);
}

View File

@@ -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 {

View File

@@ -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',
};

View File

@@ -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;
}

View File

@@ -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 (
<div className={ styles.container }>
<div className={styles.container}>
{
activeEditors.includes('MARKDOWN') ? (
<div className={ `${activeEditors.length > 1 ?
styles.panel : styles.fullscreen} ${styles.markdown}`}
>
<MarkdownEditor onChange={this.props.editMarkdown} text={this.props.text} />
<MarkdownEditor
onChange={this.props.editMarkdown}
text={this.props.text}
replaceText={this.props.replaceText}
/>
</div>
) : null
}
@@ -72,6 +75,9 @@ const mapDispatchToProps = (dispatch) => {
const text = toMarkdown(html);
dispatch(updateText(text, 'text'));
},
replaceText: (originalText, replacedText) => {
dispatch(replaceText(originalText, replacedText));
},
};
};