mirror of
https://github.com/patarapolw/cjdic2.git
synced 2026-11-09 17:39:35 +00:00
component renderer
This commit is contained in:
1 parent
098ee45b81
commit
d96346bfea
2 files changed
+155
-5
No files matched your search
+25
-5
@@ -2,9 +2,10 @@ import "./App.css";
|
|||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
import { Button, Group, Input, Stack } from "@chakra-ui/react";
|
import { Box, Button, Card, Group, Input, Stack } from "@chakra-ui/react";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
|
||||||
|
import Glossary from "./components/Glossary";
|
||||||
import { Provider } from "./components/ui/provider";
|
import { Provider } from "./components/ui/provider";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
@@ -50,16 +51,35 @@ function App() {
|
|||||||
value={q}
|
value={q}
|
||||||
onChange={(e) => setQ(e.currentTarget.value)}
|
onChange={(e) => setQ(e.currentTarget.value)}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
|
spellCheck={false}
|
||||||
placeholder="Search..."
|
placeholder="Search..."
|
||||||
/>
|
/>
|
||||||
<Button type="submit">Search</Button>
|
<Button type="submit">Search</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</form>
|
</form>
|
||||||
<ol>
|
<Box as={"ol"} listStyleType={"number"} style={{ margin: "1em" }}>
|
||||||
{entries.map((it, i) => (
|
{entries.map(({ term, reading, glossary_json, ...it }, i) => (
|
||||||
<li key={i}>{JSON.stringify(it)}</li>
|
<li key={i}>
|
||||||
|
<details>
|
||||||
|
<summary>
|
||||||
|
{term} ({reading})
|
||||||
|
</summary>
|
||||||
|
|
||||||
|
<Card.Root>
|
||||||
|
<Card.Header>{JSON.stringify(it)}</Card.Header>
|
||||||
|
<Card.Body>
|
||||||
|
{JSON.parse(glossary_json).map((g: any) => (
|
||||||
|
<Glossary
|
||||||
|
glossary={g}
|
||||||
|
onTermClicked={(t) => setQ(t + " ")}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Card.Body>
|
||||||
|
</Card.Root>
|
||||||
|
</details>
|
||||||
|
</li>
|
||||||
))}
|
))}
|
||||||
</ol>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
import { createElement as h } from "react";
|
||||||
|
|
||||||
|
export default function Glossary({
|
||||||
|
glossary,
|
||||||
|
onTermClicked,
|
||||||
|
}: {
|
||||||
|
glossary: any;
|
||||||
|
onTermClicked: (t: string) => void;
|
||||||
|
}) {
|
||||||
|
// Plain string
|
||||||
|
if (typeof glossary === "string") {
|
||||||
|
return glossary;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GlossaryText
|
||||||
|
if ("type" in glossary && glossary.type === "text") {
|
||||||
|
return glossary.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GlossaryImage
|
||||||
|
if ("type" in glossary && glossary.type === "image") {
|
||||||
|
return <GlossaryImage {...glossary} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GlossaryStructuredContent
|
||||||
|
if ("type" in glossary && glossary.type === "structured-content") {
|
||||||
|
const sc = glossary;
|
||||||
|
return (
|
||||||
|
<StructuredContent node={sc.content} onTermClicked={onTermClicked} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GlossaryDeinflection
|
||||||
|
if (Array.isArray(glossary) && glossary.length === 2) {
|
||||||
|
const [term, rules] = glossary;
|
||||||
|
return <small>{`${term} (${rules.join(", ")})`}</small>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function GlossaryImage(img: any) {
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
src={img.path}
|
||||||
|
width={img.width}
|
||||||
|
height={img.height}
|
||||||
|
title={img.title}
|
||||||
|
alt={img.alt}
|
||||||
|
style={{
|
||||||
|
imageRendering: img.imageRendering || "auto",
|
||||||
|
width: `${img.width}em`,
|
||||||
|
height: `${img.height}em`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function StructuredContent({
|
||||||
|
node,
|
||||||
|
onTermClicked,
|
||||||
|
}: {
|
||||||
|
node: any;
|
||||||
|
onTermClicked: (t: string) => void;
|
||||||
|
}): any {
|
||||||
|
if (typeof node === "string") {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(node)) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{...node.map((n) => (
|
||||||
|
<StructuredContent node={n} onTermClicked={onTermClicked} />
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Line break
|
||||||
|
if ("tag" in node && node.tag === "br") {
|
||||||
|
return <br />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Image
|
||||||
|
if ("tag" in node && node.tag === "img") {
|
||||||
|
return <GlossaryImage {...node} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Link
|
||||||
|
if ("tag" in node && node.tag === "a") {
|
||||||
|
const link = node as any;
|
||||||
|
const { href, content } = link;
|
||||||
|
|
||||||
|
const isWebLink = /^https?:\/\//.test(href);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={href}
|
||||||
|
target={isWebLink ? "_blank" : ""}
|
||||||
|
onClick={(ev) => {
|
||||||
|
if (isWebLink) return true;
|
||||||
|
ev.preventDefault();
|
||||||
|
const querySearch = "?query=";
|
||||||
|
if (href.startsWith(querySearch)) {
|
||||||
|
onTermClicked(href.substring(querySearch.length));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{content ? (
|
||||||
|
<StructuredContent node={content} onTermClicked={onTermClicked} />
|
||||||
|
) : null}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block/container elements (span, div, ol, ul, li, ruby, table, etc.)
|
||||||
|
if ("tag" in node) {
|
||||||
|
const block = node as any;
|
||||||
|
|
||||||
|
return h(
|
||||||
|
block.tag,
|
||||||
|
{ style: block.style, title: block.title, open: block.open },
|
||||||
|
StructuredContent({ node: block.content, onTermClicked }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
Reference in new issue
Block a user