@abi-software/flatmap-viewer 2.3.0-a.6 → 2.3.0-b.2

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-a.6``
41
+ * ``npm install @abi-software/flatmap-viewer@2.3.0-b.2``
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.6",
3
+ "version": "2.3.0-b.2",
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
@@ -116,12 +116,6 @@ export class Annotator
116
116
  async __authorise(panel)
117
117
  //======================
118
118
  {
119
- /*
120
- const testUser = {name: 'Testing...'};
121
- this.__setUser(testUser);
122
- callback(testUser);
123
-
124
- */
125
119
  const abortController = new AbortController();
126
120
  setTimeout((panel) => {
127
121
  if (this.user === 'undefined') {
@@ -147,7 +141,7 @@ export class Annotator
147
141
  } else {
148
142
  this.__setUser(user_data);
149
143
  this.__authorised = true;
150
- return user_data;
144
+ return Promise.resolve(user_data);
151
145
  }
152
146
  } else {
153
147
  return Promise.resolve({error: `${response.status} ${response.statusText}`});
@@ -174,7 +168,7 @@ export class Annotator
174
168
  });
175
169
  if (response.ok) {
176
170
  this.__authorised = false;
177
- return await response.json();
171
+ return response.json();
178
172
  } else {
179
173
  return Promise.resolve({error: `${response.status} ${response.statusText}`});
180
174
  }
@@ -307,8 +301,8 @@ export class Annotator
307
301
  listValues.push(inputField.value.trim());
308
302
  }
309
303
  const lastValue = field.update ? provenanceData[field.key] || [] : [];
310
- const oldValues = lastValue.map(v => v.trim()).filter(v => (v !== '')).sort();
311
- const newValues = listValues.map(v => v.trim()).filter(v => (v !== '')).sort();
304
+ const oldValues = lastValue.map(v => v.trim()).filter(v => (v !== '')).sort(Intl.Collator().compare);
305
+ const newValues = listValues.map(v => v.trim()).filter(v => (v !== '')).sort(Intl.Collator().compare);
312
306
  if (oldValues.length !== newValues.length
313
307
  || oldValues.filter(v => !newValues.includes(v)).length > 0) {
314
308
  newProperties[field.key] = newValues;
@@ -336,7 +330,7 @@ export class Annotator
336
330
  }
337
331
  }, UPDATE_TIMEOUT, panel);
338
332
 
339
- const url = this.__flatmap.addBaseUrl_(`/annotations/${this.__currentFeatureId}`);
333
+ const url = this.__flatmap.makeServerUrl(this.__currentFeatureId, 'annotator/');
340
334
  const response = await fetch(url, {
341
335
  headers: { "Content-Type": "application/json; charset=utf-8" },
342
336
  method: 'POST',
@@ -344,7 +338,7 @@ export class Annotator
344
338
  signal: abortController.signal
345
339
  });
346
340
  if (response.ok) {
347
- return await response.json();
341
+ return response.json();
348
342
  } else {
349
343
  return Promise.resolve({error: `${response.status} ${response.statusText}`});
350
344
  }
@@ -367,6 +361,7 @@ export class Annotator
367
361
  if ('error' in response) {
368
362
  this.__setStatusMessage(response.error);
369
363
  } else {
364
+ this.__flatmap.setFeatureAnnotated(this.__currentFeatureId);
370
365
  panel.close();
371
366
  }
372
367
  } else {
@@ -490,7 +485,7 @@ export class Annotator
490
485
  '<span id="flatmap-annotation-lock" class="jsPanel-ftr-btn fa fa-lock"></span>',
491
486
  ],
492
487
  contentFetch: {
493
- resource: flatmap.addBaseUrl_(`/annotations/${this.__currentFeatureId}`),
488
+ resource: flatmap.makeServerUrl(this.__currentFeatureId, 'annotator/'),
494
489
  fetchInit: {
495
490
  method: 'GET',
496
491
  mode: 'cors',
@@ -525,6 +520,24 @@ export class Annotator
525
520
  document.addEventListener('jspanelclosed', closedCallback, false);
526
521
  }
527
522
 
523
+ async annotated_features()
524
+ //========================
525
+ {
526
+ const url = this.__flatmap.makeServerUrl('', 'annotator/');
527
+ const response = await fetch(url, {
528
+ headers: {
529
+ "Accept": "application/json; charset=utf-8",
530
+ "Cache-Control": "no-store"
531
+ }
532
+ });
533
+ if (response.ok) {
534
+ return response.json();
535
+ } else {
536
+ console.error(`Annotated features: ${response.status} ${response.statusText}`);
537
+ return Promise.resolve([]);
538
+ }
539
+ }
540
+
528
541
  }
529
542
 
530
543
  //==============================================================================
@@ -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
 
@@ -85,12 +86,12 @@ class FlatMap
85
86
 
