@shipload/item-renderer 0.2.1 → 0.2.3

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 (71) hide show
  1. package/.claude/settings.local.json +6 -0
  2. package/bun.lock +2 -2
  3. package/package.json +8 -4
  4. package/scripts/check-bundle-size.ts +21 -21
  5. package/scripts/copy-fonts.ts +19 -19
  6. package/scripts/preview.ts +13 -15
  7. package/src/assets/stardust-base64.ts +1 -1
  8. package/src/errors.ts +8 -8
  9. package/src/fonts/index.ts +25 -26
  10. package/src/fonts/load-bun.ts +9 -9
  11. package/src/index.ts +21 -21
  12. package/src/links.ts +11 -11
  13. package/src/meta.ts +16 -16
  14. package/src/payload/base64url.ts +16 -16
  15. package/src/payload/codec.ts +13 -13
  16. package/src/primitives/category-icon.ts +69 -48
  17. package/src/primitives/compact-row.ts +13 -13
  18. package/src/primitives/divider.ts +9 -9
  19. package/src/primitives/icon-hex.ts +18 -16
  20. package/src/primitives/module-slot.ts +73 -73
  21. package/src/primitives/panel.ts +10 -10
  22. package/src/primitives/quantity-badge.ts +13 -13
  23. package/src/primitives/span-paragraph.ts +48 -50
  24. package/src/primitives/stat-bar.ts +24 -24
  25. package/src/primitives/svg.ts +13 -13
  26. package/src/primitives/text.ts +25 -25
  27. package/src/primitives/wrap.ts +12 -12
  28. package/src/render.ts +15 -19
  29. package/src/templates/_shared.ts +6 -7
  30. package/src/templates/component.ts +68 -63
  31. package/src/templates/index.ts +17 -17
  32. package/src/templates/item-cell.ts +48 -41
  33. package/src/templates/module.ts +84 -83
  34. package/src/templates/packed-entity.ts +12 -14
  35. package/src/templates/resource.ts +63 -65
  36. package/src/templates/ship-panel.ts +67 -72
  37. package/src/templates/social-card.ts +27 -25
  38. package/src/tokens/colors.ts +29 -29
  39. package/src/tokens/index.ts +6 -6
  40. package/src/tokens/spacing.ts +1 -1
  41. package/src/tokens/typography.ts +1 -1
  42. package/test/__image_snapshots__/component-hull-plates.diff.png +0 -0
  43. package/test/__image_snapshots__/module-engine-t1.diff.png +0 -0
  44. package/test/__image_snapshots__/module-storage-t1.diff.png +0 -0
  45. package/test/__image_snapshots__/packed-entity-ship-t1-only-engine.diff.png +0 -0
  46. package/test/__image_snapshots__/packed-entity-ship-t1-two-modules.diff.png +0 -0
  47. package/test/__image_snapshots__/resource-ore-t1.diff.png +0 -0
  48. package/test/base64url.test.ts +22 -22
  49. package/test/codec.test.ts +26 -35
  50. package/test/errors.test.ts +21 -21
  51. package/test/fixtures/cargo-items.ts +43 -43
  52. package/test/fonts.test.ts +23 -23
  53. package/test/links-meta.test.ts +37 -37
  54. package/test/pixel.test.ts +44 -41
  55. package/test/primitives-category-icon.test.ts +74 -67
  56. package/test/primitives-compact-row.test.ts +29 -29
  57. package/test/primitives-domain.test.ts +61 -50
  58. package/test/primitives-layout.test.ts +47 -47
  59. package/test/primitives-module-slot.test.ts +58 -58
  60. package/test/render.test.ts +38 -35
  61. package/test/sanity.test.ts +5 -5
  62. package/test/sdk-link.test.ts +13 -13
  63. package/test/svg.test.ts +24 -22
  64. package/test/templates-component.test.ts +32 -32
  65. package/test/templates-dispatch.test.ts +29 -29
  66. package/test/templates-item-cell.test.ts +79 -79
  67. package/test/templates-module.test.ts +52 -52
  68. package/test/templates-packed-entity.test.ts +42 -42
  69. package/test/templates-resource.test.ts +61 -61
  70. package/test/templates-ship-panel.test.ts +69 -65
  71. package/test/tokens.test.ts +28 -26
