@abi-software/map-side-bar 2.19.0 → 2.19.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.
- package/dist/map-side-bar.js +1286 -309
- package/dist/map-side-bar.umd.cjs +45 -42
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/App.vue +10 -2
- package/src/components/CellCard.vue +992 -0
- package/src/components/CellCardExplorer.vue +879 -0
- package/src/components/ConnectivityCard.vue +2 -2
- package/src/components/ConnectivityInfo.vue +10 -37
- package/src/components/SearchFilters.vue +10 -7
- package/src/components/SearchHistory.vue +9 -15
- package/src/components/SideBar.vue +104 -6
- package/src/components/Tabs.vue +15 -6
- package/src/components/icons/IconOpenExternal.vue +23 -0
- package/src/components.d.ts +3 -0
- package/src/utils/common.js +59 -0
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
|
|
34
34
|
<script>
|
|
35
35
|
import EventBus from './EventBus';
|
|
36
|
+
import { capitalise as capitaliseText } from '../utils/common.js';
|
|
36
37
|
|
|
37
38
|
export default {
|
|
38
39
|
name: 'ConnectivityCard',
|
|
@@ -81,8 +82,7 @@ export default {
|
|
|
81
82
|
},
|
|
82
83
|
methods: {
|
|
83
84
|
capitalise: function (text) {
|
|
84
|
-
|
|
85
|
-
return '';
|
|
85
|
+
return capitaliseText(text);
|
|
86
86
|
},
|
|
87
87
|
cardClicked: function (data) {
|
|
88
88
|
if (!this.loading) {
|
|
@@ -343,6 +343,11 @@ import {
|
|
|
343
343
|
ExternalResourceCard,
|
|
344
344
|
} from '@abi-software/map-utilities';
|
|
345
345
|
import '@abi-software/map-utilities/dist/style.css';
|
|
346
|
+
import {
|
|
347
|
+
capitalise,
|
|
348
|
+
formatAlertText as formatAlertTextUtil,
|
|
349
|
+
scrollToRef,
|
|
350
|
+
} from '../utils/common.js';
|
|
346
351
|
|
|
347
352
|
const titleCase = (str) => {
|
|
348
353
|
return str.replace(/\w\S*/g, (t) => {
|
|
@@ -350,11 +355,6 @@ const titleCase = (str) => {
|
|
|
350
355
|
});
|
|
351
356
|
};
|
|
352
357
|
|
|
353
|
-
const capitalise = function (str) {
|
|
354
|
-
if (str) return str.charAt(0).toUpperCase() + str.slice(1);
|
|
355
|
-
return '';
|
|
356
|
-
};
|
|
357
|
-
|
|
358
358
|
export default {
|
|
359
359
|
name: 'ConnectivityInfo',
|
|
360
360
|
components: {
|
|
@@ -935,6 +935,9 @@ export default {
|
|
|
935
935
|
return null;
|
|
936
936
|
}
|
|
937
937
|
|
|
938
|
+
//A scicrunch entry typically looks like https://scicrunch.org/resolver/RRID:XXXXXX
|
|
939
|
+
//but due to CORS issue, we need to replace it with sparc-api interface,
|
|
940
|
+
//Replacing the domain while leaving the subpath the same.
|
|
938
941
|
const APIURL = isScicrunchURL
|
|
939
942
|
? url.replace(scicrunchBase, `${apiLocationBase}scicrunch`)
|
|
940
943
|
: url.replace(orcidBase, orcidAPIBase);
|
|
@@ -1159,40 +1162,10 @@ export default {
|
|
|
1159
1162
|
EventBus.emit('trackEvent', data);
|
|
1160
1163
|
},
|
|
1161
1164
|
showAlertMessage: function () {
|
|
1162
|
-
|
|
1163
|
-
this.$nextTick(() => {
|
|
1164
|
-
const alertElement = this.$refs.alertElement;
|
|
1165
|
-
if (alertElement) {
|
|
1166
|
-
alertElement.scrollIntoView({
|
|
1167
|
-
behavior: 'smooth',
|
|
1168
|
-
block: 'start',
|
|
1169
|
-
inline: 'nearest',
|
|
1170
|
-
});
|
|
1171
|
-
}
|
|
1172
|
-
});
|
|
1165
|
+
scrollToRef(this, 'alertElement');
|
|
1173
1166
|
},
|
|
1174
1167
|
formatAlertText: function (text) {
|
|
1175
|
-
|
|
1176
|
-
const escaped = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1177
|
-
const linkified = escaped.replace(/(https?:\/\/[^\s"<>[]+)/g, (url) => {
|
|
1178
|
-
const parts = url.match(/^(.*?)([\].,;:!?]*)$/);
|
|
1179
|
-
const cleanUrl = parts ? parts[1] : url;
|
|
1180
|
-
const suffix = parts ? parts[2] : '';
|
|
1181
|
-
return `<a href="${cleanUrl}" target="_blank" rel="noopener noreferrer">${cleanUrl}</a>${suffix}`;
|
|
1182
|
-
});
|
|
1183
|
-
|
|
1184
|
-
const normalised = linkified
|
|
1185
|
-
.replace(/\\n/g, '\n')
|
|
1186
|
-
.replace(/\r\n/g, '\n')
|
|
1187
|
-
.replace(/\r/g, '\n');
|
|
1188
|
-
|
|
1189
|
-
return normalised
|
|
1190
|
-
.split('\n')
|
|
1191
|
-
.map((line) => {
|
|
1192
|
-
const withBoldLabel = line.replace(/^\s*([A-Za-z][^:<]{0,120}:)/, '<strong>$1</strong>');
|
|
1193
|
-
return `<div class="alert-line">${withBoldLabel}</div>`;
|
|
1194
|
-
})
|
|
1195
|
-
.join('\n');
|
|
1168
|
+
return formatAlertTextUtil(text, { formatLines: true });
|
|
1196
1169
|
},
|
|
1197
1170
|
},
|
|
1198
1171
|
mounted: function () {
|
|
@@ -143,10 +143,7 @@ import '@abi-software/svg-sprite/dist/style.css';
|
|
|
143
143
|
import { AlgoliaClient } from '../algolia/algolia.js';
|
|
144
144
|
import { facetPropPathMapping } from '../algolia/utils.js';
|
|
145
145
|
import EventBus from './EventBus.js';
|
|
146
|
-
|
|
147
|
-
const capitalise = function (txt) {
|
|
148
|
-
return txt.charAt(0).toUpperCase() + txt.slice(1);
|
|
149
|
-
};
|
|
146
|
+
import { capitalise } from '../utils/common.js';
|
|
150
147
|
|
|
151
148
|
const convertReadableLabel = function (original) {
|
|
152
149
|
const name = original.toLowerCase();
|
|
@@ -273,16 +270,22 @@ export default {
|
|
|
273
270
|
return value;
|
|
274
271
|
},
|
|
275
272
|
createChildrenCascaderValue: function (children, facet, facets) {
|
|
273
|
+
const parentKey = facet.key;
|
|
274
|
+
|
|
276
275
|
if (children?.length) {
|
|
277
276
|
for (let i = 0; i < children.length; i++) {
|
|
278
277
|
const facetItem = children[i];
|
|
279
278
|
//copy the facets into
|
|
280
279
|
if (children[i].facetPropPath !== 'supportingAwards.consortium.name') {
|
|
281
|
-
|
|
280
|
+
// `sourceNomenclatureLabel` is from cell card explorer filters
|
|
281
|
+
if (parentKey === 'sourceNomenclatureLabel') {
|
|
282
|
+
children[i].label = facetItem.label;
|
|
283
|
+
} else {
|
|
284
|
+
children[i].label = convertReadableLabel(facetItem.label);
|
|
285
|
+
}
|
|
282
286
|
}
|
|
283
|
-
if (facetItem.key &&
|
|
287
|
+
if (facetItem.key && parentKey.includes('flatmap.connectivity.source.')) {
|
|
284
288
|
const childKey = facetItem.key;
|
|
285
|
-
const parentKey = facet.key;
|
|
286
289
|
const key = childKey.replace(`${parentKey}.`, '');
|
|
287
290
|
children[i].value = this.createCascaderItemValue([facet.label, key]);
|
|
288
291
|
} else {
|
|
@@ -125,23 +125,10 @@
|
|
|
125
125
|
|
|
126
126
|
<script>
|
|
127
127
|
import EventBus from './EventBus.js';
|
|
128
|
+
import { generateUUID } from '../utils/common.js';
|
|
128
129
|
|
|
129
130
|
const MAX_SEARCH_HISTORY = 12;
|
|
130
131
|
|
|
131
|
-
function generateUUID() {
|
|
132
|
-
const arr = new Uint8Array(16);
|
|
133
|
-
window.crypto.getRandomValues(arr);
|
|
134
|
-
|
|
135
|
-
arr[6] = (arr[6] & 0x0f) | 0x40;
|
|
136
|
-
arr[8] = (arr[8] & 0x3f) | 0x80;
|
|
137
|
-
|
|
138
|
-
const hex = Array.from(arr)
|
|
139
|
-
.map((byte) => byte.toString(16).padStart(2, '0'))
|
|
140
|
-
.join('');
|
|
141
|
-
|
|
142
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
132
|
export default {
|
|
146
133
|
name: 'SearchHistory',
|
|
147
134
|
props: {
|
|
@@ -317,7 +304,14 @@ export default {
|
|
|
317
304
|
getParentComponentName: function () {
|
|
318
305
|
const isConnectivity = this.localStorageKey?.indexOf('connectivity') !== -1;
|
|
319
306
|
const isDataset = this.localStorageKey?.indexOf('dataset') !== -1;
|
|
320
|
-
const
|
|
307
|
+
const isCellCard = this.localStorageKey?.indexOf('cell-card') !== -1;
|
|
308
|
+
const location = isConnectivity
|
|
309
|
+
? 'connectivity'
|
|
310
|
+
: isDataset
|
|
311
|
+
? 'dataset'
|
|
312
|
+
: isCellCard
|
|
313
|
+
? 'cell_card'
|
|
314
|
+
: '';
|
|
321
315
|
return location;
|
|
322
316
|
},
|
|
323
317
|
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"
|
|
@@ -93,6 +107,7 @@ import EventBus from './EventBus.js';
|
|
|
93
107
|
import Tabs from './Tabs.vue';
|
|
94
108
|
import AnnotationTool from './AnnotationTool.vue';
|
|
95
109
|
import ConnectivityExplorer from './ConnectivityExplorer.vue';
|
|
110
|
+
import CellCardExplorer from './CellCardExplorer.vue';
|
|
96
111
|
import { removeShowAllFacets } from '../algolia/utils.js';
|
|
97
112
|
|
|
98
113
|
/**
|
|
@@ -106,6 +121,7 @@ export default {
|
|
|
106
121
|
ElIconArrowRight,
|
|
107
122
|
AnnotationTool,
|
|
108
123
|
ConnectivityExplorer,
|
|
124
|
+
CellCardExplorer,
|
|
109
125
|
},
|
|
110
126
|
name: 'SideBar',
|
|
111
127
|
props: {
|
|
@@ -115,6 +131,7 @@ export default {
|
|
|
115
131
|
{ title: 'Dataset Explorer', id: 1, type: 'datasetExplorer', closable: false },
|
|
116
132
|
{ title: 'Connectivity Explorer', id: 2, type: 'connectivityExplorer', closable: false },
|
|
117
133
|
{ title: 'Annotation', id: 3, type: 'annotation', closable: true },
|
|
134
|
+
{ title: 'Cell Card Explorer', id: 4, type: 'cellCardExplorer', closable: false },
|
|
118
135
|
],
|
|
119
136
|
},
|
|
120
137
|
/**
|
|
@@ -180,6 +197,10 @@ export default {
|
|
|
180
197
|
type: Boolean,
|
|
181
198
|
default: false,
|
|
182
199
|
},
|
|
200
|
+
showCellCards: {
|
|
201
|
+
type: Boolean,
|
|
202
|
+
default: false,
|
|
203
|
+
},
|
|
183
204
|
},
|
|
184
205
|
data: function () {
|
|
185
206
|
return {
|
|
@@ -188,6 +209,7 @@ export default {
|
|
|
188
209
|
activeTabId: 1,
|
|
189
210
|
activeAnnotationData: { tabType: 'annotation' },
|
|
190
211
|
activeConnectivityData: { tabType: 'connectivity' },
|
|
212
|
+
activeSpeciesForEntries: [],
|
|
191
213
|
state: {
|
|
192
214
|
dataset: {
|
|
193
215
|
search: '',
|
|
@@ -197,6 +219,10 @@ export default {
|
|
|
197
219
|
search: '',
|
|
198
220
|
filters: [],
|
|
199
221
|
},
|
|
222
|
+
cellCards: {
|
|
223
|
+
search: '',
|
|
224
|
+
filters: [],
|
|
225
|
+
},
|
|
200
226
|
connectivityEntries: [],
|
|
201
227
|
annotationEntries: [],
|
|
202
228
|
activeTabId: this.activeTabId,
|
|
@@ -231,6 +257,16 @@ export default {
|
|
|
231
257
|
this.activeAnnotationData = data;
|
|
232
258
|
}
|
|
233
259
|
},
|
|
260
|
+
/**
|
|
261
|
+
* This event is emitted when the mouse hover on or off a soma location in cell card explorer.
|
|
262
|
+
* @param name Soma location
|
|
263
|
+
*/
|
|
264
|
+
showSomaLocation: function (name) {
|
|
265
|
+
this.$emit('soma-location-hovered', name);
|
|
266
|
+
},
|
|
267
|
+
onSomaLocationsReady: function (somaLocations) {
|
|
268
|
+
this.$emit('soma-locations-ready', somaLocations);
|
|
269
|
+
},
|
|
234
270
|
/**
|
|
235
271
|
* This event is emitted when the show connectivity button is clicked.
|
|
236
272
|
* @arg featureIds
|
|
@@ -295,6 +331,42 @@ export default {
|
|
|
295
331
|
datasetExplorerTabRef.openSearch(facets, query);
|
|
296
332
|
});
|
|
297
333
|
},
|
|
334
|
+
openCellCardExplorerSearch: function (filters, query) {
|
|
335
|
+
this.drawerOpen = true;
|
|
336
|
+
// Because refs are in v-for, nextTick is needed here
|
|
337
|
+
this.$nextTick(() => {
|
|
338
|
+
const cellCardExplorerTabRef = this.getTabRef(undefined, 'cellCardExplorer', true);
|
|
339
|
+
if (cellCardExplorerTabRef && typeof cellCardExplorerTabRef.openSearch === 'function') {
|
|
340
|
+
cellCardExplorerTabRef.openSearch(filters, query);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
},
|
|
344
|
+
openDatasetSearchFromCellCard: function (payload) {
|
|
345
|
+
if (!payload || typeof payload !== 'object') {
|
|
346
|
+
this.openSearch([], payload);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const facets = [];
|
|
351
|
+
if (payload.species) {
|
|
352
|
+
facets.push({
|
|
353
|
+
facet: payload.species,
|
|
354
|
+
term: 'Species',
|
|
355
|
+
facetPropPath: 'organisms.primary.species.name',
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (payload.location) {
|
|
360
|
+
facets.push({
|
|
361
|
+
facet: payload.location,
|
|
362
|
+
term: 'Anatomical structure',
|
|
363
|
+
facetPropPath: 'anatomy.organ.category.name',
|
|
364
|
+
AND: true,
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
this.openSearch(facets, '');
|
|
369
|
+
},
|
|
298
370
|
/**
|
|
299
371
|
* Get the ref id of the tab by id and type.
|
|
300
372
|
*/
|
|
@@ -432,6 +504,13 @@ export default {
|
|
|
432
504
|
this.state.connectivity.filters = connectivityExplorerTabRef.getFilters();
|
|
433
505
|
}
|
|
434
506
|
|
|
507
|
+
// Update cell card explorer state if available
|
|
508
|
+
const cellCardExplorerTabRef = this.getTabRef(undefined, 'cellCardExplorer');
|
|
509
|
+
if (cellCardExplorerTabRef) {
|
|
510
|
+
this.state.cellCards.search = cellCardExplorerTabRef.getSearch();
|
|
511
|
+
this.state.cellCards.filters = cellCardExplorerTabRef.getFilters();
|
|
512
|
+
}
|
|
513
|
+
|
|
435
514
|
// Update connectivity entries if available
|
|
436
515
|
if (this.connectivityEntry && this.connectivityEntry.length > 0) {
|
|
437
516
|
this.state.connectivityEntries = this.connectivityEntry.map((entry) => entry.id);
|
|
@@ -461,11 +540,15 @@ export default {
|
|
|
461
540
|
setState: function (state) {
|
|
462
541
|
// if state is not provided or formatted incorrectly, do nothing
|
|
463
542
|
if (!state || !state.dataset || !state.connectivity) return;
|
|
464
|
-
|
|
465
|
-
const
|
|
466
|
-
|
|
467
|
-
const
|
|
468
|
-
const
|
|
543
|
+
// Create a deep copy while preserving the original state structure for future extensibility
|
|
544
|
+
const newState = JSON.parse(JSON.stringify(state));
|
|
545
|
+
this.state = { ...this.state, ...newState };
|
|
546
|
+
const datasetFilters = this.state.dataset.filters;
|
|
547
|
+
const connectivityFilters = this.state.connectivity.filters;
|
|
548
|
+
const datasetSearch = this.state.dataset.search;
|
|
549
|
+
const connectivitySearch = this.state.connectivity.search;
|
|
550
|
+
const cellCardFilters = this.state.cellCards.filters || [];
|
|
551
|
+
const cellCardSearch = this.state.cellCards.search || '';
|
|
469
552
|
|
|
470
553
|
if (datasetFilters.length || datasetSearch) {
|
|
471
554
|
this.openSearch(datasetFilters, datasetSearch);
|
|
@@ -475,6 +558,10 @@ export default {
|
|
|
475
558
|
this.openConnectivitySearch(connectivityFilters, connectivitySearch);
|
|
476
559
|
}
|
|
477
560
|
|
|
561
|
+
if (cellCardFilters.length || cellCardSearch) {
|
|
562
|
+
this.openCellCardExplorerSearch(cellCardFilters, cellCardSearch);
|
|
563
|
+
}
|
|
564
|
+
|
|
478
565
|
if (state.activeTabId) {
|
|
479
566
|
this.$nextTick(() => {
|
|
480
567
|
const hasTab = this.tabEntries.find((t) => t.id === state.activeTabId);
|
|
@@ -497,6 +584,16 @@ export default {
|
|
|
497
584
|
};
|
|
498
585
|
this.$emit('trackEvent', taggingData);
|
|
499
586
|
},
|
|
587
|
+
/**
|
|
588
|
+
* @public
|
|
589
|
+
* Update the active species to use in filters.
|
|
590
|
+
* Used by SplitFlow component.
|
|
591
|
+
* @param activeSpecies
|
|
592
|
+
*/
|
|
593
|
+
updateActiveSpeciesForEntries: function (activeSpecies) {
|
|
594
|
+
const speciesEntries = Array.isArray(activeSpecies) ? activeSpecies : [activeSpecies];
|
|
595
|
+
this.activeSpeciesForEntries = [...new Set(speciesEntries.filter(Boolean))];
|
|
596
|
+
},
|
|
500
597
|
},
|
|
501
598
|
computed: {
|
|
502
599
|
// This should respect the information provided by the property
|
|
@@ -505,7 +602,8 @@ export default {
|
|
|
505
602
|
(tab) =>
|
|
506
603
|
tab.type === 'datasetExplorer' ||
|
|
507
604
|
tab.type === 'connectivityExplorer' ||
|
|
508
|
-
(tab.type === 'annotation' && this.annotationEntry && this.annotationEntry.length > 0)
|
|
605
|
+
(tab.type === 'annotation' && this.annotationEntry && this.annotationEntry.length > 0) ||
|
|
606
|
+
(tab.type === 'cellCardExplorer' && this.showCellCards),
|
|
509
607
|
);
|
|
510
608
|
},
|
|
511
609
|
},
|
package/src/components/Tabs.vue
CHANGED
|
@@ -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>
|
|
@@ -98,14 +98,23 @@ export default {
|
|
|
98
98
|
.tab-title {
|
|
99
99
|
text-align: center;
|
|
100
100
|
font-size: 14px;
|
|
101
|
-
padding: 0
|
|
101
|
+
padding: 0 0.75rem;
|
|
102
|
+
|
|
103
|
+
.closable-tab & {
|
|
104
|
+
padding-right: 0.25rem;
|
|
105
|
+
}
|
|
102
106
|
}
|
|
103
107
|
|
|
104
108
|
.tab-close-icon {
|
|
105
|
-
width:
|
|
106
|
-
height:
|
|
107
|
-
font-size:
|
|
108
|
-
|
|
109
|
+
width: 18px !important;
|
|
110
|
+
height: 18px !important;
|
|
111
|
+
font-size: 18px !important;
|
|
112
|
+
margin-right: 4px !important;
|
|
109
113
|
color: $app-primary-color !important;
|
|
114
|
+
border-radius: 2px;
|
|
115
|
+
|
|
116
|
+
&:hover {
|
|
117
|
+
background-color: rgba($app-primary-color, 0.15) !important;
|
|
118
|
+
}
|
|
110
119
|
}
|
|
111
120
|
</style>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
|
|
3
|
+
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
|
4
|
+
<polygon
|
|
5
|
+
fill="currentColor"
|
|
6
|
+
fill-rule="nonzero"
|
|
7
|
+
transform="translate(15.500000, 8.878680) rotate(225.000000) translate(-15.500000, -8.878680)"
|
|
8
|
+
points="16.25 4.12867966 16.25 13.6286797 14.75 13.6286797 14.75 4.12867966"
|
|
9
|
+
/>
|
|
10
|
+
<polygon
|
|
11
|
+
fill="currentColor"
|
|
12
|
+
fill-rule="nonzero"
|
|
13
|
+
transform="translate(17.500000, 6.918996) rotate(225.000000) translate(-17.500000, -6.918996)"
|
|
14
|
+
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"
|
|
15
|
+
/>
|
|
16
|
+
<polygon
|
|
17
|
+
fill="currentColor"
|
|
18
|
+
fill-rule="nonzero"
|
|
19
|
+
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"
|
|
20
|
+
/>
|
|
21
|
+
</g>
|
|
22
|
+
</svg>
|
|
23
|
+
</template>
|
package/src/components.d.ts
CHANGED
|
@@ -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']
|
|
@@ -0,0 +1,59 @@
|
|
|
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).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
25
|
+
|
|
26
|
+
const linkified = escaped.replace(/(https?:\/\/[^\s"<>[]+)/g, (url) => {
|
|
27
|
+
const parts = url.match(/^(.*?)([\].,;:!?]*)$/);
|
|
28
|
+
const cleanUrl = parts ? parts[1] : url;
|
|
29
|
+
const suffix = parts ? parts[2] : '';
|
|
30
|
+
return `<a href="${cleanUrl}" target="_blank" rel="noopener noreferrer">${cleanUrl}</a>${suffix}`;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const normalised = linkified.replace(/\\n/g, '\n').replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
34
|
+
|
|
35
|
+
if (!formatLines) {
|
|
36
|
+
return normalised;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return normalised
|
|
40
|
+
.split('\n')
|
|
41
|
+
.map((line) => {
|
|
42
|
+
const withBoldLabel = line.replace(/^\s*([A-Za-z][^:<]{0,120}:)/, '<strong>$1</strong>');
|
|
43
|
+
return `<div class="alert-line">${withBoldLabel}</div>`;
|
|
44
|
+
})
|
|
45
|
+
.join('\n');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function scrollToRef(vm, refName) {
|
|
49
|
+
vm?.$nextTick(() => {
|
|
50
|
+
const element = vm?.$refs?.[refName];
|
|
51
|
+
if (element) {
|
|
52
|
+
element.scrollIntoView({
|
|
53
|
+
behavior: 'smooth',
|
|
54
|
+
block: 'start',
|
|
55
|
+
inline: 'nearest',
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|