@featurevisor/catalog 0.0.1 → 3.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.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/dist/assets/index-DB7fEbI6.js +25 -0
  3. package/dist/assets/index-DgA0Oqrg.css +2 -0
  4. package/dist/favicon.png +0 -0
  5. package/dist/index.html +14 -0
  6. package/dist/logo-text.png +0 -0
  7. package/lib/entityTypes.d.ts +16 -0
  8. package/lib/entityTypes.js +96 -0
  9. package/lib/entityTypes.js.map +1 -0
  10. package/lib/index.d.ts +1 -0
  11. package/lib/index.js +18 -0
  12. package/lib/index.js.map +1 -0
  13. package/lib/node/index.d.ts +74 -0
  14. package/lib/node/index.js +1368 -0
  15. package/lib/node/index.js.map +1 -0
  16. package/lib/types.d.ts +95 -0
  17. package/lib/types.js +3 -0
  18. package/lib/types.js.map +1 -0
  19. package/package.json +66 -13
  20. package/public/favicon.png +0 -0
  21. package/public/logo-text.png +0 -0
  22. package/src/App.tsx +62 -0
  23. package/src/api.spec.ts +14 -0
  24. package/src/api.ts +74 -0
  25. package/src/components/tests.spec.ts +134 -0
  26. package/src/components/tests.tsx +301 -0
  27. package/src/components/trees.tsx +202 -0
  28. package/src/components/ui.tsx +660 -0
  29. package/src/components/variables.tsx +700 -0
  30. package/src/components/variations.tsx +450 -0
  31. package/src/context/CatalogContext.tsx +30 -0
  32. package/src/entityTypes.spec.ts +13 -0
  33. package/src/entityTypes.ts +102 -0
  34. package/src/index.ts +1 -0
  35. package/src/main.tsx +26 -0
  36. package/src/node/index.spec.ts +380 -0
  37. package/src/node/index.ts +1906 -0
  38. package/src/pages/EntityDetailPage.tsx +1144 -0
  39. package/src/pages/HistoryPage.tsx +144 -0
  40. package/src/pages/HomePage.tsx +21 -0
  41. package/src/pages/ListPage.tsx +737 -0
  42. package/src/styles.css +59 -0
  43. package/src/testModel.ts +123 -0
  44. package/src/types.ts +112 -0
  45. package/src/vite-env.d.ts +1 -0
  46. package/index.js +0 -1