@@ -1,13 +1,13 @@
1
- import type { ResolvedItem } from '@shipload/sdk'
2
- import type { CargoItem } from '../payload/codec.ts'
3
- import { RenderError } from '../errors.ts'
4
- import { renderResource } from './resource.ts'
5
- import { renderPackedEntity } from './packed-entity.ts'
6
- import { renderComponent } from './component.ts'
7
- import { renderModule } from './module.ts'
1
+ import type { ResolvedItem } from "@shipload/sdk";
2
+ import type { CargoItem } from "../payload/codec.ts";
3
+ import { RenderError } from "../errors.ts";
4
+ import { renderResource } from "./resource.ts";
5
+ import { renderPackedEntity } from "./packed-entity.ts";
6
+ import { renderComponent } from "./component.ts";
7
+ import { renderModule } from "./module.ts";
8
8
 
9
9
  export interface RenderByTypeOpts {
10
- mode?: 'values' | 'ranges'
10
+ mode?: "values" | "ranges";
11
11
  }
12
12
 
13
13
  export function renderByType(
@@ -16,15 +16,15 @@ export function renderByType(
16
16
  opts?: RenderByTypeOpts,
17
17
  ): string {
18
18
  switch (resolved.itemType) {
19
- case 'resource':
20
- return renderResource(item, resolved, opts)
21
- case 'entity':
22
- return renderPackedEntity(item, resolved)
23
- case 'component':
24
- return renderComponent(item, resolved, opts)
25
- case 'module':
26
- return renderModule(item, resolved, opts)
19
+ case "resource":
20
+ return renderResource(item, resolved, opts);
21
+ case "entity":
22
+ return renderPackedEntity(item, resolved);
23
+ case "component":
24
+ return renderComponent(item, resolved, opts);
25
+ case "module":
26
+ return renderModule(item, resolved, opts);
27
27
  default:
28
- throw new RenderError(`unknown itemType '${String(resolved.itemType)}'`)
28
+ throw new RenderError(`unknown itemType '${String(resolved.itemType)}'`);
29
29
  }
30
30
  }
@@ -1,28 +1,28 @@
1
- import type { ResolvedItem } from '@shipload/sdk'
2
- import { tierColors, categoryColors, categoryIconShapes } from '@shipload/sdk'
3
- import { el } from '../primitives/svg.ts'
4
- import { text } from '../primitives/text.ts'
5
- import { categoryIconPath } from '../primitives/category-icon.ts'
6
- import { tokens } from '../tokens/index.ts'
1
+ import type { ResolvedItem } from "@shipload/sdk";
2
+ import { tierColors, categoryColors, categoryIconShapes } from "@shipload/sdk";
3
+ import { el } from "../primitives/svg.ts";
4
+ import { text } from "../primitives/text.ts";
5
+ import { categoryIconPath } from "../primitives/category-icon.ts";
6
+ import { tokens } from "../tokens/index.ts";
7
7
 
8
8
  export interface ItemCellProps {
9
- resolved: ResolvedItem
10
- quantity?: number
11
- size?: number
9
+ resolved: ResolvedItem;
10
+ quantity?: number;
11
+ size?: number;
12
12
  }
13
13
 
14
14
  export interface ItemCellGroupProps extends ItemCellProps {
15
- x: number
16
- y: number
15
+ x: number;
16
+ y: number;
17
17
  }
18
18
 
19
19
  function cellInner(props: ItemCellProps): string {
20
- const size = props.size ?? 48
21
- const height = Math.round(size * 1.25)
22
- const r = Math.max(4, Math.round(size * 0.12))
23
- const cx = size / 2
20
+ const size = props.size ?? 48;
21
+ const height = Math.round(size * 1.25);
22
+ const r = Math.max(4, Math.round(size * 0.12));
23
+ const cx = size / 2;
24
24
 
25
- const border = el('rect', {
25
+ const border = el("rect", {
26
26
  x: 0.5,
27
27
  y: 0.5,
28
28
  width: size - 1,
@@ -30,28 +30,35 @@ function cellInner(props: ItemCellProps): string {
30
30
  rx: r,
31
31
  ry: r,
32
32
  fill: tokens.colors.surface.panel,
33
- stroke: tierColors[props.resolved.tier],
34
- 'stroke-width': 1.5,
35
- })
33
+ stroke: tierColors[props.resolved.tier] ?? tokens.colors.surface.panelBorder,
34
+ "stroke-width": 1.5,
35
+ });
36
36
 
37
- let content = ''
37
+ let content = "";
38
38
  if (props.resolved.abbreviation) {
39
- const iconCy = size * 0.45
39
+ const iconCy = size * 0.45;
40
40
  content = text({
41
41
  x: cx,
42
42
  y: iconCy,
43
43
  value: props.resolved.abbreviation,
44
44
  size: Math.round(size * 0.28),
45
45
  weight: 700,
46
- anchor: 'middle',
46
+ anchor: "middle",
47
47
  color: tokens.colors.text.primary,
48
48
  family: tokens.typography.display,
49
- })
49
+ });
50
50
  } else if (props.resolved.category) {
51
- const shape = categoryIconShapes[props.resolved.category]
52
- const color = categoryColors[props.resolved.category]
53
- const iconCy = size * 0.4
54
- content = categoryIconPath({ shape, cx, cy: iconCy, size: size * 0.32, color, strokeWidth: 1.5 })
51
+ const shape = categoryIconShapes[props.resolved.category];
52
+ const color = categoryColors[props.resolved.category];
53
+ const iconCy = size * 0.4;
54
+ content = categoryIconPath({
55
+ shape,
56
+ cx,
57
+ cy: iconCy,
58
+ size: size * 0.32,
59
+ color,
60
+ strokeWidth: 1.5,
61
+ });
55
62
  } else if (props.resolved.icon) {
56
63
  content = text({
57
64
  x: cx,
@@ -59,38 +66,38 @@ function cellInner(props: ItemCellProps): string {
59
66
  value: props.resolved.icon,
60
67
  size: Math.round(size * 0.44),
61
68
  weight: 400,
62
- anchor: 'middle',
63
- dominantBaseline: 'central',
69
+ anchor: "middle",
70
+ dominantBaseline: "central",
64
71
  color: tokens.colors.text.primary,
65
- })
72
+ });
66
73
  }
