Closes #33: subviews for /document/view/id

This commit is contained in:
jendib 2015-11-01 14:48:07 +01:00
parent 08633a993d
commit 66cb7333c1
12 changed files with 373 additions and 310 deletions

View File

@ -105,7 +105,7 @@ public class FileUtil {
PDDocument pdfDocument = null; PDDocument pdfDocument = null;
try { try {
PDFTextStripper stripper = new PDFTextStripper(); PDFTextStripper stripper = new PDFTextStripper();
pdfDocument = PDDocument.load(inputStream, true); pdfDocument = PDDocument.load(inputStream);
content = stripper.getText(pdfDocument); content = stripper.getText(pdfDocument);
} catch (IOException e) { } catch (IOException e) {
log.error("Error while extracting text from the PDF", e); log.error("Error while extracting text from the PDF", e);
@ -157,7 +157,7 @@ public class FileUtil {
// Generate preview from the first page of the PDF // Generate preview from the first page of the PDF
PDDocument pdfDocument = null; PDDocument pdfDocument = null;
try { try {
pdfDocument = PDDocument.load(inputStream, true); pdfDocument = PDDocument.load(inputStream);
PDFRenderer renderer = new PDFRenderer(pdfDocument); PDFRenderer renderer = new PDFRenderer(pdfDocument);
image = renderer.renderImage(0); image = renderer.renderImage(0);
} finally { } finally {

View File

@ -156,6 +156,7 @@ angular.module('docs',
}) })
.state('document.view', { .state('document.view', {
url: '/view/:id', url: '/view/:id',
redirectTo: 'document.view.content',
views: { views: {
'document': { 'document': {
templateUrl: 'partial/docs/document.view.html', templateUrl: 'partial/docs/document.view.html',
@ -163,6 +164,33 @@ angular.module('docs',
} }
} }
}) })
.state('document.view.content', {
url: '/content',
views: {
'tab': {
templateUrl: 'partial/docs/document.view.content.html',
controller: 'DocumentViewContent'
}
}
})
.state('document.view.permissions', {
url: '/permissions',
views: {
'tab': {
templateUrl: 'partial/docs/document.view.permissions.html',
controller: 'DocumentViewPermissions'
}
}
})
.state('document.view.activity', {
url: '/activity',
views: {
'tab': {
templateUrl: 'partial/docs/document.view.activity.html',
controller: 'DocumentViewActivity'
}
}
})
.state('document.view.file', { .state('document.view.file', {
url: '/file/:fileId', url: '/file/:fileId',
views: { views: {
@ -231,4 +259,18 @@ angular.module('docs',
$rootScope.$state = $state; $rootScope.$state = $state;
$rootScope.$stateParams = $stateParams; $rootScope.$stateParams = $stateParams;
$rootScope.pageTitle = 'Sismics Docs'; $rootScope.pageTitle = 'Sismics Docs';
})
/**
* Redirection support for ui-router.
* Thanks to https://github.com/acollard
* See https://github.com/angular-ui/ui-router/issues/1584#issuecomment-76993045
*/
.run(function($rootScope, $state){
$rootScope.$on('$stateChangeStart', function(event, toState, toParams) {
var redirect = toState.redirectTo;
if (redirect) {
event.preventDefault();
$state.go(redirect, toParams);
}
});
}); });

View File

@ -3,7 +3,7 @@
/** /**
* Document view controller. * Document view controller.
*/ */
angular.module('docs').controller('DocumentView', function ($scope, $state, $stateParams, $location, $dialog, $modal, Restangular, $upload, $q) { angular.module('docs').controller('DocumentView', function ($scope, $state, $stateParams, $location, $dialog, $modal, Restangular, $timeout) {
// Load document data from server // Load document data from server
Restangular.one('document', $stateParams.id).get().then(function(data) { Restangular.one('document', $stateParams.id).get().then(function(data) {
$scope.document = data; $scope.document = data;
@ -11,60 +11,6 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta
$scope.error = response; $scope.error = response;
}); });
// Load audit log data from server
Restangular.one('auditlog').get({
document: $stateParams.id
}).then(function(data) {
$scope.logs = data.logs;
});
// Watch for ACLs change and group them for easy displaying
$scope.$watch('document.acls', function(acls) {
$scope.acls = _.groupBy(acls, function(acl) {
return acl.id;
});
});
// Initialize add ACL
$scope.acl = { perm: 'READ' };
/**
* Configuration for file sorting.
*/
$scope.fileSortableOptions = {
forceHelperSize: true,
forcePlaceholderSize: true,
tolerance: 'pointer',
handle: '.handle',
stop: function () {
// Send new positions to server
$scope.$apply(function () {
Restangular.one('file').post('reorder', {
id: $stateParams.id,
order: _.pluck($scope.files, 'id')
});
});
}
};
/**
* Load files from server.
*/
$scope.loadFiles = function () {
Restangular.one('file').getList('list', { id: $stateParams.id }).then(function (data) {
$scope.files = data.files;
// TODO Keep currently uploading files
});
};
$scope.loadFiles();
/**
* Navigate to the selected file.
*/
$scope.openFile = function (file) {
$state.go('document.view.file', { id: $stateParams.id, fileId: file.id })
};
/** /**
* Delete a document. * Delete a document.
*/ */
@ -86,26 +32,6 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta
}); });
}; };
/**
* Delete a file.
*/
$scope.deleteFile = function (file) {
var title = 'Delete file';
var msg = 'Do you really want to delete this file?';
var btns = [
{result: 'cancel', label: 'Cancel'},
{result: 'ok', label: 'OK', cssClass: 'btn-primary'}
];
$dialog.messageBox(title, msg, btns, function (result) {
if (result == 'ok') {
Restangular.one('file', file.id).remove().then(function () {
$scope.loadFiles();
});
}
});
};
/** /**
* Open the share dialog. * Open the share dialog.
*/ */
@ -157,125 +83,4 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta
} }
}); });
}; };
/**
* File has been drag & dropped.
*/
$scope.fileDropped = function(files) {
if (!$scope.document.writable) {
return;
}
if (files && files.length) {
// Adding files to the UI
var newfiles = [];
_.each(files, function(file) {
var newfile = {
progress: 0,
name: file.name,
create_date: new Date().getTime(),
mimetype: file.type,
status: 'Pending...'
};
$scope.files.push(newfile);
newfiles.push(newfile);
});
// Uploading files sequentially
var key = 0;
var then = function() {
if (files[key]) {
$scope.uploadFile(files[key], newfiles[key++]).then(then);
}
};
then();
}
};
/**
* Upload a file.
*/
$scope.uploadFile = function(file, newfile) {
// Upload the file
newfile.status = 'Uploading...';
return $upload.upload({
method: 'PUT',
url: '../api/file',
file: file,
fields: {
id: $stateParams.id
}
})
.progress(function (e) {
newfile.progress = parseInt(100.0 * e.loaded / e.total);
})
.success(function (data) {
newfile.id = data.id;
});
};
/**
* Delete an ACL.
*/
$scope.deleteAcl = function(acl) {
Restangular.one('acl/' + $stateParams.id + '/' + acl.perm + '/' + acl.id, null).remove().then(function () {
$scope.document.acls = _.reject($scope.document.acls, function(s) {
return angular.equals(acl, s);
});
});
};
/**
* Add an ACL.
*/
$scope.addAcl = function() {
// Compute ACLs to add
$scope.acl.source = $stateParams.id;
var acls = [];
if ($scope.acl.perm == 'READWRITE') {
acls = [{
source: $stateParams.id,
username: $scope.acl.username,
perm: 'READ'
}, {
source: $stateParams.id,
username: $scope.acl.username,
perm: 'WRITE'
}];
} else {
acls = [{
source: $stateParams.id,
username: $scope.acl.username,
perm: $scope.acl.perm
}];
}
// Add ACLs
_.each(acls, function(acl) {
Restangular.one('acl').put(acl).then(function(acl) {
if (_.isUndefined(acl.id)) {
return;
}
$scope.document.acls.push(acl);
$scope.document.acls = angular.copy($scope.document.acls);
});
});
// Reset form
$scope.acl = { perm: 'READ' };
};
/**
* Auto-complete on ACL target.
*/
$scope.getTargetAclTypeahead = function($viewValue) {
var deferred = $q.defer();
Restangular.one('acl/target/search')
.get({
search: $viewValue
}).then(function(data) {
deferred.resolve(_.pluck(data.users, 'username'), true);
});
return deferred.promise;
};
}); });

