Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion web/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1099,7 +1099,6 @@ export const en = {
tabClasses: "Classes",
colName: "Name",
multiParentHint: "This class has more than one parent; the indentation follows one of them, and the Parent column lists them all",
colKey: "Key",
colSignature: "Subject → Object",
colInstances: "Instances",
colFacts: "Facts",
Expand Down
1 change: 0 additions & 1 deletion web/src/i18n/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -984,7 +984,6 @@ export const zh: Strings = {
tabClasses: "类",
colName: "名称",
multiParentHint: "这个类有不止一个父类;缩进挂在其中一个,父类那一列把它们都列着",
colKey: "键",
colSignature: "主语 → 宾语",
colInstances: "实例",
colFacts: "事实",
Expand Down
97 changes: 73 additions & 24 deletions web/src/pages/ontologyTables.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,17 @@
// (#482 里那句「一列卡片跟我们刚重做的任何东西都不像」)。本体是最后一个,
// 也是列数最多的一个。
//
// **不列 key**:它是机器身份(`accepted_answer`),不是人要读的东西。三张表
// 是拿来横着看这个库的——哪些类没实例、哪些属性没 domain——而 key 在每一行
// 里都只是把名字重说一遍。要看它的时候(对词表、写规则)在右侧详情里,
// 那儿一次只讲一个,读得完整。
//
// **层级怎么办**:`subClassOf` 是本体的骨架,拍平就丢了。所以默认按层级排、
// 名字列带缩进;按别的列排序就拍平,而父类那一列一直在,拍平不丢信息,只是
// 换了一种读法。文件管理器就是这么做的。

import { useMemo, useState } from "react";
import { Search } from "lucide-react";

import type { EntityTypeView, RelationTypeView } from "../api";
import { S } from "../i18n";
Expand All @@ -24,9 +30,11 @@ import {
GroupLabel,
Input,
LinkButton,
Pager,
pageSlice,
SkeletonTableRows,
ROW_TRAILING,
Segmented,
SkeletonRows,
Table,
TBody,
Td,
Expand All @@ -37,6 +45,17 @@ import {

export type TableTab = "classes" | "properties" | "attributes";

/** 一页画多少行。
*
* 三张表分别是 916 / 1102 / 599 行,从前一次全画进 DOM——**滚动条那一下就能
* 感觉到**,而且真正在看的从来只有屏幕里那十几行。左栏的类树早就是分页的
* (每页 14),这里只是把同一件事补上;50 比 14 大,是因为整幅宽度的表一屏
* 本来就装得下更多,翻页翻得太勤也烦。
*
* 类那张表分的是**已经拍平的树**,所以一页仍是树序里连续的一段,父子关系
* 不会被切散到两页去(与左栏同一个做法)。 */
const TABLE_PAGE = 50;

/** 这一行挂在谁下面。**没有主父类就退回第一个父类**:`primary_parent` 只在
* 人手动指定时才写,而导入的包一个都没有——schema.org 那 916 个类里是 0 个。
* 只认它的话,任何导入来的本体在树里都是平的一长条,层级完全看不见。
Expand Down Expand Up @@ -148,6 +167,7 @@ function ClassesTable({
onSeeInstances: (t: EntityTypeView) => void;
}) {
const [sort, setSort] = useState<Sort>(null);
const [page, setPage] = useState(0);
const nameOf = useMemo(
() => new Map(types.map((t) => [t.id, t.label])),
[types],
Expand All @@ -171,12 +191,14 @@ function ClassesTable({
: t.label,
), !!sort || !!q);

const { rows: paged, safe } = pageSlice(rows, page, TABLE_PAGE);

return (
<Table>
<>
<Table>
<THead>
<Tr>
<Th>{S.ontology.colName}</Th>
<Th className="hidden md:table-cell">{S.ontology.colKey}</Th>
<SortTh col="parent" sort={sort} onSort={setSort}>
{S.ontology.parent}
</SortTh>
Expand All @@ -190,7 +212,7 @@ function ClassesTable({
</Tr>
</THead>
<TBody>
{rows.map(({ t, depth }) => (
{paged.map(({ t, depth }) => (
<Tr key={t.id} interactive onClick={() => onOpen(t)}>
<Td>
<span
Expand Down Expand Up @@ -218,9 +240,6 @@ function ClassesTable({
{t.builtin && <Chip tone="neutral">{S.ontology.builtin}</Chip>}
</span>
</Td>
<Td className="hidden font-mono text-small text-ink-2 md:table-cell">
{t.key}
</Td>
<Td className="text-small text-ink-2">
{t.parents.length
? t.parents.map((p) => nameOf.get(p) ?? p).join(", ")
Expand Down Expand Up @@ -252,7 +271,16 @@ function ClassesTable({
</Tr>
))}
</TBody>
</Table>
</Table>
{/* 翻页器跟在表后面,不做固定底栏:这一块本来就是滚动区,
再钉一条栏会把最后一行压掉半截 */}
<Pager
total={rows.length}
pageSize={TABLE_PAGE}
page={safe}
onPage={setPage}
/>
</>
);
}

Expand Down Expand Up @@ -294,6 +322,7 @@ function PropertiesTable({
onOpen: (r: RelationTypeView) => void;
}) {
const [sort, setSort] = useState<Sort>(null);
const [page, setPage] = useState(0);
const nameOf = useMemo(
() => new Map(types.map((t) => [t.id, t.label])),
[types],
Expand All @@ -315,12 +344,14 @@ function PropertiesTable({
: r.label,
);

const { rows: paged, safe } = pageSlice(rows, page, TABLE_PAGE);

return (
<Table>
<>
<Table>
<THead>
<Tr>
<Th>{S.ontology.colName}</Th>
<Th className="hidden md:table-cell">{S.ontology.colKey}</Th>
<Th>{S.ontology.colSignature}</Th>
<SortTh col="temporal" sort={sort} onSort={setSort}>
{S.ontology.temporal}
Expand All @@ -333,17 +364,14 @@ function PropertiesTable({
</Tr>
</THead>
<TBody>
{rows.map((r) => (
{paged.map((r) => (
<Tr key={r.id} interactive onClick={() => onOpen(r)}>
<Td>
<span className="flex items-center gap-2">
<span className="truncate">{r.label}</span>
{r.builtin && <Chip tone="neutral">{S.ontology.builtin}</Chip>}
</span>
</Td>
<Td className="hidden font-mono text-small text-ink-2 md:table-cell">
{r.key}
</Td>
<Td className="text-small text-ink-2">
{side(r.domains)} → {side(r.ranges)}
</Td>
Expand All @@ -364,7 +392,16 @@ function PropertiesTable({
</Tr>
))}
</TBody>
</Table>
</Table>
{/* 翻页器跟在表后面,不做固定底栏:这一块本来就是滚动区,
再钉一条栏会把最后一行压掉半截 */}
<Pager
total={rows.length}
pageSize={TABLE_PAGE}
page={safe}
onPage={setPage}
/>
</>
);
}

Expand All @@ -384,6 +421,7 @@ function AttributesTable({
onOpen: (a: RelationTypeView) => void;
}) {
const [sort, setSort] = useState<Sort>(null);
const [page, setPage] = useState(0);
const nameOf = useMemo(
() => new Map(types.map((t) => [t.id, t.label])),
[types],
Expand All @@ -400,12 +438,14 @@ function AttributesTable({
: a.label,
);

const { rows: paged, safe } = pageSlice(rows, page, TABLE_PAGE);

return (
<Table>
<>
<Table>
<THead>
<Tr>
<Th>{S.ontology.colName}</Th>
<Th className="hidden md:table-cell">{S.ontology.colKey}</Th>
<SortTh col="domain" sort={sort} onSort={setSort}>
{S.ontology.colOnClass}
</SortTh>
Expand All @@ -420,12 +460,9 @@ function AttributesTable({
</Tr>
</THead>
<TBody>
{rows.map((a) => (
{paged.map((a) => (
<Tr key={a.id} interactive onClick={() => onOpen(a)}>
<Td className="truncate">{a.label}</Td>
<Td className="hidden font-mono text-small text-ink-2 md:table-cell">
{a.key}
</Td>
<Td className="text-small text-ink-2">
{a.domains.length
? a.domains.map((d) => nameOf.get(d) ?? d).join(", ")
Expand All @@ -440,7 +477,16 @@ function AttributesTable({
</Tr>
))}
</TBody>
</Table>
</Table>
{/* 翻页器跟在表后面,不做固定底栏:这一块本来就是滚动区,
再钉一条栏会把最后一行压掉半截 */}
<Pager
total={rows.length}
pageSize={TABLE_PAGE}
page={safe}
onPage={setPage}
/>
</>
);
}

Expand Down Expand Up @@ -497,7 +543,10 @@ export function OntologyTables({
)}
/>
<Input
icon={<span className="text-ink-2">/</span>}
// 从前这里是一个 `/`,照惯例它是"按斜杠聚焦"的提示——**可这一页没绑
// 那个键**(只有文档页绑了),提示了一个不存在的功能。而左栏那个
// 一模一样的过滤框用的是放大镜,同一页两个图标也说不通
icon={<Search size={12} />}
className="w-58"
placeholder={S.ontology.filter}
value={filter}
Expand All @@ -518,7 +567,7 @@ export function OntologyTables({
<div className="u-scroll min-h-0 flex-1 overflow-y-auto px-8 pb-6">
{/* 表身出骨架,**表头不画**:列名是什么此刻还没定(三张表的列不一样),
画一排灰条冒充列名是在编。分段控件、过滤框、滚动区都已经在了 */}
{loading && <SkeletonRows rows={10} />}
{loading && <SkeletonTableRows rows={10} />}
{!loading && tab === "classes" && (
<ClassesTable
types={entityTypes}
Expand Down
11 changes: 11 additions & 0 deletions web/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -1272,6 +1272,17 @@ a:hover > .u-mark-arrow {
}
}

/* 骨架行里装灰条的那一格:撑**一个行高**,而不是灰条自己的高度。
表格的单元格里本来是一行字(8 + 22 + 8 = 38),少了这一层就只有
8 + 16 + 8 = 32,一列排下来比真表矮一截、密一截。
高度取字号令牌自带的行高,字号改了它跟着改。
左栏那一档不用它:那儿的行本来就矮,套上反而显松(见 SkeletonRows)。 */
.u-skel-line {
display: flex;
align-items: center;
height: var(--text-body--line-height);
}


/* ---- numbers in chrome: 默认字体(Geist)+ 等宽数字,不再用 mono ---- */
.u-num {
Expand Down
36 changes: 36 additions & 0 deletions web/src/ui/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ import {
Search as SearchIcon,
} from "lucide-react";
import { S } from "../i18n";
// 表格原件在 ./table 里,下面 re-export;`SkeletonTableRows` 自己也要用,
// 所以这里另取一份别名,避免与 re-export 的同名标识撞车
import {
Table as SkelTable,
TBody as SkelTBody,
Td as SkelTd,
Tr as SkelTr,
} from "./table";

/** 应用内左栏统一底座:宽度 + 玻璃面(各页在此之上加 flex/padding)。
以最宽的 Ontology(w-64)为基准——rail 装的是名字,宽一档少截断。 */
Expand Down Expand Up @@ -1147,6 +1155,34 @@ export function CanvasLoading({ size = 28 }: { size?: number }) {
);
}

/** 表格版的骨架行。
*
* 与 `SkeletonRows` 同一条道理,只是行的形状换成表的:用**真的 `Tr` / `Td`**
* 搭,行高就由构造保证(8 + 22 + 8 = 38),不靠写死一个数。
*
* 这一条是踩出来的:表格的骨架一度直接用了左栏那一档的行(24px),比真表行
* 矮了三分之一,一列排下来又矮又密,一眼就看得出不是那张表将来的样子。
* 骨架说的是"等的是什么形状",形状说错了就不如不说。 */
export function SkeletonTableRows({ rows = 8 }: { rows?: number }) {
return (
<SkelTable>
<SkelTBody>
{Array.from({ length: rows }, (_, i) => (
<SkelTr key={i}>
<SkelTd>
{/* 这一层撑的是一个行高(见 styles.css 的 .u-skel-line):
真单元格里是一行字,少了它整表矮一截 */}
<span className="u-skel-line">
<Skeleton className="h-4 w-full" />
</span>
</SkelTd>
</SkelTr>
))}
</SkelTBody>
</SkelTable>
);
}

/** 转圈。**形状未知的东西用它**:一整块区域还不知道会画成图、表还是空。
* 形状已知的地方别用它——那里 `Skeleton` 能多说一句"等的是什么"。 */
export function Spinner({
Expand Down
Loading