67
74
 
68
- const qty = props.quantity ?? 0
69
- let quantityText = ''
75
+ const qty = props.quantity ?? 0;
76
+ let quantityText = "";
70
77
  if (qty > 1) {
71
- const qtyFontSize = Math.max(9, Math.round(size * 0.22))
72
- const qtyPad = Math.max(3, Math.round(size * 0.12))
78
+ const qtyFontSize = Math.max(9, Math.round(size * 0.22));
79
+ const qtyPad = Math.max(3, Math.round(size * 0.12));
73
80
  quantityText = text({
74
81
  x: size - qtyPad,
75
82
  y: height - qtyPad,
76
83
  value: String(qty),
77
84
  size: qtyFontSize,
78
85
  weight: 700,
79
- anchor: 'end',
86
+ anchor: "end",
80
87
  color: tokens.colors.text.primary,
81
88
  family: tokens.typography.display,
82
- })
89
+ });
83
90
  }
84
91
 
85
- return border + content + quantityText
92
+ return border + content + quantityText;
86
93
  }
87
94
 
88
95
  export function itemCellGroup(props: ItemCellGroupProps): string {
89
- return `<g transform="translate(${props.x}, ${props.y})">${cellInner(props)}</g>`
96
+ return `<g transform="translate(${props.x}, ${props.y})">${cellInner(props)}</g>`;
90
97
  }
