-
Notifications
You must be signed in to change notification settings - Fork 275
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
### What problem does this PR solve? feat: Render database tree #1841 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
- Loading branch information
Showing
15 changed files
with
481 additions
and
17 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
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,57 @@ | ||
import ColumnIcon from '/public/column.svg'; | ||
import IndexIcon from '/public/index.svg'; | ||
import SegmentIcon from '/public/segment.svg'; | ||
|
||
export enum Leaf { | ||
Columns = 'Columns', | ||
Index = 'Index', | ||
Segments = 'Segments' | ||
} | ||
|
||
export const LeafIconMap = { | ||
[Leaf.Columns]: <ColumnIcon className="h-4 w-4"></ColumnIcon>, | ||
[Leaf.Index]: <IndexIcon className="h-4 w-4"></IndexIcon>, | ||
[Leaf.Segments]: <SegmentIcon className="h-4 w-4"></SegmentIcon> | ||
}; | ||
|
||
export const initialData = [ | ||
{ | ||
name: '', | ||
id: 0, | ||
children: [1, 2, 3], | ||
parent: null | ||
}, | ||
{ | ||
name: 'Fruits', | ||
children: [], | ||
id: 1, | ||
parent: 0, | ||
isBranch: true | ||
}, | ||
{ | ||
name: 'Drinks', | ||
children: [4, 5], | ||
id: 2, | ||
parent: 0, | ||
isBranch: true | ||
}, | ||
{ | ||
name: 'Vegetables', | ||
children: [], | ||
id: 3, | ||
parent: 0, | ||
isBranch: true | ||
}, | ||
{ | ||
name: 'Pine colada', | ||
children: [], | ||
id: 4, | ||
parent: 2 | ||
}, | ||
{ | ||
name: 'Water', | ||
children: [], | ||
id: 5, | ||
parent: 2 | ||
} | ||
]; |
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,144 @@ | ||
import { useRouter } from 'next/navigation'; | ||
import { useCallback, useEffect, useRef, useState } from 'react'; | ||
import { listDatabase, listTable } from '../actions'; | ||
import { initialData } from './constants'; | ||
import { TreeNode, TreeParentId } from './interface'; | ||
import { buildLeafData, getParentIdById, updateTreeData } from './utils'; | ||
|
||
export const useHandleClickTreeName = () => { | ||
const router = useRouter(); | ||
|
||
const handleClickTreeName = useCallback( | ||
({ | ||
level, | ||
name, | ||
parent, | ||
data | ||
}: { | ||
level: number; | ||
name: string; | ||
parent: TreeParentId; | ||
data: TreeNode[]; | ||
}) => | ||
() => { | ||
if (level === 3) { | ||
const databaseId = getParentIdById(data, parent); | ||
if (databaseId) { | ||
router.push(`/database/${databaseId}/table/${parent}?tab=${name}`); | ||
} | ||
} | ||
}, | ||
[] | ||
); | ||
|
||
return { handleClickTreeName }; | ||
}; | ||
|
||
export const useBuildTreeData = () => { | ||
const loadedAlertElement = useRef(null); | ||
const [data, setData] = useState<TreeNode[]>(initialData); | ||
const [nodesAlreadyLoaded, setNodesAlreadyLoaded] = useState<any[]>([]); | ||
|
||
const fetchDatabases = useCallback(async () => { | ||
const ret = await listDatabase(); | ||
if (ret.databases.length > 0) { | ||
setData((value) => | ||
updateTreeData( | ||
value, | ||
0, | ||
ret.databases.map((x: string) => ({ | ||
name: x, | ||
children: [], | ||
id: x, | ||
parent: 0, | ||
isBranch: true | ||
})) | ||
) | ||
); | ||
} | ||
}, []); | ||
|
||
const fetchTables = useCallback(async (databaseName: string) => { | ||
const ret = await listTable(databaseName); | ||
if (ret?.tables?.length > 0) { | ||
setData((value) => { | ||
const tablePropertyList: TreeNode[] = []; | ||
const tableList = ret.tables.map((x: string) => { | ||
const leafs = buildLeafData(x); | ||
tablePropertyList.push(...leafs); | ||
|
||
return { | ||
name: x, | ||
children: leafs.map((x) => x.id), | ||
id: x, | ||
parent: databaseName, | ||
isBranch: true | ||
}; | ||
}); | ||
|
||
return [ | ||
...updateTreeData(value, databaseName, tableList), | ||
...tablePropertyList | ||
]; | ||
}); | ||
} | ||
}, []); | ||
|
||
useEffect(() => { | ||
fetchDatabases(); | ||
}, [fetchDatabases]); | ||
|
||
const onLoadData = async ({ element }: { element: TreeNode }) => { | ||
if (element.children.length > 0) { | ||
return; | ||
} | ||
|
||
await fetchTables(element.id as string); | ||
|
||
return undefined; | ||
// return new Promise((resolve) => { | ||
// setTimeout(() => { | ||
// setData((value) => | ||
// updateTreeData(value, element.id, [ | ||
// { | ||
// name: `Child Node ${value.length}`, | ||
// children: [], | ||
// id: value.length, | ||
// parent: element.id, | ||
// isBranch: true | ||
// }, | ||
// { | ||
// name: 'Another child Node', | ||
// children: [], | ||
// id: value.length + 1, | ||
// parent: element.id | ||
// } | ||
// ]) | ||
// ); | ||
// resolve(undefined); | ||
// }, 1000); | ||
// }); | ||
}; | ||
|
||
const wrappedOnLoadData = async (props: any) => { | ||
const nodeHasNoChildData = props.element.children.length === 0; | ||
const nodeHasAlreadyBeenLoaded = nodesAlreadyLoaded.find( | ||
(e) => e.id === props.element.id | ||
); | ||
|
||
await onLoadData(props); | ||
|
||
if (nodeHasNoChildData && !nodeHasAlreadyBeenLoaded) { | ||
const el: any = loadedAlertElement.current; | ||
setNodesAlreadyLoaded([...nodesAlreadyLoaded, props.element]); | ||
el && (el.innerHTML = `${props.element.name} loaded`); | ||
|
||
// Clearing aria-live region so loaded node alerts no longer appear in DOM | ||
setTimeout(() => { | ||
el && (el.innerHTML = ''); | ||
}, 5000); | ||
} | ||
}; | ||
|
||
return { wrappedOnLoadData, data, loadedAlertElement }; | ||
}; |
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,9 @@ | ||
export type TreeParentId = string | number | null; | ||
|
||
export interface TreeNode { | ||
name: string; | ||
id: string | number; | ||
children: Array<string | number>; | ||
parent: TreeParentId; | ||
isBranch?: boolean; | ||
} |
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
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,3 +1,3 @@ | ||
export default async function DatabasePage() { | ||
return <div>DatabasePage</div>; | ||
return <div className="w-1/4 overflow-auto">DatabasePage</div>; | ||
} |
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,47 @@ | ||
@keyframes spinner { | ||
0% { | ||
transform: rotate(0deg); | ||
} | ||
100% { | ||
transform: rotate(360deg); | ||
} | ||
} | ||
|
||
.loading-icon { | ||
animation: spinner 1.5s linear infinite; | ||
margin-left: 5px; | ||
} | ||
|
||
.visually-hidden { | ||
position: absolute; | ||
clip-path: circle(0); | ||
border: 0; | ||
height: 1px; | ||
margin: -1px; | ||
overflow: hidden; | ||
padding: 0; | ||
width: 1px; | ||
white-space: nowrap; | ||
} | ||
|
||
.tree-node { | ||
display: flex; | ||
align-items: center; | ||
cursor: pointer; | ||
} | ||
|
||
.tree-node:hover { | ||
background: rgba(255, 255, 255, 0.1); | ||
} | ||
|
||
.tree-node--focused { | ||
background-color: #d7d7d7; | ||
} | ||
|
||
.arrow--open { | ||
transform: rotate(90deg); | ||
} | ||
|
||
.name { | ||
margin-left: 6px; | ||
} |
Oops, something went wrong.