View File

@ -0,0 +1,13 @@
'use strict';
/**
* Document view activity controller.
*/
angular.module('docs').controller('DocumentViewActivity', function ($scope, $stateParams, Restangular) {
// Load audit log data from server
Restangular.one('auditlog').get({
document: $stateParams.id
}).then(function(data) {
$scope.logs = data.logs;
});
});

View File

@ -0,0 +1,119 @@
'use strict';
/**
* Document view content controller.
*/
angular.module('docs').controller('DocumentViewContent', function ($scope, $stateParams, Restangular, $dialog, $state, $upload) {
/**
* Configuration for file sorting.
*/
$scope.fileSortableOptions = {
forceHelperSize: true,
forcePlaceholderSize: true,
tolerance: 'pointer',
handle: '.handle',
stop: function () {
// Send new positions to server
$scope.$apply(function () {
Restangular.one('file').post('reorder', {
id: $stateParams.id,
order: _.pluck($scope.files, 'id')
});
});
}
};
/**
* Load files from server.
*/
$scope.loadFiles = function () {
Restangular.one('file').getList('list', { id: $stateParams.id }).then(function (data) {
$scope.files = data.files;
// TODO Keep currently uploading files
});
};
$scope.loadFiles();
/**
* Navigate to the selected file.
*/
$scope.openFile = function (file) {
$state.go('document.view.file', { id: $stateParams.id, fileId: file.id })
};
/**
* Delete a file.
*/
$scope.deleteFile = function (file) {
var title = 'Delete file';
var msg = 'Do you really want to delete this file?';
var btns = [
{result: 'cancel', label: 'Cancel'},
{result: 'ok', label: 'OK', cssClass: 'btn-primary'}
];
$dialog.messageBox(title, msg, btns, function (result) {
if (result == 'ok') {
Restangular.one('file', file.id).remove().then(function () {
$scope.loadFiles();
});
}
});
};
/**
* File has been drag & dropped.
*/
$scope.fileDropped = function(files) {
if (!$scope.document.writable) {
return;
}
if (files && files.length) {
// Adding files to the UI
var newfiles = [];
_.each(files, function(file) {
var newfile = {
progress: 0,
name: file.name,
create_date: new Date().getTime(),
mimetype: file.type,
status: 'Pending...'
};
$scope.files.push(newfile);
newfiles.push(newfile);
});
// Uploading files sequentially
var key = 0;
var then = function() {
if (files[key]) {
$scope.uploadFile(files[key], newfiles[key++]).then(then);
}
};
then();
}
};
/**
* Upload a file.
*/
$scope.uploadFile = function(file, newfile) {
// Upload the file
newfile.status = 'Uploading...';
return $upload.upload({
method: 'PUT',
url: '../api/file',
file: file,
fields: {
id: $stateParams.id
}
})
.progress(function (e) {
newfile.progress = parseInt(100.0 * e.loaded / e.total);
})
.success(function (data) {
newfile.id = data.id;
});
};
});

