mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-13 13:50:07 +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:
@@ -1,9 +1,15 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
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 { gettext } from '../../utils/constants';
|
||||
import { gettext, isPro } from '../../utils/constants';
|
||||
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 = {
|
||||
path: PropTypes.string.isRequired,
|
||||
@@ -14,6 +20,7 @@ const propTypes = {
|
||||
onItemMove: PropTypes.func,
|
||||
onItemsMove: PropTypes.func,
|
||||
onCancelMove: PropTypes.func.isRequired,
|
||||
onAddFolder: PropTypes.func,
|
||||
};
|
||||
|
||||
class MoveDirent extends React.Component {
|
||||
@@ -21,8 +28,14 @@ class MoveDirent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
mode: MODE_TYPE_MAP.ONLY_CURRENT_LIBRARY,
|
||||
repo: { repo_id: this.props.repoID },
|
||||
selectedPath: this.props.path,
|
||||
selectedSearchedItem: null,
|
||||
selectedSearchedRepo: null,
|
||||
searchStatus: '',
|
||||
searchResults: [],
|
||||
showSearchBar: false,
|
||||
errMessage: '',
|
||||
};
|
||||
}
|
||||
@@ -138,6 +151,100 @@ class MoveDirent extends React.Component {
|
||||
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 = () => {
|
||||
const { dirent, isMultipleOperation } = this.props;
|
||||
let title = gettext('Move {placeholder} to');
|
||||
@@ -151,6 +258,7 @@ class MoveDirent extends React.Component {
|
||||
|
||||
render() {
|
||||
const { dirent, selectedDirentList, isMultipleOperation, path, repoID } = this.props;
|
||||
const { mode, selectedPath, showSearchBar, searchStatus, searchResults, selectedSearchedRepo, errMessage } = this.state;
|
||||
const movedDirent = dirent || selectedDirentList[0];
|
||||
const { permission } = movedDirent;
|
||||
const { isCustomPermission } = Utils.getUserPermission(permission);
|
||||
@@ -158,19 +266,47 @@ class MoveDirent extends React.Component {
|
||||
return (
|
||||
<Modal className='custom-modal' isOpen={true} 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>
|
||||
<SelectDirentBody
|
||||
path={path}
|
||||
selectedPath={this.state.selectedPath}
|
||||
selectedPath={selectedPath}
|
||||
repoID={repoID}
|
||||
isSupportOtherLibraries={!isCustomPermission}
|
||||
errMessage={this.state.errMessage}
|
||||
errMessage={errMessage}
|
||||
onCancel={this.toggle}
|
||||
selectRepo={this.selectRepo}
|
||||
setSelectedPath={this.setSelectedPath}
|
||||
setErrMessage={this.setErrMessage}
|
||||
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>
|
||||
);
|
||||
|
@@ -2,16 +2,17 @@ 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 { gettext } from '../../utils/constants';
|
||||
import { Utils } from '../../utils/utils';
|
||||
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 (
|
||||
<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>
|
||||
</div>
|
||||
);
|
||||
@@ -22,15 +23,13 @@ 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: '',
|
||||
showCreateFolderDialog: false,
|
||||
};
|
||||
this.newFolderName = '';
|
||||
}
|
||||
|
||||
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 () => {
|
||||
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 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 selectedRepo = sortedRepoList.find((repo) => repo.repo_id === this.props.repoID);
|
||||
this.setState({
|
||||
@@ -69,29 +89,10 @@ class SelectDirentBody extends React.Component {
|
||||
}
|
||||
};
|
||||
|
||||
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,
|
||||
});
|
||||
};
|
||||
|
||||
setBrowsingPath = (path) => {
|
||||
this.setState({ browsingPath: path });
|
||||
};
|
||||
|
||||
handleSubmit = () => {
|
||||
if (this.props.handleSubmit) {
|
||||
this.props.handleSubmit();
|
||||
@@ -118,23 +119,60 @@ class SelectDirentBody extends React.Component {
|
||||
this.setState({ selectedRepo: repo });
|
||||
};
|
||||
|
||||
selectedMode = (mode) => {
|
||||
const { repoID, path } = this.props;
|
||||
|
||||
// reset selecting status
|
||||
this.props.selectRepo({ repo_id: repoID });
|
||||
this.props.setSelectedPath(path);
|
||||
|
||||
onUpdateMode = (mode) => {
|
||||
const { path } = this.props;
|
||||
const { repoList } = this.state;
|
||||
if (mode === MODE_TYPE_MAP.ONLY_OTHER_LIBRARIES) {
|
||||
this.setState({
|
||||
mode,
|
||||
selectedSearchedItem: null,
|
||||
searchStatus: SearchStatus.RESULTS,
|
||||
selectedRepo: repoList[0],
|
||||
});
|
||||
this.props.setSelectedPath('/');
|
||||
} else {
|
||||
this.setState({ selectedRepo: this.state.currentRepoInfo });
|
||||
this.props.setSelectedPath(path);
|
||||
}
|
||||
|
||||
this.props.onUpdateMode(mode);
|
||||
};
|
||||
|
||||
loadRepoDirentList = (repo) => {
|
||||
try {
|
||||
const { data } = seafileAPI.listDir(repo.repo_id, '/');
|
||||
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() {
|
||||
const { path, selectedPath, isSupportOtherLibraries, errMessage } = this.props;
|
||||
const { mode, searchStatus, selectedSearchedItem, selectedRepo, repoList, currentRepoInfo, browsingPath } = this.state;
|
||||
const { mode, path, selectedPath, isSupportOtherLibraries, errMessage, searchStatus, searchResults, selectedSearchedRepo } = this.props;
|
||||
const { selectedSearchedItem, selectedRepo, repoList, currentRepoInfo } = this.state;
|
||||
let repoListWrapperKey = 'repo-list-wrapper';
|
||||
if (selectedSearchedItem && selectedSearchedItem.repoID) {
|
||||
repoListWrapperKey = `${repoListWrapperKey}-${selectedSearchedItem.repoID}`;
|
||||
@@ -143,47 +181,33 @@ class SelectDirentBody extends React.Component {
|
||||
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}
|
||||
setSelectedPath={this.props.setSelectedPath}
|
||||
setBrowsingPath={this.setBrowsingPath}
|
||||
/>
|
||||
)}
|
||||
<LibraryOption
|
||||
mode={MODE_TYPE_MAP.ONLY_CURRENT_LIBRARY}
|
||||
label={gettext('Current Library')}
|
||||
currentMode={mode}
|
||||
selectedMode={this.selectedMode}
|
||||
onUpdateMode={this.onUpdateMode}
|
||||
/>
|
||||
{isSupportOtherLibraries && (
|
||||
<LibraryOption
|
||||
mode={MODE_TYPE_MAP.ONLY_OTHER_LIBRARIES}
|
||||
label={gettext('Other Libraries')}
|
||||
currentMode={mode}
|
||||
selectedMode={this.selectedMode}
|
||||
onUpdateMode={this.onUpdateMode}
|
||||
/>
|
||||
)}
|
||||
<LibraryOption
|
||||
mode={MODE_TYPE_MAP.RECENTLY_USED}
|
||||
label={gettext('Recently Used')}
|
||||
currentMode={mode}
|
||||
selectedMode={this.selectedMode}
|
||||
onUpdateMode={this.onUpdateMode}
|
||||
/>
|
||||
</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}
|
||||
@@ -191,15 +215,41 @@ class SelectDirentBody extends React.Component {
|
||||
repoList={repoList}
|
||||
handleClickRepo={this.onRepoItemClick}
|
||||
handleClickDirent={this.onDirentItemClick}
|
||||
searchStatus={searchStatus}
|
||||
searchResults={searchResults}
|
||||
onSearchedItemClick={this.props.onSearchedItemClick}
|
||||
onSearchedItemDoubleClick={this.props.onSearchedItemDoubleClick}
|
||||
selectedSearchedRepo={selectedSearchedRepo}
|
||||
newFolderName={this.newFolderName}
|
||||
/>
|
||||
)}
|
||||
{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 className='move-dirent-dialog-footer'>
|
||||
<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>
|
||||
</Col>
|
||||
{this.state.showCreateFolderDialog && (
|
||||
<ModalPortal>
|
||||
<CreateFolder
|
||||
parentPath={this.props.selectedPath}
|
||||
onAddFolder={this.createFolder}
|
||||
checkDuplicatedName={this.checkDuplicatedName}
|
||||
addFolderCancel={this.onToggleCreateFolder}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
@@ -215,6 +265,14 @@ SelectDirentBody.propTypes = {
|
||||
selectRepo: PropTypes.func,
|
||||
setSelectedPath: 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 = {
|
||||
|
@@ -935,6 +935,7 @@ class DirentGridView extends React.Component {
|
||||
onItemsMove={this.props.onItemsMove}
|
||||
onCancelMove={this.onMoveToggle}
|
||||
dirent={this.state.activeDirent}
|
||||
onAddFolder={this.props.onAddFolder}
|
||||
/>
|
||||
}
|
||||
{this.state.isZipDialogOpen &&
|
||||
|
@@ -968,6 +968,7 @@ class DirentListItem extends React.Component {
|
||||
onItemMove={this.props.onItemMove}
|
||||
onCancelMove={this.onItemMoveToggle}
|
||||
repoEncrypted={this.props.repoEncrypted}
|
||||
onAddFolder={this.props.onAddFolder}
|
||||
/>
|
||||
</ModalPortal>
|
||||
}
|
||||
|
@@ -779,6 +779,7 @@ class DirentListView extends React.Component {
|
||||
onItemsMove={this.props.onItemsMove}
|
||||
onShowDirentsDraggablePreview={this.onShowDirentsDraggablePreview}
|
||||
loadDirentList={this.props.loadDirentList}
|
||||
onAddFolder={this.props.onAddFolder}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@@ -859,6 +860,7 @@ class DirentListView extends React.Component {
|
||||
selectedDirentList={this.props.selectedDirentList}
|
||||
onItemsMove={this.props.onItemsMove}
|
||||
onCancelMove={this.onMoveToggle}
|
||||
onAddFolder={this.props.onAddFolder}
|
||||
/>
|
||||
}
|
||||
{this.state.isCopyDialogShow &&
|
||||
|
@@ -21,8 +21,7 @@ const propTypes = {
|
||||
fileSuffixes: PropTypes.array,
|
||||
selectedItemInfo: PropTypes.object,
|
||||
hideLibraryName: PropTypes.bool,
|
||||
isBrowsing: PropTypes.bool,
|
||||
browsingPath: PropTypes.string,
|
||||
newFolderName: PropTypes.string,
|
||||
};
|
||||
|
||||
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() {
|
||||
this.clearLoadRepoTimer();
|
||||
this.setState({ isMounted: false, hasLoaded: false });
|
||||
@@ -213,7 +232,7 @@ class RepoListItem extends React.Component {
|
||||
|
||||
return (
|
||||
<li>
|
||||
{!this.props.hideLibraryName && !this.props.isBrowsing &&
|
||||
{!this.props.hideLibraryName &&
|
||||
<div className={`${repoActive ? 'item-active' : ''} item-info`} onClick={this.onRepoItemClick}>
|
||||
<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>
|
||||
@@ -236,8 +255,6 @@ class RepoListItem extends React.Component {
|
||||
treeData={this.state.treeData}
|
||||
onNodeCollapse={this.onNodeCollapse}
|
||||
onNodeExpanded={this.onNodeExpanded}
|
||||
isBrowsing={this.props.isBrowsing}
|
||||
browsingPath={this.props.browsingPath}
|
||||
/>
|
||||
)}
|
||||
</li>
|
||||
|
@@ -15,8 +15,8 @@ const propTypes = {
|
||||
fileSuffixes: PropTypes.array,
|
||||
selectedItemInfo: PropTypes.object,
|
||||
currentPath: PropTypes.string,
|
||||
isBrowsing: PropTypes.bool,
|
||||
browsingPath: PropTypes.string,
|
||||
selectedSearchedRepo: PropTypes.object,
|
||||
newFolderName: PropTypes.string,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
@@ -29,25 +29,28 @@ const defaultProps = {
|
||||
fileSuffixes: [],
|
||||
selectedItemInfo: null,
|
||||
currentPath: '',
|
||||
isBrowsing: false,
|
||||
browsingPath: '',
|
||||
};
|
||||
|
||||
class RepoListView extends React.Component {
|
||||
|
||||
render() {
|
||||
let { currentRepoInfo, currentPath, repoList } = this.props;
|
||||
let { currentRepoInfo, currentPath, repoList, selectedSearchedRepo } = this.props;
|
||||
if (currentRepoInfo) {
|
||||
repoList = [];
|
||||
repoList.push(currentRepoInfo);
|
||||
}
|
||||
|
||||
if (selectedSearchedRepo) {
|
||||
repoList = [];
|
||||
repoList.push(selectedSearchedRepo);
|
||||
}
|
||||
|
||||
return (
|
||||
<ul className="list-view-content file-chooser-item" >
|
||||
{repoList.length > 0 && repoList.map((repoItem, index) => {
|
||||
return (
|
||||
<RepoListItem
|
||||
key={index}
|
||||
key={repoItem.repo_id}
|
||||
isCurrentRepo={currentRepoInfo ? true : false}
|
||||
currentPath={currentPath}
|
||||
repo={repoItem}
|
||||
@@ -59,8 +62,7 @@ class RepoListView extends React.Component {
|
||||
isShowFile={this.props.isShowFile}
|
||||
fileSuffixes={this.props.fileSuffixes}
|
||||
selectedItemInfo={this.props.selectedItemInfo}
|
||||
isBrowsing={this.props.isBrowsing}
|
||||
browsingPath={this.props.browsingPath}
|
||||
newFolderName={this.props.newFolderName}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
@@ -3,6 +3,9 @@ import PropTypes from 'prop-types';
|
||||
import RepoListView from './repo-list-view';
|
||||
import RecentlyUsedListView from './recently-used-list-view';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import SearchedListView from './searched-list-view';
|
||||
import { SearchStatus } from './searcher';
|
||||
import Loading from '../loading';
|
||||
|
||||
export const MODE_TYPE_MAP = {
|
||||
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_OTHER_LIBRARIES: 'only_other_libraries',
|
||||
RECENTLY_USED: 'recently_used',
|
||||
SEARCH_RESULTS: 'search_results',
|
||||
};
|
||||
|
||||
const RepoListWrapper = (props) => {
|
||||
const {
|
||||
mode, isShowFile, fileSuffixes, currentPath, isBrowsing, browsingPath, isCurrentRepoShow, currentRepoInfo, selectedRepo,
|
||||
mode, isShowFile, fileSuffixes, currentPath, isCurrentRepoShow, currentRepoInfo, selectedRepo,
|
||||
selectedPath, isOtherRepoShow, selectedItemInfo, repoList,
|
||||
searchStatus, searchResults, onSearchedItemClick, onSearchedItemDoubleClick, selectedSearchedRepo, newFolderName
|
||||
} = props;
|
||||
|
||||
const renderRecentlyUsed = () => {
|
||||
@@ -34,6 +39,29 @@ const RepoListWrapper = (props) => {
|
||||
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 (
|
||||
<div className='file-chooser-scroll-wrapper' onScroll={onScroll}>
|
||||
<div className="file-chooser-container user-select-none">
|
||||
@@ -91,10 +119,10 @@ const RepoListWrapper = (props) => {
|
||||
isShowFile={isShowFile}
|
||||
fileSuffixes={fileSuffixes}
|
||||
selectedItemInfo={selectedItemInfo}
|
||||
isBrowsing={isBrowsing}
|
||||
browsingPath={browsingPath}
|
||||
onRepoItemClick={props.handleClickRepo}
|
||||
onDirentItemClick={props.handleClickDirent}
|
||||
selectedSearchedRepo={selectedSearchedRepo}
|
||||
newFolderName={newFolderName}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -129,14 +157,19 @@ const RepoListWrapper = (props) => {
|
||||
isShowFile={isShowFile}
|
||||
fileSuffixes={fileSuffixes}
|
||||
selectedItemInfo={selectedItemInfo}
|
||||
isBrowsing={isBrowsing}
|
||||
browsingPath={browsingPath}
|
||||
onRepoItemClick={props.handleClickRepo}
|
||||
onDirentItemClick={props.handleClickDirent}
|
||||
selectedSearchedRepo={selectedSearchedRepo}
|
||||
newFolderName={newFolderName}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{mode === MODE_TYPE_MAP.RECENTLY_USED && renderRecentlyUsed()}
|
||||
{mode === MODE_TYPE_MAP.SEARCH_RESULTS && (
|
||||
<div className="list-view">
|
||||
{renderSearchResults()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -147,8 +180,6 @@ RepoListWrapper.propTypes = {
|
||||
currentPath: PropTypes.string,
|
||||
isShowFile: PropTypes.bool,
|
||||
fileSuffixes: PropTypes.array,
|
||||
isBrowsing: PropTypes.bool,
|
||||
browsingPath: PropTypes.string,
|
||||
selectedItemInfo: PropTypes.object,
|
||||
currentRepoInfo: PropTypes.object,
|
||||
selectedRepo: PropTypes.object,
|
||||
@@ -160,6 +191,12 @@ RepoListWrapper.propTypes = {
|
||||
onOtherRepoToggle: PropTypes.func,
|
||||
handleClickRepo: 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 = {
|
||||
|
@@ -1,8 +1,9 @@
|
||||
.file-chooser-searcher.search-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
margin-bottom: 16px;
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
right: 48px;
|
||||
}
|
||||
|
||||
.file-chooser-searcher .search-input-container {
|
||||
@@ -15,14 +16,14 @@
|
||||
|
||||
.file-chooser-searcher .search-input-container .search-input {
|
||||
width: 100%;
|
||||
height: 2.375rem;
|
||||
height: 1.875em;
|
||||
padding-left: 2rem;
|
||||
padding-right: 1.5rem;
|
||||
}
|
||||
|
||||
.file-chooser-searcher .search-input-container .search-control {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
top: 6px;
|
||||
right: 8px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
@@ -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 { 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 { Input } from 'reactstrap';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import { seafileAPI } from '../../../utils/seafile-api';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import { SEARCH_CONTAINER } from '../../../constants/zIndexes';
|
||||
import { MODE_TYPE_MAP } from '../repo-list-wrapper';
|
||||
|
||||
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, setSelectedPath, setBrowsingPath }) => {
|
||||
const Searcher = ({ onUpdateMode, onUpdateSearchStatus, onUpdateSearchResults, onClose }) => {
|
||||
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);
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
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();
|
||||
setInputValue(newValue);
|
||||
|
||||
if (newValue.length === 0) {
|
||||
onUpdateSearchStatus(SearchStatus.IDLE);
|
||||
setSearchResults([]);
|
||||
onUpdateSearchResults([]);
|
||||
return;
|
||||
}
|
||||
|
||||
onUpdateSearchStatus(SearchStatus.LOADING);
|
||||
onPopoverToggle(true);
|
||||
|
||||
const queryData = {
|
||||
q: newValue,
|
||||
@@ -61,23 +75,7 @@ const Searcher = ({ searchStatus, onUpdateSearchStatus, onDirentItemClick, selec
|
||||
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]);
|
||||
}, [onUpdateSearchStatus, onUpdateSearchResults, getSearchResult]);
|
||||
|
||||
const formatResultItems = (data) => {
|
||||
let items = [];
|
||||
@@ -96,66 +94,18 @@ const Searcher = ({ searchStatus, onUpdateSearchStatus, onDirentItemClick, selec
|
||||
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(() => {
|
||||
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);
|
||||
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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
onClose();
|
||||
}, [onClose]);
|
||||
|
||||
return (
|
||||
<div className='search-container file-chooser-searcher' style={{ zIndex: SEARCH_CONTAINER }}>
|
||||
@@ -168,39 +118,22 @@ const Searcher = ({ searchStatus, onUpdateSearchStatus, onDirentItemClick, selec
|
||||
type='text'
|
||||
value={inputValue}
|
||||
onChange={handleSearchInputChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
autoFocus
|
||||
/>
|
||||
{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,
|
||||
onUpdateMode: PropTypes.func,
|
||||
onUpdateSearchStatus: PropTypes.func,
|
||||
onDirentItemClick: PropTypes.func,
|
||||
selectSearchedItem: PropTypes.func,
|
||||
selectRepo: PropTypes.func,
|
||||
setSelectedPath: PropTypes.func,
|
||||
setBrowsingPath: PropTypes.func,
|
||||
onUpdateSearchResults: PropTypes.func,
|
||||
onClose: PropTypes.func,
|
||||
};
|
||||
|
||||
export default Searcher;
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TreeListItem from './tree-list-item';
|
||||
import treeHelper from '../tree-view/tree-helper';
|
||||
|
||||
const propTypes = {
|
||||
selectedPath: PropTypes.string,
|
||||
@@ -12,16 +11,12 @@ const propTypes = {
|
||||
onNodeCollapse: PropTypes.func.isRequired,
|
||||
onNodeExpanded: PropTypes.func.isRequired,
|
||||
fileSuffixes: PropTypes.array,
|
||||
isBrowsing: PropTypes.bool,
|
||||
browsingPath: PropTypes.string,
|
||||
};
|
||||
|
||||
class TreeListView extends React.Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
isBrowsing,
|
||||
browsingPath,
|
||||
treeData,
|
||||
selectedPath,
|
||||
onNodeCollapse,
|
||||
@@ -32,10 +27,7 @@ class TreeListView extends React.Component {
|
||||
fileSuffixes
|
||||
} = this.props;
|
||||
|
||||
const browsingNode = treeHelper.findNodeByPath(treeData, browsingPath);
|
||||
if (isBrowsing && !browsingNode) return null;
|
||||
|
||||
const node = isBrowsing ? browsingNode : treeData.root;
|
||||
const node = treeData.root;
|
||||
|
||||
return (
|
||||
<div className="list-view-content">
|
||||
|
@@ -40,6 +40,7 @@ const propTypes = {
|
||||
onItemRename: PropTypes.func.isRequired,
|
||||
showDirentDetail: PropTypes.func.isRequired,
|
||||
isGroupOwnedRepo: PropTypes.bool.isRequired,
|
||||
onAddFolder: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
class SelectedDirentsToolbar extends React.Component {
|
||||
@@ -417,6 +418,7 @@ class SelectedDirentsToolbar extends React.Component {
|
||||
selectedDirentList={this.props.selectedDirentList}
|
||||
onItemsMove={this.props.onItemsMove}
|
||||
onCancelMove={this.onMoveToggle}
|
||||
onAddFolder={this.props.onAddFolder}
|
||||
/>
|
||||
}
|
||||
{this.state.isCopyDialogShow &&
|
||||
|
@@ -301,6 +301,57 @@
|
||||
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 {
|
||||
min-height: 534px;
|
||||
border: 0;
|
||||
@@ -310,8 +361,8 @@
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
padding: 1rem 0;
|
||||
justify-content: flex-start;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -378,9 +429,41 @@
|
||||
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 {
|
||||
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 {
|
||||
|
@@ -959,7 +959,8 @@ class LibContentView extends React.Component {
|
||||
localStorage.setItem('recently-used-list', JSON.stringify(updatedRecentlyUsed));
|
||||
};
|
||||
|
||||
onAddFolder = (dirPath) => {
|
||||
onAddFolder = (dirPath, options = {}) => {
|
||||
const { successCallback = () => {} } = options;
|
||||
let repoID = this.props.repoID;
|
||||
seafileAPI.createDir(repoID, dirPath).then(() => {
|
||||
let name = Utils.getFileName(dirPath);
|
||||
@@ -972,6 +973,8 @@ class LibContentView extends React.Component {
|
||||
if (parentPath === this.state.path && !this.state.isViewFile) {
|
||||
this.addDirent(name, 'dir');
|
||||
}
|
||||
|
||||
successCallback();
|
||||
}).catch((error) => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
@@ -2280,6 +2283,7 @@ class LibContentView extends React.Component {
|
||||
currentMode={this.state.currentMode}
|
||||
switchViewMode={this.switchViewMode}
|
||||
onItemConvert={this.onConvertItem}
|
||||
onAddFolder={this.onAddFolder}
|
||||
/>
|
||||
:
|
||||
<CurDirPath
|
||||
@@ -2312,6 +2316,7 @@ class LibContentView extends React.Component {
|
||||
onItemMove={this.onMoveItem}
|
||||
isDesktop={isDesktop}
|
||||
loadDirentList={this.loadDirentList}
|
||||
onAddFolderNode={this.onAddFolder}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user