Source AddDataFromUrl
Source AddDataFromUrl
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/add-data/add-data-from-url/add-data-from-url.declaration.tspackages/common/src/lib/widgets/add-data/add-data-from-url/add-data-from-url-widget.config.tspackages/common/src/lib/widgets/add-data/add-data-from-url/add-data-from-url.i18n.tspackages/common/src/lib/widgets/add-data/add-data-from-url/add-data-from-url.model.tspackages/common/src/lib/widgets/add-data/add-data-from-url/AddDataFromUrl.sveltepackages/common/src/lib/widgets/add-data/add-data-from-url/AddDataFromUrlWidget.sveltepackages/common/src/lib/widgets/add-data/add-data-from-url/LayerHierarchySelect.sveltepackages/common/src/lib/widgets/add-data/add-data-from-url/tree-item.svelte.ts
packages/common/src/lib/widgets/add-data/add-data-from-url/add-data-from-url.declaration.ts
import { widgetFactorySvelte, type WidgetProps } from '$lib/api/managers/widget';import { addDataFromUrlWidgetSchema, type AddDataFromUrlWidgetConfig } from './add-data-from-url-widget.config';import type { WidgetDeclaration } from '$lib/api/managers/widget/widget-declaration';
export const declaration = { factory: () => import('./AddDataFromUrlWidget.svelte').then((AddDataFromUrlWidget) => widgetFactorySvelte(AddDataFromUrlWidget), ), schema: () => addDataFromUrlWidgetSchema,} satisfies WidgetDeclaration;
export type AddDataFromUrlProps = WidgetProps<AddDataFromUrlWidgetConfig>;packages/common/src/lib/widgets/add-data/add-data-from-url/add-data-from-url-widget.config.ts
import { defineWidgetConfig } from '$lib/api/managers/configuration/models/widget/widget-configuration.schema';import { inToolbarSchemaFrom } from '$lib/api/managers/configuration/models/widget/widget-in-toolbar.schema';import { i18nSchemaFrom } from '$lib/api/managers/i18n/i18n.schema';import { z } from 'zod';import { addDataFromUrlI18n } from '$lib/widgets/add-data/add-data-from-url/add-data-from-url.i18n';
export const addDataFromUrlConfigSchema = z .object({ closeOnMapServiceAdded: z.boolean().default(true), showServicePreview: z.boolean().default(false), }) .prefault({});export type AddDataFromUrlConfig = z.infer<typeof addDataFromUrlConfigSchema>;
export const addDataFromUrlWidgetSchema = defineWidgetConfig({ i18n: i18nSchemaFrom(addDataFromUrlI18n), icon: { lucide: 'Plus', }, title: addDataFromUrlI18n['add-data-from-url-title'], inToolbar: inToolbarSchemaFrom({ type: 'button', }), config: addDataFromUrlConfigSchema,});
export type AddDataFromUrlWidgetConfig = z.infer<typeof addDataFromUrlWidgetSchema>;packages/common/src/lib/widgets/add-data/add-data-from-url/add-data-from-url.i18n.ts
import type { I18nRegistry } from '$lib/api/managers/i18n';
export const addDataFromUrlI18n = { 'add-data-from-url-title': { fr: 'Ajouter des données', nl: 'NL - Ajouter des données', }, 'add-data-from-url-sub-title': { fr: 'Depuis une URL', nl: 'NL - Depuis une URL', }, 'default-map-service-name': { fr: 'Nouveau map service', nl: 'NL - Nouveau map service', }, 'select-unselect-all': { fr: 'Tout sélectionner / déselectionner', nl: 'NL - Tout sélectionner / déselectionner', }, 'service-type': { fr: 'Type de service', nl: 'NL - Type de service', }, 'data-type': { fr: 'Type de service', nl: 'NL - Type de service', }, 'override-style': { fr: 'Surcharger le style', nl: 'NL - Surcharger le style', }, 'loading-error': { fr: "Impossible de charger le service. l'URL encodée est-elle valide ?", nl: "NL - Impossible de charger le service, l'URL encodée est-elle valide ?", }, 'wms-error': { fr: "L'URL encodée n'est pas conforme, il ne faut pas renseigner de paramètre de requête (?request=GetCapabilities par exemple)", nl: "NL - L'URL encodée n'est pas conforme, il ne faut pas renseigner de paramètre de requête (?request=GetCapabilities par exemple)", }, url: { fr: 'URL du service', nl: 'NL - URL du service', }, 'service-name': { fr: 'Nom de la couche', nl: 'Naam van de layer', }, 'ags-dynamic': { fr: 'ArcGIS Dynamique', nl: 'NL - ArcGIS Dynamique', }, 'ags-tiled': { fr: 'ArcGIS Tuilé', nl: 'NL - ArcGIS Tuilé', }, wms: { fr: 'WMS', nl: 'NL - WMS', },} satisfies I18nRegistry;
export type AddDataFromUrlI18n = I18nRegistry<keyof typeof addDataFromUrlI18n>;packages/common/src/lib/widgets/add-data/add-data-from-url/add-data-from-url.model.ts
import { MapServiceTypes } from '$lib/api/managers/configuration/models/service/map-service-configuration.enum';import type { ArcgisDynamicMapServiceConfiguration, ArcgisFeatureServiceConfiguration, ArcgisTiledMapServiceConfiguration, SublayerConfiguration, SublayerConfigurationMode, WmsMapServiceConfiguration,} from '$lib/api/managers/configuration/models/service/map-service-configuration.types';
export const availableMapServiceTypes = [ { label: 'ArcGIS Dynamic', value: MapServiceTypes.ARCGIS_DYNAMIC, }, { label: 'ArcGIS Tuilé', value: MapServiceTypes.ARCGIS_TILED, }, { label: 'ArcGIS Feature Service', value: MapServiceTypes.ARCGIS_FEATURE_SERVICE, }, { label: 'WMS', value: MapServiceTypes.WMS, },];
export const mapServiceTypeToExampleURL = { ARCGIS_DYNAMIC: 'https://geoservices.wallonie.be/arcgis/rest/services/MOBILITE/RAVEL_VELOROUTES/MapServer', ARCGIS_TILED: 'https://geoservices.wallonie.be/arcgis/rest/services/IMAGERIE/ORTHO_LAST/MapServer', ARCGIS_FEATURE_SERVICE: 'https://geoservices.test.wallonie.be/arcgis/rest/services/EAU/RES_HYDRO_WAL_SIMPLE/FeatureServer', WMS: 'https://geoservices.wallonie.be/arcgis/services/FORET/FORETANC/MapServer/WMSServer',};
export type AddableWithURLMapServiceTypes = AddFromUrlMapServiceConfiguration['type'];
export type AddFromUrlMapServiceConfiguration = ( | WmsMapServiceConfiguration | ArcgisDynamicMapServiceConfiguration | ArcgisTiledMapServiceConfiguration | ArcgisFeatureServiceConfiguration) & { sublayers?: SublayerConfiguration[]; sublayersConfigMode: SublayerConfigurationMode;};packages/common/src/lib/widgets/add-data/add-data-from-url/AddDataFromUrl.svelte
<script lang="ts"> import type { SublayerParent } from '$lib/api/layers/api-sublayer.svelte'; import { MapServiceTypes } from '$lib/api/managers/configuration/models/service/map-service-configuration.enum'; import { getI18n } from '$lib/api/managers/i18n'; import { getMapManager } from '$lib/api/map'; import type { ApiMapService } from '$lib/api/mapservices'; import StringUtils from '$lib/api/utils/string.utils'; import { ApiSelect } from '$lib/components/api-select'; import { Button } from '$lib/components/shadcn/ui/button'; import { Input } from '$lib/components/shadcn/ui/input'; import { Label } from '$lib/components/shadcn/ui/label'; import { onDestroy, tick } from 'svelte'; import type { AddDataFromUrlConfig } from './add-data-from-url-widget.config'; import { type AddFromUrlMapServiceConfiguration, availableMapServiceTypes, mapServiceTypeToExampleURL, } from './add-data-from-url.model'; import LayerHierarchySelect from './LayerHierarchySelect.svelte'; import type { AddDataFromUrlI18n } from '$lib/widgets/add-data/add-data-from-url/add-data-from-url.i18n'; import { getTopicManager } from '$lib/api/managers/topic'; import { getAddData } from '$lib/widgets/add-data/add-data.svelte'; import { getWidgetManager } from '$lib/api/managers/widget'; import { highlightServiceInToc } from '$lib/widgets/toc/toc.utils'; import { showToast } from '$lib/components/toast/toast.utils'; import { isCapabilitiesLoadingError, isFeatureService } from '$lib/api/utils'; import ApiSymbolEditor from '$lib/components/api-symbol-editor/ApiSymbolEditor.svelte'; import { type ApiFeatureSymbols, apiFeatureSymbolsSchema } from '$lib/api/symbol'; import { Checkbox } from '$lib/components/shadcn/ui/checkbox'; import { createTree, extractSelected } from '$lib/widgets/add-data/add-data-from-url/tree-item.svelte';
interface Props { config: AddDataFromUrlConfig; i18nRegistry: AddDataFromUrlI18n; }
const { i18nRegistry, config }: Props = $props(); const i18n = getI18n(i18nRegistry); const mapManager = getMapManager(); const widgetManager = getWidgetManager(); const addDataContext = getAddData(); const topic = getTopicManager();
let currentServiceType = $state< | MapServiceTypes.WMS | MapServiceTypes.ARCGIS_DYNAMIC | MapServiceTypes.ARCGIS_TILED | MapServiceTypes.ARCGIS_FEATURE_SERVICE >(MapServiceTypes.ARCGIS_DYNAMIC); let currentMapServiceUrl = $state(''); let currentMapService = $state<ApiMapService | undefined>(undefined); const tree = $derived(currentMapService ? createTree(currentMapService, null) : null);
let currentSymbolConfig = $state<ApiFeatureSymbols | undefined>(undefined); let overrideFeatureStyle = $state<boolean>(false);
$effect(() => { if (overrideFeatureStyle && !currentSymbolConfig) { currentSymbolConfig = apiFeatureSymbolsSchema.parse({}); }
if (!overrideFeatureStyle && currentSymbolConfig) { currentSymbolConfig = undefined; if (currentMapService && isFeatureService(currentMapService)) { currentMapService.resetDefaultRenderer(); } } });
$effect(() => { if (currentSymbolConfig && currentMapService && isFeatureService(currentMapService)) { currentMapService.updateSymbols(currentSymbolConfig); } });
function getMapServiceConfig(): AddFromUrlMapServiceConfiguration { return { id: StringUtils.uuid(), type: currentServiceType, label: '', url: currentMapServiceUrl, visible: true, identifiable: true, opacity: config.showServicePreview ? 1 : 0, sublayers: [], sublayersConfigMode: 'modify', removable: true, toc: { visible: config.showServicePreview, open: false, }, symbols: currentSymbolConfig, }; }
function loadMapService() { removeMapServicePreview(); const mapServiceConfig = getMapServiceConfig(); currentMapService = mapManager.addMapService(mapServiceConfig); if (currentMapService) { currentMapService.onLoad( () => { if (currentMapService && !currentMapService.label) { currentMapService.label = i18n('default-map-service-name'); } }, (error) => onLoadError(error), ); } }
function onLoadError(error: unknown) { removeMapServicePreview(); const message = currentServiceType === MapServiceTypes.WMS && isCapabilitiesLoadingError(error) ? i18n('wms-error') : i18n('loading-error'); showToast({ level: 'error', message }); }
function addMapService() { if (!currentMapService || !tree) { return; } const layer = extractSelected(tree); layer.toc.visible = true; layer.opacity = 1; topic.publish({ type: 'AddData-add-mapService-from-url', layer: layer, }); highlightServiceInToc(layer.id, widgetManager, mapManager); currentMapService = undefined; if (config.closeOnMapServiceAdded) { tick().then(() => { addDataContext.closeWidget(); }); } }
function reset() { removeMapServicePreview(); }
function removeMapServicePreview() { if (currentMapService) { mapManager.layerList.remove(currentMapService); currentMapService = undefined; } }
onDestroy(() => removeMapServicePreview());</script>
<div class="gv-flex gv-flex-col gv-gap-3"> <span class="gv-font-bold gv-text-lg gv-my-2">{i18n('add-data-from-url-sub-title')}</span>
<div class="gv-flex gv-flex-col gv-gap-3"> <div class="gv-flex gv-flex-col gv-gap-1"> <Label class="gv-font-bold">{i18n('service-type')}</Label> <ApiSelect dataTestId="AddDataUrl-ServiceTypeSelect" bind:value={currentServiceType} options={availableMapServiceTypes} size="sm" /> </div>
<div class="gv-flex gv-flex-col gv-gap-1"> <Label class="gv-font-bold" for="add-data-url-input">{i18n('url')}</Label> <div class="gv-flex gv-gap-1"> <Input id="add-data-url-input" bind:value={currentMapServiceUrl} class="gv-flex-1" data-test-id="AddDataUrl-ServiceUrl" size="sm" /> <Button disabled={!currentMapServiceUrl || currentMapServiceUrl.length === 0} onclick={() => loadMapService()} class="gv-w-1/5" variant="secondary" data-test-id="AddDataUrl-LoadButton" size="sm" > {i18n('common.load')} </Button> </div> <div class="gv-flex"> <span class="gv-text-sm gv-text-grey-600"> {i18n('common.example')}: {mapServiceTypeToExampleURL[currentServiceType]} </span> </div> </div>
{#if currentMapService && tree} <div class="gv-flex gv-flex-col gv-gap-1"> <Label class="gv-font-bold" for="add-data-service-name-input">{i18n('service-name')}</Label> <Input id="add-data-service-name-input" bind:value={currentMapService.label} data-test-id="AddDataUrl-ServiceName" /> </div> <div class="gv-flex gv-flex-col gv-gap-1"> <Label class="gv-font-bold" for="add-data-service-name-input">{i18n('loaded-layers')}</Label> <div class="gv-max-h-[25vh] gv-overflow-y-auto gv-flex gv-flex-col gv-gap-4"> <LayerHierarchySelect item={tree} label={i18n('select-unselect-all')} checkboxDataTestId="AddDataUrl-SelectAll" /> {#if currentServiceType === 'ARCGIS_FEATURE_SERVICE'} <div class="gv-flex gv-flex-col gv-gap-2"> <div class="gv-flex gv-gap-1"> <Checkbox title={i18n('override-style')} bind:checked={overrideFeatureStyle} /> <Label class="gv-font-bold">{i18n('override-style')}</Label> </div> {#if overrideFeatureStyle && currentSymbolConfig} <div class="gv-w-1/2 gv-h-10"> <ApiSymbolEditor bind:value={currentSymbolConfig} /> </div> {/if} </div> {/if} </div> </div> {/if} </div>
<div class="gv-flex gv-justify-end gv-gap-2"> <Button disabled={!currentMapService} onclick={addMapService} data-test-id="AddDataUrl-AddButton" size="sm"> {i18n('common.add')} </Button> <Button onclick={() => reset()} variant="secondary" data-test-id="AddDataUrl-Reset" size="sm"> {i18n('common.reset')} </Button> </div></div>packages/common/src/lib/widgets/add-data/add-data-from-url/AddDataFromUrlWidget.svelte
<script lang="ts"> import AddDataFromUrl from './AddDataFromUrl.svelte'; import type { AddDataFromUrlProps } from './add-data-from-url.declaration';
let { fullConfig }: AddDataFromUrlProps = $props();</script>
<AddDataFromUrl config={fullConfig.config} i18nRegistry={fullConfig.i18n} />packages/common/src/lib/widgets/add-data/add-data-from-url/LayerHierarchySelect.svelte
<script lang="ts"> import SvelteSelf from './LayerHierarchySelect.svelte'; import { Checkbox } from '$lib/components/shadcn/ui/checkbox'; import { Label } from '$lib/components/shadcn/ui/label'; import type { TreeItem } from './tree-item.svelte.js';
interface Props { item: TreeItem; label?: string | undefined; checkboxDataTestId?: string; class?: string; }
let { item, label = undefined, checkboxDataTestId, class: className }: Props = $props(); const testId = $derived(checkboxDataTestId ?? `LayerHierarchySelect-${item.service.id}-Checkbox`);</script>
<div class={className}> <div class="gv-flex gv-items-center gv-space-x-2" data-test-id={`LayerHierarchySelect-${item.service.id}`}> <Checkbox bind:checked={item.selected} indeterminate={!item.allSublayerAreSelected && item.selected} data-test-id={testId} id={testId} /> <Label for={testId}>{label ?? item.service.label}</Label> </div> {#if item.children.length > 0} <div class="gv-flex gv-flex-col gv-gap-2 gv-mt-2 gv-ml-4"> {#each item.children.toReversed() as child (child.service.id)} <SvelteSelf item={child} /> {/each} </div> {/if}</div>packages/common/src/lib/widgets/add-data/add-data-from-url/tree-item.svelte.ts
import type { ApiMapService } from '$lib/api/mapservices';import { type ApiSublayer, hasSublayers } from '$lib/api/layers';import { isMapService } from '$lib/api/utils';
export class TreeItem { _selected = $state(true); public children: TreeItem[] = $state([]); public readonly allSublayerAreSelected = $derived.by(() => { if (this.children.length === 0) return true; return this.children.every((c) => c.selected); });
constructor( public readonly service: ApiMapService | ApiSublayer, public readonly parent: TreeItem | null, ) {}
public get selected() { return this._selected; }
public set selected(checked: boolean) { this._selected = checked; setChildrenSelected(this, checked); if (checked) { setParentSelected(this, checked); } }}
function setParentSelected(item: TreeItem, selected: boolean) { item._selected = selected; if (item.parent) { setParentSelected(item.parent, selected); }}function setChildrenSelected(item: TreeItem, selected: boolean) { if (item.children) { for (const child of item.children) { child._selected = selected; setChildrenSelected(child, selected); } }}
export function createTree(service: ApiMapService | ApiSublayer, parent: TreeItem | null): TreeItem { const item = new TreeItem(service, parent); if (hasSublayers(service) && service.sublayers?.length) { item.children = service.sublayers?.list.map((c) => createTree(c, item)); } return item;}
export function extractSelected(item: TreeItem): ApiMapService { if (!isMapService(item.service)) { throw new Error('Item service is not a mapService'); }
function removeNotSelected(item: TreeItem) { const service = item.service; if (hasSublayers(service)) { item.children.forEach((child) => { if (child.selected) { removeNotSelected(child); } else { service.sublayers.remove(child.service as ApiSublayer); } }); } }
removeNotSelected(item);
return item.service;}