diff --git a/package.json b/package.json
index 3b9afa3de..3d5f2ec39 100644
--- a/package.json
+++ b/package.json
@@ -16,7 +16,6 @@
"angular-translate": "^2.19.0",
"angular-ui-bootstrap": "^2.5.6",
"angular-ui-router": "^1.0.30",
- "angular-upload": "^1.0.13",
"bootstrap-sass": "^3.4.1",
"change-case-object": "^2.0.0",
"cru-payments": "^1.2.2",
diff --git a/src/app/designationEditor/carouselModal/carouselModal.tpl.html b/src/app/designationEditor/carouselModal/carouselModal.tpl.html
index 60ee7030d..9bf7847bf 100644
--- a/src/app/designationEditor/carouselModal/carouselModal.tpl.html
+++ b/src/app/designationEditor/carouselModal/carouselModal.tpl.html
@@ -72,13 +72,15 @@
Click + to add photos to the carousel.
-
-
+
+
diff --git a/src/app/designationEditor/photoModal/photo.modal.js b/src/app/designationEditor/photoModal/photo.modal.js
index 91e1f1cd3..f3f1b5d22 100644
--- a/src/app/designationEditor/photoModal/photo.modal.js
+++ b/src/app/designationEditor/photoModal/photo.modal.js
@@ -1,6 +1,6 @@
import angular from 'angular'
-import 'angular-upload'
+import imageUploadDirective from 'common/directives/imageUpload.directive'
import designationEditorService from 'common/services/api/designationEditor.service'
const controllerName = 'photoCtrl'
@@ -49,7 +49,7 @@ class ModalInstanceCtrl {
export default angular
.module(controllerName, [
- 'lr.upload',
+ imageUploadDirective.name,
designationEditorService.name
])
.controller(controllerName, ModalInstanceCtrl)
diff --git a/src/app/designationEditor/photoModal/photoModal.tpl.html b/src/app/designationEditor/photoModal/photoModal.tpl.html
index 2884685cc..dd1e58c5b 100644
--- a/src/app/designationEditor/photoModal/photoModal.tpl.html
+++ b/src/app/designationEditor/photoModal/photoModal.tpl.html
@@ -10,16 +10,17 @@
-
-
-
+
+
+ >
diff --git a/src/common/directives/imageUpload.directive.js b/src/common/directives/imageUpload.directive.js
new file mode 100644
index 000000000..f17e71ba2
--- /dev/null
+++ b/src/common/directives/imageUpload.directive.js
@@ -0,0 +1,68 @@
+import angular from 'angular'
+
+const directiveName = 'imageUpload'
+
+// Based on https://github.com/leon/angular-upload
+// Fixes bug where even if accept is "image/*", you could still drag-and-drop non-image files onto the upload button
+const imageUpload = /* @ngInject */ ($http) => ({
+ restrict: 'EA',
+ scope: {
+ url: '@',
+ onInvalidFileType: '&',
+ onUpload: '&',
+ onSuccess: '&',
+ onError: '&',
+ onComplete: '&'
+ },
+ link: (scope, element) => {
+ const validMimeTypes = ['image/jpeg', 'image/png']
+
+ const el = angular.element(element)
+ const fileInput = angular.element(``)
+ el.append(fileInput)
+
+ fileInput.on('change', (event) => {
+ const files = event.target.files
+ const file = files[0]
+ if (!file) {
+ return
+ }
+
+ if (!validMimeTypes.includes(file.type)) {
+ // Clear the selected file because it's not a valid image
+ event.target.value = null
+ scope.$apply(() => {
+ scope.onInvalidFileType()
+ })
+ return
+ }
+
+ scope.$apply(() => {
+ scope.onUpload({ files })
+ })
+
+ const formData = new FormData()
+ formData.append('file', file)
+ $http({
+ url: scope.url,
+ method: 'POST',
+ headers: {
+ // Allow the browser to automatically determine the content type
+ 'Content-Type': undefined
+ },
+ data: formData
+ }).then((response) => {
+ scope.onSuccess({ response })
+ scope.onComplete({ response })
+ }).catch((response) => {
+ scope.onError({ response })
+ scope.onComplete({ response })
+ }
+ )
+ })
+ }
+})
+
+export default angular
+ .module(directiveName, [])
+ .directive(directiveName, imageUpload)
diff --git a/src/common/directives/imageUpload.directive.spec.js b/src/common/directives/imageUpload.directive.spec.js
new file mode 100644
index 000000000..d366fdae0
--- /dev/null
+++ b/src/common/directives/imageUpload.directive.spec.js
@@ -0,0 +1,112 @@
+import angular from 'angular'
+import 'angular-mocks'
+import module from './imageUpload.directive'
+
+const uploadFile = (input, file) => {
+ // jsdom doesn't yet support changing an inputs' files because it doesn't let you instantiate a
+ // FileList and it doesn't yet support DataTransfer. The easiest way around this to send a change
+ // event with `target.files` manually mocked.
+ // Reference: https://github.com/jsdom/jsdom/issues/1272
+ const event = new Event('change')
+ // Use defineProperty because `target` is a getter and can't be set normally
+ Object.defineProperty(event, 'target', {
+ get: () => ({ files: file ? [file] : [] })
+ })
+ input.dispatchEvent(event)
+}
+
+describe('imageUpload', () => {
+ beforeEach(angular.mock.module(module.name))
+
+ let $httpBackend, $scope, element
+ beforeEach(() => {
+ inject(($compile, $injector, $rootScope) => {
+ $httpBackend = $injector.get('$httpBackend')
+ $httpBackend.whenPOST(/^\/upload$/).respond(() => [200, 'Success', {}])
+
+ $scope = $rootScope.$new()
+ $scope.onInvalidFileType = jest.fn()
+ $scope.onUpload = jest.fn()
+ $scope.onSuccess = jest.fn()
+ $scope.onError = jest.fn()
+ $scope.onComplete = jest.fn()
+
+ element = $compile('
')($scope)
+ })
+ })
+
+ it('should display input', () => {
+ $scope.$digest()
+ expect(element.html()).toContain('type="file"')
+ })
+
+ it('should set accept', () => {
+ $scope.$digest()
+ expect(element.find('input').attr('accept')).toEqual('image/jpeg,image/png')
+ })
+
+ it('should accept file uploads for JPEG', () => {
+ const file = new File(['contents'], 'file.jpg', { type: 'image/jpeg' })
+ uploadFile(element.find('input')[0], file)
+ $scope.$digest()
+
+ expect($scope.onUpload).toHaveBeenCalled()
+ })
+
+ it('should accept file uploads for PNG', () => {
+ const file = new File(['contents'], 'file.png', { type: 'image/png' })
+ uploadFile(element.find('input')[0], file)
+ $scope.$digest()
+
+ expect($scope.onUpload).toHaveBeenCalled()
+ })
+
+ it('should ignore null file', () => {
+ uploadFile(element.find('input')[0], null)
+ $scope.$digest()
+
+ expect($scope.onUpload).not.toHaveBeenCalled()
+ })
+
+ it('should reject non-image files', () => {
+ const file = new File(['contents'], 'file.txt', { type: 'text/plain' })
+ uploadFile(element.find('input')[0], file)
+ $scope.$digest()
+
+ expect($scope.onInvalidFileType).toHaveBeenCalled()
+ expect($scope.onUpload).not.toHaveBeenCalled()
+ })
+
+ it('should reject HEIC files', () => {
+ const file = new File(['contents'], 'file.heic', { type: 'image/heic' })
+ uploadFile(element.find('input')[0], file)
+ $scope.$digest()
+
+ expect($scope.onInvalidFileType).toHaveBeenCalled()
+ expect($scope.onUpload).not.toHaveBeenCalled()
+ })
+
+ it('should call onSuccess and onComplete', () => {
+ const file = new File(['contents'], 'file.png', { type: 'image/png' })
+ uploadFile(element.find('input')[0], file)
+ $scope.$digest()
+ $httpBackend.flush()
+
+ expect($scope.onSuccess).toHaveBeenCalled()
+ expect($scope.onComplete).toHaveBeenCalled()
+ })
+
+ it('should call onError and onComplete', () => {
+ $httpBackend.matchLatestDefinitionEnabled(true)
+ $httpBackend.whenPOST(/^\/upload$/).respond(() => [500, 'Error', {}])
+
+ const file = new File(['contents'], 'file.png', { type: 'image/png' })
+ uploadFile(element.find('input')[0], file)
+ $scope.$digest()
+ $httpBackend.flush()
+
+ expect($scope.onSuccess).not.toHaveBeenCalled()
+ expect($scope.onError).toHaveBeenCalled()
+ expect($scope.onComplete).toHaveBeenCalled()
+ })
+})
diff --git a/yarn.lock b/yarn.lock
index 235b538d8..a1dc5c604 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2134,14 +2134,7 @@ angular-ui-router@^1.0.30:
dependencies:
"@uirouter/core" "6.0.8"
-angular-upload@^1.0.13:
- version "1.0.13"
- resolved "https://registry.yarnpkg.com/angular-upload/-/angular-upload-1.0.13.tgz#f622bde164e5ed96ba062409e659a3021921c407"
- integrity sha1-9iK94WTl7Za6BiQJ5lmjAhkhxAc=
- dependencies:
- angular ">=1.2.0"
-
-angular@*, angular@>=1.2.0:
+angular@*:
version "1.7.9"
resolved "https://registry.yarnpkg.com/angular/-/angular-1.7.9.tgz#e52616e8701c17724c3c238cfe4f9446fd570bc4"
integrity sha512-5se7ZpcOtu0MBFlzGv5dsM1quQDoDeUTwZrWjGtTNA7O88cD8TEk5IEKCTDa3uECV9XnvKREVUr7du1ACiWGFQ==