Skip to content

Commit 8a6f5d7

Browse files
committed
chore: Add demo site
1 parent 5c77dda commit 8a6f5d7

File tree

5 files changed

+323
-72
lines changed

5 files changed

+323
-72
lines changed

.github/workflows/pages.yml

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Deploy to GitHub Pages
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
pages: write
11+
id-token: write
12+
13+
concurrency:
14+
group: "pages"
15+
cancel-in-progress: true
16+
17+
jobs:
18+
Deploy:
19+
environment:
20+
name: github-pages
21+
url: ${{ steps.deployment.outputs.page_url }}
22+
runs-on: ubuntu-latest
23+
steps:
24+
- uses: actions/checkout@v4
25+
- uses: actions/setup-node@v4
26+
with:
27+
node-version: latest
28+
cache: 'npm'
29+
- run: npx vite build
30+
- uses: actions/configure-pages@v4
31+
- uses: actions/upload-pages-artifact@v3
32+
with:
33+
path: './dist'
34+
- id: deployment
35+
uses: actions/deploy-pages@v4

index.html

+80-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,85 @@
11
<!doctype html>
22
<html lang="en">
33
<head>
4-
<meta charset="utf-8">
5-
<title>quak 🦆</title>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" type="image/svg+xml" href="./assets/logo-color.svg" />
6+
<title>quak | demo</title>
7+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
8+
<meta name="description" content="juypyter widgets made easy">
9+
<!-- Open Graph Meta Tags -->
10+
<meta property="og:url" content="https://github.com/manzt/quak">
11+
<meta property="og:type" content="website">
12+
<meta property="og:title" content="quak | demo">
13+
<meta property="og:description" content="a scalable data profiler for quickly scanning large tables">
14+
<meta property="og:image" content="https://raw.githubusercontent.com/manzt/quak/main/assets/og.png">
15+
<!-- Twitter Meta Tags -->
16+
<meta name="twitter:card" content="summary_large_image">
17+
<meta property="twitter:domain" content="https://github.com">
18+
<meta property="twitter:url" content="https://github.com/manzt/quak">
19+
<meta name="twitter:title" content="quak | demo">
20+
<meta property="twitter:description" content="a scalable data profiler for quickly scanning large tables">
21+
<meta property="twitter:image" content="https://raw.githubusercontent.com/manzt/quak/main/assets/og.png">
22+
<script src="https://cdn.tailwindcss.com"></script>
23+
<script type="module" src="./lib/demo.ts"></script>
624
</head>
7-
<script type="module" src="./lib/example.ts"></script>
8-
<body></body>
25+
<body>
26+
<div id="banner" class="hidden bg-amber-600">
27+
<div class="mx-auto py-1 px-4">
28+
<div class="flex items-center justify-between flex-wrap">
29+
<div class="w-0 flex-1 flex items-center truncate">
30+
<p class="ml-3 text-white">
31+
<b>quak</b> a scalable data profiler for quickly scanning large tables&ensp;
32+
<a class="underline font-medium" href="https://github.com/manzt/quak">Learn more →</a>
33+
</p>
34+
</div>
35+
<div class="order-2 flex-shrink-0 sm:order-3 sm:ml-3">
36+
<button
37+
type="button"
38+
id="dismiss"
39+
class="-mr-1 flex p-1 rounded-md hover:bg-amber-500 focus:outline-none sm:-mr-2"
40+
>
41+
<span class="sr-only">Dismiss</span>
42+
<svg
43+
class="h-4 w-4 text-white"
44+
xmlns="http://www.w3.org/2000/svg"
45+
fill="none"
46+
viewBox="0 0 24 24"
47+
stroke="currentColor"
48+
aria-hidden="true"
49+
>
50+
<path
51+
stroke-linecap="round"
52+
stroke-linejoin="round"
53+
stroke-width="2"
54+
d="M6 18L18 6M6 6l12 12"
55+
/>
56+
</svg>
57+
</button>
58+
</div>
59+
</div>
60+
</div>
61+
</div>
62+
63+
<div id="options" class="hidden relative max-w-2xl mx-auto m-8 flex flex-col opacity-60">
64+
65+
<label
66+
id="dropzone"
67+
class="rounded-lg h-48 border-2 border-gray-200 border-dashed text-gray-600 flex flex-col justify-center items-center"
68+
>
69+
<input type="file" class="absolute w-full h-full opacity-0" accept=".csv,.parquet"/>
70+
<img src="./assets/logo-color.svg" class="h-20 w-20" />
71+
<div id="message" class="text-center">
72+
Drag & Drop a <strong>.csv</strong> or <strong>.parquet</strong>
73+
</div>
74+
</label>
75+
76+
<a class="mt-2 hover:underline hover:opacity-100 self-end opacity-70" href="?source=https://raw.githubusercontent.com/uwdata/mosaic/main/data/athletes.csv">example →</a>
77+
78+
</div>
79+
80+
<div class="m-5" id="table">
81+
82+
</div>
83+
84+
</body>
985
</html>

lib/clients/DataTable.ts

