Skip to content

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

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

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

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

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

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

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

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;
}

Aller plus loin