91
98
 
92
99
  export function renderItemCell(props: ItemCellProps): string {
93
- const size = props.size ?? 48
94
- const height = Math.round(size * 1.25)
95
- return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${height}" viewBox="0 0 ${size} ${height}">${cellInner(props)}</svg>`
100
+ const size = props.size ?? 48;
101
+ const height = Math.round(size * 1.25);
102
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${height}" viewBox="0 0 ${size} ${height}">${cellInner(props)}</svg>`;
96
103
  }
@@ -1,81 +1,83 @@
1
- import type { ResolvedItem } from '@shipload/sdk'
2
- import { describeModuleForItem, renderDescription } from '@shipload/sdk'
3
- import type { CargoItem } from '../payload/codec.ts'
4
- import { panel } from '../primitives/panel.ts'
5
- import { iconHex } from '../primitives/icon-hex.ts'
6
- import { text } from '../primitives/text.ts'
7
- import { divider } from '../primitives/divider.ts'
8
- import { compactRow } from '../primitives/compact-row.ts'
9
- import { quantityBadge } from '../primitives/quantity-badge.ts'
10
- import { spanParagraph } from '../primitives/span-paragraph.ts'
11
- import { tokens } from '../tokens/index.ts'
12
- import { shortCode, formatMass, tierBorder } from './_shared.ts'
1
+ import type { ResolvedItem } from "@shipload/sdk";
2
+ import { describeModuleForItem, formatTier, renderDescription } from "@shipload/sdk";
3
+ import type { CargoItem } from "../payload/codec.ts";
4
+ import { panel } from "../primitives/panel.ts";
5
+ import { iconHex } from "../primitives/icon-hex.ts";
6
+ import { text } from "../primitives/text.ts";
7
+ import { divider } from "../primitives/divider.ts";
8
+ import { compactRow } from "../primitives/compact-row.ts";
9
+ import { quantityBadge } from "../primitives/quantity-badge.ts";
10
+ import { spanParagraph } from "../primitives/span-paragraph.ts";
11
+ import { tokens } from "../tokens/index.ts";
12
+ import { shortCode, formatMass, tierBorder } from "./_shared.ts";
13
13
 
14
14
  function capabilityColor(name: string): string {
15
- const key = name.toLowerCase().replace(/\s+/g, '') as keyof typeof tokens.colors.capability
16
- return tokens.colors.capability[key] ?? tokens.colors.accent.component
15
+ const key = name.toLowerCase().replace(/\s+/g, "") as keyof typeof tokens.colors.capability;
16
+ return tokens.colors.capability[key] ?? tokens.colors.accent.component;
17
17
  }
18
18
 
19
19
  export interface RenderModuleOpts {
20
- mode?: 'values' | 'ranges'
20
+ mode?: "values" | "ranges";
21
21
  }
22
22
 
