@disy/cadenza.js 2.2.3 β†’ 2.3.0

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 (43) hide show
  1. package/CHANGELOG.md +15 -4
  2. package/README.md +1 -322
  3. package/apidoc/assets/highlight.css +0 -7
  4. package/apidoc/assets/main.js +2 -2
  5. package/apidoc/assets/navigation.js +1 -1
  6. package/apidoc/assets/search.js +1 -1
  7. package/apidoc/assets/style.css +46 -15
  8. package/apidoc/classes/AbortError.html +3 -3
  9. package/apidoc/classes/CadenzaClient.html +68 -89
  10. package/apidoc/classes/CadenzaError.html +8 -6
  11. package/apidoc/functions/cadenza.html +4 -4
  12. package/apidoc/index.html +152 -9
  13. package/apidoc/interfaces/CadenzaEvent.html +6 -6
  14. package/apidoc/interfaces/ExternalLinkKey.html +6 -3
  15. package/apidoc/interfaces/Geometry.html +2 -2
  16. package/apidoc/interfaces/PageSource.html +2 -2
  17. package/apidoc/modules.html +14 -2
  18. package/apidoc/types/CadenzaChangeSelectionEvent.html +2 -0
  19. package/apidoc/types/CadenzaDrillThroughEvent.html +7 -0
  20. package/apidoc/types/CadenzaEditGeometryCancelEvent.html +2 -0
  21. package/apidoc/types/CadenzaEditGeometryOkEvent.html +2 -0
  22. package/apidoc/types/CadenzaEditGeometryUpdateEvent.html +2 -0
  23. package/apidoc/types/CadenzaErrorEvent.html +2 -2
  24. package/apidoc/types/CadenzaEventByType.html +1 -0
  25. package/apidoc/types/CadenzaEventType.html +2 -0
  26. package/apidoc/types/CadenzaObjectInfoEvent.html +2 -0
  27. package/apidoc/types/CadenzaSelectObjectsCancelEvent.html +2 -0
  28. package/apidoc/types/CadenzaSelectObjectsOkEvent.html +2 -0
  29. package/apidoc/types/DataType.html +3 -2
  30. package/apidoc/types/EmbeddingTargetId.html +9 -2
  31. package/apidoc/types/Extent.html +1 -1
  32. package/apidoc/types/FilterVariables.html +5 -2
  33. package/apidoc/types/GeometryType.html +1 -1
  34. package/apidoc/types/GlobalId.html +2 -2
  35. package/apidoc/types/OpaqueString.html +6 -0
  36. package/apidoc/types/OperationMode.html +1 -1
  37. package/apidoc/types/TablePart.html +1 -1
  38. package/apidoc/types/UiFeature.html +3 -3
  39. package/apidoc/types/WorkbookLayerPath.html +3 -0
  40. package/cadenza.d.ts +320 -56
  41. package/cadenza.js +344 -69
  42. package/package.json +6 -6
  43. package/sandbox.html +197 -31
package/cadenza.js CHANGED
@@ -24,13 +24,42 @@ globalThis.cadenza = Object.assign((/** @type Parameters<cadenza> */ ...args) =>
24
24
  return cadenza;
25
25
  },
26
26
  });
27
- /** @typedef {string} EmbeddingTargetId - The ID of an embedding target */
28
- /** @typedef {string} GlobalId - The ID of a navigator item */
27
+ /**
28
+ * @template {string} T
29
+ * @typedef {string & {__type: T}} OpaqueString - A specific `string` type that is not assignable from another string
30
+ *
31
+ * The idea is to have a specific type e.g. for the {@link EmbeddingTargetId} instead of a plain `string`.
32
+ * You don't need to _actually_ add that `__type` property. In TS code, just use a
33
+ * [type assertion](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-assertions)
34
+ * (e.g. `cadenzaClient.show('{embeddingTargetId}' as EmbeddingTargetId)`).
35
+ */
36
+ /**
37
+ * @typedef {OpaqueString<'EmbeddingTargetId'>} EmbeddingTargetId - The ID of a Cadenza embedding target
38
+ *
39
+ * Embedding targets are called πŸ‡©πŸ‡ͺ "Einbettbarer Inhalt" / πŸ‡ΊπŸ‡Έ "Embeddable content" throughout the Cadenza UI and help.
40
+ * They're managed within the respective workbook:
41
+ *
42
+ * - πŸ‡©πŸ‡ͺ "Mehr" > "Arbeitsmappe verwalten" > "Einbettung"
43
+ * - πŸ‡ΊπŸ‡Έ "More" > "Manage workbook" > "Embedding"
44
+ *
45
+ * The name of an embedding target (as entered in the UI) is its ID.
46
+ */
47
+ /** @typedef {OpaqueString<'GlobalId'>} GlobalId - The ID of a navigator item */
29
48
  /**
30
49
  * @typedef ExternalLinkKey - A tuple qualifying a Cadenza external link
50
+ *
51
+ * You get the `repositoryName` and `externalLinkId` from the URL of the external link's page in the Cadenza management center:
52
+ * ```
53
+ * {baseUrl}/admin/repositories/{repositoryName}/external-links/{externalLinkId}?...
54
+ * ```
55
+ *
31
56
  * @property {string} repositoryName - The name of the link's repository
32
57
  * @property {string} externalLinkId - The ID of the external link
33
58
  */
