react-native-maplibre-lite 0.2.1 → 0.2.3
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 +12 -0
- package/lib/module/components/MapView.js +23 -3
- package/lib/module/components/MapView.js.map +1 -1
- package/lib/module/components/navigatorVoiceCatalog.js +50 -14
- package/lib/module/components/navigatorVoiceCatalog.js.map +1 -1
- package/lib/module/components/types.js +2 -0
- package/lib/module/components/types.js.map +1 -1
- package/lib/module/components/webMapBuild.js +1 -1
- package/lib/module/components/webMapBuild.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/components/MapView.d.ts +9 -0
- package/lib/typescript/src/components/MapView.d.ts.map +1 -1
- package/lib/typescript/src/components/navigatorVoiceCatalog.d.ts.map +1 -1
- package/lib/typescript/src/components/types.d.ts +7 -0
- package/lib/typescript/src/components/types.d.ts.map +1 -1
- package/lib/typescript/src/components/webMapBuild.d.ts +1 -1
- package/lib/typescript/src/components/webMapBuild.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/resources/map.html +43 -43
- package/src/components/MapView.tsx +26 -2
- package/src/components/navigatorVoiceCatalog.ts +55 -14
- package/src/components/types.ts +6 -0
- package/src/components/webMapBuild.ts +1 -1
- package/src/index.tsx +1 -0
|
@@ -122,6 +122,13 @@ interface MapViewProps {
|
|
|
122
122
|
* стороне. Без него озвучка и FAB выбора голоса отключены.
|
|
123
123
|
*/
|
|
124
124
|
navigatorVoiceUrl?: string;
|
|
125
|
+
/**
|
|
126
|
+
* Dev-only: автоматически вести маркер по построенному маршруту
|
|
127
|
+
* (симуляция поездки для отладки). Требует `navigator: true`. Пока
|
|
128
|
+
* включено, реальный GPS в WebView не пробрасывается, чтобы не
|
|
129
|
+
* конфликтовать с симуляцией.
|
|
130
|
+
*/
|
|
131
|
+
navigatorSimulate?: boolean;
|
|
125
132
|
onNavigatorRouteSet?: (params: NavigatorRouteSetParams) => void;
|
|
126
133
|
onNavigatorInstruction?: (params: NavigatorInstructionParams) => void;
|
|
127
134
|
onNavigatorPositionSet?: (params: NavigatorPositionSetParams) => void;
|
|
@@ -150,6 +157,8 @@ export type MapViewRef = {
|
|
|
150
157
|
setNavigatorPosition: (latitude: number, longitude: number, accuracy?: number) => void;
|
|
151
158
|
/** Режим «клик по карте = новая позиция» (удобно в dev). */
|
|
152
159
|
pickNavigatorPosition: () => void;
|
|
160
|
+
/** Dev-only: включить/выключить симуляцию поездки по маршруту. */
|
|
161
|
+
setNavigatorSimulation: (enabled: boolean) => void;
|
|
153
162
|
};
|
|
154
163
|
|
|
155
164
|
type MapViewRegistry = {
|
|
@@ -437,6 +446,10 @@ export const MapView = forwardRef<MapViewRef, MapViewProps>((props, ref) => {
|
|
|
437
446
|
sendToWebView({ function: 'recenterNavigatorCamera', params: {} });
|
|
438
447
|
};
|
|
439
448
|
|
|
449
|
+
const setNavigatorSimulation = (enabled: boolean) => {
|
|
450
|
+
sendToWebView({ function: 'setNavigatorSimulation', params: { enabled } });
|
|
451
|
+
};
|
|
452
|
+
|
|
440
453
|
useImperativeHandle(ref, () => ({
|
|
441
454
|
fitBounds,
|
|
442
455
|
flyTo,
|
|
@@ -444,6 +457,7 @@ export const MapView = forwardRef<MapViewRef, MapViewProps>((props, ref) => {
|
|
|
444
457
|
advanceNavigatorInstruction,
|
|
445
458
|
setNavigatorPosition,
|
|
446
459
|
pickNavigatorPosition,
|
|
460
|
+
setNavigatorSimulation,
|
|
447
461
|
}), [fitBounds]);
|
|
448
462
|
|
|
449
463
|
|
|
@@ -563,7 +577,17 @@ export const MapView = forwardRef<MapViewRef, MapViewProps>((props, ref) => {
|
|
|
563
577
|
};
|
|
564
578
|
|
|
565
579
|
useEffect(() => {
|
|
566
|
-
if (!props.navigator) {
|
|
580
|
+
if (!inited || !props.navigator) {
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
sendToWebViewRef.current({
|
|
584
|
+
function: 'setNavigatorSimulation',
|
|
585
|
+
params: { enabled: !!props.navigatorSimulate },
|
|
586
|
+
});
|
|
587
|
+
}, [inited, props.navigator, props.navigatorSimulate]);
|
|
588
|
+
|
|
589
|
+
useEffect(() => {
|
|
590
|
+
if (!props.navigator || props.navigatorSimulate) {
|
|
567
591
|
return;
|
|
568
592
|
}
|
|
569
593
|
|
|
@@ -615,7 +639,7 @@ export const MapView = forwardRef<MapViewRef, MapViewProps>((props, ref) => {
|
|
|
615
639
|
return () => {
|
|
616
640
|
Geolocation.clearWatch(watchId);
|
|
617
641
|
};
|
|
618
|
-
}, [props.navigator]);
|
|
642
|
+
}, [props.navigator, props.navigatorSimulate]);
|
|
619
643
|
|
|
620
644
|
return (
|
|
621
645
|
<View style={props.style}>
|
|
@@ -95,32 +95,73 @@ export async function writeVoicePref(pref: VoicePref): Promise<void> {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
const HTTP_URL_PREFIX_RE = /^https?:\/\//i;
|
|
99
|
+
|
|
100
|
+
function trimTrailingSlashes(s: string): string {
|
|
101
|
+
return s.replace(/\/+$/, '');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Strips `?query` and `#hash` — catalog URLs are plain paths. */
|
|
105
|
+
function stripUrlQueryAndHash(url: string): string {
|
|
106
|
+
const q = url.indexOf('?');
|
|
107
|
+
const h = url.indexOf('#');
|
|
108
|
+
let end = url.length;
|
|
109
|
+
if (q >= 0) end = Math.min(end, q);
|
|
110
|
+
if (h >= 0) end = Math.min(end, h);
|
|
111
|
+
return url.slice(0, end);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function pathnameSegments(pathname: string): string[] {
|
|
115
|
+
return pathname.split('/').filter(Boolean);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Parses `http(s)://host[:port]/path` without the URL API (Hermes-safe).
|
|
120
|
+
*/
|
|
121
|
+
function parseHttpUrl(url: string): { origin: string; pathname: string } | null {
|
|
122
|
+
const bare = stripUrlQueryAndHash(url.trim());
|
|
123
|
+
if (!HTTP_URL_PREFIX_RE.test(bare)) return null;
|
|
124
|
+
|
|
125
|
+
const schemeEnd = bare.indexOf('://');
|
|
126
|
+
const rest = bare.slice(schemeEnd + 3);
|
|
127
|
+
const slash = rest.indexOf('/');
|
|
128
|
+
const authority = slash === -1 ? rest : rest.slice(0, slash);
|
|
129
|
+
if (!authority) return null;
|
|
130
|
+
|
|
131
|
+
const pathname = slash === -1 ? '/' : rest.slice(slash);
|
|
132
|
+
const origin = bare.slice(0, schemeEnd + 3) + authority;
|
|
133
|
+
return { origin, pathname };
|
|
134
|
+
}
|
|
135
|
+
|
|
98
136
|
export function resolveVoiceCatalogUrl(url: string): string | null {
|
|
99
137
|
const t = url.trim();
|
|
100
138
|
if (!t) return null;
|
|
101
|
-
|
|
102
|
-
// eslint-disable-next-line no-new
|
|
103
|
-
new URL(t);
|
|
104
|
-
return t;
|
|
105
|
-
} catch {
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
139
|
+
return parseHttpUrl(t) ? t : null;
|
|
108
140
|
}
|
|
109
141
|
|
|
110
142
|
/** Base URL for a voice directory: `.../voices/{dir}`. */
|
|
111
143
|
export function voiceDirBaseUrl(catalogUrl: string, dir: string): string {
|
|
112
|
-
const
|
|
113
|
-
|
|
144
|
+
const parsed = parseHttpUrl(catalogUrl);
|
|
145
|
+
if (!parsed) {
|
|
146
|
+
throw new Error('voiceDirBaseUrl: invalid catalog URL');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const parts = pathnameSegments(parsed.pathname);
|
|
114
150
|
if (parts.length > 0 && parts[parts.length - 1]!.toLowerCase().endsWith('.json')) {
|
|
115
151
|
parts.pop();
|
|
116
152
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
153
|
+
|
|
154
|
+
const safeDir = dir.trim().replace(/[/\\]+/g, '');
|
|
155
|
+
if (!safeDir) {
|
|
156
|
+
throw new Error('voiceDirBaseUrl: empty dir');
|
|
157
|
+
}
|
|
158
|
+
parts.push(safeDir);
|
|
159
|
+
|
|
160
|
+
return trimTrailingSlashes(`${parsed.origin}/${parts.join('/')}`);
|
|
120
161
|
}
|
|
121
162
|
|
|
122
163
|
function voiceManifestUrl(baseUrl: string): string {
|
|
123
|
-
return `${baseUrl
|
|
164
|
+
return `${trimTrailingSlashes(baseUrl)}/data.json`;
|
|
124
165
|
}
|
|
125
166
|
|
|
126
167
|
export async function fetchVoiceCatalog(catalogUrl: string): Promise<VoiceCatalogEntry[]> {
|
|
@@ -257,7 +298,7 @@ export function keysToClipUrls(
|
|
|
257
298
|
clipBaseUrl: string,
|
|
258
299
|
keys: string[]
|
|
259
300
|
): string[] {
|
|
260
|
-
const base = clipBaseUrl
|
|
301
|
+
const base = trimTrailingSlashes(clipBaseUrl);
|
|
261
302
|
const variants = new Map<VoicePhraseKey, string>();
|
|
262
303
|
const urls: string[] = [];
|
|
263
304
|
for (const raw of keys) {
|
package/src/components/types.ts
CHANGED
|
@@ -238,6 +238,11 @@ export type MapLiteSetNavigatorPositionParams = {
|
|
|
238
238
|
accuracy?: number,
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
+
/** RN → Web: `setNavigatorSimulation`. Отладочная симуляция поездки по маршруту. */
|
|
242
|
+
export type MapLiteSetNavigatorSimulationParams = {
|
|
243
|
+
enabled: boolean,
|
|
244
|
+
}
|
|
245
|
+
|
|
241
246
|
/** RN → Web: JSON в `WebView.postMessage` (`webProject/src/map/MapLiteController.receive`). */
|
|
242
247
|
export type NativeToWebCommand =
|
|
243
248
|
| { function: 'init', params: MapLiteInitParams }
|
|
@@ -255,6 +260,7 @@ export type NativeToWebCommand =
|
|
|
255
260
|
| { function: 'setNavigatorPosition', params: MapLiteSetNavigatorPositionParams }
|
|
256
261
|
| { function: 'pickNavigatorPosition', params: Record<string, never> }
|
|
257
262
|
| { function: 'recenterNavigatorCamera', params: Record<string, never> }
|
|
263
|
+
| { function: 'setNavigatorSimulation', params: MapLiteSetNavigatorSimulationParams }
|
|
258
264
|
|
|
259
265
|
/** Web → RN: `postToNative` (`webProject/src/map/types.ts`). */
|
|
260
266
|
export type WebToNativeMessage =
|