Refactoring mediafile templates. (Fixes #1839, #1818, #1833)

- uploader not visible/loaded after reload list view
- use same list view style as in other apps
- use dialog for create/edit form
- show error message in form
- edit form: remove file upload form element, add uploader form element
- create form: add uploader form element (only if user has manager permissions)
- Refactor mediafile upload method
- show not-implemented message for project pdf
- use pdf filter in list view
This commit is contained in:
Emanuel Schuetze 2016-01-24 00:19:54 +01:00
parent cc657b1fee
commit f4e4166e24
6 changed files with 290 additions and 145 deletions

View File

@ -401,11 +401,10 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
.controller('AssignmentCreateCtrl', [
'$scope',
'$state',
'Assignment',
'AssignmentForm',
'Agenda',
function($scope, $state, Assignment, AssignmentForm, Agenda) {
function($scope, Assignment, AssignmentForm, Agenda) {
$scope.model = {};
// set default value for open posts form field
$scope.model.open_posts = 1;
@ -435,12 +434,11 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
.controller('AssignmentUpdateCtrl', [
'$scope',
'$state',
'Assignment',
'AssignmentForm',
'Agenda',
'assignment',
function($scope, $state, Assignment, AssignmentForm, Agenda, assignment) {
function($scope, Assignment, AssignmentForm, Agenda, assignment) {
$scope.alert = {};
// set initial values for form model by create deep copy of assignment object
// so list/detail view is not updated while editing
@ -487,12 +485,11 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
.controller('AssignmentPollUpdateCtrl', [
'$scope',
'$state',
'gettextCatalog',
'AssignmentPoll',
'assignmentpoll',
'ballot',
function($scope, $state, gettextCatalog, AssignmentPoll, assignmentpoll, ballot) {
function($scope, gettextCatalog, AssignmentPoll, assignmentpoll, ballot) {
// set initial values for form model
$scope.model = assignmentpoll;
$scope.ballot = ballot;

View File

@ -23,6 +23,9 @@ angular.module('OpenSlidesApp.mediafiles', [])
var filename = this.mediafile.name;
return /\/(.+?)$/.exec(filename)[1];
}],
filetype: [function () {
return this.mediafile.type;
}],
title_or_filename: ['title', 'mediafile', function (title) {
return title || this.filename;
}]

View File

@ -35,38 +35,25 @@ angular.module('OpenSlidesApp.mediafiles.site', ['ngFileUpload', 'OpenSlidesApp.
resolve: {
mediafiles: function(Mediafile) {
return Mediafile.findAll();
}
}
})
.state('mediafiles.mediafile.create', {})
.state('mediafiles.mediafile.detail', {
url: '/{id:int}',
abstract: true,
resolve: {
mediafile: function(Mediafile, $stateParams) {
var id = $stateParams.id;
var file = Mediafile.find(id);
return file;
}
},
template: "<ui-view/>",
})
.state('mediafiles.mediafile.detail.update', {
views: {
'@mediafiles.mediafile': {}
users: function(User) {
return User.findAll();
},
}
});
})
}
])
.controller('MediafileListCtrl', [
'$scope',
'$http',
'$timeout',
'Upload',
'ngDialog',
'Mediafile',
function($scope, $http, $timeout, Upload, Mediafile) {
'MediafileForm',
'User',
function($scope, $http, ngDialog, Mediafile, MediafileForm, User) {
Mediafile.bindAll({}, $scope, 'mediafiles');
User.bindAll({}, $scope, 'users');
// setup table sorting
$scope.sortColumn = 'title';
@ -89,62 +76,158 @@ angular.module('OpenSlidesApp.mediafiles.site', ['ngFileUpload', 'OpenSlidesApp.
].join(" ");
}
// delete
$scope.delete = function (mediafile) {
//TODO: add confirm message
Mediafile.destroy(mediafile.id).then(
function(success) {
//TODO: success message
// open new/edit dialog
$scope.openDialog = function (mediafile) {
ngDialog.open(MediafileForm.getDialog(mediafile));
};
// *** delete mode functions ***
$scope.isDeleteMode = false;
// check all checkboxes
$scope.checkAll = function () {
angular.forEach($scope.mediafiles, function (mediafile) {
mediafile.selected = $scope.selectedAll;
});
};
// uncheck all checkboxes if isDeleteMode is closed
$scope.uncheckAll = function () {
if (!$scope.isDeleteMode) {
$scope.selectedAll = false;
angular.forEach($scope.mediafiles, function (mediafile) {
mediafile.selected = false;
});
}
);
};
// delete all selected mediafiles
$scope.deleteMultiple = function () {
angular.forEach($scope.mediafiles, function (mediafile) {
if (mediafile.selected)
Mediafile.destroy(mediafile.id);
});
$scope.isDeleteMode = false;
$scope.uncheckAll();
};
// delete single mediafile
$scope.delete = function (mediafile) {
Mediafile.destroy(mediafile.id);
};
}
])
.controller('MediafileCreateCtrl', [
'$scope',
'$state',
'$timeout',
'operator',
'Upload',
function($scope, $state, $timeout, operator, Upload) {
'MediafileForm',
'User',
function($scope, MediafileForm, User) {
User.bindAll({}, $scope, 'users');
$scope.mediafile = {};
$scope.save = uploadFile($timeout, $scope, $state, operator, Upload);
$scope.alert = {};
$scope.users = User.getAll();
// upload and save mediafile
$scope.save = function (mediafile) {
MediafileForm.uploadFile(mediafile).then(
function (success) {
$scope.closeThisDialog();
},
function (error) {
var message = '';
for (var e in error.data) {
message += e + ': ' + error.data[e] + ' ';
}
$scope.alert = {type: 'danger', msg: message, show: true};
}
);
}
}
])
.controller('MediafileUpdateCtrl', [
'$scope',
'operator',
'Mediafile',
'User',
'mediafile',
function($scope, operator, Mediafile, User, mediafile) {
User.bindAll({}, $scope, 'users');
$scope.alert = {};
$scope.users = User.getAll();
// set initial values for form model by create deep copy of motion object
// so list/detail view is not updated while editing
$scope.mediafile = angular.copy(mediafile);
// save mediafile
$scope.save = function (mediafile) {
// reset title and uploader_id if empty
if (!mediafile.title) {
mediafile.title = mediafile.filename;
}
if (!mediafile.uploader_id) {
mediafile.uploader_id = operator.user.id;
}
// inject the changed mediafile (copy) object back into DS store
Mediafile.inject(mediafile);
// save change mediafile object on server
Mediafile.save(mediafile, { method: 'PATCH' }).then(
function (success) {
$scope.closeThisDialog();
},
function (error) {
Mediafile.refresh(mediafile);
var message = '';
for (var e in error.data) {
message += e + ': ' + error.data[e] + ' ';
}
$scope.alert = {type: 'danger', msg: message, show: true};
}
);
}
}
])
// Service for mediafile form
.factory('MediafileForm', [
'$state',
'$timeout',
'operator',
'Upload',
'Mediafile',
'mediafile',
function($scope, $state, $timeout, operator, Upload, Mediafile, mediafile) {
$scope.mediafile = mediafile;
$scope.save = uploadFile($timeout, $scope, $state, operator, Upload, mediafile);
'gettextCatalog',
'User',
function ($state, operator, Upload, gettextCatalog, User) {
return {
// ngDialog for mediafile form
getDialog: function (mediafile) {
if (mediafile) {
var resolve = {
mediafile: function(Assignment) {return mediafile;}
};
}
return {
template: 'static/templates/mediafiles/mediafile-form.html',
controller: (mediafile) ? 'MediafileUpdateCtrl' : 'MediafileCreateCtrl',
className: 'ngdialog-theme-default wide-form',
closeByEscape: false,
closeByDocument: false,
resolve: (resolve) ? resolve : null
}
},
// upload selected file (used by create view only)
uploadFile: function (mediafile) {
if (!mediafile.title) {
mediafile.title = mediafile.newFile.name;
}
if (!mediafile.uploader_id) {
mediafile.uploader_id = operator.user.id;
}
return Upload.upload({
url: '/rest/mediafiles/mediafile/',
method: 'POST',
data: {mediafile: mediafile.newFile, title: mediafile.title, uploader_id: mediafile.uploader_id}
});
}
};
}
]);
function uploadFile($timeout, $scope, $state, operator, Upload, mediafile) {
return function(file) {
file.upload = Upload.upload({
url: '/rest/mediafiles/mediafile/' + (mediafile ? mediafile.id : ''),
method: mediafile ? 'PUT' : 'POST',
data: {mediafile: file.newFile, title: file.title, uploader_id: operator.user.id}
});
file.upload.then(function (response) {
$timeout(function () {
file.result = response.data;
$state.go('mediafiles.mediafile.list');
});
}, function (response) {
if (response.status > 0)
$scope.errorMsg = response.status + ': ' + response.data;
});
};
}
}());

View File

@ -1,42 +1,46 @@
<div class="header">
<div class="title">
<div class="submenu">
<a ui-sref="mediafiles.mediafile.list" class="btn btn-sm btn-default">
<i class="fa fa-angle-double-left fa-lg"></i>
<translate>Back to overview</translate>
</a>
</div>
<h1 ng-if="mediafile.id" translate>Edit file</h1>
<h1 ng-if="!mediafile.id" translate>New file</h1>
</div>
</div>
<h1 ng-if="mediafile.id" translate>Edit file</h1>
<h1 ng-if="!mediafile.id" translate>New file</h1>
<div class="details">
<form name="mediafileForm">
<uib-alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
{{ alert.msg }}
</uib-alert>
<form name="mediafileForm" ng-submit="save(mediafile)">
<!-- file -->
<div class="form-group">
<div ng-if="mediafile.id">
<span translate>Current value: </span>{{ mediafile.filename }}
</div>
<input type="file" ngf-select ng-model="mediafile.newFile" required/>
<label for="inputTitle" translate>File *</label>
<!-- create view: show file select field -->
<input ng-if="!mediafile.id" type="file" ngf-select ng-model="mediafile.newFile" required>
<!-- update view: show filename only -->
<div ng-if="mediafile.id">{{ mediafile.filename }}</div>
</div>
<!-- title -->
<div class="form-group">
<label for="inputTitle" translate>Title</label>
<input type="text" ng-model="mediafile.title" class="form-control" name="inputTitle">
</div>
<span ng-show="mediafile.mediafile.result">Upload Successful</span>
<!-- uploader -->
<div os-perms="mediafiles.can_manage" class="form-group">
<label for="inputTitle" translate>Uploaded by</label>
<ui-select ng-model="mediafile.uploader_id">
<ui-select-match placeholder="{{ 'Select or search a participant ...' | translate }}" data-allow-clear="true">
{{ $select.selected.get_full_name() }}
</ui-select-match>
<ui-select-choices repeat="user.id as user in users | filter: $select.search">
<div ng-bind-html="user.get_full_name() | highlight: $select.search"></div>
</ui-select-choices>
</ui-select>
</div>
<i ng-show="mediafile.file.$error.required">*required</i><br>
<i ng-show="mediafile.file.$error.maxSize">File too large
{{ picFile.size / 1000000|number:1}}MB: max {{ mediafile.mediafile.$errorParam}}</i>
<button type="submit" ng-click="save(mediafile)" class="btn btn-primary" translate>
<button type="submit" ng-disabled="mediafileForm.$invalid" class="btn btn-primary" translate>
Save
</button>
<button ui-sref="mediafiles.mediafile.list" class="btn btn-default" translate>
<button ng-click="closeThisDialog()" class="btn btn-default" translate>
Cancel
</button>
</form>
</div>
</form>

View File

@ -1,7 +1,7 @@
<div class="header">
<div class="title">
<div class="submenu">
<a ui-sref="mediafiles.mediafile.create" os-perms="mediafiles.can_upload" class="btn btn-primary btn-sm">
<a ng-click="openDialog()" os-perms="mediafiles.can_upload" class="btn btn-primary btn-sm">
<i class="fa fa-plus fa-lg"></i>
<translate>New</translate>
</a>
@ -12,71 +12,132 @@
<div class="details">
<div class="row form-group">
<div class="col-sm-8"></div>
<div class="col-sm-4">
<input type="text" os-focus-me ng-model="filter.search" class="form-control"
placeholder="{{ 'Filter' | translate }}">
<div class="col-sm-6">
<form class="form-inline">
<!-- delete mode -->
<button os-perms="mediafiles.can_manage" class="btn"
ng-class="$parent.isDeleteMode ? 'btn-primary' : 'btn-default'"
ng-click="$parent.isDeleteMode = !$parent.isDeleteMode; uncheckAll()">
<i class="fa fa-check-square-o"></i>
<translate>Select ...</translate>
</button>
</form>
</div>
<div class="col-sm-6">
<div class="form-inline text-right">
<div class="form-group">
<div class="input-group">
<div class="input-group-addon"><i class="fa fa-search"></i></div>
<input type="text" os-focus-me ng-model="filter.search" class="form-control"
placeholder="{{ 'Search' | translate}}">
</div>
</div>
<button class="btn btn-default" ng-click="isFilterOpen = !isFilterOpen"
ng-class="isFilterOpen ? 'btn-primary' : 'btn-default'">
<i class="fa fa-filter"></i>
<translate>Filter ...</translate>
</button>
</div>
</div>
</div>
<div uib-collapse="!isFilterOpen" class="row">
<div class="col-sm-6 text-right"></div>
<div class="col-sm-6 text-right">
<!-- pdf filter -->
<input type="checkbox" ng-model="filter.showPDFs" ng-true-value="'application/pdf'" ng-false-value=''>
<translate> Show PDF files only</translate>
</div>
</div>
<div uib-collapse="!isDeleteMode" class="row spacer">
<div class="col-sm-12 text-left">
<!-- delete button -->
<a ng-show="isDeleteMode"
os-perms="mediafiles.can_manage" ng-click="deleteMultiple()"
class="btn btn-primary">
<i class="fa fa-trash fa-lg"></i>
<translate>Delete selected mediafiles</translate>
</a>
</div>
</div>
<div class="spacer-top-lg italic">
{{ mediafilesFiltered.length }} /
{{ mediafiles.length }} {{ "files" | translate }}<span ng-if="(mediafiles|filter:{selected:true}).length > 0">,
{{(mediafiles|filter:{selected:true}).length}} {{ "selected" | translate }}</span>
</div>
<table class="table table-striped table-bordered table-hover">
<thead>
<tr>
<!-- projector column -->
<th ng-show="!isDeleteMode" os-perms="core.can_manage_projector" class="minimum">
<!-- delete selection column -->
<th ng-show="isDeleteMode" os-perms="mediafiles.can_manage" class="minimum deleteColumn">
<input type="checkbox" ng-model="$parent.selectedAll" ng-change="checkAll()">
<!-- title -->
<th ng-click="toggleSort('title_or_filename')" class="sortable">
<translate>Title</translate>
<i class="pull-right fa" ng-show="sortColumn === 'title_or_filename' && header.sortable != false"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
<th ng-click="toggleSort('filetype')" class="sortable">
<!-- filetype -->
<th ng-click="toggleSort('filetype')" class="sortable optional">
<translate>Filetype</translate>
<i class="pull-right fa" ng-show="sortColumn === 'filetype' && header.sortable != false"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
<th ng-click="toggleSort('filesize')" class="sortable">
<!-- filesize -->
<th ng-click="toggleSort('filesize')" class="sortable optional">
<translate>Filesize</translate>
<i class="pull-right fa" ng-show="sortColumn === 'filesize' && header.sortable != false"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
<th ng-click="toggleSort('timestamp')" class="sortable">
<!-- upload time -->
<th ng-click="toggleSort('timestamp')" class="sortable optional">
<translate>Upload time</translate>
<i class="pull-right fa" ng-show="sortColumn === 'timestamp' && header.sortable != false"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
<!-- uploaded by -->
<th ng-click="toggleSort('uploader')" class="sortable">
<translate>Uploaded by</translate>
<i class="pull-right fa" ng-show="sortColumn === 'uploader' && header.sortable != false"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
<th os-perms="mediafiles.can_manage core.can_manage_projector">
<translate>Actions</translate>
</th>
</tr>
<tbody>
<tr ng-repeat="mediafile in mediafiles | osFilter: filter.search : getFilterString |
orderBy: sortColumn:reverse">
<td><a ng-href="{{ mediafile.mediafileUrl }}" target="_blank">{{ mediafile.title_or_filename }}</a>
<td class="optional">{{ mediafile.mediafile.type }}
<td>{{ mediafile.filesize }}
<td>{{ mediafile.timestamp | date:'yyyy-MM-dd HH:mm:ss' }}
<td>{{ mediafile.uploader.get_full_name() }}
<td os-perms="mediafiles.can_manage core.can_manage_projector" class="nobr">
<!-- projector, TODO: add link to activate slide -->
<a href="" ng-show="mediafile.mediafile.type == 'application/pdf'" os-perms="core.can_manage_projector"
class="btn btn-default btn-sm"
title="{{ 'Show' | translate }}">
<tr ng-repeat="mediafile in mediafilesFiltered = (mediafiles | osFilter: filter.search : getFilterString |
filter: {filetype: filter.showPDFs} | orderBy: sortColumn:reverse)"
class="animate-item"
ng-class="{ 'activeline': mediafile.isProjected(), 'selected': mediafile.selected }">
<!-- projector column -->
<!-- TOOD: implement project pdf feature -->
<td ng-show="!isDeleteMode"
os-perms="core.can_manage_projector">
<a class="btn btn-default btn-sm"
ng-if="mediafile.mediafile.type == 'application/pdf'"
ng-class="{ 'btn-primary': mediafile.isProjected() }"
ng-click="mediafile.project()"
ng-bootbox-alert="{{ 'Sorry, the function to project pdf files is not yet implemented.' | translate }}"
title="{{ 'Project mediafile' | translate }}">
<i class="fa fa-video-camera"></i>
</a>
<!-- edit -->
<a ui-sref="mediafiles.mediafile.detail.update({id: mediafile.id })" os-perms="mediafiles.can_manage"
class="btn btn-default btn-sm"
title="{{ 'Edit' | translate }}">
<i class="fa fa-pencil"></i>
</a>
<!-- delete -->
<a ng-click="delete(mediafile)" os-perms="mediafiles.can_manage" class="btn btn-danger btn-sm"
title="{{ 'Delete' | translate }}">
<i class="fa fa-trash-o"></i>
</a>
</tr>
<!-- delete selection column -->
<td ng-show="isDeleteMode" os-perms="mediafiles.can_manage" class="deleteColumn">
<input type="checkbox" ng-model="mediafile.selected">
<!-- mediafile data colums -->
<td ng-mouseover="mediafile.hover=true" ng-mouseleave="mediafile.hover=false">
<strong><a ng-href="{{ mediafile.mediafileUrl }}" target="_blank">{{ mediafile.title_or_filename }}</a></strong>
<div os-perms="mediafiles.can_manage" class="hoverActions" ng-class="{'hiddenDiv': !mediafile.hover}">
<a href="" ng-click="openDialog(mediafile)" translate>Edit</a> |
<a href="" class="text-danger"
ng-bootbox-confirm="{{ 'Are you sure you want to delete this file?' | translate }}<br>
<b>{{ mediafile.title }} [{{ mediafile.mediafile.name }}]</b>"
ng-bootbox-confirm-action="delete(mediafile)" translate>Delete</a>
</div>
<td class="optional">{{ mediafile.mediafile.type }}
<td class="optional">{{ mediafile.filesize }}
<td class="optional">{{ mediafile.timestamp | date:'yyyy-MM-dd HH:mm:ss' }}
<td>{{ mediafile.uploader.get_full_name() }}
</tbody>
</table>
</div>

View File

@ -603,7 +603,6 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
.controller('MotionCreateCtrl', [
'$scope',
'$state',
'gettext',
'Motion',
'MotionForm',
@ -614,7 +613,7 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
'User',
'Workflow',
'Agenda',
function($scope, $state, gettext, Motion, MotionForm, Category, Config, Mediafile, Tag, User, Workflow, Agenda) {
function($scope, gettext, Motion, MotionForm, Category, Config, Mediafile, Tag, User, Workflow, Agenda) {
Category.bindAll({}, $scope, 'categories');
Mediafile.bindAll({}, $scope, 'mediafiles');
Tag.bindAll({}, $scope, 'tags');
@ -652,7 +651,6 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
.controller('MotionUpdateCtrl', [
'$scope',
'$state',
'Motion',
'Category',
'Config',
@ -663,7 +661,7 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
'Workflow',
'Agenda',
'motion',
function($scope, $state, Motion, Category, Config, Mediafile, MotionForm, Tag, User, Workflow, Agenda, motion) {
function($scope, Motion, Category, Config, Mediafile, MotionForm, Tag, User, Workflow, Agenda, motion) {
Category.bindAll({}, $scope, 'categories');
Mediafile.bindAll({}, $scope, 'mediafiles');
Tag.bindAll({}, $scope, 'tags');
@ -746,13 +744,12 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
.controller('MotionPollUpdateCtrl', [
'$scope',
'$state',
'gettextCatalog',
'MotionPoll',
'MotionPollForm',
'motionpoll',
'voteNumber',
function($scope, $state, gettextCatalog, MotionPoll, MotionPollForm, motionpoll, voteNumber) {
function($scope, gettextCatalog, MotionPoll, MotionPollForm, motionpoll, voteNumber) {
// set initial values for form model
$scope.model = motionpoll;
$scope.voteNumber = voteNumber;