Skip to content

Commit

Permalink
refactor and add type validation
Browse files Browse the repository at this point in the history
  • Loading branch information
LoganWalls committed Nov 2, 2023
1 parent 06d5c01 commit 6ed0e0b
Show file tree
Hide file tree
Showing 8 changed files with 255 additions and 142 deletions.
3 changes: 2 additions & 1 deletion ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"@types/sql.js": "^1.4.7",
"open-props": "^1.6.10",
"solid-js": "^1.7.8",
"sql.js": "^1.8.0"
"sql.js": "^1.8.0",
"zod": "^3.22.4"
},
"devDependencies": {
"typescript": "^5.0.2",
Expand Down
7 changes: 7 additions & 0 deletions ui/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 5 additions & 6 deletions ui/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@
border-radius: var(--radius-2);
padding: var(--size-2);
background-color: var(--brand);
&:hover {
box-shadow: var(--shadow-3);
}
}

.pos {
Expand All @@ -35,8 +32,11 @@
border-radius: var(--radius-2);
padding: var(--size-fluid-1);
box-shadow: var(--shadow-2);
@media (--motionOK) {
animation: var(--animation-fade-in);
animation: var(--animation-fade-in-out);
cursor: pointer;

&:hover {
box-shadow: var(--shadow-3);
}
}

Expand All @@ -47,4 +47,3 @@
.spanish {
background-color: var(--color-spanish);
}

145 changes: 10 additions & 135 deletions ui/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,148 +1,23 @@
import { For, Component, createResource, Resource } from "solid-js";

import initSqlJs, { Database } from "sql.js";
import dbUrl from "./assets/escoco.db?url";
import sqlJsWasm from "../node_modules/sql.js/dist/sql-wasm.wasm?url";
import { For, Component, createResource } from "solid-js";
import { loadDatabase, queryToArray } from "./sql";
import "./App.css";

interface Segment {
id: number;
sourceName: string;
tokens: Word[];
}

interface Word {
id: number;
surface_form: string;
annotations: Annotation[];
}

interface Annotation {
type: string;
value: string;
}

function queryToMaps(
db: Resource<Database>,
query: string,
...params: any[]
): Map<string, string | number>[] {
const connection = db();
if (!connection) {
return [];
}
const result = connection.exec(query, params)[0];
return result.values.map((values) => {
const m = new Map();
for (let i = 0; i < result.columns.length; i++) {
m.set(result.columns[i], values[i]);
}
return m;
});
}

const Segment: Component<{ data: Segment }> = (props) => {
return (
<div class="segment card">
<For each={props.data.tokens}>{(t) => <Word data={t} />}</For>
</div>
);
};

const Word: Component<{ data: Word }> = (props) => {
const d = props.data;
const language = d.annotations.filter((a) => a.type == "language")[0].value;
const tag = d.annotations.filter((a) => a.type == "pos")[0].value;
return (
<div
classList={{
token: true,
english: language == "eng",
spanish: language == "spa",
}}
>
{d.surface_form.replace("##", "")}
<div class="pos">{tag}</div>
</div>
);
};
import { Segment, fetchSegments } from "./components/Segment";
import { z } from "zod";

const App: Component = () => {
const [db] = createResource(async () => {
const sqlPromise = initSqlJs({ locateFile: () => sqlJsWasm });
const dataPromise = fetch(dbUrl).then((res) => res.arrayBuffer());
const [SQL, buf] = await Promise.all([sqlPromise, dataPromise]);
return new SQL.Database(new Uint8Array(buf));
});
const [db] = createResource(loadDatabase);
const segments = () => {
const segments = queryToMaps(
const segmentIds = z.array(z.number()).parse(queryToArray(
db,
`
SELECT DISTINCT s.id, s.start_ms, s.end_ms, d.name as source_name
SELECT DISTINCT s.id
FROM WordAnnotations AS a
JOIN AnnotationTypes AS at ON a.annotation_type_id = at.id
JOIN Words AS w ON a.word_id = w.id
JOIN Segments AS s ON w.segment_id = s.id
JOIN DataSources AS d ON s.data_source_id = d.id
WHERE at.name = 'switch';
`,
);
const placeholders = "?,".repeat(segments.length).slice(0, -1);
const segment_ids = segments.map((s) => s.get("id"));
const surface_forms = queryToMaps(
db,
`
SELECT segment_id, id, surface_form
FROM Words
WHERE segment_id IN (${placeholders})
ORDER BY segment_id, word_index;
WHERE a.annotation_type_id = (SELECT id from AnnotationTypes WHERE name = 'switch');
`,
...segment_ids,
);
const annotations = queryToMaps(
db,
`
SELECT w.id as word_id, at.name as type, a.value
FROM Words as w
JOIN WordAnnotations AS a ON a.word_id = w.id
JOIN AnnotationTypes AS at ON a.annotation_type_id = at.id
WHERE w.segment_id IN (${placeholders})
ORDER BY w.segment_id, w.word_index, at.name;
`,
...segment_ids,
);

const result: Map<number, Segment> = new Map();
for (let i = 0; i < segments.length; i++) {
const s = segments[i];
const id = s.get("id") as number;
result.set(id, {
id,
sourceName: s.get("source_name"),
tokens: [],
} as Segment);
}

const words: Map<number, Word> = new Map();
for (const t of surface_forms) {
const id = t.get("id") as number;
const word = {
id,
surface_form: t.get("surface_form"),
annotations: [],
} as Word;
words.set(id, word);
result.get(t.get("segment_id") as number)!.tokens.push(word);
}

for (const a of annotations) {
const word = words.get(a.get("word_id") as number)!;
word.annotations.push({
type: a.get("type"),
value: a.get("value"),
} as Annotation);
}
return Array.from(result.values());
));
return fetchSegments(db, segmentIds);
};

// <input onInput={(e) => setQuery(e.target.value)} />
Expand Down
25 changes: 25 additions & 0 deletions ui/src/components/DataSource.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// import { Component, Resource } from "solid-js";
// import typia from "typia";
//
// import { Database } from "sql.js";
// import "./App.css";
//
// import { SegmentData } from "./Segment";
//
// export interface DataSourceData {
// id: string;
// name: string;
// url: string;
// segments: SegmentData[];
// }
//
//
// export const DataSource: Component<{
// data: DataSourceData;
// selectedSegmentId: number;
// }> = (props) => {
// const { data, selectedSegmentId } = props;
//
//
//
// };
74 changes: 74 additions & 0 deletions ui/src/components/Segment.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { For, Component, Resource } from "solid-js";
import { Word, WordData, fetchWords } from "./Word";
import { placeholders, queryToArray, queryToMaps } from "../sql";
import { Database } from "sql.js";
import { z } from "zod";

export const SegmentData = z.object({
id: z.number(),
start: z.number(),
end: z.number(),
sourceName: z.string(),
words: z.array(WordData),
});
export type SegmentData = z.infer<typeof SegmentData>;

export function fetchSegments(
db: Resource<Database>,
segmentIds: number[],
): SegmentData[] {
const connection = db();
if (!connection) {
return [];
}
const segments = new Map(
queryToMaps(
db,
`
SELECT DISTINCT s.id, s.start_ms as start, s.end_ms as end, d.name as source_name
FROM Segments as s
JOIN DataSources AS d ON data_source_id = d.id
WHERE s.id IN (${placeholders(segmentIds.length)})
ORDER BY source_name, start;
`,
...segmentIds,
).map((s) => {
const segment = SegmentData.parse({
id: s.get("id"),
start: s.get("start"),
end: s.get("end"),
sourceName: s.get("source_name"),
words: [],
});
return [segment.id, segment];
}),
);
const wordIds = z.array(z.number()).parse(
queryToArray(
db,
`
SELECT id
FROM Words
WHERE segment_id IN (${placeholders(segmentIds.length)});
`,
...segmentIds,
),
);
for (const w of fetchWords(db, wordIds)) {
const segment = segments.get(w.segmentId);
if (segment) {
segment.words.push(w);
} else {
console.error(`Cannot find segment with id: ${w.segmentId}`);
}
}
return Array.from(segments.values());
}

export const Segment: Component<{ data: SegmentData }> = (props) => {
return (
<div class="segment card">
<For each={props.data.words}>{(t) => <Word data={t} />}</For>
</div>
);
};
Loading

0 comments on commit 6ed0e0b

Please sign in to comment.