@abi-software/map-side-bar 2.14.8-demo.4 → 2.14.8-demo.5

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.4",
3
+ "version": "2.14.8-demo.5",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
package/src/App.vue CHANGED
@@ -137,6 +137,7 @@ export default {
137
137
  ROOT_URL: import.meta.env.VITE_APP_ROOT_URL,
138
138
  FLATMAPAPI_LOCATION: import.meta.env.VITE_FLATMAPAPI_LOCATION,
139
139
  CELL_CARDS_API: import.meta.env.VITE_APP_CELL_CARDS_API,
140
+ CORS_PROXY_API: import.meta.env.VITE_CORS_PROXY_API,
140
141
  },
141
142
  connectivityEntry: [],
142
143
  createData: {
@@ -303,6 +303,22 @@
303
303
  ></div>
304
304
  </div>
305
305
  </div>
306
+
307
+ <div class="content-container" v-if="expertConsultants.length">
308
+ <div class="block attribute-title-container">
309
+ <span class="attribute-title">Expert Consultants</span>
310
+ </div>
311
+ <ul class="block consultant-block">
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>
317
+ <span>Loading {{ consultant.url }}</span>
318
+ </div>
319
+ </li>
320
+ </ul>
321
+ </div>
306
322
  </div>
307
323
  </template>
308
324
 
@@ -320,7 +336,7 @@ import {
320
336
  ElContainer as Container,
321
337
  ElIcon as Icon,
322
338
  } from 'element-plus'
323
-
339
+ import ContributorItem from './ContributorItem.vue'
324
340
  import EventBus from './EventBus.js'
325
341
  import {
326
342
  CopyToClipboard,
@@ -354,6 +370,7 @@ export default {
354
370
  ConnectivityGraph,
355
371
  ConnectivityList,
356
372
  ConnectivityReconciliationList,
373
+ ContributorItem,
357
374
  },
358
375
  props: {
359
376
  connectivityEntry: {
@@ -397,6 +414,8 @@ export default {
397
414
  connectivityFromMap: null,
398
415
  isTitleExpanded: false,
399
416
  showTitleToggle: false,
417
+ storedReferences: null,
418
+ expertConsultants: [],
400
419
  };
401
420
  },
402
421
  computed: {
@@ -468,8 +487,17 @@ export default {
468
487
  flatmapApi: function () {
469
488
  return this.envVars.FLATMAPAPI_LOCATION;
470
489
  },
490
+ expertConsultantURLs: function () {
491
+ return this.entryData['expert-consultants'] || [];
492
+ },
471
493
  },
472
494
  watch: {
495
+ entryData: {
496
+ deep: true,
497
+ handler: function () {
498
+ this.fetchExpertConsultants();
499
+ },
500
+ },
473
501
  entry: {
474
502
  deep: true,
475
503
  immediate: true,
@@ -613,6 +641,7 @@ export default {
613
641
  this.$emit('show-reference-connectivities', refSource);
614
642
  },
615
643
  onReferencesLoaded: function (references) {
644
+ this.storedReferences = references;
616
645
  this.updatedCopyContent = this.getUpdateCopyContent(references);
617
646
  },
618
647
  getUpdateCopyContent: function (references) {
@@ -794,6 +823,47 @@ export default {
794
823
  contentArray.push(contentString);
795
824
  }
796
825
 
826
+ // Expert Consultants
827
+ if (this.expertConsultants?.length) {
828
+ let contentString = `<div><strong>Expert Consultants</strong></div>`;
829
+ contentString += '\n';
830
+ const contentList = this.expertConsultants
831
+ .filter((consultant) => consultant.name)
832
+ .map((consultant) => {
833
+ const fields = [`<div><strong>${consultant.name}</strong></div>`];
834
+
835
+ if (consultant.orcidId) {
836
+ fields.push(`<div>`)
837
+ fields.push(`<strong>ORCID iD</strong>: <span>${consultant.orcidId}</span>`)
838
+ fields.push(`(<a href="${consultant.url}" target="_blank" rel="noopener noreferrer">${consultant.url}</a>)`)
839
+ fields.push(`</div>`);
840
+ }
841
+
842
+ const rrid = consultant.url.indexOf('RRID:') > -1
843
+ ? 'RRID:' + consultant.url.split('RRID:')[1]
844
+ : '';
845
+ if (rrid) {
846
+ fields.push(`<div>`)
847
+ fields.push(`<strong>RRID</strong>: <span>${rrid}</span>`)
848
+ fields.push(`(<a href="${consultant.url}" target="_blank" rel="noopener noreferrer">${consultant.url}</a>)`)
849
+ fields.push(`</div>`);
850
+ }
851
+
852
+ if (consultant.organization) {
853
+ fields.push(`<div><strong>Organization</strong>: ${consultant.organization}</div>`);
854
+ }
855
+
856
+ if (consultant.role) {
857
+ fields.push(`<div><strong>Title</strong>: ${consultant.role}</div>`);
858
+ }
859
+
860
+ return `<li>${fields.join('\n')}</li>`;
861
+ })
862
+ .join('\n');
863
+ contentString += `<ul>${contentList}</ul>`;
864
+ contentArray.push(contentString);
865
+ }
866
+
797
867
  // Alert (Notes)
798
868
  if (this.entry.featuresAlert?.length) {
799
869
  const alertContent = this.entry.featuresAlert
@@ -825,6 +895,70 @@ export default {
825
895
  });
826
896
  return data
827
897
  },
898
+ fetchExpertConsultants: async function () {
899
+ this.expertConsultants = this.expertConsultantURLs.map(url => ({ name: '', url }));
900
+
901
+ for (const url of this.expertConsultantURLs) {
902
+ const isScicrunchURL = url.startsWith('https://scicrunch.org');
903
+ const isOrcidURL = url.startsWith('https://orcid.org');
904
+
905
+ if (!isScicrunchURL && !isOrcidURL) {
906
+ console.warn(`Unsupported expert consultant URL: ${url}`);
907
+ continue;
908
+ }
909
+
910
+ // TEMPORARY: Use a CORS proxy for SciCrunch API requests
911
+ const corsProxy = this.envVars.CORS_PROXY_API;
912
+ const scicrunchAPIURL = (targetURL) => `${corsProxy}?target=${targetURL}.json`;
913
+ const orcidAPIURL = (targetURL) => targetURL.replace('orcid.org', 'pub.orcid.org/v2.1');
914
+ const APIURL = isScicrunchURL ? scicrunchAPIURL(url) : orcidAPIURL(url);
915
+
916
+ try {
917
+ const response = await fetch(APIURL, {
918
+ headers: {
919
+ 'Accept': 'application/json'
920
+ }
921
+ });
922
+ const data = await response.json();
923
+ if (isScicrunchURL) {
924
+ const { hits } = data?.hits || {};
925
+ if (hits?.length) {
926
+ const name = hits[0]?._source?.item?.name;
927
+ if (name) {
928
+ const consultantIndex = this.expertConsultants.findIndex((consultant) => consultant.url === url);
929
+ if (consultantIndex !== -1) {
930
+ this.expertConsultants[consultantIndex].name = name;
931
+ }
932
+ }
933
+ }
934
+ } else {
935
+ const nameInfo = data?.person?.name;
936
+ if (nameInfo) {
937
+ const givenName = nameInfo['given-names']?.value || '';
938
+ const familyName = nameInfo['family-name']?.value || '';
939
+ const creditName = nameInfo['credit-name']?.value || '';
940
+ const fullName = creditName || `${givenName} ${familyName}`.trim();
941
+ if (fullName) {
942
+ const consultantIndex = this.expertConsultants.findIndex((consultant) => consultant.url === url);
943
+ if (consultantIndex !== -1) {
944
+ this.expertConsultants[consultantIndex].name = fullName;
945
+ const orcidId = data['orcid-identifier']?.path || '';
946
+ this.expertConsultants[consultantIndex].orcidId = orcidId;
947
+ const employmentSummary = data?.['activities-summary']?.['employments']?.['employment-summary'] || [];
948
+ const latestEmployment = employmentSummary[0] || {};
949
+ this.expertConsultants[consultantIndex].organization = latestEmployment?.organization?.name || '';
950
+ this.expertConsultants[consultantIndex].role = latestEmployment?.['role-title'] || '';
951
+ }
952
+ }
953
+ }
954
+ }
955
+ } catch (error) {
956
+ console.error('Error fetching expert consultant:', error);
957
+ }
958
+ }
959
+
960
+ this.updatedCopyContent = this.getUpdateCopyContent(this.storedReferences);
961
+ },
828
962
  onConnectivityHovered: function (label) {
829
963
  const payload = {
830
964
  connectivityInfo: this.entry,
@@ -938,6 +1072,7 @@ export default {
938
1072
  mounted: function () {
939
1073
  this.updatedCopyContent = this.getUpdateCopyContent();
940
1074
  this.updateTitleToggleVisibility();
1075
+ this.fetchExpertConsultants();
941
1076
 
942
1077
  EventBus.on('connectivity-error', (errorInfo) => {
943
1078
  const connectivityError = this.getConnectivityError(errorInfo);
@@ -1504,6 +1639,49 @@ export default {
1504
1639
  }
1505
1640
  }
1506
1641
 
1642
+ .consultant-block {
1643
+ padding-left: 1rem;
1644
+
1645
+ li + li {
1646
+ margin-top: 0.5rem;
1647
+ }
1648
+
1649
+ a {
1650
+ color: $app-primary-color;
1651
+ }
1652
+ }
1653
+
1654
+ .consultant-loading {
1655
+ margin: 0;
1656
+ position: relative;
1657
+
1658
+ span {
1659
+ position: static;
1660
+ visibility: hidden;
1661
+ opacity: 0;
1662
+ }
1663
+
1664
+ &::after {
1665
+ content: "";
1666
+ display: block;
1667
+ width: 100%;
1668
+ height: 100%;
1669
+ position: absolute;
1670
+ top: 0;
1671
+ left: 0;
1672
+ animation-duration: 3s;
1673
+ animation-fill-mode: forwards;
1674
+ animation-iteration-count: infinite;
1675
+ animation-name: loadingAnimation;
1676
+ animation-timing-function: linear;
1677
+ background: linear-gradient(to right,
1678
+ var(--el-bg-color-page) 5%,
1679
+ var(--el-color-info-light-8) 15%,
1680
+ var(--el-bg-color-page) 30%
1681
+ );
1682
+ }
1683
+ }
1684
+
1507
1685
  .alert-chip {
1508
1686
  margin-left: 5px;
1509
1687
  background-color: $app-primary-color;
@@ -1527,4 +1705,23 @@ export default {
1527
1705
  margin: 0;
1528
1706
  }
1529
1707
  }
1708
+
1709
+ .sr-only {
1710
+ clip: rect(0 0 0 0);
1711
+ clip-path: inset(50%);
1712
+ height: 1px;
1713
+ overflow: hidden;
1714
+ position: absolute;
1715
+ white-space: nowrap;
1716
+ width: 1px;
1717
+ }
1718
+
1719
+ @keyframes loadingAnimation {
1720
+ 0% {
1721
+ background-position: -30vw 0;
1722
+ }
1723
+ 100% {
1724
+ background-position: 70vw 0;
1725
+ }
1726
+ }
1530
1727
  </style>
@@ -0,0 +1,108 @@
1
+ <template>
2
+ <div class="contributor-item">
3
+ <el-popover
4
+ placement="top-start"
5
+ width="320"
6
+ trigger="hover"
7
+ popper-class="contributor-popover"
8
+ :teleported="false"
9
+ >
10
+ <div class="popover-content">
11
+ <div class="popover-name">{{ contributor.name }}</div>
12
+ <div class="popover-url" v-if="contributor.orcidId">
13
+ <strong>ORCID iD</strong>:
14
+ <a :href="contributor.url" target="_blank">{{ orcidId }}</a>
15
+ </div>
16
+ <div class="popover-url" v-if="rrid">
17
+ <strong>RRID</strong>:
18
+ <a :href="contributor.url" target="_blank">{{ rrid }}</a>
19
+ </div>
20
+ <div class="popover-organization" v-if="contributor.organization">
21
+ <strong>Organization</strong>: {{ contributor.organization }}
22
+ </div>
23
+ <div class="popover-role" v-if="contributor.role">
24
+ <strong>Title</strong>: {{ contributor.role }}
25
+ </div>
26
+ </div>
27
+ <template #reference>
28
+ <span>{{ contributor.name }}</span>
29
+ </template>
30
+ </el-popover>
31
+ </div>
32
+ </template>
33
+
34
+ <script>
35
+ export default {
36
+ name: 'ContributorItem',
37
+
38
+ props: {
39
+ contributor: {
40
+ type: Object,
41
+ default: () => ({})
42
+ }
43
+ },
44
+
45
+ computed: {
46
+ orcidId: function () {
47
+ return this.contributor.orcidId || ''
48
+ },
49
+
50
+ rrid: function () {
51
+ return this.contributor.url.indexOf('RRID:') > -1
52
+ ? 'RRID:' + this.contributor.url.split('RRID:')[1]
53
+ : ''
54
+ }
55
+ }
56
+ }
57
+ </script>
58
+
59
+ <style lang="scss" scoped>
60
+ @import '../assets/_variables.scss';
61
+
62
+ .contributor-item {
63
+ display: inline;
64
+ text-decoration: underline;
65
+ color: $app-primary-color;
66
+ }
67
+ </style>
68
+
69
+ <style lang="scss">
70
+ @import '../assets/_variables.scss';
71
+
72
+ .contributor-popover.el-popover {
73
+ font-family: $font-family;
74
+ background: #f3ecf6 !important;
75
+ border: 1px solid $app-primary-color !important;
76
+ border-radius: 4px !important;
77
+ color: $grey !important;
78
+ text-transform: none !important;
79
+ font-weight: 400;
80
+
81
+ .el-popper__arrow::before {
82
+ background: #f3ecf6 !important;
83
+ border-color: $app-primary-color !important;
84
+ background-color: #ffffff;
85
+ }
86
+
87
+ .popover-content {
88
+ line-height: inherit;
89
+ }
90
+
91
+ .popover-name {
92
+ font-size: 1.3em;
93
+ margin-bottom: 0.25em;
94
+ }
95
+
96
+ .popover-url {
97
+ a {
98
+ color: $app-primary-color;
99
+ word-break: break-all;
100
+ }
101
+ }
102
+
103
+ .popover-organization,
104
+ .popover-role {
105
+ word-break: normal;
106
+ }
107
+ }
108
+ </style>
@@ -14,6 +14,7 @@ declare module 'vue' {
14
14
  ConnectivityCard: typeof import('./components/ConnectivityCard.vue')['default']
15
15
  ConnectivityExplorer: typeof import('./components/ConnectivityExplorer.vue')['default']
16
16
  ConnectivityInfo: typeof import('./components/ConnectivityInfo.vue')['default']
17
+ ContributorItem: typeof import('./components/ContributorItem.vue')['default']
17
18
  DatasetCard: typeof import('./components/DatasetCard.vue')['default']
18
19
  DatasetExplorer: typeof import('./components/DatasetExplorer.vue')['default']
19
20
  ElButton: typeof import('element-plus/es')['ElButton']