Skip to content

Commit

Permalink
feat: sqlite 表格渲染
Browse files Browse the repository at this point in the history
  • Loading branch information
huayemao committed Jun 20, 2024
1 parent 98f576a commit bf91648
Show file tree
Hide file tree
Showing 8 changed files with 217 additions and 63 deletions.
8 changes: 5 additions & 3 deletions app/(content)/data-process/DBContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ export const DBContext = createContext<{
interface WorkerProxy {
new (): WorkerProxy;

removeDb(path: string): Promise<void>;

getDbs(): Promise<string[]>;

readWriteDB(fileURL: string, name: string): Promise<boolean>;
readWriteDB(name: string, fileURL?: string): Promise<boolean>;

execSql(sql: string): string;
execSql(sql: string): any[];

getStatus(): string;
}
Expand All @@ -33,7 +35,7 @@ export function DBContextProvider({ children }) {
const _dbWorker = new Worker(new URL("dbworker.js", import.meta.url));
const Myclass = Comlink.wrap<WorkerProxy>(_dbWorker);
new Myclass().then((_i) => {
setDbWorker(()=>_i);
setDbWorker(() => _i);
});
}
}, []);
Expand Down
37 changes: 21 additions & 16 deletions app/(content)/data-process/dbworker.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,28 @@ class WorkerProxy {
}
return dbs
};
async removeDb(path) {
const root = await navigator.storage.getDirectory();
await root.removeEntry(path)
};