86
87
  for (const [id, source] of Object.entries(mapDescription.style.sources)) {
87
88
  if (source.url) {
88
- source.url = this.addBaseUrl_(source.url);
89
+ source.url = this.makeServerUrl(source.url);
89
90
  }
90
91
  if (source.tiles) {
91
92
  const tiles = [];
92
93
  for (const tileUrl of source.tiles) {
93
- tiles.push(this.addBaseUrl_(tileUrl));
94
+ tiles.push(this.makeServerUrl(tileUrl));
94
95
  }
95
96
  source.tiles = tiles;
96
97
  }
@@ -361,21 +362,23 @@ class FlatMap
361
362
  {
362
363
  if (!this._map.hasImage(id)) {
363
364
  const image = await (path.startsWith('data:image') ? this.loadEncodedImage_(path)
364
- : this.loadImage_(path.startsWith('/') ? this.addBaseUrl_(path)
365
+ : this.loadImage_(path.startsWith('/') ? this.makeServerUrl(path)
365
366
  : new URL(path, baseUrl)));
366
367
  this._map.addImage(id, image, options);
367
368
  }
368
369
  }
369
370
 
370
- addBaseUrl_(url)
371
- //==============
371
+ makeServerUrl(url, resource='flatmap/')
372
+ //=====================================
372
373
  {
373
- if (url.startsWith('/')) {
374
- return `${this._baseUrl}flatmap/${this.__uuid}${url}`; // We don't want embedded `{` and `}` characters escaped
375
- } else if (!url.startsWith('http://') && !url.startsWith('https://')) {
376
- console.log(`Invalid URL (${url}) in map's sources`);
374
+ if (url.startsWith('http://') || url.startsWith('https://')) {
375
+ return url;
376
+ } else if (url.startsWith('/')) {
377
+ // We don't want embedded `{` and `}` characters escaped
378
+ return `${this._baseUrl}${resource}${this.__uuid}${url}`;
379
+ } else {
380
+ return `${this._baseUrl}${resource}${this.__uuid}/${url}`;
377
381
  }
378
- return url;
379
382
  }
380
383
 
381
384
  /**
@@ -470,6 +473,19 @@ class FlatMap
470
473
  return this.__idToAnnotation.get(featureId.toString());
471
474
  }
472
475
 
476
+ /**
477
+ * Flag the feature as having external annotation.
478
+ *
479
+ * @param {string} featureId The feature's identifier
480
+ */
481
+ setFeatureAnnotated(featureId)
482
+ //============================
483
+ {
484
+ if (this._userInteractions !== null) {
485
+ this._userInteractions.setFeatureAnnotated(featureId);
486
+ }
487
+ }
488
+
473
489
  __updateFeatureIdMap(property, featureIdMap, annotation)
474
490
  //======================================================
475
491
  {
@@ -1286,6 +1302,7 @@ export class MapManager
1286
1302
  * @arg options.showPosition {boolean} Show ``position`` of tooltip.
1287
1303
  * @arg options.standalone {boolean} Viewer is running ``standalone``, as opposed to integrated into
1288
1304
  * another application so show a number of controls. Defaults to ``false``.
1305
+ * @arg options.annotator {boolean} Allow interactive annotation of features and paths.
1289
1306
  * @example
1290
1307
  * const humanMap1 = mapManager.loadMap('humanV1', 'div-1');
1291
1308
  *
@@ -34,14 +34,16 @@ import polylabel from 'polylabel';
34
34
  //==============================================================================
35
35
 
36
36
  import {Annotator} from './annotation';
37
- import {displayedProperties, InfoControl} from './info';
38
37
  import {LayerManager} from './layers';
39
38
  import {PATHWAYS_LAYER, Pathways} from './pathways';
39
+ import {COLOUR_ERROR, VECTOR_TILES_SOURCE} from './styling';
40
+ import {SystemsManager} from './systems';
41
+
42
+ import {displayedProperties, InfoControl} from './controls/info';
40
43
  import {BackgroundControl, LayerControl, NerveControl,
41
- PathControl, SCKANControl} from './controls';
42
- import {SearchControl} from './search';
43
- import {VECTOR_TILES_SOURCE} from './styling';
44
- import {SystemsControl, SystemsManager} from './systems';
44
+ PathControl, SCKANControl} from './controls/controls';
45
+ import {SearchControl} from './controls/search';
46
+ import {SystemsControl} from './controls/systems';
45
47
 
46
48
  import * as pathways from './pathways';
47
49
  import * as utils from './utils';
@@ -144,27 +146,17 @@ export class UserInteractions
144
146
  }
145
147
  }
146
148
 
147
- // Flag features that have annotations and note which are FC systems
148
-
149
- this.__systems = new Map();
150
- for (const [id, ann] of flatmap.annotations) {
151
- const feature = this.mapFeature_(id);
152
- if (feature !== undefined) {
153
- this._map.setFeatureState(feature, { 'annotated': true });
154
- }
155
- if (ann['fc-class'] === 'fc-class:System') {
156
- if (this.__systems.has(ann.name)) {
157
- this.__systems.get(ann.name).featureIds.push(ann.featureId)
158
- } else {
159
- this.__systems.set(ann.name, {
160
- id: ann.name.replaceAll(' ', '_'),
161
- colour: ann.colour,
162
- featureIds: [ ann.featureId ]
163
- });
164
- }
165
- }
149
+ // Add annotation capability
150
+ if (flatmap.options.annotator) {
151
+ this.__setupAnnotation();
152
+ } else {
153
+ this.__annotator = null;
166
154
  }
167
155
 
156
+ // Note features that are FC systems
157
+
158
+ this.__systemsManager = new SystemsManager(this._flatmap, this);
159
+
168
160
  // Add various controls when running standalone
169
161
 
170
162
  if (flatmap.options.standalone) {
@@ -192,15 +184,11 @@ export class UserInteractions
192
184
 
193
185
  // SCKAN path and SYSTEMS controls for FC maps
194
186
  if (flatmap.options.style === 'functional') {
195
- this._map.addControl(new SystemsControl(flatmap, this.__systems));
187
+ this._map.addControl(new SystemsControl(flatmap, this.__systemsManager.systems));
196
188
  this._map.addControl(new SCKANControl(flatmap, flatmap.options.layerOptions));
197
189
  }
198
190
  }
199
191
 
200
- // Add annotation capabilities
201
-
202
- this.__annotator = new Annotator(flatmap);
203
-
204
192
  // Handle mouse events
205
193
 
206
194
  this._map.on('click', this.clickEvent_.bind(this));
@@ -254,6 +242,41 @@ export class UserInteractions
254
242
  }
255
243
  }
