Source UrlQuery
Source UrlQuery
Cette page est générée automatiquement à partir du dépôt local au moment de la génération de la documentation.
Fichiers inclus
packages/common/src/lib/widgets/url-query/url-query.declaration.tspackages/common/src/lib/widgets/url-query/url-query.config.tspackages/common/src/lib/widgets/url-query/url-query.models.tspackages/common/src/lib/widgets/url-query/url-query.utils.tspackages/common/src/lib/widgets/url-query/UrlQuery.sveltepackages/common/src/lib/widgets/url-query/UrlQueryPopupContent.svelte
packages/common/src/lib/widgets/url-query/url-query.declaration.ts
import { widgetFactorySvelte, type WidgetProps } from '$lib/api/managers/widget';import { type UrlQueryFullConfig, urlQueryFullConfig } from '$lib/widgets/url-query/url-query.config';import type { WidgetDeclaration } from '$lib/api/managers/widget/widget-declaration';
export const declaration = { factory: () => import('./UrlQuery.svelte').then((UrlQuery) => widgetFactorySvelte(UrlQuery)), schema: () => urlQueryFullConfig,} satisfies WidgetDeclaration;
export type UrlQueryProps = WidgetProps<UrlQueryFullConfig>;packages/common/src/lib/widgets/url-query/url-query.config.ts
import { inToolbarSchemaFrom } from '$lib/api/managers/configuration/models/widget/widget-in-toolbar.schema';import { cadMapQueryParamsSchema, coordsQueryParamsSchema, defaultCadMapQueryParamsSchema, defaultCoordsQueryParamsSchema, defaultSpwSearchAllParams, esriLayerQueryParamsSchema, spwSearchAllParamsSchema,} from '$lib/api/tools/query';import { hiddenContainerId } from '$lib/components/containers/hidden/hidden.schema';import { z } from 'zod';import { defineWidgetConfig } from '$lib/api/managers/configuration/models/widget/widget-configuration.schema';import type { PopupPosition } from '$lib/api/managers/popup';
const urlAnchorBboxQuerySchema = z.object({ type: z.literal('BBOX'), wkid: z.number().optional().default(31370), syncToUrl: z.boolean().optional().default(true),});export type UrlAnchorBboxQuery = z.infer<typeof urlAnchorBboxQuerySchema>;const defaultUrlAnchorBboxQuery = urlAnchorBboxQuerySchema.parse({ type: 'BBOX' });
const urlAnchorCustomQuerySchema = z.object({ type: z.literal('CUSTOM'), anchor: z.string(), queryConfig: esriLayerQueryParamsSchema,});export type UrlAnchorCustomQuery = z.infer<typeof urlAnchorCustomQuerySchema>;
const urlAnchorCoordQuerySchema = z.object({ type: z.literal('COOR'), queryConfig: coordsQueryParamsSchema.optional().default({ ...defaultCoordsQueryParamsSchema, wkid: 31370 }),});export type UrlAnchorCoorQuery = z.infer<typeof urlAnchorCoordQuerySchema>;const defaultUrlAnchorCoordQuery = urlAnchorCoordQuerySchema.parse({ type: 'COOR' });
const urlAnchorAdrQuerySchema = z.object({ type: z.literal('ADR'), queryConfig: spwSearchAllParamsSchema.optional().default(defaultSpwSearchAllParams),});export type UrlAnchorAdrQuery = z.infer<typeof urlAnchorAdrQuerySchema>;const defaultUrlAnchorAdrQuery = urlAnchorAdrQuerySchema.parse({ type: 'ADR' });
const urlAnchorCadQuerySchema = z.object({ type: z.literal('CAD'), queryConfig: cadMapQueryParamsSchema.optional().default(defaultCadMapQueryParamsSchema),});export type UrlAnchorCadQuery = z.infer<typeof urlAnchorCadQuerySchema>;const defaultUrlAnchorCadQuery = urlAnchorCadQuerySchema.parse({ type: 'CAD' });
const urlAnchorAddQuerySchema = z.object({ type: z.literal('ADD'),});export type UrlAnchorAddQuery = z.infer<typeof urlAnchorAddQuerySchema>;const defaultUrlAnchorAddQuery = urlAnchorAddQuerySchema.parse({ type: 'ADD' });
const urlAnchorQuerySchema = z.union([ urlAnchorCustomQuerySchema, urlAnchorBboxQuerySchema, urlAnchorAdrQuerySchema, urlAnchorCadQuerySchema, urlAnchorCoordQuerySchema, urlAnchorAddQuerySchema,]);export type UrlAnchorQuery = z.infer<typeof urlAnchorQuerySchema>;
const defaultAnchors = [ defaultUrlAnchorBboxQuery, defaultUrlAnchorCoordQuery, defaultUrlAnchorAdrQuery, defaultUrlAnchorCadQuery, defaultUrlAnchorAddQuery,];const urlQueryConfigSchema = z.object({ anchors: z.array(urlAnchorQuerySchema).optional().default(defaultAnchors), useParentWindowHash: z.boolean().optional().default(false), resultHighlightParamsSplitter: z.string().optional().default('|'), resultHighlightParamsValueSplitter: z.string().optional().default('='), popupPosition: z.custom<PopupPosition>().default('bottom-right'),});const urlQueryConfig = z.preprocess((value) => { if (!value || typeof value !== 'object' || Array.isArray(value)) return value;
const shortConfig = value as Record<string, unknown>; if (shortConfig.type !== 'BBOX' || Array.isArray(shortConfig.anchors)) return value;
const bboxAnchor = urlAnchorBboxQuerySchema.parse({ type: shortConfig.type, wkid: shortConfig.wkid, syncToUrl: shortConfig.syncToUrl, }); const config = Object.fromEntries( Object.entries(shortConfig).filter(([key]) => !['type', 'wkid', 'syncToUrl'].includes(key)), ); return { ...config, anchors: [bboxAnchor, ...defaultAnchors.filter((anchor) => anchor.type !== 'BBOX')], };}, urlQueryConfigSchema);export const urlQueryFullConfig = defineWidgetConfig({ container: hiddenContainerId, inToolbar: inToolbarSchemaFrom(false), active: true, config: urlQueryConfig.optional().prefault({}),});
export type UrlQueryFullConfig = z.infer<typeof urlQueryFullConfig>;packages/common/src/lib/widgets/url-query/url-query.models.ts
import { z } from 'zod';import { MapServiceTypes } from '$lib/api/managers/configuration';
const urlAnchorResultHighlightParamsSchema = z.object({ show: z.boolean().optional().default(false), tooltipTitle: z.string().optional().default('Informations'), tooltipText: z.string().optional().default(''), tooltipOpen: z.boolean().optional().default(false), scale: z.number().optional().default(500), allowDelete: z.boolean().optional().default(true),});export type UrlAnchorResultHighlightParams = z.infer<typeof urlAnchorResultHighlightParamsSchema>;
export function getUrlAnchorResultHighlightParams(fromUrl: Record<string, string>): UrlAnchorResultHighlightParams { return urlAnchorResultHighlightParamsSchema.parse({ show: getBooleanValue(fromUrl['SHOW']), tooltipTitle: fromUrl['TOOLTIPTITLE'], tooltipText: fromUrl['TOOLTIPTEXT'], tooltipOpen: getBooleanValue(fromUrl['TOOLTIPOPEN']), scale: fromUrl['SCALE'] != undefined ? Number(fromUrl['SCALE']) : 500, allowDelete: getBooleanValue(fromUrl['ALLOWDELETE']), });}
const urlAnchorAddServiceParamsSchema = z.object({ type: z.custom<MapServiceTypes>().default(MapServiceTypes.ARCGIS_DYNAMIC), url: z.string().optional(), metadataId: z.string().optional(), visible: z.boolean().default(true), opacity: z.number().default(1), label: z.string().optional(), description: z.string().optional(),});export type UrlAnchorAddServiceParams = z.infer<typeof urlAnchorAddServiceParamsSchema>;
const legacyPanierItemSchema = z .object({ type: z.string().optional(), url: z.string().nullish(), metadataId: z.string().optional(), metadataID: z.string().optional(), uuid: z.string().optional(), identifier: z.string().optional(), serviceId: z.string().optional(), visible: z.boolean().optional(), toLoad: z.boolean().optional(), opacity: z.number().optional(), alpha: z.number().optional(), label: z.string().optional(), description: z.string().optional(), }) .passthrough();
const legacyPanierSchema = z.array(legacyPanierItemSchema);type LegacyPanierItem = z.infer<typeof legacyPanierItemSchema>;
const legacyMapServiceTypes: Record<string, MapServiceTypes> = { AGS_DYNAMIC: MapServiceTypes.ARCGIS_DYNAMIC, AGS_TILED: MapServiceTypes.ARCGIS_TILED, ARCGIS_DYNAMIC: MapServiceTypes.ARCGIS_DYNAMIC, ARCGIS_TILED: MapServiceTypes.ARCGIS_TILED, ARCGIS_FEATURE_SERVICE: MapServiceTypes.ARCGIS_FEATURE_SERVICE, ARCGIS_FEATURE_LAYER: MapServiceTypes.ARCGIS_FEATURE_LAYER, WMS: MapServiceTypes.WMS, WMTS: MapServiceTypes.WMTS, OSM: MapServiceTypes.OSM, GOOGLE: MapServiceTypes.GOOGLE, TIMER: MapServiceTypes.TIME_TRAVEL, TIME_TRAVEL: MapServiceTypes.TIME_TRAVEL,};
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
export function getUrlAnchorAddServiceParams(fromUrls: Record<string, string>[]): UrlAnchorAddServiceParams[] { return fromUrls.map((fromUrl) => { return urlAnchorAddServiceParamsSchema.parse({ type: fromUrl['TYPE'], url: fromUrl['URL'], description: fromUrl['DESCRIPTION'], metadataId: fromUrl['METADATAID'], visible: getBooleanValue(fromUrl['VISIBLE']), opacity: fromUrl['OPACITY'] != undefined ? Number(fromUrl['OPACITY']) : 1, label: fromUrl['LABEL'], }); });}
export function getLegacyPanierAddServiceParams(hash: string): UrlAnchorAddServiceParams[] { const matches = [...hash.matchAll(/#PANIER=([^#]*)/gi)];
return matches.flatMap((match) => { try { const panier = legacyPanierSchema.parse(JSON.parse(decodeURIComponent(match[1]))); return panier.flatMap(toAddServiceParams); } catch (error) { console.warn('Unable to convert legacy #PANIER anchor to #ADD parameters', error); return []; } });}
function toAddServiceParams(item: LegacyPanierItem): UrlAnchorAddServiceParams[] { if (item.url) { const type = getLegacyMapServiceType(item.type); if (!type) { console.warn('Unable to convert legacy #PANIER service with unsupported type', item); return []; } return [ urlAnchorAddServiceParamsSchema.parse({ type, url: item.url, visible: item.toLoad !== false && item.visible !== false, opacity: getLegacyOpacity(item), label: item.label, description: item.description, }), ]; }
const metadataId = getLegacyMetadataId(item); if (metadataId) { return [ urlAnchorAddServiceParamsSchema.parse({ metadataId, visible: item.toLoad !== false && item.visible !== false, opacity: getLegacyOpacity(item), label: item.label, description: item.description, }), ]; }
console.warn('Unable to convert legacy #PANIER service without URL or metadata UUID', item); return [];}
function getLegacyMapServiceType(type: string | undefined): MapServiceTypes | undefined { return type ? legacyMapServiceTypes[type.toUpperCase()] : MapServiceTypes.ARCGIS_DYNAMIC;}
function getLegacyMetadataId(item: LegacyPanierItem): string | undefined { return [item.metadataId, item.metadataID, item.uuid, item.identifier, item.serviceId].find( (candidate): candidate is string => !!candidate && uuidPattern.test(candidate), );}
function getLegacyOpacity(item: LegacyPanierItem): number { if (item.opacity != null) { return item.opacity; } if (item.alpha == null) { return 1; } return item.alpha > 1 ? item.alpha / 100 : item.alpha;}
function getBooleanValue(urlValue: string): boolean | undefined { if (!urlValue) { return undefined; } return urlValue.toLowerCase() === 'true';}packages/common/src/lib/widgets/url-query/url-query.utils.ts
export interface UrlSpatialCoordinates { coordinates: number[]; wkid: number;}
interface ProjectionUnits { definition: string; wktDefinition: string;}
export function getUrlStateWindow(currentWindow: Window, useParentWindowHash: boolean): Window { if (!useParentWindowHash || currentWindow.parent === currentWindow) { return currentWindow; }
try { void currentWindow.parent.location.hash; return currentWindow.parent; } catch { return currentWindow; }}
export function getHashAnchorValue(hash: string, key: string): string | undefined { const match = new RegExp(`#${escapeRegExp(key)}=([^#&]*)`, 'i').exec(hash); return match?.[1];}
export function upsertHashAnchor(hash: string, key: string, value: string): string { const anchor = `#${key}=${value}`; const normalizedHash = hash === '#' ? '' : hash; const anchorPattern = new RegExp(`#${escapeRegExp(key)}=[^#]*`, 'i');
return anchorPattern.test(normalizedHash) ? normalizedHash.replace(anchorPattern, anchor) : `${normalizedHash}${anchor}`;}
export function replaceHashAnchor(targetWindow: Window, key: string, value: string): void { const url = new URL(targetWindow.location.href); url.hash = upsertHashAnchor(url.hash, key, value); targetWindow.history.replaceState(targetWindow.history.state, '', url);}
export function parseUrlSpatialCoordinates( value: string, coordinateCount: number, defaultWkid: number, separator = ',',): UrlSpatialCoordinates { const parts = value.split(separator).map((part) => part.trim()); if (parts.length !== coordinateCount && parts.length !== coordinateCount + 1) { throw new Error(`Expected ${coordinateCount} coordinates with an optional SRID`); } if (parts.some((part) => part.length === 0)) { throw new Error('Coordinates and SRID cannot be empty'); }
const values = parts.map(Number); if (values.some((part) => !Number.isFinite(part))) { throw new Error('Coordinates and SRID must be finite numbers'); }
const hasSrid = values.length === coordinateCount + 1; const wkid = hasSrid ? values[coordinateCount] : defaultWkid; if (!Number.isInteger(wkid) || wkid <= 0) { throw new Error('SRID must be a positive integer'); }
return { coordinates: values.slice(0, coordinateCount), wkid, };}
export function formatUrlSpatialCoordinates( coordinates: number[], wkid: number, metricProjection: boolean, separator = ',',): string { if (coordinates.some((coordinate) => !Number.isFinite(coordinate))) { throw new Error('Coordinates must be finite numbers'); } if (!Number.isInteger(wkid) || wkid <= 0) { throw new Error('SRID must be a positive integer'); }
const formattedCoordinates = metricProjection ? coordinates.map(Math.trunc) : coordinates; return [...formattedCoordinates, wkid].join(separator);}
export function isMetricProjection(projection: ProjectionUnits | undefined): boolean { if (!projection) return false;
return ( /(?:^|\s)\+units=m(?:\s|$)/i.test(projection.definition) || /UNIT\["metre",\s*1(?:[,\]])/i.test(projection.wktDefinition) );}
function escapeRegExp(value: string): string { return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');}packages/common/src/lib/widgets/url-query/UrlQuery.svelte
<script lang="ts"> import type { UrlAnchorAdrQuery, UrlAnchorBboxQuery, UrlAnchorCadQuery, UrlAnchorCoorQuery, UrlAnchorCustomQuery, UrlAnchorQuery, } from './url-query.config'; import { fromBbox } from '$lib/api/domain/api-extent.utils'; import { getLegacyPanierAddServiceParams, getUrlAnchorAddServiceParams, getUrlAnchorResultHighlightParams, type UrlAnchorAddServiceParams, type UrlAnchorResultHighlightParams, } from './url-query.models'; import PopupComponent from '$lib/components/popup/PopupComponent.svelte'; import UrlQueryPopupContent from './UrlQueryPopupContent.svelte'; import { onDestroy } from 'svelte'; import type { ApiFeature } from '$lib/api/feature'; import { isApiFeature } from '$lib/widgets/global-search/models/global.search.models'; import { initGraphicMapServiceConfiguration, mapServiceConfigWithDefaults } from '$lib/api/managers/configuration'; import type { UrlQueryProps } from './url-query.declaration'; import { getMapManager } from '$lib/api/map'; import { formatUrlSpatialCoordinates, getHashAnchorValue, getUrlStateWindow, isMetricProjection, parseUrlSpatialCoordinates, replaceHashAnchor, } from './url-query.utils'; import type { ApiExtent } from '$lib/api/domain'; import { projectionManager } from '$lib/api/managers/projection';
let { fullConfig }: UrlQueryProps = $props();
const { config } = fullConfig; const { popupPosition } = config; const mapManager = getMapManager(); const urlStateWindow = getUrlStateWindow(window, config.useParentWindowHash); const metawal = mapManager.services.metawal; const zoomTool = mapManager.tools.zoom; const geometryEngine = mapManager.tools.geometryEngine;
type QueryAnchor = UrlAnchorAdrQuery | UrlAnchorCadQuery | UrlAnchorCoorQuery | UrlAnchorCustomQuery;
const addServiceParams = [ ...getUrlAnchorAddServiceParams(getPipeValuesForAnchor('ADD')), ...getLegacyPanierAddServiceParams(urlStateWindow.location.hash), ]; let selectedAnchor: UrlAnchorQuery | undefined = selectCurrentAnchor(); let onFeatureClickUnsubscriber: () => void; let bboxSyncUnsubscriber: (() => void) | undefined; let bboxSyncTimeout: ReturnType<typeof setTimeout> | undefined; let destroyed = false;
let open = $state<boolean>(false); let allowDelete = $state<boolean>(false); let title = $state<string>(''); let content = $state<string>(''); let queryFeature = $state<ApiFeature | undefined>();
const serviceIdentifier = 'UrlQueryMapserviceId'; const graphicMapService = mapManager.addGraphicMapService( initGraphicMapServiceConfiguration({ id: serviceIdentifier, label: serviceIdentifier, toc: { visible: false, }, }), );
let popupLocation = $derived.by(() => { if (queryFeature) return geometryEngine.getCenter(queryFeature); });
if (selectedAnchor) { startQuery(); } startBboxSynchronization();
function startQuery() { if (!selectedAnchor) { return; } const resultHighlightParams: UrlAnchorResultHighlightParams = getUrlAnchorResultHighlightParams(getPipeValue()); if (selectedAnchor.type === 'BBOX') { resolveBboxQuery(selectedAnchor, getAnchor(selectedAnchor.type)); } else if (selectedAnchor.type === 'ADR' || selectedAnchor.type === 'CAD' || selectedAnchor.type === 'COOR') { resolveAnchorQuery(selectedAnchor, getAnchor(selectedAnchor.type), resultHighlightParams); } else if (selectedAnchor.type === 'CUSTOM') { const searchedValue = getAnchor(selectedAnchor.anchor); if (searchedValue) { resolveAnchorQuery(selectedAnchor, searchedValue, resultHighlightParams); } } else if (selectedAnchor.type === 'ADD') { resolveAddQuery(addServiceParams); } }
function resolveAddQuery(addServicesParams: UrlAnchorAddServiceParams[]) { // Handle services added from URL addServicesParams .filter((param) => !!param.url && !param.metadataId) .forEach((params) => { if (params.url) { const mapServiceConfig = mapServiceConfigWithDefaults({ url: params.url, type: params.type, opacity: params.opacity, visible: params.visible, id: crypto.randomUUID(), label: params.label ?? '', }); mapManager.addMapService(mapServiceConfig); } });
// Handle services added from METADATA ID addServicesParams .filter((param) => !!param.metadataId && !param.url) .forEach((param) => { if (param.metadataId) { metawal.addMapServiceFromMetadataId(param.metadataId, param); } });
// Log error for each service without URL & METADATA ID addServicesParams .filter((param) => !param.metadataId && !param.url) .forEach((param) => { console.error( 'Unable to add this #ADD anchor to the map, as either METADATAID or URL must be defined', param, ); }); }
function resolveBboxQuery(bboxQuery: UrlAnchorBboxQuery, bbox: string | undefined) { if (!bbox) return; try { const parsedBbox = parseUrlSpatialCoordinates(bbox, 4, bboxQuery.wkid); const extent = fromBbox(parsedBbox.coordinates, parsedBbox.wkid); zoomTool.zoomToExtent(extent); } catch (err) { console.log('Unable to zoom to extent', err); } }
function startBboxSynchronization(): void { const bboxAnchor = config.anchors.find((anchor): anchor is UrlAnchorBboxQuery => anchor.type === 'BBOX'); if (!bboxAnchor?.syncToUrl) return;
mapManager.whenInnerMapReady(() => { if (destroyed || bboxSyncUnsubscriber) return;
synchronizeBbox(mapManager.getMapExtent(), bboxAnchor); bboxSyncUnsubscriber = mapManager.tools.events.watch('EXTENT', (extent) => { if (bboxSyncTimeout) clearTimeout(bboxSyncTimeout); bboxSyncTimeout = setTimeout(() => synchronizeBbox(extent, bboxAnchor), 250); }); }); }
function synchronizeBbox(extent: ApiExtent, bboxAnchor: UrlAnchorBboxQuery): void { try { const synchronizedExtent = extent.wkid === bboxAnchor.wkid ? extent : mapManager.tools.transform.transformExtent(extent, bboxAnchor.wkid); const coordinates = [ synchronizedExtent.xmin, synchronizedExtent.xmax, synchronizedExtent.ymin, synchronizedExtent.ymax, ]; replaceHashAnchor( urlStateWindow, 'BBOX', formatUrlSpatialCoordinates( coordinates, synchronizedExtent.wkid, isMetricProjection(projectionManager.getByWkid(synchronizedExtent.wkid)), ), ); } catch (error) { console.warn('Unable to synchronize BBOX with URL', error); } }
export function resolveAnchorQuery( anchorQuery: QueryAnchor, searchedValue: string | undefined, resultHighlightParams: UrlAnchorResultHighlightParams, ) { if (!searchedValue) return; resolveQuery(decodeURIComponent(searchedValue), anchorQuery).then((res) => onQueryResults(res, resultHighlightParams), ); }
function onQueryResults(results: ApiFeature[], resultHighlightParams: UrlAnchorResultHighlightParams): void { if (!results || !results[0]) { return; } const feature = results[0]; zoomTool.zoomToFeature(feature); if (resultHighlightParams.show) { graphicMapService.addFeature(feature); if (onFeatureClickUnsubscriber) { onFeatureClickUnsubscriber(); } onFeatureClickUnsubscriber = mapManager.tools.events.hitGraphicMapService({ event: 'click', mapService: graphicMapService, cb: () => { const features = results ? [...results.values()].flat() : []; const matchingFeature = features.some((x) => x.id === feature.id); if (matchingFeature) { openPopup(resultHighlightParams, feature); } }, }); } if (resultHighlightParams.tooltipOpen) { openPopup(resultHighlightParams, feature); } }
function openPopup(resultParams: UrlAnchorResultHighlightParams, feature: ApiFeature): void { queryFeature = feature; title = resultParams.tooltipTitle; content = resultParams.tooltipText; allowDelete = resultParams.allowDelete; open = false; setTimeout(() => { open = true; }, 50); }
function resolveQuery(searchText: string, queryParams: QueryAnchor): Promise<ApiFeature[]> { return mapManager.services .dynamicQuery({ searchText: decodeURIComponent(searchText), queryParams: queryParams.queryConfig, }) .then((res) => { return res.filter((x) => isApiFeature(x)) as ApiFeature[]; }); }
function selectCurrentAnchor(): UrlAnchorQuery | undefined { let currentAnchor; config.anchors.forEach((anchor) => { if (anchor.type === 'ADR' && getHashValue('ADR')) { currentAnchor = anchor; } else if (anchor.type === 'CAD' && getHashValue('CAD')) { currentAnchor = anchor; } else if (anchor.type === 'COOR' && getHashValue('COOR')) { currentAnchor = anchor; } else if (anchor.type === 'BBOX' && getHashValue('BBOX')) { currentAnchor = anchor; } else if (anchor.type === 'CUSTOM' && getHashValue(anchor.anchor)) { currentAnchor = anchor; } else if (anchor.type === 'ADD' && addServiceParams.length > 0) { currentAnchor = anchor; } return; }); return currentAnchor; }
function getAnchor(anchor: string): string | undefined { let anchorValue = getHashValue(anchor); if (!anchorValue) return; if (anchorValue.indexOf(config.resultHighlightParamsSplitter) > -1) { anchorValue = anchorValue.slice(0, anchorValue.indexOf(config.resultHighlightParamsSplitter)); } return anchorValue; }
function getHashValue(anchor: string): string | undefined { return getHashAnchorValue(urlStateWindow.location.hash, anchor); }
function getPipeValue(): Record<string, string> { const regExp = new RegExp(`\\|([A-Za-z0-9]+=[^|]+)`, 'g'); const match = [...urlStateWindow.location.hash.matchAll(regExp)]; const records: Record<string, string> = {};
match .map((m) => m[1]) .forEach((paramValue) => { const splitted: string[] = paramValue.split(config.resultHighlightParamsValueSplitter); return (records[splitted[0]] = decodeURIComponent(splitted[1])); }); return records; }
function getPipeValuesForAnchor(anchor: string): Record<string, string>[] { const regExp = new RegExp(`#${anchor}\\|([^#]+)`, 'g'); const match = [...urlStateWindow.location.hash.matchAll(regExp)]; const results: Record<string, string>[] = [];
match.forEach((m) => { const parameters = m[1].split(config.resultHighlightParamsSplitter); const record: Record<string, string> = {}; parameters.forEach((param) => { const [key, value] = param.split(config.resultHighlightParamsValueSplitter); if (key && value) { record[key] = decodeURIComponent(value); } }); results.push(record); });
return results; }
onDestroy(() => { destroyed = true; if (onFeatureClickUnsubscriber) { onFeatureClickUnsubscriber(); } bboxSyncUnsubscriber?.(); if (bboxSyncTimeout) clearTimeout(bboxSyncTimeout); });</script>
{#if queryFeature} <PopupComponent {popupPosition} {title} {open} location={popupLocation}> <UrlQueryPopupContent {allowDelete} {mapManager} {content} {graphicMapService} feature={queryFeature} /> </PopupComponent>{/if}packages/common/src/lib/widgets/url-query/UrlQueryPopupContent.svelte
<script lang="ts"> import { Button } from '$lib/components/shadcn/ui/button'; import type { MapManager } from '$lib/api/map'; import { getI18n } from '$lib/api/managers/i18n'; import type { ApiFeature } from '$lib/api/feature'; import type { ApiGraphicsMapService } from '$lib/api/mapservices';
interface Props { feature: ApiFeature; mapManager: MapManager; content: string; allowDelete: boolean; graphicMapService: ApiGraphicsMapService; }
let { feature, mapManager, content, allowDelete, graphicMapService }: Props = $props();
const i18n = getI18n();
function deleteFeature(feature: ApiFeature) { graphicMapService.removeFeature(feature); mapManager.closePopup(); }</script>
<div> <div>{content}</div> {#if allowDelete} <div class="gv-flex gv-justify-end"> <Button onclick={() => deleteFeature(feature)}>{i18n('common.delete')}</Button> </div> {/if}</div>