@abi-software/mapintegratedvuer 1.21.1 → 1.21.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.
Files changed (68) hide show
  1. package/README.md +23 -5
  2. package/assets/location-icon.html +33 -1
  3. package/assets/styleguide.css +18 -18
  4. package/cypress.config.js +7 -9
  5. package/dist/{ConnectivityGraph-CZMROk9r.js → ConnectivityGraph-DrSaFEDd.js} +3 -3
  6. package/dist/{ContentMixin-Vn34n5t2.js → ContentMixin-Xg0PjBqG.js} +2 -2
  7. package/dist/{DynamicMarkerMixin-BS6t7w_i.js → DynamicMarkerMixin-D5Ruu_hd.js} +1 -1
  8. package/dist/{Flatmap-BejxA0uz.js → Flatmap-CxN8TbnS.js} +4 -4
  9. package/dist/{Iframe-BWbhTjCR.js → Iframe-CkgbLfJt.js} +4 -4
  10. package/dist/{MultiFlatmap-nlmsH-Ib.js → MultiFlatmap-CkejvAJe.js} +4 -4
  11. package/dist/{Plot-ChbPsDZX.js → Plot-BfwoipSl.js} +2 -2
  12. package/dist/{Scaffold-C6f7c7Ha.js → Scaffold-DCqsTKpg.js} +4 -4
  13. package/dist/{Simulation-DcJScRYo.js → Simulation-BOtU3kTT.js} +2 -2
  14. package/dist/{connectivities-UXTqf7_0.js → connectivities-DyeS8w0c.js} +126 -95
  15. package/dist/index.html +9 -6
  16. package/dist/mapintegratedvuer.js +5489 -5578
  17. package/dist/mapintegratedvuer.umd.cjs +196 -196
  18. package/dist/style.css +1 -1
  19. package/eslint.config.js +39 -15
  20. package/package.json +11 -5
  21. package/public/index.html +9 -6
  22. package/src/App.vue +234 -249
  23. package/src/assets/_variables.scss +12 -10
  24. package/src/assets/header-icon.scss +8 -9
  25. package/src/assets/mapicon-species-style.css +10 -8
  26. package/src/assets/styles.scss +1 -1
  27. package/src/components/ContentBar.vue +98 -106
  28. package/src/components/ContentVuer.vue +36 -39
  29. package/src/components/ContextCard.vue +238 -199
  30. package/src/components/ContextHelp.vue +22 -26
  31. package/src/components/CustomSplitter.vue +32 -43
  32. package/src/components/DatasetHeader.vue +37 -41
  33. package/src/components/DialogToolbarContent.vue +292 -256
  34. package/src/components/FlatmapContextCard.vue +52 -57
  35. package/src/components/MapContent.vue +90 -86
  36. package/src/components/ResizeSensor.vue +8 -13
  37. package/src/components/SearchControls.vue +26 -35
  38. package/src/components/SimulatedData.js +196 -219
  39. package/src/components/SplitDialog.vue +139 -129
  40. package/src/components/SplitFlow.vue +421 -237
  41. package/src/components/index.js +1 -3
  42. package/src/components/scripts/simulation_data.js +393 -397
  43. package/src/components/scripts/utilities.js +108 -78
  44. package/src/components/viewers/CellDLViewer.vue +9 -9
  45. package/src/components/viewers/ConnectivityGraph.vue +9 -12
  46. package/src/components/viewers/Flatmap.vue +23 -25
  47. package/src/components/viewers/Iframe.vue +8 -10
  48. package/src/components/viewers/MultiFlatmap.vue +59 -69
  49. package/src/components/viewers/Plot.vue +6 -7
  50. package/src/components/viewers/Scaffold.vue +73 -63
  51. package/src/components/viewers/Simulation.vue +10 -15
  52. package/src/main.js +16 -21
  53. package/src/mixins/ContentMixin.js +208 -178
  54. package/src/mixins/DynamicMarkerMixin.js +14 -18
  55. package/src/mixins/RetrieveContextCardMixin.js +35 -30
  56. package/src/mixins/S3Bucket.vue +10 -13
  57. package/src/services/knowledgeMapping.js +53 -53
  58. package/src/services/tagging.js +4 -4
  59. package/src/stores/connectivities.js +46 -8
  60. package/src/stores/entries.js +7 -9
  61. package/src/stores/index.js +7 -7
  62. package/src/stores/settings.js +23 -31
  63. package/src/stores/splitFlow.js +364 -298
  64. package/vite.bundle-build.js +24 -26
  65. package/vite.config.js +22 -21
  66. package/vite.static-build.js +4 -4
  67. package/vitest.workspace.js +1 -3
  68. package/vuese-generator.js +47 -55
@@ -15,9 +15,7 @@
15
15
  />
16
16
  </el-header>
17
17
  <el-main class="dialog-main">
18
- <div
19
- style="width: 100%; height: 100%; position: relative; overflow: hidden"
20
- >
18
+ <div style="width: 100%; height: 100%; position: relative; overflow: hidden">
21
19
  <SideBar
22
20
  ref="sideBar"
23
21
  :envVars="envVars"
@@ -66,36 +64,28 @@
66
64
  </template>
67
65
 
68
66
  <script>
69
- /* eslint-disable no-alert, no-console */
70
67
  import { provide, markRaw, computed } from 'vue';
71
68
  import Tagging from '../services/tagging.js';
72
- import DialogToolbarContent from "./DialogToolbarContent.vue";
73
- import EventBus from "./EventBus";
74
- import SplitDialog from "./SplitDialog.vue";
69
+ import DialogToolbarContent from './DialogToolbarContent.vue';
70
+ import EventBus from './EventBus';
71
+ import SplitDialog from './SplitDialog.vue';
75
72
  // import contextCards from './context-cards'
76
- import { SideBar } from "@abi-software/map-side-bar";
77
- import "@abi-software/map-side-bar/dist/style.css";
78
- import {
79
- queryForwardBackwardConnections
80
- } from '@abi-software/map-utilities';
73
+ import { SideBar } from '@abi-software/map-side-bar';
74
+ import '@abi-software/map-side-bar/dist/style.css';
75
+ import { queryForwardBackwardConnections } from '@abi-software/map-utilities';
81
76
  import {
82
77
  capitalise,
83
78
  getNewMapEntry,
84
79
  initialDefaultState,
85
80
  intersectArrays,
86
- } from "./scripts/utilities.js";
87
- import { AnnotationService } from '@abi-software/sparc-annotation'
81
+ } from './scripts/utilities.js';
82
+ import { AnnotationService } from '@abi-software/sparc-annotation';
88
83
  import { mapStores } from 'pinia';
89
84
  import { useEntriesStore } from '../stores/entries';
90
- import { useMainStore } from '../stores/index'
85
+ import { useMainStore } from '../stores/index';
91
86
  import { useSettingsStore } from '../stores/settings';
92
87
  import { useSplitFlowStore } from '../stores/splitFlow';
93
88
  import { useConnectivitiesStore } from '../stores/connectivities';
94
- import {
95
- ElContainer as Container,
96
- ElHeader as Header,
97
- ElMain as Main,
98
- } from "element-plus";
99
89
 
100
90
  const getAllFacetLabels = (children) => {
101
91
  const labels = [];
@@ -108,12 +98,12 @@ const getAllFacetLabels = (children) => {
108
98
  });
109
99
  }
110
100
  return labels;
111
- }
101
+ };
112
102
 
113
103
  const getFacetsFromAction = (action) => {
114
104
  const actionFacets = action.facets ? action.facets : action.labels ? action.labels : [];
115
105
  return actionFacets;
116
- }
106
+ };
117
107
 
118
108
  const getAnatomyTermsForFilters = (action, availableNameCurieMapping) => {
119
109
  const facets = [];
@@ -126,17 +116,14 @@ const getAnatomyTermsForFilters = (action, availableNameCurieMapping) => {
126
116
  }
127
117
  }
128
118
  return facets;
129
- }
119
+ };
130
120
 
