zenit-sdk 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +279 -0
- package/dist/chunk-RGS6AZWV.mjs +3057 -0
- package/dist/chunk-RGS6AZWV.mjs.map +1 -0
- package/dist/index-C_2fk0Fk.d.mts +690 -0
- package/dist/index-C_2fk0Fk.d.ts +690 -0
- package/dist/index.d.mts +105 -0
- package/dist/index.d.ts +105 -0
- package/dist/index.js +4165 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1095 -0
- package/dist/index.mjs.map +1 -0
- package/dist/react/index.d.mts +4 -0
- package/dist/react/index.d.ts +4 -0
- package/dist/react/index.js +3088 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/index.mjs +53 -0
- package/dist/react/index.mjs.map +1 -0
- package/package.json +65 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,4165 @@
|
|
|
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
|
+
ChevronLeft: () => import_lucide_react.ChevronLeft,
|
|
34
|
+
ChevronRight: () => import_lucide_react.ChevronRight,
|
|
35
|
+
DEFAULT_MAX_FEATURES_FULL_GEOJSON: () => DEFAULT_MAX_FEATURES_FULL_GEOJSON,
|
|
36
|
+
Eye: () => import_lucide_react.Eye,
|
|
37
|
+
EyeOff: () => import_lucide_react.EyeOff,
|
|
38
|
+
FilterEngine: () => FilterEngine,
|
|
39
|
+
FloatingChatBox: () => FloatingChatBox,
|
|
40
|
+
HttpClient: () => HttpClient,
|
|
41
|
+
Layers: () => import_lucide_react.Layers,
|
|
42
|
+
Upload: () => import_lucide_react.Upload,
|
|
43
|
+
X: () => import_lucide_react.X,
|
|
44
|
+
ZenitAuthClient: () => ZenitAuthClient,
|
|
45
|
+
ZenitCatalogNotSupportedError: () => ZenitCatalogNotSupportedError,
|
|
46
|
+
ZenitClient: () => ZenitClient,
|
|
47
|
+
ZenitFeatureFilterPanel: () => ZenitFeatureFilterPanel,
|
|
48
|
+
ZenitLayerManager: () => ZenitLayerManager,
|
|
49
|
+
ZenitLayersClient: () => ZenitLayersClient,
|
|
50
|
+
ZenitMap: () => ZenitMap,
|
|
51
|
+
ZenitMapsClient: () => ZenitMapsClient,
|
|
52
|
+
ZenitSdkAuthClient: () => ZenitSdkAuthClient,
|
|
53
|
+
ZoomIn: () => import_lucide_react.ZoomIn,
|
|
54
|
+
applyFilteredGeoJSONStrategy: () => applyFilteredGeoJSONStrategy,
|
|
55
|
+
applyLayerOverrides: () => applyLayerOverrides,
|
|
56
|
+
applyPrefiltersToFeatureCollection: () => applyPrefiltersToFeatureCollection,
|
|
57
|
+
buildAoiFromFeatureCollection: () => buildAoiFromFeatureCollection,
|
|
58
|
+
buildFilterMultipleParams: () => buildFilterMultipleParams,
|
|
59
|
+
buildLayerFilters: () => buildLayerFilters,
|
|
60
|
+
buildLimitedMessage: () => buildLimitedMessage,
|
|
61
|
+
centroidFromBBox: () => centroidFromBBox,
|
|
62
|
+
clampNumber: () => clampNumber,
|
|
63
|
+
clampOpacity: () => clampOpacity3,
|
|
64
|
+
computeBBoxFromFeature: () => computeBBoxFromFeature,
|
|
65
|
+
createChatService: () => createChatService,
|
|
66
|
+
decorateFeaturesWithLayerId: () => decorateFeaturesWithLayerId,
|
|
67
|
+
extractMapDto: () => extractMapDto,
|
|
68
|
+
filterEngine: () => filterEngine,
|
|
69
|
+
getAccentByLayerId: () => getAccentByLayerId,
|
|
70
|
+
getCatalogSupport: () => getCatalogSupport,
|
|
71
|
+
getEffectiveLayerOpacity: () => getEffectiveLayerOpacity,
|
|
72
|
+
getLayerColor: () => getLayerColor,
|
|
73
|
+
getLayerZoomOpacityFactor: () => getLayerZoomOpacityFactor,
|
|
74
|
+
getStyleByLayerId: () => getStyleByLayerId,
|
|
75
|
+
getZoomOpacityFactor: () => getZoomOpacityFactor,
|
|
76
|
+
initLayerStates: () => initLayerStates,
|
|
77
|
+
isPolygonLayer: () => isPolygonLayer,
|
|
78
|
+
normalizeBbox: () => normalizeBbox,
|
|
79
|
+
normalizeMapCenter: () => normalizeMapCenter,
|
|
80
|
+
normalizeMapLayers: () => normalizeMapLayers,
|
|
81
|
+
resetOverrides: () => resetOverrides,
|
|
82
|
+
resolveLayerAccent: () => resolveLayerAccent,
|
|
83
|
+
resolveLayerStrategy: () => resolveLayerStrategy,
|
|
84
|
+
sendMessage: () => sendMessage,
|
|
85
|
+
sendMessageStream: () => sendMessageStream,
|
|
86
|
+
shouldSkipGeojsonDownload: () => shouldSkipGeojsonDownload,
|
|
87
|
+
shouldUseFilterMultiple: () => shouldUseFilterMultiple,
|
|
88
|
+
useSendMessage: () => useSendMessage,
|
|
89
|
+
useSendMessageStream: () => useSendMessageStream
|
|
90
|
+
});
|
|
91
|
+
module.exports = __toCommonJS(src_exports);
|
|
92
|
+
|
|
93
|
+
// src/http/HttpClient.ts
|
|
94
|
+
var HttpClient = class {
|
|
95
|
+
constructor(options) {
|
|
96
|
+
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
97
|
+
this.resolveTokens = options.resolveTokens;
|
|
98
|
+
this.resolveAuthorizationHeader = options.resolveAuthorizationHeader ?? (() => ({}));
|
|
99
|
+
}
|
|
100
|
+
async get(path, options = {}) {
|
|
101
|
+
const headers = {
|
|
102
|
+
...options.headers,
|
|
103
|
+
...this.resolveAuthorizationHeader()
|
|
104
|
+
};
|
|
105
|
+
return this.request(path, { ...options, method: "GET", headers });
|
|
106
|
+
}
|
|
107
|
+
async post(path, body, options = {}) {
|
|
108
|
+
const headers = {
|
|
109
|
+
"Content-Type": "application/json",
|
|
110
|
+
...options.headers,
|
|
111
|
+
...this.resolveAuthorizationHeader()
|
|
112
|
+
};
|
|
113
|
+
return this.request(path, {
|
|
114
|
+
...options,
|
|
115
|
+
method: "POST",
|
|
116
|
+
headers,
|
|
117
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
async put(path, body, options = {}) {
|
|
121
|
+
const headers = {
|
|
122
|
+
"Content-Type": "application/json",
|
|
123
|
+
...options.headers,
|
|
124
|
+
...this.resolveAuthorizationHeader()
|
|
125
|
+
};
|
|
126
|
+
return this.request(path, {
|
|
127
|
+
...options,
|
|
128
|
+
method: "PUT",
|
|
129
|
+
headers,
|
|
130
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
async patch(path, body, options = {}) {
|
|
134
|
+
const headers = {
|
|
135
|
+
"Content-Type": "application/json",
|
|
136
|
+
...options.headers,
|
|
137
|
+
...this.resolveAuthorizationHeader()
|
|
138
|
+
};
|
|
139
|
+
return this.request(path, {
|
|
140
|
+
...options,
|
|
141
|
+
method: "PATCH",
|
|
142
|
+
headers,
|
|
143
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
async delete(path, options = {}) {
|
|
147
|
+
const headers = {
|
|
148
|
+
...options.headers,
|
|
149
|
+
...this.resolveAuthorizationHeader()
|
|
150
|
+
};
|
|
151
|
+
return this.request(path, { ...options, method: "DELETE", headers });
|
|
152
|
+
}
|
|
153
|
+
async request(path, options) {
|
|
154
|
+
const url = `${this.baseUrl}${path}`;
|
|
155
|
+
const tokenState = this.resolveTokens?.();
|
|
156
|
+
const headers = {
|
|
157
|
+
...options.headers
|
|
158
|
+
};
|
|
159
|
+
if (!headers["Authorization"] && tokenState?.accessToken) {
|
|
160
|
+
headers["Authorization"] = `Bearer ${tokenState.accessToken}`;
|
|
161
|
+
}
|
|
162
|
+
if (tokenState?.sdkToken) {
|
|
163
|
+
headers["X-SDK-Token"] = tokenState.sdkToken;
|
|
164
|
+
}
|
|
165
|
+
const response = await fetch(url, {
|
|
166
|
+
...options,
|
|
167
|
+
headers
|
|
168
|
+
});
|
|
169
|
+
const contentType = response.headers.get("content-type");
|
|
170
|
+
const isJson = contentType?.includes("application/json");
|
|
171
|
+
const payload = isJson ? await response.json() : await response.text();
|
|
172
|
+
if (!response.ok) {
|
|
173
|
+
const normalizedError = {
|
|
174
|
+
success: false,
|
|
175
|
+
statusCode: response.status,
|
|
176
|
+
message: typeof payload === "string" ? payload : payload?.message || "Unknown error",
|
|
177
|
+
timestamp: typeof payload === "object" ? payload.timestamp : void 0,
|
|
178
|
+
path: typeof payload === "object" ? payload.path : void 0,
|
|
179
|
+
method: typeof payload === "object" ? payload.method : void 0,
|
|
180
|
+
error: typeof payload === "object" ? payload.error : void 0
|
|
181
|
+
};
|
|
182
|
+
throw normalizedError;
|
|
183
|
+
}
|
|
184
|
+
return payload;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// src/auth/ZenitAuthClient.ts
|
|
189
|
+
var ZenitAuthClient = class {
|
|
190
|
+
constructor(http, updateTokens, config) {
|
|
191
|
+
this.http = http;
|
|
192
|
+
this.updateTokens = updateTokens;
|
|
193
|
+
this.config = config;
|
|
194
|
+
}
|
|
195
|
+
async login(credentials) {
|
|
196
|
+
try {
|
|
197
|
+
const response = await this.http.post("/auth/login", credentials);
|
|
198
|
+
const accessToken = response?.accessToken ?? response?.data?.accessToken;
|
|
199
|
+
const refreshToken = response?.refreshToken ?? response?.data?.refreshToken;
|
|
200
|
+
this.updateTokens({ accessToken, refreshToken });
|
|
201
|
+
return response;
|
|
202
|
+
} catch (error) {
|
|
203
|
+
this.config.onAuthError?.(error);
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
async refresh(refreshToken) {
|
|
208
|
+
const tokenToUse = refreshToken || this.config.refreshToken;
|
|
209
|
+
const headers = tokenToUse ? { Authorization: `Bearer ${tokenToUse}` } : void 0;
|
|
210
|
+
const response = await this.http.post("/auth/refresh", void 0, { headers });
|
|
211
|
+
const accessToken = response?.accessToken ?? response?.data?.accessToken;
|
|
212
|
+
const nextRefreshToken = response?.refreshToken ?? response?.data?.refreshToken;
|
|
213
|
+
this.updateTokens({ accessToken, refreshToken: nextRefreshToken });
|
|
214
|
+
return response;
|
|
215
|
+
}
|
|
216
|
+
async me() {
|
|
217
|
+
return this.http.get("/auth/me");
|
|
218
|
+
}
|
|
219
|
+
async validate() {
|
|
220
|
+
return this.http.get("/auth/validate");
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// src/sdkAuth/ZenitSdkAuthClient.ts
|
|
225
|
+
var ZenitSdkAuthClient = class {
|
|
226
|
+
constructor(http, updateAccessToken, config) {
|
|
227
|
+
this.http = http;
|
|
228
|
+
this.updateAccessToken = updateAccessToken;
|
|
229
|
+
this.config = config;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Validate an SDK token. If no token argument is provided, it falls back to config.sdkToken.
|
|
233
|
+
*/
|
|
234
|
+
async validateSdkToken(token) {
|
|
235
|
+
const sdkToken = token || this.config.sdkToken;
|
|
236
|
+
return this.http.post("/sdk-auth/validate", { token: sdkToken });
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Exchange an SDK token for an access token. Token can come from method args or config.sdkToken.
|
|
240
|
+
*/
|
|
241
|
+
async exchangeSdkToken(token) {
|
|
242
|
+
const sdkToken = token || this.config.sdkToken;
|
|
243
|
+
const response = await this.http.post("/sdk-auth/exchange", { token: sdkToken });
|
|
244
|
+
const accessToken = response?.accessToken ?? response?.data?.accessToken;
|
|
245
|
+
this.updateAccessToken(accessToken);
|
|
246
|
+
return response;
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// src/maps/ZenitMapsClient.ts
|
|
251
|
+
var ZenitMapsClient = class {
|
|
252
|
+
constructor(httpClient) {
|
|
253
|
+
this.httpClient = httpClient;
|
|
254
|
+
}
|
|
255
|
+
async getMap(mapId, includeLayers = true) {
|
|
256
|
+
const include = includeLayers ? "?includeLayers=true" : "";
|
|
257
|
+
return this.httpClient.get(`/maps/${mapId}${include}`);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// src/layers/catalogSupport.ts
|
|
262
|
+
function isPolygonLike(value) {
|
|
263
|
+
if (!value) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
const normalized = value.toLowerCase();
|
|
267
|
+
return normalized === "polygon" || normalized === "multipolygon";
|
|
268
|
+
}
|
|
269
|
+
function getCatalogSupport(args) {
|
|
270
|
+
const { layerType, geometryType } = args;
|
|
271
|
+
if (layerType !== void 0) {
|
|
272
|
+
const supported = isPolygonLike(layerType);
|
|
273
|
+
return {
|
|
274
|
+
supported,
|
|
275
|
+
reason: supported ? "supported" : "unsupported-geometry",
|
|
276
|
+
layerType,
|
|
277
|
+
geometryType
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
if (geometryType !== void 0) {
|
|
281
|
+
const supported = isPolygonLike(geometryType);
|
|
282
|
+
return {
|
|
283
|
+
supported,
|
|
284
|
+
reason: supported ? "supported" : "unsupported-geometry",
|
|
285
|
+
geometryType
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
supported: false,
|
|
290
|
+
reason: "unknown-geometry"
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
var ZenitCatalogNotSupportedError = class extends Error {
|
|
294
|
+
constructor(support, message) {
|
|
295
|
+
super(message ?? "Catalog is not supported for this layer geometry");
|
|
296
|
+
this.name = "ZenitCatalogNotSupportedError";
|
|
297
|
+
this.support = support;
|
|
298
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// src/engine/PrefilterEngine.ts
|
|
303
|
+
var normalizeString = (value) => value.trim().toLowerCase();
|
|
304
|
+
var isPrefilterValue = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
305
|
+
var matchesPrefilterValue = (candidate, expected) => {
|
|
306
|
+
if (typeof expected === "string") {
|
|
307
|
+
if (candidate === null || candidate === void 0) return false;
|
|
308
|
+
return normalizeString(String(candidate)) === normalizeString(expected);
|
|
309
|
+
}
|
|
310
|
+
return candidate === expected;
|
|
311
|
+
};
|
|
312
|
+
var shouldApplyPrefilters = (prefilters) => !!prefilters && Object.keys(prefilters).length > 0;
|
|
313
|
+
var featureMatchesPrefilters = (feature, prefilters) => {
|
|
314
|
+
const properties = feature.properties ?? {};
|
|
315
|
+
return Object.entries(prefilters).every(([key, expected]) => {
|
|
316
|
+
if (!isPrefilterValue(expected)) return true;
|
|
317
|
+
return matchesPrefilterValue(properties[key], expected);
|
|
318
|
+
});
|
|
319
|
+
};
|
|
320
|
+
var applyPrefiltersToFeatureCollection = (featureCollection, prefilters) => {
|
|
321
|
+
if (!shouldApplyPrefilters(prefilters)) return featureCollection;
|
|
322
|
+
const nextFeatures = featureCollection.features.filter(
|
|
323
|
+
(feature) => featureMatchesPrefilters(feature, prefilters)
|
|
324
|
+
);
|
|
325
|
+
return {
|
|
326
|
+
...featureCollection,
|
|
327
|
+
features: nextFeatures
|
|
328
|
+
};
|
|
329
|
+
};
|
|
330
|
+
var decorateFeaturesWithLayerId = (featureCollection, layerId) => {
|
|
331
|
+
let shouldClone = false;
|
|
332
|
+
featureCollection.features.forEach((feature) => {
|
|
333
|
+
const existing = feature.properties?.__zenit_layerId;
|
|
334
|
+
if (existing !== layerId) {
|
|
335
|
+
shouldClone = true;
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
if (!shouldClone) {
|
|
339
|
+
return featureCollection;
|
|
340
|
+
}
|
|
341
|
+
return {
|
|
342
|
+
...featureCollection,
|
|
343
|
+
features: featureCollection.features.map((feature) => ({
|
|
344
|
+
...feature,
|
|
345
|
+
properties: {
|
|
346
|
+
...feature.properties ?? {},
|
|
347
|
+
__zenit_layerId: layerId
|
|
348
|
+
}
|
|
349
|
+
}))
|
|
350
|
+
};
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
// src/layers/ZenitLayersClient.ts
|
|
354
|
+
function parseQueryParams(options = {}) {
|
|
355
|
+
const params = new URLSearchParams();
|
|
356
|
+
if (options.maxFeatures !== void 0) {
|
|
357
|
+
params.set("maxFeatures", String(options.maxFeatures));
|
|
358
|
+
}
|
|
359
|
+
if (options.simplify !== void 0) {
|
|
360
|
+
params.set("simplify", String(options.simplify));
|
|
361
|
+
}
|
|
362
|
+
return params;
|
|
363
|
+
}
|
|
364
|
+
function serializeFilterValue(value) {
|
|
365
|
+
if (Array.isArray(value)) {
|
|
366
|
+
const serialized2 = value.map(String).filter((item) => item.trim().length > 0).join(",");
|
|
367
|
+
return serialized2.length > 0 ? serialized2 : void 0;
|
|
368
|
+
}
|
|
369
|
+
if (value === void 0 || value === null) {
|
|
370
|
+
return void 0;
|
|
371
|
+
}
|
|
372
|
+
const serialized = String(value);
|
|
373
|
+
return serialized.trim().length > 0 ? serialized : void 0;
|
|
374
|
+
}
|
|
375
|
+
function buildLayerFilters(filters) {
|
|
376
|
+
const query = new URLSearchParams();
|
|
377
|
+
if (!filters) {
|
|
378
|
+
return query;
|
|
379
|
+
}
|
|
380
|
+
Object.entries(filters).forEach(([key, value]) => {
|
|
381
|
+
const serialized = serializeFilterValue(value);
|
|
382
|
+
if (serialized !== void 0) {
|
|
383
|
+
query.set(key, serialized);
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
return query;
|
|
387
|
+
}
|
|
388
|
+
function buildFilterMultipleParams(params) {
|
|
389
|
+
const query = buildLayerFilters(params.filters);
|
|
390
|
+
if (!Array.isArray(params.layerIds) || params.layerIds.length === 0) {
|
|
391
|
+
throw new Error("layerIds is required and cannot be empty");
|
|
392
|
+
}
|
|
393
|
+
query.set("layerIds", params.layerIds.map((id) => String(id)).join(","));
|
|
394
|
+
return query;
|
|
395
|
+
}
|
|
396
|
+
function asOptionalNumber(value) {
|
|
397
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
398
|
+
}
|
|
399
|
+
function createEmptyFeatureCollection() {
|
|
400
|
+
return { type: "FeatureCollection", features: [] };
|
|
401
|
+
}
|
|
402
|
+
function hasFilters(filters) {
|
|
403
|
+
return buildLayerFilters(filters).size > 0;
|
|
404
|
+
}
|
|
405
|
+
function mergeLayerFilters(base, extra) {
|
|
406
|
+
if (!base && !extra) return void 0;
|
|
407
|
+
const merged = { ...base ?? {}, ...extra ?? {} };
|
|
408
|
+
return hasFilters(merged) ? merged : void 0;
|
|
409
|
+
}
|
|
410
|
+
var DEFAULT_MAX_FEATURES_FULL_GEOJSON = 5e4;
|
|
411
|
+
function normalizeLayerType(layerType) {
|
|
412
|
+
return typeof layerType === "string" ? layerType.toLowerCase() : void 0;
|
|
413
|
+
}
|
|
414
|
+
function shouldUseFilterMultiple(layerType) {
|
|
415
|
+
return normalizeLayerType(layerType) === "multipolygon";
|
|
416
|
+
}
|
|
417
|
+
function shouldSkipGeojsonDownload(layerType, totalFeatures, thresholds = {
|
|
418
|
+
maxFeatures: DEFAULT_MAX_FEATURES_FULL_GEOJSON
|
|
419
|
+
}) {
|
|
420
|
+
if (thresholds.allowLargeGeojson) {
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
const normalizedType = normalizeLayerType(layerType);
|
|
424
|
+
if (normalizedType === "mixed") {
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
427
|
+
if (typeof totalFeatures === "number" && totalFeatures > thresholds.maxFeatures) {
|
|
428
|
+
return true;
|
|
429
|
+
}
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
function parseBbox(value) {
|
|
433
|
+
if (!value || typeof value !== "object") {
|
|
434
|
+
return void 0;
|
|
435
|
+
}
|
|
436
|
+
const candidate = value;
|
|
437
|
+
if (typeof candidate.minLon === "number" && typeof candidate.minLat === "number" && typeof candidate.maxLon === "number" && typeof candidate.maxLat === "number") {
|
|
438
|
+
return {
|
|
439
|
+
minLon: candidate.minLon,
|
|
440
|
+
minLat: candidate.minLat,
|
|
441
|
+
maxLon: candidate.maxLon,
|
|
442
|
+
maxLat: candidate.maxLat
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
if (Array.isArray(value) && value.length === 4) {
|
|
446
|
+
const [minLon, minLat, maxLon, maxLat] = value;
|
|
447
|
+
if (typeof minLon === "number" && typeof minLat === "number" && typeof maxLon === "number" && typeof maxLat === "number") {
|
|
448
|
+
return { minLon, minLat, maxLon, maxLat };
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return void 0;
|
|
452
|
+
}
|
|
453
|
+
function buildBboxFromFeatureCollection(featureCollection) {
|
|
454
|
+
const coords = [];
|
|
455
|
+
const collect = (candidate) => {
|
|
456
|
+
if (!Array.isArray(candidate)) return;
|
|
457
|
+
if (candidate.length === 2 && typeof candidate[0] === "number" && typeof candidate[1] === "number" && Number.isFinite(candidate[0]) && Number.isFinite(candidate[1])) {
|
|
458
|
+
coords.push([candidate[0], candidate[1]]);
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
candidate.forEach((item) => collect(item));
|
|
462
|
+
};
|
|
463
|
+
featureCollection.features.forEach((feature) => {
|
|
464
|
+
collect(feature.geometry?.coordinates);
|
|
465
|
+
});
|
|
466
|
+
if (coords.length === 0) return void 0;
|
|
467
|
+
const [firstLon, firstLat] = coords[0];
|
|
468
|
+
const bbox = { minLon: firstLon, minLat: firstLat, maxLon: firstLon, maxLat: firstLat };
|
|
469
|
+
coords.forEach(([lon, lat]) => {
|
|
470
|
+
bbox.minLon = Math.min(bbox.minLon, lon);
|
|
471
|
+
bbox.minLat = Math.min(bbox.minLat, lat);
|
|
472
|
+
bbox.maxLon = Math.max(bbox.maxLon, lon);
|
|
473
|
+
bbox.maxLat = Math.max(bbox.maxLat, lat);
|
|
474
|
+
});
|
|
475
|
+
return bbox;
|
|
476
|
+
}
|
|
477
|
+
function buildGeometryCollectionFromFeatureCollection(featureCollection) {
|
|
478
|
+
return {
|
|
479
|
+
type: "GeometryCollection",
|
|
480
|
+
geometries: featureCollection.features.map((feature) => feature.geometry).filter((geometry) => geometry !== void 0 && geometry !== null)
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
function buildAoiFromFeatureCollection(featureCollection, metadata) {
|
|
484
|
+
const metadataBbox = parseBbox(metadata?.bbox);
|
|
485
|
+
const derivedBbox = metadataBbox ?? buildBboxFromFeatureCollection(featureCollection);
|
|
486
|
+
const hasFeatures = featureCollection.features.length > 0;
|
|
487
|
+
const geometry = hasFeatures ? buildGeometryCollectionFromFeatureCollection(featureCollection) : void 0;
|
|
488
|
+
if (!derivedBbox && !geometry) {
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
491
|
+
return { bbox: derivedBbox, geometry };
|
|
492
|
+
}
|
|
493
|
+
function splitFeatureCollectionByLayerId(featureCollection, layerIds) {
|
|
494
|
+
const grouped = {};
|
|
495
|
+
layerIds.forEach((layerId) => {
|
|
496
|
+
grouped[String(layerId)] = createEmptyFeatureCollection();
|
|
497
|
+
});
|
|
498
|
+
featureCollection.features.forEach((feature) => {
|
|
499
|
+
const properties = feature.properties;
|
|
500
|
+
const layerId = properties?.layerId ?? properties?.layer_id ?? properties?.__zenit_layerId;
|
|
501
|
+
if (layerId === void 0 || layerId === null) {
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
const key = String(layerId);
|
|
505
|
+
if (!grouped[key]) {
|
|
506
|
+
grouped[key] = createEmptyFeatureCollection();
|
|
507
|
+
}
|
|
508
|
+
grouped[key] = {
|
|
509
|
+
...grouped[key],
|
|
510
|
+
features: [...grouped[key].features, feature]
|
|
511
|
+
};
|
|
512
|
+
});
|
|
513
|
+
return grouped;
|
|
514
|
+
}
|
|
515
|
+
function resolveLayerStrategy(params) {
|
|
516
|
+
const {
|
|
517
|
+
layerDetail,
|
|
518
|
+
bbox,
|
|
519
|
+
filterBBox,
|
|
520
|
+
filteredGeoJSON,
|
|
521
|
+
isVisible = true,
|
|
522
|
+
maxFeaturesFullGeojson,
|
|
523
|
+
allowLargeGeojson
|
|
524
|
+
} = params;
|
|
525
|
+
if (!isVisible) {
|
|
526
|
+
return { strategy: "geojson", effectiveBBox: bbox };
|
|
527
|
+
}
|
|
528
|
+
if (filteredGeoJSON && layerDetail.layerType !== "multipolygon") {
|
|
529
|
+
return {
|
|
530
|
+
strategy: "intersect",
|
|
531
|
+
intersectionGeometry: buildGeometryCollectionFromFeatureCollection(filteredGeoJSON)
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
const shouldSkipGeojson = shouldSkipGeojsonDownload(layerDetail.layerType, layerDetail.totalFeatures, {
|
|
535
|
+
maxFeatures: maxFeaturesFullGeojson ?? DEFAULT_MAX_FEATURES_FULL_GEOJSON,
|
|
536
|
+
allowLargeGeojson
|
|
537
|
+
});
|
|
538
|
+
if (shouldSkipGeojson && (filterBBox ?? bbox)) {
|
|
539
|
+
return { strategy: "bbox", effectiveBBox: filterBBox ?? bbox };
|
|
540
|
+
}
|
|
541
|
+
if ((layerDetail.totalFeatures ?? 0) > 5e3 && (filterBBox ?? bbox)) {
|
|
542
|
+
return { strategy: "bbox", effectiveBBox: filterBBox ?? bbox };
|
|
543
|
+
}
|
|
544
|
+
return { strategy: "geojson", effectiveBBox: bbox };
|
|
545
|
+
}
|
|
546
|
+
function buildLimitedMessage(metadata) {
|
|
547
|
+
if (!metadata) {
|
|
548
|
+
return void 0;
|
|
549
|
+
}
|
|
550
|
+
const explicit = metadata.limitedMessage;
|
|
551
|
+
if (typeof explicit === "string" && explicit.trim().length > 0) {
|
|
552
|
+
return explicit;
|
|
553
|
+
}
|
|
554
|
+
const limit = typeof metadata.limit === "number" ? metadata.limit : void 0;
|
|
555
|
+
const total = typeof metadata.totalFeatures === "number" ? metadata.totalFeatures : void 0;
|
|
556
|
+
if (limit !== void 0 && total !== void 0 && total > limit) {
|
|
557
|
+
return `Mostrando ${limit.toLocaleString()} de ${total.toLocaleString()} elementos por l\xEDmite de capa.`;
|
|
558
|
+
}
|
|
559
|
+
if (metadata.limited && limit !== void 0) {
|
|
560
|
+
return `Mostrando un m\xE1ximo de ${limit.toLocaleString()} elementos por l\xEDmite de capa.`;
|
|
561
|
+
}
|
|
562
|
+
return void 0;
|
|
563
|
+
}
|
|
564
|
+
var ZenitLayersClient = class {
|
|
565
|
+
constructor(httpClient) {
|
|
566
|
+
this.httpClient = httpClient;
|
|
567
|
+
}
|
|
568
|
+
async getLayer(layerId) {
|
|
569
|
+
const response = await this.httpClient.get(
|
|
570
|
+
`/layers/${layerId}`
|
|
571
|
+
);
|
|
572
|
+
return this.unwrapResponse(response);
|
|
573
|
+
}
|
|
574
|
+
async getLayerGeoJson(layerId, options = {}) {
|
|
575
|
+
const params = parseQueryParams(options);
|
|
576
|
+
const suffix = params.size > 0 ? `?${params.toString()}` : "";
|
|
577
|
+
const response = await this.httpClient.get(
|
|
578
|
+
`/layers/${layerId}/geojson${suffix}`
|
|
579
|
+
);
|
|
580
|
+
return this.unwrapResponse(response);
|
|
581
|
+
}
|
|
582
|
+
async getLayerFeaturesCatalog(layerId, options) {
|
|
583
|
+
if (options?.strict) {
|
|
584
|
+
const support = getCatalogSupport({
|
|
585
|
+
layerType: options.layerType,
|
|
586
|
+
geometryType: options.geometryType
|
|
587
|
+
});
|
|
588
|
+
if (!support.supported) {
|
|
589
|
+
throw new ZenitCatalogNotSupportedError(
|
|
590
|
+
support,
|
|
591
|
+
support.reason === "unknown-geometry" ? "Catalog support cannot be determined without a geometry type" : "Catalog is only supported for polygon or multipolygon geometries"
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
const response = await this.httpClient.get(
|
|
596
|
+
`/layers/${layerId}/features/catalog`
|
|
597
|
+
);
|
|
598
|
+
return this.unwrapResponse(response);
|
|
599
|
+
}
|
|
600
|
+
async getLayerFeatures(layerId, featureIds) {
|
|
601
|
+
if (!Array.isArray(featureIds) || featureIds.length === 0) {
|
|
602
|
+
throw new Error("featureIds is required and cannot be empty");
|
|
603
|
+
}
|
|
604
|
+
const query = featureIds.map(String).join(",");
|
|
605
|
+
const response = await this.httpClient.get(
|
|
606
|
+
`/layers/${layerId}/features?featureIds=${encodeURIComponent(query)}`
|
|
607
|
+
);
|
|
608
|
+
return this.unwrapResponse(response);
|
|
609
|
+
}
|
|
610
|
+
async filterLayerFeatures(layerId, filters) {
|
|
611
|
+
const query = buildLayerFilters(filters);
|
|
612
|
+
const suffix = query.toString();
|
|
613
|
+
const response = await this.httpClient.get(
|
|
614
|
+
`/layers/${layerId}/features/filter${suffix ? `?${suffix}` : ""}`
|
|
615
|
+
);
|
|
616
|
+
return this.unwrapResponse(response);
|
|
617
|
+
}
|
|
618
|
+
async filterMultipleLayersFeatures(params) {
|
|
619
|
+
const query = buildFilterMultipleParams(params);
|
|
620
|
+
const suffix = query.toString();
|
|
621
|
+
const response = await this.httpClient.get(`/layers/features/filter-multiple${suffix ? `?${suffix}` : ""}`);
|
|
622
|
+
return this.unwrapResponse(response);
|
|
623
|
+
}
|
|
624
|
+
async filterMultiple(params) {
|
|
625
|
+
return this.filterMultipleLayersFeatures(params);
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Resilient filter-multiple helper:
|
|
629
|
+
* - Usa filter-multiple SOLO en capas multipolygon.
|
|
630
|
+
* - Para otras capas aplica filterLayerFeatures o getLayerGeoJson según haya filtros.
|
|
631
|
+
* - Nunca falla toda la operación por una capa inválida.
|
|
632
|
+
*
|
|
633
|
+
* Para mejor detección de layerType, pasa layerMetas con el layerType de cada capa.
|
|
634
|
+
*/
|
|
635
|
+
async filterMultipleWithFallback(params) {
|
|
636
|
+
const layerIds = params.layerIds ?? [];
|
|
637
|
+
const perLayer = {};
|
|
638
|
+
const warnings = [];
|
|
639
|
+
const hasLayerMetas = (params.layerMetas ?? []).length > 0;
|
|
640
|
+
const maxFeaturesFullGeojson = params.maxFeaturesFullGeojson ?? DEFAULT_MAX_FEATURES_FULL_GEOJSON;
|
|
641
|
+
const allowLargeGeojson = params.allowLargeGeojson ?? false;
|
|
642
|
+
layerIds.forEach((layerId) => {
|
|
643
|
+
perLayer[String(layerId)] = createEmptyFeatureCollection();
|
|
644
|
+
});
|
|
645
|
+
if (layerIds.length === 0) {
|
|
646
|
+
return { perLayer };
|
|
647
|
+
}
|
|
648
|
+
const globalFilters = hasFilters(params.filters) ? params.filters : void 0;
|
|
649
|
+
const metaIndex = /* @__PURE__ */ new Map();
|
|
650
|
+
(params.layerMetas ?? []).forEach((meta) => {
|
|
651
|
+
metaIndex.set(String(meta.layerId), meta);
|
|
652
|
+
});
|
|
653
|
+
let multipolygonIds = [];
|
|
654
|
+
let otherIds = [];
|
|
655
|
+
const layerIdsWithData = /* @__PURE__ */ new Set();
|
|
656
|
+
let aoi = null;
|
|
657
|
+
const pushWarning = (message) => {
|
|
658
|
+
warnings.push(message);
|
|
659
|
+
console.warn(message);
|
|
660
|
+
};
|
|
661
|
+
const buildSkipWarning = (layerId, layerType, totalFeatures, reason) => {
|
|
662
|
+
const typeLabel = layerType ?? "unknown";
|
|
663
|
+
const totalLabel = typeof totalFeatures === "number" ? ` totalFeatures=${totalFeatures}` : "";
|
|
664
|
+
const reasonLabel = reason ? ` ${reason}` : "";
|
|
665
|
+
return `Layer ${layerId} skipped: layerType=${typeLabel}${totalLabel}${reasonLabel}`;
|
|
666
|
+
};
|
|
667
|
+
if (hasLayerMetas) {
|
|
668
|
+
layerIds.forEach((layerId) => {
|
|
669
|
+
const meta = metaIndex.get(String(layerId));
|
|
670
|
+
if (shouldUseFilterMultiple(meta?.layerType)) {
|
|
671
|
+
multipolygonIds.push(layerId);
|
|
672
|
+
} else {
|
|
673
|
+
otherIds.push(layerId);
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
} else {
|
|
677
|
+
multipolygonIds = [...layerIds];
|
|
678
|
+
}
|
|
679
|
+
let metadata;
|
|
680
|
+
if (multipolygonIds.length > 0) {
|
|
681
|
+
try {
|
|
682
|
+
const response = await this.filterMultipleLayersFeatures({
|
|
683
|
+
layerIds: multipolygonIds,
|
|
684
|
+
filters: globalFilters
|
|
685
|
+
});
|
|
686
|
+
metadata = response.metadata;
|
|
687
|
+
const geojson = response.data ?? createEmptyFeatureCollection();
|
|
688
|
+
if (globalFilters) {
|
|
689
|
+
aoi = buildAoiFromFeatureCollection(geojson, metadata);
|
|
690
|
+
}
|
|
691
|
+
const perLayerFromMultiple = splitFeatureCollectionByLayerId(geojson, layerIds);
|
|
692
|
+
Object.entries(perLayerFromMultiple).forEach(([layerKey, collection]) => {
|
|
693
|
+
if (collection.features.length === 0) {
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
perLayer[layerKey] = decorateFeaturesWithLayerId(collection, layerKey);
|
|
697
|
+
layerIdsWithData.add(layerKey);
|
|
698
|
+
});
|
|
699
|
+
} catch {
|
|
700
|
+
if (hasLayerMetas) {
|
|
701
|
+
otherIds = [.../* @__PURE__ */ new Set([...otherIds, ...multipolygonIds])];
|
|
702
|
+
} else {
|
|
703
|
+
otherIds = [...layerIds];
|
|
704
|
+
}
|
|
705
|
+
multipolygonIds = [];
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
if (otherIds.length > 0) {
|
|
709
|
+
const pendingIds = otherIds.filter(
|
|
710
|
+
(layerId) => !layerIdsWithData.has(String(layerId))
|
|
711
|
+
);
|
|
712
|
+
const resolved = await Promise.all(
|
|
713
|
+
pendingIds.map(async (layerId) => {
|
|
714
|
+
const meta = metaIndex.get(String(layerId));
|
|
715
|
+
const layerType = meta?.layerType;
|
|
716
|
+
const totalFeatures = meta?.totalFeatures;
|
|
717
|
+
const layerFilters = mergeLayerFilters(globalFilters, meta?.prefilters);
|
|
718
|
+
const canUseIntersect = !!globalFilters && !shouldUseFilterMultiple(layerType) && !!aoi?.geometry;
|
|
719
|
+
if (canUseIntersect) {
|
|
720
|
+
const intersectionGeometry = aoi?.geometry;
|
|
721
|
+
if (!intersectionGeometry) {
|
|
722
|
+
pushWarning(
|
|
723
|
+
buildSkipWarning(
|
|
724
|
+
layerId,
|
|
725
|
+
layerType,
|
|
726
|
+
totalFeatures,
|
|
727
|
+
"no AOI geometry available"
|
|
728
|
+
)
|
|
729
|
+
);
|
|
730
|
+
return { layerId, geojson: createEmptyFeatureCollection() };
|
|
731
|
+
}
|
|
732
|
+
try {
|
|
733
|
+
const response = await this.getLayerGeoJsonIntersect({
|
|
734
|
+
id: layerId,
|
|
735
|
+
geometry: intersectionGeometry
|
|
736
|
+
});
|
|
737
|
+
if (aoi?.bbox) {
|
|
738
|
+
console.debug(
|
|
739
|
+
`Layer ${layerId} fetched by intersect using AOI bbox=${JSON.stringify(
|
|
740
|
+
aoi.bbox
|
|
741
|
+
)}`
|
|
742
|
+
);
|
|
743
|
+
} else {
|
|
744
|
+
console.debug(`Layer ${layerId} fetched by intersect using AOI geometry`);
|
|
745
|
+
}
|
|
746
|
+
const geojson = response.data ?? createEmptyFeatureCollection();
|
|
747
|
+
return {
|
|
748
|
+
layerId,
|
|
749
|
+
geojson: decorateFeaturesWithLayerId(geojson, layerId)
|
|
750
|
+
};
|
|
751
|
+
} catch (error) {
|
|
752
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
753
|
+
pushWarning(
|
|
754
|
+
buildSkipWarning(
|
|
755
|
+
layerId,
|
|
756
|
+
layerType,
|
|
757
|
+
totalFeatures,
|
|
758
|
+
`intersect failed: ${message}`
|
|
759
|
+
)
|
|
760
|
+
);
|
|
761
|
+
return { layerId, geojson: createEmptyFeatureCollection() };
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
if (layerFilters && (hasLayerMetas ? shouldUseFilterMultiple(layerType) : true)) {
|
|
765
|
+
try {
|
|
766
|
+
const response = await this.filterLayerFeatures(layerId, layerFilters);
|
|
767
|
+
const geojson = response.data ?? createEmptyFeatureCollection();
|
|
768
|
+
return {
|
|
769
|
+
layerId,
|
|
770
|
+
geojson: decorateFeaturesWithLayerId(geojson, layerId)
|
|
771
|
+
};
|
|
772
|
+
} catch (error) {
|
|
773
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
774
|
+
pushWarning(
|
|
775
|
+
`Layer ${layerId} filter failed: ${message}`
|
|
776
|
+
);
|
|
777
|
+
return { layerId, geojson: createEmptyFeatureCollection() };
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
if (shouldSkipGeojsonDownload(layerType, totalFeatures, {
|
|
781
|
+
maxFeatures: maxFeaturesFullGeojson,
|
|
782
|
+
allowLargeGeojson
|
|
783
|
+
})) {
|
|
784
|
+
pushWarning(
|
|
785
|
+
buildSkipWarning(
|
|
786
|
+
layerId,
|
|
787
|
+
layerType,
|
|
788
|
+
totalFeatures,
|
|
789
|
+
"not supported by filter endpoint and too large for /geojson; use intersect with AOI"
|
|
790
|
+
)
|
|
791
|
+
);
|
|
792
|
+
return { layerId, geojson: createEmptyFeatureCollection() };
|
|
793
|
+
}
|
|
794
|
+
try {
|
|
795
|
+
const response = await this.getLayerGeoJson(layerId);
|
|
796
|
+
const geojson = response.data ?? createEmptyFeatureCollection();
|
|
797
|
+
return {
|
|
798
|
+
layerId,
|
|
799
|
+
geojson: decorateFeaturesWithLayerId(geojson, layerId)
|
|
800
|
+
};
|
|
801
|
+
} catch (error) {
|
|
802
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
803
|
+
pushWarning(
|
|
804
|
+
buildSkipWarning(layerId, layerType, totalFeatures, `error=${message}`)
|
|
805
|
+
);
|
|
806
|
+
return { layerId, geojson: createEmptyFeatureCollection() };
|
|
807
|
+
}
|
|
808
|
+
})
|
|
809
|
+
);
|
|
810
|
+
resolved.forEach(({ layerId, geojson }) => {
|
|
811
|
+
perLayer[String(layerId)] = geojson;
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
const mergedMetadata = warnings.length > 0 ? {
|
|
815
|
+
...metadata ?? {},
|
|
816
|
+
warnings: [...metadata?.warnings ?? [], ...warnings]
|
|
817
|
+
} : metadata;
|
|
818
|
+
return { perLayer, metadata: mergedMetadata, aoi };
|
|
819
|
+
}
|
|
820
|
+
async getLayerGeoJsonBBox(request) {
|
|
821
|
+
const params = new URLSearchParams({
|
|
822
|
+
minLon: String(request.bbox.minLon),
|
|
823
|
+
minLat: String(request.bbox.minLat),
|
|
824
|
+
maxLon: String(request.bbox.maxLon),
|
|
825
|
+
maxLat: String(request.bbox.maxLat)
|
|
826
|
+
});
|
|
827
|
+
parseQueryParams(request).forEach((value, key) => params.set(key, value));
|
|
828
|
+
const response = await this.httpClient.get(
|
|
829
|
+
`/layers/${request.id}/geojson/bbox?${params.toString()}`
|
|
830
|
+
);
|
|
831
|
+
return this.unwrapResponse(response);
|
|
832
|
+
}
|
|
833
|
+
async getLayerGeoJsonIntersect(request) {
|
|
834
|
+
const params = parseQueryParams(request);
|
|
835
|
+
const suffix = params.size > 0 ? `?${params.toString()}` : "";
|
|
836
|
+
const response = await this.httpClient.post(
|
|
837
|
+
`/layers/${request.id}/geojson/intersects${suffix}`,
|
|
838
|
+
{ geometry: request.geometry }
|
|
839
|
+
);
|
|
840
|
+
return this.unwrapResponse(response);
|
|
841
|
+
}
|
|
842
|
+
async getLayerTileTemplate(layerId) {
|
|
843
|
+
const response = await this.httpClient.get(
|
|
844
|
+
`/layers/${layerId}/tiles/template`
|
|
845
|
+
);
|
|
846
|
+
const unwrapped = this.unwrapResponse(response);
|
|
847
|
+
if (typeof unwrapped.data === "string") {
|
|
848
|
+
return unwrapped.data;
|
|
849
|
+
}
|
|
850
|
+
if (unwrapped.data && typeof unwrapped.data.template === "string") {
|
|
851
|
+
return unwrapped.data.template;
|
|
852
|
+
}
|
|
853
|
+
throw new Error("Invalid tile template response");
|
|
854
|
+
}
|
|
855
|
+
async loadLayerData(params) {
|
|
856
|
+
const { id, layerDetail, bbox, filterBBox, filteredGeoJSON, isVisible = true, ...options } = params;
|
|
857
|
+
const resolution = resolveLayerStrategy({
|
|
858
|
+
layerDetail,
|
|
859
|
+
bbox,
|
|
860
|
+
filterBBox,
|
|
861
|
+
filteredGeoJSON,
|
|
862
|
+
isVisible,
|
|
863
|
+
maxFeaturesFullGeojson: params.maxFeaturesFullGeojson,
|
|
864
|
+
allowLargeGeojson: params.allowLargeGeojson
|
|
865
|
+
});
|
|
866
|
+
const response = await this.fetchByStrategy(id, resolution, options);
|
|
867
|
+
const totalFeatures = asOptionalNumber(response.totalFeatures) ?? asOptionalNumber(response.metadata?.totalFeatures) ?? asOptionalNumber(layerDetail.totalFeatures);
|
|
868
|
+
const limitedMessage = response.limitedMessage ?? buildLimitedMessage(response.metadata);
|
|
869
|
+
return {
|
|
870
|
+
geojson: response.data,
|
|
871
|
+
strategy: resolution.strategy,
|
|
872
|
+
limitedMessage,
|
|
873
|
+
totalFeatures,
|
|
874
|
+
metadata: response.metadata
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
async loadFilteredFeatures(params) {
|
|
878
|
+
const response = await this.filterMultipleLayersFeatures(params);
|
|
879
|
+
return {
|
|
880
|
+
geojson: response.data,
|
|
881
|
+
metadata: response.metadata,
|
|
882
|
+
totalFeatures: response.metadata?.totalFeatures ?? response.totalFeatures
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
async fetchByStrategy(layerId, resolution, options) {
|
|
886
|
+
switch (resolution.strategy) {
|
|
887
|
+
case "bbox": {
|
|
888
|
+
if (!resolution.effectiveBBox) {
|
|
889
|
+
throw new Error("Bounding box is required for bbox strategy");
|
|
890
|
+
}
|
|
891
|
+
return this.getLayerGeoJsonBBox({ id: layerId, bbox: resolution.effectiveBBox, ...options });
|
|
892
|
+
}
|
|
893
|
+
case "intersect": {
|
|
894
|
+
if (!resolution.intersectionGeometry) {
|
|
895
|
+
throw new Error("Geometry is required for intersect strategy");
|
|
896
|
+
}
|
|
897
|
+
return this.getLayerGeoJsonIntersect({
|
|
898
|
+
id: layerId,
|
|
899
|
+
geometry: resolution.intersectionGeometry,
|
|
900
|
+
...options
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
case "geojson":
|
|
904
|
+
default:
|
|
905
|
+
return this.getLayerGeoJson(layerId, options);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
unwrapResponse(response) {
|
|
909
|
+
if (!response || typeof response !== "object" || !("data" in response)) {
|
|
910
|
+
throw new Error("Invalid API response: missing data field");
|
|
911
|
+
}
|
|
912
|
+
if ("success" in response && response.success === false) {
|
|
913
|
+
throw new Error(response.message ?? "Request was not successful");
|
|
914
|
+
}
|
|
915
|
+
return response;
|
|
916
|
+
}
|
|
917
|
+
};
|
|
918
|
+
|
|
919
|
+
// src/config/ZenitSdkConfig.ts
|
|
920
|
+
var ZenitClient = class {
|
|
921
|
+
constructor(config) {
|
|
922
|
+
this.config = config;
|
|
923
|
+
this.accessToken = config.accessToken;
|
|
924
|
+
this.refreshToken = config.refreshToken;
|
|
925
|
+
this.sdkToken = config.sdkToken;
|
|
926
|
+
this.httpClient = new HttpClient({
|
|
927
|
+
baseUrl: config.baseUrl,
|
|
928
|
+
resolveTokens: () => ({ accessToken: this.accessToken, sdkToken: this.sdkToken }),
|
|
929
|
+
resolveAuthorizationHeader: this.getAuthorizationHeader.bind(this)
|
|
930
|
+
});
|
|
931
|
+
this.auth = new ZenitAuthClient(this.httpClient, this.updateTokens.bind(this), config);
|
|
932
|
+
this.sdkAuth = new ZenitSdkAuthClient(
|
|
933
|
+
this.httpClient,
|
|
934
|
+
this.updateAccessTokenFromSdkExchange.bind(this),
|
|
935
|
+
config
|
|
936
|
+
);
|
|
937
|
+
this.maps = new ZenitMapsClient(this.httpClient);
|
|
938
|
+
this.layers = new ZenitLayersClient(this.httpClient);
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Update tokens in memory and propagate to config callbacks.
|
|
942
|
+
*/
|
|
943
|
+
updateTokens(tokens) {
|
|
944
|
+
if (tokens.accessToken) {
|
|
945
|
+
this.setAccessToken(tokens.accessToken);
|
|
946
|
+
}
|
|
947
|
+
if (tokens.refreshToken) {
|
|
948
|
+
this.setRefreshToken(tokens.refreshToken);
|
|
949
|
+
}
|
|
950
|
+
this.config.onTokenRefreshed?.({
|
|
951
|
+
accessToken: this.accessToken || "",
|
|
952
|
+
refreshToken: this.refreshToken
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
// Se usa cuando /sdk-auth/exchange devuelve un accessToken; pasa a ser el accessToken principal del SDK.
|
|
956
|
+
updateAccessTokenFromSdkExchange(token) {
|
|
957
|
+
if (token) {
|
|
958
|
+
this.setAccessToken(token);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
setAccessToken(token) {
|
|
962
|
+
this.accessToken = token;
|
|
963
|
+
this.config.accessToken = token;
|
|
964
|
+
}
|
|
965
|
+
setRefreshToken(token) {
|
|
966
|
+
this.refreshToken = token;
|
|
967
|
+
this.config.refreshToken = token;
|
|
968
|
+
}
|
|
969
|
+
getAuthorizationHeader() {
|
|
970
|
+
const header = {};
|
|
971
|
+
if (this.accessToken) {
|
|
972
|
+
header.Authorization = `Bearer ${this.accessToken}`;
|
|
973
|
+
}
|
|
974
|
+
return header;
|
|
975
|
+
}
|
|
976
|
+
getHttpClient() {
|
|
977
|
+
return this.httpClient;
|
|
978
|
+
}
|
|
979
|
+
};
|
|
980
|
+
|
|
981
|
+
// src/maps/helpers.ts
|
|
982
|
+
function toNumber(value) {
|
|
983
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
984
|
+
return value;
|
|
985
|
+
}
|
|
986
|
+
if (typeof value === "string") {
|
|
987
|
+
const parsed = parseFloat(value);
|
|
988
|
+
if (Number.isFinite(parsed)) {
|
|
989
|
+
return parsed;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
return void 0;
|
|
993
|
+
}
|
|
994
|
+
function clampOpacity(value) {
|
|
995
|
+
if (!Number.isFinite(value ?? NaN)) {
|
|
996
|
+
return 1;
|
|
997
|
+
}
|
|
998
|
+
return Math.min(1, Math.max(0, value ?? 1));
|
|
999
|
+
}
|
|
1000
|
+
function resolveLayerId(mapLayer) {
|
|
1001
|
+
if (mapLayer.layerId !== void 0 && mapLayer.layerId !== null) {
|
|
1002
|
+
return mapLayer.layerId;
|
|
1003
|
+
}
|
|
1004
|
+
const embedded = mapLayer.layer;
|
|
1005
|
+
return embedded?.id;
|
|
1006
|
+
}
|
|
1007
|
+
function extractMapDto(map) {
|
|
1008
|
+
if (map && typeof map === "object" && "data" in map) {
|
|
1009
|
+
const data = map.data;
|
|
1010
|
+
return data ?? null;
|
|
1011
|
+
}
|
|
1012
|
+
if (map && typeof map === "object") {
|
|
1013
|
+
return map;
|
|
1014
|
+
}
|
|
1015
|
+
return null;
|
|
1016
|
+
}
|
|
1017
|
+
function normalizeMapCenter(map) {
|
|
1018
|
+
const center = map.settings?.center;
|
|
1019
|
+
if (Array.isArray(center) && center.length >= 2) {
|
|
1020
|
+
const [lon, lat] = center;
|
|
1021
|
+
return {
|
|
1022
|
+
...map,
|
|
1023
|
+
settings: {
|
|
1024
|
+
...map.settings,
|
|
1025
|
+
center: { lon, lat }
|
|
1026
|
+
}
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
return map;
|
|
1030
|
+
}
|
|
1031
|
+
function normalizeMapLayers(mapOrResponse) {
|
|
1032
|
+
const map = extractMapDto(mapOrResponse);
|
|
1033
|
+
if (!map?.mapLayers) {
|
|
1034
|
+
return [];
|
|
1035
|
+
}
|
|
1036
|
+
const normalized = [];
|
|
1037
|
+
map.mapLayers.forEach((mapLayer, index) => {
|
|
1038
|
+
const layerId = resolveLayerId(mapLayer);
|
|
1039
|
+
if (layerId === void 0) {
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
const embeddedLayer = mapLayer.layer;
|
|
1043
|
+
const visible = mapLayer.isVisible !== false;
|
|
1044
|
+
const opacity = clampOpacity(toNumber(mapLayer.opacity) ?? 1);
|
|
1045
|
+
const layerType = embeddedLayer?.layerType ?? mapLayer.layerType;
|
|
1046
|
+
const geometryType = embeddedLayer?.geometryType ?? mapLayer.geometryType;
|
|
1047
|
+
const layerName = embeddedLayer?.name ?? mapLayer.name;
|
|
1048
|
+
const orderCandidate = toNumber(mapLayer.displayOrder) ?? toNumber(mapLayer.order) ?? toNumber(embeddedLayer?.displayOrder) ?? index;
|
|
1049
|
+
normalized.push({
|
|
1050
|
+
layerId,
|
|
1051
|
+
displayOrder: orderCandidate ?? index,
|
|
1052
|
+
isVisible: visible,
|
|
1053
|
+
opacity,
|
|
1054
|
+
layer: embeddedLayer,
|
|
1055
|
+
layerType,
|
|
1056
|
+
geometryType,
|
|
1057
|
+
layerName,
|
|
1058
|
+
mapLayer
|
|
1059
|
+
});
|
|
1060
|
+
});
|
|
1061
|
+
return normalized.sort((a, b) => a.displayOrder - b.displayOrder);
|
|
1062
|
+
}
|
|
1063
|
+
function computeBBoxFromFeature(feature) {
|
|
1064
|
+
if (!feature || typeof feature !== "object") return null;
|
|
1065
|
+
const geometry = feature.geometry;
|
|
1066
|
+
if (!geometry || !("coordinates" in geometry)) return null;
|
|
1067
|
+
const coords = [];
|
|
1068
|
+
const collect = (candidate) => {
|
|
1069
|
+
if (!Array.isArray(candidate)) return;
|
|
1070
|
+
if (candidate.length === 2 && typeof candidate[0] === "number" && typeof candidate[1] === "number") {
|
|
1071
|
+
coords.push([candidate[0], candidate[1]]);
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
candidate.forEach((entry) => collect(entry));
|
|
1075
|
+
};
|
|
1076
|
+
collect(geometry.coordinates);
|
|
1077
|
+
if (coords.length === 0) return null;
|
|
1078
|
+
const [firstLon, firstLat] = coords[0];
|
|
1079
|
+
const bbox = { minLon: firstLon, minLat: firstLat, maxLon: firstLon, maxLat: firstLat };
|
|
1080
|
+
coords.forEach(([lon, lat]) => {
|
|
1081
|
+
bbox.minLon = Math.min(bbox.minLon, lon);
|
|
1082
|
+
bbox.minLat = Math.min(bbox.minLat, lat);
|
|
1083
|
+
bbox.maxLon = Math.max(bbox.maxLon, lon);
|
|
1084
|
+
bbox.maxLat = Math.max(bbox.maxLat, lat);
|
|
1085
|
+
});
|
|
1086
|
+
return bbox;
|
|
1087
|
+
}
|
|
1088
|
+
function centroidFromBBox(bbox) {
|
|
1089
|
+
return [(bbox.minLat + bbox.maxLat) / 2, (bbox.minLon + bbox.maxLon) / 2];
|
|
1090
|
+
}
|
|
1091
|
+
function applyFilteredGeoJSONStrategy(options) {
|
|
1092
|
+
const layerControls = options.normalizedLayers.map((entry) => ({
|
|
1093
|
+
layerId: entry.layerId,
|
|
1094
|
+
visible: entry.isVisible,
|
|
1095
|
+
opacity: entry.opacity
|
|
1096
|
+
}));
|
|
1097
|
+
return {
|
|
1098
|
+
overlay: options.filteredGeojson,
|
|
1099
|
+
metadata: options.metadata,
|
|
1100
|
+
layerControls
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// src/engine/FilterEngine.ts
|
|
1105
|
+
var FilterEngine = class {
|
|
1106
|
+
decide(params) {
|
|
1107
|
+
if (params.request) {
|
|
1108
|
+
return { nextRequest: params.request };
|
|
1109
|
+
}
|
|
1110
|
+
if (params.filters && params.layerIds && params.layerIds.length > 1) {
|
|
1111
|
+
return {
|
|
1112
|
+
nextRequest: {
|
|
1113
|
+
type: "filter-multiple",
|
|
1114
|
+
layerIds: params.layerIds,
|
|
1115
|
+
filters: params.filters
|
|
1116
|
+
},
|
|
1117
|
+
reason: "filters+layerIds"
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
if (params.filters && params.layerId !== void 0) {
|
|
1121
|
+
return {
|
|
1122
|
+
nextRequest: {
|
|
1123
|
+
type: "filter-layer",
|
|
1124
|
+
layerId: params.layerId,
|
|
1125
|
+
filters: params.filters
|
|
1126
|
+
},
|
|
1127
|
+
reason: "filters+layerId"
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
if (params.bbox && params.layerId !== void 0) {
|
|
1131
|
+
return {
|
|
1132
|
+
nextRequest: {
|
|
1133
|
+
type: "bbox",
|
|
1134
|
+
layerId: params.layerId,
|
|
1135
|
+
bbox: params.bbox,
|
|
1136
|
+
options: params.options
|
|
1137
|
+
},
|
|
1138
|
+
reason: "bbox+layerId"
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
if (params.geometry && params.layerId !== void 0) {
|
|
1142
|
+
return {
|
|
1143
|
+
nextRequest: {
|
|
1144
|
+
type: "intersect",
|
|
1145
|
+
layerId: params.layerId,
|
|
1146
|
+
geometry: params.geometry,
|
|
1147
|
+
options: params.options
|
|
1148
|
+
},
|
|
1149
|
+
reason: "geometry+layerId"
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
if (params.layerId !== void 0) {
|
|
1153
|
+
return {
|
|
1154
|
+
nextRequest: {
|
|
1155
|
+
type: "geojson",
|
|
1156
|
+
layerId: params.layerId,
|
|
1157
|
+
options: params.options
|
|
1158
|
+
},
|
|
1159
|
+
reason: "layerId"
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
throw new Error("FilterEngine could not resolve a next request");
|
|
1163
|
+
}
|
|
1164
|
+
};
|
|
1165
|
+
var filterEngine = new FilterEngine();
|
|
1166
|
+
|
|
1167
|
+
// src/engine/LayerStateEngine.ts
|
|
1168
|
+
function clampOpacity2(value) {
|
|
1169
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
1170
|
+
return Math.min(1, Math.max(0, value));
|
|
1171
|
+
}
|
|
1172
|
+
if (typeof value === "string") {
|
|
1173
|
+
const parsed = parseFloat(value);
|
|
1174
|
+
if (Number.isFinite(parsed)) {
|
|
1175
|
+
return Math.min(1, Math.max(0, parsed));
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
return 1;
|
|
1179
|
+
}
|
|
1180
|
+
var resolveBaseVisibility = (_layer) => false;
|
|
1181
|
+
function resolveBaseOpacity(layer) {
|
|
1182
|
+
if ("opacity" in layer) {
|
|
1183
|
+
return clampOpacity2(layer.opacity);
|
|
1184
|
+
}
|
|
1185
|
+
return 1;
|
|
1186
|
+
}
|
|
1187
|
+
function toEffectiveState(base, override) {
|
|
1188
|
+
const overrideVisible = override?.overrideVisible ?? void 0;
|
|
1189
|
+
const overrideOpacity = override?.overrideOpacity ?? void 0;
|
|
1190
|
+
const opacitySource = overrideOpacity !== void 0 && overrideOpacity !== null ? overrideOpacity : base.baseOpacity;
|
|
1191
|
+
return {
|
|
1192
|
+
layerId: base.layerId,
|
|
1193
|
+
baseVisible: base.baseVisible,
|
|
1194
|
+
baseOpacity: base.baseOpacity,
|
|
1195
|
+
overrideVisible,
|
|
1196
|
+
overrideOpacity,
|
|
1197
|
+
visible: overrideVisible ?? base.baseVisible,
|
|
1198
|
+
opacity: clampOpacity2(opacitySource)
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
function initLayerStates(mapLayers) {
|
|
1202
|
+
const bases = mapLayers.filter((layer) => layer.layerId !== void 0).map((layer) => ({
|
|
1203
|
+
layerId: layer.layerId,
|
|
1204
|
+
baseVisible: resolveBaseVisibility(layer),
|
|
1205
|
+
baseOpacity: resolveBaseOpacity(layer)
|
|
1206
|
+
}));
|
|
1207
|
+
return bases.map((base) => toEffectiveState(base));
|
|
1208
|
+
}
|
|
1209
|
+
function applyLayerOverrides(states, overrides) {
|
|
1210
|
+
const overrideMap = /* @__PURE__ */ new Map();
|
|
1211
|
+
overrides.forEach((entry) => overrideMap.set(entry.layerId, entry));
|
|
1212
|
+
return states.map((state) => {
|
|
1213
|
+
const nextOverride = overrideMap.get(state.layerId);
|
|
1214
|
+
const mergedOverrideVisible = nextOverride && "overrideVisible" in nextOverride && nextOverride.overrideVisible !== void 0 ? nextOverride.overrideVisible : state.overrideVisible;
|
|
1215
|
+
const mergedOverrideOpacity = nextOverride && "overrideOpacity" in nextOverride && nextOverride.overrideOpacity !== void 0 ? nextOverride.overrideOpacity : state.overrideOpacity;
|
|
1216
|
+
return toEffectiveState(state, {
|
|
1217
|
+
layerId: state.layerId,
|
|
1218
|
+
overrideVisible: mergedOverrideVisible ?? void 0,
|
|
1219
|
+
overrideOpacity: mergedOverrideOpacity ?? void 0
|
|
1220
|
+
});
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
function resetOverrides(states) {
|
|
1224
|
+
return states.map((state) => toEffectiveState(state));
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
// src/ai/chat.service.ts
|
|
1228
|
+
var DEFAULT_ERROR_MESSAGE = "No fue posible completar la solicitud al asistente.";
|
|
1229
|
+
var normalizeBaseUrl = (baseUrl) => baseUrl.replace(/\/$/, "");
|
|
1230
|
+
var resolveBaseUrl = (config, options) => {
|
|
1231
|
+
const baseUrl = options?.baseUrl ?? config?.baseUrl;
|
|
1232
|
+
if (!baseUrl) {
|
|
1233
|
+
throw new Error("baseUrl es requerido para usar el chat de Zenit AI.");
|
|
1234
|
+
}
|
|
1235
|
+
return normalizeBaseUrl(baseUrl);
|
|
1236
|
+
};
|
|
1237
|
+
var resolveAccessToken = async (config, options) => {
|
|
1238
|
+
if (options?.accessToken) return options.accessToken;
|
|
1239
|
+
if (config?.accessToken) return config.accessToken;
|
|
1240
|
+
if (options?.getAccessToken) return await options.getAccessToken();
|
|
1241
|
+
if (config?.getAccessToken) return await config.getAccessToken();
|
|
1242
|
+
return void 0;
|
|
1243
|
+
};
|
|
1244
|
+
var buildAuthHeaders = (token) => {
|
|
1245
|
+
if (!token) return {};
|
|
1246
|
+
return { Authorization: `Bearer ${token}` };
|
|
1247
|
+
};
|
|
1248
|
+
var parseError = async (response) => {
|
|
1249
|
+
try {
|
|
1250
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
1251
|
+
if (contentType.includes("application/json")) {
|
|
1252
|
+
const payload = await response.json();
|
|
1253
|
+
const message = payload?.message ?? DEFAULT_ERROR_MESSAGE;
|
|
1254
|
+
return new Error(message);
|
|
1255
|
+
}
|
|
1256
|
+
const text = await response.text();
|
|
1257
|
+
return new Error(text || DEFAULT_ERROR_MESSAGE);
|
|
1258
|
+
} catch (error) {
|
|
1259
|
+
return error instanceof Error ? error : new Error(DEFAULT_ERROR_MESSAGE);
|
|
1260
|
+
}
|
|
1261
|
+
};
|
|
1262
|
+
var sendMessage = async (mapId, request, options, config) => {
|
|
1263
|
+
const baseUrl = resolveBaseUrl(config, options);
|
|
1264
|
+
const token = await resolveAccessToken(config, options);
|
|
1265
|
+
const response = await fetch(`${baseUrl}/ai/maps/chat/${mapId}`, {
|
|
1266
|
+
method: "POST",
|
|
1267
|
+
headers: {
|
|
1268
|
+
"Content-Type": "application/json",
|
|
1269
|
+
...buildAuthHeaders(token)
|
|
1270
|
+
},
|
|
1271
|
+
body: JSON.stringify(request),
|
|
1272
|
+
signal: options?.signal
|
|
1273
|
+
});
|
|
1274
|
+
if (!response.ok) {
|
|
1275
|
+
throw await parseError(response);
|
|
1276
|
+
}
|
|
1277
|
+
return await response.json();
|
|
1278
|
+
};
|
|
1279
|
+
var mergeResponse = (previous, chunk, aggregatedAnswer) => ({
|
|
1280
|
+
...previous ?? chunk,
|
|
1281
|
+
...chunk,
|
|
1282
|
+
answer: aggregatedAnswer
|
|
1283
|
+
});
|
|
1284
|
+
var resolveAggregatedAnswer = (current, incoming) => {
|
|
1285
|
+
if (!incoming) return current;
|
|
1286
|
+
if (incoming.startsWith(current)) return incoming;
|
|
1287
|
+
return current + incoming;
|
|
1288
|
+
};
|
|
1289
|
+
var sendMessageStream = async (mapId, request, callbacks = {}, options, config) => {
|
|
1290
|
+
const baseUrl = resolveBaseUrl(config, options);
|
|
1291
|
+
const token = await resolveAccessToken(config, options);
|
|
1292
|
+
const response = await fetch(`${baseUrl}/ai/maps/chat/${mapId}/stream`, {
|
|
1293
|
+
method: "POST",
|
|
1294
|
+
headers: {
|
|
1295
|
+
Accept: "text/event-stream",
|
|
1296
|
+
"Content-Type": "application/json",
|
|
1297
|
+
...buildAuthHeaders(token)
|
|
1298
|
+
},
|
|
1299
|
+
body: JSON.stringify(request),
|
|
1300
|
+
signal: options?.signal
|
|
1301
|
+
});
|
|
1302
|
+
if (!response.ok) {
|
|
1303
|
+
const error = await parseError(response);
|
|
1304
|
+
callbacks.onError?.(error);
|
|
1305
|
+
throw error;
|
|
1306
|
+
}
|
|
1307
|
+
const reader = response.body?.getReader();
|
|
1308
|
+
if (!reader) {
|
|
1309
|
+
const error = new Error("No se pudo iniciar el streaming de Zenit AI.");
|
|
1310
|
+
callbacks.onError?.(error);
|
|
1311
|
+
throw error;
|
|
1312
|
+
}
|
|
1313
|
+
const decoder = new TextDecoder("utf-8");
|
|
1314
|
+
let buffer = "";
|
|
1315
|
+
let aggregated = "";
|
|
1316
|
+
let latestResponse = null;
|
|
1317
|
+
try {
|
|
1318
|
+
while (true) {
|
|
1319
|
+
const { value, done } = await reader.read();
|
|
1320
|
+
if (done) break;
|
|
1321
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1322
|
+
const lines = buffer.split(/\r?\n/);
|
|
1323
|
+
buffer = lines.pop() ?? "";
|
|
1324
|
+
for (const line of lines) {
|
|
1325
|
+
const trimmed = line.trim();
|
|
1326
|
+
if (!trimmed || trimmed.startsWith("event:")) continue;
|
|
1327
|
+
if (!trimmed.startsWith("data:")) continue;
|
|
1328
|
+
const payload = trimmed.replace(/^data:\s*/, "");
|
|
1329
|
+
if (!payload || payload === "[DONE]") continue;
|
|
1330
|
+
try {
|
|
1331
|
+
const parsed = JSON.parse(payload);
|
|
1332
|
+
aggregated = resolveAggregatedAnswer(aggregated, parsed.answer ?? "");
|
|
1333
|
+
latestResponse = mergeResponse(latestResponse, parsed, aggregated);
|
|
1334
|
+
if (latestResponse) {
|
|
1335
|
+
callbacks.onChunk?.(latestResponse, aggregated);
|
|
1336
|
+
}
|
|
1337
|
+
} catch (error) {
|
|
1338
|
+
callbacks.onError?.(error);
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
} catch (error) {
|
|
1343
|
+
callbacks.onError?.(error);
|
|
1344
|
+
throw error;
|
|
1345
|
+
}
|
|
1346
|
+
if (!latestResponse) {
|
|
1347
|
+
const error = new Error(DEFAULT_ERROR_MESSAGE);
|
|
1348
|
+
callbacks.onError?.(error);
|
|
1349
|
+
throw error;
|
|
1350
|
+
}
|
|
1351
|
+
callbacks.onComplete?.(latestResponse);
|
|
1352
|
+
return latestResponse;
|
|
1353
|
+
};
|
|
1354
|
+
var createChatService = (config) => ({
|
|
1355
|
+
sendMessage: (mapId, request, options) => sendMessage(mapId, request, options, config),
|
|
1356
|
+
sendMessageStream: (mapId, request, callbacks, options) => sendMessageStream(mapId, request, callbacks, options, config)
|
|
1357
|
+
});
|
|
1358
|
+
|
|
1359
|
+
// src/ai/normalize.ts
|
|
1360
|
+
function normalizeBbox(input) {
|
|
1361
|
+
if (!input) return null;
|
|
1362
|
+
if (Array.isArray(input)) {
|
|
1363
|
+
if (input.length !== 4) {
|
|
1364
|
+
console.warn("[normalizeBbox] Array must have exactly 4 elements, got:", input.length);
|
|
1365
|
+
return null;
|
|
1366
|
+
}
|
|
1367
|
+
const [minLon, minLat, maxLon, maxLat] = input;
|
|
1368
|
+
if (typeof minLon !== "number" || typeof minLat !== "number" || typeof maxLon !== "number" || typeof maxLat !== "number" || !Number.isFinite(minLon) || !Number.isFinite(minLat) || !Number.isFinite(maxLon) || !Number.isFinite(maxLat)) {
|
|
1369
|
+
console.warn("[normalizeBbox] Array contains non-finite numbers:", input);
|
|
1370
|
+
return null;
|
|
1371
|
+
}
|
|
1372
|
+
if (minLon >= maxLon || minLat >= maxLat) {
|
|
1373
|
+
console.warn("[normalizeBbox] Invalid bbox: min must be < max", {
|
|
1374
|
+
minLon,
|
|
1375
|
+
maxLon,
|
|
1376
|
+
minLat,
|
|
1377
|
+
maxLat
|
|
1378
|
+
});
|
|
1379
|
+
return null;
|
|
1380
|
+
}
|
|
1381
|
+
return { minLon, minLat, maxLon, maxLat };
|
|
1382
|
+
}
|
|
1383
|
+
if (typeof input === "object") {
|
|
1384
|
+
const candidate = input;
|
|
1385
|
+
const { minLon, minLat, maxLon, maxLat } = candidate;
|
|
1386
|
+
if (typeof minLon !== "number" || typeof minLat !== "number" || typeof maxLon !== "number" || typeof maxLat !== "number" || !Number.isFinite(minLon) || !Number.isFinite(minLat) || !Number.isFinite(maxLon) || !Number.isFinite(maxLat)) {
|
|
1387
|
+
console.warn("[normalizeBbox] Object missing valid bbox keys or contains non-finite numbers:", input);
|
|
1388
|
+
return null;
|
|
1389
|
+
}
|
|
1390
|
+
if (minLon >= maxLon || minLat >= maxLat) {
|
|
1391
|
+
console.warn("[normalizeBbox] Invalid bbox: min must be < max", {
|
|
1392
|
+
minLon,
|
|
1393
|
+
maxLon,
|
|
1394
|
+
minLat,
|
|
1395
|
+
maxLat
|
|
1396
|
+
});
|
|
1397
|
+
return null;
|
|
1398
|
+
}
|
|
1399
|
+
return { minLon, minLat, maxLon, maxLat };
|
|
1400
|
+
}
|
|
1401
|
+
console.warn("[normalizeBbox] Unsupported bbox format:", typeof input);
|
|
1402
|
+
return null;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
// src/react/ZenitMap.tsx
|
|
1406
|
+
var import_react = require("react");
|
|
1407
|
+
var import_react_leaflet = require("react-leaflet");
|
|
1408
|
+
var import_leaflet = __toESM(require("leaflet"));
|
|
1409
|
+
|
|
1410
|
+
// src/react/layerStyleHelpers.ts
|
|
1411
|
+
function resolveLayerAccent(style) {
|
|
1412
|
+
if (!style) return null;
|
|
1413
|
+
return style.fillColor ?? style.color ?? null;
|
|
1414
|
+
}
|
|
1415
|
+
function getLayerColor(style, fallback = "#94a3b8") {
|
|
1416
|
+
return resolveLayerAccent(style) ?? fallback;
|
|
1417
|
+
}
|
|
1418
|
+
function getStyleByLayerId(layerId, mapLayers) {
|
|
1419
|
+
if (!mapLayers) return null;
|
|
1420
|
+
const match = mapLayers.find((ml) => String(ml.layerId) === String(layerId));
|
|
1421
|
+
return match?.style ?? null;
|
|
1422
|
+
}
|
|
1423
|
+
function getAccentByLayerId(layerId, mapLayers) {
|
|
1424
|
+
const style = getStyleByLayerId(layerId, mapLayers);
|
|
1425
|
+
return resolveLayerAccent(style);
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
// src/react/zoomOpacity.ts
|
|
1429
|
+
var DEFAULT_OPTIONS = {
|
|
1430
|
+
minZoom: 10,
|
|
1431
|
+
maxZoom: 17,
|
|
1432
|
+
minFactor: 0.6,
|
|
1433
|
+
maxFactor: 1,
|
|
1434
|
+
minOpacity: 0.1,
|
|
1435
|
+
maxOpacity: 0.92
|
|
1436
|
+
};
|
|
1437
|
+
function clampNumber(value, min, max) {
|
|
1438
|
+
return Math.min(max, Math.max(min, value));
|
|
1439
|
+
}
|
|
1440
|
+
function clampOpacity3(value) {
|
|
1441
|
+
return clampNumber(value, 0, 1);
|
|
1442
|
+
}
|
|
1443
|
+
function isPolygonLayer(layerType, geometryType) {
|
|
1444
|
+
const candidate = (layerType ?? geometryType ?? "").toLowerCase();
|
|
1445
|
+
return candidate === "polygon" || candidate === "multipolygon";
|
|
1446
|
+
}
|
|
1447
|
+
function getZoomOpacityFactor(zoom, options) {
|
|
1448
|
+
const settings = { ...DEFAULT_OPTIONS, ...options };
|
|
1449
|
+
const { minZoom, maxZoom, minFactor, maxFactor } = settings;
|
|
1450
|
+
if (maxZoom <= minZoom) return clampNumber(maxFactor, minFactor, maxFactor);
|
|
1451
|
+
const t = clampNumber((zoom - minZoom) / (maxZoom - minZoom), 0, 1);
|
|
1452
|
+
const factor = maxFactor + (minFactor - maxFactor) * t;
|
|
1453
|
+
return clampNumber(factor, minFactor, maxFactor);
|
|
1454
|
+
}
|
|
1455
|
+
function getLayerZoomOpacityFactor(zoom, layerType, geometryType, options) {
|
|
1456
|
+
if (!isPolygonLayer(layerType, geometryType)) return 1;
|
|
1457
|
+
return getZoomOpacityFactor(zoom, options);
|
|
1458
|
+
}
|
|
1459
|
+
function getEffectiveLayerOpacity(baseOpacity, zoom, layerType, geometryType, options) {
|
|
1460
|
+
if (!isPolygonLayer(layerType, geometryType)) {
|
|
1461
|
+
return clampOpacity3(baseOpacity);
|
|
1462
|
+
}
|
|
1463
|
+
const settings = { ...DEFAULT_OPTIONS, ...options };
|
|
1464
|
+
const factor = getZoomOpacityFactor(zoom, options);
|
|
1465
|
+
const effective = clampOpacity3(baseOpacity) * factor;
|
|
1466
|
+
return clampNumber(effective, settings.minOpacity, settings.maxOpacity);
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
// src/react/ZenitMap.tsx
|
|
1470
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
1471
|
+
var DEFAULT_CENTER = [0, 0];
|
|
1472
|
+
var DEFAULT_ZOOM = 3;
|
|
1473
|
+
function computeBBoxFromGeojson(geojson) {
|
|
1474
|
+
if (!geojson) return null;
|
|
1475
|
+
if (!Array.isArray(geojson.features) || geojson.features.length === 0) return null;
|
|
1476
|
+
const coords = [];
|
|
1477
|
+
const collect = (candidate) => {
|
|
1478
|
+
if (!Array.isArray(candidate)) return;
|
|
1479
|
+
if (candidate.length === 2 && typeof candidate[0] === "number" && typeof candidate[1] === "number") {
|
|
1480
|
+
coords.push([candidate[0], candidate[1]]);
|
|
1481
|
+
return;
|
|
1482
|
+
}
|
|
1483
|
+
candidate.forEach((entry) => collect(entry));
|
|
1484
|
+
};
|
|
1485
|
+
geojson.features.forEach((feature) => {
|
|
1486
|
+
collect(feature.geometry?.coordinates);
|
|
1487
|
+
});
|
|
1488
|
+
if (coords.length === 0) return null;
|
|
1489
|
+
const [firstLon, firstLat] = coords[0];
|
|
1490
|
+
const bbox = { minLon: firstLon, minLat: firstLat, maxLon: firstLon, maxLat: firstLat };
|
|
1491
|
+
coords.forEach(([lon, lat]) => {
|
|
1492
|
+
bbox.minLon = Math.min(bbox.minLon, lon);
|
|
1493
|
+
bbox.minLat = Math.min(bbox.minLat, lat);
|
|
1494
|
+
bbox.maxLon = Math.max(bbox.maxLon, lon);
|
|
1495
|
+
bbox.maxLat = Math.max(bbox.maxLat, lat);
|
|
1496
|
+
});
|
|
1497
|
+
return bbox;
|
|
1498
|
+
}
|
|
1499
|
+
function mergeBBoxes(bboxes) {
|
|
1500
|
+
const valid = bboxes.filter((bbox) => !!bbox);
|
|
1501
|
+
if (valid.length === 0) return null;
|
|
1502
|
+
const first = valid[0];
|
|
1503
|
+
return valid.slice(1).reduce(
|
|
1504
|
+
(acc, bbox) => ({
|
|
1505
|
+
minLon: Math.min(acc.minLon, bbox.minLon),
|
|
1506
|
+
minLat: Math.min(acc.minLat, bbox.minLat),
|
|
1507
|
+
maxLon: Math.max(acc.maxLon, bbox.maxLon),
|
|
1508
|
+
maxLat: Math.max(acc.maxLat, bbox.maxLat)
|
|
1509
|
+
}),
|
|
1510
|
+
{ ...first }
|
|
1511
|
+
);
|
|
1512
|
+
}
|
|
1513
|
+
function getFeatureLayerId(feature) {
|
|
1514
|
+
const layerId = feature?.properties?.__zenit_layerId ?? feature?.properties?.layerId ?? feature?.properties?.layer_id;
|
|
1515
|
+
if (layerId === void 0 || layerId === null) return null;
|
|
1516
|
+
return layerId;
|
|
1517
|
+
}
|
|
1518
|
+
function escapeHtml(value) {
|
|
1519
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1520
|
+
}
|
|
1521
|
+
function withAlpha(color, alpha) {
|
|
1522
|
+
const trimmed = color.trim();
|
|
1523
|
+
if (trimmed.startsWith("#")) {
|
|
1524
|
+
const hex = trimmed.replace("#", "");
|
|
1525
|
+
const expanded = hex.length === 3 ? hex.split("").map((char) => `${char}${char}`).join("") : hex;
|
|
1526
|
+
if (expanded.length === 6) {
|
|
1527
|
+
const r = parseInt(expanded.slice(0, 2), 16);
|
|
1528
|
+
const g = parseInt(expanded.slice(2, 4), 16);
|
|
1529
|
+
const b = parseInt(expanded.slice(4, 6), 16);
|
|
1530
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
if (trimmed.startsWith("rgb(")) {
|
|
1534
|
+
const inner = trimmed.slice(4, -1);
|
|
1535
|
+
return `rgba(${inner}, ${alpha})`;
|
|
1536
|
+
}
|
|
1537
|
+
if (trimmed.startsWith("rgba(")) {
|
|
1538
|
+
const inner = trimmed.slice(5, -1).split(",").slice(0, 3).map((value) => value.trim());
|
|
1539
|
+
return `rgba(${inner.join(", ")}, ${alpha})`;
|
|
1540
|
+
}
|
|
1541
|
+
return color;
|
|
1542
|
+
}
|
|
1543
|
+
function getRgbFromColor(color) {
|
|
1544
|
+
const trimmed = color.trim();
|
|
1545
|
+
if (trimmed.startsWith("#")) {
|
|
1546
|
+
const hex = trimmed.replace("#", "");
|
|
1547
|
+
const expanded = hex.length === 3 ? hex.split("").map((char) => `${char}${char}`).join("") : hex;
|
|
1548
|
+
if (expanded.length === 6) {
|
|
1549
|
+
const r = parseInt(expanded.slice(0, 2), 16);
|
|
1550
|
+
const g = parseInt(expanded.slice(2, 4), 16);
|
|
1551
|
+
const b = parseInt(expanded.slice(4, 6), 16);
|
|
1552
|
+
return { r, g, b };
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
const rgbMatch = trimmed.match(/rgba?\(([^)]+)\)/i);
|
|
1556
|
+
if (rgbMatch) {
|
|
1557
|
+
const [r, g, b] = rgbMatch[1].split(",").map((value) => parseFloat(value.trim())).slice(0, 3);
|
|
1558
|
+
if ([r, g, b].every((value) => Number.isFinite(value))) {
|
|
1559
|
+
return { r, g, b };
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
return null;
|
|
1563
|
+
}
|
|
1564
|
+
function getLabelTextStyles(color) {
|
|
1565
|
+
const rgb = getRgbFromColor(color);
|
|
1566
|
+
if (!rgb) {
|
|
1567
|
+
return { color: "#0f172a", shadow: "0 1px 2px rgba(255, 255, 255, 0.6)" };
|
|
1568
|
+
}
|
|
1569
|
+
const luminance = (0.2126 * rgb.r + 0.7152 * rgb.g + 0.0722 * rgb.b) / 255;
|
|
1570
|
+
if (luminance > 0.6) {
|
|
1571
|
+
return { color: "#0f172a", shadow: "0 1px 2px rgba(255, 255, 255, 0.7)" };
|
|
1572
|
+
}
|
|
1573
|
+
return { color: "#ffffff", shadow: "0 1px 2px rgba(0, 0, 0, 0.4)" };
|
|
1574
|
+
}
|
|
1575
|
+
function getFeatureStyleOverrides(feature) {
|
|
1576
|
+
const candidate = feature?.properties?._style;
|
|
1577
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return null;
|
|
1578
|
+
const styleCandidate = candidate;
|
|
1579
|
+
return {
|
|
1580
|
+
color: styleCandidate.color,
|
|
1581
|
+
weight: styleCandidate.weight,
|
|
1582
|
+
fillColor: styleCandidate.fillColor,
|
|
1583
|
+
fillOpacity: styleCandidate.fillOpacity
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
function buildFeaturePopupHtml(feature) {
|
|
1587
|
+
const properties = feature?.properties;
|
|
1588
|
+
if (!properties) return null;
|
|
1589
|
+
const layerName = properties.layerName ?? properties.layer_name ?? properties.name;
|
|
1590
|
+
const descripcion = properties.descripcion ?? properties.description;
|
|
1591
|
+
const reservedKeys = /* @__PURE__ */ new Set([
|
|
1592
|
+
"_style",
|
|
1593
|
+
"layerId",
|
|
1594
|
+
"layer_id",
|
|
1595
|
+
"__zenit_layerId",
|
|
1596
|
+
"layerName",
|
|
1597
|
+
"layer_name",
|
|
1598
|
+
"name",
|
|
1599
|
+
"descripcion",
|
|
1600
|
+
"description"
|
|
1601
|
+
]);
|
|
1602
|
+
const extraEntries = Object.entries(properties).filter(([key, value]) => {
|
|
1603
|
+
if (reservedKeys.has(key)) return false;
|
|
1604
|
+
return ["string", "number", "boolean"].includes(typeof value);
|
|
1605
|
+
}).slice(0, 5);
|
|
1606
|
+
if (!layerName && !descripcion && extraEntries.length === 0) return null;
|
|
1607
|
+
const parts = [];
|
|
1608
|
+
if (layerName) {
|
|
1609
|
+
parts.push(`<div style="font-weight:600;margin-bottom:4px;">${escapeHtml(layerName)}</div>`);
|
|
1610
|
+
}
|
|
1611
|
+
if (descripcion) {
|
|
1612
|
+
parts.push(`<div style="margin-bottom:6px;">${escapeHtml(descripcion)}</div>`);
|
|
1613
|
+
}
|
|
1614
|
+
if (extraEntries.length > 0) {
|
|
1615
|
+
const rows = extraEntries.map(([key, value]) => {
|
|
1616
|
+
const label = escapeHtml(key.replace(/_/g, " "));
|
|
1617
|
+
const val = escapeHtml(String(value));
|
|
1618
|
+
return `<div><strong>${label}:</strong> ${val}</div>`;
|
|
1619
|
+
}).join("");
|
|
1620
|
+
parts.push(`<div style="font-size:12px;line-height:1.4;">${rows}</div>`);
|
|
1621
|
+
}
|
|
1622
|
+
return `<div>${parts.join("")}</div>`;
|
|
1623
|
+
}
|
|
1624
|
+
function normalizeCenterTuple(center) {
|
|
1625
|
+
if (!center) return null;
|
|
1626
|
+
if (Array.isArray(center) && center.length >= 2) {
|
|
1627
|
+
const lat = center[0];
|
|
1628
|
+
const lon = center[1];
|
|
1629
|
+
if (typeof lat === "number" && typeof lon === "number") return [lat, lon];
|
|
1630
|
+
return null;
|
|
1631
|
+
}
|
|
1632
|
+
const maybeObj = center;
|
|
1633
|
+
if (typeof maybeObj.lat === "number" && typeof maybeObj.lon === "number") {
|
|
1634
|
+
return [maybeObj.lat, maybeObj.lon];
|
|
1635
|
+
}
|
|
1636
|
+
return null;
|
|
1637
|
+
}
|
|
1638
|
+
var FitToBounds = ({ bbox }) => {
|
|
1639
|
+
const mapInstance = (0, import_react_leaflet.useMap)();
|
|
1640
|
+
const lastAppliedBBox = (0, import_react.useRef)(null);
|
|
1641
|
+
(0, import_react.useEffect)(() => {
|
|
1642
|
+
const targetBBox = bbox;
|
|
1643
|
+
if (!targetBBox) return;
|
|
1644
|
+
const serialized = JSON.stringify(targetBBox);
|
|
1645
|
+
if (lastAppliedBBox.current === serialized) return;
|
|
1646
|
+
const bounds = [
|
|
1647
|
+
[targetBBox.minLat, targetBBox.minLon],
|
|
1648
|
+
[targetBBox.maxLat, targetBBox.maxLon]
|
|
1649
|
+
];
|
|
1650
|
+
mapInstance.fitBounds(bounds, { padding: [12, 12] });
|
|
1651
|
+
lastAppliedBBox.current = serialized;
|
|
1652
|
+
}, [bbox, mapInstance]);
|
|
1653
|
+
return null;
|
|
1654
|
+
};
|
|
1655
|
+
var AutoFitToBounds = ({
|
|
1656
|
+
bbox,
|
|
1657
|
+
enabled = true
|
|
1658
|
+
}) => {
|
|
1659
|
+
const mapInstance = (0, import_react_leaflet.useMap)();
|
|
1660
|
+
const lastAutoBBoxApplied = (0, import_react.useRef)(null);
|
|
1661
|
+
const lastUserInteracted = (0, import_react.useRef)(false);
|
|
1662
|
+
(0, import_react.useEffect)(() => {
|
|
1663
|
+
if (!enabled) return;
|
|
1664
|
+
const handleInteraction = () => {
|
|
1665
|
+
lastUserInteracted.current = true;
|
|
1666
|
+
};
|
|
1667
|
+
mapInstance.on("dragstart", handleInteraction);
|
|
1668
|
+
mapInstance.on("zoomstart", handleInteraction);
|
|
1669
|
+
return () => {
|
|
1670
|
+
mapInstance.off("dragstart", handleInteraction);
|
|
1671
|
+
mapInstance.off("zoomstart", handleInteraction);
|
|
1672
|
+
};
|
|
1673
|
+
}, [enabled, mapInstance]);
|
|
1674
|
+
(0, import_react.useEffect)(() => {
|
|
1675
|
+
if (!enabled) return;
|
|
1676
|
+
if (!bbox) return;
|
|
1677
|
+
const serialized = JSON.stringify(bbox);
|
|
1678
|
+
if (lastAutoBBoxApplied.current === serialized) return;
|
|
1679
|
+
if (lastUserInteracted.current) {
|
|
1680
|
+
lastUserInteracted.current = false;
|
|
1681
|
+
}
|
|
1682
|
+
const bounds = [
|
|
1683
|
+
[bbox.minLat, bbox.minLon],
|
|
1684
|
+
[bbox.maxLat, bbox.maxLon]
|
|
1685
|
+
];
|
|
1686
|
+
mapInstance.fitBounds(bounds, { padding: [12, 12] });
|
|
1687
|
+
lastAutoBBoxApplied.current = serialized;
|
|
1688
|
+
}, [bbox, enabled, mapInstance]);
|
|
1689
|
+
return null;
|
|
1690
|
+
};
|
|
1691
|
+
var ZoomBasedOpacityHandler = ({ onZoomChange }) => {
|
|
1692
|
+
const mapInstance = (0, import_react_leaflet.useMap)();
|
|
1693
|
+
(0, import_react.useEffect)(() => {
|
|
1694
|
+
const handleZoom = () => {
|
|
1695
|
+
onZoomChange(mapInstance.getZoom());
|
|
1696
|
+
};
|
|
1697
|
+
mapInstance.on("zoomend", handleZoom);
|
|
1698
|
+
handleZoom();
|
|
1699
|
+
return () => {
|
|
1700
|
+
mapInstance.off("zoomend", handleZoom);
|
|
1701
|
+
};
|
|
1702
|
+
}, [mapInstance, onZoomChange]);
|
|
1703
|
+
return null;
|
|
1704
|
+
};
|
|
1705
|
+
var MapInstanceBridge = ({ onReady }) => {
|
|
1706
|
+
const mapInstance = (0, import_react_leaflet.useMap)();
|
|
1707
|
+
(0, import_react.useEffect)(() => {
|
|
1708
|
+
onReady(mapInstance);
|
|
1709
|
+
}, [mapInstance, onReady]);
|
|
1710
|
+
return null;
|
|
1711
|
+
};
|
|
1712
|
+
var ZenitMap = (0, import_react.forwardRef)(({
|
|
1713
|
+
client,
|
|
1714
|
+
mapId,
|
|
1715
|
+
height = "500px",
|
|
1716
|
+
width = "100%",
|
|
1717
|
+
initialZoom,
|
|
1718
|
+
initialCenter,
|
|
1719
|
+
showLayerPanel = true,
|
|
1720
|
+
overlayGeojson,
|
|
1721
|
+
overlayStyle,
|
|
1722
|
+
layerControls,
|
|
1723
|
+
layerStates,
|
|
1724
|
+
layerGeojson,
|
|
1725
|
+
onLayerStateChange,
|
|
1726
|
+
onError,
|
|
1727
|
+
onLoadingChange,
|
|
1728
|
+
onFeatureClick,
|
|
1729
|
+
onFeatureHover,
|
|
1730
|
+
featureInfoMode = "popup",
|
|
1731
|
+
mapLayers,
|
|
1732
|
+
zoomToBbox,
|
|
1733
|
+
zoomToGeojson,
|
|
1734
|
+
onZoomChange,
|
|
1735
|
+
onMapReady
|
|
1736
|
+
}, ref) => {
|
|
1737
|
+
const [map, setMap] = (0, import_react.useState)(null);
|
|
1738
|
+
const [layers, setLayers] = (0, import_react.useState)([]);
|
|
1739
|
+
const [effectiveStates, setEffectiveStates] = (0, import_react.useState)([]);
|
|
1740
|
+
const [loadingMap, setLoadingMap] = (0, import_react.useState)(false);
|
|
1741
|
+
const [mapError, setMapError] = (0, import_react.useState)(null);
|
|
1742
|
+
const [mapInstance, setMapInstance] = (0, import_react.useState)(null);
|
|
1743
|
+
const [currentZoom, setCurrentZoom] = (0, import_react.useState)(initialZoom ?? DEFAULT_ZOOM);
|
|
1744
|
+
const [isMobile, setIsMobile] = (0, import_react.useState)(() => {
|
|
1745
|
+
if (typeof window === "undefined") return false;
|
|
1746
|
+
return window.matchMedia("(max-width: 768px)").matches;
|
|
1747
|
+
});
|
|
1748
|
+
const normalizedLayers = (0, import_react.useMemo)(() => normalizeMapLayers(map), [map]);
|
|
1749
|
+
(0, import_react.useEffect)(() => {
|
|
1750
|
+
if (typeof window === "undefined") return;
|
|
1751
|
+
const mql = window.matchMedia("(max-width: 768px)");
|
|
1752
|
+
const onChange = (e) => {
|
|
1753
|
+
setIsMobile(e.matches);
|
|
1754
|
+
};
|
|
1755
|
+
setIsMobile(mql.matches);
|
|
1756
|
+
if (typeof mql.addEventListener === "function") {
|
|
1757
|
+
mql.addEventListener("change", onChange);
|
|
1758
|
+
return () => mql.removeEventListener("change", onChange);
|
|
1759
|
+
}
|
|
1760
|
+
const legacy = mql;
|
|
1761
|
+
if (typeof legacy.addListener === "function") {
|
|
1762
|
+
legacy.addListener(onChange);
|
|
1763
|
+
return () => legacy.removeListener(onChange);
|
|
1764
|
+
}
|
|
1765
|
+
return;
|
|
1766
|
+
}, []);
|
|
1767
|
+
const layerStyleIndex = (0, import_react.useMemo)(() => {
|
|
1768
|
+
const index = /* @__PURE__ */ new Map();
|
|
1769
|
+
(map?.mapLayers ?? []).forEach((entry) => {
|
|
1770
|
+
const layerStyle = entry.layer?.style ?? entry.mapLayer?.layer?.style ?? entry.style ?? null;
|
|
1771
|
+
const layerId = entry.layerId ?? entry.mapLayer?.layerId ?? entry.mapLayer?.layer?.id ?? entry.layer?.id;
|
|
1772
|
+
if (layerId !== void 0 && layerId !== null && layerStyle) {
|
|
1773
|
+
index.set(String(layerId), layerStyle);
|
|
1774
|
+
}
|
|
1775
|
+
});
|
|
1776
|
+
return index;
|
|
1777
|
+
}, [map]);
|
|
1778
|
+
const labelKeyIndex = (0, import_react.useMemo)(() => {
|
|
1779
|
+
const index = /* @__PURE__ */ new Map();
|
|
1780
|
+
normalizedLayers.forEach((entry) => {
|
|
1781
|
+
const label = entry.layer?.label ?? entry.mapLayer?.label ?? entry.mapLayer.layerConfig?.label;
|
|
1782
|
+
if (typeof label === "string" && label.trim().length > 0) {
|
|
1783
|
+
index.set(String(entry.layerId), label);
|
|
1784
|
+
}
|
|
1785
|
+
});
|
|
1786
|
+
return index;
|
|
1787
|
+
}, [normalizedLayers]);
|
|
1788
|
+
const layerMetaIndex = (0, import_react.useMemo)(() => {
|
|
1789
|
+
const index = /* @__PURE__ */ new Map();
|
|
1790
|
+
normalizedLayers.forEach((entry) => {
|
|
1791
|
+
index.set(String(entry.layerId), {
|
|
1792
|
+
layerType: typeof entry.layer?.layerType === "string" ? entry.layer.layerType : typeof entry.mapLayer.layerType === "string" ? entry.mapLayer.layerType : void 0,
|
|
1793
|
+
geometryType: typeof entry.layer?.geometryType === "string" ? entry.layer.geometryType : typeof entry.mapLayer.layerConfig?.geometryType === "string" ? entry.mapLayer.layerConfig.geometryType : void 0
|
|
1794
|
+
});
|
|
1795
|
+
});
|
|
1796
|
+
return index;
|
|
1797
|
+
}, [normalizedLayers]);
|
|
1798
|
+
const overlayStyleFunction = (0, import_react.useMemo)(() => {
|
|
1799
|
+
return (feature) => {
|
|
1800
|
+
const featureLayerId = getFeatureLayerId(feature);
|
|
1801
|
+
const featureStyleOverrides = getFeatureStyleOverrides(feature);
|
|
1802
|
+
let style = null;
|
|
1803
|
+
if (featureLayerId !== null) {
|
|
1804
|
+
style = getStyleByLayerId(featureLayerId, mapLayers) ?? layerStyleIndex.get(String(featureLayerId)) ?? null;
|
|
1805
|
+
}
|
|
1806
|
+
const resolvedStyle = featureStyleOverrides ? { ...style ?? {}, ...featureStyleOverrides } : style;
|
|
1807
|
+
const defaultOptions = {
|
|
1808
|
+
color: resolvedStyle?.color ?? resolvedStyle?.fillColor ?? "#2563eb",
|
|
1809
|
+
weight: resolvedStyle?.weight ?? 2,
|
|
1810
|
+
fillColor: resolvedStyle?.fillColor ?? resolvedStyle?.color ?? "#2563eb",
|
|
1811
|
+
fillOpacity: resolvedStyle?.fillOpacity ?? 0.15,
|
|
1812
|
+
...overlayStyle ?? {}
|
|
1813
|
+
};
|
|
1814
|
+
return defaultOptions;
|
|
1815
|
+
};
|
|
1816
|
+
}, [layerStyleIndex, mapLayers, overlayStyle]);
|
|
1817
|
+
const [baseStates, setBaseStates] = (0, import_react.useState)([]);
|
|
1818
|
+
const [mapOverrides, setMapOverrides] = (0, import_react.useState)([]);
|
|
1819
|
+
const [controlOverrides, setControlOverrides] = (0, import_react.useState)([]);
|
|
1820
|
+
const [uiOverrides, setUiOverrides] = (0, import_react.useState)([]);
|
|
1821
|
+
(0, import_react.useEffect)(() => {
|
|
1822
|
+
let isMounted = true;
|
|
1823
|
+
setLoadingMap(true);
|
|
1824
|
+
setMapError(null);
|
|
1825
|
+
onLoadingChange?.("map", true);
|
|
1826
|
+
client.maps.getMap(mapId, true).then((mapResponse) => {
|
|
1827
|
+
if (!isMounted) return;
|
|
1828
|
+
const extracted = extractMapDto(mapResponse);
|
|
1829
|
+
const resolved = extracted ? normalizeMapCenter(extracted) : null;
|
|
1830
|
+
setMap(resolved);
|
|
1831
|
+
}).catch((err) => {
|
|
1832
|
+
if (!isMounted) return;
|
|
1833
|
+
setMapError(err instanceof Error ? err.message : "No se pudo obtener el mapa");
|
|
1834
|
+
onError?.(err, "map");
|
|
1835
|
+
}).finally(() => {
|
|
1836
|
+
if (!isMounted) return;
|
|
1837
|
+
setLoadingMap(false);
|
|
1838
|
+
onLoadingChange?.("map", false);
|
|
1839
|
+
});
|
|
1840
|
+
return () => {
|
|
1841
|
+
isMounted = false;
|
|
1842
|
+
};
|
|
1843
|
+
}, [client.maps, mapId, onError, onLoadingChange]);
|
|
1844
|
+
(0, import_react.useEffect)(() => {
|
|
1845
|
+
if (normalizedLayers.length === 0) {
|
|
1846
|
+
setLayers([]);
|
|
1847
|
+
setBaseStates([]);
|
|
1848
|
+
setMapOverrides([]);
|
|
1849
|
+
setUiOverrides([]);
|
|
1850
|
+
return;
|
|
1851
|
+
}
|
|
1852
|
+
const nextLayers = normalizedLayers.map((entry) => ({
|
|
1853
|
+
mapLayer: { ...entry.mapLayer, layerId: entry.layerId },
|
|
1854
|
+
layer: entry.layer,
|
|
1855
|
+
displayOrder: entry.displayOrder
|
|
1856
|
+
}));
|
|
1857
|
+
const base = initLayerStates(
|
|
1858
|
+
normalizedLayers.map((entry) => ({
|
|
1859
|
+
...entry.mapLayer,
|
|
1860
|
+
layerId: entry.layerId,
|
|
1861
|
+
opacity: entry.opacity,
|
|
1862
|
+
isVisible: entry.isVisible
|
|
1863
|
+
}))
|
|
1864
|
+
);
|
|
1865
|
+
const initialOverrides = normalizedLayers.map((entry) => ({
|
|
1866
|
+
layerId: entry.layerId,
|
|
1867
|
+
overrideVisible: entry.isVisible,
|
|
1868
|
+
overrideOpacity: entry.opacity
|
|
1869
|
+
}));
|
|
1870
|
+
setLayers(nextLayers);
|
|
1871
|
+
setBaseStates(base);
|
|
1872
|
+
setMapOverrides(initialOverrides);
|
|
1873
|
+
setUiOverrides([]);
|
|
1874
|
+
}, [normalizedLayers]);
|
|
1875
|
+
(0, import_react.useEffect)(() => {
|
|
1876
|
+
if (!layerControls) {
|
|
1877
|
+
setControlOverrides([]);
|
|
1878
|
+
return;
|
|
1879
|
+
}
|
|
1880
|
+
const overrides = layerControls.map((entry) => ({
|
|
1881
|
+
layerId: entry.layerId,
|
|
1882
|
+
overrideVisible: entry.visible,
|
|
1883
|
+
overrideOpacity: entry.opacity
|
|
1884
|
+
}));
|
|
1885
|
+
setControlOverrides(overrides);
|
|
1886
|
+
}, [layerControls]);
|
|
1887
|
+
(0, import_react.useEffect)(() => {
|
|
1888
|
+
if (layerStates) {
|
|
1889
|
+
return;
|
|
1890
|
+
}
|
|
1891
|
+
if (Array.isArray(layerControls) && layerControls.length === 0 && effectiveStates.length > 0) {
|
|
1892
|
+
const reset = resetOverrides(baseStates);
|
|
1893
|
+
setEffectiveStates(reset);
|
|
1894
|
+
onLayerStateChange?.(reset);
|
|
1895
|
+
}
|
|
1896
|
+
}, [baseStates, effectiveStates.length, layerControls, layerStates, onLayerStateChange]);
|
|
1897
|
+
(0, import_react.useEffect)(() => {
|
|
1898
|
+
if (layerStates) {
|
|
1899
|
+
setEffectiveStates(layerStates);
|
|
1900
|
+
}
|
|
1901
|
+
}, [layerStates]);
|
|
1902
|
+
(0, import_react.useEffect)(() => {
|
|
1903
|
+
if (layerStates) {
|
|
1904
|
+
return;
|
|
1905
|
+
}
|
|
1906
|
+
if (mapOverrides.length === 0 && controlOverrides.length === 0 && uiOverrides.length === 0) {
|
|
1907
|
+
setEffectiveStates(baseStates);
|
|
1908
|
+
return;
|
|
1909
|
+
}
|
|
1910
|
+
const combined = [...mapOverrides, ...controlOverrides, ...uiOverrides];
|
|
1911
|
+
const next = applyLayerOverrides(baseStates, combined);
|
|
1912
|
+
setEffectiveStates(next);
|
|
1913
|
+
onLayerStateChange?.(next);
|
|
1914
|
+
}, [baseStates, controlOverrides, layerStates, mapOverrides, onLayerStateChange, uiOverrides]);
|
|
1915
|
+
(0, import_react.useEffect)(() => {
|
|
1916
|
+
if (!Array.isArray(layerControls) || layerControls.length > 0) return;
|
|
1917
|
+
setUiOverrides([]);
|
|
1918
|
+
}, [layerControls]);
|
|
1919
|
+
(0, import_react.useEffect)(() => {
|
|
1920
|
+
if (layerStates) {
|
|
1921
|
+
return;
|
|
1922
|
+
}
|
|
1923
|
+
if (!Array.isArray(layerControls) && effectiveStates.length > 0 && baseStates.length > 0) {
|
|
1924
|
+
const next = resetOverrides(baseStates);
|
|
1925
|
+
setEffectiveStates(next);
|
|
1926
|
+
onLayerStateChange?.(next);
|
|
1927
|
+
}
|
|
1928
|
+
}, [baseStates, effectiveStates.length, layerControls, layerStates, onLayerStateChange]);
|
|
1929
|
+
const upsertUiOverride = (layerId, patch) => {
|
|
1930
|
+
setUiOverrides((prev) => {
|
|
1931
|
+
const filtered = prev.filter((entry) => entry.layerId !== layerId);
|
|
1932
|
+
const nextEntry = {
|
|
1933
|
+
layerId,
|
|
1934
|
+
...patch
|
|
1935
|
+
};
|
|
1936
|
+
return [...filtered, nextEntry];
|
|
1937
|
+
});
|
|
1938
|
+
};
|
|
1939
|
+
const updateOpacityFromUi = (0, import_react.useCallback)(
|
|
1940
|
+
(layerId, uiOpacity) => {
|
|
1941
|
+
const meta = layerMetaIndex.get(String(layerId));
|
|
1942
|
+
const zoomFactor = getLayerZoomOpacityFactor(
|
|
1943
|
+
currentZoom,
|
|
1944
|
+
meta?.layerType,
|
|
1945
|
+
meta?.geometryType
|
|
1946
|
+
);
|
|
1947
|
+
const baseOpacity = clampOpacity3(uiOpacity / zoomFactor);
|
|
1948
|
+
const effectiveOpacity = getEffectiveLayerOpacity(
|
|
1949
|
+
baseOpacity,
|
|
1950
|
+
currentZoom,
|
|
1951
|
+
meta?.layerType,
|
|
1952
|
+
meta?.geometryType
|
|
1953
|
+
);
|
|
1954
|
+
if (layerStates && onLayerStateChange) {
|
|
1955
|
+
const next = effectiveStates.map(
|
|
1956
|
+
(state) => state.layerId === layerId ? {
|
|
1957
|
+
...state,
|
|
1958
|
+
baseOpacity,
|
|
1959
|
+
opacity: effectiveOpacity,
|
|
1960
|
+
overrideOpacity: uiOpacity
|
|
1961
|
+
} : state
|
|
1962
|
+
);
|
|
1963
|
+
onLayerStateChange(next);
|
|
1964
|
+
return;
|
|
1965
|
+
}
|
|
1966
|
+
setBaseStates(
|
|
1967
|
+
(prev) => prev.map((state) => state.layerId === layerId ? { ...state, baseOpacity } : state)
|
|
1968
|
+
);
|
|
1969
|
+
setUiOverrides((prev) => {
|
|
1970
|
+
const existing = prev.find((entry) => entry.layerId === layerId);
|
|
1971
|
+
const filtered = prev.filter((entry) => entry.layerId !== layerId);
|
|
1972
|
+
if (existing && existing.overrideVisible !== void 0) {
|
|
1973
|
+
return [
|
|
1974
|
+
...filtered,
|
|
1975
|
+
{ layerId, overrideVisible: existing.overrideVisible }
|
|
1976
|
+
];
|
|
1977
|
+
}
|
|
1978
|
+
return filtered;
|
|
1979
|
+
});
|
|
1980
|
+
},
|
|
1981
|
+
[currentZoom, effectiveStates, layerMetaIndex, layerStates, onLayerStateChange]
|
|
1982
|
+
);
|
|
1983
|
+
const center = (0, import_react.useMemo)(() => {
|
|
1984
|
+
if (initialCenter) {
|
|
1985
|
+
return initialCenter;
|
|
1986
|
+
}
|
|
1987
|
+
const normalized = normalizeCenterTuple(map?.settings?.center);
|
|
1988
|
+
if (normalized) {
|
|
1989
|
+
return normalized;
|
|
1990
|
+
}
|
|
1991
|
+
return DEFAULT_CENTER;
|
|
1992
|
+
}, [initialCenter, map?.settings?.center]);
|
|
1993
|
+
const zoom = initialZoom ?? map?.settings?.zoom ?? DEFAULT_ZOOM;
|
|
1994
|
+
(0, import_react.useEffect)(() => {
|
|
1995
|
+
setCurrentZoom(zoom);
|
|
1996
|
+
}, [zoom]);
|
|
1997
|
+
const decoratedLayers = (0, import_react.useMemo)(() => {
|
|
1998
|
+
return layers.map((layer) => ({
|
|
1999
|
+
...layer,
|
|
2000
|
+
effective: effectiveStates.find((state) => state.layerId === layer.mapLayer.layerId),
|
|
2001
|
+
data: layerGeojson?.[layer.mapLayer.layerId] ?? layerGeojson?.[String(layer.mapLayer.layerId)] ?? null
|
|
2002
|
+
}));
|
|
2003
|
+
}, [effectiveStates, layerGeojson, layers]);
|
|
2004
|
+
const orderedLayers = (0, import_react.useMemo)(() => {
|
|
2005
|
+
return [...decoratedLayers].filter((layer) => layer.effective?.visible && layer.data).sort((a, b) => a.displayOrder - b.displayOrder);
|
|
2006
|
+
}, [decoratedLayers]);
|
|
2007
|
+
const explicitZoomBBox = (0, import_react.useMemo)(() => {
|
|
2008
|
+
if (zoomToBbox) return zoomToBbox;
|
|
2009
|
+
if (zoomToGeojson) return computeBBoxFromGeojson(zoomToGeojson);
|
|
2010
|
+
return null;
|
|
2011
|
+
}, [zoomToBbox, zoomToGeojson]);
|
|
2012
|
+
const autoZoomBBox = (0, import_react.useMemo)(() => {
|
|
2013
|
+
if (explicitZoomBBox) return null;
|
|
2014
|
+
const visibleBBoxes = orderedLayers.map((layer) => computeBBoxFromGeojson(layer.data));
|
|
2015
|
+
return mergeBBoxes(visibleBBoxes);
|
|
2016
|
+
}, [explicitZoomBBox, orderedLayers]);
|
|
2017
|
+
const resolveLayerStyle = (0, import_react.useCallback)(
|
|
2018
|
+
(layerId) => {
|
|
2019
|
+
return getStyleByLayerId(layerId, mapLayers) ?? layerStyleIndex.get(String(layerId)) ?? null;
|
|
2020
|
+
},
|
|
2021
|
+
[layerStyleIndex, mapLayers]
|
|
2022
|
+
);
|
|
2023
|
+
const labelMarkers = (0, import_react.useMemo)(() => {
|
|
2024
|
+
const markers = [];
|
|
2025
|
+
decoratedLayers.forEach((layerState) => {
|
|
2026
|
+
if (!layerState.effective?.visible) return;
|
|
2027
|
+
const labelKey = labelKeyIndex.get(String(layerState.mapLayer.layerId));
|
|
2028
|
+
if (!labelKey) return;
|
|
2029
|
+
const data = layerState.data;
|
|
2030
|
+
if (!data) return;
|
|
2031
|
+
const resolvedStyle = resolveLayerStyle(layerState.mapLayer.layerId);
|
|
2032
|
+
const layerColor = resolvedStyle?.fillColor ?? resolvedStyle?.color ?? "rgba(37, 99, 235, 1)";
|
|
2033
|
+
const opacity = layerState.effective?.opacity ?? 1;
|
|
2034
|
+
data.features.forEach((feature, index) => {
|
|
2035
|
+
const properties = feature.properties;
|
|
2036
|
+
const value = properties?.[labelKey];
|
|
2037
|
+
if (value === void 0 || value === null || value === "") return;
|
|
2038
|
+
const bbox = computeBBoxFromFeature(feature);
|
|
2039
|
+
if (!bbox) return;
|
|
2040
|
+
const [lat, lng] = centroidFromBBox(bbox);
|
|
2041
|
+
markers.push({
|
|
2042
|
+
key: `${layerState.mapLayer.layerId}-label-${index}`,
|
|
2043
|
+
position: [lat, lng],
|
|
2044
|
+
label: String(value),
|
|
2045
|
+
opacity,
|
|
2046
|
+
layerId: layerState.mapLayer.layerId,
|
|
2047
|
+
color: layerColor
|
|
2048
|
+
});
|
|
2049
|
+
});
|
|
2050
|
+
});
|
|
2051
|
+
return markers;
|
|
2052
|
+
}, [decoratedLayers, labelKeyIndex, resolveLayerStyle]);
|
|
2053
|
+
const ensureLayerPanes = (0, import_react.useCallback)(
|
|
2054
|
+
(targetMap, targetLayers) => {
|
|
2055
|
+
const baseZIndex = 400;
|
|
2056
|
+
targetLayers.forEach((layer) => {
|
|
2057
|
+
const paneName = `zenit-layer-${layer.layerId}`;
|
|
2058
|
+
const pane = targetMap.getPane(paneName) ?? targetMap.createPane(paneName);
|
|
2059
|
+
const order = Number.isFinite(layer.displayOrder) ? layer.displayOrder : 0;
|
|
2060
|
+
pane.style.zIndex = String(baseZIndex + order);
|
|
2061
|
+
});
|
|
2062
|
+
},
|
|
2063
|
+
[]
|
|
2064
|
+
);
|
|
2065
|
+
const handleMapReady = (0, import_react.useCallback)(
|
|
2066
|
+
(instance) => {
|
|
2067
|
+
setMapInstance(instance);
|
|
2068
|
+
onMapReady?.(instance);
|
|
2069
|
+
},
|
|
2070
|
+
[onMapReady]
|
|
2071
|
+
);
|
|
2072
|
+
(0, import_react.useEffect)(() => {
|
|
2073
|
+
if (!mapInstance) {
|
|
2074
|
+
return;
|
|
2075
|
+
}
|
|
2076
|
+
if (orderedLayers.length === 0) {
|
|
2077
|
+
return;
|
|
2078
|
+
}
|
|
2079
|
+
const layerTargets = orderedLayers.map((layer) => ({
|
|
2080
|
+
layerId: layer.mapLayer.layerId,
|
|
2081
|
+
displayOrder: layer.displayOrder
|
|
2082
|
+
}));
|
|
2083
|
+
ensureLayerPanes(mapInstance, layerTargets);
|
|
2084
|
+
}, [mapInstance, orderedLayers, ensureLayerPanes]);
|
|
2085
|
+
const overlayOnEachFeature = (0, import_react.useMemo)(() => {
|
|
2086
|
+
return (feature, layer) => {
|
|
2087
|
+
const layerId = getFeatureLayerId(feature) ?? void 0;
|
|
2088
|
+
const geometryType = feature?.geometry?.type;
|
|
2089
|
+
const isPointFeature = geometryType === "Point" || geometryType === "MultiPoint" || layer instanceof import_leaflet.default.CircleMarker;
|
|
2090
|
+
const originalStyle = layer instanceof import_leaflet.default.Path ? {
|
|
2091
|
+
color: layer.options.color,
|
|
2092
|
+
weight: layer.options.weight,
|
|
2093
|
+
fillColor: layer.options.fillColor,
|
|
2094
|
+
opacity: layer.options.opacity,
|
|
2095
|
+
fillOpacity: layer.options.fillOpacity
|
|
2096
|
+
} : null;
|
|
2097
|
+
const originalRadius = layer instanceof import_leaflet.default.CircleMarker ? layer.getRadius() : null;
|
|
2098
|
+
if (featureInfoMode === "popup") {
|
|
2099
|
+
const content = buildFeaturePopupHtml(feature);
|
|
2100
|
+
if (content) {
|
|
2101
|
+
layer.bindPopup(content, { maxWidth: 320 });
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
if (isPointFeature && layer.bindTooltip) {
|
|
2105
|
+
layer.bindTooltip("Click para ver detalle", {
|
|
2106
|
+
sticky: true,
|
|
2107
|
+
direction: "top",
|
|
2108
|
+
opacity: 0.9,
|
|
2109
|
+
className: "zenit-map-tooltip"
|
|
2110
|
+
});
|
|
2111
|
+
}
|
|
2112
|
+
layer.on("click", () => onFeatureClick?.(feature, layerId));
|
|
2113
|
+
layer.on("mouseover", () => {
|
|
2114
|
+
if (layer instanceof import_leaflet.default.Path && originalStyle) {
|
|
2115
|
+
layer.setStyle({
|
|
2116
|
+
...originalStyle,
|
|
2117
|
+
weight: (originalStyle.weight ?? 2) + 1,
|
|
2118
|
+
opacity: Math.min(1, (originalStyle.opacity ?? 1) + 0.2),
|
|
2119
|
+
fillOpacity: Math.min(1, (originalStyle.fillOpacity ?? 0.8) + 0.1)
|
|
2120
|
+
});
|
|
2121
|
+
}
|
|
2122
|
+
if (layer instanceof import_leaflet.default.CircleMarker && typeof originalRadius === "number") {
|
|
2123
|
+
layer.setRadius(originalRadius + 1);
|
|
2124
|
+
}
|
|
2125
|
+
onFeatureHover?.(feature, layerId);
|
|
2126
|
+
});
|
|
2127
|
+
layer.on("mouseout", () => {
|
|
2128
|
+
if (layer instanceof import_leaflet.default.Path && originalStyle) {
|
|
2129
|
+
layer.setStyle(originalStyle);
|
|
2130
|
+
}
|
|
2131
|
+
if (layer instanceof import_leaflet.default.CircleMarker && typeof originalRadius === "number") {
|
|
2132
|
+
layer.setRadius(originalRadius);
|
|
2133
|
+
}
|
|
2134
|
+
});
|
|
2135
|
+
};
|
|
2136
|
+
}, [featureInfoMode, onFeatureClick, onFeatureHover]);
|
|
2137
|
+
const buildLayerStyle = (layerId, baseOpacity, feature, layerType) => {
|
|
2138
|
+
const style = resolveLayerStyle(layerId);
|
|
2139
|
+
const featureStyleOverrides = getFeatureStyleOverrides(feature);
|
|
2140
|
+
const resolvedStyle = featureStyleOverrides ? { ...style ?? {}, ...featureStyleOverrides } : style;
|
|
2141
|
+
const geometryType = feature?.geometry?.type;
|
|
2142
|
+
const resolvedLayerType = layerType ?? geometryType;
|
|
2143
|
+
const sanitizedBaseOpacity = clampOpacity3(baseOpacity);
|
|
2144
|
+
const normalizedStyleFill = typeof resolvedStyle?.fillOpacity === "number" ? clampOpacity3(resolvedStyle.fillOpacity) : 0.8;
|
|
2145
|
+
const effectiveOpacity = getEffectiveLayerOpacity(
|
|
2146
|
+
sanitizedBaseOpacity,
|
|
2147
|
+
currentZoom,
|
|
2148
|
+
resolvedLayerType,
|
|
2149
|
+
geometryType
|
|
2150
|
+
);
|
|
2151
|
+
const fillOpacity = clampOpacity3(effectiveOpacity * normalizedStyleFill);
|
|
2152
|
+
const strokeOpacity = clampOpacity3(Math.max(0.35, effectiveOpacity * 0.9));
|
|
2153
|
+
return {
|
|
2154
|
+
color: resolvedStyle?.color ?? resolvedStyle?.fillColor ?? "#2563eb",
|
|
2155
|
+
weight: resolvedStyle?.weight ?? 2,
|
|
2156
|
+
fillColor: resolvedStyle?.fillColor ?? resolvedStyle?.color ?? "#2563eb",
|
|
2157
|
+
opacity: strokeOpacity,
|
|
2158
|
+
fillOpacity
|
|
2159
|
+
};
|
|
2160
|
+
};
|
|
2161
|
+
const buildLabelIcon = (0, import_react.useCallback)((label, opacity, color) => {
|
|
2162
|
+
const size = 60;
|
|
2163
|
+
const innerSize = 44;
|
|
2164
|
+
const textStyles = getLabelTextStyles(color);
|
|
2165
|
+
const safeLabel = escapeHtml(label);
|
|
2166
|
+
const clampedOpacity = Math.min(1, Math.max(0.92, opacity));
|
|
2167
|
+
const innerBackground = withAlpha(color, 0.9);
|
|
2168
|
+
return import_leaflet.default.divIcon({
|
|
2169
|
+
className: "zenit-label-marker",
|
|
2170
|
+
iconSize: [size, size],
|
|
2171
|
+
iconAnchor: [size / 2, size / 2],
|
|
2172
|
+
html: `
|
|
2173
|
+
<div
|
|
2174
|
+
title="${safeLabel}"
|
|
2175
|
+
style="
|
|
2176
|
+
width:${size}px;
|
|
2177
|
+
height:${size}px;
|
|
2178
|
+
border-radius:9999px;
|
|
2179
|
+
background:rgba(255, 255, 255, 0.95);
|
|
2180
|
+
border:3px solid rgba(255, 255, 255, 1);
|
|
2181
|
+
display:flex;
|
|
2182
|
+
align-items:center;
|
|
2183
|
+
justify-content:center;
|
|
2184
|
+
opacity:${clampedOpacity};
|
|
2185
|
+
box-shadow:0 2px 6px rgba(0, 0, 0, 0.25);
|
|
2186
|
+
pointer-events:none;
|
|
2187
|
+
"
|
|
2188
|
+
>
|
|
2189
|
+
<div
|
|
2190
|
+
style="
|
|
2191
|
+
width:${innerSize}px;
|
|
2192
|
+
height:${innerSize}px;
|
|
2193
|
+
border-radius:9999px;
|
|
2194
|
+
background:${innerBackground};
|
|
2195
|
+
display:flex;
|
|
2196
|
+
align-items:center;
|
|
2197
|
+
justify-content:center;
|
|
2198
|
+
box-shadow:inset 0 0 0 1px rgba(15, 23, 42, 0.12);
|
|
2199
|
+
"
|
|
2200
|
+
>
|
|
2201
|
+
<span
|
|
2202
|
+
style="
|
|
2203
|
+
color:${textStyles.color};
|
|
2204
|
+
font-size:20px;
|
|
2205
|
+
font-weight:800;
|
|
2206
|
+
text-shadow:${textStyles.shadow};
|
|
2207
|
+
"
|
|
2208
|
+
>
|
|
2209
|
+
${safeLabel}
|
|
2210
|
+
</span>
|
|
2211
|
+
</div>
|
|
2212
|
+
</div>
|
|
2213
|
+
`
|
|
2214
|
+
});
|
|
2215
|
+
}, []);
|
|
2216
|
+
(0, import_react.useImperativeHandle)(ref, () => ({
|
|
2217
|
+
setLayerOpacity: (layerId, opacity) => {
|
|
2218
|
+
upsertUiOverride(layerId, { overrideOpacity: opacity });
|
|
2219
|
+
},
|
|
2220
|
+
setLayerVisibility: (layerId, visible) => {
|
|
2221
|
+
upsertUiOverride(layerId, { overrideVisible: visible });
|
|
2222
|
+
},
|
|
2223
|
+
fitBounds: (bbox, options) => {
|
|
2224
|
+
if (!mapInstance) return;
|
|
2225
|
+
if (typeof bbox.minLat !== "number" || typeof bbox.minLon !== "number" || typeof bbox.maxLat !== "number" || typeof bbox.maxLon !== "number" || !Number.isFinite(bbox.minLat) || !Number.isFinite(bbox.minLon) || !Number.isFinite(bbox.maxLat) || !Number.isFinite(bbox.maxLon)) {
|
|
2226
|
+
console.warn("[ZenitMap.fitBounds] Invalid bbox: missing or non-finite coordinates", bbox);
|
|
2227
|
+
return;
|
|
2228
|
+
}
|
|
2229
|
+
const bounds = [
|
|
2230
|
+
[bbox.minLat, bbox.minLon],
|
|
2231
|
+
[bbox.maxLat, bbox.maxLon]
|
|
2232
|
+
];
|
|
2233
|
+
const fitOptions = {
|
|
2234
|
+
padding: options?.padding ?? [12, 12],
|
|
2235
|
+
animate: options?.animate ?? true
|
|
2236
|
+
};
|
|
2237
|
+
mapInstance.fitBounds(bounds, fitOptions);
|
|
2238
|
+
},
|
|
2239
|
+
setView: (coordinates, zoom2) => {
|
|
2240
|
+
if (!mapInstance) return;
|
|
2241
|
+
mapInstance.setView([coordinates.lat, coordinates.lon], zoom2 ?? mapInstance.getZoom(), {
|
|
2242
|
+
animate: true
|
|
2243
|
+
});
|
|
2244
|
+
},
|
|
2245
|
+
getLayerSnapshot: () => {
|
|
2246
|
+
return effectiveStates.map((state) => ({
|
|
2247
|
+
layerId: state.layerId,
|
|
2248
|
+
visible: state.visible,
|
|
2249
|
+
opacity: state.opacity
|
|
2250
|
+
}));
|
|
2251
|
+
},
|
|
2252
|
+
restoreLayerSnapshot: (snapshot) => {
|
|
2253
|
+
const overrides = snapshot.map((s) => ({
|
|
2254
|
+
layerId: s.layerId,
|
|
2255
|
+
overrideVisible: s.visible,
|
|
2256
|
+
overrideOpacity: s.opacity
|
|
2257
|
+
}));
|
|
2258
|
+
setUiOverrides(overrides);
|
|
2259
|
+
},
|
|
2260
|
+
highlightFeature: (layerId, featureId) => {
|
|
2261
|
+
upsertUiOverride(layerId, { overrideVisible: true, overrideOpacity: 1 });
|
|
2262
|
+
},
|
|
2263
|
+
getMapInstance: () => mapInstance
|
|
2264
|
+
}), [effectiveStates, mapInstance]);
|
|
2265
|
+
if (loadingMap) {
|
|
2266
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { padding: 16, height, width }, children: "Cargando mapa..." });
|
|
2267
|
+
}
|
|
2268
|
+
if (mapError) {
|
|
2269
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { padding: 16, height, width, color: "red" }, children: [
|
|
2270
|
+
"Error al cargar mapa: ",
|
|
2271
|
+
mapError
|
|
2272
|
+
] });
|
|
2273
|
+
}
|
|
2274
|
+
if (!map) {
|
|
2275
|
+
return null;
|
|
2276
|
+
}
|
|
2277
|
+
const handleZoomChange = (zoomValue) => {
|
|
2278
|
+
setCurrentZoom(zoomValue);
|
|
2279
|
+
onZoomChange?.(zoomValue);
|
|
2280
|
+
};
|
|
2281
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
2282
|
+
"div",
|
|
2283
|
+
{
|
|
2284
|
+
style: {
|
|
2285
|
+
display: "flex",
|
|
2286
|
+
height,
|
|
2287
|
+
width,
|
|
2288
|
+
border: "1px solid #e2e8f0",
|
|
2289
|
+
borderRadius: 4,
|
|
2290
|
+
overflow: "hidden",
|
|
2291
|
+
boxSizing: "border-box"
|
|
2292
|
+
},
|
|
2293
|
+
children: [
|
|
2294
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { flex: 1, position: "relative" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
2295
|
+
import_react_leaflet.MapContainer,
|
|
2296
|
+
{
|
|
2297
|
+
center,
|
|
2298
|
+
zoom,
|
|
2299
|
+
style: { height: "100%", width: "100%" },
|
|
2300
|
+
scrollWheelZoom: true,
|
|
2301
|
+
zoomControl: false,
|
|
2302
|
+
children: [
|
|
2303
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2304
|
+
import_react_leaflet.TileLayer,
|
|
2305
|
+
{
|
|
2306
|
+
url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
|
2307
|
+
attribution: "\xA9 OpenStreetMap contributors"
|
|
2308
|
+
}
|
|
2309
|
+
),
|
|
2310
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_leaflet.ZoomControl, { position: "topright" }),
|
|
2311
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MapInstanceBridge, { onReady: handleMapReady }),
|
|
2312
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FitToBounds, { bbox: explicitZoomBBox ?? void 0 }),
|
|
2313
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AutoFitToBounds, { bbox: autoZoomBBox ?? void 0, enabled: !explicitZoomBBox }),
|
|
2314
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ZoomBasedOpacityHandler, { onZoomChange: handleZoomChange }),
|
|
2315
|
+
orderedLayers.map((layerState) => {
|
|
2316
|
+
const baseOpacity = layerState.effective?.baseOpacity ?? layerState.effective?.opacity ?? 1;
|
|
2317
|
+
const paneName = `zenit-layer-${layerState.mapLayer.layerId}`;
|
|
2318
|
+
const layerType = layerState.layer?.layerType ?? layerState.mapLayer.layerType ?? void 0;
|
|
2319
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2320
|
+
import_react_leaflet.GeoJSON,
|
|
2321
|
+
{
|
|
2322
|
+
data: layerState.data,
|
|
2323
|
+
pane: mapInstance?.getPane(paneName) ? paneName : void 0,
|
|
2324
|
+
style: (feature) => buildLayerStyle(layerState.mapLayer.layerId, baseOpacity, feature, layerType),
|
|
2325
|
+
pointToLayer: (feature, latlng) => import_leaflet.default.circleMarker(latlng, {
|
|
2326
|
+
radius: isMobile ? 8 : 6,
|
|
2327
|
+
...buildLayerStyle(layerState.mapLayer.layerId, baseOpacity, feature, layerType)
|
|
2328
|
+
}),
|
|
2329
|
+
onEachFeature: overlayOnEachFeature
|
|
2330
|
+
},
|
|
2331
|
+
layerState.mapLayer.layerId.toString()
|
|
2332
|
+
);
|
|
2333
|
+
}),
|
|
2334
|
+
overlayGeojson && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2335
|
+
import_react_leaflet.GeoJSON,
|
|
2336
|
+
{
|
|
2337
|
+
data: overlayGeojson,
|
|
2338
|
+
style: overlayStyleFunction,
|
|
2339
|
+
onEachFeature: overlayOnEachFeature
|
|
2340
|
+
},
|
|
2341
|
+
"zenit-overlay-geojson"
|
|
2342
|
+
),
|
|
2343
|
+
labelMarkers.map((marker) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2344
|
+
import_react_leaflet.Marker,
|
|
2345
|
+
{
|
|
2346
|
+
position: marker.position,
|
|
2347
|
+
icon: buildLabelIcon(marker.label, marker.opacity, marker.color),
|
|
2348
|
+
interactive: false
|
|
2349
|
+
},
|
|
2350
|
+
marker.key
|
|
2351
|
+
))
|
|
2352
|
+
]
|
|
2353
|
+
},
|
|
2354
|
+
String(mapId)
|
|
2355
|
+
) }),
|
|
2356
|
+
showLayerPanel && decoratedLayers.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
2357
|
+
"div",
|
|
2358
|
+
{
|
|
2359
|
+
style: {
|
|
2360
|
+
width: 260,
|
|
2361
|
+
borderLeft: "1px solid #e2e8f0",
|
|
2362
|
+
padding: "12px 16px",
|
|
2363
|
+
background: "#fafafa",
|
|
2364
|
+
fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
|
|
2365
|
+
fontSize: 14,
|
|
2366
|
+
overflowY: "auto"
|
|
2367
|
+
},
|
|
2368
|
+
children: [
|
|
2369
|
+
overlayGeojson && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
2370
|
+
"div",
|
|
2371
|
+
{
|
|
2372
|
+
style: {
|
|
2373
|
+
border: "1px solid #bfdbfe",
|
|
2374
|
+
background: "#eff6ff",
|
|
2375
|
+
color: "#1e40af",
|
|
2376
|
+
padding: "8px 10px",
|
|
2377
|
+
borderRadius: 8,
|
|
2378
|
+
marginBottom: 12
|
|
2379
|
+
},
|
|
2380
|
+
children: [
|
|
2381
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontWeight: 600, marginBottom: 4 }, children: "Overlay activo" }),
|
|
2382
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { fontSize: 13 }, children: [
|
|
2383
|
+
"GeoJSON externo con ",
|
|
2384
|
+
(overlayGeojson.features?.length ?? 0).toLocaleString(),
|
|
2385
|
+
" elementos."
|
|
2386
|
+
] })
|
|
2387
|
+
]
|
|
2388
|
+
}
|
|
2389
|
+
),
|
|
2390
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontWeight: 600, marginBottom: 12 }, children: "Capas" }),
|
|
2391
|
+
decoratedLayers.map((layerState) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
2392
|
+
"div",
|
|
2393
|
+
{
|
|
2394
|
+
style: { borderBottom: "1px solid #e5e7eb", paddingBottom: 10, marginBottom: 10 },
|
|
2395
|
+
children: [
|
|
2396
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
|
|
2397
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2398
|
+
"input",
|
|
2399
|
+
{
|
|
2400
|
+
type: "checkbox",
|
|
2401
|
+
checked: layerState.effective?.visible ?? false,
|
|
2402
|
+
onChange: (e) => {
|
|
2403
|
+
const visible = e.target.checked;
|
|
2404
|
+
upsertUiOverride(layerState.mapLayer.layerId, { overrideVisible: visible });
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
),
|
|
2408
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: layerState.layer?.name ?? `Capa ${layerState.mapLayer.layerId}` })
|
|
2409
|
+
] }),
|
|
2410
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginTop: 8 }, children: [
|
|
2411
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: 4 }, children: [
|
|
2412
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "#4a5568" }, children: "Opacidad" }),
|
|
2413
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
|
|
2414
|
+
Math.round((layerState.effective?.opacity ?? 1) * 100),
|
|
2415
|
+
"%"
|
|
2416
|
+
] })
|
|
2417
|
+
] }),
|
|
2418
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2419
|
+
"input",
|
|
2420
|
+
{
|
|
2421
|
+
type: "range",
|
|
2422
|
+
min: 0,
|
|
2423
|
+
max: 1,
|
|
2424
|
+
step: 0.05,
|
|
2425
|
+
value: layerState.effective?.opacity ?? 1,
|
|
2426
|
+
onChange: (e) => {
|
|
2427
|
+
const value = Number(e.target.value);
|
|
2428
|
+
updateOpacityFromUi(layerState.mapLayer.layerId, value);
|
|
2429
|
+
},
|
|
2430
|
+
style: { width: "100%" }
|
|
2431
|
+
}
|
|
2432
|
+
)
|
|
2433
|
+
] })
|
|
2434
|
+
]
|
|
2435
|
+
},
|
|
2436
|
+
layerState.mapLayer.layerId.toString()
|
|
2437
|
+
))
|
|
2438
|
+
]
|
|
2439
|
+
}
|
|
2440
|
+
)
|
|
2441
|
+
]
|
|
2442
|
+
}
|
|
2443
|
+
);
|
|
2444
|
+
});
|
|
2445
|
+
ZenitMap.displayName = "ZenitMap";
|
|
2446
|
+
|
|
2447
|
+
// src/react/ZenitLayerManager.tsx
|
|
2448
|
+
var import_react2 = __toESM(require("react"));
|
|
2449
|
+
|
|
2450
|
+
// src/react/icons.tsx
|
|
2451
|
+
var import_lucide_react = require("lucide-react");
|
|
2452
|
+
|
|
2453
|
+
// src/react/ZenitLayerManager.tsx
|
|
2454
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
2455
|
+
var FLOAT_TOLERANCE = 1e-3;
|
|
2456
|
+
function areEffectiveStatesEqual(a, b) {
|
|
2457
|
+
if (a.length !== b.length) return false;
|
|
2458
|
+
return a.every((state, index) => {
|
|
2459
|
+
const other = b[index];
|
|
2460
|
+
if (!other) return false;
|
|
2461
|
+
const opacityDiff = Math.abs((state.opacity ?? 1) - (other.opacity ?? 1));
|
|
2462
|
+
return String(state.layerId) === String(other.layerId) && (state.visible ?? false) === (other.visible ?? false) && opacityDiff <= FLOAT_TOLERANCE;
|
|
2463
|
+
});
|
|
2464
|
+
}
|
|
2465
|
+
function getLayerColor2(style) {
|
|
2466
|
+
if (!style) return "#94a3b8";
|
|
2467
|
+
return style.fillColor ?? style.color ?? "#94a3b8";
|
|
2468
|
+
}
|
|
2469
|
+
function resolveDisplayOrder(value) {
|
|
2470
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
2471
|
+
if (typeof value === "string") {
|
|
2472
|
+
const parsed = Number.parseFloat(value);
|
|
2473
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
2474
|
+
}
|
|
2475
|
+
return 0;
|
|
2476
|
+
}
|
|
2477
|
+
var ZenitLayerManager = ({
|
|
2478
|
+
client,
|
|
2479
|
+
mapId,
|
|
2480
|
+
side = "right",
|
|
2481
|
+
className,
|
|
2482
|
+
style,
|
|
2483
|
+
height,
|
|
2484
|
+
layerStates,
|
|
2485
|
+
onLayerStatesChange,
|
|
2486
|
+
mapZoom,
|
|
2487
|
+
autoOpacityOnZoom = false,
|
|
2488
|
+
autoOpacityConfig,
|
|
2489
|
+
showUploadTab = true,
|
|
2490
|
+
showLayerVisibilityIcon = true,
|
|
2491
|
+
layerFeatureCounts,
|
|
2492
|
+
mapLayers
|
|
2493
|
+
}) => {
|
|
2494
|
+
const [map, setMap] = (0, import_react2.useState)(null);
|
|
2495
|
+
const [loadingMap, setLoadingMap] = (0, import_react2.useState)(false);
|
|
2496
|
+
const [mapError, setMapError] = (0, import_react2.useState)(null);
|
|
2497
|
+
const [layers, setLayers] = (0, import_react2.useState)([]);
|
|
2498
|
+
const [activeTab, setActiveTab] = (0, import_react2.useState)("layers");
|
|
2499
|
+
const [panelVisible, setPanelVisible] = (0, import_react2.useState)(true);
|
|
2500
|
+
const lastEmittedStatesRef = (0, import_react2.useRef)(null);
|
|
2501
|
+
const isControlled = Array.isArray(layerStates) && typeof onLayerStatesChange === "function";
|
|
2502
|
+
const baseStates = (0, import_react2.useMemo)(
|
|
2503
|
+
() => initLayerStates(
|
|
2504
|
+
layers.map((entry) => ({
|
|
2505
|
+
...entry.mapLayer,
|
|
2506
|
+
layerId: entry.mapLayer.layerId,
|
|
2507
|
+
isVisible: entry.visible,
|
|
2508
|
+
opacity: entry.opacity
|
|
2509
|
+
}))
|
|
2510
|
+
),
|
|
2511
|
+
[layers]
|
|
2512
|
+
);
|
|
2513
|
+
const overrideStates = (0, import_react2.useMemo)(
|
|
2514
|
+
() => layers.map(
|
|
2515
|
+
(entry) => ({
|
|
2516
|
+
layerId: entry.mapLayer.layerId,
|
|
2517
|
+
overrideVisible: entry.visible,
|
|
2518
|
+
overrideOpacity: entry.opacity
|
|
2519
|
+
})
|
|
2520
|
+
),
|
|
2521
|
+
[layers]
|
|
2522
|
+
);
|
|
2523
|
+
const effectiveStates = (0, import_react2.useMemo)(
|
|
2524
|
+
() => layerStates ?? applyLayerOverrides(baseStates, overrideStates),
|
|
2525
|
+
[baseStates, layerStates, overrideStates]
|
|
2526
|
+
);
|
|
2527
|
+
const layerMetaIndex = (0, import_react2.useMemo)(() => {
|
|
2528
|
+
const index = /* @__PURE__ */ new Map();
|
|
2529
|
+
mapLayers?.forEach((entry) => {
|
|
2530
|
+
const key = String(entry.layerId);
|
|
2531
|
+
index.set(key, { layerType: entry.layerType ?? void 0, geometryType: entry.geometryType ?? void 0 });
|
|
2532
|
+
});
|
|
2533
|
+
map?.mapLayers?.forEach((entry) => {
|
|
2534
|
+
const key = String(entry.layerId);
|
|
2535
|
+
if (!index.has(key)) {
|
|
2536
|
+
index.set(key, { layerType: entry.layerType ?? void 0, geometryType: entry.geometryType ?? void 0 });
|
|
2537
|
+
}
|
|
2538
|
+
});
|
|
2539
|
+
return index;
|
|
2540
|
+
}, [map, mapLayers]);
|
|
2541
|
+
const resolveUserOpacity = import_react2.default.useCallback((state) => {
|
|
2542
|
+
if (typeof state.overrideOpacity === "number") return state.overrideOpacity;
|
|
2543
|
+
if (typeof state.overrideOpacity === "string") {
|
|
2544
|
+
const parsed = Number.parseFloat(state.overrideOpacity);
|
|
2545
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
2546
|
+
}
|
|
2547
|
+
return state.opacity ?? 1;
|
|
2548
|
+
}, []);
|
|
2549
|
+
const resolveEffectiveOpacity = import_react2.default.useCallback(
|
|
2550
|
+
(layerId, userOpacity) => {
|
|
2551
|
+
if (!autoOpacityOnZoom || typeof mapZoom !== "number") {
|
|
2552
|
+
return userOpacity;
|
|
2553
|
+
}
|
|
2554
|
+
const meta = layerMetaIndex.get(String(layerId));
|
|
2555
|
+
return getEffectiveLayerOpacity(
|
|
2556
|
+
userOpacity,
|
|
2557
|
+
mapZoom,
|
|
2558
|
+
meta?.layerType,
|
|
2559
|
+
meta?.geometryType,
|
|
2560
|
+
autoOpacityConfig
|
|
2561
|
+
);
|
|
2562
|
+
},
|
|
2563
|
+
[autoOpacityConfig, autoOpacityOnZoom, layerMetaIndex, mapZoom]
|
|
2564
|
+
);
|
|
2565
|
+
const effectiveStatesWithZoom = (0, import_react2.useMemo)(() => {
|
|
2566
|
+
if (!autoOpacityOnZoom || typeof mapZoom !== "number") {
|
|
2567
|
+
return effectiveStates;
|
|
2568
|
+
}
|
|
2569
|
+
return effectiveStates.map((state) => {
|
|
2570
|
+
const userOpacity = resolveUserOpacity(state);
|
|
2571
|
+
const adjustedOpacity = resolveEffectiveOpacity(state.layerId, userOpacity);
|
|
2572
|
+
return {
|
|
2573
|
+
...state,
|
|
2574
|
+
opacity: adjustedOpacity,
|
|
2575
|
+
overrideOpacity: userOpacity
|
|
2576
|
+
};
|
|
2577
|
+
});
|
|
2578
|
+
}, [autoOpacityOnZoom, effectiveStates, mapZoom, resolveEffectiveOpacity, resolveUserOpacity]);
|
|
2579
|
+
(0, import_react2.useEffect)(() => {
|
|
2580
|
+
let cancelled = false;
|
|
2581
|
+
setLoadingMap(true);
|
|
2582
|
+
setMapError(null);
|
|
2583
|
+
setLayers([]);
|
|
2584
|
+
client.maps.getMap(mapId, true).then((mapResponse) => {
|
|
2585
|
+
if (cancelled) return;
|
|
2586
|
+
const extractedMap = extractMapDto(mapResponse);
|
|
2587
|
+
const resolvedMap = extractedMap ? normalizeMapCenter(extractedMap) : null;
|
|
2588
|
+
setMap(resolvedMap);
|
|
2589
|
+
const normalizedLayers = normalizeMapLayers(resolvedMap);
|
|
2590
|
+
setLayers(
|
|
2591
|
+
normalizedLayers.map((entry, index) => ({
|
|
2592
|
+
mapLayer: { ...entry.mapLayer, layerId: entry.layerId },
|
|
2593
|
+
layer: entry.layer,
|
|
2594
|
+
visible: entry.isVisible,
|
|
2595
|
+
opacity: entry.opacity,
|
|
2596
|
+
displayOrder: entry.displayOrder,
|
|
2597
|
+
initialIndex: index
|
|
2598
|
+
}))
|
|
2599
|
+
);
|
|
2600
|
+
}).catch((err) => {
|
|
2601
|
+
if (cancelled) return;
|
|
2602
|
+
const message = err instanceof Error ? err.message : "No se pudo cargar el mapa";
|
|
2603
|
+
setMapError(message);
|
|
2604
|
+
}).finally(() => {
|
|
2605
|
+
if (!cancelled) setLoadingMap(false);
|
|
2606
|
+
});
|
|
2607
|
+
return () => {
|
|
2608
|
+
cancelled = true;
|
|
2609
|
+
};
|
|
2610
|
+
}, [client.maps, mapId]);
|
|
2611
|
+
(0, import_react2.useEffect)(() => {
|
|
2612
|
+
if (!showUploadTab && activeTab === "upload") {
|
|
2613
|
+
setActiveTab("layers");
|
|
2614
|
+
}
|
|
2615
|
+
}, [activeTab, showUploadTab]);
|
|
2616
|
+
(0, import_react2.useEffect)(() => {
|
|
2617
|
+
if (isControlled) return;
|
|
2618
|
+
if (!onLayerStatesChange) return;
|
|
2619
|
+
const emitStates = autoOpacityOnZoom && typeof mapZoom === "number" ? effectiveStatesWithZoom : effectiveStates;
|
|
2620
|
+
const previous = lastEmittedStatesRef.current;
|
|
2621
|
+
if (previous && areEffectiveStatesEqual(previous, emitStates)) {
|
|
2622
|
+
return;
|
|
2623
|
+
}
|
|
2624
|
+
lastEmittedStatesRef.current = emitStates;
|
|
2625
|
+
onLayerStatesChange(emitStates);
|
|
2626
|
+
}, [
|
|
2627
|
+
autoOpacityOnZoom,
|
|
2628
|
+
effectiveStates,
|
|
2629
|
+
effectiveStatesWithZoom,
|
|
2630
|
+
isControlled,
|
|
2631
|
+
mapZoom,
|
|
2632
|
+
onLayerStatesChange
|
|
2633
|
+
]);
|
|
2634
|
+
const updateLayerVisible = import_react2.default.useCallback(
|
|
2635
|
+
(layerId, visible) => {
|
|
2636
|
+
if (!onLayerStatesChange) return;
|
|
2637
|
+
const next = effectiveStates.map(
|
|
2638
|
+
(state) => state.layerId === layerId ? { ...state, visible, overrideVisible: visible } : state
|
|
2639
|
+
);
|
|
2640
|
+
onLayerStatesChange(next);
|
|
2641
|
+
},
|
|
2642
|
+
[effectiveStates, onLayerStatesChange]
|
|
2643
|
+
);
|
|
2644
|
+
const updateLayerOpacity = import_react2.default.useCallback(
|
|
2645
|
+
(layerId, opacity) => {
|
|
2646
|
+
if (!onLayerStatesChange) return;
|
|
2647
|
+
const adjustedOpacity = resolveEffectiveOpacity(layerId, opacity);
|
|
2648
|
+
const next = effectiveStates.map(
|
|
2649
|
+
(state) => state.layerId === layerId ? { ...state, opacity: adjustedOpacity, overrideOpacity: opacity } : state
|
|
2650
|
+
);
|
|
2651
|
+
onLayerStatesChange(next);
|
|
2652
|
+
},
|
|
2653
|
+
[effectiveStates, onLayerStatesChange, resolveEffectiveOpacity]
|
|
2654
|
+
);
|
|
2655
|
+
const resolveFeatureCount = import_react2.default.useCallback(
|
|
2656
|
+
(layerId, layer) => {
|
|
2657
|
+
const resolvedFeatureCount = layerFeatureCounts?.[layerId] ?? layerFeatureCounts?.[String(layerId)];
|
|
2658
|
+
if (typeof resolvedFeatureCount === "number") return resolvedFeatureCount;
|
|
2659
|
+
const featureCount = layer?.featuresCount ?? layer?.featureCount ?? layer?.totalFeatures;
|
|
2660
|
+
return typeof featureCount === "number" ? featureCount : null;
|
|
2661
|
+
},
|
|
2662
|
+
[layerFeatureCounts]
|
|
2663
|
+
);
|
|
2664
|
+
const decoratedLayers = (0, import_react2.useMemo)(() => {
|
|
2665
|
+
return layers.map((entry) => ({
|
|
2666
|
+
...entry,
|
|
2667
|
+
effective: effectiveStates.find((state) => state.layerId === entry.mapLayer.layerId),
|
|
2668
|
+
featureCount: resolveFeatureCount(entry.mapLayer.layerId, entry.layer),
|
|
2669
|
+
layerName: entry.layer?.name ?? entry.mapLayer.name ?? `Capa ${entry.mapLayer.layerId}`
|
|
2670
|
+
})).sort((a, b) => {
|
|
2671
|
+
const aOrder = resolveDisplayOrder(
|
|
2672
|
+
a.displayOrder ?? a.mapLayer.displayOrder ?? a.mapLayer.order
|
|
2673
|
+
);
|
|
2674
|
+
const bOrder = resolveDisplayOrder(
|
|
2675
|
+
b.displayOrder ?? b.mapLayer.displayOrder ?? b.mapLayer.order
|
|
2676
|
+
);
|
|
2677
|
+
const aHasOrder = a.displayOrder ?? a.mapLayer.displayOrder ?? a.mapLayer.order ?? null;
|
|
2678
|
+
const bHasOrder = b.displayOrder ?? b.mapLayer.displayOrder ?? b.mapLayer.order ?? null;
|
|
2679
|
+
if (aHasOrder !== null && bHasOrder !== null) {
|
|
2680
|
+
const orderCompare = aOrder - bOrder;
|
|
2681
|
+
if (orderCompare !== 0) return orderCompare;
|
|
2682
|
+
}
|
|
2683
|
+
const aInitial = a.initialIndex ?? 0;
|
|
2684
|
+
const bInitial = b.initialIndex ?? 0;
|
|
2685
|
+
if (aInitial !== bInitial) return aInitial - bInitial;
|
|
2686
|
+
const aName = a.layerName ?? String(a.mapLayer.layerId);
|
|
2687
|
+
const bName = b.layerName ?? String(b.mapLayer.layerId);
|
|
2688
|
+
const nameCompare = aName.localeCompare(bName, void 0, { sensitivity: "base" });
|
|
2689
|
+
if (nameCompare !== 0) return nameCompare;
|
|
2690
|
+
return String(a.mapLayer.layerId).localeCompare(String(b.mapLayer.layerId));
|
|
2691
|
+
});
|
|
2692
|
+
}, [effectiveStates, layers, resolveFeatureCount]);
|
|
2693
|
+
const resolveLayerStyle = import_react2.default.useCallback(
|
|
2694
|
+
(layerId) => {
|
|
2695
|
+
const layerKey = String(layerId);
|
|
2696
|
+
const fromProp = mapLayers?.find((entry) => String(entry.layerId) === layerKey)?.style;
|
|
2697
|
+
if (fromProp) return fromProp;
|
|
2698
|
+
const fromMapDto = map?.mapLayers?.find((entry) => String(entry.layerId) === layerKey);
|
|
2699
|
+
if (fromMapDto?.layer) {
|
|
2700
|
+
const layer = fromMapDto.layer;
|
|
2701
|
+
if (layer.style) return layer.style;
|
|
2702
|
+
}
|
|
2703
|
+
return void 0;
|
|
2704
|
+
},
|
|
2705
|
+
[map, mapLayers]
|
|
2706
|
+
);
|
|
2707
|
+
const panelStyle = {
|
|
2708
|
+
width: 360,
|
|
2709
|
+
borderLeft: side === "right" ? "1px solid #e2e8f0" : void 0,
|
|
2710
|
+
borderRight: side === "left" ? "1px solid #e2e8f0" : void 0,
|
|
2711
|
+
background: "#f1f5f9",
|
|
2712
|
+
fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
|
|
2713
|
+
fontSize: 14,
|
|
2714
|
+
boxSizing: "border-box",
|
|
2715
|
+
height: height ?? "100%",
|
|
2716
|
+
display: "flex",
|
|
2717
|
+
flexDirection: "column",
|
|
2718
|
+
boxShadow: "0 12px 28px rgba(15, 23, 42, 0.08)",
|
|
2719
|
+
...style,
|
|
2720
|
+
...height ? { height } : {}
|
|
2721
|
+
};
|
|
2722
|
+
if (loadingMap) {
|
|
2723
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className, style: panelStyle, children: "Cargando capas\u2026" });
|
|
2724
|
+
}
|
|
2725
|
+
if (mapError) {
|
|
2726
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className, style: { ...panelStyle, color: "#c53030" }, children: [
|
|
2727
|
+
"Error al cargar mapa: ",
|
|
2728
|
+
mapError
|
|
2729
|
+
] });
|
|
2730
|
+
}
|
|
2731
|
+
if (!map) {
|
|
2732
|
+
return null;
|
|
2733
|
+
}
|
|
2734
|
+
const headerStyle = {
|
|
2735
|
+
padding: "14px 16px",
|
|
2736
|
+
borderBottom: "1px solid #e2e8f0",
|
|
2737
|
+
background: "#fff",
|
|
2738
|
+
position: "sticky",
|
|
2739
|
+
top: 0,
|
|
2740
|
+
zIndex: 2,
|
|
2741
|
+
boxShadow: "0 1px 0 rgba(148, 163, 184, 0.25)"
|
|
2742
|
+
};
|
|
2743
|
+
const renderLayerCards = () => {
|
|
2744
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: decoratedLayers.map((layerState) => {
|
|
2745
|
+
const layerId = layerState.mapLayer.layerId;
|
|
2746
|
+
const layerName = layerState.layerName ?? `Capa ${layerId}`;
|
|
2747
|
+
const visible = layerState.effective?.visible ?? false;
|
|
2748
|
+
const userOpacity = layerState.effective ? resolveUserOpacity(layerState.effective) : 1;
|
|
2749
|
+
const featureCount = layerState.featureCount;
|
|
2750
|
+
const layerColor = getLayerColor2(resolveLayerStyle(layerId));
|
|
2751
|
+
const muted = !visible;
|
|
2752
|
+
const opacityPercent = Math.round(userOpacity * 100);
|
|
2753
|
+
const sliderBackground = `linear-gradient(to right, ${layerColor} 0%, ${layerColor} ${opacityPercent}%, #e5e7eb ${opacityPercent}%, #e5e7eb 100%)`;
|
|
2754
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
2755
|
+
"div",
|
|
2756
|
+
{
|
|
2757
|
+
className: `zlm-card${muted ? " is-muted" : ""}`,
|
|
2758
|
+
style: {
|
|
2759
|
+
border: "1px solid #e2e8f0",
|
|
2760
|
+
borderRadius: 12,
|
|
2761
|
+
padding: 12,
|
|
2762
|
+
background: "#fff",
|
|
2763
|
+
display: "flex",
|
|
2764
|
+
flexDirection: "column",
|
|
2765
|
+
gap: 10,
|
|
2766
|
+
width: "100%"
|
|
2767
|
+
},
|
|
2768
|
+
children: [
|
|
2769
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", gap: 12 }, children: [
|
|
2770
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: 10, alignItems: "flex-start", minWidth: 0, flex: 1 }, children: [
|
|
2771
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2772
|
+
"div",
|
|
2773
|
+
{
|
|
2774
|
+
style: {
|
|
2775
|
+
width: 14,
|
|
2776
|
+
height: 14,
|
|
2777
|
+
borderRadius: "50%",
|
|
2778
|
+
backgroundColor: layerColor,
|
|
2779
|
+
border: "2px solid #e2e8f0",
|
|
2780
|
+
flexShrink: 0,
|
|
2781
|
+
marginTop: 4
|
|
2782
|
+
},
|
|
2783
|
+
title: "Color de la capa"
|
|
2784
|
+
}
|
|
2785
|
+
),
|
|
2786
|
+
showLayerVisibilityIcon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2787
|
+
"button",
|
|
2788
|
+
{
|
|
2789
|
+
type: "button",
|
|
2790
|
+
className: `zlm-icon-button${visible ? " is-active" : ""}`,
|
|
2791
|
+
onClick: () => isControlled ? updateLayerVisible(layerId, !visible) : setLayers(
|
|
2792
|
+
(prev) => prev.map(
|
|
2793
|
+
(entry) => entry.mapLayer.layerId === layerId ? { ...entry, visible: !visible } : entry
|
|
2794
|
+
)
|
|
2795
|
+
),
|
|
2796
|
+
"aria-label": visible ? "Ocultar capa" : "Mostrar capa",
|
|
2797
|
+
children: visible ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.Eye, { size: 16 }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.EyeOff, { size: 16 })
|
|
2798
|
+
}
|
|
2799
|
+
),
|
|
2800
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { minWidth: 0, flex: 1 }, children: [
|
|
2801
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2802
|
+
"div",
|
|
2803
|
+
{
|
|
2804
|
+
className: "zlm-layer-name",
|
|
2805
|
+
style: {
|
|
2806
|
+
fontWeight: 700,
|
|
2807
|
+
display: "-webkit-box",
|
|
2808
|
+
WebkitLineClamp: 2,
|
|
2809
|
+
WebkitBoxOrient: "vertical",
|
|
2810
|
+
overflow: "hidden",
|
|
2811
|
+
overflowWrap: "anywhere",
|
|
2812
|
+
lineHeight: 1.2,
|
|
2813
|
+
color: muted ? "#64748b" : "#0f172a"
|
|
2814
|
+
},
|
|
2815
|
+
title: layerName,
|
|
2816
|
+
children: layerName
|
|
2817
|
+
}
|
|
2818
|
+
),
|
|
2819
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { color: muted ? "#94a3b8" : "#64748b", fontSize: 12 }, children: [
|
|
2820
|
+
"ID ",
|
|
2821
|
+
layerId
|
|
2822
|
+
] })
|
|
2823
|
+
] })
|
|
2824
|
+
] }),
|
|
2825
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "flex", alignItems: "flex-start", gap: 6, flexShrink: 0 }, children: typeof featureCount === "number" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "zlm-badge", children: [
|
|
2826
|
+
featureCount.toLocaleString(),
|
|
2827
|
+
" features"
|
|
2828
|
+
] }) })
|
|
2829
|
+
] }),
|
|
2830
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "flex", gap: 10, alignItems: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { flex: 1 }, children: [
|
|
2831
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: 6, color: "#64748b", fontSize: 12 }, children: [
|
|
2832
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "Opacidad" }),
|
|
2833
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [
|
|
2834
|
+
opacityPercent,
|
|
2835
|
+
"%"
|
|
2836
|
+
] })
|
|
2837
|
+
] }),
|
|
2838
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2839
|
+
"input",
|
|
2840
|
+
{
|
|
2841
|
+
className: "zlm-range",
|
|
2842
|
+
type: "range",
|
|
2843
|
+
min: 0,
|
|
2844
|
+
max: 1,
|
|
2845
|
+
step: 0.05,
|
|
2846
|
+
value: userOpacity,
|
|
2847
|
+
style: {
|
|
2848
|
+
width: "100%",
|
|
2849
|
+
accentColor: layerColor,
|
|
2850
|
+
background: sliderBackground,
|
|
2851
|
+
height: 6,
|
|
2852
|
+
borderRadius: 999
|
|
2853
|
+
},
|
|
2854
|
+
onChange: (e) => {
|
|
2855
|
+
const value = Number(e.target.value);
|
|
2856
|
+
if (isControlled) {
|
|
2857
|
+
updateLayerOpacity(layerId, value);
|
|
2858
|
+
return;
|
|
2859
|
+
}
|
|
2860
|
+
setLayers(
|
|
2861
|
+
(prev) => prev.map(
|
|
2862
|
+
(entry) => entry.mapLayer.layerId === layerId ? { ...entry, opacity: value } : entry
|
|
2863
|
+
)
|
|
2864
|
+
);
|
|
2865
|
+
},
|
|
2866
|
+
"aria-label": `Opacidad de la capa ${layerName}`
|
|
2867
|
+
}
|
|
2868
|
+
)
|
|
2869
|
+
] }) })
|
|
2870
|
+
]
|
|
2871
|
+
},
|
|
2872
|
+
layerId.toString()
|
|
2873
|
+
);
|
|
2874
|
+
}) });
|
|
2875
|
+
};
|
|
2876
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: ["zenit-layer-manager", className].filter(Boolean).join(" "), style: panelStyle, children: [
|
|
2877
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: `
|
|
2878
|
+
.zenit-layer-manager .zlm-card {
|
|
2879
|
+
transition: box-shadow 0.2s ease, transform 0.2s ease, opacity 0.2s ease;
|
|
2880
|
+
box-shadow: 0 6px 16px rgba(15, 23, 42, 0.08);
|
|
2881
|
+
}
|
|
2882
|
+
.zenit-layer-manager .zlm-card.is-muted {
|
|
2883
|
+
opacity: 0.7;
|
|
2884
|
+
}
|
|
2885
|
+
.zenit-layer-manager .zlm-badge {
|
|
2886
|
+
display: inline-flex;
|
|
2887
|
+
align-items: center;
|
|
2888
|
+
gap: 4px;
|
|
2889
|
+
margin-top: 6px;
|
|
2890
|
+
padding: 2px 8px;
|
|
2891
|
+
border-radius: 999px;
|
|
2892
|
+
background: #f1f5f9;
|
|
2893
|
+
color: #475569;
|
|
2894
|
+
font-size: 11px;
|
|
2895
|
+
font-weight: 600;
|
|
2896
|
+
}
|
|
2897
|
+
.zenit-layer-manager .zlm-icon-button {
|
|
2898
|
+
border: 1px solid #e2e8f0;
|
|
2899
|
+
background: #f8fafc;
|
|
2900
|
+
color: #475569;
|
|
2901
|
+
border-radius: 8px;
|
|
2902
|
+
width: 34px;
|
|
2903
|
+
height: 34px;
|
|
2904
|
+
display: inline-flex;
|
|
2905
|
+
align-items: center;
|
|
2906
|
+
justify-content: center;
|
|
2907
|
+
cursor: pointer;
|
|
2908
|
+
transition: all 0.15s ease;
|
|
2909
|
+
}
|
|
2910
|
+
.zenit-layer-manager .zlm-icon-button.is-active {
|
|
2911
|
+
background: #0f172a;
|
|
2912
|
+
color: #fff;
|
|
2913
|
+
border-color: #0f172a;
|
|
2914
|
+
}
|
|
2915
|
+
.zenit-layer-manager .zlm-icon-button:hover {
|
|
2916
|
+
box-shadow: 0 4px 10px rgba(15, 23, 42, 0.12);
|
|
2917
|
+
}
|
|
2918
|
+
.zenit-layer-manager .zlm-icon-button:focus-visible {
|
|
2919
|
+
outline: 2px solid #60a5fa;
|
|
2920
|
+
outline-offset: 2px;
|
|
2921
|
+
}
|
|
2922
|
+
.zenit-layer-manager .zlm-range {
|
|
2923
|
+
width: 100%;
|
|
2924
|
+
accent-color: #0f172a;
|
|
2925
|
+
}
|
|
2926
|
+
.zenit-layer-manager .zlm-tab {
|
|
2927
|
+
flex: 1;
|
|
2928
|
+
padding: 8px 12px;
|
|
2929
|
+
border: none;
|
|
2930
|
+
background: transparent;
|
|
2931
|
+
color: #475569;
|
|
2932
|
+
font-weight: 600;
|
|
2933
|
+
cursor: pointer;
|
|
2934
|
+
display: inline-flex;
|
|
2935
|
+
align-items: center;
|
|
2936
|
+
justify-content: center;
|
|
2937
|
+
gap: 6px;
|
|
2938
|
+
font-size: 13px;
|
|
2939
|
+
}
|
|
2940
|
+
.zenit-layer-manager .zlm-tab.is-active {
|
|
2941
|
+
background: #fff;
|
|
2942
|
+
color: #0f172a;
|
|
2943
|
+
box-shadow: 0 4px 10px rgba(15, 23, 42, 0.08);
|
|
2944
|
+
border-radius: 10px;
|
|
2945
|
+
}
|
|
2946
|
+
.zenit-layer-manager .zlm-tab:focus-visible {
|
|
2947
|
+
outline: 2px solid #60a5fa;
|
|
2948
|
+
outline-offset: 2px;
|
|
2949
|
+
}
|
|
2950
|
+
.zenit-layer-manager .zlm-panel-toggle {
|
|
2951
|
+
border: 1px solid #e2e8f0;
|
|
2952
|
+
background: #fff;
|
|
2953
|
+
color: #0f172a;
|
|
2954
|
+
border-radius: 10px;
|
|
2955
|
+
padding: 6px 10px;
|
|
2956
|
+
display: inline-flex;
|
|
2957
|
+
align-items: center;
|
|
2958
|
+
gap: 6px;
|
|
2959
|
+
font-size: 12px;
|
|
2960
|
+
font-weight: 600;
|
|
2961
|
+
cursor: pointer;
|
|
2962
|
+
transition: all 0.15s ease;
|
|
2963
|
+
}
|
|
2964
|
+
.zenit-layer-manager .zlm-panel-toggle:hover {
|
|
2965
|
+
box-shadow: 0 4px 10px rgba(15, 23, 42, 0.12);
|
|
2966
|
+
}
|
|
2967
|
+
.zenit-layer-manager .zlm-panel-toggle:focus-visible {
|
|
2968
|
+
outline: 2px solid #60a5fa;
|
|
2969
|
+
outline-offset: 2px;
|
|
2970
|
+
}
|
|
2971
|
+
` }),
|
|
2972
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: headerStyle, children: [
|
|
2973
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
|
|
2974
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
2975
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontWeight: 800, fontSize: 16, color: "#0f172a" }, children: "Gesti\xF3n de Capas" }),
|
|
2976
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { color: "#64748b", fontSize: 12 }, children: [
|
|
2977
|
+
"Mapa #",
|
|
2978
|
+
map.id
|
|
2979
|
+
] })
|
|
2980
|
+
] }),
|
|
2981
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
2982
|
+
"button",
|
|
2983
|
+
{
|
|
2984
|
+
type: "button",
|
|
2985
|
+
onClick: () => setPanelVisible((prev) => !prev),
|
|
2986
|
+
className: "zlm-panel-toggle",
|
|
2987
|
+
"aria-label": panelVisible ? "Ocultar panel de capas" : "Mostrar panel de capas",
|
|
2988
|
+
children: [
|
|
2989
|
+
panelVisible ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.Eye, { size: 16 }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.EyeOff, { size: 16 }),
|
|
2990
|
+
panelVisible ? "Ocultar" : "Mostrar"
|
|
2991
|
+
]
|
|
2992
|
+
}
|
|
2993
|
+
)
|
|
2994
|
+
] }),
|
|
2995
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
2996
|
+
"div",
|
|
2997
|
+
{
|
|
2998
|
+
style: {
|
|
2999
|
+
display: "flex",
|
|
3000
|
+
gap: 6,
|
|
3001
|
+
marginTop: 12,
|
|
3002
|
+
padding: 4,
|
|
3003
|
+
border: "1px solid #e2e8f0",
|
|
3004
|
+
borderRadius: 12,
|
|
3005
|
+
background: "#f1f5f9"
|
|
3006
|
+
},
|
|
3007
|
+
children: [
|
|
3008
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
3009
|
+
"button",
|
|
3010
|
+
{
|
|
3011
|
+
type: "button",
|
|
3012
|
+
className: `zlm-tab${activeTab === "layers" ? " is-active" : ""}`,
|
|
3013
|
+
onClick: () => setActiveTab("layers"),
|
|
3014
|
+
children: [
|
|
3015
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.Layers, { size: 16 }),
|
|
3016
|
+
"Capas"
|
|
3017
|
+
]
|
|
3018
|
+
}
|
|
3019
|
+
),
|
|
3020
|
+
showUploadTab && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
3021
|
+
"button",
|
|
3022
|
+
{
|
|
3023
|
+
type: "button",
|
|
3024
|
+
className: `zlm-tab${activeTab === "upload" ? " is-active" : ""}`,
|
|
3025
|
+
onClick: () => setActiveTab("upload"),
|
|
3026
|
+
children: [
|
|
3027
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.Upload, { size: 16 }),
|
|
3028
|
+
"Subir"
|
|
3029
|
+
]
|
|
3030
|
+
}
|
|
3031
|
+
)
|
|
3032
|
+
]
|
|
3033
|
+
}
|
|
3034
|
+
)
|
|
3035
|
+
] }),
|
|
3036
|
+
panelVisible && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { padding: "12px 10px 18px", overflowY: "auto", flex: 1, minHeight: 0 }, children: [
|
|
3037
|
+
activeTab === "layers" && renderLayerCards(),
|
|
3038
|
+
showUploadTab && activeTab === "upload" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { color: "#475569", fontSize: 13 }, children: "Pr\xF3ximamente podr\xE1s subir capas desde este panel." })
|
|
3039
|
+
] })
|
|
3040
|
+
] });
|
|
3041
|
+
};
|
|
3042
|
+
|
|
3043
|
+
// src/react/ZenitFeatureFilterPanel.tsx
|
|
3044
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
3045
|
+
var ZenitFeatureFilterPanel = ({
|
|
3046
|
+
title = "Filtros",
|
|
3047
|
+
description,
|
|
3048
|
+
className,
|
|
3049
|
+
style,
|
|
3050
|
+
children
|
|
3051
|
+
}) => {
|
|
3052
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
3053
|
+
"section",
|
|
3054
|
+
{
|
|
3055
|
+
className,
|
|
3056
|
+
style: {
|
|
3057
|
+
border: "1px solid #e2e8f0",
|
|
3058
|
+
borderRadius: 12,
|
|
3059
|
+
padding: 16,
|
|
3060
|
+
background: "#fff",
|
|
3061
|
+
fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
|
|
3062
|
+
...style
|
|
3063
|
+
},
|
|
3064
|
+
children: [
|
|
3065
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("header", { style: { marginBottom: 12 }, children: [
|
|
3066
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { style: { margin: 0, fontSize: 16 }, children: title }),
|
|
3067
|
+
description && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: { margin: "6px 0 0", color: "#475569", fontSize: 13 }, children: description })
|
|
3068
|
+
] }),
|
|
3069
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { children })
|
|
3070
|
+
]
|
|
3071
|
+
}
|
|
3072
|
+
);
|
|
3073
|
+
};
|
|
3074
|
+
|
|
3075
|
+
// src/react/ai/FloatingChatBox.tsx
|
|
3076
|
+
var import_react4 = require("react");
|
|
3077
|
+
var import_react_dom = require("react-dom");
|
|
3078
|
+
|
|
3079
|
+
// src/react/hooks/use-chat.ts
|
|
3080
|
+
var import_react3 = require("react");
|
|
3081
|
+
var useSendMessage = (config) => {
|
|
3082
|
+
const [isLoading, setIsLoading] = (0, import_react3.useState)(false);
|
|
3083
|
+
const [error, setError] = (0, import_react3.useState)(null);
|
|
3084
|
+
const send = (0, import_react3.useCallback)(
|
|
3085
|
+
async (mapId, request, options) => {
|
|
3086
|
+
setIsLoading(true);
|
|
3087
|
+
setError(null);
|
|
3088
|
+
try {
|
|
3089
|
+
return await sendMessage(mapId, request, options, config);
|
|
3090
|
+
} catch (err) {
|
|
3091
|
+
setError(err);
|
|
3092
|
+
throw err;
|
|
3093
|
+
} finally {
|
|
3094
|
+
setIsLoading(false);
|
|
3095
|
+
}
|
|
3096
|
+
},
|
|
3097
|
+
[config]
|
|
3098
|
+
);
|
|
3099
|
+
return { sendMessage: send, isLoading, error };
|
|
3100
|
+
};
|
|
3101
|
+
var useSendMessageStream = (config) => {
|
|
3102
|
+
const [isStreaming, setIsStreaming] = (0, import_react3.useState)(false);
|
|
3103
|
+
const [streamingText, setStreamingText] = (0, import_react3.useState)("");
|
|
3104
|
+
const [completeResponse, setCompleteResponse] = (0, import_react3.useState)(null);
|
|
3105
|
+
const [error, setError] = (0, import_react3.useState)(null);
|
|
3106
|
+
const requestIdRef = (0, import_react3.useRef)(0);
|
|
3107
|
+
const reset = (0, import_react3.useCallback)(() => {
|
|
3108
|
+
setIsStreaming(false);
|
|
3109
|
+
setStreamingText("");
|
|
3110
|
+
setCompleteResponse(null);
|
|
3111
|
+
setError(null);
|
|
3112
|
+
}, []);
|
|
3113
|
+
const send = (0, import_react3.useCallback)(
|
|
3114
|
+
async (mapId, request, options) => {
|
|
3115
|
+
const requestId = requestIdRef.current + 1;
|
|
3116
|
+
requestIdRef.current = requestId;
|
|
3117
|
+
setIsStreaming(true);
|
|
3118
|
+
setStreamingText("");
|
|
3119
|
+
setCompleteResponse(null);
|
|
3120
|
+
setError(null);
|
|
3121
|
+
try {
|
|
3122
|
+
const response = await sendMessageStream(
|
|
3123
|
+
mapId,
|
|
3124
|
+
request,
|
|
3125
|
+
{
|
|
3126
|
+
onChunk: (_chunk, aggregated) => {
|
|
3127
|
+
if (requestIdRef.current !== requestId) return;
|
|
3128
|
+
setStreamingText(aggregated);
|
|
3129
|
+
},
|
|
3130
|
+
onError: (err) => {
|
|
3131
|
+
if (requestIdRef.current !== requestId) return;
|
|
3132
|
+
setError(err);
|
|
3133
|
+
},
|
|
3134
|
+
onComplete: (finalResponse) => {
|
|
3135
|
+
if (requestIdRef.current !== requestId) return;
|
|
3136
|
+
setCompleteResponse(finalResponse);
|
|
3137
|
+
}
|
|
3138
|
+
},
|
|
3139
|
+
options,
|
|
3140
|
+
config
|
|
3141
|
+
);
|
|
3142
|
+
return response;
|
|
3143
|
+
} catch (err) {
|
|
3144
|
+
setError(err);
|
|
3145
|
+
throw err;
|
|
3146
|
+
} finally {
|
|
3147
|
+
if (requestIdRef.current === requestId) {
|
|
3148
|
+
setIsStreaming(false);
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
},
|
|
3152
|
+
[config]
|
|
3153
|
+
);
|
|
3154
|
+
return {
|
|
3155
|
+
sendMessage: send,
|
|
3156
|
+
isStreaming,
|
|
3157
|
+
streamingText,
|
|
3158
|
+
completeResponse,
|
|
3159
|
+
error,
|
|
3160
|
+
reset
|
|
3161
|
+
};
|
|
3162
|
+
};
|
|
3163
|
+
|
|
3164
|
+
// src/react/components/MarkdownRenderer.tsx
|
|
3165
|
+
var import_react_markdown = __toESM(require("react-markdown"));
|
|
3166
|
+
var import_remark_gfm = __toESM(require("remark-gfm"));
|
|
3167
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
3168
|
+
function normalizeAssistantMarkdown(text) {
|
|
3169
|
+
if (!text || typeof text !== "string") return "";
|
|
3170
|
+
let normalized = text;
|
|
3171
|
+
normalized = normalized.replace(/\r\n/g, "\n");
|
|
3172
|
+
normalized = normalized.replace(/^\s*#{1,6}\s*$/gm, "");
|
|
3173
|
+
normalized = normalized.replace(/\n{3,}/g, "\n\n");
|
|
3174
|
+
normalized = normalized.split("\n").map((line) => line.trimEnd()).join("\n");
|
|
3175
|
+
normalized = normalized.trim();
|
|
3176
|
+
return normalized;
|
|
3177
|
+
}
|
|
3178
|
+
var MarkdownRenderer = ({ content, className }) => {
|
|
3179
|
+
const normalizedContent = normalizeAssistantMarkdown(content);
|
|
3180
|
+
if (!normalizedContent) {
|
|
3181
|
+
return null;
|
|
3182
|
+
}
|
|
3183
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className, style: { wordBreak: "break-word" }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
3184
|
+
import_react_markdown.default,
|
|
3185
|
+
{
|
|
3186
|
+
remarkPlugins: [import_remark_gfm.default],
|
|
3187
|
+
components: {
|
|
3188
|
+
// Headings with proper spacing
|
|
3189
|
+
h1: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h1", { style: { fontSize: "1.5em", fontWeight: 700, marginTop: "1em", marginBottom: "0.5em" }, ...props, children }),
|
|
3190
|
+
h2: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h2", { style: { fontSize: "1.3em", fontWeight: 700, marginTop: "0.9em", marginBottom: "0.45em" }, ...props, children }),
|
|
3191
|
+
h3: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { style: { fontSize: "1.15em", fontWeight: 600, marginTop: "0.75em", marginBottom: "0.4em" }, ...props, children }),
|
|
3192
|
+
h4: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h4", { style: { fontSize: "1.05em", fontWeight: 600, marginTop: "0.6em", marginBottom: "0.35em" }, ...props, children }),
|
|
3193
|
+
h5: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h5", { style: { fontSize: "1em", fontWeight: 600, marginTop: "0.5em", marginBottom: "0.3em" }, ...props, children }),
|
|
3194
|
+
h6: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h6", { style: { fontSize: "0.95em", fontWeight: 600, marginTop: "0.5em", marginBottom: "0.3em" }, ...props, children }),
|
|
3195
|
+
// Paragraphs with comfortable line height
|
|
3196
|
+
p: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: { marginTop: "0.5em", marginBottom: "0.5em", lineHeight: 1.6 }, ...props, children }),
|
|
3197
|
+
// Lists with proper indentation
|
|
3198
|
+
ul: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { style: { paddingLeft: "1.5em", marginTop: "0.5em", marginBottom: "0.5em" }, ...props, children }),
|
|
3199
|
+
ol: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ol", { style: { paddingLeft: "1.5em", marginTop: "0.5em", marginBottom: "0.5em" }, ...props, children }),
|
|
3200
|
+
li: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { style: { marginTop: "0.25em", marginBottom: "0.25em" }, ...props, children }),
|
|
3201
|
+
// Code blocks
|
|
3202
|
+
code: ({ inline, children, ...props }) => {
|
|
3203
|
+
if (inline) {
|
|
3204
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
3205
|
+
"code",
|
|
3206
|
+
{
|
|
3207
|
+
style: {
|
|
3208
|
+
backgroundColor: "rgba(0, 0, 0, 0.08)",
|
|
3209
|
+
padding: "0.15em 0.4em",
|
|
3210
|
+
borderRadius: "4px",
|
|
3211
|
+
fontSize: "0.9em",
|
|
3212
|
+
fontFamily: "monospace"
|
|
3213
|
+
},
|
|
3214
|
+
...props,
|
|
3215
|
+
children
|
|
3216
|
+
}
|
|
3217
|
+
);
|
|
3218
|
+
}
|
|
3219
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
3220
|
+
"code",
|
|
3221
|
+
{
|
|
3222
|
+
style: {
|
|
3223
|
+
display: "block",
|
|
3224
|
+
backgroundColor: "rgba(0, 0, 0, 0.08)",
|
|
3225
|
+
padding: "0.75em",
|
|
3226
|
+
borderRadius: "6px",
|
|
3227
|
+
fontSize: "0.9em",
|
|
3228
|
+
fontFamily: "monospace",
|
|
3229
|
+
overflowX: "auto",
|
|
3230
|
+
marginTop: "0.5em",
|
|
3231
|
+
marginBottom: "0.5em"
|
|
3232
|
+
},
|
|
3233
|
+
...props,
|
|
3234
|
+
children
|
|
3235
|
+
}
|
|
3236
|
+
);
|
|
3237
|
+
},
|
|
3238
|
+
// Pre (code block wrapper)
|
|
3239
|
+
pre: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("pre", { style: { margin: 0 }, ...props, children }),
|
|
3240
|
+
// Blockquotes
|
|
3241
|
+
blockquote: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
3242
|
+
"blockquote",
|
|
3243
|
+
{
|
|
3244
|
+
style: {
|
|
3245
|
+
borderLeft: "4px solid rgba(0, 0, 0, 0.2)",
|
|
3246
|
+
paddingLeft: "1em",
|
|
3247
|
+
marginLeft: 0,
|
|
3248
|
+
marginTop: "0.5em",
|
|
3249
|
+
marginBottom: "0.5em",
|
|
3250
|
+
color: "rgba(0, 0, 0, 0.7)"
|
|
3251
|
+
},
|
|
3252
|
+
...props,
|
|
3253
|
+
children
|
|
3254
|
+
}
|
|
3255
|
+
),
|
|
3256
|
+
// Strong/bold
|
|
3257
|
+
strong: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { style: { fontWeight: 600 }, ...props, children }),
|
|
3258
|
+
// Emphasis/italic
|
|
3259
|
+
em: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("em", { style: { fontStyle: "italic" }, ...props, children }),
|
|
3260
|
+
// Horizontal rule
|
|
3261
|
+
hr: (props) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
3262
|
+
"hr",
|
|
3263
|
+
{
|
|
3264
|
+
style: {
|
|
3265
|
+
border: "none",
|
|
3266
|
+
borderTop: "1px solid rgba(0, 0, 0, 0.1)",
|
|
3267
|
+
marginTop: "1em",
|
|
3268
|
+
marginBottom: "1em"
|
|
3269
|
+
},
|
|
3270
|
+
...props
|
|
3271
|
+
}
|
|
3272
|
+
),
|
|
3273
|
+
// Tables (GFM)
|
|
3274
|
+
table: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { overflowX: "auto", marginTop: "0.5em", marginBottom: "0.5em" }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
3275
|
+
"table",
|
|
3276
|
+
{
|
|
3277
|
+
style: {
|
|
3278
|
+
borderCollapse: "collapse",
|
|
3279
|
+
width: "100%",
|
|
3280
|
+
fontSize: "0.9em"
|
|
3281
|
+
},
|
|
3282
|
+
...props,
|
|
3283
|
+
children
|
|
3284
|
+
}
|
|
3285
|
+
) }),
|
|
3286
|
+
th: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
3287
|
+
"th",
|
|
3288
|
+
{
|
|
3289
|
+
style: {
|
|
3290
|
+
border: "1px solid rgba(0, 0, 0, 0.2)",
|
|
3291
|
+
padding: "0.5em",
|
|
3292
|
+
backgroundColor: "rgba(0, 0, 0, 0.05)",
|
|
3293
|
+
fontWeight: 600,
|
|
3294
|
+
textAlign: "left"
|
|
3295
|
+
},
|
|
3296
|
+
...props,
|
|
3297
|
+
children
|
|
3298
|
+
}
|
|
3299
|
+
),
|
|
3300
|
+
td: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
3301
|
+
"td",
|
|
3302
|
+
{
|
|
3303
|
+
style: {
|
|
3304
|
+
border: "1px solid rgba(0, 0, 0, 0.2)",
|
|
3305
|
+
padding: "0.5em"
|
|
3306
|
+
},
|
|
3307
|
+
...props,
|
|
3308
|
+
children
|
|
3309
|
+
}
|
|
3310
|
+
)
|
|
3311
|
+
},
|
|
3312
|
+
children: normalizedContent
|
|
3313
|
+
}
|
|
3314
|
+
) });
|
|
3315
|
+
};
|
|
3316
|
+
|
|
3317
|
+
// src/react/ai/FloatingChatBox.tsx
|
|
3318
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
3319
|
+
var ChatIcon = () => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) });
|
|
3320
|
+
var CloseIcon = () => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
3321
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
3322
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
3323
|
+
] });
|
|
3324
|
+
var ExpandIcon = () => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
3325
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "15 3 21 3 21 9" }),
|
|
3326
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "9 21 3 21 3 15" }),
|
|
3327
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "21", y1: "3", x2: "14", y2: "10" }),
|
|
3328
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "3", y1: "21", x2: "10", y2: "14" })
|
|
3329
|
+
] });
|
|
3330
|
+
var CollapseIcon = () => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
3331
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "4 14 10 14 10 20" }),
|
|
3332
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "20 10 14 10 14 4" }),
|
|
3333
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "14", y1: "10", x2: "21", y2: "3" }),
|
|
3334
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "3", y1: "21", x2: "10", y2: "14" })
|
|
3335
|
+
] });
|
|
3336
|
+
var SendIcon = () => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
3337
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
|
|
3338
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
|
|
3339
|
+
] });
|
|
3340
|
+
var LayersIcon = () => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
3341
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polygon", { points: "12 2 2 7 12 12 22 7 12 2" }),
|
|
3342
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "2 17 12 22 22 17" }),
|
|
3343
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "2 12 12 17 22 12" })
|
|
3344
|
+
] });
|
|
3345
|
+
var styles = {
|
|
3346
|
+
root: {
|
|
3347
|
+
fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
|
|
3348
|
+
},
|
|
3349
|
+
// Floating button (closed state - wide with text, open state - circular with X)
|
|
3350
|
+
floatingButton: {
|
|
3351
|
+
position: "fixed",
|
|
3352
|
+
bottom: 24,
|
|
3353
|
+
right: 24,
|
|
3354
|
+
borderRadius: "999px",
|
|
3355
|
+
border: "none",
|
|
3356
|
+
cursor: "pointer",
|
|
3357
|
+
background: "linear-gradient(135deg, #10b981, #059669)",
|
|
3358
|
+
color: "#fff",
|
|
3359
|
+
boxShadow: "0 12px 28px rgba(16, 185, 129, 0.4)",
|
|
3360
|
+
display: "flex",
|
|
3361
|
+
alignItems: "center",
|
|
3362
|
+
justifyContent: "center",
|
|
3363
|
+
fontSize: 15,
|
|
3364
|
+
fontWeight: 600,
|
|
3365
|
+
zIndex: 99999,
|
|
3366
|
+
transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)"
|
|
3367
|
+
},
|
|
3368
|
+
floatingButtonClosed: {
|
|
3369
|
+
padding: "14px 24px",
|
|
3370
|
+
gap: 8
|
|
3371
|
+
},
|
|
3372
|
+
floatingButtonOpen: {
|
|
3373
|
+
width: 56,
|
|
3374
|
+
height: 56,
|
|
3375
|
+
padding: 0
|
|
3376
|
+
},
|
|
3377
|
+
// Panel (expandable)
|
|
3378
|
+
panel: {
|
|
3379
|
+
position: "fixed",
|
|
3380
|
+
bottom: 92,
|
|
3381
|
+
right: 24,
|
|
3382
|
+
background: "#fff",
|
|
3383
|
+
borderRadius: 16,
|
|
3384
|
+
boxShadow: "0 20px 60px rgba(15, 23, 42, 0.3), 0 0 0 1px rgba(15, 23, 42, 0.05)",
|
|
3385
|
+
display: "flex",
|
|
3386
|
+
flexDirection: "column",
|
|
3387
|
+
overflow: "hidden",
|
|
3388
|
+
zIndex: 99999,
|
|
3389
|
+
transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)"
|
|
3390
|
+
},
|
|
3391
|
+
panelNormal: {
|
|
3392
|
+
width: 400,
|
|
3393
|
+
height: 550
|
|
3394
|
+
},
|
|
3395
|
+
panelExpanded: {
|
|
3396
|
+
width: 520,
|
|
3397
|
+
height: 700
|
|
3398
|
+
},
|
|
3399
|
+
// Header with green gradient
|
|
3400
|
+
header: {
|
|
3401
|
+
padding: "16px 18px",
|
|
3402
|
+
background: "linear-gradient(135deg, #10b981, #059669)",
|
|
3403
|
+
color: "#fff",
|
|
3404
|
+
display: "flex",
|
|
3405
|
+
alignItems: "center",
|
|
3406
|
+
justifyContent: "space-between"
|
|
3407
|
+
},
|
|
3408
|
+
title: {
|
|
3409
|
+
margin: 0,
|
|
3410
|
+
fontSize: 16,
|
|
3411
|
+
fontWeight: 600,
|
|
3412
|
+
letterSpacing: "-0.01em"
|
|
3413
|
+
},
|
|
3414
|
+
headerButtons: {
|
|
3415
|
+
display: "flex",
|
|
3416
|
+
alignItems: "center",
|
|
3417
|
+
gap: 8
|
|
3418
|
+
},
|
|
3419
|
+
headerButton: {
|
|
3420
|
+
border: "none",
|
|
3421
|
+
background: "rgba(255, 255, 255, 0.15)",
|
|
3422
|
+
color: "#fff",
|
|
3423
|
+
width: 32,
|
|
3424
|
+
height: 32,
|
|
3425
|
+
borderRadius: 8,
|
|
3426
|
+
cursor: "pointer",
|
|
3427
|
+
display: "flex",
|
|
3428
|
+
alignItems: "center",
|
|
3429
|
+
justifyContent: "center",
|
|
3430
|
+
transition: "background 0.2s"
|
|
3431
|
+
},
|
|
3432
|
+
// Messages area
|
|
3433
|
+
messages: {
|
|
3434
|
+
flex: 1,
|
|
3435
|
+
padding: "20px 18px",
|
|
3436
|
+
overflowY: "auto",
|
|
3437
|
+
background: "#f8fafc",
|
|
3438
|
+
display: "flex",
|
|
3439
|
+
flexDirection: "column",
|
|
3440
|
+
gap: 16
|
|
3441
|
+
},
|
|
3442
|
+
// Message bubbles
|
|
3443
|
+
messageWrapper: {
|
|
3444
|
+
display: "flex",
|
|
3445
|
+
flexDirection: "column",
|
|
3446
|
+
gap: 8
|
|
3447
|
+
},
|
|
3448
|
+
messageBubble: {
|
|
3449
|
+
maxWidth: "85%",
|
|
3450
|
+
padding: "12px 14px",
|
|
3451
|
+
borderRadius: 16,
|
|
3452
|
+
lineHeight: 1.5,
|
|
3453
|
+
fontSize: 14,
|
|
3454
|
+
whiteSpace: "pre-wrap",
|
|
3455
|
+
wordBreak: "break-word"
|
|
3456
|
+
},
|
|
3457
|
+
userMessage: {
|
|
3458
|
+
alignSelf: "flex-end",
|
|
3459
|
+
background: "linear-gradient(135deg, #10b981, #059669)",
|
|
3460
|
+
color: "#fff",
|
|
3461
|
+
borderBottomRightRadius: 4
|
|
3462
|
+
},
|
|
3463
|
+
assistantMessage: {
|
|
3464
|
+
alignSelf: "flex-start",
|
|
3465
|
+
background: "#e2e8f0",
|
|
3466
|
+
color: "#0f172a",
|
|
3467
|
+
borderBottomLeftRadius: 4
|
|
3468
|
+
},
|
|
3469
|
+
// Streaming cursor
|
|
3470
|
+
cursor: {
|
|
3471
|
+
display: "inline-block",
|
|
3472
|
+
width: 2,
|
|
3473
|
+
height: 16,
|
|
3474
|
+
background: "#0f172a",
|
|
3475
|
+
marginLeft: 2,
|
|
3476
|
+
animation: "zenitBlink 1s infinite",
|
|
3477
|
+
verticalAlign: "text-bottom"
|
|
3478
|
+
},
|
|
3479
|
+
thinkingText: {
|
|
3480
|
+
fontStyle: "italic",
|
|
3481
|
+
opacity: 0.7,
|
|
3482
|
+
display: "flex",
|
|
3483
|
+
alignItems: "center",
|
|
3484
|
+
gap: 8
|
|
3485
|
+
},
|
|
3486
|
+
typingIndicator: {
|
|
3487
|
+
display: "flex",
|
|
3488
|
+
gap: 4,
|
|
3489
|
+
alignItems: "center"
|
|
3490
|
+
},
|
|
3491
|
+
typingDot: {
|
|
3492
|
+
width: 8,
|
|
3493
|
+
height: 8,
|
|
3494
|
+
borderRadius: "50%",
|
|
3495
|
+
backgroundColor: "#059669",
|
|
3496
|
+
animation: "typingDotBounce 1.4s infinite ease-in-out"
|
|
3497
|
+
},
|
|
3498
|
+
// Metadata section (Referenced Layers)
|
|
3499
|
+
metadataSection: {
|
|
3500
|
+
marginTop: 12,
|
|
3501
|
+
padding: "10px 12px",
|
|
3502
|
+
background: "rgba(255, 255, 255, 0.5)",
|
|
3503
|
+
borderRadius: 10,
|
|
3504
|
+
fontSize: 12
|
|
3505
|
+
},
|
|
3506
|
+
metadataTitle: {
|
|
3507
|
+
fontWeight: 600,
|
|
3508
|
+
color: "#059669",
|
|
3509
|
+
marginBottom: 6,
|
|
3510
|
+
display: "flex",
|
|
3511
|
+
alignItems: "center",
|
|
3512
|
+
gap: 6
|
|
3513
|
+
},
|
|
3514
|
+
metadataList: {
|
|
3515
|
+
margin: 0,
|
|
3516
|
+
paddingLeft: 18,
|
|
3517
|
+
color: "#475569"
|
|
3518
|
+
},
|
|
3519
|
+
metadataItem: {
|
|
3520
|
+
marginBottom: 2
|
|
3521
|
+
},
|
|
3522
|
+
// Suggested actions
|
|
3523
|
+
actionsSection: {
|
|
3524
|
+
marginTop: 12
|
|
3525
|
+
},
|
|
3526
|
+
sectionLabel: {
|
|
3527
|
+
fontSize: 11,
|
|
3528
|
+
fontWeight: 600,
|
|
3529
|
+
color: "#64748b",
|
|
3530
|
+
marginBottom: 8,
|
|
3531
|
+
textTransform: "uppercase",
|
|
3532
|
+
letterSpacing: "0.05em"
|
|
3533
|
+
},
|
|
3534
|
+
actionsGrid: {
|
|
3535
|
+
display: "flex",
|
|
3536
|
+
flexWrap: "wrap",
|
|
3537
|
+
gap: 6
|
|
3538
|
+
},
|
|
3539
|
+
actionButton: {
|
|
3540
|
+
border: "1px solid #d1fae5",
|
|
3541
|
+
background: "#d1fae5",
|
|
3542
|
+
color: "#065f46",
|
|
3543
|
+
borderRadius: 999,
|
|
3544
|
+
padding: "6px 12px",
|
|
3545
|
+
fontSize: 12,
|
|
3546
|
+
fontWeight: 500,
|
|
3547
|
+
cursor: "pointer",
|
|
3548
|
+
transition: "all 0.2s"
|
|
3549
|
+
},
|
|
3550
|
+
// Follow-up questions
|
|
3551
|
+
followUpButton: {
|
|
3552
|
+
border: "1px solid #cbd5e1",
|
|
3553
|
+
background: "#fff",
|
|
3554
|
+
color: "#475569",
|
|
3555
|
+
borderRadius: 10,
|
|
3556
|
+
padding: "8px 12px",
|
|
3557
|
+
fontSize: 12,
|
|
3558
|
+
fontWeight: 500,
|
|
3559
|
+
cursor: "pointer",
|
|
3560
|
+
textAlign: "left",
|
|
3561
|
+
width: "100%",
|
|
3562
|
+
transition: "all 0.2s"
|
|
3563
|
+
},
|
|
3564
|
+
// Input area
|
|
3565
|
+
inputWrapper: {
|
|
3566
|
+
borderTop: "1px solid #e2e8f0",
|
|
3567
|
+
padding: "14px 16px",
|
|
3568
|
+
display: "flex",
|
|
3569
|
+
gap: 10,
|
|
3570
|
+
alignItems: "flex-end",
|
|
3571
|
+
background: "#fff"
|
|
3572
|
+
},
|
|
3573
|
+
textarea: {
|
|
3574
|
+
flex: 1,
|
|
3575
|
+
resize: "none",
|
|
3576
|
+
borderRadius: 12,
|
|
3577
|
+
border: "1.5px solid #cbd5e1",
|
|
3578
|
+
padding: "10px 12px",
|
|
3579
|
+
fontSize: 14,
|
|
3580
|
+
fontFamily: "inherit",
|
|
3581
|
+
lineHeight: 1.4,
|
|
3582
|
+
transition: "border-color 0.2s"
|
|
3583
|
+
},
|
|
3584
|
+
textareaFocus: {
|
|
3585
|
+
borderColor: "#10b981",
|
|
3586
|
+
outline: "none"
|
|
3587
|
+
},
|
|
3588
|
+
sendButton: {
|
|
3589
|
+
borderRadius: 12,
|
|
3590
|
+
border: "none",
|
|
3591
|
+
padding: "10px 14px",
|
|
3592
|
+
background: "linear-gradient(135deg, #10b981, #059669)",
|
|
3593
|
+
color: "#fff",
|
|
3594
|
+
cursor: "pointer",
|
|
3595
|
+
fontSize: 14,
|
|
3596
|
+
fontWeight: 600,
|
|
3597
|
+
display: "flex",
|
|
3598
|
+
alignItems: "center",
|
|
3599
|
+
justifyContent: "center",
|
|
3600
|
+
transition: "opacity 0.2s, transform 0.2s",
|
|
3601
|
+
minWidth: 44,
|
|
3602
|
+
height: 44
|
|
3603
|
+
},
|
|
3604
|
+
// Status messages
|
|
3605
|
+
statusNote: {
|
|
3606
|
+
padding: "0 16px 14px",
|
|
3607
|
+
fontSize: 12,
|
|
3608
|
+
color: "#64748b",
|
|
3609
|
+
textAlign: "center"
|
|
3610
|
+
},
|
|
3611
|
+
errorText: {
|
|
3612
|
+
padding: "0 16px 14px",
|
|
3613
|
+
fontSize: 12,
|
|
3614
|
+
color: "#dc2626",
|
|
3615
|
+
textAlign: "center"
|
|
3616
|
+
}
|
|
3617
|
+
};
|
|
3618
|
+
var FloatingChatBox = ({
|
|
3619
|
+
mapId,
|
|
3620
|
+
filteredLayerIds,
|
|
3621
|
+
filters,
|
|
3622
|
+
userId,
|
|
3623
|
+
baseUrl,
|
|
3624
|
+
accessToken,
|
|
3625
|
+
getAccessToken,
|
|
3626
|
+
onActionClick
|
|
3627
|
+
}) => {
|
|
3628
|
+
const [open, setOpen] = (0, import_react4.useState)(false);
|
|
3629
|
+
const [expanded, setExpanded] = (0, import_react4.useState)(false);
|
|
3630
|
+
const [messages, setMessages] = (0, import_react4.useState)([]);
|
|
3631
|
+
const [inputValue, setInputValue] = (0, import_react4.useState)("");
|
|
3632
|
+
const [conversationId, setConversationId] = (0, import_react4.useState)();
|
|
3633
|
+
const [errorMessage, setErrorMessage] = (0, import_react4.useState)(null);
|
|
3634
|
+
const [isFocused, setIsFocused] = (0, import_react4.useState)(false);
|
|
3635
|
+
const [isMobile, setIsMobile] = (0, import_react4.useState)(false);
|
|
3636
|
+
const messagesEndRef = (0, import_react4.useRef)(null);
|
|
3637
|
+
const messagesContainerRef = (0, import_react4.useRef)(null);
|
|
3638
|
+
const chatBoxRef = (0, import_react4.useRef)(null);
|
|
3639
|
+
const chatConfig = (0, import_react4.useMemo)(() => {
|
|
3640
|
+
if (!baseUrl) return void 0;
|
|
3641
|
+
return { baseUrl, accessToken, getAccessToken };
|
|
3642
|
+
}, [accessToken, baseUrl, getAccessToken]);
|
|
3643
|
+
const { sendMessage: sendMessage2, isStreaming, streamingText, completeResponse } = useSendMessageStream(chatConfig);
|
|
3644
|
+
const canSend = Boolean(mapId) && Boolean(baseUrl) && inputValue.trim().length > 0 && !isStreaming;
|
|
3645
|
+
const scrollToBottom = (0, import_react4.useCallback)(() => {
|
|
3646
|
+
if (messagesEndRef.current) {
|
|
3647
|
+
messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
|
|
3648
|
+
}
|
|
3649
|
+
}, []);
|
|
3650
|
+
(0, import_react4.useEffect)(() => {
|
|
3651
|
+
if (open && messages.length === 0) {
|
|
3652
|
+
setMessages([
|
|
3653
|
+
{
|
|
3654
|
+
id: "welcome",
|
|
3655
|
+
role: "assistant",
|
|
3656
|
+
content: "Hola, soy el asistente de IA de ZENIT. Estoy aqu\xED para ayudarte a analizar tu mapa y responder tus preguntas sobre las capas y datos. \xBFEn qu\xE9 puedo ayudarte hoy?"
|
|
3657
|
+
}
|
|
3658
|
+
]);
|
|
3659
|
+
}
|
|
3660
|
+
}, [open, messages.length]);
|
|
3661
|
+
(0, import_react4.useEffect)(() => {
|
|
3662
|
+
scrollToBottom();
|
|
3663
|
+
}, [messages, streamingText, scrollToBottom]);
|
|
3664
|
+
(0, import_react4.useEffect)(() => {
|
|
3665
|
+
if (!open) return;
|
|
3666
|
+
const handleClickOutside = (event) => {
|
|
3667
|
+
if (open && chatBoxRef.current && !chatBoxRef.current.contains(event.target)) {
|
|
3668
|
+
setOpen(false);
|
|
3669
|
+
}
|
|
3670
|
+
};
|
|
3671
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
3672
|
+
return () => {
|
|
3673
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
3674
|
+
};
|
|
3675
|
+
}, [open]);
|
|
3676
|
+
(0, import_react4.useEffect)(() => {
|
|
3677
|
+
if (typeof window === "undefined") return;
|
|
3678
|
+
const mediaQuery = window.matchMedia("(max-width: 768px)");
|
|
3679
|
+
const updateMobile = () => setIsMobile(mediaQuery.matches);
|
|
3680
|
+
updateMobile();
|
|
3681
|
+
if (mediaQuery.addEventListener) {
|
|
3682
|
+
mediaQuery.addEventListener("change", updateMobile);
|
|
3683
|
+
} else {
|
|
3684
|
+
mediaQuery.addListener(updateMobile);
|
|
3685
|
+
}
|
|
3686
|
+
return () => {
|
|
3687
|
+
if (mediaQuery.removeEventListener) {
|
|
3688
|
+
mediaQuery.removeEventListener("change", updateMobile);
|
|
3689
|
+
} else {
|
|
3690
|
+
mediaQuery.removeListener(updateMobile);
|
|
3691
|
+
}
|
|
3692
|
+
};
|
|
3693
|
+
}, []);
|
|
3694
|
+
(0, import_react4.useEffect)(() => {
|
|
3695
|
+
if (typeof window === "undefined" || typeof document === "undefined") return;
|
|
3696
|
+
const mediaQuery = window.matchMedia("(max-width: 768px)");
|
|
3697
|
+
const originalOverflow = document.body.style.overflow;
|
|
3698
|
+
const updateOverflow = () => {
|
|
3699
|
+
if (open && expanded && mediaQuery.matches) {
|
|
3700
|
+
document.body.style.overflow = "hidden";
|
|
3701
|
+
} else {
|
|
3702
|
+
document.body.style.overflow = originalOverflow;
|
|
3703
|
+
}
|
|
3704
|
+
};
|
|
3705
|
+
updateOverflow();
|
|
3706
|
+
if (mediaQuery.addEventListener) {
|
|
3707
|
+
mediaQuery.addEventListener("change", updateOverflow);
|
|
3708
|
+
} else {
|
|
3709
|
+
mediaQuery.addListener(updateOverflow);
|
|
3710
|
+
}
|
|
3711
|
+
return () => {
|
|
3712
|
+
if (mediaQuery.removeEventListener) {
|
|
3713
|
+
mediaQuery.removeEventListener("change", updateOverflow);
|
|
3714
|
+
} else {
|
|
3715
|
+
mediaQuery.removeListener(updateOverflow);
|
|
3716
|
+
}
|
|
3717
|
+
document.body.style.overflow = originalOverflow;
|
|
3718
|
+
};
|
|
3719
|
+
}, [open, expanded]);
|
|
3720
|
+
const addMessage = (0, import_react4.useCallback)((message) => {
|
|
3721
|
+
setMessages((prev) => [...prev, message]);
|
|
3722
|
+
}, []);
|
|
3723
|
+
const handleSend = (0, import_react4.useCallback)(async () => {
|
|
3724
|
+
if (!mapId) {
|
|
3725
|
+
setErrorMessage("Selecciona un mapa para usar el asistente.");
|
|
3726
|
+
return;
|
|
3727
|
+
}
|
|
3728
|
+
if (!baseUrl) {
|
|
3729
|
+
setErrorMessage("Configura la baseUrl del SDK para usar Zenit AI.");
|
|
3730
|
+
return;
|
|
3731
|
+
}
|
|
3732
|
+
if (!inputValue.trim()) return;
|
|
3733
|
+
const messageText = inputValue.trim();
|
|
3734
|
+
setInputValue("");
|
|
3735
|
+
setErrorMessage(null);
|
|
3736
|
+
addMessage({
|
|
3737
|
+
id: `user-${Date.now()}`,
|
|
3738
|
+
role: "user",
|
|
3739
|
+
content: messageText
|
|
3740
|
+
});
|
|
3741
|
+
const request = {
|
|
3742
|
+
message: messageText,
|
|
3743
|
+
conversationId,
|
|
3744
|
+
filteredLayerIds,
|
|
3745
|
+
filters,
|
|
3746
|
+
userId
|
|
3747
|
+
};
|
|
3748
|
+
try {
|
|
3749
|
+
const response = await sendMessage2(mapId, request);
|
|
3750
|
+
setConversationId(response.conversationId ?? conversationId);
|
|
3751
|
+
addMessage({
|
|
3752
|
+
id: `assistant-${Date.now()}`,
|
|
3753
|
+
role: "assistant",
|
|
3754
|
+
content: response.answer,
|
|
3755
|
+
response
|
|
3756
|
+
});
|
|
3757
|
+
} catch (error) {
|
|
3758
|
+
setErrorMessage(error instanceof Error ? error.message : "Ocurri\xF3 un error inesperado.");
|
|
3759
|
+
addMessage({
|
|
3760
|
+
id: `error-${Date.now()}`,
|
|
3761
|
+
role: "assistant",
|
|
3762
|
+
content: `\u274C Error: ${error instanceof Error ? error.message : "Ocurri\xF3 un error inesperado."}`
|
|
3763
|
+
});
|
|
3764
|
+
}
|
|
3765
|
+
}, [
|
|
3766
|
+
addMessage,
|
|
3767
|
+
baseUrl,
|
|
3768
|
+
conversationId,
|
|
3769
|
+
filteredLayerIds,
|
|
3770
|
+
filters,
|
|
3771
|
+
inputValue,
|
|
3772
|
+
mapId,
|
|
3773
|
+
sendMessage2,
|
|
3774
|
+
userId
|
|
3775
|
+
]);
|
|
3776
|
+
const handleKeyDown = (0, import_react4.useCallback)(
|
|
3777
|
+
(event) => {
|
|
3778
|
+
if (event.key === "Enter" && !event.shiftKey) {
|
|
3779
|
+
event.preventDefault();
|
|
3780
|
+
if (canSend) {
|
|
3781
|
+
void handleSend();
|
|
3782
|
+
}
|
|
3783
|
+
}
|
|
3784
|
+
},
|
|
3785
|
+
[canSend, handleSend]
|
|
3786
|
+
);
|
|
3787
|
+
const handleFollowUpClick = (0, import_react4.useCallback)((question) => {
|
|
3788
|
+
setInputValue(question);
|
|
3789
|
+
}, []);
|
|
3790
|
+
const renderMetadata = (response) => {
|
|
3791
|
+
if (!response?.metadata) return null;
|
|
3792
|
+
const referencedLayers = response.metadata.referencedLayers;
|
|
3793
|
+
if (!referencedLayers || referencedLayers.length === 0) return null;
|
|
3794
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: styles.metadataSection, children: [
|
|
3795
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: styles.metadataTitle, children: [
|
|
3796
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(LayersIcon, {}),
|
|
3797
|
+
"Capas Analizadas"
|
|
3798
|
+
] }),
|
|
3799
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { style: styles.metadataList, children: referencedLayers.map((layer, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("li", { style: styles.metadataItem, children: [
|
|
3800
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("strong", { children: layer.layerName }),
|
|
3801
|
+
" (",
|
|
3802
|
+
layer.featureCount,
|
|
3803
|
+
" ",
|
|
3804
|
+
layer.featureCount === 1 ? "elemento" : "elementos",
|
|
3805
|
+
")"
|
|
3806
|
+
] }, index)) })
|
|
3807
|
+
] });
|
|
3808
|
+
};
|
|
3809
|
+
const renderActions = (response) => {
|
|
3810
|
+
if (!response?.suggestedActions?.length) return null;
|
|
3811
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: styles.actionsSection, children: [
|
|
3812
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: styles.sectionLabel, children: "Acciones Sugeridas" }),
|
|
3813
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: styles.actionsGrid, children: response.suggestedActions.map((action, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
3814
|
+
"button",
|
|
3815
|
+
{
|
|
3816
|
+
type: "button",
|
|
3817
|
+
style: {
|
|
3818
|
+
...styles.actionButton,
|
|
3819
|
+
opacity: isStreaming ? 0.5 : 1,
|
|
3820
|
+
cursor: isStreaming ? "not-allowed" : "pointer"
|
|
3821
|
+
},
|
|
3822
|
+
onClick: () => !isStreaming && onActionClick?.(action),
|
|
3823
|
+
disabled: isStreaming,
|
|
3824
|
+
onMouseEnter: (e) => {
|
|
3825
|
+
if (!isStreaming) {
|
|
3826
|
+
e.currentTarget.style.background = "#a7f3d0";
|
|
3827
|
+
e.currentTarget.style.borderColor = "#a7f3d0";
|
|
3828
|
+
}
|
|
3829
|
+
},
|
|
3830
|
+
onMouseLeave: (e) => {
|
|
3831
|
+
if (!isStreaming) {
|
|
3832
|
+
e.currentTarget.style.background = "#d1fae5";
|
|
3833
|
+
e.currentTarget.style.borderColor = "#d1fae5";
|
|
3834
|
+
}
|
|
3835
|
+
},
|
|
3836
|
+
children: action.label ?? action.action ?? "Acci\xF3n"
|
|
3837
|
+
},
|
|
3838
|
+
`${action.label ?? action.action ?? "action"}-${index}`
|
|
3839
|
+
)) })
|
|
3840
|
+
] });
|
|
3841
|
+
};
|
|
3842
|
+
const renderFollowUps = (response) => {
|
|
3843
|
+
if (!response?.followUpQuestions?.length) return null;
|
|
3844
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: styles.actionsSection, children: [
|
|
3845
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: styles.sectionLabel, children: "Preguntas Relacionadas" }),
|
|
3846
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: response.followUpQuestions.map((question, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
3847
|
+
"button",
|
|
3848
|
+
{
|
|
3849
|
+
type: "button",
|
|
3850
|
+
style: {
|
|
3851
|
+
...styles.followUpButton,
|
|
3852
|
+
opacity: isStreaming ? 0.5 : 1,
|
|
3853
|
+
cursor: isStreaming ? "not-allowed" : "pointer"
|
|
3854
|
+
},
|
|
3855
|
+
onClick: () => !isStreaming && handleFollowUpClick(question),
|
|
3856
|
+
disabled: isStreaming,
|
|
3857
|
+
onMouseEnter: (e) => {
|
|
3858
|
+
if (!isStreaming) {
|
|
3859
|
+
e.currentTarget.style.background = "#f8fafc";
|
|
3860
|
+
e.currentTarget.style.borderColor = "#10b981";
|
|
3861
|
+
}
|
|
3862
|
+
},
|
|
3863
|
+
onMouseLeave: (e) => {
|
|
3864
|
+
if (!isStreaming) {
|
|
3865
|
+
e.currentTarget.style.background = "#fff";
|
|
3866
|
+
e.currentTarget.style.borderColor = "#cbd5e1";
|
|
3867
|
+
}
|
|
3868
|
+
},
|
|
3869
|
+
children: question
|
|
3870
|
+
},
|
|
3871
|
+
`followup-${index}`
|
|
3872
|
+
)) })
|
|
3873
|
+
] });
|
|
3874
|
+
};
|
|
3875
|
+
const chatContent = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: styles.root, children: [
|
|
3876
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("style", { children: `
|
|
3877
|
+
@keyframes zenitBlink {
|
|
3878
|
+
0%, 49% { opacity: 1; }
|
|
3879
|
+
50%, 100% { opacity: 0; }
|
|
3880
|
+
}
|
|
3881
|
+
@keyframes zenitPulse {
|
|
3882
|
+
0% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7); }
|
|
3883
|
+
70% { box-shadow: 0 0 0 14px rgba(16, 185, 129, 0); }
|
|
3884
|
+
100% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); }
|
|
3885
|
+
}
|
|
3886
|
+
@keyframes typingDotBounce {
|
|
3887
|
+
0%, 60%, 100% { transform: translateY(0); }
|
|
3888
|
+
30% { transform: translateY(-8px); }
|
|
3889
|
+
}
|
|
3890
|
+
.zenit-typing-dot:nth-child(1) { animation-delay: 0s; }
|
|
3891
|
+
.zenit-typing-dot:nth-child(2) { animation-delay: 0.2s; }
|
|
3892
|
+
.zenit-typing-dot:nth-child(3) { animation-delay: 0.4s; }
|
|
3893
|
+
.zenit-ai-button:not(.open) {
|
|
3894
|
+
animation: zenitPulse 2.5s infinite;
|
|
3895
|
+
}
|
|
3896
|
+
.zenit-ai-button:hover {
|
|
3897
|
+
transform: scale(1.05);
|
|
3898
|
+
}
|
|
3899
|
+
.zenit-ai-button:active {
|
|
3900
|
+
transform: scale(0.95);
|
|
3901
|
+
}
|
|
3902
|
+
.zenit-send-button:disabled {
|
|
3903
|
+
opacity: 0.5;
|
|
3904
|
+
cursor: not-allowed;
|
|
3905
|
+
}
|
|
3906
|
+
.zenit-send-button:not(:disabled):hover {
|
|
3907
|
+
opacity: 0.9;
|
|
3908
|
+
transform: translateY(-1px);
|
|
3909
|
+
}
|
|
3910
|
+
.zenit-send-button:not(:disabled):active {
|
|
3911
|
+
transform: translateY(0);
|
|
3912
|
+
}
|
|
3913
|
+
.zenit-chat-panel {
|
|
3914
|
+
box-sizing: border-box;
|
|
3915
|
+
}
|
|
3916
|
+
@media (max-width: 768px) {
|
|
3917
|
+
.zenit-chat-panel.zenit-chat-panel--fullscreen {
|
|
3918
|
+
position: fixed !important;
|
|
3919
|
+
inset: 0 !important;
|
|
3920
|
+
width: 100vw !important;
|
|
3921
|
+
max-width: 100vw !important;
|
|
3922
|
+
height: 100vh !important;
|
|
3923
|
+
height: 100dvh !important;
|
|
3924
|
+
border-radius: 0 !important;
|
|
3925
|
+
display: flex !important;
|
|
3926
|
+
flex-direction: column !important;
|
|
3927
|
+
overflow: hidden !important;
|
|
3928
|
+
z-index: 3000 !important;
|
|
3929
|
+
padding-top: env(safe-area-inset-top);
|
|
3930
|
+
padding-bottom: env(safe-area-inset-bottom);
|
|
3931
|
+
}
|
|
3932
|
+
.zenit-chat-panel.zenit-chat-panel--fullscreen .zenit-ai-body {
|
|
3933
|
+
flex: 1;
|
|
3934
|
+
overflow: auto;
|
|
3935
|
+
}
|
|
3936
|
+
.zenit-ai-button.zenit-ai-button--hidden-mobile {
|
|
3937
|
+
display: none !important;
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
` }),
|
|
3941
|
+
open && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
3942
|
+
"div",
|
|
3943
|
+
{
|
|
3944
|
+
ref: chatBoxRef,
|
|
3945
|
+
className: `zenit-chat-panel${expanded ? " zenit-chat-panel--expanded" : ""}${expanded && isMobile ? " zenit-chat-panel--fullscreen" : ""}`,
|
|
3946
|
+
style: {
|
|
3947
|
+
...styles.panel,
|
|
3948
|
+
...expanded ? styles.panelExpanded : styles.panelNormal
|
|
3949
|
+
},
|
|
3950
|
+
children: [
|
|
3951
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("header", { style: styles.header, children: [
|
|
3952
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h3", { style: styles.title, children: "Asistente Zenit AI" }),
|
|
3953
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: styles.headerButtons, children: [
|
|
3954
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
3955
|
+
"button",
|
|
3956
|
+
{
|
|
3957
|
+
type: "button",
|
|
3958
|
+
style: styles.headerButton,
|
|
3959
|
+
onClick: () => setExpanded(!expanded),
|
|
3960
|
+
onMouseEnter: (e) => {
|
|
3961
|
+
e.currentTarget.style.background = "rgba(255, 255, 255, 0.25)";
|
|
3962
|
+
},
|
|
3963
|
+
onMouseLeave: (e) => {
|
|
3964
|
+
e.currentTarget.style.background = "rgba(255, 255, 255, 0.15)";
|
|
3965
|
+
},
|
|
3966
|
+
"aria-label": expanded ? "Contraer" : "Expandir",
|
|
3967
|
+
children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CollapseIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ExpandIcon, {})
|
|
3968
|
+
}
|
|
3969
|
+
),
|
|
3970
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
3971
|
+
"button",
|
|
3972
|
+
{
|
|
3973
|
+
type: "button",
|
|
3974
|
+
style: styles.headerButton,
|
|
3975
|
+
onClick: () => setOpen(false),
|
|
3976
|
+
onMouseEnter: (e) => {
|
|
3977
|
+
e.currentTarget.style.background = "rgba(255, 255, 255, 0.25)";
|
|
3978
|
+
},
|
|
3979
|
+
onMouseLeave: (e) => {
|
|
3980
|
+
e.currentTarget.style.background = "rgba(255, 255, 255, 0.15)";
|
|
3981
|
+
},
|
|
3982
|
+
"aria-label": "Cerrar",
|
|
3983
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CloseIcon, {})
|
|
3984
|
+
}
|
|
3985
|
+
)
|
|
3986
|
+
] })
|
|
3987
|
+
] }),
|
|
3988
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { ref: messagesContainerRef, className: "zenit-ai-body", style: styles.messages, children: [
|
|
3989
|
+
messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
3990
|
+
"div",
|
|
3991
|
+
{
|
|
3992
|
+
style: {
|
|
3993
|
+
...styles.messageWrapper,
|
|
3994
|
+
alignItems: message.role === "user" ? "flex-end" : "flex-start"
|
|
3995
|
+
},
|
|
3996
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
3997
|
+
"div",
|
|
3998
|
+
{
|
|
3999
|
+
style: {
|
|
4000
|
+
...styles.messageBubble,
|
|
4001
|
+
...message.role === "user" ? styles.userMessage : styles.assistantMessage
|
|
4002
|
+
},
|
|
4003
|
+
children: [
|
|
4004
|
+
message.role === "assistant" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MarkdownRenderer, { content: message.content }) : message.content,
|
|
4005
|
+
message.role === "assistant" && renderMetadata(message.response),
|
|
4006
|
+
message.role === "assistant" && renderActions(message.response),
|
|
4007
|
+
message.role === "assistant" && renderFollowUps(message.response)
|
|
4008
|
+
]
|
|
4009
|
+
}
|
|
4010
|
+
)
|
|
4011
|
+
},
|
|
4012
|
+
message.id
|
|
4013
|
+
)),
|
|
4014
|
+
isStreaming && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
4015
|
+
"div",
|
|
4016
|
+
{
|
|
4017
|
+
style: {
|
|
4018
|
+
...styles.messageWrapper,
|
|
4019
|
+
alignItems: "flex-start"
|
|
4020
|
+
},
|
|
4021
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
4022
|
+
"div",
|
|
4023
|
+
{
|
|
4024
|
+
style: {
|
|
4025
|
+
...styles.messageBubble,
|
|
4026
|
+
...styles.assistantMessage
|
|
4027
|
+
},
|
|
4028
|
+
children: streamingText ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
4029
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MarkdownRenderer, { content: streamingText }),
|
|
4030
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: styles.cursor })
|
|
4031
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: styles.thinkingText, children: [
|
|
4032
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "Analizando" }),
|
|
4033
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: styles.typingIndicator, children: [
|
|
4034
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "zenit-typing-dot", style: styles.typingDot }),
|
|
4035
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "zenit-typing-dot", style: styles.typingDot }),
|
|
4036
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "zenit-typing-dot", style: styles.typingDot })
|
|
4037
|
+
] })
|
|
4038
|
+
] })
|
|
4039
|
+
}
|
|
4040
|
+
)
|
|
4041
|
+
}
|
|
4042
|
+
),
|
|
4043
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { ref: messagesEndRef })
|
|
4044
|
+
] }),
|
|
4045
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: styles.inputWrapper, children: [
|
|
4046
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
4047
|
+
"textarea",
|
|
4048
|
+
{
|
|
4049
|
+
style: {
|
|
4050
|
+
...styles.textarea,
|
|
4051
|
+
...isFocused ? styles.textareaFocus : {}
|
|
4052
|
+
},
|
|
4053
|
+
rows: 2,
|
|
4054
|
+
value: inputValue,
|
|
4055
|
+
onChange: (event) => setInputValue(event.target.value),
|
|
4056
|
+
onKeyDown: handleKeyDown,
|
|
4057
|
+
onFocus: () => setIsFocused(true),
|
|
4058
|
+
onBlur: () => setIsFocused(false),
|
|
4059
|
+
placeholder: !mapId ? "Selecciona un mapa para comenzar" : isStreaming ? "Esperando respuesta..." : "Escribe tu pregunta...",
|
|
4060
|
+
disabled: !mapId || !baseUrl || isStreaming
|
|
4061
|
+
}
|
|
4062
|
+
),
|
|
4063
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
4064
|
+
"button",
|
|
4065
|
+
{
|
|
4066
|
+
type: "button",
|
|
4067
|
+
className: "zenit-send-button",
|
|
4068
|
+
style: styles.sendButton,
|
|
4069
|
+
onClick: () => void handleSend(),
|
|
4070
|
+
disabled: !canSend,
|
|
4071
|
+
"aria-label": "Enviar mensaje",
|
|
4072
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SendIcon, {})
|
|
4073
|
+
}
|
|
4074
|
+
)
|
|
4075
|
+
] }),
|
|
4076
|
+
errorMessage && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: styles.errorText, children: errorMessage }),
|
|
4077
|
+
!mapId && !errorMessage && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: styles.statusNote, children: "Selecciona un mapa para usar el asistente" }),
|
|
4078
|
+
!baseUrl && !errorMessage && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: styles.statusNote, children: "Configura la baseUrl del SDK" })
|
|
4079
|
+
]
|
|
4080
|
+
}
|
|
4081
|
+
),
|
|
4082
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
4083
|
+
"button",
|
|
4084
|
+
{
|
|
4085
|
+
type: "button",
|
|
4086
|
+
className: `zenit-ai-button ${open ? "open" : ""}${open && expanded && isMobile ? " zenit-ai-button--hidden-mobile" : ""}`,
|
|
4087
|
+
style: {
|
|
4088
|
+
...styles.floatingButton,
|
|
4089
|
+
...open ? styles.floatingButtonOpen : styles.floatingButtonClosed
|
|
4090
|
+
},
|
|
4091
|
+
onClick: () => setOpen((prev) => !prev),
|
|
4092
|
+
"aria-label": open ? "Cerrar asistente" : "Abrir asistente Zenit AI",
|
|
4093
|
+
children: open ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CloseIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
4094
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ChatIcon, {}),
|
|
4095
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "Asistente IA" })
|
|
4096
|
+
] })
|
|
4097
|
+
}
|
|
4098
|
+
)
|
|
4099
|
+
] });
|
|
4100
|
+
if (typeof document !== "undefined") {
|
|
4101
|
+
return (0, import_react_dom.createPortal)(chatContent, document.body);
|
|
4102
|
+
}
|
|
4103
|
+
return chatContent;
|
|
4104
|
+
};
|
|
4105
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
4106
|
+
0 && (module.exports = {
|
|
4107
|
+
ChevronLeft,
|
|
4108
|
+
ChevronRight,
|
|
4109
|
+
DEFAULT_MAX_FEATURES_FULL_GEOJSON,
|
|
4110
|
+
Eye,
|
|
4111
|
+
EyeOff,
|
|
4112
|
+
FilterEngine,
|
|
4113
|
+
FloatingChatBox,
|
|
4114
|
+
HttpClient,
|
|
4115
|
+
Layers,
|
|
4116
|
+
Upload,
|
|
4117
|
+
X,
|
|
4118
|
+
ZenitAuthClient,
|
|
4119
|
+
ZenitCatalogNotSupportedError,
|
|
4120
|
+
ZenitClient,
|
|
4121
|
+
ZenitFeatureFilterPanel,
|
|
4122
|
+
ZenitLayerManager,
|
|
4123
|
+
ZenitLayersClient,
|
|
4124
|
+
ZenitMap,
|
|
4125
|
+
ZenitMapsClient,
|
|
4126
|
+
ZenitSdkAuthClient,
|
|
4127
|
+
ZoomIn,
|
|
4128
|
+
applyFilteredGeoJSONStrategy,
|
|
4129
|
+
applyLayerOverrides,
|
|
4130
|
+
applyPrefiltersToFeatureCollection,
|
|
4131
|
+
buildAoiFromFeatureCollection,
|
|
4132
|
+
buildFilterMultipleParams,
|
|
4133
|
+
buildLayerFilters,
|
|
4134
|
+
buildLimitedMessage,
|
|
4135
|
+
centroidFromBBox,
|
|
4136
|
+
clampNumber,
|
|
4137
|
+
clampOpacity,
|
|
4138
|
+
computeBBoxFromFeature,
|
|
4139
|
+
createChatService,
|
|
4140
|
+
decorateFeaturesWithLayerId,
|
|
4141
|
+
extractMapDto,
|
|
4142
|
+
filterEngine,
|
|
4143
|
+
getAccentByLayerId,
|
|
4144
|
+
getCatalogSupport,
|
|
4145
|
+
getEffectiveLayerOpacity,
|
|
4146
|
+
getLayerColor,
|
|
4147
|
+
getLayerZoomOpacityFactor,
|
|
4148
|
+
getStyleByLayerId,
|
|
4149
|
+
getZoomOpacityFactor,
|
|
4150
|
+
initLayerStates,
|
|
4151
|
+
isPolygonLayer,
|
|
4152
|
+
normalizeBbox,
|
|
4153
|
+
normalizeMapCenter,
|
|
4154
|
+
normalizeMapLayers,
|
|
4155
|
+
resetOverrides,
|
|
4156
|
+
resolveLayerAccent,
|
|
4157
|
+
resolveLayerStrategy,
|
|
4158
|
+
sendMessage,
|
|
4159
|
+
sendMessageStream,
|
|
4160
|
+
shouldSkipGeojsonDownload,
|
|
4161
|
+
shouldUseFilterMultiple,
|
|
4162
|
+
useSendMessage,
|
|
4163
|
+
useSendMessageStream
|
|
4164
|
+
});
|
|
4165
|
+
//# sourceMappingURL=index.js.map
|