59
+ /**
60
+ * @typedef {string[]} WorkbookLayerPath - Identifies a layer within a workbook map view
61
+ * using the print names of the layer and - if the layer is grouped - its ancestors
62
+ */
34
63
  /**
35
64
  * @typedef PageSource - A well-known Cadenza page
36
65
  * @property {'welcome'} page - The name of the page (Only `"welcome"` is currently supported.)
@@ -40,8 +69,8 @@ globalThis.cadenza = Object.assign((/** @type Parameters<cadenza> */ ...args) =>
40
69
  * @typedef {'workbook-design'|'workbook-view-management'} UiFeature - The name of a Cadenza UI feature
41
70
  *
42
71
  * _Note:_ Supported features are:
43
- * * 'workbook-design' - The workbook designer
44
- * * 'workbook-view-management' - Add/Edit/Remove workbook views (Is included in 'workbook-design'.)
72
+ * * `"workbook-design"` - The workbook designer
73
+ * * `"workbook-view-management"` - Add/Edit/Remove workbook views (Is included in 'workbook-design'.)
45
74
  * */
46
75
  /**
47
76
  * @typedef Geometry - A [GeoJSON](https://geojson.org/) geometry object
@@ -53,9 +82,20 @@ globalThis.cadenza = Object.assign((/** @type Parameters<cadenza> */ ...args) =>
53
82
  * _Note:_ The GeoJSON geometry type "GeometryCollection" is currently not supported.
54
83
  */
55
84
  /** @typedef {[number,number,number,number]} Extent - An array of numbers representing an extent: [minx, miny, maxx, maxy] */
56
- /** @typedef {'csv' | 'excel' | 'json' | 'pdf'} DataType - A data type */
85
+ /**
86
+ * @typedef {'csv' | 'excel' | 'json' | 'pdf' | 'png'} DataType - A data type
87
+ *
88
+ * See [JSON Representation of Cadenza Object Data](../index.html#md:json-representation-of-cadenza-object-data) for JSON data.
89
+ */
57
90
  /** @typedef {'columns' | 'values' | 'totals'} TablePart - A part of a table to export */
58
- /** @typedef {Record<string, string | number | Date>} FilterVariables - Filter variable names and values */
91
+ /**
92
+ * @typedef {Record<string, string | string[] | number | Date | null>} FilterVariables - Filter variable names and values
93
+ *
94
+ * Variables of type String, Integer, Long, Double and Date can be set.
95
+ *
96
+ * _Note:_ Since numbers in JavaScript are Double values ([more info on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#number_encoding)),
97
+ * for Long variables, the API is currently limited to the Double value range.
98
+ */
59
99
  /**
60
100
  * _Notes:_
61
101
  * * Most public methods can be aborted using an [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal).
@@ -78,7 +118,7 @@ export class CadenzaClient {
78
118
  #iframeElement;
79
119
  /** @readonly */
80
120
  #debug;
81
- /** @type {[ string, (event: CadenzaEvent<never>) => void ][]} */
121
+ /** @type {[ CadenzaEventType | string, (event: CadenzaEvent<never>) => void ][]} */
82
122
  #subscriptions = [];
83
123
  /**
84
124
  * @hidden
@@ -142,15 +182,12 @@ export class CadenzaClient {
142
182
  * @param {String} [options.labelSet] - The name of a label set defined in the `basicweb-config.xml` (only supported for the welcome page)
143
183
  * @param {OperationMode} [options.operationMode] - The mode in which a workbook should be operated
144
184
  * @param {AbortSignal} [options.signal] - A signal to abort the iframe loading
145
- * @return {Promise<void>} A Promise for when the iframe is loaded
185
+ * @return {Promise<void>} A `Promise` for when the iframe is loaded
146
186
  * @throws For invalid arguments
147
- * @fires `drillThrough` - When the user executed a POST message drill-through.
148
- * The event includes a row of values for each row in the workbook selection, each row consisting of the values of
149
- * the attributes that were selected for the POST message content. If the drill-through was executed from a map
150
- * view, each row includes the geometry of the select object as the last value.
187
+ * @fires {@link CadenzaDrillThroughEvent}
151
188
  */
152
189
  show(source, { dataType, disabledUiFeatures, expandNavigator, filter, hideMainHeaderAndFooter, hideWorkbookToolBar, highlightGlobalId, labelSet, operationMode, signal, } = {}) {
153
- this.#log('CadenzaClient#show', source);
190
+ this.#log('CadenzaClient#show', ...arguments);
154
191
  if (dataType) {
155
192
  assertSupportedDataType(dataType, ['pdf']);
156
193
  }
@@ -169,7 +206,6 @@ export class CadenzaClient {
169
206
  labelSet,
170
207
  dataType,
171
208
  operationMode,
172
- webApplication: this.#webApplication,
173
209
  });
