@controleonline/ui-default 1.0.263
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/.github/agents/developer.agent.md +30 -0
- package/.github/agents/devops.agent.md +30 -0
- package/.github/agents/qa.agent.md +30 -0
- package/.github/agents/security.agent.md +30 -0
- package/.scrutinizer.yml +20 -0
- package/AGENTS.md +47 -0
- package/FUNDING.yml +1 -0
- package/package.json +21 -0
- package/src/react/components/errors/DefaultErrors.js +360 -0
- package/src/react/components/files/DefaultFile.js +46 -0
- package/src/react/components/filters/CompactFilterSelector.js +262 -0
- package/src/react/components/filters/CompactFilterSelector.styles.js +124 -0
- package/src/react/components/filters/DateShortcutFilter.js +264 -0
- package/src/react/components/filters/DateShortcutFilter.styles.js +82 -0
- package/src/react/components/filters/DefaultColumnFilter.js +97 -0
- package/src/react/components/filters/DefaultColumnFilter.styles.js +21 -0
- package/src/react/components/filters/DefaultExternalFilters.js +441 -0
- package/src/react/components/filters/DefaultExternalFilters.styles.js +177 -0
- package/src/react/components/filters/DefaultSearch.js +103 -0
- package/src/react/components/filters/DefaultSearch.styles.js +70 -0
- package/src/react/components/filters/dateFilterSelection.js +29 -0
- package/src/react/components/form/DefaultForm.js +198 -0
- package/src/react/components/form/DefaultForm.styles.js +70 -0
- package/src/react/components/help/DefaultTooltip.js +87 -0
- package/src/react/components/help/DefaultTooltip.styles.js +61 -0
- package/src/react/components/inputs/DefaultInput.js +160 -0
- package/src/react/components/inputs/DefaultInput.styles.js +93 -0
- package/src/react/components/inputs/DefaultSelect.js +192 -0
- package/src/react/components/inputs/DefaultSelect.styles.js +65 -0
- package/src/react/components/inputs/defaultInputUtils.js +230 -0
- package/src/react/components/map/DefaultGoogleMap.styles.js +9 -0
- package/src/react/components/map/DefaultGoogleMap.web.js +698 -0
- package/src/react/components/map/DefaultMap.native.js +71 -0
- package/src/react/components/map/DefaultMap.shared.js +762 -0
- package/src/react/components/map/DefaultMap.styles.js +16 -0
- package/src/react/components/map/DefaultMap.web.js +66 -0
- package/src/react/components/map/DefaultNativeMap.native.js +615 -0
- package/src/react/components/map/DefaultNativeMap.shared.js +532 -0
- package/src/react/components/map/DefaultNativeMap.styles.js +122 -0
- package/src/react/components/table/DefaultTable.js +1897 -0
- package/src/react/components/table/DefaultTable.styles.js +610 -0
- package/src/react/index.js +10 -0
- package/src/react/utils/tableVisibleColumnsPreferences.js +264 -0
- package/src/store/default/actions.js +444 -0
- package/src/store/default/getters.js +28 -0
- package/src/store/default/mutation_types.js +26 -0
- package/src/store/default/mutations.js +138 -0
- package/src/tests/react/components/DateShortcutFilter.test.js +96 -0
- package/src/tests/react/components/errors/DefaultErrors.test.js +162 -0
- package/src/tests/react/components/files/DefaultFile.test.js +80 -0
- package/src/tests/react/components/map/DefaultMap.shared.test.js +162 -0
- package/src/tests/react/filters/dateFilterSelection.test.js +46 -0
- package/src/tests/react/inputs/defaultInputUtils.test.js +45 -0
- package/src/tests/react/store/defaultActions.test.js +112 -0
- package/src/tests/react/utils/tableVisibleColumnsPreferences.test.js +223 -0
- package/src/utils/filters.js +56 -0
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
import React, {useEffect, useRef} from 'react';
|
|
2
|
+
import {Platform, View} from 'react-native';
|
|
3
|
+
|
|
4
|
+
import styles from './DefaultGoogleMap.styles';
|
|
5
|
+
import {
|
|
6
|
+
buildGoogleMapsNavigationUrl,
|
|
7
|
+
buildWazeNavigationUrl,
|
|
8
|
+
} from './DefaultMap.shared';
|
|
9
|
+
|
|
10
|
+
const GOOGLE_MAPS_SCRIPT_ID = 'shop-google-maps-api-script';
|
|
11
|
+
const GOOGLE_MAPS_CALLBACK_NAME = '__shopGoogleMapsApiReady__';
|
|
12
|
+
const GOOGLE_MAPS_LOAD_TIMEOUT_MS = 15000;
|
|
13
|
+
const GOOGLE_MAPS_POLL_INTERVAL_MS = 100;
|
|
14
|
+
const ROUTE_COLOR = '#0EA5E9';
|
|
15
|
+
const markerIconUrlValidationCache = new Map();
|
|
16
|
+
|
|
17
|
+
const escapeHtml = value =>
|
|
18
|
+
String(value || '')
|
|
19
|
+
.replace(/&/g, '&')
|
|
20
|
+
.replace(/</g, '<')
|
|
21
|
+
.replace(/>/g, '>')
|
|
22
|
+
.replace(/"/g, '"')
|
|
23
|
+
.replace(/'/g, ''');
|
|
24
|
+
|
|
25
|
+
const buildPopupLine = value => {
|
|
26
|
+
if (!value) {
|
|
27
|
+
return '';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return `<div class="shop-map-popup-line">${escapeHtml(value)}</div>`;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const buildPopupMeta = (label, value) => {
|
|
34
|
+
if (!value) {
|
|
35
|
+
return '';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return `
|
|
39
|
+
<div class="shop-map-popup-meta">
|
|
40
|
+
<span class="shop-map-popup-meta-label">${escapeHtml(label)}</span>
|
|
41
|
+
<span>${escapeHtml(value)}</span>
|
|
42
|
+
</div>
|
|
43
|
+
`;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const buildPopupContent = (item, position) => {
|
|
47
|
+
const navigationPosition = position || {
|
|
48
|
+
latitude: Number(item?.latitude),
|
|
49
|
+
longitude: Number(item?.longitude),
|
|
50
|
+
};
|
|
51
|
+
const googleMapsUrl =
|
|
52
|
+
item?.googleMapsUrl ||
|
|
53
|
+
buildGoogleMapsNavigationUrl(navigationPosition);
|
|
54
|
+
const wazeUrl = item?.wazeUrl || buildWazeNavigationUrl(navigationPosition);
|
|
55
|
+
|
|
56
|
+
return `
|
|
57
|
+
<div class="shop-map-popup">
|
|
58
|
+
<div class="shop-map-popup-company">${escapeHtml(item.companyName)}</div>
|
|
59
|
+
<div class="shop-map-popup-title">${escapeHtml(item.title)}</div>
|
|
60
|
+
${buildPopupLine(item.addressLine)}
|
|
61
|
+
${buildPopupLine(item.addressExtra)}
|
|
62
|
+
<div class="shop-map-popup-meta-list">
|
|
63
|
+
${buildPopupMeta('Telefone', item.phoneLabel)}
|
|
64
|
+
${buildPopupMeta('Distancia', item.distanceLabel)}
|
|
65
|
+
${buildPopupMeta('Horario', item.openingHours)}
|
|
66
|
+
</div>
|
|
67
|
+
<div class="shop-map-popup-actions">
|
|
68
|
+
<a
|
|
69
|
+
class="shop-map-popup-action shop-map-popup-action-primary"
|
|
70
|
+
href="${escapeHtml(googleMapsUrl)}"
|
|
71
|
+
target="_blank"
|
|
72
|
+
rel="noopener noreferrer"
|
|
73
|
+
>
|
|
74
|
+
Abrir no Maps
|
|
75
|
+
</a>
|
|
76
|
+
<a
|
|
77
|
+
class="shop-map-popup-action"
|
|
78
|
+
href="${escapeHtml(wazeUrl)}"
|
|
79
|
+
target="_blank"
|
|
80
|
+
rel="noopener noreferrer"
|
|
81
|
+
>
|
|
82
|
+
Waze
|
|
83
|
+
</a>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
`;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const normalizeCoordinate = value => {
|
|
90
|
+
const numeric = Number(value);
|
|
91
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const extractCoordinates = address => {
|
|
95
|
+
if (!address || typeof address !== 'object') {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const latitude = normalizeCoordinate(address.latitude);
|
|
100
|
+
const longitude = normalizeCoordinate(address.longitude);
|
|
101
|
+
|
|
102
|
+
if (latitude === null || longitude === null) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {latitude, longitude};
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const injectPopupStyles = () => {
|
|
110
|
+
if (typeof document === 'undefined') {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (document.getElementById('shop-google-map-popup-styles')) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const styleElement = document.createElement('style');
|
|
119
|
+
styleElement.id = 'shop-google-map-popup-styles';
|
|
120
|
+
styleElement.textContent = `
|
|
121
|
+
.shop-map-popup {
|
|
122
|
+
min-width: 220px;
|
|
123
|
+
max-width: 280px;
|
|
124
|
+
color: #0f172a;
|
|
125
|
+
font-family: Arial, sans-serif;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.shop-map-popup-company {
|
|
129
|
+
font-size: 11px;
|
|
130
|
+
font-weight: 700;
|
|
131
|
+
text-transform: uppercase;
|
|
132
|
+
letter-spacing: 0.08em;
|
|
133
|
+
color: #0369a1;
|
|
134
|
+
margin-bottom: 6px;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.shop-map-popup-title {
|
|
138
|
+
font-size: 16px;
|
|
139
|
+
font-weight: 700;
|
|
140
|
+
margin-bottom: 8px;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.shop-map-popup-line {
|
|
144
|
+
font-size: 13px;
|
|
145
|
+
line-height: 1.45;
|
|
146
|
+
color: #0f172a;
|
|
147
|
+
margin-bottom: 4px;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.shop-map-popup-meta-list {
|
|
151
|
+
display: grid;
|
|
152
|
+
gap: 6px;
|
|
153
|
+
margin-top: 10px;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.shop-map-popup-meta {
|
|
157
|
+
display: flex;
|
|
158
|
+
justify-content: space-between;
|
|
159
|
+
gap: 10px;
|
|
160
|
+
font-size: 12px;
|
|
161
|
+
color: #334155;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.shop-map-popup-meta-label {
|
|
165
|
+
color: #64748b;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.shop-map-popup-actions {
|
|
169
|
+
display: flex;
|
|
170
|
+
gap: 8px;
|
|
171
|
+
margin-top: 14px;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.shop-map-popup-action {
|
|
175
|
+
flex: 1;
|
|
176
|
+
border-radius: 999px;
|
|
177
|
+
border: 1px solid #cbd5e1;
|
|
178
|
+
padding: 10px 12px;
|
|
179
|
+
text-align: center;
|
|
180
|
+
text-decoration: none;
|
|
181
|
+
color: #0f172a;
|
|
182
|
+
font-size: 12px;
|
|
183
|
+
font-weight: 700;
|
|
184
|
+
background: #ffffff;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.shop-map-popup-action-primary {
|
|
188
|
+
border-color: transparent;
|
|
189
|
+
background: #0ea5e9;
|
|
190
|
+
color: #ffffff;
|
|
191
|
+
}
|
|
192
|
+
`;
|
|
193
|
+
document.head.appendChild(styleElement);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const loadGoogleMapsApi = apiKey => {
|
|
197
|
+
if (Platform.OS !== 'web' || typeof window === 'undefined') {
|
|
198
|
+
return Promise.resolve(null);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (window.google?.maps) {
|
|
202
|
+
return Promise.resolve(window.google.maps);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (window.__shopGoogleMapsPromise) {
|
|
206
|
+
return window.__shopGoogleMapsPromise;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const loadPromise = new Promise((resolve, reject) => {
|
|
210
|
+
let settled = false;
|
|
211
|
+
let pollTimer = null;
|
|
212
|
+
let timeoutTimer = null;
|
|
213
|
+
|
|
214
|
+
const cleanup = () => {
|
|
215
|
+
if (pollTimer) {
|
|
216
|
+
window.clearInterval(pollTimer);
|
|
217
|
+
pollTimer = null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (timeoutTimer) {
|
|
221
|
+
window.clearTimeout(timeoutTimer);
|
|
222
|
+
timeoutTimer = null;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const resolveMaps = () => {
|
|
227
|
+
if (settled || !window.google?.maps) {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
settled = true;
|
|
232
|
+
cleanup();
|
|
233
|
+
resolve(window.google.maps);
|
|
234
|
+
return true;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const rejectMaps = error => {
|
|
238
|
+
if (settled) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
settled = true;
|
|
243
|
+
cleanup();
|
|
244
|
+
reject(error);
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const startWatching = () => {
|
|
248
|
+
if (settled || pollTimer || timeoutTimer) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
pollTimer = window.setInterval(() => {
|
|
253
|
+
resolveMaps();
|
|
254
|
+
}, GOOGLE_MAPS_POLL_INTERVAL_MS);
|
|
255
|
+
|
|
256
|
+
timeoutTimer = window.setTimeout(() => {
|
|
257
|
+
rejectMaps(new Error('google-maps-load-failed'));
|
|
258
|
+
}, GOOGLE_MAPS_LOAD_TIMEOUT_MS);
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const handleLoad = () => {
|
|
262
|
+
if (resolveMaps()) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
startWatching();
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const handleError = () => {
|
|
270
|
+
rejectMaps(new Error('google-maps-load-failed'));
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
window[GOOGLE_MAPS_CALLBACK_NAME] = handleLoad;
|
|
274
|
+
|
|
275
|
+
const existingScript = document.getElementById(GOOGLE_MAPS_SCRIPT_ID);
|
|
276
|
+
if (existingScript) {
|
|
277
|
+
existingScript.addEventListener('load', handleLoad, {once: true});
|
|
278
|
+
existingScript.addEventListener('error', handleError, {once: true});
|
|
279
|
+
startWatching();
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const existingMapsScript = document.querySelector(
|
|
284
|
+
'script[src*="maps.googleapis.com/maps/api/js"]',
|
|
285
|
+
);
|
|
286
|
+
if (existingMapsScript) {
|
|
287
|
+
existingMapsScript.addEventListener('load', handleLoad, {once: true});
|
|
288
|
+
existingMapsScript.addEventListener('error', handleError, {once: true});
|
|
289
|
+
startWatching();
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const script = document.createElement('script');
|
|
294
|
+
script.id = GOOGLE_MAPS_SCRIPT_ID;
|
|
295
|
+
script.async = true;
|
|
296
|
+
script.defer = true;
|
|
297
|
+
script.src = `https://maps.googleapis.com/maps/api/js?key=${encodeURIComponent(
|
|
298
|
+
apiKey,
|
|
299
|
+
)}&loading=async&callback=${GOOGLE_MAPS_CALLBACK_NAME}`;
|
|
300
|
+
script.addEventListener('load', handleLoad, {once: true});
|
|
301
|
+
script.addEventListener('error', handleError, {once: true});
|
|
302
|
+
document.head.appendChild(script);
|
|
303
|
+
startWatching();
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
window.__shopGoogleMapsPromise = loadPromise.catch(error => {
|
|
307
|
+
window.__shopGoogleMapsPromise = null;
|
|
308
|
+
throw error;
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
return window.__shopGoogleMapsPromise;
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const resolveMarkerIconUrl = url => {
|
|
315
|
+
const normalizedUrl = String(url || '').trim();
|
|
316
|
+
|
|
317
|
+
if (
|
|
318
|
+
!normalizedUrl ||
|
|
319
|
+
typeof window === 'undefined' ||
|
|
320
|
+
typeof window.Image !== 'function'
|
|
321
|
+
) {
|
|
322
|
+
return Promise.resolve('');
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (markerIconUrlValidationCache.has(normalizedUrl)) {
|
|
326
|
+
return markerIconUrlValidationCache.get(normalizedUrl);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const validationPromise = new Promise(resolve => {
|
|
330
|
+
const image = new window.Image();
|
|
331
|
+
const finalize = value => {
|
|
332
|
+
const resolvedValue = String(value || '').trim();
|
|
333
|
+
markerIconUrlValidationCache.set(
|
|
334
|
+
normalizedUrl,
|
|
335
|
+
Promise.resolve(resolvedValue),
|
|
336
|
+
);
|
|
337
|
+
resolve(resolvedValue);
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
image.onload = () => finalize(normalizedUrl);
|
|
341
|
+
image.onerror = () => finalize('');
|
|
342
|
+
image.src = normalizedUrl;
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
markerIconUrlValidationCache.set(normalizedUrl, validationPromise);
|
|
346
|
+
return validationPromise;
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const fitMapToBounds = ({google, map, markerCount, userCoordinates}) => {
|
|
350
|
+
const bounds = new google.maps.LatLngBounds();
|
|
351
|
+
|
|
352
|
+
if (
|
|
353
|
+
userCoordinates &&
|
|
354
|
+
Number.isFinite(userCoordinates.latitude) &&
|
|
355
|
+
Number.isFinite(userCoordinates.longitude)
|
|
356
|
+
) {
|
|
357
|
+
bounds.extend({
|
|
358
|
+
lat: userCoordinates.latitude,
|
|
359
|
+
lng: userCoordinates.longitude,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return {
|
|
364
|
+
bounds,
|
|
365
|
+
finalize() {
|
|
366
|
+
if (bounds.isEmpty()) {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
map.fitBounds(bounds, {
|
|
371
|
+
top: 56,
|
|
372
|
+
right: 32,
|
|
373
|
+
bottom: 56,
|
|
374
|
+
left: 32,
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
google.maps.event.addListenerOnce(map, 'idle', () => {
|
|
378
|
+
if (markerCount === 1 && map.getZoom() > 15) {
|
|
379
|
+
map.setZoom(15);
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
},
|
|
383
|
+
};
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
export default function DefaultGoogleMap({
|
|
387
|
+
apiKey,
|
|
388
|
+
markerPayloads = [],
|
|
389
|
+
paths = [],
|
|
390
|
+
userCoordinates = null,
|
|
391
|
+
}) {
|
|
392
|
+
const containerRef = useRef(null);
|
|
393
|
+
|
|
394
|
+
useEffect(() => {
|
|
395
|
+
if (Platform.OS !== 'web') {
|
|
396
|
+
return undefined;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const container = containerRef.current;
|
|
400
|
+
if (
|
|
401
|
+
!container ||
|
|
402
|
+
!apiKey ||
|
|
403
|
+
(markerPayloads.length === 0 && !(Array.isArray(paths) && paths.length > 0) && !userCoordinates)
|
|
404
|
+
) {
|
|
405
|
+
return undefined;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
let cancelled = false;
|
|
409
|
+
|
|
410
|
+
injectPopupStyles();
|
|
411
|
+
|
|
412
|
+
loadGoogleMapsApi(apiKey)
|
|
413
|
+
.then(async () => {
|
|
414
|
+
if (cancelled || !container || !window.google?.maps) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const google = window.google;
|
|
419
|
+
const map = new google.maps.Map(container, {
|
|
420
|
+
mapTypeControl: false,
|
|
421
|
+
streetViewControl: false,
|
|
422
|
+
fullscreenControl: false,
|
|
423
|
+
clickableIcons: false,
|
|
424
|
+
gestureHandling: 'greedy',
|
|
425
|
+
zoomControl: false,
|
|
426
|
+
disableDefaultUI: true,
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
const infoWindow = new google.maps.InfoWindow({maxWidth: 320});
|
|
430
|
+
const hasUserCoordinates =
|
|
431
|
+
Number.isFinite(userCoordinates?.latitude) &&
|
|
432
|
+
Number.isFinite(userCoordinates?.longitude);
|
|
433
|
+
const directionsService = new google.maps.DirectionsService();
|
|
434
|
+
const directionsRenderer = directionsService
|
|
435
|
+
&& hasUserCoordinates
|
|
436
|
+
? new google.maps.DirectionsRenderer({
|
|
437
|
+
map,
|
|
438
|
+
suppressMarkers: true,
|
|
439
|
+
preserveViewport: false,
|
|
440
|
+
polylineOptions: {
|
|
441
|
+
strokeColor: ROUTE_COLOR,
|
|
442
|
+
strokeOpacity: 0.92,
|
|
443
|
+
strokeWeight: 5,
|
|
444
|
+
},
|
|
445
|
+
})
|
|
446
|
+
: null;
|
|
447
|
+
let activeRouteRequestId = 0;
|
|
448
|
+
const resolveMarkerPayload = async item => {
|
|
449
|
+
const latitude = Number(item?.latitude);
|
|
450
|
+
const longitude = Number(item?.longitude);
|
|
451
|
+
const markerIconUrl = await resolveMarkerIconUrl(item?.markerIconUrl);
|
|
452
|
+
const resolvedItem = markerIconUrl
|
|
453
|
+
? {
|
|
454
|
+
...item,
|
|
455
|
+
markerIconUrl,
|
|
456
|
+
}
|
|
457
|
+
: item?.markerIconUrl
|
|
458
|
+
? {
|
|
459
|
+
...item,
|
|
460
|
+
markerIconUrl: '',
|
|
461
|
+
}
|
|
462
|
+
: item;
|
|
463
|
+
|
|
464
|
+
if (Number.isFinite(latitude) && Number.isFinite(longitude)) {
|
|
465
|
+
return {
|
|
466
|
+
item: resolvedItem,
|
|
467
|
+
position: {
|
|
468
|
+
lat: latitude,
|
|
469
|
+
lng: longitude,
|
|
470
|
+
},
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (!resolvedItem?.geocodeQuery) {
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const geocoder = new google.maps.Geocoder();
|
|
479
|
+
|
|
480
|
+
return new Promise(resolve => {
|
|
481
|
+
geocoder.geocode({address: resolvedItem.geocodeQuery}, (results, status) => {
|
|
482
|
+
const location = results?.[0]?.geometry?.location;
|
|
483
|
+
|
|
484
|
+
if (status !== 'OK' || !location) {
|
|
485
|
+
resolve(null);
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const resolvedLatitude = Number(location.lat());
|
|
490
|
+
const resolvedLongitude = Number(location.lng());
|
|
491
|
+
|
|
492
|
+
if (
|
|
493
|
+
!Number.isFinite(resolvedLatitude) ||
|
|
494
|
+
!Number.isFinite(resolvedLongitude)
|
|
495
|
+
) {
|
|
496
|
+
resolve(null);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
resolve({
|
|
501
|
+
item: resolvedItem,
|
|
502
|
+
position: {
|
|
503
|
+
lat: resolvedLatitude,
|
|
504
|
+
lng: resolvedLongitude,
|
|
505
|
+
},
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
};
|
|
510
|
+
const resolvedMarkers = (await Promise.all(
|
|
511
|
+
markerPayloads.map(resolveMarkerPayload),
|
|
512
|
+
)).filter(Boolean);
|
|
513
|
+
const {bounds, finalize} = fitMapToBounds({
|
|
514
|
+
google,
|
|
515
|
+
map,
|
|
516
|
+
markerCount: resolvedMarkers.length,
|
|
517
|
+
userCoordinates,
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
if (
|
|
521
|
+
userCoordinates &&
|
|
522
|
+
Number.isFinite(userCoordinates.latitude) &&
|
|
523
|
+
Number.isFinite(userCoordinates.longitude)
|
|
524
|
+
) {
|
|
525
|
+
new google.maps.Marker({
|
|
526
|
+
position: {
|
|
527
|
+
lat: userCoordinates.latitude,
|
|
528
|
+
lng: userCoordinates.longitude,
|
|
529
|
+
},
|
|
530
|
+
map,
|
|
531
|
+
title: 'Sua localizacao',
|
|
532
|
+
zIndex: 999,
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
resolvedMarkers.forEach(entry => {
|
|
537
|
+
const item = entry.item;
|
|
538
|
+
const position = entry.position;
|
|
539
|
+
const markerOptions = {
|
|
540
|
+
position,
|
|
541
|
+
map,
|
|
542
|
+
title: item.title,
|
|
543
|
+
animation: google.maps.Animation.DROP,
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
if (item.markerIconUrl) {
|
|
547
|
+
markerOptions.icon = {
|
|
548
|
+
url: item.markerIconUrl,
|
|
549
|
+
scaledSize: new google.maps.Size(42, 42),
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const marker = new google.maps.Marker(markerOptions);
|
|
554
|
+
|
|
555
|
+
bounds.extend(position);
|
|
556
|
+
|
|
557
|
+
marker.addListener('click', () => {
|
|
558
|
+
infoWindow.setContent(buildPopupContent(item, position));
|
|
559
|
+
infoWindow.open({
|
|
560
|
+
anchor: marker,
|
|
561
|
+
map,
|
|
562
|
+
shouldFocus: false,
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
if (!directionsService || !directionsRenderer) {
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const routeRequestId = activeRouteRequestId + 1;
|
|
570
|
+
activeRouteRequestId = routeRequestId;
|
|
571
|
+
|
|
572
|
+
directionsService.route(
|
|
573
|
+
{
|
|
574
|
+
origin: {
|
|
575
|
+
lat: userCoordinates.latitude,
|
|
576
|
+
lng: userCoordinates.longitude,
|
|
577
|
+
},
|
|
578
|
+
destination: position,
|
|
579
|
+
travelMode: google.maps.TravelMode.DRIVING,
|
|
580
|
+
},
|
|
581
|
+
(response, status) => {
|
|
582
|
+
if (
|
|
583
|
+
cancelled ||
|
|
584
|
+
!directionsRenderer ||
|
|
585
|
+
routeRequestId !== activeRouteRequestId
|
|
586
|
+
) {
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
if (status === 'OK' && response) {
|
|
591
|
+
directionsRenderer.setDirections(response);
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
directionsRenderer.set('directions', null);
|
|
596
|
+
},
|
|
597
|
+
);
|
|
598
|
+
});
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
const resolveRouteCoordinates = path => {
|
|
602
|
+
const from = extractCoordinates(path?.from || path?.origin || path?.start);
|
|
603
|
+
const to = extractCoordinates(path?.to || path?.destination || path?.end);
|
|
604
|
+
|
|
605
|
+
if (!from || !to) {
|
|
606
|
+
return Promise.resolve(null);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
return new Promise(resolve => {
|
|
610
|
+
directionsService.route(
|
|
611
|
+
{
|
|
612
|
+
origin: from,
|
|
613
|
+
destination: to,
|
|
614
|
+
travelMode: google.maps.TravelMode.DRIVING,
|
|
615
|
+
},
|
|
616
|
+
(response, status) => {
|
|
617
|
+
if (cancelled) {
|
|
618
|
+
resolve(null);
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const overviewPath = response?.routes?.[0]?.overview_path;
|
|
623
|
+
const coordinates =
|
|
624
|
+
Array.isArray(overviewPath) && overviewPath.length > 1
|
|
625
|
+
? overviewPath
|
|
626
|
+
.map(point => ({
|
|
627
|
+
lat: Number(typeof point.lat === 'function' ? point.lat() : point.lat),
|
|
628
|
+
lng: Number(typeof point.lng === 'function' ? point.lng() : point.lng),
|
|
629
|
+
}))
|
|
630
|
+
.filter(
|
|
631
|
+
item => Number.isFinite(item.lat) && Number.isFinite(item.lng),
|
|
632
|
+
)
|
|
633
|
+
: [from, to];
|
|
634
|
+
|
|
635
|
+
if (status !== 'OK' && coordinates.length < 2) {
|
|
636
|
+
resolve(null);
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
resolve({
|
|
641
|
+
path,
|
|
642
|
+
coordinates,
|
|
643
|
+
});
|
|
644
|
+
},
|
|
645
|
+
);
|
|
646
|
+
});
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
const resolvedRoutes = await Promise.all(
|
|
650
|
+
(Array.isArray(paths) ? paths : []).map(resolveRouteCoordinates),
|
|
651
|
+
);
|
|
652
|
+
|
|
653
|
+
if (cancelled) {
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
resolvedRoutes.filter(Boolean).forEach(entry => {
|
|
658
|
+
const coordinates = Array.isArray(entry?.coordinates) ? entry.coordinates : [];
|
|
659
|
+
|
|
660
|
+
if (coordinates.length < 2) {
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
coordinates.forEach(point => {
|
|
665
|
+
bounds.extend(point);
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
new google.maps.Polyline({
|
|
669
|
+
path: coordinates,
|
|
670
|
+
geodesic: true,
|
|
671
|
+
strokeColor: normalizeText(entry?.path?.color || '') || ROUTE_COLOR,
|
|
672
|
+
strokeOpacity: 0.72,
|
|
673
|
+
strokeWeight: 4,
|
|
674
|
+
map,
|
|
675
|
+
});
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
finalize();
|
|
679
|
+
})
|
|
680
|
+
.catch(() => {});
|
|
681
|
+
|
|
682
|
+
return () => {
|
|
683
|
+
cancelled = true;
|
|
684
|
+
};
|
|
685
|
+
}, [apiKey, markerPayloads, paths, userCoordinates]);
|
|
686
|
+
|
|
687
|
+
if (
|
|
688
|
+
Platform.OS !== 'web' ||
|
|
689
|
+
!apiKey ||
|
|
690
|
+
(markerPayloads.length === 0 &&
|
|
691
|
+
!Boolean(userCoordinates) &&
|
|
692
|
+
(!Array.isArray(paths) || paths.length === 0))
|
|
693
|
+
) {
|
|
694
|
+
return null;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
return <View ref={containerRef} style={styles.mapViewport} />;
|
|
698
|
+
}
|