Skip to content

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.ts

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

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

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

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

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

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>

Aller plus loin