Skip to content

Source Legend

Source Legend

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/legend/legend.declaration.ts

packages/common/src/lib/widgets/legend/legend.declaration.ts
import { widgetFactorySvelte, type WidgetProps } from '$lib/api/managers/widget';
import { type LegendConfig, legendConfigSchema } from '$lib/widgets/legend/legend.config';
import type { WidgetDeclaration } from '$lib/api/managers/widget/widget-declaration';
export const declaration = {
factory: () => import('./Legend.svelte').then((Legend) => widgetFactorySvelte(Legend)),
schema: () => legendConfigSchema,
} satisfies WidgetDeclaration;
export type LegendProps = WidgetProps<LegendConfig>;

packages/common/src/lib/widgets/legend/legend.config.ts

packages/common/src/lib/widgets/legend/legend.config.ts
import { i18nSchemaFrom } from '$lib/api/managers/i18n';
import { inToolbarSchemaFrom } from '$lib/api/managers/configuration/models/widget/widget-in-toolbar.schema';
import type { z } from 'zod';
import { defineWidgetConfig } from '$lib/api/managers/configuration';
export const legendI18n = i18nSchemaFrom({
dataToShow: {
fr: 'Données à afficher',
nl: 'NL - Données à afficher',
},
legend: {
fr: 'Légende',
nl: 'NL - Légende',
},
noData: {
fr: 'Pas de données à afficher',
nl: 'NL - Pas de données à afficher',
},
selectUnselectAll: {
fr: 'Tout cocher / décocher',
nl: 'NL - Tout cocher / décocher',
},
});
export const legendConfigSchema = defineWidgetConfig({
title: {
fr: 'Légende',
nl: 'NL - Légende',
},
icon: {
lucide: 'Image',
},
i18n: legendI18n,
inToolbar: inToolbarSchemaFrom({
type: 'button',
}),
});
export type LegendConfig = z.infer<typeof legendConfigSchema>;

packages/common/src/lib/widgets/legend/legend-data-selector/LegendDataSelector.svelte

