mirror of
https://github.com/haiwen/seahub.git
synced 2025-08-27 03:01:26 +00:00
Improve move and copy dialog ui components (#6467)
* implement catalog view in move dialog * update move dialog ui components * improve move dialog ui * remove debug info * improve copy dialog ui * improve search view ui in move and copy dialog, refactor part of file-chooser.js and file-chooser.css * handle cases where repo_name is too long, truncate text with an ellipsis, remove search container border * handle cases where repo_name too long in search result item * update move and dialog ui details * add radius to repo list item
This commit is contained in:
parent
044124e2d8
commit
8623e01e99
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Button, Modal, ModalHeader, ModalFooter, ModalBody, Alert } from 'reactstrap';
|
import { Button, Modal, ModalHeader, ModalFooter, ModalBody, Alert, Row, Col } from 'reactstrap';
|
||||||
import { gettext } from '../../utils/constants';
|
import { gettext } from '../../utils/constants';
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
import FileChooser from '../file-chooser/file-chooser';
|
import FileChooser from '../file-chooser/file-chooser';
|
||||||
@ -25,17 +25,11 @@ class CopyDirent extends React.Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
repo: { repo_id: this.props.repoID },
|
repo: { repo_id: this.props.repoID },
|
||||||
selectedPath: this.props.path,
|
selectedPath: this.props.path,
|
||||||
errMessage: ''
|
errMessage: '',
|
||||||
|
mode: 'only_current_library',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
|
||||||
if (this.state.errMessage === nextState.errMessage) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSubmit = () => {
|
handleSubmit = () => {
|
||||||
if (this.props.isMultipleOperation) {
|
if (this.props.isMultipleOperation) {
|
||||||
this.copyItems();
|
this.copyItems();
|
||||||
@ -137,34 +131,64 @@ class CopyDirent extends React.Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
onSelectedMode = (mode) => {
|
||||||
|
this.setState({ mode: mode });
|
||||||
|
};
|
||||||
|
|
||||||
|
renderTitle = () => {
|
||||||
|
const { dirent, isMultipleOperation } = this.props;
|
||||||
let title = gettext('Copy {placeholder} to');
|
let title = gettext('Copy {placeholder} to');
|
||||||
if (!this.props.isMultipleOperation) {
|
|
||||||
title = title.replace('{placeholder}', '<span class="op-target text-truncate mx-1">' + Utils.HTMLescape(this.props.dirent.name) + '</span>');
|
if (isMultipleOperation) {
|
||||||
|
return gettext('Copy selected item(s) to:');
|
||||||
} else {
|
} else {
|
||||||
title = gettext('Copy selected item(s) to:');
|
return title.replace('{placeholder}', `<span class="op-target text-truncate mx-1">${Utils.HTMLescape(dirent.name)}</span>`);
|
||||||
}
|
}
|
||||||
let mode = 'current_repo_and_other_repos';
|
};
|
||||||
const { isMultipleOperation } = this.props;
|
|
||||||
|
render() {
|
||||||
|
const { dirent, selectedDirentList, isMultipleOperation, repoID, path } = this.props;
|
||||||
|
const { mode, errMessage } = this.state;
|
||||||
|
|
||||||
|
const copiedDirent = dirent || selectedDirentList[0];
|
||||||
|
const { permission } = copiedDirent;
|
||||||
|
const { isCustomPermission } = Utils.getUserPermission(permission);
|
||||||
|
|
||||||
|
const LibraryOption = ({ mode, label }) => (
|
||||||
|
<div className={`repo-list-item ${this.state.mode === mode ? 'active' : ''}`} onClick={() => this.onSelectedMode(mode)}>
|
||||||
|
<span className='library'>{label}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={true} toggle={this.toggle}>
|
<Modal className='custom-modal' isOpen={true} toggle={this.toggle}>
|
||||||
<ModalHeader toggle={this.toggle}>
|
<ModalHeader toggle={this.toggle}>
|
||||||
{isMultipleOperation ? title : <div dangerouslySetInnerHTML={{ __html: title }} className="d-flex mw-100"></div>}
|
{isMultipleOperation ? this.renderTitle() : <div dangerouslySetInnerHTML={{ __html: this.renderTitle() }} className="d-flex mw-100"></div>}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<ModalBody>
|
<Row>
|
||||||
<FileChooser
|
<Col className='repo-list-col border-right'>
|
||||||
repoID={this.props.repoID}
|
<LibraryOption mode='only_current_library' label={gettext('Current Library')} />
|
||||||
currentPath={this.props.path}
|
{!isCustomPermission && <LibraryOption mode='only_other_libraries' label={gettext('Other Libraries')} />}
|
||||||
onDirentItemClick={this.onDirentItemClick}
|
<LibraryOption mode='recently_used' label={gettext('Recently Used')} />
|
||||||
onRepoItemClick={this.onRepoItemClick}
|
</Col>
|
||||||
mode={mode}
|
<Col className='file-list-col'>
|
||||||
/>
|
<ModalBody>
|
||||||
{this.state.errMessage && <Alert color="danger" className="mt-2">{this.state.errMessage}</Alert>}
|
<FileChooser
|
||||||
</ModalBody>
|
repoID={repoID}
|
||||||
<ModalFooter>
|
currentPath={path}
|
||||||
<Button color="secondary" onClick={this.toggle}>{gettext('Cancel')}</Button>
|
onDirentItemClick={this.onDirentItemClick}
|
||||||
<Button color="primary" onClick={this.handleSubmit}>{gettext('Submit')}</Button>
|
onRepoItemClick={this.onRepoItemClick}
|
||||||
</ModalFooter>
|
mode={mode}
|
||||||
|
hideLibraryName={false}
|
||||||
|
/>
|
||||||
|
{errMessage && <Alert color="danger" className="mt-2">{errMessage}</Alert>}
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button color="secondary" onClick={this.toggle}>{gettext('Cancel')}</Button>
|
||||||
|
<Button color="primary" onClick={this.handleSubmit}>{gettext('Submit')}</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Button, Modal, ModalHeader, ModalFooter, ModalBody, Alert } from 'reactstrap';
|
import { Button, Modal, ModalHeader, ModalFooter, ModalBody, Alert, Row, Col } from 'reactstrap';
|
||||||
import { gettext } from '../../utils/constants';
|
import { gettext } from '../../utils/constants';
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
import FileChooser from '../file-chooser/file-chooser';
|
import FileChooser from '../file-chooser/file-chooser';
|
||||||
@ -25,17 +25,11 @@ class MoveDirent extends React.Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
repo: { repo_id: this.props.repoID },
|
repo: { repo_id: this.props.repoID },
|
||||||
selectedPath: this.props.path,
|
selectedPath: this.props.path,
|
||||||
errMessage: ''
|
errMessage: '',
|
||||||
|
mode: 'only_current_library',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
|
||||||
if (this.state.errMessage === nextState.errMessage) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSubmit = () => {
|
handleSubmit = () => {
|
||||||
if (this.props.isMultipleOperation) {
|
if (this.props.isMultipleOperation) {
|
||||||
this.moveItems();
|
this.moveItems();
|
||||||
@ -151,40 +145,64 @@ class MoveDirent extends React.Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
onSelectedMode = (mode) => {
|
||||||
|
this.setState({ mode: mode });
|
||||||
|
};
|
||||||
|
|
||||||
|
renderTitle = () => {
|
||||||
|
const { dirent, isMultipleOperation } = this.props;
|
||||||
let title = gettext('Move {placeholder} to');
|
let title = gettext('Move {placeholder} to');
|
||||||
if (!this.props.isMultipleOperation) {
|
|
||||||
title = title.replace('{placeholder}', '<span class="op-target text-truncate mx-1">' + Utils.HTMLescape(this.props.dirent.name) + '</span>');
|
if (isMultipleOperation) {
|
||||||
|
return gettext('Move selected item(s) to:');
|
||||||
} else {
|
} else {
|
||||||
title = gettext('Move selected item(s) to:');
|
return title.replace('{placeholder}', `<span class="op-target text-truncate mx-1">${Utils.HTMLescape(dirent.name)}</span>`);
|
||||||
}
|
}
|
||||||
let mode = 'current_repo_and_other_repos';
|
};
|
||||||
const { dirent, selectedDirentList, isMultipleOperation } = this.props;
|
|
||||||
const movedDirent = dirent ? dirent : selectedDirentList[0];
|
render() {
|
||||||
|
const { dirent, selectedDirentList, isMultipleOperation, repoID, path } = this.props;
|
||||||
|
const { mode, errMessage } = this.state;
|
||||||
|
|
||||||
|
const movedDirent = dirent || selectedDirentList[0];
|
||||||
const { permission } = movedDirent;
|
const { permission } = movedDirent;
|
||||||
const { isCustomPermission } = Utils.getUserPermission(permission);
|
const { isCustomPermission } = Utils.getUserPermission(permission);
|
||||||
if (isCustomPermission) {
|
|
||||||
mode = 'only_current_library';
|
const LibraryOption = ({ mode, label }) => (
|
||||||
}
|
<div className={`repo-list-item ${this.state.mode === mode ? 'active' : ''}`} onClick={() => this.onSelectedMode(mode)}>
|
||||||
|
<span className='library'>{label}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={true} toggle={this.toggle}>
|
<Modal className='custom-modal' isOpen={true} toggle={this.toggle}>
|
||||||
<ModalHeader toggle={this.toggle}>
|
<ModalHeader toggle={this.toggle}>
|
||||||
{isMultipleOperation ? title : <div dangerouslySetInnerHTML={{ __html: title }} className="d-flex mw-100"></div>}
|
{isMultipleOperation ? this.renderTitle() : <div dangerouslySetInnerHTML={{ __html: this.renderTitle() }} className='d-flex mw-100'></div>}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<ModalBody>
|
<Row>
|
||||||
<FileChooser
|
<Col className='repo-list-col border-right' >
|
||||||
repoID={this.props.repoID}
|
<LibraryOption mode='only_current_library' label={gettext('Current Library')} />
|
||||||
currentPath={this.props.path}
|
{!isCustomPermission && <LibraryOption mode='only_other_libraries' label={gettext('Other Libraries')} />}
|
||||||
onDirentItemClick={this.onDirentItemClick}
|
<LibraryOption mode='recently_used' label={gettext('Recently Used')} />
|
||||||
onRepoItemClick={this.onRepoItemClick}
|
</Col>
|
||||||
mode={mode}
|
<Col className='file-list-col'>
|
||||||
/>
|
<ModalBody>
|
||||||
{this.state.errMessage && <Alert color="danger" className="mt-2">{this.state.errMessage}</Alert>}
|
<FileChooser
|
||||||
</ModalBody>
|
repoID={repoID}
|
||||||
<ModalFooter>
|
currentPath={path}
|
||||||
<Button color="secondary" onClick={this.toggle}>{gettext('Cancel')}</Button>
|
onDirentItemClick={this.onDirentItemClick}
|
||||||
<Button color="primary" onClick={this.handleSubmit}>{gettext('Submit')}</Button>
|
onRepoItemClick={this.onRepoItemClick}
|
||||||
</ModalFooter>
|
mode={mode}
|
||||||
|
hideLibraryName={false}
|
||||||
|
/>
|
||||||
|
{errMessage && <Alert color="danger" className="alert-message">{errMessage}</Alert>}
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button color="secondary" onClick={this.toggle}>{gettext('Cancel')}</Button>
|
||||||
|
<Button color="primary" onClick={this.handleSubmit}>{gettext('Submit')}</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -821,6 +821,7 @@ class DirentGridView extends React.Component {
|
|||||||
repoEncrypted={this.props.currentRepoInfo.encrypted}
|
repoEncrypted={this.props.currentRepoInfo.encrypted}
|
||||||
isMultipleOperation={this.state.isMultipleOperation}
|
isMultipleOperation={this.state.isMultipleOperation}
|
||||||
selectedDirentList={selectedDirentList}
|
selectedDirentList={selectedDirentList}
|
||||||
|
onItemMove={this.props.onItemMove}
|
||||||
onItemsMove={this.props.onItemsMove}
|
onItemsMove={this.props.onItemsMove}
|
||||||
onCancelMove={this.onMoveToggle}
|
onCancelMove={this.onMoveToggle}
|
||||||
dirent={this.state.activeDirent}
|
dirent={this.state.activeDirent}
|
||||||
|
@ -18,11 +18,27 @@ const propTypes = {
|
|||||||
repoID: PropTypes.string,
|
repoID: PropTypes.string,
|
||||||
onDirentItemClick: PropTypes.func,
|
onDirentItemClick: PropTypes.func,
|
||||||
onRepoItemClick: PropTypes.func,
|
onRepoItemClick: PropTypes.func,
|
||||||
mode: PropTypes.oneOf(['current_repo_and_other_repos', 'only_all_repos', 'only_current_library']),
|
mode: PropTypes.oneOf([
|
||||||
fileSuffixes: PropTypes.array,
|
'current_repo_and_other_repos',
|
||||||
|
'only_all_repos',
|
||||||
|
'only_current_library',
|
||||||
|
'only_other_libraries',
|
||||||
|
'recently_used'
|
||||||
|
]).isRequired,
|
||||||
|
fileSuffixes: PropTypes.arrayOf(PropTypes.string),
|
||||||
currentPath: PropTypes.string,
|
currentPath: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
isShowFile: false,
|
||||||
|
hideLibraryName: false,
|
||||||
|
repoID: '',
|
||||||
|
onDirentItemClick: () => {},
|
||||||
|
onRepoItemClick: () => {},
|
||||||
|
fileSuffixes: [],
|
||||||
|
currentPath: '',
|
||||||
|
};
|
||||||
|
|
||||||
class FileChooser extends React.Component {
|
class FileChooser extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -46,71 +62,95 @@ class FileChooser extends React.Component {
|
|||||||
this.source = null;
|
this.source = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
async componentDidMount() {
|
||||||
if (this.props.repoID) { // current_repo_and_other_repos, only_current_library
|
const { repoID } = this.props;
|
||||||
let repoID = this.props.repoID;
|
|
||||||
seafileAPI.getRepoInfo(repoID).then(res => {
|
const fetchRepoInfo = async (repoID) => {
|
||||||
// need to optimized
|
try {
|
||||||
let repoInfo = new RepoInfo(res.data);
|
const res = await seafileAPI.getRepoInfo(repoID);
|
||||||
|
const repoInfo = new RepoInfo(res.data);
|
||||||
this.setState({
|
this.setState({
|
||||||
currentRepoInfo: repoInfo,
|
currentRepoInfo: repoInfo,
|
||||||
selectedRepo: repoInfo
|
selectedRepo: repoInfo,
|
||||||
});
|
});
|
||||||
}).catch(error => {
|
} catch (error) {
|
||||||
let errMessage = Utils.getErrorMsg(error);
|
const errMessage = Utils.getErrorMsg(error);
|
||||||
toaster.danger(errMessage);
|
toaster.danger(errMessage);
|
||||||
});
|
}
|
||||||
} else { // only_all_repos
|
};
|
||||||
seafileAPI.listRepos().then(res => {
|
|
||||||
let repos = res.data.repos;
|
const fetchRepoList = async () => {
|
||||||
let repoList = [];
|
try {
|
||||||
let repoIdList = [];
|
const res = await seafileAPI.listRepos();
|
||||||
for (let i = 0; i < repos.length; i++) {
|
const repos = res.data.repos;
|
||||||
if (repos[i].permission !== 'rw') {
|
const repoList = [];
|
||||||
continue;
|
const repoIdList = [];
|
||||||
|
|
||||||
|
repos.forEach(repo => {
|
||||||
|
if (repo.permission !== 'rw' || repoIdList.includes(repo.repo_id)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (repoIdList.indexOf(repos[i].repo_id) > -1) {
|
repoList.push(repo);
|
||||||
continue;
|
repoIdList.push(repo.repo_id);
|
||||||
}
|
});
|
||||||
repoList.push(repos[i]);
|
|
||||||
repoIdList.push(repos[i].repo_id);
|
const sortedRepoList = Utils.sortRepos(repoList, 'name', 'asc');
|
||||||
}
|
this.setState({ repoList: sortedRepoList });
|
||||||
repoList = Utils.sortRepos(repoList, 'name', 'asc');
|
} catch (error) {
|
||||||
this.setState({ repoList: repoList });
|
const errMessage = Utils.getErrorMsg(error);
|
||||||
});
|
toaster.danger(errMessage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (repoID) {
|
||||||
|
await fetchRepoInfo(repoID);
|
||||||
|
} else {
|
||||||
|
await fetchRepoList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onOtherRepoToggle = () => {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
if (!this.state.hasRequest) {
|
if (prevProps.mode !== this.props.mode) {
|
||||||
let that = this;
|
this.setState({
|
||||||
seafileAPI.listRepos().then(res => {
|
isSearching: false,
|
||||||
// todo optimized code
|
isResultGot: false,
|
||||||
let repos = res.data.repos;
|
searchInfo: '',
|
||||||
let repoList = [];
|
searchResults: [],
|
||||||
let repoIdList = [];
|
|
||||||
for (let i = 0; i < repos.length; i++) {
|
|
||||||
if (repos[i].permission !== 'rw') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (that.props.repoID && (repos[i].repo_name === that.state.currentRepoInfo.repo_name)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (repoIdList.indexOf(repos[i].repo_id) > -1) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
repoList.push(repos[i]);
|
|
||||||
repoIdList.push(repos[i].repo_id);
|
|
||||||
}
|
|
||||||
repoList = Utils.sortRepos(repoList, 'name', 'asc');
|
|
||||||
this.setState({
|
|
||||||
repoList: repoList,
|
|
||||||
isOtherRepoShow: !this.state.isOtherRepoShow,
|
|
||||||
selectedItemInfo: {}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
if (this.props.mode === 'only_other_libraries') {
|
||||||
|
this.onOtherRepoToggle();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
}
|
||||||
|
|
||||||
|
onOtherRepoToggle = async () => {
|
||||||
|
if (!this.state.hasRequest) {
|
||||||
|
try {
|
||||||
|
const res = await seafileAPI.listRepos();
|
||||||
|
const repos = res.data.repos;
|
||||||
|
const repoList = [];
|
||||||
|
const repoIdList = [];
|
||||||
|
|
||||||
|
repos.forEach(repo => {
|
||||||
|
if (repo.permission !== 'rw') return;
|
||||||
|
if (this.props.repoID && repo.repo_name === this.state.currentRepoInfo.repo_name) return;
|
||||||
|
if (repoIdList.includes(repo.repo_id)) return;
|
||||||
|
|
||||||
|
repoList.push(repo);
|
||||||
|
repoIdList.push(repo.repo_id);
|
||||||
|
});
|
||||||
|
|
||||||
|
const sortedRepoList = Utils.sortRepos(repoList, 'name', 'asc');
|
||||||
|
this.setState({
|
||||||
|
repoList: sortedRepoList,
|
||||||
|
isOtherRepoShow: !this.state.isOtherRepoShow,
|
||||||
|
selectedItemInfo: {},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
const errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
this.setState({ isOtherRepoShow: !this.state.isOtherRepoShow });
|
this.setState({ isOtherRepoShow: !this.state.isOtherRepoShow });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -151,32 +191,22 @@ class FileChooser extends React.Component {
|
|||||||
|
|
||||||
onSearchInfoChanged = (event) => {
|
onSearchInfoChanged = (event) => {
|
||||||
let searchInfo = event.target.value.trim();
|
let searchInfo = event.target.value.trim();
|
||||||
|
|
||||||
this.setState({ searchInfo: searchInfo });
|
|
||||||
|
|
||||||
if (this.inputValue === searchInfo) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.inputValue = searchInfo;
|
|
||||||
|
|
||||||
if (searchInfo.length === 0) {
|
|
||||||
this.setState({
|
|
||||||
isSearching: false,
|
|
||||||
searchResults: [],
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.state.searchResults.length && searchInfo.length > 0) {
|
if (!this.state.searchResults.length && searchInfo.length > 0) {
|
||||||
this.setState({
|
this.setState({
|
||||||
isSearching: true,
|
isSearching: true,
|
||||||
isResultGot: false,
|
isResultGot: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
this.setState({ searchInfo: searchInfo });
|
||||||
|
if (this.inputValue === searchInfo) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.inputValue = searchInfo;
|
||||||
|
|
||||||
if (this.inputValue === '' || this.getValueLength(this.inputValue) < 3) {
|
if (this.inputValue === '' || this.getValueLength(this.inputValue) < 3) {
|
||||||
this.setState({ isResultGot: false });
|
this.setState({
|
||||||
|
isResultGot: false,
|
||||||
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,205 +327,248 @@ class FileChooser extends React.Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onSearchedItemDoubleClick = (item) => {
|
onSearchedItemDoubleClick = async (item) => {
|
||||||
if (item.type !== 'dir') {
|
if (item.type !== 'dir') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let selectedItemInfo = {
|
const { repoID } = this.props;
|
||||||
|
const { hasRequest, currentRepoInfo } = this.state;
|
||||||
|
|
||||||
|
const selectedItemInfo = {
|
||||||
repoID: item.repo_id,
|
repoID: item.repo_id,
|
||||||
filePath: item.path,
|
filePath: item.path,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.setState({
|
this.setState({ selectedItemInfo });
|
||||||
selectedItemInfo: selectedItemInfo
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.props.repoID && item.repo_id === this.props.repoID) {
|
const updateStateForRepo = (repoInfo, path) => {
|
||||||
seafileAPI.getRepoInfo(this.props.repoID).then(res => {
|
this.setState({
|
||||||
// need to optimized
|
selectedRepo: repoInfo,
|
||||||
let repoInfo = new RepoInfo(res.data);
|
selectedPath: path,
|
||||||
let path = item.path.substring(0, (item.path.length - 1));
|
isCurrentRepoShow: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleError = (error) => {
|
||||||
|
const errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchRepoInfo = async () => {
|
||||||
|
try {
|
||||||
|
const res = await seafileAPI.getRepoInfo(repoID);
|
||||||
|
const repoInfo = new RepoInfo(res.data);
|
||||||
|
const path = item.path.substring(0, item.path.length - 1);
|
||||||
|
updateStateForRepo(repoInfo, path);
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchRepoList = async () => {
|
||||||
|
try {
|
||||||
|
const res = await seafileAPI.listRepos();
|
||||||
|
const repos = res.data.repos;
|
||||||
|
const repoList = [];
|
||||||
|
const repoIdList = [];
|
||||||
|
|
||||||
|
repos.forEach(repo => {
|
||||||
|
if (repo.permission !== 'rw') return;
|
||||||
|
if (repoID && repo.repo_name === currentRepoInfo.repo_name) return;
|
||||||
|
if (repoIdList.includes(repo.repo_id)) return;
|
||||||
|
|
||||||
|
repoList.push(repo);
|
||||||
|
repoIdList.push(repo.repo_id);
|
||||||
|
});
|
||||||
|
|
||||||
|
const sortedRepoList = Utils.sortRepos(repoList, 'name', 'asc');
|
||||||
|
const selectedRepo = sortedRepoList.find(repo => repo.repo_id === item.repo_id);
|
||||||
|
const path = item.path.substring(0, item.path.length - 1);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
selectedRepo: repoInfo,
|
repoList: sortedRepoList,
|
||||||
|
isOtherRepoShow: true,
|
||||||
selectedPath: path,
|
selectedPath: path,
|
||||||
isCurrentRepoShow: true,
|
selectedRepo,
|
||||||
});
|
|
||||||
}).catch(error => {
|
|
||||||
let errMessage = Utils.getErrorMsg(error);
|
|
||||||
toaster.danger(errMessage);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (!this.state.hasRequest) {
|
|
||||||
let that = this;
|
|
||||||
seafileAPI.listRepos().then(res => {
|
|
||||||
// todo optimized code
|
|
||||||
let repos = res.data.repos;
|
|
||||||
let repoList = [];
|
|
||||||
let repoIdList = [];
|
|
||||||
for (let i = 0; i < repos.length; i++) {
|
|
||||||
if (repos[i].permission !== 'rw') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (that.props.repoID && (repos[i].repo_name === that.state.currentRepoInfo.repo_name)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (repoIdList.indexOf(repos[i].repo_id) > -1) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
repoList.push(repos[i]);
|
|
||||||
repoIdList.push(repos[i].repo_id);
|
|
||||||
}
|
|
||||||
repoList = Utils.sortRepos(repoList, 'name', 'asc');
|
|
||||||
let repo = repoList.filter(repoItem => repoItem.repo_id === item.repo_id);
|
|
||||||
let path = item.path.substring(0, (item.path.length - 1));
|
|
||||||
|
|
||||||
let selectRepo = repo[0];
|
|
||||||
this.setState({
|
|
||||||
repoList: repoList,
|
|
||||||
isOtherRepoShow: true,
|
|
||||||
selectedPath: path,
|
|
||||||
selectedRepo: selectRepo,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error);
|
||||||
}
|
}
|
||||||
else {
|
};
|
||||||
this.setState({ isOtherRepoShow: !this.state.isOtherRepoShow });
|
|
||||||
|
if (repoID && item.repo_id === repoID) {
|
||||||
|
await fetchRepoInfo();
|
||||||
|
} else {
|
||||||
|
if (!hasRequest) {
|
||||||
|
await fetchRepoList();
|
||||||
|
} else {
|
||||||
|
this.setState(prevState => ({ isOtherRepoShow: !prevState.isOtherRepoShow }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.onCloseSearching();
|
this.onCloseSearching();
|
||||||
};
|
};
|
||||||
|
|
||||||
onScroll = (event) => {
|
onScroll = (event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
};
|
};
|
||||||
|
|
||||||
renderRepoListView = () => {
|
renderRepoListView = () => {
|
||||||
|
const { mode, currentPath, isShowFile, fileSuffixes } = this.props;
|
||||||
|
const { isCurrentRepoShow, isOtherRepoShow, currentRepoInfo, repoList, selectedRepo, selectedPath, selectedItemInfo } = this.state;
|
||||||
|
const recentlyUsedRepos = JSON.parse(localStorage.getItem('recently-used-repos')) || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="file-chooser-container user-select-none" onScroll={this.onScroll}>
|
<div className='scroll-wrapper' onScroll={this.onScroll}>
|
||||||
{this.props.mode === 'current_repo_and_other_repos' && (
|
<div className="file-chooser-container user-select-none" >
|
||||||
<Fragment>
|
{mode === 'current_repo_and_other_repos' && (
|
||||||
<div className="list-view">
|
<Fragment>
|
||||||
<div className="list-view-header">
|
<div className="list-view">
|
||||||
<span className={`item-toggle sf3-font ${this.state.isCurrentRepoShow ? 'sf3-font-down' : 'sf3-font-down rotate-270 d-inline-block'}`} onClick={this.onCurrentRepoToggle}></span>
|
<div className="list-view-header">
|
||||||
<span className="library">{gettext('Current Library')}</span>
|
<span className={`item-toggle sf3-font ${isCurrentRepoShow ? 'sf3-font-down' : 'sf3-font-down rotate-270 d-inline-block'}`} onClick={this.onCurrentRepoToggle}></span>
|
||||||
|
<span className="library">{gettext('Current Library')}</span>
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
isCurrentRepoShow && currentRepoInfo &&
|
||||||
|
<RepoListView
|
||||||
|
initToShowChildren={true}
|
||||||
|
currentRepoInfo={currentRepoInfo}
|
||||||
|
currentPath={currentPath}
|
||||||
|
selectedRepo={selectedRepo}
|
||||||
|
selectedPath={selectedPath}
|
||||||
|
onRepoItemClick={this.onRepoItemClick}
|
||||||
|
onDirentItemClick={this.onDirentItemClick}
|
||||||
|
isShowFile={isShowFile}
|
||||||
|
fileSuffixes={fileSuffixes}
|
||||||
|
selectedItemInfo={selectedItemInfo}
|
||||||
|
/>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
{
|
<div className="list-view">
|
||||||
this.state.isCurrentRepoShow && this.state.currentRepoInfo &&
|
<div className="list-view-header">
|
||||||
<RepoListView
|
<span className={`item-toggle sf3-font ${isOtherRepoShow ? 'sf3-font-down' : 'sf3-font-down rotate-270 d-inline-block'}`} onClick={this.onOtherRepoToggle}></span>
|
||||||
initToShowChildren={true}
|
<span className="library">{gettext('Other Libraries')}</span>
|
||||||
currentRepoInfo={this.state.currentRepoInfo}
|
</div>
|
||||||
currentPath={this.props.currentPath}
|
{
|
||||||
selectedRepo={this.state.selectedRepo}
|
isOtherRepoShow &&
|
||||||
selectedPath={this.state.selectedPath}
|
<RepoListView
|
||||||
onRepoItemClick={this.onRepoItemClick}
|
initToShowChildren={false}
|
||||||
onDirentItemClick={this.onDirentItemClick}
|
repoList={repoList}
|
||||||
isShowFile={this.props.isShowFile}
|
selectedRepo={selectedRepo}
|
||||||
fileSuffixes={this.props.fileSuffixes}
|
selectedPath={selectedPath}
|
||||||
selectedItemInfo={this.state.selectedItemInfo}
|
onRepoItemClick={this.onRepoItemClick}
|
||||||
/>
|
onDirentItemClick={this.onDirentItemClick}
|
||||||
}
|
isShowFile={isShowFile}
|
||||||
</div>
|
fileSuffixes={fileSuffixes}
|
||||||
<div className="list-view">
|
selectedItemInfo={selectedItemInfo}
|
||||||
<div className="list-view-header">
|
/>
|
||||||
<span className={`item-toggle sf3-font ${this.state.isOtherRepoShow ? 'sf3-font-down' : 'sf3-font-down rotate-270 d-inline-block'}`} onClick={this.onOtherRepoToggle}></span>
|
}
|
||||||
<span className="library">{gettext('Other Libraries')}</span>
|
|
||||||
</div>
|
</div>
|
||||||
{
|
</Fragment>
|
||||||
this.state.isOtherRepoShow &&
|
)}
|
||||||
<RepoListView
|
{mode === 'only_current_library' && (
|
||||||
initToShowChildren={false}
|
<div className="list-view">
|
||||||
repoList={this.state.repoList}
|
|
||||||
selectedRepo={this.state.selectedRepo}
|
|
||||||
selectedPath={this.state.selectedPath}
|
|
||||||
onRepoItemClick={this.onRepoItemClick}
|
|
||||||
onDirentItemClick={this.onDirentItemClick}
|
|
||||||
isShowFile={this.props.isShowFile}
|
|
||||||
fileSuffixes={this.props.fileSuffixes}
|
|
||||||
selectedItemInfo={this.state.selectedItemInfo}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</Fragment>
|
|
||||||
)}
|
|
||||||
{this.props.mode === 'only_current_library' && (
|
|
||||||
<div className="list-view">
|
|
||||||
{!this.props.hideLibraryName &&
|
|
||||||
<div className="list-view-header">
|
|
||||||
<span className={`item-toggle sf3-font ${this.state.isCurrentRepoShow ? 'sf3-font-down' : 'sf3-font-down rotate-270 d-inline-block'}`} onClick={this.onCurrentRepoToggle}></span>
|
|
||||||
<span className="library">{gettext('Current Library')}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
{
|
|
||||||
this.state.isCurrentRepoShow && this.state.currentRepoInfo &&
|
|
||||||
<RepoListView
|
<RepoListView
|
||||||
initToShowChildren={true}
|
initToShowChildren={true}
|
||||||
currentRepoInfo={this.state.currentRepoInfo}
|
currentRepoInfo={currentRepoInfo}
|
||||||
currentPath={this.props.currentPath}
|
currentPath={currentPath}
|
||||||
selectedRepo={this.state.selectedRepo}
|
selectedRepo={selectedRepo}
|
||||||
selectedPath={this.state.selectedPath}
|
selectedPath={selectedPath}
|
||||||
onRepoItemClick={this.onRepoItemClick}
|
onRepoItemClick={this.onRepoItemClick}
|
||||||
onDirentItemClick={this.onDirentItemClick}
|
onDirentItemClick={this.onDirentItemClick}
|
||||||
isShowFile={this.props.isShowFile}
|
isShowFile={isShowFile}
|
||||||
fileSuffixes={this.props.fileSuffixes}
|
fileSuffixes={fileSuffixes}
|
||||||
selectedItemInfo={this.state.selectedItemInfo}
|
selectedItemInfo={selectedItemInfo}
|
||||||
hideLibraryName={this.props.hideLibraryName}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{this.props.mode === 'only_all_repos' && (
|
|
||||||
<div className="file-chooser-container">
|
|
||||||
<div className="list-view">
|
|
||||||
<div className="list-view-header">
|
|
||||||
<span className="item-toggle sf3-font sf3-font-down"></span>
|
|
||||||
<span className="library">{gettext('Libraries')}</span>
|
|
||||||
</div>
|
|
||||||
<RepoListView
|
|
||||||
initToShowChildren={false}
|
|
||||||
repoList={this.state.repoList}
|
|
||||||
selectedRepo={this.state.selectedRepo}
|
|
||||||
selectedPath={this.state.selectedPath}
|
|
||||||
onRepoItemClick={this.onRepoItemClick}
|
|
||||||
onDirentItemClick={this.onDirentItemClick}
|
|
||||||
isShowFile={this.props.isShowFile}
|
|
||||||
fileSuffixes={this.props.fileSuffixes}
|
|
||||||
selectedItemInfo={this.state.selectedItemInfo}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
{mode === 'only_all_repos' && (
|
||||||
|
<div className="file-chooser-container">
|
||||||
|
<div className="list-view">
|
||||||
|
<div className="list-view-header">
|
||||||
|
<span className="item-toggle sf3-font sf3-font-down"></span>
|
||||||
|
<span className="library">{gettext('Libraries')}</span>
|
||||||
|
</div>
|
||||||
|
<RepoListView
|
||||||
|
initToShowChildren={false}
|
||||||
|
repoList={repoList}
|
||||||
|
selectedRepo={selectedRepo}
|
||||||
|
selectedPath={selectedPath}
|
||||||
|
onRepoItemClick={this.onRepoItemClick}
|
||||||
|
onDirentItemClick={this.onDirentItemClick}
|
||||||
|
isShowFile={isShowFile}
|
||||||
|
fileSuffixes={fileSuffixes}
|
||||||
|
selectedItemInfo={selectedItemInfo}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{mode === 'only_other_libraries' && (
|
||||||
|
<div className="list-view">
|
||||||
|
<RepoListView
|
||||||
|
initToShowChildren={false}
|
||||||
|
repoList={repoList}
|
||||||
|
selectedRepo={selectedRepo}
|
||||||
|
selectedPath={selectedPath}
|
||||||
|
onRepoItemClick={this.onRepoItemClick}
|
||||||
|
onDirentItemClick={this.onDirentItemClick}
|
||||||
|
isShowFile={isShowFile}
|
||||||
|
fileSuffixes={fileSuffixes}
|
||||||
|
selectedItemInfo={selectedItemInfo}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{mode === 'recently_used' && (
|
||||||
|
<div className="list-view">
|
||||||
|
<RepoListView
|
||||||
|
initToShowChildren={false}
|
||||||
|
repoList={recentlyUsedRepos}
|
||||||
|
selectedRepo={selectedRepo}
|
||||||
|
selectedPath={selectedPath}
|
||||||
|
onRepoItemClick={this.onRepoItemClick}
|
||||||
|
onDirentItemClick={this.onDirentItemClick}
|
||||||
|
isShowFile={isShowFile}
|
||||||
|
fileSuffixes={fileSuffixes}
|
||||||
|
selectedItemInfo={selectedItemInfo}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (!this.state.selectedRepo && this.props.repoID) {
|
const { repoID } = this.props;
|
||||||
|
const { selectedRepo, searchInfo, isSearching } = this.state;
|
||||||
|
|
||||||
|
if (!selectedRepo && repoID) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
{isPro && (
|
{isPro && this.props.mode !== 'recently_used' && (
|
||||||
<div className="file-chooser-search-input">
|
<div className="file-chooser-search-input">
|
||||||
<Input className="search-input mb-2" placeholder={gettext('Search')} type='text' value={this.state.searchInfo} onChange={this.onSearchInfoChanged}></Input>
|
<Input className="search-input" placeholder={gettext('Search')} type='text' value={searchInfo} onChange={this.onSearchInfoChanged}></Input>
|
||||||
{this.state.searchInfo.length !== 0 && (
|
{searchInfo.length !== 0 && (
|
||||||
<span className="search-control attr-action-icon sf3-font sf3-font-x-01" onClick={this.onCloseSearching}></span>
|
<span className="search-control attr-action-icon sf3-font sf3-font-x-01" onClick={this.onCloseSearching}></span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{this.state.isSearching && (
|
{isSearching ? (
|
||||||
<div className="file-chooser-search-container">
|
<div className="file-chooser-search-container">
|
||||||
{this.renderSearchedView()}
|
{this.renderSearchedView()}
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
this.renderRepoListView()
|
||||||
)}
|
)}
|
||||||
{!this.state.isSearching && this.renderRepoListView()}
|
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FileChooser.propTypes = propTypes;
|
FileChooser.propTypes = propTypes;
|
||||||
|
FileChooser.defaultProps = defaultProps;
|
||||||
|
|
||||||
export default FileChooser;
|
export default FileChooser;
|
||||||
|
@ -31,10 +31,12 @@ class RepoListItem extends React.Component {
|
|||||||
isShowChildren: this.props.initToShowChildren,
|
isShowChildren: this.props.initToShowChildren,
|
||||||
treeData: treeHelper.buildTree(),
|
treeData: treeHelper.buildTree(),
|
||||||
hasLoaded: false,
|
hasLoaded: false,
|
||||||
|
isMounted: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
this.setState({ isMounted: true });
|
||||||
const { isCurrentRepo, currentPath, repo, selectedItemInfo } = this.props;
|
const { isCurrentRepo, currentPath, repo, selectedItemInfo } = this.props;
|
||||||
|
|
||||||
// render search result
|
// render search result
|
||||||
@ -61,27 +63,31 @@ class RepoListItem extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loadRepoDirentList = (repo) => {
|
componentWillUnmount() {
|
||||||
|
this.setState({ isMounted: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
loadRepoDirentList = async (repo) => {
|
||||||
const { hasLoaded } = this.state;
|
const { hasLoaded } = this.state;
|
||||||
if (hasLoaded) return;
|
if (hasLoaded) return;
|
||||||
|
|
||||||
const repoID = repo.repo_id;
|
const repoID = repo.repo_id;
|
||||||
|
|
||||||
seafileAPI.listDir(repoID, '/').then(res => {
|
try {
|
||||||
|
const res = await seafileAPI.listDir(repoID, '/');
|
||||||
|
if (!this.state.isMounted) return;
|
||||||
|
|
||||||
let tree = this.state.treeData.clone();
|
let tree = this.state.treeData.clone();
|
||||||
let direntList = [];
|
let direntList = this.props.isShowFile ? res.data.dirent_list : res.data.dirent_list.filter(item => item.type === 'dir');
|
||||||
if (this.props.isShowFile === true) {
|
|
||||||
direntList = res.data.dirent_list;
|
|
||||||
} else {
|
|
||||||
direntList = res.data.dirent_list.filter(item => item.type === 'dir');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.addResponseListToNode(direntList, tree.root);
|
this.addResponseListToNode(direntList, tree.root);
|
||||||
this.setState({ treeData: tree, hasLoaded: true });
|
this.setState({ treeData: tree, hasLoaded: true });
|
||||||
}).catch(error => {
|
} catch (error) {
|
||||||
|
if (!this.state.isMounted) return;
|
||||||
|
|
||||||
let errMessage = Utils.getErrorMsg(error);
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
toaster.danger(errMessage);
|
toaster.danger(errMessage);
|
||||||
});
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
addResponseListToNode = (list, node) => {
|
addResponseListToNode = (list, node) => {
|
||||||
@ -201,15 +207,15 @@ class RepoListItem extends React.Component {
|
|||||||
<li>
|
<li>
|
||||||
{!this.props.hideLibraryName &&
|
{!this.props.hideLibraryName &&
|
||||||
<div className={`${repoActive ? 'item-active' : ''} item-info`} onClick={this.onRepoItemClick}>
|
<div className={`${repoActive ? 'item-active' : ''} item-info`} onClick={this.onRepoItemClick}>
|
||||||
<div className="item-text">
|
|
||||||
<span className="name user-select-none ellipsis" title={this.props.repo.repo_name}>{this.props.repo.repo_name}</span>
|
|
||||||
</div>
|
|
||||||
<div className="item-left-icon">
|
<div className="item-left-icon">
|
||||||
<span className={`item-toggle icon sf3-font ${this.state.isShowChildren ? 'sf3-font-down' : 'sf3-font-down rotate-270 d-inline-block'}`} onClick={this.onToggleClick}></span>
|
<span className={`item-toggle icon sf3-font ${this.state.isShowChildren ? 'sf3-font-down' : 'sf3-font-down rotate-270 d-inline-block'}`} onClick={this.onToggleClick}></span>
|
||||||
<i className="tree-node-icon">
|
<i className="tree-node-icon">
|
||||||
<span className="icon sf3-font sf3-font-folder tree-node-icon"></span>
|
<span className="icon sf3-font sf3-font-folder tree-node-icon"></span>
|
||||||
</i>
|
</i>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="item-text">
|
||||||
|
<span className="name user-select-none ellipsis" title={this.props.repo.repo_name}>{this.props.repo.repo_name}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
{this.state.isShowChildren && (
|
{this.state.isShowChildren && (
|
||||||
|
@ -15,7 +15,6 @@ const propTypes = {
|
|||||||
fileSuffixes: PropTypes.array,
|
fileSuffixes: PropTypes.array,
|
||||||
selectedItemInfo: PropTypes.object,
|
selectedItemInfo: PropTypes.object,
|
||||||
currentPath: PropTypes.string,
|
currentPath: PropTypes.string,
|
||||||
hideLibraryName: PropTypes.bool,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class RepoListView extends React.Component {
|
class RepoListView extends React.Component {
|
||||||
@ -26,12 +25,9 @@ class RepoListView extends React.Component {
|
|||||||
repoList = [];
|
repoList = [];
|
||||||
repoList.push(currentRepoInfo);
|
repoList.push(currentRepoInfo);
|
||||||
}
|
}
|
||||||
let style = {};
|
|
||||||
if (this.props.hideLibraryName) {
|
|
||||||
style = { marginLeft: '-44px' };
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<ul className="list-view-content file-chooser-item" style={style}>
|
<ul className="list-view-content file-chooser-item" >
|
||||||
{repoList.length > 0 && repoList.map((repoItem, index) => {
|
{repoList.length > 0 && repoList.map((repoItem, index) => {
|
||||||
return (
|
return (
|
||||||
<RepoListItem
|
<RepoListItem
|
||||||
@ -47,7 +43,6 @@ class RepoListView extends React.Component {
|
|||||||
isShowFile={this.props.isShowFile}
|
isShowFile={this.props.isShowFile}
|
||||||
fileSuffixes={this.props.fileSuffixes}
|
fileSuffixes={this.props.fileSuffixes}
|
||||||
selectedItemInfo={this.props.selectedItemInfo}
|
selectedItemInfo={this.props.selectedItemInfo}
|
||||||
hideLibraryName={this.props.hideLibraryName}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -11,13 +11,22 @@ const propTypes = {
|
|||||||
onNodeExpanded: PropTypes.func.isRequired,
|
onNodeExpanded: PropTypes.func.isRequired,
|
||||||
filePath: PropTypes.string,
|
filePath: PropTypes.string,
|
||||||
fileSuffixes: PropTypes.array,
|
fileSuffixes: PropTypes.array,
|
||||||
|
level: PropTypes.number,
|
||||||
};
|
};
|
||||||
|
|
||||||
class TreeViewItem extends React.Component {
|
class TreeViewItem extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
let filePath = this.props.filePath ? this.props.filePath + '/' + this.props.node.object.name : this.props.node.path;
|
let filePath;
|
||||||
|
|
||||||
|
if (this.props.filePath === '/') {
|
||||||
|
filePath = '/' + this.props.node.object.name;
|
||||||
|
} else if (this.props.filePath) {
|
||||||
|
filePath = this.props.filePath + '/' + this.props.node.object.name;
|
||||||
|
} else {
|
||||||
|
filePath = this.props.node.path;
|
||||||
|
}
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
filePath: filePath,
|
filePath: filePath,
|
||||||
@ -73,6 +82,8 @@ class TreeViewItem extends React.Component {
|
|||||||
selectedRepo={this.props.selectedRepo}
|
selectedRepo={this.props.selectedRepo}
|
||||||
selectedPath={this.props.selectedPath}
|
selectedPath={this.props.selectedPath}
|
||||||
fileSuffixes={this.props.fileSuffixes}
|
fileSuffixes={this.props.fileSuffixes}
|
||||||
|
filePath={this.state.filePath}
|
||||||
|
level={(this.props.level || 0) + 1}
|
||||||
/>);
|
/>);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
@ -97,13 +108,15 @@ class TreeViewItem extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const paddingLeft = `${this.props.level * 20}px`;
|
||||||
return (
|
return (
|
||||||
<div className="file-chooser-item">
|
<div className="file-chooser-item">
|
||||||
<div className={`${node.path === '/' ? 'hide' : ''}`}>
|
<div className={`${node.path === '/' ? 'hide' : ''}`}>
|
||||||
<div className={`${(isCurrentRepo && isCurrentPath) ? 'item-active' : ''} item-info`} onClick={this.onItemClick}>
|
<div
|
||||||
<div className="item-text">
|
className={`${(isCurrentRepo && isCurrentPath) ? 'item-active' : ''} item-info`}
|
||||||
<span className="name user-select-none ellipsis" title={node.object && node.object.name}>{node.object && node.object.name}</span>
|
onClick={this.onItemClick}
|
||||||
</div>
|
style={{ paddingLeft }}
|
||||||
|
>
|
||||||
<div className="item-left-icon">
|
<div className="item-left-icon">
|
||||||
{
|
{
|
||||||
node.object.type !== 'file' &&
|
node.object.type !== 'file' &&
|
||||||
@ -113,6 +126,9 @@ class TreeViewItem extends React.Component {
|
|||||||
<span className={`icon sf3-font ${node.object.type === 'dir' ? 'sf3-font-folder' : 'sf3-font-file'}`}></span>
|
<span className={`icon sf3-font ${node.object.type === 'dir' ? 'sf3-font-folder' : 'sf3-font-file'}`}></span>
|
||||||
</i>
|
</i>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="item-text">
|
||||||
|
<span className="name user-select-none ellipsis" title={node.object && node.object.name}>{node.object && node.object.name}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{node.isExpanded && this.renderChildren()}
|
{node.isExpanded && this.renderChildren()}
|
||||||
|
@ -17,7 +17,7 @@ class TreeListView extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="list-view-content" style={{ 'marginLeft': '-1.5rem' }}>
|
<div className="list-view-content">
|
||||||
<TreeListItem
|
<TreeListItem
|
||||||
node={this.props.treeData.root}
|
node={this.props.treeData.root}
|
||||||
onNodeCollapse={this.props.onNodeCollapse}
|
onNodeCollapse={this.props.onNodeCollapse}
|
||||||
@ -27,6 +27,7 @@ class TreeListView extends React.Component {
|
|||||||
selectedRepo={this.props.selectedRepo}
|
selectedRepo={this.props.selectedRepo}
|
||||||
selectedPath={this.props.selectedPath}
|
selectedPath={this.props.selectedPath}
|
||||||
fileSuffixes={this.props.fileSuffixes}
|
fileSuffixes={this.props.fileSuffixes}
|
||||||
|
level={0}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,78 +1,97 @@
|
|||||||
.file-chooser-container {
|
.file-chooser-container {
|
||||||
padding: 0.5rem;
|
|
||||||
height: 20rem;
|
|
||||||
border: 1px solid rgba(0, 40, 100, 0.12);
|
|
||||||
border-radius: 3px;
|
|
||||||
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
|
|
||||||
overflow: auto;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-toggle{
|
|
||||||
position: absolute;
|
|
||||||
height: 1.5rem;
|
|
||||||
width: 1.5rem;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
line-height: 1.5rem !important;
|
|
||||||
text-align: center;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #c0c0c0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-chooser-container .list-view {
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-view-header {
|
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-left: 1.5rem;
|
height: 20rem;
|
||||||
}
|
border-radius: 3px;
|
||||||
.list-view-header:hover {
|
font-size: 1rem;
|
||||||
background-color: #FDEFB9;
|
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
|
||||||
}
|
|
||||||
|
|
||||||
.list-view-header .name {
|
|
||||||
color: #eb8205;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-view-content {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
list-style: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-chooser-item {
|
.file-chooser-item {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-left: 22px;
|
width: 100%;
|
||||||
}
|
|
||||||
|
|
||||||
.file-chooser-item .item-info {
|
|
||||||
height: 1.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
position: relative;
|
|
||||||
line-height: 1.625;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-chooser-item .item-active {
|
.file-chooser-item .item-active {
|
||||||
background: #F3AF7D !important;
|
background: #f2f4f6 !important;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-chooser-item .item-active::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
left: -8px;
|
||||||
|
width: 4px;
|
||||||
|
height: 24px;
|
||||||
|
background-color: #ff8000;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
box-shadow: inset 0 0 1px #999;
|
display: block;
|
||||||
color: #fff;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-chooser-item .item-info:hover {
|
.file-chooser-item .item-info {
|
||||||
background: #FDEFB9;
|
position: relative;
|
||||||
border-radius: 2px;
|
display: flex;
|
||||||
box-shadow: inset 0 0 1px #999;
|
align-items: center;
|
||||||
|
height: 1.75rem;
|
||||||
|
line-height: 1.625;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease, color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-chooser-item .item-info .name {
|
.file-chooser-item .item-info:hover {
|
||||||
flex: 1;
|
background: #f5f5f5;
|
||||||
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-chooser-item .item-active .icon {
|
.file-chooser-item .item-info .item-left-icon {
|
||||||
color: #fff !important;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-chooser-item .item-info .item-text {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.5;
|
||||||
|
padding-left: 0.25rem;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: color 0.3s ease, background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-chooser-search-close {
|
||||||
|
position: absolute;
|
||||||
|
top: -0.5rem;
|
||||||
|
right: -0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-chooser-search-container {
|
||||||
|
position: relative;
|
||||||
|
height: 20rem;
|
||||||
|
padding: 10px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-chooser-search-container td {
|
||||||
|
height: 40px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-chooser-search-container td .span {
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-chooser-search-container .tr-highlight {
|
||||||
|
background-color: #f5f5f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-chooser-search-input {
|
.file-chooser-search-input {
|
||||||
@ -82,67 +101,64 @@
|
|||||||
.file-chooser-search-input .search-control {
|
.file-chooser-search-input .search-control {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0.5rem;
|
top: 0.5rem;
|
||||||
right: 0.7rem;
|
right: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-chooser-search-input .search-input {
|
.file-chooser-search-input .search-input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-chooser-search-container {
|
.file-chooser-table td {
|
||||||
height: 20rem;
|
border-bottom: 1px solid rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-toggle {
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
line-height: 1.5rem !important;
|
||||||
|
color: #c0c0c0;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-view-content {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-view-header {
|
||||||
position: relative;
|
position: relative;
|
||||||
border: 1px solid #eee;
|
padding-left: 1.5rem;
|
||||||
padding: 10px;
|
}
|
||||||
|
|
||||||
|
.list-view-header:hover {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-view-header .name {
|
||||||
|
color: #eb8205;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-wrapper {
|
||||||
|
max-height: 100%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-chooser-search-close {
|
|
||||||
position: absolute;
|
|
||||||
right: -0.5rem;
|
|
||||||
top: -0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.searched-active {
|
.searched-active {
|
||||||
background: #F3AF7D !important;
|
background: #f3af7d !important;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
box-shadow: inset 0 0 1px #999;
|
box-shadow: inset 0 0 1px #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.searched-active td {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.searched-active .icon {
|
.searched-active .icon {
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.searched-active td {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
.select-open-repo {
|
.select-open-repo {
|
||||||
background: #FDEFB9;
|
background: #fdefb9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-chooser-table td {
|
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-chooser-item .item-info .item-text {
|
|
||||||
padding-left: 2.8rem;
|
|
||||||
font-size: 15px;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
line-height: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-chooser-item .item-info .item-left-icon {
|
|
||||||
position: absolute;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
padding-left: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -289,3 +289,92 @@
|
|||||||
.dir-view-path .path-split {
|
.dir-view-path .path-split {
|
||||||
padding: 0 2px;
|
padding: 0 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.custom-modal {
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 1rem;
|
||||||
|
max-width: 740px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-modal .row {
|
||||||
|
flex: 1;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-modal .modal-content {
|
||||||
|
min-height: 534px;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-modal .modal-body {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-modal .modal-body .alert-message {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 10;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-modal .repo-list-col {
|
||||||
|
max-width: 240px;
|
||||||
|
padding: 1rem;
|
||||||
|
line-height: 24px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-modal .repo-list-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease, font-weight 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-modal .repo-list-item.active {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-modal .repo-list-item:hover{
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-modal .repo-list-item.active::before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
left: -8px;
|
||||||
|
width: 4px;
|
||||||
|
height: 24px;
|
||||||
|
background-color: #ff8000;
|
||||||
|
border-radius: 2px;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-modal .file-list-col {
|
||||||
|
max-width: 500px;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-modal .file-list-col .file-chooser-container,
|
||||||
|
.custom-modal .file-list-col .file-chooser-search-input {
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
@ -78,7 +78,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.search-input {
|
.search-input {
|
||||||
height: 1.875rem;
|
height: 2.375rem;
|
||||||
width: 15rem;
|
width: 15rem;
|
||||||
font-size: .875rem;
|
font-size: .875rem;
|
||||||
}
|
}
|
||||||
|
@ -715,6 +715,27 @@ class LibContentView extends React.Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
updateRecentlyUsedRepos = (destRepo) => {
|
||||||
|
const recentlyUsed = JSON.parse(localStorage.getItem('recently-used-repos')) || [];
|
||||||
|
const updatedRecentlyUsed = [destRepo, ...recentlyUsed.filter(repo => repo.repo_id !== destRepo.repo_id)];
|
||||||
|
|
||||||
|
const seen = new Set();
|
||||||
|
const filteredRecentlyUsed = updatedRecentlyUsed.filter(repo => {
|
||||||
|
if (seen.has(repo.repo_id)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
seen.add(repo.repo_id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (filteredRecentlyUsed.length > 10) {
|
||||||
|
updatedRecentlyUsed.pop(); // Limit to 10 recent directories
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem('recently-used-repos', JSON.stringify(filteredRecentlyUsed));
|
||||||
|
};
|
||||||
|
|
||||||
// toolbar operations
|
// toolbar operations
|
||||||
onMoveItems = (destRepo, destDirentPath) => {
|
onMoveItems = (destRepo, destDirentPath) => {
|
||||||
let repoID = this.props.repoID;
|
let repoID = this.props.repoID;
|
||||||
@ -722,6 +743,7 @@ class LibContentView extends React.Component {
|
|||||||
|
|
||||||
let dirNames = this.getSelectedDirentNames();
|
let dirNames = this.getSelectedDirentNames();
|
||||||
let direntPaths = this.getSelectedDirentPaths();
|
let direntPaths = this.getSelectedDirentPaths();
|
||||||
|
|
||||||
seafileAPI.moveDir(repoID, destRepo.repo_id, destDirentPath, this.state.path, dirNames).then(res => {
|
seafileAPI.moveDir(repoID, destRepo.repo_id, destDirentPath, this.state.path, dirNames).then(res => {
|
||||||
if (repoID !== destRepo.repo_id) {
|
if (repoID !== destRepo.repo_id) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -753,6 +775,8 @@ class LibContentView extends React.Component {
|
|||||||
toaster.success(message);
|
toaster.success(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.updateRecentlyUsedRepos(destRepo);
|
||||||
|
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
if (!error.response.data.lib_need_decrypt) {
|
if (!error.response.data.lib_need_decrypt) {
|
||||||
let errMessage = Utils.getErrorMsg(error);
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
@ -1222,6 +1246,9 @@ class LibContentView extends React.Component {
|
|||||||
message = message.replace('{name}', dirName);
|
message = message.replace('{name}', dirName);
|
||||||
toaster.success(message);
|
toaster.success(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.updateRecentlyUsedRepos(destRepo);
|
||||||
|
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
if (!error.response.data.lib_need_decrypt) {
|
if (!error.response.data.lib_need_decrypt) {
|
||||||
let errMessage = Utils.getErrorMsg(error);
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
Loading…
Reference in New Issue
Block a user