@disy/cadenza.js 0.0.1
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 +15 -0
- package/LICENSE +21 -0
- package/README.md +189 -0
- package/apidoc/.nojekyll +1 -0
- package/apidoc/assets/highlight.css +120 -0
- package/apidoc/assets/main.js +58 -0
- package/apidoc/assets/search.js +1 -0
- package/apidoc/assets/style.css +1367 -0
- package/apidoc/classes/AbortError.html +541 -0
- package/apidoc/classes/CadenzaClient.html +569 -0
- package/apidoc/classes/CadenzaError.html +227 -0
- package/apidoc/functions/cadenza.html +90 -0
- package/apidoc/index.html +137 -0
- package/apidoc/interfaces/CadenzaEvent.html +104 -0
- package/apidoc/interfaces/Geometry.html +88 -0
- package/apidoc/interfaces/WorkbookKey.html +98 -0
- package/apidoc/interfaces/WorkbookViewKey.html +108 -0
- package/apidoc/interfaces/WorksheetKey.html +108 -0
- package/apidoc/modules.html +82 -0
- package/apidoc/types/CadenzaErrorEvent.html +67 -0
- package/apidoc/types/EmbeddingTargetId.html +60 -0
- package/apidoc/types/Extent.html +60 -0
- package/apidoc/types/GeometryType.html +61 -0
- package/apidoc/types/WorkbookSource.html +60 -0
- package/apidoc/types/WorkbookViewSource.html +60 -0
- package/apidoc/types/WorksheetSource.html +60 -0
- package/cadenza.d.ts +296 -0
- package/cadenza.js +446 -0
- package/package.json +41 -0
package/cadenza.js
ADDED
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a new Cadenza instance of the Cadenza JS client.
|
|
3
|
+
*
|
|
4
|
+
* @param {string} baseUrl - The base URL of the Cadenza server
|
|
5
|
+
* @param {object} [options] - Options
|
|
6
|
+
* @param {HTMLIFrameElement | string} [options.iframe] - An iframe for embedding Cadenza or the iframe's ID
|
|
7
|
+
* @param {boolean} [options.debug] - Whether to enable debug logging
|
|
8
|
+
* @throws For invalid base URL
|
|
9
|
+
*/
|
|
10
|
+
export function cadenza(baseUrl, options) {
|
|
11
|
+
return new CadenzaClient(baseUrl, options);
|
|
12
|
+
}
|
|
13
|
+
/* @ts-ignore */
|
|
14
|
+
const previousGlobalCadenza = globalThis.cadenza;
|
|
15
|
+
globalThis.cadenza = Object.assign(
|
|
16
|
+
/** @param {Parameters<cadenza>} args */
|
|
17
|
+
(...args) => cadenza(...args), {
|
|
18
|
+
noConflict() {
|
|
19
|
+
globalThis.cadenza = previousGlobalCadenza;
|
|
20
|
+
return cadenza;
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
/** @typedef {string} EmbeddingTargetId - The ID of an embedding target */
|
|
24
|
+
/**
|
|
25
|
+
* @typedef WorkbookKey - A tuple qualifying a workbook
|
|
26
|
+
* @property {string} repositoryName - The name of the workbook's repository
|
|
27
|
+
* @property {string} workbookId - The ID of the workbook
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* @typedef WorksheetKey - A tuple qualifying a worksheet
|
|
31
|
+
* @property {string} repositoryName - The name of the workbook's repository
|
|
32
|
+
* @property {string} workbookId - The ID of the workbook
|
|
33
|
+
* @property {string} worksheetId - The ID of the worksheet
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* @typedef WorkbookViewKey - A tuple qualifying a workbook view
|
|
37
|
+
* @property {string} repositoryName - The name of the workbook's repository
|
|
38
|
+
* @property {string} workbookId - The ID of the workbook
|
|
39
|
+
* @property {string} viewId - The ID of the view
|
|
40
|
+
*/
|
|
41
|
+
/** @typedef {EmbeddingTargetId | WorkbookKey} WorkbookSource - A workbook source */
|
|
42
|
+
/** @typedef {EmbeddingTargetId | WorksheetKey} WorksheetSource - A worksheet source */
|
|
43
|
+
/** @typedef {EmbeddingTargetId | WorkbookViewKey} WorkbookViewSource - A workbook view source */
|
|
44
|
+
/**
|
|
45
|
+
* @typedef Geometry - A [GeoJSON](https://geojson.org/) geometry object
|
|
46
|
+
* @property {GeometryType} type - The type of the geometry
|
|
47
|
+
*/
|
|
48
|
+
/**
|
|
49
|
+
* @typedef {'Point'|'MultiPoint'|'LineString'|'MultiLineString'|'Polygon'|'MultiPolygon'} GeometryType - A GeoJSON geometry type
|
|
50
|
+
*
|
|
51
|
+
* _Note:_ The GeoJSON geometry type "GeometryCollection" is currently not supported.
|
|
52
|
+
*/
|
|
53
|
+
/** @typedef {[number,number,number,number]} Extent - An array of numbers representing an extent: [minx, miny, maxx, maxy] */
|
|
54
|
+
/**
|
|
55
|
+
* _Notes:_
|
|
56
|
+
* * Most public methods can be aborted using an [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal).
|
|
57
|
+
* When aborted, the result Promise is rejected with an {@link AbortError}.
|
|
58
|
+
* * If there's another error, the result Promise is rejected with a {@link CadenzaError}.
|
|
59
|
+
* * For methods that support the `hideMainHeaderAndFooter` and `hideWorkbookToolBar` parameters - the parameters cannot override the configuration of an embedding target.
|
|
60
|
+
* * For methods that support the `locationFinder` and `mapExtent` parameters - when both are given, the `mapExtent` takes precedence.
|
|
61
|
+
*/
|
|
62
|
+
// Must be exported to be included in the docs.
|
|
63
|
+
export class CadenzaClient {
|
|
64
|
+
/** @readonly */
|
|
65
|
+
#baseUrl;
|
|
66
|
+
/** @readonly */
|
|
67
|
+
#origin;
|
|
68
|
+
/** @readonly */
|
|
69
|
+
#iframe;
|
|
70
|
+
/** @type {HTMLIFrameElement | undefined} */
|
|
71
|
+
#iframeElement;
|
|
72
|
+
/** @readonly */
|
|
73
|
+
#debug;
|
|
74
|
+
/** @type {[ string, (event: CadenzaEvent<never>) => void ][]} */
|
|
75
|
+
#subscriptions = [];
|
|
76
|
+
/**
|
|
77
|
+
* @hidden
|
|
78
|
+
* @param {string} baseUrl
|
|
79
|
+
* @param {object} [options]
|
|
80
|
+
* @param {HTMLIFrameElement | string} [options.iframe]
|
|
81
|
+
* @param {boolean} [options.debug]
|
|
82
|
+
*/
|
|
83
|
+
constructor(baseUrl, { debug = false, iframe } = {}) {
|
|
84
|
+
assert(validUrl(baseUrl), `Invalid baseUrl: ${baseUrl}`);
|
|
85
|
+
// Remove trailing /
|
|
86
|
+
if (baseUrl.at(-1) === '/') {
|
|
87
|
+
baseUrl = baseUrl.substring(0, baseUrl.length - 1);
|
|
88
|
+
}
|
|
89
|
+
this.#log('Create Cadenza client', baseUrl, iframe);
|
|
90
|
+
this.#baseUrl = baseUrl;
|
|
91
|
+
this.#origin = new URL(baseUrl).origin;
|
|
92
|
+
this.#iframe = iframe;
|
|
93
|
+
this.#debug = debug;
|
|
94
|
+
}
|
|
95
|
+
/** The base URL of the Cadenza server this client is requesting */
|
|
96
|
+
get baseUrl() {
|
|
97
|
+
return this.#baseUrl;
|
|
98
|
+
}
|
|
99
|
+
/** The iframe this client is using for embedding Cadenza. */
|
|
100
|
+
get iframe() {
|
|
101
|
+
const iframe = this.#iframe;
|
|
102
|
+
if (!this.#iframeElement && iframe) {
|
|
103
|
+
this.#iframeElement =
|
|
104
|
+
typeof iframe === 'string'
|
|
105
|
+
? /** @type {HTMLIFrameElement} */ (document.getElementById(iframe) ?? undefined)
|
|
106
|
+
: iframe;
|
|
107
|
+
}
|
|
108
|
+
return this.#iframeElement;
|
|
109
|
+
}
|
|
110
|
+
get #requiredIframe() {
|
|
111
|
+
const iframe = this.iframe;
|
|
112
|
+
assert(iframe instanceof HTMLIFrameElement, 'Required iframe is not present.');
|
|
113
|
+
return /** @type {HTMLIFrameElement} */ (iframe);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Show a workbook, worksheet or workbook view in an iframe.
|
|
117
|
+
*
|
|
118
|
+
* @param {WorkbookSource | WorksheetSource | WorkbookViewSource} source - The source to show
|
|
119
|
+
* @param {object} [options]
|
|
120
|
+
* @param {boolean} [options.hideMainHeaderAndFooter] - Whether to hide the main Cadenza header and footer.
|
|
121
|
+
* @param {boolean} [options.hideWorkbookToolBar] - Whether to hide the workbook toolbar.
|
|
122
|
+
* @param {AbortSignal} [options.signal] - A signal to abort the iframe loading
|
|
123
|
+
* @return {Promise<void>} A Promise for when the iframe is loaded
|
|
124
|
+
* @throws For an invalid source
|
|
125
|
+
*/
|
|
126
|
+
show(source, { hideMainHeaderAndFooter, hideWorkbookToolBar, signal } = {}) {
|
|
127
|
+
this.#log('CadenzaClient#show', source);
|
|
128
|
+
const params = new URLSearchParams({
|
|
129
|
+
...(hideMainHeaderAndFooter && { hideMainHeaderAndFooter: 'true' }),
|
|
130
|
+
...(hideWorkbookToolBar && { hideWorkbookToolBar: 'true' }),
|
|
131
|
+
});
|
|
132
|
+
return this.#show(resolvePath(source), { params, signal });
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Show a workbook map view in an iframe.
|
|
136
|
+
*
|
|
137
|
+
* @param {WorkbookViewSource} mapView - The workbook map view to show
|
|
138
|
+
* @param {object} [options] - Options
|
|
139
|
+
* @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.
|
|
142
|
+
* @param {string} [options.locationFinder] - A search query for the location finder
|
|
143
|
+
* @param {Extent} [options.mapExtent] - A map extent to set
|
|
144
|
+
* @param {boolean} [options.useMapSrs] - Whether the geometry and the extent are in the map's SRS (otherwise EPSG:4326 is assumed)
|
|
145
|
+
* @param {AbortSignal} [options.signal] - A signal to abort the iframe loading
|
|
146
|
+
* @return {Promise<void>} A Promise for when the iframe is loaded
|
|
147
|
+
* @throws For an invalid workbook view source or geometry type
|
|
148
|
+
*/
|
|
149
|
+
showMap(mapView, { geometry, hideMainHeaderAndFooter, hideWorkbookToolBar, locationFinder, mapExtent, useMapSrs, signal, } = {}) {
|
|
150
|
+
this.#log('CadenzaClient#showMap', mapView, geometry);
|
|
151
|
+
if (geometry) {
|
|
152
|
+
assertValidGeometryType(geometry.type);
|
|
153
|
+
}
|
|
154
|
+
const params = new URLSearchParams({
|
|
155
|
+
...(hideMainHeaderAndFooter && { hideMainHeaderAndFooter: 'true' }),
|
|
156
|
+
...(hideWorkbookToolBar && { hideWorkbookToolBar: 'true' }),
|
|
157
|
+
...(locationFinder && { locationFinder }),
|
|
158
|
+
...(mapExtent && { mapExtent: mapExtent.join() }),
|
|
159
|
+
...(useMapSrs && { useMapSrs: 'true' }),
|
|
160
|
+
});
|
|
161
|
+
return this.#show(resolvePath(mapView), { params, signal }).then(() => this.#postEvent('setGeometry', { geometry }));
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Create a geometry.
|
|
165
|
+
*
|
|
166
|
+
* _Note:_ Under the hood, creating a geometry is similar to editing a geometry.
|
|
167
|
+
* That's why the events use the `editGeometry` prefix.
|
|
168
|
+
*
|
|
169
|
+
* @param {WorkbookViewSource} backgroundMapView - The workbook map view in the background
|
|
170
|
+
* @param {GeometryType} geometryType - The geometry type
|
|
171
|
+
* @param {object} [options] - Options
|
|
172
|
+
* @param {Extent} [options.mapExtent] - A map extent to set
|
|
173
|
+
* @param {string} [options.locationFinder] - A search query for the location finder
|
|
174
|
+
* @param {boolean} [options.useMapSrs] - Whether the created geometry should use the map's SRS (otherwise EPSG:4326 will be used)
|
|
175
|
+
* @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.
|
|
176
|
+
* @param {AbortSignal} [options.signal] - A signal to abort the iframe loading
|
|
177
|
+
* @return {Promise<void>} A Promise for when the iframe is loaded
|
|
178
|
+
* @throws For an invalid workbook view source or geometry type
|
|
179
|
+
* @fires `editGeometry:update` - When the user changed the geometry. The event includes the edited geometry.
|
|
180
|
+
* @fires `editGeometry:ok` - When the user completed the geometry editing. The event includes the edited geometry.
|
|
181
|
+
* @fires `editGeometry:cancel` - When the user cancelled the geometry editing in Cadenza.
|
|
182
|
+
*/
|
|
183
|
+
createGeometry(backgroundMapView, geometryType, { mapExtent, locationFinder, useMapSrs, minScale, signal } = {}) {
|
|
184
|
+
this.#log('CadenzaClient#createGeometry', backgroundMapView, geometryType);
|
|
185
|
+
assertValidGeometryType(geometryType);
|
|
186
|
+
const params = new URLSearchParams({
|
|
187
|
+
action: 'editGeometry',
|
|
188
|
+
geometryType,
|
|
189
|
+
...(useMapSrs && { useMapSrs: 'true' }),
|
|
190
|
+
...(locationFinder && { locationFinder }),
|
|
191
|
+
...(mapExtent && { mapExtent: mapExtent.join() }),
|
|
192
|
+
...(minScale && { minScale: String(minScale) }),
|
|
193
|
+
});
|
|
194
|
+
return this.#show(resolvePath(backgroundMapView), { params, signal });
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Edit a geometry.
|
|
198
|
+
*
|
|
199
|
+
* @param {WorkbookViewSource} backgroundMapView - The workbook map view in the background
|
|
200
|
+
* @param {Geometry} geometry - The geometry
|
|
201
|
+
* @param {object} [options] - Options
|
|
202
|
+
* @param {string} [options.locationFinder] - A search query for the location finder
|
|
203
|
+
* @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
|
+
* @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.
|
|
206
|
+
* @param {AbortSignal} [options.signal] - A signal to abort the iframe loading
|
|
207
|
+
* @return {Promise<void>} A Promise for when the iframe is loaded
|
|
208
|
+
* @throws For an invalid workbook view source
|
|
209
|
+
* @fires `editGeometry:update` - When the user changed the geometry. The event includes the edited geometry.
|
|
210
|
+
* @fires `editGeometry:ok` - When the user completed the geometry editing. The event includes the edited geometry.
|
|
211
|
+
* @fires `editGeometry:cancel` - When the user cancelled the geometry editing in Cadenza.
|
|
212
|
+
*/
|
|
213
|
+
editGeometry(backgroundMapView, geometry, { mapExtent, locationFinder, useMapSrs, minScale, signal } = {}) {
|
|
214
|
+
this.#log('CadenzaClient#editGeometry', backgroundMapView, geometry);
|
|
215
|
+
assertValidGeometryType(geometry.type);
|
|
216
|
+
const params = new URLSearchParams({
|
|
217
|
+
action: 'editGeometry',
|
|
218
|
+
...(locationFinder && { locationFinder }),
|
|
219
|
+
...(mapExtent && { mapExtent: mapExtent.join() }),
|
|
220
|
+
...(useMapSrs && { useMapSrs: 'true' }),
|
|
221
|
+
...(minScale && { minScale: String(minScale) }),
|
|
222
|
+
});
|
|
223
|
+
return this.#show(resolvePath(backgroundMapView), { params, signal }).then(() => this.#postEvent('setGeometry', { geometry }));
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* @param {string} path
|
|
227
|
+
* @param {object} options
|
|
228
|
+
* @param {URLSearchParams} [options.params]
|
|
229
|
+
* @param {AbortSignal} [options.signal]
|
|
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
|
+
}
|
|
238
|
+
this.#log('Load iframe', url.toString());
|
|
239
|
+
this.#requiredIframe.src = url.toString();
|
|
240
|
+
return this.#getIframePromise(signal);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* @param {AbortSignal} [signal]
|
|
244
|
+
*/
|
|
245
|
+
#getIframePromise(signal) {
|
|
246
|
+
const iframe = this.#requiredIframe;
|
|
247
|
+
/** @type {() => void} */
|
|
248
|
+
let onerror;
|
|
249
|
+
/** @type {() => void} */
|
|
250
|
+
let onabort;
|
|
251
|
+
/** @type {(() => void)[]} */
|
|
252
|
+
let unsubscribes;
|
|
253
|
+
/** @type {Promise<void>} */
|
|
254
|
+
let promise = new Promise((resolve, reject) => {
|
|
255
|
+
onerror = () => reject(new CadenzaError('loading-error', 'Loading failed'));
|
|
256
|
+
iframe.addEventListener('error', onerror);
|
|
257
|
+
if (signal) {
|
|
258
|
+
onabort = () => {
|
|
259
|
+
iframe.contentWindow?.stop();
|
|
260
|
+
reject(new AbortError());
|
|
261
|
+
};
|
|
262
|
+
signal.addEventListener('abort', onabort);
|
|
263
|
+
}
|
|
264
|
+
unsubscribes = [
|
|
265
|
+
this.on('ready', () => resolve()),
|
|
266
|
+
this.on('error', (/** @type {CadenzaErrorEvent} */ event) => {
|
|
267
|
+
const { type, message } = event.detail;
|
|
268
|
+
reject(new CadenzaError(type, message ?? 'Loading failed'));
|
|
269
|
+
}),
|
|
270
|
+
];
|
|
271
|
+
});
|
|
272
|
+
promise.then(() => this.#log('Iframe loaded'), (error) => this.#log('Iframe loading failed', error));
|
|
273
|
+
promise.finally(() => {
|
|
274
|
+
iframe.removeEventListener('error', onerror);
|
|
275
|
+
signal?.removeEventListener('abort', onabort);
|
|
276
|
+
unsubscribes.forEach((unsubscribe) => unsubscribe());
|
|
277
|
+
});
|
|
278
|
+
return promise;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Subscribe to a `postMessage()` event.
|
|
282
|
+
*
|
|
283
|
+
* @template [T=unknown]
|
|
284
|
+
* @param {string} type - The event type
|
|
285
|
+
* @param {(event: CadenzaEvent<T>) => void} subscriber - The subscriber function
|
|
286
|
+
* @return {() => void} An unsubscribe function
|
|
287
|
+
*/
|
|
288
|
+
on(type, subscriber) {
|
|
289
|
+
const subscriptions = this.#subscriptions;
|
|
290
|
+
if (subscriptions.length === 0) {
|
|
291
|
+
window.addEventListener('message', this.#onMessage);
|
|
292
|
+
}
|
|
293
|
+
subscriptions.push([type, subscriber]);
|
|
294
|
+
return () => {
|
|
295
|
+
subscriptions.forEach(([subscriptionType, subscriptionSubscriber], i) => {
|
|
296
|
+
if (subscriptionType === type &&
|
|
297
|
+
subscriptionSubscriber === subscriber) {
|
|
298
|
+
subscriptions.splice(i, 1);
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
if (subscriptions.length === 0) {
|
|
302
|
+
window.removeEventListener('message', this.#onMessage);
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
/** @param {MessageEvent<CadenzaEvent<never>>} event */
|
|
307
|
+
// Use arrow function so that it's bound to this.
|
|
308
|
+
#onMessage = (event) => {
|
|
309
|
+
this.#log('Received message', event);
|
|
310
|
+
if (event.origin !== this.#origin ||
|
|
311
|
+
event.source !== this.#requiredIframe.contentWindow) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const cadenzaEvent = event.data;
|
|
315
|
+
this.#subscriptions.forEach(([type, subscriber]) => {
|
|
316
|
+
if (type === cadenzaEvent.type) {
|
|
317
|
+
subscriber(cadenzaEvent);
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
};
|
|
321
|
+
/**
|
|
322
|
+
* @param {string} type
|
|
323
|
+
* @param {unknown} detail
|
|
324
|
+
*/
|
|
325
|
+
#postEvent(type, detail) {
|
|
326
|
+
const event = { type, detail };
|
|
327
|
+
this.#log('postMessage', event);
|
|
328
|
+
const contentWindow = /** @type {WindowProxy} */ (this.#requiredIframe.contentWindow);
|
|
329
|
+
contentWindow.postMessage(event, { targetOrigin: this.#origin });
|
|
330
|
+
}
|
|
331
|
+
/** @param {unknown[]} args */
|
|
332
|
+
#log(...args) {
|
|
333
|
+
if (this.#debug) {
|
|
334
|
+
console.log(...args);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
/** @param {WorkbookSource | WorksheetSource | WorkbookViewSource} source */
|
|
339
|
+
function resolvePath(source) {
|
|
340
|
+
if (typeof source === 'string') {
|
|
341
|
+
assert(validEmbeddingTargetId(source), `Invalid embedding target ID: ${source}`);
|
|
342
|
+
return `/w/${source}`;
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
const { repositoryName, workbookId } = source;
|
|
346
|
+
assert(validRepositoryName(repositoryName), `Invalid repository name: ${repositoryName}`);
|
|
347
|
+
assert(validWorkbookId(workbookId), `Invalid workbook ID: ${workbookId}`);
|
|
348
|
+
const path = `/public/repositories/${repositoryName}/workbooks/${workbookId}`;
|
|
349
|
+
if ('worksheetId' in source) {
|
|
350
|
+
const worksheetId = source.worksheetId;
|
|
351
|
+
assert(validWorkbookId(worksheetId), `Invalid worksheet ID: ${worksheetId}`);
|
|
352
|
+
return `${path}/worksheets/${worksheetId}`;
|
|
353
|
+
}
|
|
354
|
+
if ('viewId' in source) {
|
|
355
|
+
const viewId = source.viewId;
|
|
356
|
+
assert(validWorkbookId(viewId), `Invalid view ID: ${viewId}`);
|
|
357
|
+
return `${path}/views/${viewId}`;
|
|
358
|
+
}
|
|
359
|
+
return path;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* @param {boolean} assertion
|
|
364
|
+
* @param {string} message
|
|
365
|
+
*/
|
|
366
|
+
function assert(assertion, message) {
|
|
367
|
+
if (!assertion) {
|
|
368
|
+
throw new Error(message);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
/** @param {string} value */
|
|
372
|
+
function validUrl(value) {
|
|
373
|
+
try {
|
|
374
|
+
new URL(value);
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
catch {
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/** @param {string} value */
|
|
382
|
+
function validEmbeddingTargetId(value) {
|
|
383
|
+
return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value);
|
|
384
|
+
}
|
|
385
|
+
/** @param {string} value */
|
|
386
|
+
function validRepositoryName(value) {
|
|
387
|
+
return /^[\w -]{1,255}$/.test(value);
|
|
388
|
+
}
|
|
389
|
+
/** @param {string} value */
|
|
390
|
+
function validWorkbookId(value) {
|
|
391
|
+
try {
|
|
392
|
+
// Workbook IDs are url-safe base64 strings.
|
|
393
|
+
// https://stackoverflow.com/a/44528376
|
|
394
|
+
atob(value.replace(/_/g, '/').replace(/-/g, '+'));
|
|
395
|
+
return value !== '';
|
|
396
|
+
}
|
|
397
|
+
catch {
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
/** @param {string} value */
|
|
402
|
+
function assertValidGeometryType(value) {
|
|
403
|
+
assert(validGeometryType(value), 'Invalid geometry type');
|
|
404
|
+
}
|
|
405
|
+
/** @param {string} value */
|
|
406
|
+
function validGeometryType(value) {
|
|
407
|
+
return [
|
|
408
|
+
'Point',
|
|
409
|
+
'MultiPoint',
|
|
410
|
+
'LineString',
|
|
411
|
+
'MultiLineString',
|
|
412
|
+
'Polygon',
|
|
413
|
+
'MultiPolygon',
|
|
414
|
+
].includes(value);
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* @template [T=unknown]
|
|
418
|
+
* @typedef CadenzaEvent - A Cadenza `postMessage()` event
|
|
419
|
+
* @property {string} type - The event type
|
|
420
|
+
* @property {T} detail - Optional event details (depending on the event type)
|
|
421
|
+
*/
|
|
422
|
+
/** @typedef {CadenzaEvent<{type: string, message?: string}>} CadenzaErrorEvent - An error event that is mapped to a {@link CadenzaError} */
|
|
423
|
+
export class AbortError extends DOMException {
|
|
424
|
+
constructor() {
|
|
425
|
+
super('Aborted', 'AbortError');
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* An `Error` implementation for errors in the communication with Cadenza.
|
|
430
|
+
*
|
|
431
|
+
* _Note:_ For invalid parameters, the Cadenza client will throw "normal" `Error`s.
|
|
432
|
+
*/
|
|
433
|
+
export class CadenzaError extends Error {
|
|
434
|
+
#type;
|
|
435
|
+
/**
|
|
436
|
+
* @param {string} type - The technical identifier of the error
|
|
437
|
+
* @param {string} message - A description of the error
|
|
438
|
+
*/
|
|
439
|
+
constructor(type, message) {
|
|
440
|
+
super(message);
|
|
441
|
+
this.#type = type;
|
|
442
|
+
}
|
|
443
|
+
get type() {
|
|
444
|
+
return this.#type;
|
|
445
|
+
}
|
|
446
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@disy/cadenza.js",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./cadenza.js",
|
|
6
|
+
"types": "./cadenza.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"apidoc/**/*",
|
|
9
|
+
"cadenza.js",
|
|
10
|
+
"cadenza.d.ts",
|
|
11
|
+
"CHANGELOG.md"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "run-p build:*",
|
|
15
|
+
"build:docs": "typedoc",
|
|
16
|
+
"build:ts": "tsc",
|
|
17
|
+
"reformat": "prettier ./src --write",
|
|
18
|
+
"start": "run-p start:*",
|
|
19
|
+
"start:docs": "npm run build:docs -- --watch",
|
|
20
|
+
"start:test": "npm test -- --watch",
|
|
21
|
+
"start:reformat": "onchange \"src/**/*\" -- prettier {{changed}} --write",
|
|
22
|
+
"test": "cross-env NODE_OPTIONS=--unhandled-rejections=warn jest",
|
|
23
|
+
"deploy:docs": "gh-pages --dist apidoc",
|
|
24
|
+
"sandbox": "npx http-server -a localhost -c-1 -d false --proxy http://localhost:8000 -o /sandbox.html"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/jest": "29.5.3",
|
|
28
|
+
"cross-env": "7.0.3",
|
|
29
|
+
"gh-pages": "6.0.0",
|
|
30
|
+
"jest": "29.6.1",
|
|
31
|
+
"jest-environment-jsdom": "29.6.1",
|
|
32
|
+
"npm-run-all": "4.1.5",
|
|
33
|
+
"onchange": "7.1.0",
|
|
34
|
+
"prettier": "3.0.0",
|
|
35
|
+
"ts-jest": "29.1.1",
|
|
36
|
+
"typedoc": "0.24.8"
|
|
37
|
+
},
|
|
38
|
+
"volta": {
|
|
39
|
+
"node": "16.13.1"
|
|
40
|
+
}
|
|
41
|
+
}
|