@abi-software/map-side-bar 2.14.8-demo.6 → 2.15.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abi-software/map-side-bar",
3
- "version": "2.14.8-demo.6",
3
+ "version": "2.15.0",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
package/src/App.vue CHANGED
@@ -19,7 +19,6 @@
19
19
  <el-button @click="getFacets">Get facets</el-button>
20
20
  <el-button @click="toggleCreateData">Create Data/Annotation</el-button>
21
21
  <el-button @click="openConnectivitySearch()">Connectivity Search</el-button>
22
- <el-button @click="showCellCardExplorer()">Show Cell Card Explorer</el-button>
23
22
  </div>
24
23
  <SideBar
25
24
  :envVars="envVars"
@@ -31,7 +30,6 @@
31
30
  :connectivityEntry="connectivityEntry"
32
31
  :connectivityKnowledge="connectivityKnowledge"
33
32
  :showVisibilityFilter="true"
34
- :showCellCards="showCellCards"
35
33
  @search-changed="searchChanged($event)"
36
34
  @hover-changed="hoverChanged($event)"
37
35
  @connectivity-hovered="onConnectivityHovered"
@@ -47,7 +45,9 @@
47
45
  import SideBar from './components/SideBar.vue'
48
46
  import EventBus from './components/EventBus.js'
49
47
  import exampleConnectivityInput from './exampleConnectivityInput.js'
50
- import { capitalise } from './utils/common.js'
48
+
49
+
50
+ const capitalise = (str) => str.charAt(0).toUpperCase() + str.slice(1);
51
51
 
52
52
  const flatmapQuery = (flatmapApi, sql) => {
53
53
  const data = { sql: sql };
@@ -136,7 +136,6 @@ export default {
136
136
  BL_SERVER_URL: import.meta.env.VITE_APP_BL_SERVER_URL,
137
137
  ROOT_URL: import.meta.env.VITE_APP_ROOT_URL,
138
138
  FLATMAPAPI_LOCATION: import.meta.env.VITE_FLATMAPAPI_LOCATION,
139
- CELL_CARDS_API: import.meta.env.VITE_APP_CELL_CARDS_API,
140
139
  },
141
140
  connectivityEntry: [],
142
141
  createData: {
@@ -153,7 +152,6 @@ export default {
153
152
  query: '',
154
153
  filter: [],
155
154
  target: [],
156
- showCellCards: false,
157
155
  }
158
156
  },
159
157
  methods: {
@@ -368,12 +366,7 @@ export default {
368
366
  },
369
367
  onConnectivityCollapseChange: function () {
370
368
  this.connectivityEntry = [...exampleConnectivityInput]
371
- },
372
- showCellCardExplorer: function () {
373
- this.showCellCards = true;
374
- this.$refs.sideBar.tabClicked({ id: 4, type: 'cellCardExplorer' });
375
- this.$refs.sideBar.setDrawerOpen(true);
376
- },
369
+ }
377
370
  },
