@shipload/item-renderer 0.2.2 → 1.0.0-beta1
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/package.json +47 -49
- package/src/assets/stardust-base64.ts +1 -1
- package/src/errors.ts +14 -14
- package/src/fonts/index.ts +23 -24
- package/src/fonts/load-bun.ts +11 -11
- package/src/index.ts +28 -28
- package/src/links.ts +11 -11
- package/src/meta.ts +25 -25
- package/src/payload/base64url.ts +21 -21
- package/src/payload/codec.ts +17 -17
- package/src/primitives/category-icon.ts +90 -67
- package/src/primitives/compact-row.ts +32 -32
- package/src/primitives/divider.ts +14 -14
- package/src/primitives/icon-hex.ts +36 -34
- package/src/primitives/module-slot.ts +131 -131
- package/src/primitives/panel.ts +18 -18
- package/src/primitives/quantity-badge.ts +32 -32
- package/src/primitives/span-paragraph.ts +55 -57
- package/src/primitives/stat-bar.ts +71 -71
- package/src/primitives/svg.ts +15 -15
- package/src/primitives/text.ts +33 -33
- package/src/primitives/wrap.ts +19 -19
- package/src/render.ts +21 -25
- package/src/templates/_shared.ts +5 -6
- package/src/templates/component.ts +123 -121
- package/src/templates/index.ts +23 -23
- package/src/templates/item-cell.ts +84 -81
- package/src/templates/module.ts +177 -174
- package/src/templates/packed-entity.ts +22 -24
- package/src/templates/resource.ts +134 -134
- package/src/templates/ship-panel.ts +120 -121
- package/src/templates/social-card.ts +28 -26
- package/src/tokens/colors.ts +38 -38
- package/src/tokens/index.ts +5 -5
- package/src/tokens/spacing.ts +8 -8
- package/src/tokens/typography.ts +17 -17
- package/.github/workflows/ci.yml +0 -14
- package/.gitignore +0 -6
- package/Makefile +0 -50
- package/biome.json +0 -18
- package/bun.lock +0 -123
- package/scripts/check-bundle-size.ts +0 -37
- package/scripts/copy-fonts.ts +0 -41
- package/scripts/preview.ts +0 -43
- package/test/__image_snapshots__/.gitkeep +0 -0
- package/test/__image_snapshots__/component-hull-plates.diff.png +0 -0
- package/test/__image_snapshots__/component-hull-plates.png +0 -0
- package/test/__image_snapshots__/module-engine-t1.diff.png +0 -0
- package/test/__image_snapshots__/module-engine-t1.png +0 -0
- package/test/__image_snapshots__/module-storage-t1.diff.png +0 -0
- package/test/__image_snapshots__/module-storage-t1.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-only-engine.png +0 -0
- package/test/__image_snapshots__/packed-entity-ship-t1-two-modules.diff.png +0 -0
- package/test/__image_snapshots__/packed-entity-ship-t1-two-modules.png +0 -0
- package/test/__image_snapshots__/resource-ore-t1.diff.png +0 -0
- package/test/__image_snapshots__/resource-ore-t1.png +0 -0
- package/test/__snapshots__/templates-component.test.ts.snap +0 -5
- package/test/__snapshots__/templates-item-cell.test.ts.snap +0 -9
- package/test/__snapshots__/templates-module.test.ts.snap +0 -17
- package/test/__snapshots__/templates-packed-entity.test.ts.snap +0 -5
- package/test/__snapshots__/templates-resource.test.ts.snap +0 -7
- package/test/base64url.test.ts +0 -33
- package/test/codec.test.ts +0 -43
- package/test/errors.test.ts +0 -24
- package/test/fixtures/cargo-items.ts +0 -122
- package/test/fonts.test.ts +0 -28
- package/test/links-meta.test.ts +0 -43
- package/test/pixel.test.ts +0 -66
- package/test/primitives-category-icon.test.ts +0 -79
- package/test/primitives-compact-row.test.ts +0 -44
- package/test/primitives-domain.test.ts +0 -72
- package/test/primitives-layout.test.ts +0 -56
- package/test/primitives-module-slot.test.ts +0 -88
- package/test/render.test.ts +0 -40
- package/test/sanity.test.ts +0 -6
- package/test/sdk-link.test.ts +0 -19
- package/test/snapshots/.gitkeep +0 -0
- package/test/svg.test.ts +0 -28
- package/test/templates-component.test.ts +0 -36
- package/test/templates-dispatch.test.ts +0 -35
- package/test/templates-item-cell.test.ts +0 -94
- package/test/templates-module.test.ts +0 -63
- package/test/templates-packed-entity.test.ts +0 -47
- package/test/templates-resource.test.ts +0 -71
- package/test/templates-ship-panel.test.ts +0 -87
- package/test/tokens.test.ts +0 -32
- package/tsconfig.json +0 -20
package/src/primitives/panel.ts
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import {el} from './svg.ts'
|
|
2
|
+
import {tokens} from '../tokens/index.ts'
|
|
3
3
|
|
|
4
4
|
export interface PanelProps {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
width: number
|
|
6
|
+
height: number
|
|
7
|
+
borderColor?: string
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export function panel(props: PanelProps): string {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
11
|
+
const {width, height, borderColor} = props
|
|
12
|
+
const r = tokens.spacing.cornerRadius
|
|
13
|
+
return el('rect', {
|
|
14
|
+
x: 0.5,
|
|
15
|
+
y: 0.5,
|
|
16
|
+
width: width - 1,
|
|
17
|
+
height: height - 1,
|
|
18
|
+
rx: r,
|
|
19
|
+
ry: r,
|
|
20
|
+
fill: tokens.colors.surface.panel,
|
|
21
|
+
stroke: borderColor ?? tokens.colors.surface.panelBorder,
|
|
22
|
+
'stroke-width': 1,
|
|
23
|
+
})
|
|
24
24
|
}
|
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import {el} from './svg.ts'
|
|
2
|
+
import {text} from './text.ts'
|
|
3
|
+
import {tokens} from '../tokens/index.ts'
|
|
4
4
|
|
|
5
5
|
export interface QuantityBadgeProps {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
x: number
|
|
7
|
+
y: number
|
|
8
|
+
quantity: number
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export function quantityBadge({
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
11
|
+
export function quantityBadge({x, y, quantity}: QuantityBadgeProps): string {
|
|
12
|
+
if (quantity <= 1) return ''
|
|
13
|
+
const label = `×${quantity}`
|
|
14
|
+
const w = label.length * 7 + 12
|
|
15
|
+
const h = tokens.spacing.quantityBadgeHeight
|
|
16
|
+
return (
|
|
17
|
+
el('rect', {
|
|
18
|
+
x: x - w,
|
|
19
|
+
y,
|
|
20
|
+
width: w,
|
|
21
|
+
height: h,
|
|
22
|
+
rx: h / 2,
|
|
23
|
+
ry: h / 2,
|
|
24
|
+
fill: tokens.colors.text.accent,
|
|
25
|
+
}) +
|
|
26
|
+
text({
|
|
27
|
+
x: x - w / 2,
|
|
28
|
+
y: y + h / 2 + 4,
|
|
29
|
+
value: label,
|
|
30
|
+
size: tokens.typography.sizes.label,
|
|
31
|
+
weight: 700,
|
|
32
|
+
family: tokens.typography.mono,
|
|
33
|
+
color: tokens.colors.surface.background,
|
|
34
|
+
anchor: 'middle',
|
|
35
|
+
})
|
|
36
|
+
)
|
|
37
37
|
}
|
|
@@ -1,72 +1,70 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import type {
|
|
1
|
+
import {wrapText} from './wrap.ts'
|
|
2
|
+
import {escapeXml as escapeAttr} from './svg.ts'
|
|
3
|
+
import {tokens} from '../tokens/index.ts'
|
|
4
|
+
import type {TextSpan} from '@shipload/sdk'
|
|
5
5
|
|
|
6
6
|
export interface SpanParagraphProps {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
x: number
|
|
8
|
+
y: number
|
|
9
|
+
spans: TextSpan[]
|
|
10
|
+
charsPerLine?: number
|
|
11
|
+
lineHeight?: number
|
|
12
|
+
bodyColor?: string
|
|
13
|
+
highlightColor?: string
|
|
14
|
+
fontSize?: number
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
function escapeXml(s: string): string {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
return s
|
|
19
|
+
.replace(/&/g, '&')
|
|
20
|
+
.replace(/</g, '<')
|
|
21
|
+
.replace(/>/g, '>')
|
|
22
|
+
.replace(/"/g, '"')
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
function sliceSpans(spans: TextSpan[], start: number, end: number): TextSpan[] {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
26
|
+
const out: TextSpan[] = []
|
|
27
|
+
let cursor = 0
|
|
28
|
+
for (const span of spans) {
|
|
29
|
+
const spanStart = cursor
|
|
30
|
+
const spanEnd = cursor + span.text.length
|
|
31
|
+
cursor = spanEnd
|
|
32
|
+
if (spanEnd <= start || spanStart >= end) continue
|
|
33
|
+
const sliceStart = Math.max(0, start - spanStart)
|
|
34
|
+
const sliceEnd = span.text.length - Math.max(0, spanEnd - end)
|
|
35
|
+
const txt = span.text.slice(sliceStart, sliceEnd)
|
|
36
|
+
if (txt.length === 0) continue
|
|
37
|
+
out.push(span.highlight ? {text: txt, highlight: true} : {text: txt})
|
|
38
|
+
}
|
|
39
|
+
return out
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
export function spanParagraph(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const highlightColor = props.highlightColor ?? tokens.colors.text.accent
|
|
49
|
-
const size = props.fontSize ?? tokens.typography.sizes.body
|
|
42
|
+
export function spanParagraph(props: SpanParagraphProps): {svg: string; lineCount: number} {
|
|
43
|
+
const chars = props.charsPerLine ?? 36
|
|
44
|
+
const lh = props.lineHeight ?? 14
|
|
45
|
+
const bodyColor = props.bodyColor ?? tokens.colors.text.secondary
|
|
46
|
+
const highlightColor = props.highlightColor ?? tokens.colors.text.accent
|
|
47
|
+
const size = props.fontSize ?? tokens.typography.sizes.body
|
|
50
48
|
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
const plain = props.spans.map((s) => s.text).join('')
|
|
50
|
+
const lines = wrapText({value: plain, charsPerLine: chars})
|
|
53
51
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
52
|
+
let charOffset = 0
|
|
53
|
+
const out = lines
|
|
54
|
+
.map((line, i) => {
|
|
55
|
+
const lineStart = charOffset
|
|
56
|
+
const lineEnd = lineStart + line.length
|
|
57
|
+
charOffset = lineEnd + 1
|
|
58
|
+
const lineSpans = sliceSpans(props.spans, lineStart, lineEnd)
|
|
59
|
+
const y = props.y + i * lh
|
|
60
|
+
const tspans = lineSpans
|
|
61
|
+
.map((s) => {
|
|
62
|
+
const fill = s.highlight ? highlightColor : bodyColor
|
|
63
|
+
return `<tspan fill="${fill}">${escapeXml(s.text)}</tspan>`
|
|
64
|
+
})
|
|
65
|
+
.join('')
|
|
66
|
+
return `<text x="${props.x}" y="${y}" font-family="${escapeAttr(tokens.typography.sans)}" font-size="${size}">${tspans}</text>`
|
|
66
67
|
})
|
|
67
68
|
.join('')
|
|
68
|
-
|
|
69
|
-
})
|
|
70
|
-
.join('')
|
|
71
|
-
return { svg: out, lineCount: lines.length }
|
|
69
|
+
return {svg: out, lineCount: lines.length}
|
|
72
70
|
}
|
|
@@ -1,85 +1,85 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import {el} from './svg.ts'
|
|
2
|
+
import {text} from './text.ts'
|
|
3
|
+
import {tokens} from '../tokens/index.ts'
|
|
4
4
|
|
|
5
5
|
export interface StatBarProps {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
x: number
|
|
7
|
+
y: number
|
|
8
|
+
width: number
|
|
9
|
+
label: string
|
|
10
|
+
abbreviation: string
|
|
11
|
+
value: number | null // 0..1023, or null for ranges mode (no value text, no fill)
|
|
12
|
+
color: string
|
|
13
|
+
inverted?: boolean
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export function statBar({
|
|
17
|
-
x,
|
|
18
|
-
y,
|
|
19
|
-
width,
|
|
20
|
-
label,
|
|
21
|
-
abbreviation,
|
|
22
|
-
value,
|
|
23
|
-
color,
|
|
24
|
-
inverted,
|
|
25
|
-
}: StatBarProps): string {
|
|
26
|
-
const h = tokens.spacing.statBarHeight
|
|
27
|
-
|
|
28
|
-
let labelOut =
|
|
29
|
-
text({
|
|
30
|
-
x,
|
|
31
|
-
y: y - 6,
|
|
32
|
-
value: abbreviation,
|
|
33
|
-
size: tokens.typography.sizes.label,
|
|
34
|
-
weight: 700,
|
|
35
|
-
family: tokens.typography.mono,
|
|
36
|
-
color,
|
|
37
|
-
}) +
|
|
38
|
-
text({
|
|
39
|
-
x: x + 22,
|
|
40
|
-
y: y - 6,
|
|
41
|
-
value: label,
|
|
42
|
-
size: tokens.typography.sizes.stat,
|
|
43
|
-
weight: 400,
|
|
44
|
-
color: tokens.colors.text.primary,
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
const track = el('rect', {
|
|
48
17
|
x,
|
|
49
18
|
y,
|
|
50
19
|
width,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
20
|
+
label,
|
|
21
|
+
abbreviation,
|
|
22
|
+
value,
|
|
23
|
+
color,
|
|
24
|
+
inverted,
|
|
25
|
+
}: StatBarProps): string {
|
|
26
|
+
const h = tokens.spacing.statBarHeight
|
|
56
27
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
28
|
+
let labelOut =
|
|
29
|
+
text({
|
|
30
|
+
x,
|
|
31
|
+
y: y - 6,
|
|
32
|
+
value: abbreviation,
|
|
33
|
+
size: tokens.typography.sizes.label,
|
|
34
|
+
weight: 700,
|
|
35
|
+
family: tokens.typography.mono,
|
|
36
|
+
color,
|
|
37
|
+
}) +
|
|
38
|
+
text({
|
|
39
|
+
x: x + 22,
|
|
40
|
+
y: y - 6,
|
|
41
|
+
value: label,
|
|
42
|
+
size: tokens.typography.sizes.stat,
|
|
43
|
+
weight: 400,
|
|
44
|
+
color: tokens.colors.text.primary,
|
|
45
|
+
})
|
|
61
46
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
47
|
+
const track = el('rect', {
|
|
48
|
+
x,
|
|
49
|
+
y,
|
|
50
|
+
width,
|
|
51
|
+
height: h,
|
|
52
|
+
rx: h / 2,
|
|
53
|
+
ry: h / 2,
|
|
54
|
+
fill: tokens.colors.surface.panelBorder,
|
|
70
55
|
})
|
|
71
56
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
57
|
+
if (value !== null) {
|
|
58
|
+
const clamped = Math.max(0, Math.min(1023, value))
|
|
59
|
+
const displayFraction = inverted ? 1 - clamped / 1023 : clamped / 1023
|
|
60
|
+
const filled = Math.floor(width * displayFraction)
|
|
61
|
+
|
|
62
|
+
labelOut += text({
|
|
63
|
+
x: x + width,
|
|
64
|
+
y: y - 6,
|
|
65
|
+
value: String(clamped),
|
|
66
|
+
size: tokens.typography.sizes.statValue,
|
|
67
|
+
weight: 700,
|
|
68
|
+
color,
|
|
69
|
+
anchor: 'end',
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
const bar = el('rect', {
|
|
73
|
+
x,
|
|
74
|
+
y,
|
|
75
|
+
width: filled,
|
|
76
|
+
height: h,
|
|
77
|
+
rx: h / 2,
|
|
78
|
+
ry: h / 2,
|
|
79
|
+
fill: color,
|
|
80
|
+
})
|
|
81
|
+
return labelOut + track + bar
|
|
82
|
+
}
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
return labelOut + track
|
|
85
85
|
}
|
package/src/primitives/svg.ts
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
export function escapeXml(input: string): string {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
return input
|
|
3
|
+
.replaceAll('&', '&')
|
|
4
|
+
.replaceAll('<', '<')
|
|
5
|
+
.replaceAll('>', '>')
|
|
6
|
+
.replaceAll('"', '"')
|
|
7
|
+
.replaceAll("'", ''')
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export type AttrValue = string | number | null | undefined
|
|
11
11
|
|
|
12
12
|
export function attr(attrs: Record<string, AttrValue>): string {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
let out = ''
|
|
14
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
15
|
+
if (v === undefined || v === null) continue
|
|
16
|
+
const value = typeof v === 'number' ? String(v) : escapeXml(v)
|
|
17
|
+
out += ` ${k}="${value}"`
|
|
18
|
+
}
|
|
19
|
+
return out
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export function el(tag: string, attrs: Record<string, AttrValue>, children?: string): string {
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
if (children === undefined) return `<${tag}${attr(attrs)}/>`
|
|
24
|
+
return `<${tag}${attr(attrs)}>${children}</${tag}>`
|
|
25
25
|
}
|
package/src/primitives/text.ts
CHANGED
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import {el} from './svg.ts'
|
|
2
|
+
import {tokens} from '../tokens/index.ts'
|
|
3
3
|
|
|
4
4
|
export interface TextProps {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
x: number
|
|
6
|
+
y: number
|
|
7
|
+
value: string
|
|
8
|
+
size?: number
|
|
9
|
+
weight?: 400 | 600 | 700 | 500
|
|
10
|
+
family?: string
|
|
11
|
+
color?: string
|
|
12
|
+
anchor?: 'start' | 'middle' | 'end'
|
|
13
|
+
letterSpacing?: number
|
|
14
|
+
dominantBaseline?: 'auto' | 'middle' | 'central' | 'hanging' | 'text-top' | 'text-bottom'
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export function text(props: TextProps): string {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
18
|
+
return el(
|
|
19
|
+
'text',
|
|
20
|
+
{
|
|
21
|
+
x: props.x,
|
|
22
|
+
y: props.y,
|
|
23
|
+
'font-family': props.family ?? tokens.typography.sans,
|
|
24
|
+
'font-size': props.size ?? tokens.typography.sizes.body,
|
|
25
|
+
'font-weight': props.weight ?? 400,
|
|
26
|
+
fill: props.color ?? tokens.colors.text.primary,
|
|
27
|
+
'text-anchor': props.anchor,
|
|
28
|
+
'letter-spacing': props.letterSpacing,
|
|
29
|
+
'dominant-baseline': props.dominantBaseline,
|
|
30
|
+
},
|
|
31
|
+
escapeValue(props.value)
|
|
32
|
+
)
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
function escapeValue(v: string): string {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
return v
|
|
37
|
+
.replaceAll('&', '&')
|
|
38
|
+
.replaceAll('<', '<')
|
|
39
|
+
.replaceAll('>', '>')
|
|
40
|
+
.replaceAll('"', '"')
|
|
41
|
+
.replaceAll("'", ''')
|
|
42
42
|
}
|
package/src/primitives/wrap.ts
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
export interface WrapProps {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
value: string
|
|
3
|
+
charsPerLine: number
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
export function wrapText({
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
export function wrapText({value, charsPerLine}: WrapProps): string[] {
|
|
7
|
+
const words = value.split(/\s+/).filter((w) => w.length > 0)
|
|
8
|
+
const lines: string[] = []
|
|
9
|
+
let current = ''
|
|
10
|
+
for (const word of words) {
|
|
11
|
+
if (current.length === 0) {
|
|
12
|
+
current = word
|
|
13
|
+
continue
|
|
14
|
+
}
|
|
15
|
+
if (current.length + 1 + word.length <= charsPerLine) {
|
|
16
|
+
current += ` ${word}`
|
|
17
|
+
} else {
|
|
18
|
+
lines.push(current)
|
|
19
|
+
current = word
|
|
20
|
+
}
|
|
14
21
|
}
|
|
15
|
-
if (current.length
|
|
16
|
-
|
|
17
|
-
} else {
|
|
18
|
-
lines.push(current)
|
|
19
|
-
current = word
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
if (current.length > 0) lines.push(current)
|
|
23
|
-
return lines
|
|
22
|
+
if (current.length > 0) lines.push(current)
|
|
23
|
+
return lines
|
|
24
24
|
}
|
package/src/render.ts
CHANGED
|
@@ -1,33 +1,29 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import {resolveItem, type ResolvedItem} from '@shipload/sdk'
|
|
2
|
+
import type {CargoItem} from './payload/codec.ts'
|
|
3
|
+
import {decodePayload} from './payload/codec.ts'
|
|
4
|
+
import {renderByType} from './templates/index.ts'
|
|
5
|
+
import {UnknownItemError} from './errors.ts'
|
|
6
6
|
|
|
7
7
|
export interface RenderOptions {
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
width?: number
|
|
9
|
+
theme?: 'dark' | 'light'
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export function renderItem(
|
|
13
|
-
|
|
14
|
-
resolved: ResolvedItem,
|
|
15
|
-
_opts?: RenderOptions,
|
|
16
|
-
): string {
|
|
17
|
-
return renderByType(item, resolved)
|
|
12
|
+
export function renderItem(item: CargoItem, resolved: ResolvedItem, _opts?: RenderOptions): string {
|
|
13
|
+
return renderByType(item, resolved)
|
|
18
14
|
}
|
|
19
15
|
|
|
20
16
|
export async function renderFromPayload(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
): Promise<{
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
17
|
+
payload: string,
|
|
18
|
+
opts?: RenderOptions
|
|
19
|
+
): Promise<{svg: string; item: ResolvedItem}> {
|
|
20
|
+
const cargoItem = decodePayload(payload)
|
|
21
|
+
let resolved: ResolvedItem
|
|
22
|
+
try {
|
|
23
|
+
resolved = resolveItem(cargoItem.item_id, cargoItem.stats, cargoItem.modules)
|
|
24
|
+
} catch {
|
|
25
|
+
throw new UnknownItemError(Number(BigInt(cargoItem.item_id.toString())))
|
|
26
|
+
}
|
|
27
|
+
const svg = renderItem(cargoItem, resolved, opts)
|
|
28
|
+
return {svg, item: resolved}
|
|
33
29
|
}
|
package/src/templates/_shared.ts
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {tokens} from '../tokens/index.ts'
|
|
2
2
|
|
|
3
3
|
export function formatMass(n: number): string {
|
|
4
|
-
|
|
4
|
+
return n.toLocaleString('en-US')
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
export function tierBorder(tier: number): string {
|
|
8
|
-
|
|
9
|
-
return tokens.colors.tier[key] ?? tokens.colors.surface.panelBorder
|
|
8
|
+
return tokens.colors.tier[tier] ?? tokens.colors.surface.panelBorder
|
|
10
9
|
}
|
|
11
10
|
|
|
12
11
|
export function shortCode(itemId: number): string {
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
const str = itemId.toString(10)
|
|
13
|
+
return str.slice(-2).padStart(2, '0')
|
|
15
14
|
}
|