@abi-software/map-side-bar 2.15.0 → 2.15.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 +9910 -8786
- package/dist/map-side-bar.umd.cjs +80 -75
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/App.vue +11 -4
- package/src/components/CellCard.vue +994 -0
- package/src/components/CellCardExplorer.vue +882 -0
- package/src/components/ConnectivityCard.vue +2 -2
- package/src/components/ConnectivityExplorer.vue +2 -2
- package/src/components/ConnectivityInfo.vue +3 -46
- package/src/components/SearchFilters.vue +10 -9
- package/src/components/SearchHistory.vue +3 -15
- package/src/components/SideBar.vue +108 -7
- package/src/components/Tabs.vue +15 -6
- package/src/components/icons/IconOpenExternal.vue +28 -0
- package/src/components.d.ts +3 -0
- package/src/utils/common.js +71 -0
|
@@ -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
|
-
|
|
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: '
|
|
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: '
|
|
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
|
-
|
|
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
|
-
|
|
1174
|
-
const escaped = text
|
|
1175
|
-
.replace(/&/g, '&')
|
|
1176
|
-
.replace(/</g, '<')
|
|
1177
|
-
.replace(/>/g, '>');
|
|
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()
|
|
@@ -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
|
-
|
|
297
|
-
|
|
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 &&
|
|
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: {
|
|
@@ -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
|
|
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
|
-
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
const
|
|
473
|
-
const
|
|
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
|
},
|
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>
|
|
@@ -103,14 +103,23 @@ export default {
|
|
|
103
103
|
.tab-title {
|
|
104
104
|
text-align: center;
|
|
105
105
|
font-size: 14px;
|
|
106
|
-
padding: 0
|
|
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:
|
|
111
|
-
height:
|
|
112
|
-
font-size:
|
|
113
|
-
|
|
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>
|
package/src/components.d.ts
CHANGED
|
@@ -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']
|
|
@@ -44,6 +46,7 @@ declare module 'vue' {
|
|
|
44
46
|
ElRow: typeof import('element-plus/es')['ElRow']
|
|
45
47
|
ElSelect: typeof import('element-plus/es')['ElSelect']
|
|
46
48
|
ElTag: typeof import('element-plus/es')['ElTag']
|
|
49
|
+
IconOpenExternal: typeof import('./components/icons/IconOpenExternal.vue')['default']
|
|
47
50
|
ImageGallery: typeof import('./components/ImageGallery.vue')['default']
|
|
48
51
|
SearchFilters: typeof import('./components/SearchFilters.vue')['default']
|
|
49
52
|
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, '&')
|
|
26
|
+
.replace(/</g, '<')
|
|
27
|
+
.replace(/>/g, '>');
|
|
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
|
+
}
|