File list sorting by clicking on column headerstags/v7.0.0alpha2
@@ -17,8 +17,11 @@ $baseUrl = OCP\Util::linkTo('files', 'index.php') . '?dir='; | |||
$permissions = $dirInfo->getPermissions(); | |||
$sortAttribute = isset( $_GET['sort'] ) ? $_GET['sort'] : 'name'; | |||
$sortDirection = isset( $_GET['sortdirection'] ) ? ($_GET['sortdirection'] === 'desc') : false; | |||
// make filelist | |||
$files = \OCA\Files\Helper::getFiles($dir); | |||
$files = \OCA\Files\Helper::getFiles($dir, $sortAttribute, $sortDirection); | |||
$data['directory'] = $dir; | |||
$data['files'] = \OCA\Files\Helper::formatFileInfos($files); |
@@ -116,10 +116,29 @@ tr:hover span.extension { | |||
table tr.mouseOver td { background-color:#eee; } | |||
table th { height:24px; padding:0 8px; color:#999; } | |||
table th .name { | |||
position: absolute; | |||
left: 55px; | |||
top: 15px; | |||
table th .columntitle { | |||
display: inline-block; | |||
padding: 15px; | |||
width: 100%; | |||
height: 50px; | |||
box-sizing: border-box; | |||
-moz-box-sizing: border-box; | |||
vertical-align: middle; | |||
} | |||
table th .columntitle.name { | |||
padding-left: 5px; | |||
margin-left: 50px; | |||
max-width: 300px; | |||
} | |||
/* hover effect on sortable column */ | |||
table th a.columntitle:hover { | |||
background-color: #F0F0F0; | |||
} | |||
table th .sort-indicator { | |||
width: 10px; | |||
height: 8px; | |||
margin-left: 10px; | |||
display: inline-block; | |||
} | |||
table th, table td { border-bottom:1px solid #ddd; text-align:left; font-weight:normal; } | |||
table td { | |||
@@ -139,8 +158,11 @@ table th#headerName { | |||
} | |||
table th#headerSize, table td.filesize { | |||
min-width: 48px; | |||
padding: 0 16px; | |||
text-align: right; | |||
padding: 0; | |||
} | |||
table table td.filesize { | |||
padding: 0 16px; | |||
} | |||
table th#headerDate, table td.date { | |||
-moz-box-sizing: border-box; | |||
@@ -197,10 +219,6 @@ table td.filename input.filename { | |||
table td.filename a, table td.login, table td.logout, table td.download, table td.upload, table td.create, table td.delete { padding:3px 8px 8px 3px; } | |||
table td.filename .nametext, .uploadtext, .modified { float:left; padding:14px 0; } | |||
#modified { | |||
position: absolute; | |||
top: 15px; | |||
} | |||
.modified { | |||
position: relative; | |||
padding-left: 8px; |
@@ -11,6 +11,9 @@ | |||
/* global OC, t, n, FileList, FileActions, Files, FileSummary, BreadCrumb */ | |||
/* global dragOptions, folderDropOptions */ | |||
window.FileList = { | |||
SORT_INDICATOR_ASC_CLASS: 'icon-triangle-s', | |||
SORT_INDICATOR_DESC_CLASS: 'icon-triangle-n', | |||
appName: t('files', 'Files'), | |||
isEmpty: true, | |||
useUndo:true, | |||
@@ -45,18 +48,19 @@ window.FileList = { | |||
_selectionSummary: null, | |||
/** | |||
* Compare two file info objects, sorting by | |||
* folders first, then by name. | |||
* Sort attribute | |||
*/ | |||
_fileInfoCompare: function(fileInfo1, fileInfo2) { | |||
if (fileInfo1.type === 'dir' && fileInfo2.type !== 'dir') { | |||
return -1; | |||
} | |||
if (fileInfo1.type !== 'dir' && fileInfo2.type === 'dir') { | |||
return 1; | |||
} | |||
return fileInfo1.name.localeCompare(fileInfo2.name); | |||
}, | |||
_sort: 'name', | |||
/** | |||
* Sort direction: 'asc' or 'desc' | |||
*/ | |||
_sortDirection: 'asc', | |||
/** | |||
* Sort comparator function for the current sort | |||
*/ | |||
_sortComparator: null, | |||
/** | |||
* Initialize the file list and its components | |||
@@ -76,6 +80,8 @@ window.FileList = { | |||
this.fileSummary = this._createSummary(); | |||
this.setSort('name', 'asc'); | |||
this.breadcrumb = new BreadCrumb({ | |||
onClick: this._onClickBreadCrumb, | |||
onDrop: _.bind(this._onDropOnBreadCrumb, this), | |||
@@ -86,6 +92,8 @@ window.FileList = { | |||
$('#controls').prepend(this.breadcrumb.$el); | |||
this.$el.find('thead th .columntitle').click(_.bind(this._onClickHeader, this)); | |||
$(window).resize(function() { | |||
// TODO: debounce this ? | |||
var width = $(this).width(); | |||
@@ -236,6 +244,27 @@ window.FileList = { | |||
return false; | |||
}, | |||
/** | |||
* Event handler when clicking on a table header | |||
*/ | |||
_onClickHeader: function(e) { | |||
var $target = $(e.target); | |||
var sort; | |||
if (!$target.is('a')) { | |||
$target = $target.closest('a'); | |||
} | |||
sort = $target.attr('data-sort'); | |||
if (sort) { | |||
if (this._sort === sort) { | |||
this.setSort(sort, (this._sortDirection === 'desc')?'asc':'desc'); | |||
} | |||
else { | |||
this.setSort(sort, 'asc'); | |||
} | |||
this.reload(); | |||
} | |||
}, | |||
/** | |||
* Event handler when clicking on a bread crumb | |||
*/ | |||
@@ -685,8 +714,6 @@ window.FileList = { | |||
previousDir: currentDir | |||
} | |||
)); | |||
this._selectedFiles = {}; | |||
this._selectionSummary.clear(); | |||
this.reload(); | |||
}, | |||
linkTo: function(dir) { | |||
@@ -722,10 +749,34 @@ window.FileList = { | |||
} | |||
this.breadcrumb.setDirectory(this.getCurrentDirectory()); | |||
}, | |||
/** | |||
* Sets the current sorting and refreshes the list | |||
* | |||
* @param sort sort attribute name | |||
* @param direction sort direction, one of "asc" or "desc" | |||
*/ | |||
setSort: function(sort, direction) { | |||
var comparator = this.Comparators[sort] || this.Comparators.name; | |||
this._sort = sort; | |||
this._sortDirection = (direction === 'desc')?'desc':'asc'; | |||
this._sortComparator = comparator; | |||
if (direction === 'desc') { | |||
this._sortComparator = function(fileInfo1, fileInfo2) { | |||
return -comparator(fileInfo1, fileInfo2); | |||
}; | |||
} | |||
this.$el.find('thead th .sort-indicator') | |||
.removeClass(this.SORT_INDICATOR_ASC_CLASS + ' ' + this.SORT_INDICATOR_DESC_CLASS); | |||
this.$el.find('thead th.column-' + sort + ' .sort-indicator') | |||
.addClass(direction === 'desc' ? this.SORT_INDICATOR_DESC_CLASS : this.SORT_INDICATOR_ASC_CLASS); | |||
}, | |||
/** | |||
* @brief Reloads the file list using ajax call | |||
*/ | |||
reload: function() { | |||
this._selectedFiles = {}; | |||
this._selectionSummary.clear(); | |||
this.$el.find('#select_all').prop('checked', false); | |||
FileList.showMask(); | |||
if (FileList._reloadCall) { | |||
FileList._reloadCall.abort(); | |||
@@ -733,7 +784,9 @@ window.FileList = { | |||
FileList._reloadCall = $.ajax({ | |||
url: Files.getAjaxUrl('list'), | |||
data: { | |||
dir : $('#dir').val() | |||
dir: $('#dir').val(), | |||
sort: FileList._sort, | |||
sortdirection: FileList._sortDirection | |||
}, | |||
error: function(result) { | |||
FileList.reloadCallback(result); | |||
@@ -859,7 +912,7 @@ window.FileList = { | |||
*/ | |||
_findInsertionIndex: function(fileData) { | |||
var index = 0; | |||
while (index < this.files.length && this._fileInfoCompare(fileData, this.files[index]) > 0) { | |||
while (index < this.files.length && this._sortComparator(fileData, this.files[index]) > 0) { | |||
index++; | |||
} | |||
return index; | |||
@@ -924,7 +977,7 @@ window.FileList = { | |||
OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error')); | |||
} | |||
$td.css('background-image', oldBackgroundImage); | |||
}); | |||
}); | |||
}); | |||
}, | |||
@@ -1221,15 +1274,15 @@ window.FileList = { | |||
updateSelectionSummary: function() { | |||
var summary = this._selectionSummary.summary; | |||
if (summary.totalFiles === 0 && summary.totalDirs === 0) { | |||
$('#headerName span.name').text(t('files','Name')); | |||
$('#headerSize').text(t('files','Size')); | |||
$('#modified').text(t('files','Modified')); | |||
$('#headerName a.name>span:first').text(t('files','Name')); | |||
$('#headerSize a>span:first').text(t('files','Size')); | |||
$('#modified a>span:first').text(t('files','Modified')); | |||
$('table').removeClass('multiselect'); | |||
$('.selectedActions').addClass('hidden'); | |||
} | |||
else { | |||
$('.selectedActions').removeClass('hidden'); | |||
$('#headerSize').text(OC.Util.humanFileSize(summary.totalSize)); | |||
$('#headerSize a>span:first').text(OC.Util.humanFileSize(summary.totalSize)); | |||
var selection = ''; | |||
if (summary.totalDirs > 0) { | |||
selection += n('files', '%n folder', '%n folders', summary.totalDirs); | |||
@@ -1240,8 +1293,8 @@ window.FileList = { | |||
if (summary.totalFiles > 0) { | |||
selection += n('files', '%n file', '%n files', summary.totalFiles); | |||
} | |||
$('#headerName span.name').text(selection); | |||
$('#modified').text(''); | |||
$('#headerName a.name>span:first').text(selection); | |||
$('#modified a>span:first').text(''); | |||
$('table').addClass('multiselect'); | |||
} | |||
}, | |||
@@ -1545,3 +1598,49 @@ $(document).ready(function() { | |||
}, 0); | |||
}); | |||
/** | |||
* Sort comparators. | |||
*/ | |||
FileList.Comparators = { | |||
/** | |||
* Compares two file infos by name, making directories appear | |||
* first. | |||
* | |||
* @param fileInfo1 file info | |||
* @param fileInfo2 file info | |||
* @return -1 if the first file must appear before the second one, | |||
* 0 if they are identify, 1 otherwise. | |||
*/ | |||
name: function(fileInfo1, fileInfo2) { | |||
if (fileInfo1.type === 'dir' && fileInfo2.type !== 'dir') { | |||
return -1; | |||
} | |||
if (fileInfo1.type !== 'dir' && fileInfo2.type === 'dir') { | |||
return 1; | |||
} | |||
return fileInfo1.name.localeCompare(fileInfo2.name); | |||
}, | |||
/** | |||
* Compares two file infos by size. | |||
* | |||
* @param fileInfo1 file info | |||
* @param fileInfo2 file info | |||
* @return -1 if the first file must appear before the second one, | |||
* 0 if they are identify, 1 otherwise. | |||
*/ | |||
size: function(fileInfo1, fileInfo2) { | |||
return fileInfo1.size - fileInfo2.size; | |||
}, | |||
/** | |||
* Compares two file infos by timestamp. | |||
* | |||
* @param fileInfo1 file info | |||
* @param fileInfo2 file info | |||
* @return -1 if the first file must appear before the second one, | |||
* 0 if they are identify, 1 otherwise. | |||
*/ | |||
mtime: function(fileInfo1, fileInfo2) { | |||
return fileInfo1.mtime - fileInfo2.mtime; | |||
} | |||
}; | |||
@@ -1,7 +1,16 @@ | |||
<?php | |||
/** | |||
* Copyright (c) 2014 Vincent Petry <pvince81@owncloud.com> | |||
* This file is licensed under the Affero General Public License version 3 or | |||
* later. | |||
* See the COPYING-README file. | |||
*/ | |||
namespace OCA\Files; | |||
/** | |||
* Helper class for manipulating file information | |||
*/ | |||
class Helper | |||
{ | |||
public static function buildFileStorageStatistics($dir) { | |||
@@ -9,12 +18,12 @@ class Helper | |||
$storageInfo = \OC_Helper::getStorageInfo($dir); | |||
$l = new \OC_L10N('files'); | |||
$maxUploadFilesize = \OCP\Util::maxUploadFilesize($dir, $storageInfo['free']); | |||
$maxHumanFilesize = \OCP\Util::humanFileSize($maxUploadFilesize); | |||
$maxHumanFilesize = $l->t('Upload (max. %s)', array($maxHumanFilesize)); | |||
$maxUploadFileSize = \OCP\Util::maxUploadFilesize($dir, $storageInfo['free']); | |||
$maxHumanFileSize = \OCP\Util::humanFileSize($maxUploadFileSize); | |||
$maxHumanFileSize = $l->t('Upload (max. %s)', array($maxHumanFileSize)); | |||
return array('uploadMaxFilesize' => $maxUploadFilesize, | |||
'maxHumanFilesize' => $maxHumanFilesize, | |||
return array('uploadMaxFilesize' => $maxUploadFileSize, | |||
'maxHumanFilesize' => $maxHumanFileSize, | |||
'freeSpace' => $storageInfo['free'], | |||
'usedSpacePercent' => (int)$storageInfo['relative']); | |||
} | |||
@@ -27,7 +36,6 @@ class Helper | |||
*/ | |||
public static function determineIcon($file) { | |||
if($file['type'] === 'dir') { | |||
$dir = $file['directory']; | |||
$icon = \OC_Helper::mimetypeIcon('dir'); | |||
$absPath = $file->getPath(); | |||
$mount = \OC\Files\Filesystem::getMountManager()->find($absPath); | |||
@@ -57,7 +65,7 @@ class Helper | |||
* @param \OCP\Files\FileInfo $b file | |||
* @return int -1 if $a must come before $b, 1 otherwise | |||
*/ | |||
public static function fileCmp($a, $b) { | |||
public static function compareFileNames($a, $b) { | |||
$aType = $a->getType(); | |||
$bType = $b->getType(); | |||
if ($aType === 'dir' and $bType !== 'dir') { | |||
@@ -69,6 +77,32 @@ class Helper | |||
} | |||
} | |||
/** | |||
* Comparator function to sort files by date | |||
* | |||
* @param \OCP\Files\FileInfo $a file | |||
* @param \OCP\Files\FileInfo $b file | |||
* @return int -1 if $a must come before $b, 1 otherwise | |||
*/ | |||
public static function compareTimestamp($a, $b) { | |||
$aTime = $a->getMTime(); | |||
$bTime = $b->getMTime(); | |||
return $aTime - $bTime; | |||
} | |||
/** | |||
* Comparator function to sort files by size | |||
* | |||
* @param \OCP\Files\FileInfo $a file | |||
* @param \OCP\Files\FileInfo $b file | |||
* @return int -1 if $a must come before $b, 1 otherwise | |||
*/ | |||
public static function compareSize($a, $b) { | |||
$aSize = $a->getSize(); | |||
$bSize = $b->getSize(); | |||
return $aSize - $bSize; | |||
} | |||
/** | |||
* Formats the file info to be returned as JSON to the client. | |||
* | |||
@@ -120,12 +154,35 @@ class Helper | |||
* returns it as a sorted array of FileInfo. | |||
* | |||
* @param string $dir path to the directory | |||
* @param string $sortAttribute attribute to sort on | |||
* @param bool $sortDescending true for descending sort, false otherwise | |||
* @return \OCP\Files\FileInfo[] files | |||
*/ | |||
public static function getFiles($dir) { | |||
public static function getFiles($dir, $sortAttribute = 'name', $sortDescending = false) { | |||
$content = \OC\Files\Filesystem::getDirectoryContent($dir); | |||
usort($content, array('\OCA\Files\Helper', 'fileCmp')); | |||
return $content; | |||
return self::sortFiles($content, $sortAttribute, $sortDescending); | |||
} | |||
/** | |||
* Sort the given file info array | |||
* | |||
* @param \OCP\Files\FileInfo[] files to sort | |||
* @param string $sortAttribute attribute to sort on | |||
* @param bool $sortDescending true for descending sort, false otherwise | |||
* @return \OCP\Files\FileInfo[] sorted files | |||
*/ | |||
public static function sortFiles($files, $sortAttribute = 'name', $sortDescending = false) { | |||
$sortFunc = 'compareFileNames'; | |||
if ($sortAttribute === 'mtime') { | |||
$sortFunc = 'compareTimestamp'; | |||
} else if ($sortAttribute === 'size') { | |||
$sortFunc = 'compareSize'; | |||
} | |||
usort($files, array('\OCA\Files\Helper', $sortFunc)); | |||
if ($sortDescending) { | |||
$files = array_reverse($files); | |||
} | |||
return $files; | |||
} | |||
} |
@@ -1,3 +1,4 @@ | |||
<?php /** @var $l OC_L10N */ ?> | |||
<div id="controls"> | |||
<div class="actions creatable hidden"> | |||
<?php if(!isset($_['dirToken'])):?> | |||
@@ -60,11 +61,11 @@ | |||
<table id="filestable" data-allow-public-upload="<?php p($_['publicUploadEnabled'])?>" data-preview-x="36" data-preview-y="36"> | |||
<thead> | |||
<tr> | |||
<th class="hidden" id='headerName'> | |||
<th id='headerName' class="hidden column-name"> | |||
<div id="headerName-container"> | |||
<input type="checkbox" id="select_all" /> | |||
<label for="select_all"></label> | |||
<span class="name"><?php p($l->t( 'Name' )); ?></span> | |||
<a class="name sort columntitle" data-sort="name"><span><?php p($l->t( 'Name' )); ?></span><span class="sort-indicator"></span></a> | |||
<span id="selectedActionsList" class="selectedActions"> | |||
<?php if($_['allowZipDownload']) : ?> | |||
<a href="" class="download"> | |||
@@ -76,9 +77,11 @@ | |||
</span> | |||
</div> | |||
</th> | |||
<th class="hidden" id="headerSize"><?php p($l->t('Size')); ?></th> | |||
<th class="hidden" id="headerDate"> | |||
<span id="modified"><?php p($l->t( 'Modified' )); ?></span> | |||
<th id="headerSize" class="hidden column-size"> | |||
<a class="size sort columntitle" data-sort="size"><span><?php p($l->t('Size')); ?></span><span class="sort-indicator"></span></a> | |||
</th> | |||
<th id="headerDate" class="hidden column-mtime"> | |||
<a id="modified" class="columntitle" data-sort="mtime"><span><?php p($l->t( 'Modified' )); ?></span><span class="sort-indicator"></span></a> | |||
<?php if ($_['permissions'] & OCP\PERMISSION_DELETE): ?> | |||
<span class="selectedActions"><a href="" class="delete-selected"> | |||
<?php p($l->t('Delete'))?> |
@@ -0,0 +1,98 @@ | |||
<?php | |||
/** | |||
* Copyright (c) 2014 Vincent Petry <pvince81@owncloud.com> | |||
* This file is licensed under the Affero General Public License version 3 or | |||
* later. | |||
* See the COPYING-README file. | |||
*/ | |||
require_once __DIR__ . '/../lib/helper.php'; | |||
use OCA\Files; | |||
/** | |||
* Class Test_Files_Helper | |||
*/ | |||
class Test_Files_Helper extends \PHPUnit_Framework_TestCase { | |||
private function makeFileInfo($name, $size, $mtime, $isDir = false) { | |||
return new \OC\Files\FileInfo( | |||
'/', | |||
null, | |||
'/', | |||
array( | |||
'name' => $name, | |||
'size' => $size, | |||
'mtime' => $mtime, | |||
'type' => $isDir ? 'dir' : 'file', | |||
'mimetype' => $isDir ? 'httpd/unix-directory' : 'application/octet-stream' | |||
) | |||
); | |||
} | |||
/** | |||
* Returns a file list for testing | |||
*/ | |||
private function getTestFileList() { | |||
return array( | |||
self::makeFileInfo('a.txt', 4, 1000), | |||
self::makeFileInfo('q.txt', 5, 150), | |||
self::makeFileInfo('subdir2', 87, 128, true), | |||
self::makeFileInfo('b.txt', 166, 800), | |||
self::makeFileInfo('o.txt', 12, 100), | |||
self::makeFileInfo('subdir', 88, 125, true), | |||
); | |||
} | |||
function sortDataProvider() { | |||
return array( | |||
array( | |||
'name', | |||
false, | |||
array('subdir', 'subdir2', 'a.txt', 'b.txt', 'o.txt', 'q.txt'), | |||
), | |||
array( | |||
'name', | |||
true, | |||
array('q.txt', 'o.txt', 'b.txt', 'a.txt', 'subdir2', 'subdir'), | |||
), | |||
array( | |||
'size', | |||
false, | |||
array('a.txt', 'q.txt', 'o.txt', 'subdir2', 'subdir', 'b.txt'), | |||
), | |||
array( | |||
'size', | |||
true, | |||
array('b.txt', 'subdir', 'subdir2', 'o.txt', 'q.txt', 'a.txt'), | |||
), | |||
array( | |||
'mtime', | |||
false, | |||
array('o.txt', 'subdir', 'subdir2', 'q.txt', 'b.txt', 'a.txt'), | |||
), | |||
array( | |||
'mtime', | |||
true, | |||
array('a.txt', 'b.txt', 'q.txt', 'subdir2', 'subdir', 'o.txt'), | |||
), | |||
); | |||
} | |||
/** | |||
* @dataProvider sortDataProvider | |||
*/ | |||
public function testSortByName($sort, $sortDescending, $expectedOrder) { | |||
$files = self::getTestFileList(); | |||
$files = \OCA\Files\Helper::sortFiles($files, $sort, $sortDescending); | |||
$fileNames = array(); | |||
foreach ($files as $fileInfo) { | |||
$fileNames[] = $fileInfo->getName(); | |||
} | |||
$this->assertEquals( | |||
$expectedOrder, | |||
$fileNames | |||
); | |||
} | |||
} |
@@ -77,13 +77,17 @@ describe('FileList tests', function() { | |||
// dummy table | |||
// TODO: at some point this will be rendered by the FileList class itself! | |||
'<table id="filestable">' + | |||
'<thead><tr><th id="headerName" class="hidden">' + | |||
'<thead><tr>' + | |||
'<th id="headerName" class="hidden column-name">' + | |||
'<input type="checkbox" id="select_all">' + | |||
'<span class="name">Name</span>' + | |||
'<a class="name columntitle" data-sort="name"><span>Name</span><span class="sort-indicator"></span></a>' + | |||
'<span class="selectedActions hidden">' + | |||
'<a href class="download">Download</a>' + | |||
'<a href class="delete-selected">Delete</a></span>' + | |||
'</th></tr></thead>' + | |||
'</th>' + | |||
'<th class="hidden column-size"><a class="columntitle" data-sort="size"><span class="sort-indicator"></span></a></th>' + | |||
'<th class="hidden column-mtime"><a class="columntitle" data-sort="mtime"><span class="sort-indicator"></span></a></th>' + | |||
'</tr></thead>' + | |||
'<tbody id="fileList"></tbody>' + | |||
'<tfoot></tfoot>' + | |||
'</table>' + | |||
@@ -940,7 +944,7 @@ describe('FileList tests', function() { | |||
expect(fakeServer.requests.length).toEqual(1); | |||
var url = fakeServer.requests[0].url; | |||
var query = url.substr(url.indexOf('?') + 1); | |||
expect(OC.parseQueryString(query)).toEqual({'dir': '/subdir'}); | |||
expect(OC.parseQueryString(query)).toEqual({'dir': '/subdir', sort: 'name', sortdirection: 'asc'}); | |||
fakeServer.respond(); | |||
expect($('#fileList tr').length).toEqual(4); | |||
expect(FileList.findFileEl('One.txt').length).toEqual(1); | |||
@@ -951,7 +955,7 @@ describe('FileList tests', function() { | |||
expect(fakeServer.requests.length).toEqual(1); | |||
var url = fakeServer.requests[0].url; | |||
var query = url.substr(url.indexOf('?') + 1); | |||
expect(OC.parseQueryString(query)).toEqual({'dir': '/anothersubdir'}); | |||
expect(OC.parseQueryString(query)).toEqual({'dir': '/anothersubdir', sort: 'name', sortdirection: 'asc'}); | |||
fakeServer.respond(); | |||
}); | |||
it('switches to root dir when current directory does not exist', function() { | |||
@@ -1260,7 +1264,7 @@ describe('FileList tests', function() { | |||
expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(42); | |||
}); | |||
it('Selecting files updates selection summary', function() { | |||
var $summary = $('#headerName span.name'); | |||
var $summary = $('#headerName a.name>span:first'); | |||
expect($summary.text()).toEqual('Name'); | |||
FileList.findFileEl('One.txt').find('input:checkbox').click(); | |||
FileList.findFileEl('Three.pdf').find('input:checkbox').click(); | |||
@@ -1268,7 +1272,7 @@ describe('FileList tests', function() { | |||
expect($summary.text()).toEqual('1 folder & 2 files'); | |||
}); | |||
it('Unselecting files hides selection summary', function() { | |||
var $summary = $('#headerName span.name'); | |||
var $summary = $('#headerName a.name>span:first'); | |||
FileList.findFileEl('One.txt').find('input:checkbox').click().click(); | |||
expect($summary.text()).toEqual('Name'); | |||
}); | |||
@@ -1431,5 +1435,150 @@ describe('FileList tests', function() { | |||
}); | |||
}); | |||
}); | |||
it('resets the file selection on reload', function() { | |||
FileList.$el.find('#select_all').click(); | |||
FileList.reload(); | |||
expect(FileList.$el.find('#select_all').prop('checked')).toEqual(false); | |||
expect(FileList.getSelectedFiles()).toEqual([]); | |||
}); | |||
}); | |||
describe('Sorting files', function() { | |||
it('Sorts by name by default', function() { | |||
FileList.reload(); | |||
expect(fakeServer.requests.length).toEqual(1); | |||
var url = fakeServer.requests[0].url; | |||
var query = OC.parseQueryString(url.substr(url.indexOf('?') + 1)); | |||
expect(query.sort).toEqual('name'); | |||
expect(query.sortdirection).toEqual('asc'); | |||
}); | |||
it('Reloads file list with a different sort when clicking on column header of unsorted column', function() { | |||
FileList.$el.find('.column-size .columntitle').click(); | |||
expect(fakeServer.requests.length).toEqual(1); | |||
var url = fakeServer.requests[0].url; | |||
var query = OC.parseQueryString(url.substr(url.indexOf('?') + 1)); | |||
expect(query.sort).toEqual('size'); | |||
expect(query.sortdirection).toEqual('asc'); | |||
}); | |||
it('Toggles sort direction when clicking on already sorted column', function() { | |||
FileList.$el.find('.column-name .columntitle').click(); | |||
expect(fakeServer.requests.length).toEqual(1); | |||
var url = fakeServer.requests[0].url; | |||
var query = OC.parseQueryString(url.substr(url.indexOf('?') + 1)); | |||
expect(query.sort).toEqual('name'); | |||
expect(query.sortdirection).toEqual('desc'); | |||
}); | |||
it('Toggles the sort indicator when clicking on a column header', function() { | |||
var ASC_CLASS = FileList.SORT_INDICATOR_ASC_CLASS; | |||
var DESC_CLASS = FileList.SORT_INDICATOR_DESC_CLASS; | |||
FileList.$el.find('.column-size .columntitle').click(); | |||
// moves triangle to size column | |||
expect( | |||
FileList.$el.find('.column-name .sort-indicator').hasClass(ASC_CLASS + ' ' + DESC_CLASS) | |||
).toEqual(false); | |||
expect( | |||
FileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS) | |||
).toEqual(true); | |||
// click again on size column, reverses direction | |||
FileList.$el.find('.column-size .columntitle').click(); | |||
expect( | |||
FileList.$el.find('.column-size .sort-indicator').hasClass(DESC_CLASS) | |||
).toEqual(true); | |||
// click again on size column, reverses direction | |||
FileList.$el.find('.column-size .columntitle').click(); | |||
expect( | |||
FileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS) | |||
).toEqual(true); | |||
// click on mtime column, moves indicator there | |||
FileList.$el.find('.column-mtime .columntitle').click(); | |||
expect( | |||
FileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS + ' ' + DESC_CLASS) | |||
).toEqual(false); | |||
expect( | |||
FileList.$el.find('.column-mtime .sort-indicator').hasClass(ASC_CLASS) | |||
).toEqual(true); | |||
}); | |||
it('Uses correct sort comparator when inserting files', function() { | |||
testFiles.sort(FileList.Comparators.size); | |||
// this will make it reload the testFiles with the correct sorting | |||
FileList.$el.find('.column-size .columntitle').click(); | |||
expect(fakeServer.requests.length).toEqual(1); | |||
fakeServer.requests[0].respond( | |||
200, | |||
{ 'Content-Type': 'application/json' }, | |||
JSON.stringify({ | |||
status: 'success', | |||
data: { | |||
files: testFiles, | |||
permissions: 31 | |||
} | |||
}) | |||
); | |||
var newFileData = { | |||
id: 999, | |||
type: 'file', | |||
name: 'new file.txt', | |||
mimetype: 'text/plain', | |||
size: 40001, | |||
etag: '999' | |||
}; | |||
FileList.add(newFileData); | |||
expect(FileList.files.length).toEqual(5); | |||
expect(FileList.$fileList.find('tr').length).toEqual(5); | |||
expect(FileList.findFileEl('One.txt').index()).toEqual(0); | |||
expect(FileList.findFileEl('somedir').index()).toEqual(1); | |||
expect(FileList.findFileEl('Two.jpg').index()).toEqual(2); | |||
expect(FileList.findFileEl('new file.txt').index()).toEqual(3); | |||
expect(FileList.findFileEl('Three.pdf').index()).toEqual(4); | |||
}); | |||
it('Uses correct reversed sort comparator when inserting files', function() { | |||
testFiles.sort(FileList.Comparators.size); | |||
testFiles.reverse(); | |||
// this will make it reload the testFiles with the correct sorting | |||
FileList.$el.find('.column-size .columntitle').click(); | |||
expect(fakeServer.requests.length).toEqual(1); | |||
fakeServer.requests[0].respond( | |||
200, | |||
{ 'Content-Type': 'application/json' }, | |||
JSON.stringify({ | |||
status: 'success', | |||
data: { | |||
files: testFiles, | |||
permissions: 31 | |||
} | |||
}) | |||
); | |||
// reverse sort | |||
FileList.$el.find('.column-size .columntitle').click(); | |||
fakeServer.requests[1].respond( | |||
200, | |||
{ 'Content-Type': 'application/json' }, | |||
JSON.stringify({ | |||
status: 'success', | |||
data: { | |||
files: testFiles, | |||
permissions: 31 | |||
} | |||
}) | |||
); | |||
var newFileData = { | |||
id: 999, | |||
type: 'file', | |||
name: 'new file.txt', | |||
mimetype: 'text/plain', | |||
size: 40001, | |||
etag: '999' | |||
}; | |||
FileList.add(newFileData); | |||
expect(FileList.files.length).toEqual(5); | |||
expect(FileList.$fileList.find('tr').length).toEqual(5); | |||
expect(FileList.findFileEl('One.txt').index()).toEqual(4); | |||
expect(FileList.findFileEl('somedir').index()).toEqual(3); | |||
expect(FileList.findFileEl('Two.jpg').index()).toEqual(2); | |||
expect(FileList.findFileEl('new file.txt').index()).toEqual(1); | |||
expect(FileList.findFileEl('Three.pdf').index()).toEqual(0); | |||
}); | |||
}); | |||
}); |
@@ -20,11 +20,6 @@ | |||
* | |||
*/ | |||
// only need filesystem apps | |||
$RUNTIME_APPTYPES=array('filesystem'); | |||
// Init owncloud | |||
if(!\OC_App::isEnabled('files_sharing')){ | |||
exit; | |||
} | |||
@@ -47,6 +42,9 @@ if (isset($_GET['dir'])) { | |||
$relativePath = $_GET['dir']; | |||
} | |||
$sortAttribute = isset( $_GET['sort'] ) ? $_GET['sort'] : 'name'; | |||
$sortDirection = isset( $_GET['sortdirection'] ) ? ($_GET['sortdirection'] === 'desc') : false; | |||
$data = \OCA\Files_Sharing\Helper::setupFromToken($token, $relativePath, $password); | |||
$linkItem = $data['linkItem']; | |||
@@ -64,7 +62,7 @@ $data = array(); | |||
$baseUrl = OCP\Util::linkTo('files_sharing', 'index.php') . '?t=' . urlencode($token) . '&dir='; | |||
// make filelist | |||
$files = \OCA\Files\Helper::getFiles($dir); | |||
$files = \OCA\Files\Helper::getFiles($dir, $sortAttribute, $sortDirection); | |||
$formattedFiles = array(); | |||
foreach ($files as $file) { |
@@ -4,11 +4,13 @@ OCP\JSON::checkLoggedIn(); | |||
// Load the files | |||
$dir = isset( $_GET['dir'] ) ? $_GET['dir'] : ''; | |||
$sortAttribute = isset( $_GET['sort'] ) ? $_GET['sort'] : 'name'; | |||
$sortDirection = isset( $_GET['sortdirection'] ) ? ($_GET['sortdirection'] === 'desc') : false; | |||
$data = array(); | |||
// make filelist | |||
try { | |||
$files = \OCA\Files_Trashbin\Helper::getTrashFiles($dir); | |||
$files = \OCA\Files_Trashbin\Helper::getTrashFiles($dir, $sortAttribute, $sortDirection); | |||
} catch (Exception $e) { | |||
header("HTTP/1.0 404 Not Found"); | |||
exit(); |
@@ -80,6 +80,7 @@ | |||
FileList.initialize = function() { | |||
var result = oldInit.apply(this, arguments); | |||
$('.undelete').click('click', FileList._onClickRestoreSelected); | |||
this.setSort('mtime', 'desc'); | |||
return result; | |||
}; | |||
@@ -8,11 +8,14 @@ class Helper | |||
{ | |||
/** | |||
* Retrieves the contents of a trash bin directory. | |||
* | |||
* @param string $dir path to the directory inside the trashbin | |||
* or empty to retrieve the root of the trashbin | |||
* @param string $sortAttribute attribute to sort on or empty to disable sorting | |||
* @param bool $sortDescending true for descending sort, false otherwise | |||
* @return \OCP\Files\FileInfo[] | |||
*/ | |||
public static function getTrashFiles($dir){ | |||
public static function getTrashFiles($dir, $sortAttribute = '', $sortDescending = false){ | |||
$result = array(); | |||
$timestamp = null; | |||
$user = \OCP\User::getUser(); | |||
@@ -57,8 +60,9 @@ class Helper | |||
closedir($dirContent); | |||
} | |||
usort($result, array('\OCA\Files\Helper', 'fileCmp')); | |||
if ($sortAttribute !== '') { | |||
return \OCA\Files\Helper::sortFiles($result, $sortAttribute, $sortDescending); | |||
} | |||
return $result; | |||
} | |||
@@ -1,3 +1,4 @@ | |||
<?php /** @var $l OC_L10N */ ?> | |||
<div id="controls"> | |||
<div id="file_action_panel"></div> | |||
</div> | |||
@@ -5,29 +6,29 @@ | |||
<div id="emptycontent" class="hidden"><?php p($l->t('Nothing in here. Your trash bin is empty!'))?></div> | |||
<input type="hidden" id="permissions" value="0"></input> | |||
<input type="hidden" id="disableSharing" data-status="<?php p($_['disableSharing']); ?>"></input> | |||
<input type="hidden" id="permissions" value="0"> | |||
<input type="hidden" id="disableSharing" data-status="<?php p($_['disableSharing']); ?>"> | |||
<input type="hidden" name="dir" value="<?php p($_['dir']) ?>" id="dir"> | |||
<table id="filestable"> | |||
<thead> | |||
<tr> | |||
<th id='headerName'> | |||
<th id='headerName' class="hidden column-name"> | |||
<div id="headerName-container"> | |||
<input type="checkbox" id="select_all" /> | |||
<label for="select_all"></label> | |||
<span class='name'><?php p($l->t( 'Name' )); ?></span> | |||
<span class='selectedActions'> | |||
<input type="checkbox" id="select_all" /> | |||
<label for="select_all"></label> | |||
<a class="name sort columntitle" data-sort="name"><span><?php p($l->t( 'Name' )); ?></span><span class="sort-indicator"></span></a> | |||
<span id="selectedActionsList" class='selectedActions'> | |||
<a href="" class="undelete"> | |||
<img class="svg" alt="<?php p($l->t( 'Restore' )); ?>" | |||
src="<?php print_unescaped(OCP\image_path("core", "actions/history.svg")); ?>" /> | |||
<?php p($l->t('Restore'))?> | |||
</a> | |||
</span> | |||
</span> | |||
</div> | |||
</th> | |||
<th id="headerDate"> | |||
<span id="modified"><?php p($l->t( 'Deleted' )); ?></span> | |||
<th id="headerDate" class="hidden column-mtime"> | |||
<a id="modified" class="columntitle" data-sort="mtime"><span><?php p($l->t( 'Deleted' )); ?></span><span class="sort-indicator"></span></a> | |||
<span class="selectedActions"> | |||
<a href="" class="delete-selected"> | |||
<?php p($l->t('Delete'))?> |