@abi-software/map-side-bar 2.16.0 → 2.17.1-demo.0

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.
@@ -28,6 +28,7 @@
28
28
 
29
29
  <script>
30
30
  import EventBus from './EventBus';
31
+ import { capitalise as capitaliseText } from '../utils/common.js';
31
32
 
32
33
  export default {
33
34
  name: "ConnectivityCard",
@@ -78,8 +79,7 @@ export default {
78
79
  },
79
80
  methods: {
80
81
  capitalise: function (text) {
81
- if (text) return text.charAt(0).toUpperCase() + text.slice(1);
82
- return "";
82
+ return capitaliseText(text);
83
83
  },
84
84
  cardClicked: function (data) {
85
85
  if (!this.loading) {
@@ -97,7 +97,7 @@ export default {
97
97
  </script>
98
98
 
99
99
  <style lang="scss" scoped>
100
- @import '../assets/connectivity-explorer.scss';
100
+ @use '../assets/connectivity-explorer.scss';
101
101
 
102
102
  .connectivity-card {
103
103
  padding-left: 5px;
@@ -420,7 +420,7 @@ export default {
420
420
  this.openSearch([], '');
421
421
  this.$emit('search-changed', {
422
422
  value: [],
423
- tabType: 'dataset',
423
+ tabType: 'connectivity',
424
424
  type: 'reset-update',
425
425
  })
426
426
 
@@ -468,7 +468,7 @@ export default {
468
468
  if (notFoundItems.length) {
469
469
  this.$emit('search-changed', {
470
470
  value: notFoundItems,
471
- tabType: 'dataset',
471
+ tabType: 'connectivity',
472
472
  type: 'reset-update',
473
473
  })
474
474
  }
@@ -653,8 +653,8 @@ export default {
653
653
  </script>
654
654
 
655
655
  <style lang="scss" scoped>
656
- @import '../assets/searchPopover.scss';
657
- @import '../assets/pagination.scss';
656
+ @use '../assets/searchPopover.scss';
657
+ @use '../assets/pagination.scss';
658
658
 
659
659
  .connectivity-card {
660
660
  position: relative;
@@ -355,6 +355,7 @@ import {
355
355
  ExternalResourceCard,
356
356
  } from '@abi-software/map-utilities';
357
357
  import '@abi-software/map-utilities/dist/style.css';
358
+ import { capitalise, formatAlertText as formatAlertTextUtil, scrollToRef } from '../utils/common.js'
358
359
 
359
360
  const titleCase = (str) => {
360
361
  return str.replace(/\w\S*/g, (t) => {
@@ -362,11 +363,6 @@ const titleCase = (str) => {
362
363
  })
363
364
  }
364
365
 
365
- const capitalise = function (str) {
366
- if (str) return str.charAt(0).toUpperCase() + str.slice(1)
367
- return ''
368
- }
369
-
370
366
  export default {
371
367
  name: 'ConnectivityInfo',
372
368
  components: {
@@ -1157,49 +1153,10 @@ export default {
1157
1153
  EventBus.emit('trackEvent', data);
1158
1154
  },
1159
1155
  showAlertMessage: function () {
1160
- // scroll to alert message
1161
- this.$nextTick(() => {
1162
- const alertElement = this.$refs.alertElement;
1163
- if (alertElement) {
1164
- alertElement.scrollIntoView({
1165
- behavior: 'smooth',
1166
- block: 'start',
1167
- inline: 'nearest',
1168
- });
1169
- }
1170
- });
1156
+ scrollToRef(this, 'alertElement');
1171
1157
  },
1172
1158
  formatAlertText: function (text) {
1173
- if (!text) return '';
1174
- const escaped = text
1175
- .replace(/&/g, '&amp;')
1176
- .replace(/</g, '&lt;')
1177
- .replace(/>/g, '&gt;');
1178
- const linkified = escaped.replace(
1179
- /(https?:\/\/[^\s"<>\[]+)/g,
1180
- (url) => {
1181
- const parts = url.match(/^(.*?)([\].,;:!?]*)$/);
1182
- const cleanUrl = parts ? parts[1] : url;
1183
- const suffix = parts ? parts[2] : '';
1184
- return `<a href="${cleanUrl}" target="_blank" rel="noopener noreferrer">${cleanUrl}</a>${suffix}`;
1185
- }
1186
- );
1187
-
1188
- const normalised = linkified
1189
- .replace(/\\n/g, '\n')
1190
- .replace(/\r\n/g, '\n')
1191
- .replace(/\r/g, '\n');
1192
-
1193
- return normalised
1194
- .split('\n')
1195
- .map((line) => {
1196
- const withBoldLabel = line.replace(
1197
- /^\s*([A-Za-z][^:<]{0,120}:)/,
1198
- '<strong>$1</strong>'
1199
- );
1200
- return `<div class="alert-line">${withBoldLabel}</div>`;
1201
- })
1202
- .join('\n');
1159
+ return formatAlertTextUtil(text, { formatLines: true });
1203
1160
  },
1204
1161
  },
1205
1162
  mounted: function () {
@@ -1216,7 +1173,7 @@ export default {
1216
1173
  </script>
1217
1174
 
1218
1175
  <style lang="scss" scoped>
1219
- @import '../assets/connectivity-explorer.scss';
1176
+ @use '../assets/connectivity-explorer.scss';
1220
1177
 
1221
1178
  .connectivity-info-title {
1222
1179
  padding: 0;
@@ -57,7 +57,7 @@ export default {
57
57
  </script>
58
58
 
59
59
  <style lang="scss" scoped>
60
- @import '../assets/_variables.scss';
60
+ @use '../assets/_variables.scss';
61
61
 
62
62
  .contributor-item {
63
63
  display: inline;
@@ -67,7 +67,7 @@ export default {
67
67
  </style>
68
68
 
69
69
  <style lang="scss">
70
- @import '../assets/_variables.scss';
70
+ @use '../assets/_variables.scss';
71
71
 
72
72
  .contributor-popover.el-popover {
73
73
  font-family: $font-family;
@@ -217,6 +217,7 @@ export default {
217
217
  },
218
218
  createFlatmapItems: function () {
219
219
  if (this.entry.flatmaps) {
220
+ const flatmaps = []
220
221
  this.entry.flatmaps.forEach((flatmap) => {
221
222
  if (flatmap.associated_flatmap?.identifier) {
222
223
  const filePath = flatmap.dataset.path
@@ -245,7 +246,7 @@ export default {
245
246
  doi: this.entry.doi,
246
247
  version: this.version,
247
248
  }
248
- this.items['Flatmaps'].push({
249
+ flatmaps.push({
249
250
  id,
250
251
  title: baseName(filePath),
251
252
  type: 'Flatmap',
@@ -256,6 +257,7 @@ export default {
256
257
  })
257
258
  }
258
259
  })
260
+ this.items['Flatmaps'].push(...flatmaps.sort((a, b) => a.title.localeCompare(b.title)))
259
261
  }
260
262
  },
261
263
  createImageItems: function () {
@@ -276,6 +278,7 @@ export default {
276
278
  },
277
279
  createPlotItems: function () {
278
280
  if (this.entry.plots) {
281
+ const plots = []
279
282
  this.entry.plots.forEach((plot) => {
280
283
  const filePath = plot.dataset.path
281
284
  const id = plot.identifier
@@ -333,7 +336,7 @@ export default {
333
336
  discoverId: this.discoverId,
334
337
  version: this.version,
335
338
  }
336
- this.items['Plots'].push({
339
+ plots.push({
337
340
  id,
338
341
  title: baseName(filePath),
339
342
  type: 'Plot',
@@ -343,11 +346,14 @@ export default {
343
346
  mimetype,
344
347
  })
345
348
  })
349
+ this.items['Plots'].push(...plots.sort((a, b) => a.title.localeCompare(b.title)))
350
+
346
351
  }
347
352
  },
348
353
  createScaffoldItems: function () {
349
354
  if (this.entry.scaffolds) {
350
355
  let index = 0
356
+ const scaffolds = []
351
357
  this.entry.scaffolds.forEach((scaffold, i) => {
352
358
  const filePath = scaffold.dataset.path
353
359
  const id = scaffold.identifier
@@ -381,7 +387,7 @@ export default {
381
387
  s3uri: this.entry.s3uri,
382
388
  contextCardUrl: this.getContextCardUrl(i),
383
389
  }
384
- this.items['Scaffolds'].push({
390
+ scaffolds.push({
385
391
  id,
386
392
  title: baseName(filePath),
387
393
  type: 'Scaffold',
@@ -391,10 +397,12 @@ export default {
391
397
  mimetype,
392
398
  })
393
399
  })
400
+ this.items['Scaffolds'].push(...scaffolds.sort((a, b) => a.title.localeCompare(b.title)))
394
401
  }
395
402
  },
396
403
  createSimulationItems: function () {
397
404
  if (this.entry.simulation) {
405
+ const simulations = []
398
406
  this.entry.simulation.forEach((simulation) => {
399
407
  if (simulation.additional_mimetype.name === "application/x.vnd.abi.simulation+json") {
400
408
  let action = {
@@ -409,7 +417,7 @@ export default {
409
417
  discoverId: this.discoverId,
410
418
  dataset: `${this.envVars.ROOT_URL}/datasets/${this.discoverId}?type=dataset`,
411
419
  }
412
- this.items['Simulations'].push({
420
+ simulations.push({
413
421
  id: 'simulation',
414
422
  title: ' ',
415
423
  type: 'Simulation',
@@ -448,7 +456,7 @@ export default {
448
456
  doi: this.entry.doi,
449
457
  version: this.version,
450
458
  }
451
- this.items['Simulations'].push({
459
+ simulations.push({
452
460
  id,
453
461
  title: baseName(filePath),
454
462
  type: 'Simulation',
@@ -459,6 +467,7 @@ export default {
459
467
  })
460
468
  }
461
469
  })
470
+ this.items['Simulations'].push(...simulations.sort((a, b) => a.title.localeCompare(b.title)))
462
471
  }
463
472
  },
464
473
  createVideoItems: function () {
@@ -573,8 +573,8 @@ export default {
573
573
  </script>
574
574
 
575
575
  <style lang="scss" scoped>
576
- @import '../assets/searchPopover.scss';
577
- @import '../assets/pagination.scss';
576
+ @use '../assets/searchPopover.scss';
577
+ @use '../assets/pagination.scss';
578
578
 
579
579
  .dataset-card {
580
580
  position: relative;
@@ -157,10 +157,7 @@ import '@abi-software/svg-sprite/dist/style.css'
157
157
  import { AlgoliaClient } from '../algolia/algolia.js'
158
158
  import { facetPropPathMapping } from '../algolia/utils.js'
159
159
  import EventBus from './EventBus.js'
160
-
161
- const capitalise = function (txt) {
162
- return txt.charAt(0).toUpperCase() + txt.slice(1)
163
- }
160
+ import { capitalise } from '../utils/common.js'
164
161
 
165
162
  const convertReadableLabel = function (original) {
166
163
  const name = original.toLowerCase()
@@ -208,6 +205,10 @@ export default {
208
205
  showFiltersText: true,
209
206
  cascadeSelected: [],
210
207
  cascadeSelectedWithBoolean: [],
208
+ // Re-entrancy lock: el-cascader can echo an extra @change
209
+ // after we reassign cascadeSelected in setCascader;
210
+ // stays locked until the filterTimeout debounce below unlocks it.
211
+ cascadeEventLocked: false,
211
212
  filterTimeout: null,
212
213
  numberShown: 10,
213
214
  filters: [],
@@ -258,7 +259,6 @@ export default {
258
259
  if (this.options.length) {
259
260
  this.checkShowAllBoxes()
260
261
  // this.setCascader(this.entry.filterFacets)
261
- this.cssMods()
262
262
  this.$emit('cascaderReady')
263
263
  }
264
264
  })
@@ -288,18 +288,22 @@ export default {
288
288
  return value;
289
289
  },
290
290
  createChildrenCascaderValue: function(children, facet, facets) {
291
+ const parentKey = facet.key;
292
+
291
293
  if (children?.length) {
292
294
  for (let i = 0; i < children.length; i++) {
293
295
  const facetItem = children[i];
294
296
  //copy the facets into
295
297
  if (children[i].facetPropPath !== 'supportingAwards.consortium.name') {
296
- children[i].label = convertReadableLabel(
297
- facetItem.label
298
- )
298
+ // `sourceNomenclatureLabel` is from cell card explorer filters
299
+ if (parentKey === 'sourceNomenclatureLabel') {
300
+ children[i].label = facetItem.label;
301
+ } else {
302
+ children[i].label = convertReadableLabel(facetItem.label);
303
+ }
299
304
  }
300
- if (facetItem.key && facet.key.includes('flatmap.connectivity.source.')) {
305
+ if (facetItem.key && parentKey.includes('flatmap.connectivity.source.')) {
301
306
  const childKey = facetItem.key;
302
- const parentKey = facet.key;
303
307
  const key = childKey.replace(`${parentKey}.`, '');
304
308
  children[i].value = this.createCascaderItemValue([facet.label, key]);
305
309
  } else {
@@ -570,6 +574,12 @@ export default {
570
574
  },
571
575
  // cascadeEvent: initiate searches based off cascader changes
572
576
  cascadeEvent: function (eventIn) {
577
+ // Ignore the echoed @change el-cascader fires
578
+ // after we reassign cascadeSelected in setCascader
579
+ if (this.cascadeEventLocked) {
580
+ return
581
+ }
582
+ this.cascadeEventLocked = true
573
583
  let event = [...eventIn]
574
584
  if (event) {
575
585
  // Check for show all in selected cascade options
@@ -667,7 +677,7 @@ export default {
667
677
 
668
678
  this.filterTimeout = setTimeout(() => {
669
679
  this.$emit('filterResults', filters) // emit filters for apps above sidebar
670
- this.cssMods() // update css for the cascader
680
+ this.cascadeEventLocked = false // safe to process real cascader changes again
671
681
  }, 600);
672
682
  }
673
683
  },
@@ -775,7 +785,6 @@ export default {
775
785
  this.updateListFilters(this.__expandItem__[0])
776
786
  }
777
787
  this.updateListStyleOrder()
778
- this.cssMods()
779
788
  },
780
789
  updateListStyleOrder: function () {
781
790
  this.$nextTick(() => {
@@ -954,30 +963,6 @@ export default {
954
963
  })
955
964
  )
956
965
  },
957
- makeCascadeLabelsClickable: function () {
958
- // Next tick allows the cascader menu to change
959
- this.$nextTick(() => {
960
- document
961
- .querySelectorAll('.sidebar-cascader-popper .el-cascader-node__label')
962
- .forEach((el) => {
963
- // step through each cascade label
964
- el.onclick = function () {
965
- const checkbox = this.previousElementSibling
966
- if (checkbox) {
967
- if (!checkbox.parentElement.attributes['aria-owns']) {
968
- // check if we are at the lowest level of cascader
969
- this.previousElementSibling.click() // Click the checkbox
970
- }
971
- }
972
- }
973
- })
974
- })
975
- },
976
-
977
- cssMods: function () {
978
- this.makeCascadeLabelsClickable()
979
- },
980
-
981
966
  flattenToFilters: function (filter, targetOption, previousMatched = false) {
982
967
  let filtersArray = []
983
968
 
@@ -1142,7 +1127,6 @@ export default {
1142
1127
  this.cascaderIsReady = true
1143
1128
  this.checkShowAllBoxes()
1144
1129
  this.setCascader(this.entry.filterFacets)
1145
- this.cssMods()
1146
1130
  this.$emit('cascaderReady')
1147
1131
  })
1148
1132
  },
@@ -147,23 +147,10 @@ import {
147
147
  } from 'element-plus'
148
148
 
149
149
  import EventBus from './EventBus.js'
150
+ import { generateUUID } from '../utils/common.js';
150
151
 
151
152
  const MAX_SEARCH_HISTORY = 12;
152
153
 
153
- function generateUUID() {
154
- const arr = new Uint8Array(16);
155
- window.crypto.getRandomValues(arr);
156
-
157
- arr[6] = (arr[6] & 0x0f) | 0x40;
158
- arr[8] = (arr[8] & 0x3f) | 0x80;
159
-
160
- const hex = Array.from(arr)
161
- .map(byte => byte.toString(16).padStart(2, '0'))
162
- .join('');
163
-
164
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
165
- }
166
-
167
154
  export default {
168
155
  name: 'SearchHistory',
169
156
  components: {
@@ -347,7 +334,8 @@ export default {
347
334
  getParentComponentName: function () {
348
335
  const isConnectivity = this.localStorageKey?.indexOf('connectivity') !== -1;
349
336
  const isDataset = this.localStorageKey?.indexOf('dataset') !== -1;
350
- const location = isConnectivity ? 'connectivity' : isDataset ? 'dataset' : '';
337
+ const isCellCard = this.localStorageKey?.indexOf('cell-card') !== -1;
338
+ const location = isConnectivity ? 'connectivity' : isDataset ? 'dataset' : isCellCard ? 'cell_card' : '';
351
339
  return location;
352
340
  },
353
341
  search: function (item) {
@@ -65,6 +65,20 @@
65
65
  @connectivity-item-close="onConnectivityItemClose"
66
66
  />
67
67
  </template>
68
+ <template v-else-if="tab.type === 'cellCardExplorer' && showCellCards">
69
+ <CellCardExplorer
70
+ :ref="'cellCardExplorerTab_' + tab.id"
71
+ class="sidebar-content-container"
72
+ v-show="tab.id === activeTabId"
73
+ :envVars="envVars"
74
+ :activeSpecies="activeSpeciesForEntries"
75
+ @search-changed="searchChanged(tab.id, $event)"
76
+ @dataset-search="openDatasetSearchFromCellCard($event)"
77
+ @connectivity-search="openConnectivitySearch($event.facets, $event.query)"
78
+ @soma-location-hovered="showSomaLocation"
79
+ @soma-locations-ready="onSomaLocationsReady"
80
+ />
81
+ </template>
68
82
  <template v-else>
69
83
  <DatasetExplorer
70
84
  class="sidebar-content-container"
@@ -95,6 +109,7 @@ import EventBus from './EventBus.js'
95
109
  import Tabs from './Tabs.vue'
96
110
  import AnnotationTool from './AnnotationTool.vue'
97
111
  import ConnectivityExplorer from './ConnectivityExplorer.vue'
112
+ import CellCardExplorer from './CellCardExplorer.vue'
98
113
  import { removeShowAllFacets } from '../algolia/utils.js'
99
114
 
100
115
  /**
@@ -110,6 +125,7 @@ export default {
110
125
  Icon,
111
126
  AnnotationTool,
112
127
  ConnectivityExplorer,
128
+ CellCardExplorer,
113
129
  },
114
130
  name: 'SideBar',
115
131
  props: {
@@ -119,6 +135,7 @@ export default {
119
135
  { title: 'Dataset Explorer', id: 1, type: 'datasetExplorer', closable: false },
120
136
  { title: 'Connectivity Explorer', id: 2, type: 'connectivityExplorer', closable: false },
121
137
  { title: 'Annotation', id: 3, type: 'annotation', closable: true },
138
+ { title: 'Cell Card Explorer', id: 4, type: 'cellCardExplorer', closable: false },
122
139
  ],
123
140
  },
124
141
  /**
@@ -184,6 +201,10 @@ export default {
184
201
  type: Boolean,
185
202
  default: false,
186
203
  },
204
+ showCellCards: {
205
+ type: Boolean,
206
+ default: false,
207
+ },
187
208
  },
188
209
  data: function () {
189
210
  return {
@@ -192,12 +213,17 @@ export default {
192
213
  activeTabId: 1,
193
214
  activeAnnotationData: { tabType: "annotation" },
194
215
  activeConnectivityData: { tabType: "connectivity" },
216
+ activeSpeciesForEntries: [],
195
217
  state: {
196
218
  dataset: {
197
219
  search: '',
198
220
  filters: [],
199
221
  },
200
- connectivity: {
222
+ connectivity: {
223
+ search: '',
224
+ filters: [],
225
+ },
226
+ cellCards: {
201
227
  search: '',
202
228
  filters: [],
203
229
  },
@@ -235,6 +261,16 @@ export default {
235
261
  this.activeAnnotationData = data;
236
262
  }
237
263
  },
264
+ /**
265
+ * This event is emitted when the mouse hover on or off a soma location in cell card explorer.
266
+ * @param name Soma location
267
+ */
268
+ showSomaLocation: function (name) {
269
+ this.$emit('soma-location-hovered', name);
270
+ },
271
+ onSomaLocationsReady: function (somaLocations) {
272
+ this.$emit('soma-locations-ready', somaLocations);
273
+ },
238
274
  /**
239
275
  * This event is emitted when the show connectivity button is clicked.
240
276
  * @arg featureIds
@@ -299,6 +335,42 @@ export default {
299
335
  datasetExplorerTabRef.openSearch(facets, query);
300
336
  })
301
337
  },
338
+ openCellCardExplorerSearch: function (filters, query) {
339
+ this.drawerOpen = true
340
+ // Because refs are in v-for, nextTick is needed here
341
+ this.$nextTick(() => {
342
+ const cellCardExplorerTabRef = this.getTabRef(undefined, 'cellCardExplorer', true);
343
+ if (cellCardExplorerTabRef && typeof cellCardExplorerTabRef.openSearch === 'function') {
344
+ cellCardExplorerTabRef.openSearch(filters, query);
345
+ }
346
+ })
347
+ },
348
+ openDatasetSearchFromCellCard: function (payload) {
349
+ if (!payload || typeof payload !== 'object') {
350
+ this.openSearch([], payload);
351
+ return;
352
+ }
353
+
354
+ const facets = [];
355
+ if (payload.species) {
356
+ facets.push({
357
+ facet: payload.species,
358
+ term: 'Species',
359
+ facetPropPath: 'organisms.primary.species.name',
360
+ });
361
+ }
362
+
363
+ if (payload.location) {
364
+ facets.push({
365
+ facet: payload.location,
366
+ term: 'Anatomical structure',
367
+ facetPropPath: 'anatomy.organ.category.name',
368
+ AND: true,
369
+ });
370
+ }
371
+
372
+ this.openSearch(facets, '');
373
+ },
302
374
  /**
303
375
  * Get the ref id of the tab by id and type.
304
376
  */
@@ -437,6 +509,13 @@ export default {
437
509
  this.state.connectivity.filters = connectivityExplorerTabRef.getFilters();
438
510
  }
439
511
 
512
+ // Update cell card explorer state if available
513
+ const cellCardExplorerTabRef = this.getTabRef(undefined, 'cellCardExplorer');
514
+ if (cellCardExplorerTabRef) {
515
+ this.state.cellCards.search = cellCardExplorerTabRef.getSearch();
516
+ this.state.cellCards.filters = cellCardExplorerTabRef.getFilters();
517
+ }
518
+
440
519
  // Update connectivity entries if available
441
520
  if (this.connectivityEntry && this.connectivityEntry.length > 0) {
442
521
  this.state.connectivityEntries = this.connectivityEntry.map((entry) => entry.id);
@@ -466,11 +545,15 @@ export default {
466
545
  setState: function (state) {
467
546
  // if state is not provided or formatted incorrectly, do nothing
468
547
  if (!state || !state.dataset || !state.connectivity) return;
469
- this.state = JSON.parse(JSON.stringify(state)); // deep copy to avoid reference issues
470
- const datasetFilters = state.dataset.filters;
471
- const connectivityFilters = state.connectivity.filters;
472
- const datasetSearch = state.dataset.search;
473
- const connectivitySearch = state.connectivity.search;
548
+ // Create a deep copy while preserving the original state structure for future extensibility
549
+ const newState = JSON.parse(JSON.stringify(state));
550
+ this.state = { ...this.state, ...newState };
551
+ const datasetFilters = this.state.dataset.filters;
552
+ const connectivityFilters = this.state.connectivity.filters;
553
+ const datasetSearch = this.state.dataset.search;
554
+ const connectivitySearch = this.state.connectivity.search;
555
+ const cellCardFilters = this.state.cellCards.filters || [];
556
+ const cellCardSearch = this.state.cellCards.search || '';
474
557
 
475
558
  if (datasetFilters.length || datasetSearch) {
476
559
  this.openSearch(datasetFilters, datasetSearch);
@@ -480,6 +563,10 @@ export default {
480
563
  this.openConnectivitySearch(connectivityFilters, connectivitySearch);
481
564
  }
482
565
 
566
+ if (cellCardFilters.length || cellCardSearch) {
567
+ this.openCellCardExplorerSearch(cellCardFilters, cellCardSearch);
568
+ }
569
+
483
570
  if (state.activeTabId) {
484
571
  this.$nextTick(() => {
485
572
  const hasTab = this.tabEntries.find((t) => t.id === state.activeTabId);
@@ -501,7 +588,17 @@ export default {
501
588
  ...data,
502
589
  };
503
590
  this.$emit('trackEvent', taggingData);
504
- }
591
+ },
592
+ /**
593
+ * @public
594
+ * Update the active species to use in filters.
595
+ * Used by SplitFlow component.
596
+ * @param activeSpecies
597
+ */
598
+ updateActiveSpeciesForEntries: function (activeSpecies) {
599
+ const speciesEntries = Array.isArray(activeSpecies) ? activeSpecies : [activeSpecies];
600
+ this.activeSpeciesForEntries = [...new Set(speciesEntries.filter(Boolean))];
601
+ },
505
602
  },
506
603
  computed: {
507
604
  // This should respect the information provided by the property
@@ -513,6 +610,10 @@ export default {
513
610
  tab.type === "annotation" &&
514
611
  this.annotationEntry &&
515
612
  this.annotationEntry.length > 0
613
+ ) ||
614
+ (
615
+ tab.type === "cellCardExplorer" &&
616
+ this.showCellCards
516
617
  )
517
618
  );
518
619
  },
@@ -4,7 +4,7 @@
4
4
  class="tab"
5
5
  v-for="tab in tabs"
6
6
  :key="tab.id"
7
- :class="{ 'active-tab': tab.id == activeId }"
7
+ :class="{ 'active-tab': tab.id == activeId, 'closable-tab': tab.closable }"
8
8
  @click="tabClicked(tab)"
9
9
  >
10
10
  <span class="tab-title">{{ tab.title }} </span>
@@ -103,14 +103,23 @@ export default {
103
103
  .tab-title {
104
104
  text-align: center;
105
105
  font-size: 14px;
106
- padding: 0 1rem;
106
+ padding: 0 0.75rem;
107
+
108
+ .closable-tab & {
109
+ padding-right: 0.25rem;
110
+ }
107
111
  }
108
112
 
109
113
  .tab-close-icon {
110
- width: 20px !important;
111
- height: 20px !important;
112
- font-size: 20px !important;
113
- padding-right: 4px !important;
114
+ width: 18px !important;
115
+ height: 18px !important;
116
+ font-size: 18px !important;
117
+ margin-right: 4px !important;
114
118
  color: $app-primary-color !important;
119
+ border-radius: 2px;
120
+
121
+ &:hover {
122
+ background-color: rgba($app-primary-color, 0.15) !important;
123
+ }
115
124
  }
116
125
  </style>