Skip to content

Commit

Permalink
feat: add save sqlite directly back (#118)
Browse files Browse the repository at this point in the history
* feat: add save sqlite directly back

* fixing the lint problem
  • Loading branch information
invisal authored Jul 23, 2024
1 parent d129ba5 commit 63ccb2c
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 82 deletions.
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
"@types/react-dom": "^18",
"@types/showdown": "^2.0.6",
"@types/sql.js": "^1.4.9",
"@types/wicg-file-system-access": "^2023.10.5",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"autoprefixer": "^10.4.19",
Expand Down
7 changes: 0 additions & 7 deletions src/app/playground/client/layout.tsx

This file was deleted.

180 changes: 113 additions & 67 deletions src/app/playground/client/page-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,24 @@ import { saveAs } from "file-saver";
import MyStudio from "@/components/my-studio";
import { Button } from "@/components/ui/button";
import SqljsDriver from "@/drivers/sqljs-driver";
import { LucideLoader } from "lucide-react";
import { LucideFile, LucideLoader } from "lucide-react";
import Script from "next/script";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { Database, SqlJsStatic } from "sql.js";
import ScreenDropZone from "@/components/screen-dropzone";
import { toast } from "sonner";

export default function PlaygroundEditorBody({
preloadDatabase,
}: {
preloadDatabase?: string | null;
}) {
const fileInput = useRef<HTMLInputElement>(null);
const [sqlInit, setSqlInit] = useState<SqlJsStatic>();
const [databaseLoading, setDatabaseLoading] = useState(!!preloadDatabase);
const [rawDb, setRawDb] = useState<Database>();
const [db, setDb] = useState<SqljsDriver>();
const [handler, setHandler] = useState<FileSystemFileHandle>();
const [fileName, setFilename] = useState("");

const onReady = useCallback(() => {
window
Expand Down Expand Up @@ -47,66 +49,118 @@ export default function PlaygroundEditorBody({
}
}, [sqlInit, preloadDatabase]);

const onFileChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
if (e.currentTarget.files && sqlInit) {
const file = e.currentTarget.files[0];
if (file) {
file.arrayBuffer().then((buffer) => {
const sqljsDatabase = new sqlInit.Database(new Uint8Array(buffer));
setRawDb(sqljsDatabase);
setDb(new SqljsDriver(sqljsDatabase));
});
}
}
},
[sqlInit]
);

const onFileDrop = useCallback(
(buffer: ArrayBuffer) => {
if (sqlInit) {
const sqljsDatabase = new sqlInit.Database(new Uint8Array(buffer));
setRawDb(sqljsDatabase);
setDb(new SqljsDriver(sqljsDatabase));
}
},
[sqlInit]
);
useEffect(() => {
if (handler && sqlInit) {
handler.getFile().then((file) => {
setFilename(file.name);
file.arrayBuffer().then((buffer) => {
const sqljsDatabase = new sqlInit.Database(new Uint8Array(buffer));
setRawDb(sqljsDatabase);
setDb(new SqljsDriver(sqljsDatabase));
});
});
}
}, [handler, sqlInit]);

const sidebarMenu = useMemo(() => {
return (
<div className="flex flex-row gap-2 px-2 pb-2">
<Button
className="flex-grow"
size="sm"
onClick={() => {
if (rawDb) {
saveAs(
new Blob([rawDb.export()], {
type: "application/x-sqlite3",
}),
"sqlite-dump.db"
);
}
}}
>
Save
</Button>
<Button
className="flex-grow"
size="sm"
onClick={() => {
if (fileInput.current) {
fileInput.current.click();
}
}}
>
Open
</Button>
<div>
{fileName && (
<div className="p-2 text-sm rounded m-2 bg-yellow-300 text-black flex gap-2">
<div className="flex justify-center items-center">
<LucideFile />
</div>
<div>
<div className="text-xs">Editing File</div>
<strong>{fileName}</strong>
</div>
</div>
)}
<div className="flex flex-row gap-2 px-2 pb-2">
<Button
className="flex-grow"
size="sm"
onClick={() => {
if (rawDb) {
if (handler) {
handler
.createWritable()
.then((writable) => {
writable.write(rawDb.export());
writable.close();
toast.success(
<div>
Successfully save <strong>{fileName}</strong>
</div>
);
db?.resetChange();
})
.catch(console.error);
} else {
saveAs(
new Blob([rawDb.export()], {
type: "application/x-sqlite3",
}),
"sqlite-dump.db"
);
}
}
}}
>
Save
</Button>
<Button
className="flex-grow"
size="sm"
onClick={() => {
window
.showOpenFilePicker({
types: [
{
description: "SQLite Files",
accept: {
"application/x-sqlite3": [
".db",
".sdb",
".sqlite",
".db3",
".s3db",
".sqlite3",
".sl3",
".db2",
".s2db",
".sqlite2",
".sl2",
],
},
},
],
})
.then(([fileHandler]) => {
setHandler(fileHandler);
});
}}
>
Open
</Button>
</div>
</div>
);
}, [rawDb, fileInput]);
}, [rawDb, handler, db, fileName]);

