diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index 826676e8d..7657c2d28 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -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", diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index b2fc1d786..46dc059ed 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -984,7 +984,6 @@ export const zh: Strings = { tabClasses: "类", colName: "名称", multiParentHint: "这个类有不止一个父类;缩进挂在其中一个,父类那一列把它们都列着", - colKey: "键", colSignature: "主语 → 宾语", colInstances: "实例", colFacts: "事实", diff --git a/web/src/pages/ontologyTables.tsx b/web/src/pages/ontologyTables.tsx index 9c6229214..2e5bcf5e1 100644 --- a/web/src/pages/ontologyTables.tsx +++ b/web/src/pages/ontologyTables.tsx @@ -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"; @@ -24,9 +30,11 @@ import { GroupLabel, Input, LinkButton, + Pager, + pageSlice, + SkeletonTableRows, ROW_TRAILING, Segmented, - SkeletonRows, Table, TBody, Td, @@ -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 个。 * 只认它的话,任何导入来的本体在树里都是平的一长条,层级完全看不见。 @@ -148,6 +167,7 @@ function ClassesTable({ onSeeInstances: (t: EntityTypeView) => void; }) { const [sort, setSort] = useState(null); + const [page, setPage] = useState(0); const nameOf = useMemo( () => new Map(types.map((t) => [t.id, t.label])), [types], @@ -171,12 +191,14 @@ function ClassesTable({ : t.label, ), !!sort || !!q); + const { rows: paged, safe } = pageSlice(rows, page, TABLE_PAGE); + return ( - + <> +
- {S.ontology.parent} @@ -190,7 +212,7 @@ function ClassesTable({ - {rows.map(({ t, depth }) => ( + {paged.map(({ t, depth }) => ( onOpen(t)}> - ))} -
{S.ontology.colName}{S.ontology.colKey}
{S.ontology.builtin}} - {t.key} - {t.parents.length ? t.parents.map((p) => nameOf.get(p) ?? p).join(", ") @@ -252,7 +271,16 @@ function ClassesTable({
+ + {/* 翻页器跟在表后面,不做固定底栏:这一块本来就是滚动区, + 再钉一条栏会把最后一行压掉半截 */} + + ); } @@ -294,6 +322,7 @@ function PropertiesTable({ onOpen: (r: RelationTypeView) => void; }) { const [sort, setSort] = useState(null); + const [page, setPage] = useState(0); const nameOf = useMemo( () => new Map(types.map((t) => [t.id, t.label])), [types], @@ -315,12 +344,14 @@ function PropertiesTable({ : r.label, ); + const { rows: paged, safe } = pageSlice(rows, page, TABLE_PAGE); + return ( - + <> +
- {S.ontology.temporal} @@ -333,7 +364,7 @@ function PropertiesTable({ - {rows.map((r) => ( + {paged.map((r) => ( onOpen(r)}> - @@ -364,7 +392,16 @@ function PropertiesTable({ ))} -
{S.ontology.colName}{S.ontology.colKey} {S.ontology.colSignature}
@@ -341,9 +372,6 @@ function PropertiesTable({ {r.builtin && {S.ontology.builtin}} - {r.key} - {side(r.domains)} → {side(r.ranges)}
+ + {/* 翻页器跟在表后面,不做固定底栏:这一块本来就是滚动区, + 再钉一条栏会把最后一行压掉半截 */} + + ); } @@ -384,6 +421,7 @@ function AttributesTable({ onOpen: (a: RelationTypeView) => void; }) { const [sort, setSort] = useState(null); + const [page, setPage] = useState(0); const nameOf = useMemo( () => new Map(types.map((t) => [t.id, t.label])), [types], @@ -400,12 +438,14 @@ function AttributesTable({ : a.label, ); + const { rows: paged, safe } = pageSlice(rows, page, TABLE_PAGE); + return ( - + <> +
- {S.ontology.colOnClass} @@ -420,12 +460,9 @@ function AttributesTable({ - {rows.map((a) => ( + {paged.map((a) => ( onOpen(a)}> - ))} -
{S.ontology.colName}{S.ontology.colKey}
{a.label} - {a.key} - {a.domains.length ? a.domains.map((d) => nameOf.get(d) ?? d).join(", ") @@ -440,7 +477,16 @@ function AttributesTable({
+ + {/* 翻页器跟在表后面,不做固定底栏:这一块本来就是滚动区, + 再钉一条栏会把最后一行压掉半截 */} + + ); } @@ -497,7 +543,10 @@ export function OntologyTables({ )} /> /} + // 从前这里是一个 `/`,照惯例它是"按斜杠聚焦"的提示——**可这一页没绑 + // 那个键**(只有文档页绑了),提示了一个不存在的功能。而左栏那个 + // 一模一样的过滤框用的是放大镜,同一页两个图标也说不通 + icon={} className="w-58" placeholder={S.ontology.filter} value={filter} @@ -518,7 +567,7 @@ export function OntologyTables({
{/* 表身出骨架,**表头不画**:列名是什么此刻还没定(三张表的列不一样), 画一排灰条冒充列名是在编。分段控件、过滤框、滚动区都已经在了 */} - {loading && } + {loading && } {!loading && tab === "classes" && ( .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 { diff --git a/web/src/ui/index.tsx b/web/src/ui/index.tsx index e4774f8e2..1ff47e48a 100644 --- a/web/src/ui/index.tsx +++ b/web/src/ui/index.tsx @@ -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 装的是名字,宽一档少截断。 */ @@ -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 ( + + + {Array.from({ length: rows }, (_, i) => ( + + + {/* 这一层撑的是一个行高(见 styles.css 的 .u-skel-line): + 真单元格里是一行字,少了它整表矮一截 */} + + + + + + ))} + + + ); +} + /** 转圈。**形状未知的东西用它**:一整块区域还不知道会画成图、表还是空。 * 形状已知的地方别用它——那里 `Skeleton` 能多说一句"等的是什么"。 */ export function Spinner({