From 30f891a41e442a01757802c449ecf1afc1c8d391 Mon Sep 17 00:00:00 2001 From: "l1xnan@qq.com" Date: Mon, 16 Oct 2023 21:06:24 +0800 Subject: [PATCH] feat: show tables --- src-tauri/src/main.rs | 31 +++++++++++- src/Home.tsx | 62 ++++++++++++++++++------ src/components/FileTree.tsx | 23 +++++---- src/components/Table.tsx | 94 +++++++++++++++++++++++++++++++++++++ src/stores/store.ts | 10 ++++ 5 files changed, 193 insertions(+), 27 deletions(-) create mode 100644 src/components/Table.tsx diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 6c026dc..0962660 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -68,6 +68,30 @@ fn directory_tree(path: &str) -> FileNode { node } +fn _show_tables(path: String) -> Result { + let db = Connection::open(path)?; + let mut stmt = db.prepare("SHOW TABLES").unwrap(); + let frames = stmt.query_arrow(duckdb::params![]).unwrap(); + let schema = frames.get_schema(); + let records: Vec = frames.collect(); + let record_batch = arrow::compute::concat_batches(&schema, &records).unwrap(); + Ok(ValidationResponse { + row_count: records.len(), + total_count: records.len(), + preview: serialize_preview(&record_batch).unwrap(), + }) +} + +#[tauri::command] +async fn show_tables(path: String) -> ValidationResponse { + let res = _show_tables(path); + if let Ok(data) = res { + data + } else { + ValidationResponse::default() + } +} + #[tauri::command] fn get_folder_tree(name: &str) -> FileNode { directory_tree(name) @@ -91,7 +115,6 @@ fn _query(sql: String, limit: i32, offset: i32) -> anyhow::Result ( + +))(({}) => ({ + "& *": { + fontSize: 16, + height: 16, + width: 16, + }, +})); function Home() { const theme = useTheme(); @@ -49,8 +63,7 @@ function Home() { Database Explorer - { const res = await dialog.open({ directory: true, @@ -68,9 +81,8 @@ function Home() { }} > - - + { const res = await dialog.open({ directory: false, @@ -85,16 +97,9 @@ function Home() { openDirectory(res.path); } }} - sx={{ - "& *": { - fontSize: 16, - height: 16, - width: 16, - }, - }} > - + { @@ -107,6 +112,33 @@ function Home() { > + { + console.log(selectedPath); + if (selectedPath && selectedPath.endsWith(".duckdb")) { + const res = await showTables(selectedPath); + console.log(res); + setFolders( + folders?.map((item) => { + if (item.path == selectedPath) { + item.children = res.data.map(({ name }) => ({ + name, + path: name, + type: "table", + children: [], + })); + return item; + } + return item; + }) + ); + console.log(folders); + } + }} + > + + diff --git a/src/components/FileTree.tsx b/src/components/FileTree.tsx index 2a21023..0f9a6fa 100644 --- a/src/components/FileTree.tsx +++ b/src/components/FileTree.tsx @@ -4,18 +4,20 @@ import { Typography } from "@mui/material"; import { TreeItem } from "@mui/x-tree-view/TreeItem"; import { TreeView, TreeViewProps } from "@mui/x-tree-view/TreeView"; import { + IconDatabase, IconFile, - IconFileDatabase, IconFilePower, IconFileTypeCsv, IconFileTypeXls, IconFolder, IconFolderOpen, + IconTable, } from "@tabler/icons-react"; import * as React from "react"; export interface FileNode { name: string; + type?: string; path: string; is_dir: boolean; children: FileNode[]; @@ -25,19 +27,20 @@ export interface FileTreeProps extends TreeViewProps { data: FileNode; } -const getFileTypeIcon = (path: string) => { - const ext = path.split(".")[1]; - - if (ext == "duckdb") { - return ; +const getFileTypeIcon = (type: string) => { + if (type == "duckdb") { + return ; + } + if (type == "table") { + return ; } - if (ext == "csv") { + if (type == "csv") { return ; } - if (ext == "xlsx") { + if (type == "xlsx") { return ; } - if (ext == "parquet") { + if (type == "parquet") { return ; } return ; @@ -66,7 +69,7 @@ export default function FileTree({ } icon={ !node.is_dir ? ( - getFileTypeIcon(node.path) + getFileTypeIcon(node?.type ?? node.path.split(".")[1]) ) : expanded.includes(node.path) ? ( ) : ( diff --git a/src/components/Table.tsx b/src/components/Table.tsx new file mode 100644 index 0000000..5fca5a1 --- /dev/null +++ b/src/components/Table.tsx @@ -0,0 +1,94 @@ +import React from "react"; +import "./index.css"; + +import { + getCoreRowModel, + ColumnDef, + flexRender, + useReactTable, +} from "@tanstack/react-table"; +import { makeData, Person } from "./makeData"; + +const columns: ColumnDef[] = [ + { + header: "Name", + footer: (props) => props.column.id, + }, + { + header: "Info", + footer: (props) => props.column.id, + }, +]; + +function App() { + const data = React.useMemo(() => makeData(20), []); + + const table = useReactTable({ + data, + columns, + enableColumnResizing: true, + columnResizeMode: "onChange", + getCoreRowModel: getCoreRowModel(), + debugTable: true, + debugHeaders: true, + debugColumns: true, + }); + + return ( +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + ); + })} + + ))} + + + {table.getRowModel().rows.map((row) => { + return ( + + {row.getVisibleCells().map((cell) => { + return ( + + ); + })} + + ); + })} + +
+ {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + {header.column.getCanResize() && ( +
+ )} +
+ {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} +
+
+
+ ); +} diff --git a/src/stores/store.ts b/src/stores/store.ts index 2597a97..276cf7a 100644 --- a/src/stores/store.ts +++ b/src/stores/store.ts @@ -172,6 +172,16 @@ export function convert(preview: Array, totalCount: number) { }; } +export async function showTables(path?: string) { + const { preview, total_count }: ValidationResponse = await invoke( + "show_tables", + { + path, + } + ); + return convert(preview, total_count); +} + export async function query( sql: string, limit: number,