@terrafirmagreg-team/guide 0.0.0-stage → 1.2.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 (69) hide show
  1. package/package.json +57 -3
  2. package/src/components/book-switcher.tsx +52 -0
  3. package/src/components/mdx/guide-entry.tsx +48 -0
  4. package/src/components/mdx/multiblock.tsx +86 -0
  5. package/src/components/tree/children.tsx +75 -0
  6. package/src/components/tree/index.tsx +127 -0
  7. package/src/components/views/browser.tsx +133 -0
  8. package/src/components/views/entry/pages.tsx +188 -0
  9. package/src/components/views/entry/view.tsx +62 -0
  10. package/src/components/views/kind/links.tsx +112 -0
  11. package/src/components/views/kind/media.tsx +168 -0
  12. package/src/components/views/kind/recipe.tsx +89 -0
  13. package/src/components/views/kind/table.tsx +140 -0
  14. package/src/components/views/kind/unknown.tsx +24 -0
  15. package/src/components/views/page/category-body.tsx +58 -0
  16. package/src/components/views/page/entry-body.tsx +81 -0
  17. package/src/components/views/page/overview-body.tsx +99 -0
  18. package/src/components/views/page/text.tsx +58 -0
  19. package/src/components/views/page/view.tsx +279 -0
  20. package/src/components/views/text.tsx +132 -0
  21. package/src/hooks/use-left-sidebar-tree.ts +28 -0
  22. package/src/hooks/use-tree.ts +35 -0
  23. package/src/lib/book/convert.ts +75 -0
  24. package/src/lib/book/index-rows.ts +136 -0
  25. package/src/lib/book/index.ts +3 -0
  26. package/src/lib/book/localize.ts +77 -0
  27. package/src/lib/book/raw.ts +81 -0
  28. package/src/lib/books.ts +31 -0
  29. package/src/lib/breadcrumb.ts +56 -0
  30. package/src/lib/client/guides.ts +262 -0
  31. package/src/lib/client-payload/extras.ts +103 -0
  32. package/src/lib/client-payload/index.ts +113 -0
  33. package/src/lib/client-payload/links.ts +103 -0
  34. package/src/lib/config.ts +47 -0
  35. package/src/lib/data/dto.ts +78 -0
  36. package/src/lib/data/parse.ts +94 -0
  37. package/src/lib/dump.ts +6 -0
  38. package/src/lib/embeds.ts +12 -0
  39. package/src/lib/format.ts +232 -0
  40. package/src/lib/links.ts +2 -0
  41. package/src/lib/markdown/book.ts +74 -0
  42. package/src/lib/markdown/ctx.ts +28 -0
  43. package/src/lib/markdown/index.ts +9 -0
  44. package/src/lib/markdown/page.ts +153 -0
  45. package/src/lib/markdown/table.ts +36 -0
  46. package/src/lib/markdown/text.ts +40 -0
  47. package/src/lib/markdown-page.ts +74 -0
  48. package/src/lib/messages.ts +14 -0
  49. package/src/lib/meta.ts +10 -0
  50. package/src/lib/multiblock/index.ts +9 -0
  51. package/src/lib/multiblock/url.ts +75 -0
  52. package/src/lib/names.ts +37 -0
  53. package/src/lib/page-kind.ts +118 -0
  54. package/src/lib/paths.ts +54 -0
  55. package/src/lib/preview.ts +37 -0
  56. package/src/lib/query.ts +29 -0
  57. package/src/lib/recipe.ts +31 -0
  58. package/src/lib/reference/index.ts +41 -0
  59. package/src/lib/reference/resolver.ts +88 -0
  60. package/src/lib/search/filter.ts +18 -0
  61. package/src/lib/search/index.ts +183 -0
  62. package/src/lib/sprites.ts +18 -0
  63. package/src/lib/table.ts +11 -0
  64. package/src/lib/toc.ts +138 -0
  65. package/src/locales/en_us.json +14 -0
  66. package/src/locales/ru_ru.json +14 -0
  67. package/src/locales.ts +6 -0
  68. package/src/types/index.ts +217 -0
  69. package/README.md +0 -3