async readWriteDB(fileURL, name) {

async readWriteDB(name, fileURL = null) {
let start = Date.now();
const root = await navigator.storage.getDirectory();
const dbHandle = await root.getFileHandle(name, { create: true });
// Get sync access handle
const accessHandle = await dbHandle.createSyncAccessHandle();
const response = await fetch(fileURL);
const content = await response.blob();
const bindata = await content.arrayBuffer();
const dataview = new DataView(bindata);
const writeBuffer = accessHandle.write(dataview);
accessHandle.flush();
// Always close FileSystemSyncAccessHandle if done.
accessHandle.close();

if (fileURL) {
const root = await navigator.storage.getDirectory();
const dbHandle = await root.getFileHandle(name, { create: true });
// Get sync access handle
const accessHandle = await dbHandle.createSyncAccessHandle();
const response = await fetch(fileURL);
const content = await response.blob();
const bindata = await content.arrayBuffer();
const dataview = new DataView(bindata);
const writeBuffer = accessHandle.write(dataview);
accessHandle.flush();
// Always close FileSystemSyncAccessHandle if done.
accessHandle.close();
}

const timeToFetchDump = Date.now() - start;
console.log(`Time to write db file ${timeToFetchDump}ms`)
Expand Down Expand Up @@ -67,8 +72,8 @@ class WorkerProxy {
rowMode: 'object',
resultRows: rows,
});
const results = JSON.stringify(rows, null, 2)
return results
// const results = JSON.stringify(rows, null, 2)
return rows
};
getStatus() {
return status;
Expand Down
197 changes: 155 additions & 42 deletions app/(content)/data-process/page.tsx
Original file line number Diff line number Diff line change
@@ -1,65 +1,178 @@
"use client";
import { useContext, useEffect, useState } from "react";
import { isValidURL } from "@/lib/utils";
import {
BaseButton,
BaseButtonClose,
BaseCard,
BaseInputFile,
BaseList,
BaseListItem,
BaseTextarea,
} from "@shuriken-ui/react";
import mime from "mime";
import { useContext, useEffect, useMemo, useState } from "react";
import { DBContext } from "./DBContext";

export default function Home() {
const {
state: { dbWorker },
} = useContext(DBContext);

const [lines, setLines] = useState("");
const [lines, setLines] = useState<any[]>([]);
const [error, setError] = useState("");
const [query, setQuery] = useState("");
const [dbs, setDbs] = useState<string[]>([]);

useEffect(() => {
if (dbWorker && query && query !== "") {
// console.log("Posting a message")

if (dbWorker && !!query) {
const res = dbWorker.execSql(query);
res.then(setLines).catch((e)=>setLines(e.message));
res.then(setLines).catch((e) => setError(e.message));
}
}, [dbWorker, query]);

useEffect(() => {
console.log(dbWorker)
dbWorker?.getDbs?.()?.then(setDbs);
dbWorker?.getDbs?.()?.then((dbs) => setDbs(dbs.map(decodeURIComponent)));
}, [dbWorker]);

const tableData = useMemo(() => {
const headers = lines.length ? Object.keys(lines[0]) : [];
const rows = lines;
return { headers, rows };
}, [lines]);

return (
<div>
<div>{dbs}</div>
<input
type="file"
id="fileInput"
onChange={(e) => {
const file = e.target.files?.[0];
if (file) {
const url = URL.createObjectURL(file);
console.log(file, url, dbWorker);
dbWorker?.readWriteDB(url, encodeURIComponent("测试数据库.db"));
}
}}
/>
<p id="fileUrl"></p>
<p>Try these two queries:</p>
<pre>
select * from sqlite_schema;
<br />
select count(*) from twl;
<br />
select * from twl limit 4;
<br />
</pre>
<br />
<textarea
rows={5}
cols={80}
onInput={(e) => {
// console.log((e.target).value)
setQuery((e.target as HTMLTextAreaElement).value);
}}
/>
<pre>{lines}</pre>
<div className="bg-white w-full p-8">
<div className="grid grid-cols-12 gap-6">
<BaseCard className="col-span-6 p-4">
<BaseList>
{dbs.map((e, i) => (
<BaseListItem
key={i}
title={e}
end={
<div className="flex items-center gap-4">
<BaseButton
onClick={() => {
dbWorker?.readWriteDB?.(encodeURIComponent(e));
}}
>
选择
</BaseButton>
<BaseButtonClose
onClick={(ev) => {
dbWorker?.removeDb?.(encodeURIComponent(e)).then(() => {
dbWorker
?.getDbs?.()
?.then((dbs) =>
setDbs(dbs.map(decodeURIComponent))
);
alert("已删除");
});
ev.stopPropagation();
}}
>
{/* 删除 */}
</BaseButtonClose>
</div>
}
></BaseListItem>
))}
</BaseList>
<BaseInputFile
id="fileInput"
onChange={(files) => {
const file = files?.[0];
if (file) {
const url = URL.createObjectURL(file);
dbWorker?.readWriteDB(encodeURIComponent("测试数据库.db"), url);
}
}}
/>
</BaseCard>
<BaseCard className="prose p-8 col-span-6">
<p>Try these two queries:</p>
<pre>
select * from sqlite_schema;
<br />
select * from 测试 limit 100;
<br />
</pre>
<br />
</BaseCard>
<BaseCard className="col-span-12">
<BaseTextarea
rows={5}
defaultValue={"select * from 测试 limit 100;"}
onChange={(v) => setQuery(v)}
// onInput={(e) => {
// const target = e.target as HTMLTextAreaElement;
// // console.log((e.target).value)
// setQuery(target.value);
// }}
/>
</BaseCard>
<BaseCard className="w-full overflow-x-auto col-span-12">
<table className="w-full overflow-x-auto whitespace-nowrap">
<colgroup>
{tableData.headers.map((e) => (
<col className="w-[20%]" key={e}></col>
))}
</colgroup>
<thead>
<tr className="bg-muted-50 dark:bg-muted-900">
{tableData.headers.map((e) => (
<th
className="bg-transparent py-4 px-3 text-start font-sans text-xs font-medium uppercase text-muted-400 tracking-wide "
key={e}
>
<div
data-nui-tooltip={e}
className="max-w-[8em] min-w-[4em] overflow-hidden overflow-ellipsis"
>
{e}
</div>
</th>
))}
</tr>
</thead>
<tbody>
{tableData.rows.map((e, i) => {
return (
<tr key={i}>
{Object.entries(e).map(([key, value], i) => {
if(isValidURL(value) && mime.getType(value as string)?.startsWith('image')){
return <td
data-nui-tooltip={value}
valign="middle"
className="border-t border-muted-200 py-4 px-3 font-sans font-normal dark:border-muted-800 text-sm text-muted-400 dark:text-muted-500"
key={key}
>
<img src={'/api/files?href='+value as string} crossOrigin="" className="w-36 max-w-[9rem] h-auto object-contain"/>
</td>
}
return (
<td
data-nui-tooltip={value}
valign="middle"
className="border-t border-muted-200 py-4 px-3 font-sans font-normal dark:border-muted-800 text-sm text-muted-400 dark:text-muted-500"
key={key}
>
<div className="max-w-[8em] min-w-[4em] overflow-hidden overflow-ellipsis">
{value as string}
</div>
</td>
);
})}
</tr>
);
})}
</tbody>
</table>
</BaseCard>
</div>

<pre className="w-full h-48 overflow-auto">{JSON.stringify(lines)}</pre>
<pre>{error}</pre>
</div>
);
}
15 changes: 13 additions & 2 deletions app/api/files/route.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
import { SITE_META } from "@/constants";
import prisma from "@/lib/prisma";

export async function GET(request: Request) {
const searchParams = new URL(request.url).searchParams;
const href = searchParams.get("href");
if (href) {
const res = await fetch(decodeURIComponent(href));
return new Response(await res.arrayBuffer());
}

return new Response(``, {
status: 400,
});
}

export async function POST(request: Request) {
try {
const text = (await request.formData()).get("file");
if (text instanceof File) {

const file = await prisma.file.create({
data: {
name: text.name,
Expand All @@ -19,7 +31,6 @@ export async function POST(request: Request) {
status: 200,
});
}

} catch (error) {
return new Response(`error: ${error.message}`, {
status: 400,
Expand Down
11 changes: 11 additions & 0 deletions lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,14 @@ export function getCurrentSegmentContent(el: HTMLElement) {
}
return str
}


export function isValidURL(string) {
try {
new URL(string);
return true;
} catch (e) {
return false;
}
}

6 changes: 6 additions & 0 deletions next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ const nextConfig = {
},
async rewrites() {
return {
beforeFiles: [
{
source: '/images/:path*',
destination: 'https://mvw-pro-ynhr.oss-cn-beijing.aliyuncs.com/:path*'
}
],
afterFiles: [
// {
// source: '/v1/:path*',
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@shuriken-ui/react": "^0.0.3",
"class-variance-authority": "^0.4.0",
"clsx": "^1.2.1",
"comlink": "^4.4.1",
"dayjs": "^1.11.10",
"localforage": "^1.10.0",
"lucide-react": "^0.303.0",
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -981,6 +981,11 @@ color@^4.2.3:
color-convert "^2.0.1"
color-string "^1.9.0"

comlink@^4.4.1:
version "4.4.1"
resolved "https://registry.npmmirror.com/comlink/-/comlink-4.4.1.tgz#e568b8e86410b809e8600eb2cf40c189371ef981"
integrity sha512-+1dlx0aY5Jo1vHy/tSsIGpSkN4tS9rZSW8FIhG0JH/crs9wwweswIo/POr451r7bZww3hFbPAKnTpimzL/mm4Q==

comma-separated-tokens@^2.0.0:
version "2.0.3"
resolved "https://registry.npmmirror.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee"
Expand Down

0 comments on commit bf91648

Please sign in to comment.