File list sorting by clicking on column headerstags/v7.0.0alpha2
$permissions = $dirInfo->getPermissions(); | $permissions = $dirInfo->getPermissions(); | ||||
$sortAttribute = isset( $_GET['sort'] ) ? $_GET['sort'] : 'name'; | |||||
$sortDirection = isset( $_GET['sortdirection'] ) ? ($_GET['sortdirection'] === 'desc') : false; | |||||
// make filelist | // make filelist | ||||
$files = \OCA\Files\Helper::getFiles($dir); | |||||
$files = \OCA\Files\Helper::getFiles($dir, $sortAttribute, $sortDirection); | |||||
$data['directory'] = $dir; | $data['directory'] = $dir; | ||||
$data['files'] = \OCA\Files\Helper::formatFileInfos($files); | $data['files'] = \OCA\Files\Helper::formatFileInfos($files); |
table tr.mouseOver td { background-color:#eee; } | table tr.mouseOver td { background-color:#eee; } | ||||
table th { height:24px; padding:0 8px; color:#999; } | 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 th, table td { border-bottom:1px solid #ddd; text-align:left; font-weight:normal; } | ||||
table td { | table td { | ||||
} | } | ||||
table th#headerSize, table td.filesize { | table th#headerSize, table td.filesize { | ||||
min-width: 48px; | min-width: 48px; | ||||
padding: 0 16px; | |||||
text-align: right; | text-align: right; | ||||
padding: 0; | |||||
} | |||||
table table td.filesize { | |||||
padding: 0 16px; | |||||
} | } | ||||
table th#headerDate, table td.date { | table th#headerDate, table td.date { | ||||
-moz-box-sizing: border-box; | -moz-box-sizing: border-box; | ||||
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 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; } | table td.filename .nametext, .uploadtext, .modified { float:left; padding:14px 0; } | ||||
#modified { | |||||
position: absolute; | |||||
top: 15px; | |||||
} | |||||
.modified { | .modified { | ||||
position: relative; | position: relative; | ||||
padding-left: 8px; | padding-left: 8px; |
/* global OC, t, n, FileList, FileActions, Files, FileSummary, BreadCrumb */ | /* global OC, t, n, FileList, FileActions, Files, FileSummary, BreadCrumb */ | ||||
/* global dragOptions, folderDropOptions */ | /* global dragOptions, folderDropOptions */ | ||||
window.FileList = { | window.FileList = { | ||||
SORT_INDICATOR_ASC_CLASS: 'icon-triangle-s', | |||||
SORT_INDICATOR_DESC_CLASS: 'icon-triangle-n', | |||||
appName: t('files', 'Files'), | appName: t('files', 'Files'), | ||||
isEmpty: true, | isEmpty: true, | ||||
useUndo:true, | useUndo:true, | ||||
_selectionSummary: null, | _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 | * Initialize the file list and its components | ||||
this.fileSummary = this._createSummary(); | this.fileSummary = this._createSummary(); | ||||
this.setSort('name', 'asc'); | |||||
this.breadcrumb = new BreadCrumb({ | this.breadcrumb = new BreadCrumb({ | ||||
onClick: this._onClickBreadCrumb, | onClick: this._onClickBreadCrumb, | ||||
onDrop: _.bind(this._onDropOnBreadCrumb, this), | onDrop: _.bind(this._onDropOnBreadCrumb, this), | ||||
$('#controls').prepend(this.breadcrumb.$el); | $('#controls').prepend(this.breadcrumb.$el); | ||||
this.$el.find('thead th .columntitle').click(_.bind(this._onClickHeader, this)); | |||||
$(window).resize(function() { | $(window).resize(function() { | ||||
// TODO: debounce this ? | // TODO: debounce this ? | ||||
var width = $(this).width(); | var width = $(this).width(); | ||||
return false; | 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 | * Event handler when clicking on a bread crumb | ||||
*/ | */ | ||||
previousDir: currentDir | previousDir: currentDir | ||||
} | } | ||||
)); | )); | ||||
this._selectedFiles = {}; | |||||
this._selectionSummary.clear(); | |||||
this.reload(); | this.reload(); | ||||
}, | }, | ||||
linkTo: function(dir) { | linkTo: function(dir) { | ||||
} | } | ||||
this.breadcrumb.setDirectory(this.getCurrentDirectory()); | 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 | * @brief Reloads the file list using ajax call | ||||
*/ | */ | ||||
reload: function() { | reload: function() { | ||||
this._selectedFiles = {}; | |||||
this._selectionSummary.clear(); | |||||
this.$el.find('#select_all').prop('checked', false); | |||||
FileList.showMask(); | FileList.showMask(); | ||||
if (FileList._reloadCall) { | if (FileList._reloadCall) { | ||||
FileList._reloadCall.abort(); | FileList._reloadCall.abort(); | ||||
FileList._reloadCall = $.ajax({ | FileList._reloadCall = $.ajax({ | ||||
url: Files.getAjaxUrl('list'), | url: Files.getAjaxUrl('list'), | ||||
data: { | data: { | ||||
dir : $('#dir').val() | |||||
dir: $('#dir').val(), | |||||
sort: FileList._sort, | |||||
sortdirection: FileList._sortDirection | |||||
}, | }, | ||||
error: function(result) { | error: function(result) { | ||||
FileList.reloadCallback(result); | FileList.reloadCallback(result); | ||||
*/ | */ | ||||
_findInsertionIndex: function(fileData) { | _findInsertionIndex: function(fileData) { | ||||
var index = 0; | 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++; | index++; | ||||
} | } | ||||
return index; | return index; | ||||
OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error')); | OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error')); | ||||
} | } | ||||
$td.css('background-image', oldBackgroundImage); | $td.css('background-image', oldBackgroundImage); | ||||
}); | |||||
}); | |||||
}); | }); | ||||
}, | }, | ||||
updateSelectionSummary: function() { | updateSelectionSummary: function() { | ||||
var summary = this._selectionSummary.summary; | var summary = this._selectionSummary.summary; | ||||
if (summary.totalFiles === 0 && summary.totalDirs === 0) { | 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'); | $('table').removeClass('multiselect'); | ||||
$('.selectedActions').addClass('hidden'); | $('.selectedActions').addClass('hidden'); | ||||
} | } | ||||
else { | else { | ||||
$('.selectedActions').removeClass('hidden'); | $('.selectedActions').removeClass('hidden'); | ||||
$('#headerSize').text(OC.Util.humanFileSize(summary.totalSize)); | |||||
$('#headerSize a>span:first').text(OC.Util.humanFileSize(summary.totalSize)); | |||||
var selection = ''; | var selection = ''; | ||||
if (summary.totalDirs > 0) { | if (summary.totalDirs > 0) { | ||||
selection += n('files', '%n folder', '%n folders', summary.totalDirs); | selection += n('files', '%n folder', '%n folders', summary.totalDirs); | ||||
if (summary.totalFiles > 0) { | if (summary.totalFiles > 0) { | ||||
selection += n('files', '%n file', '%n files', summary.totalFiles); | 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'); | $('table').addClass('multiselect'); | ||||
} | } | ||||
}, | }, | ||||
}, 0); | }, 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; | |||||
} | |||||
}; | |||||
<?php | <?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; | namespace OCA\Files; | ||||
/** | |||||
* Helper class for manipulating file information | |||||
*/ | |||||
class Helper | class Helper | ||||
{ | { | ||||
public static function buildFileStorageStatistics($dir) { | public static function buildFileStorageStatistics($dir) { | ||||
$storageInfo = \OC_Helper::getStorageInfo($dir); | $storageInfo = \OC_Helper::getStorageInfo($dir); | ||||
$l = new \OC_L10N('files'); | $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'], | 'freeSpace' => $storageInfo['free'], | ||||
'usedSpacePercent' => (int)$storageInfo['relative']); | 'usedSpacePercent' => (int)$storageInfo['relative']); | ||||
} | } | ||||
*/ | */ | ||||
public static function determineIcon($file) { | public static function determineIcon($file) { | ||||
if($file['type'] === 'dir') { | if($file['type'] === 'dir') { | ||||
$dir = $file['directory']; | |||||
$icon = \OC_Helper::mimetypeIcon('dir'); | $icon = \OC_Helper::mimetypeIcon('dir'); | ||||
$absPath = $file->getPath(); | $absPath = $file->getPath(); | ||||
$mount = \OC\Files\Filesystem::getMountManager()->find($absPath); | $mount = \OC\Files\Filesystem::getMountManager()->find($absPath); | ||||
* @param \OCP\Files\FileInfo $b file | * @param \OCP\Files\FileInfo $b file | ||||
* @return int -1 if $a must come before $b, 1 otherwise | * @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(); | $aType = $a->getType(); | ||||
$bType = $b->getType(); | $bType = $b->getType(); | ||||
if ($aType === 'dir' and $bType !== 'dir') { | if ($aType === 'dir' and $bType !== 'dir') { | ||||
} | } | ||||
} | } | ||||
/** | |||||
* 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. | * Formats the file info to be returned as JSON to the client. | ||||
* | * | ||||
* returns it as a sorted array of FileInfo. | * returns it as a sorted array of FileInfo. | ||||
* | * | ||||
* @param string $dir path to the directory | * @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 | * @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); | $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; | |||||
} | } | ||||
} | } |
<?php /** @var $l OC_L10N */ ?> | |||||
<div id="controls"> | <div id="controls"> | ||||
<div class="actions creatable hidden"> | <div class="actions creatable hidden"> | ||||
<?php if(!isset($_['dirToken'])):?> | <?php if(!isset($_['dirToken'])):?> | ||||
<table id="filestable" data-allow-public-upload="<?php p($_['publicUploadEnabled'])?>" data-preview-x="36" data-preview-y="36"> | <table id="filestable" data-allow-public-upload="<?php p($_['publicUploadEnabled'])?>" data-preview-x="36" data-preview-y="36"> | ||||
<thead> | <thead> | ||||
<tr> | <tr> | ||||
<th class="hidden" id='headerName'> | |||||
<th id='headerName' class="hidden column-name"> | |||||
<div id="headerName-container"> | <div id="headerName-container"> | ||||
<input type="checkbox" id="select_all" /> | <input type="checkbox" id="select_all" /> | ||||
<label for="select_all"></label> | <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"> | <span id="selectedActionsList" class="selectedActions"> | ||||
<?php if($_['allowZipDownload']) : ?> | <?php if($_['allowZipDownload']) : ?> | ||||
<a href="" class="download"> | <a href="" class="download"> | ||||
</span> | </span> | ||||
</div> | </div> | ||||
</th> | </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): ?> | <?php if ($_['permissions'] & OCP\PERMISSION_DELETE): ?> | ||||
<span class="selectedActions"><a href="" class="delete-selected"> | <span class="selectedActions"><a href="" class="delete-selected"> | ||||
<?php p($l->t('Delete'))?> | <?php p($l->t('Delete'))?> |
<?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 | |||||
); | |||||
} | |||||
} |
// dummy table | // dummy table | ||||
// TODO: at some point this will be rendered by the FileList class itself! | // TODO: at some point this will be rendered by the FileList class itself! | ||||
'<table id="filestable">' + | '<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">' + | '<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">' + | '<span class="selectedActions hidden">' + | ||||
'<a href class="download">Download</a>' + | '<a href class="download">Download</a>' + | ||||
'<a href class="delete-selected">Delete</a></span>' + | '<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>' + | '<tbody id="fileList"></tbody>' + | ||||
'<tfoot></tfoot>' + | '<tfoot></tfoot>' + | ||||
'</table>' + | '</table>' + | ||||
expect(fakeServer.requests.length).toEqual(1); | expect(fakeServer.requests.length).toEqual(1); | ||||
var url = fakeServer.requests[0].url; | var url = fakeServer.requests[0].url; | ||||
var query = url.substr(url.indexOf('?') + 1); | 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(); | fakeServer.respond(); | ||||
expect($('#fileList tr').length).toEqual(4); | expect($('#fileList tr').length).toEqual(4); | ||||
expect(FileList.findFileEl('One.txt').length).toEqual(1); | expect(FileList.findFileEl('One.txt').length).toEqual(1); | ||||
expect(fakeServer.requests.length).toEqual(1); | expect(fakeServer.requests.length).toEqual(1); | ||||
var url = fakeServer.requests[0].url; | var url = fakeServer.requests[0].url; | ||||
var query = url.substr(url.indexOf('?') + 1); | 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(); | fakeServer.respond(); | ||||
}); | }); | ||||
it('switches to root dir when current directory does not exist', function() { | it('switches to root dir when current directory does not exist', function() { | ||||
expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(42); | expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(42); | ||||
}); | }); | ||||
it('Selecting files updates selection summary', function() { | it('Selecting files updates selection summary', function() { | ||||
var $summary = $('#headerName span.name'); | |||||
var $summary = $('#headerName a.name>span:first'); | |||||
expect($summary.text()).toEqual('Name'); | expect($summary.text()).toEqual('Name'); | ||||
FileList.findFileEl('One.txt').find('input:checkbox').click(); | FileList.findFileEl('One.txt').find('input:checkbox').click(); | ||||
FileList.findFileEl('Three.pdf').find('input:checkbox').click(); | FileList.findFileEl('Three.pdf').find('input:checkbox').click(); | ||||
expect($summary.text()).toEqual('1 folder & 2 files'); | expect($summary.text()).toEqual('1 folder & 2 files'); | ||||
}); | }); | ||||
it('Unselecting files hides selection summary', function() { | 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(); | FileList.findFileEl('One.txt').find('input:checkbox').click().click(); | ||||
expect($summary.text()).toEqual('Name'); | expect($summary.text()).toEqual('Name'); | ||||
}); | }); | ||||
}); | }); | ||||
}); | }); | ||||
}); | }); | ||||
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); | |||||
}); | |||||
}); | }); | ||||
}); | }); |
* | * | ||||
*/ | */ | ||||
// only need filesystem apps | |||||
$RUNTIME_APPTYPES=array('filesystem'); | |||||
// Init owncloud | |||||
if(!\OC_App::isEnabled('files_sharing')){ | if(!\OC_App::isEnabled('files_sharing')){ | ||||
exit; | exit; | ||||
} | } | ||||
$relativePath = $_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); | $data = \OCA\Files_Sharing\Helper::setupFromToken($token, $relativePath, $password); | ||||
$linkItem = $data['linkItem']; | $linkItem = $data['linkItem']; | ||||
$baseUrl = OCP\Util::linkTo('files_sharing', 'index.php') . '?t=' . urlencode($token) . '&dir='; | $baseUrl = OCP\Util::linkTo('files_sharing', 'index.php') . '?t=' . urlencode($token) . '&dir='; | ||||
// make filelist | // make filelist | ||||
$files = \OCA\Files\Helper::getFiles($dir); | |||||
$files = \OCA\Files\Helper::getFiles($dir, $sortAttribute, $sortDirection); | |||||
$formattedFiles = array(); | $formattedFiles = array(); | ||||
foreach ($files as $file) { | foreach ($files as $file) { |
// Load the files | // Load the files | ||||
$dir = isset( $_GET['dir'] ) ? $_GET['dir'] : ''; | $dir = isset( $_GET['dir'] ) ? $_GET['dir'] : ''; | ||||
$sortAttribute = isset( $_GET['sort'] ) ? $_GET['sort'] : 'name'; | |||||
$sortDirection = isset( $_GET['sortdirection'] ) ? ($_GET['sortdirection'] === 'desc') : false; | |||||
$data = array(); | $data = array(); | ||||
// make filelist | // make filelist | ||||
try { | try { | ||||
$files = \OCA\Files_Trashbin\Helper::getTrashFiles($dir); | |||||
$files = \OCA\Files_Trashbin\Helper::getTrashFiles($dir, $sortAttribute, $sortDirection); | |||||
} catch (Exception $e) { | } catch (Exception $e) { | ||||
header("HTTP/1.0 404 Not Found"); | header("HTTP/1.0 404 Not Found"); | ||||
exit(); | exit(); |
FileList.initialize = function() { | FileList.initialize = function() { | ||||
var result = oldInit.apply(this, arguments); | var result = oldInit.apply(this, arguments); | ||||
$('.undelete').click('click', FileList._onClickRestoreSelected); | $('.undelete').click('click', FileList._onClickRestoreSelected); | ||||
this.setSort('mtime', 'desc'); | |||||
return result; | return result; | ||||
}; | }; | ||||
{ | { | ||||
/** | /** | ||||
* Retrieves the contents of a trash bin directory. | * Retrieves the contents of a trash bin directory. | ||||
* | |||||
* @param string $dir path to the directory inside the trashbin | * @param string $dir path to the directory inside the trashbin | ||||
* or empty to retrieve the root of 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[] | * @return \OCP\Files\FileInfo[] | ||||
*/ | */ | ||||
public static function getTrashFiles($dir){ | |||||
public static function getTrashFiles($dir, $sortAttribute = '', $sortDescending = false){ | |||||
$result = array(); | $result = array(); | ||||
$timestamp = null; | $timestamp = null; | ||||
$user = \OCP\User::getUser(); | $user = \OCP\User::getUser(); | ||||
closedir($dirContent); | closedir($dirContent); | ||||
} | } | ||||
usort($result, array('\OCA\Files\Helper', 'fileCmp')); | |||||
if ($sortAttribute !== '') { | |||||
return \OCA\Files\Helper::sortFiles($result, $sortAttribute, $sortDescending); | |||||
} | |||||
return $result; | return $result; | ||||
} | } | ||||
<?php /** @var $l OC_L10N */ ?> | |||||
<div id="controls"> | <div id="controls"> | ||||
<div id="file_action_panel"></div> | <div id="file_action_panel"></div> | ||||
</div> | </div> | ||||
<div id="emptycontent" class="hidden"><?php p($l->t('Nothing in here. Your trash bin is empty!'))?></div> | <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"> | <input type="hidden" name="dir" value="<?php p($_['dir']) ?>" id="dir"> | ||||
<table id="filestable"> | <table id="filestable"> | ||||
<thead> | <thead> | ||||
<tr> | <tr> | ||||
<th id='headerName'> | |||||
<th id='headerName' class="hidden column-name"> | |||||
<div id="headerName-container"> | <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"> | <a href="" class="undelete"> | ||||
<img class="svg" alt="<?php p($l->t( 'Restore' )); ?>" | <img class="svg" alt="<?php p($l->t( 'Restore' )); ?>" | ||||
src="<?php print_unescaped(OCP\image_path("core", "actions/history.svg")); ?>" /> | src="<?php print_unescaped(OCP\image_path("core", "actions/history.svg")); ?>" /> | ||||
<?php p($l->t('Restore'))?> | <?php p($l->t('Restore'))?> | ||||
</a> | </a> | ||||
</span> | |||||
</span> | |||||
</div> | </div> | ||||
</th> | </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"> | <span class="selectedActions"> | ||||
<a href="" class="delete-selected"> | <a href="" class="delete-selected"> | ||||
<?php p($l->t('Delete'))?> | <?php p($l->t('Delete'))?> |