Remove WASM demo from main repo
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}
|
||||
/>
|
||||
|
||||
<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…
Reference in New Issue