23
- export function renderModule(item: CargoItem, resolved: ResolvedItem, opts?: RenderModuleOpts): string {
24
- const mode = opts?.mode ?? 'values'
25
- const w = tokens.spacing.panelWidth
26
- const pad = tokens.spacing.panelPadding
27
- const innerW = w - pad * 2
28
-
29
- const group = resolved.attributes?.[0]
30
- const attrs = group?.attributes ?? []
31
- const desc = mode === 'values' ? describeModuleForItem(resolved) : undefined
32
-
33
- const capabilityName =
34
- group?.capability ??
35
- resolved.name.replace(/\s+T\d+$/i, '')
36
-
37
- const headerH = 48
38
- const metaRowH = 28
39
- const sepY = pad + headerH + metaRowH + 6
40
-
41
- let bodyHeight = 0
42
- if (mode === 'ranges') {
43
- bodyHeight = 20 + 8
23
+ export function renderModule(
24
+ item: CargoItem,
25
+ resolved: ResolvedItem,
26
+ opts?: RenderModuleOpts,
27
+ ): string {
28
+ const mode = opts?.mode ?? "values";
29
+ const w = tokens.spacing.panelWidth;
30
+ const pad = tokens.spacing.panelPadding;
31
+ const innerW = w - pad * 2;
32
+
33
+ const group = resolved.attributes?.[0];
34
+ const attrs = group?.attributes ?? [];
35
+ const desc = mode === "values" ? describeModuleForItem(resolved) : undefined;
36
+
37
+ const capabilityName = group?.capability ?? resolved.name.replace(/\s+T\d+$/i, "");
38
+
39
+ const headerH = 48;
40
+ const metaRowH = 28;
41
+ const sepY = pad + headerH + metaRowH + 6;
42
+
43
+ let bodyHeight = 0;
44
+ if (mode === "ranges") {
45
+ bodyHeight = 20 + 8;
44
46
  } else if (desc && group) {
45
47
  const plain = renderDescription(desc)
46
48
  .map((s) => s.text)
47
- .join('')
49
+ .join("");
48
50
  const lines = plain.split(/\s+/).reduce(
49
51
  (acc, word) => {
50
- const last = acc[acc.length - 1] ?? ''
51
- if (last.length === 0) return [...acc.slice(0, -1), word]
52
- if (last.length + 1 + word.length <= 36) return [...acc.slice(0, -1), `${last} ${word}`]
53
- return [...acc, word]
52
+ const last = acc[acc.length - 1] ?? "";
53
+ if (last.length === 0) return [...acc.slice(0, -1), word];
54
+ if (last.length + 1 + word.length <= 36) return [...acc.slice(0, -1), `${last} ${word}`];
55
+ return [...acc, word];
54
56
  },
55
- [''],
56
- )
57
- const lineCount = lines.filter((l) => l.length > 0).length
58
- bodyHeight = 20 + lineCount * 14 + 8
57
+ [""],
58
+ );
59
+ const lineCount = lines.filter((l) => l.length > 0).length;
60
+ bodyHeight = 20 + lineCount * 14 + 8;
59
61
  } else if (group && attrs.length > 0) {
60
- const capHeaderH = 22
61
- const attrsH = attrs.length * 18
62
- bodyHeight = capHeaderH + attrsH + 8
62
+ const capHeaderH = 22;
63
+ const attrsH = attrs.length * 18;
64
+ bodyHeight = capHeaderH + attrsH + 8;
63
65
  }
64
66
 
65
- const height = headerH + metaRowH + 14 + bodyHeight + pad
67
+ const height = headerH + metaRowH + 14 + bodyHeight + pad;
66
68
 
67
- const chrome = panel({ width: w, height, borderColor: tierBorder(resolved.tier) })
69
+ const chrome = panel({ width: w, height, borderColor: tierBorder(resolved.tier) });
68
70
 
69
- const quantity = Number(BigInt(item.quantity.toString()))
70
- const badge = quantityBadge({ x: w - pad, y: pad, quantity })
71
+ const quantity = Number(BigInt(item.quantity.toString()));
72
+ const badge = quantityBadge({ x: w - pad, y: pad, quantity });
71
73
 
72
- const iconColor = group ? capabilityColor(group.capability) : capabilityColor(capabilityName)
74
+ const iconColor = group ? capabilityColor(group.capability) : capabilityColor(capabilityName);
73
75
  const icon = iconHex({
74
76
  x: pad,
75
77
  y: pad + 4,
76
78
  color: iconColor,
77
79
  code: shortCode(resolved.itemId),
78
- })
80
+ });
79
81
 
