New file upload form (fixes #3510, fixed #3082)

This commit is contained in:
FinnStutzenstein 2018-03-12 15:53:30 +01:00 committed by Emanuel Schütze
parent 09e74481cb
commit a4d460a8f0
9 changed files with 282 additions and 90 deletions

View File

@ -125,6 +125,7 @@ Core:
easier development [#3566]. easier development [#3566].
Mediafiles: Mediafiles:
- New form for uploading multiple files [#3650].
- Fixed reloading of PDF on page change [#3274]. - Fixed reloading of PDF on page change [#3274].
- Custom CKEditor plugin for browsing mediafiles [#3337]. - Custom CKEditor plugin for browsing mediafiles [#3337].
- Project images always in fullscreen [#3355]. - Project images always in fullscreen [#3355].

View File

@ -9,3 +9,15 @@
width: 90%; width: 90%;
} }
} }
#dropzone {
padding: 20px 10px;
border-radius: 4px;
border: 1px solid #e6e8eb;
text-align: center;
background-color: #fff;
&.dragover {
border-color: #317796;
}
}

View File

@ -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();
};
}
]);
}());

View File

@ -12,57 +12,31 @@ angular.module('OpenSlidesApp.mediafiles.forms', [
// Service for mediafile form // Service for mediafile form
.factory('MediafileForm', [ .factory('MediafileForm', [
'gettextCatalog', 'gettextCatalog',
'Upload',
'operator', 'operator',
'User', 'User',
function (gettextCatalog, Upload, operator, User) { function (gettextCatalog, operator, User) {
return { return {
// ngDialog for mediafile form // ngDialog for mediafile form
getDialog: function (mediafile) { getDialog: function (mediafile) {
return { var dialog = {
template: 'static/templates/mediafiles/mediafile-form.html',
controller: (mediafile) ? 'MediafileUpdateCtrl' : 'MediafileCreateCtrl',
className: 'ngdialog-theme-default wide-form', className: 'ngdialog-theme-default wide-form',
closeByEscape: false, closeByEscape: false,
closeByDocument: false, closeByDocument: false,
resolve: { };
if (mediafile) {
dialog.template = 'static/templates/mediafiles/mediafile-form.html';
dialog.controller = 'MediafileUpdateCtrl';
dialog.resolve = {
mediafileId: function () {return mediafile ? mediafile.id : void 0;} mediafileId: function () {return mediafile ? mediafile.id : void 0;}
},
}; };
}, } else {
// upload selected file (used by create view only) dialog.template = 'static/templates/mediafiles/mediafile-upload-form.html';
uploadFile: function (mediafile) { dialog.controller = 'MediafileUploadCtrl';
var file = mediafile.getFile();
if (!mediafile.title) {
mediafile.title = file.name;
} }
if (!mediafile.uploader_id) { return dialog;
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}
});
}, },
getFormFields: function (isCreateForm) { getFormFields: function () {
return [ 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', key: 'title',
type: 'input', type: 'input',

View File

@ -120,7 +120,8 @@ angular.module('OpenSlidesApp.mediafiles.list', [
$scope.isSelectMode = false; $scope.isSelectMode = false;
// check all checkboxes // check all checkboxes
$scope.checkAll = function () { $scope.checkAll = function () {
angular.forEach($scope.mediafiles, function (mediafile) { $scope.selectedAll = !$scope.selectedAll;
_.forEach($scope.mediafiles, function (mediafile) {
mediafile.selected = $scope.selectedAll; mediafile.selected = $scope.selectedAll;
}); });
}; };
@ -128,7 +129,7 @@ angular.module('OpenSlidesApp.mediafiles.list', [
$scope.uncheckAll = function () { $scope.uncheckAll = function () {
if (!$scope.isSelectMode) { if (!$scope.isSelectMode) {
$scope.selectedAll = false; $scope.selectedAll = false;
angular.forEach($scope.mediafiles, function (mediafile) { _.forEach($scope.mediafiles, function (mediafile) {
mediafile.selected = false; mediafile.selected = false;
}); });
} }

View File

@ -3,10 +3,10 @@
'use strict'; 'use strict';
angular.module('OpenSlidesApp.mediafiles.site', [ angular.module('OpenSlidesApp.mediafiles.site', [
'OpenSlidesApp.mediafiles.create',
'OpenSlidesApp.mediafiles.list', 'OpenSlidesApp.mediafiles.list',
'OpenSlidesApp.mediafiles.states', 'OpenSlidesApp.mediafiles.states',
'OpenSlidesApp.mediafiles.update', 'OpenSlidesApp.mediafiles.update',
'OpenSlidesApp.mediafiles.upload',
'OpenSlidesApp.mediafiles.image-plugin', 'OpenSlidesApp.mediafiles.image-plugin',
]); ]);

View File

@ -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');
}
]);
}());

View File

@ -1,5 +1,4 @@
<h1 ng-if="model.id" translate>Edit file</h1> <h1 translate>Edit File</h1>
<h1 ng-if="!model.id" translate>New file</h1>
<div uib-alert ng-show="alert.show" ng-class="'alert-' + (alert.type || 'warning')" close="alert={}"> <div uib-alert ng-show="alert.show" ng-class="'alert-' + (alert.type || 'warning')" close="alert={}">
{{ alert.msg }} {{ alert.msg }}

View File

@ -0,0 +1,97 @@
<h1 translate>Upload files</h1>
<div uib-alert ng-show="alert.show" ng-class="'alert-' + (alert.type || 'warning')" close="alert={}">
{{ alert.msg }}
</div>
<form editable-form name="mediafileForm" ng-submit="upload()" novalidate>
<div class="form-group" ng-hide="uploading">
<div id="dropzone" ngf-drop ngf-select ng-model="dropFiles" ngf-drag-over-class="'dragover'" ngf-multiple="true">
Drop or
<a href= ng-disabled="files.length" type="button" ngf-select="addFiles($files)" multiple translate>
select files
</a>
</div>
</div>
<div class="form-group" ng-if="files.length">
<table class="table table-striped table-bordered table-hover">
<thead>
<tr>
<th translate>
File information
</th>
<th translate>
Title
</th>
<th ng-if="!uploading && operator.hasPerms('mediafiles.can_see_hidden')" translate>
Hidden
</th>
<th ng-if="!uploading && operator.hasPerms('mediafiles.can_manage')" translate>
Uploader
</th>
<th ng-if="uploading" translate>
Upload status
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="file in files">
<td>
<div>
{{ $index+1 }}. {{ file.name }}
<span class="pull-right pointer" ng-click="removeFile(file.id)">
<i class="fa fa-times text-danger"></i>
</span>
</div>
<div ng-if="file.type"><i class="fa fa-file"></i> {{ file.type }}</div>
<div><i class="fa fa-database"></i> {{ file.humanSize }}</div>
<div ng-if="file.error" class="text-danger">
{{ file.error }}
</div>
</td>
<td ng-if="!uploading">
<input type="text" class="form-control" ng-model="file.title">
</td>
<td ng-if="uploading">
{{ file.title }}
</td>
<td ng-if="!uploading && operator.hasPerms('mediafiles.can_see_hidden')">
<i class="fa" ng-class="file.hidden ? 'fa-check-square-o' : 'fa-square-o'"
ng-click="file.hidden = !file.hidden"></i>
</td>
<td ng-if="!uploading && operator.hasPerms('mediafiles.can_manage')">
<select chosen
data-ng-model="file.uploader_id"
ng-options="user.id as user.full_name for user in users"
allow-single-deselect="true"
search-contains="true"
placeholder-text-single="'Select or search a participant ...' | translate"
no-results-text="'No results available ...' | translate"
class="form-control">
<option value=""></option>
</select>
</td>
<td ng-if="uploading">
<uib-progressbar value="file.progress" animate="false">
<span class="nobr">{{ file.progress }}%</span>
</uib-progressbar>
</td>
</tr>
</tbody>
</table>
</div>
<div class="form-field">
<button type="submit" ng-disabled="files.length === 0 || uploading" class="btn btn-primary" translate>
Upload
</button>
<button type="button" ng-disabled="uploading" ng-click="clear()" class="btn btn-default" translate>
Clear list
</button>
<button type="button" ng-click="close()" class="btn btn-default pull-right" translate>
Close
</button>
</div>
</form>