Ziyang Hu 2 years ago
parent 2ad4dccb69
commit 9ff4f06cd3

@ -9,6 +9,11 @@ struct CozoAppState {
db: Mutex<Option<cozo::Db>>, db: Mutex<Option<cozo::Db>>,
} }
#[tauri::command]
fn is_opened(state: tauri::State<CozoAppState>) -> bool {
state.db.lock().unwrap().is_some()
}
#[tauri::command(async)] #[tauri::command(async)]
fn open_db(path: String, state: tauri::State<CozoAppState>) -> Result<(), String> { fn open_db(path: String, state: tauri::State<CozoAppState>) -> Result<(), String> {
let mut cur_db = state.db.lock().unwrap(); let mut cur_db = state.db.lock().unwrap();
@ -50,7 +55,7 @@ fn main() {
.manage(CozoAppState { .manage(CozoAppState {
db: Mutex::new(None), db: Mutex::new(None),
}) })
.invoke_handler(tauri::generate_handler![open_db, close_db, run_query]) .invoke_handler(tauri::generate_handler![open_db, close_db, run_query, is_opened])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");
} }

@ -1,22 +1,72 @@
<script> <script>
import { Button } from "carbon-components-svelte";
import {message, open, save} from '@tauri-apps/api/dialog';
import {invoke} from '@tauri-apps/api/tauri'
import {Folder, FolderAdd, Close, DirectionStraightRight} from "carbon-icons-svelte";
let queryText = ''; let queryText = '';
let queryArea;
let hasError = false; let queryResults = null;
let resultText = '';
let started; let started;
let inProgress = false; let inProgress = false;
let errorMessage = '';
let statusMessage = ''; let statusMessage = '';
import {invoke} from "@tauri-apps/api/tauri";
import {message, open, save} from "@tauri-apps/api/dialog"
import {Button, DataTable, InlineLoading, Link, TextArea} from "carbon-components-svelte";
import {DirectionStraightRight, Folder, FolderAdd} from "carbon-icons-svelte";
import {onMount} from "svelte";
async function handleQuery() {
const query = queryText.trim();
if (query) {
inProgress = true;
started = performance.now();
errorMessage = '';
statusMessage = '';
queryResults = null;
try {
const res = await invoke('run_query', {query});
const headers = res.headers.map((h) => ({key: h, value: h}));
const rows = res.rows.map((v, idx) => {
let ret = {};
ret.id = idx;
for (let i = 0; i < v.length; i++) {
ret[headers[i].key] = v[i];
}
return ret;
});
queryResults = {rows, headers}
} catch (e) {
errorMessage = '' + e;
} finally {
inProgress = false;
let time = Math.round(performance.now() - started);
statusMessage = `finished in ${time}ms`
}
}
document.getElementById("query-area").focus();
}
let recent = [];
onMount(async () => {
const recentData = localStorage.getItem("recent_dbs");
if (recentData) {
try {
recent = JSON.parse(recentData);
if (recent.length) {
let is_opened = await invoke('is_opened');
if (is_opened) {
db_opened = recent[0];
}
}
} catch (e) {
}
}
})
async function open_db(path) { async function open_db(path) {
if (path) { if (path) {
try { try {
await invoke('open_db', {path}) await invoke('open_db', {path});
onOpenedDb(path);
} catch (e) { } catch (e) {
await message('' + e, {type: 'error', title: 'Cannot open'}) await message('' + e, {type: 'error', title: 'Cannot open'})
} }
@ -24,6 +74,15 @@
} }
function onOpenedDb(new_db_path) {
db_opened = new_db_path;
if (!recent.includes(new_db_path)) {
recent.unshift(new_db_path);
recent = recent.slice(0, 10);
localStorage.setItem("recent_dbs", JSON.stringify(recent))
}
}
async function handleOpen() { async function handleOpen() {
const path = await open({directory: true}); const path = await open({directory: true});
await open_db(path); await open_db(path);
@ -34,75 +93,81 @@
await open_db(path); await open_db(path);
} }
async function handleClose() { async function handleOpenRecent(db) {
try { await open_db(db)
await invoke('close_db');
} catch (e) {
await message('' + e, {type: 'error', title: 'Cannot close'});
}
}
async function handleQuery() {
const query = queryText.trim();
if (query) {
hasError = false;
inProgress = true;
started = performance.now();
statusMessage = 'Querying ...'
try {
let res = await invoke('run_query', {query});
// await message(JSON.stringify(res, null, 2), 'Result')
resultText = JSON.stringify(res, null, 2);
} catch (e) {
resultText = '' + e;
hasError = true;
// await message('' + e, {type: 'error', title: 'Cannot query'})
}
finally {
inProgress = false;
let time = Math.round(performance.now() - started);
statusMessage = `Query finished in ${time}ms`
}
}
queryArea.focus();
} }
let db_opened = null;
</script> </script>
<main> <main>
<div> {#if db_opened}
<Button icon={Folder} on:click={handleOpen}>Open</Button> <div id="main">
<Button icon={FolderAdd} on:click={handleCreate}>Create</Button> <div id="upper">
<Button icon={Close} on:click={handleClose}>Close</Button> <TextArea bind:value={queryText} labelText={`Using database ${db_opened}`} rows={10}
id="query-area"></TextArea>
<div style="width: 100%; display: flex; align-items: stretch; justify-content: center; flex-direction: row; padding-bottom: 10px">
<div style="flex: 1">
{#if inProgress}
<InlineLoading status="active" description="In progress..."/>
{:else if statusMessage}
<InlineLoading status={errorMessage ? 'error' : 'finished'} description={statusMessage}/>
{/if}
</div> </div>
<textarea bind:this={queryArea} bind:value={queryText}></textarea>
<div> <div>
<Button icon={DirectionStraightRight} on:click={handleQuery} disabled={inProgress}>Query</Button> <Button size="small" icon={DirectionStraightRight} on:click={handleQuery} disabled={inProgress}>
Query
</Button>
</div>
</div>
</div> </div>
<div>{statusMessage}</div> {#if errorMessage}
<div class="result-display"> <pre id="error-message">{errorMessage}</pre>
<pre class:hasError> {/if}
{resultText} {#if queryResults}
</pre> <DataTable headers={queryResults.headers} rows={queryResults.rows} zebra
size="compact"></DataTable>
{/if}
</div> </div>
{:else}
<div style="padding: 0.5em 0.5em 1em">
<h1 style="padding-bottom: 0.5em">Start</h1>
<Button size="xl" icon={Folder} on:click={handleOpen}>Open existing</Button>
<Button size="xl" icon={FolderAdd} on:click={handleCreate}>Create new</Button>
{#if recent.length}
<div style="padding-top: 1em; padding-left: 0.5em">
{#each recent as dbPath}
<Link style="cursor: pointer" on:click={() => handleOpenRecent(dbPath)}>{dbPath}</Link>
{/each}
</div>
{/if}
</div>
{/if}
</main> </main>
<style> <style>
main { #main {
padding: 1em; padding: 1rem;
margin: 0 auto; margin: 0 auto;
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
} }
textarea { :global(#query-area) {
width: 100%;
height: 200px;
font-family: monospace; font-family: monospace;
} }
.hasError { :global(.bx--data-table-container) {
color: #ff3e00; flex: 1;
overflow: scroll;
} }
.result-display {
text-align: left; #error-message {
font-family: monospace;
} }
</style> </style>
Loading…
Cancel
Save