174
210
  return this.#show(resolvePath(source), params, signal);
175
211
  }
@@ -190,14 +226,12 @@ export class CadenzaClient {
190
226
  * @param {OperationMode} [options.operationMode] - The mode in which a workbook should be operated
191
227
  * @param {boolean} [options.useMapSrs] - Whether the geometry and the extent are in the map's SRS (otherwise EPSG:4326 is assumed)
192
228
  * @param {AbortSignal} [options.signal] - A signal to abort the iframe loading
193
- * @return {Promise<void>} A Promise for when the iframe is loaded
229
+ * @return {Promise<void>} A `Promise` for when the iframe is loaded
194
230
  * @throws For invalid arguments
195
- * @fires `drillThrough` - When the user executed a POST message drill-through.
196
- * The event includes a row of values for each row in the workbook selection, each row consisting of the values of
197
- * the attributes that were selected for the POST message content plus the geometry of the select object as the last value.
231
+ * @fires {@link CadenzaDrillThroughEvent}
198
232
  */
199
233
  async showMap(mapView, { disabledUiFeatures, expandNavigator, filter, geometry, hideMainHeaderAndFooter, hideWorkbookToolBar, highlightGlobalId, locationFinder, mapExtent, operationMode, useMapSrs, signal, } = {}) {
200
- this.#log('CadenzaClient#showMap', mapView, geometry);
234
+ this.#log('CadenzaClient#showMap', ...arguments);
201
235
  if (geometry) {
202
236
  assertValidGeometryType(geometry.type);
203
237
  }
@@ -211,11 +245,13 @@ export class CadenzaClient {
211
245
  locationFinder,
212
246
  mapExtent,
213
247
  operationMode,
248
+ targetType: 'MAP',
214
249
  useMapSrs,
215
- webApplication: this.#webApplication,
216
250
  });
217
251
  await this.#show(resolvePath(mapView), params, signal);
218
- this.#postEvent('setGeometry', { geometry });
252
+ if (geometry) {
253
+ this.#postEvent('setGeometry', { geometry });
254
+ }
219
255
  }
220
256
  /**
221
257
  * Expand/collapse the navigator.
@@ -223,9 +259,93 @@ export class CadenzaClient {
223
259
  * @param {boolean} expanded - The expansion state of the navigator
224
260
  */
225
261
  expandNavigator(expanded = true) {
226
- this.#log('CadenzaClient#expandNavigator', expanded);
262
+ this.#log('CadenzaClient#expandNavigator', ...arguments);
227
263
  this.#postEvent('expandNavigator', { expandNavigator: Boolean(expanded) });
228
264
  }