378
371
  mounted: async function () {
379
372
  console.log('mounted app')
@@ -28,7 +28,6 @@
28
28
 
29
29
  <script>
30
30
  import EventBus from './EventBus';
31
- import { capitalise as capitaliseText } from '../utils/common.js';
32
31
 
33
32
  export default {
34
33
  name: "ConnectivityCard",
@@ -79,7 +78,8 @@ export default {
79
78
  },
80
79
  methods: {
81
80
  capitalise: function (text) {
82
- return capitaliseText(text);
81
+ if (text) return text.charAt(0).toUpperCase() + text.slice(1);
82
+ return "";
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: 'connectivity',
423
+ tabType: 'dataset',
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: 'connectivity',
471
+ tabType: 'dataset',
472
472
  type: 'reset-update',
473
473
  })
474
474
  }
@@ -310,12 +310,21 @@
310
310
  </div>
311
311
  <ul class="block consultant-block">
312
312
  <li v-for="consultant in expertConsultants" :key="consultant.url">
313
- <template v-if="consultant.name">
314
- <contributor-item :contributor="consultant" />
315
- </template>
316
- <div class="consultant-loading" v-else>
313
+ <contributor-item :contributor="consultant" v-if="consultant.name" />
314
+ <div class="consultant-loading" v-else-if="!consultant.error">
317
315
  <span>Loading {{ consultant.url }}</span>
318
316
  </div>
317
+ <div class="consultant-error" v-if="consultant.error">
318
+ <span v-if="consultant.errorMessage">{{ consultant.errorMessage }}</span>
319
+ <span v-else>Sorry, something went wrong.</span>
320
+ <template v-if="consultant.refreshable">
321
+ <br />
322
+ Please try again.
323
+ <span class="reload-button" @click="reloadConsultant(consultant)">
324
+ Reload
325
+ </span>
326
+ </template>
327
+ </div>
319
328
  </li>
320
329
  </ul>
321
330
  </div>
@@ -346,7 +355,6 @@ import {
346
355
  ExternalResourceCard,
347
356
  } from '@abi-software/map-utilities';
348
357
  import '@abi-software/map-utilities/dist/style.css';
349
- import { capitalise, formatAlertText as formatAlertTextUtil, scrollToRef } from '../utils/common.js'
350
358
 
351
359
  const titleCase = (str) => {
352
360
  return str.replace(/\w\S*/g, (t) => {
@@ -354,6 +362,11 @@ const titleCase = (str) => {
354
362
  })
355
363
  }
356
364
 
365
+ const capitalise = function (str) {
366
+ if (str) return str.charAt(0).toUpperCase() + str.slice(1)
367
+ return ''
368
+ }
369
+
357
370
  export default {
358
371
  name: 'ConnectivityInfo',
359
372
  components: {
@@ -896,69 +909,148 @@ export default {
896
909
  return data
897
910
  },
898
911
  fetchExpertConsultants: async function () {
899
- this.expertConsultants = this.expertConsultantURLs.map(url => ({ name: '', url }));
912
+ this.expertConsultants = this.expertConsultantURLs.map((url) => ({
913
+ name: '',
914
+ url,
915
+ error: false,
916
+ errorMessage: '',
917
+ refreshable: false,
918
+ }));
900
919
 
901
920
  for (const url of this.expertConsultantURLs) {
902
- const scicrunchBase = 'https://scicrunch.org';
903
- const orcidBase = 'https://orcid.org';
904
- const orcidAPIBase = 'https://pub.orcid.org/v2.1';
905
- const apiLocationBase = (this.envVars.API_LOCATION ?? '').replace(/\/?$/, '/');
906
- const isScicrunchURL = url.startsWith(scicrunchBase);
907
- const isOrcidURL = url.startsWith(orcidBase);
908
-
909
- if (!isScicrunchURL && !isOrcidURL) {
910
- console.warn(`Unsupported expert consultant URL: ${url}`);
911
- continue;
921
+ await this.fetchSingleExpertConsultant(url);
922
+ }
923
+
924
+ this.updatedCopyContent = this.getUpdateCopyContent(this.storedReferences);
925
+ },
926
+ buildExpertConsultantRequestInfo: function (url) {
927
+ const scicrunchBase = 'https://scicrunch.org';
928
+ const orcidBase = 'https://orcid.org';
929
+ const orcidAPIBase = 'https://pub.orcid.org/v2.1';
930
+ const apiLocationBase = (this.envVars.API_LOCATION ?? '').replace(/\/?$/, '/');
931
+ const isScicrunchURL = url.startsWith(scicrunchBase);
932
+ const isOrcidURL = url.startsWith(orcidBase);
933
+
934
+ if (!isScicrunchURL && !isOrcidURL) {
935
+ return null;
936
+ }
937
+
938
+ const APIURL = isScicrunchURL
939
+ ? url.replace(scicrunchBase, `${apiLocationBase}scicrunch`)
940
+ : url.replace(orcidBase, orcidAPIBase);
941
+
942
+ return {
943
+ APIURL,
944
+ isScicrunchURL,
945
+ };
946
+ },
947
+ fetchSingleExpertConsultant: async function (url) {
948
+ const requestInfo = this.buildExpertConsultantRequestInfo(url);
949
+ if (!requestInfo) {
950
+ console.warn(`Unsupported expert consultant URL: ${url}`);
951
+ this.updateExpertConsultantByURL(url, {
952
+ name: '',
953
+ error: true,
954
+ errorMessage: 'Unsupported expert consultant URL.',
955
+ refreshable: false,
956
+ orcidId: '',
957
+ organization: '',
958
+ role: '',
959
+ });
960
+ return;
961
+ }
962
+
963
+ const { APIURL, isScicrunchURL } = requestInfo;
964
+
965
+ // Reset the consultant data before fetching
966
+ this.updateExpertConsultantByURL(url, {
967
+ name: '',
968
+ error: false,
969
+ errorMessage: '',
970
+ refreshable: false,
971
+ orcidId: '',
972
+ organization: '',
973
+ role: '',
974
+ });
975
+
976
+ try {
977
+ const response = await fetch(APIURL, {
978
+ headers: {
979
+ Accept: 'application/json',
980
+ },
981
+ });
982
+
983
+ if (!response.ok) {
984
+ throw new Error(`Response status: ${response.status}`);
912
985
  }
913
986
 
914
- const scicrunchAPIURL = (targetURL) => targetURL.replace(scicrunchBase, `${apiLocationBase}scicrunch`);
915
- const orcidAPIURL = (targetURL) => targetURL.replace(orcidBase, orcidAPIBase);
916
- const APIURL = isScicrunchURL ? scicrunchAPIURL(url) : orcidAPIURL(url);
987
+ const data = await response.json();
917
988
 
918
- try {
919
- const response = await fetch(APIURL, {
920
- headers: {
921
- 'Accept': 'application/json'
922
- }
989
+ if (isScicrunchURL) {
990
+ const { hits } = data?.hits || {};
991
+ const name = hits?.[0]?._source?.item?.name || '';
992
+
993
+ if (!name) {
994
+ throw new Error('No consultant data returned from SciCrunch.');
995
+ }
996
+
997
+ this.updateExpertConsultantByURL(url, {
998
+ name,
999
+ error: false,
1000
+ errorMessage: '',
1001
+ refreshable: false,
923
1002
  });
924
- const data = await response.json();
925
- if (isScicrunchURL) {
926
- const { hits } = data?.hits || {};
927
- if (hits?.length) {
928
- const name = hits[0]?._source?.item?.name;
929
- if (name) {
930
- const consultantIndex = this.expertConsultants.findIndex((consultant) => consultant.url === url);
931
- if (consultantIndex !== -1) {
932
- this.expertConsultants[consultantIndex].name = name;
933
- }
934
- }
935
- }
936
- } else {
937
- const nameInfo = data?.person?.name;
938
- if (nameInfo) {
939
- const givenName = nameInfo['given-names']?.value || '';
940
- const familyName = nameInfo['family-name']?.value || '';
941
- const creditName = nameInfo['credit-name']?.value || '';
942
- const fullName = creditName || `${givenName} ${familyName}`.trim();
943
- if (fullName) {
944
- const consultantIndex = this.expertConsultants.findIndex((consultant) => consultant.url === url);
945
- if (consultantIndex !== -1) {
946
- this.expertConsultants[consultantIndex].name = fullName;
947
- const orcidId = data['orcid-identifier']?.path || '';
948
- this.expertConsultants[consultantIndex].orcidId = orcidId;
949
- const employmentSummary = data?.['activities-summary']?.['employments']?.['employment-summary'] || [];
950
- const latestEmployment = employmentSummary[0] || {};
951
- this.expertConsultants[consultantIndex].organization = latestEmployment?.organization?.name || '';
952
- this.expertConsultants[consultantIndex].role = latestEmployment?.['role-title'] || '';
953
- }
954
- }
955
- }
1003
+ } else {
1004
+ const nameInfo = data?.person?.name;
1005
+ const givenName = nameInfo?.['given-names']?.value || '';
1006
+ const familyName = nameInfo?.['family-name']?.value || '';
1007
+ const creditName = nameInfo?.['credit-name']?.value || '';
1008
+ const fullName = creditName || `${givenName} ${familyName}`.trim();
1009
+
1010
+ if (!fullName) {
1011
+ throw new Error('No consultant data returned from ORCID.');
956
1012
  }
957
- } catch (error) {
958
- console.error('Error fetching expert consultant:', error);
1013
+
1014
+ const orcidId = data?.['orcid-identifier']?.path || '';
1015
+ const employmentSummary = data?.['activities-summary']?.['employments']?.['employment-summary'] || [];
1016
+ const latestEmployment = employmentSummary[0] || {};
1017
+
1018
+ this.updateExpertConsultantByURL(url, {
1019
+ name: fullName,
1020
+ error: false,
1021
+ errorMessage: '',
1022
+ refreshable: false,
1023
+ orcidId,
1024
+ organization: latestEmployment?.organization?.name || '',
1025
+ role: latestEmployment?.['role-title'] || '',
1026
+ });
959
1027
  }
1028
+ } catch (error) {
1029
+ console.error('Error fetching expert consultant:', error);
1030
+ this.updateExpertConsultantByURL(url, {
1031
+ name: '',
1032
+ error: true,
1033
+ errorMessage: 'Failed to fetch expert consultant data.',
1034
+ refreshable: true,
1035
+ orcidId: '',
1036
+ organization: '',
1037
+ role: '',
1038
+ });
1039
+ }
1040
+ },
1041
+ updateExpertConsultantByURL: function (url, fields = {}) {
1042
+ const consultantIndex = this.expertConsultants.findIndex((consultant) => consultant.url === url);
1043
+ if (consultantIndex === -1) {
1044
+ return;
960
1045
  }
961
1046
 
1047
+ this.expertConsultants.splice(consultantIndex, 1, {
1048
+ ...this.expertConsultants[consultantIndex],
1049
+ ...fields,
1050
+ });
1051
+ },
1052
+ reloadConsultant: async function (consultant) {
1053
+ await this.fetchSingleExpertConsultant(consultant.url);
962
1054
  this.updatedCopyContent = this.getUpdateCopyContent(this.storedReferences);
963
1055
  },
964
1056
  onConnectivityHovered: function (label) {
@@ -1065,10 +1157,49 @@ export default {
1065
1157
  EventBus.emit('trackEvent', data);
1066
1158
  },
1067
1159
  showAlertMessage: function () {
1068
- scrollToRef(this, 'alertElement');
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
+ });
1069
1171
  },
1070
1172
  formatAlertText: function (text) {
1071
- return formatAlertTextUtil(text, { formatLines: true });
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');
1072
1203
  },
1073
1204
  },
1074
1205
  mounted: function () {
@@ -1684,6 +1815,14 @@ export default {
1684
1815
  }
1685
1816
  }
1686
1817
 
1818
+ .consultant-error {
1819
+ padding: 0.25rem 0.5rem;
1820
+ font-style: italic;
1821
+ color: var(--el-color-info);
1822
+ border: 1px dotted red;
1823
+ border-radius: 4px;
1824
+ }
1825
+
1687
1826
  .alert-chip {
1688
1827
  margin-left: 5px;
1689
1828
  background-color: $app-primary-color;
@@ -1718,6 +1857,12 @@ export default {
1718
1857
  width: 1px;
1719
1858
  }
1720
1859
 
1860
+ .reload-button {
1861
+ color: $app-primary-color;
1862
+ text-decoration: underline;
1863
+ cursor: pointer;
1864
+ }
1865
+
1721
1866
  @keyframes loadingAnimation {
1722
1867
  0% {
1723
1868
  background-position: -30vw 0;
@@ -157,7 +157,10 @@ 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
- import { capitalise } from '../utils/common.js'
160
+
161
+ const capitalise = function (txt) {
162
+ return txt.charAt(0).toUpperCase() + txt.slice(1)
163
+ }
161
164
 
162
165
  const convertReadableLabel = function (original) {
163
166
  const name = original.toLowerCase()
@@ -285,22 +288,18 @@ export default {
285
288
  return value;
286
289
  },
287
290
  createChildrenCascaderValue: function(children, facet, facets) {
288
- const parentKey = facet.key;
289
-
290
291
  if (children?.length) {
291
292
  for (let i = 0; i < children.length; i++) {
292
293
  const facetItem = children[i];
293
294
  //copy the facets into
294
295
  if (children[i].facetPropPath !== 'supportingAwards.consortium.name') {
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
- }
296
+ children[i].label = convertReadableLabel(
297
+ facetItem.label
298
+ )
301
299
  }
302
- if (facetItem.key && parentKey.includes('flatmap.connectivity.source.')) {
300
+ if (facetItem.key && facet.key.includes('flatmap.connectivity.source.')) {
303
301
  const childKey = facetItem.key;
302
+ const parentKey = facet.key;
304
303
  const key = childKey.replace(`${parentKey}.`, '');
305
304
  children[i].value = this.createCascaderItemValue([facet.label, key]);
306
305
  } else {
@@ -147,10 +147,23 @@ import {
147
147
  } from 'element-plus'
148
148
 
149
149
  import EventBus from './EventBus.js'
150
- import { generateUUID } from '../utils/common.js';
151
150
 
152
151
  const MAX_SEARCH_HISTORY = 12;
153
152
 
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
+
154
167
  export default {
155
168
  name: 'SearchHistory',
156
169
  components: {
@@ -334,8 +347,7 @@ export default {
334
347
  getParentComponentName: function () {
335
348
  const isConnectivity = this.localStorageKey?.indexOf('connectivity') !== -1;
336
349
  const isDataset = this.localStorageKey?.indexOf('dataset') !== -1;
337
- const isCellCard = this.localStorageKey?.indexOf('cell-card') !== -1;
338
- const location = isConnectivity ? 'connectivity' : isDataset ? 'dataset' : isCellCard ? 'cell_card' : '';
350
+ const location = isConnectivity ? 'connectivity' : isDataset ? 'dataset' : '';
339
351
  return location;
340
352
  },
341
353
  search: function (item) {