@disy/cadenza.js 1.0.0 → 1.1.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.
- package/CHANGELOG.md +11 -0
- package/README.md +54 -5
- package/apidoc/assets/highlight.css +22 -22
- package/apidoc/assets/search.js +1 -1
- package/apidoc/classes/AbortError.html +7 -2
- package/apidoc/classes/CadenzaClient.html +258 -63
- package/apidoc/classes/CadenzaError.html +9 -4
- package/apidoc/functions/cadenza.html +13 -3
- package/apidoc/index.html +43 -18
- package/apidoc/interfaces/CadenzaEvent.html +8 -3
- package/apidoc/interfaces/ExternalLinkKey.html +103 -0
- package/apidoc/interfaces/Geometry.html +7 -2
- package/apidoc/interfaces/WorkbookKey.html +8 -3
- package/apidoc/interfaces/WorkbookViewKey.html +9 -4
- package/apidoc/interfaces/WorksheetKey.html +9 -4
- package/apidoc/modules.html +10 -0
- package/apidoc/types/CadenzaErrorEvent.html +6 -1
- package/apidoc/types/EmbeddingTargetId.html +6 -1
- package/apidoc/types/Extent.html +6 -1
- package/apidoc/types/GeometryType.html +6 -1
- package/apidoc/types/GlobalId.html +65 -0
- package/apidoc/types/MediaType.html +66 -0
- package/apidoc/types/OperationMode.html +65 -0
- package/apidoc/types/UiFeature.html +70 -0
- package/apidoc/types/WorkbookSource.html +6 -1
- package/apidoc/types/WorkbookViewSource.html +6 -1
- package/apidoc/types/WorksheetSource.html +6 -1
- package/cadenza.d.ts +130 -20
- package/cadenza.js +264 -93
- package/package.json +12 -5
- package/sandbox.html +342 -0
package/cadenza.js
CHANGED
|
@@ -4,23 +4,28 @@
|
|
|
4
4
|
* @param {string} baseUrl - The base URL of the Cadenza server
|
|
5
5
|
* @param {object} [options] - Options
|
|
6
6
|
* @param {HTMLIFrameElement | string} [options.iframe] - An iframe for embedding Cadenza or the iframe's ID
|
|
7
|
+
* @param {ExternalLinkKey} [options.webApplication] - An external link that Cadenza uses to resolve the [target origin](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage#targetorigin) when posting events. This is required if Cadenza and your application are not running on the same origin.
|
|
7
8
|
* @param {boolean} [options.debug] - Whether to enable debug logging
|
|
8
|
-
* @throws For invalid
|
|
9
|
+
* @throws For invalid arguments
|
|
9
10
|
*/
|
|
10
11
|
export function cadenza(baseUrl, options) {
|
|
11
12
|
return new CadenzaClient(baseUrl, options);
|
|
12
13
|
}
|
|
13
14
|
/* @ts-ignore */
|
|
14
15
|
const previousGlobalCadenza = globalThis.cadenza;
|
|
15
|
-
globalThis.cadenza = Object.assign(
|
|
16
|
-
/** @param {Parameters<cadenza>} args */
|
|
17
|
-
(...args) => cadenza(...args), {
|
|
16
|
+
globalThis.cadenza = Object.assign((/** @type Parameters<cadenza> */ ...args) => cadenza(...args), {
|
|
18
17
|
noConflict() {
|
|
19
18
|
globalThis.cadenza = previousGlobalCadenza;
|
|
20
19
|
return cadenza;
|
|
21
20
|
},
|
|
22
21
|
});
|
|
23
22
|
/** @typedef {string} EmbeddingTargetId - The ID of an embedding target */
|
|
23
|
+
/** @typedef {string} GlobalId - The ID of a navigator item */
|
|
24
|
+
/**
|
|
25
|
+
* @typedef ExternalLinkKey - A tuple qualifying a Cadenza external link
|
|
26
|
+
* @property {string} repositoryName - The name of the link's repository
|
|
27
|
+
* @property {string} externalLinkId - The ID of the external link
|
|
28
|
+
*/
|
|
24
29
|
/**
|
|
25
30
|
* @typedef WorkbookKey - A tuple qualifying a workbook
|
|
26
31
|
* @property {string} repositoryName - The name of the workbook's repository
|
|
@@ -41,6 +46,14 @@ globalThis.cadenza = Object.assign(
|
|
|
41
46
|
/** @typedef {EmbeddingTargetId | WorkbookKey} WorkbookSource - A workbook source */
|
|
42
47
|
/** @typedef {EmbeddingTargetId | WorksheetKey} WorksheetSource - A worksheet source */
|
|
43
48
|
/** @typedef {EmbeddingTargetId | WorkbookViewKey} WorkbookViewSource - A workbook view source */
|
|
49
|
+
/** @typedef {'normal'|'simplified'} OperationMode - The mode in which a workbook should be operated */
|
|
50
|
+
/**
|
|
51
|
+
* @typedef {'workbook-design'|'workbook-view-management'} UiFeature - The name of a Cadenza UI feature
|
|
52
|
+
*
|
|
53
|
+
* _Note:_ Supported features are:
|
|
54
|
+
* * 'workbook-design' - The workbook designer
|
|
55
|
+
* * 'workbook-view-management' - Add/Edit/Remove workbook views (Is included in 'workbook-design'.)
|
|
56
|
+
* */
|
|
44
57
|
/**
|
|
45
58
|
* @typedef Geometry - A [GeoJSON](https://geojson.org/) geometry object
|
|
46
59
|
* @property {GeometryType} type - The type of the geometry
|
|
@@ -67,6 +80,8 @@ export class CadenzaClient {
|
|
|
67
80
|
#origin;
|
|
68
81
|
/** @readonly */
|
|
69
82
|
#iframe;
|
|
83
|
+
/** @readonly @type {ExternalLinkKey | undefined} */
|
|
84
|
+
#webApplication;
|
|
70
85
|
/** @type {HTMLIFrameElement | undefined} */
|
|
71
86
|
#iframeElement;
|
|
72
87
|
/** @readonly */
|
|
@@ -78,19 +93,24 @@ export class CadenzaClient {
|
|
|
78
93
|
* @param {string} baseUrl
|
|
79
94
|
* @param {object} [options]
|
|
80
95
|
* @param {HTMLIFrameElement | string} [options.iframe]
|
|
96
|
+
* @param {ExternalLinkKey} [options.webApplication]
|
|
81
97
|
* @param {boolean} [options.debug]
|
|
82
98
|
*/
|
|
83
|
-
constructor(baseUrl, { debug = false, iframe } = {}) {
|
|
99
|
+
constructor(baseUrl, { debug = false, iframe, webApplication } = {}) {
|
|
84
100
|
assert(validUrl(baseUrl), `Invalid baseUrl: ${baseUrl}`);
|
|
101
|
+
if (webApplication) {
|
|
102
|
+
assert(validExternalLinkKey(webApplication), `Invalid webApplication parameter: ${webApplication}`);
|
|
103
|
+
}
|
|
85
104
|
// Remove trailing /
|
|
86
105
|
if (baseUrl.at(-1) === '/') {
|
|
87
106
|
baseUrl = baseUrl.substring(0, baseUrl.length - 1);
|
|
88
107
|
}
|
|
89
|
-
this.#log('Create Cadenza client', baseUrl, iframe);
|
|
90
108
|
this.#baseUrl = baseUrl;
|
|
91
109
|
this.#origin = new URL(baseUrl).origin;
|
|
92
110
|
this.#iframe = iframe;
|
|
93
111
|
this.#debug = debug;
|
|
112
|
+
this.#webApplication = webApplication;
|
|
113
|
+
this.#log('Create Cadenza client', baseUrl, iframe);
|
|
94
114
|
}
|
|
95
115
|
/** The base URL of the Cadenza server this client is requesting */
|
|
96
116
|
get baseUrl() {
|
|
@@ -117,48 +137,69 @@ export class CadenzaClient {
|
|
|
117
137
|
*
|
|
118
138
|
* @param {WorkbookSource | WorksheetSource | WorkbookViewSource} source - The source to show
|
|
119
139
|
* @param {object} [options]
|
|
120
|
-
* @param {
|
|
121
|
-
* @param {boolean} [options.
|
|
140
|
+
* @param {UiFeature[]} [options.disabledUiFeatures] - Cadenza UI features to disable
|
|
141
|
+
* @param {boolean} [options.hideMainHeaderAndFooter] - Whether to hide the main Cadenza header and footer
|
|
142
|
+
* @param {boolean} [options.hideWorkbookToolBar] - Whether to hide the workbook toolbar
|
|
143
|
+
* @param {GlobalId} [options.highlightGlobalId] - The ID of an item to highlight / expand in the navigator
|
|
144
|
+
* @param {string} [options.mediaType] - Set to 'application/pdf' for views of type "JasperReports report"
|
|
145
|
+
* to show the report PDF directly, without any Cadenza headers or footers.
|
|
146
|
+
* @param {OperationMode} [options.operationMode] - The mode in which a workbook should be operated
|
|
122
147
|
* @param {AbortSignal} [options.signal] - A signal to abort the iframe loading
|
|
123
148
|
* @return {Promise<void>} A Promise for when the iframe is loaded
|
|
124
|
-
* @throws For
|
|
149
|
+
* @throws For invalid arguments
|
|
125
150
|
*/
|
|
126
|
-
show(source, { hideMainHeaderAndFooter, hideWorkbookToolBar, signal } = {}) {
|
|
151
|
+
show(source, { disabledUiFeatures, hideMainHeaderAndFooter, hideWorkbookToolBar, highlightGlobalId, mediaType, operationMode, signal, } = {}) {
|
|
127
152
|
this.#log('CadenzaClient#show', source);
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
153
|
+
if (mediaType) {
|
|
154
|
+
assertSupportedMediaType(mediaType, [MediaType.PDF]);
|
|
155
|
+
}
|
|
156
|
+
const params = createParams({
|
|
157
|
+
disabledUiFeatures,
|
|
158
|
+
hideMainHeaderAndFooter,
|
|
159
|
+
hideWorkbookToolBar,
|
|
160
|
+
highlightGlobalId,
|
|
161
|
+
operationMode,
|
|
162
|
+
mediaType,
|
|
163
|
+
webApplication: this.#webApplication,
|
|
131
164
|
});
|
|
132
|
-
return this.#show(resolvePath(source),
|
|
165
|
+
return this.#show(resolvePath(source), params, signal);
|
|
133
166
|
}
|
|
134
167
|
/**
|
|
135
168
|
* Show a workbook map view in an iframe.
|
|
136
169
|
*
|
|
137
170
|
* @param {WorkbookViewSource} mapView - The workbook map view to show
|
|
138
171
|
* @param {object} [options] - Options
|
|
172
|
+
* @param {UiFeature[]} [options.disabledUiFeatures] - Cadenza UI features to disable
|
|
139
173
|
* @param {Geometry} [options.geometry] - A geometry to show on the map
|
|
140
|
-
* @param {boolean} [options.hideMainHeaderAndFooter] - Whether to hide the main Cadenza header and footer
|
|
141
|
-
* @param {boolean} [options.hideWorkbookToolBar] - Whether to hide the workbook toolbar
|
|
174
|
+
* @param {boolean} [options.hideMainHeaderAndFooter] - Whether to hide the main Cadenza header and footer
|
|
175
|
+
* @param {boolean} [options.hideWorkbookToolBar] - Whether to hide the workbook toolbar
|
|
176
|
+
* @param {GlobalId} [options.highlightGlobalId] - The ID of an item to highlight / expand in the navigator
|
|
142
177
|
* @param {string} [options.locationFinder] - A search query for the location finder
|
|
143
178
|
* @param {Extent} [options.mapExtent] - A map extent to set
|
|
179
|
+
* @param {OperationMode} [options.operationMode] - The mode in which a workbook should be operated
|
|
144
180
|
* @param {boolean} [options.useMapSrs] - Whether the geometry and the extent are in the map's SRS (otherwise EPSG:4326 is assumed)
|
|
145
181
|
* @param {AbortSignal} [options.signal] - A signal to abort the iframe loading
|
|
146
182
|
* @return {Promise<void>} A Promise for when the iframe is loaded
|
|
147
|
-
* @throws For
|
|
183
|
+
* @throws For invalid arguments
|
|
148
184
|
*/
|
|
149
|
-
showMap(mapView, { geometry, hideMainHeaderAndFooter, hideWorkbookToolBar, locationFinder, mapExtent, useMapSrs, signal, } = {}) {
|
|
185
|
+
async showMap(mapView, { disabledUiFeatures, geometry, hideMainHeaderAndFooter, hideWorkbookToolBar, highlightGlobalId, locationFinder, mapExtent, operationMode, useMapSrs, signal, } = {}) {
|
|
150
186
|
this.#log('CadenzaClient#showMap', mapView, geometry);
|
|
151
187
|
if (geometry) {
|
|
152
188
|
assertValidGeometryType(geometry.type);
|
|
153
189
|
}
|
|
154
|
-
const params =
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
190
|
+
const params = createParams({
|
|
191
|
+
disabledUiFeatures,
|
|
192
|
+
hideMainHeaderAndFooter,
|
|
193
|
+
hideWorkbookToolBar,
|
|
194
|
+
highlightGlobalId,
|
|
195
|
+
locationFinder,
|
|
196
|
+
mapExtent,
|
|
197
|
+
operationMode,
|
|
198
|
+
useMapSrs,
|
|
199
|
+
webApplication: this.#webApplication,
|
|
160
200
|
});
|
|
161
|
-
|
|
201
|
+
await this.#show(resolvePath(mapView), params, signal);
|
|
202
|
+
this.#postEvent('setGeometry', { geometry });
|
|
162
203
|
}
|
|
163
204
|
/**
|
|
164
205
|
* Create a geometry.
|
|
@@ -169,29 +210,29 @@ export class CadenzaClient {
|
|
|
169
210
|
* @param {WorkbookViewSource} backgroundMapView - The workbook map view in the background
|
|
170
211
|
* @param {GeometryType} geometryType - The geometry type
|
|
171
212
|
* @param {object} [options] - Options
|
|
172
|
-
* @param {Extent} [options.mapExtent] - A map extent to set
|
|
173
213
|
* @param {string} [options.locationFinder] - A search query for the location finder
|
|
174
|
-
* @param {
|
|
214
|
+
* @param {Extent} [options.mapExtent] - A map extent to set
|
|
175
215
|
* @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.
|
|
216
|
+
* @param {boolean} [options.useMapSrs] - Whether the created geometry should use the map's SRS (otherwise EPSG:4326 will be used)
|
|
176
217
|
* @param {AbortSignal} [options.signal] - A signal to abort the iframe loading
|
|
177
218
|
* @return {Promise<void>} A Promise for when the iframe is loaded
|
|
178
|
-
* @throws For
|
|
219
|
+
* @throws For invalid arguments
|
|
179
220
|
* @fires `editGeometry:update` - When the user changed the geometry. The event includes the edited geometry.
|
|
180
221
|
* @fires `editGeometry:ok` - When the user completed the geometry editing. The event includes the edited geometry.
|
|
181
222
|
* @fires `editGeometry:cancel` - When the user cancelled the geometry editing in Cadenza.
|
|
182
223
|
*/
|
|
183
|
-
createGeometry(backgroundMapView, geometryType, { mapExtent,
|
|
224
|
+
createGeometry(backgroundMapView, geometryType, { locationFinder, mapExtent, minScale, useMapSrs, signal } = {}) {
|
|
184
225
|
this.#log('CadenzaClient#createGeometry', backgroundMapView, geometryType);
|
|
185
|
-
|
|
186
|
-
const params = new URLSearchParams({
|
|
226
|
+
const params = createParams({
|
|
187
227
|
action: 'editGeometry',
|
|
188
228
|
geometryType,
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
229
|
+
locationFinder,
|
|
230
|
+
mapExtent,
|
|
231
|
+
minScale,
|
|
232
|
+
useMapSrs,
|
|
233
|
+
webApplication: this.#webApplication,
|
|
193
234
|
});
|
|
194
|
-
return this.#show(resolvePath(backgroundMapView),
|
|
235
|
+
return this.#show(resolvePath(backgroundMapView), params, signal);
|
|
195
236
|
}
|
|
196
237
|
/**
|
|
197
238
|
* Edit a geometry.
|
|
@@ -201,48 +242,39 @@ export class CadenzaClient {
|
|
|
201
242
|
* @param {object} [options] - Options
|
|
202
243
|
* @param {string} [options.locationFinder] - A search query for the location finder
|
|
203
244
|
* @param {Extent} [options.mapExtent] - A map extent to set
|
|
204
|
-
* @param {boolean} [options.useMapSrs] - Whether the geometry is in the map's SRS (otherwise EPSG:4326 is assumed)
|
|
205
245
|
* @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.
|
|
246
|
+
* @param {boolean} [options.useMapSrs] - Whether the geometry is in the map's SRS (otherwise EPSG:4326 is assumed)
|
|
206
247
|
* @param {AbortSignal} [options.signal] - A signal to abort the iframe loading
|
|
207
248
|
* @return {Promise<void>} A Promise for when the iframe is loaded
|
|
208
|
-
* @throws For
|
|
249
|
+
* @throws For invalid arguments
|
|
209
250
|
* @fires `editGeometry:update` - When the user changed the geometry. The event includes the edited geometry.
|
|
210
251
|
* @fires `editGeometry:ok` - When the user completed the geometry editing. The event includes the edited geometry.
|
|
211
252
|
* @fires `editGeometry:cancel` - When the user cancelled the geometry editing in Cadenza.
|
|
212
253
|
*/
|
|
213
|
-
editGeometry(backgroundMapView, geometry, { mapExtent,
|
|
254
|
+
async editGeometry(backgroundMapView, geometry, { locationFinder, mapExtent, minScale, useMapSrs, signal } = {}) {
|
|
214
255
|
this.#log('CadenzaClient#editGeometry', backgroundMapView, geometry);
|
|
215
256
|
assertValidGeometryType(geometry.type);
|
|
216
|
-
const params =
|
|
257
|
+
const params = createParams({
|
|
217
258
|
action: 'editGeometry',
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
259
|
+
locationFinder,
|
|
260
|
+
mapExtent,
|
|
261
|
+
minScale,
|
|
262
|
+
useMapSrs,
|
|
263
|
+
webApplication: this.#webApplication,
|
|
222
264
|
});
|
|
223
|
-
|
|
265
|
+
await this.#show(resolvePath(backgroundMapView), params, signal);
|
|
266
|
+
this.#postEvent('setGeometry', { geometry });
|
|
224
267
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
*/
|
|
231
|
-
#show(path, { params, signal }) {
|
|
232
|
-
const url = new URL(this.baseUrl + path);
|
|
233
|
-
if (params) {
|
|
234
|
-
for (const [param, value] of params) {
|
|
235
|
-
url.searchParams.append(param, value);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
268
|
+
#show(
|
|
269
|
+
/** @type string */ path,
|
|
270
|
+
/** @type URLSearchParams */ params,
|
|
271
|
+
/** @type AbortSignal | undefined */ signal) {
|
|
272
|
+
const url = this.#createUrl(path, params);
|
|
238
273
|
this.#log('Load iframe', url.toString());
|
|
239
274
|
this.#requiredIframe.src = url.toString();
|
|
240
275
|
return this.#getIframePromise(signal);
|
|
241
276
|
}
|
|
242
|
-
/**
|
|
243
|
-
* @param {AbortSignal} [signal]
|
|
244
|
-
*/
|
|
245
|
-
#getIframePromise(signal) {
|
|
277
|
+
#getIframePromise(/** @type AbortSignal | undefined */ signal) {
|
|
246
278
|
const iframe = this.#requiredIframe;
|
|
247
279
|
/** @type {() => void} */
|
|
248
280
|
let onerror;
|
|
@@ -269,8 +301,9 @@ export class CadenzaClient {
|
|
|
269
301
|
}),
|
|
270
302
|
];
|
|
271
303
|
});
|
|
272
|
-
promise
|
|
273
|
-
|
|
304
|
+
promise
|
|
305
|
+
.then(() => this.#log('Iframe loaded'), (error) => this.#log('Iframe loading failed', error))
|
|
306
|
+
.finally(() => {
|
|
274
307
|
iframe.removeEventListener('error', onerror);
|
|
275
308
|
signal?.removeEventListener('abort', onabort);
|
|
276
309
|
unsubscribes.forEach((unsubscribe) => unsubscribe());
|
|
@@ -303,9 +336,8 @@ export class CadenzaClient {
|
|
|
303
336
|
}
|
|
304
337
|
};
|
|
305
338
|
}
|
|
306
|
-
/** @param {MessageEvent<CadenzaEvent<never>>} event */
|
|
307
339
|
// Use arrow function so that it's bound to this.
|
|
308
|
-
#onMessage = (event) => {
|
|
340
|
+
#onMessage = (/** @type MessageEvent<CadenzaEvent<never>> */ event) => {
|
|
309
341
|
this.#log('Received message', event);
|
|
310
342
|
if (event.origin !== this.#origin ||
|
|
311
343
|
event.source !== this.#requiredIframe.contentWindow) {
|
|
@@ -318,25 +350,98 @@ export class CadenzaClient {
|
|
|
318
350
|
}
|
|
319
351
|
});
|
|
320
352
|
};
|
|
321
|
-
/**
|
|
322
|
-
* @param {string} type
|
|
323
|
-
* @param {unknown} detail
|
|
324
|
-
*/
|
|
325
|
-
#postEvent(type, detail) {
|
|
353
|
+
#postEvent(/** @type string */ type, /** @type unknown */ detail) {
|
|
326
354
|
const event = { type, detail };
|
|
327
355
|
this.#log('postMessage', event);
|
|
328
356
|
const contentWindow = /** @type {WindowProxy} */ (this.#requiredIframe.contentWindow);
|
|
329
357
|
contentWindow.postMessage(event, { targetOrigin: this.#origin });
|
|
330
358
|
}
|
|
331
|
-
/**
|
|
332
|
-
|
|
359
|
+
/**
|
|
360
|
+
* Fetch data from a workbook view.
|
|
361
|
+
*
|
|
362
|
+
* @param {WorkbookViewSource} source - The workbook view to fetch data from
|
|
363
|
+
* @param {MediaType} mediaType - The media type to use for the data. Allowed are:
|
|
364
|
+
* * 'application/json'
|
|
365
|
+
* * 'application/pdf' (for Jasper Report views)
|
|
366
|
+
* * 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' (Excel)
|
|
367
|
+
* * 'text/csv'
|
|
368
|
+
* @param {object} options - Options
|
|
369
|
+
* @param {AbortSignal} [options.signal] - A signal to abort the data fetching
|
|
370
|
+
* @return {Promise<Response>} A Promise for the fetch response
|
|
371
|
+
* @throws For invalid arguments
|
|
372
|
+
*/
|
|
373
|
+
fetchData(source, mediaType, { signal } = {}) {
|
|
374
|
+
this.#log('CadenzaClient#fetchData', source, mediaType);
|
|
375
|
+
assertSupportedMediaType(mediaType);
|
|
376
|
+
const params = createParams({ mediaType });
|
|
377
|
+
return this.#fetch(resolvePath(source), params, signal);
|
|
378
|
+
}
|
|
379
|
+
async #fetch(
|
|
380
|
+
/** @type string */ path,
|
|
381
|
+
/** @type URLSearchParams */ params,
|
|
382
|
+
/** @type AbortSignal | undefined */ signal) {
|
|
383
|
+
const url = this.#createUrl(path, params);
|
|
384
|
+
this.#log('Fetch', url.toString());
|
|
385
|
+
const res = await fetch(url, { signal });
|
|
386
|
+
if (!res.ok) {
|
|
387
|
+
const errorType = {
|
|
388
|
+
400: 'bad-request',
|
|
389
|
+
401: 'unauthorized',
|
|
390
|
+
404: 'not-found',
|
|
391
|
+
}[res.status] ?? 'internal-error';
|
|
392
|
+
throw new CadenzaError(errorType, 'Failed to fetch data');
|
|
393
|
+
}
|
|
394
|
+
return res;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Download data from a workbook view.
|
|
398
|
+
*
|
|
399
|
+
* _Note:_ The file name, if not provided, is generated from the name of the workbook view and the current date.
|
|
400
|
+
*
|
|
401
|
+
* @param {WorkbookViewSource} source - The workbook view to download data from
|
|
402
|
+
* @param {MediaType} mediaType - The media type to use for the data. Allowed are:
|
|
403
|
+
* * 'application/json'
|
|
404
|
+
* * 'application/pdf' (for Jasper Report views)
|
|
405
|
+
* * 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' (Excel)
|
|
406
|
+
* * 'text/csv'
|
|
407
|
+
* @param {object} options - Options
|
|
408
|
+
* @param {string} [options.fileName] - The file name to use; The file extension is appended by Cadenza.
|
|
409
|
+
* @throws For invalid arguments
|
|
410
|
+
*/
|
|
411
|
+
downloadData(source, mediaType, { fileName }) {
|
|
412
|
+
this.#log('CadenzaClient#downloadData', source, mediaType);
|
|
413
|
+
assertSupportedMediaType(mediaType);
|
|
414
|
+
const params = createParams({ fileName, mediaType });
|
|
415
|
+
this.#download(resolvePath(source), params);
|
|
416
|
+
}
|
|
417
|
+
#download(/** @type string */ path, /** @type URLSearchParams */ params) {
|
|
418
|
+
const url = this.#createUrl(path, params);
|
|
419
|
+
const a = document.createElement('a');
|
|
420
|
+
a.href = url.toString();
|
|
421
|
+
// causes the file to be downloaded even if the server sends a "Content-disposition: inline" header
|
|
422
|
+
a.download = '';
|
|
423
|
+
a.hidden = true;
|
|
424
|
+
document.body.append(a);
|
|
425
|
+
a.click();
|
|
426
|
+
a.remove();
|
|
427
|
+
}
|
|
428
|
+
#createUrl(/** @type string */ path, /** @type URLSearchParams */ params) {
|
|
429
|
+
const url = new URL(this.baseUrl + path);
|
|
430
|
+
if (params) {
|
|
431
|
+
for (const [param, value] of params) {
|
|
432
|
+
url.searchParams.append(param, value);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return url;
|
|
436
|
+
}
|
|
437
|
+
#log(/** @type unknown[] */ ...args) {
|
|
333
438
|
if (this.#debug) {
|
|
334
439
|
console.log(...args);
|
|
335
440
|
}
|
|
336
441
|
}
|
|
337
442
|
}
|
|
338
|
-
|
|
339
|
-
|
|
443
|
+
function resolvePath(
|
|
444
|
+
/** @type WorkbookSource | WorksheetSource | WorkbookViewSource */ source) {
|
|
340
445
|
if (typeof source === 'string') {
|
|
341
446
|
assert(validEmbeddingTargetId(source), `Invalid embedding target ID: ${source}`);
|
|
342
447
|
return `/w/${source}`;
|
|
@@ -359,17 +464,12 @@ function resolvePath(source) {
|
|
|
359
464
|
return path;
|
|
360
465
|
}
|
|
361
466
|
}
|
|
362
|
-
/**
|
|
363
|
-
* @param {boolean} assertion
|
|
364
|
-
* @param {string} message
|
|
365
|
-
*/
|
|
366
|
-
function assert(assertion, message) {
|
|
467
|
+
function assert(/** @type boolean */ assertion, /** @type string */ message) {
|
|
367
468
|
if (!assertion) {
|
|
368
469
|
throw new Error(message);
|
|
369
470
|
}
|
|
370
471
|
}
|
|
371
|
-
/** @
|
|
372
|
-
function validUrl(value) {
|
|
472
|
+
function validUrl(/** @type string */ value) {
|
|
373
473
|
try {
|
|
374
474
|
new URL(value);
|
|
375
475
|
return true;
|
|
@@ -378,16 +478,13 @@ function validUrl(value) {
|
|
|
378
478
|
return false;
|
|
379
479
|
}
|
|
380
480
|
}
|
|
381
|
-
/** @
|
|
382
|
-
function validEmbeddingTargetId(value) {
|
|
481
|
+
function validEmbeddingTargetId(/** @type string */ value) {
|
|
383
482
|
return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value);
|
|
384
483
|
}
|
|
385
|
-
/** @
|
|
386
|
-
function validRepositoryName(value) {
|
|
484
|
+
function validRepositoryName(/** @type string */ value) {
|
|
387
485
|
return /^[\w -]{1,255}$/.test(value);
|
|
388
486
|
}
|
|
389
|
-
/** @
|
|
390
|
-
function validWorkbookId(value) {
|
|
487
|
+
function validWorkbookId(/** @type string */ value) {
|
|
391
488
|
try {
|
|
392
489
|
// Workbook IDs are url-safe base64 strings.
|
|
393
490
|
// https://stackoverflow.com/a/44528376
|
|
@@ -398,12 +495,14 @@ function validWorkbookId(value) {
|
|
|
398
495
|
return false;
|
|
399
496
|
}
|
|
400
497
|
}
|
|
401
|
-
/** @
|
|
402
|
-
|
|
403
|
-
|
|
498
|
+
function validExternalLinkKey(/** @type ExternalLinkKey */ linkKey) {
|
|
499
|
+
return (validRepositoryName(linkKey.repositoryName) &&
|
|
500
|
+
validWorkbookId(linkKey.externalLinkId));
|
|
501
|
+
}
|
|
502
|
+
function assertValidGeometryType(/** @type string */ value) {
|
|
503
|
+
assert(validGeometryType(value), `Invalid geometry type: ${value}`);
|
|
404
504
|
}
|
|
405
|
-
/** @
|
|
406
|
-
function validGeometryType(value) {
|
|
505
|
+
function validGeometryType(/** @type string */ value) {
|
|
407
506
|
return [
|
|
408
507
|
'Point',
|
|
409
508
|
'MultiPoint',
|
|
@@ -413,6 +512,78 @@ function validGeometryType(value) {
|
|
|
413
512
|
'MultiPolygon',
|
|
414
513
|
].includes(value);
|
|
415
514
|
}
|
|
515
|
+
function validOperationMode(/** @type string */ value) {
|
|
516
|
+
return ['normal', 'simplified'].includes(value);
|
|
517
|
+
}
|
|
518
|
+
function validUiFeature(/** @type string */ value) {
|
|
519
|
+
return ['workbook-design', 'workbook-view-management'].includes(value);
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* @typedef {string} MediaType - A media type
|
|
523
|
+
*
|
|
524
|
+
* See {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types}
|
|
525
|
+
*/
|
|
526
|
+
const MediaType = /** @type {Record<string, MediaType>} */ {
|
|
527
|
+
CSV: 'text/csv',
|
|
528
|
+
EXCEL: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
529
|
+
JSON: 'application/json',
|
|
530
|
+
PDF: 'application/pdf',
|
|
531
|
+
};
|
|
532
|
+
function assertSupportedMediaType(
|
|
533
|
+
/** @type MediaType */ type,
|
|
534
|
+
/** @type MediaType[] */ supportedTypes = Object.values(MediaType)) {
|
|
535
|
+
return assert(supportedTypes.includes(type), `Invalid media type: ${type}`);
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* @param {object} params - Options
|
|
539
|
+
* @param {string} [params.action]
|
|
540
|
+
* @param {UiFeature[]} [params.disabledUiFeatures]
|
|
541
|
+
* @param {string} [params.fileName]
|
|
542
|
+
* @param {GeometryType} [params.geometryType]
|
|
543
|
+
* @param {boolean} [params.hideMainHeaderAndFooter]
|
|
544
|
+
* @param {boolean} [params.hideWorkbookToolBar]
|
|
545
|
+
* @param {GlobalId} [params.highlightGlobalId]
|
|
546
|
+
* @param {string} [params.locationFinder]
|
|
547
|
+
* @param {Extent} [params.mapExtent]
|
|
548
|
+
* @param {string} [params.mediaType]
|
|
549
|
+
* @param {number} [params.minScale]
|
|
550
|
+
* @param {OperationMode} [params.operationMode]
|
|
551
|
+
* @param {boolean} [params.useMapSrs]
|
|
552
|
+
* @param {ExternalLinkKey} [params.webApplication]
|
|
553
|
+
* @return {URLSearchParams}
|
|
554
|
+
*/
|
|
555
|
+
function createParams({ action, fileName, geometryType, hideMainHeaderAndFooter, hideWorkbookToolBar, highlightGlobalId, locationFinder, mapExtent, mediaType, minScale, useMapSrs, webApplication, operationMode, disabledUiFeatures, }) {
|
|
556
|
+
if (disabledUiFeatures) {
|
|
557
|
+
disabledUiFeatures.forEach((feature) => assert(validUiFeature(feature), `Invalid UI feature: ${feature}`));
|
|
558
|
+
}
|
|
559
|
+
if (geometryType) {
|
|
560
|
+
assertValidGeometryType(geometryType);
|
|
561
|
+
}
|
|
562
|
+
if (operationMode) {
|
|
563
|
+
assert(validOperationMode(operationMode), `Invalid operation mode: ${operationMode}`);
|
|
564
|
+
}
|
|
565
|
+
return new URLSearchParams({
|
|
566
|
+
...(action && { action }),
|
|
567
|
+
...(disabledUiFeatures && {
|
|
568
|
+
disabledUiFeatures: disabledUiFeatures.join(),
|
|
569
|
+
}),
|
|
570
|
+
...(fileName && { fileName }),
|
|
571
|
+
...(geometryType && { geometryType }),
|
|
572
|
+
...(hideMainHeaderAndFooter && { hideMainHeaderAndFooter: 'true' }),
|
|
573
|
+
...(hideWorkbookToolBar && { hideWorkbookToolBar: 'true' }),
|
|
574
|
+
...(highlightGlobalId && { highlightGlobalId }),
|
|
575
|
+
...(locationFinder && { locationFinder }),
|
|
576
|
+
...(mapExtent && { mapExtent: mapExtent.join() }),
|
|
577
|
+
...(mediaType && { mediaType }),
|
|
578
|
+
...(minScale && { minScale: String(minScale) }),
|
|
579
|
+
...(operationMode && { operationMode }),
|
|
580
|
+
...(useMapSrs && { useMapSrs: 'true' }),
|
|
581
|
+
...(webApplication && {
|
|
582
|
+
webApplicationLink: webApplication.externalLinkId,
|
|
583
|
+
webApplicationLinkRepository: webApplication.repositoryName,
|
|
584
|
+
}),
|
|
585
|
+
});
|
|
586
|
+
}
|
|
416
587
|
/**
|
|
417
588
|
* @template [T=unknown]
|
|
418
589
|
* @typedef CadenzaEvent - A Cadenza `postMessage()` event
|
package/package.json
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@disy/cadenza.js",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"repository": "github:DisyInformationssysteme/cadenza.js",
|
|
4
6
|
"type": "module",
|
|
5
7
|
"main": "./cadenza.js",
|
|
6
8
|
"types": "./cadenza.d.ts",
|
|
@@ -8,27 +10,32 @@
|
|
|
8
10
|
"apidoc/**/*",
|
|
9
11
|
"cadenza.js",
|
|
10
12
|
"cadenza.d.ts",
|
|
11
|
-
"CHANGELOG.md"
|
|
13
|
+
"CHANGELOG.md",
|
|
14
|
+
"sandbox.html"
|
|
12
15
|
],
|
|
13
16
|
"scripts": {
|
|
14
|
-
"build": "run-p build:*",
|
|
17
|
+
"build": "run-p validate:* build:*",
|
|
15
18
|
"build:docs": "typedoc",
|
|
16
19
|
"build:ts": "tsc",
|
|
17
|
-
"
|
|
20
|
+
"validate:format": "prettier ./src --check",
|
|
21
|
+
"validate:changelog": "changelog --latest-release",
|
|
18
22
|
"start": "run-p start:*",
|
|
19
23
|
"start:docs": "npm run build:docs -- --watch",
|
|
20
24
|
"start:test": "npm test -- --watch",
|
|
25
|
+
"start:ts": "npm run build:ts -- --watch",
|
|
21
26
|
"start:reformat": "onchange \"src/**/*\" -- prettier {{changed}} --write",
|
|
22
27
|
"test": "cross-env NODE_OPTIONS=--unhandled-rejections=warn jest",
|
|
23
28
|
"deploy:docs": "gh-pages --dist apidoc",
|
|
24
|
-
"sandbox": "
|
|
29
|
+
"sandbox": "node ./sandbox.cjs"
|
|
25
30
|
},
|
|
26
31
|
"devDependencies": {
|
|
27
32
|
"@types/jest": "29.5.3",
|
|
28
33
|
"cross-env": "7.0.3",
|
|
29
34
|
"gh-pages": "6.0.0",
|
|
35
|
+
"http-server": "14.1.1",
|
|
30
36
|
"jest": "29.6.1",
|
|
31
37
|
"jest-environment-jsdom": "29.6.1",
|
|
38
|
+
"keep-a-changelog": "2.3.0",
|
|
32
39
|
"npm-run-all": "4.1.5",
|
|
33
40
|
"onchange": "7.1.0",
|
|
34
41
|
"prettier": "3.0.0",
|