265
+ /**
266
+ * Get data from the currently shown workbook view.
267
+ *
268
+ * Currently, only map views are supported.
269
+ *
270
+ * @hidden
271
+ * @template {DataType} T
272
+ * @param {T} dataType - The requested data type. Currently, only `"png"` is supported.
273
+ * @return {Promise<T extends 'png' ? Blob : never>}
274
+ */
275
+ async getData(dataType) {
276
+ this.#log('CadenzaClient#getData', ...arguments);
277
+ assertSupportedDataType(dataType, ['png']);
278
+ return this.#postRequest('getData', { dataType });
279
+ }
280
+ /**
281
+ * Set filter variables in the currently shown workbook.
282
+ *
283
+ * @hidden
284
+ * @param {FilterVariables} filter - The variable values
285
+ * @return {Promise<void>} A `Promise` for when the filter variables were set.
286
+ */
287
+ setFilter(filter) {
288
+ this.#log('CadenzaClient#setFilter', ...arguments);
289
+ return this.#postRequest('setFilter', { filter });
290
+ }
291
+ /**
292
+ * Set the visibility of a layer in the currently shown workbook map view.
293
+ *
294
+ * When making a layer visible, its ancestors will be made visible, too.
295
+ * When hiding a layer, the ancestors are not affected.
296
+ *
297
+ * @hidden
298
+ * @param {WorkbookLayerPath | string} layer - The layer to show or hide
299
+ * (identified using a layer path or a print name)
300
+ * @param {boolean} visible - The visibility state of the layer
301
+ * @return {Promise<void>} A `Promise` for when the layer visibility was set.
302
+ */
303
+ setLayerVisibility(layer, visible) {
304
+ this.#log('CadenzaClient#setLayerVisibility', ...arguments);
305
+ return this.#postRequest('setLayerVisibility', {
306
+ layer: array(layer),
307
+ visible,
308
+ });
309
+ }
310
+ /**
311
+ * Set the selection in the currently shown workbook map view.
312
+ *
313
+ * @hidden
314
+ * @param {WorkbookLayerPath | string} layer - The data view layer to set the selection in
315
+ * @param {unknown[]} values - The IDs of the objects to select
316
+ * @return {Promise<void>} A `Promise` for when the selection was set.
317
+ */
318
+ setSelection(layer, values) {
319
+ this.#log('CadenzaClient#setSelection', ...arguments);
320
+ return this.#postRequest('setSelection', { layer: array(layer), values });
321
+ }
322
+ /**
323
+ * Add to the selection in the currently shown workbook map view.
324
+ *
325
+ * @hidden
326
+ * @param {WorkbookLayerPath | string} layer - The data view layer to change the selection in
327
+ * @param {unknown[]} values - The IDs of the objects to select
328
+ * @return {Promise<void>} A `Promise` for when the selection was changed.
329
+ */
330
+ addSelection(layer, values) {
331
+ this.#log('CadenzaClient#addSelection', ...arguments);
332
+ return this.#postRequest('addSelection', { layer: array(layer), values });
333
+ }
334
+ /**
335
+ * Remove from the selection in the currently shown workbook map view.
336
+ *
337
+ * @hidden
338
+ * @param {WorkbookLayerPath | string} layer - The data view layer to change the selection in
339
+ * @param {unknown[]} values - The IDs of the objects to unselect
340
+ * @return {Promise<void>} A `Promise` for when the selection was changed.
341
+ */
342
+ removeSelection(layer, values) {
343
+ this.#log('CadenzaClient#removeSelection', ...arguments);
344
+ return this.#postRequest('removeSelection', {
345
+ layer: array(layer),
346
+ values,
347
+ });
348
+ }
229
349
  /**
230
350
  * Create a geometry.
231
351
  *
@@ -240,14 +360,15 @@ export class CadenzaClient {
240
360
  * @param {number} [options.minScale] - The minimum scale where the user should work on. A warning is shown when the map is zoomed out above the threshold.
241
361
  * @param {boolean} [options.useMapSrs] - Whether the created geometry should use the map's SRS (otherwise EPSG:4326 will be used)
242
362
  * @param {AbortSignal} [options.signal] - A signal to abort the iframe loading
243
- * @return {Promise<void>} A Promise for when the iframe is loaded
363
+ * @return {Promise<void>} A `Promise` for when the iframe is loaded
244
364
  * @throws For invalid arguments
245
- * @fires `editGeometry:update` - When the user changed the geometry. The event includes the edited geometry.
246
- * @fires `editGeometry:ok` - When the user completed the geometry editing. The event includes the edited geometry.
247
- * @fires `editGeometry:cancel` - When the user cancelled the geometry editing in Cadenza.
365
+ * @fires
366
+ * - {@link CadenzaEditGeometryUpdateEvent}
367
+ * - {@link CadenzaEditGeometryOkEvent}
368
+ * - {@link CadenzaEditGeometryCancelEvent}
248
369
  */