View File

@ -0,0 +1,81 @@
'use strict';
/**
* Document view permissions controller.
*/
angular.module('docs').controller('DocumentViewPermissions', function ($scope, $stateParams, Restangular, $q) {
// Watch for ACLs change and group them for easy displaying
$scope.$watch('document.acls', function(acls) {
$scope.acls = _.groupBy(acls, function(acl) {
return acl.id;
});
});
// Initialize add ACL
$scope.acl = { perm: 'READ' };
/**
* Delete an ACL.
*/
$scope.deleteAcl = function(acl) {
Restangular.one('acl/' + $stateParams.id + '/' + acl.perm + '/' + acl.id, null).remove().then(function () {
$scope.document.acls = _.reject($scope.document.acls, function(s) {
return angular.equals(acl, s);
});
});
};
/**
* Add an ACL.
*/
$scope.addAcl = function() {
// Compute ACLs to add
$scope.acl.source = $stateParams.id;
var acls = [];
if ($scope.acl.perm == 'READWRITE') {
acls = [{
source: $stateParams.id,
username: $scope.acl.username,
perm: 'READ'
}, {
source: $stateParams.id,
username: $scope.acl.username,
perm: 'WRITE'
}];
} else {
acls = [{
source: $stateParams.id,
username: $scope.acl.username,
perm: $scope.acl.perm
}];
}
// Add ACLs
_.each(acls, function(acl) {
Restangular.one('acl').put(acl).then(function(acl) {
if (_.isUndefined(acl.id)) {
return;
}
$scope.document.acls.push(acl);
$scope.document.acls = angular.copy($scope.document.acls);
});
});
// Reset form
$scope.acl = { perm: 'READ' };
};
/**
* Auto-complete on ACL target.
*/
$scope.getTargetAclTypeahead = function($viewValue) {
var deferred = $q.defer();
Restangular.one('acl/target/search')
.get({
search: $viewValue
}).then(function(data) {
deferred.resolve(_.pluck(data.users, 'username'), true);
});
return deferred.promise;
};
});