256
244
 
245
+ async __setupAnnotation()
246
+ //=======================
247
+ {
248
+ // Add annotation capability
249
+
250
+ this.__annotator = new Annotator(this._flatmap);
251
+ const annotated_features = await this.__annotator.annotated_features();
252
+
253
+ // Flag features that have annotations
254
+ this.__featureIdToMapId = new Map();
255
+ for (const [mapId, ann] of this._flatmap.annotations) {
256
+ this.__featureIdToMapId.set(ann.id, mapId);
257
+ const feature = this.mapFeature(mapId);
258
+ if (feature !== undefined) {
259
+ this._map.setFeatureState(feature, { 'map-annotation': true });
260
+ if (annotated_features.indexOf(ann.id) >= 0) {
261
+ this._map.setFeatureState(feature, { 'annotated': true });
262
+ }
263
+ }
264
+ }
265
+ }
266
+
267
+ setFeatureAnnotated(featureId)
268
+ //============================
269
+ {
270
+ if (this.__annotator) {
271
+ // featureId v's geoJSON id
272
+ const mapId = this.__featureIdToMapId.get(featureId);
273
+ const feature = this.mapFeature(mapId);
274
+ if (feature !== undefined) {
275
+ this._map.setFeatureState(feature, { 'annotated': true });
276
+ }
277
+ }
278
+ }
279
+
257
280
  setPaint(options)
258
281
  //===============