@@ -0,0 +1,140 @@
1
+ import { GuideText } from '@terrafirmagreg-team/guide/components/views/text';
2
+ import { Card } from '@terrafirmagreg-team/ui/components/ui/card';
3
+ import { Entry } from '@terrafirmagreg-team/game/components/entry/entry';
4
+ import {
5
+ Table,
6
+ TableBody,
7
+ TableCell,
8
+ TableHead,
9
+ TableHeader,
10
+ TableRow,
11
+ } from '@terrafirmagreg-team/ui/components/ui/table';
12
+ import { collectItemIds } from '@terrafirmagreg-team/guide/lib/data/parse';
13
+ import { cssColor, cellTexts } from '@terrafirmagreg-team/guide/lib/table';
14
+ import { guideFormatOpts } from '@terrafirmagreg-team/guide/lib/format';
15
+ import type { GuideTableCell, PageRendererProps } from '@terrafirmagreg-team/guide/types';
16
+
17
+ export function TablePage(props: PageRendererProps) {
18
+ const { page } = props;
19
+ const raw = page.strings ?? page.rows ?? [];
20
+ const cells = Array.isArray(raw) ? raw : [];
21
+ const columnCount =
22
+ typeof page.columns === 'number' ? page.columns : undefined;
23
+ const rows =
24
+ columnCount && columnCount > 0
25
+ ? Array.from(
26
+ { length: Math.ceil(cells.length / (columnCount + 1)) },
27
+ (_, index) =>
28
+ cells.slice(
29
+ index * (columnCount + 1),
30
+ (index + 1) * (columnCount + 1),
31
+ ),
32
+ )
33
+ : cells.map((cell) => (Array.isArray(cell) ? cell : [cell]));
34
+ const opts = guideFormatOpts(props);
35
+ if (!rows.length) {
36
+ return <Card className="min-h-16 bg-secondary p-0" aria-hidden />;
37
+ }
38
+ const legend = Array.isArray(page.legend) ? page.legend : [];
39
+ const legendItems = legend
40
+ .map((entry) => ({
41
+ color: cssColor(entry.color),
42
+ lines: (Array.isArray(entry.texts)
43
+ ? entry.texts.filter(Boolean)
44
+ : entry.text
45
+ ? [entry.text]
46
+ : []
47
+ ) as string[],
48
+ }))
49
+ .filter((entry) => entry.lines.length);
50
+
51
+ return (
52
+ <>
53
+ <Table className="border-collapse text-left">
54
+ {Array.isArray(page.columns) && page.columns.length ? (
55
+ <TableHeader>
56
+ <TableRow className="border-0 hover:bg-transparent">
57
+ {page.columns.map((column) => (
58
+ <TableHead
59
+ key={column}
60
+ className="h-auto border border-border bg-secondary px-2 py-1 whitespace-normal"
61
+ >
62
+ <GuideText text={column} {...opts} />
63
+ </TableHead>
64
+ ))}
65
+ </TableRow>
66
+ </TableHeader>
67
+ ) : null}
68
+ <TableBody>
69
+ {rows.map((row, rowIndex) => (
70
+ <TableRow key={rowIndex} className="border-0 hover:bg-transparent">
71
+ {row.map((cell, columnIndex) => {
72
+ const value: GuideTableCell | undefined =
73
+ cell && typeof cell === 'object' && !Array.isArray(cell)
74
+ ? (cell as GuideTableCell)
75
+ : undefined;
76
+ const items = value ? collectItemIds(value.item) : [];
77
+ const lines = value
78
+ ? cellTexts(value)
79
+ : typeof cell === 'string'
80
+ ? [cell]
81
+ : [];
82
+ return (
83
+ <TableCell
84
+ key={columnIndex}
85
+ className={
86
+ value?.bold
87
+ ? 'border border-border px-2 py-1 font-semibold whitespace-normal'
88
+ : 'border border-border px-2 py-1 whitespace-normal'
89
+ }
90
+ style={{ backgroundColor: cssColor(value?.fill) }}
91
+ >
92
+ <span className="inline-flex flex-wrap items-center gap-1">
93
+ {items.map((id) => (
94
+ <Entry
95
+ kind="item"
96
+ key={id}
97
+ id={id}
98
+ name={id}
99
+ size="sm"
100
+ href={false}
101
+ />
102
+ ))}
103
+ {lines.map((line, index) => (
104
+ <span key={index}>
105
+ <GuideText text={line} {...opts} />
106
+ </span>
107
+ ))}
108
+ </span>
109
+ </TableCell>
110
+ );
111
+ })}
112
+ </TableRow>
113
+ ))}
114
+ </TableBody>
115
+ </Table>
116
+ {legendItems.length ? (
117
+ <ul className="mt-2 space-y-1 text-sm text-muted-foreground">
118
+ {legendItems.map((entry, index) => (
119
+ <li key={index} className="flex items-center gap-2">
120
+ {entry.color ? (
121
+ <span
122
+ className="inline-block size-3 rounded-sm"
123
+ style={{ backgroundColor: entry.color }}
124
+ />
125
+ ) : null}
126
+ <span className="flex flex-wrap gap-x-2">
127
+ {entry.lines.map((line, lineIndex) => (
128
+ <span key={lineIndex}>
129
+ <GuideText text={line} {...opts} />
130
+ </span>
131
+ ))}
132
+ </span>
133
+ </li>
134
+ ))}
135
+ </ul>
136
+ ) : null}
137
+ </>
138
+ );
139
+ }
140
+
@@ -0,0 +1,24 @@
1
+ import { Alert, AlertTitle } from '@terrafirmagreg-team/ui/components/ui/alert';
2
+ import type { GuidePage } from '@terrafirmagreg-team/guide/lib/data/dto';
3
+ import type { PageRendererProps } from '@terrafirmagreg-team/guide/types';
4
+
5
+ export function UnknownPage({
6
+ page,
7
+ index,
8
+ }: { page: GuidePage; index?: number } & PageRendererProps) {
9
+ const keys = Object.keys(page).filter((key) => key !== 'type');
10
+ return (
11
+ <Alert variant="destructive">
12
+ <AlertTitle>
13
+ Unknown page type &quot;{page.type ?? 'text'}&quot;
14
+ {index !== undefined ? ` on page ${index + 1}` : ''}
15
+ </AlertTitle>
16
+ {keys.length ? (
17
+ <pre className="mt-2 overflow-auto text-xs">
18
+ {JSON.stringify(page, null, 2)}
19
+ </pre>
20
+ ) : null}
21
+ </Alert>
22
+ );
23
+ }
24
+
@@ -0,0 +1,58 @@
1
+ import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
2
+ import { Entry } from '@terrafirmagreg-team/game/components/entry/entry';
3
+ import { ItemRow } from '@terrafirmagreg-team/ui/components/blocks/list/row';
4
+ import { guideBookHref } from '@terrafirmagreg-team/guide/lib/paths';
5
+ import { guideMessage } from '@terrafirmagreg-team/guide/lib/messages';
6
+ import type { GuideIndex, GuideMessages } from '@terrafirmagreg-team/guide/types';
7
+
8
+ type GuideCategoryBodyProps = {
9
+ dataset: DatasetRef;
10
+ lang: string;
11
+ modId?: string;
12
+ bookPath?: string;
13
+ overview?: GuideIndex;
14
+ categoryId?: string;
15
+ messages: GuideMessages;
16
+ };
17
+
18
+ export function GuideCategoryBody({
19
+ dataset,
20
+ lang,
21
+ modId,
22
+ bookPath,
23
+ overview,
24
+ categoryId,
25
+ messages,
26
+ }: GuideCategoryBodyProps) {
27
+ const cat = overview?.categories.find((c) => c.id === categoryId);
28
+ return (
29
+ <div className="not-prose grid gap-3 sm:grid-cols-2">
30
+ {(cat?.entries ?? []).map((entry) => (
31
+ <ItemRow
32
+ key={entry.id}
33
+ href={guideBookHref(
34
+ dataset,
35
+ lang,
36
+ modId ?? overview?.modId ?? '',
37
+ bookPath ?? '',
38
+ entry.id,
39
+ )}
40
+ variant="outline"
41
+ className="rounded-xl bg-card p-4"
42
+ media={entry.icon?.id ? (
43
+ <Entry id={entry.icon.id} kind={entry.icon.kind} dataset={dataset} href={false} tooltip={false} />
44
+ ) : null}
45
+ title={entry.name}
46
+ description={
47
+ entry.pageCount
48
+ ? guideMessage(messages, 'guide.page_count', {
49
+ count: entry.pageCount,
50
+ })
51
+ : undefined
52
+ }
53
+ />
54
+ ))}
55
+ </div>
56
+ );
57
+ }
58
+
@@ -0,0 +1,81 @@
1
+ import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
2
+ import { EmptyState } from '@terrafirmagreg-team/ui/components/blocks/page/empty-state';
3
+ import { EntryView } from '@terrafirmagreg-team/guide/components/views/entry/view';
4
+ import { guideMessage } from '@terrafirmagreg-team/guide/lib/messages';
5
+ import type { PagePreviewData } from '@terrafirmagreg-team/ui/types/preview';
6
+ import type { Lang } from '@terrafirmagreg-team/translate/types/locale';
7
+ import type {
8
+ GuideEntry,
9
+ GuideEntryExtras,
10
+ GuideIndex,
11
+ GuideMessages,
12
+ } from '@terrafirmagreg-team/guide/types';
13
+
14
+ type GuideEntryBodyProps = {
15
+ dataset: DatasetRef;
16
+ lang: Lang;
17
+ bookPath?: string;
18
+ overview: GuideIndex;
19
+ entry?: GuideEntry;
20
+ categories: Map<string, string>;
21
+ langMap: Record<string, string>;
22
+ extras?: GuideEntryExtras;
23
+ questLinks?: Record<string, { filename: string; questId: string }>;
24
+ previews?: Record<string, PagePreviewData>;
25
+ messages: GuideMessages;
26
+ };
27
+
28
+ export function GuideEntryBody({
29
+ dataset,
30
+ lang,
31
+ bookPath,
32
+ overview,
33
+ entry,
34
+ categories,
35
+ langMap,
36
+ extras,
37
+ questLinks,
38
+ previews,
39
+ messages,
40
+ }: GuideEntryBodyProps) {
41
+ const catIconByEntry = new Map(
42
+ overview.categories.flatMap((category) =>
43
+ category.entries.map((item) => [item.id, category.icon] as const),
44
+ ),
45
+ );
46
+ const entryRefs = new Map(
47
+ overview.entries.map((item) => [
48
+ item.id,
49
+ {
50
+ id: item.id,
51
+ name: item.name ?? item.id,
52
+ icon: item.icon ?? catIconByEntry.get(item.id),
53
+ },
54
+ ]),
55
+ );
56
+ if (!entry || !bookPath) {
57
+ return (
58
+ <EmptyState variant="missing">
59
+ {guideMessage(messages, 'guide.entry_missing')}
60
+ </EmptyState>
61
+ );
62
+ }
63
+ return (
64
+ <EntryView
65
+ dataset={dataset}
66
+ lang={lang}
67
+ bookPath={bookPath}
68
+ entry={entry}
69
+ bookModId={overview.modId}
70
+ categoryByEntry={categories}
71
+ book={overview.book}
72
+ langMap={langMap}
73
+ entryRefs={entryRefs}
74
+ extras={extras}
75
+ questLinks={questLinks}
76
+ previews={previews}
77
+ messages={messages}
78
+ />
79
+ );
80
+ }
81
+
@@ -0,0 +1,99 @@
1
+ import { Entry } from '@terrafirmagreg-team/game/components/entry/entry';
2
+ import { ItemRow } from '@terrafirmagreg-team/ui/components/blocks/list/row';
3
+ import { MediaImage } from '@terrafirmagreg-team/ui/components/blocks/media/image';
4
+ import { GuideText } from '@terrafirmagreg-team/guide/components/views/text';
5
+ import {
6
+ guideCategoryHref,
7
+ isRootCategory,
8
+ } from '@terrafirmagreg-team/guide/lib/paths';
9
+ import { texturePublicUrl } from '@terrafirmagreg-team/dump/lib/dump/assets/urls';
10
+ import { useDumpLocation } from '@terrafirmagreg-team/game/hooks/use-dump-location';
11
+ import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
12
+ import { guideMessage } from '@terrafirmagreg-team/guide/lib/messages';
13
+ import type { PagePreviewData } from '@terrafirmagreg-team/ui/types/preview';
14
+ import type { Lang } from '@terrafirmagreg-team/translate/types/locale';
15
+ import type { GuideIndex, GuideMessages } from '@terrafirmagreg-team/guide/types';
16
+
17
+ type GuideOverviewBodyProps = {
18
+ dataset: DatasetRef;
19
+ lang: Lang;
20
+ modId?: string;
21
+ bookPath?: string;
22
+ overview: GuideIndex;
23
+ categories: Map<string, string>;
24
+ langMap: Record<string, string>;
25
+ questLinks?: Record<string, { filename: string; questId: string }>;
26
+ previews?: Record<string, PagePreviewData>;
27
+ messages: GuideMessages;
28
+ };
29
+
30
+ export function GuideOverviewBody({
31
+ dataset,
32
+ lang,
33
+ modId,
34
+ bookPath,
35
+ overview,
36
+ categories,
37
+ langMap,
38
+ questLinks,
39
+ previews,
40
+ messages,
41
+ }: GuideOverviewBodyProps) {
42
+ useDumpLocation(dataset);
43
+ if (overview.status === 'missing') return null;
44
+ return (
45
+ <div className="space-y-6">
46
+ {overview.book?.cover ? (
47
+ <MediaImage
48
+ src={texturePublicUrl(dataset, overview.book.cover.id)}
49
+ alt={overview.book.name}
50
+ eager
51
+ className="h-auto max-h-64 max-w-full rounded-lg border border-border object-contain"
52
+ pixelated
53
+ />
54
+ ) : null}
55
+ {overview.book?.landingText ? (
56
+ <p className="whitespace-pre-wrap text-muted-foreground">
57
+ <GuideText
58
+ text={overview.book.landingText}
59
+ dataset={dataset}
60
+ lang={lang}
61
+ modId={modId}
62
+ bookPath={bookPath}
63
+ categoryByEntry={categories}
64
+ macros={overview.book.macros}
65
+ langMap={langMap}
66
+ questLinks={questLinks}
67
+ previews={previews}
68
+ />
69
+ </p>
70
+ ) : null}
71
+ <div className="not-prose grid gap-3 sm:grid-cols-2">
72
+ {overview.categories
73
+ .filter((cat) => isRootCategory(cat, overview.categories))
74
+ .map((cat) => (
75
+ <ItemRow
76
+ key={cat.id}
77
+ href={guideCategoryHref(
78
+ dataset,
79
+ lang,
80
+ modId ?? overview.modId,
81
+ bookPath ?? '',
82
+ cat,
83
+ )}
84
+ variant="outline"
85
+ className="rounded-xl bg-card p-4"
86
+ media={cat.icon?.id ? (
87
+ <Entry id={cat.icon.id} kind={cat.icon.kind} dataset={dataset} href={false} tooltip={false} />
88
+ ) : null}
89
+ title={cat.name}
90
+ description={guideMessage(messages, 'guide.entry_count', {
91
+ count: cat.entries.length,
92
+ })}
93
+ />
94
+ ))}
95
+ </div>
96
+ </div>
97
+ );
98
+ }
99
+
@@ -0,0 +1,58 @@
1
+ import { GuideText } from '@terrafirmagreg-team/guide/components/views/text';
2
+ import { pageTexts } from '@terrafirmagreg-team/guide/lib/page-kind';
3
+ import type { PageRendererProps } from '@terrafirmagreg-team/guide/types';
4
+
5
+ type PageTextProps = Pick<
6
+ PageRendererProps,
7
+ | 'page'
8
+ | 'dataset'
9
+ | 'lang'
10
+ | 'bookPath'
11
+ | 'bookModId'
12
+ | 'categoryByEntry'
13
+ | 'langMap'
14
+ | 'keybinds'
15
+ | 'book'
16
+ | 'questLinks'
17
+ | 'previews'
18
+ >;
19
+
20
+ export function PageText({
21
+ page,
22
+ dataset,
23
+ lang,
24
+ bookPath,
25
+ bookModId,
26
+ categoryByEntry,
27
+ book,
28
+ langMap,
29
+ keybinds,
30
+ questLinks,
31
+ previews,
32
+ }: PageTextProps) {
33
+ return (
34
+ <>
35
+ {pageTexts(page).map((text, index) => (
36
+ <div
37
+ key={index}
38
+ className="whitespace-pre-wrap text-sm leading-relaxed"
39
+ >
40
+ <GuideText
41
+ text={text}
42
+ dataset={dataset}
43
+ lang={lang}
44
+ modId={bookModId}
45
+ bookPath={bookPath}
46
+ categoryByEntry={categoryByEntry}
47
+ macros={book?.macros}
48
+ langMap={langMap}
49
+ keybinds={keybinds}
50
+ questLinks={questLinks}
51
+ previews={previews}
52
+ />
53
+ </div>
54
+ ))}
55
+ </>
56
+ );
57
+ }
58
+