View File

@ -6,9 +6,13 @@
angular.module('docs').controller('Main', function($scope, $rootScope, $state, User) { angular.module('docs').controller('Main', function($scope, $rootScope, $state, User) {
User.userInfo().then(function(data) { User.userInfo().then(function(data) {
if (data.anonymous) { if (data.anonymous) {
$state.go('login'); $state.go('login', {}, {
location: 'replace'
});
} else { } else {
$state.go('document.default'); $state.go('document.default', {}, {
location: 'replace'
});
} }
}); });
}); });

View File

@ -43,6 +43,9 @@
<script src="app/docs/controller/DocumentDefault.js" type="text/javascript"></script> <script src="app/docs/controller/DocumentDefault.js" type="text/javascript"></script>
<script src="app/docs/controller/DocumentEdit.js" type="text/javascript"></script> <script src="app/docs/controller/DocumentEdit.js" type="text/javascript"></script>
<script src="app/docs/controller/DocumentView.js" type="text/javascript"></script> <script src="app/docs/controller/DocumentView.js" type="text/javascript"></script>
<script src="app/docs/controller/DocumentViewContent.js" type="text/javascript"></script>
<script src="app/docs/controller/DocumentViewPermissions.js" type="text/javascript"></script>
<script src="app/docs/controller/DocumentViewActivity.js" type="text/javascript"></script>
<script src="app/docs/controller/DocumentModalShare.js" type="text/javascript"></script> <script src="app/docs/controller/DocumentModalShare.js" type="text/javascript"></script>
<script src="app/docs/controller/FileView.js" type="text/javascript"></script> <script src="app/docs/controller/FileView.js" type="text/javascript"></script>
<script src="app/docs/controller/FileModalView.js" type="text/javascript"></script> <script src="app/docs/controller/FileModalView.js" type="text/javascript"></script>

View File

@ -0,0 +1 @@
<audit-log logs="logs" />

View File

