1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-08 18:30:53 +00:00

Feature/refactor move dialog (#6990)

* update move dialog ui

* create new folder in move dialog

* optimize create new folder

* optimize code

* update ui

* optimize ui, fix new folder bug

* update new folder button

* update create folder

* optimize ui

* optimize ui

* optimize ui

---------

Co-authored-by: zhouwenxuan <aries@Mac.local>
This commit is contained in:
Aries
2024-11-08 18:04:48 +08:00
committed by GitHub
parent 89760c7114
commit 9d4c9b8f4b
15 changed files with 503 additions and 233 deletions

View File

@@ -1,9 +1,15 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { Modal, ModalHeader } from 'reactstrap'; import { Modal, ModalHeader } from 'reactstrap';
import PropTypes from 'prop-types';
import { IconBtn } from '@seafile/sf-metadata-ui-component';
import SelectDirentBody from './select-dirent-body'; import SelectDirentBody from './select-dirent-body';
import { gettext } from '../../utils/constants'; import { gettext, isPro } from '../../utils/constants';
import { Utils } from '../../utils/utils'; import { Utils } from '../../utils/utils';
import Searcher from '../file-chooser/searcher';
import { MODE_TYPE_MAP } from '../file-chooser/repo-list-wrapper';
import { RepoInfo } from '../../models';
import { seafileAPI } from '../../utils/seafile-api';
import toaster from '../toast';
const propTypes = { const propTypes = {
path: PropTypes.string.isRequired, path: PropTypes.string.isRequired,
@@ -14,6 +20,7 @@ const propTypes = {
onItemMove: PropTypes.func, onItemMove: PropTypes.func,
onItemsMove: PropTypes.func, onItemsMove: PropTypes.func,
onCancelMove: PropTypes.func.isRequired, onCancelMove: PropTypes.func.isRequired,
onAddFolder: PropTypes.func,
}; };
class MoveDirent extends React.Component { class MoveDirent extends React.Component {
@@ -21,8 +28,14 @@ class MoveDirent extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
mode: MODE_TYPE_MAP.ONLY_CURRENT_LIBRARY,
repo: { repo_id: this.props.repoID }, repo: { repo_id: this.props.repoID },
selectedPath: this.props.path, selectedPath: this.props.path,
selectedSearchedItem: null,
selectedSearchedRepo: null,
searchStatus: '',
searchResults: [],
showSearchBar: false,
errMessage: '', errMessage: '',
}; };
} }
@@ -138,6 +151,100 @@ class MoveDirent extends React.Component {
this.setState({ errMessage: message }); this.setState({ errMessage: message });
}; };
onUpdateMode = (mode) => {
if (mode === this.state.mode) return;
if (this.state.mode === MODE_TYPE_MAP.SEARCH_RESULTS) {
this.setState({
selectedSearchedRepo: null,
selectedSearchedItem: null,
searchResults: [],
showSearchBar: false,
});
}
if (this.state.selectedSearchedRepo) {
this.setState({
selectedSearchedRepo: null,
selectedSearchedItem: null,
searchResults: [],
showSearchBar: false,
});
}
this.setState({
mode,
});
};
onUpdateSearchStatus = (status) => {
this.setState({ searchStatus: status });
};
onUpdateSearchResults = (results) => {
this.setState({
searchResults: results
});
};
onDirentItemClick = (repo, selectedPath) => {
this.setState({
selectedPath: selectedPath,
repo,
errMessage: '',
});
};
onOpenSearchBar = () => {
this.setState({ showSearchBar: true });
};
onCloseSearchBar = () => {
const { selectedSearchedRepo } = this.state;
const mode = (!selectedSearchedRepo || selectedSearchedRepo.repo_id === this.props.repoID) ? MODE_TYPE_MAP.ONLY_CURRENT_LIBRARY : MODE_TYPE_MAP.ONLY_OTHER_LIBRARIES;
this.setState({
mode,
searchStatus: '',
searchResults: [],
selectedSearchedRepo: null,
showSearchBar: false
});
};
onSearchedItemClick = (item) => {
item['type'] = item.is_dir ? 'dir' : 'file';
let repo = new RepoInfo(item);
this.onDirentItemClick(repo, item.path, item);
};
onSearchedItemDoubleClick = (item) => {
if (item.type !== 'dir') return;
const selectedItemInfo = {
repoID: item.repo_id,
filePath: item.path,
};
this.setState({ selectedSearchedItem: selectedItemInfo });
seafileAPI.getRepoInfo(item.repo_id).then(res => {
const repoInfo = new RepoInfo(res.data);
const path = item.path.substring(0, item.path.length - 1);
const mode = item.repo_id === this.props.repoID ? MODE_TYPE_MAP.ONLY_CURRENT_LIBRARY : MODE_TYPE_MAP.ONLY_OTHER_LIBRARIES;
this.setState({
mode,
searchResults: [],
selectedSearchedRepo: repoInfo,
selectedPath: path,
showSearchBar: mode === MODE_TYPE_MAP.ONLY_OTHER_LIBRARIES,
});
}).catch(err => {
const errMessage = Utils.getErrorMsg(err);
toaster.danger(errMessage);
});
};
renderTitle = () => { renderTitle = () => {
const { dirent, isMultipleOperation } = this.props; const { dirent, isMultipleOperation } = this.props;
let title = gettext('Move {placeholder} to'); let title = gettext('Move {placeholder} to');
@@ -151,6 +258,7 @@ class MoveDirent extends React.Component {
render() { render() {
const { dirent, selectedDirentList, isMultipleOperation, path, repoID } = this.props; const { dirent, selectedDirentList, isMultipleOperation, path, repoID } = this.props;
const { mode, selectedPath, showSearchBar, searchStatus, searchResults, selectedSearchedRepo, 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);
@@ -158,19 +266,47 @@ class MoveDirent extends React.Component {
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'></div>}
{isPro && (
showSearchBar ? (
<Searcher
onUpdateMode={this.onUpdateMode}
onUpdateSearchStatus={this.onUpdateSearchStatus}
onUpdateSearchResults={this.onUpdateSearchResults}
onClose={this.onCloseSearchBar}
/>
) : (
<IconBtn
iconName="search"
size={24}
className="search"
onClick={this.onOpenSearchBar}
role="button"
onKeyDown={() => {}}
tabIndex={0}
/>
)
)}
</ModalHeader> </ModalHeader>
<SelectDirentBody <SelectDirentBody
path={path} path={path}
selectedPath={this.state.selectedPath} selectedPath={selectedPath}
repoID={repoID} repoID={repoID}
isSupportOtherLibraries={!isCustomPermission} isSupportOtherLibraries={!isCustomPermission}
errMessage={this.state.errMessage} errMessage={errMessage}
onCancel={this.toggle} onCancel={this.toggle}
selectRepo={this.selectRepo} selectRepo={this.selectRepo}
setSelectedPath={this.setSelectedPath} setSelectedPath={this.setSelectedPath}
setErrMessage={this.setErrMessage} setErrMessage={this.setErrMessage}
handleSubmit={this.handleSubmit} handleSubmit={this.handleSubmit}
mode={mode}
onUpdateMode={this.onUpdateMode}
searchStatus={searchStatus}
searchResults={searchResults}
onSearchedItemClick={this.onSearchedItemClick}
onSearchedItemDoubleClick={this.onSearchedItemDoubleClick}
selectedSearchedRepo={selectedSearchedRepo}
onAddFolder={this.props.onAddFolder}
/> />
</Modal> </Modal>
); );

View File

@@ -2,16 +2,17 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Button, ModalFooter, ModalBody, Alert, Row, Col } from 'reactstrap'; import { Button, ModalFooter, ModalBody, Alert, Row, Col } from 'reactstrap';
import toaster from '../toast'; import toaster from '../toast';
import Searcher, { SearchStatus } from '../file-chooser/searcher';
import RepoListWrapper, { MODE_TYPE_MAP } from '../file-chooser/repo-list-wrapper'; import RepoListWrapper, { MODE_TYPE_MAP } from '../file-chooser/repo-list-wrapper';
import { seafileAPI } from '../../utils/seafile-api'; import { seafileAPI } from '../../utils/seafile-api';
import { gettext, isPro } from '../../utils/constants'; import { gettext } from '../../utils/constants';
import { Utils } from '../../utils/utils'; import { Utils } from '../../utils/utils';
import { RepoInfo } from '../../models'; import { RepoInfo } from '../../models';
import { ModalPortal } from '@seafile/sf-metadata-ui-component';
import CreateFolder from '../dialog/create-folder-dialog';
const LibraryOption = ({ mode, label, currentMode, selectedMode }) => { const LibraryOption = ({ mode, label, currentMode, onUpdateMode }) => {
return ( return (
<div className={`repo-list-item ${mode === currentMode ? 'active' : ''}`} onClick={() => selectedMode(mode)}> <div className={`repo-list-item ${mode === currentMode ? 'active' : ''}`} onClick={() => onUpdateMode(mode)}>
<span className='library'>{label}</span> <span className='library'>{label}</span>
</div> </div>
); );
@@ -22,15 +23,13 @@ class SelectDirentBody extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
mode: MODE_TYPE_MAP.ONLY_CURRENT_LIBRARY,
currentRepoInfo: null, currentRepoInfo: null,
repoList: [], repoList: [],
selectedSearchedItem: null,
selectedRepo: null, selectedRepo: null,
browsingPath: '',
searchStatus: SearchStatus.IDLE,
errMessage: '', errMessage: '',
showCreateFolderDialog: false,
}; };
this.newFolderName = '';
} }
componentDidMount() { componentDidMount() {
@@ -52,11 +51,32 @@ class SelectDirentBody extends React.Component {
} }
}; };
fetchSelectedRepoInfo = async (repoId) => {
try {
const res = await seafileAPI.getRepoInfo(repoId);
const repoInfo = new RepoInfo(res.data);
this.setState({
selectedRepo: repoInfo,
selectedSearchedRepo: repoInfo,
});
} catch (err) {
const errMessage = Utils.getErrorMsg(err);
toaster.danger(errMessage);
}
};
fetchRepoList = async () => { fetchRepoList = async () => {
try { try {
const res = await seafileAPI.listRepos(); const res = await seafileAPI.listRepos();
const repos = res.data.repos; const repos = res.data.repos;
const repoList = repos.filter((repo) => repo.permission === 'rw' && repo.repo_id !== this.props.repoID); const repoList = [];
const uniqueRepoIds = new Set();
for (const repo of repos) {
if (repo.permission === 'rw' && repo.repo_id !== this.props.repoID && !uniqueRepoIds.has(repo.repo_id)) {
uniqueRepoIds.add(repo.repo_id);
repoList.push(repo);
}
}
const sortedRepoList = Utils.sortRepos(repoList, 'name', 'asc'); const sortedRepoList = Utils.sortRepos(repoList, 'name', 'asc');
const selectedRepo = sortedRepoList.find((repo) => repo.repo_id === this.props.repoID); const selectedRepo = sortedRepoList.find((repo) => repo.repo_id === this.props.repoID);
this.setState({ this.setState({
@@ -69,29 +89,10 @@ class SelectDirentBody extends React.Component {
} }
}; };
onUpdateSearchStatus = (status) => {
this.setState({ searchStatus: status });
};
onUpdateRepoList = (repoList) => { onUpdateRepoList = (repoList) => {
this.setState({ repoList: 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,
});
};
setBrowsingPath = (path) => {
this.setState({ browsingPath: path });
};
handleSubmit = () => { handleSubmit = () => {
if (this.props.handleSubmit) { if (this.props.handleSubmit) {
this.props.handleSubmit(); this.props.handleSubmit();
@@ -118,23 +119,60 @@ class SelectDirentBody extends React.Component {
this.setState({ selectedRepo: repo }); this.setState({ selectedRepo: repo });
}; };
selectedMode = (mode) => { onUpdateMode = (mode) => {
const { repoID, path } = this.props; const { path } = this.props;
const { repoList } = this.state;
if (mode === MODE_TYPE_MAP.ONLY_OTHER_LIBRARIES) {
this.setState({
selectedRepo: repoList[0],
});
this.props.setSelectedPath('/');
} else {
this.setState({ selectedRepo: this.state.currentRepoInfo });
this.props.setSelectedPath(path);
}
// reset selecting status this.props.onUpdateMode(mode);
this.props.selectRepo({ repo_id: repoID }); };
this.props.setSelectedPath(path);
this.setState({ loadRepoDirentList = (repo) => {
mode, try {
selectedSearchedItem: null, const { data } = seafileAPI.listDir(repo.repo_id, '/');
searchStatus: SearchStatus.RESULTS, return data.dirent_list.filter(item => item.type === 'dir');
}); } catch (error) {
return [];
}
};
createFolder = (fullPath) => {
this.newFolderName = fullPath.split('/').pop();
const selectedRepoId = this.state.selectedRepo.repo_id;
if (selectedRepoId === this.props.repoID) {
this.props.onAddFolder(fullPath, { successCallback: this.fetchRepoInfo });
} else {
seafileAPI.createDir(selectedRepoId, fullPath).then(() => {
this.fetchSelectedRepoInfo(selectedRepoId);
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
this.props.setErrMessage(errMessage);
});
}
this.setState({ showCreateFolderDialog: false });
};
onToggleCreateFolder = () => {
this.setState({ showCreateFolderDialog: !this.state.showCreateFolderDialog });
};
checkDuplicatedName = (newName) => {
const folderList = this.loadRepoDirentList(this.state.selectedRepo);
return folderList.some(folder => folder.name === newName);
}; };
render() { render() {
const { path, selectedPath, isSupportOtherLibraries, errMessage } = this.props; const { mode, path, selectedPath, isSupportOtherLibraries, errMessage, searchStatus, searchResults, selectedSearchedRepo } = this.props;
const { mode, searchStatus, selectedSearchedItem, selectedRepo, repoList, currentRepoInfo, browsingPath } = this.state; const { selectedSearchedItem, selectedRepo, repoList, currentRepoInfo } = this.state;
let repoListWrapperKey = 'repo-list-wrapper'; let repoListWrapperKey = 'repo-list-wrapper';
if (selectedSearchedItem && selectedSearchedItem.repoID) { if (selectedSearchedItem && selectedSearchedItem.repoID) {
repoListWrapperKey = `${repoListWrapperKey}-${selectedSearchedItem.repoID}`; repoListWrapperKey = `${repoListWrapperKey}-${selectedSearchedItem.repoID}`;
@@ -143,63 +181,75 @@ class SelectDirentBody extends React.Component {
return ( return (
<Row> <Row>
<Col className='repo-list-col border-right'> <Col className='repo-list-col border-right'>
{isPro && (
<Searcher
searchStatus={searchStatus}
onUpdateSearchStatus={this.onUpdateSearchStatus}
onDirentItemClick={this.onDirentItemClick}
selectSearchedItem={this.selectSearchedItem}
selectRepo={this.onSelectSearchedRepo}
setSelectedPath={this.props.setSelectedPath}
setBrowsingPath={this.setBrowsingPath}
/>
)}
<LibraryOption <LibraryOption
mode={MODE_TYPE_MAP.ONLY_CURRENT_LIBRARY} mode={MODE_TYPE_MAP.ONLY_CURRENT_LIBRARY}
label={gettext('Current Library')} label={gettext('Current Library')}
currentMode={mode} currentMode={mode}
selectedMode={this.selectedMode} onUpdateMode={this.onUpdateMode}
/> />
{isSupportOtherLibraries && ( {isSupportOtherLibraries && (
<LibraryOption <LibraryOption
mode={MODE_TYPE_MAP.ONLY_OTHER_LIBRARIES} mode={MODE_TYPE_MAP.ONLY_OTHER_LIBRARIES}
label={gettext('Other Libraries')} label={gettext('Other Libraries')}
currentMode={mode} currentMode={mode}
selectedMode={this.selectedMode} onUpdateMode={this.onUpdateMode}
/> />
)} )}
<LibraryOption <LibraryOption
mode={MODE_TYPE_MAP.RECENTLY_USED} mode={MODE_TYPE_MAP.RECENTLY_USED}
label={gettext('Recently Used')} label={gettext('Recently Used')}
currentMode={mode} currentMode={mode}
selectedMode={this.selectedMode} onUpdateMode={this.onUpdateMode}
/> />
</Col> </Col>
<Col className='file-list-col'> <Col className='file-list-col'>
<ModalBody> <ModalBody>
{currentRepoInfo && ( <RepoListWrapper
<RepoListWrapper key={repoListWrapperKey}
key={repoListWrapperKey} mode={mode}
mode={mode} currentPath={path}
currentPath={path} selectedItemInfo={selectedSearchedItem}
isBrowsing={searchStatus === SearchStatus.BROWSING} currentRepoInfo={currentRepoInfo}
browsingPath={browsingPath} selectedRepo={selectedRepo}
selectedItemInfo={selectedSearchedItem} selectedPath={selectedPath}
currentRepoInfo={currentRepoInfo} repoList={repoList}
selectedRepo={selectedRepo} handleClickRepo={this.onRepoItemClick}
selectedPath={selectedPath} handleClickDirent={this.onDirentItemClick}
repoList={repoList} searchStatus={searchStatus}
handleClickRepo={this.onRepoItemClick} searchResults={searchResults}
handleClickDirent={this.onDirentItemClick} onSearchedItemClick={this.props.onSearchedItemClick}
/> onSearchedItemDoubleClick={this.props.onSearchedItemDoubleClick}
)} selectedSearchedRepo={selectedSearchedRepo}
newFolderName={this.newFolderName}
/>
{errMessage && <Alert color="danger" className="alert-message">{errMessage}</Alert>} {errMessage && <Alert color="danger" className="alert-message">{errMessage}</Alert>}
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter className='move-dirent-dialog-footer'>
<Button color="secondary" onClick={this.onCancel}>{gettext('Cancel')}</Button> <Button
<Button color="primary" onClick={this.handleSubmit}>{gettext('Submit')}</Button> className="footer-left-btn"
color="secondary"
onClick={this.onToggleCreateFolder}
disabled={mode === MODE_TYPE_MAP.SEARCH_RESULTS}
>
<i className='sf3-font-new sf3-font mr-2'></i>
<span>{gettext('New folder')}</span>
</Button>
<div className='footer-right-btns'>
<Button color="secondary m-1" onClick={this.onCancel}>{gettext('Cancel')}</Button>
<Button color="primary m-1" onClick={this.handleSubmit}>{gettext('Submit')}</Button>
</div>
</ModalFooter> </ModalFooter>
</Col> </Col>
{this.state.showCreateFolderDialog && (
<ModalPortal>
<CreateFolder
parentPath={this.props.selectedPath}
onAddFolder={this.createFolder}
checkDuplicatedName={this.checkDuplicatedName}
addFolderCancel={this.onToggleCreateFolder}
/>
</ModalPortal>
)}
</Row> </Row>
); );
} }
@@ -215,6 +265,14 @@ SelectDirentBody.propTypes = {
selectRepo: PropTypes.func, selectRepo: PropTypes.func,
setSelectedPath: PropTypes.func, setSelectedPath: PropTypes.func,
setErrMessage: PropTypes.func, setErrMessage: PropTypes.func,
mode: PropTypes.string,
onUpdateMode: PropTypes.func,
searchStatus: PropTypes.string,
searchResults: PropTypes.array,
onSearchedItemClick: PropTypes.func,
onSearchedItemDoubleClick: PropTypes.func,
selectedSearchedRepo: PropTypes.object,
onAddFolder: PropTypes.func,
}; };
SelectDirentBody.defaultProps = { SelectDirentBody.defaultProps = {

View File

@@ -935,6 +935,7 @@ class DirentGridView extends React.Component {
onItemsMove={this.props.onItemsMove} onItemsMove={this.props.onItemsMove}
onCancelMove={this.onMoveToggle} onCancelMove={this.onMoveToggle}
dirent={this.state.activeDirent} dirent={this.state.activeDirent}
onAddFolder={this.props.onAddFolder}
/> />
} }
{this.state.isZipDialogOpen && {this.state.isZipDialogOpen &&

View File

@@ -968,6 +968,7 @@ class DirentListItem extends React.Component {
onItemMove={this.props.onItemMove} onItemMove={this.props.onItemMove}
onCancelMove={this.onItemMoveToggle} onCancelMove={this.onItemMoveToggle}
repoEncrypted={this.props.repoEncrypted} repoEncrypted={this.props.repoEncrypted}
onAddFolder={this.props.onAddFolder}
/> />
</ModalPortal> </ModalPortal>
} }

View File

@@ -779,6 +779,7 @@ class DirentListView extends React.Component {
onItemsMove={this.props.onItemsMove} onItemsMove={this.props.onItemsMove}
onShowDirentsDraggablePreview={this.onShowDirentsDraggablePreview} onShowDirentsDraggablePreview={this.onShowDirentsDraggablePreview}
loadDirentList={this.props.loadDirentList} loadDirentList={this.props.loadDirentList}
onAddFolder={this.props.onAddFolder}
/> />
); );
})} })}
@@ -859,6 +860,7 @@ class DirentListView extends React.Component {
selectedDirentList={this.props.selectedDirentList} selectedDirentList={this.props.selectedDirentList}
onItemsMove={this.props.onItemsMove} onItemsMove={this.props.onItemsMove}
onCancelMove={this.onMoveToggle} onCancelMove={this.onMoveToggle}
onAddFolder={this.props.onAddFolder}
/> />
} }
{this.state.isCopyDialogShow && {this.state.isCopyDialogShow &&

View File

@@ -21,8 +21,7 @@ const propTypes = {
fileSuffixes: PropTypes.array, fileSuffixes: PropTypes.array,
selectedItemInfo: PropTypes.object, selectedItemInfo: PropTypes.object,
hideLibraryName: PropTypes.bool, hideLibraryName: PropTypes.bool,
isBrowsing: PropTypes.bool, newFolderName: PropTypes.string,
browsingPath: PropTypes.string,
}; };
class RepoListItem extends React.Component { class RepoListItem extends React.Component {
@@ -65,6 +64,26 @@ class RepoListItem extends React.Component {
} }
} }
componentDidUpdate(prevProps) {
const { repo, selectedRepo, selectedPath, newFolderName } = this.props;
if (repo.repo_id === selectedRepo.repo_id && prevProps.selectedRepo !== selectedRepo) {
seafileAPI.listDir(repo.repo_id, selectedPath).then(res => {
if (!this.state.isMounted) return;
const direntData = res.data.dirent_list.find(item => item.type === 'dir' && item.name === newFolderName);
if (direntData) {
const object = new Dirent(direntData);
const direntNode = new TreeNode({ object });
const newTreeData = treeHelper.addNodeToParentByPath(this.state.treeData, direntNode, selectedPath);
this.setState({ treeData: newTreeData });
}
}).catch(error => {
if (!this.state.isMounted) return;
const errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
}
componentWillUnmount() { componentWillUnmount() {
this.clearLoadRepoTimer(); this.clearLoadRepoTimer();
this.setState({ isMounted: false, hasLoaded: false }); this.setState({ isMounted: false, hasLoaded: false });
@@ -213,7 +232,7 @@ class RepoListItem extends React.Component {
return ( return (
<li> <li>
{!this.props.hideLibraryName && !this.props.isBrowsing && {!this.props.hideLibraryName &&
<div className={`${repoActive ? 'item-active' : ''} item-info`} onClick={this.onRepoItemClick}> <div className={`${repoActive ? 'item-active' : ''} item-info`} onClick={this.onRepoItemClick}>
<div className="item-left-icon"> <div className="item-left-icon">
<span className={`item-toggle icon sf3-font ${this.state.isShowChildren ? 'sf3-font-down' : 'sf3-font-down rotate-270 d-inline-block'}`} onClick={this.onToggleClick}></span> <span className={`item-toggle icon sf3-font ${this.state.isShowChildren ? 'sf3-font-down' : 'sf3-font-down rotate-270 d-inline-block'}`} onClick={this.onToggleClick}></span>
@@ -236,8 +255,6 @@ class RepoListItem extends React.Component {
treeData={this.state.treeData} treeData={this.state.treeData}
onNodeCollapse={this.onNodeCollapse} onNodeCollapse={this.onNodeCollapse}
onNodeExpanded={this.onNodeExpanded} onNodeExpanded={this.onNodeExpanded}
isBrowsing={this.props.isBrowsing}
browsingPath={this.props.browsingPath}
/> />
)} )}
</li> </li>

View File

@@ -15,8 +15,8 @@ const propTypes = {
fileSuffixes: PropTypes.array, fileSuffixes: PropTypes.array,
selectedItemInfo: PropTypes.object, selectedItemInfo: PropTypes.object,
currentPath: PropTypes.string, currentPath: PropTypes.string,
isBrowsing: PropTypes.bool, selectedSearchedRepo: PropTypes.object,
browsingPath: PropTypes.string, newFolderName: PropTypes.string,
}; };
const defaultProps = { const defaultProps = {
@@ -29,25 +29,28 @@ const defaultProps = {
fileSuffixes: [], fileSuffixes: [],
selectedItemInfo: null, selectedItemInfo: null,
currentPath: '', currentPath: '',
isBrowsing: false,
browsingPath: '',
}; };
class RepoListView extends React.Component { class RepoListView extends React.Component {
render() { render() {
let { currentRepoInfo, currentPath, repoList } = this.props; let { currentRepoInfo, currentPath, repoList, selectedSearchedRepo } = this.props;
if (currentRepoInfo) { if (currentRepoInfo) {
repoList = []; repoList = [];
repoList.push(currentRepoInfo); repoList.push(currentRepoInfo);
} }
if (selectedSearchedRepo) {
repoList = [];
repoList.push(selectedSearchedRepo);
}
return ( return (
<ul className="list-view-content file-chooser-item" > <ul className="list-view-content file-chooser-item" >
{repoList.length > 0 && repoList.map((repoItem, index) => { {repoList.length > 0 && repoList.map((repoItem, index) => {
return ( return (
<RepoListItem <RepoListItem
key={index} key={repoItem.repo_id}
isCurrentRepo={currentRepoInfo ? true : false} isCurrentRepo={currentRepoInfo ? true : false}
currentPath={currentPath} currentPath={currentPath}
repo={repoItem} repo={repoItem}
@@ -59,8 +62,7 @@ class RepoListView extends React.Component {
isShowFile={this.props.isShowFile} isShowFile={this.props.isShowFile}
fileSuffixes={this.props.fileSuffixes} fileSuffixes={this.props.fileSuffixes}
selectedItemInfo={this.props.selectedItemInfo} selectedItemInfo={this.props.selectedItemInfo}
isBrowsing={this.props.isBrowsing} newFolderName={this.props.newFolderName}
browsingPath={this.props.browsingPath}
/> />
); );
})} })}

View File

@@ -3,6 +3,9 @@ import PropTypes from 'prop-types';
import RepoListView from './repo-list-view'; import RepoListView from './repo-list-view';
import RecentlyUsedListView from './recently-used-list-view'; import RecentlyUsedListView from './recently-used-list-view';
import { gettext } from '../../utils/constants'; import { gettext } from '../../utils/constants';
import SearchedListView from './searched-list-view';
import { SearchStatus } from './searcher';
import Loading from '../loading';
export const MODE_TYPE_MAP = { export const MODE_TYPE_MAP = {
CURRENT_AND_OTHER_REPOS: 'current_repo_and_other_repos', CURRENT_AND_OTHER_REPOS: 'current_repo_and_other_repos',
@@ -10,12 +13,14 @@ export const MODE_TYPE_MAP = {
ONLY_ALL_REPOS: 'only_all_repos', ONLY_ALL_REPOS: 'only_all_repos',
ONLY_OTHER_LIBRARIES: 'only_other_libraries', ONLY_OTHER_LIBRARIES: 'only_other_libraries',
RECENTLY_USED: 'recently_used', RECENTLY_USED: 'recently_used',
SEARCH_RESULTS: 'search_results',
}; };
const RepoListWrapper = (props) => { const RepoListWrapper = (props) => {
const { const {
mode, isShowFile, fileSuffixes, currentPath, isBrowsing, browsingPath, isCurrentRepoShow, currentRepoInfo, selectedRepo, mode, isShowFile, fileSuffixes, currentPath, isCurrentRepoShow, currentRepoInfo, selectedRepo,
selectedPath, isOtherRepoShow, selectedItemInfo, repoList, selectedPath, isOtherRepoShow, selectedItemInfo, repoList,
searchStatus, searchResults, onSearchedItemClick, onSearchedItemDoubleClick, selectedSearchedRepo, newFolderName
} = props; } = props;
const renderRecentlyUsed = () => { const renderRecentlyUsed = () => {
@@ -34,6 +39,29 @@ const RepoListWrapper = (props) => {
event.stopPropagation(); event.stopPropagation();
}; };
const renderSearchResults = () => {
switch (searchStatus) {
case SearchStatus.LOADING:
return <Loading />;
case SearchStatus.RESULTS:
return (
<>
{searchResults.length === 0 ? (
<div className='search-results-none text-center'>{gettext('No results matching')}</div>
) : (
<SearchedListView
searchResults={searchResults}
onItemClick={onSearchedItemClick}
onSearchedItemDoubleClick={onSearchedItemDoubleClick}
/>
)}
</>
);
default:
return null;
}
};
return ( return (
<div className='file-chooser-scroll-wrapper' onScroll={onScroll}> <div className='file-chooser-scroll-wrapper' onScroll={onScroll}>
<div className="file-chooser-container user-select-none"> <div className="file-chooser-container user-select-none">
@@ -91,10 +119,10 @@ const RepoListWrapper = (props) => {
isShowFile={isShowFile} isShowFile={isShowFile}
fileSuffixes={fileSuffixes} fileSuffixes={fileSuffixes}
selectedItemInfo={selectedItemInfo} selectedItemInfo={selectedItemInfo}
isBrowsing={isBrowsing}
browsingPath={browsingPath}
onRepoItemClick={props.handleClickRepo} onRepoItemClick={props.handleClickRepo}
onDirentItemClick={props.handleClickDirent} onDirentItemClick={props.handleClickDirent}
selectedSearchedRepo={selectedSearchedRepo}
newFolderName={newFolderName}
/> />
</div> </div>
)} )}
@@ -129,14 +157,19 @@ const RepoListWrapper = (props) => {
isShowFile={isShowFile} isShowFile={isShowFile}
fileSuffixes={fileSuffixes} fileSuffixes={fileSuffixes}
selectedItemInfo={selectedItemInfo} selectedItemInfo={selectedItemInfo}
isBrowsing={isBrowsing}
browsingPath={browsingPath}
onRepoItemClick={props.handleClickRepo} onRepoItemClick={props.handleClickRepo}
onDirentItemClick={props.handleClickDirent} onDirentItemClick={props.handleClickDirent}
selectedSearchedRepo={selectedSearchedRepo}
newFolderName={newFolderName}
/> />
</div> </div>
)} )}
{mode === MODE_TYPE_MAP.RECENTLY_USED && renderRecentlyUsed()} {mode === MODE_TYPE_MAP.RECENTLY_USED && renderRecentlyUsed()}
{mode === MODE_TYPE_MAP.SEARCH_RESULTS && (
<div className="list-view">
{renderSearchResults()}
</div>
)}
</div> </div>
</div> </div>
); );
@@ -147,8 +180,6 @@ RepoListWrapper.propTypes = {
currentPath: PropTypes.string, currentPath: PropTypes.string,
isShowFile: PropTypes.bool, isShowFile: PropTypes.bool,
fileSuffixes: PropTypes.array, fileSuffixes: PropTypes.array,
isBrowsing: PropTypes.bool,
browsingPath: PropTypes.string,
selectedItemInfo: PropTypes.object, selectedItemInfo: PropTypes.object,
currentRepoInfo: PropTypes.object, currentRepoInfo: PropTypes.object,
selectedRepo: PropTypes.object, selectedRepo: PropTypes.object,
@@ -160,6 +191,12 @@ RepoListWrapper.propTypes = {
onOtherRepoToggle: PropTypes.func, onOtherRepoToggle: PropTypes.func,
handleClickRepo: PropTypes.func, handleClickRepo: PropTypes.func,
handleClickDirent: PropTypes.func, handleClickDirent: PropTypes.func,
searchStatus: PropTypes.string,
searchResults: PropTypes.array,
onSearchedItemClick: PropTypes.func,
onSearchedItemDoubleClick: PropTypes.func,
selectedSearchedRepo: PropTypes.object,
newFolderName: PropTypes.string,
}; };
RepoListWrapper.defaultProps = { RepoListWrapper.defaultProps = {

View File

@@ -1,8 +1,9 @@
.file-chooser-searcher.search-container { .file-chooser-searcher.search-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
position: relative; position: absolute;
margin-bottom: 16px; top: 14px;
right: 48px;
} }
.file-chooser-searcher .search-input-container { .file-chooser-searcher .search-input-container {
@@ -15,14 +16,14 @@
.file-chooser-searcher .search-input-container .search-input { .file-chooser-searcher .search-input-container .search-input {
width: 100%; width: 100%;
height: 2.375rem; height: 1.875em;
padding-left: 2rem; padding-left: 2rem;
padding-right: 1.5rem; padding-right: 1.5rem;
} }
.file-chooser-searcher .search-input-container .search-control { .file-chooser-searcher .search-input-container .search-control {
position: absolute; position: absolute;
top: 12px; top: 6px;
right: 8px; right: 8px;
font-size: 16px; font-size: 16px;
} }

View File

@@ -1,51 +1,65 @@
import React, { useState, useRef, useCallback } from 'react'; import React, { useState, useRef, useCallback, useEffect } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Input, UncontrolledPopover } from 'reactstrap'; import { Input } 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 { gettext } from '../../../utils/constants';
import { seafileAPI } from '../../../utils/seafile-api'; import { seafileAPI } from '../../../utils/seafile-api';
import { Utils } from '../../../utils/utils';
import { SEARCH_CONTAINER } from '../../../constants/zIndexes'; import { SEARCH_CONTAINER } from '../../../constants/zIndexes';
import { MODE_TYPE_MAP } from '../repo-list-wrapper';
import './index.css'; import './index.css';
export const SearchStatus = { export const SearchStatus = {
IDLE: 'idle',
LOADING: 'loading', LOADING: 'loading',
RESULTS: 'results', RESULTS: 'results',
NO_RESULTS: 'no_results',
BROWSING: 'browsing',
}; };
const Searcher = ({ searchStatus, onUpdateSearchStatus, onDirentItemClick, selectSearchedItem, selectRepo, setSelectedPath, setBrowsingPath }) => { const Searcher = ({ onUpdateMode, onUpdateSearchStatus, onUpdateSearchResults, onClose }) => {
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState('');
const [isResultsPopoverOpen, setIsResultsPopoverOpen] = useState(false);
const [searchResults, setSearchResults] = useState([]);
const inputRef = useRef(null); const inputRef = useRef(null);
const searchTimer = useRef(null); const searchTimer = useRef(null);
const source = useRef(null); const source = useRef(null);
const onPopoverToggle = useCallback((show) => { useEffect(() => {
setIsResultsPopoverOpen(show); const handleClickOutside = (event) => {
}, []); if (inputRef.current && !inputRef.current.contains(event.target) && inputValue === '') {
onClose();
}
};
const handleSearchInputChange = (e) => { document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [inputValue, onClose]);
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 => {
onUpdateSearchStatus(SearchStatus.RESULTS);
onUpdateSearchResults(res.data.total ? formatResultItems(res.data.results) : []);
source.current = null;
}).catch(err => {
source.current = null;
});
}, [onUpdateSearchStatus, onUpdateSearchResults]);
const handleSearchInputChange = useCallback((e) => {
const newValue = e.target.value.trim(); const newValue = e.target.value.trim();
setInputValue(newValue); setInputValue(newValue);
if (newValue.length === 0) { if (newValue.length === 0) {
onUpdateSearchStatus(SearchStatus.IDLE); onUpdateSearchResults([]);
setSearchResults([]);
return; return;
} }
onUpdateSearchStatus(SearchStatus.LOADING); onUpdateSearchStatus(SearchStatus.LOADING);
onPopoverToggle(true);
const queryData = { const queryData = {
q: newValue, q: newValue,
@@ -61,23 +75,7 @@ const Searcher = ({ searchStatus, onUpdateSearchStatus, onDirentItemClick, selec
searchTimer.current = setTimeout(() => { searchTimer.current = setTimeout(() => {
getSearchResult(queryData); getSearchResult(queryData);
}, 500); }, 500);
}; }, [onUpdateSearchStatus, onUpdateSearchResults, getSearchResult]);
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) => { const formatResultItems = (data) => {
let items = []; let items = [];
@@ -96,66 +94,18 @@ const Searcher = ({ searchStatus, onUpdateSearchStatus, onDirentItemClick, selec
return items; return items;
}; };
const handleKeyDown = useCallback((e) => {
e.stopPropagation();
if (e.key === 'Enter' && inputValue.trim().length > 0) {
onUpdateMode(MODE_TYPE_MAP.SEARCH_RESULTS);
}
}, [inputValue, onUpdateMode]);
const onCloseSearching = useCallback(() => { const onCloseSearching = useCallback(() => {
setInputValue(''); setInputValue('');
setSearchResults([]); onClose();
onUpdateSearchStatus(SearchStatus.IDLE); }, [onClose]);
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);
setSelectedPath(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 ( return (
<div className='search-container file-chooser-searcher' style={{ zIndex: SEARCH_CONTAINER }}> <div className='search-container file-chooser-searcher' style={{ zIndex: SEARCH_CONTAINER }}>
@@ -168,39 +118,22 @@ const Searcher = ({ searchStatus, onUpdateSearchStatus, onDirentItemClick, selec
type='text' type='text'
value={inputValue} value={inputValue}
onChange={handleSearchInputChange} onChange={handleSearchInputChange}
onKeyDown={handleKeyDown}
autoFocus
/> />
{inputValue.length !== 0 && ( {inputValue.length !== 0 && (
<span className="search-control attr-action-icon sf3-font sf3-font-x-01" onClick={onCloseSearching}></span> <span className="search-control attr-action-icon sf3-font sf3-font-x-01" onClick={onCloseSearching}></span>
)} )}
</div> </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> </div>
); );
}; };
Searcher.propTypes = { Searcher.propTypes = {
searchStatus: PropTypes.string, onUpdateMode: PropTypes.func,
onUpdateSearchStatus: PropTypes.func, onUpdateSearchStatus: PropTypes.func,
onDirentItemClick: PropTypes.func, onUpdateSearchResults: PropTypes.func,
selectSearchedItem: PropTypes.func, onClose: PropTypes.func,
selectRepo: PropTypes.func,
setSelectedPath: PropTypes.func,
setBrowsingPath: PropTypes.func,
}; };
export default Searcher; export default Searcher;

View File

@@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import TreeListItem from './tree-list-item'; import TreeListItem from './tree-list-item';
import treeHelper from '../tree-view/tree-helper';
const propTypes = { const propTypes = {
selectedPath: PropTypes.string, selectedPath: PropTypes.string,
@@ -12,16 +11,12 @@ const propTypes = {
onNodeCollapse: PropTypes.func.isRequired, onNodeCollapse: PropTypes.func.isRequired,
onNodeExpanded: PropTypes.func.isRequired, onNodeExpanded: PropTypes.func.isRequired,
fileSuffixes: PropTypes.array, fileSuffixes: PropTypes.array,
isBrowsing: PropTypes.bool,
browsingPath: PropTypes.string,
}; };
class TreeListView extends React.Component { class TreeListView extends React.Component {
render() { render() {
const { const {
isBrowsing,
browsingPath,
treeData, treeData,
selectedPath, selectedPath,
onNodeCollapse, onNodeCollapse,
@@ -32,10 +27,7 @@ class TreeListView extends React.Component {
fileSuffixes fileSuffixes
} = this.props; } = this.props;
const browsingNode = treeHelper.findNodeByPath(treeData, browsingPath); const node = treeData.root;
if (isBrowsing && !browsingNode) return null;
const node = isBrowsing ? browsingNode : treeData.root;
return ( return (
<div className="list-view-content"> <div className="list-view-content">

View File

@@ -40,6 +40,7 @@ const propTypes = {
onItemRename: PropTypes.func.isRequired, onItemRename: PropTypes.func.isRequired,
showDirentDetail: PropTypes.func.isRequired, showDirentDetail: PropTypes.func.isRequired,
isGroupOwnedRepo: PropTypes.bool.isRequired, isGroupOwnedRepo: PropTypes.bool.isRequired,
onAddFolder: PropTypes.func.isRequired,
}; };
class SelectedDirentsToolbar extends React.Component { class SelectedDirentsToolbar extends React.Component {
@@ -417,6 +418,7 @@ class SelectedDirentsToolbar extends React.Component {
selectedDirentList={this.props.selectedDirentList} selectedDirentList={this.props.selectedDirentList}
onItemsMove={this.props.onItemsMove} onItemsMove={this.props.onItemsMove}
onCancelMove={this.onMoveToggle} onCancelMove={this.onMoveToggle}
onAddFolder={this.props.onAddFolder}
/> />
} }
{this.state.isCopyDialogShow && {this.state.isCopyDialogShow &&

View File

@@ -301,6 +301,57 @@
margin: 0; margin: 0;
} }
.custom-modal .modal-header {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
border-bottom: 1px solid #e5e5e5;
position: relative;
font-size: 1rem;
}
.custom-modal .modal-header .modal-title {
max-width: calc(100% - 260px);
}
.custom-modal .modal-header .search {
position: absolute;
top: 16px;
right: 48px;
color: #666;
}
.custom-modal .modal-header .close {
width: 24px;
height: 24px;
position: absolute;
right: 16px;
display: flex;
justify-content: center;
align-items: center;
padding: 0;
margin: 0;
line-height: 12px;
color: #666 !important;
opacity: 1;
}
.custom-modal .modal-header .search:hover,
.custom-modal .modal-header .close:hover {
cursor: pointer;
background-color: #f0f0f0;
border-radius: 3px;
color: #666 !important;
opacity: 1;
}
.custom-modal .modal-header .close span{
width: 16px;
height: 16px;
}
.custom-modal .modal-content { .custom-modal .modal-content {
min-height: 534px; min-height: 534px;
border: 0; border: 0;
@@ -310,8 +361,8 @@
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1rem; justify-content: flex-start;
padding: 1rem 0; padding: 0;
position: relative; position: relative;
} }
@@ -378,9 +429,41 @@
flex-direction: column; flex-direction: column;
} }
.custom-modal .file-list-col .file-chooser-container, .custom-modal .file-list-col .file-chooser-container {
padding: 1rem;
}
.custom-modal .file-list-col .file-chooser-search-input { .custom-modal .file-list-col .file-chooser-search-input {
padding: 0 1rem; padding: 1rem 1rem 0 1rem;
}
.custom-modal .modal-footer {
position: relative;
justify-content: flex-end;
}
.custom-modal .move-dirent-dialog-footer {
justify-content: space-between;
}
.custom-modal .modal-footer .footer-left-btn {
display: flex;
justify-content: center;
align-items: center;
padding: 6px 12px;
border: 0;
cursor: pointer;
box-shadow: none;
background-color: #fff;
}
.custom-modal .modal-footer .footer-left-btn.disabled {
cursor: default;
}
.custom-modal .modal-footer .footer-left-btn:not(.disabled):hover {
background-color: #f0f0f0;
border-radius: 3px;
} }
.cur-view-container .cur-view-path { .cur-view-container .cur-view-path {

View File

@@ -959,7 +959,8 @@ class LibContentView extends React.Component {
localStorage.setItem('recently-used-list', JSON.stringify(updatedRecentlyUsed)); localStorage.setItem('recently-used-list', JSON.stringify(updatedRecentlyUsed));
}; };
onAddFolder = (dirPath) => { onAddFolder = (dirPath, options = {}) => {
const { successCallback = () => {} } = options;
let repoID = this.props.repoID; let repoID = this.props.repoID;
seafileAPI.createDir(repoID, dirPath).then(() => { seafileAPI.createDir(repoID, dirPath).then(() => {
let name = Utils.getFileName(dirPath); let name = Utils.getFileName(dirPath);
@@ -972,6 +973,8 @@ class LibContentView extends React.Component {
if (parentPath === this.state.path && !this.state.isViewFile) { if (parentPath === this.state.path && !this.state.isViewFile) {
this.addDirent(name, 'dir'); this.addDirent(name, 'dir');
} }
successCallback();
}).catch((error) => { }).catch((error) => {
let errMessage = Utils.getErrorMsg(error); let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage); toaster.danger(errMessage);
@@ -2280,6 +2283,7 @@ class LibContentView extends React.Component {
currentMode={this.state.currentMode} currentMode={this.state.currentMode}
switchViewMode={this.switchViewMode} switchViewMode={this.switchViewMode}
onItemConvert={this.onConvertItem} onItemConvert={this.onConvertItem}
onAddFolder={this.onAddFolder}
/> />
: :
<CurDirPath <CurDirPath
@@ -2312,6 +2316,7 @@ class LibContentView extends React.Component {
onItemMove={this.onMoveItem} onItemMove={this.onMoveItem}
isDesktop={isDesktop} isDesktop={isDesktop}
loadDirentList={this.loadDirentList} loadDirentList={this.loadDirentList}
onAddFolderNode={this.onAddFolder}
/> />
} }
</div> </div>