From 99bfe41553414b77d77ffa205bac5590f52a3af9 Mon Sep 17 00:00:00 2001 From: EnixCoda Date: Wed, 20 Feb 2019 17:56:56 +0800 Subject: [PATCH] refactor: visible nodes generator --- src/components/FileExplorer.tsx | 2 +- src/components/SideBar.tsx | 3 - src/driver/core/FileExplorer.ts | 27 ++-- src/utils/visibleNodesGenerator.ts | 210 +++++++++++++++++++---------- 4 files changed, 155 insertions(+), 87 deletions(-) diff --git a/src/components/FileExplorer.tsx b/src/components/FileExplorer.tsx index 674f999..b752827 100644 --- a/src/components/FileExplorer.tsx +++ b/src/components/FileExplorer.tsx @@ -5,9 +5,9 @@ import SearchBar from 'components/SearchBar' import Node from 'components/Node' import LoadingIndicator from 'components/LoadingIndicator' import cx from 'utils/cx' -import { VisibleNodes } from './SideBar' import { ConnectorState } from 'driver/core/FileExplorer' import { TreeData, MetaData } from 'utils/GitHubHelper' +import { VisibleNodes } from 'utils/VisibleNodesGenerator' export type Props = { treeData: TreeData diff --git a/src/components/SideBar.tsx b/src/components/SideBar.tsx index 2598d0a..2130b32 100644 --- a/src/components/SideBar.tsx +++ b/src/components/SideBar.tsx @@ -8,11 +8,8 @@ import SettingsBar from 'components/SettingsBar' import Portal from 'components/Portal' import Resizable from 'components/Resizable' import cx from 'utils/cx' -import VisibleNodesGenerator from 'utils/VisibleNodesGenerator' import { ConnectorState } from 'driver/core/SideBar' -export type VisibleNodes = VisibleNodesGenerator['visibleNodes'] - export type Props = {} class Gitako extends React.PureComponent { diff --git a/src/driver/core/FileExplorer.ts b/src/driver/core/FileExplorer.ts index 175d7e0..dacd793 100644 --- a/src/driver/core/FileExplorer.ts +++ b/src/driver/core/FileExplorer.ts @@ -2,12 +2,11 @@ import * as ini from 'ini' import DOMHelper from 'utils/DOMHelper' import treeParser from 'utils/treeParser' import URLHelper from 'utils/URLHelper' -import VisibleNodesGenerator, { TreeNode } from 'utils/VisibleNodesGenerator' +import VisibleNodesGenerator, { TreeNode, VisibleNodes } from 'utils/VisibleNodesGenerator' import GitHubHelper, { BlobData, TreeData, MetaData } from 'utils/GitHubHelper' import { MethodCreator } from 'driver/connect' import { Props } from 'components/FileExplorer' import Node from 'components/Node' -import { VisibleNodes } from 'components/SideBar' export type ConnectorState = { stateText: string @@ -42,7 +41,7 @@ function getVisibleParentNode(nodes: TreeNode[], focusedNode: TreeNode, depths: type Task = () => void const tasksAfterRender: (Task)[] = [] -const visibleNodesGenerator = new VisibleNodesGenerator() +let visibleNodesGenerator: VisibleNodesGenerator const init: MethodCreator = dispatch => () => dispatch.call(setStateText, 'Fetching File List...') @@ -72,18 +71,22 @@ const setUpTree: MethodCreator = dispatch => () => if (gitModules) { if (metaData.userName && metaData.repoName && gitModules.sha) { - const blobData = await GitHubHelper.getBlobData({ - userName: metaData.userName, - repoName: metaData.repoName, - fileSHA: gitModules.sha, - accessToken, - }) + const blobData = await GitHubHelper.getBlobData({ + userName: metaData.userName, + repoName: metaData.repoName, + fileSHA: gitModules.sha, + accessToken, + }) - resolveGitModules(root as TreeNode, blobData) + resolveGitModules(root as TreeNode, blobData) + } } - visibleNodesGenerator.setCompress(compressSingletonFolder) - await visibleNodesGenerator.plantTree(root as TreeNode) + visibleNodesGenerator = new VisibleNodesGenerator(root as TreeNode, { + compress: compressSingletonFolder, + }) + + await visibleNodesGenerator.init() tasksAfterRender.push(DOMHelper.focusSearchInput) dispatch.call(setStateText, '') diff --git a/src/utils/visibleNodesGenerator.ts b/src/utils/visibleNodesGenerator.ts index 4f92f34..71ad683 100644 --- a/src/utils/visibleNodesGenerator.ts +++ b/src/utils/visibleNodesGenerator.ts @@ -53,19 +53,19 @@ function search(treeNodes: TreeNode[], searchKey: string): TreeNode[] { .replace(/\//, '') .split('') .join('.*?'), - 'i' + 'i', ), ] - const searchResults = [].concat( - ...keyRegexes.map(keyRegex => treeNodes.filter(getFilterFunc(keyRegex))) + const searchResults = ([] as TreeNode[]).concat( + ...keyRegexes.map(keyRegex => treeNodes.filter(getFilterFunc(keyRegex))), ) return filterDuplications(searchResults) } -function debounce( - func: (...args: Arg[]) => T, - delay: number -): (...args: Arg[]) => Promise { +function debounce( + func: (...args: Args) => T, + delay: number, +): (...args: Args) => Promise { let timer: number return (...args) => new Promise(resolve => { @@ -77,11 +77,12 @@ function debounce( export const debouncedSearch = debounce(search, SEARCH_DELAY) function getNodes(root: TreeNode, nodes: TreeNode[] = []) { - if (!root.contents) return - root.contents.forEach(node => { - nodes.push(node) - getNodes(node, nodes) - }) + if (root.contents) { + root.contents.forEach(node => { + nodes.push(node) + getNodes(node, nodes) + }) + } return nodes } @@ -101,50 +102,64 @@ function compressTree(root: TreeNode, prefix: string[] = []): TreeNode { } } -export default class VisibleNodesGenerator { - // LEVEL 1 - root: TreeNode = null - nodes: TreeNode[] = null - compress = false - compressed = false - compressedRoot: TreeNode | undefined +class L1 { + root: TreeNode + nodes: TreeNode[] + compressedRoot: TreeNode - getRoot() { - return this.compress && this.compressed ? this.compressedRoot : this.root - } - - async plantTree(root: TreeNode) { + constructor(root: TreeNode) { this.root = root this.nodes = getNodes(root) this.compressedRoot = compressTree(root) + } +} - await this.search('') +class L2 { + l1: L1 + couldCompress: boolean + compressed: boolean + searchedNodes: TreeNode[] | null + + constructor(l1: L1, options: Options) { + this.l1 = l1 + this.couldCompress = Boolean(options.compress) + this.compressed = false + this.searchedNodes = null } - // LEVEL 2 - searchedNodes: TreeNode[] | null = null - async search(searchKey: string) { - this.compressed = !Boolean(searchKey) + search = async (searchKey: string) => { + this.compressed = !searchKey this.searchedNodes = searchKey - ? await debouncedSearch(this.nodes, searchKey) - : this.getRoot().contents - - this.expandedNodes.clear() - this.generateVisibleNodes() + ? await debouncedSearch(this.l1.nodes, searchKey) + : this.getRoot().contents || [] } - setCompress(compress: VisibleNodesGenerator['compress']) { - this.compress = compress + getRoot = () => { + return this.couldCompress && this.compressed ? this.l1.compressedRoot : this.l1.root + } +} + +class L3 { + l1: L1 + l2: L2 + + nodes: TreeNode[] + expandedNodes: Set + depths: Map + + constructor(l1: L1, l2: L2) { + this.l1 = l1 + this.l2 = l2 + this.expandedNodes = new Set() + this.depths = new Map() + this.nodes = [] } - // LEVEL 3 - expandedNodes = new Set() - depths = new Map() - toggleExpand(node: TreeNode) { + toggleExpand = (node: TreeNode) => { this.setExpand(node, !this.expandedNodes.has(node)) } - setExpand(node: TreeNode, expand: boolean) { + setExpand = (node: TreeNode, expand: boolean) => { if (expand && node.contents) { // only node with contents is expandable this.expandedNodes.add(node) @@ -154,9 +169,9 @@ export default class VisibleNodesGenerator { this.generateVisibleNodes() } - expandTo(path: string) { - let root = this.getRoot() - const findNode: (root: TreeNode) => TreeNode = root => { + expandTo = (path: string) => { + let root = this.l2.getRoot() + const findNode: (root: TreeNode) => TreeNode | undefined = root => { if (path.indexOf(root.path) === 0) { if (root.path === path) return root this.setExpand(root, true) @@ -172,26 +187,19 @@ export default class VisibleNodesGenerator { if (node) { this.setExpand(node, true) } - this.focusNode(node) return node } - visibleNodes: { - nodes: VisibleNodesGenerator['nodes'] - depths: VisibleNodesGenerator['depths'] - expandedNodes: VisibleNodesGenerator['expandedNodes'] - focusedNode: TreeNode - } = null - generateVisibleNodes() { - this.focusedNode = null + generateVisibleNodes = () => { + if (this.l2.searchedNodes === null) return this.depths.clear() const nodesSet = new Set() // prevent duplication const nodes = [], - stack = this.searchedNodes.slice().reverse() - let current, + stack: (TreeNode | null)[] = this.l2.searchedNodes.slice().reverse() + let current: TreeNode | null, depth = 0 while (stack.length) { - current = stack.pop() + current = stack.pop()! if (current === null) { depth -= 1 continue @@ -200,28 +208,88 @@ export default class VisibleNodesGenerator { nodes.push(current) nodesSet.add(current) this.depths.set(current, depth) - if (this.expandedNodes.has(current)) { + if (current && this.expandedNodes.has(current)) { stack.push(null) // use null as pop depth flag - stack.push(...current.contents.slice().reverse()) + if (current.contents) stack.push(...current.contents.slice().reverse()) depth += 1 } } - this.visibleNodes = { - nodes, - depths: this.depths, - expandedNodes: this.expandedNodes, - focusedNode: null, - } - this.focusNode(null) + this.nodes = nodes + } +} + +export type VisibleNodes = { + nodes: L1['nodes'] + depths: L3['depths'] + expandedNodes: L3['expandedNodes'] + focusedNode: L4['focusedNode'] +} + +class L4 { + l1: L1 + l2: L2 + l3: L3 + + focusedNode: TreeNode | null + + constructor(l1: L1, l2: L2, l3: L3) { + this.l1 = l1 + this.l2 = l2 + this.l3 = l3 + this.focusedNode = null + this.l3.generateVisibleNodes() } - // LEVEL 4 - focusedNode: TreeNode = null - focusNode(node: TreeNode) { + focusNode = (node: TreeNode | null) => { this.focusedNode = node - this.visibleNodes = { - ...this.visibleNodes, - focusedNode: node, + } +} + +type Options = { + compress?: boolean +} + +export default class VisibleNodesGenerator { + l1: L1 + l2: L2 + l3: L3 + l4: L4 + + search: L2['search'] + setExpand: L3['setExpand'] + toggleExpand: L3['toggleExpand'] + expandTo: L3['expandTo'] + focusNode: L4['focusNode'] + + constructor(root: TreeNode, options: Options) { + this.l1 = new L1(root) + this.l2 = new L2(this.l1, options) + this.l3 = new L3(this.l1, this.l2) + this.l4 = new L4(this.l1, this.l2, this.l3) + + this.search = async (...args) => { + const r = await this.l2.search(...args) + this.l3.generateVisibleNodes() + this.l4 = new L4(this.l1, this.l2, this.l3) + return r + } + this.setExpand = (...args) => this.l3.setExpand(...args) + this.toggleExpand = (...args) => this.l3.toggleExpand(...args) + this.expandTo = (...args) => this.l3.expandTo(...args) + this.focusNode = (...args) => this.l4.focusNode(...args) + } + + async init() { + await this.l2.search('') + this.l3.generateVisibleNodes() + } + + get visibleNodes() { + return { + nodes: this.l3.nodes, + depths: this.l3.depths, + expandedNodes: this.l3.expandedNodes, + focusedNode: this.l4.focusedNode, } } }