@ -0,0 +1,37 @@
<p ng-bind-html="document.description | newline"></p>
<div ng-file-drop drag-over-class="bg-success" ng-multiple="true" allow-dir="false" ng-model="dropFiles"
accept="image/*,application/pdf,application/zip" ng-file-change="fileDropped($files, $event, $rejectedFiles)">
<div class="row upload-zone" ui-sortable="fileSortableOptions" ng-model="files">
<div class="col-xs-6 col-sm-4 col-md-3 col-lg-2 text-center" ng-repeat="file in files">
<div class="thumbnail" ng-if="file.id">
<a ng-click="openFile(file)">
<img class="thumbnail-file" ng-src="../api/file/{{ file.id }}/data?size=thumb" tooltip="{{ file.mimetype }}" tooltip-placement="top" />
</a>
<div class="caption" ng-show="document.writable">
<div class="pull-left">
<div class="btn btn-default handle"><span class="glyphicon glyphicon-resize-horizontal"></span></div>
</div>
<div class="pull-right">
<button class="btn btn-danger" ng-click="deleteFile(file)"><span class="glyphicon glyphicon-trash"></span></button>
</div>
<div class="clearfix"></div>
</div>
</div>
<div class="thumbnail" ng-if="!file.id">
<p class="text-center lead">
{{ file.status }}
</p>
<div class="caption">
<progressbar value="file.progress" class="progress-info active"></progressbar>
</div>
</div>
</div>
<p class="text-center well-lg" ng-if="files.length == 0">
<span class="glyphicon glyphicon-move"></span>
Drag &amp; drop files here to upload
</p>
</div>
</div>

View File

@ -38,119 +38,24 @@
</ul> </ul>
</div> </div>
<tabset> <ul class="nav nav-tabs">
<tab> <li ng-class="{ active: $state.current.name == 'document.view.content' }">
<tab-heading class="pointer"> <a href="#/document/view/{{ document.id }}/content">
<span class="glyphicon glyphicon-file"></span> Content <span class="glyphicon glyphicon-file"></span> Content
</tab-heading> </a>
</li>
<p ng-bind-html="document.description | newline"></p> <li ng-class="{ active: $state.current.name == 'document.view.permissions' }">
<a href="#/document/view/{{ document.id }}/permissions">
<div ng-file-drop drag-over-class="bg-success" ng-multiple="true" allow-dir="false" ng-model="dropFiles"
accept="image/*,application/pdf,application/zip" ng-file-change="fileDropped($files, $event, $rejectedFiles)">
<div class="row upload-zone" ui-sortable="fileSortableOptions" ng-model="files">
<div class="col-xs-6 col-sm-4 col-md-3 col-lg-2 text-center" ng-repeat="file in files">
<div class="thumbnail" ng-if="file.id">
<a ng-click="openFile(file)">
<img class="thumbnail-file" ng-src="../api/file/{{ file.id }}/data?size=thumb" tooltip="{{ file.mimetype }}" tooltip-placement="top" />
</a>
<div class="caption" ng-show="document.writable">
<div class="pull-left">
<div class="btn btn-default handle"><span class="glyphicon glyphicon-resize-horizontal"></span></div>
</div>
<div class="pull-right">
<button class="btn btn-danger" ng-click="deleteFile(file)"><span class="glyphicon glyphicon-trash"></span></button>
</div>
<div class="clearfix"></div>
</div>
</div>
<div class="thumbnail" ng-if="!file.id">
<p class="text-center lead">
{{ file.status }}
</p>
<div class="caption">
<progressbar value="file.progress" class="progress-info active"></progressbar>
</div>
</div>
</div>
<p class="text-center well-lg" ng-if="files.length == 0">
<span class="glyphicon glyphicon-move"></span>
Drag &amp; drop files here to upload
</p>
</div>
</div>
</tab>
<tab>
<tab-heading class="pointer">
<span class="glyphicon glyphicon-user"></span> Permissions <span class="glyphicon glyphicon-user"></span> Permissions
</tab-heading> </a>
</li>
<table class="table"> <li ng-class="{ active: $state.current.name == 'document.view.activity' }">
<tr> <a href="#/document/view/{{ document.id }}/activity">
<th style="width: 40%">For</th>
<th style="width: 40%">Permission</th>
</tr>
<tr ng-repeat="(id, acl) in acls">
<td><em>{{ acl[0].type == 'SHARE' ? 'Shared' : 'User' }}</em> {{ acl[0].name }}</td>
<td>
<span class="label label-default" style="margin-right: 6px;" ng-repeat="a in acl | orderBy: 'perm'">
{{ a.perm }}
<span ng-show="(document.creator != a.name && a.type == 'USER' || a.type != 'USER') && document.writable"
class="glyphicon glyphicon-remove pointer"
ng-click="deleteAcl(a)"></span>
</span>
</td>
</tr>
</table>
<div ng-show="document.writable">
<h4>Add a permission</h4>
<form name="aclForm" class="form-horizontal">
<div class="form-group">
<label class=" col-sm-2 control-label" for="inputTarget">User</label>
<div class="col-sm-3">
<input required ng-maxlength="50" class="form-control" type="text" id="inputTarget"
placeholder="Type a username" name="username" ng-model="acl.username" autocomplete="off"
typeahead="username for username in getTargetAclTypeahead($viewValue) | filter: $viewValue"
typeahead-wait-ms="200" />
</div>
</div>
<div class="form-group">
<label class=" col-sm-2 control-label" for="inputPermission">Permission</label>
<div class="col-sm-3">
<select class="form-control" ng-model="acl.perm" id="inputPermission">
<option value="READ">Can read</option>
<option value="READWRITE">Can edit</option>
</select>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary" ng-disabled="!aclForm.$valid" ng-click="addAcl()">
<span class="glyphicon glyphicon-plus"></span>
Add
</button>
</div>
</div>
</form>
</div>
</tab>
<tab>
<tab-heading class="pointer">
<span class="glyphicon glyphicon-tasks"></span> Activity <span class="glyphicon glyphicon-tasks"></span> Activity
</tab-heading> </a>
</li>
<audit-log logs="logs" /> </ul>
</tab>
</tabset>
<div ui-view="tab"></div>
<div ui-view="file"></div> <div ui-view="file"></div>
</div> </div>

