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),
});
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),
});
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 urlQueryConfig = z.object({
anchors: z.array(urlAnchorQuerySchema).optional().default(defaultAnchors),
resultHighlightParamsSplitter: z.string().optional().default('|'),
resultHighlightParamsValueSplitter: z.string().optional().default('='),
popupPosition: z.custom<PopupPosition>().default('bottom-right'),
});
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>;
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'],
});
});
}
function getBooleanValue(urlValue: string): boolean | undefined {
if (!urlValue) {
return undefined;
}
return urlValue.toLowerCase() === 'true';
}

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 { UrlUtils } from '$lib/api/utils';
import {
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';
let { fullConfig }: UrlQueryProps = $props();
const { config } = fullConfig;
const { popupPosition } = config;
const mapManager = getMapManager();
const metawal = mapManager.services.metawal;
const zoomTool = mapManager.tools.zoom;
const geometryEngine = mapManager.tools.geometryEngine;
type QueryAnchor = UrlAnchorAdrQuery | UrlAnchorCadQuery | UrlAnchorCoorQuery | UrlAnchorCustomQuery;
let selectedAnchor: UrlAnchorQuery | undefined = selectCurrentAnchor();
let onFeatureClickUnsubscriber: () => void;
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();
}
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') {
const pipeValues = getPipeValuesForAnchor(selectedAnchor.type);
resolveAddQuery(getUrlAnchorAddServiceParams(pipeValues));
}
}
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 extent = fromBbox(bbox, bboxQuery.wkid);
zoomTool.zoomToExtent(extent);
} catch (err) {
console.log('Unable to zoom to extent', err);
}
}
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.listenToAnyGraphicMapServiceClick((results) => {
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' && UrlUtils.getHashValue('ADR')) {
currentAnchor = anchor;
} else if (anchor.type === 'CAD' && UrlUtils.getHashValue('CAD')) {
currentAnchor = anchor;
} else if (anchor.type === 'COOR' && UrlUtils.getHashValue('COOR')) {
currentAnchor = anchor;
} else if (anchor.type === 'BBOX' && UrlUtils.getHashValue('BBOX')) {
currentAnchor = anchor;
} else if (anchor.type === 'CUSTOM' && UrlUtils.getHashValue(anchor.anchor)) {
currentAnchor = anchor;
} else if (anchor.type === 'ADD' && getPipeValuesForAnchor('ADD').length > 0) {
currentAnchor = anchor;
}
return;
});
return currentAnchor;
}
function getAnchor(anchor: string): string | undefined {
let anchorValue = UrlUtils.getHashValue(anchor);
if (!anchorValue) return;
if (anchorValue.indexOf(config.resultHighlightParamsSplitter) > -1) {
anchorValue = anchorValue.slice(0, anchorValue.indexOf(config.resultHighlightParamsSplitter));
}
return anchorValue;
}
function getPipeValue(): Record<string, string> {
const regExp = new RegExp(`\\|([A-Za-z0-9]+=[^|]+)`, 'g');
const match = [...window.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 = [...window.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(() => {
if (onFeatureClickUnsubscriber) {
onFeatureClickUnsubscriber();
}
});
</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