mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-02 23:48:47 +00:00
Improve/move dirent dialog UI (#6786)
* update move dialog and search input * update move dialog ui * clean up code * update code --------- Co-authored-by: renjie-run <rj.aiyayao@gmail.com>
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
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, Row, Col } from 'reactstrap';
|
import { Button, Modal, ModalHeader, ModalFooter, ModalBody, Alert, Row, Col } from 'reactstrap';
|
||||||
|
import FileChooser from '../file-chooser';
|
||||||
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';
|
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
path: PropTypes.string.isRequired,
|
path: PropTypes.string.isRequired,
|
||||||
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
|
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
|
||||||
import { gettext } from '../../utils/constants';
|
import { gettext } from '../../utils/constants';
|
||||||
import FileChooser from '../file-chooser/file-chooser';
|
import FileChooser from '../file-chooser';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
repoID: PropTypes.string.isRequired,
|
repoID: PropTypes.string.isRequired,
|
||||||
|
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
|
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } 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';
|
||||||
import '../../css/insert-repo-image-dialog.css';
|
import '../../css/insert-repo-image-dialog.css';
|
||||||
|
|
||||||
const { siteRoot, serviceUrl } = window.app.config;
|
const { siteRoot, serviceUrl } = window.app.config;
|
||||||
|
@@ -5,7 +5,7 @@ import { gettext, isPro, siteRoot } from '../../utils/constants';
|
|||||||
import { seafileAPI } from '../../utils/seafile-api';
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
import SharePermissionEditor from '../select-editor/share-permission-editor';
|
import SharePermissionEditor from '../select-editor/share-permission-editor';
|
||||||
import FileChooser from '../file-chooser/file-chooser';
|
import FileChooser from '../file-chooser';
|
||||||
import { SeahubSelect, NoGroupMessage } from '../common/select';
|
import { SeahubSelect, NoGroupMessage } from '../common/select';
|
||||||
import toaster from '../../components/toast';
|
import toaster from '../../components/toast';
|
||||||
|
|
||||||
|
@@ -6,7 +6,7 @@ import { seafileAPI } from '../../utils/seafile-api';
|
|||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
import UserSelect from '../user-select';
|
import UserSelect from '../user-select';
|
||||||
import SharePermissionEditor from '../select-editor/share-permission-editor';
|
import SharePermissionEditor from '../select-editor/share-permission-editor';
|
||||||
import FileChooser from '../file-chooser/file-chooser';
|
import FileChooser from '../file-chooser';
|
||||||
import toaster from '../../components/toast';
|
import toaster from '../../components/toast';
|
||||||
|
|
||||||
class UserItem extends React.Component {
|
class UserItem extends React.Component {
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
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, Row, Col } from 'reactstrap';
|
import { Modal, ModalHeader } from 'reactstrap';
|
||||||
|
import SelectDirentBody from './select-dirent-body';
|
||||||
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';
|
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
path: PropTypes.string.isRequired,
|
path: PropTypes.string.isRequired,
|
||||||
@@ -14,10 +14,8 @@ const propTypes = {
|
|||||||
onItemMove: PropTypes.func,
|
onItemMove: PropTypes.func,
|
||||||
onItemsMove: PropTypes.func,
|
onItemsMove: PropTypes.func,
|
||||||
onCancelMove: PropTypes.func.isRequired,
|
onCancelMove: PropTypes.func.isRequired,
|
||||||
repoEncrypted: PropTypes.bool.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// need dirent file Path;
|
|
||||||
class MoveDirent extends React.Component {
|
class MoveDirent extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -26,7 +24,6 @@ class MoveDirent extends React.Component {
|
|||||||
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',
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +41,7 @@ class MoveDirent extends React.Component {
|
|||||||
let message = gettext('Invalid destination path');
|
let message = gettext('Invalid destination path');
|
||||||
|
|
||||||
if (!repo || selectedPath === '') {
|
if (!repo || selectedPath === '') {
|
||||||
this.setState({ errMessage: message });
|
this.setErrMessage(message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,13 +54,13 @@ class MoveDirent extends React.Component {
|
|||||||
|
|
||||||
// move dirents to one of them. eg: A/B, A/C -> A/B
|
// move dirents to one of them. eg: A/B, A/C -> A/B
|
||||||
if (direntPaths.some(direntPath => { return direntPath === selectedPath;})) {
|
if (direntPaths.some(direntPath => { return direntPath === selectedPath;})) {
|
||||||
this.setState({ errMessage: message });
|
this.setErrMessage(message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// move dirents to current path
|
// move dirents to current path
|
||||||
if (selectedPath && selectedPath === this.props.path && (repo.repo_id === repoID)) {
|
if (selectedPath && selectedPath === this.props.path && (repo.repo_id === repoID)) {
|
||||||
this.setState({ errMessage: message });
|
this.setErrMessage(message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +78,7 @@ class MoveDirent extends React.Component {
|
|||||||
message = gettext('Can not move folder %(src)s to its subfolder %(des)s');
|
message = gettext('Can not move folder %(src)s to its subfolder %(des)s');
|
||||||
message = message.replace('%(src)s', moveDirentPath);
|
message = message.replace('%(src)s', moveDirentPath);
|
||||||
message = message.replace('%(des)s', selectedPath);
|
message = message.replace('%(des)s', selectedPath);
|
||||||
this.setState({ errMessage: message });
|
this.setErrMessage(message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,19 +93,19 @@ class MoveDirent extends React.Component {
|
|||||||
let message = gettext('Invalid destination path');
|
let message = gettext('Invalid destination path');
|
||||||
|
|
||||||
if (!repo || (repo.repo_id === repoID && selectedPath === '')) {
|
if (!repo || (repo.repo_id === repoID && selectedPath === '')) {
|
||||||
this.setState({ errMessage: message });
|
this.setErrMessage(message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy the dirent to itself. eg: A/B -> A/B
|
// copy the dirent to itself. eg: A/B -> A/B
|
||||||
if (selectedPath && direntPath === selectedPath) {
|
if (selectedPath && direntPath === selectedPath) {
|
||||||
this.setState({ errMessage: message });
|
this.setErrMessage(message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy the dirent to current path
|
// copy the dirent to current path
|
||||||
if (selectedPath && this.props.path === selectedPath && repo.repo_id === repoID) {
|
if (selectedPath && this.props.path === selectedPath && repo.repo_id === repoID) {
|
||||||
this.setState({ errMessage: message });
|
this.setErrMessage(message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +114,7 @@ class MoveDirent extends React.Component {
|
|||||||
message = gettext('Can not move folder %(src)s to its subfolder %(des)s');
|
message = gettext('Can not move folder %(src)s to its subfolder %(des)s');
|
||||||
message = message.replace('%(src)s', direntPath);
|
message = message.replace('%(src)s', direntPath);
|
||||||
message = message.replace('%(des)s', selectedPath);
|
message = message.replace('%(des)s', selectedPath);
|
||||||
this.setState({ errMessage: message });
|
this.setErrMessage(message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,24 +126,16 @@ class MoveDirent extends React.Component {
|
|||||||
this.props.onCancelMove();
|
this.props.onCancelMove();
|
||||||
};
|
};
|
||||||
|
|
||||||
onDirentItemClick = (repo, selectedPath) => {
|
selectRepo = (repo) => {
|
||||||
this.setState({
|
this.setState({ repo });
|
||||||
repo: repo,
|
|
||||||
selectedPath: selectedPath,
|
|
||||||
errMessage: ''
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onRepoItemClick = (repo) => {
|
setSelectedPath = (selectedPath) => {
|
||||||
this.setState({
|
this.setState({ selectedPath });
|
||||||
repo: repo,
|
|
||||||
selectedPath: '/',
|
|
||||||
errMessage: ''
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onSelectedMode = (mode) => {
|
setErrMessage = (message) => {
|
||||||
this.setState({ mode: mode });
|
this.setState({ errMessage: message });
|
||||||
};
|
};
|
||||||
|
|
||||||
renderTitle = () => {
|
renderTitle = () => {
|
||||||
@@ -161,48 +150,28 @@ class MoveDirent extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { dirent, selectedDirentList, isMultipleOperation, repoID, path } = this.props;
|
const { dirent, selectedDirentList, isMultipleOperation, path, repoID } = this.props;
|
||||||
const { mode, errMessage } = this.state;
|
|
||||||
|
|
||||||
const movedDirent = dirent || selectedDirentList[0];
|
const movedDirent = dirent || selectedDirentList[0];
|
||||||
const { permission } = movedDirent;
|
const { permission } = movedDirent;
|
||||||
const { isCustomPermission } = Utils.getUserPermission(permission);
|
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 className='custom-modal' isOpen={true} toggle={this.toggle}>
|
<Modal className='custom-modal' isOpen={true} toggle={this.toggle}>
|
||||||
<ModalHeader toggle={this.toggle}>
|
<ModalHeader toggle={this.toggle}>
|
||||||
{isMultipleOperation ? this.renderTitle() : <div dangerouslySetInnerHTML={{ __html: this.renderTitle() }} className='d-flex mw-100'></div>}
|
{isMultipleOperation ? this.renderTitle() : <div dangerouslySetInnerHTML={{ __html: this.renderTitle() }} className='d-flex mw-100'></div>}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<Row>
|
<SelectDirentBody
|
||||||
<Col className='repo-list-col border-right' >
|
path={path}
|
||||||
<LibraryOption mode='only_current_library' label={gettext('Current Library')} />
|
selectedPath={this.state.selectedPath}
|
||||||
{!isCustomPermission && <LibraryOption mode='only_other_libraries' label={gettext('Other Libraries')} />}
|
repoID={repoID}
|
||||||
<LibraryOption mode='recently_used' label={gettext('Recently Used')} />
|
isSupportOtherLibraries={!isCustomPermission}
|
||||||
</Col>
|
errMessage={this.state.errMessage}
|
||||||
<Col className='file-list-col'>
|
onCancel={this.toggle}
|
||||||
<ModalBody>
|
selectRepo={this.selectRepo}
|
||||||
<FileChooser
|
setSelectedPath={this.setSelectedPath}
|
||||||
repoID={repoID}
|
setErrMessage={this.setErrMessage}
|
||||||
currentPath={path}
|
handleSubmit={this.handleSubmit}
|
||||||
onDirentItemClick={this.onDirentItemClick}
|
/>
|
||||||
onRepoItemClick={this.onRepoItemClick}
|
|
||||||
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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Alert } from 'reactstrap';
|
import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Alert } from 'reactstrap';
|
||||||
import { gettext } from '../../utils/constants';
|
import { gettext } from '../../utils/constants';
|
||||||
import FileChooser from '../file-chooser/file-chooser';
|
import FileChooser from '../file-chooser';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
sharedToken: PropTypes.string.isRequired,
|
sharedToken: PropTypes.string.isRequired,
|
||||||
|
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Alert } from 'reactstrap';
|
import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Alert } from 'reactstrap';
|
||||||
import { gettext } from '../../utils/constants';
|
import { gettext } from '../../utils/constants';
|
||||||
import { seafileAPI } from '../../utils/seafile-api';
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
import FileChooser from '../file-chooser/file-chooser';
|
import FileChooser from '../file-chooser';
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
|
232
frontend/src/components/dialog/select-dirent-body.jsx
Normal file
232
frontend/src/components/dialog/select-dirent-body.jsx
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Button, ModalFooter, ModalBody, Alert, Row, Col } from 'reactstrap';
|
||||||
|
import toaster from '../toast';
|
||||||
|
import Searcher, { SearchStatus } from '../file-chooser/searcher';
|
||||||
|
import RepoListWrapper, { MODE_TYPE_MAP } from '../file-chooser/repo-list-wrapper';
|
||||||
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
|
import { gettext, isPro } from '../../utils/constants';
|
||||||
|
import { Utils } from '../../utils/utils';
|
||||||
|
import { RepoInfo } from '../../models';
|
||||||
|
|
||||||
|
const LibraryOption = ({ mode, label, currentMode, selectedMode }) => {
|
||||||
|
return (
|
||||||
|
<div className={`repo-list-item ${mode === currentMode ? 'active' : ''}`} onClick={() => selectedMode(mode)}>
|
||||||
|
<span className='library'>{label}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
class SelectDirentBody extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
mode: MODE_TYPE_MAP.ONLY_CURRENT_LIBRARY,
|
||||||
|
currentRepoInfo: null,
|
||||||
|
repoList: [],
|
||||||
|
selectedSearchedItem: null,
|
||||||
|
selectedRepo: null,
|
||||||
|
browsingPath: '',
|
||||||
|
searchStatus: SearchStatus.IDLE,
|
||||||
|
errMessage: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.fetchRepoInfo();
|
||||||
|
this.fetchRepoList();
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchRepoInfo = async () => {
|
||||||
|
try {
|
||||||
|
const res = await seafileAPI.getRepoInfo(this.props.repoID);
|
||||||
|
const repoInfo = new RepoInfo(res.data);
|
||||||
|
this.props.setSelectedPath('/');
|
||||||
|
this.setState({
|
||||||
|
currentRepoInfo: repoInfo,
|
||||||
|
selectedRepo: repoInfo,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
const errMessage = Utils.getErrorMsg(err);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchRepoList = async () => {
|
||||||
|
try {
|
||||||
|
const res = await seafileAPI.listRepos();
|
||||||
|
const repos = res.data.repos;
|
||||||
|
const repoList = repos.filter((repo) => repo.permission === 'rw' && repo.repo_id !== this.props.repoID);
|
||||||
|
const sortedRepoList = Utils.sortRepos(repoList, 'name', 'asc');
|
||||||
|
const selectedRepo = sortedRepoList.find((repo) => repo.repo_id === this.props.repoID);
|
||||||
|
const path = this.props.path.substring(0, this.props.path.length - 1);
|
||||||
|
|
||||||
|
this.props.setSelectedPath(path);
|
||||||
|
this.setState({
|
||||||
|
repoList: sortedRepoList,
|
||||||
|
selectedRepo: selectedRepo || this.state.selectedRepo,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
const errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onUpdateSearchStatus = (status) => {
|
||||||
|
this.setState({ searchStatus: status });
|
||||||
|
};
|
||||||
|
|
||||||
|
onUpdateRepoList = (repoList) => {
|
||||||
|
this.setState({ repoList: repoList });
|
||||||
|
};
|
||||||
|
|
||||||
|
selectSearchedItem = (item) => {
|
||||||
|
this.setState({ selectedSearchedItem: item });
|
||||||
|
};
|
||||||
|
|
||||||
|
onSelectSearchedRepo = (repo) => {
|
||||||
|
this.setState({
|
||||||
|
selectedRepo: repo,
|
||||||
|
mode: repo.repo_id === this.props.repoID ? MODE_TYPE_MAP.ONLY_CURRENT_LIBRARY : MODE_TYPE_MAP.ONLY_OTHER_LIBRARIES,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
selectPath = (path) => {
|
||||||
|
this.props.setSelectedPath(path);
|
||||||
|
};
|
||||||
|
|
||||||
|
setBrowsingPath = (path) => {
|
||||||
|
this.setState({ browsingPath: path });
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSubmit = () => {
|
||||||
|
if (this.props.handleSubmit) {
|
||||||
|
this.props.handleSubmit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onCancel = () => {
|
||||||
|
if (this.props.onCancel) {
|
||||||
|
this.props.onCancel();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onDirentItemClick = (repo, selectedPath) => {
|
||||||
|
this.props.selectRepo(repo);
|
||||||
|
this.props.setSelectedPath(selectedPath);
|
||||||
|
this.props.setErrMessage('');
|
||||||
|
this.setState({ selectedRepo: repo });
|
||||||
|
};
|
||||||
|
|
||||||
|
onRepoItemClick = (repo) => {
|
||||||
|
this.props.selectRepo(repo);
|
||||||
|
this.props.setSelectedPath('/');
|
||||||
|
this.props.setErrMessage('');
|
||||||
|
this.setState({ selectedRepo: repo });
|
||||||
|
};
|
||||||
|
|
||||||
|
selectedMode = (mode) => {
|
||||||
|
const { repoID, path } = this.props;
|
||||||
|
|
||||||
|
// reset selecting status
|
||||||
|
this.props.selectRepo({ repo_id: repoID });
|
||||||
|
this.props.setSelectedPath(path);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
mode,
|
||||||
|
selectedSearchedItem: null,
|
||||||
|
searchStatus: SearchStatus.RESULTS,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { path, selectedPath, isSupportOtherLibraries, errMessage } = this.props;
|
||||||
|
const { mode, searchStatus, selectedSearchedItem, selectedRepo, repoList, currentRepoInfo, browsingPath } = this.state;
|
||||||
|
let repoListWrapperKey = 'repo-list-wrapper';
|
||||||
|
if (selectedSearchedItem && selectedSearchedItem.repoID) {
|
||||||
|
repoListWrapperKey = `${repoListWrapperKey}-${selectedSearchedItem.repoID}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row>
|
||||||
|
<Col className='repo-list-col border-right'>
|
||||||
|
{isPro && (
|
||||||
|
<Searcher
|
||||||
|
searchStatus={searchStatus}
|
||||||
|
onUpdateSearchStatus={this.onUpdateSearchStatus}
|
||||||
|
onDirentItemClick={this.onDirentItemClick}
|
||||||
|
selectSearchedItem={this.selectSearchedItem}
|
||||||
|
selectRepo={this.onSelectSearchedRepo}
|
||||||
|
selectPath={this.selectPath}
|
||||||
|
setBrowsingPath={this.setBrowsingPath}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<LibraryOption
|
||||||
|
mode={MODE_TYPE_MAP.ONLY_CURRENT_LIBRARY}
|
||||||
|
label={gettext('Current Library')}
|
||||||
|
currentMode={mode}
|
||||||
|
selectedMode={this.selectedMode}
|
||||||
|
/>
|
||||||
|
{isSupportOtherLibraries && (
|
||||||
|
<LibraryOption
|
||||||
|
mode={MODE_TYPE_MAP.ONLY_OTHER_LIBRARIES}
|
||||||
|
label={gettext('Other Libraries')}
|
||||||
|
currentMode={mode}
|
||||||
|
selectedMode={this.selectedMode}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<LibraryOption
|
||||||
|
mode={MODE_TYPE_MAP.RECENTLY_USED}
|
||||||
|
label={gettext('Recently Used')}
|
||||||
|
currentMode={mode}
|
||||||
|
selectedMode={this.selectedMode}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col className='file-list-col'>
|
||||||
|
<ModalBody>
|
||||||
|
{currentRepoInfo && (
|
||||||
|
<RepoListWrapper
|
||||||
|
key={repoListWrapperKey}
|
||||||
|
mode={mode}
|
||||||
|
currentPath={path}
|
||||||
|
isBrowsing={searchStatus === SearchStatus.BROWSING}
|
||||||
|
browsingPath={browsingPath}
|
||||||
|
selectedItemInfo={selectedSearchedItem}
|
||||||
|
currentRepoInfo={currentRepoInfo}
|
||||||
|
selectedRepo={selectedRepo}
|
||||||
|
selectedPath={selectedPath}
|
||||||
|
repoList={repoList}
|
||||||
|
handleClickRepo={this.onRepoItemClick}
|
||||||
|
handleClickDirent={this.onDirentItemClick}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{errMessage && <Alert color="danger" className="alert-message">{errMessage}</Alert>}
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button color="secondary" onClick={this.onCancel}>{gettext('Cancel')}</Button>
|
||||||
|
<Button color="primary" onClick={this.handleSubmit}>{gettext('Submit')}</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectDirentBody.propTypes = {
|
||||||
|
path: PropTypes.string,
|
||||||
|
selectedPath: PropTypes.string,
|
||||||
|
repoID: PropTypes.string,
|
||||||
|
isSupportOtherLibraries: PropTypes.bool,
|
||||||
|
onCancel: PropTypes.func,
|
||||||
|
handleSubmit: PropTypes.func,
|
||||||
|
selectRepo: PropTypes.func,
|
||||||
|
setSelectedPath: PropTypes.func,
|
||||||
|
setErrMessage: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
SelectDirentBody.defaultProps = {
|
||||||
|
isSupportOtherLibraries: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SelectDirentBody;
|
@@ -1,15 +1,14 @@
|
|||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Input } from 'reactstrap';
|
import { Input } from 'reactstrap';
|
||||||
|
import toaster from '../toast';
|
||||||
|
import Loading from '../loading';
|
||||||
|
import RepoListWrapper from './repo-list-wrapper';
|
||||||
|
import SearchedListView from './searched-list-view';
|
||||||
|
import RepoInfo from '../../models/repo-info';
|
||||||
import { seafileAPI } from '../../utils/seafile-api';
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
import { gettext, isPro } from '../../utils/constants';
|
import { gettext, isPro } from '../../utils/constants';
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
import toaster from '../toast';
|
|
||||||
import RepoInfo from '../../models/repo-info';
|
|
||||||
import RepoListView from './repo-list-view';
|
|
||||||
import RecentlyUsedListView from './recently-used-list-view';
|
|
||||||
import Loading from '../loading';
|
|
||||||
import SearchedListView from './searched-list-view';
|
|
||||||
|
|
||||||
import '../../css/file-chooser.css';
|
import '../../css/file-chooser.css';
|
||||||
|
|
||||||
@@ -28,6 +27,10 @@ const propTypes = {
|
|||||||
]).isRequired,
|
]).isRequired,
|
||||||
fileSuffixes: PropTypes.arrayOf(PropTypes.string),
|
fileSuffixes: PropTypes.arrayOf(PropTypes.string),
|
||||||
currentPath: PropTypes.string,
|
currentPath: PropTypes.string,
|
||||||
|
searchResults: PropTypes.array,
|
||||||
|
selectedSearchedItem: PropTypes.object,
|
||||||
|
selectedRepo: PropTypes.object,
|
||||||
|
selectedPath: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
@@ -38,6 +41,10 @@ const defaultProps = {
|
|||||||
onRepoItemClick: () => {},
|
onRepoItemClick: () => {},
|
||||||
fileSuffixes: [],
|
fileSuffixes: [],
|
||||||
currentPath: '',
|
currentPath: '',
|
||||||
|
searchResults: [],
|
||||||
|
selectedSearchedItem: {},
|
||||||
|
selectedRepo: null,
|
||||||
|
selectedPath: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
class FileChooser extends React.Component {
|
class FileChooser extends React.Component {
|
||||||
@@ -45,7 +52,6 @@ class FileChooser extends React.Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
hasRequest: false,
|
|
||||||
isCurrentRepoShow: true,
|
isCurrentRepoShow: true,
|
||||||
isOtherRepoShow: false,
|
isOtherRepoShow: false,
|
||||||
repoList: [],
|
repoList: [],
|
||||||
@@ -129,34 +135,30 @@ class FileChooser extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onOtherRepoToggle = async () => {
|
onOtherRepoToggle = async () => {
|
||||||
if (!this.state.hasRequest) {
|
try {
|
||||||
try {
|
const res = await seafileAPI.listRepos();
|
||||||
const res = await seafileAPI.listRepos();
|
const repos = res.data.repos;
|
||||||
const repos = res.data.repos;
|
const repoList = [];
|
||||||
const repoList = [];
|
const repoIdList = [];
|
||||||
const repoIdList = [];
|
|
||||||
|
|
||||||
repos.forEach(repo => {
|
repos.forEach(repo => {
|
||||||
if (repo.permission !== 'rw') return;
|
if (repo.permission !== 'rw') return;
|
||||||
if (this.props.repoID && repo.repo_name === this.state.currentRepoInfo.repo_name) return;
|
if (this.props.repoID && repo.repo_name === this.state.currentRepoInfo.repo_name) return;
|
||||||
if (repoIdList.includes(repo.repo_id)) return;
|
if (repoIdList.includes(repo.repo_id)) return;
|
||||||
|
|
||||||
repoList.push(repo);
|
repoList.push(repo);
|
||||||
repoIdList.push(repo.repo_id);
|
repoIdList.push(repo.repo_id);
|
||||||
});
|
});
|
||||||
|
|
||||||
const sortedRepoList = Utils.sortRepos(repoList, 'name', 'asc');
|
const sortedRepoList = Utils.sortRepos(repoList, 'name', 'asc');
|
||||||
this.setState({
|
this.setState({
|
||||||
repoList: sortedRepoList,
|
repoList: sortedRepoList,
|
||||||
isOtherRepoShow: !this.state.isOtherRepoShow,
|
isOtherRepoShow: !this.state.isOtherRepoShow,
|
||||||
selectedItemInfo: {},
|
selectedItemInfo: {},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errMessage = Utils.getErrorMsg(error);
|
const errMessage = Utils.getErrorMsg(error);
|
||||||
toaster.danger(errMessage);
|
toaster.danger(errMessage);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.setState({ isOtherRepoShow: !this.state.isOtherRepoShow });
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -214,6 +216,7 @@ class FileChooser extends React.Component {
|
|||||||
|
|
||||||
if (this.inputValue === '') {
|
if (this.inputValue === '') {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
isSearching: false,
|
||||||
isResultGot: false,
|
isResultGot: false,
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
@@ -339,7 +342,7 @@ class FileChooser extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { repoID } = this.props;
|
const { repoID } = this.props;
|
||||||
const { hasRequest, currentRepoInfo } = this.state;
|
const { currentRepoInfo } = this.state;
|
||||||
|
|
||||||
const selectedItemInfo = {
|
const selectedItemInfo = {
|
||||||
repoID: item.repo_id,
|
repoID: item.repo_id,
|
||||||
@@ -406,11 +409,7 @@ class FileChooser extends React.Component {
|
|||||||
if (repoID && item.repo_id === repoID) {
|
if (repoID && item.repo_id === repoID) {
|
||||||
await fetchRepoInfo();
|
await fetchRepoInfo();
|
||||||
} else {
|
} else {
|
||||||
if (!hasRequest) {
|
await fetchRepoList();
|
||||||
await fetchRepoList();
|
|
||||||
} else {
|
|
||||||
this.setState(prevState => ({ isOtherRepoShow: !prevState.isOtherRepoShow }));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -424,6 +423,7 @@ class FileChooser extends React.Component {
|
|||||||
this.timer = null;
|
this.timer = null;
|
||||||
this.source = null;
|
this.source = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
onScroll = (event) => {
|
onScroll = (event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
};
|
};
|
||||||
@@ -431,122 +431,26 @@ class FileChooser extends React.Component {
|
|||||||
renderRepoListView = () => {
|
renderRepoListView = () => {
|
||||||
const { mode, currentPath, isShowFile, fileSuffixes } = this.props;
|
const { mode, currentPath, isShowFile, fileSuffixes } = this.props;
|
||||||
const { isCurrentRepoShow, isOtherRepoShow, currentRepoInfo, repoList, selectedRepo, selectedPath, selectedItemInfo } = this.state;
|
const { isCurrentRepoShow, isOtherRepoShow, currentRepoInfo, repoList, selectedRepo, selectedPath, selectedItemInfo } = this.state;
|
||||||
const recentlyUsedList = JSON.parse(localStorage.getItem('recently-used-list')) || [];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='file-chooser-scroll-wrapper' onScroll={this.onScroll}>
|
<RepoListWrapper
|
||||||
<div className="file-chooser-container user-select-none">
|
mode={mode}
|
||||||
{mode === 'current_repo_and_other_repos' && (
|
currentPath={currentPath}
|
||||||
<Fragment>
|
isShowFile={isShowFile}
|
||||||
<div className="list-view">
|
fileSuffixes={fileSuffixes}
|
||||||
<div className="file-chooser-list-view-header">
|
isBrowsing={this.state.isBrowsing}
|
||||||
<span className={`item-toggle sf3-font ${isCurrentRepoShow ? 'sf3-font-down' : 'sf3-font-down rotate-270 d-inline-block'}`} onClick={this.onCurrentRepoToggle}></span>
|
browsingPath={this.state.browsingPath}
|
||||||
<span className="library">{gettext('Current Library')}</span>
|
selectedItemInfo={selectedItemInfo}
|
||||||
</div>
|
currentRepoInfo={currentRepoInfo}
|
||||||
{
|
selectedRepo={selectedRepo}
|
||||||
isCurrentRepoShow && currentRepoInfo &&
|
isCurrentRepoShow={isCurrentRepoShow}
|
||||||
<RepoListView
|
isOtherRepoShow={isOtherRepoShow}
|
||||||
initToShowChildren={true}
|
selectedPath={selectedPath}
|
||||||
currentRepoInfo={currentRepoInfo}
|
repoList={repoList}
|
||||||
currentPath={currentPath}
|
onCurrentRepoToggle={this.onCurrentRepoToggle}
|
||||||
selectedRepo={selectedRepo}
|
onOtherRepoToggle={this.onOtherRepoToggle}
|
||||||
selectedPath={selectedPath}
|
handleClickRepo={this.onRepoItemClick}
|
||||||
onRepoItemClick={this.onRepoItemClick}
|
handleClickDirent={this.onDirentItemClick}
|
||||||
onDirentItemClick={this.onDirentItemClick}
|
/>
|
||||||
isShowFile={isShowFile}
|
|
||||||
fileSuffixes={fileSuffixes}
|
|
||||||
selectedItemInfo={selectedItemInfo}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div className="list-view">
|
|
||||||
<div className="file-chooser-list-view-header">
|
|
||||||
<span className={`item-toggle sf3-font ${isOtherRepoShow ? 'sf3-font-down' : 'sf3-font-down rotate-270 d-inline-block'}`} onClick={this.onOtherRepoToggle}></span>
|
|
||||||
<span className="library">{gettext('Other Libraries')}</span>
|
|
||||||
</div>
|
|
||||||
{
|
|
||||||
isOtherRepoShow &&
|
|
||||||
<RepoListView
|
|
||||||
initToShowChildren={false}
|
|
||||||
repoList={repoList}
|
|
||||||
selectedRepo={selectedRepo}
|
|
||||||
selectedPath={selectedPath}
|
|
||||||
onRepoItemClick={this.onRepoItemClick}
|
|
||||||
onDirentItemClick={this.onDirentItemClick}
|
|
||||||
isShowFile={isShowFile}
|
|
||||||
fileSuffixes={fileSuffixes}
|
|
||||||
selectedItemInfo={selectedItemInfo}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</Fragment>
|
|
||||||
)}
|
|
||||||
{mode === 'only_current_library' && (
|
|
||||||
<div className="list-view">
|
|
||||||
<RepoListView
|
|
||||||
initToShowChildren={true}
|
|
||||||
currentRepoInfo={currentRepoInfo}
|
|
||||||
currentPath={currentPath}
|
|
||||||
selectedRepo={selectedRepo}
|
|
||||||
selectedPath={selectedPath}
|
|
||||||
onRepoItemClick={this.onRepoItemClick}
|
|
||||||
onDirentItemClick={this.onDirentItemClick}
|
|
||||||
isShowFile={isShowFile}
|
|
||||||
fileSuffixes={fileSuffixes}
|
|
||||||
selectedItemInfo={selectedItemInfo}
|
|
||||||
isBrowsing={this.state.isBrowsing}
|
|
||||||
browsingPath={this.state.browsingPath}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{mode === 'only_all_repos' && (
|
|
||||||
<div className="file-chooser-container">
|
|
||||||
<div className="list-view">
|
|
||||||
<div className="file-chooser-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}
|
|
||||||
isBrowsing={this.state.isBrowsing}
|
|
||||||
browsingPath={this.state.browsingPath}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{mode === 'recently_used' && (
|
|
||||||
<div className="list-view">
|
|
||||||
<RecentlyUsedListView
|
|
||||||
recentlyUsedList={recentlyUsedList}
|
|
||||||
onDirentItemClick={this.onDirentItemClick}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -560,7 +464,7 @@ class FileChooser extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
{isPro && mode !== 'recently_used' && (
|
{(isPro && mode !== 'recently_used') && (
|
||||||
<div className="file-chooser-search-input">
|
<div className="file-chooser-search-input">
|
||||||
<Input className="search-input" placeholder={gettext('Search')} type='text' value={searchInfo} onChange={this.onSearchInfoChanged}></Input>
|
<Input className="search-input" placeholder={gettext('Search')} type='text' value={searchInfo} onChange={this.onSearchInfoChanged}></Input>
|
||||||
{searchInfo.length !== 0 && (
|
{searchInfo.length !== 0 && (
|
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const RecentlyUsedListItem = ({ item, isSelected, onItemClick }) => {
|
const RecentlyUsedListItem = ({ item, isSelected, onItemClick }) => {
|
||||||
const title = item.path.split('/').pop();
|
const title = item.path === '/' ? item.path : item.path.split('/').pop();
|
||||||
|
|
||||||
const handleItemClick = () => {
|
const handleItemClick = () => {
|
||||||
onItemClick(item.repo, item.path);
|
onItemClick(item.repo, item.path);
|
||||||
|
@@ -35,6 +35,7 @@ class RepoListItem extends React.Component {
|
|||||||
hasLoaded: false,
|
hasLoaded: false,
|
||||||
isMounted: false,
|
isMounted: false,
|
||||||
};
|
};
|
||||||
|
this.loadRepoTimer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -45,17 +46,16 @@ class RepoListItem extends React.Component {
|
|||||||
const { repoID, filePath } = selectedItemInfo || {};
|
const { repoID, filePath } = selectedItemInfo || {};
|
||||||
if (repoID && repoID === repo.repo_id) {
|
if (repoID && repoID === repo.repo_id) {
|
||||||
this.loadRepoDirentList(repo);
|
this.loadRepoDirentList(repo);
|
||||||
setTimeout(() => {
|
this.loadRepoTimer = setTimeout(() => {
|
||||||
this.setState({ isShowChildren: true });
|
this.setState({ isShowChildren: true });
|
||||||
this.loadNodeAndParentsByPath(repoID, filePath);
|
this.loadNodeAndParentsByPath(repoID, filePath);
|
||||||
}, 0);
|
}, 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// the repo is current repo and currentPath is not '/'
|
if (repo.repo_id === this.props.selectedRepo.repo_id || isCurrentRepo) {
|
||||||
if (isCurrentRepo && !repoID) {
|
|
||||||
this.loadRepoDirentList(repo);
|
this.loadRepoDirentList(repo);
|
||||||
setTimeout(() => {
|
this.loadRepoTimer = setTimeout(() => {
|
||||||
const repoID = repo.repo_id;
|
const repoID = repo.repo_id;
|
||||||
if (isCurrentRepo && currentPath && currentPath != '/') {
|
if (isCurrentRepo && currentPath && currentPath != '/') {
|
||||||
const expandNode = true;
|
const expandNode = true;
|
||||||
@@ -65,38 +65,20 @@ class RepoListItem extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
|
||||||
if (prevProps.isBrowsing && !this.props.isBrowsing) {
|
|
||||||
this.setState({
|
|
||||||
treeData: treeHelper.buildTree(),
|
|
||||||
isShowChildren: this.props.initToShowChildren,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { isCurrentRepo, currentPath, repo, selectedItemInfo } = this.props;
|
|
||||||
const { repoID } = selectedItemInfo || {};
|
|
||||||
|
|
||||||
if (isCurrentRepo && !repoID) {
|
|
||||||
this.loadRepoDirentList(repo);
|
|
||||||
setTimeout(() => {
|
|
||||||
const repoID = repo.repo_id;
|
|
||||||
if (isCurrentRepo && currentPath && currentPath != '/') {
|
|
||||||
const expandNode = true;
|
|
||||||
this.loadNodeAndParentsByPath(repoID, currentPath, expandNode);
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.setState({ isMounted: false });
|
this.clearLoadRepoTimer();
|
||||||
|
this.setState({ isMounted: false, hasLoaded: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearLoadRepoTimer = () => {
|
||||||
|
if (!this.loadRepoTimer) return;
|
||||||
|
clearTimeout(this.loadRepoTimer);
|
||||||
|
this.loadRepoTimer = null;
|
||||||
|
};
|
||||||
|
|
||||||
loadRepoDirentList = async (repo) => {
|
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;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@@ -19,6 +19,20 @@ const propTypes = {
|
|||||||
browsingPath: PropTypes.string,
|
browsingPath: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
currentRepoInfo: null,
|
||||||
|
isShowFile: false,
|
||||||
|
repo: null,
|
||||||
|
repoList: [],
|
||||||
|
selectedRepo: null,
|
||||||
|
selectedPath: '',
|
||||||
|
fileSuffixes: [],
|
||||||
|
selectedItemInfo: null,
|
||||||
|
currentPath: '',
|
||||||
|
isBrowsing: false,
|
||||||
|
browsingPath: '',
|
||||||
|
};
|
||||||
|
|
||||||
class RepoListView extends React.Component {
|
class RepoListView extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -56,5 +70,6 @@ class RepoListView extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
RepoListView.propTypes = propTypes;
|
RepoListView.propTypes = propTypes;
|
||||||
|
RepoListView.defaultProps = defaultProps;
|
||||||
|
|
||||||
export default RepoListView;
|
export default RepoListView;
|
||||||
|
170
frontend/src/components/file-chooser/repo-list-wrapper.jsx
Normal file
170
frontend/src/components/file-chooser/repo-list-wrapper.jsx
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import RepoListView from './repo-list-view';
|
||||||
|
import RecentlyUsedListView from './recently-used-list-view';
|
||||||
|
import { gettext } from '../../utils/constants';
|
||||||
|
|
||||||
|
export const MODE_TYPE_MAP = {
|
||||||
|
CURRENT_AND_OTHER_REPOS: 'current_repo_and_other_repos',
|
||||||
|
ONLY_CURRENT_LIBRARY: 'only_current_library',
|
||||||
|
ONLY_ALL_REPOS: 'only_all_repos',
|
||||||
|
ONLY_OTHER_LIBRARIES: 'only_other_libraries',
|
||||||
|
RECENTLY_USED: 'recently_used',
|
||||||
|
};
|
||||||
|
|
||||||
|
const RepoListWrapper = (props) => {
|
||||||
|
const {
|
||||||
|
mode, isShowFile, fileSuffixes, currentPath, isBrowsing, browsingPath, isCurrentRepoShow, currentRepoInfo, selectedRepo,
|
||||||
|
selectedPath, isOtherRepoShow, selectedItemInfo, repoList,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const renderRecentlyUsed = () => {
|
||||||
|
const recentlyUsedList = JSON.parse(localStorage.getItem('recently-used-list')) || [];
|
||||||
|
return (
|
||||||
|
<div className="list-view">
|
||||||
|
<RecentlyUsedListView
|
||||||
|
recentlyUsedList={recentlyUsedList}
|
||||||
|
onDirentItemClick={props.handleClickDirent}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onScroll = (event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='file-chooser-scroll-wrapper' onScroll={onScroll}>
|
||||||
|
<div className="file-chooser-container user-select-none">
|
||||||
|
{mode === MODE_TYPE_MAP.CURRENT_AND_OTHER_REPOS && (
|
||||||
|
<>
|
||||||
|
<div className="list-view">
|
||||||
|
<div className="file-chooser-list-view-header">
|
||||||
|
<span className={`item-toggle sf3-font ${isCurrentRepoShow ? 'sf3-font-down' : 'sf3-font-down rotate-270 d-inline-block'}`} onClick={props.onCurrentRepoToggle}></span>
|
||||||
|
<span className="library">{gettext('Current Library')}</span>
|
||||||
|
</div>
|
||||||
|
{(isCurrentRepoShow && currentRepoInfo) &&
|
||||||
|
<RepoListView
|
||||||
|
initToShowChildren
|
||||||
|
currentRepoInfo={currentRepoInfo}
|
||||||
|
currentPath={currentPath}
|
||||||
|
selectedRepo={selectedRepo}
|
||||||
|
selectedPath={selectedPath}
|
||||||
|
isShowFile={isShowFile}
|
||||||
|
fileSuffixes={fileSuffixes}
|
||||||
|
selectedItemInfo={selectedItemInfo}
|
||||||
|
onRepoItemClick={props.handleClickRepo}
|
||||||
|
onDirentItemClick={props.handleClickDirent}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className="list-view">
|
||||||
|
<div className="file-chooser-list-view-header">
|
||||||
|
<span className={`item-toggle sf3-font ${isOtherRepoShow ? 'sf3-font-down' : 'sf3-font-down rotate-270 d-inline-block'}`} onClick={props.onOtherRepoToggle}></span>
|
||||||
|
<span className="library">{gettext('Other Libraries')}</span>
|
||||||
|
</div>
|
||||||
|
{isOtherRepoShow &&
|
||||||
|
<RepoListView
|
||||||
|
initToShowChildren
|
||||||
|
repoList={repoList}
|
||||||
|
selectedRepo={selectedRepo}
|
||||||
|
selectedPath={selectedPath}
|
||||||
|
isShowFile={isShowFile}
|
||||||
|
fileSuffixes={fileSuffixes}
|
||||||
|
selectedItemInfo={selectedItemInfo}
|
||||||
|
onRepoItemClick={props.handleClickRepo}
|
||||||
|
onDirentItemClick={props.handleClickDirent}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{mode === MODE_TYPE_MAP.ONLY_CURRENT_LIBRARY && (
|
||||||
|
<div className="list-view">
|
||||||
|
<RepoListView
|
||||||
|
initToShowChildren
|
||||||
|
currentRepoInfo={currentRepoInfo}
|
||||||
|
currentPath={currentPath}
|
||||||
|
selectedRepo={selectedRepo}
|
||||||
|
selectedPath={selectedPath}
|
||||||
|
isShowFile={isShowFile}
|
||||||
|
fileSuffixes={fileSuffixes}
|
||||||
|
selectedItemInfo={selectedItemInfo}
|
||||||
|
isBrowsing={isBrowsing}
|
||||||
|
browsingPath={browsingPath}
|
||||||
|
onRepoItemClick={props.handleClickRepo}
|
||||||
|
onDirentItemClick={props.handleClickDirent}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{mode === MODE_TYPE_MAP.ONLY_ALL_REPOS && (
|
||||||
|
<div className="file-chooser-container">
|
||||||
|
<div className="list-view">
|
||||||
|
<div className="file-chooser-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}
|
||||||
|
isShowFile={isShowFile}
|
||||||
|
fileSuffixes={fileSuffixes}
|
||||||
|
selectedItemInfo={selectedItemInfo}
|
||||||
|
onRepoItemClick={props.handleClickRepo}
|
||||||
|
onDirentItemClick={props.handleClickDirent}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{mode === MODE_TYPE_MAP.ONLY_OTHER_LIBRARIES && (
|
||||||
|
<div className="list-view">
|
||||||
|
<RepoListView
|
||||||
|
initToShowChildren={false}
|
||||||
|
repoList={repoList}
|
||||||
|
selectedRepo={selectedRepo}
|
||||||
|
selectedPath={selectedPath}
|
||||||
|
isShowFile={isShowFile}
|
||||||
|
fileSuffixes={fileSuffixes}
|
||||||
|
selectedItemInfo={selectedItemInfo}
|
||||||
|
isBrowsing={isBrowsing}
|
||||||
|
browsingPath={browsingPath}
|
||||||
|
onRepoItemClick={props.handleClickRepo}
|
||||||
|
onDirentItemClick={props.handleClickDirent}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{mode === MODE_TYPE_MAP.RECENTLY_USED && renderRecentlyUsed()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
RepoListWrapper.propTypes = {
|
||||||
|
mode: PropTypes.string,
|
||||||
|
currentPath: PropTypes.string,
|
||||||
|
isShowFile: PropTypes.bool,
|
||||||
|
fileSuffixes: PropTypes.array,
|
||||||
|
isBrowsing: PropTypes.bool,
|
||||||
|
browsingPath: PropTypes.string,
|
||||||
|
selectedItemInfo: PropTypes.object,
|
||||||
|
currentRepoInfo: PropTypes.object,
|
||||||
|
selectedRepo: PropTypes.object,
|
||||||
|
isCurrentRepoShow: PropTypes.bool,
|
||||||
|
isOtherRepoShow: PropTypes.bool,
|
||||||
|
selectedPath: PropTypes.string,
|
||||||
|
repoList: PropTypes.array,
|
||||||
|
onCurrentRepoToggle: PropTypes.func,
|
||||||
|
onOtherRepoToggle: PropTypes.func,
|
||||||
|
handleClickRepo: PropTypes.func,
|
||||||
|
handleClickDirent: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
RepoListWrapper.defaultProps = {
|
||||||
|
isShowFile: false,
|
||||||
|
fileSuffixes: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RepoListWrapper;
|
@@ -40,13 +40,12 @@ class SearchedListItem extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
let { item, currentItem } = this.props;
|
let { item, currentItem } = this.props;
|
||||||
let folderIconUrl = item.link_content ? Utils.getFolderIconUrl(false, 192) : Utils.getDefaultLibIconUrl(false);
|
|
||||||
let fileIconUrl = item.is_dir ? folderIconUrl : Utils.getFileIconUrl(item.name);
|
|
||||||
return (
|
return (
|
||||||
<tr
|
<tr
|
||||||
className={classnames('searched-list-item', {
|
className={classnames('searched-list-item', {
|
||||||
'tr-highlight': this.state.highlight,
|
'tr-highlight': this.state.highlight,
|
||||||
'tr-active': currentItem && item.repo_id === currentItem.repo_id && item.path === currentItem.path
|
'tr-active': currentItem && item.repo_id === currentItem.repo_id && item.path === currentItem.path,
|
||||||
|
'searched-dir': item.is_dir,
|
||||||
})}
|
})}
|
||||||
onClick={this.onClick}
|
onClick={this.onClick}
|
||||||
onMouseEnter={this.onMouseEnter}
|
onMouseEnter={this.onMouseEnter}
|
||||||
@@ -54,7 +53,7 @@ class SearchedListItem extends React.Component {
|
|||||||
onDoubleClick={this.searchItemDoubleClick}
|
onDoubleClick={this.searchItemDoubleClick}
|
||||||
>
|
>
|
||||||
<td className="text-center searched-item-icon">
|
<td className="text-center searched-item-icon">
|
||||||
<img className="item-img" src={fileIconUrl} alt="" width="24"/>
|
{item.is_dir ? <span className="icon sf3-font sf3-font-folder tree-node-icon"></span> : <img className="item-img" src={Utils.getFileIconUrl(item.name)} alt="" width="24"/>}
|
||||||
</td>
|
</td>
|
||||||
<td className='searched-item-link'>
|
<td className='searched-item-link'>
|
||||||
<span className="item-link">{item.repo_name}/{item.link_content}</span>
|
<span className="item-link">{item.repo_name}/{item.link_content}</span>
|
||||||
|
55
frontend/src/components/file-chooser/searcher/index.css
Normal file
55
frontend/src/components/file-chooser/searcher/index.css
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
.file-chooser-searcher.search-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-chooser-searcher .search-input-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-chooser-searcher .search-icon-left {
|
||||||
|
min-width: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-chooser-searcher .search-input-container .search-input {
|
||||||
|
width: 100%;
|
||||||
|
height: 2.375rem;
|
||||||
|
padding-left: 2rem;
|
||||||
|
padding-right: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-chooser-searcher .search-input-container .search-control {
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
right: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-chooser-search-results-popover .popover {
|
||||||
|
min-width: 400px;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-chooser-search-results-popover .search-results-popover-body {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-chooser-search-results-popover .search-results-popover-body .search-results-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-chooser-search-results-popover .search-results-popover-body .searched-list-item.searched-dir td {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-chooser-search-results-popover .search-results-popover-body .searched-item-icon {
|
||||||
|
border-radius: 3px 0 0 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-chooser-search-results-popover .search-results-popover-body .searched-item-link {
|
||||||
|
border-radius: 0 3px 3px 0;
|
||||||
|
}
|
205
frontend/src/components/file-chooser/searcher/index.js
Normal file
205
frontend/src/components/file-chooser/searcher/index.js
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
import React, { useState, useRef, useCallback } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Input, UncontrolledPopover } from 'reactstrap';
|
||||||
|
import Loading from '../../loading';
|
||||||
|
import toaster from '../../toast';
|
||||||
|
import RepoInfo from '../../../models/repo-info';
|
||||||
|
import SearchedListView from '../searched-list-view';
|
||||||
|
import { gettext } from '../../../utils/constants';
|
||||||
|
import { seafileAPI } from '../../../utils/seafile-api';
|
||||||
|
import { Utils } from '../../../utils/utils';
|
||||||
|
|
||||||
|
import './index.css';
|
||||||
|
|
||||||
|
export const SearchStatus = {
|
||||||
|
IDLE: 'idle',
|
||||||
|
LOADING: 'loading',
|
||||||
|
RESULTS: 'results',
|
||||||
|
NO_RESULTS: 'no_results',
|
||||||
|
BROWSING: 'browsing',
|
||||||
|
};
|
||||||
|
|
||||||
|
const Searcher = ({ searchStatus, onUpdateSearchStatus, onDirentItemClick, selectSearchedItem, selectRepo, selectPath, setBrowsingPath }) => {
|
||||||
|
const [inputValue, setInputValue] = useState('');
|
||||||
|
const [isResultsPopoverOpen, setIsResultsPopoverOpen] = useState(false);
|
||||||
|
const [searchResults, setSearchResults] = useState([]);
|
||||||
|
|
||||||
|
const inputRef = useRef(null);
|
||||||
|
|
||||||
|
const searchTimer = useRef(null);
|
||||||
|
const source = useRef(null);
|
||||||
|
|
||||||
|
const onPopoverToggle = useCallback((show) => {
|
||||||
|
setIsResultsPopoverOpen(show);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSearchInputChange = (e) => {
|
||||||
|
const newValue = e.target.value.trim();
|
||||||
|
setInputValue(newValue);
|
||||||
|
|
||||||
|
if (newValue.length === 0) {
|
||||||
|
onUpdateSearchStatus(SearchStatus.IDLE);
|
||||||
|
setSearchResults([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpdateSearchStatus(SearchStatus.LOADING);
|
||||||
|
onPopoverToggle(true);
|
||||||
|
|
||||||
|
const queryData = {
|
||||||
|
q: newValue,
|
||||||
|
search_repo: 'all',
|
||||||
|
search_ftypes: 'all',
|
||||||
|
obj_type: 'dir',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (searchTimer) {
|
||||||
|
clearTimeout(searchTimer.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
searchTimer.current = setTimeout(() => {
|
||||||
|
getSearchResult(queryData);
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSearchResult = useCallback((queryData) => {
|
||||||
|
if (source.current) {
|
||||||
|
source.current.cancel('prev request is cancelled');
|
||||||
|
}
|
||||||
|
|
||||||
|
source.current = seafileAPI.getSource();
|
||||||
|
seafileAPI.searchFiles(queryData, source.current.token).then(res => {
|
||||||
|
setSearchResults(res.data.total ? formatResultItems(res.data.results) : []);
|
||||||
|
onUpdateSearchStatus(res.data.results.length > 0 ? SearchStatus.RESULTS : SearchStatus.NO_RESULTS);
|
||||||
|
source.current = null;
|
||||||
|
}).catch(err => {
|
||||||
|
onUpdateSearchStatus(SearchStatus.NO_RESULTS);
|
||||||
|
source.current = null;
|
||||||
|
});
|
||||||
|
}, [onUpdateSearchStatus]);
|
||||||
|
|
||||||
|
const formatResultItems = (data) => {
|
||||||
|
let items = [];
|
||||||
|
let length = data.length > 10 ? 10 : data.length;
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
items[i] = {};
|
||||||
|
items[i]['index'] = [i];
|
||||||
|
items[i]['name'] = data[i].name;
|
||||||
|
items[i]['path'] = data[i].fullpath;
|
||||||
|
items[i]['repo_id'] = data[i].repo_id;
|
||||||
|
items[i]['repo_name'] = data[i].repo_name;
|
||||||
|
items[i]['is_dir'] = data[i].is_dir;
|
||||||
|
items[i]['link_content'] = decodeURI(data[i].fullpath).substring(1);
|
||||||
|
items[i]['content'] = data[i].content_highlight;
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCloseSearching = useCallback(() => {
|
||||||
|
setInputValue('');
|
||||||
|
setSearchResults([]);
|
||||||
|
onUpdateSearchStatus(SearchStatus.IDLE);
|
||||||
|
onPopoverToggle(false);
|
||||||
|
selectSearchedItem(null);
|
||||||
|
}, [onUpdateSearchStatus, selectSearchedItem, onPopoverToggle]);
|
||||||
|
|
||||||
|
const onSearchedItemClick = (item) => {
|
||||||
|
item['type'] = item.is_dir ? 'dir' : 'file';
|
||||||
|
let repo = new RepoInfo(item);
|
||||||
|
onDirentItemClick(repo, item.path, item);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSearchedItemDoubleClick = (item) => {
|
||||||
|
if (item.type !== 'dir') return;
|
||||||
|
|
||||||
|
const selectedItemInfo = {
|
||||||
|
repoID: item.repo_id,
|
||||||
|
filePath: item.path,
|
||||||
|
};
|
||||||
|
|
||||||
|
selectSearchedItem(selectedItemInfo);
|
||||||
|
onPopoverToggle(false);
|
||||||
|
|
||||||
|
seafileAPI.getRepoInfo(item.repo_id).then(res => {
|
||||||
|
const repoInfo = new RepoInfo(res.data);
|
||||||
|
const path = item.path.substring(0, item.path.length - 1);
|
||||||
|
selectRepo(repoInfo);
|
||||||
|
selectPath(path);
|
||||||
|
setBrowsingPath(item.path.substring(0, item.path.length - 1));
|
||||||
|
}).catch(err => {
|
||||||
|
const errMessage = Utils.getErrorMsg(err);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUpdateSearchStatus(SearchStatus.BROWSING);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderSearchResults = () => {
|
||||||
|
switch (searchStatus) {
|
||||||
|
case SearchStatus.IDLE:
|
||||||
|
return null;
|
||||||
|
case SearchStatus.LOADING:
|
||||||
|
return <Loading />;
|
||||||
|
case SearchStatus.NO_RESULTS:
|
||||||
|
return (
|
||||||
|
<div className='search-results-none'>{gettext('No results matching')}</div>
|
||||||
|
);
|
||||||
|
case SearchStatus.BROWSING:
|
||||||
|
case SearchStatus.RESULTS:
|
||||||
|
return (
|
||||||
|
<SearchedListView
|
||||||
|
searchResults={searchResults}
|
||||||
|
onItemClick={onSearchedItemClick}
|
||||||
|
onSearchedItemDoubleClick={onSearchedItemDoubleClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='search-container file-chooser-searcher'>
|
||||||
|
<div className='search-input-container'>
|
||||||
|
<i className="search-icon-left input-icon-addon sf3-font sf3-font-search"></i>
|
||||||
|
<Input
|
||||||
|
innerRef={inputRef}
|
||||||
|
className='search-input'
|
||||||
|
placeholder={gettext('Global search')}
|
||||||
|
type='text'
|
||||||
|
value={inputValue}
|
||||||
|
onChange={handleSearchInputChange}
|
||||||
|
/>
|
||||||
|
{inputValue.length !== 0 && (
|
||||||
|
<span className="search-control attr-action-icon sf3-font sf3-font-x-01" onClick={onCloseSearching}></span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{searchStatus !== SearchStatus.IDLE &&
|
||||||
|
<UncontrolledPopover
|
||||||
|
className='file-chooser-search-results-popover'
|
||||||
|
isOpen={isResultsPopoverOpen}
|
||||||
|
toggle={() => onPopoverToggle(!isResultsPopoverOpen)}
|
||||||
|
target={inputRef.current}
|
||||||
|
placement='bottom-start'
|
||||||
|
hideArrow={true}
|
||||||
|
fade={false}
|
||||||
|
trigger="legacy"
|
||||||
|
>
|
||||||
|
<div className='search-results-popover-body'>
|
||||||
|
{renderSearchResults()}
|
||||||
|
</div>
|
||||||
|
</UncontrolledPopover>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Searcher.propTypes = {
|
||||||
|
searchStatus: PropTypes.string,
|
||||||
|
onUpdateSearchStatus: PropTypes.func,
|
||||||
|
onDirentItemClick: PropTypes.func,
|
||||||
|
selectSearchedItem: PropTypes.func,
|
||||||
|
selectRepo: PropTypes.func,
|
||||||
|
selectPath: PropTypes.func,
|
||||||
|
setBrowsingPath: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Searcher;
|
@@ -307,7 +307,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.custom-modal .modal-body {
|
.custom-modal .modal-body {
|
||||||
flex: 1;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
@@ -325,6 +325,10 @@
|
|||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.custom-modal .modal-body .file-chooser-container {
|
||||||
|
height: 24rem;
|
||||||
|
}
|
||||||
|
|
||||||
.custom-modal .repo-list-col {
|
.custom-modal .repo-list-col {
|
||||||
max-width: 240px;
|
max-width: 240px;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
@@ -938,6 +938,8 @@ class LibContentView extends React.Component {
|
|||||||
|
|
||||||
this.deleteDirents(dirNames);
|
this.deleteDirents(dirNames);
|
||||||
|
|
||||||
|
this.removeFromRecentlyUsed(repoID, this.state.path);
|
||||||
|
|
||||||
let msg = '';
|
let msg = '';
|
||||||
if (direntPaths.length > 1) {
|
if (direntPaths.length > 1) {
|
||||||
msg = gettext('Successfully deleted {name} and {n} other items.');
|
msg = gettext('Successfully deleted {name} and {n} other items.');
|
||||||
@@ -965,6 +967,14 @@ class LibContentView extends React.Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
removeFromRecentlyUsed = (repoID, path) => {
|
||||||
|
const recentlyUsed = JSON.parse(localStorage.getItem('recently-used-list')) || [];
|
||||||
|
const updatedRecentlyUsed = recentlyUsed.filter(item =>
|
||||||
|
!(item.repo.repo_id === repoID && item.path === path)
|
||||||
|
);
|
||||||
|
localStorage.setItem('recently-used-list', JSON.stringify(updatedRecentlyUsed));
|
||||||
|
};
|
||||||
|
|
||||||
onAddFolder = (dirPath) => {
|
onAddFolder = (dirPath) => {
|
||||||
let repoID = this.props.repoID;
|
let repoID = this.props.repoID;
|
||||||
seafileAPI.createDir(repoID, dirPath).then(() => {
|
seafileAPI.createDir(repoID, dirPath).then(() => {
|
||||||
@@ -1237,6 +1247,7 @@ class LibContentView extends React.Component {
|
|||||||
this.deleteTreeNode(path);
|
this.deleteTreeNode(path);
|
||||||
}
|
}
|
||||||
this.deleteDirent(path);
|
this.deleteDirent(path);
|
||||||
|
this.removeFromRecentlyUsed(this.props.repoID, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// list operations
|
// list operations
|
||||||
|
Reference in New Issue
Block a user