mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-03 07:55:36 +00:00
Improve search in file move/copy dialog (#3543)
This commit is contained in:
@@ -151,7 +151,7 @@ class CopyDirent extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let title = gettext('Copy {placeholder} to:');
|
let title = gettext('Copy {placeholder} to');
|
||||||
if (!this.props.isMutipleOperation) {
|
if (!this.props.isMutipleOperation) {
|
||||||
title = title.replace('{placeholder}', '<span class="op-target">' + Utils.HTMLescape(this.props.dirent.name) + '</span>');
|
title = title.replace('{placeholder}', '<span class="op-target">' + Utils.HTMLescape(this.props.dirent.name) + '</span>');
|
||||||
} else {
|
} else {
|
||||||
|
@@ -151,7 +151,7 @@ class MoveDirent extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let title = gettext('Move {placeholder} to:');
|
let title = gettext('Move {placeholder} to');
|
||||||
if (!this.props.isMutipleOperation) {
|
if (!this.props.isMutipleOperation) {
|
||||||
title = title.replace('{placeholder}', '<span class="op-target">' + Utils.HTMLescape(this.props.dirent.name) + '</span>');
|
title = title.replace('{placeholder}', '<span class="op-target">' + Utils.HTMLescape(this.props.dirent.name) + '</span>');
|
||||||
} else {
|
} else {
|
||||||
|
@@ -560,6 +560,11 @@ class DirentListView extends React.Component {
|
|||||||
if (nodeRootPath === this.props.path || nodeParentPath === this.props.path) {
|
if (nodeRootPath === this.props.path || nodeParentPath === this.props.path) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.props.path.indexOf(nodeRootPath) !== -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.props.onItemMove(this.props.currentRepoInfo, nodeDirent, this.props.path, nodeParentPath);
|
this.props.onItemMove(this.props.currentRepoInfo, nodeDirent, this.props.path, nodeParentPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,130 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { seafileAPI } from '../../utils/seafile-api';
|
|
||||||
import Dirent from '../../models/dirent';
|
|
||||||
import { Utils } from '../../utils/utils';
|
|
||||||
|
|
||||||
const propTypes = {
|
|
||||||
isShowFile: PropTypes.bool,
|
|
||||||
filePath: PropTypes.string,
|
|
||||||
selectedPath: PropTypes.string,
|
|
||||||
selectedRepo: PropTypes.object,
|
|
||||||
dirent: PropTypes.object.isRequired,
|
|
||||||
repo: PropTypes.object.isRequired,
|
|
||||||
onDirentItemClick: PropTypes.func.isRequired,
|
|
||||||
fileSuffixes: PropTypes.array,
|
|
||||||
};
|
|
||||||
|
|
||||||
class DirentListItem extends React.Component {
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
let filePath = this.props.filePath ? this.props.filePath + '/' + this.props.dirent.name : '/' + this.props.dirent.name;
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
isShowChildren: false,
|
|
||||||
hasRequest: false,
|
|
||||||
hasChildren: true,
|
|
||||||
filePath: filePath,
|
|
||||||
direntList: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
onItemClick = (e) => {
|
|
||||||
e.stopPropagation(); // need prevent event popup
|
|
||||||
let isCurrentRepo = this.props.selectedRepo.repo_id === this.props.repo.repo_id;
|
|
||||||
if (isCurrentRepo) {
|
|
||||||
if (this.props.selectedPath !== this.state.filePath) {
|
|
||||||
this.props.onDirentItemClick(this.state.filePath, this.props.dirent);
|
|
||||||
} else {
|
|
||||||
if (this.props.dirent.type === 'dir') {
|
|
||||||
this.onToggleClick(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.props.onDirentItemClick(this.state.filePath, this.props.dirent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onToggleClick = (e) => {
|
|
||||||
e.stopPropagation(); // need prevent event popup
|
|
||||||
if (!this.state.hasRequest) {
|
|
||||||
seafileAPI.listDir(this.props.repo.repo_id, this.state.filePath).then(res => {
|
|
||||||
let direntList = [];
|
|
||||||
res.data.dirent_list.forEach(item => {
|
|
||||||
if (this.props.isShowFile === true) { // show dir and file
|
|
||||||
let dirent = new Dirent(item);
|
|
||||||
direntList.push(dirent);
|
|
||||||
} else { // just show dir
|
|
||||||
if (item.type === 'dir') {
|
|
||||||
let dirent = new Dirent(item);
|
|
||||||
direntList.push(dirent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
direntList = Utils.sortDirents(direntList, 'name', 'asc');
|
|
||||||
this.setState({
|
|
||||||
hasRequest: true,
|
|
||||||
direntList: direntList,
|
|
||||||
});
|
|
||||||
if (res.data.length === 0 || direntList.length === 0) {
|
|
||||||
this.setState({hasChildren: false});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({isShowChildren: !this.state.isShowChildren});
|
|
||||||
}
|
|
||||||
|
|
||||||
renderChildren = () => {
|
|
||||||
return (
|
|
||||||
<ul className="list-view-content">
|
|
||||||
{this.state.direntList.map((dirent, index) => {
|
|
||||||
return (
|
|
||||||
<DirentListItem
|
|
||||||
key={index}
|
|
||||||
dirent={dirent}
|
|
||||||
repo={this.props.repo}
|
|
||||||
filePath={this.state.filePath}
|
|
||||||
onItemClick={this.onItemClick}
|
|
||||||
selectedPath={this.props.selectedPath}
|
|
||||||
selectedRepo={this.props.selectedRepo}
|
|
||||||
onDirentItemClick={this.props.onDirentItemClick}
|
|
||||||
isShowFile={this.props.isShowFile}
|
|
||||||
fileSuffixes={this.props.fileSuffixes}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
let isCurrentRepo = this.props.selectedRepo.repo_id === this.props.repo.repo_id;
|
|
||||||
let isCurrentPath = this.props.selectedPath === this.state.filePath;
|
|
||||||
|
|
||||||
const fileName = this.props.dirent.name;
|
|
||||||
if (this.props.fileSuffixes && fileName && fileName.indexOf('.') !== -1) {
|
|
||||||
const suffix = fileName.slice(fileName.lastIndexOf('.') + 1).toLowerCase();
|
|
||||||
if (!this.props.fileSuffixes.includes(suffix)) return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<li className="file-chooser-item">
|
|
||||||
{
|
|
||||||
this.state.hasChildren && this.props.dirent.type !== 'file' &&
|
|
||||||
<span className={`item-toggle fa ${this.state.isShowChildren ? 'fa-caret-down' : 'fa-caret-right'}`} onClick={this.onToggleClick}></span>
|
|
||||||
}
|
|
||||||
<span className={`item-info ${(isCurrentRepo && isCurrentPath) ? 'item-active' : ''}`} onClick={this.onItemClick}>
|
|
||||||
<span className={`icon far ${this.props.dirent.type === 'dir' ? 'fa-folder' : 'fa-file'}`}></span>
|
|
||||||
<span className="name user-select-none ellipsis">{this.props.dirent && this.props.dirent.name}</span>
|
|
||||||
</span>
|
|
||||||
{this.state.isShowChildren && this.renderChildren()}
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DirentListItem.propTypes = propTypes;
|
|
||||||
|
|
||||||
export default DirentListItem;
|
|
@@ -1,72 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { seafileAPI } from '../../utils/seafile-api';
|
|
||||||
import Dirent from '../../models/dirent';
|
|
||||||
import DirentListItem from './dirent-list-item';
|
|
||||||
import { Utils } from '../../utils/utils';
|
|
||||||
|
|
||||||
const propTypes = {
|
|
||||||
isShowFile: PropTypes.bool,
|
|
||||||
selectedPath: PropTypes.string,
|
|
||||||
selectedRepo: PropTypes.object,
|
|
||||||
fileSuffixes: PropTypes.array,
|
|
||||||
repo: PropTypes.object.isRequired,
|
|
||||||
isShowChildren: PropTypes.bool.isRequired,
|
|
||||||
onDirentItemClick: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
class DirentListView extends React.Component {
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
direntList: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
let repo = this.props.repo;
|
|
||||||
seafileAPI.listDir(repo.repo_id, '/').then(res => {
|
|
||||||
let direntList = [];
|
|
||||||
res.data.dirent_list.forEach(item => {
|
|
||||||
if (this.props.isShowFile === true) {
|
|
||||||
let dirent = new Dirent(item);
|
|
||||||
direntList.push(dirent);
|
|
||||||
} else {
|
|
||||||
if (item.type === 'dir') {
|
|
||||||
let dirent = new Dirent(item);
|
|
||||||
direntList.push(dirent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
direntList = Utils.sortDirents(direntList, 'name', 'asc');
|
|
||||||
this.setState({direntList: direntList});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
let { direntList } = this.state;
|
|
||||||
return (
|
|
||||||
<ul className={`list-view-content ${this.props.isShowChildren ? '' : 'hide'}`}>
|
|
||||||
{ direntList.map((dirent, index) => {
|
|
||||||
return (
|
|
||||||
<DirentListItem
|
|
||||||
key={index}
|
|
||||||
repo={this.props.repo}
|
|
||||||
dirent={dirent}
|
|
||||||
selectedRepo={this.props.selectedRepo}
|
|
||||||
onDirentItemClick={this.props.onDirentItemClick}
|
|
||||||
selectedPath={this.props.selectedPath}
|
|
||||||
isShowFile={this.props.isShowFile}
|
|
||||||
fileSuffixes={this.props.fileSuffixes}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DirentListView.propTypes = propTypes;
|
|
||||||
|
|
||||||
export default DirentListView;
|
|
@@ -36,6 +36,7 @@ class FileChooser extends React.Component {
|
|||||||
isResultGot: false,
|
isResultGot: false,
|
||||||
searchInfo: '',
|
searchInfo: '',
|
||||||
searchResults: [],
|
searchResults: [],
|
||||||
|
selectedItemInfo: {},
|
||||||
};
|
};
|
||||||
this.inputValue = '';
|
this.inputValue = '';
|
||||||
this.timer = null;
|
this.timer = null;
|
||||||
@@ -100,6 +101,7 @@ class FileChooser extends React.Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
repoList: repoList,
|
repoList: repoList,
|
||||||
isOtherRepoShow: !this.state.isOtherRepoShow,
|
isOtherRepoShow: !this.state.isOtherRepoShow,
|
||||||
|
selectedItemInfo: {}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -285,13 +287,83 @@ class FileChooser extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.isResultGot && this.state.searchResults.length > 0) {
|
if (this.state.isResultGot && this.state.searchResults.length > 0) {
|
||||||
return (<SearchedListView searchResults={this.state.searchResults} onItemClick={this.onSearchedItemClick}/>);
|
return (
|
||||||
|
<SearchedListView
|
||||||
|
searchResults={this.state.searchResults}
|
||||||
|
onItemClick={this.onSearchedItemClick}
|
||||||
|
onSearchedItemDoubleClick={this.onSearchedItemDoubleClick}
|
||||||
|
/>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSearchedItemDoubleClick = (item) => {
|
||||||
|
|
||||||
|
let selectedItemInfo = {
|
||||||
|
repoID: item.repo_id,
|
||||||
|
filePath: item.path,
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
selectedItemInfo: selectedItemInfo
|
||||||
|
});
|
||||||
|
|
||||||
|
if (item.repo_id === this.props.repoID) {
|
||||||
|
seafileAPI.getRepoInfo(this.props.repoID).then(res => {
|
||||||
|
// need to optimized
|
||||||
|
let repoInfo = new RepoInfo(res.data);
|
||||||
|
let path = item.path.substring(0, (item.path.length - 1));
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
selectedRepo: repoInfo,
|
||||||
|
selectedPath: path,
|
||||||
|
isCurrentRepoShow: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (!this.state.hasRequest) {
|
||||||
|
let that = this;
|
||||||
|
seafileAPI.listRepos().then(res => {
|
||||||
|
// todo optimized code
|
||||||
|
let repos = res.data.repos;
|
||||||
|
let repoList = [];
|
||||||
|
let repoIdList = [];
|
||||||
|
for (let i = 0; i < repos.length; i++) {
|
||||||
|
if (repos[i].permission !== 'rw') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (that.props.repoID && (repos[i].repo_name === that.state.currentRepoInfo.repo_name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (repoIdList.indexOf(repos[i].repo_id) > -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
repoList.push(repos[i]);
|
||||||
|
repoIdList.push(repos[i].repo_id);
|
||||||
|
}
|
||||||
|
repoList = Utils.sortRepos(repoList, 'name', 'asc');
|
||||||
|
let repo = repoList.filter(repoItem => repoItem.repo_id === item.repo_id);
|
||||||
|
let path = item.path.substring(0, (item.path.length - 1));
|
||||||
|
|
||||||
|
let selectRepo = repo[0];
|
||||||
|
this.setState({
|
||||||
|
repoList: repoList,
|
||||||
|
isOtherRepoShow: true,
|
||||||
|
selectedPath: path,
|
||||||
|
selectedRepo: selectRepo,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.setState({isOtherRepoShow: !this.state.isOtherRepoShow});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.onCloseSearching();
|
||||||
|
}
|
||||||
|
|
||||||
renderRepoListView = () => {
|
renderRepoListView = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="file-chooser-container">
|
<div className="file-chooser-container user-select-none">
|
||||||
{this.props.mode === 'current_repo_and_other_repos' && (
|
{this.props.mode === 'current_repo_and_other_repos' && (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className="list-view">
|
<div className="list-view">
|
||||||
@@ -310,6 +382,7 @@ class FileChooser extends React.Component {
|
|||||||
onDirentItemClick={this.onDirentItemClick}
|
onDirentItemClick={this.onDirentItemClick}
|
||||||
isShowFile={this.props.isShowFile}
|
isShowFile={this.props.isShowFile}
|
||||||
fileSuffixes={this.props.fileSuffixes}
|
fileSuffixes={this.props.fileSuffixes}
|
||||||
|
selectedItemInfo={this.state.selectedItemInfo}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -329,6 +402,7 @@ class FileChooser extends React.Component {
|
|||||||
onDirentItemClick={this.onDirentItemClick}
|
onDirentItemClick={this.onDirentItemClick}
|
||||||
isShowFile={this.props.isShowFile}
|
isShowFile={this.props.isShowFile}
|
||||||
fileSuffixes={this.props.fileSuffixes}
|
fileSuffixes={this.props.fileSuffixes}
|
||||||
|
selectedItemInfo={this.state.selectedItemInfo}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -351,6 +425,7 @@ class FileChooser extends React.Component {
|
|||||||
onDirentItemClick={this.onDirentItemClick}
|
onDirentItemClick={this.onDirentItemClick}
|
||||||
isShowFile={this.props.isShowFile}
|
isShowFile={this.props.isShowFile}
|
||||||
fileSuffixes={this.props.fileSuffixes}
|
fileSuffixes={this.props.fileSuffixes}
|
||||||
|
selectedItemInfo={this.state.selectedItemInfo}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -371,6 +446,7 @@ class FileChooser extends React.Component {
|
|||||||
onDirentItemClick={this.onDirentItemClick}
|
onDirentItemClick={this.onDirentItemClick}
|
||||||
isShowFile={this.props.isShowFile}
|
isShowFile={this.props.isShowFile}
|
||||||
fileSuffixes={this.props.fileSuffixes}
|
fileSuffixes={this.props.fileSuffixes}
|
||||||
|
selectedItemInfo={this.state.selectedItemInfo}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,6 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import DirentListView from './dirent-list-view';
|
import TreeListView from './tree-list-view'
|
||||||
|
|
||||||
|
import TreeNode from '../../components/tree-view/tree-node';
|
||||||
|
import Dirent from '../../models/dirent';
|
||||||
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
|
import treeHelper from '../../components/tree-view/tree-helper';
|
||||||
|
import { Utils } from '../../utils/utils';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
isShowFile: PropTypes.bool,
|
isShowFile: PropTypes.bool,
|
||||||
@@ -11,6 +17,7 @@ const propTypes = {
|
|||||||
onDirentItemClick: PropTypes.func.isRequired,
|
onDirentItemClick: PropTypes.func.isRequired,
|
||||||
onRepoItemClick: PropTypes.func.isRequired,
|
onRepoItemClick: PropTypes.func.isRequired,
|
||||||
fileSuffixes: PropTypes.array,
|
fileSuffixes: PropTypes.array,
|
||||||
|
selectedItemInfo: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
class RepoListItem extends React.Component {
|
class RepoListItem extends React.Component {
|
||||||
@@ -19,9 +26,89 @@ class RepoListItem extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
isShowChildren: this.props.initToShowChildren,
|
isShowChildren: this.props.initToShowChildren,
|
||||||
|
treeData: treeHelper.buildTree(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
let repoID = this.props.repo.repo_id;
|
||||||
|
seafileAPI.listDir(repoID, '/').then(res => {
|
||||||
|
let tree = this.state.treeData.clone();
|
||||||
|
let direntList = res.data.dirent_list.filter(item => item.type === 'dir')
|
||||||
|
this.addResponseListToNode(direntList, tree.root);
|
||||||
|
this.setState({treeData: tree});
|
||||||
|
})
|
||||||
|
|
||||||
|
if (this.props.selectedItemInfo.repoID === this.props.repo.repo_id) {
|
||||||
|
this.setState({isShowChildren: true});
|
||||||
|
this.loadNodeAndParentsByPath(this.props.selectedItemInfo.repoID, this.props.selectedItemInfo.filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addResponseListToNode = (list, node) => {
|
||||||
|
node.isLoaded = true;
|
||||||
|
node.isExpanded = true;
|
||||||
|
let direntList = list.map(item => {
|
||||||
|
return new Dirent(item);
|
||||||
|
});
|
||||||
|
direntList = Utils.sortDirents(direntList, 'name', 'asc');
|
||||||
|
|
||||||
|
let nodeList = direntList.map(object => {
|
||||||
|
return new TreeNode({object});
|
||||||
|
});
|
||||||
|
node.addChildren(nodeList);
|
||||||
|
}
|
||||||
|
|
||||||
|
onNodeExpanded = (node) => {
|
||||||
|
let repoID = this.props.repo.repo_id;
|
||||||
|
let tree = this.state.treeData.clone();
|
||||||
|
node = tree.getNodeByPath(node.path);
|
||||||
|
if (!node.isLoaded) {
|
||||||
|
seafileAPI.listDir(repoID, node.path).then(res => {
|
||||||
|
let direntList = res.data.dirent_list.filter(item => item.type === 'dir')
|
||||||
|
this.addResponseListToNode(direntList, node);
|
||||||
|
this.setState({treeData: tree});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
tree.expandNode(node);
|
||||||
|
this.setState({treeData: tree});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onNodeCollapse = (node) => {
|
||||||
|
let tree = treeHelper.collapseNode(this.state.treeData, node);
|
||||||
|
this.setState({treeData: tree});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadNodeAndParentsByPath = (repoID, path) => {
|
||||||
|
|
||||||
|
let tree = this.state.treeData.clone();
|
||||||
|
|
||||||
|
seafileAPI.listDir(repoID, path, {with_parents: true}).then(res => {
|
||||||
|
let direntList = res.data.dirent_list;
|
||||||
|
direntList = direntList.filter(item => item.type === 'dir');
|
||||||
|
let results = {};
|
||||||
|
for (let i = 0; i < direntList.length; i++) {
|
||||||
|
let object = direntList[i];
|
||||||
|
let parentDir = object.parent_dir;
|
||||||
|
let key = parentDir === '/' ? '/' : parentDir.slice(0, parentDir.length - 1);
|
||||||
|
if (!results[key]) {
|
||||||
|
results[key] = [];
|
||||||
|
}
|
||||||
|
results[key].push(object);
|
||||||
|
}
|
||||||
|
for (let key in results) {
|
||||||
|
let node = tree.getNodeByPath(key);
|
||||||
|
if (!node.isLoaded) {
|
||||||
|
this.addResponseListToNode(results[key], node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
treeData: tree
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onToggleClick = () => {
|
onToggleClick = () => {
|
||||||
this.setState({isShowChildren: !this.state.isShowChildren});
|
this.setState({isShowChildren: !this.state.isShowChildren});
|
||||||
}
|
}
|
||||||
@@ -50,6 +137,7 @@ class RepoListItem extends React.Component {
|
|||||||
if (isCurrentRepo && !this.props.selectedPath) {
|
if (isCurrentRepo && !this.props.selectedPath) {
|
||||||
repoActive = true;
|
repoActive = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className="file-chooser-item">
|
<li className="file-chooser-item">
|
||||||
<span className={`item-toggle fa ${this.state.isShowChildren ? 'fa-caret-down' : 'fa-caret-right'}`} onClick={this.onToggleClick}></span>
|
<span className={`item-toggle fa ${this.state.isShowChildren ? 'fa-caret-down' : 'fa-caret-right'}`} onClick={this.onToggleClick}></span>
|
||||||
@@ -58,14 +146,15 @@ class RepoListItem extends React.Component {
|
|||||||
<span className="name user-select-none ellipsis">{this.props.repo.repo_name}</span>
|
<span className="name user-select-none ellipsis">{this.props.repo.repo_name}</span>
|
||||||
</span>
|
</span>
|
||||||
{this.state.isShowChildren && (
|
{this.state.isShowChildren && (
|
||||||
<DirentListView
|
<TreeListView
|
||||||
repo={this.props.repo}
|
repo={this.props.repo}
|
||||||
isShowChildren={this.state.isShowChildren}
|
|
||||||
onDirentItemClick={this.onDirentItemClick}
|
onDirentItemClick={this.onDirentItemClick}
|
||||||
selectedRepo={this.props.selectedRepo}
|
selectedRepo={this.props.selectedRepo}
|
||||||
selectedPath={this.props.selectedPath}
|
selectedPath={this.props.selectedPath}
|
||||||
isShowFile={this.props.isShowFile}
|
|
||||||
fileSuffixes={this.props.fileSuffixes}
|
fileSuffixes={this.props.fileSuffixes}
|
||||||
|
treeData={this.state.treeData}
|
||||||
|
onNodeCollapse={this.onNodeCollapse}
|
||||||
|
onNodeExpanded={this.onNodeExpanded}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
|
@@ -13,6 +13,7 @@ const propTypes = {
|
|||||||
onDirentItemClick: PropTypes.func.isRequired,
|
onDirentItemClick: PropTypes.func.isRequired,
|
||||||
onRepoItemClick: PropTypes.func.isRequired,
|
onRepoItemClick: PropTypes.func.isRequired,
|
||||||
fileSuffixes: PropTypes.array,
|
fileSuffixes: PropTypes.array,
|
||||||
|
selectedItemInfo: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
class RepoListView extends React.Component {
|
class RepoListView extends React.Component {
|
||||||
@@ -37,6 +38,7 @@ class RepoListView extends React.Component {
|
|||||||
onDirentItemClick={this.props.onDirentItemClick}
|
onDirentItemClick={this.props.onDirentItemClick}
|
||||||
isShowFile={this.props.isShowFile}
|
isShowFile={this.props.isShowFile}
|
||||||
fileSuffixes={this.props.fileSuffixes}
|
fileSuffixes={this.props.fileSuffixes}
|
||||||
|
selectedItemInfo={this.props.selectedItemInfo}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@@ -5,6 +5,7 @@ import { Utils } from '../../utils/utils';
|
|||||||
const propTypes = {
|
const propTypes = {
|
||||||
currentItem: PropTypes.object,
|
currentItem: PropTypes.object,
|
||||||
onItemClick: PropTypes.func.isRequired,
|
onItemClick: PropTypes.func.isRequired,
|
||||||
|
onSearchedItemDoubleClick: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
class SearchedListItem extends React.Component {
|
class SearchedListItem extends React.Component {
|
||||||
@@ -29,6 +30,12 @@ class SearchedListItem extends React.Component {
|
|||||||
this.props.onItemClick(item);
|
this.props.onItemClick(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
searchItemDoubleClick = (e) => {
|
||||||
|
let item = this.props.item;
|
||||||
|
|
||||||
|
this.props.onSearchedItemDoubleClick(item);
|
||||||
|
}
|
||||||
|
|
||||||
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 folderIconUrl = item.link_content ? Utils.getFolderIconUrl(false, 192) : Utils.getDefaultLibIconUrl(false);
|
||||||
@@ -36,11 +43,11 @@ class SearchedListItem extends React.Component {
|
|||||||
let trClass = this.state.highlight ? 'tr-highlight' : '';
|
let trClass = this.state.highlight ? 'tr-highlight' : '';
|
||||||
if (currentItem) {
|
if (currentItem) {
|
||||||
if (item.repo_id === currentItem.repo_id && item.path === currentItem.path) {
|
if (item.repo_id === currentItem.repo_id && item.path === currentItem.path) {
|
||||||
trClass = 'searched-active';
|
trClass = 'tr-active';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<tr className={trClass} onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
<tr className={trClass} onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} onDoubleClick={this.searchItemDoubleClick}>
|
||||||
<td className="text-center"><img className="item-img" src={fileIconUrl} alt="" width="24"/></td>
|
<td className="text-center"><img className="item-img" src={fileIconUrl} alt="" width="24"/></td>
|
||||||
<td><span className="item-link">{item.repo_name}/{item.link_content}</span></td>
|
<td><span className="item-link">{item.repo_name}/{item.link_content}</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@@ -5,6 +5,7 @@ import SearchedListItem from './searched-list-item';
|
|||||||
const propTypes = {
|
const propTypes = {
|
||||||
searchResults: PropTypes.array.isRequired,
|
searchResults: PropTypes.array.isRequired,
|
||||||
onItemClick: PropTypes.func.isRequired,
|
onItemClick: PropTypes.func.isRequired,
|
||||||
|
onSearchedItemDoubleClick: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
class SearchedListView extends React.Component {
|
class SearchedListView extends React.Component {
|
||||||
@@ -23,7 +24,7 @@ class SearchedListView extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<table className="table-thead-hidden">
|
<table className="table-thead-hidden file-chooser-table" rules="node" frame="void">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th width="8%"></th>
|
<th width="8%"></th>
|
||||||
@@ -32,7 +33,14 @@ class SearchedListView extends React.Component {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{this.props.searchResults.map((item, index) => {
|
{this.props.searchResults.map((item, index) => {
|
||||||
return (<SearchedListItem key={index} item={item} currentItem={this.state.currentItem} onItemClick={this.onItemClick} />);
|
return (
|
||||||
|
<SearchedListItem
|
||||||
|
key={index}
|
||||||
|
item={item}
|
||||||
|
currentItem={this.state.currentItem}
|
||||||
|
onItemClick={this.onItemClick}
|
||||||
|
onSearchedItemDoubleClick={this.props.onSearchedItemDoubleClick}
|
||||||
|
/>);
|
||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
99
frontend/src/components/file-chooser/tree-list-item.js
Normal file
99
frontend/src/components/file-chooser/tree-list-item.js
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
selectedPath: PropTypes.string,
|
||||||
|
selectedRepo: PropTypes.object,
|
||||||
|
repo: PropTypes.object.isRequired,
|
||||||
|
onDirentItemClick: PropTypes.func.isRequired,
|
||||||
|
node: PropTypes.object.isRequired,
|
||||||
|
onNodeCollapse: PropTypes.func.isRequired,
|
||||||
|
onNodeExpanded: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
class TreeViewItem extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
let filePath = this.props.filePath ? this.props.filePath + '/' + this.props.node.object.name : this.props.node.path;
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
filePath: filePath,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onToggleClick = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
let { node } = this.props;
|
||||||
|
if (node.isExpanded) {
|
||||||
|
this.props.onNodeCollapse(node);
|
||||||
|
} else {
|
||||||
|
this.props.onNodeExpanded(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onItemClick = (e) => {
|
||||||
|
e.stopPropagation(); // need prevent event popup
|
||||||
|
let isCurrentRepo = this.props.selectedRepo.repo_id === this.props.repo.repo_id;
|
||||||
|
if (isCurrentRepo) {
|
||||||
|
if (this.props.selectedPath !== this.state.filePath) {
|
||||||
|
this.props.onDirentItemClick(this.state.filePath, this.props.node.object);
|
||||||
|
} else {
|
||||||
|
if (this.props.node.object.type === 'dir') {
|
||||||
|
this.onToggleClick(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.props.onDirentItemClick(this.state.filePath, this.props.node.object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderChildren = () => {
|
||||||
|
let { node } = this.props;
|
||||||
|
if (!node.hasChildren()) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return(
|
||||||
|
<div className="list-view-content">
|
||||||
|
{node.children.map(item => {
|
||||||
|
return (
|
||||||
|
<TreeViewItem
|
||||||
|
key={item.path}
|
||||||
|
node={item}
|
||||||
|
onNodeCollapse={this.props.onNodeCollapse}
|
||||||
|
onNodeExpanded={this.props.onNodeExpanded}
|
||||||
|
onNodeClick={this.props.onTreeNodeClick}
|
||||||
|
repo={this.props.repo}
|
||||||
|
onDirentItemClick={this.props.onDirentItemClick}
|
||||||
|
selectedRepo={this.props.selectedRepo}
|
||||||
|
selectedPath={this.props.selectedPath}
|
||||||
|
fileSuffixes={this.props.fileSuffixes}
|
||||||
|
/>)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let { node } = this.props;
|
||||||
|
let isCurrentRepo = this.props.selectedRepo.repo_id === this.props.repo.repo_id;
|
||||||
|
let isCurrentPath = this.props.selectedPath === this.state.filePath;
|
||||||
|
|
||||||
|
return(
|
||||||
|
<div className={`file-chooser-item `}>
|
||||||
|
<div className={`${node.path === '/'? 'hide': ''}`}>
|
||||||
|
<span className={`item-toggle fa ${node.isExpanded ? 'fa-caret-down' : 'fa-caret-right'}`} onClick={this.onToggleClick}></span>
|
||||||
|
<span className={`item-info ${(isCurrentRepo && isCurrentPath) ? 'item-active' : ''}`} onClick={this.onItemClick}>
|
||||||
|
<span className={`icon far ${node.object.type === 'dir' ? 'fa-folder' : 'fa-file'}`}></span>
|
||||||
|
<span className="name user-select-none ellipsis">{node.object.name}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{node.isExpanded && this.renderChildren()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TreeViewItem.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default TreeViewItem;
|
39
frontend/src/components/file-chooser/tree-list-view.js
Normal file
39
frontend/src/components/file-chooser/tree-list-view.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import TreeListItem from './tree-list-item';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
selectedPath: PropTypes.string,
|
||||||
|
selectedRepo: PropTypes.object,
|
||||||
|
repo: PropTypes.object.isRequired,
|
||||||
|
onDirentItemClick: PropTypes.func.isRequired,
|
||||||
|
treeData: PropTypes.object.isRequired,
|
||||||
|
onNodeCollapse: PropTypes.func.isRequired,
|
||||||
|
onNodeExpanded: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
class TreeListView extends React.Component {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return(
|
||||||
|
<div className="list-view-content" style={{'marginLeft': '-1.5rem'}}>
|
||||||
|
<TreeListItem
|
||||||
|
node={this.props.treeData.root}
|
||||||
|
onNodeCollapse={this.props.onNodeCollapse}
|
||||||
|
onNodeExpanded={this.props.onNodeExpanded}
|
||||||
|
onNodeClick={this.props.onTreeNodeClick}
|
||||||
|
repo={this.props.repo}
|
||||||
|
onDirentItemClick={this.props.onDirentItemClick}
|
||||||
|
selectedRepo={this.props.selectedRepo}
|
||||||
|
selectedPath={this.props.selectedPath}
|
||||||
|
fileSuffixes={this.props.fileSuffixes}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TreeListView.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default TreeListView;
|
@@ -26,6 +26,9 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
padding-left: 1.5rem;
|
padding-left: 1.5rem;
|
||||||
}
|
}
|
||||||
|
.list-view-header:hover {
|
||||||
|
background-color: #FDEFB9;
|
||||||
|
}
|
||||||
|
|
||||||
.list-view-header .name {
|
.list-view-header .name {
|
||||||
color: #eb8205;
|
color: #eb8205;
|
||||||
@@ -50,15 +53,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.file-chooser-item .item-info:hover {
|
.file-chooser-item .item-info:hover {
|
||||||
background: #e7f4f9;
|
background: #FDEFB9;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
box-shadow: inset 0 0 1px #999;
|
box-shadow: inset 0 0 1px #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-chooser-item .item-active {
|
.file-chooser-item .item-active {
|
||||||
background: #beebff !important;
|
background: #F3AF7D !important;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
box-shadow: inset 0 0 1px #999;
|
box-shadow: inset 0 0 1px #999;
|
||||||
|
color: #fff
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-chooser-item .item-info .icon {
|
.file-chooser-item .item-info .icon {
|
||||||
@@ -74,16 +78,24 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-chooser-item .item-active .icon {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
.file-chooser-search-input {
|
.file-chooser-search-input {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-chooser-search-input .search-control {
|
.file-chooser-search-input .search-control {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0.7rem;
|
top: 0.5rem;
|
||||||
right: 0.7rem;
|
right: 0.7rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-chooser-search-input .search-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.file-chooser-search-container {
|
.file-chooser-search-container {
|
||||||
height: 20rem;
|
height: 20rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -99,12 +111,27 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.searched-active {
|
.searched-active {
|
||||||
background: #beebff !important;
|
background: #F3AF7D !important;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
box-shadow: inset 0 0 1px #999;
|
box-shadow: inset 0 0 1px #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.searched-active td {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searched-active .icon {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-open-repo {
|
||||||
|
background: #FDEFB9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-chooser-table td {
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user