@abi-software/flatmap-viewer 2.3.0-b.1 → 2.3.1-b.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/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-b.1``
41
+ * ``npm install @abi-software/flatmap-viewer@2.3.1-b.1``
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-b.1",
3
+ "version": "2.3.1-b.1",
4
4
  "description": "Flatmap viewer using Maplibre GL",
5
5
  "repository": "https://github.com/AnatomicMaps/flatmap-viewer.git",
6
6
  "main": "src/main.js",
@@ -38,14 +38,12 @@
38
38
  "browser-sync": "^2.26.7",
39
39
  "bs-fullscreen-message": "^1.1.0",
40
40
  "clean-webpack-plugin": "^3.0.0",
41
- "css-loader": "^6.5.1",
41
+ "css-loader": "^6.7.3",
42
42
  "eslint": "^8.7.0",
43
43
  "express": "^4.17.1",
44
- "file-loader": "^6.2.0",
45
44
  "html-webpack-plugin": "^4.5.2",
46
45
  "strip-ansi": "^7.0.1",
47
- "style-loader": "^1.0.0",
48
- "url-loader": "^4.1.0",
46
+ "style-loader": "^3.3.2",
49
47
  "webpack": "^5.16.0",
50
48
  "webpack-cli": "^4.4.0",
51
49
  "webpack-dev-middleware": "^4.1.0",
package/src/annotation.js CHANGED
@@ -82,9 +82,10 @@ function stopSpinner(panel)
82
82
 
83
83
  export class Annotator
