@abi-software/flatmap-viewer 2.3.0-a.4 → 2.3.0-a.5
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/README.rst +1 -1
- package/package.json +2 -1
- package/src/annotation.js +169 -134
package/README.rst
CHANGED
|
@@ -38,7 +38,7 @@ The map server endpoint is specified as ``MAP_ENDPOINT`` in ``src/main.js``. It
|
|
|
38
38
|
Package Installation
|
|
39
39
|
====================
|
|
40
40
|
|
|
41
|
-
* ``npm install @abi-software/flatmap-viewer@2.3.0-a.
|
|
41
|
+
* ``npm install @abi-software/flatmap-viewer@2.3.0-a.5``
|
|
42
42
|
|
|
43
43
|
Documentation
|
|
44
44
|
-------------
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abi-software/flatmap-viewer",
|
|
3
|
-
"version": "2.3.0-a.
|
|
3
|
+
"version": "2.3.0-a.5",
|
|
4
4
|
"description": "Flatmap viewer using Maplibre GL",
|
|
5
5
|
"repository": "https://github.com/AnatomicMaps/flatmap-viewer.git",
|
|
6
6
|
"main": "src/main.js",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"css-loader": "^6.5.1",
|
|
42
42
|
"eslint": "^8.7.0",
|
|
43
43
|
"express": "^4.17.1",
|
|
44
|
+
"file-loader": "^6.2.0",
|
|
44
45
|
"html-webpack-plugin": "^4.5.2",
|
|
45
46
|
"strip-ansi": "^7.0.1",
|
|
46
47
|
"style-loader": "^1.0.0",
|
package/src/annotation.js
CHANGED
|
@@ -31,9 +31,9 @@ import 'jspanel4/dist/jspanel.css';
|
|
|
31
31
|
//==============================================================================
|
|
32
32
|
|
|
33
33
|
const FETCH_TIMEOUT = 3000; // 3 seconds
|
|
34
|
-
const UPDATE_TIMEOUT =
|
|
34
|
+
const UPDATE_TIMEOUT = 3000; // 5 seconds
|
|
35
35
|
const LOGIN_TIMEOUT = 30000; // 30 seconds
|
|
36
|
-
const LOGOUT_TIMEOUT =
|
|
36
|
+
const LOGOUT_TIMEOUT = 3000; // 5 seconds
|
|
37
37
|
|
|
38
38
|
const STATUS_MESSAGE_TIMEOUT = 3000;
|
|
39
39
|
|
|
@@ -68,6 +68,18 @@ const ANNOTATION_FIELDS = [
|
|
|
68
68
|
|
|
69
69
|
//==============================================================================
|
|
70
70
|
|
|
71
|
+
function startSpinner(panel)
|
|
72
|
+
{
|
|
73
|
+
panel.headerlogo.innerHTML = '<span class="fa fa-spinner fa-spin ml-2"></span>';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function stopSpinner(panel)
|
|
77
|
+
{
|
|
78
|
+
panel.headerlogo.innerHTML = '';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
//==============================================================================
|
|
82
|
+
|
|
71
83
|
export class Annotator
|
|
72
84
|
{
|
|
73
85
|
constructor(flatmap)
|
|
@@ -101,61 +113,51 @@ export class Annotator
|
|
|
101
113
|
this.__setStatusMessage('', 0);
|
|
102
114
|
}
|
|
103
115
|
|
|
104
|
-
__authorise(panel
|
|
105
|
-
|
|
116
|
+
async __authorise(panel)
|
|
117
|
+
//======================
|
|
106
118
|
{
|
|
119
|
+
/*
|
|
120
|
+
const testUser = {name: 'Testing...'};
|
|
121
|
+
this.__setUser(testUser);
|
|
122
|
+
callback(testUser);
|
|
123
|
+
|
|
124
|
+
*/
|
|
107
125
|
const abortController = new AbortController();
|
|
108
|
-
const url = `${this.__flatmap._baseUrl}login`;
|
|
109
|
-
panel.headerlogo.innerHTML = '<span class="fa fa-spinner fa-spin ml-2"></span>';
|
|
110
|
-
fetch(url, {
|
|
111
|
-
headers: { "Content-Type": "application/json; charset=utf-8" },
|
|
112
|
-
signal: abortController.signal
|
|
113
|
-
}).then((response) => {
|
|
114
|
-
panel.headerlogo.innerHTML = '';
|
|
115
|
-
if (response.ok) {
|
|
116
|
-
return response.json();
|
|
117
|
-
} else {
|
|
118
|
-
callback({error: `${response.status} ${response.statusText}`});
|
|
119
|
-
}
|
|
120
|
-
}).then((response) => {
|
|
121
|
-
if ('error' in response) {
|
|
122
|
-
callback({error: response.error});
|
|
123
|
-
} else {
|
|
124
|
-
this.__setUser(response);
|
|
125
|
-
this.__authorised = true;
|
|
126
|
-
callback(response);
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
126
|
setTimeout((panel) => {
|
|
130
127
|
if (this.user === 'undefined') {
|
|
131
128
|
console.log("Aborting login...");
|
|
132
129
|
abortController.abort();
|
|
133
|
-
panel
|
|
130
|
+
stopSpinner(panel);
|
|
134
131
|
this.__setStatusMessage('Unable to login...');
|
|
135
132
|
}
|
|
136
133
|
},
|
|
137
134
|
LOGIN_TIMEOUT, panel);
|
|
138
|
-
}
|
|
139
135
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
this.__clearUser();
|
|
144
|
-
const abortController = new AbortController();
|
|
145
|
-
const url = `${this.__flatmap._baseUrl}logout`;
|
|
146
|
-
fetch(url, {
|
|
136
|
+
const url = `${this.__flatmap._baseUrl}login`;
|
|
137
|
+
startSpinner(panel);
|
|
138
|
+
const response = await fetch(url, {
|
|
147
139
|
headers: { "Content-Type": "application/json; charset=utf-8" },
|
|
148
140
|
signal: abortController.signal
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
141
|
+
});
|
|
142
|
+
stopSpinner(panel);
|
|
143
|
+
if (response.ok) {
|
|
144
|
+
const user_data = await response.json();
|
|
145
|
+
if ('error' in user_data) {
|
|
146
|
+
return Promise.resolve({error: response.error});
|
|
153
147
|
} else {
|
|
154
|
-
|
|
148
|
+
this.__setUser(user_data);
|
|
149
|
+
this.__authorised = true;
|
|
150
|
+
return user_data;
|
|
155
151
|
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
}
|
|
152
|
+
} else {
|
|
153
|
+
return Promise.resolve({error: `${response.status} ${response.statusText}`});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async __unauthorise()
|
|
158
|
+
//===================
|
|
159
|
+
{
|
|
160
|
+
const abortController = new AbortController();
|
|
159
161
|
setTimeout(() => {
|
|
160
162
|
if (this.__authorised) {
|
|
161
163
|
console.log("Aborting logout...");
|
|
@@ -164,6 +166,18 @@ export class Annotator
|
|
|
164
166
|
}
|
|
165
167
|
},
|
|
166
168
|
LOGOUT_TIMEOUT);
|
|
169
|
+
|
|
170
|
+
const url = `${this.__flatmap._baseUrl}logout`;
|
|
171
|
+
const response = fetch(url, {
|
|
172
|
+
headers: { "Content-Type": "application/json; charset=utf-8" },
|
|
173
|
+
signal: abortController.signal
|
|
174
|
+
});
|
|
175
|
+
if (response.ok) {
|
|
176
|
+
this.__authorised = false;
|
|
177
|
+
return await response.json();
|
|
178
|
+
} else {
|
|
179
|
+
return Promise.resolve({error: `${response.status} ${response.statusText}`});
|
|
180
|
+
}
|
|
167
181
|
}
|
|
168
182
|
|
|
169
183
|
__setStatusMessage(message, timeout=STATUS_MESSAGE_TIMEOUT)
|
|
@@ -223,15 +237,15 @@ export class Annotator
|
|
|
223
237
|
return html.join('\n');
|
|
224
238
|
}
|
|
225
239
|
|
|
226
|
-
__editFormHtml(
|
|
227
|
-
|
|
240
|
+
__editFormHtml(provenanceData)
|
|
241
|
+
//============================
|
|
228
242
|
{
|
|
229
243
|
const html = [];
|
|
230
244
|
html.push('<div id="flatmap-annotation-formdata">');
|
|
231
245
|
for (const field of ANNOTATION_FIELDS) {
|
|
232
246
|
html.push('<div class="flatmap-annotation-entry">');
|
|
233
247
|
html.push(` <label for="${field.key}">${field.prompt}:</label>`);
|
|
234
|
-
const value = field.update ?
|
|
248
|
+
const value = field.update ? provenanceData[field.key] || '' : '';
|
|
235
249
|
if (field.kind === 'textbox') {
|
|
236
250
|
html.push(` <textarea rows="5" cols="40" id="${field.key}" name="${field.key}">${value.trim()}</textarea>`)
|
|
237
251
|
} else if (!('kind' in field) || field.kind !== 'list') {
|
|
@@ -251,30 +265,51 @@ export class Annotator
|
|
|
251
265
|
return html.join('\n');
|
|
252
266
|
}
|
|
253
267
|
|
|
254
|
-
|
|
268
|
+
__provenanceData(annotations)
|
|
269
|
+
//===========================
|
|
270
|
+
{
|
|
271
|
+
const provenanceData = {};
|
|
272
|
+
for (const annotation of annotations) { // In order of most recent to oldest
|
|
273
|
+
if (annotation['rdf:type'] === 'prov:Entity') {
|
|
274
|
+
for (const field of ANNOTATION_FIELDS) {
|
|
275
|
+
if (field.update) {
|
|
276
|
+
const value = annotation[field.key];
|
|
277
|
+
if (value !== undefined && !(field.key in provenanceData)) {
|
|
278
|
+
provenanceData[field.key] = value;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return provenanceData;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
__changedAnnotation(provenanceData)
|
|
255
288
|
//=================================
|
|
256
289
|
{
|
|
257
290
|
const newProperties = {};
|
|
258
291
|
let propertiesChanged = false;
|
|
259
292
|
for (const field of ANNOTATION_FIELDS) {
|
|
260
|
-
const lastValue = field.update ?
|
|
293
|
+
const lastValue = field.update ? provenanceData[field.key] || '' : '';
|
|
261
294
|
if (!('kind' in field) || field.kind !== 'list') {
|
|
262
295
|
const inputField = document.getElementById(field.key);
|
|
263
|
-
|
|
264
|
-
if (
|
|
296
|
+
const newValue = inputField.value.trim();
|
|
297
|
+
if (newValue !== lastValue.trim()) {
|
|
298
|
+
newProperties[field.key] = newValue;
|
|
265
299
|
propertiesChanged = true;
|
|
266
300
|
}
|
|
267
301
|
} else { // field.kind === 'list'
|
|
268
|
-
|
|
269
|
-
const changedList = false;
|
|
302
|
+
const listValues = [];
|
|
270
303
|
for (let n = 1; n <= field.size; n++) {
|
|
271
|
-
const lastListValue = (n <= lastValue.length) ? lastValue[n-1].trim() : '';
|
|
272
304
|
const inputField = document.getElementById(`${field.key}_${n}`);
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
305
|
+
listValues.push(inputField.value.trim());
|
|
306
|
+
}
|
|
307
|
+
const oldValues = lastValue.map(v => v.trim()).filter(v => (v !== '')).sort();
|
|
308
|
+
const newValues = listValues.map(v => v.trim()).filter(v => (v !== '')).sort();
|
|
309
|
+
if (oldValues.length !== newValues.length
|
|
310
|
+
|| oldValues.filter(v => !newValues.includes(v)).length > 0) {
|
|
311
|
+
newProperties[field.key] = newValues;
|
|
312
|
+
propertiesChanged = true;
|
|
278
313
|
}
|
|
279
314
|
}
|
|
280
315
|
}
|
|
@@ -284,32 +319,38 @@ export class Annotator
|
|
|
284
319
|
}
|
|
285
320
|
}
|
|
286
321
|
|
|
287
|
-
__updateRemoteAnnotation(
|
|
288
|
-
|
|
322
|
+
async __updateRemoteAnnotation(panel, annotation)
|
|
323
|
+
//===============================================
|
|
289
324
|
{
|
|
290
325
|
const abortController = new AbortController();
|
|
326
|
+
|
|
327
|
+
setTimeout((panel) => {
|
|
328
|
+
if (panel.status !== 'closed') {
|
|
329
|
+
console.log("Aborting remote update...");
|
|
330
|
+
abortController.abort();
|
|
331
|
+
stopSpinner(panel);
|
|
332
|
+
this.__setStatusMessage('Cannot update annotation...');
|
|
333
|
+
}
|
|
334
|
+
}, UPDATE_TIMEOUT, panel);
|
|
335
|
+
|
|
291
336
|
const url = this.__flatmap.addBaseUrl_(`/annotations/${this.__currentFeatureId}`);
|
|
292
|
-
fetch(url, {
|
|
337
|
+
const response = await fetch(url, {
|
|
293
338
|
headers: { "Content-Type": "application/json; charset=utf-8" },
|
|
294
339
|
method: 'POST',
|
|
295
340
|
body: JSON.stringify(annotation),
|
|
296
341
|
signal: abortController.signal
|
|
297
|
-
}).then((response) => {
|
|
298
|
-
if (response.ok) {
|
|
299
|
-
return response.json();
|
|
300
|
-
} else {
|
|
301
|
-
callback({error: `${response.status} ${response.statusText}`});
|
|
302
|
-
}
|
|
303
|
-
}).then((response) => {
|
|
304
|
-
callback(response);
|
|
305
342
|
});
|
|
306
|
-
|
|
343
|
+
if (response.ok) {
|
|
344
|
+
return await response.json();
|
|
345
|
+
} else {
|
|
346
|
+
return Promise.resolve({error: `${response.status} ${response.statusText}`});
|
|
347
|
+
}
|
|
307
348
|
}
|
|
308
349
|
|
|
309
|
-
__saveAnnotation(panel,
|
|
310
|
-
|
|
350
|
+
async __saveAnnotation(panel, provenanceData)
|
|
351
|
+
//===========================================
|
|
311
352
|
{
|
|
312
|
-
const changedProperties = this.__changedAnnotation(
|
|
353
|
+
const changedProperties = this.__changedAnnotation(provenanceData);
|
|
313
354
|
if (this.__currentFeatureId !== undefined && changedProperties.changed) {
|
|
314
355
|
const annotation = {
|
|
315
356
|
...changedProperties.properties,
|
|
@@ -317,26 +358,15 @@ export class Annotator
|
|
|
317
358
|
'dct:subject': `flatmaps:${this.__flatmap.uuid}/${this.__currentFeatureId}`,
|
|
318
359
|
'dct:creator': this.user
|
|
319
360
|
}
|
|
320
|
-
panel
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
});
|
|
330
|
-
setTimeout((panel) => {
|
|
331
|
-
if (panel.status !== 'closed') {
|
|
332
|
-
console.log("Aborting remote update...");
|
|
333
|
-
remoteUpdate.abort();
|
|
334
|
-
panel.headerlogo.innerHTML = '';
|
|
335
|
-
this.__setStatusMessage('Cannot update annotation...');
|
|
336
|
-
}
|
|
337
|
-
}, UPDATE_TIMEOUT, panel);
|
|
361
|
+
startSpinner(panel);
|
|
362
|
+
const response = await this.__updateRemoteAnnotation(panel, annotation);
|
|
363
|
+
stopSpinner(panel);
|
|
364
|
+
if ('error' in response) {
|
|
365
|
+
this.__setStatusMessage(response.error);
|
|
366
|
+
} else {
|
|
367
|
+
panel.close();
|
|
368
|
+
}
|
|
338
369
|
} else {
|
|
339
|
-
this.__
|
|
340
370
|
this.__setStatusMessage('No changes to save...');
|
|
341
371
|
}
|
|
342
372
|
}
|
|
@@ -345,9 +375,9 @@ export class Annotator
|
|
|
345
375
|
//====================================
|
|
346
376
|
{
|
|
347
377
|
this.__haveAnnotation = true;
|
|
378
|
+
const provenanceData = this.__provenanceData(response);
|
|
348
379
|
this.__existingAnnotation.innerHTML = this.__annotationHtml(response);
|
|
349
|
-
|
|
350
|
-
this.__annotationForm.innerHTML = this.__editFormHtml(lastAnnotation);
|
|
380
|
+
this.__annotationForm.innerHTML = this.__editFormHtml(provenanceData);
|
|
351
381
|
|
|
352
382
|
// Lock focus to focusable elements within the panel
|
|
353
383
|
const inputElements = panel.content.querySelectorAll('input, textarea, button');
|
|
@@ -370,16 +400,52 @@ export class Annotator
|
|
|
370
400
|
}
|
|
371
401
|
} else if (e.key === 'Enter') {
|
|
372
402
|
if (e.target === saveButton) {
|
|
373
|
-
this.__saveAnnotation(panel,
|
|
403
|
+
this.__saveAnnotation(panel, provenanceData);
|
|
374
404
|
}
|
|
375
405
|
}
|
|
376
406
|
}.bind(this));
|
|
377
407
|
|
|
378
408
|
saveButton.addEventListener('mousedown', function (e) {
|
|
379
|
-
this.__saveAnnotation(panel,
|
|
409
|
+
this.__saveAnnotation(panel, provenanceData);
|
|
380
410
|
}.bind(this));
|
|
381
411
|
}
|
|
382
412
|
|
|
413
|
+
__panelCallback(panel)
|
|
414
|
+
//====================
|
|
415
|
+
{
|
|
416
|
+
this.__annotationForm = document.getElementById('flatmap-annotation-form');
|
|
417
|
+
// Data entry only once authorised
|
|
418
|
+
this.__annotationForm.hidden = true;
|
|
419
|
+
|
|
420
|
+
// Populate once we have content from server
|
|
421
|
+
this.__existingAnnotation = document.getElementById('flatmap-annotation-existing');
|
|
422
|
+
this.__statusMessage = document.getElementById('flatmap-annotation-status');
|
|
423
|
+
|
|
424
|
+
this.__authoriseLock = document.getElementById('flatmap-annotation-lock');
|
|
425
|
+
this.__authoriseLock.addEventListener('click', (e) => {
|
|
426
|
+
const lockClasses = this.__authoriseLock.classList;
|
|
427
|
+
if (lockClasses.contains('fa-lock')) {
|
|
428
|
+
this.__authorise(panel).then((response) => {
|
|
429
|
+
if ('error' in response) {
|
|
430
|
+
this.__setStatusMessage(response.error);
|
|
431
|
+
} else {
|
|
432
|
+
this.__annotationForm.hidden = false;
|
|
433
|
+
this.__firstInputField.focus();
|
|
434
|
+
lockClasses.remove('fa-lock');
|
|
435
|
+
lockClasses.add('fa-unlock');
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
} else {
|
|
439
|
+
this.__unauthorise().then((response) => {
|
|
440
|
+
console.log(`Annotator logout: ${response}`);
|
|
441
|
+
});
|
|
442
|
+
this.__annotationForm.hidden = true;
|
|
443
|
+
lockClasses.remove('fa-unlock');
|
|
444
|
+
lockClasses.add('fa-lock');
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
383
449
|
annotate(feature, closedCallback)
|
|
384
450
|
//===============================
|
|
385
451
|
{
|
|
@@ -399,8 +465,8 @@ export class Annotator
|
|
|
399
465
|
panelContent.push(' <div id="flatmap-annotation-existing"></div>');
|
|
400
466
|
panelContent.push('</div>');
|
|
401
467
|
|
|
402
|
-
const annotator = this; // To use in panel code
|
|
403
|
-
const flatmap = this.__flatmap; // To use in panel code
|
|
468
|
+
const annotator = this; // To use in panel creation code
|
|
469
|
+
const flatmap = this.__flatmap; // To use in panel creation code
|
|
404
470
|
const contentFetchAbort = new AbortController();
|
|
405
471
|
this.__panel = jsPanel.create({
|
|
406
472
|
theme: 'light',
|
|
@@ -433,12 +499,12 @@ export class Annotator
|
|
|
433
499
|
},
|
|
434
500
|
bodyMethod: 'json',
|
|
435
501
|
beforeSend: (fetchConfig, panel) => {
|
|
436
|
-
panel
|
|
502
|
+
startSpinner(panel);
|
|
437
503
|
setTimeout((panel) => {
|
|
438
504
|
if (!annotator.__haveAnnotation) {
|
|
439
505
|
console.log("Aborting content fetch...");
|
|
440
506
|
contentFetchAbort.abort();
|
|
441
|
-
panel
|
|
507
|
+
stopSpinner(panel);
|
|
442
508
|
annotator.__setStatusMessage('Cannot fetch annotation...');
|
|
443
509
|
annotator.__authoriseLock.className = '';
|
|
444
510
|
}
|
|
@@ -446,45 +512,14 @@ export class Annotator
|
|
|
446
512
|
},
|
|
447
513
|
done: (response, panel) => {
|
|
448
514
|
annotator.__finishPanelContent(panel, response);
|
|
449
|
-
panel
|
|
515
|
+
stopSpinner(panel);
|
|
450
516
|
}
|
|
451
517
|
},
|
|
452
|
-
callback: (
|
|
453
|
-
annotator.__annotationForm = document.getElementById('flatmap-annotation-form');
|
|
454
|
-
// Data entry only once authorised
|
|
455
|
-
annotator.__annotationForm.hidden = true;
|
|
456
|
-
|
|
457
|
-
// Populate once we have content from server
|
|
458
|
-
annotator.__existingAnnotation = document.getElementById('flatmap-annotation-existing');
|
|
459
|
-
annotator.__statusMessage = document.getElementById('flatmap-annotation-status');
|
|
460
|
-
|
|
461
|
-
annotator.__authoriseLock = document.getElementById('flatmap-annotation-lock');
|
|
462
|
-
annotator.__authoriseLock.addEventListener('click', (e) => {
|
|
463
|
-
const lockClasses = annotator.__authoriseLock.classList;
|
|
464
|
-
if (lockClasses.contains('fa-lock')) {
|
|
465
|
-
annotator.__authorise(panel, (response) => {
|
|
466
|
-
if ('error' in response) {
|
|
467
|
-
annotator.__setStatusMessage(response.error);
|
|
468
|
-
} else {
|
|
469
|
-
annotator.__annotationForm.hidden = false;
|
|
470
|
-
annotator.__firstInputField.focus();
|
|
471
|
-
lockClasses.remove('fa-lock');
|
|
472
|
-
lockClasses.add('fa-unlock');
|
|
473
|
-
}
|
|
474
|
-
});
|
|
475
|
-
} else {
|
|
476
|
-
annotator.__unauthorise();
|
|
477
|
-
annotator.__annotationForm.hidden = true;
|
|
478
|
-
lockClasses.remove('fa-unlock');
|
|
479
|
-
lockClasses.add('fa-lock');
|
|
480
|
-
}
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
// should we warn if unsaved changes when closing??
|
|
484
|
-
document.addEventListener('jspanelclosed', closedCallback, false);
|
|
485
|
-
}
|
|
518
|
+
callback: annotator.__panelCallback.bind(annotator)
|
|
486
519
|
});
|
|
487
520
|
|
|
521
|
+
// should we warn if unsaved changes when closing??
|
|
522
|
+
document.addEventListener('jspanelclosed', closedCallback, false);
|
|
488
523
|
}
|
|
489
524
|
|
|
490
525
|
}
|