@kiva/kv-components 3.106.0 → 3.107.1
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/.eslintrc.cjs +1 -0
- package/CHANGELOG.md +22 -0
- package/dist/components/.storybook/main.js +85 -0
- package/dist/components/.storybook/package.json +3 -0
- package/dist/components/.storybook/preview.js +61 -0
- package/dist/components/.storybook/tailwind.css +5 -0
- package/dist/components/KvAccordionItem.vue +130 -0
- package/dist/components/KvActivityRow.vue +33 -0
- package/dist/components/KvBorrowerImage.vue +179 -0
- package/dist/components/KvButton.vue +287 -0
- package/dist/components/KvCarousel.vue +297 -0
- package/dist/components/KvCartModal.vue +365 -0
- package/dist/components/KvCheckbox.vue +203 -0
- package/dist/components/KvChip.vue +54 -0
- package/dist/components/KvClassicLoanCard.vue +527 -0
- package/dist/components/KvCommentsAdd.vue +135 -0
- package/dist/components/KvCommentsContainer.vue +84 -0
- package/dist/components/KvCommentsHeartButton.vue +70 -0
- package/dist/components/KvCommentsList.vue +68 -0
- package/dist/components/KvCommentsListItem.vue +241 -0
- package/dist/components/KvCommentsReplyButton.vue +52 -0
- package/dist/components/KvContentfulImg.vue +273 -0
- package/dist/components/KvCountdownTimer.vue +59 -0
- package/dist/components/KvExpandable.vue +84 -0
- package/dist/components/KvExpandableQuestion.vue +120 -0
- package/dist/components/KvFlag.vue +120 -0
- package/dist/components/KvGrid.vue +28 -0
- package/dist/components/KvImpactDashboardHeader.vue +40 -0
- package/dist/components/KvInlineActivityCard.vue +55 -0
- package/dist/components/KvInlineActivityFeed.vue +38 -0
- package/dist/components/KvIntroductionLoanCard.vue +446 -0
- package/dist/components/KvLendAmountButton.vue +65 -0
- package/dist/components/KvLendCta.vue +451 -0
- package/dist/components/KvLightbox.vue +334 -0
- package/dist/components/KvLineGraph.vue +128 -0
- package/dist/components/KvLoadingPlaceholder.vue +38 -0
- package/dist/components/KvLoadingSpinner.vue +81 -0
- package/dist/components/KvLoanActivities.vue +268 -0
- package/dist/components/KvLoanBookmark.vue +39 -0
- package/dist/components/KvLoanCallouts.vue +53 -0
- package/dist/components/KvLoanProgressGroup.vue +76 -0
- package/dist/components/KvLoanTag.vue +88 -0
- package/dist/components/KvLoanTeamPick.vue +44 -0
- package/dist/components/KvLoanUse.vue +92 -0
- package/dist/components/KvMap.vue +599 -0
- package/dist/components/KvMaterialIcon.vue +47 -0
- package/dist/components/KvPageContainer.vue +15 -0
- package/dist/components/KvPagination.vue +198 -0
- package/dist/components/KvPieChart.vue +257 -0
- package/dist/components/KvPopper.vue +178 -0
- package/dist/components/KvProgressBar.vue +149 -0
- package/dist/components/KvRadio.vue +198 -0
- package/dist/components/KvSelect.vue +114 -0
- package/dist/components/KvSideSheet.vue +134 -0
- package/dist/components/KvSwitch.vue +143 -0
- package/dist/components/KvTab.vue +90 -0
- package/dist/components/KvTabPanel.vue +64 -0
- package/dist/components/KvTabs.vue +182 -0
- package/dist/components/KvTextInput.vue +247 -0
- package/dist/components/KvTextLink.vue +138 -0
- package/dist/components/KvThemeProvider.vue +122 -0
- package/dist/components/KvToast.vue +221 -0
- package/dist/components/KvTooltip.vue +168 -0
- package/dist/components/KvTreeMapChart.vue +229 -0
- package/dist/components/KvUserAvatar.vue +132 -0
- package/dist/components/KvVerticalCarousel.vue +156 -0
- package/dist/components/KvVotingCard.vue +160 -0
- package/dist/components/KvVotingCardV2.vue +154 -0
- package/dist/components/KvWideLoanCard.vue +432 -0
- package/dist/components/stories/Forms.stories.js +62 -0
- package/dist/components/stories/KvAccordionItem.stories.js +24 -0
- package/dist/components/stories/KvActivityRow.stories.js +25 -0
- package/dist/components/stories/KvBorrowerImage.stories.js +68 -0
- package/dist/components/stories/KvButton.stories.js +144 -0
- package/dist/components/stories/KvCarousel.stories.js +426 -0
- package/dist/components/stories/KvCartModal.stories.js +54 -0
- package/dist/components/stories/KvCheckbox.stories.js +163 -0
- package/dist/components/stories/KvChip.stories.js +43 -0
- package/dist/components/stories/KvClassicLoanCard.stories.js +480 -0
- package/dist/components/stories/KvCommentsAdd.stories.js +32 -0
- package/dist/components/stories/KvCommentsContainer.stories.js +42 -0
- package/dist/components/stories/KvCommentsHeartButton.stories.js +25 -0
- package/dist/components/stories/KvCommentsList.stories.js +39 -0
- package/dist/components/stories/KvCommentsListItem.stories.js +45 -0
- package/dist/components/stories/KvCommentsReplyButton.stories.js +21 -0
- package/dist/components/stories/KvContentfulImg.stories.js +196 -0
- package/dist/components/stories/KvCountdownTimer.stories.js +30 -0
- package/dist/components/stories/KvExpandableQuestion.stories.js +129 -0
- package/dist/components/stories/KvFlag.stories.js +36 -0
- package/dist/components/stories/KvGrid.stories.js +97 -0
- package/dist/components/stories/KvImpactDashboardHeader.stories.js +22 -0
- package/dist/components/stories/KvInlineActivityCard.stories.js +69 -0
- package/dist/components/stories/KvInlineActivityFeed.stories.js +76 -0
- package/dist/components/stories/KvIntroductionLoanCard.stories.js +208 -0
- package/dist/components/stories/KvLendAmountButton.stories.js +31 -0
- package/dist/components/stories/KvLendCta.stories.js +177 -0
- package/dist/components/stories/KvLightbox.stories.js +304 -0
- package/dist/components/stories/KvLineGraph.stories.js +52 -0
- package/dist/components/stories/KvLoadingPlaceholder.stories.js +17 -0
- package/dist/components/stories/KvLoadingSpinner.stories.js +52 -0
- package/dist/components/stories/KvLoanActivities.stories.js +104 -0
- package/dist/components/stories/KvLoanBookmark.stories.js +22 -0
- package/dist/components/stories/KvLoanCallouts.stories.js +22 -0
- package/dist/components/stories/KvLoanProgressGroup.stories.js +29 -0
- package/dist/components/stories/KvLoanTag.stories.js +61 -0
- package/dist/components/stories/KvLoanTeamPick.stories.js +20 -0
- package/dist/components/stories/KvLoanUse.stories.js +60 -0
- package/dist/components/stories/KvMap.stories.js +121 -0
- package/dist/components/stories/KvMaterialIcon.stories.js +201 -0
- package/dist/components/stories/KvPageContainer.stories.js +50 -0
- package/dist/components/stories/KvPagination.stories.js +70 -0
- package/dist/components/stories/KvPieChart.stories.js +47 -0
- package/dist/components/stories/KvProgressBar.stories.js +53 -0
- package/dist/components/stories/KvRadio.stories.js +140 -0
- package/dist/components/stories/KvSelect.stories.js +125 -0
- package/dist/components/stories/KvSideSheet.stories.js +50 -0
- package/dist/components/stories/KvSwitch.stories.js +66 -0
- package/dist/components/stories/KvTabs.stories.js +106 -0
- package/dist/components/stories/KvTextInput.stories.js +194 -0
- package/dist/components/stories/KvTextLink.stories.js +55 -0
- package/dist/components/stories/KvThemeProvider.stories.js +178 -0
- package/dist/components/stories/KvToast.stories.js +117 -0
- package/dist/components/stories/KvTooltip.stories.js +26 -0
- package/dist/components/stories/KvTreeMapChart.stories.js +42 -0
- package/dist/components/stories/KvUserAvatar.stories.js +47 -0
- package/dist/components/stories/KvVerticalCarousel.stories.js +168 -0
- package/dist/components/stories/KvVotingCard.stories.js +33 -0
- package/dist/components/stories/KvVotingCardV2.stories.js +89 -0
- package/dist/components/stories/KvWideLoanCard.stories.js +292 -0
- package/dist/components/stories/StyleguidePrimitives.stories.js +499 -0
- package/dist/components/stories/StyleguideProse.stories.js +215 -0
- package/dist/data/countries-borders.json +1 -0
- package/dist/data/ne_110m_admin_0_countries.json +1 -0
- package/dist/utils/Alea.cjs +87 -0
- package/dist/utils/Alea.js +9 -0
- package/dist/utils/attrs.cjs +50 -0
- package/dist/utils/attrs.js +7 -0
- package/dist/utils/carousels.cjs +184 -0
- package/dist/utils/carousels.js +8 -0
- package/dist/utils/chunk-3HK4G4NT.js +27 -0
- package/dist/utils/chunk-55HF2ORX.js +201 -0
- package/dist/utils/chunk-AY3PR5S4.js +54 -0
- package/dist/utils/chunk-AZPWOFD5.js +148 -0
- package/dist/utils/chunk-B5J5WLAH.js +18 -0
- package/dist/utils/chunk-GPSH6OPA.js +64 -0
- package/dist/utils/chunk-HIY5IW65.js +28 -0
- package/dist/utils/chunk-HV3AUBFT.js +15 -0
- package/dist/utils/chunk-MSMZIN54.js +110 -0
- package/dist/utils/chunk-OXJCCNNW.js +30 -0
- package/dist/utils/chunk-S3MABILA.js +22 -0
- package/dist/utils/chunk-VIGEMAKO.js +249 -0
- package/dist/utils/chunk-YCNMJ4YV.js +37 -0
- package/dist/utils/chunk-YFEC5ODJ.js +129 -0
- package/dist/utils/expander.cjs +78 -0
- package/dist/utils/expander.js +9 -0
- package/dist/utils/imageUtils.cjs +54 -0
- package/dist/utils/imageUtils.js +9 -0
- package/dist/utils/index.cjs +1118 -0
- package/dist/utils/index.js +166 -0
- package/dist/utils/loanCard.cjs +222 -0
- package/dist/utils/loanCard.js +9 -0
- package/dist/utils/loanUtils.cjs +170 -0
- package/dist/utils/loanUtils.js +23 -0
- package/dist/utils/mapUtils.cjs +276 -0
- package/dist/utils/mapUtils.js +15 -0
- package/dist/utils/printing.cjs +42 -0
- package/dist/utils/printing.js +9 -0
- package/dist/utils/scrollLock.cjs +54 -0
- package/dist/utils/scrollLock.js +13 -0
- package/dist/utils/throttle.cjs +38 -0
- package/dist/utils/throttle.js +7 -0
- package/dist/utils/touchEvents.cjs +47 -0
- package/dist/utils/touchEvents.js +11 -0
- package/dist/utils/treemap.cjs +133 -0
- package/dist/utils/treemap.js +7 -0
- package/package.json +12 -4
- package/utils/index.js +14 -0
- package/vue/KvVerticalCarousel.vue +1 -1
- package/index.js +0 -3
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<!-- eslint-disable vue/no-v-html -->
|
|
2
|
+
<template>
|
|
3
|
+
<p
|
|
4
|
+
class="tw-line-clamp-4"
|
|
5
|
+
v-html="loanUse"
|
|
6
|
+
></p>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<script>
|
|
10
|
+
import numeral from 'numeral';
|
|
11
|
+
|
|
12
|
+
const DIRECT = 'direct';
|
|
13
|
+
|
|
14
|
+
export default {
|
|
15
|
+
name: 'KvLoanUse',
|
|
16
|
+
props: {
|
|
17
|
+
anonymizationLevel: {
|
|
18
|
+
type: String,
|
|
19
|
+
default: 'none',
|
|
20
|
+
},
|
|
21
|
+
use: {
|
|
22
|
+
type: String,
|
|
23
|
+
default: '',
|
|
24
|
+
},
|
|
25
|
+
loanAmount: {
|
|
26
|
+
type: String,
|
|
27
|
+
default: '',
|
|
28
|
+
},
|
|
29
|
+
status: {
|
|
30
|
+
type: String,
|
|
31
|
+
default: '',
|
|
32
|
+
},
|
|
33
|
+
borrowerCount: {
|
|
34
|
+
type: Number,
|
|
35
|
+
default: 1,
|
|
36
|
+
},
|
|
37
|
+
name: {
|
|
38
|
+
type: String,
|
|
39
|
+
default: '',
|
|
40
|
+
},
|
|
41
|
+
distributionModel: {
|
|
42
|
+
type: String,
|
|
43
|
+
default: DIRECT,
|
|
44
|
+
},
|
|
45
|
+
whySpecial: {
|
|
46
|
+
type: String,
|
|
47
|
+
default: '',
|
|
48
|
+
},
|
|
49
|
+
hideLoanAmount: {
|
|
50
|
+
type: Boolean,
|
|
51
|
+
default: false,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
computed: {
|
|
55
|
+
helpLanguage() {
|
|
56
|
+
if (this.status === 'fundraising' || this.status === 'inactive' || this.status === 'reviewed') {
|
|
57
|
+
return 'helps';
|
|
58
|
+
}
|
|
59
|
+
return 'helped';
|
|
60
|
+
},
|
|
61
|
+
isDirect() {
|
|
62
|
+
return this.distributionModel === DIRECT;
|
|
63
|
+
},
|
|
64
|
+
whySpecialSentence() {
|
|
65
|
+
return this.whySpecial
|
|
66
|
+
? ` This loan is special because ${this.whySpecial.charAt(0).toLowerCase() + this.whySpecial.slice(1)}`
|
|
67
|
+
: '';
|
|
68
|
+
},
|
|
69
|
+
loanUse() {
|
|
70
|
+
if (this.anonymizationLevel === 'full' || this.use.length === 0) {
|
|
71
|
+
return 'For the borrower\'s privacy, this loan has been made anonymous.';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (this.hideLoanAmount) {
|
|
75
|
+
return `Help <span class="data-hj-suppress">${this.name}</span> `
|
|
76
|
+
+ `${this.use.charAt(0).toLowerCase() + this.use.slice(1)} `
|
|
77
|
+
+ `${this.whySpecialSentence}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const isGroup = this.borrowerCount > 1;
|
|
81
|
+
|
|
82
|
+
return `${numeral(this.loanAmount).format('$0,0')} `
|
|
83
|
+
+ `${this.isDirect ? 'to' : this.helpLanguage} `
|
|
84
|
+
+ `${isGroup ? 'a member of ' : ''}`
|
|
85
|
+
+ `<span class="data-hj-suppress">${this.name}</span> `
|
|
86
|
+
+ `${this.isDirect ? `${this.helpLanguage} ` : ''}`
|
|
87
|
+
+ `${this.use.charAt(0).toLowerCase() + this.use.slice(1)}`
|
|
88
|
+
+ `${this.whySpecialSentence}`;
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
</script>
|
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="tw-relative tw-block tw-w-full"
|
|
4
|
+
:style="mapDimensions"
|
|
5
|
+
>
|
|
6
|
+
<div
|
|
7
|
+
:id="`kv-map-holder-${mapId}`"
|
|
8
|
+
:ref="refString"
|
|
9
|
+
class="tw-w-full tw-h-full tw-bg-black"
|
|
10
|
+
:style="{ position: 'absolute' }"
|
|
11
|
+
></div>
|
|
12
|
+
</div>
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<script>
|
|
16
|
+
import kvTokensPrimitives from '@kiva/kv-tokens/primitives.json';
|
|
17
|
+
import { animationCoordinator, generateMapMarkers, getCountryColor } from '../utils/mapUtils';
|
|
18
|
+
|
|
19
|
+
export default {
|
|
20
|
+
name: 'KvMap',
|
|
21
|
+
props: {
|
|
22
|
+
/**
|
|
23
|
+
* Aspect Ration for computed map dimensions
|
|
24
|
+
* We'll divide the container width by this to determine the height
|
|
25
|
+
*/
|
|
26
|
+
aspectRatio: {
|
|
27
|
+
type: Number,
|
|
28
|
+
default: 1,
|
|
29
|
+
},
|
|
30
|
+
/**
|
|
31
|
+
* Control how quickly the autoZoom occurs
|
|
32
|
+
*/
|
|
33
|
+
autoZoomDelay: {
|
|
34
|
+
type: Number,
|
|
35
|
+
default: 1500,
|
|
36
|
+
},
|
|
37
|
+
/**
|
|
38
|
+
* Set the height to override aspect ratio driven and/or default dimensions
|
|
39
|
+
*/
|
|
40
|
+
height: {
|
|
41
|
+
type: Number,
|
|
42
|
+
default: null,
|
|
43
|
+
},
|
|
44
|
+
/**
|
|
45
|
+
* Setting this initialZoom will zoom the map from initialZoom to zoom when the map enters the viewport
|
|
46
|
+
*/
|
|
47
|
+
initialZoom: {
|
|
48
|
+
type: Number,
|
|
49
|
+
default: null,
|
|
50
|
+
},
|
|
51
|
+
/**
|
|
52
|
+
* Set the center point latitude
|
|
53
|
+
*/
|
|
54
|
+
lat: {
|
|
55
|
+
type: Number,
|
|
56
|
+
default: null,
|
|
57
|
+
},
|
|
58
|
+
/**
|
|
59
|
+
* Set the center point longitude
|
|
60
|
+
*/
|
|
61
|
+
long: {
|
|
62
|
+
type: Number,
|
|
63
|
+
default: null,
|
|
64
|
+
},
|
|
65
|
+
/**
|
|
66
|
+
* Set this if there are more than one map on the page
|
|
67
|
+
*/
|
|
68
|
+
mapId: {
|
|
69
|
+
type: Number,
|
|
70
|
+
default: 0,
|
|
71
|
+
},
|
|
72
|
+
/**
|
|
73
|
+
* Force use of Leaflet
|
|
74
|
+
*/
|
|
75
|
+
useLeaflet: {
|
|
76
|
+
type: Boolean,
|
|
77
|
+
default: false,
|
|
78
|
+
},
|
|
79
|
+
/**
|
|
80
|
+
* Set the width to override aspect ratio driven and/or default dimensions
|
|
81
|
+
*/
|
|
82
|
+
width: {
|
|
83
|
+
type: Number,
|
|
84
|
+
default: null,
|
|
85
|
+
},
|
|
86
|
+
/**
|
|
87
|
+
* Default zoom level
|
|
88
|
+
*/
|
|
89
|
+
zoomLevel: {
|
|
90
|
+
type: Number,
|
|
91
|
+
default: 4,
|
|
92
|
+
},
|
|
93
|
+
/**
|
|
94
|
+
* Borrower points object.
|
|
95
|
+
* If this object is present, the advanced animation will be triggered
|
|
96
|
+
* Sample object:
|
|
97
|
+
* {
|
|
98
|
+
borrowerPoints: [
|
|
99
|
+
{
|
|
100
|
+
image: 'https://www-kiva-org.freetls.fastly.net/img/w80h80fz50/e60a3d61ff052d60991c5d6bbf4a45d3.jpg',
|
|
101
|
+
location: [-77.032, 38.913],
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
image: 'https://www-kiva-org.freetls.fastly.net/img/w80h80fz50/6101929097c6e5de48232a4d1ae3b71c.jpg',
|
|
105
|
+
location: [41.402, 7.160],
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
image: 'https://www-kiva-org.freetls.fastly.net/img/w80h80fz50/11e018ee3d8b9c5adee459c16a29d264.jpg',
|
|
109
|
+
location: [-73.356596, 3.501],
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
* }
|
|
113
|
+
*/
|
|
114
|
+
advancedAnimation: {
|
|
115
|
+
type: Object,
|
|
116
|
+
required: false,
|
|
117
|
+
default: () => ({}),
|
|
118
|
+
},
|
|
119
|
+
/**
|
|
120
|
+
* Show the zoom control
|
|
121
|
+
*/
|
|
122
|
+
showZoomControl: {
|
|
123
|
+
type: Boolean,
|
|
124
|
+
default: false,
|
|
125
|
+
},
|
|
126
|
+
/**
|
|
127
|
+
* Allow dragging of the map
|
|
128
|
+
*/
|
|
129
|
+
allowDragging: {
|
|
130
|
+
type: Boolean,
|
|
131
|
+
default: false,
|
|
132
|
+
},
|
|
133
|
+
/**
|
|
134
|
+
* Show labels on the map
|
|
135
|
+
* Working for leaflet only
|
|
136
|
+
*/
|
|
137
|
+
showLabels: {
|
|
138
|
+
type: Boolean,
|
|
139
|
+
default: true,
|
|
140
|
+
},
|
|
141
|
+
/**
|
|
142
|
+
* Lender data for the map
|
|
143
|
+
* Working for leaflet only
|
|
144
|
+
*/
|
|
145
|
+
countriesData: {
|
|
146
|
+
type: Array,
|
|
147
|
+
default: () => ([]),
|
|
148
|
+
},
|
|
149
|
+
/**
|
|
150
|
+
* Show fundraising loans
|
|
151
|
+
* Working for leaflet only
|
|
152
|
+
*/
|
|
153
|
+
showFundraisingLoans: {
|
|
154
|
+
type: Boolean,
|
|
155
|
+
default: false,
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
data() {
|
|
159
|
+
return {
|
|
160
|
+
hasWebGL: false,
|
|
161
|
+
leafletReady: false,
|
|
162
|
+
mapInstance: null,
|
|
163
|
+
mapLibreReady: false,
|
|
164
|
+
mapLoaded: false,
|
|
165
|
+
zoomActive: false,
|
|
166
|
+
countriesBorders: {},
|
|
167
|
+
};
|
|
168
|
+
},
|
|
169
|
+
computed: {
|
|
170
|
+
mapDimensions() {
|
|
171
|
+
// Use container to derive height based on aspect ration + width
|
|
172
|
+
const container = this.$el?.getBoundingClientRect();
|
|
173
|
+
const height = container ? `${container.width / this.aspectRatio}px` : '300px';
|
|
174
|
+
const width = container ? `${container.width}px` : '100%';
|
|
175
|
+
// Override values if deliberate height or width are provided
|
|
176
|
+
return {
|
|
177
|
+
height: this.height ? `${this.height}px` : height,
|
|
178
|
+
width: this.width ? `${this.width}px` : width,
|
|
179
|
+
paddingBottom: this.height ? `${this.height}px` : `${100 / this.aspectRatio}%`,
|
|
180
|
+
};
|
|
181
|
+
},
|
|
182
|
+
refString() {
|
|
183
|
+
return `mapholder${this.mapId}`;
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
watch: {
|
|
187
|
+
lat(next, prev) {
|
|
188
|
+
if (prev === null && this.long && !this.mapLibreReady && !this.leafletReady) {
|
|
189
|
+
this.initializeMap();
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
long(next, prev) {
|
|
193
|
+
if (prev === null && this.lat && !this.mapLibreReady && !this.leafletReady) {
|
|
194
|
+
this.initializeMap();
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
showFundraisingLoans() {
|
|
198
|
+
if (this.mapInstance) {
|
|
199
|
+
this.mapInstance.remove();
|
|
200
|
+
this.initializeLeaflet();
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
async mounted() {
|
|
205
|
+
if (this.countriesData) {
|
|
206
|
+
// current source data is from https://geojson.xyz/ under "admin 0 countries"
|
|
207
|
+
// https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_110m_admin_0_countries.geojson
|
|
208
|
+
this.countriesBorders = await import('../data/ne_110m_admin_0_countries.json');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (!this.mapLibreReady && !this.leafletReady) {
|
|
212
|
+
this.initializeMap();
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
beforeDestroy() {
|
|
216
|
+
if (this.mapInstance) {
|
|
217
|
+
if (!this.hasWebGL && !this.leafletReady) {
|
|
218
|
+
// turn off the leaflet instance
|
|
219
|
+
this.mapInstance.off();
|
|
220
|
+
}
|
|
221
|
+
// remove either leaflet or maplibregl
|
|
222
|
+
this.mapInstance.remove();
|
|
223
|
+
}
|
|
224
|
+
this.destroyWrapperObserver();
|
|
225
|
+
},
|
|
226
|
+
methods: {
|
|
227
|
+
activateZoom(zoomOut = false) {
|
|
228
|
+
const { mapInstance, hasWebGL, mapLibreReady } = this;
|
|
229
|
+
const currentZoomLevel = mapInstance.getZoom();
|
|
230
|
+
// exit if already zoomed in (getZoom() works for both leaflet + maplibregl)
|
|
231
|
+
if ((!zoomOut && currentZoomLevel === this.zoomLevel)
|
|
232
|
+
|| (zoomOut && currentZoomLevel === this.initialZoom)) return false;
|
|
233
|
+
|
|
234
|
+
this.zoomActive = true;
|
|
235
|
+
// establish delayed zoom duration
|
|
236
|
+
const timedZoom = window.setTimeout(() => {
|
|
237
|
+
if (hasWebGL && mapLibreReady) {
|
|
238
|
+
// maplibregl specific zoom method
|
|
239
|
+
mapInstance.zoomTo(
|
|
240
|
+
zoomOut ? this.initialZoom : this.zoomLevel,
|
|
241
|
+
{ duration: 1200 },
|
|
242
|
+
);
|
|
243
|
+
} else {
|
|
244
|
+
// leaflet specific zoom method
|
|
245
|
+
mapInstance.setZoom(zoomOut ? this.initialZoom : this.zoomLevel);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
clearTimeout(timedZoom);
|
|
249
|
+
this.zoomActive = false;
|
|
250
|
+
}, this.autoZoomDelay);
|
|
251
|
+
},
|
|
252
|
+
createWrapperObserver() {
|
|
253
|
+
// Watch for the wrapper element moving in and out of the viewport
|
|
254
|
+
this.wrapperObserver = this.createIntersectionObserver({
|
|
255
|
+
targets: [this.$refs?.[this.refString]],
|
|
256
|
+
callback: (entries) => {
|
|
257
|
+
// only activate autoZoom if we have an initialZoom set
|
|
258
|
+
entries.forEach((entry) => {
|
|
259
|
+
if (entry.target === this.$refs?.[this.refString] && !this.zoomActive) {
|
|
260
|
+
if (entry.intersectionRatio > 0) {
|
|
261
|
+
// activate zoom
|
|
262
|
+
if (this.initialZoom !== null) {
|
|
263
|
+
this.activateZoom();
|
|
264
|
+
}
|
|
265
|
+
// animate map
|
|
266
|
+
if (this.advancedAnimation?.borrowerPoints) {
|
|
267
|
+
this.animateMap();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
},
|
|
275
|
+
destroyWrapperObserver() {
|
|
276
|
+
if (this.wrapperObserver) {
|
|
277
|
+
this.wrapperObserver.disconnect();
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
checkWebGL() {
|
|
281
|
+
// exit and use leaflet if specified or document isn't present
|
|
282
|
+
if (this.useLeaflet || typeof document === 'undefined') return false;
|
|
283
|
+
// via. https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/By_example/Detect_WebGL
|
|
284
|
+
// Create canvas element. The canvas is not added to the document itself,
|
|
285
|
+
// so it is never displayed in the browser window.
|
|
286
|
+
const canvas = document.createElement('canvas');
|
|
287
|
+
// Get WebGLRenderingContext from canvas element.
|
|
288
|
+
const gl = canvas.getContext('webgl')
|
|
289
|
+
|| canvas.getContext('experimental-webgl');
|
|
290
|
+
// Report the result.
|
|
291
|
+
if (gl && gl instanceof WebGLRenderingContext) {
|
|
292
|
+
this.hasWebGL = true;
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
return false;
|
|
296
|
+
},
|
|
297
|
+
initializeMap() {
|
|
298
|
+
/**
|
|
299
|
+
* This initial checkWebGL() call kicks off the library asset inclusion
|
|
300
|
+
* We then start polling for the readiness of our selected map library and initialize it once ready
|
|
301
|
+
*/
|
|
302
|
+
const mapScript = document.createElement('script');
|
|
303
|
+
const mapStyle = document.createElement('link');
|
|
304
|
+
mapScript.setAttribute('async', true);
|
|
305
|
+
mapScript.setAttribute('defer', true);
|
|
306
|
+
mapStyle.setAttribute('rel', 'stylesheet');
|
|
307
|
+
if (this.checkWebGL()) {
|
|
308
|
+
mapScript.setAttribute('vmid', `maplibregljs${this.mapId}`);
|
|
309
|
+
mapStyle.setAttribute('vmid', `maplibreglcss${this.mapId}`);
|
|
310
|
+
mapScript.setAttribute('src', 'https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.js');
|
|
311
|
+
mapStyle.setAttribute('href', 'https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.css');
|
|
312
|
+
|
|
313
|
+
this.testDelayedGlobalLibrary('maplibregl').then((response) => {
|
|
314
|
+
if (response.loaded && !this.mapLoaded && !this.useLeaflet && this.lat && this.long) {
|
|
315
|
+
this.initializeMapLibre();
|
|
316
|
+
this.mapLibreReady = true;
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
} else {
|
|
320
|
+
mapScript.setAttribute('vmid', `leafletjs${this.mapId}`);
|
|
321
|
+
mapStyle.setAttribute('vmid', `leaftletcss${this.mapId}`);
|
|
322
|
+
mapScript.setAttribute('src', 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js');
|
|
323
|
+
mapStyle.setAttribute('href', 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css');
|
|
324
|
+
|
|
325
|
+
this.testDelayedGlobalLibrary('L').then((leafletTest) => {
|
|
326
|
+
if (leafletTest.loaded && !this.mapLoaded && this.lat && this.long) {
|
|
327
|
+
this.initializeLeaflet();
|
|
328
|
+
this.leafletReady = true;
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
document.head.appendChild(mapScript);
|
|
333
|
+
document.head.appendChild(mapStyle);
|
|
334
|
+
},
|
|
335
|
+
initializeLeaflet() {
|
|
336
|
+
/* eslint-disable no-undef, max-len */
|
|
337
|
+
// Initialize primary mapInstance
|
|
338
|
+
this.mapInstance = L.map(`kv-map-holder-${this.mapId}`, {
|
|
339
|
+
center: [this.lat, this.long],
|
|
340
|
+
zoom: this.initialZoom || this.zoomLevel,
|
|
341
|
+
// todo make props for the following options
|
|
342
|
+
dragging: this.allowDragging,
|
|
343
|
+
zoomControl: this.showZoomControl,
|
|
344
|
+
animate: true,
|
|
345
|
+
scrollWheelZoom: false,
|
|
346
|
+
doubleClickZoom: false,
|
|
347
|
+
attributionControl: false,
|
|
348
|
+
});
|
|
349
|
+
/* eslint-disable quotes */
|
|
350
|
+
// Add our tileset to the mapInstance
|
|
351
|
+
let tileLayer = 'https://api.maptiler.com/maps/landscape/{z}/{x}/{y}.png?key=n1Mz5ziX3k6JfdjFe7mx';
|
|
352
|
+
if (this.showLabels) {
|
|
353
|
+
tileLayer = 'https://api.maptiler.com/maps/bright/{z}/{x}/{y}.png?key=n1Mz5ziX3k6JfdjFe7mx';
|
|
354
|
+
}
|
|
355
|
+
L.tileLayer(tileLayer, {
|
|
356
|
+
tileSize: 512,
|
|
357
|
+
zoomOffset: -1,
|
|
358
|
+
minZoom: 1,
|
|
359
|
+
crossOrigin: true,
|
|
360
|
+
}).addTo(this.mapInstance);
|
|
361
|
+
|
|
362
|
+
if (this.countriesData.length > 0) {
|
|
363
|
+
L.geoJson(
|
|
364
|
+
this.getCountriesData(),
|
|
365
|
+
{
|
|
366
|
+
style: this.countryStyle,
|
|
367
|
+
onEachFeature: this.onEachCountryFeature,
|
|
368
|
+
},
|
|
369
|
+
).addTo(this.mapInstance);
|
|
370
|
+
|
|
371
|
+
this.countriesData.forEach((country) => {
|
|
372
|
+
if (country.numLoansFundraising > 0 && this.showFundraisingLoans) {
|
|
373
|
+
const circle = L.circle([country.lat, country.long], {
|
|
374
|
+
color: kvTokensPrimitives.colors.black,
|
|
375
|
+
weight: 1,
|
|
376
|
+
fillColor: kvTokensPrimitives.colors.brand[900],
|
|
377
|
+
fillOpacity: 1,
|
|
378
|
+
radius: 130000,
|
|
379
|
+
}).addTo(this.mapInstance);
|
|
380
|
+
|
|
381
|
+
const tooltipText = `Click to see ${country.numLoansFundraising} fundraising loans in ${country.label}`;
|
|
382
|
+
circle.bindTooltip(tooltipText);
|
|
383
|
+
|
|
384
|
+
circle.on('click', () => {
|
|
385
|
+
this.circleMapClicked(country.isoCode);
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
/* eslint-enable quotes */
|
|
391
|
+
/* eslint-enable no-undef, max-len */
|
|
392
|
+
|
|
393
|
+
// signify map has loaded
|
|
394
|
+
this.mapLoaded = true;
|
|
395
|
+
// only activate autoZoom if we have an initialZoom set
|
|
396
|
+
if (this.initialZoom !== null) {
|
|
397
|
+
this.createWrapperObserver();
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
initializeMapLibre() {
|
|
401
|
+
// Initialize primary mapInstance
|
|
402
|
+
/* eslint-disable no-undef */
|
|
403
|
+
let tileLayer = 'https://api.maptiler.com/maps/landscape/style.json?key=n1Mz5ziX3k6JfdjFe7mx';
|
|
404
|
+
if (this.showLabels) {
|
|
405
|
+
tileLayer = 'https://api.maptiler.com/maps/bright/style.json?key=n1Mz5ziX3k6JfdjFe7mx';
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
this.mapInstance = new maplibregl.Map({
|
|
409
|
+
container: `kv-map-holder-${this.mapId}`,
|
|
410
|
+
style: tileLayer,
|
|
411
|
+
center: [this.long, this.lat],
|
|
412
|
+
zoom: this.initialZoom || this.zoomLevel,
|
|
413
|
+
attributionControl: false,
|
|
414
|
+
dragPan: this.allowDragging,
|
|
415
|
+
scrollZoom: false,
|
|
416
|
+
doubleClickZoom: false,
|
|
417
|
+
dragRotate: false,
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
if (this.showZoomControl) {
|
|
421
|
+
this.mapInstance.addControl(new maplibregl.NavigationControl());
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
this.mapInstance.on('load', () => {
|
|
425
|
+
// signify map has loaded
|
|
426
|
+
this.mapLoaded = true;
|
|
427
|
+
// Create wrapper observer to watch for map entering viewport
|
|
428
|
+
if (this.initialZoom !== null || this.advancedAnimation?.borrowerPoints) {
|
|
429
|
+
this.createWrapperObserver();
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
/* eslint-enable no-undef */
|
|
433
|
+
},
|
|
434
|
+
animateMap() {
|
|
435
|
+
// remove country labels
|
|
436
|
+
this.mapInstance.style.stylesheet.layers.forEach((layer) => {
|
|
437
|
+
if (layer.type === 'symbol') {
|
|
438
|
+
this.mapInstance.removeLayer(layer.id);
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
// generate map markers for borrower points
|
|
442
|
+
generateMapMarkers(this.mapInstance, this.advancedAnimation.borrowerPoints);
|
|
443
|
+
|
|
444
|
+
// wait 500 ms before calling the animation coordinator promise
|
|
445
|
+
// to allow the map to scroll into view
|
|
446
|
+
setTimeout(() => {
|
|
447
|
+
animationCoordinator(this.mapInstance, this.advancedAnimation.borrowerPoints)
|
|
448
|
+
.then(() => {
|
|
449
|
+
// when animation is complete reset map to component properties
|
|
450
|
+
this.mapInstance.dragPan.enable();
|
|
451
|
+
this.mapInstance.scrollZoom.enable();
|
|
452
|
+
this.mapInstance.scrollZoom.enable();
|
|
453
|
+
this.mapInstance.easeTo({
|
|
454
|
+
center: [this.long, this.lat],
|
|
455
|
+
zoom: this.initialZoom || this.zoomLevel,
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
}, 500);
|
|
459
|
+
},
|
|
460
|
+
checkIntersectionObserverSupport() {
|
|
461
|
+
if (typeof window === 'undefined'
|
|
462
|
+
|| !('IntersectionObserver' in window)
|
|
463
|
+
|| !('IntersectionObserverEntry' in window)
|
|
464
|
+
|| !('intersectionRatio' in window.IntersectionObserverEntry.prototype)) {
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
return true;
|
|
468
|
+
},
|
|
469
|
+
createIntersectionObserver({ callback, options, targets } = {}) {
|
|
470
|
+
if (this.checkIntersectionObserverSupport()) {
|
|
471
|
+
const observer = new IntersectionObserver(callback, options);
|
|
472
|
+
targets.forEach((target) => observer.observe(target));
|
|
473
|
+
return observer;
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
testDelayedGlobalLibrary(library, timeout = 3000) {
|
|
477
|
+
// return a promise
|
|
478
|
+
return new Promise((resolve, reject) => {
|
|
479
|
+
if (typeof window === 'undefined') {
|
|
480
|
+
reject(new Error('window object not available'));
|
|
481
|
+
}
|
|
482
|
+
// establish timeout to limit time until promise resolution
|
|
483
|
+
let readyStateTimeout;
|
|
484
|
+
// establish interval to check for library presence
|
|
485
|
+
const readyStateInterval = window.setInterval(() => {
|
|
486
|
+
// determine if library is present on window
|
|
487
|
+
if (typeof window[library] !== 'undefined') {
|
|
488
|
+
// cleanup timers
|
|
489
|
+
clearInterval(readyStateInterval);
|
|
490
|
+
clearTimeout(readyStateTimeout);
|
|
491
|
+
// resolve the promise
|
|
492
|
+
resolve({ loaded: true });
|
|
493
|
+
}
|
|
494
|
+
}, 100);
|
|
495
|
+
|
|
496
|
+
// activate timeout
|
|
497
|
+
readyStateTimeout = window.setTimeout(() => {
|
|
498
|
+
// clean up interval and timeout
|
|
499
|
+
clearInterval(readyStateInterval);
|
|
500
|
+
clearTimeout(readyStateTimeout);
|
|
501
|
+
// resolve the promise
|
|
502
|
+
resolve({ loaded: false });
|
|
503
|
+
}, timeout);
|
|
504
|
+
});
|
|
505
|
+
},
|
|
506
|
+
getCountriesData() {
|
|
507
|
+
const countriesFeatures = this.countriesBorders.features ?? [];
|
|
508
|
+
|
|
509
|
+
countriesFeatures.forEach((country, index) => {
|
|
510
|
+
const countryData = this.countriesData.find((data) => data.isoCode === country.properties.iso_a2);
|
|
511
|
+
if (countryData) {
|
|
512
|
+
countriesFeatures[index].lenderLoans = countryData.value;
|
|
513
|
+
countriesFeatures[index].numLoansFundraising = countryData.numLoansFundraising;
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
return this.countriesBorders;
|
|
518
|
+
},
|
|
519
|
+
countryStyle(feature) {
|
|
520
|
+
return {
|
|
521
|
+
color: kvTokensPrimitives.colors.white,
|
|
522
|
+
fillColor: getCountryColor(feature.lenderLoans, this.countriesData, kvTokensPrimitives),
|
|
523
|
+
weight: 1,
|
|
524
|
+
fillOpacity: 1,
|
|
525
|
+
};
|
|
526
|
+
},
|
|
527
|
+
onEachCountryFeature(feature, layer) {
|
|
528
|
+
const loansString = feature.lenderLoans
|
|
529
|
+
? `${feature.lenderLoans} loan${feature.lenderLoans > 1 ? 's' : ''}`
|
|
530
|
+
: '0 loans';
|
|
531
|
+
const countryString = `${feature.properties.name} <br/> ${loansString}`;
|
|
532
|
+
|
|
533
|
+
layer.bindTooltip(countryString, {
|
|
534
|
+
sticky: true,
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
layer.on({
|
|
538
|
+
mouseover: this.highlightFeature,
|
|
539
|
+
mouseout: this.resetHighlight,
|
|
540
|
+
});
|
|
541
|
+
},
|
|
542
|
+
highlightFeature(e) {
|
|
543
|
+
const layer = e.target;
|
|
544
|
+
|
|
545
|
+
layer.setStyle({
|
|
546
|
+
fillColor: kvTokensPrimitives.colors.gray[500],
|
|
547
|
+
});
|
|
548
|
+
},
|
|
549
|
+
resetHighlight(e) {
|
|
550
|
+
const layer = e.target;
|
|
551
|
+
const { feature } = layer;
|
|
552
|
+
|
|
553
|
+
layer.setStyle({
|
|
554
|
+
fillColor: getCountryColor(feature.lenderLoans, this.countriesData, kvTokensPrimitives),
|
|
555
|
+
});
|
|
556
|
+
},
|
|
557
|
+
circleMapClicked(countryIso) {
|
|
558
|
+
this.$emit('country-lend-filter', countryIso);
|
|
559
|
+
},
|
|
560
|
+
},
|
|
561
|
+
};
|
|
562
|
+
</script>
|
|
563
|
+
|
|
564
|
+
<style>
|
|
565
|
+
/* Styles for animation map markers defined in @kiva/kv-components/utils/mapAnimation.js */
|
|
566
|
+
.map-marker {
|
|
567
|
+
margin-top: -77px;
|
|
568
|
+
margin-left: 35px;
|
|
569
|
+
display: block;
|
|
570
|
+
border: none;
|
|
571
|
+
border-radius: 50%;
|
|
572
|
+
cursor: pointer;
|
|
573
|
+
padding: 0;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
.map-marker::after {
|
|
577
|
+
content: '';
|
|
578
|
+
position: absolute;
|
|
579
|
+
top: -8px;
|
|
580
|
+
left: -8px;
|
|
581
|
+
right: -8px;
|
|
582
|
+
bottom: -8px;
|
|
583
|
+
border-radius: 50%;
|
|
584
|
+
border: 4px solid #000;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
.map-marker::before {
|
|
588
|
+
content: "";
|
|
589
|
+
width: 0;
|
|
590
|
+
height: 0;
|
|
591
|
+
left: -13px;
|
|
592
|
+
bottom: -32px;
|
|
593
|
+
border: 9px solid transparent;
|
|
594
|
+
border-left: 40px solid #000;
|
|
595
|
+
transform: rotate(114deg);
|
|
596
|
+
position: absolute;
|
|
597
|
+
z-index: -1;
|
|
598
|
+
}
|
|
599
|
+
</style>
|