@cloudparse/up-miniapps-sdk 0.1.6 → 0.2.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/README.md +167 -2
- package/dist/chunk-S4UHRBSM.js +252 -0
- package/dist/chunk-S4UHRBSM.js.map +1 -0
- package/dist/host/index.d.cts +2 -0
- package/dist/host/index.d.ts +2 -0
- package/dist/host.cjs +279 -0
- package/dist/host.cjs.map +1 -0
- package/dist/host.js +11 -0
- package/dist/host.js.map +1 -0
- package/dist/index-B9dDciJq.d.cts +65 -0
- package/dist/index-B9dDciJq.d.ts +65 -0
- package/dist/index.browser.min.js +879 -1
- package/dist/index.browser.min.js.map +1 -1
- package/dist/index.cjs +3628 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +2 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.cts +1144 -0
- package/dist/index.d.ts +768 -152
- package/dist/index.js +3020 -57
- package/dist/index.js.map +1 -1
- package/dist/style.css +2279 -0
- package/package.json +55 -13
- package/dist/index.d.mts +0 -528
- package/dist/index.mjs +0 -348
- package/dist/index.mjs.map +0 -1
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,3628 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
BridgeMocker: () => BridgeMocker,
|
|
34
|
+
DebugConsole: () => DebugConsole,
|
|
35
|
+
GeolocationEvent: () => GeolocationEvent,
|
|
36
|
+
GlobalEvent: () => GlobalEvent,
|
|
37
|
+
Map: () => Map2,
|
|
38
|
+
MapControls: () => MapControls,
|
|
39
|
+
MapControlsContext: () => MapControlsContext,
|
|
40
|
+
MapMenu: () => MapMenu,
|
|
41
|
+
MapMenuDivider: () => MapMenuDivider,
|
|
42
|
+
MapMenuFooter: () => MapMenuFooter,
|
|
43
|
+
MapMenuItem: () => MapMenuItem,
|
|
44
|
+
MapMenuTitle: () => MapMenuTitle,
|
|
45
|
+
MiniAppSDK: () => MiniAppSDK,
|
|
46
|
+
RouteLayer: () => RouteLayer,
|
|
47
|
+
SDKMapProvider: () => SDKMapProvider,
|
|
48
|
+
SDKProvider: () => SDKProvider,
|
|
49
|
+
UIEvent: () => UIEvent,
|
|
50
|
+
default: () => src_default,
|
|
51
|
+
handleClearRoute: () => handleClearRoute,
|
|
52
|
+
handleDrawRoute: () => handleDrawRoute,
|
|
53
|
+
handleRouteRequest: () => handleRouteRequest,
|
|
54
|
+
renderMap: () => renderMap,
|
|
55
|
+
sdk: () => sdk,
|
|
56
|
+
useIsMocked: () => useIsMocked,
|
|
57
|
+
useLiveRouting: () => useLiveRouting,
|
|
58
|
+
useMapControls: () => useMapControls,
|
|
59
|
+
useRoute: () => useRoute,
|
|
60
|
+
useTrimmedRoute: () => useTrimmedRoute,
|
|
61
|
+
useUserLocation: () => useUserLocation
|
|
62
|
+
});
|
|
63
|
+
module.exports = __toCommonJS(src_exports);
|
|
64
|
+
|
|
65
|
+
// src/bridge.ts
|
|
66
|
+
var Bridge = class {
|
|
67
|
+
callbacks = /* @__PURE__ */ new Map();
|
|
68
|
+
eventListeners = /* @__PURE__ */ new Map();
|
|
69
|
+
/**
|
|
70
|
+
* Registers a global listener for messages coming from the native shell.
|
|
71
|
+
* Also listens for standard browser `message` events to support multiple
|
|
72
|
+
* communication channels (e.g., React Native WebView postMessage).
|
|
73
|
+
*/
|
|
74
|
+
constructor() {
|
|
75
|
+
if (typeof window !== "undefined") {
|
|
76
|
+
const bridgeReceiver = Object.freeze({
|
|
77
|
+
onNativeMessage: (envelope) => this.handleNativeMessage(envelope)
|
|
78
|
+
});
|
|
79
|
+
if (typeof window.__MiniAppBridge === "undefined") {
|
|
80
|
+
Object.defineProperty(window, "__MiniAppBridge", {
|
|
81
|
+
configurable: true,
|
|
82
|
+
// MUST be true for React StrictMode / HMR unmount/remount cycles
|
|
83
|
+
value: bridgeReceiver,
|
|
84
|
+
writable: false
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
window.addEventListener("message", (event) => {
|
|
88
|
+
if (!event.data) return;
|
|
89
|
+
try {
|
|
90
|
+
const data = typeof event.data === "string" ? JSON.parse(event.data) : event.data;
|
|
91
|
+
if (data && typeof data === "object" && (data.callbackId || data.event || data.eventName)) {
|
|
92
|
+
this.handleNativeMessage(data);
|
|
93
|
+
}
|
|
94
|
+
} catch (_e) {
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Subscribes to a pushed event from the native shell.
|
|
101
|
+
*
|
|
102
|
+
* @param event The event name to listen for.
|
|
103
|
+
* @param callback Function to execute when the event occurs.
|
|
104
|
+
* @returns An unsubscription function.
|
|
105
|
+
*/
|
|
106
|
+
on(event, callback) {
|
|
107
|
+
let listeners = this.eventListeners.get(event);
|
|
108
|
+
if (!listeners) {
|
|
109
|
+
listeners = [];
|
|
110
|
+
this.eventListeners.set(event, listeners);
|
|
111
|
+
}
|
|
112
|
+
listeners.push(callback);
|
|
113
|
+
return () => this.off(event, callback);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Unsubscribes a specific callback from an event.
|
|
117
|
+
*
|
|
118
|
+
* @param event The event name.
|
|
119
|
+
* @param callback The callback function to remove.
|
|
120
|
+
*/
|
|
121
|
+
off(event, callback) {
|
|
122
|
+
const listeners = this.eventListeners.get(event);
|
|
123
|
+
if (!listeners) return;
|
|
124
|
+
this.eventListeners.set(
|
|
125
|
+
event,
|
|
126
|
+
listeners.filter((cb) => cb !== callback)
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Sends a message to the native shell and waits for a response.
|
|
131
|
+
*
|
|
132
|
+
* @template T The expected response type.
|
|
133
|
+
* @param type The action type (e.g., "GET_LOCATION").
|
|
134
|
+
* @param payload Data associated with the action.
|
|
135
|
+
* @param timeoutMs Time in milliseconds to wait for a response before rejecting.
|
|
136
|
+
* @returns A promise that resolves with the native response.
|
|
137
|
+
*/
|
|
138
|
+
send(type, payload, timeoutMs = 3e4) {
|
|
139
|
+
if (!this.isNative()) {
|
|
140
|
+
return this.handleMockMessage(type, payload);
|
|
141
|
+
}
|
|
142
|
+
return new Promise((resolve, reject) => {
|
|
143
|
+
const callbackId = `${type}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
144
|
+
const timeout = setTimeout(() => {
|
|
145
|
+
if (this.callbacks.has(callbackId)) {
|
|
146
|
+
this.callbacks.delete(callbackId);
|
|
147
|
+
reject(new Error(`Bridge request timeout: ${type}`));
|
|
148
|
+
}
|
|
149
|
+
}, timeoutMs);
|
|
150
|
+
this.callbacks.set(callbackId, {
|
|
151
|
+
reject,
|
|
152
|
+
resolve,
|
|
153
|
+
timeout
|
|
154
|
+
});
|
|
155
|
+
const message = {
|
|
156
|
+
callbackId,
|
|
157
|
+
payload,
|
|
158
|
+
type
|
|
159
|
+
};
|
|
160
|
+
const win = window;
|
|
161
|
+
if (win.ReactNativeWebView?.postMessage) {
|
|
162
|
+
win.ReactNativeWebView.postMessage(JSON.stringify(message));
|
|
163
|
+
} else {
|
|
164
|
+
clearTimeout(timeout);
|
|
165
|
+
this.callbacks.delete(callbackId);
|
|
166
|
+
reject(new Error("Bridge not available"));
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Internal handler for all incoming messages from the native shell.
|
|
172
|
+
* Matches incoming payloads to either pending callbacks or event listeners.
|
|
173
|
+
*
|
|
174
|
+
* @internal
|
|
175
|
+
* @param envelope The message envelope received from native code.
|
|
176
|
+
*/
|
|
177
|
+
handleNativeMessage(envelope) {
|
|
178
|
+
if (!envelope || typeof envelope !== "object") return;
|
|
179
|
+
const callbackId = envelope.callbackId;
|
|
180
|
+
if (callbackId && typeof callbackId === "string") {
|
|
181
|
+
const callback = this.callbacks.get(callbackId);
|
|
182
|
+
if (callback) {
|
|
183
|
+
clearTimeout(callback.timeout);
|
|
184
|
+
const { resolve, reject } = callback;
|
|
185
|
+
if (envelope.success === false || envelope.error) {
|
|
186
|
+
reject(envelope.error || "Unknown error");
|
|
187
|
+
} else {
|
|
188
|
+
resolve(envelope.value ?? envelope.payload ?? envelope);
|
|
189
|
+
}
|
|
190
|
+
this.callbacks.delete(callbackId);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const eventName = envelope.eventName || envelope.event;
|
|
195
|
+
if (eventName) {
|
|
196
|
+
if (envelope.type === "EVENT" || !envelope.type || envelope.event) {
|
|
197
|
+
const listeners = this.eventListeners.get(eventName);
|
|
198
|
+
if (listeners) {
|
|
199
|
+
for (const listener of listeners) {
|
|
200
|
+
try {
|
|
201
|
+
listener(envelope.payload || envelope.value);
|
|
202
|
+
} catch (e) {
|
|
203
|
+
console.error(`Error in event listener for ${eventName}:`, e);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Internal handler for mock message processing, overrideable in test environments
|
|
212
|
+
*/
|
|
213
|
+
handleMockMessage(type, payload) {
|
|
214
|
+
console.warn(`[Bridge] Mock mode active: Unhandled message type ${type}`);
|
|
215
|
+
return Promise.resolve();
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Determine environment. Check flag or cache result.
|
|
219
|
+
*/
|
|
220
|
+
isNative() {
|
|
221
|
+
if (typeof window === "undefined") return false;
|
|
222
|
+
return window.__UP_REACT_NATIVE_APP__ === true || !!window.ReactNativeWebView;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
// src/components/Map/components/MapDrawer.tsx
|
|
227
|
+
var import_react = require("react");
|
|
228
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
229
|
+
var resolveHeightToPixels = (height, containerHeight) => {
|
|
230
|
+
if (typeof height === "number") {
|
|
231
|
+
return height;
|
|
232
|
+
}
|
|
233
|
+
if (height.endsWith("%")) {
|
|
234
|
+
return parseFloat(height) / 100 * containerHeight;
|
|
235
|
+
}
|
|
236
|
+
return parseFloat(height);
|
|
237
|
+
};
|
|
238
|
+
var MapDrawer = (0, import_react.forwardRef)(
|
|
239
|
+
({
|
|
240
|
+
menuItems,
|
|
241
|
+
state,
|
|
242
|
+
onChangeState,
|
|
243
|
+
side,
|
|
244
|
+
etaBarHeight = 0,
|
|
245
|
+
etaData,
|
|
246
|
+
touchStartY,
|
|
247
|
+
setTouchStartY,
|
|
248
|
+
currentTouchY,
|
|
249
|
+
setCurrentTouchY,
|
|
250
|
+
isDragging,
|
|
251
|
+
clampedDragOffset,
|
|
252
|
+
drawerPeekHeight,
|
|
253
|
+
drawerFullHeight,
|
|
254
|
+
containerHeight,
|
|
255
|
+
menuContent,
|
|
256
|
+
enableTouchDrag = false,
|
|
257
|
+
safeAreaInsets = { top: 0, bottom: 0, left: 0, right: 0 }
|
|
258
|
+
}, ref) => {
|
|
259
|
+
const scrollContainerRef = (0, import_react.useRef)(null);
|
|
260
|
+
const containerRef = (0, import_react.useRef)(null);
|
|
261
|
+
(0, import_react.useImperativeHandle)(
|
|
262
|
+
ref,
|
|
263
|
+
() => scrollContainerRef.current
|
|
264
|
+
);
|
|
265
|
+
const handleHandleClick = () => {
|
|
266
|
+
if (side === "left") {
|
|
267
|
+
onChangeState(state === "closed" ? "full" : "closed");
|
|
268
|
+
} else {
|
|
269
|
+
if (state === "closed") {
|
|
270
|
+
onChangeState("peek");
|
|
271
|
+
} else if (state === "peek") {
|
|
272
|
+
onChangeState("full");
|
|
273
|
+
} else {
|
|
274
|
+
onChangeState("peek");
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
const handleTouchStart = (e) => {
|
|
279
|
+
if (side !== "bottom") return;
|
|
280
|
+
setTouchStartY(e.targetTouches[0].clientY);
|
|
281
|
+
setCurrentTouchY(e.targetTouches[0].clientY);
|
|
282
|
+
};
|
|
283
|
+
const handleTouchMove = (e) => {
|
|
284
|
+
if (touchStartY === null || side !== "bottom") return;
|
|
285
|
+
const isScrollAtTop = scrollContainerRef.current ? scrollContainerRef.current.scrollTop === 0 : true;
|
|
286
|
+
const currentY = e.targetTouches[0].clientY;
|
|
287
|
+
const deltaY = touchStartY - currentY;
|
|
288
|
+
if (deltaY < 0 && !isScrollAtTop) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
setCurrentTouchY(currentY);
|
|
292
|
+
};
|
|
293
|
+
const handleTouchEnd = (e) => {
|
|
294
|
+
if (touchStartY === null || currentTouchY === null || side !== "bottom")
|
|
295
|
+
return;
|
|
296
|
+
const touchEndY = e.changedTouches[0].clientY;
|
|
297
|
+
const dragDistance = Math.abs(touchEndY - touchStartY);
|
|
298
|
+
if (dragDistance < 10) {
|
|
299
|
+
setTouchStartY(null);
|
|
300
|
+
setCurrentTouchY(null);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const parentContainer = containerRef.current?.parentElement;
|
|
304
|
+
const containerH = containerHeight || (parentContainer ? parentContainer.getBoundingClientRect().height : window.innerHeight);
|
|
305
|
+
const containerTop = parentContainer ? parentContainer.getBoundingClientRect().top : 0;
|
|
306
|
+
const touchYInContainer = touchEndY - containerTop;
|
|
307
|
+
const drawerHeightFromBottom = containerH - touchYInContainer;
|
|
308
|
+
const fullPx = resolveHeightToPixels(
|
|
309
|
+
drawerFullHeight || "85%",
|
|
310
|
+
containerH
|
|
311
|
+
);
|
|
312
|
+
const peekPx = resolveHeightToPixels(
|
|
313
|
+
drawerPeekHeight || "15%",
|
|
314
|
+
containerH
|
|
315
|
+
);
|
|
316
|
+
const closedPx = 32 + (state === "closed" ? safeAreaInsets.bottom : 0);
|
|
317
|
+
const distToFull = Math.abs(drawerHeightFromBottom - fullPx);
|
|
318
|
+
const distToPeek = Math.abs(drawerHeightFromBottom - peekPx);
|
|
319
|
+
const distToClosed = Math.abs(drawerHeightFromBottom - closedPx);
|
|
320
|
+
const minDist = Math.min(distToFull, distToPeek, distToClosed);
|
|
321
|
+
if (minDist === distToFull) {
|
|
322
|
+
onChangeState("full");
|
|
323
|
+
} else if (minDist === distToPeek) {
|
|
324
|
+
onChangeState("peek");
|
|
325
|
+
} else {
|
|
326
|
+
onChangeState("closed");
|
|
327
|
+
}
|
|
328
|
+
setTouchStartY(null);
|
|
329
|
+
setCurrentTouchY(null);
|
|
330
|
+
};
|
|
331
|
+
const isBackdropActive = state === "full";
|
|
332
|
+
const backdropStyles = isBackdropActive ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none";
|
|
333
|
+
const getBottomDrawerTransform = () => {
|
|
334
|
+
const containerH = containerHeight || window.innerHeight;
|
|
335
|
+
const fullPx = resolveHeightToPixels(
|
|
336
|
+
drawerFullHeight || "85%",
|
|
337
|
+
containerH
|
|
338
|
+
);
|
|
339
|
+
const peekPx = resolveHeightToPixels(
|
|
340
|
+
drawerPeekHeight || "15%",
|
|
341
|
+
containerH
|
|
342
|
+
);
|
|
343
|
+
if (state === "full") {
|
|
344
|
+
return `translateY(${clampedDragOffset}px)`;
|
|
345
|
+
}
|
|
346
|
+
if (state === "peek") {
|
|
347
|
+
const shiftPx2 = fullPx - peekPx;
|
|
348
|
+
return `translateY(calc(${shiftPx2}px + ${clampedDragOffset}px))`;
|
|
349
|
+
}
|
|
350
|
+
const closedVisibleHeight = 32;
|
|
351
|
+
const shiftPx = fullPx - closedVisibleHeight;
|
|
352
|
+
return `translateY(calc(${shiftPx}px + ${clampedDragOffset}px))`;
|
|
353
|
+
};
|
|
354
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
355
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `
|
|
356
|
+
.no-scrollbar {
|
|
357
|
+
-ms-overflow-style: none;
|
|
358
|
+
scrollbar-width: none;
|
|
359
|
+
}
|
|
360
|
+
.no-scrollbar::-webkit-scrollbar {
|
|
361
|
+
display: none;
|
|
362
|
+
}
|
|
363
|
+
` }),
|
|
364
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
365
|
+
"div",
|
|
366
|
+
{
|
|
367
|
+
className: `absolute inset-0 bg-black/45 backdrop-blur-[1px] transition-opacity duration-300 z-30 ${backdropStyles}`,
|
|
368
|
+
onClick: () => onChangeState("closed")
|
|
369
|
+
}
|
|
370
|
+
),
|
|
371
|
+
side === "left" ? (
|
|
372
|
+
/* Left Drawer Panel */
|
|
373
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
374
|
+
"div",
|
|
375
|
+
{
|
|
376
|
+
className: `absolute top-4 left-0 w-80 bg-zinc-950/85 backdrop-blur-md border-r border-y border-white/10 rounded-r-2xl shadow-2xl flex z-40 transition-transform duration-300 ease-out select-none ${state !== "closed" ? "translate-x-0" : "translate-x-[calc(-100%+24px)]"}`,
|
|
377
|
+
ref: containerRef,
|
|
378
|
+
style: {
|
|
379
|
+
bottom: `calc(1rem + ${etaBarHeight}px)`
|
|
380
|
+
},
|
|
381
|
+
children: [
|
|
382
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex-1 flex flex-col h-full overflow-hidden pl-4 py-4 pr-1", children: [
|
|
383
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex items-center justify-between pb-3 border-b border-white/10 mb-2", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-sm font-bold tracking-wider text-zinc-400 uppercase", children: "Menu" }) }),
|
|
384
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
385
|
+
"div",
|
|
386
|
+
{
|
|
387
|
+
className: "flex-1 overflow-y-auto no-scrollbar pr-2",
|
|
388
|
+
ref: scrollContainerRef,
|
|
389
|
+
children: menuContent ? menuContent : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex flex-col gap-1.5", children: menuItems.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
390
|
+
"button",
|
|
391
|
+
{
|
|
392
|
+
className: `flex items-center gap-3 w-full py-3 px-3.5 rounded-xl text-left cursor-pointer transition-all duration-150 ${item.disabled ? "opacity-45 cursor-not-allowed pointer-events-none" : "hover:bg-white/5 active:bg-white/10 text-white"}`,
|
|
393
|
+
disabled: item.disabled,
|
|
394
|
+
onClick: () => {
|
|
395
|
+
if (!item.disabled) {
|
|
396
|
+
item.onPress();
|
|
397
|
+
onChangeState("closed");
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
children: [
|
|
401
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative flex items-center justify-center w-8 h-8 rounded-lg bg-white/5 text-zinc-300", children: [
|
|
402
|
+
item.icon,
|
|
403
|
+
item.badge !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "absolute -top-1 -right-1 bg-cyan-500 text-black text-[9px] font-extrabold min-w-[15px] h-3.5 rounded-full flex items-center justify-center px-1 border border-zinc-950 shadow", children: item.badge })
|
|
404
|
+
] }),
|
|
405
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-sm font-medium tracking-wide flex-1", children: item.label })
|
|
406
|
+
]
|
|
407
|
+
},
|
|
408
|
+
item.id
|
|
409
|
+
)) })
|
|
410
|
+
}
|
|
411
|
+
)
|
|
412
|
+
] }),
|
|
413
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
414
|
+
"div",
|
|
415
|
+
{
|
|
416
|
+
className: "w-6 flex items-center justify-center cursor-pointer hover:bg-white/5 active:bg-white/10 transition-colors border-l border-white/5 rounded-r-2xl",
|
|
417
|
+
onClick: handleHandleClick,
|
|
418
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "w-1.5 h-12 bg-white/20 rounded-full transition-colors group-hover:bg-white/40" })
|
|
419
|
+
}
|
|
420
|
+
)
|
|
421
|
+
]
|
|
422
|
+
}
|
|
423
|
+
)
|
|
424
|
+
) : (
|
|
425
|
+
/* Bottom Sheet Panel - Hardware accelerated translateY transitions */
|
|
426
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
427
|
+
"div",
|
|
428
|
+
{
|
|
429
|
+
className: `absolute w-full bg-zinc-950/85 backdrop-blur-md border border-white/10 rounded-t-2xl shadow-2xl flex flex-col z-40 select-none ${isDragging ? "transition-none" : "transition-transform duration-300 ease-out"}`,
|
|
430
|
+
onTouchEnd: enableTouchDrag ? handleTouchEnd : void 0,
|
|
431
|
+
onTouchMove: enableTouchDrag ? handleTouchMove : void 0,
|
|
432
|
+
onTouchStart: enableTouchDrag ? handleTouchStart : void 0,
|
|
433
|
+
ref: containerRef,
|
|
434
|
+
style: {
|
|
435
|
+
bottom: "0px",
|
|
436
|
+
marginBottom: state === "closed" ? `${safeAreaInsets.bottom}px` : "0px",
|
|
437
|
+
height: typeof drawerFullHeight === "number" ? `${drawerFullHeight}px` : drawerFullHeight || "85%",
|
|
438
|
+
transform: getBottomDrawerTransform(),
|
|
439
|
+
transition: isDragging ? "none" : "transform 300ms ease-out, margin-bottom 250ms ease-in-out"
|
|
440
|
+
},
|
|
441
|
+
children: [
|
|
442
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
443
|
+
"div",
|
|
444
|
+
{
|
|
445
|
+
className: "w-full h-8 flex flex-col items-center justify-center cursor-pointer hover:bg-white/5 rounded-t-2xl transition-colors border-b border-white/5",
|
|
446
|
+
onClick: handleHandleClick,
|
|
447
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "w-12 h-1 bg-white/25 rounded-full" })
|
|
448
|
+
}
|
|
449
|
+
),
|
|
450
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
451
|
+
"div",
|
|
452
|
+
{
|
|
453
|
+
className: "flex-1 flex flex-col overflow-hidden px-4 pb-4 pt-2 transition-[margin-top] duration-250 ease-in-out",
|
|
454
|
+
style: {
|
|
455
|
+
marginTop: state === "closed" ? `${safeAreaInsets.bottom}px` : "0px"
|
|
456
|
+
},
|
|
457
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
458
|
+
"div",
|
|
459
|
+
{
|
|
460
|
+
className: "flex-1 overflow-y-auto no-scrollbar",
|
|
461
|
+
ref: scrollContainerRef,
|
|
462
|
+
children: menuContent ? menuContent : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex flex-col gap-1", children: menuItems.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
463
|
+
"button",
|
|
464
|
+
{
|
|
465
|
+
className: `flex items-center gap-3 w-full py-3 px-3.5 rounded-xl text-left cursor-pointer transition-all duration-150 ${item.disabled ? "opacity-45 cursor-not-allowed pointer-events-none" : "hover:bg-white/5 active:bg-white/10 text-white"}`,
|
|
466
|
+
disabled: item.disabled,
|
|
467
|
+
onClick: () => {
|
|
468
|
+
if (!item.disabled) {
|
|
469
|
+
item.onPress();
|
|
470
|
+
onChangeState("closed");
|
|
471
|
+
}
|
|
472
|
+
},
|
|
473
|
+
children: [
|
|
474
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative flex items-center justify-center w-8 h-8 rounded-lg bg-white/5 text-zinc-300", children: [
|
|
475
|
+
item.icon,
|
|
476
|
+
item.badge !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "absolute -top-1 -right-1 bg-cyan-500 text-black text-[9px] font-extrabold min-w-[15px] h-3.5 rounded-full flex items-center justify-center px-1 border border-zinc-950 shadow", children: item.badge })
|
|
477
|
+
] }),
|
|
478
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-sm font-medium tracking-wide", children: item.label })
|
|
479
|
+
]
|
|
480
|
+
},
|
|
481
|
+
item.id
|
|
482
|
+
)) })
|
|
483
|
+
}
|
|
484
|
+
)
|
|
485
|
+
}
|
|
486
|
+
)
|
|
487
|
+
]
|
|
488
|
+
}
|
|
489
|
+
)
|
|
490
|
+
)
|
|
491
|
+
] });
|
|
492
|
+
}
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
// src/components/Map/components/MapEtaPanel.tsx
|
|
496
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
497
|
+
var MapEtaPanel = ({
|
|
498
|
+
etaData,
|
|
499
|
+
layoutMode,
|
|
500
|
+
onHeaderClick,
|
|
501
|
+
style,
|
|
502
|
+
isDragging = false
|
|
503
|
+
}) => {
|
|
504
|
+
if (layoutMode === "closed-banner") {
|
|
505
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full h-10 flex items-center justify-between px-4 pb-2 pt-1 border-b border-white/5 bg-zinc-950/15 relative overflow-hidden", children: [
|
|
506
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
507
|
+
"div",
|
|
508
|
+
{
|
|
509
|
+
className: "flex items-center gap-2 cursor-pointer flex-1",
|
|
510
|
+
onClick: onHeaderClick,
|
|
511
|
+
children: [
|
|
512
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "w-2 h-2 rounded-full bg-emerald-500 animate-pulse" }),
|
|
513
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "text-xs font-bold text-emerald-400", children: [
|
|
514
|
+
etaData.etaMins,
|
|
515
|
+
" min (",
|
|
516
|
+
etaData.distanceRemainingKm.toFixed(1),
|
|
517
|
+
" km)"
|
|
518
|
+
] })
|
|
519
|
+
]
|
|
520
|
+
}
|
|
521
|
+
),
|
|
522
|
+
etaData.onAbort && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
523
|
+
"button",
|
|
524
|
+
{
|
|
525
|
+
className: "px-1 py-1 text-[10px] font-black bg-red-500/20 text-red-400 hover:bg-red-500/30 border border-red-500/25 active:scale-95 rounded-md transition-all cursor-pointer pointer-events-auto",
|
|
526
|
+
onClick: (e) => {
|
|
527
|
+
e.stopPropagation();
|
|
528
|
+
etaData.onAbort?.();
|
|
529
|
+
},
|
|
530
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
531
|
+
"svg",
|
|
532
|
+
{
|
|
533
|
+
className: "lucide lucide-x",
|
|
534
|
+
fill: "none",
|
|
535
|
+
height: "18",
|
|
536
|
+
stroke: "currentColor",
|
|
537
|
+
"stroke-linecap": "round",
|
|
538
|
+
"stroke-linejoin": "round",
|
|
539
|
+
"stroke-width": "2.5",
|
|
540
|
+
viewBox: "0 0 24 24",
|
|
541
|
+
width: "18",
|
|
542
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
543
|
+
children: [
|
|
544
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M18 6 6 18" }),
|
|
545
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m6 6 12 12" })
|
|
546
|
+
]
|
|
547
|
+
}
|
|
548
|
+
)
|
|
549
|
+
}
|
|
550
|
+
),
|
|
551
|
+
etaData.progress !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute bottom-0 left-0 right-0 h-[2px] bg-white/5", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
552
|
+
"div",
|
|
553
|
+
{
|
|
554
|
+
className: "h-full bg-cyan-400",
|
|
555
|
+
style: { width: `${etaData.progress}%` }
|
|
556
|
+
}
|
|
557
|
+
) })
|
|
558
|
+
] });
|
|
559
|
+
}
|
|
560
|
+
if (layoutMode === "open-card") {
|
|
561
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mx-4 mt-2 p-4 bg-white/5 border border-white/10 rounded-xl flex flex-col", children: [
|
|
562
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between mb-2", children: [
|
|
563
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
564
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "w-2 h-2 rounded-full bg-emerald-500 animate-pulse" }),
|
|
565
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-[10px] font-bold text-emerald-400 uppercase tracking-wider", children: etaData.label || "Journey Active" })
|
|
566
|
+
] }),
|
|
567
|
+
etaData.onAbort && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
568
|
+
"button",
|
|
569
|
+
{
|
|
570
|
+
className: "px-1 px-1 text-xs font-bold bg-red-500/20 text-red-400 hover:bg-red-500/30 border border-red-500/30 active:scale-95 rounded-lg transition-all cursor-pointer pointer-events-auto",
|
|
571
|
+
onClick: etaData.onAbort,
|
|
572
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
573
|
+
"svg",
|
|
574
|
+
{
|
|
575
|
+
className: "lucide lucide-x",
|
|
576
|
+
fill: "none",
|
|
577
|
+
height: "18",
|
|
578
|
+
stroke: "currentColor",
|
|
579
|
+
"stroke-linecap": "round",
|
|
580
|
+
"stroke-linejoin": "round",
|
|
581
|
+
"stroke-width": "2.5",
|
|
582
|
+
viewBox: "0 0 24 24",
|
|
583
|
+
width: "18",
|
|
584
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
585
|
+
children: [
|
|
586
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M18 6 6 18" }),
|
|
587
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m6 6 12 12" })
|
|
588
|
+
]
|
|
589
|
+
}
|
|
590
|
+
)
|
|
591
|
+
}
|
|
592
|
+
)
|
|
593
|
+
] }),
|
|
594
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "grid grid-cols-2 divide-x divide-white/10 py-1.5 mb-2.5", children: [
|
|
595
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "text-center", children: [
|
|
596
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "text-xl font-black text-white leading-none", children: [
|
|
597
|
+
etaData.etaMins,
|
|
598
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-xs text-zinc-400 font-normal", children: " min" })
|
|
599
|
+
] }),
|
|
600
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-[8px] text-zinc-500 font-bold uppercase tracking-wider mt-1", children: "Time Remaining" })
|
|
601
|
+
] }),
|
|
602
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "text-center", children: [
|
|
603
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "text-xl font-black text-white leading-none", children: [
|
|
604
|
+
etaData.distanceRemainingKm.toFixed(1),
|
|
605
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-xs text-zinc-400 font-normal", children: " km" })
|
|
606
|
+
] }),
|
|
607
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-[8px] text-zinc-500 font-bold uppercase tracking-wider mt-1", children: "Distance Remaining" })
|
|
608
|
+
] })
|
|
609
|
+
] }),
|
|
610
|
+
etaData.progress !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mb-3", children: [
|
|
611
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex justify-between text-[9px] font-bold text-zinc-450 mb-1", children: [
|
|
612
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "Progress" }),
|
|
613
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [
|
|
614
|
+
Math.round(etaData.progress),
|
|
615
|
+
"%"
|
|
616
|
+
] })
|
|
617
|
+
] }),
|
|
618
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "h-1 bg-white/10 rounded-full overflow-hidden", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
619
|
+
"div",
|
|
620
|
+
{
|
|
621
|
+
className: "h-full bg-cyan-400 rounded-full",
|
|
622
|
+
style: { width: `${etaData.progress}%` }
|
|
623
|
+
}
|
|
624
|
+
) })
|
|
625
|
+
] }),
|
|
626
|
+
etaData.mode === "preview" && etaData.onStart && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
627
|
+
"button",
|
|
628
|
+
{
|
|
629
|
+
className: "w-full py-2.5 text-xs font-bold bg-cyan-500 text-black hover:bg-cyan-400 active:scale-95 rounded-lg transition-all cursor-pointer",
|
|
630
|
+
onClick: etaData.onStart,
|
|
631
|
+
children: "Start Guidance"
|
|
632
|
+
}
|
|
633
|
+
)
|
|
634
|
+
] });
|
|
635
|
+
}
|
|
636
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
637
|
+
"div",
|
|
638
|
+
{
|
|
639
|
+
className: `absolute left-1/2 -translate-x-1/2 z-40 w-[calc(100%-2rem)] max-w-[320px] sm:max-w-[380px] bg-zinc-950/85 backdrop-blur-md border border-white/10 rounded-2xl shadow-2xl p-3 sm:p-4 flex flex-col pointer-events-auto select-none ${isDragging ? "transition-none" : "transition-all duration-300 ease-out"}`,
|
|
640
|
+
style,
|
|
641
|
+
children: [
|
|
642
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between mb-2 sm:mb-3", children: [
|
|
643
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
644
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "w-1.5 h-1.5 sm:w-2 sm:h-2 rounded-full bg-emerald-500 animate-pulse" }),
|
|
645
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-[9px] sm:text-[10px] font-bold text-emerald-400 uppercase tracking-wider", children: etaData.label || "Guidance Active" })
|
|
646
|
+
] }),
|
|
647
|
+
etaData.onAbort && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
648
|
+
"button",
|
|
649
|
+
{
|
|
650
|
+
className: "px-1 py-1 text-[10px] sm:text-xs font-bold bg-red-500/20 text-red-400 hover:bg-red-500/30 border border-red-500/30 active:scale-95 rounded-lg transition-all cursor-pointer",
|
|
651
|
+
onClick: etaData.onAbort,
|
|
652
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
653
|
+
"svg",
|
|
654
|
+
{
|
|
655
|
+
className: "lucide lucide-x",
|
|
656
|
+
fill: "none",
|
|
657
|
+
height: "18",
|
|
658
|
+
stroke: "currentColor",
|
|
659
|
+
"stroke-linecap": "round",
|
|
660
|
+
"stroke-linejoin": "round",
|
|
661
|
+
"stroke-width": "2.5",
|
|
662
|
+
viewBox: "0 0 24 24",
|
|
663
|
+
width: "18",
|
|
664
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
665
|
+
children: [
|
|
666
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M18 6 6 18" }),
|
|
667
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m6 6 12 12" })
|
|
668
|
+
]
|
|
669
|
+
}
|
|
670
|
+
)
|
|
671
|
+
}
|
|
672
|
+
)
|
|
673
|
+
] }),
|
|
674
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "grid grid-cols-2 divide-x divide-white/10 py-0.5 sm:py-1 mb-2 sm:mb-3", children: [
|
|
675
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-center", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "text-base sm:text-lg font-black text-white leading-none", children: [
|
|
676
|
+
etaData.etaMins,
|
|
677
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "text-[10px] sm:text-xs text-zinc-400 font-normal", children: [
|
|
678
|
+
" ",
|
|
679
|
+
"min"
|
|
680
|
+
] })
|
|
681
|
+
] }) }),
|
|
682
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-center", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "text-base sm:text-lg font-black text-white leading-none", children: [
|
|
683
|
+
etaData.distanceRemainingKm.toFixed(1),
|
|
684
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "text-[10px] sm:text-xs text-zinc-400 font-normal", children: [
|
|
685
|
+
" ",
|
|
686
|
+
"km"
|
|
687
|
+
] })
|
|
688
|
+
] }) })
|
|
689
|
+
] }),
|
|
690
|
+
etaData.progress !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
691
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex justify-between text-[8px] sm:text-[9px] font-bold text-zinc-400 mb-1", children: [
|
|
692
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "Progress" }),
|
|
693
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [
|
|
694
|
+
Math.round(etaData.progress),
|
|
695
|
+
"%"
|
|
696
|
+
] })
|
|
697
|
+
] }),
|
|
698
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "h-1 sm:h-1.5 bg-white/10 rounded-full overflow-hidden", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
699
|
+
"div",
|
|
700
|
+
{
|
|
701
|
+
className: "h-full bg-cyan-400 rounded-full",
|
|
702
|
+
style: { width: `${etaData.progress}%` }
|
|
703
|
+
}
|
|
704
|
+
) })
|
|
705
|
+
] }),
|
|
706
|
+
etaData.mode === "preview" && etaData.onStart && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
707
|
+
"button",
|
|
708
|
+
{
|
|
709
|
+
className: "mt-2.5 sm:mt-3 py-1.5 sm:py-2 text-[11px] sm:text-xs font-bold bg-cyan-500 text-black hover:bg-cyan-400 active:scale-95 rounded-lg transition-all cursor-pointer",
|
|
710
|
+
onClick: etaData.onStart,
|
|
711
|
+
children: "Start Guidance"
|
|
712
|
+
}
|
|
713
|
+
)
|
|
714
|
+
]
|
|
715
|
+
}
|
|
716
|
+
);
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
// src/components/Map/components/MapRightControls.tsx
|
|
720
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
721
|
+
var MapRightControls = ({
|
|
722
|
+
customControls = [],
|
|
723
|
+
etaBarHeight,
|
|
724
|
+
onRecenter,
|
|
725
|
+
isMapCentered,
|
|
726
|
+
mapBearing,
|
|
727
|
+
mapPitch,
|
|
728
|
+
onResetBearing,
|
|
729
|
+
initialViewState,
|
|
730
|
+
drawerState,
|
|
731
|
+
side,
|
|
732
|
+
etaData,
|
|
733
|
+
isDragging,
|
|
734
|
+
clampedDragOffset,
|
|
735
|
+
customRecenterButton,
|
|
736
|
+
customCompassButton,
|
|
737
|
+
drawerPeekHeight,
|
|
738
|
+
insets = { top: 0, bottom: 0, left: 0, right: 0 }
|
|
739
|
+
}) => {
|
|
740
|
+
const aboveControls = customControls.filter(
|
|
741
|
+
(c) => c.position === "above-recenter"
|
|
742
|
+
);
|
|
743
|
+
const belowControls = customControls.filter(
|
|
744
|
+
(c) => c.position === "below-recenter"
|
|
745
|
+
);
|
|
746
|
+
const isCompassVisible = Math.abs(mapBearing) >= 0.5 || Math.abs(mapPitch) >= 0.5;
|
|
747
|
+
const closedHeightOffset = "32px";
|
|
748
|
+
const activeEtaHeight = etaData ? etaBarHeight : drawerState === "closed" ? insets.bottom : 0;
|
|
749
|
+
const peekStyle = typeof drawerPeekHeight === "number" ? `${drawerPeekHeight}px` : drawerPeekHeight || "15%";
|
|
750
|
+
const controlsBottom = side === "bottom" ? drawerState === "closed" ? `calc(${closedHeightOffset} + 1rem + ${activeEtaHeight}px - ${clampedDragOffset}px)` : `calc(${peekStyle} + 1rem + ${etaData ? etaBarHeight : 0}px - ${clampedDragOffset}px)` : `calc(1rem + ${insets.bottom}px)`;
|
|
751
|
+
const isControlsVisible = side !== "bottom" || drawerState !== "full";
|
|
752
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
753
|
+
"div",
|
|
754
|
+
{
|
|
755
|
+
className: `absolute right-4 z-30 flex flex-col items-center gap-3 select-none pointer-events-none ${isDragging ? "transition-none" : "transition-all duration-300 ease-out"} ${isControlsVisible ? "opacity-100 scale-100 pointer-events-auto" : "opacity-0 scale-75 pointer-events-none"}`,
|
|
756
|
+
style: {
|
|
757
|
+
bottom: controlsBottom
|
|
758
|
+
},
|
|
759
|
+
children: [
|
|
760
|
+
aboveControls.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex flex-col gap-2 pointer-events-auto", children: aboveControls.map((control) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "relative group flex items-center", children: [
|
|
761
|
+
control.tooltip && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "absolute right-14 pointer-events-none opacity-0 scale-95 origin-right group-hover:opacity-100 group-hover:scale-100 transition-all duration-150 bg-zinc-900/95 border border-white/10 text-white text-xs px-2.5 py-1.5 rounded-lg shadow-xl whitespace-nowrap", children: control.tooltip }),
|
|
762
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
763
|
+
"button",
|
|
764
|
+
{
|
|
765
|
+
className: `w-12 h-12 rounded-full bg-zinc-950/80 backdrop-blur-md border border-white/10 shadow-lg flex items-center justify-center text-white active:scale-95 transition-all cursor-pointer ${control.activeState ? "ring-2 ring-cyan-400 ring-offset-2 ring-offset-zinc-950" : "hover:bg-zinc-900"}`,
|
|
766
|
+
onClick: control.onPress,
|
|
767
|
+
children: control.icon
|
|
768
|
+
}
|
|
769
|
+
)
|
|
770
|
+
] }, control.id)) }),
|
|
771
|
+
isCompassVisible && (customCompassButton ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "pointer-events-auto", children: customCompassButton }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "pointer-events-auto", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
772
|
+
"button",
|
|
773
|
+
{
|
|
774
|
+
className: `w-12 h-12 rounded-full bg-zinc-950/80 backdrop-blur-md border border-white/10 shadow-lg flex items-center justify-center text-white active:scale-95 transition-all duration-300 ${isCompassVisible ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none h-0 w-0 border-none m-0 p-0 overflow-hidden"}`,
|
|
775
|
+
onClick: () => onResetBearing({ pitch: initialViewState?.pitch ?? 0 }),
|
|
776
|
+
style: {
|
|
777
|
+
transform: isCompassVisible ? "scale(1) rotate(0deg)" : "scale(0.7) rotate(0deg)"
|
|
778
|
+
},
|
|
779
|
+
title: "Reset bearing to North",
|
|
780
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
781
|
+
"svg",
|
|
782
|
+
{
|
|
783
|
+
className: "w-7 h-7 transition-transform duration-75 ease-out",
|
|
784
|
+
fill: "none",
|
|
785
|
+
style: {
|
|
786
|
+
transform: `rotate(${-mapBearing}deg)`
|
|
787
|
+
},
|
|
788
|
+
viewBox: "0 0 24 24",
|
|
789
|
+
children: [
|
|
790
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
791
|
+
"circle",
|
|
792
|
+
{
|
|
793
|
+
cx: "12",
|
|
794
|
+
cy: "12",
|
|
795
|
+
r: "9.5",
|
|
796
|
+
stroke: "white",
|
|
797
|
+
strokeOpacity: "0.2",
|
|
798
|
+
strokeWidth: "1"
|
|
799
|
+
}
|
|
800
|
+
),
|
|
801
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12 4.5L15 12H9L12 4.5Z", fill: "#ef4444" }),
|
|
802
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12 19.5L9 12H15L12 19.5Z", fill: "#f4f4f5" }),
|
|
803
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "12", cy: "12", fill: "#18181b", r: "1.5" })
|
|
804
|
+
]
|
|
805
|
+
}
|
|
806
|
+
)
|
|
807
|
+
}
|
|
808
|
+
) })),
|
|
809
|
+
customRecenterButton ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "pointer-events-auto", children: customRecenterButton }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "pointer-events-auto", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
810
|
+
"button",
|
|
811
|
+
{
|
|
812
|
+
className: `w-12 h-12 rounded-full bg-zinc-950/80 backdrop-blur-md border border-white/10 shadow-lg flex items-center justify-center text-white active:scale-95 transition-all cursor-pointer ${isMapCentered ? "ring-2 ring-cyan-400 ring-offset-2 ring-offset-zinc-950" : "hover:bg-zinc-900"}`,
|
|
813
|
+
onClick: () => onRecenter(),
|
|
814
|
+
title: "Recenter Map",
|
|
815
|
+
children: isMapCentered ? (
|
|
816
|
+
/* Filled active lock state */
|
|
817
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
818
|
+
"svg",
|
|
819
|
+
{
|
|
820
|
+
className: "w-6 h-6 text-cyan-400",
|
|
821
|
+
fill: "none",
|
|
822
|
+
stroke: "currentColor",
|
|
823
|
+
strokeWidth: "2.2",
|
|
824
|
+
viewBox: "0 0 24 24",
|
|
825
|
+
children: [
|
|
826
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "12", cy: "12", r: "8" }),
|
|
827
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "12", cy: "12", fill: "currentColor", r: "3" }),
|
|
828
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "12", x2: "12", y1: "2", y2: "4" }),
|
|
829
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "12", x2: "12", y1: "20", y2: "22" }),
|
|
830
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "2", x2: "4", y1: "12", y2: "12" }),
|
|
831
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "20", x2: "22", y1: "12", y2: "12" })
|
|
832
|
+
]
|
|
833
|
+
}
|
|
834
|
+
)
|
|
835
|
+
) : (
|
|
836
|
+
/* Outlined drifted state */
|
|
837
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
838
|
+
"svg",
|
|
839
|
+
{
|
|
840
|
+
className: "w-6 h-6 text-zinc-300",
|
|
841
|
+
fill: "none",
|
|
842
|
+
stroke: "currentColor",
|
|
843
|
+
strokeWidth: "2",
|
|
844
|
+
viewBox: "0 0 24 24",
|
|
845
|
+
children: [
|
|
846
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "12", cy: "12", r: "8" }),
|
|
847
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "12", x2: "12", y1: "2", y2: "4" }),
|
|
848
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "12", x2: "12", y1: "20", y2: "22" }),
|
|
849
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "2", x2: "4", y1: "12", y2: "12" }),
|
|
850
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "20", x2: "22", y1: "12", y2: "12" })
|
|
851
|
+
]
|
|
852
|
+
}
|
|
853
|
+
)
|
|
854
|
+
)
|
|
855
|
+
}
|
|
856
|
+
) }),
|
|
857
|
+
belowControls.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex flex-col gap-2 pointer-events-auto", children: belowControls.map((control) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "relative group flex items-center", children: [
|
|
858
|
+
control.tooltip && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "absolute right-14 pointer-events-none opacity-0 scale-95 origin-right group-hover:opacity-100 group-hover:scale-100 transition-all duration-150 bg-zinc-900/95 border border-white/10 text-white text-xs px-2.5 py-1.5 rounded-lg shadow-xl whitespace-nowrap", children: control.tooltip }),
|
|
859
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
860
|
+
"button",
|
|
861
|
+
{
|
|
862
|
+
className: `w-12 h-12 rounded-full bg-zinc-950/80 backdrop-blur-md border border-white/10 shadow-lg flex items-center justify-center text-white active:scale-95 transition-all cursor-pointer ${control.activeState ? "ring-2 ring-cyan-400 ring-offset-2 ring-offset-zinc-950" : "hover:bg-zinc-900"}`,
|
|
863
|
+
onClick: control.onPress,
|
|
864
|
+
children: control.icon
|
|
865
|
+
}
|
|
866
|
+
)
|
|
867
|
+
] }, control.id)) })
|
|
868
|
+
]
|
|
869
|
+
}
|
|
870
|
+
);
|
|
871
|
+
};
|
|
872
|
+
|
|
873
|
+
// src/components/Map/MapContext.tsx
|
|
874
|
+
var import_react2 = require("react");
|
|
875
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
876
|
+
var MapControlsContext = (0, import_react2.createContext)(
|
|
877
|
+
null
|
|
878
|
+
);
|
|
879
|
+
var SDKMapProvider = ({
|
|
880
|
+
children
|
|
881
|
+
}) => {
|
|
882
|
+
const [isFollowing, setIsFollowing] = (0, import_react2.useState)(true);
|
|
883
|
+
const [bearing, setBearingState] = (0, import_react2.useState)(0);
|
|
884
|
+
const [pitch, setPitchState] = (0, import_react2.useState)(0);
|
|
885
|
+
const [drawerState, setDrawerStateInternal] = (0, import_react2.useState)("closed");
|
|
886
|
+
const drawerContentRef = (0, import_react2.useRef)(null);
|
|
887
|
+
const setDrawerState = (0, import_react2.useCallback)((val) => {
|
|
888
|
+
setDrawerStateInternal(val);
|
|
889
|
+
}, []);
|
|
890
|
+
const [hasControls, setHasControlsState] = (0, import_react2.useState)(false);
|
|
891
|
+
const setHasControls = (0, import_react2.useCallback)((val) => {
|
|
892
|
+
setHasControlsState((prev) => prev === val ? prev : val);
|
|
893
|
+
}, []);
|
|
894
|
+
const [etaData, setEtaDataState] = (0, import_react2.useState)(void 0);
|
|
895
|
+
const setEtaData = (0, import_react2.useCallback)((newData) => {
|
|
896
|
+
setEtaDataState((prev) => {
|
|
897
|
+
if (!prev && !newData) return prev;
|
|
898
|
+
if (!prev || !newData) return newData;
|
|
899
|
+
if (prev.etaMins === newData.etaMins && prev.distanceRemainingKm === newData.distanceRemainingKm && prev.label === newData.label && prev.progress === newData.progress && prev.mode === newData.mode && prev.isCalculating === newData.isCalculating && prev.error === newData.error) {
|
|
900
|
+
return prev;
|
|
901
|
+
}
|
|
902
|
+
return newData;
|
|
903
|
+
});
|
|
904
|
+
}, []);
|
|
905
|
+
const [insets, setInsetsState] = (0, import_react2.useState)({
|
|
906
|
+
top: 0,
|
|
907
|
+
bottom: 0,
|
|
908
|
+
left: 0,
|
|
909
|
+
right: 0
|
|
910
|
+
});
|
|
911
|
+
const setInsets = (0, import_react2.useCallback)((newInsets) => {
|
|
912
|
+
setInsetsState((prev) => ({ ...prev, ...newInsets }));
|
|
913
|
+
}, []);
|
|
914
|
+
const centerCallbackRef = (0, import_react2.useRef)(null);
|
|
915
|
+
const resetBearingCallbackRef = (0, import_react2.useRef)(null);
|
|
916
|
+
const centerOnUser = (0, import_react2.useCallback)((params) => {
|
|
917
|
+
if (centerCallbackRef.current) {
|
|
918
|
+
centerCallbackRef.current(params);
|
|
919
|
+
}
|
|
920
|
+
}, []);
|
|
921
|
+
const resetBearing = (0, import_react2.useCallback)((params) => {
|
|
922
|
+
if (resetBearingCallbackRef.current) {
|
|
923
|
+
resetBearingCallbackRef.current(params);
|
|
924
|
+
}
|
|
925
|
+
}, []);
|
|
926
|
+
const _registerCenterCallback = (0, import_react2.useCallback)(
|
|
927
|
+
(cb) => {
|
|
928
|
+
centerCallbackRef.current = cb;
|
|
929
|
+
},
|
|
930
|
+
[]
|
|
931
|
+
);
|
|
932
|
+
const _registerResetBearingCallback = (0, import_react2.useCallback)(
|
|
933
|
+
(cb) => {
|
|
934
|
+
resetBearingCallbackRef.current = cb;
|
|
935
|
+
},
|
|
936
|
+
[]
|
|
937
|
+
);
|
|
938
|
+
const setBearing = (0, import_react2.useCallback)((b) => {
|
|
939
|
+
setBearingState(b);
|
|
940
|
+
}, []);
|
|
941
|
+
const setPitch = (0, import_react2.useCallback)((p) => {
|
|
942
|
+
setPitchState(p);
|
|
943
|
+
}, []);
|
|
944
|
+
const contextValue = (0, import_react2.useMemo)(
|
|
945
|
+
() => ({
|
|
946
|
+
_registerCenterCallback,
|
|
947
|
+
_registerResetBearingCallback,
|
|
948
|
+
bearing,
|
|
949
|
+
centerOnUser,
|
|
950
|
+
drawerContentRef,
|
|
951
|
+
drawerState,
|
|
952
|
+
etaData,
|
|
953
|
+
hasControls,
|
|
954
|
+
isFollowing,
|
|
955
|
+
pitch,
|
|
956
|
+
resetBearing,
|
|
957
|
+
setBearing,
|
|
958
|
+
setDrawerState,
|
|
959
|
+
setEtaData,
|
|
960
|
+
setHasControls,
|
|
961
|
+
setIsFollowing,
|
|
962
|
+
setPitch,
|
|
963
|
+
insets,
|
|
964
|
+
setInsets
|
|
965
|
+
}),
|
|
966
|
+
[
|
|
967
|
+
_registerCenterCallback,
|
|
968
|
+
_registerResetBearingCallback,
|
|
969
|
+
bearing,
|
|
970
|
+
centerOnUser,
|
|
971
|
+
drawerState,
|
|
972
|
+
etaData,
|
|
973
|
+
hasControls,
|
|
974
|
+
isFollowing,
|
|
975
|
+
pitch,
|
|
976
|
+
resetBearing,
|
|
977
|
+
setBearing,
|
|
978
|
+
setDrawerState,
|
|
979
|
+
setEtaData,
|
|
980
|
+
setHasControls,
|
|
981
|
+
setPitch,
|
|
982
|
+
insets,
|
|
983
|
+
setInsets
|
|
984
|
+
]
|
|
985
|
+
);
|
|
986
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MapControlsContext.Provider, { value: contextValue, children });
|
|
987
|
+
};
|
|
988
|
+
var useMapControls = () => {
|
|
989
|
+
const context = (0, import_react2.useContext)(MapControlsContext);
|
|
990
|
+
if (!context) {
|
|
991
|
+
throw new Error("useMapControls must be used within a MapControlsProvider");
|
|
992
|
+
}
|
|
993
|
+
return context;
|
|
994
|
+
};
|
|
995
|
+
|
|
996
|
+
// src/components/Map/components/MapControls.tsx
|
|
997
|
+
var import_react3 = require("react");
|
|
998
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
999
|
+
var resolveHeightToPixels2 = (height, containerHeight) => {
|
|
1000
|
+
if (typeof height === "number") {
|
|
1001
|
+
return height;
|
|
1002
|
+
}
|
|
1003
|
+
if (height.endsWith("%")) {
|
|
1004
|
+
return parseFloat(height) / 100 * containerHeight;
|
|
1005
|
+
}
|
|
1006
|
+
return parseFloat(height);
|
|
1007
|
+
};
|
|
1008
|
+
var MapControls = ({
|
|
1009
|
+
menuItems = [],
|
|
1010
|
+
drawerSide,
|
|
1011
|
+
customControls = [],
|
|
1012
|
+
etaBarHeight = 100,
|
|
1013
|
+
onRecenter: propRecenter,
|
|
1014
|
+
isMapCentered: propMapCentered,
|
|
1015
|
+
mapBearing: propMapBearing,
|
|
1016
|
+
onResetBearing: propResetBearing,
|
|
1017
|
+
etaData,
|
|
1018
|
+
customRecenterButton,
|
|
1019
|
+
defaultState,
|
|
1020
|
+
initialViewState,
|
|
1021
|
+
customCompassButton,
|
|
1022
|
+
drawerPeekHeight,
|
|
1023
|
+
drawerFullHeight,
|
|
1024
|
+
drawerContentRef,
|
|
1025
|
+
menuContent,
|
|
1026
|
+
children,
|
|
1027
|
+
enableTouchDrag = false,
|
|
1028
|
+
isInternal = false
|
|
1029
|
+
}) => {
|
|
1030
|
+
const mapContext = (0, import_react3.useContext)(MapControlsContext);
|
|
1031
|
+
const contextRef = mapContext?.drawerContentRef;
|
|
1032
|
+
const mergedRef = (0, import_react3.useCallback)(
|
|
1033
|
+
(node) => {
|
|
1034
|
+
if (drawerContentRef) {
|
|
1035
|
+
if (typeof drawerContentRef === "function") {
|
|
1036
|
+
drawerContentRef(node);
|
|
1037
|
+
} else {
|
|
1038
|
+
drawerContentRef.current = node;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
if (contextRef) {
|
|
1042
|
+
contextRef.current = node;
|
|
1043
|
+
}
|
|
1044
|
+
},
|
|
1045
|
+
[drawerContentRef, contextRef]
|
|
1046
|
+
);
|
|
1047
|
+
const [localDrawerState, setLocalDrawerState] = (0, import_react3.useState)(
|
|
1048
|
+
defaultState ?? "closed"
|
|
1049
|
+
);
|
|
1050
|
+
const drawerState = mapContext ? mapContext.drawerState : localDrawerState;
|
|
1051
|
+
const setDrawerState = (0, import_react3.useCallback)(
|
|
1052
|
+
(state) => {
|
|
1053
|
+
if (mapContext) {
|
|
1054
|
+
mapContext.setDrawerState(state);
|
|
1055
|
+
} else {
|
|
1056
|
+
setLocalDrawerState(state);
|
|
1057
|
+
}
|
|
1058
|
+
},
|
|
1059
|
+
[mapContext]
|
|
1060
|
+
);
|
|
1061
|
+
(0, import_react3.useEffect)(() => {
|
|
1062
|
+
if (defaultState && mapContext) {
|
|
1063
|
+
mapContext.setDrawerState(defaultState);
|
|
1064
|
+
}
|
|
1065
|
+
}, []);
|
|
1066
|
+
const [side, setSide] = (0, import_react3.useState)("left");
|
|
1067
|
+
const containerRef = (0, import_react3.useRef)(null);
|
|
1068
|
+
const [mapHeight, setMapHeight] = (0, import_react3.useState)(600);
|
|
1069
|
+
(0, import_react3.useEffect)(() => {
|
|
1070
|
+
const parent = containerRef.current?.parentElement;
|
|
1071
|
+
if (!parent) return;
|
|
1072
|
+
const updateHeight = () => {
|
|
1073
|
+
setMapHeight(parent.getBoundingClientRect().height);
|
|
1074
|
+
};
|
|
1075
|
+
updateHeight();
|
|
1076
|
+
const observer = new ResizeObserver(updateHeight);
|
|
1077
|
+
observer.observe(parent);
|
|
1078
|
+
return () => {
|
|
1079
|
+
observer.disconnect();
|
|
1080
|
+
};
|
|
1081
|
+
}, []);
|
|
1082
|
+
const setHasControls = mapContext?.setHasControls;
|
|
1083
|
+
(0, import_react3.useEffect)(() => {
|
|
1084
|
+
if (setHasControls && !isInternal) {
|
|
1085
|
+
setHasControls(true);
|
|
1086
|
+
return () => setHasControls(false);
|
|
1087
|
+
}
|
|
1088
|
+
}, [setHasControls, isInternal]);
|
|
1089
|
+
const isMapCentered = propMapCentered !== void 0 ? propMapCentered : mapContext ? mapContext.isFollowing : true;
|
|
1090
|
+
const mapBearing = propMapBearing !== void 0 ? propMapBearing : mapContext ? mapContext.bearing : 0;
|
|
1091
|
+
const onRecenter = (params) => {
|
|
1092
|
+
if (propRecenter) {
|
|
1093
|
+
propRecenter(params);
|
|
1094
|
+
} else if (mapContext) {
|
|
1095
|
+
mapContext.centerOnUser(params);
|
|
1096
|
+
}
|
|
1097
|
+
};
|
|
1098
|
+
const onResetBearing = (params) => {
|
|
1099
|
+
if (propResetBearing) {
|
|
1100
|
+
propResetBearing(params);
|
|
1101
|
+
} else if (mapContext) {
|
|
1102
|
+
mapContext.resetBearing(params);
|
|
1103
|
+
}
|
|
1104
|
+
};
|
|
1105
|
+
const mapPitch = mapContext ? mapContext.pitch : 0;
|
|
1106
|
+
const resolvedEtaData = etaData !== void 0 ? etaData : mapContext?.etaData;
|
|
1107
|
+
const [touchStartY, setTouchStartY] = (0, import_react3.useState)(null);
|
|
1108
|
+
const [currentTouchY, setCurrentTouchY] = (0, import_react3.useState)(null);
|
|
1109
|
+
(0, import_react3.useEffect)(() => {
|
|
1110
|
+
if (typeof window === "undefined") return;
|
|
1111
|
+
const checkLayout = () => {
|
|
1112
|
+
if (drawerSide) {
|
|
1113
|
+
setSide(drawerSide);
|
|
1114
|
+
} else {
|
|
1115
|
+
if (window.innerWidth < 768) {
|
|
1116
|
+
setSide("bottom");
|
|
1117
|
+
} else {
|
|
1118
|
+
setSide("left");
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
};
|
|
1122
|
+
checkLayout();
|
|
1123
|
+
window.addEventListener("resize", checkLayout);
|
|
1124
|
+
return () => window.removeEventListener("resize", checkLayout);
|
|
1125
|
+
}, [drawerSide]);
|
|
1126
|
+
const isDragging = touchStartY !== null && currentTouchY !== null;
|
|
1127
|
+
const dragOffset = isDragging ? currentTouchY - touchStartY : 0;
|
|
1128
|
+
let clampedDragOffset = dragOffset;
|
|
1129
|
+
if (drawerState === "full") {
|
|
1130
|
+
clampedDragOffset = Math.max(0, dragOffset);
|
|
1131
|
+
} else if (drawerState === "closed") {
|
|
1132
|
+
clampedDragOffset = Math.min(0, dragOffset);
|
|
1133
|
+
}
|
|
1134
|
+
const fullPx = resolveHeightToPixels2(drawerFullHeight || "85%", mapHeight);
|
|
1135
|
+
const peekPx = resolveHeightToPixels2(drawerPeekHeight || "15%", mapHeight);
|
|
1136
|
+
let baseHeight = 32;
|
|
1137
|
+
if (drawerState === "peek") {
|
|
1138
|
+
baseHeight = peekPx;
|
|
1139
|
+
} else if (drawerState === "full") {
|
|
1140
|
+
baseHeight = fullPx;
|
|
1141
|
+
}
|
|
1142
|
+
const currentDrawerHeight = baseHeight - clampedDragOffset;
|
|
1143
|
+
const startFadeHeight = peekPx + 20;
|
|
1144
|
+
const endFadeHeight = peekPx + (fullPx - peekPx) * 0.35;
|
|
1145
|
+
const bottomInset = mapContext?.insets.bottom || 0;
|
|
1146
|
+
let etaOpacity = 1;
|
|
1147
|
+
let etaBottomStyle = "1rem";
|
|
1148
|
+
if (side === "bottom") {
|
|
1149
|
+
if (currentDrawerHeight <= startFadeHeight) {
|
|
1150
|
+
etaOpacity = 1;
|
|
1151
|
+
} else if (currentDrawerHeight >= endFadeHeight) {
|
|
1152
|
+
etaOpacity = 0;
|
|
1153
|
+
} else {
|
|
1154
|
+
const fraction = (currentDrawerHeight - startFadeHeight) / (endFadeHeight - startFadeHeight);
|
|
1155
|
+
etaOpacity = 1 - fraction;
|
|
1156
|
+
}
|
|
1157
|
+
const effectiveInset = drawerState === "closed" ? bottomInset : 0;
|
|
1158
|
+
etaBottomStyle = `${currentDrawerHeight + effectiveInset + 16}px`;
|
|
1159
|
+
} else {
|
|
1160
|
+
etaOpacity = drawerState === "full" ? 0 : 1;
|
|
1161
|
+
etaBottomStyle = `calc(1rem + ${bottomInset}px)`;
|
|
1162
|
+
}
|
|
1163
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
1164
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { ref: containerRef, style: { display: "none" } }),
|
|
1165
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1166
|
+
MapDrawer,
|
|
1167
|
+
{
|
|
1168
|
+
clampedDragOffset,
|
|
1169
|
+
containerHeight: mapHeight,
|
|
1170
|
+
currentTouchY,
|
|
1171
|
+
drawerFullHeight,
|
|
1172
|
+
drawerPeekHeight,
|
|
1173
|
+
enableTouchDrag,
|
|
1174
|
+
etaBarHeight,
|
|
1175
|
+
etaData: resolvedEtaData,
|
|
1176
|
+
isDragging,
|
|
1177
|
+
menuContent: children || menuContent,
|
|
1178
|
+
menuItems,
|
|
1179
|
+
onChangeState: setDrawerState,
|
|
1180
|
+
ref: mergedRef,
|
|
1181
|
+
safeAreaInsets: mapContext?.insets,
|
|
1182
|
+
setCurrentTouchY,
|
|
1183
|
+
setTouchStartY,
|
|
1184
|
+
side,
|
|
1185
|
+
state: drawerState,
|
|
1186
|
+
touchStartY
|
|
1187
|
+
}
|
|
1188
|
+
),
|
|
1189
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1190
|
+
MapRightControls,
|
|
1191
|
+
{
|
|
1192
|
+
clampedDragOffset,
|
|
1193
|
+
customCompassButton,
|
|
1194
|
+
customControls,
|
|
1195
|
+
customRecenterButton,
|
|
1196
|
+
drawerPeekHeight,
|
|
1197
|
+
drawerState,
|
|
1198
|
+
etaBarHeight: etaBarHeight + (drawerState === "closed" ? bottomInset : 0),
|
|
1199
|
+
etaData: resolvedEtaData,
|
|
1200
|
+
initialViewState,
|
|
1201
|
+
insets: mapContext?.insets,
|
|
1202
|
+
isDragging,
|
|
1203
|
+
isMapCentered,
|
|
1204
|
+
mapBearing,
|
|
1205
|
+
mapPitch,
|
|
1206
|
+
onRecenter: () => onRecenter(),
|
|
1207
|
+
onResetBearing,
|
|
1208
|
+
side
|
|
1209
|
+
}
|
|
1210
|
+
),
|
|
1211
|
+
resolvedEtaData && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1212
|
+
MapEtaPanel,
|
|
1213
|
+
{
|
|
1214
|
+
etaData: resolvedEtaData,
|
|
1215
|
+
isDragging,
|
|
1216
|
+
layoutMode: "floating-card",
|
|
1217
|
+
style: {
|
|
1218
|
+
bottom: etaBottomStyle,
|
|
1219
|
+
opacity: etaOpacity,
|
|
1220
|
+
pointerEvents: etaOpacity > 0.1 ? "auto" : "none",
|
|
1221
|
+
transition: isDragging ? "none" : "bottom 250ms ease-in-out, opacity 300ms ease-out"
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
)
|
|
1225
|
+
] });
|
|
1226
|
+
};
|
|
1227
|
+
|
|
1228
|
+
// src/components/Map/components/MapMenu.tsx
|
|
1229
|
+
var import_react4 = require("react");
|
|
1230
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
1231
|
+
var MapMenu = ({ children }) => {
|
|
1232
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex flex-col gap-1.5", children });
|
|
1233
|
+
};
|
|
1234
|
+
var MapMenuItem = ({
|
|
1235
|
+
id,
|
|
1236
|
+
label,
|
|
1237
|
+
icon,
|
|
1238
|
+
onPress,
|
|
1239
|
+
badge,
|
|
1240
|
+
disabled = false,
|
|
1241
|
+
children
|
|
1242
|
+
}) => {
|
|
1243
|
+
const context = (0, import_react4.useContext)(MapControlsContext);
|
|
1244
|
+
const handleClick = (e) => {
|
|
1245
|
+
if (disabled) return;
|
|
1246
|
+
if (onPress) onPress();
|
|
1247
|
+
if (context && context.setDrawerState) {
|
|
1248
|
+
context.setDrawerState("closed");
|
|
1249
|
+
}
|
|
1250
|
+
};
|
|
1251
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
1252
|
+
"button",
|
|
1253
|
+
{
|
|
1254
|
+
className: `flex items-center gap-3 w-full py-3 px-3.5 rounded-xl text-left cursor-pointer transition-all duration-150 ${disabled ? "opacity-45 cursor-not-allowed pointer-events-none" : "hover:bg-white/5 active:bg-white/10 text-white"}`,
|
|
1255
|
+
disabled,
|
|
1256
|
+
onClick: handleClick,
|
|
1257
|
+
type: "button",
|
|
1258
|
+
children: [
|
|
1259
|
+
icon && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "relative flex items-center justify-center w-8 h-8 rounded-lg bg-white/5 text-zinc-300", children: [
|
|
1260
|
+
icon,
|
|
1261
|
+
badge !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "absolute -top-1 -right-1 bg-cyan-500 text-black text-[9px] font-extrabold min-w-[15px] h-3.5 rounded-full flex items-center justify-center px-1 border border-zinc-950 shadow", children: badge })
|
|
1262
|
+
] }),
|
|
1263
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex-1 flex flex-col justify-center", children: [
|
|
1264
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-sm font-medium tracking-wide flex-1", children: label }),
|
|
1265
|
+
children
|
|
1266
|
+
] })
|
|
1267
|
+
]
|
|
1268
|
+
}
|
|
1269
|
+
);
|
|
1270
|
+
};
|
|
1271
|
+
MapMenu.displayName = "MapMenu";
|
|
1272
|
+
MapMenuItem.displayName = "MapMenuItem";
|
|
1273
|
+
var MapMenuTitle = ({
|
|
1274
|
+
children,
|
|
1275
|
+
showDivider = true,
|
|
1276
|
+
className = ""
|
|
1277
|
+
}) => {
|
|
1278
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1279
|
+
"div",
|
|
1280
|
+
{
|
|
1281
|
+
className: `flex items-center justify-between pb-3 first:mt-0 px-3.5 ${className} ${showDivider ? "border-b border-white/10 mb-2 mt-4" : ""}`,
|
|
1282
|
+
children
|
|
1283
|
+
}
|
|
1284
|
+
);
|
|
1285
|
+
};
|
|
1286
|
+
MapMenuTitle.displayName = "MapMenuTitle";
|
|
1287
|
+
var MapMenuDivider = ({
|
|
1288
|
+
className = ""
|
|
1289
|
+
}) => {
|
|
1290
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: `h-[1px] bg-white/10 my-3 mx-3.5 ${className}` });
|
|
1291
|
+
};
|
|
1292
|
+
MapMenuDivider.displayName = "MapMenuDivider";
|
|
1293
|
+
var MapMenuFooter = ({
|
|
1294
|
+
children,
|
|
1295
|
+
className = ""
|
|
1296
|
+
}) => {
|
|
1297
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1298
|
+
"div",
|
|
1299
|
+
{
|
|
1300
|
+
className: `mt-auto pt-4 border-t border-white/10 px-3.5 text-xs text-zinc-500 font-medium ${className}`,
|
|
1301
|
+
children
|
|
1302
|
+
}
|
|
1303
|
+
);
|
|
1304
|
+
};
|
|
1305
|
+
MapMenuFooter.displayName = "MapMenuFooter";
|
|
1306
|
+
|
|
1307
|
+
// src/types/types.ts
|
|
1308
|
+
var UIEvent = /* @__PURE__ */ ((UIEvent2) => {
|
|
1309
|
+
UIEvent2["TOP_SCROLL_MORE"] = "TOP_SCROLL_MORE";
|
|
1310
|
+
UIEvent2["BOTTOM_SCROLL_MORE"] = "BOTTOM_SCROLL_MORE";
|
|
1311
|
+
UIEvent2["BACK_GESTURE"] = "BACK_GESTURE";
|
|
1312
|
+
return UIEvent2;
|
|
1313
|
+
})(UIEvent || {});
|
|
1314
|
+
var GeolocationEvent = /* @__PURE__ */ ((GeolocationEvent2) => {
|
|
1315
|
+
GeolocationEvent2["LOCATION_UPDATE"] = "LOCATION_UPDATE";
|
|
1316
|
+
GeolocationEvent2["LOCATION_STREAM_UPDATE"] = "LOCATION_STREAM_UPDATE";
|
|
1317
|
+
return GeolocationEvent2;
|
|
1318
|
+
})(GeolocationEvent || {});
|
|
1319
|
+
var GlobalEvent = /* @__PURE__ */ ((GlobalEvent2) => {
|
|
1320
|
+
GlobalEvent2["UPDATE_CONTEXT"] = "UPDATE_CONTEXT";
|
|
1321
|
+
return GlobalEvent2;
|
|
1322
|
+
})(GlobalEvent || {});
|
|
1323
|
+
|
|
1324
|
+
// src/components/Map/hooks/useUserLocation.ts
|
|
1325
|
+
var import_react5 = require("react");
|
|
1326
|
+
var LERP_FACTOR = 0.12;
|
|
1327
|
+
var useUserLocation = () => {
|
|
1328
|
+
const [locationState, setLocationState] = (0, import_react5.useState)({
|
|
1329
|
+
accuracy: null,
|
|
1330
|
+
coords: null,
|
|
1331
|
+
heading: null
|
|
1332
|
+
});
|
|
1333
|
+
const targetRef = (0, import_react5.useRef)({
|
|
1334
|
+
accuracy: null,
|
|
1335
|
+
coords: null,
|
|
1336
|
+
heading: null
|
|
1337
|
+
});
|
|
1338
|
+
const currentRef = (0, import_react5.useRef)({
|
|
1339
|
+
accuracy: null,
|
|
1340
|
+
coords: null,
|
|
1341
|
+
heading: null
|
|
1342
|
+
});
|
|
1343
|
+
const mapPayload = (0, import_react5.useCallback)((payload) => {
|
|
1344
|
+
const coords = payload.location?.coords || payload.coords;
|
|
1345
|
+
const headingData = payload.heading || payload.coords;
|
|
1346
|
+
const newCoords = coords ? [coords.longitude, coords.latitude] : null;
|
|
1347
|
+
let heading = null;
|
|
1348
|
+
if (headingData?.trueHeading !== void 0 && headingData.trueHeading >= 0) {
|
|
1349
|
+
heading = headingData.trueHeading;
|
|
1350
|
+
} else if (headingData?.magneticHeading !== void 0 && headingData.magneticHeading >= 0) {
|
|
1351
|
+
heading = headingData.magneticHeading;
|
|
1352
|
+
} else if (headingData?.heading !== null && headingData.heading >= 0) {
|
|
1353
|
+
heading = headingData.heading;
|
|
1354
|
+
}
|
|
1355
|
+
return {
|
|
1356
|
+
accuracy: coords?.accuracy || null,
|
|
1357
|
+
coords: newCoords,
|
|
1358
|
+
heading
|
|
1359
|
+
};
|
|
1360
|
+
}, []);
|
|
1361
|
+
(0, import_react5.useEffect)(() => {
|
|
1362
|
+
let rafId;
|
|
1363
|
+
const animate = () => {
|
|
1364
|
+
let changed = false;
|
|
1365
|
+
if (targetRef.current.coords) {
|
|
1366
|
+
if (!currentRef.current.coords) {
|
|
1367
|
+
currentRef.current.coords = [...targetRef.current.coords];
|
|
1368
|
+
changed = true;
|
|
1369
|
+
} else {
|
|
1370
|
+
const [tLng, tLat] = targetRef.current.coords;
|
|
1371
|
+
const [cLng, cLat] = currentRef.current.coords;
|
|
1372
|
+
const nLng = cLng + (tLng - cLng) * LERP_FACTOR;
|
|
1373
|
+
const nLat = cLat + (tLat - cLat) * LERP_FACTOR;
|
|
1374
|
+
if (Math.abs(nLng - cLng) > 1e-7 || Math.abs(nLat - cLat) > 1e-7) {
|
|
1375
|
+
currentRef.current.coords = [nLng, nLat];
|
|
1376
|
+
changed = true;
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
if (targetRef.current.heading !== null) {
|
|
1381
|
+
if (currentRef.current.heading === null) {
|
|
1382
|
+
currentRef.current.heading = targetRef.current.heading;
|
|
1383
|
+
changed = true;
|
|
1384
|
+
} else {
|
|
1385
|
+
const target = targetRef.current.heading;
|
|
1386
|
+
const current = currentRef.current.heading;
|
|
1387
|
+
let delta = target - current;
|
|
1388
|
+
if (delta > 180) delta -= 360;
|
|
1389
|
+
if (delta < -180) delta += 360;
|
|
1390
|
+
const nHeading = current + delta * LERP_FACTOR;
|
|
1391
|
+
const normalized = (nHeading % 360 + 360) % 360;
|
|
1392
|
+
if (Math.abs(delta) > 0.1) {
|
|
1393
|
+
currentRef.current.heading = normalized;
|
|
1394
|
+
changed = true;
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
if (changed) {
|
|
1399
|
+
setLocationState({
|
|
1400
|
+
accuracy: targetRef.current.accuracy,
|
|
1401
|
+
coords: currentRef.current.coords ? [...currentRef.current.coords] : null,
|
|
1402
|
+
heading: currentRef.current.heading
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
rafId = requestAnimationFrame(animate);
|
|
1406
|
+
};
|
|
1407
|
+
rafId = requestAnimationFrame(animate);
|
|
1408
|
+
sdk.location.getCurrentPosition().then((data) => {
|
|
1409
|
+
const payload = mapPayload(data);
|
|
1410
|
+
targetRef.current = payload;
|
|
1411
|
+
}).catch((e) => console.warn("Initial location fetch failed", e));
|
|
1412
|
+
const unsubscribeStream = sdk.on(
|
|
1413
|
+
"LOCATION_STREAM_UPDATE" /* LOCATION_STREAM_UPDATE */,
|
|
1414
|
+
(payload) => {
|
|
1415
|
+
targetRef.current = mapPayload(payload);
|
|
1416
|
+
}
|
|
1417
|
+
);
|
|
1418
|
+
const unsubscribeLegacy = sdk.on(
|
|
1419
|
+
"LOCATION_UPDATE" /* LOCATION_UPDATE */,
|
|
1420
|
+
(payload) => {
|
|
1421
|
+
targetRef.current = mapPayload(payload);
|
|
1422
|
+
}
|
|
1423
|
+
);
|
|
1424
|
+
sdk.location.startStream({
|
|
1425
|
+
distanceFilter: 1,
|
|
1426
|
+
headingThreshold: 2
|
|
1427
|
+
}).catch((e) => console.warn("Failed to start location stream", e));
|
|
1428
|
+
return () => {
|
|
1429
|
+
cancelAnimationFrame(rafId);
|
|
1430
|
+
unsubscribeStream();
|
|
1431
|
+
unsubscribeLegacy();
|
|
1432
|
+
sdk.location.stopStream().catch(() => {
|
|
1433
|
+
});
|
|
1434
|
+
};
|
|
1435
|
+
}, [mapPayload]);
|
|
1436
|
+
return locationState;
|
|
1437
|
+
};
|
|
1438
|
+
|
|
1439
|
+
// src/components/Map/hooks/useLiveRouting.ts
|
|
1440
|
+
var import_react6 = require("react");
|
|
1441
|
+
var useLiveRouting = ({
|
|
1442
|
+
origin: manualOrigin,
|
|
1443
|
+
destination,
|
|
1444
|
+
profile = "driving",
|
|
1445
|
+
recalculateThreshold = 50,
|
|
1446
|
+
enabled = true,
|
|
1447
|
+
initialMode = "preview"
|
|
1448
|
+
}) => {
|
|
1449
|
+
const [route, setRoute] = (0, import_react6.useState)(null);
|
|
1450
|
+
const [totalDistance, setTotalDistance] = (0, import_react6.useState)(null);
|
|
1451
|
+
const [isCalculating, setIsCalculating] = (0, import_react6.useState)(false);
|
|
1452
|
+
const [error, setError] = (0, import_react6.useState)(null);
|
|
1453
|
+
const [mode, setMode] = (0, import_react6.useState)(initialMode);
|
|
1454
|
+
const userLocationState = useUserLocation();
|
|
1455
|
+
const userLocation = manualOrigin || userLocationState.coords;
|
|
1456
|
+
const lastCalcOrigin = (0, import_react6.useRef)(null);
|
|
1457
|
+
const lastCalcDest = (0, import_react6.useRef)(null);
|
|
1458
|
+
const isRequestInProgress = (0, import_react6.useRef)(false);
|
|
1459
|
+
const getDistance = (0, import_react6.useCallback)(
|
|
1460
|
+
(p1, p2) => {
|
|
1461
|
+
const R = 6371e3;
|
|
1462
|
+
const \u03C61 = p1[1] * Math.PI / 180;
|
|
1463
|
+
const \u03C62 = p2[1] * Math.PI / 180;
|
|
1464
|
+
const \u0394\u03C6 = (p2[1] - p1[1]) * Math.PI / 180;
|
|
1465
|
+
const \u0394\u03BB = (p2[0] - p1[0]) * Math.PI / 180;
|
|
1466
|
+
const a = Math.sin(\u0394\u03C6 / 2) * Math.sin(\u0394\u03C6 / 2) + Math.cos(\u03C61) * Math.cos(\u03C62) * Math.sin(\u0394\u03BB / 2) * Math.sin(\u0394\u03BB / 2);
|
|
1467
|
+
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
1468
|
+
return R * c;
|
|
1469
|
+
},
|
|
1470
|
+
[]
|
|
1471
|
+
);
|
|
1472
|
+
const currentDistanceMeters = (0, import_react6.useMemo)(() => {
|
|
1473
|
+
if (!userLocation || !destination) return null;
|
|
1474
|
+
return getDistance(userLocation, destination);
|
|
1475
|
+
}, [userLocation, destination, getDistance]);
|
|
1476
|
+
const calculate = (0, import_react6.useCallback)(
|
|
1477
|
+
async (origin, dest, isNewDest) => {
|
|
1478
|
+
if (isRequestInProgress.current) return;
|
|
1479
|
+
isRequestInProgress.current = true;
|
|
1480
|
+
setIsCalculating(true);
|
|
1481
|
+
try {
|
|
1482
|
+
const data = await sdk.routing.getRoute({
|
|
1483
|
+
destination: dest,
|
|
1484
|
+
origin,
|
|
1485
|
+
profile
|
|
1486
|
+
});
|
|
1487
|
+
setRoute(data);
|
|
1488
|
+
setError(null);
|
|
1489
|
+
if (isNewDest) {
|
|
1490
|
+
setTotalDistance(data.distanceMeters);
|
|
1491
|
+
}
|
|
1492
|
+
lastCalcOrigin.current = origin;
|
|
1493
|
+
lastCalcDest.current = dest;
|
|
1494
|
+
} catch (err) {
|
|
1495
|
+
setError(err.message || "Failed to fetch route");
|
|
1496
|
+
} finally {
|
|
1497
|
+
setIsCalculating(false);
|
|
1498
|
+
isRequestInProgress.current = false;
|
|
1499
|
+
}
|
|
1500
|
+
},
|
|
1501
|
+
[profile]
|
|
1502
|
+
);
|
|
1503
|
+
(0, import_react6.useEffect)(() => {
|
|
1504
|
+
if (!enabled || !destination || !userLocation) {
|
|
1505
|
+
if (!enabled || !destination) {
|
|
1506
|
+
setRoute(null);
|
|
1507
|
+
setTotalDistance(null);
|
|
1508
|
+
lastCalcOrigin.current = null;
|
|
1509
|
+
lastCalcDest.current = null;
|
|
1510
|
+
}
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
const destChanged = !lastCalcDest.current || lastCalcDest.current[0] !== destination[0] || lastCalcDest.current[1] !== destination[1];
|
|
1514
|
+
if (destChanged) {
|
|
1515
|
+
calculate(userLocation, destination, true);
|
|
1516
|
+
} else if (lastCalcOrigin.current) {
|
|
1517
|
+
const dist = getDistance(userLocation, lastCalcOrigin.current);
|
|
1518
|
+
if (dist > recalculateThreshold) {
|
|
1519
|
+
calculate(userLocation, destination, false);
|
|
1520
|
+
}
|
|
1521
|
+
} else {
|
|
1522
|
+
calculate(userLocation, destination, true);
|
|
1523
|
+
}
|
|
1524
|
+
}, [
|
|
1525
|
+
userLocation,
|
|
1526
|
+
destination,
|
|
1527
|
+
enabled,
|
|
1528
|
+
recalculateThreshold,
|
|
1529
|
+
calculate,
|
|
1530
|
+
getDistance
|
|
1531
|
+
]);
|
|
1532
|
+
return {
|
|
1533
|
+
currentDistanceMeters,
|
|
1534
|
+
error,
|
|
1535
|
+
isCalculating,
|
|
1536
|
+
mode,
|
|
1537
|
+
recalculate: () => userLocation && destination && calculate(userLocation, destination, false),
|
|
1538
|
+
route,
|
|
1539
|
+
start: () => setMode("navigation"),
|
|
1540
|
+
totalDistance
|
|
1541
|
+
};
|
|
1542
|
+
};
|
|
1543
|
+
var useRoute = (origin, destination, profile = "driving") => {
|
|
1544
|
+
const [route, setRoute] = (0, import_react6.useState)(null);
|
|
1545
|
+
const [isCalculating, setIsCalculating] = (0, import_react6.useState)(false);
|
|
1546
|
+
const [error, setError] = (0, import_react6.useState)(null);
|
|
1547
|
+
(0, import_react6.useEffect)(() => {
|
|
1548
|
+
if (!origin || !destination) {
|
|
1549
|
+
setRoute(null);
|
|
1550
|
+
return;
|
|
1551
|
+
}
|
|
1552
|
+
const fetchRoute = async () => {
|
|
1553
|
+
setIsCalculating(true);
|
|
1554
|
+
try {
|
|
1555
|
+
const data = await sdk.routing.getRoute({
|
|
1556
|
+
destination,
|
|
1557
|
+
origin,
|
|
1558
|
+
profile
|
|
1559
|
+
});
|
|
1560
|
+
setRoute(data);
|
|
1561
|
+
setError(null);
|
|
1562
|
+
} catch (err) {
|
|
1563
|
+
setError(err.message || "Failed to fetch route");
|
|
1564
|
+
} finally {
|
|
1565
|
+
setIsCalculating(false);
|
|
1566
|
+
}
|
|
1567
|
+
};
|
|
1568
|
+
fetchRoute();
|
|
1569
|
+
}, [origin, destination, profile]);
|
|
1570
|
+
return { error, isCalculating, route };
|
|
1571
|
+
};
|
|
1572
|
+
|
|
1573
|
+
// src/components/Map/hooks/useTrimmedRoute.ts
|
|
1574
|
+
var import_react7 = require("react");
|
|
1575
|
+
var useTrimmedRoute = (route, userLocation) => {
|
|
1576
|
+
return (0, import_react7.useMemo)(() => {
|
|
1577
|
+
if (!route || !userLocation || route.coordinates.length < 2) return route;
|
|
1578
|
+
const coords = route.coordinates;
|
|
1579
|
+
let minDistance = Infinity;
|
|
1580
|
+
let closestIndex = 0;
|
|
1581
|
+
const getDistSq = (p1, p2) => {
|
|
1582
|
+
return (p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2;
|
|
1583
|
+
};
|
|
1584
|
+
for (let i = 0; i < coords.length; i++) {
|
|
1585
|
+
const dist = getDistSq(userLocation, coords[i]);
|
|
1586
|
+
if (dist < minDistance) {
|
|
1587
|
+
minDistance = dist;
|
|
1588
|
+
closestIndex = i;
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
const remainingCoords = coords.slice(closestIndex + 1);
|
|
1592
|
+
return {
|
|
1593
|
+
coordinates: [userLocation, ...remainingCoords],
|
|
1594
|
+
type: "LineString"
|
|
1595
|
+
};
|
|
1596
|
+
}, [route, userLocation]);
|
|
1597
|
+
};
|
|
1598
|
+
|
|
1599
|
+
// src/components/Map/layers/RouteLayer.tsx
|
|
1600
|
+
var import_react_maplibre = require("@vis.gl/react-maplibre");
|
|
1601
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1602
|
+
var RouteLayer = ({
|
|
1603
|
+
geometry,
|
|
1604
|
+
color = "#3b82f6",
|
|
1605
|
+
width = 5,
|
|
1606
|
+
opacity = 0.8
|
|
1607
|
+
}) => {
|
|
1608
|
+
if (!geometry) return null;
|
|
1609
|
+
const data = {
|
|
1610
|
+
geometry,
|
|
1611
|
+
properties: {},
|
|
1612
|
+
type: "Feature"
|
|
1613
|
+
};
|
|
1614
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_maplibre.Source, { data, id: "sdk-route-source", type: "geojson", children: [
|
|
1615
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1616
|
+
import_react_maplibre.Layer,
|
|
1617
|
+
{
|
|
1618
|
+
id: "sdk-route-layer-casing",
|
|
1619
|
+
paint: {
|
|
1620
|
+
"line-blur": 1,
|
|
1621
|
+
"line-color": "#ffffff",
|
|
1622
|
+
"line-opacity": opacity,
|
|
1623
|
+
"line-width": width + 2
|
|
1624
|
+
},
|
|
1625
|
+
type: "line"
|
|
1626
|
+
}
|
|
1627
|
+
),
|
|
1628
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1629
|
+
import_react_maplibre.Layer,
|
|
1630
|
+
{
|
|
1631
|
+
id: "sdk-route-layer",
|
|
1632
|
+
layout: {
|
|
1633
|
+
"line-cap": "round",
|
|
1634
|
+
"line-join": "round"
|
|
1635
|
+
},
|
|
1636
|
+
paint: {
|
|
1637
|
+
"line-color": color,
|
|
1638
|
+
"line-opacity": opacity,
|
|
1639
|
+
"line-width": width
|
|
1640
|
+
},
|
|
1641
|
+
type: "line"
|
|
1642
|
+
}
|
|
1643
|
+
)
|
|
1644
|
+
] });
|
|
1645
|
+
};
|
|
1646
|
+
|
|
1647
|
+
// src/components/Map/Map.tsx
|
|
1648
|
+
var import_react_maplibre5 = require("@vis.gl/react-maplibre");
|
|
1649
|
+
var import_react9 = __toESM(require("react"), 1);
|
|
1650
|
+
var import_maplibre_gl = require("maplibre-gl/dist/maplibre-gl.css");
|
|
1651
|
+
|
|
1652
|
+
// src/components/Map/layers/POILayers.tsx
|
|
1653
|
+
var import_react_maplibre2 = require("@vis.gl/react-maplibre");
|
|
1654
|
+
var import_react8 = require("react");
|
|
1655
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1656
|
+
function createEmojiIcon(emoji, size = 64) {
|
|
1657
|
+
const canvas = document.createElement("canvas");
|
|
1658
|
+
canvas.width = size;
|
|
1659
|
+
canvas.height = size;
|
|
1660
|
+
const ctx = canvas.getContext("2d");
|
|
1661
|
+
ctx.font = `${size * 0.75}px serif`;
|
|
1662
|
+
ctx.textAlign = "center";
|
|
1663
|
+
ctx.textBaseline = "middle";
|
|
1664
|
+
ctx.fillText(emoji, size / 2, size / 2);
|
|
1665
|
+
return ctx.getImageData(0, 0, size, size);
|
|
1666
|
+
}
|
|
1667
|
+
var POILayers = ({ pois }) => {
|
|
1668
|
+
const [loadedImages, setLoadedImages] = (0, import_react8.useState)(0);
|
|
1669
|
+
const { current: map } = (0, import_react_maplibre2.useMap)();
|
|
1670
|
+
const glPois = (0, import_react8.useMemo)(
|
|
1671
|
+
() => pois.filter((p) => !p.displayMode || p.displayMode === "gl"),
|
|
1672
|
+
[pois]
|
|
1673
|
+
);
|
|
1674
|
+
(0, import_react8.useEffect)(() => {
|
|
1675
|
+
if (!map) return;
|
|
1676
|
+
const glMap = map.getMap();
|
|
1677
|
+
const defaults = [
|
|
1678
|
+
{ emoji: "\u{1F37A}", name: "sdk-default-bar" },
|
|
1679
|
+
{ emoji: "\u{1F37D}\uFE0F", name: "sdk-default-restaurant" },
|
|
1680
|
+
{ emoji: "\u{1F4CD}", name: "sdk-default-poi" }
|
|
1681
|
+
];
|
|
1682
|
+
defaults.forEach((def) => {
|
|
1683
|
+
if (!glMap.hasImage(def.name)) {
|
|
1684
|
+
glMap.addImage(def.name, createEmojiIcon(def.emoji), { pixelRatio: 2 });
|
|
1685
|
+
}
|
|
1686
|
+
});
|
|
1687
|
+
let updates = 0;
|
|
1688
|
+
glPois.forEach((poi) => {
|
|
1689
|
+
if (poi.imageUrl) {
|
|
1690
|
+
const name = `custom-url-${poi.id}`;
|
|
1691
|
+
if (!glMap.hasImage(name)) {
|
|
1692
|
+
glMap.loadImage(poi.imageUrl, (error, image) => {
|
|
1693
|
+
if (!error && image) {
|
|
1694
|
+
if (!glMap.hasImage(name)) {
|
|
1695
|
+
glMap.addImage(name, image);
|
|
1696
|
+
setLoadedImages((prev) => prev + 1);
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
});
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
if (poi.emoji) {
|
|
1703
|
+
const name = `custom-emoji-${poi.emoji}`;
|
|
1704
|
+
if (!glMap.hasImage(name)) {
|
|
1705
|
+
glMap.addImage(name, createEmojiIcon(poi.emoji), { pixelRatio: 2 });
|
|
1706
|
+
updates++;
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
});
|
|
1710
|
+
if (updates > 0) {
|
|
1711
|
+
setLoadedImages((prev) => prev + updates);
|
|
1712
|
+
}
|
|
1713
|
+
}, [map, glPois]);
|
|
1714
|
+
const geojson = (0, import_react8.useMemo)(
|
|
1715
|
+
() => ({
|
|
1716
|
+
features: glPois.map((poi) => ({
|
|
1717
|
+
geometry: {
|
|
1718
|
+
coordinates: [poi.longitude, poi.latitude],
|
|
1719
|
+
type: "Point"
|
|
1720
|
+
},
|
|
1721
|
+
properties: {
|
|
1722
|
+
...poi,
|
|
1723
|
+
// Selection logic for the icon in GL
|
|
1724
|
+
"sdk-icon": poi.imageUrl ? `custom-url-${poi.id}` : poi.spriteName ? poi.spriteName : poi.emoji ? `custom-emoji-${poi.emoji}` : poi.type === "bar" ? "sdk-default-bar" : poi.type === "restaurant" ? "sdk-default-restaurant" : "sdk-default-poi"
|
|
1725
|
+
},
|
|
1726
|
+
type: "Feature"
|
|
1727
|
+
})),
|
|
1728
|
+
type: "FeatureCollection"
|
|
1729
|
+
}),
|
|
1730
|
+
[glPois]
|
|
1731
|
+
);
|
|
1732
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_maplibre2.Source, { data: geojson, id: "sdk-pois", type: "geojson", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1733
|
+
import_react_maplibre2.Layer,
|
|
1734
|
+
{
|
|
1735
|
+
id: "sdk-poi-layer",
|
|
1736
|
+
layout: {
|
|
1737
|
+
"icon-allow-overlap": true,
|
|
1738
|
+
"icon-image": ["get", "sdk-icon"],
|
|
1739
|
+
"icon-size": 1,
|
|
1740
|
+
"text-anchor": "top",
|
|
1741
|
+
"text-field": [
|
|
1742
|
+
"step",
|
|
1743
|
+
["zoom"],
|
|
1744
|
+
"",
|
|
1745
|
+
// Show nothing when zoomed out
|
|
1746
|
+
14,
|
|
1747
|
+
["get", "name"]
|
|
1748
|
+
// Show name when zoomed in
|
|
1749
|
+
],
|
|
1750
|
+
"text-font": ["Open Sans Bold"],
|
|
1751
|
+
"text-offset": [0, 1.5],
|
|
1752
|
+
"text-optional": true,
|
|
1753
|
+
"text-size": 11
|
|
1754
|
+
},
|
|
1755
|
+
paint: {
|
|
1756
|
+
"icon-opacity": ["interpolate", ["linear"], ["zoom"], 11, 0, 12, 1],
|
|
1757
|
+
"text-color": "#ffffff",
|
|
1758
|
+
"text-halo-color": "#000000",
|
|
1759
|
+
"text-halo-width": 1,
|
|
1760
|
+
"text-opacity": ["interpolate", ["linear"], ["zoom"], 13.5, 0, 14, 1]
|
|
1761
|
+
},
|
|
1762
|
+
type: "symbol"
|
|
1763
|
+
}
|
|
1764
|
+
) });
|
|
1765
|
+
};
|
|
1766
|
+
|
|
1767
|
+
// src/components/Map/layers/POIMarkers.tsx
|
|
1768
|
+
var import_react_maplibre3 = require("@vis.gl/react-maplibre");
|
|
1769
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
1770
|
+
var POIMarkers = ({ pois, onClick }) => {
|
|
1771
|
+
const markerPois = pois.filter((p) => p.displayMode === "marker");
|
|
1772
|
+
if (markerPois.length === 0) return null;
|
|
1773
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_jsx_runtime9.Fragment, { children: markerPois.map((poi) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1774
|
+
import_react_maplibre3.Marker,
|
|
1775
|
+
{
|
|
1776
|
+
anchor: "bottom",
|
|
1777
|
+
latitude: poi.latitude,
|
|
1778
|
+
longitude: poi.longitude,
|
|
1779
|
+
onClick: (e) => {
|
|
1780
|
+
e.originalEvent.stopPropagation();
|
|
1781
|
+
if (onClick) onClick(poi);
|
|
1782
|
+
},
|
|
1783
|
+
children: poi.markerComponent ? poi.markerComponent : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
1784
|
+
"div",
|
|
1785
|
+
{
|
|
1786
|
+
style: {
|
|
1787
|
+
cursor: "pointer",
|
|
1788
|
+
filter: "drop-shadow(0 4px 6px rgba(0,0,0,0.1))",
|
|
1789
|
+
transform: "transition-transform duration-200 hover:scale-110"
|
|
1790
|
+
},
|
|
1791
|
+
children: [
|
|
1792
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1793
|
+
"div",
|
|
1794
|
+
{
|
|
1795
|
+
style: {
|
|
1796
|
+
alignItems: "center",
|
|
1797
|
+
backgroundColor: poi.type === "bar" ? "#f97316" : "#10b981",
|
|
1798
|
+
border: "2px solid white",
|
|
1799
|
+
borderRadius: "50%",
|
|
1800
|
+
display: "flex",
|
|
1801
|
+
height: "32px",
|
|
1802
|
+
justifyContent: "center",
|
|
1803
|
+
width: "32px"
|
|
1804
|
+
},
|
|
1805
|
+
children: poi.emoji ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { fontSize: "16px" }, children: poi.emoji }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1806
|
+
"div",
|
|
1807
|
+
{
|
|
1808
|
+
style: {
|
|
1809
|
+
backgroundColor: "white",
|
|
1810
|
+
borderRadius: "50%",
|
|
1811
|
+
height: "8px",
|
|
1812
|
+
width: "8px"
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
)
|
|
1816
|
+
}
|
|
1817
|
+
),
|
|
1818
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1819
|
+
"div",
|
|
1820
|
+
{
|
|
1821
|
+
style: {
|
|
1822
|
+
borderLeft: "6px solid transparent",
|
|
1823
|
+
borderRight: "6px solid transparent",
|
|
1824
|
+
borderTop: `8px solid ${poi.type === "bar" ? "#f97316" : "#10b981"}`,
|
|
1825
|
+
height: 0,
|
|
1826
|
+
margin: "-2px auto 0",
|
|
1827
|
+
width: 0
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
)
|
|
1831
|
+
]
|
|
1832
|
+
}
|
|
1833
|
+
)
|
|
1834
|
+
},
|
|
1835
|
+
poi.id
|
|
1836
|
+
)) });
|
|
1837
|
+
};
|
|
1838
|
+
|
|
1839
|
+
// src/components/Map/layers/UserLocationLayer.tsx
|
|
1840
|
+
var import_react_maplibre4 = require("@vis.gl/react-maplibre");
|
|
1841
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
1842
|
+
var UserLocationLayer = ({
|
|
1843
|
+
forcedLocation,
|
|
1844
|
+
heading
|
|
1845
|
+
}) => {
|
|
1846
|
+
if (!forcedLocation) return null;
|
|
1847
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1848
|
+
import_react_maplibre4.Marker,
|
|
1849
|
+
{
|
|
1850
|
+
anchor: "center",
|
|
1851
|
+
latitude: forcedLocation[1],
|
|
1852
|
+
longitude: forcedLocation[0],
|
|
1853
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
1854
|
+
"div",
|
|
1855
|
+
{
|
|
1856
|
+
style: {
|
|
1857
|
+
alignItems: "center",
|
|
1858
|
+
display: "flex",
|
|
1859
|
+
justifyContent: "center",
|
|
1860
|
+
position: "relative"
|
|
1861
|
+
},
|
|
1862
|
+
children: [
|
|
1863
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1864
|
+
"div",
|
|
1865
|
+
{
|
|
1866
|
+
style: {
|
|
1867
|
+
animation: "pulse 2s infinite",
|
|
1868
|
+
backgroundColor: "rgba(59, 130, 246, 0.4)",
|
|
1869
|
+
borderRadius: "50%",
|
|
1870
|
+
height: "40px",
|
|
1871
|
+
position: "absolute",
|
|
1872
|
+
width: "40px"
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
),
|
|
1876
|
+
heading !== void 0 && heading !== null && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1877
|
+
"div",
|
|
1878
|
+
{
|
|
1879
|
+
style: {
|
|
1880
|
+
height: "60px",
|
|
1881
|
+
position: "absolute",
|
|
1882
|
+
transform: `rotate(${heading}deg)`,
|
|
1883
|
+
width: "60px",
|
|
1884
|
+
zIndex: 0
|
|
1885
|
+
},
|
|
1886
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
1887
|
+
"svg",
|
|
1888
|
+
{
|
|
1889
|
+
style: { height: "100%", width: "100%" },
|
|
1890
|
+
viewBox: "0 0 100 100",
|
|
1891
|
+
children: [
|
|
1892
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
1893
|
+
"radialGradient",
|
|
1894
|
+
{
|
|
1895
|
+
cx: "50%",
|
|
1896
|
+
cy: "50%",
|
|
1897
|
+
fx: "50%",
|
|
1898
|
+
fy: "50%",
|
|
1899
|
+
id: "grad1",
|
|
1900
|
+
r: "50%",
|
|
1901
|
+
children: [
|
|
1902
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1903
|
+
"stop",
|
|
1904
|
+
{
|
|
1905
|
+
offset: "0%",
|
|
1906
|
+
style: {
|
|
1907
|
+
stopColor: "rgba(37, 99, 235, 0.6)",
|
|
1908
|
+
stopOpacity: 1
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
),
|
|
1912
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1913
|
+
"stop",
|
|
1914
|
+
{
|
|
1915
|
+
offset: "100%",
|
|
1916
|
+
style: {
|
|
1917
|
+
stopColor: "rgba(37, 99, 235, 0)",
|
|
1918
|
+
stopOpacity: 0
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
)
|
|
1922
|
+
]
|
|
1923
|
+
}
|
|
1924
|
+
) }),
|
|
1925
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1926
|
+
"path",
|
|
1927
|
+
{
|
|
1928
|
+
d: "M 50 50 L 35 0 A 50 50 0 0 1 65 0 Z",
|
|
1929
|
+
fill: "url(#grad1)"
|
|
1930
|
+
}
|
|
1931
|
+
)
|
|
1932
|
+
]
|
|
1933
|
+
}
|
|
1934
|
+
)
|
|
1935
|
+
}
|
|
1936
|
+
),
|
|
1937
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1938
|
+
"div",
|
|
1939
|
+
{
|
|
1940
|
+
style: {
|
|
1941
|
+
alignItems: "center",
|
|
1942
|
+
backgroundColor: "white",
|
|
1943
|
+
borderRadius: "50%",
|
|
1944
|
+
boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 0 0 2px rgba(37, 99, 235, 0.2)",
|
|
1945
|
+
display: "flex",
|
|
1946
|
+
height: "20px",
|
|
1947
|
+
justifyContent: "center",
|
|
1948
|
+
width: "20px",
|
|
1949
|
+
zIndex: 1
|
|
1950
|
+
},
|
|
1951
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1952
|
+
"div",
|
|
1953
|
+
{
|
|
1954
|
+
style: {
|
|
1955
|
+
backgroundColor: "#2563eb",
|
|
1956
|
+
border: "2px solid white",
|
|
1957
|
+
borderRadius: "50%",
|
|
1958
|
+
height: "12px",
|
|
1959
|
+
width: "12px"
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
)
|
|
1963
|
+
}
|
|
1964
|
+
),
|
|
1965
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("style", { children: `@keyframes pulse { 0% { transform: scale(0.95); opacity: 0.7; } 70% { transform: scale(1.5); opacity: 0; } 100% { transform: scale(0.95); opacity: 0; } }` })
|
|
1966
|
+
]
|
|
1967
|
+
}
|
|
1968
|
+
)
|
|
1969
|
+
}
|
|
1970
|
+
);
|
|
1971
|
+
};
|
|
1972
|
+
|
|
1973
|
+
// src/components/Map/Map.tsx
|
|
1974
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
1975
|
+
var MapInternal = ({
|
|
1976
|
+
initialViewState = {
|
|
1977
|
+
latitude: 45.90583599233422,
|
|
1978
|
+
longitude: 6.12589018919138,
|
|
1979
|
+
zoom: 12
|
|
1980
|
+
},
|
|
1981
|
+
style = {},
|
|
1982
|
+
mapStyle = "https://tiles.cloudparse.fr/style/up_dark_matter",
|
|
1983
|
+
className = "",
|
|
1984
|
+
maxBounds,
|
|
1985
|
+
children,
|
|
1986
|
+
onClick,
|
|
1987
|
+
onMapClickMiss,
|
|
1988
|
+
onContextMenu,
|
|
1989
|
+
interactiveLayerIds = [],
|
|
1990
|
+
onLoad,
|
|
1991
|
+
showUserLocation = true,
|
|
1992
|
+
followUser: initialFollowUser = false,
|
|
1993
|
+
showUserLocationButton = true,
|
|
1994
|
+
renderUserLocation,
|
|
1995
|
+
pois = [],
|
|
1996
|
+
onPoiClick,
|
|
1997
|
+
route: externalRoute,
|
|
1998
|
+
showNavigationUI: externalShowNavigationUI,
|
|
1999
|
+
navigationData: externalNavigationData,
|
|
2000
|
+
onNavigationAbort,
|
|
2001
|
+
onNavigationModeChange,
|
|
2002
|
+
navigation,
|
|
2003
|
+
showControls = true,
|
|
2004
|
+
menuItems,
|
|
2005
|
+
menuContent,
|
|
2006
|
+
drawerState: propDrawerState,
|
|
2007
|
+
drawerContentRef,
|
|
2008
|
+
controlProps = {},
|
|
2009
|
+
enableTouchDrag = false
|
|
2010
|
+
}) => {
|
|
2011
|
+
const mapControls = (0, import_react9.useContext)(MapControlsContext);
|
|
2012
|
+
const mapRef = (0, import_react9.useRef)(null);
|
|
2013
|
+
const userLocationState = useUserLocation();
|
|
2014
|
+
const [progNav, setProgNav] = (0, import_react9.useState)(null);
|
|
2015
|
+
(0, import_react9.useEffect)(() => {
|
|
2016
|
+
const unsubCreate = sdk.on("SDK_NAVIGATION_CREATE", (payload) => {
|
|
2017
|
+
setProgNav(payload);
|
|
2018
|
+
});
|
|
2019
|
+
const unsubStart = sdk.on("SDK_NAVIGATION_START", () => {
|
|
2020
|
+
setProgNav((prev) => prev ? { ...prev, mode: "navigation" } : null);
|
|
2021
|
+
});
|
|
2022
|
+
const unsubAbort = sdk.on("SDK_NAVIGATION_ABORT", () => {
|
|
2023
|
+
setProgNav(null);
|
|
2024
|
+
});
|
|
2025
|
+
return () => {
|
|
2026
|
+
unsubCreate();
|
|
2027
|
+
unsubStart();
|
|
2028
|
+
unsubAbort();
|
|
2029
|
+
};
|
|
2030
|
+
}, []);
|
|
2031
|
+
const effectiveNavOptions = progNav || navigation;
|
|
2032
|
+
const internalNav = useLiveRouting({
|
|
2033
|
+
destination: effectiveNavOptions?.destination || null,
|
|
2034
|
+
enabled: !!effectiveNavOptions?.destination,
|
|
2035
|
+
initialMode: effectiveNavOptions?.mode || "preview",
|
|
2036
|
+
origin: effectiveNavOptions?.origin,
|
|
2037
|
+
profile: effectiveNavOptions?.profile || "driving",
|
|
2038
|
+
recalculateThreshold: effectiveNavOptions?.recalculateThreshold || 50
|
|
2039
|
+
});
|
|
2040
|
+
(0, import_react9.useEffect)(() => {
|
|
2041
|
+
if (onNavigationModeChange && navigation) {
|
|
2042
|
+
onNavigationModeChange(internalNav.mode);
|
|
2043
|
+
}
|
|
2044
|
+
}, [internalNav.mode, onNavigationModeChange, navigation]);
|
|
2045
|
+
const navigationData = effectiveNavOptions ? internalNav : externalNavigationData;
|
|
2046
|
+
const route = effectiveNavOptions ? internalNav.route?.geometry : externalRoute;
|
|
2047
|
+
const showNavigationUI = effectiveNavOptions?.showUI !== false && (effectiveNavOptions ? !!effectiveNavOptions.destination : externalShowNavigationUI);
|
|
2048
|
+
const mode = navigationData?.mode || "preview";
|
|
2049
|
+
const resolvedInitialFollow = mapControls ? mapControls.isFollowing : initialFollowUser;
|
|
2050
|
+
const isFollowingRef = (0, import_react9.useRef)(resolvedInitialFollow);
|
|
2051
|
+
const isFlyingRef = (0, import_react9.useRef)(false);
|
|
2052
|
+
const [localIsFollowingUI, setLocalIsFollowingUI] = (0, import_react9.useState)(
|
|
2053
|
+
resolvedInitialFollow
|
|
2054
|
+
);
|
|
2055
|
+
const isFollowingUI = mapControls ? mapControls.isFollowing : localIsFollowingUI;
|
|
2056
|
+
const setIsFollowingCtx = mapControls?.setIsFollowing;
|
|
2057
|
+
const setFollowMode = (0, import_react9.useCallback)(
|
|
2058
|
+
(follow) => {
|
|
2059
|
+
isFollowingRef.current = follow;
|
|
2060
|
+
setLocalIsFollowingUI(follow);
|
|
2061
|
+
if (setIsFollowingCtx) {
|
|
2062
|
+
setIsFollowingCtx(follow);
|
|
2063
|
+
}
|
|
2064
|
+
},
|
|
2065
|
+
[setIsFollowingCtx]
|
|
2066
|
+
);
|
|
2067
|
+
(0, import_react9.useEffect)(() => {
|
|
2068
|
+
if (mapControls && mapControls.isFollowing !== isFollowingRef.current) {
|
|
2069
|
+
isFollowingRef.current = mapControls.isFollowing;
|
|
2070
|
+
}
|
|
2071
|
+
}, [mapControls?.isFollowing, mapControls]);
|
|
2072
|
+
const mergedInteractiveLayerIds = [...interactiveLayerIds];
|
|
2073
|
+
if (pois.length > 0 && !mergedInteractiveLayerIds.includes("sdk-poi-layer")) {
|
|
2074
|
+
mergedInteractiveLayerIds.push("sdk-poi-layer");
|
|
2075
|
+
}
|
|
2076
|
+
const prevInitialFollow = (0, import_react9.useRef)(initialFollowUser);
|
|
2077
|
+
(0, import_react9.useEffect)(() => {
|
|
2078
|
+
if (initialFollowUser !== prevInitialFollow.current) {
|
|
2079
|
+
setFollowMode(initialFollowUser);
|
|
2080
|
+
prevInitialFollow.current = initialFollowUser;
|
|
2081
|
+
if (initialFollowUser && userLocationState.coords && mapRef.current) {
|
|
2082
|
+
isFlyingRef.current = true;
|
|
2083
|
+
mapRef.current.flyTo({
|
|
2084
|
+
center: userLocationState.coords,
|
|
2085
|
+
duration: 1e3,
|
|
2086
|
+
essential: true,
|
|
2087
|
+
zoom: Math.max(mapRef.current.getZoom(), 16)
|
|
2088
|
+
});
|
|
2089
|
+
setTimeout(() => {
|
|
2090
|
+
isFlyingRef.current = false;
|
|
2091
|
+
}, 1050);
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
}, [initialFollowUser, setFollowMode, userLocationState.coords]);
|
|
2095
|
+
const lastAutoLockedRouteRef = (0, import_react9.useRef)(null);
|
|
2096
|
+
(0, import_react9.useEffect)(() => {
|
|
2097
|
+
if (!route || !mapRef.current) {
|
|
2098
|
+
if (!route) lastAutoLockedRouteRef.current = null;
|
|
2099
|
+
return;
|
|
2100
|
+
}
|
|
2101
|
+
const isNewRoute = route !== lastAutoLockedRouteRef.current;
|
|
2102
|
+
if (!isNewRoute && mode === "navigation") return;
|
|
2103
|
+
if (mode === "navigation") {
|
|
2104
|
+
setFollowMode(true);
|
|
2105
|
+
} else if (mode === "preview" && isNewRoute) {
|
|
2106
|
+
setFollowMode(false);
|
|
2107
|
+
const coords = route.coordinates;
|
|
2108
|
+
if (coords.length > 0) {
|
|
2109
|
+
const bounds = coords.reduce(
|
|
2110
|
+
(acc, coord) => {
|
|
2111
|
+
return [
|
|
2112
|
+
[Math.min(acc[0][0], coord[0]), Math.min(acc[0][1], coord[1])],
|
|
2113
|
+
[Math.max(acc[1][0], coord[0]), Math.max(acc[1][1], coord[1])]
|
|
2114
|
+
];
|
|
2115
|
+
},
|
|
2116
|
+
[
|
|
2117
|
+
[coords[0][0], coords[0][1]],
|
|
2118
|
+
[coords[0][0], coords[0][1]]
|
|
2119
|
+
]
|
|
2120
|
+
);
|
|
2121
|
+
mapRef.current.fitBounds(
|
|
2122
|
+
[bounds[0][0], bounds[0][1], bounds[1][0], bounds[1][1]],
|
|
2123
|
+
{ padding: 100, speed: 1.2 }
|
|
2124
|
+
);
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
lastAutoLockedRouteRef.current = route;
|
|
2128
|
+
}, [route, mode, setFollowMode]);
|
|
2129
|
+
(0, import_react9.useEffect)(() => {
|
|
2130
|
+
if (isFollowingRef.current && !isFlyingRef.current && userLocationState.coords && mapRef.current) {
|
|
2131
|
+
mapRef.current.easeTo({
|
|
2132
|
+
center: userLocationState.coords,
|
|
2133
|
+
duration: 50,
|
|
2134
|
+
// Ultra-short for high-frequency interpolated stream
|
|
2135
|
+
easing: (t) => t,
|
|
2136
|
+
essential: true
|
|
2137
|
+
});
|
|
2138
|
+
}
|
|
2139
|
+
}, [userLocationState.coords]);
|
|
2140
|
+
const displayRoute = useTrimmedRoute(
|
|
2141
|
+
route,
|
|
2142
|
+
showUserLocation ? userLocationState.coords : null
|
|
2143
|
+
);
|
|
2144
|
+
const remainingMeters = navigationData?.currentDistanceMeters ?? navigationData?.route?.distanceMeters ?? 0;
|
|
2145
|
+
const etaMins = navigationData?.route?.durationSeconds ? Math.ceil(navigationData.route.durationSeconds / 60) : 0;
|
|
2146
|
+
const kmRemaining = remainingMeters / 1e3;
|
|
2147
|
+
const totalDist = navigationData?.totalDistance ?? 0;
|
|
2148
|
+
const progressValue = totalDist > 0 ? (totalDist - remainingMeters) / totalDist * 100 : 0;
|
|
2149
|
+
const showEtaData = (0, import_react9.useMemo)(() => {
|
|
2150
|
+
if (!navigationData || !route) return void 0;
|
|
2151
|
+
return {
|
|
2152
|
+
distanceRemainingKm: kmRemaining,
|
|
2153
|
+
etaMins,
|
|
2154
|
+
label: mode === "navigation" ? "Walking Guidance Active" : "Route Preview",
|
|
2155
|
+
mode,
|
|
2156
|
+
onAbort: onNavigationAbort || (() => {
|
|
2157
|
+
setProgNav(null);
|
|
2158
|
+
}),
|
|
2159
|
+
onStart: navigationData.start,
|
|
2160
|
+
progress: progressValue
|
|
2161
|
+
};
|
|
2162
|
+
}, [
|
|
2163
|
+
navigationData,
|
|
2164
|
+
route,
|
|
2165
|
+
etaMins,
|
|
2166
|
+
kmRemaining,
|
|
2167
|
+
mode,
|
|
2168
|
+
progressValue,
|
|
2169
|
+
onNavigationAbort
|
|
2170
|
+
]);
|
|
2171
|
+
const setEtaDataCtx = mapControls?.setEtaData;
|
|
2172
|
+
(0, import_react9.useEffect)(() => {
|
|
2173
|
+
if (setEtaDataCtx) {
|
|
2174
|
+
setEtaDataCtx(showEtaData);
|
|
2175
|
+
}
|
|
2176
|
+
}, [showEtaData, setEtaDataCtx]);
|
|
2177
|
+
const setDrawerStateCtx = mapControls?.setDrawerState;
|
|
2178
|
+
(0, import_react9.useEffect)(() => {
|
|
2179
|
+
if (setDrawerStateCtx && propDrawerState !== void 0) {
|
|
2180
|
+
setDrawerStateCtx(propDrawerState);
|
|
2181
|
+
}
|
|
2182
|
+
}, [propDrawerState, setDrawerStateCtx]);
|
|
2183
|
+
const defaultMenuItems = (0, import_react9.useMemo)(
|
|
2184
|
+
() => [
|
|
2185
|
+
{
|
|
2186
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2187
|
+
"svg",
|
|
2188
|
+
{
|
|
2189
|
+
className: "w-5 h-5",
|
|
2190
|
+
fill: "none",
|
|
2191
|
+
stroke: "currentColor",
|
|
2192
|
+
strokeWidth: "2",
|
|
2193
|
+
viewBox: "0 0 24 24",
|
|
2194
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2195
|
+
"path",
|
|
2196
|
+
{
|
|
2197
|
+
d: "M12 11c0 3.517-1.009 6.799-2.753 9.571m-3.44-2.04l.054-.09A13.916 13.916 0 008 11a4 4 0 118 0c0 1.017-.07 2.019-.203 3m-2.118 6.844A21.88 21.88 0 0015.171 17m3.839 1.132c.645-2.266.99-4.659.99-7.132A8 8 0 008 4.07M3 15.364c.64-1.319 1-2.8 1-4.364 0-1.457.39-2.823 1.07-4",
|
|
2198
|
+
strokeLinecap: "round",
|
|
2199
|
+
strokeLinejoin: "round"
|
|
2200
|
+
}
|
|
2201
|
+
)
|
|
2202
|
+
}
|
|
2203
|
+
),
|
|
2204
|
+
id: "walking",
|
|
2205
|
+
label: "Walking Mode",
|
|
2206
|
+
onPress: () => {
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
],
|
|
2210
|
+
[]
|
|
2211
|
+
);
|
|
2212
|
+
const resolvedMenuItems = menuItems || defaultMenuItems;
|
|
2213
|
+
const { mapLibreChildren, menuContentFromChildren } = (0, import_react9.useMemo)(() => {
|
|
2214
|
+
const mapLibreChildren2 = [];
|
|
2215
|
+
const extractedMenuItems = [];
|
|
2216
|
+
import_react9.default.Children.forEach(children, (child) => {
|
|
2217
|
+
if (import_react9.default.isValidElement(child)) {
|
|
2218
|
+
const type = child.type;
|
|
2219
|
+
const displayName = type?.displayName || type?.name;
|
|
2220
|
+
const isMenu = type === MapMenu || displayName === "MapMenu";
|
|
2221
|
+
const isMenuComponent = isMenu || type === MapMenuItem || type === MapMenuTitle || type === MapMenuDivider || type === MapMenuFooter || [
|
|
2222
|
+
"MapMenuItem",
|
|
2223
|
+
"MapMenuTitle",
|
|
2224
|
+
"MapMenuDivider",
|
|
2225
|
+
"MapMenuFooter"
|
|
2226
|
+
].includes(displayName);
|
|
2227
|
+
if (isMenu) {
|
|
2228
|
+
import_react9.default.Children.forEach(
|
|
2229
|
+
child.props.children,
|
|
2230
|
+
(nestedChild) => {
|
|
2231
|
+
if (nestedChild) {
|
|
2232
|
+
extractedMenuItems.push(nestedChild);
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
);
|
|
2236
|
+
} else if (isMenuComponent) {
|
|
2237
|
+
extractedMenuItems.push(child);
|
|
2238
|
+
} else {
|
|
2239
|
+
mapLibreChildren2.push(child);
|
|
2240
|
+
}
|
|
2241
|
+
} else {
|
|
2242
|
+
mapLibreChildren2.push(child);
|
|
2243
|
+
}
|
|
2244
|
+
});
|
|
2245
|
+
const menuContentFromChildren2 = extractedMenuItems.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MapMenu, { children: extractedMenuItems }) : null;
|
|
2246
|
+
return { mapLibreChildren: mapLibreChildren2, menuContentFromChildren: menuContentFromChildren2 };
|
|
2247
|
+
}, [children]);
|
|
2248
|
+
const handleMapClick = (e) => {
|
|
2249
|
+
const clickedFeatures = e.features || [];
|
|
2250
|
+
if (onPoiClick) {
|
|
2251
|
+
const poiFeature = clickedFeatures.find(
|
|
2252
|
+
(f) => f.layer.id === "sdk-poi-layer"
|
|
2253
|
+
);
|
|
2254
|
+
if (poiFeature) {
|
|
2255
|
+
onPoiClick(poiFeature.properties);
|
|
2256
|
+
return;
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
const hitInteractive = clickedFeatures.some(
|
|
2260
|
+
(f) => mergedInteractiveLayerIds.includes(f.layer.id)
|
|
2261
|
+
);
|
|
2262
|
+
if (!hitInteractive) {
|
|
2263
|
+
if (onMapClickMiss) {
|
|
2264
|
+
onMapClickMiss(e);
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
if (onClick) onClick(e);
|
|
2268
|
+
};
|
|
2269
|
+
const handleCenterOnUser = (0, import_react9.useCallback)(
|
|
2270
|
+
(payload) => {
|
|
2271
|
+
if (userLocationState.coords && mapRef.current) {
|
|
2272
|
+
setFollowMode(true);
|
|
2273
|
+
isFlyingRef.current = true;
|
|
2274
|
+
mapRef.current.flyTo({
|
|
2275
|
+
center: userLocationState.coords,
|
|
2276
|
+
duration: 1e3,
|
|
2277
|
+
essential: true,
|
|
2278
|
+
pitch: payload?.pitch || 0,
|
|
2279
|
+
zoom: Math.max(mapRef.current.getZoom(), 16)
|
|
2280
|
+
});
|
|
2281
|
+
setTimeout(() => {
|
|
2282
|
+
isFlyingRef.current = false;
|
|
2283
|
+
}, 1050);
|
|
2284
|
+
}
|
|
2285
|
+
},
|
|
2286
|
+
[userLocationState.coords, setFollowMode]
|
|
2287
|
+
);
|
|
2288
|
+
const handleResetBearing = (0, import_react9.useCallback)((params) => {
|
|
2289
|
+
if (mapRef.current) {
|
|
2290
|
+
mapRef.current.easeTo({
|
|
2291
|
+
bearing: 0,
|
|
2292
|
+
duration: 500,
|
|
2293
|
+
essential: true,
|
|
2294
|
+
pitch: params?.pitch ?? 0
|
|
2295
|
+
});
|
|
2296
|
+
}
|
|
2297
|
+
}, []);
|
|
2298
|
+
(0, import_react9.useEffect)(() => {
|
|
2299
|
+
if (mapControls) {
|
|
2300
|
+
mapControls._registerCenterCallback(handleCenterOnUser);
|
|
2301
|
+
mapControls._registerResetBearingCallback(handleResetBearing);
|
|
2302
|
+
}
|
|
2303
|
+
}, [mapControls, handleCenterOnUser, handleResetBearing]);
|
|
2304
|
+
const handleMove = (0, import_react9.useCallback)(() => {
|
|
2305
|
+
if (mapRef.current && mapControls) {
|
|
2306
|
+
mapControls.setBearing(mapRef.current.getBearing());
|
|
2307
|
+
mapControls.setPitch(mapRef.current.getPitch());
|
|
2308
|
+
}
|
|
2309
|
+
}, [mapControls]);
|
|
2310
|
+
const handleUserInteraction = () => {
|
|
2311
|
+
if (isFollowingRef.current) {
|
|
2312
|
+
console.log("[SDK:Map] Manual interaction detected. Halting tracking.");
|
|
2313
|
+
setFollowMode(false);
|
|
2314
|
+
if (mapRef.current) {
|
|
2315
|
+
mapRef.current.stop();
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
};
|
|
2319
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
2320
|
+
"div",
|
|
2321
|
+
{
|
|
2322
|
+
className: `w-full h-full overflow-hidden relative ${className}`,
|
|
2323
|
+
style,
|
|
2324
|
+
children: [
|
|
2325
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
2326
|
+
import_react_maplibre5.Map,
|
|
2327
|
+
{
|
|
2328
|
+
attributionControl: false,
|
|
2329
|
+
initialViewState,
|
|
2330
|
+
interactiveLayerIds: mergedInteractiveLayerIds,
|
|
2331
|
+
mapStyle,
|
|
2332
|
+
maxBounds,
|
|
2333
|
+
onClick: handleMapClick,
|
|
2334
|
+
onContextMenu,
|
|
2335
|
+
onDragStart: handleUserInteraction,
|
|
2336
|
+
onLoad: (e) => {
|
|
2337
|
+
if (onLoad) onLoad(e);
|
|
2338
|
+
if (initialFollowUser && userLocationState.coords && mapRef.current) {
|
|
2339
|
+
mapRef.current.jumpTo({
|
|
2340
|
+
center: userLocationState.coords,
|
|
2341
|
+
zoom: 16
|
|
2342
|
+
});
|
|
2343
|
+
}
|
|
2344
|
+
},
|
|
2345
|
+
onMouseDown: handleUserInteraction,
|
|
2346
|
+
onMove: handleMove,
|
|
2347
|
+
onTouchStart: handleUserInteraction,
|
|
2348
|
+
onWheel: handleUserInteraction,
|
|
2349
|
+
ref: mapRef,
|
|
2350
|
+
children: [
|
|
2351
|
+
showUserLocation && userLocationState.coords && (renderUserLocation ? renderUserLocation({
|
|
2352
|
+
coords: userLocationState.coords,
|
|
2353
|
+
heading: userLocationState.heading
|
|
2354
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2355
|
+
UserLocationLayer,
|
|
2356
|
+
{
|
|
2357
|
+
forcedLocation: userLocationState.coords,
|
|
2358
|
+
heading: userLocationState.heading
|
|
2359
|
+
}
|
|
2360
|
+
)),
|
|
2361
|
+
displayRoute && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(RouteLayer, { geometry: displayRoute }),
|
|
2362
|
+
pois.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
|
|
2363
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(POILayers, { pois }),
|
|
2364
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(POIMarkers, { onClick: onPoiClick, pois })
|
|
2365
|
+
] }),
|
|
2366
|
+
mapLibreChildren
|
|
2367
|
+
]
|
|
2368
|
+
}
|
|
2369
|
+
),
|
|
2370
|
+
showControls && mapControls && !mapControls.hasControls && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2371
|
+
MapControls,
|
|
2372
|
+
{
|
|
2373
|
+
drawerContentRef,
|
|
2374
|
+
enableTouchDrag,
|
|
2375
|
+
etaData: showEtaData,
|
|
2376
|
+
initialViewState,
|
|
2377
|
+
isInternal: true,
|
|
2378
|
+
menuContent: menuContent || menuContentFromChildren,
|
|
2379
|
+
menuItems: resolvedMenuItems,
|
|
2380
|
+
...controlProps
|
|
2381
|
+
}
|
|
2382
|
+
)
|
|
2383
|
+
]
|
|
2384
|
+
}
|
|
2385
|
+
);
|
|
2386
|
+
};
|
|
2387
|
+
var SDKMapProviderWrapper = ({
|
|
2388
|
+
children
|
|
2389
|
+
}) => {
|
|
2390
|
+
const context = (0, import_react9.useContext)(MapControlsContext);
|
|
2391
|
+
if (context) {
|
|
2392
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_jsx_runtime11.Fragment, { children });
|
|
2393
|
+
}
|
|
2394
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SDKMapProvider, { children });
|
|
2395
|
+
};
|
|
2396
|
+
var Map2 = (props) => {
|
|
2397
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SDKMapProviderWrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_maplibre5.MapProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MapInternal, { ...props }) }) });
|
|
2398
|
+
};
|
|
2399
|
+
|
|
2400
|
+
// src/components/MapWrapper.tsx
|
|
2401
|
+
var import_react10 = __toESM(require("react"), 1);
|
|
2402
|
+
var import_client = require("react-dom/client");
|
|
2403
|
+
function renderMap(containerId, props = {}) {
|
|
2404
|
+
const container = document.getElementById(containerId);
|
|
2405
|
+
if (!container) {
|
|
2406
|
+
throw new Error(`Container with id "${containerId}" not found.`);
|
|
2407
|
+
}
|
|
2408
|
+
const root = (0, import_client.createRoot)(container);
|
|
2409
|
+
root.render(import_react10.default.createElement(Map2, props));
|
|
2410
|
+
return root;
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
// src/services/routingService.ts
|
|
2414
|
+
var RoutingService = {
|
|
2415
|
+
createErrorResponse(req, code, message) {
|
|
2416
|
+
return {
|
|
2417
|
+
error: { code, message },
|
|
2418
|
+
requestId: req.requestId,
|
|
2419
|
+
sdkVersion: req.sdkVersion,
|
|
2420
|
+
success: false,
|
|
2421
|
+
type: "SDK_ROUTING_GETROUTE_RESPONSE"
|
|
2422
|
+
};
|
|
2423
|
+
},
|
|
2424
|
+
decodePolyline(str, precision) {
|
|
2425
|
+
let index = 0, lat = 0, lng = 0;
|
|
2426
|
+
const coordinates = [];
|
|
2427
|
+
const shift = 10 ** precision;
|
|
2428
|
+
while (index < str.length) {
|
|
2429
|
+
let b, shiftNum = 0, result = 0;
|
|
2430
|
+
do {
|
|
2431
|
+
b = str.charCodeAt(index++) - 63;
|
|
2432
|
+
result |= (b & 31) << shiftNum;
|
|
2433
|
+
shiftNum += 5;
|
|
2434
|
+
} while (b >= 32);
|
|
2435
|
+
const dlat = result & 1 ? ~(result >> 1) : result >> 1;
|
|
2436
|
+
lat += dlat;
|
|
2437
|
+
shiftNum = 0;
|
|
2438
|
+
result = 0;
|
|
2439
|
+
do {
|
|
2440
|
+
b = str.charCodeAt(index++) - 63;
|
|
2441
|
+
result |= (b & 31) << shiftNum;
|
|
2442
|
+
shiftNum += 5;
|
|
2443
|
+
} while (b >= 32);
|
|
2444
|
+
const dlng = result & 1 ? ~(result >> 1) : result >> 1;
|
|
2445
|
+
lng += dlng;
|
|
2446
|
+
coordinates.push([lng / shift, lat / shift]);
|
|
2447
|
+
}
|
|
2448
|
+
return coordinates;
|
|
2449
|
+
},
|
|
2450
|
+
async fetchOSRMRoute(req, coords, profile, baseUrl) {
|
|
2451
|
+
const coordsString = coords.map((c) => `${c[0]},${c[1]}`).join(";");
|
|
2452
|
+
const osrmProfile = profile === "walking" ? "foot" : profile === "driving" ? "car" : "bike";
|
|
2453
|
+
const url = `${baseUrl}/route/v1/${osrmProfile}/${coordsString}?overview=full&geometries=geojson&steps=true`;
|
|
2454
|
+
const response = await fetch(url);
|
|
2455
|
+
if (!response.ok) {
|
|
2456
|
+
throw new Error(`OSRM Error: ${response.statusText}`);
|
|
2457
|
+
}
|
|
2458
|
+
const data = await response.json();
|
|
2459
|
+
if (data.code !== "Ok" || !data.routes || data.routes.length === 0) {
|
|
2460
|
+
throw new Error("NO_ROUTE");
|
|
2461
|
+
}
|
|
2462
|
+
const route = data.routes[0];
|
|
2463
|
+
const steps = route.legs?.flatMap(
|
|
2464
|
+
(leg) => leg.steps?.map((step) => ({
|
|
2465
|
+
distance: step.distance,
|
|
2466
|
+
duration: step.duration,
|
|
2467
|
+
instruction: step.maneuver?.instruction || "Continue"
|
|
2468
|
+
}))
|
|
2469
|
+
) || [];
|
|
2470
|
+
return {
|
|
2471
|
+
data: {
|
|
2472
|
+
distanceMeters: route.distance,
|
|
2473
|
+
durationSeconds: route.duration,
|
|
2474
|
+
geometry: route.geometry,
|
|
2475
|
+
steps
|
|
2476
|
+
},
|
|
2477
|
+
requestId: req.requestId,
|
|
2478
|
+
sdkVersion: req.sdkVersion,
|
|
2479
|
+
success: true,
|
|
2480
|
+
type: "SDK_ROUTING_GETROUTE_RESPONSE"
|
|
2481
|
+
};
|
|
2482
|
+
},
|
|
2483
|
+
async fetchRoute(req, config) {
|
|
2484
|
+
const { origin, destination, waypoints, profile, language } = req.payload;
|
|
2485
|
+
if (!this.isValidCoordinate(origin) || !this.isValidCoordinate(destination)) {
|
|
2486
|
+
return this.createErrorResponse(
|
|
2487
|
+
req,
|
|
2488
|
+
"INVALID_INPUT",
|
|
2489
|
+
"Origin or destination coordinates are invalid."
|
|
2490
|
+
);
|
|
2491
|
+
}
|
|
2492
|
+
if (waypoints && !waypoints.every(this.isValidCoordinate)) {
|
|
2493
|
+
return this.createErrorResponse(
|
|
2494
|
+
req,
|
|
2495
|
+
"INVALID_INPUT",
|
|
2496
|
+
"One or more waypoints are invalid."
|
|
2497
|
+
);
|
|
2498
|
+
}
|
|
2499
|
+
const allCoords = [origin, ...waypoints || [], destination];
|
|
2500
|
+
try {
|
|
2501
|
+
if (config.backend === "osrm") {
|
|
2502
|
+
return await this.fetchOSRMRoute(
|
|
2503
|
+
req,
|
|
2504
|
+
allCoords,
|
|
2505
|
+
profile,
|
|
2506
|
+
config.baseUrl
|
|
2507
|
+
);
|
|
2508
|
+
} else {
|
|
2509
|
+
return await this.fetchValhallaRoute(
|
|
2510
|
+
req,
|
|
2511
|
+
allCoords,
|
|
2512
|
+
profile,
|
|
2513
|
+
language,
|
|
2514
|
+
config.baseUrl
|
|
2515
|
+
);
|
|
2516
|
+
}
|
|
2517
|
+
} catch (error) {
|
|
2518
|
+
if (error instanceof Error && error.message.includes("NO_ROUTE")) {
|
|
2519
|
+
return this.createErrorResponse(
|
|
2520
|
+
req,
|
|
2521
|
+
"NO_ROUTE",
|
|
2522
|
+
"No route could be found for the given points."
|
|
2523
|
+
);
|
|
2524
|
+
}
|
|
2525
|
+
if (error instanceof TypeError) {
|
|
2526
|
+
return this.createErrorResponse(
|
|
2527
|
+
req,
|
|
2528
|
+
"NETWORK_ERROR",
|
|
2529
|
+
"Failed to connect to the routing backend."
|
|
2530
|
+
);
|
|
2531
|
+
}
|
|
2532
|
+
return this.createErrorResponse(
|
|
2533
|
+
req,
|
|
2534
|
+
"BACKEND_ERROR",
|
|
2535
|
+
`Routing backend error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
2536
|
+
);
|
|
2537
|
+
}
|
|
2538
|
+
},
|
|
2539
|
+
async fetchValhallaRoute(req, coords, profile, language, baseUrl) {
|
|
2540
|
+
const valhallaProfile = profile === "driving" ? "auto" : profile === "walking" ? "pedestrian" : "bicycle";
|
|
2541
|
+
const body = {
|
|
2542
|
+
costing: valhallaProfile,
|
|
2543
|
+
directions_options: {
|
|
2544
|
+
language: language || "en-US"
|
|
2545
|
+
},
|
|
2546
|
+
locations: coords.map((c) => ({ lat: c[1], lon: c[0] }))
|
|
2547
|
+
};
|
|
2548
|
+
const response = await fetch(`${baseUrl}/route`, {
|
|
2549
|
+
body: JSON.stringify(body),
|
|
2550
|
+
headers: { "Content-Type": "application/json" },
|
|
2551
|
+
method: "POST"
|
|
2552
|
+
});
|
|
2553
|
+
if (!response.ok) {
|
|
2554
|
+
throw new Error(`Valhalla Error: ${response.statusText}`);
|
|
2555
|
+
}
|
|
2556
|
+
const data = await response.json();
|
|
2557
|
+
if (!data.trip || !data.trip.legs || data.trip.legs.length === 0) {
|
|
2558
|
+
throw new Error("NO_ROUTE");
|
|
2559
|
+
}
|
|
2560
|
+
const trip = data.trip;
|
|
2561
|
+
const decodedCoordinates = this.decodePolyline(trip.legs[0].shape, 6);
|
|
2562
|
+
const steps = trip.legs.flatMap(
|
|
2563
|
+
(leg) => leg.maneuvers?.map((maneuver) => ({
|
|
2564
|
+
distance: maneuver.length * 1e3,
|
|
2565
|
+
// Valhalla returns km
|
|
2566
|
+
duration: maneuver.time,
|
|
2567
|
+
instruction: maneuver.instruction || "Continue"
|
|
2568
|
+
}))
|
|
2569
|
+
) || [];
|
|
2570
|
+
return {
|
|
2571
|
+
data: {
|
|
2572
|
+
distanceMeters: trip.summary.length * 1e3,
|
|
2573
|
+
// km to m
|
|
2574
|
+
durationSeconds: trip.summary.time,
|
|
2575
|
+
geometry: {
|
|
2576
|
+
coordinates: decodedCoordinates,
|
|
2577
|
+
type: "LineString"
|
|
2578
|
+
},
|
|
2579
|
+
steps
|
|
2580
|
+
},
|
|
2581
|
+
requestId: req.requestId,
|
|
2582
|
+
sdkVersion: req.sdkVersion,
|
|
2583
|
+
success: true,
|
|
2584
|
+
type: "SDK_ROUTING_GETROUTE_RESPONSE"
|
|
2585
|
+
};
|
|
2586
|
+
},
|
|
2587
|
+
isValidCoordinate(coord) {
|
|
2588
|
+
const [lng, lat] = coord;
|
|
2589
|
+
return lng >= -180 && lng <= 180 && lat >= -90 && lat <= 90;
|
|
2590
|
+
}
|
|
2591
|
+
};
|
|
2592
|
+
|
|
2593
|
+
// package.json
|
|
2594
|
+
var version = "0.2.0";
|
|
2595
|
+
|
|
2596
|
+
// src/sdk.ts
|
|
2597
|
+
function deepFreeze(obj) {
|
|
2598
|
+
const propNames = Object.getOwnPropertyNames(obj);
|
|
2599
|
+
for (const name of propNames) {
|
|
2600
|
+
const value = obj[name];
|
|
2601
|
+
if (value && typeof value === "object" && !Object.isFrozen(value) && // Skip freezing of native objects or circular refs if any (though not expected here)
|
|
2602
|
+
!(value instanceof Bridge)) {
|
|
2603
|
+
deepFreeze(value);
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
return Object.freeze(obj);
|
|
2607
|
+
}
|
|
2608
|
+
var globalRoutingConfig = null;
|
|
2609
|
+
var MiniAppSDK = class {
|
|
2610
|
+
/**
|
|
2611
|
+
* Map services.
|
|
2612
|
+
* Provides access to MapLibre-based mapping components.
|
|
2613
|
+
*/
|
|
2614
|
+
maps = Object.freeze({
|
|
2615
|
+
/**
|
|
2616
|
+
* Renders a MapLibre map into a DOM element.
|
|
2617
|
+
* Useful for non-React environments.
|
|
2618
|
+
*/
|
|
2619
|
+
render: renderMap
|
|
2620
|
+
});
|
|
2621
|
+
bridge;
|
|
2622
|
+
/**
|
|
2623
|
+
* Local SQLite Storage.
|
|
2624
|
+
* Data is persisted on the device and is isolated per Mini-App.
|
|
2625
|
+
*/
|
|
2626
|
+
storage = Object.freeze({
|
|
2627
|
+
/** Retrieves a value by key. */
|
|
2628
|
+
get: (key) => this.bridge.send("STORAGE_GET", { key }),
|
|
2629
|
+
/** Retrieves all key-value pairs stored for this Mini-App. */
|
|
2630
|
+
getAll: () => this.bridge.send("STORAGE_GET_ALL"),
|
|
2631
|
+
/** Sets a value for a specific key. */
|
|
2632
|
+
set: (key, value) => this.bridge.send("STORAGE_SET", { key, value })
|
|
2633
|
+
});
|
|
2634
|
+
/**
|
|
2635
|
+
* Hardware-level Secure Storage.
|
|
2636
|
+
* Data is stored in the device's secure enclave (Keychain/Keystore) and is isolated per Mini-App.
|
|
2637
|
+
*/
|
|
2638
|
+
secureStore = Object.freeze({
|
|
2639
|
+
/** Retrieves a value by key from secure storage. */
|
|
2640
|
+
get: (key) => this.bridge.send("SECURE_GET", { key }),
|
|
2641
|
+
/** Sets a value for a specific key in secure storage. */
|
|
2642
|
+
set: (key, value) => this.bridge.send("SECURE_SET", { key, value })
|
|
2643
|
+
});
|
|
2644
|
+
/**
|
|
2645
|
+
* Database Storage.
|
|
2646
|
+
* Access to user-app specific persistent database storage.
|
|
2647
|
+
*/
|
|
2648
|
+
database = Object.freeze({
|
|
2649
|
+
/** Retrieves the user data stored in the backend database. */
|
|
2650
|
+
getUserData: () => this.bridge.send("DATABASE_GET_USER_DATA"),
|
|
2651
|
+
/** Updates the user data stored in the backend database. */
|
|
2652
|
+
setUserData: (data) => this.bridge.send("DATABASE_SET_USER_DATA", { data })
|
|
2653
|
+
});
|
|
2654
|
+
/**
|
|
2655
|
+
* Device Location services.
|
|
2656
|
+
*/
|
|
2657
|
+
location = Object.freeze({
|
|
2658
|
+
/**
|
|
2659
|
+
* Retrieves the current GPS position of the device.
|
|
2660
|
+
*/
|
|
2661
|
+
getCurrentPosition: () => this.bridge.send("GET_CURRENT_POSITION"),
|
|
2662
|
+
/**
|
|
2663
|
+
* Starts the continuous location and sensor stream.
|
|
2664
|
+
* @param config Optional configuration for thresholds and intervals.
|
|
2665
|
+
*/
|
|
2666
|
+
startStream: (config) => this.bridge.send("START_LOCATION_STREAM", config),
|
|
2667
|
+
/**
|
|
2668
|
+
* Stops the continuous location and sensor stream.
|
|
2669
|
+
*/
|
|
2670
|
+
stopStream: () => this.bridge.send("STOP_LOCATION_STREAM")
|
|
2671
|
+
});
|
|
2672
|
+
/**
|
|
2673
|
+
* Identity & Profile management.
|
|
2674
|
+
*/
|
|
2675
|
+
user = Object.freeze({
|
|
2676
|
+
/**
|
|
2677
|
+
* Fetch user profile data.
|
|
2678
|
+
* @param fields Optional list of specific fields to retrieve (e.g., ['fullName', 'username']).
|
|
2679
|
+
*/
|
|
2680
|
+
getProfile: (fields) => this.bridge.send("GET_USER_PROFILE", { fields })
|
|
2681
|
+
});
|
|
2682
|
+
/**
|
|
2683
|
+
* Explicit Permission Management.
|
|
2684
|
+
* Allows requesting access to sensitive device features or user data.
|
|
2685
|
+
*/
|
|
2686
|
+
permission = Object.freeze({
|
|
2687
|
+
/**
|
|
2688
|
+
* Explicitly request access to sensitive data.
|
|
2689
|
+
* @param type The type of permission to request ('location' or 'profile').
|
|
2690
|
+
* @param fields If requesting 'profile', the specific fields required.
|
|
2691
|
+
*/
|
|
2692
|
+
request: ((type, fields) => {
|
|
2693
|
+
return this.bridge.send("REQUEST_PERMISSION", { fields, type });
|
|
2694
|
+
})
|
|
2695
|
+
});
|
|
2696
|
+
/**
|
|
2697
|
+
* Payments integration.
|
|
2698
|
+
*/
|
|
2699
|
+
payment = Object.freeze({
|
|
2700
|
+
/**
|
|
2701
|
+
* Triggers the native payment flow.
|
|
2702
|
+
* @param payload Amount, currency, and itemized list for the payment.
|
|
2703
|
+
*/
|
|
2704
|
+
request: (payload) => this.bridge.send("PAYMENT_REQUEST", payload)
|
|
2705
|
+
});
|
|
2706
|
+
/**
|
|
2707
|
+
* Live Activities & Notifications.
|
|
2708
|
+
* Allows the Mini-App to display real-time updates in the host shell.
|
|
2709
|
+
*/
|
|
2710
|
+
activity = Object.freeze({
|
|
2711
|
+
/**
|
|
2712
|
+
* Starts a new live activity.
|
|
2713
|
+
* @param id Unique identifier for the activity.
|
|
2714
|
+
* @param title Display title for the activity.
|
|
2715
|
+
* @param status Initial status message.
|
|
2716
|
+
*/
|
|
2717
|
+
start: (id, title, status) => this.bridge.send("START_ACTIVITY", { id, status, title })
|
|
2718
|
+
});
|
|
2719
|
+
/**
|
|
2720
|
+
* Haptic feedback.
|
|
2721
|
+
*/
|
|
2722
|
+
haptic = Object.freeze({
|
|
2723
|
+
/**
|
|
2724
|
+
* Triggers a haptic vibration on the device.
|
|
2725
|
+
* @param style The style of haptic feedback (e.g., 'light', 'medium', 'heavy', 'success', 'warning', 'error').
|
|
2726
|
+
*/
|
|
2727
|
+
trigger: (style) => this.bridge.send("TRIGGER_HAPTIC", { style })
|
|
2728
|
+
});
|
|
2729
|
+
/**
|
|
2730
|
+
* Network Proxy.
|
|
2731
|
+
* Allows the Mini-App to make network requests through the host shell to bypass CORS or use host-level authentication.
|
|
2732
|
+
*/
|
|
2733
|
+
network = Object.freeze({
|
|
2734
|
+
/**
|
|
2735
|
+
* Performs a network request via the host shell.
|
|
2736
|
+
* @param options Request method, URL, headers, and body.
|
|
2737
|
+
*/
|
|
2738
|
+
request: async (options) => {
|
|
2739
|
+
return this.bridge.send("NETWORK_REQUEST", options);
|
|
2740
|
+
}
|
|
2741
|
+
});
|
|
2742
|
+
/**
|
|
2743
|
+
* Navigation and Journey controls.
|
|
2744
|
+
* Allows programmatically starting itineraries and managing navigation state.
|
|
2745
|
+
*/
|
|
2746
|
+
navigation = Object.freeze({
|
|
2747
|
+
/**
|
|
2748
|
+
* Aborts the current navigation journey.
|
|
2749
|
+
*/
|
|
2750
|
+
abort: () => {
|
|
2751
|
+
this.bridge.handleNativeMessage({
|
|
2752
|
+
eventName: "SDK_NAVIGATION_ABORT",
|
|
2753
|
+
type: "EVENT"
|
|
2754
|
+
});
|
|
2755
|
+
},
|
|
2756
|
+
/**
|
|
2757
|
+
* Creates a new itinerary preview.
|
|
2758
|
+
* @param options Destination and routing options.
|
|
2759
|
+
*/
|
|
2760
|
+
createJourney: (options) => {
|
|
2761
|
+
this.bridge.handleNativeMessage({
|
|
2762
|
+
eventName: "SDK_NAVIGATION_CREATE",
|
|
2763
|
+
payload: { ...options, mode: "preview" },
|
|
2764
|
+
type: "EVENT"
|
|
2765
|
+
});
|
|
2766
|
+
},
|
|
2767
|
+
/**
|
|
2768
|
+
* Starts the active navigation (active guidance).
|
|
2769
|
+
*/
|
|
2770
|
+
start: () => {
|
|
2771
|
+
this.bridge.handleNativeMessage({
|
|
2772
|
+
eventName: "SDK_NAVIGATION_START",
|
|
2773
|
+
type: "EVENT"
|
|
2774
|
+
});
|
|
2775
|
+
}
|
|
2776
|
+
});
|
|
2777
|
+
/**
|
|
2778
|
+
* Fidelity & Rewards program integration.
|
|
2779
|
+
*/
|
|
2780
|
+
fidelity = Object.freeze({
|
|
2781
|
+
/** Returns current fidelity balance. */
|
|
2782
|
+
getFidelityBalance: () => this.bridge.send("GET_FIDELITY_BALANCE"),
|
|
2783
|
+
/**
|
|
2784
|
+
* Retrieves fidelity transaction history.
|
|
2785
|
+
* @param params Filtering and pagination parameters.
|
|
2786
|
+
*/
|
|
2787
|
+
getFidelityHistory: (params) => this.bridge.send("GET_FIDELITY_HISTORY", params),
|
|
2788
|
+
/**
|
|
2789
|
+
* Triggers the native fidelity history view.
|
|
2790
|
+
*/
|
|
2791
|
+
showHistory: () => this.bridge.send("SHOW_FIDELITY_HISTORY"),
|
|
2792
|
+
/**
|
|
2793
|
+
* Authorize spending fidelity points (FPoints) via Two-Phase Authorization.
|
|
2794
|
+
* @param intentUuid UUID generated by your backend for the transaction.
|
|
2795
|
+
*/
|
|
2796
|
+
spendFPoints: (intentUuid) => this.bridge.send("SPEND_FPOINTS", { intentUuid })
|
|
2797
|
+
});
|
|
2798
|
+
/**
|
|
2799
|
+
* UI and Navigation controls.
|
|
2800
|
+
*/
|
|
2801
|
+
ui = Object.freeze({
|
|
2802
|
+
/** Closes the Mini-App and returns to the host shell. */
|
|
2803
|
+
exit: () => this.bridge.send("EXIT"),
|
|
2804
|
+
/**
|
|
2805
|
+
* Configures native scroll behaviors like Pull-to-Refresh and Infinite Scroll.
|
|
2806
|
+
* @param config Scroll configuration for top/bottom detection.
|
|
2807
|
+
*/
|
|
2808
|
+
setScrollConfig: (config) => this.bridge.send("SET_SCROLL_CONFIG", config)
|
|
2809
|
+
});
|
|
2810
|
+
/**
|
|
2811
|
+
* Debug controls
|
|
2812
|
+
*/
|
|
2813
|
+
debug = Object.freeze({
|
|
2814
|
+
/**
|
|
2815
|
+
* Log to the shell app
|
|
2816
|
+
* @param message
|
|
2817
|
+
*/
|
|
2818
|
+
log: (...message) => this.bridge.send("DEBUG_LOG", { message })
|
|
2819
|
+
});
|
|
2820
|
+
/**
|
|
2821
|
+
* Routing integration (OSRM / Valhalla).
|
|
2822
|
+
* Provides access to route calculation and rendering on the host map.
|
|
2823
|
+
*/
|
|
2824
|
+
routing = Object.freeze({
|
|
2825
|
+
/**
|
|
2826
|
+
* Clears the currently rendered route.
|
|
2827
|
+
*/
|
|
2828
|
+
clearRoute: () => {
|
|
2829
|
+
const requestId = crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).substring(2);
|
|
2830
|
+
return this.bridge.send("SDK_ROUTING_CLEARROUTE", {
|
|
2831
|
+
requestId,
|
|
2832
|
+
sdkVersion: version
|
|
2833
|
+
});
|
|
2834
|
+
},
|
|
2835
|
+
/**
|
|
2836
|
+
* Draws the calculated route on the host map.
|
|
2837
|
+
* @param routeData The route data returned by getRoute.
|
|
2838
|
+
*/
|
|
2839
|
+
drawRoute: (routeData) => {
|
|
2840
|
+
const requestId = crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).substring(2);
|
|
2841
|
+
return this.bridge.send("SDK_ROUTING_DRAWROUTE", {
|
|
2842
|
+
payload: { routeData },
|
|
2843
|
+
requestId,
|
|
2844
|
+
sdkVersion: version
|
|
2845
|
+
});
|
|
2846
|
+
},
|
|
2847
|
+
/**
|
|
2848
|
+
* Calculates a route between points.
|
|
2849
|
+
* @param payload Request payload containing origin, destination, and options.
|
|
2850
|
+
*/
|
|
2851
|
+
getRoute: async (payload) => {
|
|
2852
|
+
const requestId = crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).substring(2);
|
|
2853
|
+
const request = {
|
|
2854
|
+
payload,
|
|
2855
|
+
requestId,
|
|
2856
|
+
sdkVersion: version,
|
|
2857
|
+
type: "SDK_ROUTING_GETROUTE"
|
|
2858
|
+
};
|
|
2859
|
+
if (globalRoutingConfig) {
|
|
2860
|
+
const response = await RoutingService.fetchRoute(
|
|
2861
|
+
request,
|
|
2862
|
+
globalRoutingConfig
|
|
2863
|
+
);
|
|
2864
|
+
if (response.success && response.data) {
|
|
2865
|
+
return response.data;
|
|
2866
|
+
}
|
|
2867
|
+
throw new Error(response.error?.message || "Routing failed");
|
|
2868
|
+
}
|
|
2869
|
+
return this.bridge.send("SDK_ROUTING_GETROUTE", {
|
|
2870
|
+
payload,
|
|
2871
|
+
requestId,
|
|
2872
|
+
sdkVersion: version
|
|
2873
|
+
});
|
|
2874
|
+
}
|
|
2875
|
+
});
|
|
2876
|
+
constructor() {
|
|
2877
|
+
this.bridge = new Bridge();
|
|
2878
|
+
this.initialize = this.initialize.bind(this);
|
|
2879
|
+
this.on = this.on.bind(this);
|
|
2880
|
+
this.off = this.off.bind(this);
|
|
2881
|
+
}
|
|
2882
|
+
/**
|
|
2883
|
+
* Configures global SDK settings.
|
|
2884
|
+
*/
|
|
2885
|
+
config(options) {
|
|
2886
|
+
if (options.routingBackend) {
|
|
2887
|
+
globalRoutingConfig = options.routingBackend;
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
/**
|
|
2891
|
+
* Updates the global context shared between the Mini-App and the native shell.
|
|
2892
|
+
* Useful for synchronizing state like theme preferences, UI flags, or custom app-specific context.
|
|
2893
|
+
* @param context Partial context object to merge into the existing shell context.
|
|
2894
|
+
*/
|
|
2895
|
+
updateContext(context) {
|
|
2896
|
+
return this.bridge.send("UPDATE_CONTEXT", context);
|
|
2897
|
+
}
|
|
2898
|
+
/**
|
|
2899
|
+
* The starting point for the Mini-App. Must be called before any other SDK methods.
|
|
2900
|
+
* @param options The options to provide to the shell app
|
|
2901
|
+
*/
|
|
2902
|
+
initialize(options) {
|
|
2903
|
+
return this.bridge.send("INITIALIZE", options);
|
|
2904
|
+
}
|
|
2905
|
+
/**
|
|
2906
|
+
* Registers a callback for a specific event from the native shell.
|
|
2907
|
+
* @param event The name of the event to listen for.
|
|
2908
|
+
* @param callback The function to execute when the event is triggered.
|
|
2909
|
+
* @returns A function to unregister the listener.
|
|
2910
|
+
*/
|
|
2911
|
+
on(event, callback) {
|
|
2912
|
+
return this.bridge.on(event, callback);
|
|
2913
|
+
}
|
|
2914
|
+
/**
|
|
2915
|
+
* Unregisters a previously registered event listener.
|
|
2916
|
+
* @param event The name of the event.
|
|
2917
|
+
* @param callback The callback function to remove.
|
|
2918
|
+
*/
|
|
2919
|
+
off(event, callback) {
|
|
2920
|
+
this.bridge.off(event, callback);
|
|
2921
|
+
}
|
|
2922
|
+
};
|
|
2923
|
+
var sdk = deepFreeze(new MiniAppSDK());
|
|
2924
|
+
|
|
2925
|
+
// src/components/BridgeMocker.tsx
|
|
2926
|
+
var import_react11 = require("react");
|
|
2927
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
2928
|
+
var getBearing = (p1, p2) => {
|
|
2929
|
+
const lat1 = p1[1] * Math.PI / 180;
|
|
2930
|
+
const lat2 = p2[1] * Math.PI / 180;
|
|
2931
|
+
const dLon = (p2[0] - p1[0]) * Math.PI / 180;
|
|
2932
|
+
const y = Math.sin(dLon) * Math.cos(lat2);
|
|
2933
|
+
const x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(dLon);
|
|
2934
|
+
return (Math.atan2(y, x) * 180 / Math.PI + 360) % 360;
|
|
2935
|
+
};
|
|
2936
|
+
var BridgeMocker = ({
|
|
2937
|
+
appId,
|
|
2938
|
+
initialLocation = [6.1258, 45.9058],
|
|
2939
|
+
// Annecy default
|
|
2940
|
+
isMoving = false,
|
|
2941
|
+
simulationPath,
|
|
2942
|
+
simulationSpeed = 1,
|
|
2943
|
+
mockJWTToken,
|
|
2944
|
+
children
|
|
2945
|
+
}) => {
|
|
2946
|
+
const [autoMove, setAutoMove] = (0, import_react11.useState)(isMoving);
|
|
2947
|
+
const [location, setLocation] = (0, import_react11.useState)(initialLocation);
|
|
2948
|
+
const [heading, setHeading] = (0, import_react11.useState)(0);
|
|
2949
|
+
const [speedMultiplier, setSpeedMultiplier] = (0, import_react11.useState)(simulationSpeed);
|
|
2950
|
+
const [isReady, setIsReady] = (0, import_react11.useState)(false);
|
|
2951
|
+
const [isMinimized, setIsMinimized] = (0, import_react11.useState)(true);
|
|
2952
|
+
const streamIntervalRef = (0, import_react11.useRef)(null);
|
|
2953
|
+
const pathIndexRef = (0, import_react11.useRef)(0);
|
|
2954
|
+
const activeConfigRef = (0, import_react11.useRef)(null);
|
|
2955
|
+
const stateRef = (0, import_react11.useRef)({
|
|
2956
|
+
autoMove,
|
|
2957
|
+
heading,
|
|
2958
|
+
location,
|
|
2959
|
+
mockJWTToken,
|
|
2960
|
+
pushLocationUpdate: () => {
|
|
2961
|
+
},
|
|
2962
|
+
setHeading,
|
|
2963
|
+
setLocation,
|
|
2964
|
+
simulationPath,
|
|
2965
|
+
speedMultiplier
|
|
2966
|
+
});
|
|
2967
|
+
const pushLocationUpdate = (0, import_react11.useCallback)(() => {
|
|
2968
|
+
const currentState = stateRef.current;
|
|
2969
|
+
const payload = {
|
|
2970
|
+
accelerometer: null,
|
|
2971
|
+
heading: {
|
|
2972
|
+
accuracy: 3,
|
|
2973
|
+
magneticHeading: currentState.heading,
|
|
2974
|
+
trueHeading: currentState.heading
|
|
2975
|
+
},
|
|
2976
|
+
location: {
|
|
2977
|
+
coords: {
|
|
2978
|
+
accuracy: 10,
|
|
2979
|
+
altitude: 0,
|
|
2980
|
+
altitudeAccuracy: null,
|
|
2981
|
+
heading: currentState.heading,
|
|
2982
|
+
latitude: currentState.location[1],
|
|
2983
|
+
longitude: currentState.location[0],
|
|
2984
|
+
speed: currentState.autoMove ? 1.5 * currentState.speedMultiplier : 0
|
|
2985
|
+
},
|
|
2986
|
+
timestamp: Date.now()
|
|
2987
|
+
},
|
|
2988
|
+
timestamp: Date.now()
|
|
2989
|
+
};
|
|
2990
|
+
window.__MiniAppBridge?.onNativeMessage({
|
|
2991
|
+
eventName: "LOCATION_STREAM_UPDATE" /* LOCATION_STREAM_UPDATE */,
|
|
2992
|
+
payload,
|
|
2993
|
+
type: "EVENT"
|
|
2994
|
+
});
|
|
2995
|
+
}, []);
|
|
2996
|
+
(0, import_react11.useEffect)(() => {
|
|
2997
|
+
stateRef.current = {
|
|
2998
|
+
autoMove,
|
|
2999
|
+
heading,
|
|
3000
|
+
location,
|
|
3001
|
+
mockJWTToken,
|
|
3002
|
+
pushLocationUpdate,
|
|
3003
|
+
setHeading,
|
|
3004
|
+
setLocation,
|
|
3005
|
+
simulationPath,
|
|
3006
|
+
speedMultiplier
|
|
3007
|
+
};
|
|
3008
|
+
}, [
|
|
3009
|
+
location,
|
|
3010
|
+
heading,
|
|
3011
|
+
autoMove,
|
|
3012
|
+
pushLocationUpdate,
|
|
3013
|
+
simulationPath,
|
|
3014
|
+
speedMultiplier,
|
|
3015
|
+
mockJWTToken
|
|
3016
|
+
]);
|
|
3017
|
+
(0, import_react11.useEffect)(() => {
|
|
3018
|
+
setAutoMove(isMoving);
|
|
3019
|
+
}, [isMoving]);
|
|
3020
|
+
(0, import_react11.useEffect)(() => {
|
|
3021
|
+
setSpeedMultiplier(simulationSpeed);
|
|
3022
|
+
}, [simulationSpeed]);
|
|
3023
|
+
(0, import_react11.useEffect)(() => {
|
|
3024
|
+
if (simulationPath && simulationPath.length > 0) {
|
|
3025
|
+
pathIndexRef.current = 0;
|
|
3026
|
+
setLocation(simulationPath[0]);
|
|
3027
|
+
}
|
|
3028
|
+
}, [simulationPath]);
|
|
3029
|
+
(0, import_react11.useEffect)(() => {
|
|
3030
|
+
if (streamIntervalRef.current && activeConfigRef.current) {
|
|
3031
|
+
sdk.bridge.send(
|
|
3032
|
+
"START_LOCATION_STREAM",
|
|
3033
|
+
activeConfigRef.current
|
|
3034
|
+
);
|
|
3035
|
+
}
|
|
3036
|
+
}, [speedMultiplier]);
|
|
3037
|
+
(0, import_react11.useEffect)(() => {
|
|
3038
|
+
const bridge = sdk.bridge;
|
|
3039
|
+
const originalSend = bridge.send.bind(bridge);
|
|
3040
|
+
const stopStream = () => {
|
|
3041
|
+
if (streamIntervalRef.current) {
|
|
3042
|
+
clearInterval(streamIntervalRef.current);
|
|
3043
|
+
streamIntervalRef.current = null;
|
|
3044
|
+
}
|
|
3045
|
+
activeConfigRef.current = null;
|
|
3046
|
+
};
|
|
3047
|
+
const startStream = (config) => {
|
|
3048
|
+
stopStream();
|
|
3049
|
+
activeConfigRef.current = config || {};
|
|
3050
|
+
const baseInterval = config?.interval || 500;
|
|
3051
|
+
const interval = Math.max(
|
|
3052
|
+
10,
|
|
3053
|
+
baseInterval / stateRef.current.speedMultiplier
|
|
3054
|
+
);
|
|
3055
|
+
streamIntervalRef.current = setInterval(() => {
|
|
3056
|
+
const state = stateRef.current;
|
|
3057
|
+
if (state.autoMove) {
|
|
3058
|
+
if (state.simulationPath && state.simulationPath.length > 0) {
|
|
3059
|
+
if (pathIndexRef.current < state.simulationPath.length - 1) {
|
|
3060
|
+
const currentPoint = state.simulationPath[pathIndexRef.current];
|
|
3061
|
+
const nextPoint = state.simulationPath[pathIndexRef.current + 1];
|
|
3062
|
+
pathIndexRef.current += 1;
|
|
3063
|
+
const newHeading = getBearing(currentPoint, nextPoint);
|
|
3064
|
+
state.setHeading(newHeading);
|
|
3065
|
+
state.setLocation(nextPoint);
|
|
3066
|
+
} else {
|
|
3067
|
+
state.setLocation(
|
|
3068
|
+
state.simulationPath[state.simulationPath.length - 1]
|
|
3069
|
+
);
|
|
3070
|
+
}
|
|
3071
|
+
} else {
|
|
3072
|
+
state.setLocation((prev) => [
|
|
3073
|
+
prev[0] + (Math.random() - 0.5) * 1e-4,
|
|
3074
|
+
prev[1] + (Math.random() - 0.5) * 1e-4
|
|
3075
|
+
]);
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
state.pushLocationUpdate();
|
|
3079
|
+
}, interval);
|
|
3080
|
+
};
|
|
3081
|
+
const simulateNetworkRequest = async (payload) => {
|
|
3082
|
+
const {
|
|
3083
|
+
url,
|
|
3084
|
+
method = "GET",
|
|
3085
|
+
headers: incomingHeaders = {},
|
|
3086
|
+
body
|
|
3087
|
+
} = payload;
|
|
3088
|
+
const state = stateRef.current;
|
|
3089
|
+
const headers = new Headers(incomingHeaders);
|
|
3090
|
+
if (state.mockJWTToken) {
|
|
3091
|
+
headers.set(
|
|
3092
|
+
"Authorization",
|
|
3093
|
+
`Bearer ${state.mockJWTToken.replace("Bearer ", "")}`
|
|
3094
|
+
);
|
|
3095
|
+
}
|
|
3096
|
+
if (body && typeof body === "object" && !headers.has("Content-Type")) {
|
|
3097
|
+
headers.set("Content-Type", "application/json");
|
|
3098
|
+
}
|
|
3099
|
+
try {
|
|
3100
|
+
const response = await fetch(url, {
|
|
3101
|
+
body: body ? typeof body === "string" ? body : JSON.stringify(body) : void 0,
|
|
3102
|
+
headers,
|
|
3103
|
+
method
|
|
3104
|
+
});
|
|
3105
|
+
const contentType = response.headers.get("content-type");
|
|
3106
|
+
let responseData;
|
|
3107
|
+
if (contentType?.includes("application/json")) {
|
|
3108
|
+
responseData = await response.json();
|
|
3109
|
+
} else {
|
|
3110
|
+
responseData = await response.text();
|
|
3111
|
+
}
|
|
3112
|
+
const headersObj = {};
|
|
3113
|
+
response.headers.forEach((value, key) => {
|
|
3114
|
+
headersObj[key] = value;
|
|
3115
|
+
});
|
|
3116
|
+
return {
|
|
3117
|
+
data: responseData,
|
|
3118
|
+
headers: headersObj,
|
|
3119
|
+
status: response.status,
|
|
3120
|
+
statusText: response.statusText
|
|
3121
|
+
};
|
|
3122
|
+
} catch (error) {
|
|
3123
|
+
throw {
|
|
3124
|
+
error: error.message || "Network request failed",
|
|
3125
|
+
status: 0
|
|
3126
|
+
};
|
|
3127
|
+
}
|
|
3128
|
+
};
|
|
3129
|
+
bridge.send = async (type, payload) => {
|
|
3130
|
+
if (type === "INITIALIZE") {
|
|
3131
|
+
return Promise.resolve({
|
|
3132
|
+
appId,
|
|
3133
|
+
// Legacy support
|
|
3134
|
+
context: { theme: "dark" },
|
|
3135
|
+
// Legacy support
|
|
3136
|
+
description: "Mocked for Dev",
|
|
3137
|
+
env: {},
|
|
3138
|
+
insets: { bottom: 0, left: 0, right: 0, top: 0 },
|
|
3139
|
+
name: "Vite Dev (Mock)",
|
|
3140
|
+
params: {},
|
|
3141
|
+
profile: {
|
|
3142
|
+
countryCode: "FR",
|
|
3143
|
+
email: "caveman@up.app",
|
|
3144
|
+
fullName: "Smart Caveman",
|
|
3145
|
+
localNumber: "0600000000",
|
|
3146
|
+
username: "smart_caveman",
|
|
3147
|
+
uuid: "mock-user-42"
|
|
3148
|
+
},
|
|
3149
|
+
success: true,
|
|
3150
|
+
userData: {},
|
|
3151
|
+
uuid: appId || "mock-app-id",
|
|
3152
|
+
version: "1.0.0"
|
|
3153
|
+
});
|
|
3154
|
+
}
|
|
3155
|
+
if (type === "GET_CURRENT_POSITION") {
|
|
3156
|
+
const state = stateRef.current;
|
|
3157
|
+
return Promise.resolve({
|
|
3158
|
+
coords: {
|
|
3159
|
+
accuracy: 10,
|
|
3160
|
+
altitude: 0,
|
|
3161
|
+
altitudeAccuracy: 0,
|
|
3162
|
+
heading: state.heading,
|
|
3163
|
+
latitude: state.location[1],
|
|
3164
|
+
longitude: state.location[0],
|
|
3165
|
+
speed: state.autoMove ? 1.5 * state.speedMultiplier : 0
|
|
3166
|
+
},
|
|
3167
|
+
timestamp: Date.now()
|
|
3168
|
+
});
|
|
3169
|
+
}
|
|
3170
|
+
if (type === "START_LOCATION_STREAM") {
|
|
3171
|
+
startStream(payload);
|
|
3172
|
+
return Promise.resolve();
|
|
3173
|
+
}
|
|
3174
|
+
if (type === "STOP_LOCATION_STREAM") {
|
|
3175
|
+
stopStream();
|
|
3176
|
+
return Promise.resolve();
|
|
3177
|
+
}
|
|
3178
|
+
if (type === "NETWORK_REQUEST") {
|
|
3179
|
+
return simulateNetworkRequest(payload);
|
|
3180
|
+
}
|
|
3181
|
+
if (type === "STORAGE_GET" || type === "SECURE_GET") {
|
|
3182
|
+
return Promise.resolve(localStorage.getItem(`mock_sdk_${payload.key}`));
|
|
3183
|
+
}
|
|
3184
|
+
if (type === "STORAGE_SET" || type === "SECURE_SET") {
|
|
3185
|
+
localStorage.setItem(`mock_sdk_${payload.key}`, payload.value);
|
|
3186
|
+
return Promise.resolve();
|
|
3187
|
+
}
|
|
3188
|
+
if (type === "GET_USER_PROFILE") {
|
|
3189
|
+
return Promise.resolve({
|
|
3190
|
+
countryCode: "FR",
|
|
3191
|
+
email: "caveman@up.app",
|
|
3192
|
+
fullName: "Smart Caveman",
|
|
3193
|
+
localNumber: "0600000000",
|
|
3194
|
+
username: "smart_caveman",
|
|
3195
|
+
uuid: "mock-user-42"
|
|
3196
|
+
});
|
|
3197
|
+
}
|
|
3198
|
+
if (type === "GET_FIDELITY_BALANCE") {
|
|
3199
|
+
return Promise.resolve({ balance: 1337, currency: "FPoints" });
|
|
3200
|
+
}
|
|
3201
|
+
if (type === "PAYMENT_REQUEST") {
|
|
3202
|
+
return Promise.resolve({
|
|
3203
|
+
success: true,
|
|
3204
|
+
transactionId: `tx_${Math.random().toString(36).slice(2)}`
|
|
3205
|
+
});
|
|
3206
|
+
}
|
|
3207
|
+
if (type === "TRIGGER_HAPTIC" || type === "EXIT" || type === "UPDATE_CONTEXT") {
|
|
3208
|
+
return Promise.resolve({ success: true });
|
|
3209
|
+
}
|
|
3210
|
+
return originalSend(type, payload);
|
|
3211
|
+
};
|
|
3212
|
+
setIsReady(true);
|
|
3213
|
+
return () => {
|
|
3214
|
+
stopStream();
|
|
3215
|
+
bridge.send = originalSend;
|
|
3216
|
+
};
|
|
3217
|
+
}, [appId]);
|
|
3218
|
+
if (!isReady) return null;
|
|
3219
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "relative w-full h-full", children: [
|
|
3220
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "w-full h-full", children }),
|
|
3221
|
+
isMinimized ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
3222
|
+
"button",
|
|
3223
|
+
{
|
|
3224
|
+
className: "fixed bottom-4 right-4 w-8 h-8 rounded-full bg-slate-900/60 backdrop-blur-sm border border-slate-700/50 flex items-center justify-center text-slate-400 hover:text-blue-400 z-[9999] transition-all shadow-lg group",
|
|
3225
|
+
onClick: () => setIsMinimized(false),
|
|
3226
|
+
title: "Open Simulation Controls",
|
|
3227
|
+
type: "button",
|
|
3228
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
3229
|
+
"svg",
|
|
3230
|
+
{
|
|
3231
|
+
className: "group-hover:scale-110 transition-transform",
|
|
3232
|
+
fill: "none",
|
|
3233
|
+
height: "14",
|
|
3234
|
+
stroke: "currentColor",
|
|
3235
|
+
strokeLinecap: "round",
|
|
3236
|
+
strokeLinejoin: "round",
|
|
3237
|
+
strokeWidth: "2",
|
|
3238
|
+
viewBox: "0 0 24 24",
|
|
3239
|
+
width: "14",
|
|
3240
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
3241
|
+
children: [
|
|
3242
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M21 4H3" }),
|
|
3243
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M10 8H3" }),
|
|
3244
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M21 12H3" }),
|
|
3245
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M10 16H3" }),
|
|
3246
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M21 20H3" }),
|
|
3247
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("circle", { cx: "14", cy: "8", r: "2" }),
|
|
3248
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("circle", { cx: "14", cy: "16", r: "2" })
|
|
3249
|
+
]
|
|
3250
|
+
}
|
|
3251
|
+
)
|
|
3252
|
+
}
|
|
3253
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
3254
|
+
"div",
|
|
3255
|
+
{
|
|
3256
|
+
className: "fixed bottom-4 right-4 bg-slate-900 text-slate-300 font-mono text-xs rounded-xl shadow-2xl border border-slate-700 pointer-events-auto flex flex-col w-80 overflow-hidden animate-in slide-in-from-right duration-300",
|
|
3257
|
+
style: { zIndex: 9998 },
|
|
3258
|
+
children: [
|
|
3259
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
3260
|
+
"div",
|
|
3261
|
+
{
|
|
3262
|
+
className: "flex justify-between items-center p-3 border-b border-slate-800 cursor-pointer bg-slate-800/50 hover:bg-slate-800 transition-colors",
|
|
3263
|
+
onClick: () => setIsMinimized(true),
|
|
3264
|
+
children: [
|
|
3265
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "font-bold text-slate-100 flex items-center gap-2", children: [
|
|
3266
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-lg", children: "\u{1F6E0}\uFE0F" }),
|
|
3267
|
+
" Simulation Controls"
|
|
3268
|
+
] }),
|
|
3269
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { className: "text-slate-500 hover:text-white", type: "button", children: "\u25BC" })
|
|
3270
|
+
]
|
|
3271
|
+
}
|
|
3272
|
+
),
|
|
3273
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "p-4 flex flex-col gap-3", children: [
|
|
3274
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex justify-between items-center", children: [
|
|
3275
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("label", { className: "flex items-center gap-2 cursor-pointer", children: [
|
|
3276
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
3277
|
+
"input",
|
|
3278
|
+
{
|
|
3279
|
+
checked: autoMove,
|
|
3280
|
+
className: "accent-blue-500 w-4 h-4",
|
|
3281
|
+
onChange: (e) => setAutoMove(e.target.checked),
|
|
3282
|
+
type: "checkbox"
|
|
3283
|
+
}
|
|
3284
|
+
),
|
|
3285
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "text-slate-200 font-bold", children: [
|
|
3286
|
+
"Auto-Move ",
|
|
3287
|
+
simulationPath ? "(Path)" : "(Random)"
|
|
3288
|
+
] })
|
|
3289
|
+
] }),
|
|
3290
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
3291
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-[10px] text-slate-500", children: "Speed:" }),
|
|
3292
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
3293
|
+
"select",
|
|
3294
|
+
{
|
|
3295
|
+
className: "bg-slate-800 border border-slate-700 rounded px-1 py-0.5 text-white focus:outline-none focus:border-blue-500 text-[10px]",
|
|
3296
|
+
onChange: (e) => setSpeedMultiplier(Number(e.target.value)),
|
|
3297
|
+
value: speedMultiplier,
|
|
3298
|
+
children: [1, 2, 5, 10, 50, 100, 250].map((s) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("option", { value: s, children: [
|
|
3299
|
+
s,
|
|
3300
|
+
"x"
|
|
3301
|
+
] }, s))
|
|
3302
|
+
}
|
|
3303
|
+
)
|
|
3304
|
+
] })
|
|
3305
|
+
] }),
|
|
3306
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "grid grid-cols-2 gap-2", children: [
|
|
3307
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-col gap-1", children: [
|
|
3308
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-[10px] text-slate-500 text-left", children: "Lng" }),
|
|
3309
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
3310
|
+
"input",
|
|
3311
|
+
{
|
|
3312
|
+
className: "bg-slate-800 border border-slate-700 rounded px-2 py-1 text-white focus:outline-none focus:border-blue-500",
|
|
3313
|
+
onChange: (e) => {
|
|
3314
|
+
const lng = parseFloat(e.target.value) || 0;
|
|
3315
|
+
setLocation([lng, location[1]]);
|
|
3316
|
+
pushLocationUpdate();
|
|
3317
|
+
},
|
|
3318
|
+
step: "0.001",
|
|
3319
|
+
type: "number",
|
|
3320
|
+
value: location[0]
|
|
3321
|
+
}
|
|
3322
|
+
)
|
|
3323
|
+
] }),
|
|
3324
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-col gap-1", children: [
|
|
3325
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-[10px] text-slate-500 text-left", children: "Lat" }),
|
|
3326
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
3327
|
+
"input",
|
|
3328
|
+
{
|
|
3329
|
+
className: "bg-slate-800 border border-slate-700 rounded px-2 py-1 text-white focus:outline-none focus:border-blue-500",
|
|
3330
|
+
onChange: (e) => {
|
|
3331
|
+
const lat = parseFloat(e.target.value) || 0;
|
|
3332
|
+
setLocation([location[0], lat]);
|
|
3333
|
+
pushLocationUpdate();
|
|
3334
|
+
},
|
|
3335
|
+
step: "0.001",
|
|
3336
|
+
type: "number",
|
|
3337
|
+
value: location[1]
|
|
3338
|
+
}
|
|
3339
|
+
)
|
|
3340
|
+
] }),
|
|
3341
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-col gap-1 col-span-2", children: [
|
|
3342
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-[10px] text-slate-500", children: "Heading (Degrees)" }),
|
|
3343
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
3344
|
+
"input",
|
|
3345
|
+
{
|
|
3346
|
+
className: "w-full accent-blue-500",
|
|
3347
|
+
max: "360",
|
|
3348
|
+
min: "0",
|
|
3349
|
+
onChange: (e) => {
|
|
3350
|
+
setHeading(parseInt(e.target.value, 10));
|
|
3351
|
+
pushLocationUpdate();
|
|
3352
|
+
},
|
|
3353
|
+
type: "range",
|
|
3354
|
+
value: heading
|
|
3355
|
+
}
|
|
3356
|
+
),
|
|
3357
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "text-right text-slate-400", children: [
|
|
3358
|
+
heading,
|
|
3359
|
+
"\xB0"
|
|
3360
|
+
] })
|
|
3361
|
+
] })
|
|
3362
|
+
] })
|
|
3363
|
+
] })
|
|
3364
|
+
]
|
|
3365
|
+
}
|
|
3366
|
+
)
|
|
3367
|
+
] });
|
|
3368
|
+
};
|
|
3369
|
+
|
|
3370
|
+
// src/components/DebugConsole.tsx
|
|
3371
|
+
var import_react12 = require("react");
|
|
3372
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
3373
|
+
var DebugConsole = ({
|
|
3374
|
+
isOpen: initialOpen = false,
|
|
3375
|
+
onClose
|
|
3376
|
+
}) => {
|
|
3377
|
+
const [showConsole, setShowConsole] = (0, import_react12.useState)(initialOpen);
|
|
3378
|
+
const [logs, setLogs] = (0, import_react12.useState)([]);
|
|
3379
|
+
const scrollRef = (0, import_react12.useRef)(null);
|
|
3380
|
+
(0, import_react12.useEffect)(() => {
|
|
3381
|
+
setShowConsole(initialOpen);
|
|
3382
|
+
}, [initialOpen]);
|
|
3383
|
+
const clearLogs = () => setLogs([]);
|
|
3384
|
+
const addLog = (0, import_react12.useCallback)((label, data) => {
|
|
3385
|
+
if (label.includes("LOCATION_STREAM_UPDATE" /* LOCATION_STREAM_UPDATE */)) return;
|
|
3386
|
+
const entry = {
|
|
3387
|
+
content: typeof data === "string" ? data : JSON.stringify(data, null, 2),
|
|
3388
|
+
id: Math.random().toString(36).slice(2),
|
|
3389
|
+
label,
|
|
3390
|
+
time: (/* @__PURE__ */ new Date()).toLocaleTimeString()
|
|
3391
|
+
};
|
|
3392
|
+
setLogs((prev) => [entry, ...prev].slice(0, 100));
|
|
3393
|
+
}, []);
|
|
3394
|
+
(0, import_react12.useEffect)(() => {
|
|
3395
|
+
if (typeof window === "undefined") return;
|
|
3396
|
+
const bridge = sdk.bridge;
|
|
3397
|
+
const originalSend = bridge.send.bind(bridge);
|
|
3398
|
+
bridge.send = async (type, payload) => {
|
|
3399
|
+
addLog(`OUT: ${type}`, payload);
|
|
3400
|
+
return originalSend(type, payload);
|
|
3401
|
+
};
|
|
3402
|
+
const originalHandle = bridge.handleNativeMessage.bind(bridge);
|
|
3403
|
+
bridge.handleNativeMessage = (envelope) => {
|
|
3404
|
+
const type = envelope.eventName || envelope.event || envelope.type || "MSG";
|
|
3405
|
+
if (typeof type === "string") {
|
|
3406
|
+
addLog(`IN: ${type}`, envelope.payload || envelope.value || envelope);
|
|
3407
|
+
originalHandle(envelope);
|
|
3408
|
+
}
|
|
3409
|
+
};
|
|
3410
|
+
return () => {
|
|
3411
|
+
bridge.send = originalSend;
|
|
3412
|
+
bridge.handleNativeMessage = originalHandle;
|
|
3413
|
+
};
|
|
3414
|
+
}, [addLog]);
|
|
3415
|
+
if (!showConsole) {
|
|
3416
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3417
|
+
"button",
|
|
3418
|
+
{
|
|
3419
|
+
className: "fixed bottom-4 left-4 w-8 h-8 rounded-full bg-slate-900/60 backdrop-blur-sm border border-slate-700/50 flex items-center justify-center text-slate-400 hover:text-emerald-400 transition-all z-[9998] shadow-lg group",
|
|
3420
|
+
onClick: () => setShowConsole(true),
|
|
3421
|
+
title: "Open Debug Console",
|
|
3422
|
+
type: "button",
|
|
3423
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
3424
|
+
"svg",
|
|
3425
|
+
{
|
|
3426
|
+
className: "group-hover:scale-110 transition-transform",
|
|
3427
|
+
fill: "none",
|
|
3428
|
+
height: "14",
|
|
3429
|
+
stroke: "currentColor",
|
|
3430
|
+
strokeLinecap: "round",
|
|
3431
|
+
strokeLinejoin: "round",
|
|
3432
|
+
strokeWidth: "2",
|
|
3433
|
+
viewBox: "0 0 24 24",
|
|
3434
|
+
width: "14",
|
|
3435
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
3436
|
+
children: [
|
|
3437
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "m8 2 1.88 1.88" }),
|
|
3438
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "M14.12 3.88 16 2" }),
|
|
3439
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "M9 7.13v-1a3.003 3.003 0 1 1 6 0v1" }),
|
|
3440
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6" }),
|
|
3441
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "M12 20v-9" }),
|
|
3442
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "M6.53 9C4.6 8.8 3 7.1 3 5" }),
|
|
3443
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "M6 13H2" }),
|
|
3444
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "M3 21c0-2.1 1.7-3.9 3.8-4" }),
|
|
3445
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "M20.97 5c0 2.1-1.6 3.8-3.5 4" }),
|
|
3446
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "M22 13h-4" }),
|
|
3447
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "M17.2 17c2.1.1 3.8 1.9 3.8 4" })
|
|
3448
|
+
]
|
|
3449
|
+
}
|
|
3450
|
+
)
|
|
3451
|
+
}
|
|
3452
|
+
);
|
|
3453
|
+
}
|
|
3454
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "fixed inset-x-0 bottom-0 w-full bg-[#0a080f] border-t border-slate-800 z-[9998] p-4 shadow-2xl flex flex-col animate-in slide-in-from-bottom duration-300", children: [
|
|
3455
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center justify-between mb-2", children: [
|
|
3456
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "text-[10px] font-bold uppercase tracking-widest text-slate-400 flex items-center gap-2", children: [
|
|
3457
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "w-2 h-2 rounded-full bg-emerald-500 animate-pulse" }),
|
|
3458
|
+
"UP SDK Debug Console"
|
|
3459
|
+
] }),
|
|
3460
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex gap-4", children: [
|
|
3461
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3462
|
+
"button",
|
|
3463
|
+
{
|
|
3464
|
+
className: "text-[10px] text-slate-500 font-bold uppercase hover:text-slate-300 transition-colors",
|
|
3465
|
+
onClick: clearLogs,
|
|
3466
|
+
type: "button",
|
|
3467
|
+
children: "Clear"
|
|
3468
|
+
}
|
|
3469
|
+
),
|
|
3470
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3471
|
+
"button",
|
|
3472
|
+
{
|
|
3473
|
+
className: "text-[10px] text-pink-500 font-bold uppercase hover:underline transition-colors",
|
|
3474
|
+
onClick: () => {
|
|
3475
|
+
setShowConsole(false);
|
|
3476
|
+
if (onClose) onClose();
|
|
3477
|
+
},
|
|
3478
|
+
type: "button",
|
|
3479
|
+
children: "Close"
|
|
3480
|
+
}
|
|
3481
|
+
)
|
|
3482
|
+
] })
|
|
3483
|
+
] }),
|
|
3484
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3485
|
+
"div",
|
|
3486
|
+
{
|
|
3487
|
+
className: "bg-black/80 rounded-xl p-3 font-mono text-[10px] text-emerald-400 min-h-[120px] max-h-[240px] overflow-y-auto border border-slate-900 custom-scrollbar",
|
|
3488
|
+
ref: scrollRef,
|
|
3489
|
+
children: logs.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "text-slate-600 italic text-center py-8", children: "No logs yet. Interacting with the SDK will show events here." }) : logs.map((log) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
3490
|
+
"div",
|
|
3491
|
+
{
|
|
3492
|
+
className: "mb-3 pb-3 border-b border-slate-900/50 last:border-0 last:mb-0 last:pb-0",
|
|
3493
|
+
children: [
|
|
3494
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex gap-2 text-slate-500 mb-1 items-center", children: [
|
|
3495
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-[8px]", children: log.time }),
|
|
3496
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "font-bold text-white uppercase text-[8px] bg-slate-800 px-1.5 py-0.5 rounded leading-none", children: log.label })
|
|
3497
|
+
] }),
|
|
3498
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("pre", { className: "whitespace-pre-wrap break-all opacity-90", children: log.content })
|
|
3499
|
+
]
|
|
3500
|
+
},
|
|
3501
|
+
log.id
|
|
3502
|
+
))
|
|
3503
|
+
}
|
|
3504
|
+
)
|
|
3505
|
+
] });
|
|
3506
|
+
};
|
|
3507
|
+
|
|
3508
|
+
// src/components/SDKProvider.tsx
|
|
3509
|
+
var import_react13 = require("react");
|
|
3510
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
3511
|
+
var SDKContext = (0, import_react13.createContext)({ isMocked: false });
|
|
3512
|
+
var useIsMocked = () => (0, import_react13.useContext)(SDKContext).isMocked;
|
|
3513
|
+
var SDKProvider = ({
|
|
3514
|
+
children,
|
|
3515
|
+
debug = false,
|
|
3516
|
+
...mockerProps
|
|
3517
|
+
}) => {
|
|
3518
|
+
const isMocked = typeof window !== "undefined" && !(window.__UP_REACT_NATIVE_APP__ === true || !!window.ReactNativeWebView);
|
|
3519
|
+
const value = (0, import_react13.useMemo)(() => ({ isMocked }), [isMocked]);
|
|
3520
|
+
const content = /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
|
|
3521
|
+
children,
|
|
3522
|
+
debug && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DebugConsole, {})
|
|
3523
|
+
] });
|
|
3524
|
+
if (isMocked) {
|
|
3525
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SDKContext.Provider, { value, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(BridgeMocker, { ...mockerProps, children: content }) });
|
|
3526
|
+
}
|
|
3527
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SDKContext.Provider, { value, children: content });
|
|
3528
|
+
};
|
|
3529
|
+
|
|
3530
|
+
// src/host/routingHandler.ts
|
|
3531
|
+
async function handleRouteRequest(req, config) {
|
|
3532
|
+
return RoutingService.fetchRoute(req, config);
|
|
3533
|
+
}
|
|
3534
|
+
function handleDrawRoute(map, miniAppId, req) {
|
|
3535
|
+
const sourceId = `sdk-route-${miniAppId}`;
|
|
3536
|
+
const casingLayerId = `sdk-route-casing-${miniAppId}`;
|
|
3537
|
+
const lineLayerId = `sdk-route-line-${miniAppId}`;
|
|
3538
|
+
const geojson = {
|
|
3539
|
+
features: [
|
|
3540
|
+
{
|
|
3541
|
+
geometry: req.payload.routeData.geometry,
|
|
3542
|
+
properties: {},
|
|
3543
|
+
type: "Feature"
|
|
3544
|
+
}
|
|
3545
|
+
],
|
|
3546
|
+
type: "FeatureCollection"
|
|
3547
|
+
};
|
|
3548
|
+
const source = map.getSource(sourceId);
|
|
3549
|
+
if (source) {
|
|
3550
|
+
source.setData(geojson);
|
|
3551
|
+
} else {
|
|
3552
|
+
map.addSource(sourceId, {
|
|
3553
|
+
data: geojson,
|
|
3554
|
+
type: "geojson"
|
|
3555
|
+
});
|
|
3556
|
+
map.addLayer({
|
|
3557
|
+
id: casingLayerId,
|
|
3558
|
+
layout: {
|
|
3559
|
+
"line-cap": "round",
|
|
3560
|
+
"line-join": "round"
|
|
3561
|
+
},
|
|
3562
|
+
paint: {
|
|
3563
|
+
"line-color": "#111111",
|
|
3564
|
+
"line-opacity": 0.8,
|
|
3565
|
+
"line-width": 8
|
|
3566
|
+
},
|
|
3567
|
+
source: sourceId,
|
|
3568
|
+
type: "line"
|
|
3569
|
+
});
|
|
3570
|
+
map.addLayer({
|
|
3571
|
+
id: lineLayerId,
|
|
3572
|
+
layout: {
|
|
3573
|
+
"line-cap": "round",
|
|
3574
|
+
"line-join": "round"
|
|
3575
|
+
},
|
|
3576
|
+
paint: {
|
|
3577
|
+
"line-color": "#3b82f6",
|
|
3578
|
+
// Bright blue
|
|
3579
|
+
"line-width": 4
|
|
3580
|
+
},
|
|
3581
|
+
source: sourceId,
|
|
3582
|
+
type: "line"
|
|
3583
|
+
});
|
|
3584
|
+
}
|
|
3585
|
+
}
|
|
3586
|
+
function handleClearRoute(map, miniAppId) {
|
|
3587
|
+
const sourceId = `sdk-route-${miniAppId}`;
|
|
3588
|
+
const casingLayerId = `sdk-route-casing-${miniAppId}`;
|
|
3589
|
+
const lineLayerId = `sdk-route-line-${miniAppId}`;
|
|
3590
|
+
if (map.getLayer(lineLayerId)) map.removeLayer(lineLayerId);
|
|
3591
|
+
if (map.getLayer(casingLayerId)) map.removeLayer(casingLayerId);
|
|
3592
|
+
if (map.getSource(sourceId)) map.removeSource(sourceId);
|
|
3593
|
+
}
|
|
3594
|
+
|
|
3595
|
+
// src/index.ts
|
|
3596
|
+
var src_default = sdk;
|
|
3597
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
3598
|
+
0 && (module.exports = {
|
|
3599
|
+
BridgeMocker,
|
|
3600
|
+
DebugConsole,
|
|
3601
|
+
GeolocationEvent,
|
|
3602
|
+
GlobalEvent,
|
|
3603
|
+
Map,
|
|
3604
|
+
MapControls,
|
|
3605
|
+
MapControlsContext,
|
|
3606
|
+
MapMenu,
|
|
3607
|
+
MapMenuDivider,
|
|
3608
|
+
MapMenuFooter,
|
|
3609
|
+
MapMenuItem,
|
|
3610
|
+
MapMenuTitle,
|
|
3611
|
+
MiniAppSDK,
|
|
3612
|
+
RouteLayer,
|
|
3613
|
+
SDKMapProvider,
|
|
3614
|
+
SDKProvider,
|
|
3615
|
+
UIEvent,
|
|
3616
|
+
handleClearRoute,
|
|
3617
|
+
handleDrawRoute,
|
|
3618
|
+
handleRouteRequest,
|
|
3619
|
+
renderMap,
|
|
3620
|
+
sdk,
|
|
3621
|
+
useIsMocked,
|
|
3622
|
+
useLiveRouting,
|
|
3623
|
+
useMapControls,
|
|
3624
|
+
useRoute,
|
|
3625
|
+
useTrimmedRoute,
|
|
3626
|
+
useUserLocation
|
|
3627
|
+
});
|
|
3628
|
+
//# sourceMappingURL=index.cjs.map
|