mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-16 15:19:06 +00:00
optimized my-libs (#2935)
* optimized my-libs * Remove redundant directory hierarchies * repair reabse bug
This commit is contained in:
41
frontend/package-lock.json
generated
41
frontend/package-lock.json
generated
@@ -2945,6 +2945,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"css-mediaquery": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/css-mediaquery/-/css-mediaquery-0.1.2.tgz",
|
||||
"integrity": "sha1-aiw3NEkoYYYxxUvTPO3TAdoYvqA="
|
||||
},
|
||||
"css-select": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
|
||||
@@ -5286,7 +5291,7 @@
|
||||
},
|
||||
"git-up": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "http://registry.npmjs.org/git-up/-/git-up-1.2.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/git-up/-/git-up-1.2.1.tgz",
|
||||
"integrity": "sha1-JkSAoAax2EJhrB/gmjpRacV+oZ0=",
|
||||
"requires": {
|
||||
"is-ssh": "^1.0.0",
|
||||
@@ -5295,7 +5300,7 @@
|
||||
},
|
||||
"git-url-parse": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "http://registry.npmjs.org/git-url-parse/-/git-url-parse-5.0.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-5.0.1.tgz",
|
||||
"integrity": "sha1-/j15xnRq4FBIz6UIyB553du6OEM=",
|
||||
"requires": {
|
||||
"git-up": "^1.0.0"
|
||||
@@ -7487,6 +7492,14 @@
|
||||
"resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-1.1.2.tgz",
|
||||
"integrity": "sha512-NcWuJFHDA8V3wkDgR/j4+gZx+YQwstPgfQDV8ndUeWWzta3dnDTBxpVzqS9lkmJAuV5YX35lmyojl6HO5JXAgw=="
|
||||
},
|
||||
"matchmediaquery": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/matchmediaquery/-/matchmediaquery-0.3.0.tgz",
|
||||
"integrity": "sha512-u0dlv+VENJ+3YepvwSPBieuvnA6DWfaYa/ctwysAR13y4XLJNyt7bEVKzNj/Nvjo+50d88Pj+xL9xaSo6JmX/w==",
|
||||
"requires": {
|
||||
"css-mediaquery": "^0.1.2"
|
||||
}
|
||||
},
|
||||
"math-expression-evaluator": {
|
||||
"version": "1.2.17",
|
||||
"resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz",
|
||||
@@ -7858,7 +7871,7 @@
|
||||
},
|
||||
"node-status-codes": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "http://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz",
|
||||
"integrity": "sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8="
|
||||
},
|
||||
"noop6": {
|
||||
@@ -8165,7 +8178,7 @@
|
||||
},
|
||||
"package.json": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "http://registry.npmjs.org/package.json/-/package.json-2.0.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/package.json/-/package.json-2.0.1.tgz",
|
||||
"integrity": "sha1-+IYFnSpJ7QduZIg2ldc7K0bSHW0=",
|
||||
"requires": {
|
||||
"git-package-json": "^1.4.0",
|
||||
@@ -8175,7 +8188,7 @@
|
||||
"dependencies": {
|
||||
"got": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "http://registry.npmjs.org/got/-/got-5.7.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/got/-/got-5.7.1.tgz",
|
||||
"integrity": "sha1-X4FjWmHkplifGAVp6k44FoClHzU=",
|
||||
"requires": {
|
||||
"create-error-class": "^3.0.1",
|
||||
@@ -8197,7 +8210,7 @@
|
||||
},
|
||||
"package-json": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "http://registry.npmjs.org/package-json/-/package-json-2.4.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/package-json/-/package-json-2.4.0.tgz",
|
||||
"integrity": "sha1-DRW9Z9HLvduyyiIv8u24a8sxqLs=",
|
||||
"requires": {
|
||||
"got": "^5.0.0",
|
||||
@@ -10034,6 +10047,16 @@
|
||||
"react-deep-force-update": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"react-responsive": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-6.1.1.tgz",
|
||||
"integrity": "sha512-Po6pOEz70Agp+2lUmTxAnhfdkk0zp0IFgo/6bGcxv/S4Pa1sz0YG06WzkrIcASbyKSQ8x6AkcggeozXW3zj3kA==",
|
||||
"requires": {
|
||||
"hyphenate-style-name": "^1.0.0",
|
||||
"matchmediaquery": "^0.3.0",
|
||||
"prop-types": "^15.6.1"
|
||||
}
|
||||
},
|
||||
"react-s-alert": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/react-s-alert/-/react-s-alert-1.4.1.tgz",
|
||||
@@ -10846,9 +10869,9 @@
|
||||
}
|
||||
},
|
||||
"seafile-js": {
|
||||
"version": "0.2.61",
|
||||
"resolved": "https://registry.npmjs.org/seafile-js/-/seafile-js-0.2.61.tgz",
|
||||
"integrity": "sha512-fVQ8GlU95TgwnjOtRqvyiBtOah/lV/CnnT0yZzJnFYgCKKLpoGm+2aazUZaSjHQgP+r9u8ei14CMXnzQUiPTWg==",
|
||||
"version": "0.2.62",
|
||||
"resolved": "https://registry.npmjs.org/seafile-js/-/seafile-js-0.2.62.tgz",
|
||||
"integrity": "sha512-SA4LFmy65CmIsMr1zzgRxXPeGo+V+IOYs9pzwlgrTaNXHJBfQ/iS1rUhXmrj3EcBfLOdpdmUQlbutoz95jiwUA==",
|
||||
"requires": {
|
||||
"axios": "^0.18.0",
|
||||
"form-data": "^2.3.2",
|
||||
|
@@ -31,9 +31,10 @@
|
||||
"react-dom": "^16.5.2",
|
||||
"react-image-lightbox": "^5.1.0",
|
||||
"react-moment": "^0.7.9",
|
||||
"react-responsive": "^6.1.1",
|
||||
"react-select": "^2.1.1",
|
||||
"reactstrap": "^6.4.0",
|
||||
"seafile-js": "^0.2.61",
|
||||
"seafile-js": "^0.2.62",
|
||||
"socket.io-client": "^2.2.0",
|
||||
"sw-precache-webpack-plugin": "0.11.4",
|
||||
"unified": "^7.0.0",
|
||||
|
@@ -5,7 +5,9 @@ import { gettext } from '../../utils/constants';
|
||||
import { Utils } from '../../utils/utils';
|
||||
|
||||
const propTypes = {
|
||||
repo: PropTypes.object.isRequired,
|
||||
toggle: PropTypes.func.isRequired,
|
||||
onDeleteRepo: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
class DeleteRepoDialog extends Component {
|
||||
@@ -14,35 +16,29 @@ class DeleteRepoDialog extends Component {
|
||||
this.props.toggle();
|
||||
}
|
||||
|
||||
clickYes = () => {
|
||||
this.toggle();
|
||||
|
||||
const data = this.props.data;
|
||||
if (data) {
|
||||
data.yesCallback.bind(data._this)();
|
||||
}
|
||||
onDeleteRepo = () => {
|
||||
this.props.onDeleteRepo(this.props.repo);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
const data = this.props.data;
|
||||
const repoName = data ? '<span class="op-target">' + Utils.HTMLescape(data.repoName) + '</span>' : null;
|
||||
const repo = this.props.repo;
|
||||
const repoName = '<span class="op-target">' + Utils.HTMLescape(repo.repo_name) + '</span>';
|
||||
let message = gettext('Are you sure you want to delete %s ?');
|
||||
message = message.replace('%s', repoName);
|
||||
const popup = (
|
||||
|
||||
return (
|
||||
<Modal isOpen={true} toggle={this.toggle}>
|
||||
<ModalHeader toggle={this.toggle}>{gettext('Delete Library')}</ModalHeader>
|
||||
<ModalBody>
|
||||
<p dangerouslySetInnerHTML={{__html: message}}></p>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="primary" onClick={this.clickYes}>{gettext('Delete')}</Button>
|
||||
<Button color="primary" onClick={this.onDeleteRepo}>{gettext('Delete')}</Button>
|
||||
<Button color="secondary" onClick={this.toggle}>{gettext('Cancel')}</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
return popup;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -56,7 +56,6 @@ class TransferDialog extends React.Component {
|
||||
let message = gettext('Successfully transferred the library.');
|
||||
toaster.success(message);
|
||||
this.props.submit(repoID);
|
||||
this.props.toggleDialog();
|
||||
}).catch(res => {
|
||||
let message = gettext('Failed. Please check the network.');
|
||||
this.props.toggleDialog();
|
||||
|
@@ -1,209 +0,0 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { gettext, storages} from '../../utils/constants';
|
||||
import Loading from '../../components/loading';
|
||||
import TableBody from './table-body';
|
||||
import ModalPortal from '../../components/modal-portal';
|
||||
import LibHistorySetting from '../../components/dialog/lib-history-setting-dialog';
|
||||
import TransferDialog from '../../components/dialog/transfer-dialog';
|
||||
import ResetEncryptedRepoPasswordDialog from '../../components/dialog/reset-encrypted-repo-password-dialog';
|
||||
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,
|
||||
onTransferRepo: PropTypes.func.isRequired,
|
||||
onRepoDetails: PropTypes.func.isRequired,
|
||||
onItemClick: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
class Content extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
deleteItemPopupOpen: false,
|
||||
showTransfer: false,
|
||||
showResetEncryptedRepoPassword: false,
|
||||
itemName: '',
|
||||
showHistorySetting: false,
|
||||
showDetails: false,
|
||||
libID: '',
|
||||
libSize: '',
|
||||
libUpdateTime: ''
|
||||
};
|
||||
}
|
||||
|
||||
toggleDeleteItemPopup = () => {
|
||||
this.setState({
|
||||
deleteItemPopupOpen: !this.state.deleteItemPopupOpen
|
||||
});
|
||||
}
|
||||
|
||||
showDeleteItemPopup = (data) => {
|
||||
this.toggleDeleteItemPopup();
|
||||
this.setState({
|
||||
deleteItemPopupData: data
|
||||
});
|
||||
}
|
||||
|
||||
onTransfer = (itemName, itemID) => {
|
||||
this.setState({
|
||||
showTransfer: !this.state.showTransfer,
|
||||
itemName: itemName,
|
||||
libID: itemID
|
||||
});
|
||||
}
|
||||
|
||||
onHistorySetting = (itemName, itemID) => {
|
||||
this.setState({
|
||||
showHistorySetting: !this.state.showHistorySetting,
|
||||
itemName: itemName,
|
||||
libID: itemID
|
||||
});
|
||||
}
|
||||
|
||||
onResetEncryptedRepoPassword = (itemName, itemID) => {
|
||||
this.setState({
|
||||
showResetEncryptedRepoPassword: !this.state.showResetEncryptedRepoPassword,
|
||||
itemName: itemName,
|
||||
libID: itemID
|
||||
});
|
||||
}
|
||||
|
||||
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, sortBy, sortOrder } = this.props;
|
||||
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
} else if (errorMsg) {
|
||||
return <p className="error text-center">{errorMsg}</p>;
|
||||
} else {
|
||||
const emptyTip = (
|
||||
<div className="empty-tip">
|
||||
<h2>{gettext('You have not created any libraries')}</h2>
|
||||
<p>{gettext('You can create a library to organize your files. For example, you can create one for each of your projects. Each library can be synchronized and shared separately.')}</p>
|
||||
</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%"><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%'}><a className="d-block table-sort-op" href="#" onClick={this.sortByTime}>{gettext('Last Update')} {sortByTime && sortIcon}</a></th>
|
||||
</tr>
|
||||
</thead>
|
||||
);
|
||||
|
||||
const mobileThead = (
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="18%"><span className="sr-only">{gettext('Library Type')}</span></th>
|
||||
<th width="76%">
|
||||
{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>
|
||||
</thead>
|
||||
);
|
||||
|
||||
const table = (
|
||||
<table>
|
||||
{window.innerWidth >= 768 ? desktopThead : mobileThead}
|
||||
<TableBody
|
||||
items={items}
|
||||
onRenameRepo={this.props.onRenameRepo}
|
||||
onDeleteRepo={this.props.onDeleteRepo}
|
||||
onRepoDetails={this.props.onRepoDetails}
|
||||
onItemClick={this.props.onItemClick}
|
||||
onTransfer={this.onTransfer}
|
||||
showDeleteItemPopup={this.showDeleteItemPopup}
|
||||
onHistorySetting={this.onHistorySetting}
|
||||
onResetEncryptedRepoPassword={this.onResetEncryptedRepoPassword}
|
||||
/>
|
||||
</table>
|
||||
);
|
||||
|
||||
const nonEmpty = (
|
||||
<Fragment>
|
||||
{table}
|
||||
{this.state.deleteItemPopupOpen && (
|
||||
<ModalPortal>
|
||||
<DeleteRepoDialog
|
||||
toggle={this.toggleDeleteItemPopup}
|
||||
data={this.state.deleteItemPopupData}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
{this.state.showTransfer &&
|
||||
<ModalPortal>
|
||||
<TransferDialog
|
||||
toggleDialog={this.onTransfer}
|
||||
itemName={this.state.itemName}
|
||||
repoID={this.state.libID}
|
||||
submit={this.props.onTransferRepo}
|
||||
/>
|
||||
</ModalPortal>
|
||||
}
|
||||
{this.state.showHistorySetting &&
|
||||
<ModalPortal>
|
||||
<LibHistorySetting
|
||||
toggleDialog={this.onHistorySetting}
|
||||
itemName={this.state.itemName}
|
||||
repoID={this.state.libID}
|
||||
/>
|
||||
</ModalPortal>
|
||||
}
|
||||
{this.state.showResetEncryptedRepoPassword &&
|
||||
<ModalPortal>
|
||||
<ResetEncryptedRepoPasswordDialog
|
||||
repoID={this.state.libID}
|
||||
toggleDialog={this.onResetEncryptedRepoPassword}
|
||||
/>
|
||||
</ModalPortal>
|
||||
}
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
return items.length ? nonEmpty : emptyTip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Content.propTypes = propTypes;
|
||||
|
||||
export default Content;
|
@@ -1,341 +0,0 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import moment from 'moment';
|
||||
import { Link } from '@reach/router';
|
||||
import { Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap';
|
||||
import { gettext, siteRoot, storages, folderPermEnabled, enableResetEncryptedRepoPassword, isEmailConfigured, enableRepoSnapshotLabel } from '../../utils/constants';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import Rename from '../../components/rename';
|
||||
import ModalPotal from '../../components/modal-portal';
|
||||
import ShareDialog from '../../components/dialog/share-dialog';
|
||||
import ChangeRepoPasswordDialog from '../../components/dialog/change-repo-password-dialog';
|
||||
|
||||
const propTypes = {
|
||||
data: PropTypes.object.isRequired,
|
||||
isItemFreezed: PropTypes.bool.isRequired,
|
||||
onItemFreezedToggle: PropTypes.func.isRequired,
|
||||
onRenameRepo: PropTypes.func.isRequired,
|
||||
onDeleteRepo: PropTypes.func.isRequired,
|
||||
onTransfer: PropTypes.func.isRequired,
|
||||
showDeleteItemPopup: PropTypes.func.isRequired,
|
||||
onHistorySetting: PropTypes.func.isRequired,
|
||||
onResetEncryptedRepoPassword: PropTypes.func.isRequired,
|
||||
onRepoDetails: PropTypes.func.isRequired,
|
||||
onItemClick: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
class Item extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showOpIcon: false,
|
||||
operationMenuOpen: false,
|
||||
showChangeLibName: false,
|
||||
isShowSharedDialog: false,
|
||||
highlight: false,
|
||||
isChangeRepoPasswordDialogOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
handleMouseOver = () => {
|
||||
if (!this.props.isItemFreezed) {
|
||||
this.setState({
|
||||
showOpIcon: true,
|
||||
highlight: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseOut = () => {
|
||||
if (!this.props.isItemFreezed) {
|
||||
this.setState({
|
||||
showOpIcon: false,
|
||||
highlight: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
clickOperationMenuToggle = (e) => {
|
||||
e.preventDefault();
|
||||
this.toggleOperationMenu(e);
|
||||
}
|
||||
|
||||
toggleOperationMenu = (e) => {
|
||||
if (e.target.dataset.toggle !== 'item') {
|
||||
if (this.props.isItemFreezed) {
|
||||
this.setState({
|
||||
highlight: false,
|
||||
showOpIcon: false,
|
||||
});
|
||||
}
|
||||
this.setState({
|
||||
operationMenuOpen: !this.state.operationMenuOpen
|
||||
});
|
||||
this.props.onItemFreezedToggle();
|
||||
}
|
||||
}
|
||||
|
||||
share = (e) => {
|
||||
e.preventDefault();
|
||||
this.setState({isShowSharedDialog: true});
|
||||
}
|
||||
|
||||
toggleShareDialog = () => {
|
||||
this.setState({isShowSharedDialog: false});
|
||||
}
|
||||
|
||||
showDeleteItemPopup = (e) => {
|
||||
e.preventDefault(); // for `<a>`
|
||||
const data = this.props.data;
|
||||
this.props.showDeleteItemPopup({
|
||||
repoName: data.repo_name,
|
||||
yesCallback: this.deleteItem,
|
||||
_this: this
|
||||
});
|
||||
}
|
||||
|
||||
deleteItem = () => {
|
||||
const repo = this.props.data;
|
||||
seafileAPI.deleteRepo(repo.repo_id).then((res) => {
|
||||
this.props.onDeleteRepo(repo);
|
||||
// TODO: show feedback msg
|
||||
}).catch((error) => {
|
||||
|
||||
// TODO: show feedback msg
|
||||
});
|
||||
}
|
||||
|
||||
onRenameToggle = () => {
|
||||
this.setState({
|
||||
showOpIcon: false,
|
||||
showChangeLibName: true,
|
||||
operationMenuOpen: false,
|
||||
});
|
||||
}
|
||||
|
||||
onRenameConfirm = (newName) => {
|
||||
let repo = this.props.data;
|
||||
let repoID = repo.repo_id;
|
||||
seafileAPI.renameRepo(repoID, newName).then(() => {
|
||||
this.props.onRenameRepo(repo, newName);
|
||||
this.onRenameCancel();
|
||||
});
|
||||
}
|
||||
|
||||
onRenameCancel = () => {
|
||||
this.setState({
|
||||
showChangeLibName: !this.state.showChangeLibName,
|
||||
});
|
||||
this.props.onItemFreezedToggle();
|
||||
}
|
||||
|
||||
transfer = () => {
|
||||
const itemName = this.props.data.repo_name;
|
||||
const itemID = this.props.data.repo_id;
|
||||
this.props.onTransfer(itemName, itemID);
|
||||
}
|
||||
|
||||
historySetting = () => {
|
||||
const itemName = this.props.data.repo_name;
|
||||
const itemID = this.props.data.repo_id;
|
||||
this.props.onHistorySetting(itemName, itemID);
|
||||
}
|
||||
|
||||
changePassword = () => {
|
||||
this.setState({isChangeRepoPasswordDialogOpen: true});
|
||||
}
|
||||
|
||||
toggleChangePasswordDialog = () => {
|
||||
this.setState({isChangeRepoPasswordDialogOpen: !this.state.isChangeRepoPasswordDialogOpen});
|
||||
}
|
||||
|
||||
resetEncryptedRepoPassword = () => {
|
||||
const repoName = this.props.data.repo_name;
|
||||
const repoID = this.props.data.repo_id;
|
||||
this.props.onResetEncryptedRepoPassword(repoName, repoID);
|
||||
}
|
||||
|
||||
folderPerm = () => {
|
||||
}
|
||||
|
||||
// on '<tr>'
|
||||
onItemClick = (e) => {
|
||||
// '<td>' is clicked
|
||||
if (e.target.tagName == 'TD') {
|
||||
this.props.onItemClick(this.props.data);
|
||||
}
|
||||
}
|
||||
|
||||
showDetails = () => {
|
||||
let data = this.props.data;
|
||||
this.props.onRepoDetails(data);
|
||||
}
|
||||
|
||||
label = () => {
|
||||
}
|
||||
|
||||
render() {
|
||||
const data = this.props.data;
|
||||
const showResetPasswordMenuItem = data.encrypted && enableResetEncryptedRepoPassword && isEmailConfigured;
|
||||
|
||||
data.icon_url = Utils.getLibIconUrl(data);
|
||||
data.icon_title = Utils.getLibIconTitle(data);
|
||||
data.url = `${siteRoot}library/${data.repo_id}/${Utils.encodePath(data.repo_name)}/`;
|
||||
|
||||
let iconVisibility = this.state.showOpIcon ? '' : ' invisible';
|
||||
let shareIconClassName = 'op-icon sf2-icon-share' + iconVisibility;
|
||||
let deleteIconClassName = 'op-icon sf2-icon-delete' + iconVisibility;
|
||||
let operationMenuToggleIconClassName = 'sf-dropdown-toggle sf2-icon-caret-down';
|
||||
if (window.innerWidth >= 768) {
|
||||
operationMenuToggleIconClassName += iconVisibility;
|
||||
}
|
||||
|
||||
const commonToggle = (
|
||||
<DropdownToggle
|
||||
tag="i"
|
||||
className={operationMenuToggleIconClassName}
|
||||
title={gettext('More Operations')}
|
||||
// onClick={this.clickOperationMenuToggle}
|
||||
data-toggle="dropdown"
|
||||
aria-expanded={this.state.operationMenuOpen}
|
||||
/>
|
||||
);
|
||||
const commonOperationsInMenu = (
|
||||
<React.Fragment>
|
||||
<DropdownItem data-toggle="item" onClick={this.onRenameToggle}>{gettext('Rename')}</DropdownItem>
|
||||
<DropdownItem onClick={this.transfer}>{gettext('Transfer')}</DropdownItem>
|
||||
<DropdownItem onClick={this.historySetting}>{gettext('History Setting')}</DropdownItem>
|
||||
{data.encrypted ? <DropdownItem onClick={this.changePassword}>{gettext('Change Password')}</DropdownItem> : ''}
|
||||
{showResetPasswordMenuItem ? <DropdownItem onClick={this.resetEncryptedRepoPassword}>{gettext('Reset Password')}</DropdownItem> : ''}
|
||||
{folderPermEnabled ? <DropdownItem onClick={this.folderPerm}>{gettext('Folder Permission')}</DropdownItem> : ''}
|
||||
<DropdownItem onClick={this.showDetails}>{gettext('Details')}</DropdownItem>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
const desktopOperations = (
|
||||
<div>
|
||||
<a href="#" className={shareIconClassName} title={gettext('Share')} onClick={this.share}></a>
|
||||
<a href="#" className={deleteIconClassName} title={gettext('Delete')} onClick={this.showDeleteItemPopup}></a>
|
||||
<Dropdown isOpen={this.state.operationMenuOpen} toggle={this.toggleOperationMenu}>
|
||||
{commonToggle}
|
||||
<DropdownMenu>
|
||||
{commonOperationsInMenu}
|
||||
{enableRepoSnapshotLabel ? <DropdownItem onClick={this.label}>{gettext('Label current state')}</DropdownItem> : ''}
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
|
||||
const mobileOperations = (
|
||||
<Dropdown isOpen={this.state.operationMenuOpen} toggle={this.toggleOperationMenu}>
|
||||
{commonToggle}
|
||||
<div className={`${this.state.operationMenuOpen ? '' : 'd-none'}`} onClick={this.toggleOperationMenu}>
|
||||
<div className="mobile-operation-menu-bg-layer"></div>
|
||||
<div className="mobile-operation-menu">
|
||||
<DropdownItem onClick={this.share}>{gettext('Share')}</DropdownItem>
|
||||
<DropdownItem onClick={this.showDeleteItemPopup}>{gettext('Delete')}</DropdownItem>
|
||||
{commonOperationsInMenu}
|
||||
</div>
|
||||
</div>
|
||||
</Dropdown>
|
||||
);
|
||||
|
||||
const desktopItem = (
|
||||
<Fragment>
|
||||
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut} onClick={this.onItemClick}>
|
||||
<td><img src={data.icon_url} title={data.icon_title} alt={data.icon_title} width="24" /></td>
|
||||
<td>
|
||||
{this.state.showChangeLibName && (
|
||||
<Rename
|
||||
name={data.repo_name}
|
||||
onRenameConfirm={this.onRenameConfirm}
|
||||
onRenameCancel={this.onRenameCancel}
|
||||
/>
|
||||
)}
|
||||
{!this.state.showChangeLibName && data.repo_name && (
|
||||
<Link to={data.url}>{data.repo_name}</Link>
|
||||
)}
|
||||
{!this.state.showChangeLibName && !data.repo_name &&
|
||||
(gettext('Broken (please contact your administrator to fix this library)'))
|
||||
}
|
||||
</td>
|
||||
<td>{data.repo_name ? desktopOperations : ''}</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>
|
||||
{this.state.isShowSharedDialog && (
|
||||
<ModalPotal>
|
||||
<ShareDialog
|
||||
itemType={'library'}
|
||||
itemName={data.repo_name}
|
||||
itemPath={'/'}
|
||||
repoID={data.repo_id}
|
||||
repoEncrypted={data.encrypted}
|
||||
enableDirPrivateShare={true}
|
||||
userPerm={data.permission}
|
||||
toggleDialog={this.toggleShareDialog}
|
||||
/>
|
||||
</ModalPotal>
|
||||
)}
|
||||
{this.state.isChangeRepoPasswordDialogOpen && (
|
||||
<ModalPotal>
|
||||
<ChangeRepoPasswordDialog
|
||||
repoID={data.repo_id}
|
||||
repoName={data.repo_name}
|
||||
toggleDialog={this.toggleChangePasswordDialog}
|
||||
/>
|
||||
</ModalPotal>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
const mobileItem = (
|
||||
<Fragment>
|
||||
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut} onClick={this.onItemClick}>
|
||||
<td><img src={data.icon_url} title={data.icon_title} alt={data.icon_title} width="24" /></td>
|
||||
<td>
|
||||
{data.repo_name ?
|
||||
<Link to={data.url}>{data.repo_name}</Link> :
|
||||
gettext('Broken (please contact your administrator to fix this library)')}
|
||||
<br />
|
||||
<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>
|
||||
</tr>
|
||||
{this.state.isShowSharedDialog && (
|
||||
<ModalPotal>
|
||||
<ShareDialog
|
||||
itemType={'library'}
|
||||
itemName={data.repo_name}
|
||||
itemPath={'/'}
|
||||
repoID={data.repo_id}
|
||||
repoEncrypted={data.encrypted}
|
||||
enableDirPrivateShare={true}
|
||||
userPerm={data.permission}
|
||||
toggleDialog={this.toggleShareDialog}
|
||||
/>
|
||||
</ModalPotal>
|
||||
)}
|
||||
{this.state.isChangeRepoPasswordDialogOpen && (
|
||||
<ModalPotal>
|
||||
<ChangeRepoPasswordDialog
|
||||
repoID={data.repo_id}
|
||||
repoName={data.repo_name}
|
||||
toggleDialog={this.toggleChangePasswordDialog}
|
||||
/>
|
||||
</ModalPotal>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
return window.innerWidth >= 768 ? desktopItem : mobileItem;
|
||||
}
|
||||
}
|
||||
|
||||
Item.propTypes = propTypes;
|
||||
|
||||
export default Item;
|
@@ -3,22 +3,30 @@ import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { gettext, loginUrl} from '../../utils/constants';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import Repo from '../../models/repo';
|
||||
import Loading from '../../components/loading';
|
||||
import CommonToolbar from '../../components/toolbar/common-toolbar';
|
||||
import RepoViewToolbar from '../../components/toolbar/repo-view-toobar';
|
||||
import LibDetail from '../../components/dirent-detail/lib-details';
|
||||
import Content from './content';
|
||||
import MylibRepoListView from './mylib-repo-list-view';
|
||||
|
||||
class MyLibraries extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isShowDetails: false,
|
||||
loading: true,
|
||||
errorMsg: '',
|
||||
items: [],
|
||||
isLoading: true,
|
||||
repoList: [],
|
||||
isShowDetails: false,
|
||||
sortBy: 'name', // 'name' or 'time'
|
||||
sortOrder: 'asc' // 'asc' or 'desc'
|
||||
};
|
||||
|
||||
this.emptyMessage = (
|
||||
<div className="empty-tip">
|
||||
<h2>{gettext('You have not created any libraries')}</h2>
|
||||
<p>{gettext('You can create a library to organize your files. For example, you can create one for each of your projects. Each library can be synchronized and shared separately.')}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -28,26 +36,26 @@ class MyLibraries extends Component {
|
||||
return new Repo(item);
|
||||
});
|
||||
this.setState({
|
||||
loading: false,
|
||||
items: Utils.sortRepos(repoList, this.state.sortBy, this.state.sortOrder)
|
||||
isLoading: false,
|
||||
repoList: Utils.sortRepos(repoList, this.state.sortBy, this.state.sortOrder)
|
||||
});
|
||||
}).catch((error) => {
|
||||
if (error.response) {
|
||||
if (error.response.status == 403) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
isLoading: false,
|
||||
errorMsg: gettext('Permission denied')
|
||||
});
|
||||
location.href = `${loginUrl}?next=${encodeURIComponent(location.href)}`;
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
isLoading: false,
|
||||
errorMsg: gettext('Error')
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
isLoading: false,
|
||||
errorMsg: gettext('Please check the network.')
|
||||
});
|
||||
}
|
||||
@@ -66,44 +74,44 @@ class MyLibraries extends Component {
|
||||
encrypted: res.data.encrypted,
|
||||
permission: permission,
|
||||
};
|
||||
this.state.items.unshift(repo);
|
||||
this.setState({items: this.state.items});
|
||||
this.state.repoList.unshift(repo);
|
||||
this.setState({repoList: this.state.repoList});
|
||||
});
|
||||
}
|
||||
|
||||
sortItems = (sortBy, sortOrder) => {
|
||||
sortRepoList = (sortBy, sortOrder) => {
|
||||
this.setState({
|
||||
sortBy: sortBy,
|
||||
sortOrder: sortOrder,
|
||||
items: Utils.sortRepos(this.state.items, sortBy, sortOrder)
|
||||
repoList: Utils.sortRepos(this.state.repoList, sortBy, sortOrder)
|
||||
});
|
||||
}
|
||||
|
||||
onTransferRepo = (repoID) => {
|
||||
let items = this.state.items.filter(item => {
|
||||
let repoList = this.state.repoList.filter(item => {
|
||||
return item.repo_id !== repoID;
|
||||
});
|
||||
this.setState({items: items});
|
||||
this.setState({repoList: repoList});
|
||||
}
|
||||
|
||||
onRenameRepo = (repo, newName) => {
|
||||
let items = this.state.items.map(item => {
|
||||
let repoList = this.state.repoList.map(item => {
|
||||
if (item.repo_id === repo.repo_id) {
|
||||
item.repo_name = newName;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
this.setState({items: items});
|
||||
this.setState({repoList: repoList});
|
||||
}
|
||||
|
||||
onDeleteRepo = (repo) => {
|
||||
let items = this.state.items.filter(item => {
|
||||
let repoList = this.state.repoList.filter(item => {
|
||||
return item.repo_id !== repo.repo_id;
|
||||
});
|
||||
this.setState({items: items});
|
||||
this.setState({repoList: repoList});
|
||||
}
|
||||
|
||||
onItemClick = (repo) => {
|
||||
onRepoClick = (repo) => {
|
||||
if (this.state.isShowDetails) {
|
||||
this.onRepoDetails(repo);
|
||||
}
|
||||
@@ -111,15 +119,13 @@ class MyLibraries extends Component {
|
||||
|
||||
onRepoDetails = (repo) => {
|
||||
this.setState({
|
||||
currentRepo: repo,
|
||||
isShowDetails: true,
|
||||
currentRepo: repo
|
||||
});
|
||||
}
|
||||
|
||||
closeDetails = () => {
|
||||
this.setState({
|
||||
isShowDetails: !this.state.isShowDetails
|
||||
});
|
||||
this.setState({isShowDetails: !this.state.isShowDetails});
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -135,19 +141,22 @@ class MyLibraries extends Component {
|
||||
<h3 className="sf-heading">{gettext('My Libraries')}</h3>
|
||||
</div>
|
||||
<div className="cur-view-content">
|
||||
<Content
|
||||
loading={this.state.loading}
|
||||
errorMsg={this.state.errorMsg}
|
||||
items={this.state.items}
|
||||
{this.state.isLoading && <Loading />}
|
||||
{!this.state.isLoading && this.state.errorMsg && <p className="error text-center">{this.state.errorMsg}</p>}
|
||||
{!this.state.isLoading && this.state.repoList.length === 0 && this.emptyMessage}
|
||||
{!this.state.isLoading && this.state.repoList.length > 0 &&
|
||||
<MylibRepoListView
|
||||
sortBy={this.state.sortBy}
|
||||
sortOrder={this.state.sortOrder}
|
||||
sortItems={this.sortItems}
|
||||
onDeleteRepo={this.onDeleteRepo}
|
||||
repoList={this.state.repoList}
|
||||
onRenameRepo={this.onRenameRepo}
|
||||
onDeleteRepo={this.onDeleteRepo}
|
||||
onTransferRepo={this.onTransferRepo}
|
||||
onRepoDetails={this.onRepoDetails}
|
||||
onItemClick={this.onItemClick}
|
||||
onRepoClick={this.onRepoClick}
|
||||
sortRepoList={this.sortRepoList}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{this.state.isShowDetails && (
|
||||
|
342
frontend/src/pages/my-libs/mylib-repo-list-item.js
Normal file
342
frontend/src/pages/my-libs/mylib-repo-list-item.js
Normal file
@@ -0,0 +1,342 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import MediaQuery from 'react-responsive';
|
||||
import moment from 'moment';
|
||||
import { Link } from '@reach/router';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { gettext, siteRoot, storages } from '../../utils/constants';
|
||||
import ModalPortal from '../../components/modal-portal';
|
||||
import ShareDialog from '../../components/dialog/share-dialog';
|
||||
import DeleteRepoDialog from '../../components/dialog/delete-repo-dialog';
|
||||
import TransferDialog from '../../components/dialog/transfer-dialog';
|
||||
import LibHistorySettingDialog from '../../components/dialog/lib-history-setting-dialog';
|
||||
import ChangeRepoPasswordDialog from '../../components/dialog/change-repo-password-dialog';
|
||||
import ResetEncryptedRepoPasswordDialog from '../../components/dialog/reset-encrypted-repo-password-dialog';
|
||||
import Rename from '../../components/rename';
|
||||
import MylibRepoMenu from './mylib-repo-menu';
|
||||
|
||||
const propTypes = {
|
||||
repo: PropTypes.object.isRequired,
|
||||
isItemFreezed: PropTypes.bool.isRequired,
|
||||
onFreezedItem: PropTypes.func.isRequired,
|
||||
onUnfreezedItem: PropTypes.func.isRequired,
|
||||
onRenameRepo: PropTypes.func.isRequired,
|
||||
onDeleteRepo: PropTypes.func.isRequired,
|
||||
onTransferRepo: PropTypes.func.isRequired,
|
||||
onRepoDetails: PropTypes.func.isRequired,
|
||||
onRepoClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
class MylibRepoListItem extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isOpIconShow: false,
|
||||
isRenaming: false,
|
||||
isShareDialogShow: false,
|
||||
isDeleteDialogShow: false,
|
||||
isTransferDialogShow: false,
|
||||
isHistorySettingDialogShow: false,
|
||||
isChangePasswordDialogShow: false,
|
||||
isResetPasswordDialogShow: false,
|
||||
};
|
||||
}
|
||||
|
||||
onMouseEnter = () => {
|
||||
if (!this.props.isItemFreezed) {
|
||||
this.setState({
|
||||
isOpIconShow: true,
|
||||
highlight: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onMouseLeave = () => {
|
||||
if (!this.props.isItemFreezed) {
|
||||
this.setState({
|
||||
isOpIconShow: false,
|
||||
highlight: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onMenuItemClick = (item) => {
|
||||
switch(item) {
|
||||
case 'Share':
|
||||
this.onShareToggle();
|
||||
break;
|
||||
case 'Delete':
|
||||
this.onDeleteToggle();
|
||||
break;
|
||||
case 'Rename':
|
||||
this.onRenameToggle();
|
||||
break;
|
||||
case 'Transfer':
|
||||
this.onTransferToggle();
|
||||
break;
|
||||
case 'History Setting':
|
||||
this.onHistorySettingToggle();
|
||||
break;
|
||||
case 'Change Password':
|
||||
this.onChangePasswordToggle();
|
||||
break;
|
||||
case 'Reset Password':
|
||||
this.onResetPasswordToggle();
|
||||
break;
|
||||
case 'Folder Permission':
|
||||
// todo
|
||||
break;
|
||||
case 'Details':
|
||||
this.onRepoDetails();
|
||||
break;
|
||||
case 'Label current state':
|
||||
this.onLabel();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
onRepoClick = () => {
|
||||
this.props.onRepoClick(this.props.repo);
|
||||
}
|
||||
|
||||
onShareToggle = () => {
|
||||
this.setState({isShareDialogShow: !this.state.isShareDialogShow});
|
||||
}
|
||||
|
||||
onDeleteToggle = () => {
|
||||
this.setState({isDeleteDialogShow: !this.state.isDeleteDialogShow});
|
||||
}
|
||||
|
||||
onRenameToggle = () => {
|
||||
this.props.onFreezedItem();
|
||||
this.setState({isRenaming: !this.state.isRenaming});
|
||||
}
|
||||
|
||||
onTransferToggle = () => {
|
||||
this.setState({isTransferDialogShow: !this.state.isTransferDialogShow});
|
||||
}
|
||||
|
||||
onHistorySettingToggle = () => {
|
||||
this.setState({isHistorySettingDialogShow: !this.state.isHistorySettingDialogShow});
|
||||
}
|
||||
|
||||
onChangePasswordToggle = () => {
|
||||
this.setState({isChangePasswordDialogShow: !this.state.isChangePasswordDialogShow});
|
||||
}
|
||||
|
||||
onResetPasswordToggle = () => {
|
||||
this.setState({isResetPasswordDialogShow: !this.state.isResetPasswordDialogShow});
|
||||
}
|
||||
|
||||
onRepoDetails = () => {
|
||||
this.props.onRepoDetails(this.props.repo);
|
||||
}
|
||||
|
||||
onLabel = () => {
|
||||
// todo
|
||||
}
|
||||
|
||||
onUnfreezedItem = () => {
|
||||
this.setState({
|
||||
highlight: false,
|
||||
isOpIconShow: false,
|
||||
});
|
||||
this.props.onUnfreezedItem();
|
||||
}
|
||||
|
||||
onRenameConfirm = (newName) => {
|
||||
let repo = this.props.repo;
|
||||
let repoID = repo.repo_id;
|
||||
seafileAPI.renameRepo(repoID, newName).then(() => {
|
||||
this.props.onRenameRepo(repo, newName);
|
||||
this.onRenameCancel();
|
||||
});
|
||||
}
|
||||
|
||||
onRenameCancel = () => {
|
||||
this.props.onUnfreezedItem();
|
||||
this.setState({isRenaming: !this.state.isRenaming});
|
||||
}
|
||||
|
||||
onTransferRepo = (repoID) => {
|
||||
this.onTransferToggle();
|
||||
this.props.onTransferRepo(repoID);
|
||||
}
|
||||
|
||||
onDeleteRepo = (repo) => {
|
||||
seafileAPI.deleteRepo(repo.repo_id).then((res) => {
|
||||
this.props.onDeleteRepo(repo);
|
||||
|
||||
// TODO: show feedback msg
|
||||
}).catch((error) => {
|
||||
|
||||
// TODO: show feedback msg
|
||||
});
|
||||
}
|
||||
|
||||
renderPCUI = () => {
|
||||
let repo = this.props.repo;
|
||||
let iconUrl = Utils.getLibIconUrl(repo);
|
||||
let iconTitle = Utils.getLibIconTitle(repo);
|
||||
let repoURL = `${siteRoot}library/${repo.repo_id}/${Utils.encodePath(repo.repo_name)}/`;
|
||||
return (
|
||||
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} onClick={this.onRepoClick}>
|
||||
<td><img src={iconUrl} title={iconTitle} alt={iconTitle} width="24" /></td>
|
||||
<td>
|
||||
{this.state.isRenaming && (
|
||||
<Rename
|
||||
name={repo.repo_name}
|
||||
onRenameConfirm={this.onRenameConfirm}
|
||||
onRenameCancel={this.onRenameCancel}
|
||||
/>
|
||||
)}
|
||||
{!this.state.isRenaming && repo.repo_name && (
|
||||
<Link to={repoURL}>{repo.repo_name}</Link>
|
||||
)}
|
||||
{!this.state.isRenaming && !repo.repo_name &&
|
||||
(gettext('Broken (please contact your administrator to fix this library)'))
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
{(repo.repo_name && this.state.isOpIconShow) && (
|
||||
<div>
|
||||
<a href="#" className="op-icon sf2-icon-share" title={gettext('Share')} onClick={this.onShareToggle}></a>
|
||||
<a href="#" className="op-icon sf2-icon-delete" title={gettext('Delete')} onClick={this.onDeleteToggle}></a>
|
||||
<MylibRepoMenu
|
||||
isPC={true}
|
||||
repo={this.props.repo}
|
||||
onMenuItemClick={this.onMenuItemClick}
|
||||
onFreezedItem={this.props.onFreezedItem}
|
||||
onUnfreezedItem={this.onUnfreezedItem}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
<td>{repo.size}</td>
|
||||
{storages.length > 0 && <td>{repo.storage_name}</td>}
|
||||
<td title={moment(repo.last_modified).format('llll')}>{moment(repo.last_modified).fromNow()}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
renderMobileUI = () => {
|
||||
let repo = this.props.repo;
|
||||
let iconUrl = Utils.getLibIconUrl(repo);
|
||||
let iconTitle = Utils.getLibIconTitle(repo);
|
||||
let repoURL = `${siteRoot}library/${repo.repo_id}/${Utils.encodePath(repo.repo_name)}/`;
|
||||
|
||||
return (
|
||||
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} onClick={this.onRepoClick}>
|
||||
<td><img src={iconUrl} title={iconTitle} alt={iconTitle} width="24" /></td>
|
||||
<td>
|
||||
{this.state.isRenaming && (
|
||||
<Rename
|
||||
name={repo.repo_name}
|
||||
onRenameConfirm={this.onRenameConfirm}
|
||||
onRenameCancel={this.onRenameCancel}
|
||||
/>
|
||||
)}
|
||||
{!this.state.isRenaming && repo.repo_name && (
|
||||
<Link to={repoURL}>{repo.repo_name}</Link>
|
||||
)}
|
||||
{!this.state.isRenaming && !repo.repo_name &&
|
||||
(gettext('Broken (please contact your administrator to fix this library)'))
|
||||
}<br />
|
||||
<span className="item-meta-info">{repo.size}</span>
|
||||
<span className="item-meta-info" title={moment(repo.last_modified).format('llll')}>{moment(repo.last_modified).fromNow()}</span>
|
||||
</td>
|
||||
<td>
|
||||
{repo.repo_name && (
|
||||
<MylibRepoMenu
|
||||
repo={this.props.repo}
|
||||
onMenuItemClick={this.onMenuItemClick}
|
||||
onFreezedItem={this.props.onFreezedItem}
|
||||
onUnfreezedItem={this.onUnfreezedItem}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
let repo = this.props.repo;
|
||||
return (
|
||||
<Fragment>
|
||||
<MediaQuery query="(min-device-width: 768px)">
|
||||
{this.renderPCUI()}
|
||||
</MediaQuery>
|
||||
<MediaQuery query="(max-device-width: 768px)">
|
||||
{this.renderMobileUI()}
|
||||
</MediaQuery>
|
||||
{this.state.isShareDialogShow && (
|
||||
<ModalPortal>
|
||||
<ShareDialog
|
||||
itemType={'library'}
|
||||
itemName={repo.repo_name}
|
||||
itemPath={'/'}
|
||||
repoID={repo.repo_id}
|
||||
repoEncrypted={repo.encrypted}
|
||||
enableDirPrivateShare={true}
|
||||
userPerm={repo.permission}
|
||||
toggleDialog={this.onShareToggle}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
{this.state.isDeleteDialogShow && (
|
||||
<ModalPortal>
|
||||
<DeleteRepoDialog
|
||||
repo={repo}
|
||||
onDeleteRepo={this.onDeleteRepo}
|
||||
toggle={this.onDeleteToggle}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
{this.state.isTransferDialogShow && (
|
||||
<ModalPortal>
|
||||
<TransferDialog
|
||||
repoID={repo.repo_id}
|
||||
itemName={repo.repo_name}
|
||||
submit={this.onTransferRepo}
|
||||
toggleDialog={this.onTransferToggle}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
{this.state.isHistorySettingDialogShow && (
|
||||
<ModalPortal>
|
||||
<LibHistorySettingDialog
|
||||
repoID={repo.repo_id}
|
||||
itemName={repo.repo_name}
|
||||
toggleDialog={this.onHistorySettingToggle}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
{this.state.isChangePasswordDialogShow && (
|
||||
<ModalPortal>
|
||||
<ChangeRepoPasswordDialog
|
||||
repoID={repo.repo_id}
|
||||
repoName={repo.repo_name}
|
||||
toggleDialog={this.onChangePasswordToggle}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
{this.state.isResetPasswordDialogShow && (
|
||||
<ModalPortal>
|
||||
<ResetEncryptedRepoPasswordDialog
|
||||
repoID={repo.repo_id}
|
||||
toggleDialog={this.onResetPasswordToggle}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
MylibRepoListItem.propTypes = propTypes;
|
||||
|
||||
export default MylibRepoListItem;
|
133
frontend/src/pages/my-libs/mylib-repo-list-view.js
Normal file
133
frontend/src/pages/my-libs/mylib-repo-list-view.js
Normal file
@@ -0,0 +1,133 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import MediaQuery from 'react-responsive';
|
||||
import { gettext, storages } from '../../utils/constants';
|
||||
import MylibRepoListItem from './mylib-repo-list-item';
|
||||
|
||||
const propTypes = {
|
||||
sortBy: PropTypes.string.isRequired,
|
||||
sortOrder: PropTypes.string.isRequired,
|
||||
repoList: PropTypes.array.isRequired,
|
||||
sortRepoList: PropTypes.func.isRequired,
|
||||
onRenameRepo: PropTypes.func.isRequired,
|
||||
onDeleteRepo: PropTypes.func.isRequired,
|
||||
onTransferRepo: PropTypes.func.isRequired,
|
||||
onRepoDetails: PropTypes.func.isRequired,
|
||||
onRepoClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
class MylibRepoListView extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isItemFreezed: false,
|
||||
};
|
||||
}
|
||||
|
||||
onFreezedItem = () => {
|
||||
this.setState({isItemFreezed: true});
|
||||
}
|
||||
|
||||
onUnfreezedItem = () => {
|
||||
this.setState({isItemFreezed: false});
|
||||
}
|
||||
|
||||
sortByName = (e) => {
|
||||
e.preventDefault();
|
||||
const sortBy = 'name';
|
||||
const sortOrder = this.props.sortOrder == 'asc' ? 'desc' : 'asc';
|
||||
this.props.sortRepoList(sortBy, sortOrder);
|
||||
}
|
||||
|
||||
sortByTime = (e) => {
|
||||
e.preventDefault();
|
||||
const sortBy = 'time';
|
||||
const sortOrder = this.props.sortOrder == 'asc' ? 'desc' : 'asc';
|
||||
this.props.sortRepoList(sortBy, sortOrder);
|
||||
}
|
||||
|
||||
renderRepoListView = () => {
|
||||
return (
|
||||
<Fragment>
|
||||
{this.props.repoList.map(item => {
|
||||
return (
|
||||
<MylibRepoListItem
|
||||
key={item.repo_id}
|
||||
repo={item}
|
||||
isItemFreezed={this.state.isItemFreezed}
|
||||
onFreezedItem={this.onFreezedItem}
|
||||
onUnfreezedItem={this.onUnfreezedItem}
|
||||
onRenameRepo={this.props.onRenameRepo}
|
||||
onDeleteRepo={this.props.onDeleteRepo}
|
||||
onTransferRepo={this.props.onTransferRepo}
|
||||
onRepoDetails={this.props.onRepoDetails}
|
||||
onRepoClick={this.props.onRepoClick}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
renderPCUI = () => {
|
||||
const showStorageBackend = storages.length > 0;
|
||||
const sortIcon = this.props.sortOrder === 'asc' ? <span className="fas fa-caret-up"></span> : <span className="fas fa-caret-down"></span>;
|
||||
return (
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="4%"><span className="sr-only">{gettext('Library Type')}</span></th>
|
||||
<th width="42%"><a className="d-block table-sort-op" href="#" onClick={this.sortByName}>{gettext('Name')} {this.props.sortBy === 'name' && 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%'}><a className="d-block table-sort-op" href="#" onClick={this.sortByTime}>{gettext('Last Update')} {this.props.sortBy === 'time' && sortIcon}</a></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{this.renderRepoListView()}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
renderMobileUI = () => {
|
||||
const sortIcon = this.props.sortOrder === 'asc' ? <span className="fas fa-caret-up"></span> : <span className="fas fa-caret-down"></span>;
|
||||
return (
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="18%"><span className="sr-only">{gettext('Library Type')}</span></th>
|
||||
<th width="76%">
|
||||
{gettext('Sort:')}
|
||||
<a className="table-sort-op" href="#" onClick={this.sortByName}>{gettext('name')} {this.props.sortBy === 'name' && sortIcon}</a>
|
||||
<a className="table-sort-op" href="#" onClick={this.sortByTime}>{gettext('last update')} {this.props.sortBy === 'time' && sortIcon}</a>
|
||||
</th>
|
||||
<th width="6%"><span className="sr-only">{gettext('Actions')}</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{this.renderRepoListView()}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
<MediaQuery query="(min-device-width: 768px)">
|
||||
{this.renderPCUI()}
|
||||
</MediaQuery>
|
||||
<MediaQuery query="(max-device-width: 768px)">
|
||||
{this.renderMobileUI()}
|
||||
</MediaQuery>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MylibRepoListView.propTypes = propTypes;
|
||||
|
||||
export default MylibRepoListView;
|
164
frontend/src/pages/my-libs/mylib-repo-menu.js
Normal file
164
frontend/src/pages/my-libs/mylib-repo-menu.js
Normal file
@@ -0,0 +1,164 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap';
|
||||
import { gettext, folderPermEnabled, enableRepoSnapshotLabel, enableResetEncryptedRepoPassword, isEmailConfigured } from '../../utils/constants';
|
||||
|
||||
const propTypes = {
|
||||
isPC: PropTypes.bool,
|
||||
repo: PropTypes.object.isRequired,
|
||||
onFreezedItem: PropTypes.func.isRequired,
|
||||
onUnfreezedItem: PropTypes.func.isRequired,
|
||||
onMenuItemClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
class MylibRepoMenu extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isItemMenuShow: false,
|
||||
};
|
||||
}
|
||||
|
||||
onMenuItemClick = (e) => {
|
||||
let operation = e.target.dataset.toggle;
|
||||
this.props.onMenuItemClick(operation);
|
||||
}
|
||||
|
||||
onDropdownToggleClick = (e) => {
|
||||
this.toggleOperationMenu(e);
|
||||
}
|
||||
|
||||
toggleOperationMenu = (e) => {
|
||||
let dataset = e.target ? e.target.dataset : null;
|
||||
if (dataset && dataset.toggle && dataset.toggle === 'Rename') {
|
||||
this.setState({isItemMenuShow: !this.state.isItemMenuShow});
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(
|
||||
{isItemMenuShow: !this.state.isItemMenuShow},
|
||||
() => {
|
||||
if (this.state.isItemMenuShow) {
|
||||
this.props.onFreezedItem();
|
||||
} else {
|
||||
this.props.onUnfreezedItem();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
generatorOperations = () => {
|
||||
let repo = this.props.repo;
|
||||
let showResetPasswordMenuItem = repo.encrypted && enableResetEncryptedRepoPassword && isEmailConfigured;
|
||||
let operations = ['Rename', 'Transfer', 'History Setting'];
|
||||
if (repo.encrypted) {
|
||||
operations.push('Change Password');
|
||||
}
|
||||
if (showResetPasswordMenuItem) {
|
||||
operations.push('Reset Password')
|
||||
}
|
||||
if (folderPermEnabled) {
|
||||
operations.push('Folder Permission');
|
||||
}
|
||||
operations.push('Details');
|
||||
if (this.props.isPC && enableRepoSnapshotLabel) {
|
||||
operations.push('Label current state');
|
||||
}
|
||||
return operations;
|
||||
}
|
||||
|
||||
translateOperations = (item) => {
|
||||
let translateResult = '';
|
||||
switch(item) {
|
||||
case 'Share':
|
||||
translateResult = gettext('Share');
|
||||
break;
|
||||
case 'Delete':
|
||||
translateResult = gettext('Delete');
|
||||
break;
|
||||
case 'Rename':
|
||||
translateResult = gettext('Rename');
|
||||
break;
|
||||
case 'Transfer':
|
||||
translateResult = gettext('Transfer');
|
||||
break;
|
||||
case 'History Setting':
|
||||
translateResult = gettext('History Setting');
|
||||
break;
|
||||
case 'Change Password':
|
||||
translateResult = gettext('Change Password');
|
||||
break;
|
||||
case 'Reset Password':
|
||||
translateResult = gettext('Reset Password');
|
||||
break;
|
||||
case 'Folder Permission':
|
||||
translateResult = gettext('Folder Permission');
|
||||
break;
|
||||
case 'Details':
|
||||
translateResult = gettext('Details');
|
||||
break;
|
||||
case 'Label current state':
|
||||
translateResult = gettext('Label current state');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return translateResult;
|
||||
}
|
||||
|
||||
render() {
|
||||
let operations = this.generatorOperations();
|
||||
|
||||
// pc menu
|
||||
if (this.props.isPC) {
|
||||
return (
|
||||
<Dropdown isOpen={this.state.isItemMenuShow} toggle={this.toggleOperationMenu}>
|
||||
<DropdownToggle
|
||||
tag="i"
|
||||
className="sf-dropdown-toggle sf2-icon-caret-down"
|
||||
title={gettext('More Operations')}
|
||||
// onClick={this.clickOperationMenuToggle}
|
||||
data-toggle="dropdown"
|
||||
aria-expanded={this.state.isItemMenuShow}
|
||||
/>
|
||||
<DropdownMenu>
|
||||
{operations.map((item, index )=> {
|
||||
return (<DropdownItem key={index} data-toggle={item} onClick={this.onMenuItemClick}>{this.translateOperations(item)}</DropdownItem>);
|
||||
})}
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
|
||||
// mobile menu
|
||||
operations.unshift('Share');
|
||||
operations.unshift('Delete');
|
||||
|
||||
return (
|
||||
<Dropdown isOpen={this.state.isItemMenuShow} toggle={this.toggleOperationMenu}>
|
||||
<DropdownToggle
|
||||
tag="i"
|
||||
className="sf-dropdown-toggle sf2-icon-caret-down"
|
||||
title={gettext('More Operations')}
|
||||
// onClick={this.clickOperationMenuToggle}
|
||||
data-toggle="dropdown"
|
||||
aria-expanded={this.state.isItemMenuShow}
|
||||
/>
|
||||
<div className={`${this.state.isItemMenuShow ? '' : 'd-none'}`} onClick={this.toggleOperationMenu}>
|
||||
<div className="mobile-operation-menu-bg-layer"></div>
|
||||
<div className="mobile-operation-menu">
|
||||
{operations.map((item, index) => {
|
||||
return (<DropdownItem key={index} data-toggle={item} onClick={this.onMenuItemClick}>{this.translateOperations(item)}</DropdownItem>);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MylibRepoMenu.propTypes = propTypes;
|
||||
|
||||
export default MylibRepoMenu;
|
@@ -1,58 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Item from './item';
|
||||
|
||||
const propTypes = {
|
||||
items: PropTypes.array.isRequired,
|
||||
onRenameRepo: PropTypes.func.isRequired,
|
||||
onDeleteRepo: PropTypes.func.isRequired,
|
||||
onTransfer: PropTypes.func.isRequired,
|
||||
onResetEncryptedRepoPassword: PropTypes.func.isRequired,
|
||||
showDeleteItemPopup: PropTypes.func.isRequired,
|
||||
onHistorySetting: PropTypes.func.isRequired,
|
||||
onRepoDetails: PropTypes.func.isRequired,
|
||||
onItemClick: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
class TableBody extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isItemFreezed: false,
|
||||
};
|
||||
}
|
||||
|
||||
onItemFreezedToggle = () => {
|
||||
this.setState({isItemFreezed: !this.state.isItemFreezed});
|
||||
}
|
||||
|
||||
render() {
|
||||
let listItems = this.props.items.map((item, index) => {
|
||||
return (
|
||||
<Item
|
||||
key={index}
|
||||
data={item}
|
||||
isItemFreezed={this.state.isItemFreezed}
|
||||
onItemFreezedToggle={this.onItemFreezedToggle}
|
||||
onRenameRepo={this.props.onRenameRepo}
|
||||
onDeleteRepo={this.props.onDeleteRepo}
|
||||
onTransfer={this.props.onTransfer}
|
||||
onResetEncryptedRepoPassword={this.props.onResetEncryptedRepoPassword}
|
||||
showDeleteItemPopup={this.props.showDeleteItemPopup}
|
||||
onHistorySetting={this.props.onHistorySetting}
|
||||
onRepoDetails={this.props.onRepoDetails}
|
||||
onItemClick={this.props.onItemClick}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<tbody>{listItems}</tbody>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TableBody.propTypes = propTypes;
|
||||
|
||||
export default TableBody;
|
Reference in New Issue
Block a user