@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.
- package/.claude/settings.local.json +6 -0
- package/bun.lock +2 -2
- package/package.json +8 -4
- package/scripts/check-bundle-size.ts +21 -21
- package/scripts/copy-fonts.ts +19 -19
- package/scripts/preview.ts +13 -15
- package/src/assets/stardust-base64.ts +1 -1
- package/src/errors.ts +8 -8
- package/src/fonts/index.ts +25 -26
- package/src/fonts/load-bun.ts +9 -9
- package/src/index.ts +21 -21
- package/src/links.ts +11 -11
- package/src/meta.ts +16 -16
- package/src/payload/base64url.ts +16 -16
- package/src/payload/codec.ts +13 -13
- package/src/primitives/category-icon.ts +69 -48
- package/src/primitives/compact-row.ts +13 -13
- package/src/primitives/divider.ts +9 -9
- package/src/primitives/icon-hex.ts +18 -16
- package/src/primitives/module-slot.ts +73 -73
- package/src/primitives/panel.ts +10 -10
- package/src/primitives/quantity-badge.ts +13 -13
- package/src/primitives/span-paragraph.ts +48 -50
- package/src/primitives/stat-bar.ts +24 -24
- package/src/primitives/svg.ts +13 -13
- package/src/primitives/text.ts +25 -25
- package/src/primitives/wrap.ts +12 -12
- package/src/render.ts +15 -19
- package/src/templates/_shared.ts +6 -7
- package/src/templates/component.ts +68 -63
- package/src/templates/index.ts +17 -17
- package/src/templates/item-cell.ts +48 -41
- package/src/templates/module.ts +84 -83
- package/src/templates/packed-entity.ts +12 -14
- package/src/templates/resource.ts +63 -65
- package/src/templates/ship-panel.ts +67 -72
- package/src/templates/social-card.ts +27 -25
- package/src/tokens/colors.ts +29 -29
- package/src/tokens/index.ts +6 -6
- package/src/tokens/spacing.ts +1 -1
- package/src/tokens/typography.ts +1 -1
- package/test/__image_snapshots__/component-hull-plates.diff.png +0 -0
- package/test/__image_snapshots__/module-engine-t1.diff.png +0 -0
- package/test/__image_snapshots__/module-storage-t1.diff.png +0 -0
- package/test/__image_snapshots__/packed-entity-ship-t1-only-engine.diff.png +0 -0
- package/test/__image_snapshots__/packed-entity-ship-t1-two-modules.diff.png +0 -0
- package/test/__image_snapshots__/resource-ore-t1.diff.png +0 -0
- package/test/base64url.test.ts +22 -22
- package/test/codec.test.ts +26 -35
- package/test/errors.test.ts +21 -21
- package/test/fixtures/cargo-items.ts +43 -43
- package/test/fonts.test.ts +23 -23
- package/test/links-meta.test.ts +37 -37
- package/test/pixel.test.ts +44 -41
- package/test/primitives-category-icon.test.ts +74 -67
- package/test/primitives-compact-row.test.ts +29 -29
- package/test/primitives-domain.test.ts +61 -50
- package/test/primitives-layout.test.ts +47 -47
- package/test/primitives-module-slot.test.ts +58 -58
- package/test/render.test.ts +38 -35
- package/test/sanity.test.ts +5 -5
- package/test/sdk-link.test.ts +13 -13
- package/test/svg.test.ts +24 -22
- package/test/templates-component.test.ts +32 -32
- package/test/templates-dispatch.test.ts +29 -29
- package/test/templates-item-cell.test.ts +79 -79
- package/test/templates-module.test.ts +52 -52
- package/test/templates-packed-entity.test.ts +42 -42
- package/test/templates-resource.test.ts +61 -61
- package/test/templates-ship-panel.test.ts +69 -65
- package/test/tokens.test.ts +28 -26
package/src/templates/index.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import type { ResolvedItem } from
|
|
2
|
-
import type { CargoItem } from
|
|
3
|
-
import { RenderError } from
|
|
4
|
-
import { renderResource } from
|
|
5
|
-
import { renderPackedEntity } from
|
|
6
|
-
import { renderComponent } from
|
|
7
|
-
import { renderModule } from
|
|
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?:
|
|
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
|
|
20
|
-
return renderResource(item, resolved, opts)
|
|
21
|
-
case
|
|
22
|
-
return renderPackedEntity(item, resolved)
|
|
23
|
-
case
|
|
24
|
-
return renderComponent(item, resolved, opts)
|
|
25
|
-
case
|
|
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
|
|
2
|
-
import { tierColors, categoryColors, categoryIconShapes } from
|
|
3
|
-
import { el } from
|
|
4
|
-
import { text } from
|
|
5
|
-
import { categoryIconPath } from
|
|
6
|
-
import { tokens } from
|
|
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(
|
|
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
|
-
|
|
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:
|
|
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({
|
|
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:
|
|
63
|
-
dominantBaseline:
|
|
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:
|
|
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
|
}
|
package/src/templates/module.ts
CHANGED
|
@@ -1,81 +1,83 @@
|
|
|
1
|
-
import type { ResolvedItem } from
|
|
2
|
-
import { describeModuleForItem, renderDescription } from
|
|
3
|
-
import type { CargoItem } from
|
|
4
|
-
import { panel } from
|
|
5
|
-
import { iconHex } from
|
|
6
|
-
import { text } from
|
|
7
|
-
import { divider } from
|
|
8
|
-
import { compactRow } from
|
|
9
|
-
import { quantityBadge } from
|
|
10
|
-
import { spanParagraph } from
|
|
11
|
-
import { tokens } from
|
|
12
|
-
import { shortCode, formatMass, tierBorder } from
|
|
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,
|
|
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?:
|
|
20
|
+
mode?: "values" | "ranges";
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
export function renderModule(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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:
|
|
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 ·
|
|
101
|
+
value: `MODULE · ${formatTier(resolved.tier)}`,
|
|
101
102
|
size: tokens.typography.sizes.body,
|
|
102
103
|
weight: 600,
|
|
103
|
-
anchor:
|
|
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:
|
|
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:
|
|
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 ===
|
|
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
|
|
2
|
-
import { describeModuleForSlot, renderDescription } from
|
|
3
|
-
import type { CargoItem } from
|
|
4
|
-
import { renderShipPanel, type ShipPanelSlot } from
|
|
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
|
-
|
|
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
|
}
|