@abi-software/map-side-bar 2.17.0 → 2.17.1-demo.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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) {
@@ -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
  }
@@ -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 () {
@@ -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()
@@ -291,18 +288,22 @@ export default {
291
288
  return value;
292
289
  },
293
290
  createChildrenCascaderValue: function(children, facet, facets) {
291
+ const parentKey = facet.key;
292
+
294
293
  if (children?.length) {
295
294
  for (let i = 0; i < children.length; i++) {
296
295
  const facetItem = children[i];
297
296
  //copy the facets into
298
297
  if (children[i].facetPropPath !== 'supportingAwards.consortium.name') {
299
- children[i].label = convertReadableLabel(
300
- facetItem.label
301
- )
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
+ }
302
304
  }
303
- if (facetItem.key && facet.key.includes('flatmap.connectivity.source.')) {
305
+ if (facetItem.key && parentKey.includes('flatmap.connectivity.source.')) {
304
306
  const childKey = facetItem.key;
305
- const parentKey = facet.key;
306
307
  const key = childKey.replace(`${parentKey}.`, '');
307
308
  children[i].value = this.createCascaderItemValue([facet.label, key]);
308
309
  } 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: {
@@ -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>
@@ -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>
@@ -1,4 +1,5 @@
1
1
  import SideBar from "./SideBar.vue"
2
+ import "../assets/fonts.scss";
2
3
 
3
4
  export {
4
5
  SideBar
@@ -13,6 +13,8 @@ declare module 'vue' {
13
13
  export interface GlobalComponents {
14
14
  AnnotationTool: typeof import('./components/AnnotationTool.vue')['default']
15
15
  BadgesGroup: typeof import('./components/BadgesGroup.vue')['default']
16
+ CellCard: typeof import('./components/CellCard.vue')['default']
17
+ CellCardExplorer: typeof import('./components/CellCardExplorer.vue')['default']
16
18
  ConnectivityCard: typeof import('./components/ConnectivityCard.vue')['default']
17
19
  ConnectivityExplorer: typeof import('./components/ConnectivityExplorer.vue')['default']
18
20
  ConnectivityInfo: typeof import('./components/ConnectivityInfo.vue')['default']
@@ -48,6 +50,7 @@ declare module 'vue' {
48
50
  ElRow: typeof import('element-plus/es')['ElRow']
49
51
  ElSelect: typeof import('element-plus/es')['ElSelect']
50
52
  ElTag: typeof import('element-plus/es')['ElTag']
53
+ IconOpenExternal: typeof import('./components/icons/IconOpenExternal.vue')['default']
51
54
  ImageGallery: typeof import('./components/ImageGallery.vue')['default']
52
55
  SearchFilters: typeof import('./components/SearchFilters.vue')['default']
53
56
  SearchHistory: typeof import('./components/SearchHistory.vue')['default']
package/src/main.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { createApp } from 'vue'
2
2
  import App from './App.vue'
3
+ import "./assets/fonts.scss"
3
4
  import MapSideBar from './components/SideBar.vue'
4
5
 
5
6
  const app = createApp(App);
@@ -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
+ }
package/vite.config.js CHANGED
@@ -38,7 +38,12 @@ export default defineConfig({
38
38
  fileName: 'map-side-bar',
39
39
  },
40
40
  rollupOptions: {
41
- external: ["vue", "@abi-software/map-utilities", "@element-plus/icons-vue"],
41
+ external: [
42
+ "vue",
43
+ "@abi-software/map-utilities",
44
+ "@element-plus/icons-vue",
45
+ "@abi-software/map-utilities/dist/style.css"
46
+ ],
42
47
  output: {
43
48
  globals: {
44
49
  vue: "Vue",