+38-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as arrow from "apache-arrow";
22
// @deno-types="../deps/mosaic-core.d.ts"
33
import {
4+
Coordinator,
45
type FieldInfo,
56
type FieldRequest,
67
MosaicClient,
@@ -30,6 +31,31 @@ interface DataTableOptions {
3031
// TODO: more
3132
type ColumnSummaryClient = Histogram | ValueCounts;
3233

34+
export async function datatable(
35+
table: string,
36+
options: {
37+
coordinator?: Coordinator;
38+
height?: number;
39+
columns?: Array<string>;
40+
} = {},
41+
) {
42+
assert(options.coordinator, "Must provide a coordinator");
43+
let empty = await options.coordinator.query(
44+
Query
45+
.from(table)
46+
.select(options.columns ?? ["*"])
47+
.limit(0)
48+
.toString(),
49+
);
50+
let client = new DataTable({
51+
table,
52+
schema: empty.schema,
53+
height: options.height,
54+
});
55+
options.coordinator.connect(client);
56+
return client;
57+
}
58+
3359
export class DataTable extends MosaicClient {
3460
/** source of the data */
3561
#meta: { table: string; schema: arrow.Schema };
@@ -120,6 +146,12 @@ export class DataTable extends MosaicClient {
120146
return this.#root;
121147
}
122148

149+
resize(height: number) {
150+
this.#rows = Math.floor(height / this.#rowHeight);
151+
this.#tableRoot.style.maxHeight = `${height}px`;
152+
this.#tableRoot.scrollTop = 0;
153+
}
154+
123155
get #columns() {
124156
return this.#meta.schema.fields.map((field) => field.name);
125157
}
@@ -134,7 +166,9 @@ export class DataTable extends MosaicClient {
134166
.orderby(
135167
this.#orderby
136168
.filter((o) => o.order !== "unset")
137-
.map((o) => o.order === "asc" ? asc(o.field) : desc(o.field)),
169+
.map((o) =>
170+
o.order === "asc" ? asc(o.field) : desc(o.field)
171+
),
138172
);
139173
this.#sql.value = query.clone().toString();
140174
return query
@@ -378,7 +412,9 @@ function thcol(
378412
});
379413

380414
signals.effect(() => {
381-
sortButton.style.visibility = buttonVisible.value ? "visible" : "hidden";
415+
sortButton.style.visibility = buttonVisible.value
416+
? "visible"
417+
: "hidden";
382418
});
383419

384420
signals.effect(() => {

lib/demo.ts

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/// <reference types="npm:vite/client" />
2+
import * as mc from "@uwdata/mosaic-core";
3+
import * as msql from "@uwdata/mosaic-sql";
4+
5+
import { assert } from "./utils/assert.ts";
6+
import { datatable } from "./clients/DataTable.ts";
7+
8+
let dropzone = document.querySelector("input")!;
9+
let options = document.querySelector("#options")!;
10+
let table = document.querySelector("#table")!;
11+
12+
function getFile(): Promise<File> {
13+
return new Promise((resolve) => {
14+
// on input file change
15+
dropzone.addEventListener("input", (e) => {
16+
let file = (e.target as HTMLInputElement).files![0];
17+
assert(file, "No file selected.");
18+
resolve(file);
19+
});
20+
});
21+
}
22+
23+
function handleBanner() {
24+
let banner = document.querySelector("#banner")!;
25+
if (localStorage.getItem("quak-hide-banner") === "true") {
26+
banner.remove();
27+
} else {
28+
banner.classList.remove("hidden");
29+
document.querySelector("#dismiss")!.addEventListener("click", () => {
30+
localStorage.setItem("quak-hide-banner", "true");
31+
banner.remove();
32+
});
33+
}
34+
}
35+
36+
function handleLoading(source: string | null) {
37+
if (!source) {
38+
options.classList.remove("hidden");
39+
return;
40+
}
41+
let loading = document.createElement("div");
42+
let file = source.split("/").pop();
43+
let text = document.createTextNode(`loading ${file}...`);
44+
loading.classList.add(
45+
"animate-bounce",
46+
"flex",
47+
"justify-center",
48+
"p-4",
49+
);
50+
loading.appendChild(text);
51+
table.appendChild(loading);
52+
}
53+
54+
async function main() {
55+
handleBanner();
56+
let source = new URLSearchParams(location.search).get("source");
57+
handleLoading(source);
58+
let tableName = "df";
59+
let coordinator = new mc.Coordinator();
60+
let connector = mc.wasmConnector();
61+
let db = await connector.getDuckDB();
62+
coordinator.databaseConnector(connector);
63+
64+
let exec;
65+
if (source) {
66+
exec = source.endsWith(".csv")
67+
? msql.loadCSV(tableName, source, { replace: true })
68+
: msql.loadParquet(tableName, source, { replace: true });
69+
} else {
70+
let file = await getFile();
71+
if (file.name.endsWith(".csv")) {
72+
await db.registerFile(file.name, await file.text());
73+
exec = msql.loadCSV(tableName, file.name, { replace: true });
74+
} else {
75+
assert(file.name.endsWith(".parquet"));
76+
await db.registerFileBuffer(
77+
file.name,
78+
new Uint8Array(await file.arrayBuffer()),
79+
);
80+
exec = msql.loadParquet(tableName, file.name, { replace: true });
81+
}
82+
}
83+
84+
await coordinator.exec([exec]);
85+
let dt = await datatable(tableName, { coordinator, height: 500 });
86+
options.remove();
87+
table.replaceChildren();
88+
table.appendChild(dt.node());
89+
}
90+
91+
main();

0 commit comments

Comments
 (0)