diff --git a/app/directives/tc-fp-file-input/tc-file-input.spec.js b/app/directives/tc-fp-file-input/tc-file-input.spec.js new file mode 100644 index 000000000..f6c0ce231 --- /dev/null +++ b/app/directives/tc-fp-file-input/tc-file-input.spec.js @@ -0,0 +1,208 @@ +/*eslint no-undef:0*/ +import angular from 'angular' + +describe('Topcoder File Input Directive', function() { + var scope, element, isolateScope, fileInput + + beforeEach(function() { + bard.appModule('topcoder') + bard.inject(this, '$compile', '$rootScope', '$timeout') + scope = $rootScope.$new() + + var html = '' + + '
' + + '' + + '' + var form = angular.element(html) + element = form.find('tc-file-input') + $compile(form)(scope) + scope.$digest() + + isolateScope = element.isolateScope() + }) + + beforeEach(function() { + fileInput = $(element).find('.none')[0] + }) + + afterEach(function() { + scope.$destroy() + fileInput = undefined + }) + + bard.verifyNoOutstandingHttpRequests() + + describe('selectFile', function() { + it('triggers a click on the file input', function() { + var mockClick = sinon.spy(fileInput, 'click') + + isolateScope.selectFile() + scope.$digest() + + expect(mockClick).calledOnce + }) + }) + + describe('a change event on the file input', function() { + var fileNameInput, fileList, mockSetFileReference + + beforeEach(function() { + fileNameInput = $(element).find('input[type=text]')[0] + fileList = { + 0: { + name: 'test.zip', + size: 50, + type: 'application/zip' + }, + length: 1, + item: function (index) { return index } + } + + mockSetFileReference = sinon.spy(isolateScope, 'setFileReference') + }) + + afterEach(function() { + fileNameInput = undefined + fileList = undefined + mockSetFileReference = undefined + }) + + it('sets the value of the fileNameInput with the name of the file', function() { + $(fileInput).triggerHandler({ + type: 'change', + target: { files: fileList } + }) + + $timeout.flush() + + expect(fileNameInput.value).to.equal('test.zip') + }) + + describe('with a valid file', function() { + beforeEach(function() { + $(fileInput).triggerHandler({ + type: 'change', + target: { files: fileList } + }) + $timeout.flush() + }) + + it('calls setFileReference', function() { + expect(mockSetFileReference).calledOnce + }) + + it('has ng-valid-filesize class', function() { + expect($(fileInput).hasClass('ng-valid-filesize')).to.be.true + }) + + it('has ng-valid-required class', function() { + expect($(fileInput).hasClass('ng-valid-required')).to.be.true + }) + + it('works with Windows file type application/x-zip', function(){ + fileList[0].type = 'application/x-zip' + + $(fileInput).triggerHandler({ + type: 'change', + target: { files: fileList } + }) + + $timeout.flush() + + expect(mockSetFileReference).called + expect($(fileInput).hasClass('ng-valid-filesize')).to.be.true + expect($(fileInput).hasClass('ng-valid-required')).to.be.true + }) + + it('works with Windows file type application/x-zip-compressed', function(){ + fileList[0].type = 'application/x-zip-compressed' + + $(fileInput).triggerHandler({ + type: 'change', + target: { files: fileList } + }) + + $timeout.flush() + + expect(mockSetFileReference).called + expect($(fileInput).hasClass('ng-valid-filesize')).to.be.true + expect($(fileInput).hasClass('ng-valid-required')).to.be.true + }) + }) + + describe('with a file type that\'s not in the list of fileTypes given to the directive', function() { + beforeEach(function() { + fileList[0].type = 'image/png' + + $(fileInput).triggerHandler({ + type: 'change', + target: { files: fileList } + }) + + $timeout.flush() + }) + + it('does not call setFileReference', function() { + expect(mockSetFileReference).not.calledOnce + }) + + it('has ng-touched and ng-invalid-required classes', function() { + expect($(fileInput).hasClass('ng-invalid-required')).to.be.true + expect($(fileInput).hasClass('ng-touched')).to.be.true + }) + }) + + describe('with a file extension that is not in the list of fileTypes given to the directive', function() { + beforeEach(function() { + fileList[0].name = 'submission.zip.jpg' + + $(fileInput).triggerHandler({ + type: 'change', + target: { files: fileList } + }) + + $timeout.flush() + }) + + it('does not call setFileReference', function() { + expect(mockSetFileReference).not.calledOnce + }) + + it('has ng-touched and ng-invalid-required classes', function() { + expect($(fileInput).hasClass('ng-invalid-required')).to.be.true + expect($(fileInput).hasClass('ng-touched')).to.be.true + }) + }) + + describe('with a file that\'s greater than 500MB', function() { + beforeEach(function() { + fileList[0].size = 524288001 + + $(fileInput).triggerHandler({ + type: 'change', + target: { files: fileList } + }) + + $timeout.flush() + }) + + it('does not call setFileReference', function() { + expect(mockSetFileReference).not.calledOnce + }) + + it('has ng-touched and ng-invalid-filesize classes', function() { + expect($(fileInput).hasClass('ng-invalid-filesize')).to.be.true + expect($(fileInput).hasClass('ng-touched')).to.be.true + }) + }) + }) +}) diff --git a/app/directives/tc-fp-file-input/tc-fp-file-input.directive.js b/app/directives/tc-fp-file-input/tc-fp-file-input.directive.js new file mode 100644 index 000000000..4b111fcd0 --- /dev/null +++ b/app/directives/tc-fp-file-input/tc-fp-file-input.directive.js @@ -0,0 +1,80 @@ +import angular from 'angular' +import _ from 'lodash' + +(function() { + 'use strict' + + angular.module('tcUIComponents').directive('tcFpFileInput', ['$rootScope', 'CONSTANTS', 'logger', 'UserService', 'filepickerService', tcFPFileInput]) + + function tcFPFileInput($rootScope, CONSTANTS, logger, UserService, filepickerService) { + return { + restrict: 'E', + require: '^form', + template: require('./tc-fp-file-input')(), + scope: { + labelText: '@', + fieldId: '@', + placeholder: '@', + fileType: '@', + showFileType: '=', + mandatory: '=', + maxFileSize: '@', + fpServices: '@', + buttonText: '@', + setFileReference: '&', + ngModel: '=' + }, + link: function(scope, element, attrs, formController) { + // set filePath + var userId = parseInt(UserService.getUserIdentity().userId) + scope.filePath = scope.fieldId + '/' + if (scope.fieldId.indexOf('ZIP') > -1) { + scope.filePath += _.join([userId, scope.fieldId, (new Date()).valueOf()], '-') + '.zip' + } + // set extensions + if (scope.fieldId.indexOf('ZIP') > -1) { + scope.extensions = ".zip" + } else if (scope.fieldId.indexOf('DESIGN_COVER') > -1) { + scope.extensions = ".png,.jpeg,.jpg,.bmp" + } + + // set default services + scope.fpServices = scope.fpServices || "COMPUTER,GOOGLE_DRIVE,BOX,DROPBOX" + scope.fpContainer = CONSTANTS.FILE_PICKER_SUBMISSION_CONTAINER_NAME || 'submission-staging-dev' + + // set max size + scope.maxSize = 500*1024*1024 + + var key, value; + /* + *pass original event + */ + element.bind('change', function(event) { + event.preventDefault() + scope.onSuccess(event.originalEvent || event); + $rootScope.$apply() + }); + element = element.length ? element[0] : element; + for (key in attrs.$attr){ + value = attrs.$attr[key] + element.setAttribute(value, attrs[key]) + } + filepickerService.constructWidget(element) + + scope.onSuccess = function (event) { + debugger + var fpFile = event.fpfile + var _file = { + name: scope.filename || fpFile.filename, + container: fpFile.container || scope.fpContainer, + path: fpFile.key, + size: fpFile.size, + mimetype: fpFile.mimetype + } + scope.ngModel = _file + scope.setFileReference({file: _file, fieldId: scope.fieldId}) + } + } + } + } +})() diff --git a/app/directives/tc-fp-file-input/tc-fp-file-input.jade b/app/directives/tc-fp-file-input/tc-fp-file-input.jade new file mode 100644 index 000000000..780dbce18 --- /dev/null +++ b/app/directives/tc-fp-file-input/tc-fp-file-input.jade @@ -0,0 +1,19 @@ +.tc-file-field__label + label.tc-label {{labelText}} + span.lowercase(ng-if="showFileType") {{ ' *(.' + fileType + ')'}} + +.tc-file-field__inputs + .tc-label__wrapper + span.tc-label__asterisk.lowercase(ng-if="mandatory") #[span *]mandatory + input.tc-file-field__input( + type="filepicker-dragdrop", + data-fp-maxSize="{{maxSize}}", + data-fp-button-class="tc-btn", + data-fp-services="{{fpServices}}", + data-fp-multiple="false", + data-fp-extensions="{{extensions}}", + data-fp-store-location="s3", + data-fp-store-container="{{fpContainer}}", + data-fp-store-path="{{filePath}}", + on-success="onFileSeleted(event.fpfile)" + ) diff --git a/app/services/submissions.service.js b/app/services/submissions.service.js index d30c4256e..c942f1149 100644 --- a/app/services/submissions.service.js +++ b/app/services/submissions.service.js @@ -11,20 +11,25 @@ import angular from 'angular' var api = ApiService.getApiServiceProvider('SUBMISSIONS') var service = { - getPresignedURL: getPresignedURL, - uploadSubmissionFileToS3: uploadSubmissionFileToS3, - updateSubmissionStatus: updateSubmissionStatus, - recordCompletedSubmission: recordCompletedSubmission + startSubmission: startSubmission, + processSubmission: processSubmission + // uploadSubmissionFileToS3: uploadSubmissionFileToS3, + // updateSubmissionStatus: updateSubmissionStatus, + // recordCompletedSubmission: recordCompletedSubmission } return service - function getPresignedURL(body, files, progressCallback) { + function startSubmission(body, progressCallback) { return api.all('submissions').customPOST(body) .then(function(response) { - progressCallback.call(progressCallback, 'PREPARE', 100) + //progressCallback.call(progressCallback, 'PREPARE', 100) - uploadSubmissionFileToS3(response, response.data.files, files, progressCallback) + // uploadSubmissionFileToS3(response, response.data.files, files, progressCallback) + + console.log(response); + + processSubmission(response, progressCallback); }) .catch(function(err) { logger.error('Could not get presigned url', err) @@ -35,6 +40,7 @@ import angular from 'angular' }) } + /** function uploadSubmissionFileToS3(presignedURLResponse, filesWithPresignedURL, files, progressCallback) { var promises = filesWithPresignedURL.map(function(fileWithPresignedURL) { @@ -120,14 +126,17 @@ import angular from 'angular' progressCallback.call(progressCallback, 'ERROR', err) }) } + */ - function recordCompletedSubmission(body, progressCallback) { + function processSubmission(body, progressCallback) { // Once all uploaded, make record and begin processing return api.one('submissions', body.id).customPOST(body, 'process') .then(function(response) { logger.info('Successfully made file record. Beginning processing') progressCallback.call(progressCallback, 'FINISH', 100) + + return response; }) .catch(function(err) { logger.error('Could not start processing', err) diff --git a/app/submissions/submissions.module.js b/app/submissions/submissions.module.js index 387625919..6e1b461fd 100644 --- a/app/submissions/submissions.module.js +++ b/app/submissions/submissions.module.js @@ -8,9 +8,15 @@ import angular from 'angular' 'tc.services', 'tcUIComponents', 'toaster', - 'appirio-tech-ng-ui-components' + 'appirio-tech-ng-ui-components', + 'angular-filepicker' ] angular.module('tc.submissions', dependencies) + .config(['filepickerProvider', 'CONSTANTS', + function (filepickerProvider, CONSTANTS) { + filepickerProvider.setKey(CONSTANTS.FILE_PICKER_API_KEY || 'AzFINuQoqTmqw0QEoaw9az') + } + ]) })() diff --git a/app/submissions/submit-design-files/submit-design-files.controller.js b/app/submissions/submit-design-files/submit-design-files.controller.js index 31c85a2c1..ef047d149 100644 --- a/app/submissions/submit-design-files/submit-design-files.controller.js +++ b/app/submissions/submit-design-files/submit-design-files.controller.js @@ -50,9 +50,7 @@ import _ from 'lodash' }, userId: userId, data: { - method: challengeToSubmitTo.challenge.track.toUpperCase() + '_CHALLENGE_ZIP_FILE', - - // Can delete below since they are processed and added later? + method: 'DESIGN_CHALLENGE_FILE_PICKER_ZIP_FILE', files: [], submitterRank: 1, submitterComments: '', @@ -80,24 +78,14 @@ import _ from 'lodash' } function setFileReference(file, fieldId) { - // Can clean up since fileValue on tcFileInput has file reference? - files[fieldId] = file - var fileObject = { name: file.name, type: fieldId, - status: 'PENDING' - } - - switch(fieldId) { - case 'SUBMISSION_ZIP': - fileObject.mediaType = 'application/octet-stream' - break - case 'SOURCE_ZIP': - fileObject.mediaType = 'application/octet-stream' - break - default: - fileObject.mediaType = file.type + status: 'STAGED', + stagedFileContainer: file.container, + stagedFilePath: file.path, + size: file.size, + mediaType: file.mimetype } // If user changes a file input's file, update the file details @@ -159,8 +147,18 @@ import _ from 'lodash' }, []) vm.submissionsBody.data.fonts = processedFonts - - SubmissionsService.getPresignedURL(vm.submissionsBody, files, updateProgress) + SubmissionsService.startSubmission(vm.submissionsBody, updateProgress) + .then(function(newSubmission) { + logger.debug("New Submission: ", newSubmission) + //SubmissionsService.processSubmission(newSubmission, updateProgress) + }) + .then(function(processedSubmission) { + logger.debug("Processed Submission: ", processedSubmission) + + }) + .catch(function(err) { + logger.error("Submission processing failed ", err) + }) } // Callback for updating submission upload process. It looks for different phases e.g. PREPARE, UPLOAD, FINISH diff --git a/app/submissions/submit-design-files/submit-design-files.jade b/app/submissions/submit-design-files/submit-design-files.jade index ce0be9d04..0da471c4e 100644 --- a/app/submissions/submit-design-files/submit-design-files.jade +++ b/app/submissions/submit-design-files/submit-design-files.jade @@ -10,7 +10,7 @@ .panel-body p.tc-error-messages.submissions-access-error(ng-if="submissions.error", ng-bind="submissions.errorMessage") - + //-
{{vm.submissionForm}}
form.form-blocks(ng-if="!submissions.error", name="submissionForm", role="form", ng-submit="submissionForm.$valid && vm.uploadSubmission()", novalidate) .form-block.flex .form-block__instructions @@ -27,18 +27,19 @@ .form-block__fields .fieldset - tc-file-input.tc-file-field( + tc-fp-file-input( label-text="Submission", field-id="SUBMISSION_ZIP", - button-text="Add File", file-type="zip", + button-text="Add File", + services="COMPUTER,GOOGLE_DRIVE,BOX,DROPBOX", show-file-type="true", placeholder="Attach all visible files as a single .zip file", mandatory="true", - set-file-reference="vm.setFileReference(file, fieldId)", - ng-model="vm.submissionForm.submissionZip" + ng-model="vm.submissionForm.submissionZip", + set-file-reference="vm.setFileReference(file, fieldId)" ) - + .tc-error-messages( ng-show="submissionForm['SUBMISSION_ZIP'].$touched && submissionForm['SUBMISSION_ZIP'].$invalid", ng-messages="submissionForm['SUBMISSION_ZIP'].$error" @@ -47,19 +48,19 @@ p(ng-message="required") This is not the correct file format. Please select a .zip file. - - tc-file-input.tc-file-field( + tc-fp-file-input.tc-file-field( label-text="Source", field-id="SOURCE_ZIP", - button-text="Add File", file-type="zip", + button-text="Add File", + services="COMPUTER,GOOGLE_DRIVE,BOX,DROPBOX", show-file-type="true", - placeholder="Attach all source files as a single .zip file", + placeholder="Attach all visible files as a single .zip file", mandatory="true", set-file-reference="vm.setFileReference(file, fieldId)", ng-model="vm.submissionForm.sourceZip" ) - + .tc-error-messages( ng-show="submissionForm['SOURCE_ZIP'].$touched && submissionForm['SOURCE_ZIP'].$invalid", ng-messages="submissionForm['SOURCE_ZIP'].$error" @@ -68,11 +69,12 @@ p(ng-message="required") This is not the correct file format. Please select a .zip file. - tc-file-input.tc-file-field( + tc-fp-file-input.tc-file-field( label-text="Preview Image", field-id="DESIGN_COVER", + file-type=".jpg,.jpeg,.png", button-text="Add File", - file-type="jpg,jpeg,png" + services="COMPUTER,GOOGLE_DRIVE,BOX,DROPBOX", placeholder="Image file as .jpg or .png", mandatory="true", set-file-reference="vm.setFileReference(file, fieldId)", diff --git a/app/submissions/submit-design-files/submit-design-files.spec.js b/app/submissions/submit-design-files/submit-design-files.spec.js index 42766baee..5244c44b9 100644 --- a/app/submissions/submit-design-files/submit-design-files.spec.js +++ b/app/submissions/submit-design-files/submit-design-files.spec.js @@ -69,7 +69,7 @@ describe('Submit Design Files Controller', function() { vm = controller scope.$digest() - expect(vm.submissionsBody.data.method).to.equal('DEVELOP_CHALLENGE_ZIP_FILE') + expect(vm.submissionsBody.data.method).to.equal('DESIGN_CHALLENGE_FILE_PICKER_ZIP_FILE') }) describe('setRankTo1', function() { @@ -93,7 +93,7 @@ describe('Submit Design Files Controller', function() { file = { name: 'Dashboard 2.png', size: 575548, - type: 'image/png' + mimetype: 'image/png' } fieldId = 'DESIGN_COVER' @@ -116,7 +116,7 @@ describe('Submit Design Files Controller', function() { var newFile = { name: 'different_image.png', size: 4321, - type: 'image/png' + mimetype: 'image/png' } vm.setFileReference(newFile, fieldId) @@ -125,37 +125,9 @@ describe('Submit Design Files Controller', function() { expect(vm.submissionsBody.data.files).to.have.length(1) expect(vm.submissionsBody.data.files[0].name).to.equal('different_image.png') }) - - it('sets the correct mediaTypes on the fileObject', function() { - expect(vm.submissionsBody.data.files[0].mediaType).to.equal('image/png') - - var newFile = { - name: 'submission.zip', - size: 43121, - type: 'application/zip' - } - var newFieldId = 'SUBMISSION_ZIP' - - vm.setFileReference(newFile, newFieldId) - scope.$digest() - - expect(vm.submissionsBody.data.files[1].mediaType).to.equal('application/octet-stream') - - var newFile2 = { - name: 'source.zip', - size: 2314, - type: 'application/zip' - } - var newFieldId2 = 'SOURCE_ZIP' - - vm.setFileReference(newFile2, newFieldId2) - scope.$digest() - - expect(vm.submissionsBody.data.files[2].mediaType).to.equal('application/octet-stream') - }) }) - describe('uploadSubmission', function() { + describe.only('uploadSubmission', function() { it('adds comments to the submissions body', function() { vm.comments = 'test comments' scope.$digest() diff --git a/app/submissions/submit-develop-files/submit-develop-files.spec.js b/app/submissions/submit-develop-files/submit-develop-files.spec.js index ffe43fd96..5386e617a 100644 --- a/app/submissions/submit-develop-files/submit-develop-files.spec.js +++ b/app/submissions/submit-develop-files/submit-develop-files.spec.js @@ -69,7 +69,7 @@ describe('Submit Develop Files Controller', function() { vm = controller scope.$digest() - expect(vm.submissionsBody.data.method).to.equal('DESIGN_CHALLENGE_ZIP_FILE') + expect(vm.submissionsBody.data.method).to.equal('DEVELOP_CHALLENGE_ZIP_FILE') }) describe('setFileReference', function() { diff --git a/assets/scripts/angular-filepicker.js b/assets/scripts/angular-filepicker.js new file mode 100644 index 000000000..b54020d71 --- /dev/null +++ b/assets/scripts/angular-filepicker.js @@ -0,0 +1 @@ +"use strict";function filepickerDirective(e,r,i){return{restrict:"A",scope:{onSuccess:"&"},link:function(i,n,t){var c,l;n.bind("change",function(r){r.preventDefault(),i.onSuccess({event:r.originalEvent||r}),e.$apply()}),n=n.length?n[0]:n;for(c in t.$attr)l=t.$attr[c],n.setAttribute(l,t[c]);r.constructWidget(n)}}}function filepickerService(e){return e.filepicker}function filepickerPreviewDirective(e,r){return{restrict:"A",scope:{url:"="},link:function(e,r,i){function n(e){e&&(e=e.replace("api/file/","api/preview/"),c.src=e)}var t=e.url,c=document.createElement("iframe");c.src=t,c.width="100%",c.height="100%",angular.element(r).append(c),e.$watch("url",n)}}}function fpUtilService(){function e(e){var i=[];for(var n in e)e.hasOwnProperty(n)&&("[object Object]"!==Object.prototype.toString.call(e[n])?i.push(n+"="+e[n]):i.push(r(e[n])));return i.join("&")}function r(e){var r=[];for(var i in e)e.hasOwnProperty(i)&&r.push(encodeURIComponent(i)+"="+encodeURIComponent(e[i]));return r.join("&")}return{toParams:e,serialize:r}}function fpConvert(e,r){return function(i,n){var t=e("fpUrlFilter")(i);if(t&&n)return t+"/convert?"+r.toParams(n)}}angular.module("angular-filepicker",[]),window.filepicker=window.filepicker||{},window.filepicker.plugin="angular_js_lib",angular.module("angular-filepicker").directive("filepicker",filepickerDirective),filepickerDirective.$inject=["$rootScope","filepickerService","$parse"],angular.module("angular-filepicker").provider("filepicker",function(){this.$get=function(){return window.filepicker},this.setKey=function(e){try{window.filepicker.setKey(e)}catch(r){console.error("Include filepicker.js script")}}}),angular.module("angular-filepicker").service("filepickerService",filepickerService),filepickerService.$inject=["$window"],angular.module("angular-filepicker").directive("filepickerPreview",filepickerPreviewDirective),filepickerPreviewDirective.$inject=["$rootScope","filepickerService"],angular.module("angular-filepicker").service("fpUtilService",fpUtilService),angular.module("angular-filepicker").filter("fpConvert",fpConvert),fpConvert.$inject=["$filter","fpUtilService"],angular.module("angular-filepicker").filter("fpUrlFilter",function(){return function(e){if(!e)return"";var r=["/convert","/metadata","?"];for(var i in r){var n=e.indexOf(r[i]);if(n>-1)return e.substr(0,n)}return e}}); diff --git a/assets/scripts/filepicker.js b/assets/scripts/filepicker.js new file mode 100644 index 000000000..3b2fd72c0 --- /dev/null +++ b/assets/scripts/filepicker.js @@ -0,0 +1,3 @@ +"use strict";!function(){var e=function(){var e={},t=function(t,r,n){for(var o=t.split("."),i=0;i-1){var l=e.util.parseUrl(c).params;l=e.conversions.mapRestParams(l),l.crop&&e.util.setDefault(l,"crop_first",!0);for(var u in l)e.util.setDefault(s,u,l[u])}e.conversions.convert(e.util.trimConvert(c),s,o,i,a)},y=function(t){return e.widgets.constructWidget(t)},b=function(t,r){return e.dragdrop.makeDropPane(t,r)},x=function(t){return e.responsiveImages.setResponsiveOptions(t)},E=function(){e.responsiveImages.update.apply(null,arguments)};return{setKey:r,setResponsiveOptions:x,pick:o,pickFolder:s,pickMultiple:i,pickAndStore:a,read:c,write:u,writeUrl:d,"export":p,exportFile:p,processImage:f,store:m,storeUrl:h,stat:g,metadata:g,remove:v,convert:w,constructWidget:y,makeDropPane:b,FilepickerException:n,responsive:E,version:t}},!0),filepicker.extend("mimetypes",function(){var e=this,t={".stl":"application/sla",".hbs":"text/html",".pdf":"application/pdf",".jpg":"image/jpeg",".jpeg":"image/jpeg",".jpe":"image/jpeg",".imp":"application/x-impressionist",".vob":"video/dvd"},r=["application/octet-stream","application/download","application/force-download","octet/stream","application/unknown","application/x-download","application/x-msdownload","application/x-secure-download"],n=function(e){if(e.type){var n=e.type;n=n.toLowerCase();for(var o=!1,i=0;i=0||o.indexOf("?")>=0)&&(o=encodeURIComponent(o)),l+e(t,r)+"&curl="+o+n(t.conversions)},h=function(t,r){return u+e(t,r)},g=function(t,r,n){return(t.indexOf("&")>=0||t.indexOf("?")>=0)&&(t=encodeURIComponent(t)),c+e(r,n)+"&url="+t+(void 0!==r.mimetype?"&m="+r.mimetype:"")+(void 0!==r.extension?"&ext="+r.extension:"")+(r.suggestedFilename?"&defaultSaveasName="+r.suggestedFilename:"")},v=function(e){return d+e.location+"?key="+o.apikey+(e.base64decode?"&base64decode=true":"")+(e.mimetype?"&mimetype="+e.mimetype:"")+(e.filename?"&filename="+encodeURIComponent(e.filename):"")+(e.path?"&path="+e.path:"")+(e.container?"&container="+e.container:"")+(e.access?"&access="+e.access:"")+t(e)+"&plugin="+r()},w=function(e,n){return e+"?nonce=fp"+(n.base64decode?"&base64decode=true":"")+(n.mimetype?"&mimetype="+n.mimetype:"")+t(n)+"&plugin="+r()},y=function(){var e=o.util.parseUrl(window.location.href);return e.origin+"/404"};return{BASE:i,DIALOG_BASE:a,API_COMM:i+"/dialog/comm_iframe/",COMM:a+"/dialog/comm_iframe/",FP_COMM_FALLBACK:a+"/dialog/comm_hash_iframe/",STORE:d,PICK:s,EXPORT:c,constructPickUrl:f,constructConvertUrl:m,constructPickFolderUrl:h,constructExportUrl:g,constructWriteUrl:w,constructStoreUrl:v,constructHostCommFallback:y,getPlugin:r}}),filepicker.extend("ajax",function(){var e=this,t=function(e,t){t.method="GET",i(e,t)},r=function(t,r){r.method="POST",t+=(t.indexOf("?")>=0?"&":"?")+"_cacheBust="+e.util.getId(),i(t,r)},n=function(t,r){var o=[];for(var i in t){var a=t[i];r&&(i=r+". + key + ");var s;switch(e.util.typeOf(a)){case"object":s=n(a,i);break;case"array":for(var c={},l=0;l=0?"&":"?")+"plugin="+e.urls.getPlugin(),u&&d&&(u=n(r.data));var v;if(r.xhr)v=r.xhr;else if(v=o(),!v)return r.error("Ajax not allowed"),v;if(h&&window.XDomainRequest&&!("withCredentials"in v))return new a(t,r);r.progress&&v.upload&&v.upload.addEventListener("progress",function(e){e.lengthComputable&&r.progress(Math.round(95*e.loaded/e.total))},!1);var w=function(){if(4==v.readyState&&!g)if(r.progress&&r.progress(100),v.status>=200&&v.status<300){var t=v.responseText;if(r.json)try{t=e.json.decode(t)}catch(n){return void y.call(v,"Invalid json: "+t)}s(t,v.status,v),g=!0}else y.call(v,v.responseText),g=!0};v.onreadystatechange=w;var y=function(e){return g?void 0:(r.progress&&r.progress(100),g=!0,400==this.status?void c("bad_params",this.status,this):403==this.status?void c("not_authorized",this.status,this):404==this.status?void c("not_found",this.status,this):h?4==this.readyState&&0===this.status?void c("CORS_not_allowed",this.status,this):void c("CORS_error",this.status,this):void c(e,this.status,this))};v.onerror=y,u&&"GET"==i&&(t+=(-1!==t.indexOf("?")?"&":"?")+u,u=null),v.open(i,t,l),r.json?v.setRequestHeader("Accept","application/json, text/javascript"):v.setRequestHeader("Accept","text/javascript, text/html, application/xml, text/xml, */*");var b=p["Content-Type"]||p["content-type"];if(u&&d&&("POST"==i||"PUT"==i)&&void 0===b&&v.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=utf-8"),p)for(var x in p)v.setRequestHeader(x,p[x]);return v.send(u),v},a=function(t,r){if(!window.XDomainRequest)return null;var o=r.method?r.method.toUpperCase():"POST",i=r.success||function(){},a=r.error||function(){},s=r.data||{};if("http:"==window.location.protocol?t=t.replace("https:","http:"):"https:"==window.location.protocol&&(t=t.replace("http:","https:")),r.async)throw new e.FilepickerException("Asyncronous Cross-domain requests are not supported");"GET"!==o&&"POST"!==o&&(s._method=o,o="POST"),r.processData!==!1&&(s=s?n(s):null),s&&"GET"==o&&(t+=(t.indexOf("?")>=0?"&":"?")+s,s=null),t+=(t.indexOf("?")>=0?"&":"?")+"_xdr=true&_cacheBust="+e.util.getId();var c=new window.XDomainRequest;return c.onload=function(){var t=c.responseText;if(r.progress&&r.progress(100),r.json)try{t=e.json.decode(t)}catch(n){ +return void a("Invalid json: "+t,200,c)}i(t,200,c)},c.onerror=function(){r.progress&&r.progress(100),a(c.responseText||"CORS_error",this.status||500,this)},c.onprogress=function(){},c.ontimeout=function(){},c.timeout=3e4,c.open(o,t,!0),c.send(s),c};return{get:t,post:r,request:i}}),filepicker.extend("files",function(){var e=this,t=function(t,n,o,i,a){var s=void 0===n.base64encode;s&&(n.base64encode=!0),n.base64encode=n.base64encode!==!1;var c=function(t){s&&(t=e.base64.decode(t,!!n.asText)),o(t)};r.call(this,t,n,c,i,a)},r=function(t,r,n,o,i){r.cache!==!0&&(r._cacheBust=e.util.getId()),e.ajax.get(t,{data:r,headers:{"X-NO-STREAM":!0},success:n,error:function(t,r,n){o("CORS_not_allowed"===t?new e.errors.FPError(113):"CORS_error"===t?new e.errors.FPError(114):"not_found"===t?new e.errors.FPError(115):"bad_params"===t?new e.errors.FPError(400):"not_authorized"===t?new e.errors.FPError(403):new e.errors.FPError(118))},progress:i})},n=function(t,r,n,o,i){if(!(window.File&&window.FileReader&&window.FileList&&window.Blob))return i(10),void e.files.storeFile(t,{},function(t){i(50),e.files.readFromFPUrl(t.url,r,n,o,function(e){i(50+e/2)})},o,function(e){i(e/2)});var a=!!r.base64encode,s=!!r.asText,c=new FileReader;c.onprogress=function(e){e.lengthComputable&&i(Math.round(e.loaded/e.total*100))},c.onload=function(t){i(100),n(a?e.base64.encode(t.target.result,s):t.target.result)},c.onerror=function(t){switch(t.target.error.code){case t.target.error.NOT_FOUND_ERR:o(new e.errors.FPError(115));break;case t.target.error.NOT_READABLE_ERR:o(new e.errors.FPError(116));break;case t.target.error.ABORT_ERR:o(new e.errors.FPError(117));break;default:o(new e.errors.FPError(118))}},s||!c.readAsBinaryString?c.readAsText(t):c.readAsBinaryString(t)},o=function(t,r,n,o,i,a){var s=n.mimetype||"text/plain";e.ajax.post(e.urls.constructWriteUrl(t,n),{headers:{"Content-Type":s},data:r,processData:!1,json:!0,success:function(t){o(e.util.standardizeFPFile(t))},error:function(t,r,n){i("not_found"===t?new e.errors.FPError(121):"bad_params"===t?new e.errors.FPError(122):"not_authorized"===t?new e.errors.FPError(403):new e.errors.FPError(123))},progress:a})},i=function(t,r,n,o,i,a){var s=function(t,r,n){i("not_found"===t?new e.errors.FPError(121):"bad_params"===t?new e.errors.FPError(122):"not_authorized"===t?new e.errors.FPError(403):new e.errors.FPError(123))},c=function(t){o(e.util.standardizeFPFile(t))};u(r,e.urls.constructWriteUrl(t,n),c,s,a)},a=function(t,r,n,o,i,a){var s=function(t,r,n){i("not_found"===t?new e.errors.FPError(121):"bad_params"===t?new e.errors.FPError(122):"not_authorized"===t?new e.errors.FPError(403):new e.errors.FPError(123))},c=function(t){o(e.util.standardizeFPFile(t))};n.mimetype=r.type,u(r,e.urls.constructWriteUrl(t,n),c,s,a)},s=function(t,r,n,o,i,a){e.ajax.post(e.urls.constructWriteUrl(t,n),{data:{url:r},json:!0,success:function(t){o(e.util.standardizeFPFile(t))},error:function(t,r,n){i("not_found"===t?new e.errors.FPError(121):"bad_params"===t?new e.errors.FPError(122):"not_authorized"===t?new e.errors.FPError(403):new e.errors.FPError(123))},progress:a})},c=function(t,r,n,o,i){if(t.files)return void(0===t.files.length?o(new e.errors.FPError(115)):l(t.files[0],r,n,o,i));e.util.setDefault(r,"location","S3"),r.filename||(r.filename=t.value.replace("C:\\fakepath\\","")||t.name);var a=t.name;t.name="fileUpload",e.iframeAjax.post(e.urls.constructStoreUrl(r),{data:t,processData:!1,json:!0,success:function(r){t.name=a,n(e.util.standardizeFPFile(r))},error:function(t,r,n){o("not_found"===t?new e.errors.FPError(121):"bad_params"===t?new e.errors.FPError(122):"not_authorized"===t?new e.errors.FPError(403):new e.errors.FPError(123))}})},l=function(t,r,n,o,i){e.util.setDefault(r,"location","S3");var a=function(t,r,n){"not_found"===t?o(new e.errors.FPError(121)):"bad_params"===t?o(new e.errors.FPError(122)):"not_authorized"===t?o(new e.errors.FPError(403)):(e.util.console.error(t),o(new e.errors.FPError(123)))},s=function(t){n(e.util.standardizeFPFile(t))};r.filename||(r.filename=t.name||t.fileName),u(t,e.urls.constructStoreUrl(r),s,a,i)},u=function(t,r,n,o,i){t.files&&(t=t.files[0]);var a=!!window.FormData&&!!window.XMLHttpRequest;if(a){var s=new window.FormData;s.append("fileUpload",t),e.ajax.post(r,{json:!0,processData:!1,data:s,success:n,error:o,progress:i})}else e.iframeAjax.post(r,{data:t,json:!0,success:n,error:o})},d=function(t,r,n,o,i){e.util.setDefault(r,"location","S3"),e.util.setDefault(r,"mimetype","text/plain"),e.ajax.post(e.urls.constructStoreUrl(r),{headers:{"Content-Type":r.mimetype},data:t,processData:!1,json:!0,success:function(t){n(e.util.standardizeFPFile(t))},error:function(t,r,n){o("not_found"===t?new e.errors.FPError(121):"bad_params"===t?new e.errors.FPError(122):"not_authorized"===t?new e.errors.FPError(403):new e.errors.FPError(123))},progress:i})},p=function(t,r,n,o,i){e.util.setDefault(r,"location","S3"),e.ajax.post(e.urls.constructStoreUrl(r),{data:{url:e.util.getFPUrl(t)},json:!0,success:function(t){n(e.util.standardizeFPFile(t))},error:function(t,r,n){o("not_found"===t?new e.errors.FPError(151):"bad_params"===t?new e.errors.FPError(152):"not_authorized"===t?new e.errors.FPError(403):new e.errors.FPError(153))},progress:i})},f=function(t,r,n,o){var i=["uploaded","modified","created"];r.cache!==!0&&(r._cacheBust=e.util.getId()),e.ajax.get(t+"/metadata",{json:!0,data:r,success:function(e){for(var t=0;t=0?"&":"?")+"_cacheBust="+e.util.getId(),s(t,r)},a=function(){if(r.length>0){var e=r.shift();s(e.url,e.options)}},s=function(o,i){if(n)return void r.push({url:o,options:i});o+=(o.indexOf("?")>=0?"&":"?")+"plugin="+e.urls.getPlugin()+"&_cacheBust="+e.util.getId(),o+="&Content-Type=text%2Fhtml",e.comm.openChannel();var a;try{a=document.createElement('