svelte cleanup

This commit is contained in:
Brent Schroeter 2025-08-13 14:46:19 -07:00
parent 0b4b7db0be
commit dc82afe00d
13 changed files with 6 additions and 467 deletions

View file

@ -11,6 +11,6 @@
<table-viewer root-path="{{ settings.root_path }}"></table-viewer>
</main>
</div>
<script src="{{ settings.root_path }}/js_dist/TableViewer.js"></script>
<script src="{{ settings.root_path }}/js_dist/table-viewer.webc.js"></script>
{% endblock %}

View file

@ -1,13 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Svelte + TS</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View file

@ -1,8 +0,0 @@
export function add(a: number, b: number): number {
return a + b;
}
// Learn more at https://docs.deno.com/runtime/manual/examples/module_metadata#concepts
if (import.meta.main) {
console.log("Add 2 + 3 =", add(2, 3));
}

View file

@ -1,6 +0,0 @@
import { assertEquals } from "@std/assert";
import { add } from "./main.ts";
Deno.test(function addTest() {
assertEquals(add(2, 3), 5);
});

View file

@ -1,47 +0,0 @@
<script lang="ts">
import svelteLogo from './assets/svelte.svg'
import viteLogo from '/vite.svg'
import Counter from './lib/Counter.svelte'
</script>
<main>
<div>
<a href="https://vite.dev" target="_blank" rel="noreferrer">
<img src={viteLogo} class="logo" alt="Vite Logo" />
</a>
<a href="https://svelte.dev" target="_blank" rel="noreferrer">
<img src={svelteLogo} class="logo svelte" alt="Svelte Logo" />
</a>
</div>
<h1>Vite + Svelte</h1>
<div class="card">
<Counter />
</div>
<p>
Check out <a href="https://github.com/sveltejs/kit#readme" target="_blank" rel="noreferrer">SvelteKit</a>, the official Svelte app framework powered by Vite!
</p>
<p class="read-the-docs">
Click on the Vite and Svelte logos to learn more
</p>
</main>
<style>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.svelte:hover {
filter: drop-shadow(0 0 2em #ff3e00aa);
}
.read-the-docs {
color: #888;
}
</style>

View file

@ -1,79 +0,0 @@
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
.card {
padding: 2em;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="26.6" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 308"><path fill="#FF3E00" d="M239.682 40.707C211.113-.182 154.69-12.301 113.895 13.69L42.247 59.356a82.198 82.198 0 0 0-37.135 55.056a86.566 86.566 0 0 0 8.536 55.576a82.425 82.425 0 0 0-12.296 30.719a87.596 87.596 0 0 0 14.964 66.244c28.574 40.893 84.997 53.007 125.787 27.016l71.648-45.664a82.182 82.182 0 0 0 37.135-55.057a86.601 86.601 0 0 0-8.53-55.577a82.409 82.409 0 0 0 12.29-30.718a87.573 87.573 0 0 0-14.963-66.244"></path><path fill="#FFF" d="M106.889 270.841c-23.102 6.007-47.497-3.036-61.103-22.648a52.685 52.685 0 0 1-9.003-39.85a49.978 49.978 0 0 1 1.713-6.693l1.35-4.115l3.671 2.697a92.447 92.447 0 0 0 28.036 14.007l2.663.808l-.245 2.659a16.067 16.067 0 0 0 2.89 10.656a17.143 17.143 0 0 0 18.397 6.828a15.786 15.786 0 0 0 4.403-1.935l71.67-45.672a14.922 14.922 0 0 0 6.734-9.977a15.923 15.923 0 0 0-2.713-12.011a17.156 17.156 0 0 0-18.404-6.832a15.78 15.78 0 0 0-4.396 1.933l-27.35 17.434a52.298 52.298 0 0 1-14.553 6.391c-23.101 6.007-47.497-3.036-61.101-22.649a52.681 52.681 0 0 1-9.004-39.849a49.428 49.428 0 0 1 22.34-33.114l71.664-45.677a52.218 52.218 0 0 1 14.563-6.398c23.101-6.007 47.497 3.036 61.101 22.648a52.685 52.685 0 0 1 9.004 39.85a50.559 50.559 0 0 1-1.713 6.692l-1.35 4.116l-3.67-2.693a92.373 92.373 0 0 0-28.037-14.013l-2.664-.809l.246-2.658a16.099 16.099 0 0 0-2.89-10.656a17.143 17.143 0 0 0-18.398-6.828a15.786 15.786 0 0 0-4.402 1.935l-71.67 45.674a14.898 14.898 0 0 0-6.73 9.975a15.9 15.9 0 0 0 2.709 12.012a17.156 17.156 0 0 0 18.404 6.832a15.841 15.841 0 0 0 4.402-1.935l27.345-17.427a52.147 52.147 0 0 1 14.552-6.397c23.101-6.006 47.497 3.037 61.102 22.65a52.681 52.681 0 0 1 9.003 39.848a49.453 49.453 0 0 1-22.34 33.12l-71.664 45.673a52.218 52.218 0 0 1-14.563 6.398"></path></svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -1,122 +0,0 @@
<script lang="ts">
import {
type Coords,
type Field,
type Row,
coords_eq,
} from "./field.svelte";
type Props = {
fields: Field[];
header_classes?: (string | boolean | undefined)[];
on_cell_click?(ev: MouseEvent, coords: Coords): void;
on_cell_dblclick?(coords: Coords): void;
on_focus?(): void;
on_keydown?(ev: KeyboardEvent): void;
rows: Row[];
root_element?: HTMLDivElement;
selections: Coords[];
show_headers?: boolean;
tab_index?: number;
};
let {
fields,
header_classes = [],
on_cell_click,
on_cell_dblclick,
on_focus,
on_keydown,
root_element = $bindable(),
rows,
selections,
show_headers = true,
tab_index = 0,
}: Props = $props();
</script>
<div
bind:this={root_element}
class="viewer-table"
onfocus={on_focus}
onkeydown={on_keydown}
role="grid"
tabindex={tab_index}
>
{#if show_headers}
<div class={["viewer-table__headers", ...header_classes]}>
{#each fields as field, field_index}
<div
aria-colindex={field_index}
class="viewer-header"
role="columnheader"
style:width={`${field.width_px}px`}
>
<div>{field.label ?? field.name}</div>
</div>
{/each}
<div class="viewer-header-actions">
TODO
</div>
</div>
{/if}
<div class="viewer-table__body">
{#each rows as row, row_index}
<div aria-rowindex={row_index} class="viewer-row" role="row">
{#each fields as field, field_index}
{@const cell_data = row.data[field_index]}
{@const cell_coords: Coords = [row_index, field_index]}
{@const cell_selected = selections.some(
(sel_coords) => coords_eq(sel_coords, cell_coords),
)}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div
aria-colindex={field_index}
aria-rowindex={row_index}
aria-selected={cell_selected}
class={[
"viewer-cell",
cell_selected && "viewer-cell--selected",
]}
onclick={(ev) => on_cell_click?.(ev, cell_coords)}
ondblclick={() => on_cell_dblclick?.(cell_coords)}
role="gridcell"
style:width={`${field.width_px}px`}
tabindex="-1"
>
{#if cell_data.t === "Text"}
<div
class={[
"viewer-cell__content",
"viewer-cell__content--text",
cell_data.c === null && "viewer-cell__content--null",
]}
>
{cell_data.c}
</div>
{:else if cell_data.t === "Uuid"}
<div
class={[
"viewer-cell__content",
"viewer-cell__content--uuid",
cell_data.c === null && "viewer-cell__content--null",
]}
>
{cell_data.c}
</div>
{:else}
<div
class={[
"viewer-cell__content",
"viewer-cell__content--unknown",
]}
>
UNKNOWN
</div>
{/if}
</div>
{/each}
</div>
{/each}
</div>
</div>

View file

@ -1,10 +0,0 @@
<script lang="ts">
let count: number = $state(0)
const increment = () => {
count += 1
}
</script>
<button onclick={increment}>
count is {count}
</button>

View file

@ -1,9 +0,0 @@
import { mount } from 'svelte'
import './app.css'
import App from './App.svelte'
const app = mount(App, {
target: document.getElementById('app')!,
})
export default app

View file

@ -1,168 +0,0 @@
<svelte:options customElement={{ tag: "table-viewer", shadow: "none" }} />
<script lang="ts">
import { type Encodable, type Field } from "./field.svelte";
type Coords = [string, string];
interface CommittedChange {
coords_initial: Coords;
// This will be identical to coords_initial, unless the change altered a
// primary key.
coords_updated: Coords;
value_initial: Encodable;
value_updated: Encodable;
}
let selections = $state<Coords[]>([]);
let table_focused = $state(false);
let editing = $state(false);
let editor_input_value = $state("");
let committed_changes = $state<CommittedChange[][]>([]);
let reverted_changes = $state<CommittedChange[][]>([]);
let editor_input_element = $state<HTMLInputElement | undefined>();
interface GetDataResult {
pkeys: string[];
data: Record<string, Record<string, Encodable>>;
fields: Record<string, Field>;
field_names: string[];
}
function coords_eq(a: Coords, b: Coords): boolean {
return a[0] === b[0] && a[1] === b[1];
}
function handle_click(ev: MouseEvent, coords: Coords, cell_data: Encodable) {
if (!editing) {
if (ev.metaKey || ev.ctrlKey) {
// TODO
// selections = [...selections.filter((prev) => !coords_eq(prev, coords)), coords];
// editor_input_value = "";
} else {
selections = [coords];
if (cell_data.t === "Text" || cell_data.t === "Uuid") {
editor_input_value = cell_data.c;
}
}
}
}
function handle_dblclick(_coords: Coords) {
if (!editing) {
editing = true;
editor_input_element?.focus();
}
}
function handle_table_focusin() {
table_focused = true;
}
function handle_table_focusout() {
table_focused = false;
}
function handle_keydown(ev: KeyboardEvent) {
if (table_focused && ev.key === "ArrowDown") {
}
}
$effect(() => {
document.addEventListener("keydown", handle_keydown);
return () => document.removeEventListener("keydown", handle_keydown);
});
const data_promise: Promise<GetDataResult> = fetch("get-data")
.then((resp) => resp.json())
.then((body) => ({
...body,
data: Object.fromEntries(body.data.map(({ pkey, data }) => [
pkey,
Object.fromEntries(body.fields.map(({ name }, i) => [name, data[i]])),
])),
field_names: body.fields.map(({ name }) => name),
fields: Object.fromEntries(body.fields.map((field) => [field.name, field])),
}));
</script>
{#await data_promise}
<div class="loading-indicator">Loading...</div>
{:then data}
<div
class="viewer-table"
onfocusin={handle_table_focusin}
onfocusout={handle_table_focusout}
role="grid"
tabindex="0"
>
<div class="viewer-table__headers">
{#each data.field_names as field_name, field_index}
<div
aria-colindex={field_index}
class="viewer-header"
role="columnheader"
style:width={`${data.fields[field_name].width_px}px`}
>
<div>{data.fields[field_name].label ?? field_name}</div>
</div>
{/each}
<div class="viewer-header-actions">
TODO
</div>
</div>
<div class="viewer-table__body">
{#each data.pkeys as pkey, row_index}
<div aria-rowindex={row_index} class="viewer-row" role="row">
{#each data.field_names as field_name, field_index}
{@const cell_data = data.data[pkey][field_name]}
{@const selected = selections.some(
([sel_pkey, sel_field]) => sel_pkey === pkey && sel_field === field_name,
)}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div
aria-colindex={field_index}
aria-rowindex={row_index}
aria-selected={selected}
class="viewer-cell"
onclick={(ev) => handle_click(ev, [pkey, field_name], cell_data)}
ondblclick={() => handle_dblclick([pkey, field_name])}
role="gridcell"
style:width={`${data.fields[field_name].width_px}px`}
tabindex="-1"
>
{#if cell_data.t === "Text"}
<div
class={[
"viewer-cell__content",
"viewer-cell__content--text",
selections.some((coords) => coords_eq(coords, [pkey, field_name])) && "viewer-cell__content--selected",
]}
>
{cell_data.c}
</div>
{:else if cell_data.t === "Uuid"}
<div class="viewer-cell__content viewer-cell__content--uuid">{cell_data.c}</div>
{:else}
<div class="viewer-cell__content viewer-cell__content--unknown">Unknown type</div>
{/if}
</div>
{/each}
</div>
{/each}
</div>
</div>
<div class="editor">
<input
class={["editor__input", selections.length !== 1 && "editor__input--hidden"]}
onfocus={() => {
editing = true;
}}
role="combobox"
bind:this={editor_input_element}
bind:value={editor_input_value}
/>
</div>
{:catch err}
Error {err}
{/await}

View file

@ -7,9 +7,11 @@ export default defineConfig({
plugins: [svelte()],
build: {
rollupOptions: {
input: path.fromFileUrl(
new URL("./src/TableViewer.svelte", import.meta.url),
),
input: [
...Deno.readDirSync("./src")
.filter(({ name }) => name.endsWith(".webc.svelte"))
.map(({ name }) => path.join("./src", name)),
],
output: {
dir: path.fromFileUrl(new URL("../js_dist", import.meta.url)),
entryFileNames: "[name].js",