mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-01 23:20:51 +00:00
[my libraries] sort by name & time (#2726)
This commit is contained in:
@@ -11,6 +11,9 @@ import DeleteRepoDialog from '../../components/dialog/delete-repo-dialog';
|
||||
const propTypes = {
|
||||
loading: PropTypes.bool.isRequired,
|
||||
errorMsg: PropTypes.string.isRequired,
|
||||
sortBy: PropTypes.string.isRequired,
|
||||
sortOrder: PropTypes.string.isRequired,
|
||||
sortItems: PropTypes.func.isRequired,
|
||||
items: PropTypes.array.isRequired,
|
||||
onRenameRepo: PropTypes.func.isRequired,
|
||||
onDeleteRepo: PropTypes.func.isRequired,
|
||||
@@ -63,8 +66,22 @@ class Content extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
sortByName = (e) => {
|
||||
e.preventDefault();
|
||||
const sortBy = 'name';
|
||||
const sortOrder = this.props.sortOrder == 'asc' ? 'desc' : 'asc';
|
||||
this.props.sortItems(sortBy, sortOrder);
|
||||
}
|
||||
|
||||
sortByTime = (e) => {
|
||||
e.preventDefault();
|
||||
const sortBy = 'time';
|
||||
const sortOrder = this.props.sortOrder == 'asc' ? 'desc' : 'asc';
|
||||
this.props.sortItems(sortBy, sortOrder);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loading, errorMsg, items } = this.props;
|
||||
const { loading, errorMsg, items, sortBy, sortOrder } = this.props;
|
||||
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
@@ -78,18 +95,23 @@ class Content extends Component {
|
||||
</div>
|
||||
);
|
||||
|
||||
// sort
|
||||
const sortByName = sortBy == 'name';
|
||||
const sortByTime = sortBy == 'time';
|
||||
const sortIcon = sortOrder == 'asc' ? <span className="fas fa-caret-up"></span> : <span className="fas fa-caret-down"></span>;
|
||||
|
||||
// TODO: test 'storage backend'
|
||||
const showStorageBackend = storages.length > 0; // only for desktop
|
||||
const desktopThead = (
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="4%"><span className="sr-only">{gettext('Library Type')}</span></th>
|
||||
<th width="42%">{gettext('Name')}<a className="table-sort-op by-name" href="#">{/*TODO: sort*/}<span className="sort-icon icon-caret-down hide"></span></a></th>
|
||||
<th width="42%"><a className="d-block table-sort-op" href="#" onClick={this.sortByName}>{gettext('Name')} {sortByName && sortIcon}</a></th>
|
||||
<th width="14%"><span className="sr-only">{gettext('Actions')}</span></th>
|
||||
|
||||
<th width={showStorageBackend ? '15%' : '20%'}>{gettext('Size')}</th>
|
||||
{showStorageBackend ? <th width="10%">{gettext('Storage backend')}</th> : null}
|
||||
<th width={showStorageBackend ? '15%' : '20%'}>{gettext('Last Update')}<a className="table-sort-op by-time" href="#">{/*TODO: sort*/}<span className="sort-icon icon-caret-up"></span></a></th>
|
||||
<th width={showStorageBackend ? '15%' : '20%'}><a className="d-block table-sort-op" href="#" onClick={this.sortByTime}>{gettext('Last Update')} {sortByTime && sortIcon}</a></th>
|
||||
</tr>
|
||||
</thead>
|
||||
);
|
||||
@@ -99,9 +121,9 @@ class Content extends Component {
|
||||
<tr>
|
||||
<th width="18%"><span className="sr-only">{gettext('Library Type')}</span></th>
|
||||
<th width="76%">
|
||||
{gettext("Sort:")} {/* TODO: sort */}
|
||||
{gettext("name")}<a className="table-sort-op mobile-table-sort-op by-name" href="#"> <span className="sort-icon icon-caret-down hide"></span></a>
|
||||
{gettext("last update")}<a className="table-sort-op mobile-table-sort-op by-time" href="#"> <span className="sort-icon icon-caret-up"></span></a>
|
||||
{gettext("Sort:")}
|
||||
<a className="table-sort-op" href="#" onClick={this.sortByName}>{gettext("name")} {sortByName && sortIcon}</a>
|
||||
<a className="table-sort-op" href="#" onClick={this.sortByTime}>{gettext("last update")} {sortByTime && sortIcon}</a>
|
||||
</th>
|
||||
<th width="6%"><span className="sr-only">{gettext('Actions')}</span></th>
|
||||
</tr>
|
||||
|
@@ -257,7 +257,7 @@ class Item extends Component {
|
||||
}
|
||||
</td>
|
||||
<td>{data.repo_name ? desktopOperations : ''}</td>
|
||||
<td>{Utils.bytesToSize(data.size)}</td>
|
||||
<td>{data.size}</td>
|
||||
{storages.length ? <td>{data.storage_name}</td> : null}
|
||||
<td title={moment(data.last_modified).format('llll')}>{moment(data.last_modified).fromNow()}</td>
|
||||
</tr>
|
||||
@@ -284,7 +284,7 @@ class Item extends Component {
|
||||
<Link to={data.url}>{data.repo_name}</Link> :
|
||||
gettext('Broken (please contact your administrator to fix this library)')}
|
||||
<br />
|
||||
<span className="item-meta-info">{Utils.bytesToSize(data.size)}</span>
|
||||
<span className="item-meta-info">{data.size}</span>
|
||||
<span className="item-meta-info" title={moment(data.last_modified).format('llll')}>{moment(data.last_modified).fromNow()}</span>
|
||||
</td>
|
||||
<td>{data.repo_name ? mobileOperations : ''}</td>
|
||||
|
@@ -1,6 +1,8 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { gettext, loginUrl} from '../../utils/constants';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import Repo from '../../models/repo';
|
||||
import CommonToolbar from '../../components/toolbar/common-toolbar';
|
||||
import RepoViewToolbar from '../../components/toolbar/repo-view-toobar';
|
||||
import LibDetail from '../../components/dirent-detail/lib-details';
|
||||
@@ -14,15 +16,20 @@ class MyLibraries extends Component {
|
||||
loading: true,
|
||||
errorMsg: '',
|
||||
items: [],
|
||||
sortBy: 'name', // 'name' or 'time'
|
||||
sortOrder: 'asc' // 'asc' or 'desc'
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
seafileAPI.listRepos({type: 'mine'}).then((res) => {
|
||||
// res: {data: {...}, status: 200, statusText: "OK", headers: {…}, config: {…}, …}
|
||||
let repoList = res.data.repos.map((item) => {
|
||||
return new Repo(item);
|
||||
});
|
||||
this.setState({
|
||||
loading: false,
|
||||
items: res.data.repos
|
||||
items: Utils.sortRepos(repoList, this.state.sortBy, this.state.sortOrder)
|
||||
});
|
||||
}).catch((error) => {
|
||||
if (error.response) {
|
||||
@@ -64,6 +71,14 @@ class MyLibraries extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
sortItems = (sortBy, sortOrder) => {
|
||||
this.setState({
|
||||
sortBy: sortBy,
|
||||
sortOrder: sortOrder,
|
||||
items: Utils.sortRepos(this.state.items, sortBy, sortOrder)
|
||||
});
|
||||
}
|
||||
|
||||
onTransferRepo = (repoID) => {
|
||||
let items = this.state.items.filter(item => {
|
||||
return item.repo_id !== repoID;
|
||||
@@ -118,6 +133,9 @@ class MyLibraries extends Component {
|
||||
loading={this.state.loading}
|
||||
errorMsg={this.state.errorMsg}
|
||||
items={this.state.items}
|
||||
sortBy={this.state.sortBy}
|
||||
sortOrder={this.state.sortOrder}
|
||||
sortItems={this.sortItems}
|
||||
onDeleteRepo={this.onDeleteRepo}
|
||||
onRenameRepo={this.onRenameRepo}
|
||||
onTransferRepo={this.onTransferRepo}
|
||||
|
1
frontend/src/utils/pinyin-by-unicode.js
Normal file
1
frontend/src/utils/pinyin-by-unicode.js
Normal file
File diff suppressed because one or more lines are too long
@@ -1,4 +1,5 @@
|
||||
import { mediaUrl, gettext } from './constants';
|
||||
import { strChineseFirstPY } from './pinyin-by-unicode';
|
||||
|
||||
export const Utils = {
|
||||
|
||||
@@ -368,5 +369,115 @@ export const Utils = {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
compareTwoWord: function(wordA, wordB) {
|
||||
// compare wordA and wordB at lower case
|
||||
// if wordA >= wordB, return 1
|
||||
// if wordA < wordB, return -1
|
||||
|
||||
var a_val, b_val,
|
||||
a_uni = wordA.charCodeAt(0),
|
||||
b_uni = wordB.charCodeAt(0);
|
||||
|
||||
if ((19968 < a_uni && a_uni < 40869) && (19968 < b_uni && b_uni < 40869)) {
|
||||
// both are chinese words
|
||||
a_val = strChineseFirstPY.charAt(a_uni - 19968).toLowerCase();
|
||||
b_val = strChineseFirstPY.charAt(b_uni - 19968).toLowerCase();
|
||||
} else if ((19968 < a_uni && a_uni < 40869) && !(19968 < b_uni && b_uni < 40869)) {
|
||||
// a is chinese and b is english
|
||||
return 1;
|
||||
} else if (!(19968 < a_uni && a_uni < 40869) && (19968 < b_uni && b_uni < 40869)) {
|
||||
// a is english and b is chinese
|
||||
return -1;
|
||||
} else {
|
||||
// both are english words
|
||||
a_val = wordA.toLowerCase();
|
||||
b_val = wordB.toLowerCase();
|
||||
return this.compareStrWithNumbersIn(a_val, b_val);
|
||||
}
|
||||
|
||||
return a_val >= b_val ? 1 : -1;
|
||||
},
|
||||
|
||||
// compare two strings which may have digits in them
|
||||
// and compare those digits as number instead of string
|
||||
compareStrWithNumbersIn: function(a, b) {
|
||||
var reParts = /\d+|\D+/g;
|
||||
var reDigit = /\d/;
|
||||
var aParts = a.match(reParts);
|
||||
var bParts = b.match(reParts);
|
||||
var isDigitPart;
|
||||
var len = Math.min(aParts.length, bParts.length);
|
||||
var aPart, bPart;
|
||||
|
||||
if (aParts && bParts &&
|
||||
(isDigitPart = reDigit.test(aParts[0])) == reDigit.test(bParts[0])) {
|
||||
// Loop through each substring part to compare the overall strings.
|
||||
for (var i = 0; i < len; i++) {
|
||||
aPart = aParts[i];
|
||||
bPart = bParts[i];
|
||||
|
||||
if (isDigitPart) {
|
||||
aPart = parseInt(aPart, 10);
|
||||
bPart = parseInt(bPart, 10);
|
||||
}
|
||||
|
||||
if (aPart != bPart) {
|
||||
return aPart < bPart ? -1 : 1;
|
||||
}
|
||||
|
||||
// Toggle the value of isDigitPart since the parts will alternate.
|
||||
isDigitPart = !isDigitPart;
|
||||
}
|
||||
}
|
||||
|
||||
// Use normal comparison.
|
||||
return (a >= b) - (a <= b);
|
||||
},
|
||||
|
||||
sortRepos: function(repos, sortBy, sortOrder) {
|
||||
const _this = this;
|
||||
let comparator;
|
||||
|
||||
switch (`${sortBy}-${sortOrder}`) {
|
||||
case 'name-asc':
|
||||
comparator = function(a, b) {
|
||||
if (!a.repo_name) {
|
||||
return 1;
|
||||
}
|
||||
if (!b.repo_name) {
|
||||
return -1;
|
||||
}
|
||||
var result = _this.compareTwoWord(a.repo_name, b.repo_name);
|
||||
return result;
|
||||
};
|
||||
break;
|
||||
case 'name-desc':
|
||||
comparator = function(a, b) {
|
||||
if (!a.repo_name) {
|
||||
return -1;
|
||||
}
|
||||
if (!b.repo_name) {
|
||||
return 1;
|
||||
}
|
||||
var result = _this.compareTwoWord(a.repo_name, b.repo_name);
|
||||
return -result;
|
||||
};
|
||||
break;
|
||||
case 'time-asc':
|
||||
comparator = function(a, b) {
|
||||
return a.last_modified < b.last_modified ? -1 : 1;
|
||||
};
|
||||
break;
|
||||
case 'time-desc':
|
||||
comparator = function(a, b) {
|
||||
return a.last_modified < b.last_modified ? 1 : -1;
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
repos.sort(comparator);
|
||||
return repos;
|
||||
}
|
||||
};
|
||||
|
@@ -899,6 +899,22 @@ table td img {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* table sort */
|
||||
a.table-sort-op {
|
||||
color: inherit;
|
||||
}
|
||||
@media (max-width:767px) {
|
||||
a.table-sort-op {
|
||||
display: inline-block;
|
||||
margin-left: 15px;
|
||||
}
|
||||
}
|
||||
a.table-sort-op:hover,
|
||||
a.table-sort-op:focus {
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* dropdown-menu style */
|
||||
.dropdown-menu {
|
||||
min-width: 8rem;
|
||||
|
Reference in New Issue
Block a user