diff --git a/src/components/FileExplorer.tsx b/src/components/FileExplorer.tsx
index eef0954..b030096 100644
--- a/src/components/FileExplorer.tsx
+++ b/src/components/FileExplorer.tsx
@@ -169,7 +169,7 @@ function VirtualNode({
node={node}
depth={depths.get(node) || 0}
focused={focusedNode === node}
- expanded={expandedNodes.has(node)}
+ expanded={expandedNodes.has(node.path)}
onClick={onNodeClick}
renderActions={renderActions}
regex={regex}
diff --git a/src/components/Node.tsx b/src/components/Node.tsx
index 2838aa6..58d9c89 100644
--- a/src/components/Node.tsx
+++ b/src/components/Node.tsx
@@ -59,7 +59,16 @@ export function Node({
>
-
+ {name.includes('/') ? (
+ name.split('/').map((chunk, index) => (
+
+ {index > 0 && '/'}
+
+
+ ))
+ ) : (
+
+ )}
{renderActions && {renderActions(node)}
}
diff --git a/src/driver/core/FileExplorer.ts b/src/driver/core/FileExplorer.ts
index 8eeb3a2..11578b5 100644
--- a/src/driver/core/FileExplorer.ts
+++ b/src/driver/core/FileExplorer.ts
@@ -62,11 +62,11 @@ export const init: BoundMethodCreator = dispatch => () =>
export const setUpTree: BoundMethodCreator<[
Pick & Pick,
-]> = dispatch => async ({ treeRoot, metaData, compressSingletonFolder, accessToken }) => {
+]> = dispatch => async ({ treeRoot, metaData, compressSingletonFolder }) => {
if (!treeRoot) return
dispatch.call(setStateText, 'Rendering File List...')
- visibleNodesGenerator = new VisibleNodesGenerator(treeRoot as TreeNode, {
+ visibleNodesGenerator = new VisibleNodesGenerator(treeRoot, {
compress: compressSingletonFolder,
})
@@ -123,7 +123,7 @@ export const handleKeyDown: BoundMethodCreator<[React.KeyboardEvent]> = dispatch
break
case 'ArrowLeft':
- if (expandedNodes.has(focusedNode)) {
+ if (expandedNodes.has(focusedNode.path)) {
dispatch.call(setExpand, focusedNode, false)
} else {
// go forward to the start of the list, find the closest node with lower depth
@@ -138,7 +138,7 @@ export const handleKeyDown: BoundMethodCreator<[React.KeyboardEvent]> = dispatch
case 'ArrowRight':
// expand node or focus on first content node or redirect to file page
if (focusedNode.type === 'tree') {
- if (expandedNodes.has(focusedNode)) {
+ if (expandedNodes.has(focusedNode.path)) {
const nextNode = nodes[focusedNodeIndex + 1]
const d1 = depths.get(nextNode)
const d2 = depths.get(focusedNode)
diff --git a/src/utils/VisibleNodesGenerator.ts b/src/utils/VisibleNodesGenerator.ts
index 4c32730..9727b36 100644
--- a/src/utils/VisibleNodesGenerator.ts
+++ b/src/utils/VisibleNodesGenerator.ts
@@ -16,7 +16,7 @@ import { findNode } from './general'
* | | | searchedNodes + toggleNode -> expandedNodes
* |2 search key | when search | onSearch
* | | | treeNodes + searchKey -> searchedNodes
- * |1 tree: { root <-> nodes } | when tree init or search | treeHelper.parse
+ * |1 tree: { root <-> nodes } | when tree init | treeHelper.parse
* | | tree data from api -> { root, nodes }
* v stable
*/
@@ -24,33 +24,42 @@ import { findNode } from './general'
function search(
root: TreeNode,
regexps: RegExp[],
- onMatch: (node: TreeNode) => void,
+ onChildMatch: (node: TreeNode) => void,
): TreeNode | null {
- if (!regexps.length) {
- return root
- }
-
// go traverse no matter root matches or not to make sure find all nodes matches
- const children =
- root.type === 'tree'
- ? root.contents
- ?.map(item => search(item, regexps, onMatch))
- .filter(function (result): result is TreeNode {
- return result !== null
- })
- : []
+ const contents = []
+ if (root.type === 'tree' && root.contents) {
+ let childMatch = false
+ for (const item of root.contents) {
+ if (isNodeMatch(item, regexps)) {
+ childMatch = true
+ break
+ }
+ }
+
+ for (const item of root.contents) {
+ const $item = search(item, regexps, onChildMatch)
+ if ($item) {
+ if ($item !== item) childMatch = true
+ contents.push($item)
+ }
+ }
+
+ if (childMatch) {
+ onChildMatch(root)
+ }
+
+ if (contents?.length) {
+ return {
+ ...root,
+ contents,
+ }
+ }
+ }
if (isNodeMatch(root, regexps)) {
- onMatch(root)
return root
}
-
- if (children?.length) {
- return {
- ...root,
- contents: children,
- }
- }
return null
}
@@ -61,17 +70,32 @@ function isNodeMatch(root: TreeNode, regexps: RegExp[]): boolean {
function compressTree(root: TreeNode, prefix: string[] = []): TreeNode {
if (root.contents) {
if (root.contents.length === 1) {
- const singleton = root.contents[0]
+ const [singleton] = root.contents
if (singleton.type === 'tree') {
return compressTree(singleton, [...prefix, root.name])
}
}
+
+ let compressed = false
+ const contents = []
+ for (const item of root.contents) {
+ const $item = compressTree(item)
+ if ($item !== item) compressed = true
+ contents.push($item)
+ }
+ if (compressed)
+ return {
+ ...root,
+ name: [...prefix, root.name].join('/'),
+ contents,
+ }
}
- return {
- ...root,
- name: [...prefix, root.name].join('/'),
- contents: root.contents ? root.contents.map(node => compressTree(node)) : undefined,
- }
+ return prefix.length
+ ? {
+ ...root,
+ name: [...prefix, root.name].join('/'),
+ }
+ : root
}
class L1 {
@@ -84,23 +108,21 @@ class L1 {
class L2 {
l1: L1
- couldCompress: boolean
- exactMatches: TreeNode[] = []
- finalRoot: TreeNode | null = null
+ compress: boolean
+ root: TreeNode | null = null
constructor(l1: L1, options: Options) {
this.l1 = l1
- this.couldCompress = Boolean(options.compress)
- this.search([])
+ this.compress = Boolean(options.compress)
}
- search = (regexps: RegExp[]) => {
- const shouldCompress = this.couldCompress && !regexps.length
- this.exactMatches.length = 0
- const rootNode = regexps.length
- ? search(this.l1.root, regexps, node => this.exactMatches.push(node))
- : this.l1.root
- this.finalRoot = rootNode && shouldCompress ? compressTree(rootNode) : rootNode
+ search = (regexps: RegExp[], onChildMatch: (node: TreeNode) => void) => {
+ const rootNode = regexps.length ? search(this.l1.root, regexps, onChildMatch) : this.l1.root
+
+ this.root =
+ rootNode && this.compress
+ ? { ...rootNode, contents: rootNode.contents?.map(node => compressTree(node)) }
+ : rootNode
}
}
@@ -108,34 +130,31 @@ class L3 {
l1: L1
l2: L2
- nodes: TreeNode[]
- expandedNodes: Set
- depths: Map
+ nodes: TreeNode[] = []
+ expandedNodes: Set = new Set()
+ depths: Map = new Map()
constructor(l1: L1, l2: L2) {
this.l1 = l1
this.l2 = l2
- this.expandedNodes = new Set()
- this.depths = new Map()
- this.nodes = []
}
toggleExpand = (node: TreeNode) => {
- this.setExpand(node, !this.expandedNodes.has(node))
+ this.setExpand(node, !this.expandedNodes.has(node.path))
}
setExpand = (node: TreeNode, expand: boolean) => {
if (expand && node.contents) {
// only node with contents is expandable
- this.expandedNodes.add(node)
+ this.expandedNodes.add(node.path)
} else {
- this.expandedNodes.delete(node)
+ this.expandedNodes.delete(node.path)
}
this.generateVisibleNodes()
}
expandTo = (path: string, expandAlongTheWay?: boolean) => {
- const rootNode = this.l2.finalRoot
+ const rootNode = this.l2.root
if (expandAlongTheWay && path.includes('/')) {
this.expandTo(path.slice(0, path.lastIndexOf('/')), true)
}
@@ -144,20 +163,26 @@ class L3 {
return node
}
+ search = (regexps: RegExp[]) => {
+ this.expandedNodes.clear()
+ this.l2.search(regexps, node => this.expandedNodes.add(node.path))
+ this.generateVisibleNodes()
+ }
+
generateVisibleNodes = () => {
this.depths.clear()
const nodes: TreeNode[] = []
- if (this.l2.finalRoot !== null) {
+ if (this.l2.root?.contents) {
const traverse = (root: TreeNode, depth = 0) => {
nodes.push(root)
this.depths.set(root, depth)
- if (this.expandedNodes.has(root) && root.type === 'tree' && root.contents?.length) {
+ if (this.expandedNodes.has(root.path) && root.type === 'tree' && root.contents?.length) {
for (const item of root.contents) {
traverse(item, depth + 1)
}
}
}
- for (const item of this.l2.finalRoot.contents || []) {
+ for (const item of this.l2.root.contents) {
traverse(item)
}
}
@@ -201,7 +226,7 @@ export class VisibleNodesGenerator {
l3: L3
l4: L4
- search: (regexps: RegExp[]) => void
+ search: L3['search']
setExpand: L3['setExpand']
toggleExpand: L3['toggleExpand']
expandTo: L3['expandTo']
@@ -214,11 +239,7 @@ export class VisibleNodesGenerator {
this.l4 = new L4(this.l1, this.l2, this.l3)
this.search = regexps => {
- this.l2.search(regexps)
- for (const node of this.l2.exactMatches) {
- this.expandTo(node.path, true)
- }
- this.l3.generateVisibleNodes()
+ this.l3.search(regexps)
this.l4.focusNode(null)
}
this.setExpand = (...args) => this.l3.setExpand(...args)
@@ -228,8 +249,7 @@ export class VisibleNodesGenerator {
}
init() {
- this.l2.search([])
- this.l3.generateVisibleNodes()
+ this.search([])
}
get visibleNodes() {