@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,188 @@
1
+ import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
2
+ import type { ReactNode } from 'react';
3
+ import { PageText } from '@terrafirmagreg-team/guide/components/views/page/text';
4
+ import {
5
+ EntityPage,
6
+ ImagePage,
7
+ MultiMultiblockPage,
8
+ MultiblockPage,
9
+ MultiblockVisual,
10
+ } from '@terrafirmagreg-team/guide/components/views/kind/media';
11
+ import {
12
+ LinkPage,
13
+ QuestPage,
14
+ RelationPage,
15
+ } from '@terrafirmagreg-team/guide/components/views/kind/links';
16
+ import {
17
+ RecipePage,
18
+ SpotlightPage,
19
+ } from '@terrafirmagreg-team/guide/components/views/kind/recipe';
20
+ import { TablePage } from '@terrafirmagreg-team/guide/components/views/kind/table';
21
+ import { UnknownPage } from '@terrafirmagreg-team/guide/components/views/kind/unknown';
22
+ import type { PageRendererProps } from '@terrafirmagreg-team/guide/types';
23
+ import type { AtlasBundle } from '@terrafirmagreg-team/dump/lib/dump/assets/atlas/dto';
24
+ import type { Lang } from '@terrafirmagreg-team/translate/types/locale';
25
+ import { guideMessage } from '@terrafirmagreg-team/guide/lib/messages';
26
+ import type { PagePreviewData } from '@terrafirmagreg-team/ui/types/preview';
27
+ import type { GuideBookMeta } from '@terrafirmagreg-team/guide/types';
28
+ import type {
29
+ GuideBook,
30
+ GuideEntryExtras,
31
+ GuideEntryRef,
32
+ GuideMessages,
33
+ GuidePage,
34
+ } from '@terrafirmagreg-team/guide/types';
35
+ import {
36
+ guidePageTitles,
37
+ pageAnchors,
38
+ pageIcons,
39
+ repeatedTitle,
40
+ } from '@terrafirmagreg-team/guide/lib/toc';
41
+ import { classify, isIndexPage, type PageKind, pageTexts } from '@terrafirmagreg-team/guide/lib/page-kind';
42
+ import {
43
+ EntryGroup,
44
+ GROUP_CYCLE_MS,
45
+ } from '@terrafirmagreg-team/game/components/entry/group';
46
+ import { Entry } from '@terrafirmagreg-team/game/components/entry/entry';
47
+
48
+ const renderers: Record<
49
+ Exclude<PageKind, 'empty' | 'text'>,
50
+ (props: PageRendererProps) => ReactNode
51
+ > = {
52
+ image: ImagePage,
53
+ recipe: RecipePage,
54
+ spotlight: SpotlightPage,
55
+ entity: EntityPage,
56
+ multiblock: MultiblockPage,
57
+ multimultiblock: MultiMultiblockPage,
58
+ link: LinkPage,
59
+ relations: RelationPage,
60
+ quest: QuestPage,
61
+ table: TablePage,
62
+ unknown: UnknownPage,
63
+ };
64
+
65
+ export function EntryPages({
66
+ dataset,
67
+ lang,
68
+ bookPath,
69
+ pages,
70
+ atlases,
71
+ meta,
72
+ bookModId,
73
+ categoryByEntry,
74
+ book,
75
+ langMap,
76
+ entryRefs,
77
+ keybinds,
78
+ extras,
79
+ questLinks,
80
+ previews,
81
+ messages,
82
+ }: {
83
+ dataset: DatasetRef;
84
+ lang: Lang;
85
+ bookPath: string;
86
+ pages: GuidePage[];
87
+ atlases?: Record<string, AtlasBundle>;
88
+ meta?: GuideBookMeta;
89
+ bookModId: string;
90
+ categoryByEntry: Map<string, string>;
91
+ book?: GuideBook | null;
92
+ langMap: Record<string, string>;
93
+ entryRefs: Map<string, GuideEntryRef>;
94
+ keybinds?: Record<string, string>;
95
+ extras?: GuideEntryExtras;
96
+ questLinks?: Record<string, { filename: string; questId: string }>;
97
+ previews?: Record<string, PagePreviewData>;
98
+ messages: GuideMessages;
99
+ }) {
100
+ const titles = guidePageTitles(
101
+ pages,
102
+ langMap,
103
+ {
104
+ related: guideMessage(messages, 'guide.related'),
105
+ objective: guideMessage(messages, 'guide.objective'),
106
+ },
107
+ extras?.entityNames,
108
+ );
109
+ const anchors = pageAnchors(pages);
110
+ return (
111
+ <div className="space-y-6">
112
+ {pages.map((page, index) => {
113
+ if (isIndexPage(page, langMap)) return null;
114
+ const kind = classify(page.type);
115
+ const attachedValue =
116
+ kind === 'multiblock' || kind === 'multimultiblock'
117
+ ? undefined
118
+ : (page.model ?? page.multiblock);
119
+ if (kind === 'empty' && !attachedValue && !pageTexts(page).length) {
120
+ return null;
121
+ }
122
+ const rendererProps: PageRendererProps = {
123
+ dataset,
124
+ lang,
125
+ bookPath,
126
+ page,
127
+ atlases,
128
+ meta: meta ?? {},
129
+ bookModId,
130
+ categoryByEntry,
131
+ langMap,
132
+ entryRefs,
133
+ keybinds,
134
+ book,
135
+ extras,
136
+ questLinks,
137
+ previews,
138
+ messages,
139
+ };
140
+ const Renderer =
141
+ kind === 'text' || kind === 'empty' ? undefined : renderers[kind];
142
+ const heading =
143
+ kind === 'image' || repeatedTitle(titles, index)
144
+ ? undefined
145
+ : titles[index];
146
+ const icons = pageIcons(page);
147
+ return (
148
+ <section
149
+ key={index}
150
+ id={anchors[index]}
151
+ className="scroll-mt-20 space-y-3"
152
+ >
153
+ {heading ? (
154
+ <h2 className="flex items-center gap-2 text-xl font-semibold">
155
+ {icons.length > 1 ? (
156
+ <EntryGroup members={icons} size="lg" cycle={GROUP_CYCLE_MS} />
157
+ ) : icons[0] ? (
158
+ <Entry
159
+ id={icons[0].id}
160
+ kind={icons[0].kind}
161
+ dataset={dataset}
162
+ name={heading}
163
+ href={false}
164
+ tooltip={false}
165
+ missing="hide"
166
+ />
167
+ ) : null}
168
+ {heading}
169
+ </h2>
170
+ ) : null}
171
+ {attachedValue ? (
172
+ <MultiblockVisual
173
+ value={attachedValue}
174
+ title={
175
+ typeof page.name === 'string' ? page.name : undefined
176
+ }
177
+ glbs={extras?.glbs}
178
+ />
179
+ ) : null}
180
+ {Renderer ? <Renderer {...rendererProps} /> : null}
181
+ <PageText {...rendererProps} />
182
+ </section>
183
+ );
184
+ })}
185
+ </div>
186
+ );
187
+ }
188
+
@@ -0,0 +1,62 @@
1
+ import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
2
+ import { EntryPages } from '@terrafirmagreg-team/guide/components/views/entry/pages';
3
+ import type { PagePreviewData } from '@terrafirmagreg-team/ui/types/preview';
4
+ import type { Lang } from '@terrafirmagreg-team/translate/types/locale';
5
+ import type {
6
+ GuideBook,
7
+ GuideEntry,
8
+ GuideEntryExtras,
9
+ GuideEntryRef,
10
+ GuideMessages,
11
+ } from '@terrafirmagreg-team/guide/types';
12
+
13
+ export function EntryView({
14
+ dataset,
15
+ lang,
16
+ bookPath,
17
+ entry,
18
+ bookModId,
19
+ categoryByEntry,
20
+ book,
21
+ langMap,
22
+ entryRefs,
23
+ extras,
24
+ questLinks,
25
+ previews,
26
+ messages,
27
+ }: {
28
+ dataset: DatasetRef;
29
+ lang: Lang;
30
+ bookPath: string;
31
+ entry: GuideEntry;
32
+ bookModId: string;
33
+ categoryByEntry: Map<string, string>;
34
+ book?: GuideBook | null;
35
+ langMap: Record<string, string>;
36
+ entryRefs: Map<string, GuideEntryRef>;
37
+ extras?: GuideEntryExtras;
38
+ questLinks?: Record<string, { filename: string; questId: string }>;
39
+ previews?: Record<string, PagePreviewData>;
40
+ messages: GuideMessages;
41
+ }) {
42
+ return (
43
+ <EntryPages
44
+ dataset={dataset}
45
+ lang={lang}
46
+ bookPath={bookPath}
47
+ pages={entry.pages ?? []}
48
+ atlases={extras?.atlases}
49
+ bookModId={bookModId}
50
+ categoryByEntry={categoryByEntry}
51
+ book={book}
52
+ langMap={langMap}
53
+ entryRefs={entryRefs}
54
+ keybinds={extras?.keybinds}
55
+ extras={extras}
56
+ questLinks={questLinks}
57
+ previews={previews}
58
+ messages={messages}
59
+ />
60
+ );
61
+ }
62
+
@@ -0,0 +1,112 @@
1
+ import { LinkPreview } from '@terrafirmagreg-team/ui/components/blocks/link/link-preview';
2
+ import { Entry } from '@terrafirmagreg-team/game/components/entry/entry';
3
+ import { guideEmbeds } from '@terrafirmagreg-team/guide/lib/embeds';
4
+ import { guideMessage } from '@terrafirmagreg-team/guide/lib/messages';
5
+ import {
6
+ entryLabel,
7
+ entryRef,
8
+ spriteName,
9
+ } from '@terrafirmagreg-team/guide/lib/names';
10
+ import { referenceHref } from '@terrafirmagreg-team/guide/lib/reference';
11
+ import { parseGuideReference } from '@terrafirmagreg-team/guide/lib/reference/resolver';
12
+ import type { PageRendererProps } from '@terrafirmagreg-team/guide/types';
13
+
14
+ export function RelationPage(props: PageRendererProps) {
15
+ const ids = (props.page.entries ?? []).filter(
16
+ (value): value is string => typeof value === 'string' && Boolean(value),
17
+ );
18
+ if (!ids.length) return null;
19
+ return (
20
+ <ul className="space-y-1">
21
+ {ids.map((id) => {
22
+ const resolved = referenceHref(props, id);
23
+ const ref = entryRef(props.entryRefs, id);
24
+ const name = entryLabel(props.entryRefs, props.langMap, id);
25
+ return (
26
+ <li key={id} className="flex items-center gap-2">
27
+ {ref?.icon ? (
28
+ <Entry
29
+ id={ref.icon.id}
30
+ kind={ref.icon.kind}
31
+ dataset={props.dataset}
32
+ size="sm"
33
+ href={false}
34
+ tooltip={false}
35
+ />
36
+ ) : null}
37
+ {resolved ? (
38
+ <LinkPreview
39
+ variant="text"
40
+ href={resolved.href}
41
+ preview={props.previews?.[resolved.href]}
42
+ >
43
+ {name}
44
+ </LinkPreview>
45
+ ) : (
46
+ <span className="text-muted-foreground">{name}</span>
47
+ )}
48
+ </li>
49
+ );
50
+ })}
51
+ </ul>
52
+ );
53
+ }
54
+
55
+ export function LinkPage(props: PageRendererProps) {
56
+ const target = props.page.url || props.page.link || props.page.entry || '';
57
+ const resolved = target ? referenceHref(props, target) : null;
58
+ const label =
59
+ (typeof props.page.link_text === 'string' && props.page.link_text) ||
60
+ (typeof props.page.text === 'string' && props.page.text) ||
61
+ (typeof props.page.name === 'string' && props.page.name) ||
62
+ target ||
63
+ guideMessage(props.messages, 'guide.link');
64
+ return (
65
+ <p>
66
+ {resolved ? (
67
+ <LinkPreview
68
+ variant="chip"
69
+ solid
70
+ href={resolved.href}
71
+ preview={props.previews?.[resolved.href]}
72
+ >
73
+ {label}
74
+ </LinkPreview>
75
+ ) : (
76
+ <span className="text-muted-foreground">{label}</span>
77
+ )}
78
+ </p>
79
+ );
80
+ }
81
+
82
+ export function QuestPage({
83
+ dataset,
84
+ page,
85
+ lang,
86
+ langMap,
87
+ atlases,
88
+ questLinks,
89
+ previews,
90
+ messages,
91
+ }: PageRendererProps) {
92
+ const id = page.trigger || page.quest || page.link;
93
+ const hit = id ? questLinks?.[parseGuideReference(id).id] : undefined;
94
+ const href = hit
95
+ ? guideEmbeds().quest?.href(dataset, lang, { category: hit.filename, id: hit.questId })
96
+ : undefined;
97
+ const name = id ? spriteName(dataset, langMap, atlases, id) : '—';
98
+ const label = id && name !== id ? name : <code>{name}</code>;
99
+ return (
100
+ <p className="text-sm text-muted-foreground">
101
+ {guideMessage(messages, 'guide.quest')}:{' '}
102
+ {href ? (
103
+ <LinkPreview href={href} preview={previews?.[href]}>
104
+ {label}
105
+ </LinkPreview>
106
+ ) : (
107
+ label
108
+ )}
109
+ </p>
110
+ );
111
+ }
112
+
@@ -0,0 +1,168 @@
1
+ import { useMemo } from 'react';
2
+ import { GuideText } from '@terrafirmagreg-team/guide/components/views/text';
3
+ import { useDumpLocation } from '@terrafirmagreg-team/game/hooks/use-dump-location';
4
+ import { asMultiblock } from '@terrafirmagreg-team/guide/lib/meta';
5
+ import {
6
+ createDumpAssetResolver,
7
+ texturePublicUrl,
8
+ } from '@terrafirmagreg-team/dump/lib/dump/assets/urls';
9
+ import { dumpRelPublicUrl } from '@terrafirmagreg-team/dump/lib/dump/paths';
10
+ import { parseStackId } from '@terrafirmagreg-team/dump/lib/dump/contract/ref';
11
+ import { langString } from '@terrafirmagreg-team/dump/lib/dump/assets/lang/names';
12
+ import { matchVariant } from '@terrafirmagreg-team/dump/lib/dump/data/registry/parse';
13
+ import { GlbViewer } from '@terrafirmagreg-team/ui/components/blocks/media/glb/viewer';
14
+ import { multiblockKey } from '@terrafirmagreg-team/guide/lib/multiblock';
15
+ import { guideFormatOpts } from '@terrafirmagreg-team/guide/lib/format';
16
+ import { spriteName } from '@terrafirmagreg-team/guide/lib/names';
17
+ import type { PageRendererProps } from '@terrafirmagreg-team/guide/types';
18
+ import { GlbPlaceholder } from '@terrafirmagreg-team/ui/components/blocks/media/glb/viewer';
19
+ import { Slideshow } from '@terrafirmagreg-team/ui/components/blocks/media/slideshow';
20
+ import type { GuideMultiblock } from '@terrafirmagreg-team/guide/types';
21
+
22
+ export function ImagePage(props: PageRendererProps) {
23
+ const { dataset, page } = props;
24
+ useDumpLocation(dataset);
25
+ const images = [
26
+ ...(page.images ?? []),
27
+ ...(typeof page.image === 'string' ? [page.image] : []),
28
+ ];
29
+ const opts = guideFormatOpts(props);
30
+ const title =
31
+ typeof page.title === 'string' && page.title ? (
32
+ <GuideText text={page.title} {...opts} />
33
+ ) : null;
34
+ return (
35
+ <Slideshow
36
+ images={
37
+ images.length
38
+ ? images.map((id) => ({
39
+ id,
40
+ url: texturePublicUrl(dataset, id) ?? null,
41
+ }))
42
+ : [{ id: 'missing', url: null }]
43
+ }
44
+ alt={typeof page.title === 'string' ? page.title : ''}
45
+ border={page.border !== false}
46
+ scale={page.scale}
47
+ offset={page.offset}
48
+ rotate={page.rotate}
49
+ caption={title ?? undefined}
50
+ />
51
+ );
52
+ }
53
+
54
+ export function EntityPage({
55
+ dataset,
56
+ page,
57
+ langMap,
58
+ atlases,
59
+ extras,
60
+ }: PageRendererProps) {
61
+ const id = typeof page.entity === 'string' ? page.entity : undefined;
62
+ useDumpLocation(dataset);
63
+ const resolveResource = useMemo(
64
+ () => createDumpAssetResolver(dataset),
65
+ [dataset],
66
+ );
67
+ const parsed = id ? parseStackId(id) : undefined;
68
+ const entry = parsed?.id ? extras?.entities?.[parsed.id] : undefined;
69
+ const variant = matchVariant(entry?.variants, parsed?.nbt);
70
+ const model = variant?.model ?? entry?.model;
71
+ const ns = parsed?.id.split(':', 1)[0];
72
+ const glb =
73
+ model && ns
74
+ ? dumpRelPublicUrl(dataset, `assets/${ns}/${model}`)
75
+ : undefined;
76
+ const name = id
77
+ ? (langString(langMap, variant?.display?.name) ??
78
+ langString(langMap, entry?.display?.name) ??
79
+ spriteName(dataset, langMap, atlases, id))
80
+ : undefined;
81
+ return (
82
+ <div className="space-y-2">
83
+ {glb ? (
84
+ <GlbViewer
85
+ src={glb}
86
+ title={name}
87
+ scale={typeof page.scale === 'number' ? page.scale : undefined}
88
+ offset={typeof page.offset === 'number' ? page.offset : undefined}
89
+ resolveResource={resolveResource}
90
+ />
91
+ ) : (
92
+ <GlbPlaceholder title={name} />
93
+ )}
94
+ </div>
95
+ );
96
+ }
97
+
98
+ function MissingVisual({ title }: { title?: string }) {
99
+ return <GlbPlaceholder title={title} />;
100
+ }
101
+
102
+ export function MultiblockVisual({
103
+ value,
104
+ title,
105
+ glbs,
106
+ resolveResource,
107
+ }: {
108
+ value: string | GuideMultiblock | undefined;
109
+ title?: string;
110
+ glbs?: Record<string, string>;
111
+ resolveResource?: (
112
+ resourceUrl: string,
113
+ modelUrl: string,
114
+ ) => string | undefined;
115
+ }) {
116
+ const spec = asMultiblock(value);
117
+ const key = multiblockKey(spec);
118
+ const src = key ? glbs?.[key] : undefined;
119
+ return src ? (
120
+ <GlbViewer src={src} title={title} resolveResource={resolveResource} />
121
+ ) : (
122
+ <MissingVisual title={title} />
123
+ );
124
+ }
125
+
126
+ export function MultiblockPage({ page, dataset, extras }: PageRendererProps) {
127
+ const modelId = typeof page.model === 'string' ? page.model : undefined;
128
+ useDumpLocation(dataset);
129
+ const resolveResource = useMemo(
130
+ () => createDumpAssetResolver(dataset),
131
+ [dataset],
132
+ );
133
+ return (
134
+ <MultiblockVisual
135
+ value={modelId ?? page.multiblock}
136
+ title={modelId ?? page.name}
137
+ glbs={extras?.glbs}
138
+ resolveResource={resolveResource}
139
+ />
140
+ );
141
+ }
142
+
143
+ export function MultiMultiblockPage({
144
+ page,
145
+ dataset,
146
+ extras,
147
+ }: PageRendererProps) {
148
+ useDumpLocation(dataset);
149
+ const resolveResource = useMemo(
150
+ () => createDumpAssetResolver(dataset),
151
+ [dataset],
152
+ );
153
+ const specs = (page.multiblocks ?? []).map(asMultiblock);
154
+ const glbs = specs
155
+ .map((spec) => multiblockKey(spec))
156
+ .map((key) => (key ? extras?.glbs?.[key] : undefined))
157
+ .filter((url): url is string => Boolean(url));
158
+ return glbs.length ? (
159
+ <GlbViewer
160
+ sources={glbs}
161
+ title={page.name}
162
+ resolveResource={resolveResource}
163
+ />
164
+ ) : (
165
+ <MissingVisual title={page.name} />
166
+ );
167
+ }
168
+
@@ -0,0 +1,89 @@
1
+ 'use client';
2
+ import { Card } from '@terrafirmagreg-team/ui/components/ui/card';
3
+ import { EmptyState } from '@terrafirmagreg-team/ui/components/blocks/page/empty-state';
4
+ import { guideEmbeds } from '@terrafirmagreg-team/guide/lib/embeds';
5
+ import { Entry } from '@terrafirmagreg-team/game/components/entry/entry';
6
+ import { EntryList } from '@terrafirmagreg-team/game/components/entry/list';
7
+ import { guideMessage } from '@terrafirmagreg-team/guide/lib/messages';
8
+ import { collectItemIds } from '@terrafirmagreg-team/guide/lib/data/parse';
9
+ import { pageTitle } from '@terrafirmagreg-team/guide/lib/toc';
10
+ import { spriteName } from '@terrafirmagreg-team/guide/lib/names';
11
+ import { recipeIds, recipeNameScope } from '@terrafirmagreg-team/guide/lib/recipe';
12
+
13
+ import type { PageRendererProps } from '@terrafirmagreg-team/guide/types';
14
+
15
+ export function RecipePage({
16
+ dataset,
17
+ page,
18
+ lang,
19
+ langMap,
20
+ atlases,
21
+ extras,
22
+ messages,
23
+ }: PageRendererProps) {
24
+ const ids = recipeIds(page);
25
+ if (!ids.length) {
26
+ return (
27
+ <EmptyState variant="empty">
28
+ {guideMessage(messages, 'guide.recipe_empty')}
29
+ </EmptyState>
30
+ );
31
+ }
32
+ const RecipeCard = guideEmbeds().recipe?.Card;
33
+ return (
34
+ <div className="space-y-4">
35
+ {ids.map((id) => {
36
+ const recipe = extras?.recipes?.[id];
37
+ if (recipe && RecipeCard) {
38
+ return (
39
+ <RecipeCard
40
+ key={id}
41
+ id={id}
42
+ recipe={recipe}
43
+ dataset={dataset}
44
+ lang={lang}
45
+ langMap={recipeNameScope(langMap, recipe)}
46
+ />
47
+ );
48
+ }
49
+ const name = spriteName(dataset, langMap, atlases, id);
50
+ return (
51
+ <Card key={id} className="flex-row items-center gap-2 px-3 py-2">
52
+ {id.includes(':') ? (
53
+ <Entry
54
+ id={id}
55
+ kind="item"
56
+ dataset={dataset}
57
+ name={name}
58
+ labeled
59
+ label={name}
60
+ />
61
+ ) : (
62
+ <span className="text-sm">{name}</span>
63
+ )}
64
+ </Card>
65
+ );
66
+ })}
67
+ </div>
68
+ );
69
+ }
70
+
71
+ export function SpotlightPage({
72
+ dataset,
73
+ page,
74
+ langMap,
75
+ extras,
76
+ }: PageRendererProps) {
77
+ if (pageTitle(page, langMap, {}, extras?.entityNames)) return null;
78
+ const ids = [...collectItemIds(page.item), ...collectItemIds(page.icon)];
79
+ if (!ids.length) return null;
80
+ return (
81
+ <EntryList
82
+ variant="row"
83
+ labeled
84
+ dataset={dataset}
85
+ items={ids.map((id) => ({ id, kind: 'item' as const }))}
86
+ />
87
+ );
88
+ }
89
+