1
0
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:
Aries
2024-09-23 09:48:43 +08:00
committed by GitHub
parent 721e9dd689
commit 69e40146c4
20 changed files with 803 additions and 257 deletions

View File

@@ -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,

View File

@@ -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,

View File

@@ -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;

View File

@@ -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';

View File

@@ -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 {

View File

@@ -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>
); );
} }

View File

@@ -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,

View File

@@ -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 = {

View 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;

View File

@@ -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 && (

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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;

View 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;

View File

@@ -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>

View 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;
}

View 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;

View File

@@ -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;

View File

@@ -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