Improving interface for media uploads

This commit is contained in:
Alex Hunt 2019-08-10 08:28:32 -07:00
parent d14518ac91
commit 9fcc14c8e6
10 changed files with 88 additions and 24 deletions

View File

@ -13,17 +13,18 @@ sys.path.insert(0,'..')
# Hack to work around ascii encode error # Hack to work around ascii encode error
# TODO: Figure out which dependency tries to encode input to ascii # TODO: Figure out which dependency tries to encode input to ascii
reload(sys) reload(sys)
sys.setdefaultencoding("utf-8") sys.setdefaultencoding('utf-8')
s3 = boto3.client('s3') s3 = boto3.client('s3')
def create_presigned_post(): def create_presigned_post():
bucket_name = config.JOYCE_S3_BUCKET bucket_name = config.JOYCE_S3_BUCKET
key_name = str(uuid.uuid4()) key_name = 'images/' + str(uuid.uuid4())
response = s3.generate_presigned_post( response = s3.generate_presigned_post(
bucket_name, bucket_name,
key_name, key_name,
ExpiresIn = 3600 ExpiresIn = 3600,
Conditions = [[ 'eq', '$acl', 'public-read' ]],
) )
return response return response
@ -158,19 +159,19 @@ def es_search_text(body):
'from': 0, 'from': 0,
'size': 10, 'size': 10,
'query': { 'query': {
"nested": { 'nested': {
"path": "search_text", 'path': 'search_text',
"query": { 'query': {
"bool": { 'bool': {
"must": [ 'must': [
{ "match": { "search_text.text": body}} { 'match': { 'search_text.text': body}}
] ]
} }
}, },
"inner_hits": { 'inner_hits': {
"highlight": { 'highlight': {
"fields": { 'fields': {
"search_text.text": {} 'search_text.text': {}
} }
} }
} }

View File

@ -128,7 +128,11 @@ const userActions = {
({ ({
type: 'UPLOAD_MEDIA_SUBMIT', type: 'UPLOAD_MEDIA_SUBMIT',
data: input data: input
}) }),
clearLoadedMedia: () =>
({
type: 'CLEAR_LOADED_MEDIA'
}),
} }
export default userActions export default userActions

View File

@ -29,7 +29,7 @@ export const EditorEditModeRichTextOptions = ({editorState, onToolButtonClick, d
</div> </div>
export const EditorSubmitOptions = ({cancelEdit, onSubmitClick}) => export const EditorSubmitOptions = ({cancelEdit, onSubmitClick}) =>
<div className='row'> <div className='row mt-2'>
<div className='submit_option_button col-5'> <div className='submit_option_button col-5'>
<EditorCancelButton onClick={()=>cancelEdit()}/> <EditorCancelButton onClick={()=>cancelEdit()}/>
</div> </div>
@ -46,4 +46,4 @@ export const EditorAnnotateOptions = ({onNewAnnotationClick, onRemoveAnnotationC
<div className='annotate_option_button col-5 offset-2'> <div className='annotate_option_button col-5 offset-2'>
<AnnotatorRemoveButton onClick={onRemoveAnnotationClick} disabled={removeDisabled} /> <AnnotatorRemoveButton onClick={onRemoveAnnotationClick} disabled={removeDisabled} />
</div> </div>
</div> </div>

View File

@ -0,0 +1,24 @@
import React from 'react'
import { MediaList } from './list'
const NoteMediaPicker = ({media}) =>
<div id='note_media_picker' className='row'>
<div className='col-12'>
<div className="btn-group">
<button className="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown">
Select Media
</button>
<div className="dropdown-menu">
{media.map(media =>
<div key={media.id}>
<input type="checkbox" />
<div>{media.title}</div>
</div>
)}
</div>
</div>
</div>
</div>
export default NoteMediaPicker

View File

@ -8,10 +8,12 @@ import actions from '../actions'
import DocumentTitle from '../components/documentTitle' import DocumentTitle from '../components/documentTitle'
import DocumentTitleInput from '../components/documentTitleInput' import DocumentTitleInput from '../components/documentTitleInput'
import TagColorPicker from '../components/tagColorPicker' import TagColorPicker from '../components/tagColorPicker'
import NoteMediaPicker from '../components/noteMediaPicker'
import MediaUploadInput from '../components/mediaUploadInput' import MediaUploadInput from '../components/mediaUploadInput'
import LoadingSpinner from '../components/loadingSpinner' import LoadingSpinner from '../components/loadingSpinner'
const EditorEditMode = ({ const EditorEditMode = ({
media,
currentDocument, currentDocument,
docType, docType,
editorState, editorState,
@ -22,6 +24,7 @@ const EditorEditMode = ({
onDocumentTitleChange, onDocumentTitleChange,
onColorPickerInputChange, onColorPickerInputChange,
onColorSwatchClick, onColorSwatchClick,
onClearLoadedMedia,
onMediaInputChange, onMediaInputChange,
onMediaUpload, onMediaUpload,
cancelEdit, cancelEdit,
@ -50,6 +53,9 @@ const EditorEditMode = ({
/> />
</EditorTextContentBlock> </EditorTextContentBlock>
<EditorAttributeContentBlock> <EditorAttributeContentBlock>
{docType === 'notes' &&
<NoteMediaPicker media={media} />
}
{docType === 'tags' && {docType === 'tags' &&
<TagColorPicker <TagColorPicker
input={inputs.colorPicker} input={inputs.colorPicker}
@ -58,7 +64,14 @@ const EditorEditMode = ({
/> />
} }
{docType === 'media' && inputs.s3Path && {docType === 'media' && inputs.s3Path &&
<p>File uploaded!</p> <div className='row'>
<div className='col-8'>File uploaded!</div>
<div className='col-2 offset-2'>
<button type='button' onClick={onClearLoadedMedia} className='btn btn-outline-info btn-sm'>
<i className={'fas fa-trash-alt'}></i>
</button>
</div>
</div>
} }
{docType === 'media' && !inputs.s3Path && {docType === 'media' && !inputs.s3Path &&
<MediaUploadInput input={inputs.uploadFile} onChange={onMediaInputChange} onUpload={onMediaUpload}/> <MediaUploadInput input={inputs.uploadFile} onChange={onMediaInputChange} onUpload={onMediaUpload}/>
@ -79,6 +92,7 @@ const EditorEditMode = ({
const mapStateToProps = (state, props) => { const mapStateToProps = (state, props) => {
return { return {
media: state.media,
currentDocument: state.currentDocument, currentDocument: state.currentDocument,
docType: state.docType, docType: state.docType,
editorState: state.editorState, editorState: state.editorState,
@ -107,6 +121,9 @@ const mapDispatchToProps = dispatch => {
onMediaUpload: input => { onMediaUpload: input => {
dispatch(actions.uploadMediaInput(input)) dispatch(actions.uploadMediaInput(input))
}, },
onClearLoadedMedia: () => {
dispatch(actions.clearLoadedMedia())
},
cancelEdit: () => { cancelEdit: () => {
dispatch(actions.cancelEdit()) dispatch(actions.cancelEdit())
}, },

View File

@ -54,8 +54,9 @@ const joyceAPI = store => next => action => {
case 'UPLOAD_TO_S3_REQUEST': case 'UPLOAD_TO_S3_REQUEST':
const formData = new FormData() const formData = new FormData()
const url = action.signed_post.url const url = action.signed_post.url
formData.append('AWSAccessKeyId', action.signed_post.fields.AWSAccessKeyId)
formData.append('key', action.signed_post.fields.key) formData.append('key', action.signed_post.fields.key)
formData.append('AWSAccessKeyId', action.signed_post.fields.AWSAccessKeyId)
formData.append('acl', 'public-read')
formData.append('policy', action.signed_post.fields.policy) formData.append('policy', action.signed_post.fields.policy)
formData.append('signature', action.signed_post.fields.signature) formData.append('signature', action.signed_post.fields.signature)
formData.append('file', action.file) formData.append('file', action.file)

View File

@ -52,7 +52,7 @@ const api = {
return {status: 'error', data: error} return {status: 'error', data: error}
}), }),
HTTPPostMedia: (url, formData) => HTTPPostMedia: (url, formData) =>
axios.post(url, formData, {headers: {'Content-Type': 'image/*', 'ACL': 'public-read'}}).then(res=> { axios.post(url, formData, {headers: {'Content-Type': formData.get('file').type}}).then(res=> {
return {status: 'success', url: url + formData.get('key')} return {status: 'success', url: url + formData.get('key')}
}).catch(error => { }).catch(error => {
return {status: 'error', data: error} return {status: 'error', data: error}

View File

@ -9,6 +9,11 @@ export const validateSubmittedDocument = (docType, inputs) => {
errors.push('Please select a valid hex code color.') errors.push('Please select a valid hex code color.')
} }
} }
if (docType === 'media') {
if (inputs.s3Path === undefined) {
errors.push('Please upload an image first.')
}
}
if (inputs.documentTitle.length < 1) { if (inputs.documentTitle.length < 1) {
errors.push('Please enter a title.') errors.push('Please enter a title.')
} }

View File

@ -2,6 +2,7 @@ const initialState = {
documentTitle: '', documentTitle: '',
search: '', search: '',
colorPicker: '', colorPicker: '',
noteMediaSelection: [],
uploadFile: undefined, uploadFile: undefined,
s3Path: undefined s3Path: undefined
} }
@ -10,9 +11,6 @@ const inputs = (state=initialState, action) => {
switch(action.type) { switch(action.type) {
// Document Title // Document Title
case 'GET_DOCUMENT_TEXT': case 'GET_DOCUMENT_TEXT':
console.log('state', action.state)
console.log('status', action.status)
console.log('docType', action.docType)
if (action.status === 'success' && action.state === 'currentDocument' && ['tags', 'media'].indexOf(action.docType) <= 0 ) { if (action.status === 'success' && action.state === 'currentDocument' && ['tags', 'media'].indexOf(action.docType) <= 0 ) {
return { return {
...state, ...state,
@ -76,8 +74,6 @@ const inputs = (state=initialState, action) => {
...state, ...state,
uploadFile: action.data uploadFile: action.data
} }
default:
return state
// S3 File // S3 File
case 'UPLOAD_TO_S3_RESPONSE': case 'UPLOAD_TO_S3_RESPONSE':
if (action.status === 'success') { if (action.status === 'success') {
@ -86,6 +82,13 @@ const inputs = (state=initialState, action) => {
s3Path: action.url s3Path: action.url
} }
} else { return state } } else { return state }
case 'CLEAR_LOADED_MEDIA':
return {
...state,
s3Path: undefined
}
default:
return state
} }
} }

View File

@ -13,6 +13,15 @@
font-size: 0.8em; font-size: 0.8em;
} }
#note_media_picker {
div {
width: 100%;
}
button {
width: 100%;
}
}
#editor_top_bar_block { #editor_top_bar_block {
button { button {
width: 100%; width: 100%;