@stapel/categories-react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1 -0
- package/MODULE.md +194 -0
- package/README.md +191 -0
- package/dist/api/categoriesApi.d.ts +121 -0
- package/dist/api/categoriesApi.d.ts.map +1 -0
- package/dist/api/categoriesApi.js +48 -0
- package/dist/api/categoriesApi.js.map +1 -0
- package/dist/api/generated/schema.d.ts +2321 -0
- package/dist/api/generated/schema.d.ts.map +1 -0
- package/dist/api/generated/schema.js +2 -0
- package/dist/api/generated/schema.js.map +1 -0
- package/dist/api/types.d.ts +92 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +2 -0
- package/dist/api/types.js.map +1 -0
- package/dist/catalog/labels.d.ts +103 -0
- package/dist/catalog/labels.d.ts.map +1 -0
- package/dist/catalog/labels.js +56 -0
- package/dist/catalog/labels.js.map +1 -0
- package/dist/catalog/pks.d.ts +25 -0
- package/dist/catalog/pks.d.ts.map +1 -0
- package/dist/catalog/pks.js +38 -0
- package/dist/catalog/pks.js.map +1 -0
- package/dist/catalog/sync.d.ts +103 -0
- package/dist/catalog/sync.d.ts.map +1 -0
- package/dist/catalog/sync.js +114 -0
- package/dist/catalog/sync.js.map +1 -0
- package/dist/catalog/tree.d.ts +119 -0
- package/dist/catalog/tree.d.ts.map +1 -0
- package/dist/catalog/tree.js +166 -0
- package/dist/catalog/tree.js.map +1 -0
- package/dist/default/CatalogPage.d.ts +16 -0
- package/dist/default/CatalogPage.d.ts.map +1 -0
- package/dist/default/CatalogPage.js +13 -0
- package/dist/default/CatalogPage.js.map +1 -0
- package/dist/default/CategoryBreadcrumbsBar.d.ts +20 -0
- package/dist/default/CategoryBreadcrumbsBar.d.ts.map +1 -0
- package/dist/default/CategoryBreadcrumbsBar.js +35 -0
- package/dist/default/CategoryBreadcrumbsBar.js.map +1 -0
- package/dist/default/CategoryCarouselStrip.d.ts +22 -0
- package/dist/default/CategoryCarouselStrip.d.ts.map +1 -0
- package/dist/default/CategoryCarouselStrip.js +24 -0
- package/dist/default/CategoryCarouselStrip.js.map +1 -0
- package/dist/default/CategoryFeatureList.d.ts +23 -0
- package/dist/default/CategoryFeatureList.d.ts.map +1 -0
- package/dist/default/CategoryFeatureList.js +28 -0
- package/dist/default/CategoryFeatureList.js.map +1 -0
- package/dist/default/CategoryPage.d.ts +43 -0
- package/dist/default/CategoryPage.d.ts.map +1 -0
- package/dist/default/CategoryPage.js +21 -0
- package/dist/default/CategoryPage.js.map +1 -0
- package/dist/default/CategoryPickerField.d.ts +26 -0
- package/dist/default/CategoryPickerField.d.ts.map +1 -0
- package/dist/default/CategoryPickerField.js +40 -0
- package/dist/default/CategoryPickerField.js.map +1 -0
- package/dist/default/CategoryTreePane.d.ts +28 -0
- package/dist/default/CategoryTreePane.d.ts.map +1 -0
- package/dist/default/CategoryTreePane.js +25 -0
- package/dist/default/CategoryTreePane.js.map +1 -0
- package/dist/default/ErrorAlert.d.ts +9 -0
- package/dist/default/ErrorAlert.d.ts.map +1 -0
- package/dist/default/ErrorAlert.js +27 -0
- package/dist/default/ErrorAlert.js.map +1 -0
- package/dist/default/index.d.ts +37 -0
- package/dist/default/index.d.ts.map +1 -0
- package/dist/default/index.js +28 -0
- package/dist/default/index.js.map +1 -0
- package/dist/default/theme.d.ts +25 -0
- package/dist/default/theme.d.ts.map +1 -0
- package/dist/default/theme.js +40 -0
- package/dist/default/theme.js.map +1 -0
- package/dist/default/types.d.ts +12 -0
- package/dist/default/types.d.ts.map +1 -0
- package/dist/default/types.js +2 -0
- package/dist/default/types.js.map +1 -0
- package/dist/headless/CategoriesProvider.d.ts +19 -0
- package/dist/headless/CategoriesProvider.d.ts.map +1 -0
- package/dist/headless/CategoriesProvider.js +15 -0
- package/dist/headless/CategoriesProvider.js.map +1 -0
- package/dist/headless/CategoryBreadcrumbs.d.ts +40 -0
- package/dist/headless/CategoryBreadcrumbs.d.ts.map +1 -0
- package/dist/headless/CategoryBreadcrumbs.js +42 -0
- package/dist/headless/CategoryBreadcrumbs.js.map +1 -0
- package/dist/headless/CategoryCarousel.d.ts +48 -0
- package/dist/headless/CategoryCarousel.d.ts.map +1 -0
- package/dist/headless/CategoryCarousel.js +34 -0
- package/dist/headless/CategoryCarousel.js.map +1 -0
- package/dist/headless/CategoryFeatures.d.ts +67 -0
- package/dist/headless/CategoryFeatures.d.ts.map +1 -0
- package/dist/headless/CategoryFeatures.js +46 -0
- package/dist/headless/CategoryFeatures.js.map +1 -0
- package/dist/headless/CategoryPicker.d.ts +69 -0
- package/dist/headless/CategoryPicker.d.ts.map +1 -0
- package/dist/headless/CategoryPicker.js +86 -0
- package/dist/headless/CategoryPicker.js.map +1 -0
- package/dist/headless/CategoryTree.d.ts +55 -0
- package/dist/headless/CategoryTree.d.ts.map +1 -0
- package/dist/headless/CategoryTree.js +51 -0
- package/dist/headless/CategoryTree.js.map +1 -0
- package/dist/i18n/errorsMap.d.ts +22 -0
- package/dist/i18n/errorsMap.d.ts.map +1 -0
- package/dist/i18n/errorsMap.js +31 -0
- package/dist/i18n/errorsMap.js.map +1 -0
- package/dist/i18n/es.d.ts +25 -0
- package/dist/i18n/es.d.ts.map +1 -0
- package/dist/i18n/es.js +74 -0
- package/dist/i18n/es.js.map +1 -0
- package/dist/i18n/generated/errors.es.gen.d.ts +16 -0
- package/dist/i18n/generated/errors.es.gen.d.ts.map +1 -0
- package/dist/i18n/generated/errors.es.gen.js +58 -0
- package/dist/i18n/generated/errors.es.gen.js.map +1 -0
- package/dist/i18n/generated/errors.gen.d.ts +407 -0
- package/dist/i18n/generated/errors.gen.d.ts.map +1 -0
- package/dist/i18n/generated/errors.gen.js +207 -0
- package/dist/i18n/generated/errors.gen.js.map +1 -0
- package/dist/i18n/generated/errors.ru.gen.d.ts +16 -0
- package/dist/i18n/generated/errors.ru.gen.d.ts.map +1 -0
- package/dist/i18n/generated/errors.ru.gen.js +58 -0
- package/dist/i18n/generated/errors.ru.gen.js.map +1 -0
- package/dist/i18n/keys.d.ts +89 -0
- package/dist/i18n/keys.d.ts.map +1 -0
- package/dist/i18n/keys.js +134 -0
- package/dist/i18n/keys.js.map +1 -0
- package/dist/i18n/ru.d.ts +41 -0
- package/dist/i18n/ru.d.ts.map +1 -0
- package/dist/i18n/ru.js +90 -0
- package/dist/i18n/ru.js.map +1 -0
- package/dist/index.d.ts +88 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +78 -0
- package/dist/index.js.map +1 -0
- package/dist/model/catalogStore.d.ts +23 -0
- package/dist/model/catalogStore.d.ts.map +1 -0
- package/dist/model/catalogStore.js +85 -0
- package/dist/model/catalogStore.js.map +1 -0
- package/dist/model/catalogSync.d.ts +49 -0
- package/dist/model/catalogSync.d.ts.map +1 -0
- package/dist/model/catalogSync.js +64 -0
- package/dist/model/catalogSync.js.map +1 -0
- package/dist/model/context.d.ts +11 -0
- package/dist/model/context.d.ts.map +1 -0
- package/dist/model/context.js +16 -0
- package/dist/model/context.js.map +1 -0
- package/dist/model/queries.d.ts +124 -0
- package/dist/model/queries.d.ts.map +1 -0
- package/dist/model/queries.js +147 -0
- package/dist/model/queries.js.map +1 -0
- package/dist/model/queryKeys.d.ts +30 -0
- package/dist/model/queryKeys.d.ts.map +1 -0
- package/dist/model/queryKeys.js +16 -0
- package/dist/model/queryKeys.js.map +1 -0
- package/dist/model/runtime.d.ts +31 -0
- package/dist/model/runtime.d.ts.map +1 -0
- package/dist/model/runtime.js +6 -0
- package/dist/model/runtime.js.map +1 -0
- package/dist/nav/manifest.d.ts +31 -0
- package/dist/nav/manifest.d.ts.map +1 -0
- package/dist/nav/manifest.js +37 -0
- package/dist/nav/manifest.js.map +1 -0
- package/llms.txt +125 -0
- package/manifest.json +918 -0
- package/nav-manifest.json +44 -0
- package/package.json +119 -0
- package/src/analytics/generated/events.json +7 -0
- package/src/api/categoriesApi.ts +179 -0
- package/src/api/generated/schema.ts +2323 -0
- package/src/api/types.ts +99 -0
- package/src/catalog/labels.ts +129 -0
- package/src/catalog/pks.ts +35 -0
- package/src/catalog/sync.ts +169 -0
- package/src/catalog/tree.ts +242 -0
- package/src/default/CatalogPage.tsx +40 -0
- package/src/default/CategoryBreadcrumbsBar.tsx +93 -0
- package/src/default/CategoryCarouselStrip.tsx +102 -0
- package/src/default/CategoryFeatureList.tsx +125 -0
- package/src/default/CategoryPage.tsx +114 -0
- package/src/default/CategoryPickerField.tsx +173 -0
- package/src/default/CategoryTreePane.tsx +134 -0
- package/src/default/ErrorAlert.tsx +46 -0
- package/src/default/index.ts +36 -0
- package/src/default/theme.tsx +67 -0
- package/src/default/types.ts +12 -0
- package/src/headless/CategoriesProvider.tsx +20 -0
- package/src/headless/CategoryBreadcrumbs.tsx +78 -0
- package/src/headless/CategoryCarousel.tsx +80 -0
- package/src/headless/CategoryFeatures.tsx +100 -0
- package/src/headless/CategoryPicker.tsx +173 -0
- package/src/headless/CategoryTree.tsx +115 -0
- package/src/i18n/errorsMap.ts +46 -0
- package/src/i18n/es.ts +92 -0
- package/src/i18n/generated/errors.es.gen.ts +64 -0
- package/src/i18n/generated/errors.gen.ts +238 -0
- package/src/i18n/generated/errors.json +495 -0
- package/src/i18n/generated/errors.ru.gen.ts +64 -0
- package/src/i18n/keys.ts +156 -0
- package/src/i18n/ru.ts +110 -0
- package/src/index.ts +191 -0
- package/src/model/catalogStore.ts +102 -0
- package/src/model/catalogSync.ts +115 -0
- package/src/model/context.tsx +28 -0
- package/src/model/queries.ts +226 -0
- package/src/model/queryKeys.ts +50 -0
- package/src/model/runtime.ts +39 -0
- package/src/nav/manifest.ts +66 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/** An empty snapshot — the state before the first sync. */
|
|
2
|
+
export const EMPTY_SNAPSHOT = {
|
|
3
|
+
version: 1,
|
|
4
|
+
cursor: 0,
|
|
5
|
+
rows: [],
|
|
6
|
+
};
|
|
7
|
+
/** A snapshot with no rows has never synced, whatever its cursor says. */
|
|
8
|
+
export function isEmptySnapshot(snapshot) {
|
|
9
|
+
return snapshot.rows.length === 0;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Accept a value read back from persistence, or reject it.
|
|
13
|
+
*
|
|
14
|
+
* A repository read can hand back anything: a snapshot written by a previous
|
|
15
|
+
* version of this pair, a half-written value, `undefined` after a cleared
|
|
16
|
+
* origin. Every one of those must degrade to "no snapshot" — a cache that
|
|
17
|
+
* throws on read is worse than no cache.
|
|
18
|
+
*/
|
|
19
|
+
export function parseSnapshot(value) {
|
|
20
|
+
if (typeof value !== "object" || value === null)
|
|
21
|
+
return undefined;
|
|
22
|
+
const candidate = value;
|
|
23
|
+
if (candidate.version !== 1)
|
|
24
|
+
return undefined;
|
|
25
|
+
if (typeof candidate.cursor !== "number" || !Number.isFinite(candidate.cursor)) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
if (!Array.isArray(candidate.rows))
|
|
29
|
+
return undefined;
|
|
30
|
+
for (const row of candidate.rows) {
|
|
31
|
+
if (typeof row !== "object" || row === null)
|
|
32
|
+
return undefined;
|
|
33
|
+
if (typeof row.id !== "number")
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
version: 1,
|
|
38
|
+
cursor: candidate.cursor,
|
|
39
|
+
rows: candidate.rows,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The request that starts a sync from `snapshot`.
|
|
44
|
+
*
|
|
45
|
+
* A cold start asks for everything with `include_deleted: false` — nothing is
|
|
46
|
+
* cached, so a tombstone has nothing to evict and the payload is smaller. A
|
|
47
|
+
* warm start asks for the delta with `include_deleted: true`, because there
|
|
48
|
+
* the tombstones are the point.
|
|
49
|
+
*/
|
|
50
|
+
export function firstPageRequest(snapshot) {
|
|
51
|
+
if (isEmptySnapshot(snapshot)) {
|
|
52
|
+
return { includeDeleted: false, page: 1 };
|
|
53
|
+
}
|
|
54
|
+
return { minRevision: snapshot.cursor, includeDeleted: true, page: 1 };
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The request for the next page of an in-flight walk, or `undefined` when the
|
|
58
|
+
* walk is done.
|
|
59
|
+
*
|
|
60
|
+
* `maxRevision` pins the window to what the first page reported, so the walk
|
|
61
|
+
* reads one consistent snapshot of the table instead of a moving one.
|
|
62
|
+
*/
|
|
63
|
+
export function nextPageRequest(previous, page) {
|
|
64
|
+
if (!page.pagination.has_next)
|
|
65
|
+
return undefined;
|
|
66
|
+
return {
|
|
67
|
+
...previous,
|
|
68
|
+
page: page.pagination.page + 1,
|
|
69
|
+
maxRevision: previous.maxRevision ?? page.revisions.global_max,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Fold one page into an accumulator.
|
|
74
|
+
*
|
|
75
|
+
* There is one merge rule, and the FULL/DELTA distinction lives in what the
|
|
76
|
+
* caller starts from, not in a flag here: a delta folds into the stored
|
|
77
|
+
* snapshot, a full sync folds into {@link EMPTY_SNAPSHOT}. That is the whole
|
|
78
|
+
* difference — a row the server no longer lists is gone from a full listing
|
|
79
|
+
* precisely because it was never folded in. Expressing it as a flag on the
|
|
80
|
+
* merge invites the two silent failures instead: a delta treated as full
|
|
81
|
+
* empties the catalogue, a full treated as delta resurrects rows deleted while
|
|
82
|
+
* the client was away.
|
|
83
|
+
*
|
|
84
|
+
* The new snapshot is not published until the walk COMPLETES (see
|
|
85
|
+
* `model/catalogSync.ts`), so a walk that fails halfway leaves the previous
|
|
86
|
+
* catalogue on screen rather than a truncated one.
|
|
87
|
+
*
|
|
88
|
+
* The cursor only ever moves FORWARD, and never past `cursorLimit`:
|
|
89
|
+
* `global_max` is the table's live maximum, which can already exceed the
|
|
90
|
+
* window a walk pinned with `max_revision`. Taking it verbatim would record
|
|
91
|
+
* having read rows the walk never asked for.
|
|
92
|
+
*/
|
|
93
|
+
export function applyCategoryPage(accumulator, page, options = {}) {
|
|
94
|
+
const byId = new Map();
|
|
95
|
+
for (const row of accumulator.rows)
|
|
96
|
+
byId.set(row.id, row);
|
|
97
|
+
for (const row of page.results) {
|
|
98
|
+
if (row.deleted === true)
|
|
99
|
+
byId.delete(row.id);
|
|
100
|
+
else
|
|
101
|
+
byId.set(row.id, row);
|
|
102
|
+
}
|
|
103
|
+
for (const id of page.revisions.deleted_ids)
|
|
104
|
+
byId.delete(id);
|
|
105
|
+
const observed = options.cursorLimit !== undefined
|
|
106
|
+
? Math.min(page.revisions.global_max, options.cursorLimit)
|
|
107
|
+
: page.revisions.global_max;
|
|
108
|
+
return {
|
|
109
|
+
version: 1,
|
|
110
|
+
cursor: Math.max(accumulator.cursor, observed),
|
|
111
|
+
rows: [...byId.values()],
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/catalog/sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tree the server does not build.
|
|
3
|
+
*
|
|
4
|
+
* `stapel-categories` has no tree endpoint. `GET /categories/` returns FLAT
|
|
5
|
+
* rows ordered by `revision`, each carrying `tn_parent`, `tn_priority` and the
|
|
6
|
+
* treenode ancestry columns — the client assembles the hierarchy (spec §4.3).
|
|
7
|
+
* Everything in this file is pure: no React, no fetch, no storage, so the same
|
|
8
|
+
* functions serve a browser, an SSR render and a test.
|
|
9
|
+
*
|
|
10
|
+
* ── Three server facts this module encodes, each a bug if forgotten ────────
|
|
11
|
+
*
|
|
12
|
+
* 1. **`deleted` rows are served.** `include_deleted` defaults to `true`, and
|
|
13
|
+
* the delta protocol NEEDS it that way. A tree built without filtering
|
|
14
|
+
* shows soft-deleted categories in a menu.
|
|
15
|
+
* 2. **`active: false` rows are served too**, and nothing on the list endpoint
|
|
16
|
+
* filters them — only `carousel` does. "Active" is the storefront's
|
|
17
|
+
* visibility switch, so the public tree drops them by default and the
|
|
18
|
+
* option to keep them is explicit.
|
|
19
|
+
* 3. **Order is `tn_priority` DESCENDING**, which is what both `children` and
|
|
20
|
+
* `carousel` do server-side (`views.py: order_by("-tn_priority")`). Ties
|
|
21
|
+
* are broken by `id` so a rebuild is deterministic — priority defaults to
|
|
22
|
+
* `0`, so a catalogue that never set it is ALL ties, and an unstable order
|
|
23
|
+
* there means the menu reshuffles between renders.
|
|
24
|
+
*/
|
|
25
|
+
import type { Category } from "../api/types.js";
|
|
26
|
+
/** One node of the assembled tree. */
|
|
27
|
+
export interface CategoryNode {
|
|
28
|
+
readonly category: Category;
|
|
29
|
+
readonly id: number;
|
|
30
|
+
readonly depth: number;
|
|
31
|
+
readonly children: readonly CategoryNode[];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A built catalogue: the rows, plus the lookups a screen actually asks for.
|
|
35
|
+
*
|
|
36
|
+
* Built once per snapshot and shared, because every one of these questions is
|
|
37
|
+
* asked on every render of every category page.
|
|
38
|
+
*/
|
|
39
|
+
export interface CategoryIndex {
|
|
40
|
+
/** Roots, in display order. */
|
|
41
|
+
readonly roots: readonly CategoryNode[];
|
|
42
|
+
/** Every node by id, including ones filtered out of `roots`' subtrees? No —
|
|
43
|
+
* only the nodes that survived the filter. What is not in the tree is not
|
|
44
|
+
* addressable. */
|
|
45
|
+
readonly byId: ReadonlyMap<number, CategoryNode>;
|
|
46
|
+
/** Slug → node. See `resolveCategorySlug` for the collision rule. */
|
|
47
|
+
readonly bySlug: ReadonlyMap<string, CategoryNode>;
|
|
48
|
+
/** How many rows the source snapshot had before filtering. */
|
|
49
|
+
readonly totalRows: number;
|
|
50
|
+
}
|
|
51
|
+
export interface BuildCategoryTreeOptions {
|
|
52
|
+
/** Keep `deleted: true` rows. Default `false`. */
|
|
53
|
+
readonly includeDeleted?: boolean;
|
|
54
|
+
/** Keep `active: false` rows. Default `false` — the storefront's answer.
|
|
55
|
+
* A catalogue admin passes `true`. */
|
|
56
|
+
readonly includeInactive?: boolean;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Assemble flat rows into a tree.
|
|
60
|
+
*
|
|
61
|
+
* A row whose `tn_parent` is not in the surviving set becomes a ROOT rather
|
|
62
|
+
* than disappearing. That is not tidiness — it is the only safe answer to the
|
|
63
|
+
* two ways it legitimately happens: a delta page that brought a child before
|
|
64
|
+
* its parent, and a parent filtered out by `active: false` while the child was
|
|
65
|
+
* left active by a catalogue editor. Dropping the subtree would delete a live
|
|
66
|
+
* branch of the catalogue from the menu and give nobody a reason.
|
|
67
|
+
*/
|
|
68
|
+
export declare function buildCategoryTree(rows: Iterable<Category>, options?: BuildCategoryTreeOptions): CategoryIndex;
|
|
69
|
+
/**
|
|
70
|
+
* Resolve a URL slug to a category — the lookup the SERVER does not offer.
|
|
71
|
+
*
|
|
72
|
+
* `CategoryViewSet` never overrides `lookup_field` and the list endpoint has
|
|
73
|
+
* no slug filter, so `GET /categories/<slug>/` is a 404 and `?slug=` is
|
|
74
|
+
* ignored. The storefront's `/c/:slug` therefore resolves against the synced
|
|
75
|
+
* tree (spec §4.3), which is one more reason the catalogue is cached rather
|
|
76
|
+
* than fetched per page.
|
|
77
|
+
*
|
|
78
|
+
* `Category.slug` is `unique=True` at the model level, so a collision cannot
|
|
79
|
+
* come from the database. It CAN come from the client's own snapshot — a
|
|
80
|
+
* delta that renamed a slug from A to B arrives as one row, and a stale row
|
|
81
|
+
* still holding B has not been evicted yet. First-in-display-order wins,
|
|
82
|
+
* deterministically, and the loser is reachable by id.
|
|
83
|
+
*/
|
|
84
|
+
export declare function resolveCategorySlug(index: CategoryIndex, slug: string): CategoryNode | undefined;
|
|
85
|
+
/**
|
|
86
|
+
* The path from the root down to `id`, inclusive — the breadcrumb.
|
|
87
|
+
*
|
|
88
|
+
* Walks `tn_parent` through the built index rather than trusting
|
|
89
|
+
* `tn_ancestors_pks`, because the index is what the screen is showing: if a
|
|
90
|
+
* filter removed an ancestor, the crumb must not name a category the visitor
|
|
91
|
+
* cannot open. `categoryAncestorIds` is the other answer — the server's — and
|
|
92
|
+
* the two are compared in `test/tree.test.ts` precisely so a divergence is a
|
|
93
|
+
* red test rather than a subtly wrong crumb.
|
|
94
|
+
*
|
|
95
|
+
* An unknown id gives `[]`, not a partial path: half a breadcrumb reads as a
|
|
96
|
+
* top-level category.
|
|
97
|
+
*/
|
|
98
|
+
export declare function categoryBreadcrumbs(index: CategoryIndex, id: number | null | undefined): readonly CategoryNode[];
|
|
99
|
+
/**
|
|
100
|
+
* The server's own answer to "who are this row's ancestors", root-first, from
|
|
101
|
+
* `tn_ancestors_pks`.
|
|
102
|
+
*
|
|
103
|
+
* Useful without a built index (a single category fetched on its own), and the
|
|
104
|
+
* cross-check for {@link categoryBreadcrumbs}.
|
|
105
|
+
*/
|
|
106
|
+
export declare function categoryAncestorIds(category: Category): readonly number[];
|
|
107
|
+
/**
|
|
108
|
+
* The server's own answer to "who are this row's children", from
|
|
109
|
+
* `tn_children_pks`.
|
|
110
|
+
*
|
|
111
|
+
* This is the FULL child set, including soft-deleted and inactive rows — the
|
|
112
|
+
* column is maintained by treenode, which knows nothing about either flag. Use
|
|
113
|
+
* it to detect "this category has sub-categories at all"; use the built node's
|
|
114
|
+
* `children` to render them.
|
|
115
|
+
*/
|
|
116
|
+
export declare function categoryChildIds(category: Category): readonly number[];
|
|
117
|
+
/** Depth-first walk of a subtree (or the whole forest), in display order. */
|
|
118
|
+
export declare function flattenCategoryNodes(nodes: readonly CategoryNode[]): readonly CategoryNode[];
|
|
119
|
+
//# sourceMappingURL=tree.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree.d.ts","sourceRoot":"","sources":["../../src/catalog/tree.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAGhD,sCAAsC;AACtC,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;CAC5C;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,+BAA+B;IAC/B,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAC;IACxC;;sBAEkB;IAClB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACjD,qEAAqE;IACrE,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACnD,8DAA8D;IAC9D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,wBAAwB;IACvC,kDAAkD;IAClD,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAClC;0CACsC;IACtC,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;CACpC;AAkBD;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,QAAQ,CAAC,QAAQ,CAAC,EACxB,OAAO,GAAE,wBAA6B,GACrC,aAAa,CAuDf;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,aAAa,EACpB,IAAI,EAAE,MAAM,GACX,YAAY,GAAG,SAAS,CAE1B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,aAAa,EACpB,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC5B,SAAS,YAAY,EAAE,CAgBzB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,MAAM,EAAE,CAEzE;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,MAAM,EAAE,CAEtE;AAED,6EAA6E;AAC7E,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,SAAS,YAAY,EAAE,GAC7B,SAAS,YAAY,EAAE,CAUzB"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { parseTreenodePks } from "./pks.js";
|
|
2
|
+
function displayOrder(a, b) {
|
|
3
|
+
const pa = a.tn_priority ?? 0;
|
|
4
|
+
const pb = b.tn_priority ?? 0;
|
|
5
|
+
if (pa !== pb)
|
|
6
|
+
return pb - pa;
|
|
7
|
+
return a.id - b.id;
|
|
8
|
+
}
|
|
9
|
+
function keeps(row, options) {
|
|
10
|
+
if (row.deleted === true && options.includeDeleted !== true)
|
|
11
|
+
return false;
|
|
12
|
+
// `active` is optional in the schema and defaults to true on the model, so
|
|
13
|
+
// the test is "not explicitly false" — an absent field is an active
|
|
14
|
+
// category, never a hidden one.
|
|
15
|
+
if (row.active === false && options.includeInactive !== true)
|
|
16
|
+
return false;
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Assemble flat rows into a tree.
|
|
21
|
+
*
|
|
22
|
+
* A row whose `tn_parent` is not in the surviving set becomes a ROOT rather
|
|
23
|
+
* than disappearing. That is not tidiness — it is the only safe answer to the
|
|
24
|
+
* two ways it legitimately happens: a delta page that brought a child before
|
|
25
|
+
* its parent, and a parent filtered out by `active: false` while the child was
|
|
26
|
+
* left active by a catalogue editor. Dropping the subtree would delete a live
|
|
27
|
+
* branch of the catalogue from the menu and give nobody a reason.
|
|
28
|
+
*/
|
|
29
|
+
export function buildCategoryTree(rows, options = {}) {
|
|
30
|
+
const all = [...rows];
|
|
31
|
+
const kept = all.filter((row) => keeps(row, options)).sort(displayOrder);
|
|
32
|
+
const childrenOf = new Map();
|
|
33
|
+
const present = new Set(kept.map((row) => row.id));
|
|
34
|
+
const roots = [];
|
|
35
|
+
for (const row of kept) {
|
|
36
|
+
const parent = row.tn_parent;
|
|
37
|
+
if (parent === null || parent === undefined || !present.has(parent)) {
|
|
38
|
+
roots.push(row);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const bucket = childrenOf.get(parent);
|
|
42
|
+
if (bucket === undefined)
|
|
43
|
+
childrenOf.set(parent, [row]);
|
|
44
|
+
else
|
|
45
|
+
bucket.push(row);
|
|
46
|
+
}
|
|
47
|
+
const byId = new Map();
|
|
48
|
+
const bySlug = new Map();
|
|
49
|
+
// Iterative build (not recursion): a catalogue is authored data, and a cycle
|
|
50
|
+
// introduced by a bad `tn_parent` would blow the stack instead of rendering.
|
|
51
|
+
// `seen` makes a cycle a finite, visible truncation.
|
|
52
|
+
function build(row, depth, seen) {
|
|
53
|
+
const kids = childrenOf.get(row.id) ?? [];
|
|
54
|
+
const nextSeen = new Set(seen).add(row.id);
|
|
55
|
+
const node = {
|
|
56
|
+
category: row,
|
|
57
|
+
id: row.id,
|
|
58
|
+
depth,
|
|
59
|
+
children: kids
|
|
60
|
+
.filter((kid) => !nextSeen.has(kid.id))
|
|
61
|
+
.map((kid) => build(kid, depth + 1, nextSeen)),
|
|
62
|
+
};
|
|
63
|
+
byId.set(row.id, node);
|
|
64
|
+
if (row.slug !== "" && !bySlug.has(row.slug))
|
|
65
|
+
bySlug.set(row.slug, node);
|
|
66
|
+
return node;
|
|
67
|
+
}
|
|
68
|
+
const builtRoots = roots.map((row) => build(row, 0, new Set()));
|
|
69
|
+
// A row that no root can reach is a row inside a PARENT CYCLE (`a.parent =
|
|
70
|
+
// b, b.parent = a` — authored data, so possible). The recursion above
|
|
71
|
+
// refuses to follow the cycle, which keeps the stack safe but would drop
|
|
72
|
+
// those rows entirely: they would vanish from the menu with no error
|
|
73
|
+
// anywhere. Promote whatever is left to roots instead, so a broken branch
|
|
74
|
+
// is VISIBLE and fixable rather than absent.
|
|
75
|
+
for (const row of kept) {
|
|
76
|
+
if (byId.has(row.id))
|
|
77
|
+
continue;
|
|
78
|
+
builtRoots.push(build(row, 0, new Set()));
|
|
79
|
+
}
|
|
80
|
+
return { roots: builtRoots, byId, bySlug, totalRows: all.length };
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Resolve a URL slug to a category — the lookup the SERVER does not offer.
|
|
84
|
+
*
|
|
85
|
+
* `CategoryViewSet` never overrides `lookup_field` and the list endpoint has
|
|
86
|
+
* no slug filter, so `GET /categories/<slug>/` is a 404 and `?slug=` is
|
|
87
|
+
* ignored. The storefront's `/c/:slug` therefore resolves against the synced
|
|
88
|
+
* tree (spec §4.3), which is one more reason the catalogue is cached rather
|
|
89
|
+
* than fetched per page.
|
|
90
|
+
*
|
|
91
|
+
* `Category.slug` is `unique=True` at the model level, so a collision cannot
|
|
92
|
+
* come from the database. It CAN come from the client's own snapshot — a
|
|
93
|
+
* delta that renamed a slug from A to B arrives as one row, and a stale row
|
|
94
|
+
* still holding B has not been evicted yet. First-in-display-order wins,
|
|
95
|
+
* deterministically, and the loser is reachable by id.
|
|
96
|
+
*/
|
|
97
|
+
export function resolveCategorySlug(index, slug) {
|
|
98
|
+
return index.bySlug.get(slug);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The path from the root down to `id`, inclusive — the breadcrumb.
|
|
102
|
+
*
|
|
103
|
+
* Walks `tn_parent` through the built index rather than trusting
|
|
104
|
+
* `tn_ancestors_pks`, because the index is what the screen is showing: if a
|
|
105
|
+
* filter removed an ancestor, the crumb must not name a category the visitor
|
|
106
|
+
* cannot open. `categoryAncestorIds` is the other answer — the server's — and
|
|
107
|
+
* the two are compared in `test/tree.test.ts` precisely so a divergence is a
|
|
108
|
+
* red test rather than a subtly wrong crumb.
|
|
109
|
+
*
|
|
110
|
+
* An unknown id gives `[]`, not a partial path: half a breadcrumb reads as a
|
|
111
|
+
* top-level category.
|
|
112
|
+
*/
|
|
113
|
+
export function categoryBreadcrumbs(index, id) {
|
|
114
|
+
if (id === null || id === undefined)
|
|
115
|
+
return [];
|
|
116
|
+
const start = index.byId.get(id);
|
|
117
|
+
if (start === undefined)
|
|
118
|
+
return [];
|
|
119
|
+
const path = [start];
|
|
120
|
+
const seen = new Set([start.id]);
|
|
121
|
+
let cursor = start.category.tn_parent;
|
|
122
|
+
while (cursor !== null && cursor !== undefined && !seen.has(cursor)) {
|
|
123
|
+
const parent = index.byId.get(cursor);
|
|
124
|
+
if (parent === undefined)
|
|
125
|
+
break;
|
|
126
|
+
path.push(parent);
|
|
127
|
+
seen.add(parent.id);
|
|
128
|
+
cursor = parent.category.tn_parent;
|
|
129
|
+
}
|
|
130
|
+
return path.reverse();
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* The server's own answer to "who are this row's ancestors", root-first, from
|
|
134
|
+
* `tn_ancestors_pks`.
|
|
135
|
+
*
|
|
136
|
+
* Useful without a built index (a single category fetched on its own), and the
|
|
137
|
+
* cross-check for {@link categoryBreadcrumbs}.
|
|
138
|
+
*/
|
|
139
|
+
export function categoryAncestorIds(category) {
|
|
140
|
+
return parseTreenodePks(category.tn_ancestors_pks);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* The server's own answer to "who are this row's children", from
|
|
144
|
+
* `tn_children_pks`.
|
|
145
|
+
*
|
|
146
|
+
* This is the FULL child set, including soft-deleted and inactive rows — the
|
|
147
|
+
* column is maintained by treenode, which knows nothing about either flag. Use
|
|
148
|
+
* it to detect "this category has sub-categories at all"; use the built node's
|
|
149
|
+
* `children` to render them.
|
|
150
|
+
*/
|
|
151
|
+
export function categoryChildIds(category) {
|
|
152
|
+
return parseTreenodePks(category.tn_children_pks);
|
|
153
|
+
}
|
|
154
|
+
/** Depth-first walk of a subtree (or the whole forest), in display order. */
|
|
155
|
+
export function flattenCategoryNodes(nodes) {
|
|
156
|
+
const out = [];
|
|
157
|
+
const walk = (list) => {
|
|
158
|
+
for (const node of list) {
|
|
159
|
+
out.push(node);
|
|
160
|
+
walk(node.children);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
walk(nodes);
|
|
164
|
+
return out;
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=tree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree.js","sourceRoot":"","sources":["../../src/catalog/tree.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<CatalogPage>` — the catalogue root at `/c`: the carousel over the top-level
|
|
3
|
+
* tree.
|
|
4
|
+
*
|
|
5
|
+
* This is a single-pair screen, which is exactly why it has a nav entry while
|
|
6
|
+
* `/` does not. The storefront's landing composes THIS pair's carousel with
|
|
7
|
+
* search's newest-listings feed (spec §5.1), and a composed route belongs to
|
|
8
|
+
* the container that composes it — see `nav/manifest.ts`.
|
|
9
|
+
*/
|
|
10
|
+
import type { ReactElement } from "react";
|
|
11
|
+
import type { ThemeModeProp } from "./types.js";
|
|
12
|
+
export interface CatalogPageProps extends ThemeModeProp {
|
|
13
|
+
readonly basePath?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function CatalogPage(props: CatalogPageProps): ReactElement;
|
|
16
|
+
//# sourceMappingURL=CatalogPage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CatalogPage.d.ts","sourceRoot":"","sources":["../../src/default/CatalogPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAO1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACrD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,YAAY,CAiBjE"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Flex, Typography } from "antd";
|
|
3
|
+
import { useT } from "@stapel/core";
|
|
4
|
+
import { CATEGORIES_I18N_KEYS } from "../i18n/keys.js";
|
|
5
|
+
import { CategoryCarouselStrip } from "./CategoryCarouselStrip.js";
|
|
6
|
+
import { CategoryTreePane } from "./CategoryTreePane.js";
|
|
7
|
+
import { CategoriesSkinTheme } from "./theme.js";
|
|
8
|
+
export function CatalogPage(props) {
|
|
9
|
+
const t = useT();
|
|
10
|
+
const base = props.basePath ?? "/c";
|
|
11
|
+
return (_jsx(CategoriesSkinTheme, { ...(props.mode !== undefined ? { mode: props.mode } : {}), children: _jsxs(Flex, { vertical: true, gap: 16, "data-testid": "categories-catalog-page", children: [_jsx(Typography.Title, { level: 3, style: { margin: 0 }, children: t(CATEGORIES_I18N_KEYS.catalogTitle) }), _jsx(CategoryCarouselStrip, { basePath: base }), _jsx(CategoryTreePane, { basePath: base })] }) }));
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=CatalogPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CatalogPage.js","sourceRoot":"","sources":["../../src/default/CatalogPage.tsx"],"names":[],"mappings":";AAUA,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACpC,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAOjD,MAAM,UAAU,WAAW,CAAC,KAAuB;IACjD,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IACjB,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,IAAI,IAAI,CAAC;IAEpC,OAAO,CACL,KAAC,mBAAmB,OACd,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,YAE1D,MAAC,IAAI,IAAC,QAAQ,QAAC,GAAG,EAAE,EAAE,iBAAc,yBAAyB,aAC3D,KAAC,UAAU,CAAC,KAAK,IAAC,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,YAC7C,CAAC,CAAC,oBAAoB,CAAC,YAAY,CAAC,GACpB,EACnB,KAAC,qBAAqB,IAAC,QAAQ,EAAE,IAAI,GAAI,EACzC,KAAC,gBAAgB,IAAC,QAAQ,EAAE,IAAI,GAAI,IAC/B,GACa,CACvB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<CategoryBreadcrumbsBar>` — root → current, as antd breadcrumbs.
|
|
3
|
+
*
|
|
4
|
+
* Every caption goes through `renderCategoryLabel`, which is the one place
|
|
5
|
+
* this skin decides between "translate this" and "print this": a category's
|
|
6
|
+
* `name` is a translation KEY unless the row says `translatable: false`
|
|
7
|
+
* (`catalog/labels.ts`). A skin that called `t()` unconditionally would print
|
|
8
|
+
* the stored string for an untranslatable category twice-wrapped, and one that
|
|
9
|
+
* never called it would show `category.electronics` at a visitor.
|
|
10
|
+
*/
|
|
11
|
+
import type { ReactElement } from "react";
|
|
12
|
+
import type { ThemeModeProp } from "./types.js";
|
|
13
|
+
export interface CategoryBreadcrumbsBarProps extends ThemeModeProp {
|
|
14
|
+
readonly slug?: string;
|
|
15
|
+
readonly categoryId?: number | null;
|
|
16
|
+
/** Path prefix for a crumb's link. Default `/c`. */
|
|
17
|
+
readonly basePath?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function CategoryBreadcrumbsBar(props: CategoryBreadcrumbsBarProps): ReactElement;
|
|
20
|
+
//# sourceMappingURL=CategoryBreadcrumbsBar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CategoryBreadcrumbsBar.d.ts","sourceRoot":"","sources":["../../src/default/CategoryBreadcrumbsBar.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAQ1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,2BAA4B,SAAQ,aAAa;IAChE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,oDAAoD;IACpD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,2BAA2B,GACjC,YAAY,CA+Dd"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Breadcrumb, Skeleton, Typography } from "antd";
|
|
3
|
+
import { matchLoad, toFlowError, useDescribeFlowError, useT } from "@stapel/core";
|
|
4
|
+
import { renderCategoryLabel } from "../catalog/labels.js";
|
|
5
|
+
import { CategoryBreadcrumbs } from "../headless/CategoryBreadcrumbs.js";
|
|
6
|
+
import { CATEGORIES_I18N_KEYS } from "../i18n/keys.js";
|
|
7
|
+
import { ErrorAlert } from "./ErrorAlert.js";
|
|
8
|
+
import { CategoriesSkinTheme } from "./theme.js";
|
|
9
|
+
export function CategoryBreadcrumbsBar(props) {
|
|
10
|
+
const t = useT();
|
|
11
|
+
const describe = useDescribeFlowError();
|
|
12
|
+
const base = props.basePath ?? "/c";
|
|
13
|
+
return (_jsx(CategoriesSkinTheme, { ...(props.mode !== undefined ? { mode: props.mode } : {}), children: _jsx(CategoryBreadcrumbs, { ...(props.slug !== undefined ? { slug: props.slug } : {}), ...(props.categoryId !== undefined
|
|
14
|
+
? { categoryId: props.categoryId }
|
|
15
|
+
: {}), children: (bag) => matchLoad(bag.state, {
|
|
16
|
+
loading: () => (_jsx(Skeleton.Input, { active: true, size: "small", "data-testid": "categories-breadcrumbs-loading" })),
|
|
17
|
+
failed: (error) => (_jsx(ErrorAlert, { testId: "categories-breadcrumbs-failed", error: {
|
|
18
|
+
...describe(toFlowError(error)),
|
|
19
|
+
message: t(CATEGORIES_I18N_KEYS.catalogLoadFailed),
|
|
20
|
+
} })),
|
|
21
|
+
ready: (crumbs) => bag.unknownSlug ? (_jsx(Typography.Text, { type: "secondary", "data-testid": "categories-breadcrumbs-unknown", children: t(CATEGORIES_I18N_KEYS.categoryUnknownSlug) })) : (_jsx(Breadcrumb, { "data-testid": "categories-breadcrumbs", items: [
|
|
22
|
+
{
|
|
23
|
+
title: t(CATEGORIES_I18N_KEYS.breadcrumbsRoot),
|
|
24
|
+
href: base,
|
|
25
|
+
},
|
|
26
|
+
...crumbs.map((crumb) => ({
|
|
27
|
+
title: renderCategoryLabel(crumb.label, t),
|
|
28
|
+
...(crumb.isCurrent
|
|
29
|
+
? {}
|
|
30
|
+
: { href: `${base}/${crumb.node.category.slug}` }),
|
|
31
|
+
})),
|
|
32
|
+
] })),
|
|
33
|
+
}) }) }));
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=CategoryBreadcrumbsBar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CategoryBreadcrumbsBar.js","sourceRoot":"","sources":["../../src/default/CategoryBreadcrumbsBar.tsx"],"names":[],"mappings":";AAWA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,oBAAoB,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAClF,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAUjD,MAAM,UAAU,sBAAsB,CACpC,KAAkC;IAElC,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IACjB,MAAM,QAAQ,GAAG,oBAAoB,EAAE,CAAC;IACxC,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,IAAI,IAAI,CAAC;IAEpC,OAAO,CACL,KAAC,mBAAmB,OACd,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,YAE1D,KAAC,mBAAmB,OACd,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KACtD,CAAC,KAAK,CAAC,UAAU,KAAK,SAAS;gBACjC,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE;gBAClC,CAAC,CAAC,EAAE,CAAC,YAEN,CAAC,GAAG,EAAE,EAAE,CACP,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE;gBACnB,OAAO,EAAE,GAAG,EAAE,CAAC,CACb,KAAC,QAAQ,CAAC,KAAK,IACb,MAAM,QACN,IAAI,EAAC,OAAO,iBACA,gCAAgC,GAC5C,CACH;gBACD,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CACjB,KAAC,UAAU,IACT,MAAM,EAAC,+BAA+B,EACtC,KAAK,EAAE;wBACL,GAAG,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;wBAC/B,OAAO,EAAE,CAAC,CAAC,oBAAoB,CAAC,iBAAiB,CAAC;qBACnD,GACD,CACH;gBACD,KAAK,EAAE,CAAC,MAAM,EAAE,EAAE,CAChB,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,CAChB,KAAC,UAAU,CAAC,IAAI,IACd,IAAI,EAAC,WAAW,iBACJ,gCAAgC,YAE3C,CAAC,CAAC,oBAAoB,CAAC,mBAAmB,CAAC,GAC5B,CACnB,CAAC,CAAC,CAAC,CACF,KAAC,UAAU,mBACG,wBAAwB,EACpC,KAAK,EAAE;wBACL;4BACE,KAAK,EAAE,CAAC,CAAC,oBAAoB,CAAC,eAAe,CAAC;4BAC9C,IAAI,EAAE,IAAI;yBACX;wBACD,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;4BACxB,KAAK,EAAE,mBAAmB,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;4BAC1C,GAAG,CAAC,KAAK,CAAC,SAAS;gCACjB,CAAC,CAAC,EAAE;gCACJ,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;yBACrD,CAAC,CAAC;qBACJ,GACD,CACH;aACJ,CAAC,GAEgB,GACF,CACvB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<CategoryCarouselStrip>` — the landing page's row of category tiles.
|
|
3
|
+
*
|
|
4
|
+
* The icon references (`carousel_icon` / `catalog_icon`) are OPAQUE STRINGS
|
|
5
|
+
* that the backend deliberately does not resolve ("Decoupled from stapel-cdn:
|
|
6
|
+
* an opaque string, resolved by the host if at all"). This skin therefore
|
|
7
|
+
* renders no `<img>` and builds no URL: it hands the reference to the host
|
|
8
|
+
* through `renderIcon`, and draws nothing when the host does not supply one. A
|
|
9
|
+
* guessed CDN path would be a broken image on every deployment that guessed
|
|
10
|
+
* differently, and a broken image is worse than no image.
|
|
11
|
+
*/
|
|
12
|
+
import type { ReactElement, ReactNode } from "react";
|
|
13
|
+
import type { CarouselEntry } from "../headless/CategoryCarousel.js";
|
|
14
|
+
import type { ThemeModeProp } from "./types.js";
|
|
15
|
+
export interface CategoryCarouselStripProps extends ThemeModeProp {
|
|
16
|
+
readonly basePath?: string;
|
|
17
|
+
/** Turn an opaque icon reference into something renderable. Absent means no
|
|
18
|
+
* icon is drawn — see this file's header. */
|
|
19
|
+
readonly renderIcon?: (reference: string, entry: CarouselEntry) => ReactNode;
|
|
20
|
+
}
|
|
21
|
+
export declare function CategoryCarouselStrip(props: CategoryCarouselStripProps): ReactElement;
|
|
22
|
+
//# sourceMappingURL=CategoryCarouselStrip.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CategoryCarouselStrip.d.ts","sourceRoot":"","sources":["../../src/default/CategoryCarouselStrip.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAKrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAIrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,0BAA2B,SAAQ,aAAa;IAC/D,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;iDAC6C;IAC7C,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,KAAK,SAAS,CAAC;CAC9E;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,0BAA0B,GAChC,YAAY,CAsEd"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Card, Empty, Flex, Skeleton, Typography } from "antd";
|
|
3
|
+
import { matchList, toFlowError, useDescribeFlowError, useT } from "@stapel/core";
|
|
4
|
+
import { renderCategoryLabel } from "../catalog/labels.js";
|
|
5
|
+
import { CategoryCarousel } from "../headless/CategoryCarousel.js";
|
|
6
|
+
import { CATEGORIES_I18N_KEYS } from "../i18n/keys.js";
|
|
7
|
+
import { ErrorAlert } from "./ErrorAlert.js";
|
|
8
|
+
import { CategoriesSkinTheme } from "./theme.js";
|
|
9
|
+
export function CategoryCarouselStrip(props) {
|
|
10
|
+
const t = useT();
|
|
11
|
+
const describe = useDescribeFlowError();
|
|
12
|
+
return (_jsx(CategoriesSkinTheme, { ...(props.mode !== undefined ? { mode: props.mode } : {}), children: _jsx(CategoryCarousel, { ...(props.basePath !== undefined ? { basePath: props.basePath } : {}), children: (bag) => (_jsxs(Flex, { vertical: true, gap: 8, "data-testid": "categories-carousel", children: [_jsx(Typography.Title, { level: 5, style: { margin: 0 }, children: t(CATEGORIES_I18N_KEYS.carouselTitle) }), matchList(bag.state, {
|
|
13
|
+
loading: () => (_jsxs(Flex, { gap: 8, children: [_jsx(Skeleton.Button, { active: true, "data-testid": "categories-carousel-loading" }), _jsx(Skeleton.Button, { active: true }), _jsx(Skeleton.Button, { active: true })] })),
|
|
14
|
+
failed: (error) => (_jsx(ErrorAlert, { testId: "categories-carousel-failed", error: {
|
|
15
|
+
...describe(toFlowError(error)),
|
|
16
|
+
message: t(CATEGORIES_I18N_KEYS.carouselLoadFailed),
|
|
17
|
+
} })),
|
|
18
|
+
empty: () => (_jsx(Empty, { "data-testid": "categories-carousel-empty", description: t(CATEGORIES_I18N_KEYS.carouselEmpty) })),
|
|
19
|
+
ready: (entries) => (_jsx(Flex, { gap: 8, wrap: true, "data-testid": "categories-carousel-list", children: entries.map((entry) => (_jsx(Card, { size: "small", hoverable: true, "data-category-slug": entry.category.slug, children: _jsx(Typography.Link, { href: entry.href, children: _jsxs(Flex, { align: "center", gap: 8, children: [entry.icon !== null && props.renderIcon !== undefined
|
|
20
|
+
? props.renderIcon(entry.icon, entry)
|
|
21
|
+
: null, _jsx("span", { children: renderCategoryLabel(entry.label, t) })] }) }) }, entry.category.id))) })),
|
|
22
|
+
})] })) }) }));
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=CategoryCarouselStrip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CategoryCarouselStrip.js","sourceRoot":"","sources":["../../src/default/CategoryCarouselStrip.tsx"],"names":[],"mappings":";AAYA,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,oBAAoB,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAClF,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAEnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAUjD,MAAM,UAAU,qBAAqB,CACnC,KAAiC;IAEjC,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IACjB,MAAM,QAAQ,GAAG,oBAAoB,EAAE,CAAC;IAExC,OAAO,CACL,KAAC,mBAAmB,OACd,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,YAE1D,KAAC,gBAAgB,OACX,CAAC,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,YAErE,CAAC,GAAG,EAAE,EAAE,CAAC,CACR,MAAC,IAAI,IAAC,QAAQ,QAAC,GAAG,EAAE,CAAC,iBAAc,qBAAqB,aACtD,KAAC,UAAU,CAAC,KAAK,IAAC,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,YAC7C,CAAC,CAAC,oBAAoB,CAAC,aAAa,CAAC,GACrB,EAElB,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE;wBACpB,OAAO,EAAE,GAAG,EAAE,CAAC,CACb,MAAC,IAAI,IAAC,GAAG,EAAE,CAAC,aACV,KAAC,QAAQ,CAAC,MAAM,IACd,MAAM,uBACM,6BAA6B,GACzC,EACF,KAAC,QAAQ,CAAC,MAAM,IAAC,MAAM,SAAG,EAC1B,KAAC,QAAQ,CAAC,MAAM,IAAC,MAAM,SAAG,IACrB,CACR;wBACD,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CACjB,KAAC,UAAU,IACT,MAAM,EAAC,4BAA4B,EACnC,KAAK,EAAE;gCACL,GAAG,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;gCAC/B,OAAO,EAAE,CAAC,CAAC,oBAAoB,CAAC,kBAAkB,CAAC;6BACpD,GACD,CACH;wBACD,KAAK,EAAE,GAAG,EAAE,CAAC,CACX,KAAC,KAAK,mBACQ,2BAA2B,EACvC,WAAW,EAAE,CAAC,CAAC,oBAAoB,CAAC,aAAa,CAAC,GAClD,CACH;wBACD,KAAK,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAClB,KAAC,IAAI,IAAC,GAAG,EAAE,CAAC,EAAE,IAAI,uBAAa,0BAA0B,YACtD,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CACtB,KAAC,IAAI,IAEH,IAAI,EAAC,OAAO,EACZ,SAAS,8BACW,KAAK,CAAC,QAAQ,CAAC,IAAI,YAEvC,KAAC,UAAU,CAAC,IAAI,IAAC,IAAI,EAAE,KAAK,CAAC,IAAI,YAC/B,MAAC,IAAI,IAAC,KAAK,EAAC,QAAQ,EAAC,GAAG,EAAE,CAAC,aACxB,KAAK,CAAC,IAAI,KAAK,IAAI,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS;gDACpD,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC;gDACrC,CAAC,CAAC,IAAI,EACR,yBAAO,mBAAmB,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,GAAQ,IAC7C,GACS,IAZb,KAAK,CAAC,QAAQ,CAAC,EAAE,CAajB,CACR,CAAC,GACG,CACR;qBACF,CAAC,IACG,CACR,GACgB,GACC,CACvB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<CategoryFeatureList>` — a category's feature SCHEMA, rendered.
|
|
3
|
+
*
|
|
4
|
+
* The display integration with `@stapel/attributes-react` in the direction
|
|
5
|
+
* this pair owns: categories serves the definitions, attributes knows what a
|
|
6
|
+
* definition MEANS. So the type badge is not a string this file invents —
|
|
7
|
+
* `featureType` is attributes-react's single reader of `config.type`, and
|
|
8
|
+
* `BUILTIN_VALUE_EDITOR_TYPES` is its own statement of which types a build can
|
|
9
|
+
* actually draw. A type outside that set is marked here, on the schema, rather
|
|
10
|
+
* than discovered when somebody opens the compose form and finds a missing
|
|
11
|
+
* field.
|
|
12
|
+
*
|
|
13
|
+
* The other direction — values, not definitions — belongs to attributes-react
|
|
14
|
+
* outright: `formatFeatureValue`, `<FeatureBadges>`, `<FeatureValueList>`. A
|
|
15
|
+
* listing card imports those directly and this pair is not in the path.
|
|
16
|
+
*/
|
|
17
|
+
import type { ReactElement } from "react";
|
|
18
|
+
import type { ThemeModeProp } from "./types.js";
|
|
19
|
+
export interface CategoryFeatureListProps extends ThemeModeProp {
|
|
20
|
+
readonly categoryId: number | null | undefined;
|
|
21
|
+
}
|
|
22
|
+
export declare function CategoryFeatureList(props: CategoryFeatureListProps): ReactElement;
|
|
23
|
+
//# sourceMappingURL=CategoryFeatureList.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CategoryFeatureList.d.ts","sourceRoot":"","sources":["../../src/default/CategoryFeatureList.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAU1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,wBAAyB,SAAQ,aAAa;IAC7D,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CAChD;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,wBAAwB,GAC9B,YAAY,CA0Fd"}
|