-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Change sidebar tree creation to use path instead of parentId. Also move
sidebar to web-ui
- Loading branch information
1 parent
6d93bf1
commit 68dc0f8
Showing
8 changed files
with
741 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
packages/web-ui/postcss.config.js → packages/web-ui/postcss.config.mjs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
module.exports = { | ||
export default { | ||
plugins: { | ||
tailwindcss: {}, | ||
autoprefixer: {}, | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
'use client' | ||
|
||
import { | ||
Node, | ||
SidebarDocument, | ||
useTree, | ||
} from '$ui/sections/DocumentsSidebar/useTree' | ||
|
||
function TreeNode({ node, level = 0 }: { node: Node; level?: number }) { | ||
return ( | ||
<div key={node.id}> | ||
<div className='flex flex-col gap-2' style={{ paddingLeft: level * 2 }}> | ||
{node.children.map((node, idx) => ( | ||
<TreeNode key={idx} node={node} level={level + 1} /> | ||
))} | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
export default function DocumentTree({ | ||
documents, | ||
}: { | ||
documents: SidebarDocument[] | ||
}) { | ||
const rootNode = useTree({ documents }) | ||
|
||
return <TreeNode node={rootNode} /> | ||
} |
89 changes: 89 additions & 0 deletions
89
packages/web-ui/src/sections/DocumentsSidebar/useTree/index.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import { renderHook } from '@testing-library/react' | ||
import { describe, expect, it } from 'vitest' | ||
|
||
const FAKE_RANDOM_ID = 'RANDOM_ID' | ||
|
||
let list = [ | ||
{ path: 'things/doc1', doumentUuid: '1' }, | ||
{ path: 'things/doc2', doumentUuid: '2' }, | ||
{ path: 'things/other-things/doc3', doumentUuid: '3' }, | ||
{ path: 'something-else/doc4', doumentUuid: '4' }, | ||
] | ||
|
||
describe('useTree', () => { | ||
it('should return a tree with children', async () => { | ||
const resultImport = await import('./index') | ||
const { useTree } = resultImport | ||
|
||
const { result } = renderHook(() => | ||
useTree({ | ||
documents: list, | ||
generateNodeId: ({ uuid }: { uuid?: string } = {}) => { | ||
if (uuid) return uuid | ||
|
||
return FAKE_RANDOM_ID | ||
}, | ||
}), | ||
) | ||
expect(result.current.toJSON()).toEqual({ | ||
id: FAKE_RANDOM_ID, | ||
doc: undefined, | ||
isRoot: true, | ||
name: 'root', | ||
children: [ | ||
{ | ||
id: FAKE_RANDOM_ID, | ||
doc: undefined, | ||
name: 'things', | ||
isRoot: false, | ||
children: [ | ||
{ | ||
id: '1', | ||
name: 'doc1', | ||
isRoot: false, | ||
doc: { path: 'things/doc1', doumentUuid: '1' }, | ||
children: [], | ||
}, | ||
{ | ||
id: '2', | ||
name: 'doc2', | ||
isRoot: false, | ||
doc: { path: 'things/doc2', doumentUuid: '2' }, | ||
children: [], | ||
}, | ||
{ | ||
id: FAKE_RANDOM_ID, | ||
isRoot: false, | ||
doc: undefined, | ||
name: 'other-things', | ||
children: [ | ||
{ | ||
id: '3', | ||
isRoot: false, | ||
name: 'doc3', | ||
doc: { path: 'things/other-things/doc3', doumentUuid: '3' }, | ||
children: [], | ||
}, | ||
], | ||
}, | ||
], | ||
}, | ||
{ | ||
id: FAKE_RANDOM_ID, | ||
isRoot: false, | ||
doc: undefined, | ||
name: 'something-else', | ||
children: [ | ||
{ | ||
id: '4', | ||
name: 'doc4', | ||
isRoot: false, | ||
doc: { path: 'something-else/doc4', doumentUuid: '4' }, | ||
children: [], | ||
}, | ||
], | ||
}, | ||
], | ||
}) | ||
}) | ||
}) |
118 changes: 118 additions & 0 deletions
118
packages/web-ui/src/sections/DocumentsSidebar/useTree/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import { useMemo } from 'react' | ||
|
||
export type SidebarDocument = { | ||
path: string | ||
doumentUuid: string | ||
} | ||
|
||
export class Node { | ||
public id: string | ||
public name: string | ||
public isRoot: boolean = false | ||
public children: Node[] = [] | ||
public doc?: SidebarDocument | ||
|
||
constructor({ | ||
id, | ||
doc, | ||
children = [], | ||
isRoot = false, | ||
name = '', | ||
}: { | ||
id: string | ||
doc?: SidebarDocument | ||
children?: Node[] | ||
isRoot?: boolean | ||
name?: string | ||
}) { | ||
this.id = id | ||
this.name = isRoot ? 'root' : name | ||
this.isRoot = isRoot | ||
this.children = children | ||
this.doc = doc | ||
} | ||
|
||
// Useful for testing | ||
toJSON(): object { | ||
return { | ||
id: this.id, | ||
name: this.name, | ||
isRoot: this.isRoot, | ||
children: this.children.map((child) => child.toJSON()), | ||
doc: this.doc, | ||
} | ||
} | ||
} | ||
|
||
function sortByPathDepth(a: SidebarDocument, b: SidebarDocument) { | ||
const depth1 = (a.path.match(/\//g) || []).length | ||
const depth2 = (b.path.match(/\//g) || []).length | ||
return depth1 - depth2 | ||
} | ||
|
||
export function defaultGenerateNodeUuid({ uuid }: { uuid?: string } = {}) { | ||
if (uuid) return uuid | ||
|
||
return Math.random().toString(36).substring(2, 15) | ||
} | ||
|
||
function buildTree({ | ||
root, | ||
nodeMap, | ||
documents, | ||
generateNodeId, | ||
}: { | ||
root: Node | ||
nodeMap: Map<string, Node> | ||
documents: SidebarDocument[] | ||
generateNodeId: typeof defaultGenerateNodeUuid | ||
}) { | ||
documents.forEach((doc) => { | ||
const segments = doc.path.split('/') | ||
let path = '' | ||
|
||
segments.forEach((segment, index) => { | ||
const isFile = index === segments.length - 1 | ||
path = path ? `${path}/${segment}` : segment | ||
|
||
if (!nodeMap.has(path)) { | ||
const file = isFile ? doc : undefined | ||
const uuid = isFile ? doc.doumentUuid : undefined | ||
const node = new Node({ | ||
id: generateNodeId({ uuid }), | ||
doc: file, | ||
name: segment, | ||
}) | ||
nodeMap.set(path, node) | ||
|
||
const parentPath = path.split('/').slice(0, -1).join('/') | ||
|
||
// We force TypeScript to check that the parentPath is not empty | ||
// We pre-sorted documents by path depth, so we know | ||
// that the parent node exists | ||
const parent = nodeMap.get(parentPath)! | ||
parent.children.push(node) | ||
} | ||
}) | ||
}) | ||
|
||
return root | ||
} | ||
|
||
export function useTree({ | ||
documents, | ||
generateNodeId = defaultGenerateNodeUuid, | ||
}: { | ||
documents: SidebarDocument[] | ||
generateNodeId?: typeof defaultGenerateNodeUuid | ||
}) { | ||
return useMemo(() => { | ||
const root = new Node({ id: generateNodeId(), children: [], isRoot: true }) | ||
const nodeMap = new Map<string, Node>() | ||
nodeMap.set('', root) | ||
const sorted = documents.slice().sort(sortByPathDepth) | ||
|
||
const tree = buildTree({ root, nodeMap, documents: sorted, generateNodeId }) | ||
return tree | ||
}, [documents]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,3 @@ | ||
export * from './DocumentEditor' | ||
export * from './DocumentsSidebar' | ||
export { useTree } from './DocumentsSidebar/useTree' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { defineConfig } from 'vitest/config' | ||
import { resolve } from 'path' | ||
|
||
export default defineConfig({ | ||
resolve: { | ||
alias: { | ||
$ui: resolve(__dirname, './src'), | ||
}, | ||
}, | ||
test: { | ||
environment: 'jsdom', | ||
}, | ||
}) |
Oops, something went wrong.