@shipload/sdk 2.0.0-rc12 → 2.0.0-rc14
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/lib/shipload.d.ts +137 -57
- package/lib/shipload.js +636 -335
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +626 -334
- package/lib/shipload.m.js.map +1 -1
- package/package.json +1 -1
- package/src/capabilities/modules.ts +3 -3
- package/src/capabilities/storage.ts +6 -9
- package/src/contracts/server.ts +37 -34
- package/src/data/colors.ts +19 -1
- package/src/data/recipes.ts +3 -3
- package/src/derivation/crafting.ts +46 -32
- package/src/derivation/stratum.ts +12 -0
- package/src/entities/container.ts +1 -0
- package/src/entities/makers.ts +126 -11
- package/src/entities/ship-deploy.ts +9 -14
- package/src/entities/ship.ts +16 -4
- package/src/entities/warehouse.ts +36 -1
- package/src/index-module.ts +22 -5
- package/src/managers/actions.ts +48 -8
- package/src/nft/description.ts +31 -30
- package/src/nft/deserializers.ts +8 -8
- package/src/resolution/describe-module.ts +165 -0
- package/src/resolution/resolve-item.ts +60 -37
- package/src/scheduling/projection.ts +17 -4
- package/src/types/capabilities.ts +5 -0
package/src/managers/actions.ts
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Action,
|
|
3
|
+
Int64,
|
|
4
|
+
Name,
|
|
5
|
+
NameType,
|
|
6
|
+
UInt16,
|
|
7
|
+
UInt16Type,
|
|
8
|
+
UInt32,
|
|
9
|
+
UInt32Type,
|
|
10
|
+
UInt64,
|
|
11
|
+
UInt64Type,
|
|
12
|
+
} from '@wharfkit/antelope'
|
|
2
13
|
import {BaseManager} from './base'
|
|
3
14
|
import {CoordinatesType, EntityType, EntityTypeName} from '../types'
|
|
4
15
|
import {ServerContract} from '../contracts'
|
|
@@ -71,7 +82,8 @@ export class ActionsManager extends BaseManager {
|
|
|
71
82
|
sourceId: UInt64Type,
|
|
72
83
|
destType: EntityTypeName,
|
|
73
84
|
destId: UInt64Type,
|
|
74
|
-
|
|
85
|
+
itemId: UInt64Type,
|
|
86
|
+
stats: UInt64Type,
|
|
75
87
|
quantity: UInt64Type
|
|
76
88
|
): Action {
|
|
77
89
|
return this.server.action('transfer', {
|
|
@@ -79,7 +91,8 @@ export class ActionsManager extends BaseManager {
|
|
|
79
91
|
source_id: UInt64.from(sourceId),
|
|
80
92
|
dest_type: destType,
|
|
81
93
|
dest_id: UInt64.from(destId),
|
|
82
|
-
item_id: UInt16.from(
|
|
94
|
+
item_id: UInt16.from(itemId),
|
|
95
|
+
stats: UInt64.from(stats),
|
|
83
96
|
quantity: UInt32.from(quantity),
|
|
84
97
|
})
|
|
85
98
|
}
|
|
@@ -97,10 +110,21 @@ export class ActionsManager extends BaseManager {
|
|
|
97
110
|
})
|
|
98
111
|
}
|
|
99
112
|
|
|
100
|
-
gather(
|
|
113
|
+
gather(
|
|
114
|
+
source: EntityRefInput,
|
|
115
|
+
destination: EntityRefInput,
|
|
116
|
+
stratum: UInt16Type,
|
|
117
|
+
quantity: UInt32Type
|
|
118
|
+
): Action {
|
|
101
119
|
return this.server.action('gather', {
|
|
102
|
-
|
|
103
|
-
|
|
120
|
+
source: ServerContract.Types.entity_ref.from({
|
|
121
|
+
entity_type: source.entityType,
|
|
122
|
+
entity_id: UInt64.from(source.entityId),
|
|
123
|
+
}),
|
|
124
|
+
destination: ServerContract.Types.entity_ref.from({
|
|
125
|
+
entity_type: destination.entityType,
|
|
126
|
+
entity_id: UInt64.from(destination.entityId),
|
|
127
|
+
}),
|
|
104
128
|
stratum: UInt16.from(stratum),
|
|
105
129
|
quantity: UInt32.from(quantity),
|
|
106
130
|
})
|
|
@@ -152,14 +176,14 @@ export class ActionsManager extends BaseManager {
|
|
|
152
176
|
entityType: EntityTypeName,
|
|
153
177
|
entityId: UInt64Type,
|
|
154
178
|
packedItemId: number,
|
|
155
|
-
|
|
179
|
+
stats: bigint,
|
|
156
180
|
entityName: string
|
|
157
181
|
): Action {
|
|
158
182
|
return this.server.action('deploy', {
|
|
159
183
|
entity_type: entityType,
|
|
160
184
|
id: UInt64.from(entityId),
|
|
161
185
|
packed_item_id: UInt16.from(packedItemId),
|
|
162
|
-
|
|
186
|
+
stats: UInt64.from(stats),
|
|
163
187
|
entity_name: entityName,
|
|
164
188
|
})
|
|
165
189
|
}
|
|
@@ -194,6 +218,22 @@ export class ActionsManager extends BaseManager {
|
|
|
194
218
|
})
|
|
195
219
|
}
|
|
196
220
|
|
|
221
|
+
wrap(
|
|
222
|
+
owner: NameType,
|
|
223
|
+
entityType: EntityTypeName,
|
|
224
|
+
entityId: UInt64Type,
|
|
225
|
+
cargoId: UInt64Type,
|
|
226
|
+
quantity: UInt64Type
|
|
227
|
+
): Action {
|
|
228
|
+
return this.server.action('wrap', {
|
|
229
|
+
owner: Name.from(owner),
|
|
230
|
+
entity_type: entityType,
|
|
231
|
+
entity_id: UInt64.from(entityId),
|
|
232
|
+
cargo_id: UInt64.from(cargoId),
|
|
233
|
+
quantity: UInt64.from(quantity),
|
|
234
|
+
})
|
|
235
|
+
}
|
|
236
|
+
|
|
197
237
|
joinGame(account: NameType, companyName: string): Action[] {
|
|
198
238
|
return [this.foundCompany(account, companyName), this.join(account)]
|
|
199
239
|
}
|
package/src/nft/description.ts
CHANGED
|
@@ -25,27 +25,28 @@ function idiv(a: number, b: number): number {
|
|
|
25
25
|
return Math.floor(a / b)
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export function computeBaseHullmass(
|
|
29
|
-
const density = decodeStat(
|
|
28
|
+
export function computeBaseHullmass(stats: bigint): number {
|
|
29
|
+
const density = decodeStat(stats, 1)
|
|
30
30
|
return 25000 + 75 * density
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
export function computeBaseCapacityShip(
|
|
34
|
-
const s = decodeStat(
|
|
33
|
+
export function computeBaseCapacityShip(stats: bigint): number {
|
|
34
|
+
const s = decodeStat(stats, 0) + decodeStat(stats, 2) + decodeStat(stats, 3)
|
|
35
35
|
return Math.floor(1_000_000 * Math.pow(10, s / 2997))
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
export function computeBaseCapacityWarehouse(
|
|
39
|
-
const s = decodeStat(
|
|
38
|
+
export function computeBaseCapacityWarehouse(stats: bigint): number {
|
|
39
|
+
const s = decodeStat(stats, 0) + decodeStat(stats, 2) + decodeStat(stats, 3)
|
|
40
40
|
return Math.floor(20_000_000 * Math.pow(10, s / 2997))
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
export const computeEngineThrust = (vol: number): number => 400 + idiv(vol * 3, 4)
|
|
44
44
|
export const computeEngineDrain = (thm: number): number => Math.max(30, 50 - idiv(thm, 70))
|
|
45
45
|
export const computeGeneratorCap = (res: number): number => 300 + idiv(res, 6)
|
|
46
|
-
export const computeGeneratorRech = (clr: number): number =>
|
|
46
|
+
export const computeGeneratorRech = (clr: number): number => 1 + idiv(clr * 3, 1000)
|
|
47
47
|
export const computeGathererYield = (str: number): number => 200 + str
|
|
48
|
-
export const computeGathererDrain = (con: number): number =>
|
|
48
|
+
export const computeGathererDrain = (con: number): number =>
|
|
49
|
+
Math.max(250, 1250 - idiv(con * 25, 20))
|
|
49
50
|
export const computeGathererDepth = (tol: number): number => 200 + idiv(tol * 3, 2)
|
|
50
51
|
export const computeGathererSpeed = (ref: number): number => 100 + idiv(ref * 4, 5)
|
|
51
52
|
export const computeLoaderMass = (duc: number): number => Math.max(200, 2000 - duc * 2)
|
|
@@ -87,7 +88,7 @@ export function moduleDisplayName(itemId: number): string {
|
|
|
87
88
|
}
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
export function formatModuleLine(slot: number, itemId: number,
|
|
91
|
+
export function formatModuleLine(slot: number, itemId: number, stats: bigint): string {
|
|
91
92
|
let out = `Slot ${slot} - `
|
|
92
93
|
if (itemId === 0) {
|
|
93
94
|
out += '(empty)'
|
|
@@ -99,43 +100,43 @@ export function formatModuleLine(slot: number, itemId: number, seed: bigint): st
|
|
|
99
100
|
|
|
100
101
|
switch (subtype) {
|
|
101
102
|
case MODULE_ENGINE: {
|
|
102
|
-
const vol = decodeStat(
|
|
103
|
-
const thm = decodeStat(
|
|
103
|
+
const vol = decodeStat(stats, 0)
|
|
104
|
+
const thm = decodeStat(stats, 1)
|
|
104
105
|
out += ` Thrust ${computeEngineThrust(vol)} Drain ${computeEngineDrain(thm)}`
|
|
105
106
|
break
|
|
106
107
|
}
|
|
107
108
|
case MODULE_GENERATOR: {
|
|
108
|
-
const res = decodeStat(
|
|
109
|
-
const clr = decodeStat(
|
|
109
|
+
const res = decodeStat(stats, 0)
|
|
110
|
+
const clr = decodeStat(stats, 1)
|
|
110
111
|
out += ` Capacity ${computeGeneratorCap(res)} Recharge ${computeGeneratorRech(clr)}`
|
|
111
112
|
break
|
|
112
113
|
}
|
|
113
114
|
case MODULE_GATHERER: {
|
|
114
|
-
const str = decodeStat(
|
|
115
|
-
const tol = decodeStat(
|
|
116
|
-
const con = decodeStat(
|
|
117
|
-
const ref = decodeStat(
|
|
115
|
+
const str = decodeStat(stats, 0)
|
|
116
|
+
const tol = decodeStat(stats, 1)
|
|
117
|
+
const con = decodeStat(stats, 3)
|
|
118
|
+
const ref = decodeStat(stats, 4)
|
|
118
119
|
out += ` Yield ${computeGathererYield(str)} Depth ${computeGathererDepth(
|
|
119
120
|
tol
|
|
120
121
|
)} Speed ${computeGathererSpeed(ref)} Drain ${computeGathererDrain(con)}`
|
|
121
122
|
break
|
|
122
123
|
}
|
|
123
124
|
case MODULE_LOADER: {
|
|
124
|
-
const duc = decodeStat(
|
|
125
|
-
const pla = decodeStat(
|
|
125
|
+
const duc = decodeStat(stats, 0)
|
|
126
|
+
const pla = decodeStat(stats, 1)
|
|
126
127
|
out += ` Mass ${computeLoaderMass(duc)} Thrust ${computeLoaderThrust(pla)}`
|
|
127
128
|
break
|
|
128
129
|
}
|
|
129
130
|
case MODULE_CRAFTER: {
|
|
130
|
-
const rea = decodeStat(
|
|
131
|
-
const clr = decodeStat(
|
|
131
|
+
const rea = decodeStat(stats, 0)
|
|
132
|
+
const clr = decodeStat(stats, 1)
|
|
132
133
|
out += ` Speed ${computeCrafterSpeed(rea)} Drain ${computeCrafterDrain(clr)}`
|
|
133
134
|
break
|
|
134
135
|
}
|
|
135
136
|
case MODULE_STORAGE: {
|
|
136
|
-
const str = decodeStat(
|
|
137
|
-
const duc = decodeStat(
|
|
138
|
-
const pur = decodeStat(
|
|
137
|
+
const str = decodeStat(stats, 0)
|
|
138
|
+
const duc = decodeStat(stats, 1)
|
|
139
|
+
const pur = decodeStat(stats, 2)
|
|
139
140
|
const sum = str + duc + pur
|
|
140
141
|
const pct = 10 + idiv(sum * 10, 2997)
|
|
141
142
|
out += ` +${pct}% capacity`
|
|
@@ -147,16 +148,16 @@ export function formatModuleLine(slot: number, itemId: number, seed: bigint): st
|
|
|
147
148
|
|
|
148
149
|
export function buildEntityDescription(
|
|
149
150
|
itemId: number,
|
|
150
|
-
|
|
151
|
+
hullStats: bigint,
|
|
151
152
|
moduleItems: number[],
|
|
152
|
-
|
|
153
|
+
moduleStats: bigint[]
|
|
153
154
|
): string {
|
|
154
|
-
const hullMass = computeBaseHullmass(
|
|
155
|
+
const hullMass = computeBaseHullmass(hullStats)
|
|
155
156
|
let baseCapacity = 0
|
|
156
157
|
if (itemId === ITEM_SHIP_T1_PACKED) {
|
|
157
|
-
baseCapacity = computeBaseCapacityShip(
|
|
158
|
+
baseCapacity = computeBaseCapacityShip(hullStats)
|
|
158
159
|
} else if (itemId === ITEM_WAREHOUSE_T1_PACKED) {
|
|
159
|
-
baseCapacity = computeBaseCapacityWarehouse(
|
|
160
|
+
baseCapacity = computeBaseCapacityWarehouse(hullStats)
|
|
160
161
|
}
|
|
161
162
|
|
|
162
163
|
let out = entityDisplayName(itemId)
|
|
@@ -167,7 +168,7 @@ export function buildEntityDescription(
|
|
|
167
168
|
out += '\n\n'
|
|
168
169
|
|
|
169
170
|
for (let i = 0; i < moduleItems.length; i++) {
|
|
170
|
-
out += formatModuleLine(i, moduleItems[i],
|
|
171
|
+
out += formatModuleLine(i, moduleItems[i], moduleStats[i] ?? 0n)
|
|
171
172
|
out += '\n'
|
|
172
173
|
}
|
|
173
174
|
|
package/src/nft/deserializers.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
|
|
10
10
|
export interface NFTInstalledModule {
|
|
11
11
|
item_id: number
|
|
12
|
-
|
|
12
|
+
stats: string
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export interface NFTModuleSlot {
|
|
@@ -20,13 +20,13 @@ export interface NFTModuleSlot {
|
|
|
20
20
|
export interface NFTCargoItem {
|
|
21
21
|
item_id: number
|
|
22
22
|
quantity: number
|
|
23
|
-
|
|
23
|
+
stats: string
|
|
24
24
|
modules?: NFTModuleSlot[]
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export interface NFTCommonBase {
|
|
28
28
|
quantity: number
|
|
29
|
-
|
|
29
|
+
stats: string
|
|
30
30
|
origin_x: string
|
|
31
31
|
origin_y: string
|
|
32
32
|
}
|
|
@@ -34,7 +34,7 @@ export interface NFTCommonBase {
|
|
|
34
34
|
export function readCommonBase(data: Record<string, any>): NFTCommonBase {
|
|
35
35
|
return {
|
|
36
36
|
quantity: Number(data.quantity),
|
|
37
|
-
|
|
37
|
+
stats: String(data.stats),
|
|
38
38
|
origin_x: String(data.origin_x),
|
|
39
39
|
origin_y: String(data.origin_y),
|
|
40
40
|
}
|
|
@@ -42,7 +42,7 @@ export function readCommonBase(data: Record<string, any>): NFTCommonBase {
|
|
|
42
42
|
|
|
43
43
|
export function deserializeScalar(data: Record<string, any>, itemId: number): NFTCargoItem {
|
|
44
44
|
const base = readCommonBase(data)
|
|
45
|
-
return {item_id: itemId, quantity: base.quantity,
|
|
45
|
+
return {item_id: itemId, quantity: base.quantity, stats: base.stats}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
export const deserializeResource = deserializeScalar
|
|
@@ -52,18 +52,18 @@ export const deserializeModule = deserializeScalar
|
|
|
52
52
|
export function deserializeEntity(data: Record<string, any>, itemId: number): NFTCargoItem {
|
|
53
53
|
const base = readCommonBase(data)
|
|
54
54
|
const moduleItems: number[] = (data.module_items ?? []).map((v: any) => Number(v))
|
|
55
|
-
const
|
|
55
|
+
const moduleStats: string[] = (data.module_stats ?? []).map((v: any) => String(v))
|
|
56
56
|
const layout = getEntitySlotLayout(itemId)
|
|
57
57
|
|
|
58
58
|
const modules: NFTModuleSlot[] = layout.map((slot, i) => ({
|
|
59
59
|
type: slot.type,
|
|
60
60
|
installed:
|
|
61
61
|
moduleItems[i] && moduleItems[i] !== 0
|
|
62
|
-
? {item_id: moduleItems[i],
|
|
62
|
+
? {item_id: moduleItems[i], stats: moduleStats[i]}
|
|
63
63
|
: undefined,
|
|
64
64
|
}))
|
|
65
65
|
|
|
66
|
-
return {item_id: itemId, quantity: base.quantity,
|
|
66
|
+
return {item_id: itemId, quantity: base.quantity, stats: base.stats, modules}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
export function deserializeAsset(data: Record<string, any>, itemId: number): NFTCargoItem {
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import type {ResolvedItem, ResolvedModuleSlot} from './resolve-item'
|
|
2
|
+
|
|
3
|
+
export interface TextSpan {
|
|
4
|
+
text: string
|
|
5
|
+
highlight?: boolean
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface CapabilityInput {
|
|
9
|
+
capability: string
|
|
10
|
+
attributes: {label: string; value: number}[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ModuleDescription {
|
|
14
|
+
id: string
|
|
15
|
+
template: string
|
|
16
|
+
params: Readonly<Record<string, number | string>>
|
|
17
|
+
highlightKeys: readonly string[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface RenderDescriptionOptions {
|
|
21
|
+
translate?: (id: string, fallback: string) => string
|
|
22
|
+
formatNumber?: (n: number) => string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface TemplateSpec {
|
|
26
|
+
id: string
|
|
27
|
+
template: string
|
|
28
|
+
params: readonly [string, string][]
|
|
29
|
+
highlightKeys: readonly string[]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const TEMPLATES: Record<string, TemplateSpec> = {
|
|
33
|
+
engine: {
|
|
34
|
+
id: 'module.engine.description',
|
|
35
|
+
template:
|
|
36
|
+
'generates {thrust} thrust for travel while draining {drain} energy per distance travelled',
|
|
37
|
+
params: [
|
|
38
|
+
['thrust', 'Thrust'],
|
|
39
|
+
['drain', 'Drain'],
|
|
40
|
+
],
|
|
41
|
+
highlightKeys: ['thrust', 'drain'],
|
|
42
|
+
},
|
|
43
|
+
generator: {
|
|
44
|
+
id: 'module.generator.description',
|
|
45
|
+
template:
|
|
46
|
+
'holds {capacity} maximum energy and restores {recharge} per second while recharging',
|
|
47
|
+
params: [
|
|
48
|
+
['capacity', 'Capacity'],
|
|
49
|
+
['recharge', 'Recharge'],
|
|
50
|
+
],
|
|
51
|
+
highlightKeys: ['capacity', 'recharge'],
|
|
52
|
+
},
|
|
53
|
+
gatherer: {
|
|
54
|
+
id: 'module.gatherer.description',
|
|
55
|
+
template:
|
|
56
|
+
'mines resources at {yield} speed to a max depth of {depth} with {speed} gather speed while draining {drain} energy per second',
|
|
57
|
+
params: [
|
|
58
|
+
['yield', 'Yield'],
|
|
59
|
+
['drain', 'Drain'],
|
|
60
|
+
['depth', 'Depth'],
|
|
61
|
+
['speed', 'Speed'],
|
|
62
|
+
],
|
|
63
|
+
highlightKeys: ['yield', 'depth', 'speed', 'drain'],
|
|
64
|
+
},
|
|
65
|
+
loader: {
|
|
66
|
+
id: 'module.loader.description',
|
|
67
|
+
template:
|
|
68
|
+
'{quantity} loader that generates {thrust} thrust with a weight of {mass} per unit',
|
|
69
|
+
params: [
|
|
70
|
+
['quantity', 'Quantity'],
|
|
71
|
+
['thrust', 'Thrust'],
|
|
72
|
+
['mass', 'Mass'],
|
|
73
|
+
],
|
|
74
|
+
highlightKeys: ['quantity', 'thrust', 'mass'],
|
|
75
|
+
},
|
|
76
|
+
manufacturing: {
|
|
77
|
+
id: 'module.manufacturing.description',
|
|
78
|
+
template: 'manufactures items at {speed} speed while draining {drain} energy per second',
|
|
79
|
+
params: [
|
|
80
|
+
['speed', 'Speed'],
|
|
81
|
+
['drain', 'Drain'],
|
|
82
|
+
],
|
|
83
|
+
highlightKeys: ['speed', 'drain'],
|
|
84
|
+
},
|
|
85
|
+
storage: {
|
|
86
|
+
id: 'module.storage.description',
|
|
87
|
+
template: 'boosts cargo capacity by {bonus}%',
|
|
88
|
+
params: [['bonus', 'Capacity Bonus']],
|
|
89
|
+
highlightKeys: ['bonus'],
|
|
90
|
+
},
|
|
91
|
+
hauler: {
|
|
92
|
+
id: 'module.hauler.description',
|
|
93
|
+
template:
|
|
94
|
+
'locks onto up to {capacity} targets at {efficiency} efficiency while draining {drain} energy per distance travelled per target',
|
|
95
|
+
params: [
|
|
96
|
+
['capacity', 'Capacity'],
|
|
97
|
+
['efficiency', 'Efficiency'],
|
|
98
|
+
['drain', 'Drain'],
|
|
99
|
+
],
|
|
100
|
+
highlightKeys: ['capacity', 'efficiency', 'drain'],
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function describeModule(input: CapabilityInput): ModuleDescription | null {
|
|
105
|
+
if (!input.attributes || input.attributes.length === 0) return null
|
|
106
|
+
const key = input.capability.toLowerCase()
|
|
107
|
+
const spec = TEMPLATES[key]
|
|
108
|
+
if (!spec) return null
|
|
109
|
+
const params: Record<string, number | string> = {}
|
|
110
|
+
for (const [paramName, attrLabel] of spec.params) {
|
|
111
|
+
const attr = input.attributes.find((a) => a.label === attrLabel)
|
|
112
|
+
if (attr) params[paramName] = attr.value
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
id: spec.id,
|
|
116
|
+
template: spec.template,
|
|
117
|
+
params,
|
|
118
|
+
highlightKeys: spec.highlightKeys,
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function describeModuleForItem(resolved: ResolvedItem): ModuleDescription | null {
|
|
123
|
+
if (resolved.itemType !== 'module') return null
|
|
124
|
+
const group = resolved.attributes?.[0]
|
|
125
|
+
if (!group) return null
|
|
126
|
+
return describeModule({capability: group.capability, attributes: group.attributes})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function describeModuleForSlot(slot: ResolvedModuleSlot): ModuleDescription | null {
|
|
130
|
+
if (!slot.installed || !slot.name || !slot.attributes) return null
|
|
131
|
+
return describeModule({capability: slot.name, attributes: slot.attributes})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function renderDescription(
|
|
135
|
+
desc: ModuleDescription,
|
|
136
|
+
options?: RenderDescriptionOptions
|
|
137
|
+
): TextSpan[] {
|
|
138
|
+
const translate = options?.translate ?? ((_id: string, fallback: string) => fallback)
|
|
139
|
+
const formatNumber = options?.formatNumber ?? ((n: number) => n.toLocaleString('en-US'))
|
|
140
|
+
const tpl = translate(desc.id, desc.template)
|
|
141
|
+
|
|
142
|
+
const spans: TextSpan[] = []
|
|
143
|
+
const regex = /\{([A-Za-z_][A-Za-z0-9_]*)\}/g
|
|
144
|
+
let lastIndex = 0
|
|
145
|
+
let m: RegExpExecArray | null
|
|
146
|
+
while ((m = regex.exec(tpl)) !== null) {
|
|
147
|
+
if (m.index > lastIndex) {
|
|
148
|
+
spans.push({text: tpl.slice(lastIndex, m.index)})
|
|
149
|
+
}
|
|
150
|
+
const paramName = m[1] ?? ''
|
|
151
|
+
const raw = desc.params[paramName]
|
|
152
|
+
if (raw === undefined) {
|
|
153
|
+
spans.push({text: `{${paramName}}`})
|
|
154
|
+
} else {
|
|
155
|
+
const formatted = typeof raw === 'number' ? formatNumber(raw) : raw
|
|
156
|
+
const highlight = (desc.highlightKeys as readonly string[]).includes(paramName)
|
|
157
|
+
spans.push(highlight ? {text: formatted, highlight: true} : {text: formatted})
|
|
158
|
+
}
|
|
159
|
+
lastIndex = m.index + m[0].length
|
|
160
|
+
}
|
|
161
|
+
if (lastIndex < tpl.length) {
|
|
162
|
+
spans.push({text: tpl.slice(lastIndex)})
|
|
163
|
+
}
|
|
164
|
+
return spans
|
|
165
|
+
}
|