80
82
  const name = text({
81
83
  x: pad + 34,
@@ -84,46 +86,45 @@ export function renderModule(item: CargoItem, resolved: ResolvedItem, opts?: Ren
84
86
  size: tokens.typography.sizes.title,
85
87
  weight: 700,
86
88
  family: tokens.typography.display,
87
- })
89
+ });
88
90
 
89
- const tierNum = resolved.tier.replace(/^t/i, '')
90
91
  const subtitleLabel = text({
91
92
  x: pad,
92
93
  y: pad + headerH + 4,
93
- value: 'Type',
94
+ value: "Type",
94
95
  size: tokens.typography.sizes.body,
95
96
  color: tokens.colors.text.secondary,
96
- })
97
+ });
97
98
  const subtitleValue = text({
98
99
  x: w - pad,
99
100
  y: pad + headerH + 4,
100
- value: `MODULE · T${tierNum}`,
101
+ value: `MODULE · ${formatTier(resolved.tier)}`,
101
102
  size: tokens.typography.sizes.body,
102
103
  weight: 600,
103
- anchor: 'end',
104
- })
104
+ anchor: "end",
105
+ });
105
106
 
106
107
  const massLabel = text({
107
108
  x: pad,
108
109
  y: pad + headerH + metaRowH - 8,
109
- value: 'Mass',
110
+ value: "Mass",
110
111
  size: tokens.typography.sizes.body,
111
112
  color: tokens.colors.text.secondary,
112
- })
113
+ });
113
114
  const massValue = text({
114
115
  x: w - pad,
115
116
  y: pad + headerH + metaRowH - 8,
116
117
  value: formatMass(resolved.mass),
117
118
  size: tokens.typography.sizes.body,
118
119
  weight: 600,
119
- anchor: 'end',
120
- })
120
+ anchor: "end",
121
+ });
121
122
 
122
- const sep = divider({ x: pad, y: sepY, width: innerW })
123
+ const sep = divider({ x: pad, y: sepY, width: innerW });
123
124
 
124
- let capSection = ''
125
- if (mode === 'ranges') {
126
- const accentColor = capabilityColor(capabilityName)
125
+ let capSection = "";
126
+ if (mode === "ranges") {
127
+ const accentColor = capabilityColor(capabilityName);
127
128
  capSection = text({
128
129
  x: pad,
129
130
  y: sepY + 16,
@@ -133,9 +134,9 @@ export function renderModule(item: CargoItem, resolved: ResolvedItem, opts?: Ren
133
134
  family: tokens.typography.sans,
134
135
  color: accentColor,
135
136
  letterSpacing: 1,
136
- })
137
+ });
137
138
  } else if (desc && group) {
138
- const accentColor = capabilityColor(group.capability)
139
+ const accentColor = capabilityColor(group.capability);
139
140
  const capHeader = text({
140
141
  x: pad,
141
142
  y: sepY + 16,
@@ -145,18 +146,18 @@ export function renderModule(item: CargoItem, resolved: ResolvedItem, opts?: Ren
145
146
  family: tokens.typography.sans,
146
147
  color: accentColor,
147
148
  letterSpacing: 1,
148
- })
149
- const spans = renderDescription(desc)
149
+ });
150
+ const spans = renderDescription(desc);
150
151
  const { svg: paraSvg } = spanParagraph({
151
152
  x: pad,
152
153
  y: sepY + 36,
153
154
  spans,
154
155
  charsPerLine: 36,
155
156
  lineHeight: 14,
156
- })
157
- capSection = capHeader + paraSvg
157
+ });
158
+ capSection = capHeader + paraSvg;
158
159
  } else if (group && attrs.length > 0) {
159
- const capY = sepY + 22
160
+ const capY = sepY + 22;
160
161
  const capHeader = text({
161
162
  x: pad,
162
163
  y: capY,
@@ -166,25 +167,25 @@ export function renderModule(item: CargoItem, resolved: ResolvedItem, opts?: Ren
166
167
  family: tokens.typography.sans,
167
168
  color: capabilityColor(group.capability),
168
169
  letterSpacing: 0.8,
169
- })
170
+ });
170
171
 
