1
0
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:
llj
2018-12-28 15:19:10 +08:00
committed by Daniel Pan
parent dd15cd0ea8
commit aa98e831e6
6 changed files with 179 additions and 11 deletions

View File

@@ -11,6 +11,9 @@ import DeleteRepoDialog from '../../components/dialog/delete-repo-dialog';
const propTypes = { const propTypes = {
loading: PropTypes.bool.isRequired, loading: PropTypes.bool.isRequired,
errorMsg: PropTypes.string.isRequired, errorMsg: PropTypes.string.isRequired,
sortBy: PropTypes.string.isRequired,
sortOrder: PropTypes.string.isRequired,
sortItems: PropTypes.func.isRequired,
items: PropTypes.array.isRequired, items: PropTypes.array.isRequired,
onRenameRepo: PropTypes.func.isRequired, onRenameRepo: PropTypes.func.isRequired,
onDeleteRepo: 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() { render() {
const { loading, errorMsg, items } = this.props; const { loading, errorMsg, items, sortBy, sortOrder } = this.props;
if (loading) { if (loading) {
return <Loading />; return <Loading />;
@@ -78,18 +95,23 @@ class Content extends Component {
</div> </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' // TODO: test 'storage backend'
const showStorageBackend = storages.length > 0; // only for desktop const showStorageBackend = storages.length > 0; // only for desktop
const desktopThead = ( const desktopThead = (
<thead> <thead>
<tr> <tr>
<th width="4%"><span className="sr-only">{gettext('Library Type')}</span></th> <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="14%"><span className="sr-only">{gettext('Actions')}</span></th>
<th width={showStorageBackend ? '15%' : '20%'}>{gettext('Size')}</th> <th width={showStorageBackend ? '15%' : '20%'}>{gettext('Size')}</th>
{showStorageBackend ? <th width="10%">{gettext('Storage backend')}</th> : null} {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> </tr>
</thead> </thead>
); );
@@ -99,9 +121,9 @@ class Content extends Component {
<tr> <tr>
<th width="18%"><span className="sr-only">{gettext('Library Type')}</span></th> <th width="18%"><span className="sr-only">{gettext('Library Type')}</span></th>
<th width="76%"> <th width="76%">
{gettext("Sort:")} {/* TODO: sort */} {gettext("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> <a className="table-sort-op" href="#" onClick={this.sortByName}>{gettext("name")} {sortByName && sortIcon}</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> <a className="table-sort-op" href="#" onClick={this.sortByTime}>{gettext("last update")} {sortByTime && sortIcon}</a>
</th> </th>
<th width="6%"><span className="sr-only">{gettext('Actions')}</span></th> <th width="6%"><span className="sr-only">{gettext('Actions')}</span></th>
</tr> </tr>
@@ -163,4 +185,4 @@ class Content extends Component {
Content.propTypes = propTypes; Content.propTypes = propTypes;
export default Content; export default Content;

View File

@@ -257,7 +257,7 @@ class Item extends Component {
} }
</td> </td>
<td>{data.repo_name ? desktopOperations : ''}</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} {storages.length ? <td>{data.storage_name}</td> : null}
<td title={moment(data.last_modified).format('llll')}>{moment(data.last_modified).fromNow()}</td> <td title={moment(data.last_modified).format('llll')}>{moment(data.last_modified).fromNow()}</td>
</tr> </tr>
@@ -284,7 +284,7 @@ class Item extends Component {
<Link to={data.url}>{data.repo_name}</Link> : <Link to={data.url}>{data.repo_name}</Link> :
gettext('Broken (please contact your administrator to fix this library)')} gettext('Broken (please contact your administrator to fix this library)')}
<br /> <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> <span className="item-meta-info" title={moment(data.last_modified).format('llll')}>{moment(data.last_modified).fromNow()}</span>
</td> </td>
<td>{data.repo_name ? mobileOperations : ''}</td> <td>{data.repo_name ? mobileOperations : ''}</td>
@@ -309,4 +309,4 @@ class Item extends Component {
Item.propTypes = propTypes; Item.propTypes = propTypes;
export default Item; export default Item;

View File

@@ -1,6 +1,8 @@
import React, { Component, Fragment } from 'react'; import React, { Component, Fragment } from 'react';
import { seafileAPI } from '../../utils/seafile-api'; import { seafileAPI } from '../../utils/seafile-api';
import { gettext, loginUrl} from '../../utils/constants'; import { gettext, loginUrl} from '../../utils/constants';
import { Utils } from '../../utils/utils';
import Repo from '../../models/repo';
import CommonToolbar from '../../components/toolbar/common-toolbar'; import CommonToolbar from '../../components/toolbar/common-toolbar';
import RepoViewToolbar from '../../components/toolbar/repo-view-toobar'; import RepoViewToolbar from '../../components/toolbar/repo-view-toobar';
import LibDetail from '../../components/dirent-detail/lib-details'; import LibDetail from '../../components/dirent-detail/lib-details';
@@ -14,15 +16,20 @@ class MyLibraries extends Component {
loading: true, loading: true,
errorMsg: '', errorMsg: '',
items: [], items: [],
sortBy: 'name', // 'name' or 'time'
sortOrder: 'asc' // 'asc' or 'desc'
}; };
} }
componentDidMount() { componentDidMount() {
seafileAPI.listRepos({type: 'mine'}).then((res) => { seafileAPI.listRepos({type: 'mine'}).then((res) => {
// res: {data: {...}, status: 200, statusText: "OK", headers: {…}, config: {…}, …} // res: {data: {...}, status: 200, statusText: "OK", headers: {…}, config: {…}, …}
let repoList = res.data.repos.map((item) => {
return new Repo(item);
});
this.setState({ this.setState({
loading: false, loading: false,
items: res.data.repos items: Utils.sortRepos(repoList, this.state.sortBy, this.state.sortOrder)
}); });
}).catch((error) => { }).catch((error) => {
if (error.response) { 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) => { onTransferRepo = (repoID) => {
let items = this.state.items.filter(item => { let items = this.state.items.filter(item => {
return item.repo_id !== repoID; return item.repo_id !== repoID;
@@ -118,6 +133,9 @@ class MyLibraries extends Component {
loading={this.state.loading} loading={this.state.loading}
errorMsg={this.state.errorMsg} errorMsg={this.state.errorMsg}
items={this.state.items} items={this.state.items}
sortBy={this.state.sortBy}
sortOrder={this.state.sortOrder}
sortItems={this.sortItems}
onDeleteRepo={this.onDeleteRepo} onDeleteRepo={this.onDeleteRepo}
onRenameRepo={this.onRenameRepo} onRenameRepo={this.onRenameRepo}
onTransferRepo={this.onTransferRepo} onTransferRepo={this.onTransferRepo}

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,5 @@
import { mediaUrl, gettext } from './constants'; import { mediaUrl, gettext } from './constants';
import { strChineseFirstPY } from './pinyin-by-unicode';
export const Utils = { export const Utils = {
@@ -368,5 +369,115 @@ export const Utils = {
return false; 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;
} }
}; };

View File

@@ -898,6 +898,22 @@ table td img {
color: #888; color: #888;
text-decoration: underline; 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 style */
.dropdown-menu { .dropdown-menu {