84
84
  {
85
- constructor(flatmap)
85
+ constructor(flatmap, ui)
86
86
  {
87
87
  this.__flatmap = flatmap;
88
+ this.__ui = ui;
88
89
  this.__haveAnnotation = false;
89
90
  this.__user = undefined;
90
91
  this.__savedStatusMessage = '';
@@ -116,12 +117,6 @@ export class Annotator
116
117
  async __authorise(panel)
117
118
  //======================
118
119
  {
119
- /*
120
- const testUser = {name: 'Testing...'};
121
- this.__setUser(testUser);
122
- this.__authorised = true;
123
- return Promise.resolve(testUser);
124
- */
125
120
  const abortController = new AbortController();
126
121
  setTimeout((panel) => {
127
122
  if (this.user === 'undefined') {
@@ -450,20 +445,134 @@ export class Annotator
450
445
  });
451
446
  }
452
447
 
453
- annotate(feature, closedCallback)
454
- //===============================
448
+ __chooseFeatureProperties(features, callback)
449
+ //===========================================
455
450
  {
456
- this.__currentFeatureId = feature.properties['id']
451
+ this.__ui.selectFeature(features[0].id);
452
+
453
+ // Feature chooser is only for multiple selections
454
+ if (features.length === 1
455
+ || features[0].properties['cd-class'] !== 'celldl:Connection'
456
+ || (features.length === 2
457
+ && features[1].properties['cd-class'] !== 'celldl:Connection')) {
458
+ callback(features[0].properties);
459
+ return;
460
+ }
461
+ const featureList = [];
462
+ const featureProperties = new Map();
463
+ const featureSeen = new Set();
464
+ let selected = 'selected'; // Select the first entry
465
+ for (const feature of features) {
466
+ if (feature.properties['cd-class'] !== 'celldl:Connection'
467
+ || feature.properties['id'] == undefined
468
+ || featureSeen.has(feature.properties['id'])) {
469
+ continue;
470
+ }
471
+ const mapFeature = this.__ui.mapFeature(feature.id);
472
+ const annotated = (mapFeature !== undefined)
473
+ ? this.__ui._map.getFeatureState(mapFeature)['annotated']
474
+ : false;
475
+ let label = '';
476
+ if (feature.properties.models) {
477
+ label = ` -- ${feature.properties.label.split('\n')[0]} (${feature.properties.models})`;
478
+ }
479
+ featureList.push(`<option value="${feature.id}" ${selected}>${annotated ? '*' : '&nbsp;'} ${feature.properties.id} -- ${feature.properties.kind}${label}</option>`);
480
+ featureProperties.set(+feature.id, feature.properties);
481
+ featureSeen.add(feature.properties['id']);
482
+ selected = '';
483
+ }
484
+ if (featureList.length == 0) {
485
+ callback(undefined);
486
+ return;
487
+ } else if (featureList.length == 1) {
488
+ callback(featureProperties.values().next().value);
489
+ return;
490
+ }
491
+ const panelContent = `
492
+ <div id="annotation-feature-selection">
493
+ <div>
494
+ <label for="annotation-feature-selector">Select feature:</label>
495
+ <select id="annotation-feature-selector" size="${Math.min(featureList.length, 7)}">
496
+ ${featureList.join('\n')}
497
+ </select>
498
+ </div>
499
+ <div id="annotation-feature-buttons">
500
+ <input id="annotation-feature-cancel" type="button" value="Cancel"/>
501
+ <input id="annotation-feature-annotate" type="button" value="Annotate"/>
502
+ </div>
503
+ </div>`;
504
+ this.__panel = jsPanel.create({
505
+ theme: 'light',
506
+ border: '2px solid #080',
507
+ borderRadius: '.5rem',
508
+ panelSize: 'auto auto',
509
+ position: 'left-top',
510
+ content: panelContent,
511
+ data: features[0].properties,
512
+ closeOnEscape: true,
513
+ closeOnBackdrop: false,
514
+ headerTitle: 'Select feature to annotate',
515
+ headerControls: 'closeonly xs',
516
+ callback: ((panel) => {
517
+ const selector = document.getElementById('annotation-feature-selector');
518
+ selector.onchange = (e) => {
519
+ if (e.target.value !== '') {
520
+ this.__ui.unselectFeatures();
521
+ this.__ui.selectFeature(e.target.value);
522
+ this.__panel.options.data = featureProperties.get(+e.target.value);
523
+ }
524
+ };
525
+ selector.ondblclick = (e) => {
526
+ if (e.target.value !== '') {
527
+ const properties = this.__panel.options.data;
528
+ this.__panel.close();
529
+ callback(properties);
530
+ }
531
+ }
532
+ selector.focus();
533
+ document.getElementById('annotation-feature-cancel')
534
+ .onclick = (e) => {
535
+ this.__panel.close();
536
+ callback(undefined);
537
+ };
538
+ document.getElementById('annotation-feature-annotate')
539
+ .onclick = (e) => {
540
+ const properties = this.__panel.options.data;
541
+ this.__panel.close();
542
+ callback(properties);
543
+ };
544
+ }).bind(this)
545
+ });
546
+ document.addEventListener('jspanelcloseduser', (e) => { callback(undefined) }, false);
547
+ }
548
+
549
+ annotate(features, closedCallback)
550
+ //================================
551
+ {
552
+ // provide a list of features so dialog needs to first provide selection list
553
+ // and highlight current one as user scrolls...
457
554
 
555
+ this.__chooseFeatureProperties(features, (featureProperties) => {
556
+ if (featureProperties) {
557
+ this.__annotateFeature(featureProperties, closedCallback);
558
+ } else {
559
+ closedCallback();
560
+ }
561
+ });
562
+ }
563
+
564
+ __annotateFeature(featureProperties, callback)
565
+ //============================================
566
+ {
567
+ this.__currentFeatureId = featureProperties['id'];
458
568
  if (this.__currentFeatureId === undefined) {
459
- closedCallback();
569
+ callback();
460
570
  return;
461
571
  }
462
-
463
572
  const panelContent = [];
464
573
  panelContent.push('<div id="flatmap-annotation-panel">');
465
574
  panelContent.push(' <div id="flatmap-annotation-feature">');
466
- panelContent.push(...this.__featureHtml(feature.properties));
575
+ panelContent.push(...this.__featureHtml(featureProperties));
467
576
  panelContent.push(' </div>');
468
577
  panelContent.push(' <form id="flatmap-annotation-form"></form>');
469
578
  panelContent.push(' <div id="flatmap-annotation-existing"></div>');
@@ -523,7 +632,7 @@ export class Annotator
523
632
  });
524
633
 
525
634
  // should we warn if unsaved changes when closing??
526
- document.addEventListener('jspanelclosed', closedCallback, false);
635
+ document.addEventListener('jspanelclosed', callback, false);
527
636
  }
528
637
 
529
638
  async annotated_features()
@@ -2,7 +2,7 @@
2
2
 
3
3
  Flatmap viewer and annotation tool
4
4
 
5
- Copyright (c) 2019 David Brooks
5
+ Copyright (c) 2019 - 2023 David Brooks
6
6
 
7
7
  Licensed under the Apache License, Version 2.0 (the "License");
8
8
  you may not use this file except in compliance with the License.
@@ -18,13 +18,6 @@ limitations under the License.
18
18
 
19
19
  ******************************************************************************/
20
20
 
21
- 'use strict';
22
-
23
- //==============================================================================
24
-
25
-
26
- //==============================================================================
27
-
28
21
  // Make sure colour string is in `#rrggbb` form.
29
22
  // Based on https://stackoverflow.com/a/47355187
30
23
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  Flatmap viewer and annotation tool
4
4
 
5
- Copyright (c) 2019 David Brooks
5
+ Copyright (c) 2019 - 2023 David Brooks
6
6
 
7
7
  Licensed under the Apache License, Version 2.0 (the "License");
8
8
  you may not use this file except in compliance with the License.
@@ -18,11 +18,7 @@ limitations under the License.
18
18
 
19
19
  ******************************************************************************/
20
20
 
21
- 'use strict';
22
-
23
- //==============================================================================
24
-
25
- import { indexedProperties } from './search.js';
21
+ import { indexedProperties } from '../search.js';
26
22
 
27
23
  //==============================================================================
28
24
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  Flatmap viewer and annotation tool
4
4
 
5
- Copyright (c) 2019 David Brooks
5
+ Copyright (c) 2019 - 2023 David Brooks
6
6
 
7
7
  Licensed under the Apache License, Version 2.0 (the "License");
8
8
  you may not use this file except in compliance with the License.
@@ -45,10 +45,6 @@ limitations under the License.
45
45
 
46
46
  //==============================================================================
47
47
 
48
- 'use strict';
49
-
50
- //==============================================================================
51
-
52
48
  import maplibre from 'maplibre-gl';
53
49
 
54
50
  //==============================================================================
@@ -0,0 +1,113 @@
1
+ /******************************************************************************
2
+
3
+ Flatmap viewer and annotation tool
4
+
5
+ Copyright (c) 2019 - 2023 David Brooks
6
+
7
+ Licensed under the Apache License, Version 2.0 (the "License");
8
+ you may not use this file except in compliance with the License.
9
+ You may obtain a copy of the License at
10
+
11
+ http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ Unless required by applicable law or agreed to in writing, software
14
+ distributed under the License is distributed on an "AS IS" BASIS,
15
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ See the License for the specific language governing permissions and
17
+ limitations under the License.
18
+
19
+ **/
20
+ //==============================================================================
21
+
22
+ export class SearchControl
23
+ {
24
+ constructor(flatmap)
25
+ {
26
+ this.__flatmap = flatmap;
27
+ }
28
+
29
+ onAdd(map)
30
+ //========
31
+ {
32
+ this._map = map;
33
+ this._container = document.createElement('div');
34
+ this._container.className = 'maplibregl-ctrl search-control';
35
+
36
+ this._input = document.createElement('input');
37
+ this._input.id = 'search-control-input';
38
+ this._input.setAttribute('type', 'search');
39
+ this._input.setAttribute('visible', 'false');
40
+ this._input.setAttribute('placeholder', 'Search...');
41
+
42
+ this._button = document.createElement('button');
43
+ this._button.id = 'search-control-button';
44
+ this._button.className = 'control-button';
45
+ this._button.title = 'Search flatmap';
46
+ this._button.setAttribute('type', 'button');
47
+ this._button.setAttribute('aria-label', 'Search flatmap');
48
+ // https://iconmonstr.com/magnifier-6-svg/
49
+ this._button.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" id="search-control-icon" viewBox="0 0 24 24">
50
+ <path d="M21.172 24l-7.387-7.387c-1.388.874-3.024 1.387-4.785 1.387-4.971 0-9-4.029-9-9s4.029-9 9-9 9 4.029 9 9c0 1.761-.514 3.398-1.387 4.785l7.387 7.387-2.828 2.828zm-12.172-8c3.859 0 7-3.14 7-7s-3.141-7-7-7-7 3.14-7 7 3.141 7 7 7z"/>
51
+ </svg>`;
52
+ this._container.appendChild(this._button);
53
+
54
+ this._container.onclick = this.onClick_.bind(this);
55
+ return this._container;
56
+ }
57
+
58
+ getDefaultPosition()
59
+ //==================
60
+ {
61
+ return 'top-right';
62
+ }
63
+
64
+ onRemove()
65
+ //========
66
+ {
67
+ this._container.parentNode.removeChild(this._container);
68
+ this._map = undefined;
69
+ }
70
+
71
+ searchMap_(search=true)
72
+ //=====================
73
+ {
74
+ this._input = this._container.removeChild(this._input);
75
+ this._input.setAttribute('visible', 'false');
76
+ const text = this._input.value;
77
+ if (search && text !== '') {
78
+ const results = this.__flatmap.search(text);
79
+ this.__flatmap.showSearchResults(results);
80
+ }
81
+ }
82
+
83
+ onKeyDown_(e)
84
+ //===========
85
+ {
86
+ if (e.key === 'Enter') {
87
+ this.searchMap_();
88
+ } else if (e.key === 'Escape') {
89
+ this.searchMap_(false);
90
+ }
91
+ }
92
+
93
+ onClick_(e)
94
+ //=========
95
+ {
96
+ const targetId = ('rangeTarget' in e) ? e.rangeTarget.id : e.target.id; // FF has rangeTarget
97
+ if (['search-control-button', 'search-control-icon'].includes(targetId)) {
98
+ if (this._input.getAttribute('visible') === 'false') {
99
+ this._container.appendChild(this._input);
100
+ this._container.appendChild(this._button);
101
+ this._input.setAttribute('visible', 'true');
102
+ this._input.onkeydown = this.onKeyDown_.bind(this);
103
+ this._input.value = '';
104
+ this.__flatmap.clearSearchResults();
105
+ this._input.focus();
106
+ } else {
107
+ this.searchMap_();
108
+ }
109
+ }
110
+ }
111
+ }
112
+
113
+ //==============================================================================
@@ -0,0 +1,66 @@
1
+ /******************************************************************************
2
+
3
+ Flatmap viewer and annotation tool
4
+
5
+ Copyright (c) 2019 - 2023 David Brooks
6
+
7
+ Licensed under the Apache License, Version 2.0 (the "License");
8
+ you may not use this file except in compliance with the License.
9
+ You may obtain a copy of the License at
10
+
11
+ http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ Unless required by applicable law or agreed to in writing, software
14
+ distributed under the License is distributed on an "AS IS" BASIS,
15
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ See the License for the specific language governing permissions and
17
+ limitations under the License.
18
+
19
+ **/
20
+ //==============================================================================
21
+
22
+ import { Control } from './controls';
23
+
24
+ //==============================================================================
25
+
26
+ export class SystemsControl extends Control
27
+ {
28
+ constructor(flatmap, systems)
29
+ {
30
+ super(flatmap, 'system', 'systems');
31
+ this.__systems = systems;
32
+ }
33
+
34
+ __innerLinesHTML()
35
+ //================
36
+ {
37
+ const html = [];
38
+ for (const system of this.__systems) {
39
+ html.push(`<label for="${this.__prefix}${system.id}" style="background: ${system.colour};">${system.name}</label><input id="${this.__prefix}${system.id}" type="checkbox" checked/>`);
40
+ }
41
+ return html;
42
+ }
43
+
44
+ __enableAll(enable)
45
+ //=================
46
+ {
47
+ for (const system of this.__systems) {
48
+ const checkbox = document.getElementById(`${this.__prefix}${system.id}`);
49
+ if (checkbox) {
50
+ checkbox.checked = enable;
51
+ this.__flatmap.enableSystem(system.name, enable);
52
+ }
53
+ }
54
+ }
55
+
56
+ __enableControl(id, enable)
57
+ //=========================
58
+ {
59
+ for (const system of this.__systems) {
60
+ if (id === system.id) {
61
+ this.__flatmap.enableSystem(system.name, enable);
62
+ }
63
+ }
64
+ }
65
+
66
+ }
@@ -34,11 +34,12 @@ import '../static/css/flatmap-viewer.css';
34
34
  //==============================================================================
35
35
 
36
36
  import {MapServer} from './mapserver.js';
37
- import {MinimapControl} from './minimap.js';
38
- import {NavigationControl} from './controls.js';
39
37
  import {SearchIndex} from './search.js';
40
38
  import {UserInteractions} from './interactions.js';
41
39
 
40
+ import {MinimapControl} from './controls/minimap.js';
41
+ import {NavigationControl} from './controls/controls.js';
42
+
42
43
  import * as images from './images.js';
43
44
  import * as utils from './utils.js';
44
45
 
@@ -259,20 +260,6 @@ class FlatMap
259
260
  this._map.zoomOut();
260
261
  }
261
262
 
262
- /**
263
- * Toggle the visibility of paths on the map.zoomIn
264
- *
265
- * * If some paths are hidden then all paths are made visible.
266
- * * If all paths are visible then they are all hidden.
267
- */
268
- togglePaths()
269
- //===========
270
- {
271
- if (this._userInteractions !== null) {
272
- this._userInteractions.togglePaths();
273
- }
274
- }
275
-
276
263
  /**
277
264
  * @returns {Array.<{type: string, label: string, colour: string}>} an array of objects giving the path types
278
265
  * present in the map along with their
@@ -1301,6 +1288,7 @@ export class MapManager
1301
1288
  * @arg options.showPosition {boolean} Show ``position`` of tooltip.
1302
1289
  * @arg options.standalone {boolean} Viewer is running ``standalone``, as opposed to integrated into
1303
1290
  * another application so show a number of controls. Defaults to ``false``.
1291
+ * @arg options.annotator {boolean} Allow interactive annotation of features and paths.
1304
1292
  * @example
1305
1293
  * const humanMap1 = mapManager.loadMap('humanV1', 'div-1');
1306
1294
  *