packages/common/src/lib/widgets/legend/legend-data-selector/LegendDataSelector.svelte
<script lang="ts">
import type { LegendConfig } from '$lib/widgets/legend/legend.config';
import * as Collapsible from '$lib/components/shadcn/ui/collapsible';
import { getI18n } from '$lib/api/managers/i18n';
import LegendLayerHierarchy from '$lib/widgets/legend/legend-data-selector/LegendLayerHierarchy.svelte';
import { getLegendData } from '$lib/widgets/legend/models/legend-data.svelte';
import { Checkbox } from '$lib/components/shadcn/ui/checkbox';
type Props = {
fullConfig: LegendConfig;
};
let { fullConfig }: Props = $props();
const legendData = getLegendData();
const i18n = getI18n(fullConfig.i18n);
const open = $state<boolean>(true);
</script>
<Collapsible.Root {open} class="gv-border-solid gv-border-2 gv-border-primary">
<Collapsible.Trigger class="gv-w-full gv-p-1 gv-bg-primary/60">
<div class="gv-flex gv-justify-items-start gv-text-lg">
{i18n('dataToShow')}
</div>
</Collapsible.Trigger>
<Collapsible.Content>
<div class="space-y-4 gv-p-2">
<div class="gv-mb-2 gv-flex gv-items-center">
<Checkbox
checked={legendData.allSelected}
onCheckedChange={(checked) => legendData.selectUnselectAll(checked)}
/>
<div class="gv-ml-1">{i18n('selectUnselectAll')}</div>
</div>
{#each legendData.nodes as node (node.id)}
{#if node.mapServiceInstance.visible}
<div class="gv-mb-2">
<LegendLayerHierarchy {node} />
</div>
{/if}
{/each}
{#if legendData.dataSelectorDisplayIsEmpty()}
{i18n('noData')}
{/if}
</div>
</Collapsible.Content>
</Collapsible.Root>

packages/common/src/lib/widgets/legend/legend-data-selector/LegendLayerHierarchy.svelte

packages/common/src/lib/widgets/legend/legend-data-selector/LegendLayerHierarchy.svelte
<script lang="ts">
import SvelteSelf from './LegendLayerHierarchy.svelte';
import * as Collapsible from '$lib/components/shadcn/ui/collapsible';
import { Checkbox } from '$lib/components/shadcn/ui/checkbox';
import { Label } from '$lib/components/shadcn/ui/label';
import { isMapService } from '$lib/api/utils/service.utils';
import Minus from 'lucide-svelte/icons/minus';
import Plus from 'lucide-svelte/icons/plus';
import { cn } from '$lib/components/shadcn/utils';
import { Button } from '$lib/components/shadcn/ui/button';
import type { LegendNode } from '$lib/api/utils/legend/legend.model.svelte';
type Props = {
node: LegendNode;
};
let { node }: Props = $props();
let open = $state<boolean>(true);
function onNodeSelectionChange(checked: boolean): void {
node.updateSelectionTree(checked);
}
</script>
<div>
<Collapsible.Root {open}>
<Collapsible.Trigger asChild let:builder class="gv-w-full">
<div class="gv-flex gv-space-x-2">
{#if isMapService(node.mapServiceInstance)}
<Button
class={cn('gv-p-0 gv-h-4 gv-bg-transparent hover:gv-bg-transparent')}
builders={[builder]}
onclick={() => (open = !open)}
>
{#if open}
<Minus class="gv-text-primary" />
{:else}
<Plus class="gv-text-primary" />
{/if}
</Button>
{/if}
{#if node}
<Checkbox
checked={node.selected}
onCheckedChange={(checked) =>
onNodeSelectionChange(checked === 'indeterminate' ? false : checked)}
/>
{/if}
<Label>{node.mapServiceInstance.label}</Label>
</div>
</Collapsible.Trigger>
<Collapsible.Content>
{#if node.children}
<div
class={cn(
'gv-flex gv-flex-col gv-gap-2 gv-mt-2',
isMapService(node.mapServiceInstance) ? 'gv-ml-[50px]' : 'gv-ml-[18px]',
)}
>
{#each node.children as child}
<SvelteSelf node={child} />
{/each}
</div>
{/if}
</Collapsible.Content>
</Collapsible.Root>
</div>

packages/common/src/lib/widgets/legend/legend-list/LegendItemDisplay.svelte

packages/common/src/lib/widgets/legend/legend-list/LegendItemDisplay.svelte
<script lang="ts">
import type { ApiLegendItem } from '$lib/api/utils/legend/legend.model.svelte';
import { cn } from '$lib/components/shadcn/utils';
import { Loader } from '$lib/components/common';
type Props = {
legendItems: ApiLegendItem[];
class?: string;
};
let { legendItems, class: className }: Props = $props();
let imageLoading = $state<boolean>(true);
</script>
<div class={cn('gv-flex gv-flex-col gv-gap-2', className)}>
{#each legendItems as legendItem}
<div class="gv-flex gv-items-center gv-gap-2">
<img
onload={() => (imageLoading = false)}
src={legendItem.url ?? legendItem.base64String}
alt="Legend symbology"
/>
{#if imageLoading}
<Loader />
{/if}
<span>{legendItem.label}</span>
</div>
{/each}
</div>

packages/common/src/lib/widgets/legend/legend-list/LegendServiceDisplay.svelte

packages/common/src/lib/widgets/legend/legend-list/LegendServiceDisplay.svelte
<script lang="ts">
import { Collapsible, CollapsibleTrigger, CollapsibleContent } from '$lib/components/shadcn/ui/collapsible';
import Error from '$lib/components/common/Error.svelte';
import Loader from '$lib/components/common/Loader.svelte';
import { Button } from '$lib/components/shadcn/ui/button';
import { Label } from '$lib/components/shadcn/ui/label';
import { cn } from '$lib/components/shadcn/utils';
import LegendServiceDisplay from '$lib/widgets/legend/legend-list/LegendServiceDisplay.svelte';
import Minus from 'lucide-svelte/icons/minus';
import Plus from 'lucide-svelte/icons/plus';
import LegendItemDisplay from '$lib/widgets/legend/legend-list/LegendItemDisplay.svelte';
import type { LegendNode } from '$lib/api/utils/legend/legend.model.svelte';
import { isSublayer } from '$lib/api/layers';
type Props = {
node: LegendNode;
};
let { node }: Props = $props();
let open = $state<boolean>(true);
</script>
<Collapsible {open}>
<CollapsibleTrigger asChild let:builder class="gv-w-full">
<div class="gv-flex gv-space-x-2">
<Button
class={cn('gv-p-0 gv-h-4 gv-bg-transparent hover:gv-bg-transparent')}
builders={[builder]}
onclick={() => (open = !open)}
>
{#if open}
<Minus class="gv-text-primary" />
{:else}
<Plus class="gv-text-primary" />
{/if}
</Button>
<Label>{node.mapServiceInstance.label}</Label>
</div>
</CollapsibleTrigger>
<CollapsibleContent>
{#if node.children && node.children.length > 0}
<div class={cn('gv-flex gv-flex-col gv-gap-2 gv-mt-2 gv-ml-[18px]')}>
{#each node.children as child}
{#if child.selected && isSublayer(child.mapServiceInstance)}
<LegendServiceDisplay node={child} />
{/if}
{/each}
</div>
{:else if node.legendCacheItem}
{#if node.legendCacheItem.loading}
<Loader />
{:else if node.legendCacheItem.error}
<Error message={node.legendCacheItem.error.message} />
{:else if node.legendItemsData}
<div class="gv-ml-10 gv-mt-2">
<LegendItemDisplay legendItems={node.legendItemsData}></LegendItemDisplay>
</div>
{/if}
{/if}
</CollapsibleContent>
</Collapsible>

packages/common/src/lib/widgets/legend/legend-list/LegendServiceList.svelte

packages/common/src/lib/widgets/legend/legend-list/LegendServiceList.svelte
<script lang="ts">
import type { LegendConfig } from '$lib/widgets/legend/legend.config';
import * as Collapsible from '$lib/components/shadcn/ui/collapsible';
import { getI18n } from '$lib/api/managers/i18n';
import LegendServiceDisplay from '$lib/widgets/legend/legend-list/LegendServiceDisplay.svelte';
import { getLegendData } from '$lib/widgets/legend/models/legend-data.svelte';
type Props = {
fullConfig: LegendConfig;
};
let { fullConfig }: Props = $props();
const i18n = getI18n(fullConfig.i18n);
const open = $state<boolean>(true);
const legendData = getLegendData();
</script>
<Collapsible.Root {open} class="gv-border-solid gv-border-2 gv-border-primary">
<Collapsible.Trigger class="gv-w-full gv-p-1 gv-bg-primary/60">
<div class="gv-flex gv-justify-items-start gv-text-lg">
{i18n('legend')}
</div>
</Collapsible.Trigger>
<Collapsible.Content>
<div class="space-y-4 gv-p-2">
{#each legendData.nodes as node}
{#if node.selected && node.mapServiceInstance.visible}
<div class="gv-mb-2">
<LegendServiceDisplay {node} />
</div>
{/if}
{/each}
{#if legendData.legendDisplayIsEmpty()}
{i18n('noData')}
{/if}
</div>
</Collapsible.Content>
</Collapsible.Root>

packages/common/src/lib/widgets/legend/Legend.svelte

packages/common/src/lib/widgets/legend/Legend.svelte
<script lang="ts">
import { getMapManager } from '$lib/api/map';
import LegendDataSelector from './legend-data-selector/LegendDataSelector.svelte';
import LegendServiceList from './legend-list/LegendServiceList.svelte';
import { LegendData, setLegendContext } from './models/legend-data.svelte';
import type { LegendProps } from './legend.declaration';
let { fullConfig }: LegendProps = $props();
const mapManager = getMapManager();
setLegendContext(new LegendData(mapManager.layerList));
</script>
<LegendDataSelector {fullConfig}></LegendDataSelector>
<LegendServiceList {fullConfig}></LegendServiceList>

packages/common/src/lib/widgets/legend/models/legend-data.svelte.ts

packages/common/src/lib/widgets/legend/models/legend-data.svelte.ts
import { getContext, setContext } from 'svelte';
import type { ApiMapService, ApiMapServiceList } from '$lib/api/mapservices';
import { reverse } from '$lib/api/utils';
import { LegendNode } from '$lib/api/utils/legend/legend.model.svelte';
export class LegendData {
public readonly nodes = $derived.by(() => {
return reverse(this.layerList.list)
.filter((x) => x.toc.visible)
.map((service) => new LegendNode(service));
});
get allSelected(): boolean {
if (this.nodes.every((x) => x.selected)) {
return true;
}
if (this.nodes.filter((x) => x.mapServiceInstance?.visible).every((x) => !x.selected)) {
return false;
}
return false;
}
selectUnselectAll(value: boolean): void {
this.nodes.forEach((node) => node.updateSelectionTree(value));
}
constructor(private readonly layerList: ApiMapServiceList) {}
legendDisplayIsEmpty(): boolean {
return !this.nodes || !this.nodes.some((x) => x.selected && x.mapServiceInstance?.visible);
}
dataSelectorDisplayIsEmpty(): boolean {
return !this.nodes || this.nodes.filter((x) => x.mapServiceInstance?.visible).length === 0;
}
}
const LEGEND_CONTEXT_KEY = 'LEGEND_CONTEXT_KEY';
export function setLegendContext(legendData: LegendData) {
setContext(LEGEND_CONTEXT_KEY, legendData);
return getLegendData();
}
export function getLegendData(): LegendData {
const legendData = getContext<LegendData>(LEGEND_CONTEXT_KEY);
if (!legendData) {
throw new Error('Legend not found in context.');
}
return legendData;
}
export function isMapServiceWithUrl(mapService: unknown): mapService is MapServiceWithUrl {
return !!mapService && typeof mapService === 'object' && 'url' in mapService;
}
export type MapServiceWithUrl = ApiMapService & { url: string };

Aller plus loin