@shipload/item-renderer 1.0.0-next.4 → 1.0.0-next.40
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 +2 -2
- package/src/index.ts +50 -8
- package/src/links.ts +11 -8
- package/src/meta.ts +1 -1
- package/src/payload/codec.ts +24 -2
- package/src/primitives/component-icon.ts +229 -0
- package/src/primitives/entity-icon.ts +167 -0
- package/src/primitives/module-slot.ts +4 -4
- package/src/primitives/quantity-badge.ts +15 -5
- package/src/primitives/resource-icon.ts +242 -0
- package/src/primitives/span-paragraph.ts +1 -1
- package/src/primitives/stat-bar.ts +17 -1
- package/src/primitives/stat-stars.ts +36 -0
- package/src/primitives/text.ts +27 -1
- package/src/render.ts +12 -7
- package/src/templates/_shared.ts +113 -2
- package/src/templates/component.ts +41 -61
- package/src/templates/index.ts +2 -1
- package/src/templates/item-cell.ts +111 -38
- package/src/templates/module.ts +59 -145
- package/src/templates/packed-entity.ts +25 -5
- package/src/templates/resource.ts +40 -68
- package/src/templates/ship-panel.ts +99 -85
- package/src/tokens/colors.ts +4 -10
- package/test/fixtures/cargo-items.ts +10 -3
- package/src/primitives/category-icon.ts +0 -110
- package/src/primitives/compact-row.ts +0 -38
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import type {ResolvedItem} from '@shipload/sdk'
|
|
2
|
-
import {getStatDefinitions, categoryColors,
|
|
2
|
+
import {getStatDefinitions, categoryColors, formatLocation, starsForStat} from '@shipload/sdk'
|
|
3
3
|
import type {CargoItem} from '../payload/codec.ts'
|
|
4
4
|
import {panel} from '../primitives/panel.ts'
|
|
5
|
-
import {
|
|
6
|
-
import {text} from '../primitives/text.ts'
|
|
7
|
-
import {divider} from '../primitives/divider.ts'
|
|
5
|
+
import {resourceIcon} from '../primitives/resource-icon.ts'
|
|
8
6
|
import {statBar} from '../primitives/stat-bar.ts'
|
|
9
7
|
import {quantityBadge} from '../primitives/quantity-badge.ts'
|
|
10
8
|
import {tokens} from '../tokens/index.ts'
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
9
|
+
import {
|
|
10
|
+
formatMass,
|
|
11
|
+
tierBorder,
|
|
12
|
+
metaRowBlock,
|
|
13
|
+
titleText,
|
|
14
|
+
BADGE_Y,
|
|
15
|
+
HEADER_H,
|
|
16
|
+
ICON_Y,
|
|
17
|
+
STAT_BLOCK_GAP,
|
|
18
|
+
STAT_ROW_H,
|
|
19
|
+
BOTTOM_PAD,
|
|
20
|
+
} from './_shared.ts'
|
|
20
21
|
|
|
21
22
|
function categoryColor(category?: string): string {
|
|
22
23
|
if (!category) return tokens.colors.text.muted
|
|
@@ -26,6 +27,7 @@ function categoryColor(category?: string): string {
|
|
|
26
27
|
|
|
27
28
|
export interface RenderResourceOpts {
|
|
28
29
|
mode?: 'values' | 'ranges'
|
|
30
|
+
location?: {x: number; y: number}
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
type StatRow = {
|
|
@@ -69,83 +71,53 @@ export function renderResource(
|
|
|
69
71
|
}))
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const height = headerH + metaRowH + 14 + statsH + pad
|
|
74
|
+
const metaRows = [
|
|
75
|
+
...(opts?.location ? [{label: 'Location', value: formatLocation(opts.location)}] : []),
|
|
76
|
+
]
|
|
76
77
|
|
|
77
|
-
const
|
|
78
|
+
const metaYStart = pad + HEADER_H
|
|
79
|
+
const {svg: metaSvg, height: metaH} = metaRowBlock(pad, metaYStart, innerW, metaRows)
|
|
80
|
+
const statsYStart = metaYStart + metaH + STAT_BLOCK_GAP
|
|
81
|
+
const statsBottom =
|
|
82
|
+
statsYStart + Math.max(0, rows.length - 1) * STAT_ROW_H + tokens.spacing.statBarHeight
|
|
83
|
+
const height = statsBottom + BOTTOM_PAD
|
|
78
84
|
|
|
79
|
-
const
|
|
80
|
-
const badge = quantityBadge({x: w - pad, y: pad, quantity})
|
|
81
|
-
|
|
82
|
-
const icon = iconHex({
|
|
83
|
-
x: pad,
|
|
84
|
-
y: pad + 4,
|
|
85
|
-
color: categoryColor(resolved.category),
|
|
86
|
-
code: shortCode(resolved.itemId),
|
|
87
|
-
})
|
|
85
|
+
const chrome = panel({width: w, height, borderColor: tierBorder(resolved.tier)})
|
|
88
86
|
|
|
89
|
-
const
|
|
90
|
-
x: pad + 34,
|
|
91
|
-
y: pad + 22,
|
|
92
|
-
value: displayName(resolved),
|
|
93
|
-
size: tokens.typography.sizes.title,
|
|
94
|
-
weight: 700,
|
|
95
|
-
family: tokens.typography.display,
|
|
96
|
-
})
|
|
87
|
+
const identity = categoryColor(resolved.category)
|
|
97
88
|
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
x: pad,
|
|
101
|
-
y: pad + headerH + 4,
|
|
102
|
-
value: 'Category',
|
|
103
|
-
size: tokens.typography.sizes.body,
|
|
104
|
-
color: tokens.colors.text.secondary,
|
|
105
|
-
})
|
|
106
|
-
const catValue = text({
|
|
107
|
-
x: w - pad,
|
|
108
|
-
y: pad + headerH + 4,
|
|
109
|
-
value: catLabel,
|
|
110
|
-
size: tokens.typography.sizes.body,
|
|
111
|
-
weight: 600,
|
|
112
|
-
anchor: 'end',
|
|
113
|
-
})
|
|
114
|
-
const massLabel = text({
|
|
115
|
-
x: pad,
|
|
116
|
-
y: pad + headerH + metaRowH - 8,
|
|
117
|
-
value: 'Mass',
|
|
118
|
-
size: tokens.typography.sizes.body,
|
|
119
|
-
color: tokens.colors.text.secondary,
|
|
120
|
-
})
|
|
121
|
-
const massValue = text({
|
|
89
|
+
const quantity = Number(BigInt(item.quantity.toString()))
|
|
90
|
+
const badge = quantityBadge({
|
|
122
91
|
x: w - pad,
|
|
123
|
-
y: pad +
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
anchor: 'end',
|
|
92
|
+
y: pad + BADGE_Y,
|
|
93
|
+
quantity,
|
|
94
|
+
label: formatMass(quantity * resolved.mass),
|
|
95
|
+
tone: identity,
|
|
128
96
|
})
|
|
129
97
|
|
|
130
|
-
const
|
|
131
|
-
|
|
98
|
+
const icon = resolved.category
|
|
99
|
+
? resourceIcon(resolved.category, {x: pad, y: pad + ICON_Y - 2, size: 28})
|
|
100
|
+
: ''
|
|
101
|
+
|
|
102
|
+
const name = titleText(pad + 34, pad + 22, resolved)
|
|
132
103
|
|
|
133
104
|
const statsSvg = rows
|
|
134
105
|
.map((row, i) =>
|
|
135
106
|
statBar({
|
|
136
107
|
x: pad,
|
|
137
|
-
y:
|
|
108
|
+
y: statsYStart + i * STAT_ROW_H,
|
|
138
109
|
width: innerW,
|
|
139
110
|
label: row.label,
|
|
140
111
|
abbreviation: row.abbreviation,
|
|
141
112
|
value: row.value,
|
|
142
113
|
color: row.color,
|
|
143
114
|
inverted: row.inverted,
|
|
115
|
+
stars: row.value !== null ? starsForStat(row.value) : undefined,
|
|
144
116
|
})
|
|
145
117
|
)
|
|
146
118
|
.join('')
|
|
147
119
|
|
|
148
|
-
const inner = `${chrome}${icon}${name}${badge}${
|
|
120
|
+
const inner = `${chrome}${icon}${name}${badge}${metaSvg}${statsSvg}`
|
|
149
121
|
|
|
150
122
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${height}" viewBox="0 0 ${w} ${height}">${inner}</svg>`
|
|
151
123
|
}
|
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
import type {TextSpan} from '@shipload/sdk'
|
|
2
|
+
import {formatLocation, formatMassScaled} from '@shipload/sdk'
|
|
2
3
|
import {panel} from '../primitives/panel.ts'
|
|
3
4
|
import {iconHex} from '../primitives/icon-hex.ts'
|
|
4
|
-
import {
|
|
5
|
-
import {divider} from '../primitives/divider.ts'
|
|
5
|
+
import {entityIcon, entityIconSlugForName} from '../primitives/entity-icon.ts'
|
|
6
6
|
import {moduleSlot} from '../primitives/module-slot.ts'
|
|
7
7
|
import {quantityBadge} from '../primitives/quantity-badge.ts'
|
|
8
8
|
import {wrapText} from '../primitives/wrap.ts'
|
|
9
9
|
import {tokens} from '../tokens/index.ts'
|
|
10
|
+
import {
|
|
11
|
+
tierBorder,
|
|
12
|
+
metaRowBlock,
|
|
13
|
+
titleParts,
|
|
14
|
+
capabilityColor,
|
|
15
|
+
BADGE_Y,
|
|
16
|
+
HEADER_H,
|
|
17
|
+
ICON_Y,
|
|
18
|
+
BOTTOM_PAD,
|
|
19
|
+
} from './_shared.ts'
|
|
20
|
+
|
|
21
|
+
const HULL_MASS_LABELS = new Set(['mass', 'capacity'])
|
|
22
|
+
|
|
23
|
+
const ENTITY_COLOR = tokens.colors.brand.cyan
|
|
10
24
|
|
|
11
25
|
export interface ShipPanelSlot {
|
|
12
26
|
name?: string
|
|
13
27
|
installed: boolean
|
|
28
|
+
capability?: string
|
|
14
29
|
description?: string | TextSpan[]
|
|
15
30
|
}
|
|
16
31
|
|
|
@@ -18,22 +33,35 @@ export interface ShipPanelProps {
|
|
|
18
33
|
name: string
|
|
19
34
|
tier: number
|
|
20
35
|
quantity?: number
|
|
36
|
+
location?: {x: number; y: number}
|
|
21
37
|
attributes: {capability: string; attributes: {label: string; value: number}[]}[]
|
|
22
38
|
slots: ShipPanelSlot[]
|
|
23
39
|
}
|
|
24
40
|
|
|
25
|
-
function
|
|
26
|
-
return
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
function tierBorder(tier: number): string {
|
|
30
|
-
return tokens.colors.tier[tier] ?? tokens.colors.surface.panelBorder
|
|
41
|
+
function formatHullValue(label: string, value: number): string {
|
|
42
|
+
return HULL_MASS_LABELS.has(label.toLowerCase())
|
|
43
|
+
? formatMassScaled(value)
|
|
44
|
+
: value.toLocaleString('en-US')
|
|
31
45
|
}
|
|
32
46
|
|
|
33
47
|
const MODULE_LABEL_PREFIX = (capability: string) => `${capability}: `
|
|
34
48
|
|
|
35
|
-
function
|
|
36
|
-
|
|
49
|
+
function fallbackEntityCode(name: string): string {
|
|
50
|
+
const words = name
|
|
51
|
+
.replace(/\s+T\d+\s*$/i, '')
|
|
52
|
+
.trim()
|
|
53
|
+
.split(/\s+/)
|
|
54
|
+
.filter(Boolean)
|
|
55
|
+
if (words.length >= 2)
|
|
56
|
+
return words
|
|
57
|
+
.map((word) => word[0])
|
|
58
|
+
.join('')
|
|
59
|
+
.slice(0, 2)
|
|
60
|
+
.toUpperCase()
|
|
61
|
+
return (words[0] ?? 'EN').slice(0, 2).toUpperCase().padEnd(2, 'N')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function lineCountFor(slot: ShipPanelSlot): number {
|
|
37
65
|
const desc = slot.description
|
|
38
66
|
const plain =
|
|
39
67
|
typeof desc === 'string'
|
|
@@ -41,12 +69,25 @@ function rowHeightFor(slot: ShipPanelSlot): number {
|
|
|
41
69
|
: Array.isArray(desc)
|
|
42
70
|
? desc.map((s) => s.text).join('')
|
|
43
71
|
: ''
|
|
44
|
-
if (plain.length === 0) return
|
|
45
|
-
const combined = MODULE_LABEL_PREFIX(slot.name ?? 'Module') + plain
|
|
46
|
-
|
|
72
|
+
if (plain.length === 0) return 0
|
|
73
|
+
const combined = MODULE_LABEL_PREFIX(slot.capability ?? slot.name ?? 'Module') + plain
|
|
74
|
+
return Math.max(1, wrapText({value: combined, charsPerLine: 36}).length)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function rowHeightFor(slot: ShipPanelSlot): number {
|
|
78
|
+
if (!slot.installed) return 24
|
|
79
|
+
const lineCount = lineCountFor(slot)
|
|
80
|
+
if (lineCount === 0) return 24
|
|
47
81
|
return 10 + lineCount * 14
|
|
48
82
|
}
|
|
49
83
|
|
|
84
|
+
function contentBottomOffsetFor(slot: ShipPanelSlot): number {
|
|
85
|
+
if (!slot.installed) return 14
|
|
86
|
+
const lineCount = lineCountFor(slot)
|
|
87
|
+
if (lineCount === 0) return 14
|
|
88
|
+
return 9 + (lineCount - 1) * 14 + 5
|
|
89
|
+
}
|
|
90
|
+
|
|
50
91
|
export function renderShipPanel(props: ShipPanelProps): string {
|
|
51
92
|
const w = tokens.spacing.panelWidth
|
|
52
93
|
const pad = tokens.spacing.panelPadding
|
|
@@ -54,91 +95,64 @@ export function renderShipPanel(props: ShipPanelProps): string {
|
|
|
54
95
|
const quantity = props.quantity ?? 0
|
|
55
96
|
|
|
56
97
|
const hullGroup = props.attributes?.find((g) => g.capability.toLowerCase() === 'hull')
|
|
57
|
-
const
|
|
98
|
+
const hullAttrs = (hullGroup?.attributes ?? []).map((a) => ({
|
|
99
|
+
label: a.label,
|
|
100
|
+
value: formatHullValue(a.label, a.value),
|
|
101
|
+
}))
|
|
102
|
+
const metaRows = props.location
|
|
103
|
+
? [{label: 'Location', value: formatLocation(props.location)}, ...hullAttrs]
|
|
104
|
+
: hullAttrs
|
|
58
105
|
|
|
59
|
-
const headerH = 48
|
|
60
|
-
const hullHeaderH = 20
|
|
61
|
-
const hullRowH = 22
|
|
62
106
|
const sectionGap = 12
|
|
63
|
-
const rowHeights = props.slots.map(rowHeightFor)
|
|
64
|
-
const modulesHeight = rowHeights.reduce((a, b) => a + b, 0)
|
|
65
|
-
const height =
|
|
66
|
-
headerH + hullHeaderH + hullRows.length * hullRowH + sectionGap + modulesHeight + pad
|
|
67
107
|
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
const icon = iconHex({
|
|
71
|
-
x: pad,
|
|
72
|
-
y: pad + 4,
|
|
73
|
-
color: tokens.colors.text.accent,
|
|
74
|
-
code: 'SH',
|
|
75
|
-
})
|
|
108
|
+
const metaYStart = pad + HEADER_H
|
|
109
|
+
const {svg: metaSvg, height: metaH} = metaRowBlock(pad, metaYStart, innerW, metaRows)
|
|
76
110
|
|
|
77
|
-
const
|
|
78
|
-
x: pad + 34,
|
|
79
|
-
y: pad + 22,
|
|
80
|
-
value: props.name,
|
|
81
|
-
size: tokens.typography.sizes.title,
|
|
82
|
-
weight: 700,
|
|
83
|
-
family: tokens.typography.display,
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
const badge = quantityBadge({x: w - pad, y: pad, quantity})
|
|
87
|
-
|
|
88
|
-
const hullHeader = text({
|
|
89
|
-
x: pad,
|
|
90
|
-
y: pad + headerH,
|
|
91
|
-
value: 'HULL',
|
|
92
|
-
size: tokens.typography.sizes.subtitle,
|
|
93
|
-
weight: 700,
|
|
94
|
-
color: tokens.colors.text.secondary,
|
|
95
|
-
letterSpacing: 1,
|
|
96
|
-
})
|
|
111
|
+
const slotsYStart = metaYStart + metaH + sectionGap
|
|
97
112
|
|
|
98
|
-
let y =
|
|
99
|
-
let hullSvg = ''
|
|
100
|
-
for (const row of hullRows) {
|
|
101
|
-
hullSvg +=
|
|
102
|
-
text({
|
|
103
|
-
x: pad,
|
|
104
|
-
y: y + 12,
|
|
105
|
-
value: row.label,
|
|
106
|
-
size: tokens.typography.sizes.body,
|
|
107
|
-
color: tokens.colors.text.secondary,
|
|
108
|
-
}) +
|
|
109
|
-
text({
|
|
110
|
-
x: w - pad,
|
|
111
|
-
y: y + 12,
|
|
112
|
-
value: formatNumber(row.value),
|
|
113
|
-
size: tokens.typography.sizes.body,
|
|
114
|
-
weight: 700,
|
|
115
|
-
anchor: 'end',
|
|
116
|
-
}) +
|
|
117
|
-
divider({
|
|
118
|
-
x: pad,
|
|
119
|
-
y: y + hullRowH - 4,
|
|
120
|
-
width: innerW,
|
|
121
|
-
color: tokens.colors.surface.panelBorderBright,
|
|
122
|
-
})
|
|
123
|
-
y += hullRowH
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
y += sectionGap
|
|
113
|
+
let y = slotsYStart
|
|
127
114
|
let modulesSvg = ''
|
|
128
|
-
|
|
129
|
-
|
|
115
|
+
let lastContentBottom = slotsYStart
|
|
116
|
+
props.slots.forEach((slot, i) => {
|
|
117
|
+
const accentColor = slot.installed
|
|
118
|
+
? capabilityColor(slot.capability ?? slot.name ?? 'Module')
|
|
119
|
+
: ENTITY_COLOR
|
|
130
120
|
modulesSvg += moduleSlot({
|
|
131
121
|
x: pad,
|
|
132
122
|
y,
|
|
133
123
|
width: innerW,
|
|
134
124
|
installed: slot.installed,
|
|
135
|
-
capability: slot.name,
|
|
125
|
+
capability: slot.capability ?? slot.name,
|
|
136
126
|
description: slot.description,
|
|
137
|
-
accentColor
|
|
127
|
+
accentColor,
|
|
138
128
|
})
|
|
139
|
-
y
|
|
140
|
-
|
|
129
|
+
lastContentBottom = y + contentBottomOffsetFor(slot)
|
|
130
|
+
if (i < props.slots.length - 1) y += rowHeightFor(slot)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
const height = lastContentBottom + BOTTOM_PAD
|
|
134
|
+
|
|
135
|
+
const chrome = panel({width: w, height, borderColor: tierBorder(props.tier)})
|
|
136
|
+
|
|
137
|
+
const entitySlug = entityIconSlugForName(props.name)
|
|
138
|
+
const icon = entitySlug
|
|
139
|
+
? entityIcon(entitySlug, {x: pad, y: pad + ICON_Y - 2, size: 28})
|
|
140
|
+
: iconHex({
|
|
141
|
+
x: pad,
|
|
142
|
+
y: pad + ICON_Y,
|
|
143
|
+
color: ENTITY_COLOR,
|
|
144
|
+
code: fallbackEntityCode(props.name),
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
const name = titleParts(pad + 34, pad + 22, props.name, props.tier)
|
|
148
|
+
|
|
149
|
+
const badge = quantityBadge({
|
|
150
|
+
x: w - pad,
|
|
151
|
+
y: pad + BADGE_Y,
|
|
152
|
+
quantity,
|
|
153
|
+
tone: ENTITY_COLOR,
|
|
154
|
+
})
|
|
141
155
|
|
|
142
|
-
const inner = `${chrome}${icon}${name}${badge}${
|
|
156
|
+
const inner = `${chrome}${icon}${name}${badge}${metaSvg}${modulesSvg}`
|
|
143
157
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${height}" viewBox="0 0 ${w} ${height}">${inner}</svg>`
|
|
144
158
|
}
|
package/src/tokens/colors.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {tierColors} from '@shipload/sdk'
|
|
1
|
+
import {categoryColors, tierColors} from '@shipload/sdk'
|
|
2
2
|
|
|
3
3
|
export const colors = {
|
|
4
4
|
surface: {
|
|
@@ -10,7 +10,7 @@ export const colors = {
|
|
|
10
10
|
text: {
|
|
11
11
|
primary: '#e6e8ec',
|
|
12
12
|
secondary: '#8f98a8',
|
|
13
|
-
muted: '#
|
|
13
|
+
muted: '#7c8698',
|
|
14
14
|
accent: '#f4c96b',
|
|
15
15
|
},
|
|
16
16
|
brand: {
|
|
@@ -18,16 +18,10 @@ export const colors = {
|
|
|
18
18
|
teal: '#2fd6d1',
|
|
19
19
|
cyan: '#6cb9ff',
|
|
20
20
|
},
|
|
21
|
-
category:
|
|
22
|
-
ore: '#C26D3F',
|
|
23
|
-
crystal: '#4ADBFF',
|
|
24
|
-
gas: '#B8E4A0',
|
|
25
|
-
regolith: '#C4A57B',
|
|
26
|
-
biomass: '#5A8B3E',
|
|
27
|
-
},
|
|
21
|
+
category: categoryColors,
|
|
28
22
|
tier: tierColors,
|
|
29
23
|
accent: {
|
|
30
|
-
component: '#
|
|
24
|
+
component: '#7E93C4',
|
|
31
25
|
},
|
|
32
26
|
capability: {
|
|
33
27
|
engine: '#4a8abf',
|
|
@@ -9,6 +9,7 @@ export const ITEM_LOADER_T1 = 10103
|
|
|
9
9
|
export const ITEM_MANUFACTURING_T1 = 10104
|
|
10
10
|
export const ITEM_STORAGE_T1 = 10105
|
|
11
11
|
export const ITEM_HAULER_T1 = 10106
|
|
12
|
+
export const ITEM_CONTAINER_T1_PACKED = 10200
|
|
12
13
|
export const ITEM_SHIP_T1_PACKED = 10201
|
|
13
14
|
|
|
14
15
|
export const MODULE_ENGINE = 1
|
|
@@ -49,7 +50,7 @@ export function cargoShipT1Packed(opts?: {
|
|
|
49
50
|
})
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
export const
|
|
53
|
+
export const ITEM_PLATE = 10001
|
|
53
54
|
|
|
54
55
|
export const FIXTURES = {
|
|
55
56
|
oreT1: cargoOreT1('0x123456789ABCDEF'),
|
|
@@ -61,8 +62,8 @@ export const FIXTURES = {
|
|
|
61
62
|
stats: '0xDEADBEEF1234',
|
|
62
63
|
modules: [],
|
|
63
64
|
}),
|
|
64
|
-
|
|
65
|
-
item_id:
|
|
65
|
+
plate: ServerContract.Types.cargo_item.from({
|
|
66
|
+
item_id: ITEM_PLATE,
|
|
66
67
|
quantity: 1,
|
|
67
68
|
stats: '0x7FFF',
|
|
68
69
|
modules: [],
|
|
@@ -115,6 +116,12 @@ export const FIXTURES = {
|
|
|
115
116
|
stats: '0',
|
|
116
117
|
modules: [],
|
|
117
118
|
}),
|
|
119
|
+
containerT1: ServerContract.Types.cargo_item.from({
|
|
120
|
+
item_id: ITEM_CONTAINER_T1_PACKED,
|
|
121
|
+
quantity: 1,
|
|
122
|
+
stats: '0',
|
|
123
|
+
modules: [],
|
|
124
|
+
}),
|
|
118
125
|
shipT1TwoModules: cargoShipT1Packed(),
|
|
119
126
|
shipT1OnlyEngine: cargoShipT1Packed({onlyEngine: true}),
|
|
120
127
|
} as const
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import type {CategoryIconShape} from '@shipload/sdk'
|
|
2
|
-
import {el} from './svg.ts'
|
|
3
|
-
|
|
4
|
-
export interface CategoryIconPathOpts {
|
|
5
|
-
shape: CategoryIconShape
|
|
6
|
-
cx: number
|
|
7
|
-
cy: number
|
|
8
|
-
size: number
|
|
9
|
-
color: string
|
|
10
|
-
strokeWidth?: number
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface CategoryIconSvgOpts {
|
|
14
|
-
size?: number
|
|
15
|
-
color?: string
|
|
16
|
-
strokeWidth?: number
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function hexPoints(cx: number, cy: number, r: number): string {
|
|
20
|
-
const pts: string[] = []
|
|
21
|
-
for (let i = 0; i < 6; i++) {
|
|
22
|
-
const angle = (Math.PI / 3) * i - Math.PI / 2
|
|
23
|
-
pts.push(
|
|
24
|
-
`${(cx + r * Math.cos(angle)).toFixed(2)},${(cy + r * Math.sin(angle)).toFixed(2)}`
|
|
25
|
-
)
|
|
26
|
-
}
|
|
27
|
-
return pts.join(' ')
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function diamondPoints(cx: number, cy: number, r: number): string {
|
|
31
|
-
return [
|
|
32
|
-
`${cx.toFixed(2)},${(cy - r).toFixed(2)}`,
|
|
33
|
-
`${(cx + r).toFixed(2)},${cy.toFixed(2)}`,
|
|
34
|
-
`${cx.toFixed(2)},${(cy + r).toFixed(2)}`,
|
|
35
|
-
`${(cx - r).toFixed(2)},${cy.toFixed(2)}`,
|
|
36
|
-
].join(' ')
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function starPoints(cx: number, cy: number, r: number): string {
|
|
40
|
-
const inner = r * 0.45
|
|
41
|
-
const pts: string[] = []
|
|
42
|
-
for (let i = 0; i < 10; i++) {
|
|
43
|
-
const angle = (Math.PI / 5) * i - Math.PI / 2
|
|
44
|
-
const radius = i % 2 === 0 ? r : inner
|
|
45
|
-
pts.push(
|
|
46
|
-
`${(cx + radius * Math.cos(angle)).toFixed(2)},${(cy + radius * Math.sin(angle)).toFixed(2)}`
|
|
47
|
-
)
|
|
48
|
-
}
|
|
49
|
-
return pts.join(' ')
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function categoryIconPath({
|
|
53
|
-
shape,
|
|
54
|
-
cx,
|
|
55
|
-
cy,
|
|
56
|
-
size,
|
|
57
|
-
color,
|
|
58
|
-
strokeWidth,
|
|
59
|
-
}: CategoryIconPathOpts): string {
|
|
60
|
-
const r = size / 2
|
|
61
|
-
const stroked = strokeWidth && strokeWidth > 0
|
|
62
|
-
const shapeAttrs = stroked
|
|
63
|
-
? {
|
|
64
|
-
fill: 'none',
|
|
65
|
-
stroke: color,
|
|
66
|
-
'stroke-width': strokeWidth,
|
|
67
|
-
'stroke-linejoin': 'round' as const,
|
|
68
|
-
}
|
|
69
|
-
: {fill: color}
|
|
70
|
-
switch (shape) {
|
|
71
|
-
case 'hex':
|
|
72
|
-
return el('polygon', {points: hexPoints(cx, cy, r), ...shapeAttrs})
|
|
73
|
-
case 'diamond':
|
|
74
|
-
return el('polygon', {points: diamondPoints(cx, cy, r), ...shapeAttrs})
|
|
75
|
-
case 'star':
|
|
76
|
-
return el('polygon', {points: starPoints(cx, cy, r), ...shapeAttrs})
|
|
77
|
-
case 'circle':
|
|
78
|
-
return el('circle', {cx, cy, r, ...shapeAttrs})
|
|
79
|
-
case 'square':
|
|
80
|
-
return el('rect', {x: cx - r, y: cy - r, width: size, height: size, ...shapeAttrs})
|
|
81
|
-
}
|
|
82
|
-
const _exhaustive: never = shape
|
|
83
|
-
throw new Error(`Unknown CategoryIconShape: ${String(_exhaustive)}`)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export function categoryIconSvg(shape: CategoryIconShape, opts: CategoryIconSvgOpts = {}): string {
|
|
87
|
-
const size = opts.size ?? 16
|
|
88
|
-
const color = opts.color ?? '#ffffff'
|
|
89
|
-
const cx = size / 2
|
|
90
|
-
const cy = size / 2
|
|
91
|
-
const iconSize = size * 0.85
|
|
92
|
-
const inner = categoryIconPath({
|
|
93
|
-
shape,
|
|
94
|
-
cx,
|
|
95
|
-
cy,
|
|
96
|
-
size: iconSize,
|
|
97
|
-
color,
|
|
98
|
-
strokeWidth: opts.strokeWidth,
|
|
99
|
-
})
|
|
100
|
-
return el(
|
|
101
|
-
'svg',
|
|
102
|
-
{
|
|
103
|
-
xmlns: 'http://www.w3.org/2000/svg',
|
|
104
|
-
width: size,
|
|
105
|
-
height: size,
|
|
106
|
-
viewBox: `0 0 ${size} ${size}`,
|
|
107
|
-
},
|
|
108
|
-
inner
|
|
109
|
-
)
|
|
110
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import {text} from './text.ts'
|
|
2
|
-
import {tokens} from '../tokens/index.ts'
|
|
3
|
-
|
|
4
|
-
export interface CompactRowProps {
|
|
5
|
-
x: number
|
|
6
|
-
y: number
|
|
7
|
-
width: number
|
|
8
|
-
label: string
|
|
9
|
-
value: string
|
|
10
|
-
labelColor?: string
|
|
11
|
-
valueColor?: string
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function compactRow(p: CompactRowProps): string {
|
|
15
|
-
const labelColor = p.labelColor ?? tokens.colors.text.secondary
|
|
16
|
-
const valueColor = p.valueColor ?? tokens.colors.text.primary
|
|
17
|
-
return (
|
|
18
|
-
text({
|
|
19
|
-
x: p.x,
|
|
20
|
-
y: p.y,
|
|
21
|
-
value: p.label,
|
|
22
|
-
size: 11,
|
|
23
|
-
weight: 500,
|
|
24
|
-
family: tokens.typography.sans,
|
|
25
|
-
color: labelColor,
|
|
26
|
-
}) +
|
|
27
|
-
text({
|
|
28
|
-
x: p.x + p.width,
|
|
29
|
-
y: p.y,
|
|
30
|
-
value: p.value,
|
|
31
|
-
size: 11,
|
|
32
|
-
weight: 700,
|
|
33
|
-
family: tokens.typography.sans,
|
|
34
|
-
color: valueColor,
|
|
35
|
-
anchor: 'end',
|
|
36
|
-
})
|
|
37
|
-
)
|
|
38
|
-
}
|