Make uploads via upload dialog work

closes CNVS-28647

Test Plan:
  - Enable course images feature flag
  - Go to the course settings
  - Open the change image modal
  - Click/Use keyboard to activate the "browse your computer"
    link
  - A file upload dialog should appear.
  - Uploading a file should close the modal and update
    the image.
  - Save the course settings and the image should persist

Change-Id: I3bd701ad282ef7c891201fe0ddee8037591a85f0
Reviewed-on: https://gerrit.instructure.com/77196
Reviewed-by: Matt Zabriskie <mzabriskie@instructure.com>
Tested-by: Jenkins
QA-Review: Pierce Arner <pierce@instructure.com>
Product-Review: Colleen Palmer <colleen@instructure.com>
This commit is contained in:
Clay Diffrient 2016-04-15 17:12:05 -06:00 committed by Stephen Jensen
parent 540ec880ac
commit 0668a4ea52
9 changed files with 136 additions and 21 deletions

View File

@ -82,8 +82,8 @@ define ([
uploadFile (event, courseId, ajaxLib = axios) {
event.preventDefault();
return (dispatch, getState) => {
const type = event.dataTransfer.files[0].type;
const file = event.dataTransfer.files[0];
const { type, file } = Helpers.extractInfoFromEvent(event);
if (Helpers.isValidImageType(type)) {
const data = {
name: file.name,
@ -111,8 +111,8 @@ define ([
$.flashWarning(I18n.t("'%{type}' is not a valid image type (try jpg, png, or gif)", {type}));
}
};
}
};
return Actions;

View File

@ -81,7 +81,9 @@ define([
</div>
</div>
<div className="CourseImagePicker__Content">
<UploadArea />
<UploadArea
courseId={this.props.courseId}
handleFileUpload={this.props.handleFileUpload}/>
</div>
</div>
);

View File

@ -6,6 +6,21 @@ define([
'./CourseImagePicker'
], (React, Modal, I18n, Actions, CourseImagePicker) => {
const modalOverrides = {
content : {
position: 'absolute',
top: '0',
left: '0',
right: '0',
bottom: '0',
border: 'none',
padding: '12px',
maxWidth: '1420px',
borderRadius: '0',
background: '#ffffff'
}
};
class CourseImageSelector extends React.Component {
constructor (props) {
@ -56,9 +71,9 @@ define([
</button>
</div>
<Modal
className="CourseImageSelector__Modal"
isOpen={this.state.showModal}
onRequestClose={this.handleModalClose}
style={modalOverrides}
>
<CourseImagePicker
courseId={this.props.courseId}

View File

@ -1,11 +1,30 @@
define([
'react',
'i18n!course_images',
'classnames'
], (React, I18n, classnames) => {
'classnames',
'str/htmlEscape'
], (React, I18n, classnames, htmlEscape) => {
class UploadArea extends React.Component {
constructor (props) {
super(props);
this.handleFileChange = this.handleFileChange.bind(this);
this.uploadFile = this.uploadFile.bind(this);
}
handleFileChange (e) {
this.props.handleFileUpload(e, this.props.courseId);
e.preventDefault();
e.stopPropagation();
}
uploadFile () {
this.refs.courseImagefileUpload.click();
}
render () {
return (
<div className="UploadArea">
<div className="UploadArea__Content">
@ -13,10 +32,25 @@ define([
<i className="icon-upload" />
</div>
<div className="UploadArea__Instructions">
<strong>{I18n.t('Drag and drop your image here or browse your computer.')}</strong>
<div className="UploadArea__FileTypes">
{I18n.t('jpg, png, or gif files')}
</div>
<strong>
{I18n.t('Drag and drop your image here or ')}
<a tabIndex="0" role="button" href="#" onClick={this.uploadFile}>
<span className="screenreader-only">{I18n.t('Browse your computer for a course image')}</span>
<span aria-hidden="true">{I18n.t('browse your computer')}</span>
</a>
</strong>
<input
type="file"
ref="courseImagefileUpload"
name="fileUpload"
className="FileUpload__Input"
accept=".jpg, .jpeg, .gif, .png, image/png, image/jpeg, image/gif"
aria-hidden="true"
onChange={this.handleFileChange}
/>
</div>
<div className="UploadArea__FileTypes">
{I18n.t('jpg, png, or gif files')}
</div>
</div>
</div>

View File

@ -20,6 +20,20 @@ define ([], () => {
formData.append(key, uploadParams[key]);
});
return formData;
},
extractInfoFromEvent (event) {
let file = '';
let type = '';
if (event.type === 'change') {
file = event.target.files[0];
type = file.type;
} else {
type = event.dataTransfer.files[0].type;
file = event.dataTransfer.files[0];
}
return {file, type};
}
};

View File

@ -11,14 +11,8 @@
justify-content: center;
}
.CourseImageSelector__Modal {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: $ic-color-light;
max-width: $max_main_width + $right_side_width;
.CourseImagePicker {
height: 100%;
}
.CourseImagePicker__Header {
@ -98,4 +92,8 @@
font-style: italic;
margin-top: $ic-sp;
font-size: $ic-font-size--xsmall;
}
.FileUpload__Input {
display: none;
}

View File

@ -121,7 +121,6 @@ define([
type: 'image/tiff'
}]
},
//placeholder method to avoid failure when calling uploadFile
preventDefault: () => {}
};
@ -175,7 +174,6 @@ define([
type: 'image/jpeg'
}]
},
//placeholder method to avoid failure when calling uploadFile
preventDefault: () => {}
};

View File

@ -15,4 +15,19 @@ define([
ok(component);
});
test('calls the handleFileUpload prop when change occurs on the file input', () => {
let called = false;
const handleFileUploadFunc = () => called = true;
const component = TestUtils.renderIntoDocument(
<UploadArea
courseId="101"
handleFileUpload={handleFileUploadFunc}
/>
);
const input = TestUtils.findRenderedDOMComponentWithClass(component, 'FileUpload__Input');
TestUtils.Simulate.change(input);
ok(called, 'handleFileUpload was called');
});
});

View File

@ -22,4 +22,43 @@ define([
ok(formData instanceof FormData, 'created a FormData object');
});
test('extractInfoFromEvent', () => {
const changeEvent = {
type: 'change',
target: {
files: [{type: 'image/jpeg'}]
}
};
const dragEvent = {
type: 'drop',
dataTransfer: {
files: [{
name: 'test',
type: 'image/jpeg'
}]
},
};
const changeResults = Helpers.extractInfoFromEvent(changeEvent);
const expectedChangeResults = {
file: {
type: 'image/jpeg'
},
type: 'image/jpeg'
};
const dragResults = Helpers.extractInfoFromEvent(dragEvent);
const expectedDragResults = {
file: {
name: 'test',
type: 'image/jpeg'
},
type: 'image/jpeg'
};
deepEqual(changeResults, expectedChangeResults, 'creates the proper info from change events');
deepEqual(dragResults, expectedDragResults, 'creates the proper info from drag events');
});
});