You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
431 lines
12 KiB
TypeScript
431 lines
12 KiB
TypeScript
import React, { ChangeEvent, Component } from "react";
|
|
import { Input } from "antd";
|
|
import { Tree } from "shineout";
|
|
import { FolderOutlined, FileOutlined, FolderOpenOutlined } from "@ant-design/icons";
|
|
import * as fs from "fs";
|
|
import * as path from "path";
|
|
import { ipcRenderer } from "electron";
|
|
import MainEventType from "../common/MainEventType";
|
|
import Settings from "../main-process/Settings";
|
|
import * as Utils from "../common/Utils";
|
|
|
|
const { Search } = Input;
|
|
const DirectoryTree = Tree;
|
|
|
|
|
|
class FileDataNode {
|
|
name: string; //display name
|
|
desc: string; //describe
|
|
filepath: string; //file full path
|
|
isFolder: boolean;
|
|
|
|
settings: Settings;
|
|
|
|
parent: FileDataNode;
|
|
visible: boolean = true;
|
|
children: FileDataNode[];
|
|
|
|
public constructor(init?: Partial<FileDataNode>, settings?: Settings) {
|
|
Object.assign(this, init);
|
|
this.settings = settings;
|
|
}
|
|
|
|
get id() {
|
|
return this.filepath;
|
|
}
|
|
|
|
get text() {
|
|
if (!this.desc) {
|
|
return this.name;
|
|
}
|
|
return `${this.name}(${this.desc})`;
|
|
}
|
|
|
|
get path() {
|
|
return this.filepath;
|
|
}
|
|
|
|
public getRenderData() {
|
|
if (!this.visible) {
|
|
return null;
|
|
}
|
|
let ret = new FileDataNode({
|
|
name: this.name,
|
|
desc: this.isFolder ? undefined : this.settings.getTreeDesc(this.filepath),
|
|
filepath: this.filepath,
|
|
isFolder: this.isFolder,
|
|
children: new Array(),
|
|
}, this.settings);
|
|
|
|
for (const child of this.children) {
|
|
const data = child.getRenderData();
|
|
if (data) {
|
|
ret.children.push(data);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
loadChilds(recursive?: boolean) {
|
|
const folder = this.path;
|
|
if (folder == "" || !fs.existsSync(folder)) {
|
|
return [];
|
|
}
|
|
const files = fs.readdirSync(folder);
|
|
const list: FileDataNode[] = [];
|
|
files.forEach((filename) => {
|
|
const fullPath = path.join(folder, filename);
|
|
const stat = fs.statSync(fullPath);
|
|
const isFolder = stat.isDirectory();
|
|
const name = isFolder ? filename : filename.slice(0, -5);
|
|
const node = new FileDataNode({
|
|
name: name,
|
|
desc: isFolder ? undefined : this.settings.getTreeDesc(fullPath),
|
|
filepath: fullPath,
|
|
isFolder: isFolder,
|
|
parent: this,
|
|
}, this.settings);
|
|
|
|
(node.children =
|
|
isFolder && recursive ? (node.loadChilds(recursive) as FileDataNode[]) : []),
|
|
list.push(node);
|
|
});
|
|
list.sort((a, b) => {
|
|
const av = a.isFolder ? 100 : 0;
|
|
const bv = b.isFolder ? 100 : 0;
|
|
return bv - av;
|
|
});
|
|
return list;
|
|
}
|
|
|
|
addChild(filePath: string) {
|
|
const isDir = fs.statSync(filePath).isDirectory();
|
|
const newNode = new FileDataNode({
|
|
name: path.basename(filePath),
|
|
filepath: filePath,
|
|
isFolder: isDir,
|
|
parent: this,
|
|
children: [],
|
|
}, this.settings);
|
|
this.children = [newNode, ...this.children];
|
|
}
|
|
|
|
removeFromParent() {
|
|
const parent = this.parent;
|
|
if (parent) {
|
|
const index = parent.children.indexOf(this);
|
|
parent.children.splice(index, 1);
|
|
}
|
|
}
|
|
|
|
expandSelf(recursive: boolean = true) {
|
|
if (this.isFolder) {
|
|
this.children = this.loadChilds(recursive);
|
|
}
|
|
}
|
|
|
|
findChild(id: string): FileDataNode {
|
|
for (const child of this.children) {
|
|
if (child.id == id) {
|
|
return child;
|
|
} else {
|
|
const ret = child.findChild(id);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
getList(): FileDataNode[] {
|
|
let ret = new Array<FileDataNode>(this);
|
|
for (const child of this.children) {
|
|
const childList = child.getList();
|
|
ret.push(...childList);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
setVisible(keyWord: string) {
|
|
let ret = false;
|
|
for (let i = this.children.length - 1; i >= 0; i--) {
|
|
const child = this.children[i];
|
|
if (!child.isFolder) {
|
|
const keyStr = child.id;
|
|
child.visible = keyStr.includes(keyWord);
|
|
} else {
|
|
child.visible = child.setVisible(keyWord);
|
|
}
|
|
ret = child.visible || ret;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// name:string;
|
|
// path: string;
|
|
// isFolder : boolean;
|
|
// isEditing?: boolean;
|
|
// children?: NodeData[];
|
|
}
|
|
|
|
const NodeActions: { [x: string]: string } = {
|
|
["create"]: "New Tree",
|
|
["createFolder"]: "New Folder",
|
|
["rename"]: "Rename",
|
|
["delete"]: "Delete",
|
|
["reveal_in_explorer"]: "Reveal In File Explorer",
|
|
};
|
|
|
|
interface ExplorerNodeProps {
|
|
visible: boolean;
|
|
title: string;
|
|
selected: boolean;
|
|
expended: boolean;
|
|
isLeaf: boolean;
|
|
searchKey: string;
|
|
}
|
|
class ExplorerNode extends Component<ExplorerNodeProps> {
|
|
shouldComponentUpdate(nextProps: ExplorerNodeProps) {
|
|
return JSON.stringify(this.props) != JSON.stringify(nextProps);
|
|
}
|
|
|
|
render() {
|
|
const { title, visible, selected, expended, isLeaf, searchKey } = this.props;
|
|
if (!visible) {
|
|
return null;
|
|
}
|
|
|
|
const index = title.indexOf(searchKey);
|
|
const beforeStr = title.substr(0, index);
|
|
const afterStr = title.substr(index + searchKey.length);
|
|
|
|
return (
|
|
<div
|
|
className={selected ? "explorer-node-selected" : "explorer-node"}
|
|
>
|
|
{!isLeaf ? (expended ? <FolderOpenOutlined /> : <FolderOutlined />) : <FileOutlined />}
|
|
{isLeaf && index > -1 ? (
|
|
<span className="explorer-node-title">
|
|
{beforeStr}
|
|
<span className="explorer-node-search-value">{searchKey}</span>
|
|
{afterStr}
|
|
</span>
|
|
) : (
|
|
<span className="explorer-node-title">{title}</span>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
export interface ExplorerProps {
|
|
workdir: string;
|
|
onOpenTree: (path: string) => void;
|
|
onDeleteTree: (path: string) => void;
|
|
}
|
|
|
|
|
|
interface ExplorerState {
|
|
root: FileDataNode;
|
|
searchKey: string;
|
|
selectedKey: string;
|
|
defaultExpandedKeys: string[];
|
|
expandedKeys: string[];
|
|
autoExpandParent: boolean;
|
|
rightClickNode: {
|
|
pageX: number;
|
|
pageY: number;
|
|
key: string;
|
|
};
|
|
}
|
|
|
|
export default class Explorer extends Component<ExplorerProps> {
|
|
state: ExplorerState = {
|
|
root: this.getRootNode(this.props.workdir),
|
|
searchKey: "",
|
|
selectedKey: "",
|
|
rightClickNode: null,
|
|
expandedKeys: [],
|
|
defaultExpandedKeys: [],
|
|
autoExpandParent: true,
|
|
};
|
|
|
|
curWorkdir: string = "";
|
|
settings: Settings;
|
|
|
|
shouldComponentUpdate(nextProps: ExplorerProps) {
|
|
const shouldUpdate = this.curWorkdir != nextProps.workdir;
|
|
this.curWorkdir = nextProps.workdir;
|
|
return shouldUpdate;
|
|
}
|
|
|
|
componentDidMount() {
|
|
this.settings = Utils.getRemoteSettings();
|
|
ipcRenderer.on(MainEventType.CREATE_TREE, (event: any, path: string) => {
|
|
console.log("on Create tree", path);
|
|
this.props.onOpenTree(path);
|
|
this.state.root.expandSelf();
|
|
this.forceUpdate();
|
|
});
|
|
|
|
ipcRenderer.on(MainEventType.OPEN_DIR, (event: any, workdir: any, workspace: string) => {
|
|
console.log("prop on open workspace", workspace);
|
|
this.updateRoot();
|
|
this.forceUpdate();
|
|
});
|
|
|
|
this.updateRoot();
|
|
}
|
|
|
|
updateRoot() {
|
|
var workdir = this.props.workdir;
|
|
if (!workdir || workdir === "") {
|
|
return;
|
|
}
|
|
const root = this.getRootNode(workdir);
|
|
root.expandSelf()
|
|
this.setState({
|
|
root: root,
|
|
expandedKeys: [root.id],
|
|
defaultExpandedKeys: [root.id],
|
|
});
|
|
|
|
this.forceUpdate();
|
|
}
|
|
|
|
getRootNode(workdir: string) {
|
|
if (workdir && workdir !== "") {
|
|
return new FileDataNode({
|
|
name: path.basename(workdir),
|
|
filepath: workdir,
|
|
isFolder: true,
|
|
parent: null,
|
|
children: []
|
|
}, this.settings);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// renderContextMeun(selectedNode: FileDataNode) {
|
|
// const actions = Object.keys(NodeActions);
|
|
// return (
|
|
// <Menu>
|
|
// {actions.map((action) => {
|
|
// const displayName: string = NodeActions[action];
|
|
// return (
|
|
// <Menu.Item
|
|
// key={action}
|
|
// onClick={(info) => {
|
|
// //this.handleContextMenu({ node: selectedNode, action:{action} });
|
|
// }}
|
|
// >{displayName}
|
|
// </Menu.Item>
|
|
// );
|
|
// })}
|
|
// </Menu>
|
|
// );
|
|
// }
|
|
|
|
handleOnSearch(value: string) {
|
|
const expandedKeys = this.state.root
|
|
.getList()
|
|
.map((item) => {
|
|
if (item.isFolder) return null;
|
|
const title = item.text;
|
|
if (title.includes(value) && item.parent) {
|
|
return item.parent.id;
|
|
}
|
|
return null;
|
|
})
|
|
.filter((item, i, self) => item && self.indexOf(item) === i);
|
|
|
|
const root = this.state.root;
|
|
root.setVisible(value || "");
|
|
if (value && value.length > 0) {
|
|
this.setState({
|
|
root: root,
|
|
searchKey: value,
|
|
expandedKeys: expandedKeys,
|
|
autoExpandParent: true,
|
|
});
|
|
} else {
|
|
this.setState({
|
|
root: root,
|
|
searchKey: "",
|
|
expandedKeys: this.state.defaultExpandedKeys,
|
|
autoExpandParent: false,
|
|
});
|
|
}
|
|
|
|
this.forceUpdate();
|
|
}
|
|
|
|
selectNode(id: string) {
|
|
this.setState({
|
|
selectedKey: id
|
|
});
|
|
this.forceUpdate();
|
|
}
|
|
|
|
renderItem(node: FileDataNode) {
|
|
return (<ExplorerNode
|
|
visible={node.visible}
|
|
title={node.text}
|
|
selected={this.state.selectedKey == node.id}
|
|
expended={this.state.expandedKeys.includes(node.id)}
|
|
isLeaf={!node.isFolder}
|
|
searchKey={this.state.searchKey}
|
|
/>);
|
|
}
|
|
|
|
render() {
|
|
console.log("render Explorer");
|
|
const { onOpenTree, onDeleteTree, workdir } = this.props;
|
|
|
|
const root = this.state.root;
|
|
if (!workdir || workdir === "" || !root) {
|
|
return `请打开workspace.json文件`;
|
|
}
|
|
|
|
const nodes = [root.getRenderData()];
|
|
|
|
return (
|
|
<div>
|
|
<Search
|
|
allowClear
|
|
placeholder="Search"
|
|
// onChange={(e) => {
|
|
// const value = e.target.value?.toLowerCase();
|
|
// this.handleOnSearch(value);
|
|
// }}
|
|
onSearch={(value, event) => {
|
|
this.handleOnSearch(value?.toLowerCase());
|
|
}}
|
|
/>
|
|
|
|
<DirectoryTree
|
|
keygen="id"
|
|
data={nodes}
|
|
line={false}
|
|
doubleClickExpand
|
|
defaultExpanded={[root.id]}
|
|
expanded={this.state.expandedKeys}
|
|
value={[this.state.selectedKey]}
|
|
onExpand={(expanded) => {
|
|
this.setState({ expandedKeys: expanded })
|
|
this.forceUpdate();
|
|
}}
|
|
renderItem={this.renderItem.bind(this)}
|
|
onClick={(node: FileDataNode) => {
|
|
if (!node.isFolder) {
|
|
onOpenTree(node.path);
|
|
}
|
|
}}
|
|
></DirectoryTree>
|
|
</div>
|
|
);
|
|
}
|
|
}
|