@abi-software/map-side-bar 2.14.7 → 2.14.8-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) {
@@ -330,6 +330,7 @@ import {
330
330
  ExternalResourceCard,
331
331
  } from '@abi-software/map-utilities';
332
332
  import '@abi-software/map-utilities/dist/style.css';
333
+ import { capitalise, formatAlertText as formatAlertTextUtil, scrollToRef } from '../utils/common.js'
333
334
 
334
335
  const titleCase = (str) => {
335
336
  return str.replace(/\w\S*/g, (t) => {
@@ -337,11 +338,6 @@ const titleCase = (str) => {
337
338
  })
338
339
  }
339
340
 
340
- const capitalise = function (str) {
341
- if (str) return str.charAt(0).toUpperCase() + str.slice(1)
342
- return ''
343
- }
344
-
345
341
  export default {
346
342
  name: 'ConnectivityInfo',
347
343
  components: {
@@ -933,49 +929,10 @@ export default {
933
929
  EventBus.emit('trackEvent', data);
934
930
  },
935
931
  showAlertMessage: function () {
936
- // scroll to alert message
937
- this.$nextTick(() => {
938
- const alertElement = this.$refs.alertElement;
939
- if (alertElement) {
940
- alertElement.scrollIntoView({
941
- behavior: 'smooth',
942
- block: 'start',
943
- inline: 'nearest',
944
- });
945
- }
946
- });
932
+ scrollToRef(this, 'alertElement');
947
933
  },
948
934
  formatAlertText: function (text) {
949
- if (!text) return '';
950
- const escaped = text
951
- .replace(/&/g, '&amp;')
952
- .replace(/</g, '&lt;')
953
- .replace(/>/g, '&gt;');
954
- const linkified = escaped.replace(
955
- /(https?:\/\/[^\s"<>\[]+)/g,
956
- (url) => {
957
- const parts = url.match(/^(.*?)([\].,;:!?]*)$/);
958
- const cleanUrl = parts ? parts[1] : url;
959
- const suffix = parts ? parts[2] : '';
960
- return `<a href="${cleanUrl}" target="_blank" rel="noopener noreferrer">${cleanUrl}</a>${suffix}`;
961
- }
962
- );
963
-
964
- const normalised = linkified
965
- .replace(/\\n/g, '\n')
966
- .replace(/\r\n/g, '\n')
967
- .replace(/\r/g, '\n');
968
-
969
- return normalised
970
- .split('\n')
971
- .map((line) => {
972
- const withBoldLabel = line.replace(
973
- /^\s*([A-Za-z][^:<]{0,120}:)/,
974
- '<strong>$1</strong>'
975
- );
976
- return `<div class="alert-line">${withBoldLabel}</div>`;
977
- })
978
- .join('\n');
935
+ return formatAlertTextUtil(text, { formatLines: true });
979
936
  },
980
937
  },
981
938
  mounted: function () {
@@ -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()
@@ -288,18 +285,22 @@ export default {
288
285
  return value;
289
286
  },
290
287
  createChildrenCascaderValue: function(children, facet, facets) {
288
+ const parentKey = facet.key;
289
+
291
290
  if (children?.length) {
292
291
  for (let i = 0; i < children.length; i++) {
293
292
  const facetItem = children[i];
294
293
  //copy the facets into
295
294
  if (children[i].facetPropPath !== 'supportingAwards.consortium.name') {
296
- children[i].label = convertReadableLabel(
297
- facetItem.label
298
- )
295
+ // `sourceNomenclatureLabel` is from cell card explorer filters
296
+ if (parentKey === 'sourceNomenclatureLabel') {
297
+ children[i].label = facetItem.label;
298
+ } else {
299
+ children[i].label = convertReadableLabel(facetItem.label);
300
+ }
299
301
  }
300
- if (facetItem.key && facet.key.includes('flatmap.connectivity.source.')) {
302
+ if (facetItem.key && parentKey.includes('flatmap.connectivity.source.')) {
301
303
  const childKey = facetItem.key;
302
- const parentKey = facet.key;
303
304
  const key = childKey.replace(`${parentKey}.`, '');
304
305
  children[i].value = this.createCascaderItemValue([facet.label, key]);
305
306
  } else {
@@ -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: {
@@ -65,6 +65,19 @@
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
+ @dataset-search="openDatasetSearchFromCellCard($event)"
76
+ @connectivity-search="openConnectivitySearch($event.facets, $event.query)"
77
+ @soma-location-hovered="showSomaLocation"
78
+ @soma-locations-ready="onSomaLocationsReady"
79
+ />
80
+ </template>
68
81
  <template v-else>
69
82
  <DatasetExplorer
70
83
  class="sidebar-content-container"
@@ -95,6 +108,7 @@ import EventBus from './EventBus.js'
95
108
  import Tabs from './Tabs.vue'
96
109
  import AnnotationTool from './AnnotationTool.vue'
97
110
  import ConnectivityExplorer from './ConnectivityExplorer.vue'
111
+ import CellCardExplorer from './CellCardExplorer.vue'
98
112
  import { removeShowAllFacets } from '../algolia/utils.js'
99
113
 
100
114
  /**
@@ -110,6 +124,7 @@ export default {
110
124
  Icon,
111
125
  AnnotationTool,
112
126
  ConnectivityExplorer,
127
+ CellCardExplorer,
113
128
  },
114
129
  name: 'SideBar',
115
130
  props: {
@@ -119,6 +134,7 @@ export default {
119
134
  { title: 'Dataset Explorer', id: 1, type: 'datasetExplorer', closable: false },
120
135
  { title: 'Connectivity Explorer', id: 2, type: 'connectivityExplorer', closable: false },
121
136
  { title: 'Annotation', id: 3, type: 'annotation', closable: true },
137
+ { title: 'Cell Card Explorer', id: 4, type: 'cellCardExplorer', closable: false },
122
138
  ],
123
139
  },
124
140
  /**
@@ -184,6 +200,10 @@ export default {
184
200
  type: Boolean,
185
201
  default: false,
186
202
  },
203
+ showCellCards: {
204
+ type: Boolean,
205
+ default: false,
206
+ },
187
207
  },
188
208
  data: function () {
189
209
  return {
@@ -192,6 +212,7 @@ export default {
192
212
  activeTabId: 1,
193
213
  activeAnnotationData: { tabType: "annotation" },
194
214
  activeConnectivityData: { tabType: "connectivity" },
215
+ activeSpeciesForEntries: [],
195
216
  state: {
196
217
  dataset: {
197
218
  search: '',
@@ -235,6 +256,16 @@ export default {
235
256
  this.activeAnnotationData = data;
236
257
  }
237
258
  },
259
+ /**
260
+ * This event is emitted when the mouse hover on or off a soma location in cell card explorer.
261
+ * @param name Soma location
262
+ */
263
+ showSomaLocation: function (name) {
264
+ this.$emit('soma-location-hovered', name);
265
+ },
266
+ onSomaLocationsReady: function (somaLocations) {
267
+ this.$emit('soma-locations-ready', somaLocations);
268
+ },
238
269
  /**
239
270
  * This event is emitted when the show connectivity button is clicked.
240
271
  * @arg featureIds
@@ -299,6 +330,42 @@ export default {
299
330
  datasetExplorerTabRef.openSearch(facets, query);
300
331
  })
301
332
  },
333
+ openCellCardExplorerSearch: function (filters, query) {
334
+ this.drawerOpen = true
335
+ // Because refs are in v-for, nextTick is needed here
336
+ this.$nextTick(() => {
337
+ const cellCardExplorerTabRef = this.getTabRef(undefined, 'cellCardExplorer', true);
338
+ if (cellCardExplorerTabRef && typeof cellCardExplorerTabRef.openSearch === 'function') {
339
+ cellCardExplorerTabRef.openSearch(filters, query);
340
+ }
341
+ })
342
+ },
343
+ openDatasetSearchFromCellCard: function (payload) {
344
+ if (!payload || typeof payload !== 'object') {
345
+ this.openSearch([], payload);
346
+ return;
347
+ }
348
+
349
+ const facets = [];
350
+ if (payload.species) {
351
+ facets.push({
352
+ facet: payload.species,
353
+ term: 'Species',
354
+ facetPropPath: 'organisms.primary.species.name',
355
+ });
356
+ }
357
+
358
+ if (payload.location) {
359
+ facets.push({
360
+ facet: payload.location,
361
+ term: 'Anatomical structure',
362
+ facetPropPath: 'anatomy.organ.category.name',
363
+ AND: true,
364
+ });
365
+ }
366
+
367
+ this.openSearch(facets, '');
368
+ },
302
369
  /**
303
370
  * Get the ref id of the tab by id and type.
304
371
  */
@@ -501,7 +568,17 @@ export default {
501
568
  ...data,
502
569
  };
503
570
  this.$emit('trackEvent', taggingData);
504
- }
571
+ },
572
+ /**
573
+ * @public
574
+ * Update the active species to use in filters.
575
+ * Used by SplitFlow component.
576
+ * @param activeSpecies
577
+ */
578
+ updateActiveSpeciesForEntries: function (activeSpecies) {
579
+ const speciesEntries = Array.isArray(activeSpecies) ? activeSpecies : [activeSpecies];
580
+ this.activeSpeciesForEntries = [...new Set(speciesEntries.filter(Boolean))];
581
+ },
505
582
  },
506
583
  computed: {
507
584
  // This should respect the information provided by the property
@@ -513,6 +590,10 @@ export default {
513
590
  tab.type === "annotation" &&
514
591
  this.annotationEntry &&
515
592
  this.annotationEntry.length > 0
593
+ ) ||
594
+ (
595
+ tab.type === "cellCardExplorer" &&
596
+ this.showCellCards
516
597
  )
517
598
  );
518
599
  },
@@ -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>
@@ -0,0 +1,28 @@
1
+ <template>
2
+ <svg
3
+ viewBox="0 0 24 24"
4
+ xmlns="http://www.w3.org/2000/svg"
5
+ aria-hidden="true"
6
+ focusable="false"
7
+ >
8
+ <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
9
+ <polygon
10
+ fill="currentColor"
11
+ fill-rule="nonzero"
12
+ transform="translate(15.500000, 8.878680) rotate(225.000000) translate(-15.500000, -8.878680)"
13
+ points="16.25 4.12867966 16.25 13.6286797 14.75 13.6286797 14.75 4.12867966"
14
+ />
15
+ <polygon
16
+ fill="currentColor"
17
+ fill-rule="nonzero"
18
+ transform="translate(17.500000, 6.918996) rotate(225.000000) translate(-17.500000, -6.918996)"
19
+ points="20.9294459 3.82036868 22.058311 4.80812559 17.5 10.0176239 12.941689 4.80812559 14.0705541 3.82036868 17.5 7.739"
20
+ />
21
+ <polygon
22
+ fill="currentColor"
23
+ fill-rule="nonzero"
24
+ points="11.6570511 7.25 11.6570511 8.75 5.75 8.75 5.75 16.25 15.25 16.25 15.25 11.2267933 16.75 11.2267933 16.75 17.75 4.25 17.75 4.25 7.25"
25
+ />
26
+ </g>
27
+ </svg>
28
+ </template>
@@ -9,6 +9,8 @@ declare module 'vue' {
9
9
  export interface GlobalComponents {
10
10
  AnnotationTool: typeof import('./components/AnnotationTool.vue')['default']
11
11
  BadgesGroup: typeof import('./components/BadgesGroup.vue')['default']
12
+ CellCard: typeof import('./components/CellCard.vue')['default']
13
+ CellCardExplorer: typeof import('./components/CellCardExplorer.vue')['default']
12
14
  ConnectivityCard: typeof import('./components/ConnectivityCard.vue')['default']
13
15
  ConnectivityExplorer: typeof import('./components/ConnectivityExplorer.vue')['default']
14
16
  ConnectivityInfo: typeof import('./components/ConnectivityInfo.vue')['default']
@@ -43,6 +45,7 @@ declare module 'vue' {
43
45
  ElRow: typeof import('element-plus/es')['ElRow']
44
46
  ElSelect: typeof import('element-plus/es')['ElSelect']
45
47
  ElTag: typeof import('element-plus/es')['ElTag']
48
+ IconOpenExternal: typeof import('./components/icons/IconOpenExternal.vue')['default']
46
49
  ImageGallery: typeof import('./components/ImageGallery.vue')['default']
47
50
  SearchFilters: typeof import('./components/SearchFilters.vue')['default']
48
51
  SearchHistory: typeof import('./components/SearchHistory.vue')['default']
@@ -0,0 +1,71 @@
1
+ export function generateUUID() {
2
+ const arr = new Uint8Array(16);
3
+ window.crypto.getRandomValues(arr);
4
+
5
+ arr[6] = (arr[6] & 0x0f) | 0x40;
6
+ arr[8] = (arr[8] & 0x3f) | 0x80;
7
+
8
+ const hex = Array.from(arr)
9
+ .map(byte => byte.toString(16).padStart(2, '0'))
10
+ .join('');
11
+
12
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
13
+ }
14
+
15
+ export function capitalise(text) {
16
+ if (!text) return '';
17
+ const value = String(text);
18
+ return value.charAt(0).toUpperCase() + value.slice(1);
19
+ }
20
+
21
+ export function formatAlertText(text, { formatLines = false } = {}) {
22
+ if (!text) return '';
23
+
24
+ const escaped = String(text)
25
+ .replace(/&/g, '&amp;')
26
+ .replace(/</g, '&lt;')
27
+ .replace(/>/g, '&gt;');
28
+
29
+ const linkified = escaped.replace(
30
+ /(https?:\/\/[^\s"<>\[]+)/g,
31
+ (url) => {
32
+ const parts = url.match(/^(.*?)([\].,;:!?]*)$/);
33
+ const cleanUrl = parts ? parts[1] : url;
34
+ const suffix = parts ? parts[2] : '';
35
+ return `<a href="${cleanUrl}" target="_blank" rel="noopener noreferrer">${cleanUrl}</a>${suffix}`;
36
+ }
37
+ );
38
+
39
+ const normalised = linkified
40
+ .replace(/\\n/g, '\n')
41
+ .replace(/\r\n/g, '\n')
42
+ .replace(/\r/g, '\n');
43
+
44
+ if (!formatLines) {
45
+ return normalised;
46
+ }
47
+
48
+ return normalised
49
+ .split('\n')
50
+ .map((line) => {
51
+ const withBoldLabel = line.replace(
52
+ /^\s*([A-Za-z][^:<]{0,120}:)/,
53
+ '<strong>$1</strong>'
54
+ );
55
+ return `<div class="alert-line">${withBoldLabel}</div>`;
56
+ })
57
+ .join('\n');
58
+ }
59
+
60
+ export function scrollToRef(vm, refName) {
61
+ vm?.$nextTick(() => {
62
+ const element = vm?.$refs?.[refName];
63
+ if (element) {
64
+ element.scrollIntoView({
65
+ behavior: 'smooth',
66
+ block: 'start',
67
+ inline: 'nearest',
68
+ });
69
+ }
70
+ });
71
+ }