Remove WASM demo from main repo

main
Ziyang Hu 1 year ago
parent 155c8ce298
commit 5b3533ef71

@ -1,23 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

@ -1,3 +0,0 @@
# Cozo WASM 示例
此示例的展示在 https://www.cozodb.org/wasm-demo/ 。源码供参考。

@ -1,4 +0,0 @@
# Cozo in WASM demo
This is the source code for the demo at https://www.cozodb.org/wasm-demo/.
It might be helpful if you want to integrate Cozo WASM in your own project.

@ -1,8 +0,0 @@
#!/usr/bin/env bash
rm -fr node_modules
rm -fr build
yarn
yarn build
rm -fr ~/cozodb_site/wasm-demo/
mv build ~/cozodb_site/wasm-demo

@ -1,44 +0,0 @@
{
"name": "cozo-web",
"version": "0.1.0",
"private": true,
"dependencies": {
"@blueprintjs/core": "^4.12.0",
"@blueprintjs/table": "^4.7.9",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@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",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"homepage": "."
}

@ -1,50 +0,0 @@
<!--
~ Copyright 2022, The Cozo Project Authors.
~
~ This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
~ If a copy of the MPL was not distributed with this file,
~ You can obtain one at https://mozilla.org/MPL/2.0/.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Cozo in WASM</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-S7J5ENQE6M"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-S7J5ENQE6M');
</script>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

@ -1,29 +0,0 @@
/*
* Copyright 2022, The Cozo Project Authors.
*
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/.
*/
@import "~normalize.css";
@import "~@blueprintjs/core/lib/css/blueprint.css";
@import "~@blueprintjs/icons/lib/css/blueprint-icons.css";
@import "~@blueprintjs/table/lib/css/table.css";
#error-message, #other-results {
padding: 10px;
overflow: scroll;
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
font-size: 90%;
}
#query-box, #params-box {
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
font-size: 100%;
min-height: 200px;
max-height: 40vh;
min-width: 200px;
}
#welcome {
padding: 15px;
}

