parent
09e74481cb
commit
a4d460a8f0
@ -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].
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
}());
|
|
@ -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',
|
||||||
|
@ -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;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
154
openslides/mediafiles/static/js/mediafiles/upload.js
Normal file
154
openslides/mediafiles/static/js/mediafiles/upload.js
Normal 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');
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
}());
|
@ -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 }}
|
||||||
|
@ -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>
|
Loading…
Reference in New Issue
Block a user