131
121
  /**
132
122
  * Component of the floating dialogs.
133
123
  */
134
124
  export default {
135
- name: "SplitFlow",
125
+ name: 'SplitFlow',
136
126
  components: {
137
- Container,
138
- Header,
139
- Main,
140
127
  DialogToolbarContent,
141
128
  SplitDialog,
142
129
  SideBar,
@@ -147,8 +134,11 @@ export default {
147
134
  let annotator = markRaw(new AnnotationService(`${settings.flatmapAPI}annotator`));
148
135
  provide('userApiKey', mainStore.userToken);
149
136
  provide('$annotator', annotator);
150
- provide('showLongLabel', computed(() => props.showLongLabel));
151
- return { annotator }
137
+ provide(
138
+ 'showLongLabel',
139
+ computed(() => props.showLongLabel),
140
+ );
141
+ return { annotator };
152
142
  },
153
143
  props: {
154
144
  state: {
@@ -168,6 +158,11 @@ export default {
168
158
  startUp: true,
169
159
  sidebarStateRestored: false,
170
160
  sidebarAnnotationState: false,
161
+ // true while a permalink's sidebar.connectivityEntries are being resolved across the active viewers,
162
+ // used to avoid flashing the full/unfiltered connectivity list
163
+ // or dropping entries resolved by other viewers.
164
+ restoringConnectivityState: false,
165
+ restoreAttempts: 0,
171
166
  search: '',
172
167
  expanded: '',
173
168
  filterTriggered: false,
@@ -187,7 +182,7 @@ export default {
187
182
  filterVisibility: true,
188
183
  filterOptions: [],
189
184
  annotationHighlight: [],
190
- }
185
+ };
191
186
  },
192
187
  watch: {
193
188
  state: {
@@ -218,29 +213,27 @@ export default {
218
213
  const filterExpression = {
219
214
  OR: [
220
215
  { NOT: { 'tile-layer': 'pathways' } },
221
- { NOT: { 'HAS': 'nerves' } },
216
+ { NOT: { HAS: 'nerves' } },
222
217
  {
223
- AND: [
224
- { 'tile-layer': 'pathways' },
225
- { 'models': this.connectivityHighlight }
226
- ]
227
- }
228
- ]
218
+ AND: [{ 'tile-layer': 'pathways' }, { models: this.connectivityHighlight }],
219
+ },
220
+ ],
229
221
  };
230
222
  const validFilter = this.filterVisibility && this.connectivityProcessed;
231
223
  const payload = validFilter ? filterExpression : undefined;
232
224
  EventBus.emit('filter-visibility', payload);
233
225
  },
234
226
  onConnectivityCollapseChange: function (payload) {
235
- this.expanded = payload.id
227
+ this.expanded = payload.id;
236
228
  const splitdialog = this.$refs.splitdialog;
237
229
  if (splitdialog) {
238
230
  const activeContents = splitdialog.getActiveContents();
239
- const hasFlatmap = activeContents.find(c => c.viewerType.includes('Flatmap'));
240
- const hasHumanMaleFlatmap = activeContents.find(c => c.activeSpecies === "Human Male");
241
- let nonFlatmapLoad = false
242
- activeContents.forEach(content => {
243
- const isFlatmap = content.viewerType === 'Flatmap' || content.viewerType === 'MultiFlatmap';
231
+ const hasFlatmap = activeContents.find((c) => c.viewerType.includes('Flatmap'));
232
+ const hasHumanMaleFlatmap = activeContents.find((c) => c.activeSpecies === 'Human Male');
233
+ let nonFlatmapLoad = false;
234
+ activeContents.forEach((content) => {
235
+ const isFlatmap =
236
+ content.viewerType === 'Flatmap' || content.viewerType === 'MultiFlatmap';
244
237
  // minimise connectivity detail fetch
245
238
  const shouldLoad =
246
239
  (hasFlatmap && isFlatmap) ||
@@ -265,7 +258,7 @@ export default {
265
258
  if (splitdialog) {
266
259
  const activeContents = splitdialog.getActiveContents();
267
260
 
268
- activeContents.forEach(content => {
261
+ activeContents.forEach((content) => {
269
262
  if (content?.$refs['viewer']) {
270
263
  const contentViewer = content.$refs['viewer'];
271
264
  const flatmapRef = contentViewer.flatmapRef;
@@ -273,7 +266,8 @@ export default {
273
266
  let flatmap = null;
274
267
 
275
268
  if (flatmapRef) flatmap = flatmapRef;
276
- if (multiflatmapRef) flatmap = multiflatmapRef.getCurrentFlatmap();
269
+ if (multiflatmapRef && contentViewer.flatmapIsReady())
270
+ flatmap = multiflatmapRef.getCurrentFlatmap();
277
271
 
278
272
  if (flatmap) {
279
273
  activeFlatmaps.push(flatmap);
@@ -283,12 +277,31 @@ export default {
283
277
  }
284
278
  return activeFlatmaps;
285
279
  },
280
+ // Scaffold viewers are not Flatmap/MultiFlatmap instances so they're not returned by getActiveFlatmaps,
281
+ // but their own nervesKnowledge (populated once loadConnectivityExplorerConfig resolves)
282
+ // carries the nerve-label data needed to restore their connectivity entries.
283
+ getActiveScaffolds: function () {
284
+ const activeScaffolds = [];
285
+ let splitdialog = this.$refs.splitdialog;
286
+
287
+ if (splitdialog) {
288
+ const activeContents = splitdialog.getActiveContents();
289
+
290
+ activeContents.forEach((content) => {
291
+ const contentViewer = content?.$refs['viewer'];
292
+ if (contentViewer?.scaffoldRef) {
293
+ activeScaffolds.push(contentViewer);
294
+ }
295
+ });
296
+ }
297
+ return activeScaffolds;
298
+ },
286
299
  /**
287
300
  * Callback when an action is performed (open new dialogs).
288
301
  */
289
302
  actionClick: function (action) {
290
303
  if (action) {
291
- if (!this.availableFacets || (this.availableFacets.length === 0)) {
304
+ if (!this.availableFacets || this.availableFacets.length === 0) {
292
305
  const availableFacetsRaw = localStorage.getItem('available-anatomy-facets');
293
306
  const availableFacetsAll = availableFacetsRaw ? JSON.parse(availableFacetsRaw) : [];
294
307
 
@@ -296,7 +309,10 @@ export default {
296
309
  this.availableFacets = markRaw([...new Set(getAllFacetLabels(availableFacetsAll))]);
297
310
  }
298
311
 
299
- if (!this.availableNameCurieMapping || (Object.keys(this.availableNameCurieMapping).length === 0)) {
312
+ if (
313
+ !this.availableNameCurieMapping ||
314
+ Object.keys(this.availableNameCurieMapping).length === 0
315
+ ) {
300
316
  const availableDataRaw = localStorage.getItem('available-name-curie-mapping');
301
317
  const availableData = availableDataRaw ? JSON.parse(availableDataRaw) : {};
302
318
 
@@ -304,7 +320,7 @@ export default {
304
320
  this.availableNameCurieMapping = markRaw(availableData);
305
321
  }
306
322
 
307
- if (action.type == "Search") {
323
+ if (action.type == 'Search') {
308
324
  if (action.nervePath) {
309
325
  this.openSearch([action.filter], action.label);
310
326
  } else {
@@ -315,67 +331,67 @@ export default {
315
331
  ? 'portal_maps_featured_dataset_search'
316
332
  : 'portal_maps_action_search';
317
333
  Tagging.sendEvent({
318
- 'event': 'interaction_event',
319
- 'event_name': eventName,
320
- 'category': action.term || 'filter',
321
- 'location': 'map_location_pin'
334
+ event: 'interaction_event',
335
+ event_name: eventName,
336
+ category: action.term || 'filter',
337
+ location: 'map_location_pin',
322
338
  });
323
339
  this.filterTriggered = true;
324
340
  }
325
- } else if (action.type == "URL") {
326
- window.open(action.resource, "_blank");
327
- } else if (action.type == "Facet") {
341
+ } else if (action.type == 'URL') {
342
+ window.open(action.resource, '_blank');
343
+ } else if (action.type == 'Facet') {
328
344
  if (this.$refs.sideBar) {
329
345
  const sendAction = {
330
- facetPropPath: "anatomy.organ.category.name",
331
- facetSubPropPath: "anatomy.organ.name",
332
- term: "Anatomical structure",
346
+ facetPropPath: 'anatomy.organ.category.name',
347
+ facetSubPropPath: 'anatomy.organ.name',
348
+ term: 'Anatomical structure',
333
349
  };
334
350
  const filters = [];
335
351
  const facets = getAnatomyTermsForFilters(action, this.availableNameCurieMapping);
336
352
  const actionFacets = getFacetsFromAction(action);
337
353
  const facetString = actionFacets.join(', ');
338
- facets.forEach(facet => filters.push({...sendAction, facet}));
354
+ facets.forEach((facet) => filters.push({ ...sendAction, facet }));
339
355
  this.$refs.sideBar.addFilter(filters);
340
356
  // GA Tagging
341
357
  // Event tracking for map action search/filter data
342
358
  Tagging.sendEvent({
343
- 'event': 'interaction_event',
344
- 'event_name': 'portal_maps_action_filter',
345
- 'category': facetString || 'filter_reset',
346
- 'location': 'map_location_pin'
359
+ event: 'interaction_event',
360
+ event_name: 'portal_maps_action_filter',
361
+ category: facetString || 'filter_reset',
362
+ location: 'map_location_pin',
347
363
  });
348
364
  this.filterTriggered = true;
349
365
  }
350
- } else if (action.type == "Facets") {
366
+ } else if (action.type == 'Facets') {
351
367
  const facets = [];
352
368
  const facetsArray = getAnatomyTermsForFilters(action, this.availableNameCurieMapping);
353
369
  const filterValuesArray = intersectArrays(this.availableFacets, facetsArray);
354
370
  const filterValues = filterValuesArray.join(', ');
355
371
 
356
- this.settingsStore.facets.species.forEach(e => {
372
+ this.settingsStore.facets.species.forEach((e) => {
357
373
  facets.push({
358
374
  facet: capitalise(e),
359
- term: "Species",
360
- facetPropPath: "organisms.primary.species.name",
375
+ term: 'Species',
376
+ facetPropPath: 'organisms.primary.species.name',
361
377
  });
362
378
  });
363
379
  facets.push(
364
- ...filterValuesArray.map(val => ({
380
+ ...filterValuesArray.map((val) => ({
365
381
  facet: capitalise(val),
366
- term: "Anatomical structure",
367
- facetPropPath: "anatomy.organ.category.name",
368
- facetSubPropPath: "anatomy.organ.name",
369
- }))
382
+ term: 'Anatomical structure',
383
+ facetPropPath: 'anatomy.organ.category.name',
384
+ facetSubPropPath: 'anatomy.organ.name',
385
+ })),
370
386
  );
371
- this.openSearch(facets, "")
387
+ this.openSearch(facets, '');
372
388
  // GA Tagging
373
389
  // Event tracking for map action search/filter data
374
390
  Tagging.sendEvent({
375
- 'event': 'interaction_event',
376
- 'event_name': 'portal_maps_action_filter',
377
- 'category': filterValues || 'filter_reset',
378
- 'location': 'map_popup_button'
391
+ event: 'interaction_event',
392
+ event_name: 'portal_maps_action_filter',
393
+ category: filterValues || 'filter_reset',
394
+ location: 'map_popup_button',
379
395
  });
380
396
  this.filterTriggered = true;
381
397
  } else {
@@ -393,7 +409,7 @@ export default {
393
409
  if (type) categoryValues.push(type);
394
410
  if (datasetId) categoryValues.push('(' + id + ')');
395
411
  if (resource) {
396
- if (type === "Plot") {
412
+ if (type === 'Plot') {
397
413
  filePath = resource.dataSource.url;
398
414
  } else {
399
415
  filePath = typeof resource === 'string' ? resource : resource.share_link;
@@ -403,12 +419,12 @@ export default {
403
419
  // GA Tagging
404
420
  // Event tracking for map sidebar gallery click
405
421
  Tagging.sendEvent({
406
- 'event': 'interaction_event',
407
- 'event_name': 'portal_maps_gallery_click',
408
- 'category': categoryValues.join(' '),
409
- 'location': 'map_sidebar_gallery',
410
- 'dataset_id': id ? id + '' : '', // change to string format
411
- 'file_path': filePath,
422
+ event: 'interaction_event',
423
+ event_name: 'portal_maps_gallery_click',
424
+ category: categoryValues.join(' '),
425
+ location: 'map_sidebar_gallery',
426
+ dataset_id: id ? id + '' : '', // change to string format
427
+ file_path: filePath,
412
428
  });
413
429
  },
414
430
  onDisplaySearch: function (payload, tracking = true) {
@@ -417,7 +433,7 @@ export default {
417
433
  let splitdialog = this.$refs.splitdialog;
418
434
  if (splitdialog) {
419
435
  const activeContents = splitdialog.getActiveContents();
420
- activeContents.forEach(content => {
436
+ activeContents.forEach((content) => {
421
437
  if (content.search(payload.term)) {
422
438
  searchFound = true;
423
439
  }
@@ -429,29 +445,31 @@ export default {
429
445
  // GA Tagging
430
446
  // Event tracking for map on display search
431
447
  Tagging.sendEvent({
432
- 'event': 'interaction_event',
433
- 'event_name': 'portal_maps_display_search',
434
- 'category': payload.term,
435
- 'location': 'map_toolbar'
448
+ event: 'interaction_event',
449
+ event_name: 'portal_maps_display_search',
450
+ category: payload.term,
451
+ location: 'map_toolbar',
436
452
  });
437
453
  }
438
454
  },
439
- fetchSuggestions: function(payload) {
455
+ fetchSuggestions: function (payload) {
440
456
  const suggestions = [];
441
457
  //Search all active viewers when global callback is on
442
458
  let splitdialog = this.$refs.splitdialog;
443
459
  const activeContents = splitdialog.getActiveContents();
444
460
  //Push new suggestions into the pre-existing suggestions array
445
- activeContents.forEach(content => content.searchSuggestions(payload.data.term, suggestions));
461
+ activeContents.forEach((content) =>
462
+ content.searchSuggestions(payload.data.term, suggestions),
463
+ );
446
464
  const parsed = [];
447
465
  //Remove double quote as it is used as a speical character
448
- suggestions.forEach(suggestion => {
449
- parsed.push(suggestion.replaceAll("\"", ""));
466
+ suggestions.forEach((suggestion) => {
467
+ parsed.push(suggestion.replaceAll('"', ''));
450
468
  });
451
469
  const unique = new Set(parsed);
452
470
  suggestions.length = 0;
453
471
  for (const item of unique) {
454
- suggestions.push({"value": "\"" + item +"\""});
472
+ suggestions.push({ value: '"' + item + '"' });
455
473
  }
456
474
  payload.data.cb(suggestions);
457
475
  },
@@ -467,7 +485,7 @@ export default {
467
485
  // offset sidebar only on singlepanel and 2horpanel views
468
486
  EventBus.emit('show-connectivity', {
469
487
  featureIds: featureIds,
470
- offset: activeView === 'singlepanel' || activeView === '2horpanel'
488
+ offset: activeView === 'singlepanel' || activeView === '2horpanel',
471
489
  });
472
490
  }
473
491
  },
@@ -475,23 +493,25 @@ export default {
475
493
  // expand connectivity card and show connectivity info
476
494
  // if expanded exist, payload should be an array of one element
477
495
  // skip payload not match the expanded in multiple views
478
- const isMatched = payload.some(entry => entry.featureId[0] === this.expanded);
496
+ const isMatched = payload.some((entry) => entry.featureId[0] === this.expanded);
479
497
  if (this.expanded && this.connectivityExplorerClicked.length && !isMatched) {
480
498
  this.connectivityExplorerClicked.pop();
481
499
  return;
482
500
  }
483
501
 
484
502
  // Remove duplicate items from payload
485
- const uniquePayload = [...new Map(payload.map((entry) => [entry.featureId[0], entry])).values()];
486
- this.connectivityEntry = uniquePayload.map((entry) => {
503
+ const uniquePayload = [
504
+ ...new Map(payload.map((entry) => [entry.featureId[0], entry])).values(),
505
+ ];
506
+ const mappedPayload = uniquePayload.map((entry) => {
487
507
  let result = {
488
508
  ...entry,
489
509
  label: entry.title,
490
510
  id: entry.featureId[0],
491
- }
492
- const ck = this.connectivityKnowledge.find(ck => ck.id === result.id);
511
+ };
512
+ const ck = this.connectivityKnowledge.find((ck) => ck.id === result.id);
493
513
  if (entry.ready) {
494
- result['nerve-label'] = entry['nerve-label'] || ck['nerve-label'];
514
+ result['nerve-label'] = entry['nerve-label'] || (ck && ck['nerve-label']);
495
515
  }
496
516
  if (ck && ck['long-label']) {
497
517
  result['long-label'] = ck['long-label'];
@@ -502,13 +522,56 @@ export default {
502
522
  return result;
503
523
  });
504
524
 
525
+ // While restoring a permalink's sidebar connectivityEntries,
526
+ // popups from multiple active viewers (e.g. two MultiFlatmaps + a Scaffold)
527
+ // resolve concurrently and each only reports the subset it recognises.
528
+ // Merge by id (keeping richer fields like nerve-label/long-label, e.g. from a Scaffold viewer,
529
+ // if a later duplicate for the same id lacks them) instead of replacing,
530
+ // so entries resolved by one viewer are not dropped or downgraded when another viewer's payload arrives.
531
+ if (this.restoringConnectivityState) {
532
+ const merged = new Map(this.connectivityEntry.map((entry) => [entry.id, entry]));
533
+ mappedPayload.forEach((entry) => {
534
+ const existing = merged.get(entry.id);
535
+ merged.set(
536
+ entry.id,
537
+ existing
538
+ ? {
539
+ ...existing,
540
+ ...entry,
541
+ // Viewers emit placeholders (ready: false) before their resolved entries (ready: true),
542
+ // so a late placeholder from one viewer must not downgrade an entry another viewer already resolved.
543
+ ready: existing.ready || entry.ready,
544
+ 'nerve-label': entry['nerve-label'] || existing['nerve-label'],
545
+ 'long-label': entry['long-label'] || existing['long-label'],
546
+ 'expert-consultants':
547
+ entry['expert-consultants'] || existing['expert-consultants'],
548
+ }
549
+ : entry,
550
+ );
551
+ });
552
+ this.connectivityEntry = Array.from(merged.values());
553
+ // Entries resolve asynchronously via connectivity-info-open,
554
+ // which is not tied to connectivity-knowledge ticks, so re-run the restore completion check here.
555
+ // Otherwise the last entries becoming ready never re-evaluates allEntriesReady
556
+ // and the restore never finalises.
557
+ this.checkConnectivityRestoreComplete(this.state?.sidebar);
558
+ } else {
559
+ this.connectivityEntry = mappedPayload;
560
+ }
561
+
505
562
  // Fetch long-label from global connectivities if not exist in the payload,
506
563
  // this is for the case when user click on the flatmap paths/features directly without going through sidebar list,
507
- // which will only have id and label in the payload
508
- if (!this.connectivityEntry[0]['long-label'] && this.connectivityEntry[0].mapuuid) {
509
- const connectivityData = this.connectivitiesStore.globalConnectivities[this.connectivityEntry[0].mapuuid] || [];
564
+ // which will only have id and label in the payload.
565
+ // Skipped while restoring since connectivityEntry[0] may not relate to the entries just merged in above.
566
+ if (
567
+ !this.restoringConnectivityState &&
568
+ !this.connectivityEntry[0]['long-label'] &&
569
+ this.connectivityEntry[0].mapuuid
570
+ ) {
571
+ const connectivityData =
572
+ this.connectivitiesStore.globalConnectivities[this.connectivityEntry[0].mapuuid] || [];
510
573
  if (connectivityData.length) {
511
- const ck = connectivityData.find(ck => ck.id === this.connectivityEntry[0].id);
574
+ const ck = connectivityData.find((ck) => ck.id === this.connectivityEntry[0].id);
512
575
  if (ck && ck['long-label']) {
513
576
  this.connectivityEntry[0]['long-label'] = ck['long-label'];
514
577
  }
@@ -517,13 +580,13 @@ export default {
517
580
 
518
581
  if (this.connectivityExplorerClicked.length) {
519
582
  // only remove clicked if not placeholder entry
520
- if (this.connectivityEntry.every(entry => entry.ready)) {
583
+ if (this.connectivityEntry.every((entry) => entry.ready)) {
521
584
  this.connectivityExplorerClicked.pop();
522
585
  }
523
586
  } else {
524
587
  // click on the flatmap paths/features directly
525
588
  // or onDisplaySearch is performed
526
- const connectivityEntries = this.connectivityEntry.map(entry => entry.id);
589
+ const connectivityEntries = this.connectivityEntry.map((entry) => entry.id);
527
590
  const flatmapAPI = this.settingsStore.flatmapAPI;
528
591
  const { viewingMode } = this.settingsStore.globalSettings;
529
592
  const knowledgeSource = this.connectivityEntry[0].mapuuid || '';
@@ -532,11 +595,12 @@ export default {
532
595
 
533
596
  // fetch forward/backward connections on Neuron Connection mode
534
597
  if (viewingMode === 'Neuron Connection' && connectivityEntries.length && knowledgeSource) {
535
- forwardBackwardConnections = await queryForwardBackwardConnections(flatmapAPI, knowledgeSource, connectivityEntries);
536
- const allConnections = [
537
- ...connectivityEntries,
538
- ...forwardBackwardConnections,
539
- ];
598
+ forwardBackwardConnections = await queryForwardBackwardConnections(
599
+ flatmapAPI,
600
+ knowledgeSource,
601
+ connectivityEntries,
602
+ );
603
+ const allConnections = [...connectivityEntries, ...forwardBackwardConnections];
540
604
  const availableConnectivities = this.connectivitiesStore.getUniqueConnectivitiesByKeys;
541
605
  mappedConnections = allConnections.map((connId) => {
542
606
  return availableConnectivities.find((ac) => ac.id === connId);
@@ -547,12 +611,12 @@ export default {
547
611
  if (forwardBackwardConnections.length) {
548
612
  this.connectivityEntry = [];
549
613
  this.connectivityKnowledge = mappedConnections;
550
- this.connectivityHighlight = this.connectivityKnowledge.map(ck => ck.id);
614
+ this.connectivityHighlight = this.connectivityKnowledge.map((ck) => ck.id);
551
615
  this.connectivityProcessed = true;
552
616
  } else {
553
617
  this.connectivityKnowledge = this.connectivityEntry;
554
- if (this.connectivityKnowledge.every(ck => ck.ready)) {
555
- this.connectivityHighlight = this.connectivityKnowledge.map(ck => ck.id);
618
+ if (this.connectivityKnowledge.every((ck) => ck.ready)) {
619
+ this.connectivityHighlight = this.connectivityKnowledge.map((ck) => ck.id);
556
620
  this.connectivityProcessed = true;
557
621
  }
558
622
  }
@@ -567,7 +631,7 @@ export default {
567
631
  this.annotationEntry = payload.annotationEntry;
568
632
  // If drawing, `entry.models` may be undefined; use an empty array instead of [undefined]
569
633
  // to prevent errors on highlight
570
- this.annotationHighlight = this.annotationEntry.map(entry => entry.models).filter(Boolean);
634
+ this.annotationHighlight = this.annotationEntry.map((entry) => entry.models).filter(Boolean);
571
635
  if (payload.commitCallback) {
572
636
  this.annotationCallback = markRaw(payload.commitCallback);
573
637
  }
@@ -589,7 +653,7 @@ export default {
589
653
  this.confirmCommentCallback = markRaw(payload.confirmComment);
590
654
  }
591
655
  if (this.$refs.sideBar) {
592
- this.$refs.sideBar.tabClicked({id: 3, type: 'annotation'});
656
+ this.$refs.sideBar.tabClicked({ id: 3, type: 'annotation' });
593
657
  this.$refs.sideBar.setDrawerOpen(true);
594
658
  }
595
659
  },
@@ -609,74 +673,95 @@ export default {
609
673
  connectivityInfo: data.connectivityInfo,
610
674
  resource: data.entry,
611
675
  type: 'ConnectivityGraph',
612
- label: data.title || data.label || data.entry ||'Connectivity Graph',
676
+ label: data.title || data.label || data.entry || 'Connectivity Graph',
613
677
  graphPayload: { ...data },
614
678
  mapServer: this.settingsStore.flatmapAPI,
615
679
  sckanVersion: data.sckanVersion,
616
680
  });
617
681
 
618
- this.splitFlowStore.updateActiveView({
619
- view: '2vertpanel',
620
- entries: this.entries,
621
- }, false);
682
+ this.splitFlowStore.updateActiveView(
683
+ {
684
+ view: '2vertpanel',
685
+ entries: this.entries,
686
+ },
687
+ false,
688
+ );
622
689
 
623
690
  if (previousPrimaryId && previousPrimaryId !== connectivityGraphId) {
624
- this.splitFlowStore.assignOrSwapPaneWithIds({
625
- source: connectivityGraphId,
626
- target: previousPrimaryId,
627
- }, false);
691
+ this.splitFlowStore.assignOrSwapPaneWithIds(
692
+ {
693
+ source: connectivityGraphId,
694
+ target: previousPrimaryId,
695
+ },
696
+ false,
697
+ );
628
698
  }
629
699
 
630
700
  const secondPaneId = this.splitFlowStore.customLayout?.['pane-2']?.id;
631
701
  if (secondPaneId && secondPaneId !== connectivityGraphId) {
632
- this.splitFlowStore.assignOrSwapPaneWithIds({
633
- source: connectivityGraphId,
634
- target: secondPaneId,
635
- }, false);
702
+ this.splitFlowStore.assignOrSwapPaneWithIds(
703
+ {
704
+ source: connectivityGraphId,
705
+ target: secondPaneId,
706
+ },
707
+ false,
708
+ );
636
709
  }
637
710
  this.splitFlowStore.updateSplitPanels();
638
711
  },
639
712
  hoverChanged: function (data) {
640
- let hoverAnatomies = [], hoverOrgans = [], hoverDOI = '', hoverConnectivity = [];
713
+ let hoverAnatomies = [],
714
+ hoverOrgans = [],
715
+ hoverDOI = '',
716
+ hoverConnectivity = [];
641
717
  if (data.tabType === 'dataset') {
642
718
  hoverAnatomies = data.anatomy ? data.anatomy : [];
643
719
  hoverOrgans = data.organs ? data.organs : [];
644
720
  hoverDOI = data.doi ? data.doi : '';
645
721
  } else if (data.tabType === 'connectivity') {
646
722
  hoverConnectivity = data.id ? [data.id] : this.connectivityHighlight;
647
- hoverOrgans = data['nerve-label'] ? data['nerve-label'].flatMap(nerve => nerve.subNerves) : [];
723
+ hoverOrgans = data['nerve-label']
724
+ ? data['nerve-label'].flatMap((nerve) => nerve.subNerves)
725
+ : [];
648
726
  } else if (data.tabType === 'annotation') {
649
727
  hoverConnectivity = data.models ? [data.models] : this.annotationHighlight;
650
728
  }
651
- this.settingsStore.updateHoverFeatures(hoverAnatomies, hoverOrgans, hoverDOI, hoverConnectivity);
652
- EventBus.emit("hoverUpdate", { connectivityProcessed: this.connectivityProcessed });
729
+ this.settingsStore.updateHoverFeatures(
730
+ hoverAnatomies,
731
+ hoverOrgans,
732
+ hoverDOI,
733
+ hoverConnectivity,
734
+ );
735
+ EventBus.emit('hoverUpdate', { connectivityProcessed: this.connectivityProcessed });
653
736
  },
654
737
  searchChanged: function (data) {
655
738
  if (data.tabType === 'dataset') {
656
- if (data && data.type == "reset-update") {
739
+ if (data && data.type == 'reset-update') {
657
740
  this.settingsStore.updateAppliedFacets([]);
658
741
  }
659
- if (data && data.type == "query-update") {
742
+ if (data && data.type == 'query-update') {
660
743
  this.search = data.value;
661
744
  if (this.search && !this.filterTriggered) {
662
745
  // GA Tagging
663
746
  // Event tracking for map action search/filter data
664
747
  Tagging.sendEvent({
665
- 'event': 'interaction_event',
666
- 'event_name': 'portal_maps_action_search',
667
- 'category': this.search,
668
- 'location': 'map_sidebar_dataset_search'
748
+ event: 'interaction_event',
749
+ event_name: 'portal_maps_action_search',
750
+ category: this.search,
751
+ location: 'map_sidebar_dataset_search',
669
752
  });
670
753
  }
671
754
  this.filterTriggered = false; // reset for next action
672
755
  }
673
- if (data && data.type == "filter-update") {
756
+ if (data && data.type == 'filter-update') {
674
757
  this.settingsStore.updateFacets(data.value);
675
758
  // Remove filter event from maps' popup
676
759
  if (!this.filterTriggered) {
677
- const filterValuesArray = data.value.filter((val) => {
678
- return val.facet && val.facet.toLowerCase() !== 'show all';
679
- }).map((val) => val.facet);
760
+ const filterValuesArray = data.value
761
+ .filter((val) => {
762
+ return val.facet && val.facet.toLowerCase() !== 'show all';
763
+ })
764
+ .map((val) => val.facet);
680
765
  const labels = filterValuesArray.map((val) => val.toLowerCase());
681
766
  const newFacets = [...new Set([...labels])];
682
767
  this.settingsStore.updateAppliedFacets(newFacets);
@@ -684,16 +769,16 @@ export default {
684
769
  // GA Tagging
685
770
  // Event tracking for map action search/filter data
686
771
  Tagging.sendEvent({
687
- 'event': 'interaction_event',
688
- 'event_name': 'portal_maps_action_filter',
689
- 'category': filterValues || 'filter_reset',
690
- 'location': 'map_sidebar_dataset_filter'
772
+ event: 'interaction_event',
773
+ event_name: 'portal_maps_action_filter',
774
+ category: filterValues || 'filter_reset',
775
+ location: 'map_sidebar_dataset_filter',
691
776
  });
692
777
  }
693
778
  this.filterTriggered = false; // reset for next action
694
779
  }
695
780
  } else if (data.tabType === 'connectivity') {
696
- if (data && data.type == "reset-update") {
781
+ if (data && data.type == 'reset-update') {
697
782
  const activeFlatmaps = this.getActiveFlatmaps();
698
783
  activeFlatmaps.forEach((activeFlatmap) => {
699
784
  activeFlatmap.resetConnectivityfilters(data.value);
@@ -706,28 +791,29 @@ export default {
706
791
  activeFlatmaps.forEach((activeFlatmap) => {
707
792
  activeFlatmap.updateConnectivityFilters(data.filter);
708
793
  });
709
- EventBus.emit("connectivity-query-filter", data);
794
+ EventBus.emit('connectivity-query-filter', data);
710
795
 
711
- const filterValues = data.filter.filter(f => (f.facet && f.facet.toLowerCase() !== 'show all'))
796
+ const filterValues = data.filter
797
+ .filter((f) => f.facet && f.facet.toLowerCase() !== 'show all')
712
798
  .map((f) => f.tagLabel)
713
799
  .join(', ');
714
800
  const searchValue = data.query;
715
801
 
716
802
  if (filterValues) {
717
803
  Tagging.sendEvent({
718
- 'event': 'interaction_event',
719
- 'event_name': 'portal_maps_action_filter',
720
- 'category': filterValues,
721
- 'location': 'map_sidebar_connectivity_filter'
804
+ event: 'interaction_event',
805
+ event_name: 'portal_maps_action_filter',
806
+ category: filterValues,
807
+ location: 'map_sidebar_connectivity_filter',
722
808
  });
723
809
  }
724
810
 
725
811
  if (searchValue) {
726
812
  Tagging.sendEvent({
727
- 'event': 'interaction_event',
728
- 'event_name': 'portal_maps_action_search',
729
- 'category': searchValue,
730
- 'location': 'map_sidebar_connectivity_search'
813
+ event: 'interaction_event',
814
+ event_name: 'portal_maps_action_search',
815
+ category: searchValue,
816
+ location: 'map_sidebar_connectivity_search',
731
817
  });
732
818
  }
733
819
  }
@@ -735,14 +821,14 @@ export default {
735
821
  },
736
822
  updateMarkers: function (data) {
737
823
  this.settingsStore.updateMarkers(data);
738
- EventBus.emit("markerUpdate");
824
+ EventBus.emit('markerUpdate');
739
825
  },
740
826
  updateScaffoldMarkers: function (data) {
741
827
  this.settingsStore.updateNumberOfDatasetsForFacets(data);
742
828
  },
743
- getNewEntryId: function() {
829
+ getNewEntryId: function () {
744
830
  if (this.entries.length) {
745
- return (this.entries[this.entries.length - 1]).id + 1;
831
+ return this.entries[this.entries.length - 1].id + 1;
746
832
  }
747
833
  return 1;
748
834
  },
@@ -755,7 +841,7 @@ export default {
755
841
  newEntry.viewUrl = undefined;
756
842
  newEntry.state = undefined;
757
843
  Object.assign(newEntry, data);
758
- newEntry.mode = "normal";
844
+ newEntry.mode = 'normal';
759
845
  newEntry.id = this.getNewEntryId();
760
846
  newEntry.discoverId = data.discoverId;
761
847
  this.entriesStore.addNewEntry(newEntry);
@@ -771,7 +857,7 @@ export default {
771
857
  const entry = await getNewMapEntry(type, this.settingsStore.sparcApi);
772
858
  this.createNewEntry(entry);
773
859
  if (entry.contextCard) {
774
- EventBus.emit("contextUpdate", entry.contextCard);
860
+ EventBus.emit('contextUpdate', entry.contextCard);
775
861
  }
776
862
  },
777
863
  openSearch: function (facets, query) {
@@ -784,12 +870,12 @@ export default {
784
870
  this._facets = facets;
785
871
  if (this.$refs && this.$refs.sideBar) {
786
872
  this.$refs.sideBar.openSearch(facets, query);
787
- this.$refs.sideBar.tabClicked({id: 1, type: 'datasetExplorer'});
873
+ this.$refs.sideBar.tabClicked({ id: 1, type: 'datasetExplorer' });
788
874
  }
789
875
  this.startUp = false;
790
876
  },
791
877
  onFullscreen: function (val) {
792
- this.$emit("onFullscreen", val);
878
+ this.$emit('onFullscreen', val);
793
879
  },
794
880
  resetApp: function () {
795
881
  this.setState(initialDefaultState());
@@ -800,39 +886,118 @@ export default {
800
886
  restoreConnectivityEntries: function (connectivityEntries) {
801
887
  const activeFlatmaps = this.getActiveFlatmaps();
802
888
  activeFlatmaps.forEach((activeFlatmap) => {
803
- const featureIds = connectivityEntries.map((entry) => {
804
- const featureId = activeFlatmap.mapImp.modelFeatureIds(entry)[0];
805
- const feature = activeFlatmap.mapImp.featureProperties(featureId);
806
- const data = {
807
- resource: [feature.models],
808
- feature: feature,
809
- label: feature.label,
810
- provenanceTaxonomy: feature.taxons,
811
- alert: feature.alert,
812
- };
813
- return data;
814
- });
815
- activeFlatmap.checkAndCreatePopups(featureIds, true)
889
+ if (!activeFlatmap.mapImp) return;
890
+ const featureIds = connectivityEntries.reduce((acc, entry) => {
891
+ // A given flatmap may not contain every restored connectivity id
892
+ // (e.g. it belongs to another species/map); skip those instead of
893
+ // throwing, which would otherwise abort processing for this map
894
+ // entirely and leave its other entries stuck loading forever.
895
+ try {
896
+ const featureId = activeFlatmap.mapImp.modelFeatureIds(entry)[0];
897
+ if (!featureId) return acc;
898
+ const feature = activeFlatmap.mapImp.featureProperties(featureId);
899
+ if (!feature) return acc;
900
+ acc.push({
901
+ resource: [feature.models],
902
+ feature: feature,
903
+ label: feature.label,
904
+ provenanceTaxonomy: feature.taxons,
905
+ alert: feature.alert,
906
+ });
907
+ } catch (_error) {
908
+ // ignore entries that cannot be resolved in this map
909
+ }
910
+ return acc;
911
+ }, []);
912
+ if (featureIds.length) {
913
+ activeFlatmap.checkAndCreatePopups(featureIds, true);
914
+ }
816
915
  });
916
+
917
+ // Restore Scaffold-only connectivity entries (e.g. carrying nerve-label)
918
+ // via each Scaffold's own resolved knowledge, since Scaffolds aren't
919
+ // flatmaps and are otherwise never asked to restore their entries.
920
+ const activeScaffolds = this.getActiveScaffolds();
921
+ activeScaffolds.forEach((scaffold) => {
922
+ const matched = (scaffold.nervesKnowledge || []).filter((knowledge) =>
923
+ connectivityEntries.includes(knowledge.id),
924
+ );
925
+ if (matched.length) {
926
+ scaffold.getKnowledgeTooltip({ data: matched, type: scaffold.entry });
927
+ }
928
+ });
929
+ },
930
+ // Maps a sidebar activeTabId to the tab 'type' expected by tabClicked.
931
+ getSidebarTabType: function (tabId) {
932
+ if (tabId === 1) return 'datasetExplorer';
933
+ if (tabId === 3) return 'annotation';
934
+ return 'connectivityExplorer';
935
+ },
936
+ // Called once every restored connectivityEntry has resolved (or we've
937
+ // given up retrying): shows only the restored/filtered connectivities
938
+ // (not the full knowledge list) and opens the sidebar on the tab stored
939
+ // in the permalink state.
940
+ finalizeConnectivityRestore: function (sidebarState) {
941
+ this.restoringConnectivityState = false;
942
+ this.connectivityKnowledge = this.connectivityEntry;
943
+ if (this.connectivityEntry.every((entry) => entry.ready)) {
944
+ this.connectivityHighlight = this.connectivityEntry.map((entry) => entry.id);
945
+ this.connectivityProcessed = true;
946
+ }
947
+ if (this.$refs.sideBar) {
948
+ const tabId = sidebarState?.activeTabId ?? 2;
949
+ this.$refs.sideBar.tabClicked({ id: tabId, type: this.getSidebarTabType(tabId) });
950
+ this.$refs.sideBar.setDrawerOpen(true);
951
+ }
952
+ },
953
+ // Shared completion check for the connectivity restore loop:
954
+ // finalises the restore once every restored entry has resolved,
955
+ // or gives up after enough retry attempts (an id may never be resolvable by any active viewer).
956
+ // Called both from the connectivity-knowledge retry ticks and from openConnectivityInfo,
957
+ // since entries resolve asynchronously through connectivity-info-open rather than the knowledge events.
958
+ checkConnectivityRestoreComplete: function (sidebarState) {
959
+ if (this.sidebarStateRestored || !sidebarState?.connectivityEntries?.length) {
960
+ return;
961
+ }
962
+ // Multiple viewers resolve connectivity knowledge independently,
963
+ // so keep retrying until all restored entries have resolved.
964
+ // A becoming-active flatmap alone is not enough (its knowledge may still be loading),
965
+ // so only finalise on that basis after enough attempts
966
+ // to avoid getting stuck if an id can never be resolved by any viewer.
967
+ const allEntriesReady =
968
+ this.connectivityEntry.length >= sidebarState.connectivityEntries.length &&
969
+ this.connectivityEntry.every((entry) => entry.ready);
970
+ const giveUp = this.restoreAttempts >= 30;
971
+ if (allEntriesReady || giveUp) {
972
+ this.sidebarStateRestored = true;
973
+ this.finalizeConnectivityRestore(sidebarState);
974
+ }
817
975
  },
818
976
  restoreSidebarState: function (state) {
819
977
  // Restore sidebar state only if
820
978
  // - there is sidebar state
821
979
  // - sidebar component is loaded
822
- // - connectivity knowledge is loaded
823
980
  // - if sidebar state is not restored yet
824
981
  const sidebarState = state?.sidebar;
825
- if (!this.sidebarStateRestored && sidebarState && this.$refs.sideBar && this.connectivityKnowledge?.length) {
982
+ if (
983
+ !this.sidebarStateRestored &&
984
+ sidebarState &&
985
+ this.$refs.sideBar &&
986
+ this.connectivityKnowledge?.length
987
+ ) {
826
988
  if (sidebarState.connectivityEntries?.length) {
827
989
  this.restoreConnectivityEntries(sidebarState.connectivityEntries);
990
+ this.restoreAttempts += 1;
991
+ this.checkConnectivityRestoreComplete(sidebarState);
828
992
  } else if (sidebarState.annotationEntries?.length && state.annotationId) {
829
993
  // Restore annotation state only if the state has annotationId
830
994
  this.restoreConnectivityEntries(sidebarState.annotationEntries);
831
995
  this.sidebarAnnotationState = true;
996
+ this.sidebarStateRestored = true;
832
997
  } else {
833
998
  this.$refs.sideBar.setState(sidebarState);
999
+ this.sidebarStateRestored = true;
834
1000
  }
835
- this.sidebarStateRestored = true;
836
1001
  }
837
1002
  },
838
1003
  setState: function (state) {
@@ -840,9 +1005,23 @@ export default {
840
1005
  //Support both old and new permalink.
841
1006
  if (state.splitFlow) {
842
1007
  this.splitFlowStore.setState(state.splitFlow);
1008
+ } else {
1009
+ this.entries.forEach((entry) => this.splitFlowStore.setIdToPrimaryPane(entry.id));
843
1010
  }
844
- else {
845
- this.entries.forEach(entry => this.splitFlowStore.setIdToPrimaryPane(entry.id));
1011
+
1012
+ // Seed placeholder (not-ready) connectivity entries immediately so the
1013
+ // sidebar shows a loading count right away, instead of either an empty
1014
+ // list or a flash of the full/unfiltered connectivity knowledge while
1015
+ // the active viewers and their connectivity knowledge are still loading.
1016
+ if (state?.sidebar?.connectivityEntries?.length && !this.sidebarStateRestored) {
1017
+ this.restoringConnectivityState = true;
1018
+ this.connectivityEntry = state.sidebar.connectivityEntries.map((id) => ({
1019
+ id,
1020
+ label: '',
1021
+ featureId: [id],
1022
+ ready: false,
1023
+ }));
1024
+ this.connectivityKnowledge = this.connectivityEntry;
846
1025
  }
847
1026
 
848
1027
  this.restoreSidebarState(state);
@@ -857,12 +1036,10 @@ export default {
857
1036
  const entry = state.entries[i];
858
1037
  entry.state = dialogStates[i];
859
1038
  //We do not want to serialise the following properties
860
- if (entry.type === "Scaffold" && "viewUrl" in entry)
861
- delete entry.viewUrl;
862
- if (entry.type === "MultiFlatmap" && "uberonId" in entry)
863
- delete entry.uberonId;
1039
+ if (entry.type === 'Scaffold' && 'viewUrl' in entry) delete entry.viewUrl;
1040
+ if (entry.type === 'MultiFlatmap' && 'uberonId' in entry) delete entry.uberonId;
864
1041
  if (anonymousAnnotations === false) {
865
- if (entry.type === "Scaffold" && entry?.state?.offlineAnnotations) {
1042
+ if (entry.type === 'Scaffold' && entry?.state?.offlineAnnotations) {
866
1043
  delete entry.state.offlineAnnotations;
867
1044
  } else if (entry?.state?.state?.offlineAnnotations) {
868
1045
  delete entry.state.state.offlineAnnotations;
@@ -882,20 +1059,20 @@ export default {
882
1059
  this.entriesStore.destroyEntry(index);
883
1060
  },
884
1061
  resourceSelected: function (result) {
885
- this.$emit("resource-selected", result);
1062
+ this.$emit('resource-selected', result);
886
1063
  },
887
- speciesChanged: function (species) {
1064
+ speciesChanged: function (_species) {
888
1065
  if (this.$refs.sideBar) {
889
1066
  // Use to update the connectivity when switch species
890
1067
  // Wait for provenance info with uuid update
891
1068
  this.$nextTick(() => {
892
1069
  EventBus.emit('species-layout-connectivity-update');
893
1070
  this.$refs.sideBar.close();
894
- })
1071
+ });
895
1072
  }
896
1073
  },
897
1074
  contextUpdate: function (payload) {
898
- EventBus.emit("contextUpdate", payload);
1075
+ EventBus.emit('contextUpdate', payload);
899
1076
  },
900
1077
  datalinkClicked: function (payload) {
901
1078
  // payload is dataset URL
@@ -904,37 +1081,37 @@ export default {
904
1081
  const substringB = '?type=dataset';
905
1082
  const datasetId = datasetURL.substring(
906
1083
  datasetURL.indexOf(substringA) + substringA.length,
907
- datasetURL.indexOf(substringB)
1084
+ datasetURL.indexOf(substringB),
908
1085
  );
909
1086
 
910
1087
  // GA Tagging
911
1088
  // Event tracking for map sidebar gallery dataset click
912
1089
  Tagging.sendEvent({
913
- 'event': 'interaction_event',
914
- 'event_name': 'portal_maps_gallery_click',
915
- 'category': datasetURL,
916
- 'location': 'map_sidebar_gallery',
917
- 'dataset_id': datasetId || ''
1090
+ event: 'interaction_event',
1091
+ event_name: 'portal_maps_gallery_click',
1092
+ category: datasetURL,
1093
+ location: 'map_sidebar_gallery',
1094
+ dataset_id: datasetId || '',
918
1095
  });
919
1096
  },
920
- onAnnotationSubmitted: function(annotation) {
1097
+ onAnnotationSubmitted: function (annotation) {
921
1098
  if (this.annotationCallback) {
922
1099
  this.annotationCallback(annotation);
923
1100
  } else if (this.confirmCommentCallback) {
924
- this.confirmCommentCallback(annotation)
1101
+ this.confirmCommentCallback(annotation);
925
1102
  }
926
1103
  },
927
- onConfirmCreate: function(payload) {
1104
+ onConfirmCreate: function (payload) {
928
1105
  if (this.confirmCreateCallback) {
929
1106
  this.confirmCreateCallback(payload);
930
1107
  }
931
1108
  },
932
- onCancelCreate: function() {
1109
+ onCancelCreate: function () {
933
1110
  if (this.cancelCreateCallback) {
934
1111
  this.cancelCreateCallback();
935
1112
  }
936
1113
  },
937
- onConfirmDelete: function(payload) {
1114
+ onConfirmDelete: function (payload) {
938
1115
  if (this.confirmDeleteCallback) {
939
1116
  this.confirmDeleteCallback(payload);
940
1117
  }
@@ -954,7 +1131,7 @@ export default {
954
1131
  this.$refs.dialogToolbar.loadGlobalSettings();
955
1132
  },
956
1133
  onSidebarTabClosed: function (tab) {
957
- if (tab.id === 3 && tab.type === "annotation") {
1134
+ if (tab.id === 3 && tab.type === 'annotation') {
958
1135
  EventBus.emit('sidebar-annotation-close');
959
1136
  }
960
1137
  },
@@ -979,16 +1156,16 @@ export default {
979
1156
  this._externalStateSet = false;
980
1157
  },
981
1158
  mounted: function () {
982
- EventBus.on("CreateNewEntry", newView => {
1159
+ EventBus.on('CreateNewEntry', (newView) => {
983
1160
  this.createNewEntry(newView);
984
1161
  });
985
- EventBus.on("RemoveEntryRequest", id => {
1162
+ EventBus.on('RemoveEntryRequest', (id) => {
986
1163
  this.removeEntry(id);
987
1164
  });
988
- EventBus.on("PopoverActionClick", payload => {
1165
+ EventBus.on('PopoverActionClick', (payload) => {
989
1166
  this.actionClick(payload);
990
1167
  });
991
- EventBus.on('annotation-open', payload => {
1168
+ EventBus.on('annotation-open', (payload) => {
992
1169
  this.openAnnotation(payload);
993
1170
  });
994
1171
  EventBus.on('sidebar-annotation-close', () => {
@@ -1000,7 +1177,7 @@ export default {
1000
1177
  // open the anootation tab and return.
1001
1178
  if (this.sidebarAnnotationState && viewingMode === 'Annotation') {
1002
1179
  this.sidebarAnnotationState = false;
1003
- this.$refs.sideBar.tabClicked({id: 3, type: 'annotation'});
1180
+ this.$refs.sideBar.tabClicked({ id: 3, type: 'annotation' });
1004
1181
  return;
1005
1182
  }
1006
1183
 
@@ -1008,10 +1185,10 @@ export default {
1008
1185
  this.createData = {};
1009
1186
 
1010
1187
  if (this.$refs.sideBar) {
1011
- if (interactiveMode === "dataset") {
1012
- this.$refs.sideBar.tabClicked({id: 1, type: 'datasetExplorer'});
1013
- } else if (interactiveMode === "connectivity") {
1014
- this.$refs.sideBar.tabClicked({id: 2, type: 'connectivityExplorer'});
1188
+ if (interactiveMode === 'dataset') {
1189
+ this.$refs.sideBar.tabClicked({ id: 1, type: 'datasetExplorer' });
1190
+ } else if (interactiveMode === 'connectivity') {
1191
+ this.$refs.sideBar.tabClicked({ id: 2, type: 'connectivityExplorer' });
1015
1192
  }
1016
1193
 
1017
1194
  this.$refs.sideBar.closeConnectivity();
@@ -1021,21 +1198,21 @@ export default {
1021
1198
  EventBus.on('update-offline-annotation-enabled', (payload) => {
1022
1199
  this.settingsStore.updateOfflineAnnotationEnabled(payload);
1023
1200
  });
1024
- EventBus.on('connectivity-info-open', payload => {
1201
+ EventBus.on('connectivity-info-open', (payload) => {
1025
1202
  this.openConnectivityInfo(payload);
1026
1203
  });
1027
- EventBus.on('connectivity-info-close', payload => {
1204
+ EventBus.on('connectivity-info-close', (_payload) => {
1028
1205
  if (this.$refs.sideBar) {
1029
1206
  this.connectivityProcessed = false;
1030
1207
  this.$refs.sideBar.resetConnectivitySearch();
1031
1208
  }
1032
1209
  });
1033
- EventBus.on('connectivity-error', payload => {
1210
+ EventBus.on('connectivity-error', (payload) => {
1034
1211
  if (this.$refs.sideBar) {
1035
1212
  this.$refs.sideBar.updateConnectivityError(payload.data);
1036
1213
  }
1037
1214
  });
1038
- EventBus.on('neuron-connection-feature-click', payload => {
1215
+ EventBus.on('neuron-connection-feature-click', (payload) => {
1039
1216
  if (this.$refs.sideBar) {
1040
1217
  const { filters, search } = payload;
1041
1218
  this.$refs.sideBar.openConnectivitySearch(filters, search);
@@ -1043,36 +1220,44 @@ export default {
1043
1220
  this.$refs.sideBar.setDrawerOpen(true);
1044
1221
  }
1045
1222
  });
1046
- EventBus.on("OpenNewMap", type => {
1223
+ EventBus.on('OpenNewMap', (type) => {
1047
1224
  this.updateFlatmapMinimap();
1048
1225
  this.openNewMap(type);
1049
1226
  });
1050
- EventBus.on("startHelp", () => {
1227
+ EventBus.on('startHelp', () => {
1051
1228
  if (this.$refs.sideBar) {
1052
1229
  this.$refs.sideBar.close();
1053
1230
  }
1054
1231
  });
1055
- EventBus.on("connectivity-knowledge", payload => {
1056
- this.connectivityKnowledge = payload.data;
1057
- this.connectivityHighlight = payload.highlight;
1058
- this.connectivityProcessed = payload.processed;
1232
+ EventBus.on('connectivity-knowledge', (payload) => {
1233
+ // Ignore the default/global connectivity dump (emitted when no active
1234
+ // viewer's map/species keys are resolved yet) while restoring a
1235
+ // permalink's sidebar connectivityEntries, otherwise the sidebar would
1236
+ // briefly flash the entire, unfiltered connectivity list.
1237
+ if (!(payload.isDefaultKnowledge && this.restoringConnectivityState)) {
1238
+ this.connectivityKnowledge = payload.data;
1239
+ this.connectivityHighlight = payload.highlight;
1240
+ this.connectivityProcessed = payload.processed;
1241
+ }
1059
1242
 
1060
1243
  // Restore sidebar state if it exists and not restored yet
1061
1244
  // after loading connectivity knowledge
1062
- this.restoreSidebarState(this.state);
1063
- })
1064
- EventBus.on("modeUpdate", payload => {
1065
- if (payload === "dataset") {
1066
- this.$refs.sideBar.tabClicked({id: 1, type: 'datasetExplorer'});
1067
- } else if (payload === "connectivity") {
1068
- this.$refs.sideBar.tabClicked({id: 2, type: 'connectivityExplorer'});
1245
+ if (!this.sidebarStateRestored) {
1246
+ this.restoreSidebarState(this.state);
1247
+ }
1248
+ });
1249
+ EventBus.on('modeUpdate', (payload) => {
1250
+ if (payload === 'dataset') {
1251
+ this.$refs.sideBar.tabClicked({ id: 1, type: 'datasetExplorer' });
1252
+ } else if (payload === 'connectivity') {
1253
+ this.$refs.sideBar.tabClicked({ id: 2, type: 'connectivityExplorer' });
1069
1254
  }
1070
- })
1071
- EventBus.on("connectivity-filter-options", payload => {
1255
+ });
1256
+ EventBus.on('connectivity-filter-options', (payload) => {
1072
1257
  this.filterOptions = payload;
1073
- })
1258
+ });
1074
1259
  this.$nextTick(() => {
1075
- if (this.search === "" && this._facets.length === 0) {
1260
+ if (this.search === '' && this._facets.length === 0) {
1076
1261
  if (this.$refs.sideBar) {
1077
1262
  this.$refs.sideBar.close();
1078
1263
  }
@@ -1095,7 +1280,7 @@ export default {
1095
1280
  FLATMAPAPI_LOCATION: this.settingsStore.flatmapAPI,
1096
1281
  };
1097
1282
  },
1098
- entries: function() {
1283
+ entries: function () {
1099
1284
  return this.entriesStore.entries;
1100
1285
  },
1101
1286
  },
@@ -1139,5 +1324,4 @@ export default {
1139
1324
  }
1140
1325
  }
1141
1326
  }
1142
-
1143
1327
  </style>