@ -1,422 +0,0 @@
/*
* Copyright 2022, The Cozo Project Authors.
*
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import {
Button,
Checkbox,
Classes,
Colors,
Dialog,
FileInput,
InputGroup,
Intent,
Tag,
TextArea,
Toaster
} from "@blueprintjs/core";
import { Cell, Column, Table2 } from "@blueprintjs/table";
import { parse } from "ansicolor";
import init, { CozoDb } from "cozo-lib-wasm";
import { saveAs } from 'file-saver';
import React, { useEffect, useState } from "react";
import './App.css';
import { useBlueprintThemeClassName, usePreferredColorScheme } from './hooks/use-color-scheme';
function App() {
const [db, setDb] = useState(null);
const [params, setParams] = useState('{}');
const [showParams, setShowParams] = useState(false);
const [queryText, setQueryText] = useState('');
const [inProgress, setInProgress] = useState(false);
const [statusMessage, setStatusMessage] = useState('');
const [errorMessage, setErrorMessage] = useState([]);
const [queryResults, setQueryResults] = useState(null);
const [queryId, setQueryId] = useState(0);
useEffect(() => {
init().then(() => {
let db = CozoDb.new();
setDb(db);
})
}, []);
const colorScheme = usePreferredColorScheme();
useBlueprintThemeClassName(colorScheme);
const renderCell = (colIdx) => (rowIdx) => <Cell>
{displayValue(queryResults.rows[rowIdx][colIdx])}
</Cell>
function handleKeyDown(e) {
if (e.key === 'Enter' && e.shiftKey) {
e.preventDefault();
e.stopPropagation();
handleQuery();
}
if (e.key === 'Tab' && !e.shiftKey) {
e.preventDefault();
e.stopPropagation();
typeInTextarea(' ');
}
}
function typeInTextarea(newText, el = document.activeElement) {
const [start, end] = [el.selectionStart, el.selectionEnd];
el.setRangeText(newText, start, end, 'end');
}
function displayValue(v) {
if (typeof v === 'string') {
return v
} else {
return <span style={{color: colorScheme === "light" ? Colors.BLUE2 : Colors.BLUE5}}>{JSON.stringify(v)}</span>
}
}
function handleQuery() {
if (!db || inProgress) {
setInProgress(false);
setErrorMessage([]);
setStatusMessage(['database not ready']);
setQueryResults(null);
return;
}
setQueryId(queryId + 1);
const query = queryText.trim();
if (query) {
setInProgress(true);
setErrorMessage([]);
setStatusMessage('');
setQueryResults(null);
requestAnimationFrame(() => {
setTimeout(() => {
try {
const t0 = performance.now();
const res_str = db.run(query, params);
const t1 = performance.now();
const res = JSON.parse(res_str);
if (res.ok) {
setStatusMessage(`finished with ${res.rows.length} rows in ${(t1 - t0).toFixed(1)}ms`);
if (!res.headers) {
res.headers = [];
if (res.rows.length) {
for (let i = 0; i < res.rows[0].length; i++) {
res.headers.push('' + i);
}
}
}
} else {
console.error('Query failed', res);
setStatusMessage(`finished with errors`);
if (res.display) {
const messages = parse(res.display);
setErrorMessage(messages.spans);
} else {
setErrorMessage([res.message]);
}
}
setQueryResults(res);
} catch (e) {
setStatusMessage(`query failed`);
setErrorMessage(['' + e]);
} finally {
setInProgress(false);
}
}, 0)
})
}
}
return (
<div style={{width: "100vw", height: "100vh", display: 'flex', flexDirection: 'column'}}>
<div style={{padding: 10}}>
<div style={{display: 'flex'}}>
<TextArea
autoFocus
placeholder="Type query, SHIFT + Enter to run"
id="query-box"
className="bp4-fill"
growVertically={true}
large={true}
intent={Intent.PRIMARY}
onChange={e => setQueryText(e.target.value)}
onKeyDown={handleKeyDown}
spellCheck="false"
value={queryText}
/>
{showParams && <TextArea
id="params-box"
style={{marginLeft: 5}}
placeholder="Your params (a JSON map)"
large={true}
onChange={e => setParams(e.target.value)}
onKeyDown={handleKeyDown}
spellCheck="false"
value={params}
/>}
</div>
<div/>
<div style={{paddingTop: 10, display: 'flex', flexDirection: 'row'}}>
<Button
icon="play"
text={db ? (inProgress ? "Query is running" : "Run script") : "Loading WASM ..."}
onClick={() => handleQuery()}
disabled={!db || inProgress}
intent={Intent.PRIMARY}
/>
&nbsp;
<div style={{marginLeft: 10, marginTop: 5}}>
{statusMessage ? <Tag intent={errorMessage.length ? Intent.DANGER : Intent.SUCCESS} minimal>
{statusMessage}
</Tag> : null}
</div>
<div style={{flex: 1}}/>
<Export db={db}/>
<ImportUrl db={db}/>
<ImportFile db={db}/>
<Button icon="properties" style={{marginLeft: 5}} onClick={() => {
setShowParams(!showParams)
}}>Params</Button>
</div>
</div>
{errorMessage.length ? <pre id="error-message">
{errorMessage.map((item, id) => {
if (typeof item === 'string') {
return <span key={id}>{item}</span>
} else {
let styles = {};
if (item.css) {
for (let pair of item.css.split(';')) {
pair = pair.trim();
if (pair) {
const [k, v] = pair.split(':');
if (k.trim() === 'font-weight') {
styles['fontWeight'] = v.trim()
} else {
styles[k.trim()] = v.trim();
}
}
}
}
return <span key={id} style={styles}>{item.text}</span>
}
})}
</pre> : null}
{queryResults ? (queryResults.rows && queryResults.headers ?
<Table2
cellRendererDependencies={[renderCell, ...queryResults.rows]}
numRows={queryResults.rows.length}
>
{queryResults.headers.map((n, idx) => <Column
name={n}
key={idx}
cellRenderer={renderCell(idx)}
/>)}
</Table2> : null) : null}
{!(queryResults || errorMessage.length || inProgress) && <div id="welcome">
<p>
This is the demo page for Cozo running in your browser as
a <a href="https://webassembly.org/">Web assembly</a> module.
</p>
<p>
All computation is done within your browser. There is no backend, nor any outgoing requests.
</p>
<p>
Please refer to the <a href="https://www.cozodb.org">project homepage</a> for
more information about the Cozo database.
</p>
<h2>Not sure what to run?</h2>
<p>
<a onClick={() => {
setQueryText(`parent[] <- [['abraham', 'isaac'],
['isaac', 'jakob'],
['jakob', 'joseph']]
grandparent[A, C] := parent[A, B], parent[B, C]
great_grandparent[A, D] := parent[A, B], parent[B, C], parent[C, D]
# This is the output query
?[who] := great_grandparent['abraham', who] # => 'joseph'
# Any position in the relation may be queried
# ?[who] := great_grandparent[who, 'joseph'] # => 'abraham'`)
}}>Here</a> is a classical recursive example.
</p>
<p>
The <a href="https://docs.cozodb.org/en/latest/tutorial.html">tutorial</a> contains many more examples.
</p>
</div>}
</div>
);
}
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) {
AppToaster.show({message: "Imported", intent: Intent.SUCCESS})
handleClose()
} else {
AppToaster.show({message: res.message, intent: Intent.DANGER})
}
} catch (e) {
AppToaster.show({message: '' + e, intent: Intent.DANGER})
}
}
return <>
<Button icon="import" style={{marginLeft: 5}} onClick={() => {
setUrl('');
setOpen(true)
}}>
URL
</Button>
<Dialog isOpen={open} title="Import data from URL" onClose={handleClose}>
<div className={Classes.DIALOG_BODY}>
<InputGroup
fill
placeholder="Enter the file URL"
value={url}
onChange={e => setUrl(e.target.value)}
/>
</div>
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button onClick={handleClose}>Cancel</Button>
<Button intent={Intent.PRIMARY} disabled={!url} onClick={handleImport}>Import</Button>
</div>
</div>
</Dialog>
</>
}
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) {
AppToaster.show({message: "Imported", intent: Intent.SUCCESS})
handleClose()
} else {
AppToaster.show({message: res.message, intent: Intent.DANGER})
}
} catch (e) {
AppToaster.show({message: '' + e, intent: Intent.DANGER})
}
}
return <>
<Button icon="import" style={{marginLeft: 5}} onClick={() => setOpen(true)}>
File
</Button>
<Dialog isOpen={open} title="Import data from local file" onClose={handleClose}>
<div className={Classes.DIALOG_BODY}>
<FileInput fill text={(file && file.name) || 'Choose file ...'} onInputChange={(e) => {
setFile(e.target.files[0]);
}}/>
</div>
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button onClick={handleClose}>Cancel</Button>
<Button intent={Intent.PRIMARY} disabled={!file} onClick={handleImport}>Import</Button>
</div>
</div>
</Dialog>
</>
}
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");
AppToaster.show({message: "Exported", intent: Intent.SUCCESS})
handleClose()
} else {
AppToaster.show({message: res.message, intent: Intent.DANGER})
}
}
return <>
<Button icon="export" style={{marginLeft: 5}} onClick={toggle}>
Export
</Button>
<Dialog isOpen={!!rels.length} onClose={handleClose} title="Export">
<div className={Classes.DIALOG_BODY}>
<p>Choose stored relations to export:</p>
{rels.map((row) => <Checkbox
key={row[0]} label={row[0]} checked={selected.includes(row[0])}
onChange={() => {
if (selected.includes(row[0])) {
setSelected(selected.filter(n => n !== row[0]))
} else {
setSelected([...selected, row[0]])
}
}}/>)}
</div>
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button onClick={handleClose}>Cancel</Button>
<Button intent={Intent.PRIMARY} disabled={!selected.length} onClick={handleExport}>Export</Button>
</div>
</div>
</Dialog>
</>
}
const AppToaster = Toaster.create({position: 'top-right'});
export default App;

@ -1,16 +0,0 @@
/*
* Copyright 2022, The Cozo Project Authors.
*
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

@ -1,35 +0,0 @@
import { useEffect, useState } from "react";
/**
* Detect user preferred color scheme based on OS/browser settings
* @returns {'light' | 'dark'}
*/
export function usePreferredColorScheme() {
const [colorScheme, setColorScheme] = useState("light");
useEffect(() => {
// reference: https://blueprintjs.com/docs/#core/typography.dark-theme
const updateColorScheme = (mediaQueryOrEvent) => setColorScheme(mediaQueryOrEvent.matches ? "dark" : "light");
const mediaQuery = window.matchMedia?.("(prefers-color-scheme: dark)");
if (!mediaQuery) return;
updateColorScheme(mediaQuery);
mediaQuery.addEventListener("change", updateColorScheme);
return () => mediaQuery.removeEventListener("change", updateColorScheme);
}, []);
return colorScheme;
}
/**
* Apply Blueprint design system's recommended theme class name to the body element
* @param {'light' | 'dark'} colorScheme
*/
export function useBlueprintThemeClassName(colorScheme) {
useEffect(() => {
// reference: https://blueprintjs.com/docs/#core/typography.dark-theme
document.body.classList[colorScheme === "dark" ? "add" : "remove"]("bp4-dark");
}, [colorScheme]);
}

@ -1,25 +0,0 @@
/*
* Copyright 2022, The Cozo Project Authors.
*
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/.
*/
:root {
color-scheme: dark light;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

@ -1,25 +0,0 @@
/*
* Copyright 2022, The Cozo Project Authors.
*
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
// <React.StrictMode>
<App/>
// </React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

@ -1,21 +0,0 @@
/*
* Copyright 2022, The Cozo Project Authors.
*
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/.
*/
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

@ -1,13 +0,0 @@
/*
* Copyright 2022, The Cozo Project Authors.
*
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/.
*/
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save