Skip to content

Commit ba4a3f5

Browse files
committed
feat: schema tree
1 parent e5b8149 commit ba4a3f5

10 files changed

+253
-115
lines changed

package-lock.json

+15
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"@tauri-apps/api": "^2.2.0",
2525
"d3": "^7.9.0",
2626
"dayjs": "^1.11.13",
27+
"lodash": "^4.17.21",
2728
"monaco-editor": "^0.52.2",
2829
"normalize.css": "^8.0.1",
2930
"p-debounce": "^4.0.0",
@@ -35,6 +36,7 @@
3536
"@sveltejs/vite-plugin-svelte": "^5.0.3",
3637
"@tauri-apps/cli": "^2.2.2",
3738
"@types/d3": "^7.4.3",
39+
"@types/lodash": "^4.17.15",
3840
"@types/node": "^22.10.5",
3941
"prettier": "^3.4.2",
4042
"prettier-plugin-svelte": "^3.3.2",

src-tauri/tauri.conf.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@
3737
"windows": [
3838
{
3939
"title": "agx",
40-
"width": 1024,
41-
"height": 768,
40+
"width": 1250,
41+
"height": 900,
4242
"theme": "Dark",
4343
"useHttpsScheme": true
4444
}
+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<script lang="ts">
2+
let { columns } = $props();
3+
</script>
4+
5+
<ul>
6+
{#each columns ?? [] as column}
7+
<li>
8+
<span>{column.name}</span>
9+
<span>{column.type.replace(/Nullable\((.*)\)/i, '$1')}</span>
10+
</li>
11+
{/each}
12+
</ul>
13+
14+
<style>
15+
ul {
16+
margin: 0;
17+
list-style: none;
18+
padding: 0;
19+
font-family: monospace;
20+
padding-bottom: 3px;
21+
padding-left: 5px;
22+
color: lightgray;
23+
}
24+
25+
li {
26+
display: flex;
27+
justify-content: space-between;
28+
white-space: nowrap;
29+
}
30+
31+
li span:first-child {
32+
text-overflow: ellipsis;
33+
overflow: hidden;
34+
white-space: nowrap;
35+
max-width: 50%;
36+
}
37+
38+
li span:last-child {
39+
text-overflow: ellipsis;
40+
overflow: hidden;
41+
white-space: nowrap;
42+
max-width: 50%;
43+
padding: 0 2px;
44+
}
45+
</style>

src/lib/components/Datasets/Datasets.svelte

+6-111
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
import type { Table } from '$lib/olap-engine';
33
44
import SearchBar from '$lib/components/SearchBar.svelte';
5-
import TableC from '$lib/icons/Table.svelte';
6-
import { filter } from './utils';
5+
import { buildTree, filter } from './utils';
6+
import Tree from './Tree.svelte';
77
88
type Props = {
99
tables?: Table[];
@@ -13,116 +13,11 @@
1313
1414
let search = $state<string>('');
1515
const filtered = $derived(filter(tables, search));
16+
const tree = $derived(buildTree(filtered));
1617
</script>
1718

1819
<SearchBar bind:value={search} />
19-
<article>
20-
{#each filtered as source, i (source.name)}
21-
<details open={i === 0}>
22-
<summary>
23-
<TableC size={15} stroke="#ccc" style="flex-shrink: 0;" />
24-
<h3>{source.name}</h3>
25-
</summary>
26-
<ul>
27-
{#each source.columns ?? [] as column}
28-
<li>
29-
<span>{column.name}</span>
30-
<span>{column.type.replace(/Nullable\((.*)\)/i, '$1')}</span>
31-
</li>
32-
{/each}
33-
</ul>
34-
</details>
35-
{/each}
36-
</article>
3720

38-
<style>
39-
article {
40-
font-family: monospace;
41-
font-size: 10px;
42-
flex: 1;
43-
overflow-y: auto;
44-
}
45-
46-
details {
47-
width: 100%;
48-
49-
details ~ & {
50-
margin-top: 12px;
51-
}
52-
}
53-
54-
summary {
55-
cursor: pointer;
56-
user-select: none;
57-
-webkit-user-select: none;
58-
display: flex;
59-
align-items: center;
60-
gap: 5px;
61-
62-
padding: 3px 0px;
63-
border-radius: 3px;
64-
65-
&::-webkit-details-marker {
66-
display: none;
67-
}
68-
69-
& > h3 {
70-
flex-shrink: 1;
71-
72-
text-overflow: ellipsis;
73-
white-space: nowrap;
74-
overflow: hidden;
75-
}
76-
}
77-
78-
h3 {
79-
color: hsl(0deg 0% 90%);
80-
font-size: 11px;
81-
font-weight: 500;
82-
margin: 0;
83-
}
84-
85-
ul {
86-
/* Reset */
87-
list-style: none;
88-
margin: 0;
89-
padding: 0;
90-
91-
/* Stack */
92-
display: flex;
93-
flex-direction: column;
94-
gap: 5px;
95-
96-
/* Custom style */
97-
padding: 12px 0;
98-
color: hsl(0deg 0% 75%);
99-
100-
& > li {
101-
display: flex;
102-
align-items: center;
103-
104-
& > span:first-of-type {
105-
flex-grow: 1;
106-
}
107-
108-
& > span:last-of-type {
109-
font-size: 8px;
110-
font-weight: 500;
111-
padding: 2px;
112-
border-radius: 3px;
113-
background-color: hsl(0deg 0% 10%);
114-
color: hsl(0deg 0% 75%);
115-
text-align: center;
116-
flex-shrink: 1;
117-
118-
text-overflow: ellipsis;
119-
white-space: nowrap;
120-
overflow: hidden;
121-
}
122-
}
123-
124-
&:last-of-type {
125-
padding-bottom: 0;
126-
}
127-
}
128-
</style>
21+
{#each tree as node}
22+
<Tree {node} />
23+
{/each}
+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<script lang="ts">
2+
import FolderOpen from '$lib/icons/FolderOpen.svelte';
3+
import Folder from '$lib/icons/Folder.svelte';
4+
import Table from '$lib/icons/Table.svelte';
5+
import Columns from './Columns.svelte';
6+
import Tree from './Tree.svelte';
7+
8+
let { node = {}, level = 0 } = $props();
9+
let expanded = $state(false);
10+
11+
function toggleExpanded() {
12+
expanded = !expanded;
13+
}
14+
</script>
15+
16+
<div class="node">
17+
<button
18+
type="button"
19+
class:folder={node.type === 'group'}
20+
onclick={toggleExpanded}
21+
style="cursor: pointer"
22+
data-level={level}
23+
>
24+
{#if node.type == 'group'}
25+
<span class="folder name">
26+
{#if expanded}
27+
<FolderOpen size={10} />
28+
{:else}
29+
<Folder size={10} />
30+
{/if}
31+
<span>{node.name}</span>
32+
</span>
33+
{/if}
34+
35+
{#if node.type == 'dataset'}
36+
<span class="name">
37+
<Table size={10} />
38+
<span>{node.name}</span>
39+
</span>
40+
{/if}
41+
</button>
42+
{#if node.type === 'group' && node.children && expanded}
43+
<div class="children">
44+
{#each node.children as child}
45+
<div class="tree-line">
46+
<Tree node={child} level={level + 1} />
47+
</div>
48+
{/each}
49+
</div>
50+
{/if}
51+
{#if node.type === 'dataset' && expanded}
52+
<div class="dataset">
53+
<Columns columns={node.columns} />
54+
</div>
55+
{/if}
56+
</div>
57+
58+
<style>
59+
* {
60+
font-family: monospace;
61+
font-size: 10px;
62+
color: lightgray;
63+
}
64+
65+
button {
66+
background: transparent;
67+
border: none;
68+
padding: 0;
69+
}
70+
71+
.folder.name {
72+
margin-bottom: 5px;
73+
}
74+
75+
.name {
76+
display: flex;
77+
align-items: center;
78+
}
79+
80+
.name span {
81+
margin-left: 3px;
82+
}
83+
84+
.tree-line {
85+
border-left: 1px solid #333;
86+
padding-left: 3px;
87+
}
88+
89+
.children {
90+
margin-left: 4px;
91+
}
92+
</style>

src/lib/components/Datasets/utils.ts

+38-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { type Table } from '$lib/olap-engine';
1+
import { type ColumnDescriptor, type Table } from '$lib/olap-engine';
2+
import _ from 'lodash';
23

34
export function filter(sources: Table[], search: string) {
45
if (!search) return sources;
@@ -10,3 +11,39 @@ export function filter(sources: Table[], search: string) {
1011
s.columns?.some((c) => c.name.toLowerCase().includes(search_))
1112
);
1213
}
14+
15+
type TreeNode = {
16+
name: string;
17+
type: 'group' | 'dataset';
18+
value?: string;
19+
columns?: ColumnDescriptor[];
20+
children?: TreeNode[];
21+
};
22+
23+
export function buildTree(tables: Table[]): TreeNode[] {
24+
const root: Record<string, any> = {};
25+
26+
tables.forEach(({ name, columns }) => {
27+
const parts = name.split('__');
28+
_.setWith(
29+
root,
30+
parts.join('.children.'),
31+
{ name: parts[parts.length - 1], type: 'dataset', value: name, columns },
32+
Object
33+
);
34+
});
35+
36+
const toArray = (obj: any): TreeNode[] =>
37+
_.map(
38+
obj,
39+
(value, key): TreeNode => ({
40+
name: key,
41+
type: value.children ? 'group' : 'dataset',
42+
value: value.value,
43+
columns: value.columns,
44+
children: value.children ? toArray(value.children) : undefined
45+
})
46+
);
47+
48+
return toArray(root);
49+
}

src/lib/components/SideBar.svelte

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
height: 100%;
6161
width: 100%;
6262
63-
padding: 14px 25px 0;
63+
padding: 14px 20px 0;
6464
background-color: hsl(0deg 0% 5%);
6565
display: flex;
6666
flex-direction: column;

0 commit comments

Comments
 (0)