From 1cf691b6b566ce5fee9751529ee1849be0b3da59 Mon Sep 17 00:00:00 2001 From: Ziyang Hu Date: Wed, 23 Nov 2022 22:35:02 +0800 Subject: [PATCH] WASM UI including export/import --- cozo-lib-wasm/wasm-react-demo/package.json | 1 + cozo-lib-wasm/wasm-react-demo/src/App.js | 200 +++++++++++++++++++-- cozo-lib-wasm/wasm-react-demo/src/index.js | 6 +- cozo-lib-wasm/wasm-react-demo/yarn.lock | 5 + 4 files changed, 199 insertions(+), 13 deletions(-) diff --git a/cozo-lib-wasm/wasm-react-demo/package.json b/cozo-lib-wasm/wasm-react-demo/package.json index 005ab5d5..92d13652 100644 --- a/cozo-lib-wasm/wasm-react-demo/package.json +++ b/cozo-lib-wasm/wasm-react-demo/package.json @@ -10,6 +10,7 @@ "@testing-library/user-event": "^13.5.0", "ansicolor": "^1.1.100", "cozo-lib-wasm": "file:../pkg", + "file-saver": "^2.0.5", "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "5.0.1", diff --git a/cozo-lib-wasm/wasm-react-demo/src/App.js b/cozo-lib-wasm/wasm-react-demo/src/App.js index b62a66d3..cb86406b 100644 --- a/cozo-lib-wasm/wasm-react-demo/src/App.js +++ b/cozo-lib-wasm/wasm-react-demo/src/App.js @@ -7,15 +7,28 @@ */ import './App.css'; -import {Button, Intent, Tag, TextArea} from "@blueprintjs/core"; +import { + Button, + Checkbox, + Classes, + Dialog, + FileInput, + InputGroup, + Intent, + Tag, + TextArea, + Toaster +} from "@blueprintjs/core"; import {Cell, Column, Table2} from "@blueprintjs/table"; import React, {useEffect, useState} from "react"; import init, {CozoDb} from "cozo-lib-wasm"; import {parse} from "ansicolor"; +import {saveAs} from 'file-saver'; + function App() { const [db, setDb] = useState(null); - const [params, setParams] = useState('{\n\n}'); + const [params, setParams] = useState('{}'); const [showParams, setShowParams] = useState(false); const [queryText, setQueryText] = useState(''); const [inProgress, setInProgress] = useState(false); @@ -83,7 +96,7 @@ function App() { const t1 = performance.now(); const res = JSON.parse(res_str); if (res.ok) { - setStatusMessage(`finished with ${res.rows.length} rows in ${(t1 - t0).toFixed(2)}ms`); + setStatusMessage(`finished with ${res.rows.length} rows in ${(t1 - t0).toFixed(1)}ms`); if (!res.headers) { res.headers = []; if (res.rows.length) { @@ -140,20 +153,26 @@ function App() {
-
{statusMessage ? {statusMessage} : null}
+
+ + + +
{errorMessage.length ?
@@ -223,4 +242,165 @@ function App() {
     );
 }
 
+function ImportUrl({db}) {
+    const [open, setOpen] = useState(false);
+    const [url, setUrl] = useState('');
+
+    function handleClose() {
+        setOpen(false)
+    }
+
+    async function handleImport() {
+        try {
+            let resp = await fetch(url);
+            let content = await resp.text();
+            const res = JSON.parse(db.import_relations(content));
+            if (res.ok) {
+                handleClose()
+            } else {
+                AppToaster.show({message: res.message, intent: Intent.DANGER})
+            }
+        } catch (e) {
+            AppToaster.show({message: '' + e, intent: Intent.DANGER})
+        }
+
+
+    }
+
+    return <>
+        
+        
+            
+ setUrl(e.target.value)} + /> +
+ +
+
+ + +
+
+
+ +} + +function ImportFile({db}) { + const [open, setOpen] = useState(false); + const [file, setFile] = useState(null); + + function handleClose() { + setOpen(false) + } + + async function handleImport() { + try { + let content = await file.text(); + const res = JSON.parse(db.import_relations(content)); + if (res.ok) { + handleClose() + } else { + AppToaster.show({message: res.message, intent: Intent.DANGER}) + } + } catch (e) { + AppToaster.show({message: '' + e, intent: Intent.DANGER}) + } + + + } + + return <> + + +
+ { + setFile(e.target.files[0]); + }}/> +
+ +
+
+ + +
+
+
+ +} + +function Export({db}) { + const [rels, setRels] = useState([]); + const [selected, setSelected] = useState([]); + + function toggle() { + if (rels.length) { + setRels([]) + } else { + const relations = JSON.parse(db.run('::relations', '')).rows; + if (!relations.length) { + AppToaster.show({message: 'No stored relations to export', intent: Intent.WARNING}) + return; + } + + setSelected([]); + setRels(relations) + } + } + + function handleClose() { + setRels([]) + } + + function handleExport() { + const res = JSON.parse(db.export_relations(JSON.stringify({relations: selected}))); + if (res.ok) { + const blob = new Blob([JSON.stringify(res.data)], {type: "text/plain;charset=utf-8"}); + saveAs(blob, "export.json"); + handleClose() + } else { + AppToaster.show({message: res.message, intent: Intent.DANGER}) + } + } + + return <> + + +
+

Choose stored relations to export:

+ {rels.map((row) => { + if (selected.includes(row[0])) { + setSelected(selected.filter(n => n !== row[0])) + } else { + setSelected([...selected, row[0]]) + } + }}/>)} +
+
+
+ + +
+
+
+ +} + +const AppToaster = Toaster.create({position: 'top-right'}); + + export default App; diff --git a/cozo-lib-wasm/wasm-react-demo/src/index.js b/cozo-lib-wasm/wasm-react-demo/src/index.js index be95b604..5f3b3169 100644 --- a/cozo-lib-wasm/wasm-react-demo/src/index.js +++ b/cozo-lib-wasm/wasm-react-demo/src/index.js @@ -14,9 +14,9 @@ import reportWebVitals from './reportWebVitals'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( - - - + // + + // ); // If you want to start measuring performance in your app, pass a function diff --git a/cozo-lib-wasm/wasm-react-demo/yarn.lock b/cozo-lib-wasm/wasm-react-demo/yarn.lock index b0940f26..519e562a 100644 --- a/cozo-lib-wasm/wasm-react-demo/yarn.lock +++ b/cozo-lib-wasm/wasm-react-demo/yarn.lock @@ -4510,6 +4510,11 @@ file-loader@^6.2.0: loader-utils "^2.0.0" schema-utils "^3.0.0" +file-saver@^2.0.5: + version "2.0.5" + resolved "https://registry.npmmirror.com/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38" + integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA== + filelist@^1.0.1: version "1.0.4" resolved "https://registry.npmmirror.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5"