249
370
  createGeometry(backgroundMapView, geometryType, { locationFinder, mapExtent, minScale, useMapSrs, signal } = {}) {
250
- this.#log('CadenzaClient#createGeometry', backgroundMapView, geometryType);
371
+ this.#log('CadenzaClient#createGeometry', ...arguments);
251
372
  const params = createParams({
252
373
  action: 'editGeometry',
253
374
  geometryType,
@@ -255,7 +376,6 @@ export class CadenzaClient {
255
376
  mapExtent,
256
377
  minScale,
257
378
  useMapSrs,
258
- webApplication: this.#webApplication,
259
379
  });
260
380
  return this.#show(resolvePath(backgroundMapView), params, signal);
261
381
  }
@@ -270,14 +390,15 @@ export class CadenzaClient {
270
390
  * @param {number} [options.minScale] - The minimum scale where the user should work on. A warning is shown when the map is zoomed out above the threshold.
271
391
  * @param {boolean} [options.useMapSrs] - Whether the geometry is in the map's SRS (otherwise EPSG:4326 is assumed)
272
392
  * @param {AbortSignal} [options.signal] - A signal to abort the iframe loading
273
- * @return {Promise<void>} A Promise for when the iframe is loaded
393
+ * @return {Promise<void>} A `Promise` for when the iframe is loaded
274
394
  * @throws For invalid arguments
275
- * @fires `editGeometry:update` - When the user changed the geometry. The event includes the edited geometry.
276
- * @fires `editGeometry:ok` - When the user completed the geometry editing. The event includes the edited geometry.
277
- * @fires `editGeometry:cancel` - When the user cancelled the geometry editing in Cadenza.
395
+ * @fires
396
+ * - {@link CadenzaEditGeometryUpdateEvent}
397
+ * - {@link CadenzaEditGeometryOkEvent}
398
+ * - {@link CadenzaEditGeometryCancelEvent}
278
399
  */
279
400
  async editGeometry(backgroundMapView, geometry, { locationFinder, mapExtent, minScale, useMapSrs, signal } = {}) {
280
- this.#log('CadenzaClient#editGeometry', backgroundMapView, geometry);
401
+ this.#log('CadenzaClient#editGeometry', ...arguments);
281
402
  assertValidGeometryType(geometry.type);
282
403
  const params = createParams({
283
404
  action: 'editGeometry',
@@ -285,15 +406,51 @@ export class CadenzaClient {
285
406
  mapExtent,
286
407
  minScale,
287
408
  useMapSrs,
288
- webApplication: this.#webApplication,
289
409
  });
290
410
  await this.#show(resolvePath(backgroundMapView), params, signal);
291
- this.#postEvent('setGeometry', { geometry });
411
+ if (geometry) {
412
+ this.#postEvent('setGeometry', { geometry });
413
+ }
414
+ }
415
+ /**
416
+ * Select objects in a workbook map.
417
+ *
418
+ * @param {EmbeddingTargetId} backgroundMapView - The workbook map view
419
+ * @param {object} [options] - Options
420
+ * @param {(WorkbookLayerPath | string)[]} [options.layers] - Layers to restrict the selection to
421
+ * (identified using layer paths or print names)
422
+ * @param {string} [options.locationFinder] - A search query for the location finder
423
+ * @param {Extent} [options.mapExtent] - A map extent to set
424
+ * @param {boolean} [options.useMapSrs] - Whether the geometry is in the map's SRS (otherwise EPSG:4326 is assumed)
425
+ * @param {AbortSignal} [options.signal] - A signal to abort the iframe loading
426
+ * @return {Promise<void>} A `Promise` for when the iframe is loaded
427
+ * @throws For invalid arguments
428
+ * @fires
429
+ * - {@link CadenzaChangeSelectionEvent}
430
+ * - {@link CadenzaObjectInfoEvent}
431
+ * - {@link CadenzaSelectObjectsOkEvent}
432
+ * - {@link CadenzaSelectObjectsCancelEvent}
433
+ */
434
+ selectObjects(backgroundMapView, { layers, locationFinder, mapExtent, useMapSrs, signal } = {}) {
435
+ this.#log('CadenzaClient#selectObjects', ...arguments);
436
+ const params = createParams({
437
+ action: 'selectObjects',
438
+ layers: layers?.map(array),
439
+ locationFinder,
440
+ mapExtent,
441
+ useMapSrs,
442
+ });
443
+ return this.#show(resolvePath(backgroundMapView), params, signal);
292
444
  }
293
445
  #show(
294
446
  /** @type string */ path,
295
447
  /** @type URLSearchParams */ params,
296
448
  /** @type AbortSignal | undefined */ signal) {
449
+ const webApplication = this.#webApplication;
450
+ if (webApplication) {
451
+ params.set('webApplicationLink', webApplication.externalLinkId);
452
+ params.set('webApplicationLinkRepository', webApplication.repositoryName);
453
+ }
297
454
  const url = this.#createUrl(path, params);
298
455
  this.#log('Load iframe', url.toString());
299
456
  this.#requiredIframe.src = url.toString();
@@ -301,14 +458,14 @@ export class CadenzaClient {
301
458
  }
302
459
  #getIframePromise(/** @type AbortSignal | undefined */ signal) {
303
460
  const iframe = this.#requiredIframe;
304
- /** @type {() => void} */
461
+ /** @type {EventListener} */
305
462
  let onerror;
306
- /** @type {() => void} */
463
+ /** @type {EventListener} */
307
464
  let onabort;
308
465
  /** @type {(() => void)[]} */
309
466
  let unsubscribes;
310
467
  /** @type {Promise<void>} */
311
- let promise = new Promise((resolve, reject) => {
468
+ const promise = new Promise((resolve, reject) => {
312
469
  onerror = () => reject(new CadenzaError('loading-error', 'Loading failed'));
313
470
  iframe.addEventListener('error', onerror);
314
471
  if (signal) {
@@ -319,8 +476,8 @@ export class CadenzaClient {
319
476
  signal.addEventListener('abort', onabort);
320
477
  }
321
478
  unsubscribes = [
322
- this.on('ready', () => resolve()),
323
- this.on('error', (/** @type {CadenzaErrorEvent} */ event) => {
479
+ this.#on('ready', () => resolve()),
480
+ this.#on('error', (/** @type {CadenzaErrorEvent} */ event) => {
324
481
  const { type, message } = event.detail;
325
482
  reject(new CadenzaError(type, message ?? 'Loading failed'));
326
483
  }),
@@ -338,17 +495,30 @@ export class CadenzaClient {
338
495
  /**
339
496
  * Subscribe to a `postMessage()` event.
340
497
  *
341
- * @template [T=unknown]
342
- * @param {string} type - The event type
343
- * @param {(event: CadenzaEvent<T>) => void} subscriber - The subscriber function
498
+ * @template {CadenzaEventType} TYPE
499
+ * @param {TYPE} type - The event type
500
+ * @param {(event: CadenzaEvent<TYPE, CadenzaEventByType<TYPE>['detail']>) => void} subscriber - The subscriber function
344
501
  * @return {() => void} An unsubscribe function
345
502
  */
346
503
  on(type, subscriber) {
504
+ return this.#on(type, subscriber);
505
+ }
506
+ /**
507
+ * @template {CadenzaEventType | string} TYPE
508
+ * @template [DETAIL=unknown]
509
+ * @param {TYPE} type
510
+ * @param {(event: CadenzaEvent<TYPE, DETAIL>) => void} subscriber
511
+ * @return {() => void} An unsubscribe function
512
+ */
513
+ #on(type, subscriber) {
347
514
  const subscriptions = this.#subscriptions;
348
515
  if (subscriptions.length === 0) {
349
516
  window.addEventListener('message', this.#onMessage);
350
517
  }
351
- subscriptions.push([type, subscriber]);
518
+ subscriptions.push([
519
+ type,
520
+ /** @type {(event: CadenzaEvent<never>) => void} */ (subscriber),
521
+ ]);
352
522
  return () => {
353
523
  subscriptions.forEach(([subscriptionType, subscriptionSubscriber], i) => {
354
524
  if (subscriptionType === type &&
@@ -362,7 +532,8 @@ export class CadenzaClient {
362
532
  };
363
533
  }
364
534
  // Use arrow function so that it's bound to this.
365
- #onMessage = (/** @type MessageEvent<CadenzaEvent<never>> */ event) => {
535
+ #onMessage = (
536
+ /** @type MessageEvent<CadenzaEvent<never, never>> */ event) => {
366
537
  if (event.origin !== this.#origin ||
367
538
  event.source !== this.#requiredIframe.contentWindow) {
368
539
  return;
@@ -375,26 +546,63 @@ export class CadenzaClient {
375
546
  }
376
547
  });
377
548
  };
378
- #postEvent(/** @type string */ type, /** @type unknown */ detail) {
549
+ /**
550
+ * Posts an event to Cadenza and returns a `Promise` for the response.
551
+ *
552
+ * It is guaranteed that a response refers to a specific request,
553
+ * even if multiple request are executed in parallel.
554
+ * @template [T=void]
555
+ * @param {string} type
556
+ * @param {unknown} [detail]
557
+ * @returns {Promise<T>}
558
+ */
559
+ #postRequest(type, detail) {
560
+ const { port1, port2 } = new MessageChannel();
561
+ const promise = new Promise((resolve, reject) => {
562
+ port1.onmessage = (
563
+ /** @type MessageEvent<CadenzaEvent<never, never>> */ event) => {
564
+ const cadenzaEvent = event.data;
565
+ if (cadenzaEvent.type === `${type}:success`) {
566
+ resolve(cadenzaEvent.detail);
567
+ }
568
+ else if (cadenzaEvent.type === `${type}:error`) {
569
+ reject();
570
+ }
571
+ };
572
+ });
573
+ this.#postEvent(type, detail, [port2]);
574
+ return promise;
575
+ }
576
+ /**
577
+ * @param {string} type
578
+ * @param {unknown} [detail]
579
+ * @param {Transferable[]} [transfer]
580
+ */
581
+ #postEvent(type, detail, transfer) {
379
582
  const cadenzaEvent = { type, detail };
380
583
  this.#log('postMessage', cadenzaEvent);
381
584
  const contentWindow = /** @type {WindowProxy} */ (this.#requiredIframe.contentWindow);
382
- contentWindow.postMessage(cadenzaEvent, { targetOrigin: this.#origin });
585
+ contentWindow.postMessage(cadenzaEvent, {
586
+ targetOrigin: this.#origin,
587
+ transfer,
588
+ });
383
589
  }
384
590
  /**
385
591
  * Fetch data from a workbook view.
386
592
  *
387
- * @param {EmbeddingTargetId} source - The workbook view to fetch data from
388
- * @param {DataType} dataType - The data type you want to get back from the server
593
+ * @param {EmbeddingTargetId} source - The workbook view to fetch data from.
594
+ * Currently only table and indicator views are supported.
595
+ * @param {DataType} dataType - The data type you want to get back from the server.
596
+ * Currently, `"csv"`, `"excel"` and `"json"` are supported.
389
597
  * @param {object} options - Options
390
598
  * @param {TablePart[]} [options.parts] - Table parts to export; If not specified, all parts are exported.
391
599
  * @param {AbortSignal} [options.signal] - A signal to abort the data fetching
392
- * @return {Promise<Response>} A Promise for the fetch response
600
+ * @return {Promise<Response>} A `Promise` for the fetch response
393
601
  * @throws For invalid arguments
394
602
  */
395
603
  fetchData(source, dataType, { parts, signal } = {}) {
396
- this.#log('CadenzaClient#fetchData', source, dataType);
397
- assertSupportedDataType(dataType);
604
+ this.#log('CadenzaClient#fetchData', ...arguments);
605
+ assertSupportedDataType(dataType, ['csv', 'excel', 'json']);
398
606
  const params = createParams({ dataType, parts });
399
607
  return this.#fetch(resolvePath(source), params, signal);
400
608
  }
@@ -420,16 +628,18 @@ export class CadenzaClient {
420
628
  *
421
629
  * _Note:_ The file name, if not provided, is generated from the name of the workbook view and the current date.
422
630
  *
423
- * @param {EmbeddingTargetId} source - The workbook view to download data from
424
- * @param {DataType} dataType - The data type you want to get back from the server
631
+ * @param {EmbeddingTargetId} source - The workbook view to download data from.
632
+ * Currently only table and indicator views are supported.
633
+ * @param {DataType} dataType - The data type you want to get back from the server.
634
+ * Currently, `"csv"`, `"excel"` and `"json"` are supported.
425
635
  * @param {object} options - Options
426
636
  * @param {string} [options.fileName] - The file name to use; The file extension is appended by Cadenza.
427
637
  * @param {TablePart[]} [options.parts] - Table parts to export; If not specified, all parts are exported.
428
638
  * @throws For invalid arguments
429
639
  */
430
640
  downloadData(source, dataType, { fileName, parts }) {
431
- this.#log('CadenzaClient#downloadData', source, dataType);
432
- assertSupportedDataType(dataType);
641
+ this.#log('CadenzaClient#downloadData', ...arguments);
642
+ assertSupportedDataType(dataType, ['csv', 'excel', 'json']);
433
643
  const params = createParams({ dataType, fileName, parts });
434
644
  this.#download(resolvePath(source), params);
435
645
  }
@@ -455,7 +665,20 @@ export class CadenzaClient {
455
665
  }
456
666
  #log(/** @type unknown[] */ ...args) {
457
667
  if (this.#debug) {
458
- console.log(...args);
668
+ /** @type {unknown[]} */
669
+ const redundantValues = [undefined, '', false];
670
+ /** @type {(value: unknown) => value is object} */
671
+ const isObject = (/** @type {unknown} */ value) => value != null && typeof value === 'object';
672
+ const sanitizedArgs = args
673
+ .map((arg) => {
674
+ if (isObject(arg)) {
675
+ return Object.fromEntries(Object.entries(arg).filter(([, value]) => !redundantValues.includes(value)));
676
+ }
677
+ return arg;
678
+ })
679
+ .filter((arg) => !redundantValues.includes(arg) &&
680
+ !(isObject(arg) && Object.keys(arg).length === 0));
681
+ console.log(...sanitizedArgs);
459
682
  }
460
683
  }
461
684
  }
@@ -532,11 +755,11 @@ function validUiFeature(/** @type string */ value) {
532
755
  }
533
756
  function assertSupportedDataType(
534
757
  /** @type DataType */ type,
535
- /** @type DataType[] */ supportedTypes = ['csv', 'excel', 'json', 'pdf']) {
758
+ /** @type DataType[] */ supportedTypes) {
536
759
  assert(supportedTypes.includes(type), `Invalid data type: ${type}`);
537
760
  }
538
761
  /**
539
- * @param {object} params - Options
762
+ * @param {object} params
540
763
  * @param {string} [params.action]
541
764
  * @param {DataType} [params.dataType]
542
765
  * @param {UiFeature[]} [params.disabledUiFeatures]
@@ -548,16 +771,17 @@ function assertSupportedDataType(
548
771
  * @param {boolean} [params.hideWorkbookToolBar]
549
772
  * @param {GlobalId} [params.highlightGlobalId]
550
773
  * @param {string} [params.labelSet]
774
+ * @param {WorkbookLayerPath[]} [params.layers]
551
775
  * @param {string} [params.locationFinder]
552
776
  * @param {Extent} [params.mapExtent]
553
777
  * @param {number} [params.minScale]
554
778
  * @param {OperationMode} [params.operationMode]
555
779
  * @param {TablePart[]} [params.parts]
780
+ * @param {'MAP'} [params.targetType]
556
781
  * @param {boolean} [params.useMapSrs]
557
- * @param {ExternalLinkKey} [params.webApplication]
558
782
  * @return {URLSearchParams}
559
783
  */
560
- function createParams({ action, dataType, disabledUiFeatures, expandNavigator, fileName, filter, geometryType, hideMainHeaderAndFooter, hideWorkbookToolBar, highlightGlobalId, labelSet, locationFinder, mapExtent, minScale, parts, useMapSrs, webApplication, operationMode, }) {
784
+ function createParams({ action, dataType, disabledUiFeatures, expandNavigator, fileName, filter, geometryType, hideMainHeaderAndFooter, hideWorkbookToolBar, highlightGlobalId, labelSet, layers, locationFinder, mapExtent, minScale, operationMode, parts, targetType, useMapSrs, }) {
561
785
  if (disabledUiFeatures) {
562
786
  disabledUiFeatures.forEach((feature) => assert(validUiFeature(feature), `Invalid UI feature: ${feature}`));
563
787
  }
@@ -591,25 +815,76 @@ function createParams({ action, dataType, disabledUiFeatures, expandNavigator, f
591
815
  ...(hideWorkbookToolBar && { hideWorkbookToolBar: 'true' }),
592
816
  ...(highlightGlobalId && { highlightGlobalId }),
593
817
  ...(labelSet && { labelSet }),
818
+ ...(layers &&
819
+ layers.length && {
820
+ layers: JSON.stringify(layers),
821
+ }),
594
822
  ...(locationFinder && { locationFinder }),
595
823
  ...(mapExtent && { mapExtent: mapExtent.join() }),
596
824
  ...(minScale && { minScale: String(minScale) }),
597
825
  ...(operationMode && operationMode !== 'normal' && { operationMode }),
598
826
  ...(parts && { parts: parts.join() }),
827
+ ...(targetType && { targetType }),
599
828
  ...(useMapSrs && { useMapSrs: 'true' }),
600
- ...(webApplication && {
601
- webApplicationLink: webApplication.externalLinkId,
602
- webApplicationLinkRepository: webApplication.repositoryName,
603
- }),
604
829
  });
605
830
  }
831
+ function array(/** @type unknown */ value) {
832
+ return Array.isArray(value) ? value : [value];
833
+ }
834
+ // Please do not add internal event types like 'ready' here.
835
+ /**
836
+ * @typedef {'change:selection'
837
+ * | 'drillThrough'
838
+ * | 'editGeometry:ok'
839
+ * | 'editGeometry:update'
840
+ * | 'editGeometry:cancel'
841
+ * | 'objectInfo'
842
+ * | 'selectObjects:ok'
843
+ * | 'selectObjects:cancel'
844
+ * } CadenzaEventType - An event type to subscribe to using {@link CadenzaClient#on}
845
+ */
846
+ /**
847
+ * @template {CadenzaEventType} T
848
+ * @typedef {T extends 'change:selection' ? CadenzaChangeSelectionEvent
849
+ * : T extends 'drillThrough' ? CadenzaDrillThroughEvent
850
+ * : T extends 'editGeometry:update' ? CadenzaEditGeometryUpdateEvent
851
+ * : T extends 'editGeometry:ok' ? CadenzaEditGeometryOkEvent
852
+ * : T extends 'editGeometry:cancel' ? CadenzaEditGeometryCancelEvent
853
+ * : T extends 'objectInfo' ? CadenzaObjectInfoEvent
854
+ * : T extends 'selectObjects:ok' ? CadenzaSelectObjectsOkEvent
855
+ * : T extends 'selectObjects:cancel' ? CadenzaSelectObjectsCancelEvent
856
+ * : never
857
+ * } CadenzaEventByType
858
+ */
606
859
  /**
607
- * @template [T=unknown]
860
+ * @template {CadenzaEventType | string} TYPE
861
+ * @template [DETAIL=unknown]
608
862
  * @typedef CadenzaEvent - A Cadenza `postMessage()` event
609
- * @property {string} type - The event type
610
- * @property {T} detail - Optional event details (depending on the event type)
863
+ * @property {TYPE} type - The event type
864
+ * @property {DETAIL} detail - Optional event details (depending on the event type)
865
+ */
866
+ /*
867
+ * @hidden
868
+ * @typedef {CadenzaEvent<'change:extent', {extent: Extent}>} CadenzaChangeExtentEvent - When the user moved the map.
869
+ * The extent is transformed according to the `useMapSrs` option.
870
+ */
871
+ /** @typedef {CadenzaEvent<'change:selection', undefined | {layer: WorkbookLayerPath, values: unknown[][]}>} CadenzaChangeSelectionEvent - When the user changed the selection. */
872
+ /**
873
+ * @typedef {CadenzaEvent<'drillThrough', {values: unknown[][]}>} CadenzaDrillThroughEvent - When the user executed a POST message drill-through.
874
+ * <p>
875
+ * The event includes a data row for every item in the workbook selection, each row consisting of the values of
876
+ * the attributes that were selected for the POST message content. If the drill-through was executed from a map
877
+ * view, each row includes the geometry of the selected object as the last value.
878
+ * <p>
879
+ * See also: <a href="../index.html#md:json-representation-of-cadenza-object-data">JSON Representation of Cadenza Object Data</a>
611
880
  */
612
- /** @typedef {CadenzaEvent<{type: string, message?: string}>} CadenzaErrorEvent - An error event that is mapped to a {@link CadenzaError} */
881
+ /** @typedef {CadenzaEvent<'editGeometry:update', {geometry: Geometry}>} CadenzaEditGeometryUpdateEvent - When the user changed the geometry. */
882
+ /** @typedef {CadenzaEvent<'editGeometry:ok', {geometry: Geometry}>} CadenzaEditGeometryOkEvent - When the user submitted the geometry. */
883
+ /** @typedef {CadenzaEvent<'editGeometry:cancel'>} CadenzaEditGeometryCancelEvent - When the user cancelled the geometry editing. */
884
+ /** @typedef {CadenzaEvent<'error', {type: string, message?: string}>} CadenzaErrorEvent - An error event that is mapped to a {@link CadenzaError} */
885
+ /** @typedef {CadenzaEvent<'objectInfo', {layer: WorkbookLayerPath, objectInfos: {selectionIndex: number, formattedValues: Record<string, string>}[]}>} CadenzaObjectInfoEvent - When the user opened the object info flyout. */
886
+ /** @typedef {CadenzaEvent<'selectObjects:ok', {layer: WorkbookLayerPath, values: unknown[][]}>} CadenzaSelectObjectsOkEvent - When the user submitted the selection. */
887
+ /** @typedef {CadenzaEvent<'selectObjects:cancel'>} CadenzaSelectObjectsCancelEvent - When the user cancelled the selection. */
613
888
  export class AbortError extends DOMException {
614
889
  constructor() {
615
890
  super('Aborted', 'AbortError');