View File

@ -0,0 +1,53 @@
<table class="table">
<tr>
<th style="width: 40%">For</th>
<th style="width: 40%">Permission</th>
</tr>
<tr ng-repeat="(id, acl) in acls">
<td><em>{{ acl[0].type == 'SHARE' ? 'Shared' : 'User' }}</em> {{ acl[0].name }}</td>
<td>
<span class="label label-default" style="margin-right: 6px;" ng-repeat="a in acl | orderBy: 'perm'">
{{ a.perm }}
<span ng-show="(document.creator != a.name && a.type == 'USER' || a.type != 'USER') && document.writable"
class="glyphicon glyphicon-remove pointer"
ng-click="deleteAcl(a)"></span>
</span>
</td>
</tr>
</table>
<div ng-show="document.writable">
<h4>Add a permission</h4>
<form name="aclForm" class="form-horizontal">
<div class="form-group">
<label class=" col-sm-2 control-label" for="inputTarget">User</label>
<div class="col-sm-3">
<input required ng-maxlength="50" class="form-control" type="text" id="inputTarget"
placeholder="Type a username" name="username" ng-model="acl.username" autocomplete="off"
typeahead="username for username in getTargetAclTypeahead($viewValue) | filter: $viewValue"
typeahead-wait-ms="200" />
</div>
</div>
<div class="form-group">
<label class=" col-sm-2 control-label" for="inputPermission">Permission</label>
<div class="col-sm-3">
<select class="form-control" ng-model="acl.perm" id="inputPermission">
<option value="READ">Can read</option>
<option value="READWRITE">Can edit</option>
</select>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary" ng-disabled="!aclForm.$valid" ng-click="addAcl()">
<span class="glyphicon glyphicon-plus"></span>
Add
</button>
</div>
</div>
</form>
</div>