From a4d460a8f0e6f0450c0e8b154981e61bb7e3133a Mon Sep 17 00:00:00 2001 From: FinnStutzenstein Date: Mon, 12 Mar 2018 15:53:30 +0100 Subject: [PATCH] New file upload form (fixes #3510, fixed #3082) --- CHANGELOG | 1 + .../static/css/mediafiles/_site.scss | 12 ++ .../mediafiles/static/js/mediafiles/create.js | 46 ------ .../mediafiles/static/js/mediafiles/forms.js | 52 ++---- .../mediafiles/static/js/mediafiles/list.js | 5 +- .../mediafiles/static/js/mediafiles/site.js | 2 +- .../mediafiles/static/js/mediafiles/upload.js | 154 ++++++++++++++++++ .../templates/mediafiles/mediafile-form.html | 3 +- .../mediafiles/mediafile-upload-form.html | 97 +++++++++++ 9 files changed, 282 insertions(+), 90 deletions(-) delete mode 100644 openslides/mediafiles/static/js/mediafiles/create.js create mode 100644 openslides/mediafiles/static/js/mediafiles/upload.js create mode 100644 openslides/mediafiles/static/templates/mediafiles/mediafile-upload-form.html diff --git a/CHANGELOG b/CHANGELOG index a46a880bd..17d8240ec 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -125,6 +125,7 @@ Core: easier development [#3566]. Mediafiles: +- New form for uploading multiple files [#3650]. - Fixed reloading of PDF on page change [#3274]. - Custom CKEditor plugin for browsing mediafiles [#3337]. - Project images always in fullscreen [#3355]. diff --git a/openslides/mediafiles/static/css/mediafiles/_site.scss b/openslides/mediafiles/static/css/mediafiles/_site.scss index 0fc76772f..81a9f3aaa 100644 --- a/openslides/mediafiles/static/css/mediafiles/_site.scss +++ b/openslides/mediafiles/static/css/mediafiles/_site.scss @@ -9,3 +9,15 @@ width: 90%; } } + +#dropzone { + padding: 20px 10px; + border-radius: 4px; + border: 1px solid #e6e8eb; + text-align: center; + background-color: #fff; + + &.dragover { + border-color: #317796; + } +} diff --git a/openslides/mediafiles/static/js/mediafiles/create.js b/openslides/mediafiles/static/js/mediafiles/create.js deleted file mode 100644 index 8b332db7c..000000000 --- a/openslides/mediafiles/static/js/mediafiles/create.js +++ /dev/null @@ -1,46 +0,0 @@ -(function () { - -'use strict'; - -angular.module('OpenSlidesApp.mediafiles.create', [ - 'OpenSlidesApp.mediafiles.forms', -]) - -.controller('MediafileCreateCtrl', [ - '$scope', - 'MediafileForm', - 'ErrorMessage', - function ($scope, MediafileForm, ErrorMessage) { - $scope.model = {}; - $scope.alert = {}; - $scope.formFields = MediafileForm.getFormFields(true); - - // upload and save mediafile - $scope.save = function (mediafile) { - if (typeof mediafile.getFile === 'function') { - $scope.activeUpload = MediafileForm.uploadFile(mediafile).then( - function (success) { - $scope.closeThisDialog(); - }, - function (error) { - $scope.activeUpload = void 0; - $scope.alert = ErrorMessage.forAlert(error); - }, - function (progress) { - $scope.progress = parseInt(100.0 * progress.loaded / progress.total); - } - ); - } - }; - $scope.close = function () { - // TODO: abort() is not a function. But it is documented in the docs. - // See https://github.com/danialfarid/ng-file-upload/issues/1844 - /*if ($scope.activeUpload) { - $scope.activeUpload.abort(); - }*/ - $scope.closeThisDialog(); - }; - } -]); - -}()); diff --git a/openslides/mediafiles/static/js/mediafiles/forms.js b/openslides/mediafiles/static/js/mediafiles/forms.js index 8a4c7c4d3..8e9124609 100644 --- a/openslides/mediafiles/static/js/mediafiles/forms.js +++ b/openslides/mediafiles/static/js/mediafiles/forms.js @@ -12,57 +12,31 @@ angular.module('OpenSlidesApp.mediafiles.forms', [ // Service for mediafile form .factory('MediafileForm', [ 'gettextCatalog', - 'Upload', 'operator', 'User', - function (gettextCatalog, Upload, operator, User) { + function (gettextCatalog, operator, User) { return { // ngDialog for mediafile form getDialog: function (mediafile) { - return { - template: 'static/templates/mediafiles/mediafile-form.html', - controller: (mediafile) ? 'MediafileUpdateCtrl' : 'MediafileCreateCtrl', + var dialog = { className: 'ngdialog-theme-default wide-form', closeByEscape: false, closeByDocument: false, - resolve: { - mediafileId: function () {return mediafile ? mediafile.id : void 0;} - }, }; - }, - // upload selected file (used by create view only) - uploadFile: function (mediafile) { - var file = mediafile.getFile(); - if (!mediafile.title) { - mediafile.title = file.name; + if (mediafile) { + dialog.template = 'static/templates/mediafiles/mediafile-form.html'; + dialog.controller = 'MediafileUpdateCtrl'; + dialog.resolve = { + mediafileId: function () {return mediafile ? mediafile.id : void 0;} + }; + } else { + dialog.template = 'static/templates/mediafiles/mediafile-upload-form.html'; + dialog.controller = 'MediafileUploadCtrl'; } - if (!mediafile.uploader_id) { - mediafile.uploader_id = operator.user.id; - } - return Upload.upload({ - url: '/rest/mediafiles/mediafile/', - method: 'POST', - data: {mediafile: file, title: mediafile.title, uploader_id: mediafile.uploader_id, hidden: mediafile.hidden} - }); + return dialog; }, - getFormFields: function (isCreateForm) { + getFormFields: function () { return [ - { - key: 'newFile', - type: 'file', - templateOptions: { - label: gettextCatalog.getString('File'), - required: true, - change: function (model, files, event, rejectedFiles) { - var file = files ? files[0] : void 0; - model.getFile = function () { - return file; - }; - model.newFile = file ? file.name : void 0; - }, - }, - hide: !isCreateForm, - }, { key: 'title', type: 'input', diff --git a/openslides/mediafiles/static/js/mediafiles/list.js b/openslides/mediafiles/static/js/mediafiles/list.js index 5af001994..74231b35e 100644 --- a/openslides/mediafiles/static/js/mediafiles/list.js +++ b/openslides/mediafiles/static/js/mediafiles/list.js @@ -120,7 +120,8 @@ angular.module('OpenSlidesApp.mediafiles.list', [ $scope.isSelectMode = false; // check all checkboxes $scope.checkAll = function () { - angular.forEach($scope.mediafiles, function (mediafile) { + $scope.selectedAll = !$scope.selectedAll; + _.forEach($scope.mediafiles, function (mediafile) { mediafile.selected = $scope.selectedAll; }); }; @@ -128,7 +129,7 @@ angular.module('OpenSlidesApp.mediafiles.list', [ $scope.uncheckAll = function () { if (!$scope.isSelectMode) { $scope.selectedAll = false; - angular.forEach($scope.mediafiles, function (mediafile) { + _.forEach($scope.mediafiles, function (mediafile) { mediafile.selected = false; }); } diff --git a/openslides/mediafiles/static/js/mediafiles/site.js b/openslides/mediafiles/static/js/mediafiles/site.js index cc1466875..5eda99266 100644 --- a/openslides/mediafiles/static/js/mediafiles/site.js +++ b/openslides/mediafiles/static/js/mediafiles/site.js @@ -3,10 +3,10 @@ 'use strict'; angular.module('OpenSlidesApp.mediafiles.site', [ - 'OpenSlidesApp.mediafiles.create', 'OpenSlidesApp.mediafiles.list', 'OpenSlidesApp.mediafiles.states', 'OpenSlidesApp.mediafiles.update', + 'OpenSlidesApp.mediafiles.upload', 'OpenSlidesApp.mediafiles.image-plugin', ]); diff --git a/openslides/mediafiles/static/js/mediafiles/upload.js b/openslides/mediafiles/static/js/mediafiles/upload.js new file mode 100644 index 000000000..53c2dc007 --- /dev/null +++ b/openslides/mediafiles/static/js/mediafiles/upload.js @@ -0,0 +1,154 @@ +(function () { + +'use strict'; + +angular.module('OpenSlidesApp.mediafiles.upload', [ + 'OpenSlidesApp.mediafiles.forms', + 'ngFileUpload', +]) + +.controller('MediafileUploadCtrl', [ + '$scope', + '$q', + 'User', + 'Upload', + 'operator', + 'gettextCatalog', + 'ErrorMessage', + function ($scope, $q, User, Upload, operator, gettextCatalog, ErrorMessage) { + User.bindAll({}, $scope, 'users'); + $scope.alert = {}; + $scope.files = []; + $scope.uploading = false; + var idCounter = 0; // Used for uniqly identifing each file in $scope.files. + + // Convert bytes to human readable si units. + var humanFileSize = function (bytes) { + if(Math.abs(bytes) < 1000) { + return bytes + ' B'; + } + var units = ['kB','MB','GB','TB','PB','EB','ZB','YB']; + var i = -1; + do { + bytes /= 1000; + i++; + } while(bytes >= 1000 && i < units.length - 1); + + return bytes.toFixed(1) + ' ' + units[i]; + }; + + $scope.addFiles = function (files) { + files = _.map(files, function (file) { + idCounter += 1; + // This is a client side representation used for the template + return { + id: idCounter, + file: file, + title: file.name, + hidden: false, + uploader_id: operator.user.id, + name: file.name, + size: file.size, + humanSize: humanFileSize(file.size), + type: file.type, + progress: 0, + }; + }); + // Add each file, that is not a duplicate to $scope.files + _.forEach(files, function (file) { + var duplicate = _.some($scope.files, function (_file) { + return file.name === _file.name && + file.size === _file.size && + file.type === _file.type; + }); + if (!duplicate) { + $scope.files.push(file); + } + }); + }; + + $scope.removeFile = function (id) { + $scope.files = _.filter($scope.files, function (file) { + return file.id !== id; + }); + }; + + // Add files via drag and drop + $scope.$watch('dropFiles', function () { + if ($scope.dropFiles) { + $scope.addFiles($scope.dropFiles); + } + }); + + // upload all files + $scope.upload = function () { + $scope.uploading = true; + var promises = _.map($scope.files, function (file) { + // clear error + file.error = void 0; + + // Check, if all necessary fields are set. + if (!file.title) { + file.title = file.file.name; + } + if (!file.uploader_id) { + file.uploader_id = operator.user.id; + } + + return Upload.upload({ + url: '/rest/mediafiles/mediafile/', + method: 'POST', + data: { + mediafile: file.file, + title: file.title, + uploader_id: file.uploader_id, + hidden: file.hidden + }, + }).then( + function (success) { + $scope.removeFile(file.id); + }, + function (error) { + file.error = ErrorMessage.forAlert(error).msg; + return error; + }, + function (progress) { + file.progress = parseInt(100.0 * progress.loaded / progress.total); + } + ); + }); + + $q.all(promises).then(function (success) { + var errors = _.filter(success, function (entry) { + return entry; + }); + + if (errors.length) { + $scope.uploading = false; + var message = gettextCatalog.getString('Some files could not be uploaded'); + $scope.alert = { type: 'danger', msg: message, show: true }; + } else { + $scope.close(); + } + }); + }; + + $scope.clear = function () { + $scope.uploading = false; + $scope.files = []; + }; + + $scope.close = function () { + $scope.closeThisDialog(); + }; + } +]) + +.run([ + 'gettext', + function (gettext) { + gettext('Some files could not be uploaded'); + } +]); + +}()); diff --git a/openslides/mediafiles/static/templates/mediafiles/mediafile-form.html b/openslides/mediafiles/static/templates/mediafiles/mediafile-form.html index 164127e7e..a9f3e4c13 100644 --- a/openslides/mediafiles/static/templates/mediafiles/mediafile-form.html +++ b/openslides/mediafiles/static/templates/mediafiles/mediafile-form.html @@ -1,5 +1,4 @@ -

Edit file

-

New file

+

Edit File

{{ alert.msg }} diff --git a/openslides/mediafiles/static/templates/mediafiles/mediafile-upload-form.html b/openslides/mediafiles/static/templates/mediafiles/mediafile-upload-form.html new file mode 100644 index 000000000..0da179423 --- /dev/null +++ b/openslides/mediafiles/static/templates/mediafiles/mediafile-upload-form.html @@ -0,0 +1,97 @@ +

Upload files

+ +
+ {{ alert.msg }} +
+ +
+
+
+ Drop or + + select files + +
+
+ +
+ + + + + + + + + + + + + + + + + + + + +
+ File information + + Title + + Hidden + + Uploader + + Upload status +
+
+ {{ $index+1 }}. {{ file.name }} + + + +
+
{{ file.type }}
+
{{ file.humanSize }}
+
+ {{ file.error }} +
+
+ + + {{ file.title }} + + + + + + + {{ file.progress }}% + +
+
+ + +
+ + + +
+