@@ -0,0 +1,144 @@
1
+ import * as React from "react";
2
+ import { Link, useParams } from "react-router-dom";
3
+
4
+ import { fetchHistoryPage } from "../api";
5
+ import { encodeRouteSegment, entityLabels, getEntityRoute } from "../entityTypes";
6
+ import { Button, EmptyState, EntityKey, PageHeader } from "../components/ui";
7
+ import { useCatalog } from "../context/CatalogContext";
8
+ import type { HistoryEntry } from "../types";
9
+
10
+ const HISTORY_VISIBLE_ENTITY_LIMIT = 10;
11
+
12
+ function formatTimestamp(value: string) {
13
+ const date = new Date(value);
14
+
15
+ if (Number.isNaN(date.getTime())) {
16
+ return value;
17
+ }
18
+
19
+ return date.toLocaleString();
20
+ }
21
+
22
+ function HistoryEntryCard(props: { entry: HistoryEntry; setKey?: string; commitUrl?: string }) {
23
+ const [expanded, setExpanded] = React.useState(false);
24
+ const entities = props.entry.entities.filter((entity) => entity.type !== "test");
25
+ const hasMore = entities.length > HISTORY_VISIBLE_ENTITY_LIMIT;
26
+ const visibleEntities = expanded ? entities : entities.slice(0, HISTORY_VISIBLE_ENTITY_LIMIT);
27
+
28
+ return (
29
+ <li className="rounded-lg border border-border bg-surface p-4 shadow-sm ring-1 ring-black/5">
30
+ <div className="text-sm">
31
+ <span className="font-semibold">{props.entry.author}</span>{" "}
32
+ <a
33
+ className="text-primary hover:underline"
34
+ href={
35
+ props.commitUrl?.replace("{{hash}}", props.entry.commit) || `#${props.entry.commit}`
36
+ }
37
+ target="_blank"
38
+ rel="noreferrer"
39
+ >
40
+ {formatTimestamp(props.entry.timestamp)}
41
+ </a>
42
+ </div>
43
+ <ul className="mt-3 list-inside list-disc space-y-1 text-sm text-muted">
44
+ {visibleEntities.map((entity) => (
45
+ <li key={`${entity.type}-${entity.key}`} className="[overflow-wrap:anywhere]">
46
+ {entityLabels[entity.type].singular}{" "}
47
+ <Link
48
+ className="font-medium text-primary hover:underline"
49
+ to={getEntityRoute(entity.type, entity.key, entity.set || props.setKey)}
50
+ >
51
+ <EntityKey value={entity.key} className="font-medium" />
52
+ </Link>
53
+ </li>
54
+ ))}
55
+ </ul>
56
+ {hasMore && (
57
+ <button
58
+ type="button"
59
+ onClick={() => setExpanded((current) => !current)}
60
+ className="mt-3 text-sm font-semibold text-primary hover:underline"
61
+ >
62
+ {expanded
63
+ ? "See less"
64
+ : `See more (${props.entry.entities.length - HISTORY_VISIBLE_ENTITY_LIMIT} more)`}
65
+ </button>
66
+ )}
67
+ </li>
68
+ );
69
+ }
70
+
71
+ function HistoryTimeline(props: { path: string; setKey?: string; commitUrl?: string }) {
72
+ const [entries, setEntries] = React.useState<HistoryEntry[]>([]);
73
+ const [page, setPage] = React.useState(0);
74
+ const [totalPages, setTotalPages] = React.useState(1);
75
+ const [error, setError] = React.useState<string | null>(null);
76
+
77
+ async function loadPage(nextPage: number) {
78
+ try {
79
+ const response = await fetchHistoryPage(props.path, nextPage);
80
+ setEntries((current) => [...current, ...response.entries]);
81
+ setPage(response.page);
82
+ setTotalPages(response.totalPages);
83
+ } catch (err) {
84
+ setError((err as Error).message);
85
+ }
86
+ }
87
+
88
+ React.useEffect(() => {
89
+ setEntries([]);
90
+ setPage(0);
91
+ setTotalPages(1);
92
+ setError(null);
93
+ loadPage(1);
94
+ }, [props.path]);
95
+
96
+ if (error) {
97
+ return <EmptyState title="History unavailable" description={error} />;
98
+ }
99
+
100
+ if (entries.length === 0 && page > 0) {
101
+ return <EmptyState title="No history found" />;
102
+ }
103
+
104
+ return (
105
+ <div className="space-y-4">
106
+ <ol className="space-y-4">
107
+ {entries.map((entry) => (
108
+ <HistoryEntryCard
109
+ key={`${entry.commit}-${entry.timestamp}`}
110
+ entry={entry}
111
+ setKey={props.setKey}
112
+ commitUrl={props.commitUrl}
113
+ />
114
+ ))}
115
+ </ol>
116
+
117
+ {page < totalPages && (
118
+ <Button onClick={() => loadPage(page + 1)} className="w-full">
119
+ Load more
120
+ </Button>
121
+ )}
122
+ </div>
123
+ );
124
+ }
125
+
126
+ export function HistoryPage() {
127
+ const { manifest } = useCatalog();
128
+ const { setKey } = useParams();
129
+ const path = setKey
130
+ ? `/data/sets/${encodeRouteSegment(setKey)}/history`
131
+ : "/data/project/history";
132
+
133
+ return (
134
+ <div>
135
+ <PageHeader
136
+ title={setKey ? `History for ${setKey}` : "Project History"}
137
+ description="Recent Git changes for authored definitions."
138
+ />
139
+ <div className="px-6 pb-6">
140
+ <HistoryTimeline path={path} setKey={setKey} commitUrl={manifest.links?.commit} />
141
+ </div>
142
+ </div>
143
+ );
144
+ }
@@ -0,0 +1,21 @@
1
+ import { Navigate } from "react-router-dom";
2
+
3
+ import { useCatalog } from "../context/CatalogContext";
4
+ import { encodeRouteSegment } from "../entityTypes";
5
+
6
+ export function HomePage() {
7
+ const { manifest } = useCatalog();
8
+
9
+ if (!manifest.sets) {
10
+ return <Navigate to="/features" replace />;
11
+ }
12
+
13
+ const firstSetKey = manifest.setKeys[0];
14
+
15
+ return (
16
+ <Navigate
17
+ to={firstSetKey ? `/sets/${encodeRouteSegment(firstSetKey)}/features` : "/history"}
18
+ replace
19
+ />
20
+ );
21
+ }