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

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.6",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -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,72 @@ 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 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;
912
+ }
913
+
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);
917
+
918
+ try {
919
+ const response = await fetch(APIURL, {
920
+ headers: {
921
+ 'Accept': 'application/json'
922
+ }
923
+ });
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
+ }
956
+ }
957
+ } catch (error) {
958
+ console.error('Error fetching expert consultant:', error);
959
+ }
960
+ }
961
+
962
+ this.updatedCopyContent = this.getUpdateCopyContent(this.storedReferences);
963
+ },
828
964
  onConnectivityHovered: function (label) {
829
965
  const payload = {
830
966
  connectivityInfo: this.entry,
@@ -938,6 +1074,7 @@ export default {
938
1074
  mounted: function () {
939
1075
  this.updatedCopyContent = this.getUpdateCopyContent();
940
1076
  this.updateTitleToggleVisibility();
1077
+ this.fetchExpertConsultants();
941
1078
 
942
1079
  EventBus.on('connectivity-error', (errorInfo) => {
943
1080
  const connectivityError = this.getConnectivityError(errorInfo);
@@ -1504,6 +1641,49 @@ export default {
1504
1641
  }
1505
1642
  }
1506
1643
 
1644
+ .consultant-block {
1645
+ padding-left: 1rem;
1646
+
1647
+ li + li {
1648
+ margin-top: 0.5rem;
1649
+ }
1650
+
1651
+ a {
1652
+ color: $app-primary-color;
1653
+ }
1654
+ }
1655
+
1656
+ .consultant-loading {
1657
+ margin: 0;
1658
+ position: relative;
1659
+
1660
+ span {
1661
+ position: static;
1662
+ visibility: hidden;
1663
+ opacity: 0;
1664
+ }
1665
+
1666
+ &::after {
1667
+ content: "";
1668
+ display: block;
1669
+ width: 100%;
1670
+ height: 100%;
1671
+ position: absolute;
1672
+ top: 0;
1673
+ left: 0;
1674
+ animation-duration: 3s;
1675
+ animation-fill-mode: forwards;
1676
+ animation-iteration-count: infinite;
1677
+ animation-name: loadingAnimation;
1678
+ animation-timing-function: linear;
1679
+ background: linear-gradient(to right,
1680
+ var(--el-bg-color-page) 5%,
1681
+ var(--el-color-info-light-8) 15%,
1682
+ var(--el-bg-color-page) 30%
1683
+ );
1684
+ }
1685
+ }
1686
+
1507
1687
  .alert-chip {
1508
1688
  margin-left: 5px;
1509
1689
  background-color: $app-primary-color;
@@ -1527,4 +1707,23 @@ export default {
1527
1707
  margin: 0;
1528
1708
  }
1529
1709
  }
1710
+
1711
+ .sr-only {
1712
+ clip: rect(0 0 0 0);
1713
+ clip-path: inset(50%);
1714
+ height: 1px;
1715
+ overflow: hidden;
1716
+ position: absolute;
1717
+ white-space: nowrap;
1718
+ width: 1px;
1719
+ }
1720
+
1721
+ @keyframes loadingAnimation {
1722
+ 0% {
1723
+ background-position: -30vw 0;
1724
+ }
1725
+ 100% {
1726
+ background-position: 70vw 0;
1727
+ }
1728
+ }
1530
1729
  </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']