useEffect(() => {
if (handler && db) {
const onBeforeClose = (e: Event) => {
if (db.hasChanged()) {
e.preventDefault();
return "Are you sure you want to close without change?";
}
};

window.addEventListener("beforeunload", onBeforeClose);
return () => window.removeEventListener("beforeunload", onBeforeClose);
}
}, [db, handler]);

const dom = useMemo(() => {
if (databaseLoading) {
Expand Down Expand Up @@ -140,15 +194,7 @@ export default function PlaygroundEditorBody({
return (
<>
<Script src="/sqljs/sql-wasm.js" onReady={onReady} />
<input
type="file"
ref={fileInput}
className="hidden"
accept=".db,.sdb,.sqlite,.db3,.s3db,.sqlite3,.sl3,.db2,.s2db,.sqlite2,.sl2"
onChange={onFileChange}
multiple={false}
/>
<ScreenDropZone onFileDrop={onFileDrop} />
<ScreenDropZone onFileDrop={setHandler} />
{dom}
</>
);
Expand Down
23 changes: 15 additions & 8 deletions src/components/screen-dropzone.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,34 @@
import { useEffect } from "react";

interface Props {
onFileDrop: (buffer: ArrayBuffer) => void;
onFileDrop: (handler: FileSystemFileHandle) => void;
}

export default function ScreenDropZone({ onFileDrop }: Props) {
useEffect(() => {
const dropEventHandler = (e: DragEvent) => {
e.preventDefault();

if (!e.dataTransfer) return;
const file = e.dataTransfer.files[0];
const fileList = e.dataTransfer.items;

if (!fileList) return;
if (fileList.length === 0) return;

if (!file) return;
const reader = new FileReader();
reader.onload = (loadEvent) => {
onFileDrop(loadEvent.target?.result as ArrayBuffer);
};
(fileList[0] as any).getAsFileSystemHandle().then(onFileDrop);
};

reader.readAsArrayBuffer(file);
const dragEventHandler = (e: DragEvent) => {
e.preventDefault();
};

window.document.addEventListener("drop", dropEventHandler);
window.document.addEventListener("dragover", dragEventHandler);
window.document.addEventListener("dragenter", dragEventHandler);

return () => {
window.document.removeEventListener("dragenter", dragEventHandler);
window.document.removeEventListener("dragover", dragEventHandler);
window.document.removeEventListener("drop", dropEventHandler);
};
}, [onFileDrop]);
Expand Down
13 changes: 13 additions & 0 deletions src/drivers/sqljs-driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default class SqljsDriver extends SqliteLikeBaseDriver {
protected db: Database;
protected username?: string;
protected password?: string;
protected hasRowsChanged: boolean = false;

constructor(sqljs: Database) {
super();
Expand Down Expand Up @@ -72,6 +73,10 @@ export default class SqljsDriver extends SqliteLikeBaseDriver {
);
}

if (this.db.getRowsModified() > 0) {
this.hasRowsChanged = true;
}

return {
headers,
rows,
Expand All @@ -90,6 +95,14 @@ export default class SqljsDriver extends SqliteLikeBaseDriver {
};
}

resetChange() {
this.hasRowsChanged = false;
}

hasChanged() {
return this.hasRowsChanged;
}

close(): void {
// do nothing
}
Expand Down

0 comments on commit 63ccb2c

Please sign in to comment.