171
172
  const attrRows = attrs
172
173
  .map((attr, i) => {
173
- const displayValue = String(attr.value)
174
+ const displayValue = String(attr.value);
174
175
  return compactRow({
175
176
  x: pad,
176
177
  y: capY + 14 + i * 18,
177
178
  width: innerW,
178
179
  label: attr.label,
179
180
  value: displayValue,
180
- })
181
+ });
181
182
  })
182
- .join('')
183
+ .join("");
183
184
 
184
- capSection = capHeader + attrRows
185
+ capSection = capHeader + attrRows;
185
186
  }
186
187
 
187
- const inner = `${chrome}${icon}${name}${badge}${subtitleLabel}${subtitleValue}${massLabel}${massValue}${sep}${capSection}`
188
+ const inner = `${chrome}${icon}${name}${badge}${subtitleLabel}${subtitleValue}${massLabel}${massValue}${sep}${capSection}`;
188
189
 
189
- return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${height}" viewBox="0 0 ${w} ${height}">${inner}</svg>`
190
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${height}" viewBox="0 0 ${w} ${height}">${inner}</svg>`;
190
191
  }
@@ -1,30 +1,28 @@
1
- import type { ResolvedItem, ResolvedModuleSlot } from '@shipload/sdk'
2
- import { describeModuleForSlot, renderDescription } from '@shipload/sdk'
3
- import type { CargoItem } from '../payload/codec.ts'
4
- import { renderShipPanel, type ShipPanelSlot } from './ship-panel.ts'
1
+ import type { ResolvedItem, ResolvedModuleSlot } from "@shipload/sdk";
2
+ import { describeModuleForSlot, renderDescription } from "@shipload/sdk";
3
+ import type { CargoItem } from "../payload/codec.ts";
4
+ import { renderShipPanel, type ShipPanelSlot } from "./ship-panel.ts";
5
5
 
6
6
  function slotToPanelSlot(slot: ResolvedModuleSlot): ShipPanelSlot {
7
7
  if (!slot.installed || !slot.attributes || !slot.name) {
8
- return { installed: false }
8
+ return { installed: false };
9
9
  }
10
- const desc = describeModuleForSlot(slot)
10
+ const desc = describeModuleForSlot(slot);
11
11
  if (desc) {
12
- return { name: slot.name, installed: true, description: renderDescription(desc) }
12
+ return { name: slot.name, installed: true, description: renderDescription(desc) };
13
13
  }
14
- const shorthand = slot.attributes
15
- .map((a) => `${a.value} ${a.label.toLowerCase()}`)
16
- .join(' · ')
17
- return { name: slot.name, installed: true, description: shorthand }
14
+ const shorthand = slot.attributes.map((a) => `${a.value} ${a.label.toLowerCase()}`).join(" · ");
15
+ return { name: slot.name, installed: true, description: shorthand };
18
16
  }
19
17
 
20
18
  export function renderPackedEntity(item: CargoItem, resolved: ResolvedItem): string {
21
- const quantity = Number(BigInt(item.quantity.toString()))
22
- const slots = (resolved.moduleSlots ?? []).map(slotToPanelSlot)
19
+ const quantity = Number(BigInt(item.quantity.toString()));
20
+ const slots = (resolved.moduleSlots ?? []).map(slotToPanelSlot);
23
21
  return renderShipPanel({
24
22
  name: `${resolved.name} (Packed)`,
25
23
  tier: resolved.tier,
26
24
  quantity,
27
25
  attributes: resolved.attributes ?? [],
28
26
  slots,
29
- })
27
+ });
30
28
  }