259
282
  {
@@ -276,34 +299,35 @@ export class UserInteractions
276
299
  getSystems()
277
300
  //==========
278
301
  {
279
- const systems = [];
280
- for (const system of this.__systems.values()) {
281
- systems.push({
282
- name: system.name,
283
- colour: system.colour,
284
- });
285
- }
286
- return systems;
302
+ return this.__systemsManager.systems;
287
303
  }
288
304
 
289
305
  enableSystem(systemName, enable=true)
290
306
  //===================================
291
307
  {
292
- if (this.__systems.has(systemName)) {
293
- for (const featureId of this.__systems.get(systemName).featureIds) {
294
- this.__enableFeatureWithChildren(featureId, enable);
308
+ this.__systemsManager.enable(systemName, enable);
309
+ }
310
+
311
+ enableFeatureWithChildren(featureId, enable=true)
312
+ //===============================================
313
+ {
314
+ const feature = this.mapFeature(featureId);
315
+ if (feature !== undefined) {
316
+ this.enableFeature(feature, enable);
317
+ for (const childFeatureId of feature.children) {
318
+ this.enableFeatureWithChildren(childFeatureId, enable);
295
319
  }
296
320
  }
297
321
  }
298
322
 
299
- __enableFeatureWithChildren(featureId, enable=true)
300
- //=================================================
323
+ __enableFeatureWithParents(featureId, enable=true)
324
+ //================================================
301
325
  {
302
- const feature = this.mapFeature_(featureId);
326
+ const feature = this.mapFeature(featureId);
303
327
  if (feature !== undefined) {
304
- this.__enableFeature(feature, enable);
305
- for (const childFeatureId of feature.children) {
306
- this.__enableFeatureWithChildren(childFeatureId, enable);
328
+ this.enableFeature(feature, enable);
329
+ for (const childFeatureId of feature.parents) {
330
+ this.__enableFeatureWithParents(childFeatureId, enable);
307
331
  }
308
332
  }
309
333
  }
@@ -320,8 +344,8 @@ export class UserInteractions
320
344
  }
321
345
  }
322
346
 
323
- __enableFeature(feature, enable=true)
324
- //===================================
347
+ enableFeature(feature, enable=true)
348
+ //=================================
325
349
  {
326
350
  if (feature !== undefined) {
327
351
  if (enable) {
@@ -333,8 +357,8 @@ export class UserInteractions
333
357
  }
334
358
  }
335
359
 
336
- mapFeature_(featureId)
337
- //====================
360
+ mapFeature(featureId)
361
+ //===================
338
362
  {
339
363
  const ann = this._flatmap.annotation(featureId);
340
364
  if (ann !== undefined) {
@@ -366,7 +390,7 @@ export class UserInteractions
366
390
  if (this._selectedFeatureIds.has(featureId)) {
367
391
  this._selectedFeatureIds.set(featureId, this._selectedFeatureIds.get(featureId) + 1);
368
392
  } else {
369
- const feature = this.mapFeature_(featureId);
393
+ const feature = this.mapFeature(featureId);
370
394
  if (feature !== undefined) {
371
395
  this._map.setFeatureState(feature, { 'selected': true });
372
396
  this._selectedFeatureIds.set(featureId, 1);
@@ -383,7 +407,7 @@ export class UserInteractions
383
407
  if (references > 1) {
384
408
  this._selectedFeatureIds.set(featureId, references - 1);
385
409
  } else {
386
- const feature = this.mapFeature_(featureId);
410
+ const feature = this.mapFeature(featureId);
387
411
  if (feature !== undefined) {
388
412
  this._map.removeFeatureState(feature, 'selected');
389
413
  this._selectedFeatureIds.delete(+featureId);
@@ -399,7 +423,7 @@ export class UserInteractions
399
423
  //==================
400
424
  {
401
425
  for (const featureId of this._selectedFeatureIds.keys()) {
402
- const feature = this.mapFeature_(featureId);
426
+ const feature = this.mapFeature(featureId);
403
427
  if (feature !== undefined) {
404
428
  this._map.removeFeatureState(feature, 'selected');
405
429
  }
@@ -429,7 +453,7 @@ export class UserInteractions
429
453
  //==========================
430
454
  {
431
455
  featureId = +featureId; // Ensure numeric
432
- this.__activateFeature(this.mapFeature_(featureId));
456
+ this.__activateFeature(this.mapFeature(featureId));
433
457
  }
434
458
 
435
459
  unhighlightFeatures_()
@@ -447,7 +471,7 @@ export class UserInteractions
447
471
  let smallestFeature = null;
448
472
  for (const feature of features) {
449
473
  if (feature.geometry.type.includes('Polygon')
450
- && this._map.getFeatureState(feature)['annotated']) {
474
+ && this._map.getFeatureState(feature)['map-annotation']) {
451
475
  const polygon = turf.geometry(feature.geometry.type, feature.geometry.coordinates);
452
476
  const area = turfArea(polygon);
453
477
  if (smallestFeature === null || smallestArea > area) {
@@ -805,7 +829,7 @@ export class UserInteractions
805
829
  const lineIds = new Set(lineFeatures.map(f => f.properties.featureId));
806
830
  for (const featureId of this._pathways.lineFeatureIds(lineIds)) {
807
831
  if (+featureId !== lineFeatureId) {
808
- this.__activateFeature(this.mapFeature_(featureId));
832
+ this.__activateFeature(this.mapFeature(featureId));
809
833
  }
810
834
  }
811
835
  }
@@ -929,6 +953,10 @@ export class UserInteractions
929
953
  __annotationEvent(feature)
930
954
  //========================
931
955
  {
956
+ if (!this.__annotator) {
957
+ return;
958
+ }
959
+
932
960
  event.preventDefault();
933
961
 
934
962
  // Remove any tooltip
@@ -988,12 +1016,12 @@ export class UserInteractions
988
1016
  {
989
1017
  if ('nerveId' in feature.properties) {
990
1018
  for (const featureId of this._pathways.nerveFeatureIds(feature.properties.nerveId)) {
991
- this.__activateFeature(this.mapFeature_(featureId));
1019
+ this.__activateFeature(this.mapFeature(featureId));
992
1020
  }
993
1021
  }
994
1022
  if ('nodeId' in feature.properties) {
995
1023
  for (const featureId of this._pathways.nodeFeatureIds(feature.properties.nodeId)) {
996
- this.__activateFeature(this.mapFeature_(featureId));
1024
+ this.__activateFeature(this.mapFeature(featureId));
997
1025
  }
998
1026
  }
999
1027
  }
@@ -1018,7 +1046,7 @@ export class UserInteractions
1018
1046
  //=====================================
1019
1047
  {
1020
1048
  for (const featureId of featureIds) {
1021
- const feature = this.mapFeature_(featureId);
1049
+ const feature = this.mapFeature(featureId);
1022
1050
  if (feature !== undefined) {
1023
1051
  if (enable) {
1024
1052
  this._map.removeFeatureState(feature, 'hidden');
@@ -1229,7 +1257,7 @@ export class UserInteractions
1229
1257
  const markerId = this.__markerIdByMarker.get(marker);
1230
1258
  const annotation = this.__annotationByMarkerId.get(markerId);
1231
1259
  // The marker's feature
1232
- const feature = this.mapFeature_(annotation.featureId);
1260
+ const feature = this.mapFeature(annotation.featureId);
1233
1261
  if (feature !== undefined) {
1234
1262
  if (event.type === 'mouseenter') {
1235
1263
  // Highlight on mouse enter
package/src/layers.js CHANGED
@@ -154,6 +154,8 @@ class MapFeatureLayers extends MapStylingLayers
154
154
  if (this.__map.getSource('vector-tiles')
155
155
  .vectorLayerIds
156
156
  .indexOf(pathwaysVectorSource) >= 0) {
157
+ this.__addStyleLayer(style.AnnotatedPathLayer, PATHWAYS_LAYER);
158
+
157
159
  this.__addStyleLayer(style.CentrelineEdgeLayer, PATHWAYS_LAYER);
158
160
  this.__addStyleLayer(style.CentrelineTrackLayer, PATHWAYS_LAYER);
159
161
 
package/src/main.js CHANGED
@@ -60,7 +60,8 @@ export async function standaloneViewer(map_endpoint=null, options={})
60
60
  debug: false,
61
61
  minimap: false,
62
62
  showPosition: false,
63
- standalone: true
63
+ standalone: true,
64
+ annotator: true
64
65
  }, options);
65
66
 
66
67
  function loadMap(id, taxon, sex)
package/src/search.js CHANGED
@@ -36,99 +36,6 @@ export const indexedProperties = [
36
36
 
37
37
  //==============================================================================
38
38
 
39
- export class SearchControl
40
- {
41
- constructor(flatmap)
42
- {
43
- this.__flatmap = flatmap;
44
- }
45
-
46
- onAdd(map)
47
- //========
48
- {
49
- this._map = map;
50
- this._container = document.createElement('div');
51
- this._container.className = 'maplibregl-ctrl search-control';
52
-
53
- this._input = document.createElement('input');
54
- this._input.id = 'search-control-input';
55
- this._input.setAttribute('type', 'search');
56
- this._input.setAttribute('visible', 'false');
57
- this._input.setAttribute('placeholder', 'Search...');
58
-
59
- this._button = document.createElement('button');
60
- this._button.id = 'search-control-button';
61
- this._button.className = 'control-button';
62
- this._button.title = 'Search flatmap';
63
- this._button.setAttribute('type', 'button');
64
- this._button.setAttribute('aria-label', 'Search flatmap');
65
- // https://iconmonstr.com/magnifier-6-svg/
66
- this._button.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" id="search-control-icon" viewBox="0 0 24 24">
67
- <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"/>
68
- </svg>`;
69
- this._container.appendChild(this._button);
70
-
71
- this._container.onclick = this.onClick_.bind(this);
72
- return this._container;
73
- }
74
-
75
- getDefaultPosition()
76
- //==================
77
- {
78
- return 'top-right';
79
- }
80
-
81
- onRemove()
82
- //========
83
- {
84
- this._container.parentNode.removeChild(this._container);
85
- this._map = undefined;
86
- }
87
-
88
- searchMap_(search=true)
89
- //=====================
90
- {
91
- this._input = this._container.removeChild(this._input);
92
- this._input.setAttribute('visible', 'false');
93
- const text = this._input.value;
94
- if (search && text !== '') {
95
- const results = this.__flatmap.search(text);
96
- this.__flatmap.showSearchResults(results);
97
- }
98
- }
99
-
100
- onKeyDown_(e)
101
- //===========
102
- {
103
- if (e.key === 'Enter') {
104
- this.searchMap_();
105
- } else if (e.key === 'Escape') {
106
- this.searchMap_(false);
107
- }
108
- }
109
-
110
- onClick_(e)
111
- //=========
112
- {
113
- const targetId = ('rangeTarget' in e) ? e.rangeTarget.id : e.target.id; // FF has rangeTarget
114
- if (['search-control-button', 'search-control-icon'].includes(targetId)) {
115
- if (this._input.getAttribute('visible') === 'false') {
116
- this._container.appendChild(this._input);
117
- this._container.appendChild(this._button);
118
- this._input.setAttribute('visible', 'true');
119
- this._input.onkeydown = this.onKeyDown_.bind(this);
120
- this._input.value = '';
121
- this.__flatmap.clearSearchResults();
122
- this._input.focus();
123
- } else {
124
- this.searchMap_();
125
- }
126
- }
127
- }
128
- }
129
-
130
- //==============================================================================
131
-
132
39
  export class SearchIndex
133
40
  {
134
41
  constructor()
package/src/styling.js CHANGED
@@ -30,6 +30,20 @@ import {PATH_STYLE_RULES} from './pathways.js';
30
30
 
31
31
  //==============================================================================
32
32
 
33
+ const COLOUR_ACTIVE = 'blue';
34
+ const COLOUR_ANNOTATED = '#0F0';
35
+ const COLOUR_SELECTED = '#0F0';
36
+
37
+ const CENTRELINE_ACTIVE = '#444';
38
+ const CENTRELINE_COLOUR = '#CCC';
39
+
40
+ const FEATURE_SELECTED_BORDER = 'black';
41
+
42
+ const NERVE_ACTIVE = '#222';
43
+ const NERVE_SELECTED = 'red';
44
+
45
+ //==============================================================================
46
+
33
47
  class VectorStyleLayer
34
48
  {
35
49
  constructor(id, suffix, sourceLayer)
@@ -121,19 +135,21 @@ export class FeatureFillLayer extends VectorStyleLayer
121
135
 
122
136
  paintStyle(options, changes=false)
123
137
  {
138
+ const ghostColour = '#D8D8D8'; // Function of BG colour?
124
139
  const coloured = !('colour' in options) || options.colour;
125
140
  const dimmed = 'dimmed' in options && options.dimmed;
126
141
  const paintStyle = {
127
142
  'fill-color': [
128
143
  'case',
129
- ['boolean', ['feature-state', 'selected'], false], '#0F0',
144
+ ['boolean', ['feature-state', 'selected'], false], COLOUR_SELECTED,
145
+ ['boolean', ['feature-state', 'hidden'], false], ghostColour,
130
146
  ['has', 'colour'], ['get', 'colour'],
131
147
  ['boolean', ['feature-state', 'active'], false], coloured ? '#D88' : '#CCC',
132
148
  'white' // background colour? body colour ??
133
149
  ],
134
150
  'fill-opacity': [
135
151
  'case',
136
- ['boolean', ['feature-state', 'hidden'], false], 0.01,
152
+ ['boolean', ['feature-state', 'hidden'], false], 0.1,
137
153
  ['boolean', ['feature-state', 'selected'], false], 0.7,
138
154
  ['has', 'opacity'], ['get', 'opacity'],
139
155
  ['has', 'colour'], 1.0,
@@ -180,11 +196,13 @@ export class FeatureBorderLayer extends VectorStyleLayer
180
196
  const activeRasterLayer = 'activeRasterLayer' in options && options.activeRasterLayer;
181
197
  const lineColour = [ 'case' ];
182
198
  lineColour.push(['boolean', ['feature-state', 'selected'], false]);
183
- lineColour.push('black');
199
+ lineColour.push(FEATURE_SELECTED_BORDER);
184
200
  if (coloured && outlined) {
185
201
  lineColour.push(['boolean', ['feature-state', 'active'], false]);
186
- lineColour.push('blue');
202
+ lineColour.push(COLOUR_ACTIVE);
187
203
  }
204
+ lineColour.push(['boolean', ['feature-state', 'annotated'], false]);
205
+ lineColour.push(COLOUR_ANNOTATED);
188
206
  lineColour.push(['has', 'colour']);
189
207
  lineColour.push(['get', 'colour']);
190
208
  lineColour.push('#444');
@@ -199,6 +217,8 @@ export class FeatureBorderLayer extends VectorStyleLayer
199
217
  }
200
218
  lineOpacity.push(['boolean', ['feature-state', 'selected'], false]);
201
219
  lineOpacity.push(0.9);
220
+ lineOpacity.push(['boolean', ['feature-state', 'annotated'], false]);
221
+ lineOpacity.push(0.9);
202
222
  if (activeRasterLayer) {
203
223
  lineOpacity.push((outlined && !dimmed) ? 0.3 : 0.1);
204
224
  } else {
@@ -215,6 +235,8 @@ export class FeatureBorderLayer extends VectorStyleLayer
215
235
  lineWidth.push(['boolean', ['feature-state', 'active'], false]);
216
236
  lineWidth.push(1.5);
217
237
  }
238
+ lineWidth.push(['boolean', ['feature-state', 'annotated'], false]);
239
+ lineWidth.push(3.5);
218
240
  lineWidth.push(['has', 'colour']);
219
241
  lineWidth.push(0.7);
220
242
  lineWidth.push((coloured && outlined) ? 0.5 : 0.1);
@@ -276,7 +298,7 @@ export class FeatureLineLayer extends VectorStyleLayer
276
298
  const paintStyle = {
277
299
  'line-color': [
278
300
  'case',
279
- ['boolean', ['feature-state', 'selected'], false], '#0F0',
301
+ ['boolean', ['feature-state', 'selected'], false], COLOUR_SELECTED,
280
302
  ['boolean', ['feature-state', 'active'], false], coloured ? '#888' : '#CCC',
281
303
  ['has', 'colour'], ['get', 'colour'],
282
304
  ['==', ['get', 'type'], 'network'], '#AFA202',
@@ -338,6 +360,104 @@ export class FeatureDashLineLayer extends FeatureLineLayer
338
360
 
339
361
  //==============================================================================
340
362
 
363
+ function sckanFilter(options)
364
+ {
365
+ const sckanState = !'sckan' in options ? 'all'
366
+ : options.sckan.toLowerCase();
367
+ const sckanFilter =
368
+ sckanState == 'none' ? [
369
+ ['!has', 'sckan']
370
+ ] :
371
+ sckanState == 'valid' ? [[
372
+ 'any',
373
+ ['!has', 'sckan'],
374
+ [
375
+ 'all',
376
+ ['has', 'sckan'],
377
+ ['==', 'sckan', true]
378
+ ]
379
+ ]] :
380
+ sckanState == 'invalid' ? [[
381
+ 'any',
382
+ ['!has', 'sckan'],
383
+ [
384
+ 'all',
385
+ ['has', 'sckan'],
386
+ ['!=', 'sckan', true]
387
+ ]
388
+ ]] :
389
+ [ ];
390
+ return sckanFilter;
391
+ }
392
+
393
+ //==============================================================================
394
+
395
+ export class AnnotatedPathLayer extends VectorStyleLayer
396
+ {
397
+ constructor(id, sourceLayer)
398
+ {
399
+ super(id, 'annotated-path', sourceLayer);
400
+ }
401
+
402
+ makeFilter(options={})
403
+ {
404
+ return [
405
+ 'all',
406
+ ['==', '$type', 'LineString'],
407
+ ...sckanFilter(options)
408
+ ];
409
+ }
410
+
411
+ paintStyle(options={}, changes=false)
412
+ {
413
+ const dimmed = 'dimmed' in options && options.dimmed;
414
+ const paintStyle = {
415
+ 'line-color': COLOUR_ANNOTATED,
416
+ 'line-dasharray': [5, 0.5, 3, 0.5],
417
+ 'line-opacity': [
418
+ 'case',
419
+ ['boolean', ['feature-state', 'hidden'], false], 0.05,
420
+ ['boolean', ['feature-state', 'annotated'], false],
421
+ (dimmed ? 0.1 : 0.8),
422
+ 0.6
423
+ ],
424
+ 'line-width': [
425
+ 'let',
426
+ 'width',
427
+ ['case',
428
+ ['boolean', ['feature-state', 'annotated'], false],
429
+ ['*', 1.2, ['case', ['has', 'stroke-width'], ['get', 'stroke-width'], 1.0]],
430
+ 0.0
431
+ ],
432
+ ['interpolate',
433
+ ['exponential', 2],
434
+ ['zoom'],
435
+ 2, ["*", ['var', 'width'], ["^", 2, -0.5]],
436
+ 7, ["*", ['var', 'width'], ["^", 2, 2.5]],
437
+ 9, ["*", ['var', 'width'], ["^", 2, 4.0]]
438
+ ]
439
+ ]
440
+ };
441
+ return super.changedPaintStyle(paintStyle, changes);
442
+ }
443
+
444
+ style(options)
445
+ {
446
+ const dimmed = 'dimmed' in options && options.dimmed;
447
+ return {
448
+ ...super.style(),
449
+ 'type': 'line',
450
+ 'filter': this.makeFilter(options),
451
+ 'paint': this.paintStyle(options),
452
+ 'layout': {
453
+ 'line-cap': 'square'
454
+ }
455
+ };
456
+ }
457
+ }
458
+
459
+ //==============================================================================
460
+
341
461
  export class PathLineLayer extends VectorStyleLayer
342
462
  {
343
463
  constructor(id, sourceLayer, options={})
@@ -350,31 +470,7 @@ export class PathLineLayer extends VectorStyleLayer
350
470
 
351
471
  makeFilter(options={})
352
472
  {
353
- const sckanState = !'sckan' in options ? 'all'
354
- : options.sckan.toLowerCase();
355
- const sckan_filter =
356
- sckanState == 'none' ? [
357
- ['!has', 'sckan']
358
- ] :
359
- sckanState == 'valid' ? [[
360
- 'any',
361
- ['!has', 'sckan'],
362
- [
363
- 'all',
364
- ['has', 'sckan'],
365
- ['==', 'sckan', true]
366
- ]
367
- ]] :
368
- sckanState == 'invalid' ? [[
369
- 'any',
370
- ['!has', 'sckan'],
371
- [
372
- 'all',
373
- ['has', 'sckan'],
374
- ['!=', 'sckan', true]
375
- ]
376
- ]] :
377
- [ ];
473
+ const sckan_filter = sckanFilter(options);
378
474
 
379
475
  return this.__dashed ? [
380
476
  'all',
@@ -402,7 +498,7 @@ export class PathLineLayer extends VectorStyleLayer
402
498
  const paintStyle = {
403
499
  'line-color': [
404
500
  'case',
405
- ['boolean', ['feature-state', 'selected'], false], '#0F0',
501
+ ['boolean', ['feature-state', 'selected'], false], COLOUR_SELECTED,
406
502
  ['boolean', ['feature-state', 'hidden'], false], '#CCC',
407
503
  ['==', ['get', 'type'], 'bezier'], 'red',
408
504
  ['==', ['get', 'kind'], 'unknown'], '#888',
@@ -487,9 +583,9 @@ class CentrelineLayer extends VectorStyleLayer
487
583
  const paintStyle = {
488
584
  'line-color': (this.__type == 'edge') ? '#000' : [
489
585
  'case',
490
- ['boolean', ['feature-state', 'selected'], false], '#0F0',
491
- ['boolean', ['feature-state', 'active'], false], '#444',
492
- '#CCC'
586
+ ['boolean', ['feature-state', 'selected'], false], COLOUR_SELECTED,
587
+ ['boolean', ['feature-state', 'active'], false], CENTRELINE_ACTIVE,
588
+ CENTRELINE_COLOUR
493
589
  ],
494
590
  'line-opacity': [
495
591
  'case',
@@ -650,8 +746,8 @@ export class FeatureNerveLayer extends VectorStyleLayer
650
746
  'line-color': [
651
747
  'case',
652
748
  ['boolean', ['feature-state', 'hidden'], false], '#CCC',
653
- ['boolean', ['feature-state', 'active'], false], '#222',
654
- ['boolean', ['feature-state', 'selected'], false], 'red',
749
+ ['boolean', ['feature-state', 'active'], false], NERVE_ACTIVE,
750
+ ['boolean', ['feature-state', 'selected'], false], NERVE_SELECTED,
655
751
  '#888'
656
752
  ],
657
753
  'line-opacity': [
@@ -702,7 +798,7 @@ export class NervePolygonBorder extends VectorStyleLayer
702
798
  'paint': {
703
799
  'line-color': [
704
800
  'case',
705
- ['boolean', ['feature-state', 'active'], false], 'blue',
801
+ ['boolean', ['feature-state', 'active'], false], COLOUR_ACTIVE,
706
802
  ['boolean', ['feature-state', 'selected'], false], 'red',
707
803
  '#444'
708
804
  ],
package/src/systems.js CHANGED
@@ -16,61 +16,78 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
16
  See the License for the specific language governing permissions and
17
17
  limitations under the License.
18
18
 
19
- ******************************************************************************/
20
-
21
-
22
- import { Control } from './controls';
23
-
19
+ **/
24
20
  //==============================================================================
25
21
 
26
22
  export class SystemsManager
27
23
  {
28
- constructor()
29
- {
30
-
31
- }
32
- }
33
-
34
- //==============================================================================
35
-
36
- export class SystemsControl extends Control
37
- {
38
- constructor(flatmap, systems)
24
+ constructor(flatmap, ui, enabled=true)
39
25
  {
40
- super(flatmap, 'system', 'systems');
41
- this.__systems = systems;
42
- }
43
-
44
- __innerLinesHTML()
45
- //================
46
- {
47
- const html = [];
48
- for (const [name, system] of this.__systems.entries()) {
49
- html.push(`<label for="${this.__prefix}${system.id}" style="background: ${system.colour};">${name}</label><input id="${this.__prefix}${system.id}" type="checkbox" checked/>`);
26
+ this.__ui = ui;
27
+ this.__systems = new Map();
28
+ this.__enabledChildren = new Map();
29
+ for (const [id, ann] of flatmap.annotations) {
30
+ if (ann['fc-class'] === 'fc-class:System') {
31
+ if (this.__systems.has(ann.name)) {
32
+ this.__systems.get(ann.name).featureIds.push(ann.featureId)
33
+ } else {
34
+ this.__systems.set(ann.name, {
35
+ id: ann.name.replaceAll(' ', '_'),
36
+ colour: ann.colour,
37
+ featureIds: [ ann.featureId ],
38
+ enabled: enabled
39
+ });
40
+ }
41
+ for (const childId of ann['children']) {
42
+ if (enabled) {
43
+ const enabledCount = (this.__enabledChildren.has(childId))
44
+ ? this.__enabledChildren.get(childId)
45
+ : 0;
46
+ this.__enabledChildren.set(childId, enabledCount + 1);
47
+ } else {
48
+ this.__enabledChildren.set(childId, 0);
49
+ }
50
+ }
51
+ }
50
52
  }
51
- return html;
52
53
  }
53
54
 
54
- __enableAll(enable)
55
- //=================
55
+ get systems()
56
+ //===========
56
57
  {
58
+ const systems = [];
57
59
  for (const [name, system] of this.__systems.entries()) {
58
- const checkbox = document.getElementById(`${this.__prefix}${system.id}`);
59
- if (checkbox) {
60
- checkbox.checked = enable;
61
- this.__flatmap.enableSystem(name, enable);
62
- }
60
+ systems.push({
61
+ name: name,
62
+ id: system.id,
63
+ colour: system.colour,
64
+ enabled: system.enabled
65
+ });
63
66
  }
67
+ return systems;
64
68
  }
65
69
 
66
- __enableControl(id, enable)
67
- //=========================
70
+ enable(systemName, enable=true)
71
+ //=============================
68
72
  {
69
- for (const [name, system] of this.__systems.entries()) {
70
- if (id === system.id) {
71
- this.__flatmap.enableSystem(name, enable);
73
+ const system = this.__systems.get(systemName);
74
+ if (system !== undefined && enable !== system.enabled) {
75
+ for (const featureId of system.featureIds) {
76
+ const feature = this.__ui.mapFeature(featureId);
77
+ if (feature !== undefined) {
78
+ this.__ui.enableFeature(feature, enable);
79
+ for (const childFeatureId of feature.children) {
80
+ const enabledCount = this.__enabledChildren.get(childFeatureId);
81
+ if (enable && enabledCount === 0 || !enable && enabledCount == 1) {
82
+ this.__ui.enableFeatureWithChildren(childFeatureId, enable);
83
+ }
84
+ this.__enabledChildren.set(childFeatureId, enabledCount + (enable ? 1 : -1));
85
+ }
86
+ }
72
87
  }
88
+ system.enabled = enable;
73
89
  }
74
90
  }
75
-
76
91
  }
92
+
93
+ //==============================================================================
File without changes