Skip to content

Source Draw

Source Draw

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

packages/common/src/lib/widgets/draw/draw.declaration.ts
import { widgetFactorySvelte, type WidgetProps } from '$lib/api/managers/widget';
import { type DrawFullConfig, drawFullConfigSchema } from './draw.config';
import type { WidgetDeclaration } from '$lib/api/managers/widget/widget-declaration';
export const declaration = {
factory: () => import('./Draw.svelte').then((Draw) => widgetFactorySvelte(Draw)),
schema: () => drawFullConfigSchema,
} satisfies WidgetDeclaration;
export type DrawProps = WidgetProps<DrawFullConfig>;

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

packages/common/src/lib/widgets/draw/draw.config.ts
import { defineWidgetConfig } from '$lib/api/managers/configuration';
import { inToolbarSchemaFrom } from '$lib/api/managers/configuration/models/widget/widget-in-toolbar.schema';
import { commonI18n, i18nDataSchema, i18nSchemaFrom } from '$lib/api/managers/i18n';
import { drawI18n } from '$lib/widgets/draw/draw.i18n';
import { z } from 'zod';
const drawConfigSchema = z.object({
text: z
.object({
label: i18nDataSchema.default(commonI18n['common.label']),
angleStep: z.number().default(45),
})
.prefault({}),
showProperties: z.boolean().default(false),
showLayerSwitch: z.boolean().default(false),
showLayerNameInput: z.boolean().default(true),
});
export const drawFullConfigSchema = defineWidgetConfig({
title: drawI18n.title,
icon: {
lucide: 'Pencil',
},
inToolbar: inToolbarSchemaFrom({
type: 'button',
}),
onActivate: {
deactivate: {
classes: [
'MeasureDistance',
'MeasureSurface',
'AdvancedSearch',
'Export',
'Report',
'Identify',
'AddData',
'MeasureElevationProfile',
],
},
},
i18n: i18nSchemaFrom(drawI18n),
config: drawConfigSchema.prefault({}),
});
export type DrawFullConfig = z.infer<typeof drawFullConfigSchema>;
export type DrawI18n = DrawFullConfig['i18n'];

packages/common/src/lib/widgets/draw/draw.i18n.ts

packages/common/src/lib/widgets/draw/draw.i18n.ts
import type { I18nRegistry } from '$lib/api/managers/i18n';
export const drawI18n = {
title: {
fr: 'Dessiner',
nl: 'NL - Dessiner',
},
'layer-name': {
fr: 'Dessin {{cnt}}',
nl: 'NL - Dessin {{cnt}}',
},
size: {
fr: 'Taille',
nl: 'NL - Taille',
},
shape: {
fr: 'Forme',
nl: 'NL - Forme',
},
angle: {
fr: 'Angle',
nl: 'NL - Angle',
},
labels: {
fr: 'Labels',
nl: 'NL - Labels',
},
coordinates: {
fr: 'Coordonnées',
nl: 'NL - Coordonnées',
},
attributes: {
fr: 'Attributs',
nl: 'NL - Attributs',
},
'attribute-name': {
fr: 'Nom de votre attribut',
nl: 'NL - Nom de votre attribut',
},
'add-attribute': {
fr: 'Ajouter un attribut',
nl: 'NL - Ajouter un attribut',
},
values: {
fr: 'Valeurs',
nl: 'NL - Valeurs',
},
value: {
fr: 'Valeur',
nl: 'NL - Valeur',
},
'service-name': {
fr: 'Intitulé de la couche',
nl: 'NL - Intitulé de la couche',
},
'select-on-map': {
fr: "Sélectionnez un dessin sur la carte pour le supprimer ou l'éditer.",
nl: "NL - Sélectionnez un dessin sur la carte pour le supprimer ou l'éditer.",
},
'allow-multi-selection': {
fr: 'Activer la sélection sur toutes les couches',
nl: 'NL - Activer la sélection sur toutes les couches',
},
'show-coordinates': {
fr: 'Coordonnées',
nl: 'NL - Coordonnées',
},
'show-buffer': {
fr: 'Lignes concentriques',
nl: 'NL - Lignes concentriques',
},
'show-buffer-label': {
fr: 'Label(s)',
nl: 'NL - Label(s)',
},
'buffer-distance': {
fr: 'Distance',
nl: 'NL - Distance',
},
'buffer-repeat': {
fr: 'Répétition',
nl: 'NL - Répétition',
},
'line-length': {
fr: 'Longueur',
nl: 'NL - Longueur',
},
'polygon-perimeter': {
fr: 'Périmètre',
nl: 'NL - Périmètre',
},
'polygon-area': {
fr: 'Superficie',
nl: 'NL - Superficie',
},
'active-layer': {
fr: 'Couche active',
nl: 'NL - Couche active',
},
'tooltip-point': {
fr: 'Point',
nl: 'NL - Point',
},
'tooltip-polygon': {
fr: 'Surface',
nl: 'NL - Surface',
},
'tooltip-polyline': {
fr: 'Ligne',
nl: 'NL - Ligne',
},
'tooltip-circle': {
fr: 'Cercle',
nl: 'NL - Cercle',
},
'tooltip-polyline-freehand': {
fr: 'Ligne à main levée',
nl: 'NL - Ligne à main levée',
},
'tooltip-polygon-freehand': {
fr: 'Surface à main levée',
nl: 'NL - Surface à main levée',
},
'tooltip-text': {
fr: 'Texte',
nl: 'NL - Texte',
},
'tooltip-selection': {
fr: 'Sélectionner',
nl: 'NL - Sélectionner',
},
} satisfies I18nRegistry;

packages/common/src/lib/widgets/draw/draw.model.ts

packages/common/src/lib/widgets/draw/draw.model.ts
import { type DrawMode, type DrawCreateType, type LengthUnit, type AreaUnit } from '$lib/api/tools';
import type { Icon } from '$lib/api/icons';
import { LineStyle, PointStyle } from '$lib/api/symbol';
export type DrawToolModel = {
type: DrawCreateType | 'selection';
mode?: DrawMode;
icon: Icon;
};
export type PointStyleModel = {
type: PointStyle;
icon: Icon;
};
export type LineStyleModel = {
type: LineStyle;
icon: Icon;
};
export type VisibleElements = {
point: {
coordinates: boolean;
};
polyline: {
showLength: boolean;
length: LengthUnit | undefined;
};
polygon: {
showArea: boolean;
area: AreaUnit | undefined;
showLength: boolean;
length: LengthUnit | undefined;
};
buffer: {
enabled: boolean;
distance: number;
repeat: number;
showLabel: boolean;
};
};
export const drawToolModels: DrawToolModel[] = [
{
type: 'point',
icon: {
lucide: 'Dot',
},
},
{
type: 'polyline',
icon: {
lucide: 'Waypoints',
},
},
{
type: 'polygon',
icon: {
geoviewer: 'geoviewer-polygon',
},
},
{
type: 'circle',
icon: {
lucide: 'Circle',
},
},
{
type: 'polyline',
mode: 'freehand',
icon: {
lucide: 'Pencil',
},
},
{
type: 'polygon',
mode: 'freehand',
icon: {
geoviewer: 'geoviewer-freehand-polygon',
},
},
{
type: 'text',
icon: {
lucide: 'Type',
},
},
{
type: 'selection',
icon: {
lucide: 'MousePointer',
},
},
];
export const pointStyleModels: PointStyleModel[] = [
{
type: PointStyle.CIRCLE,
icon: {
lucide: 'Circle',
},
},
{
type: PointStyle.X,
icon: {
lucide: 'X',
},
},
{
type: PointStyle.CROSS,
icon: {
lucide: 'Cross',
},
},
{
type: PointStyle.DIAMOND,
icon: {
lucide: 'Diamond',
},
},
{
type: PointStyle.SQUARE,
icon: {
lucide: 'Square',
},
},
];
export const lineStyleModels: LineStyleModel[] = [
{
type: LineStyle.SOLID,
icon: {
lucide: 'Circle',
},
},
{
type: LineStyle.DASH,
icon: {
lucide: 'CircleDashed',
},
},
];
export const defaultVisibleElements: VisibleElements = {
point: {
coordinates: false,
},
polyline: {
showLength: false,
length: 'm',
},
polygon: {
showLength: false,
length: 'm',
showArea: false,
area: 'm',
},
buffer: {
enabled: false,
distance: 1000,
repeat: 1,
showLabel: true,
},
};

packages/common/src/lib/widgets/draw/Draw.svelte

packages/common/src/lib/widgets/draw/Draw.svelte
<script lang="ts">
import { initGraphicMapServiceConfiguration, MapServiceTypes } from '$lib/api/managers/configuration';
import { getI18n } from '$lib/api/managers/i18n';
import { getMapManager } from '$lib/api/map';
import {
type ApiColor,
LineStyle,
PointStyle,
type PolygonSymbol,
type PolylineSymbol,
type SimplePointSymbol,
type TextSymbol,
} from '$lib/api/symbol';
import type { AreaUnit, DrawCreateType, DrawSymbol, LengthUnit } from '$lib/api/tools';
import { findOpacityFromColor, hexToRgb, isGraphicService, isNullOrUndefined, rgbToHex } from '$lib/api/utils';
import OpacitySlider from '$lib/components/opacity-slider/OpacitySlider.svelte';
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '$lib/components/shadcn/ui/accordion';
import { Button } from '$lib/components/shadcn/ui/button';
import { Input } from '$lib/components/shadcn/ui/input';
import { Slider } from '$lib/components/shadcn/ui/slider';
import {
type DrawToolModel,
drawToolModels,
type PointStyleModel,
pointStyleModels,
type VisibleElements,
} from './draw.model';
import { Trash2Icon } from 'lucide-svelte';
import { type Unsubscriber } from 'svelte/store';
import DrawEditAttributes from './DrawEditAttributes.svelte';
import type { ApiGraphicsMapService } from '$lib/api/mapservices';
import { onDestroy } from 'svelte';
import Icon from '$lib/components/icon/Icon.svelte';
import ColorChooser from '$lib/components/color-chooser/ColorChooser.svelte';
import { viewerColors } from '$lib/components/color-chooser/color-chooser.model';
import { ApiSelect, type ApiSelectItem } from '$lib/components/api-select';
import { Label } from '$lib/components/shadcn/ui/label';
import { Checkbox } from '$lib/components/shadcn/ui/checkbox';
import { ApiSimpleTooltip } from '$lib/components/api-simple-tooltip';
import LineStyleSelector from './LineStyleSelector.svelte';
import type { DrawProps } from './draw.declaration';
import type { ApiFeature } from '$lib/api/feature';
import { highlightServiceInToc } from '$lib/widgets/toc/toc.utils';
import { getWidgetManager } from '$lib/api/managers/widget';
import { deepClone2 } from '$lib/api/utils/index.js';
import { defaultVisibleElements } from '$lib/widgets/draw/draw.model.js';
import MouseTooltip from '$lib/components/tooltip/MouseTooltip.svelte';
import { cn } from '$lib/components/shadcn/utils';
const { fullConfig }: DrawProps = $props();
const { config } = fullConfig;
const i18n = getI18n(fullConfig.i18n);
const mapManager = getMapManager();
const widgetManager = getWidgetManager();
const layerList = mapManager.layerList;
const drawFactory = mapManager.tools.draw;
let mapClickListener: Unsubscriber | undefined;
const initIdentifier = (identifier: string) => {
let currentIdentifier = identifier;
let counter = 1;
while (layerList.containsById(currentIdentifier)) {
currentIdentifier = `${identifier}-${counter}`;
counter++;
}
return currentIdentifier;
};
const serviceIdentifier = initIdentifier('DrawWidgetLayerId');
const drawMapServiceCnt = layerList.list
.filter((layer) => isGraphicService(layer) && layer.isDraw)
.map((layer) => {
const possibleName = i18n('layer-name', { cnt: '$' });
const replaced = layer.label.replace(/\d/g, '$');
if (possibleName === replaced) {
const numberStr = layer.label.substring(replaced.indexOf('$'), replaced.lastIndexOf('$') + 1);
return Number(numberStr);
}
return 0;
})
.filter(Boolean)
.reduce((cnt, number) => Math.max(cnt, number), 0);
const initialDrawService = mapManager.addGraphicMapService(
initGraphicMapServiceConfiguration({
label: i18n('layer-name', { cnt: drawMapServiceCnt + 1 }),
id: serviceIdentifier,
editing: true,
isDraw: true,
toc: {
visible: false,
},
}),
);
const drawTool = drawFactory.create({ layer: initialDrawService });
const graphicServices: ApiSelectItem<ApiGraphicsMapService>[] = $derived(
mapManager.layerList.list
.filter((l) => l.type === MapServiceTypes.GRAPHICS && l.toc.visible)
.map((l) => ({
value: l as ApiGraphicsMapService,
label: l.label,
})),
);
const lengthOptions: ApiSelectItem<LengthUnit | undefined>[] = $derived([
{ value: 'm', label: i18n('common.meter') },
{ value: 'km', label: i18n('common.kilometer') },
]);
const areaOptions: ApiSelectItem<AreaUnit | undefined>[] = $derived([
{ value: 'm', label: i18n('common.square-meter') },
{ value: 'km', label: i18n('common.square-kilometer') },
]);
const pointSizeMultiplier = 2;
const textSizeMultiplier = 3;
const drawTools = $state<DrawToolModel[]>(drawToolModels);
const pointStyles = $state<PointStyleModel[]>(pointStyleModels);
let allowMultiSelection = $state(false);
let accordionOpenedItem = $state<string | undefined>();
let currentUpdatedFeature = $state<ApiFeature | null>();
let currentAttributes = $state<Record<string, unknown>>({ ['']: '' });
let visibleElements = $state<VisibleElements>(defaultVisibleElements);
let currentDrawToolModel = $state<DrawToolModel>(drawTools[0]);
let currentDrawType = $state<DrawCreateType>();
let currentSize = $state(5);
let currentOpacity = $state(0.5);
let currentColor = $state<string>(viewerColors[0]);
/** Advanced Options - Point **/
let currentPointStyle = $state<PointStyleModel>(pointStyles[0]);
/** Advanced Options - Polyline **/
let currentLineStyle = $state<LineStyle>(LineStyle.SOLID);
/** Advanced Options - Text **/
let currentText = $state<string>(i18n.translate(config.text.label));
let currentTextAngle = $state(0);
let pointSymbol = $derived.by<SimplePointSymbol>(() => {
if (currentPointStyle.type === PointStyle.X || currentPointStyle.type === PointStyle.CROSS) {
return {
type: 'simple-point',
style: currentPointStyle.type,
size: currentSize * pointSizeMultiplier,
color: currentColor,
border: {
type: 'simple-polyline',
style: LineStyle.SOLID,
width: 2,
color: currentColor,
},
visibleElements: {
showCoordinates: visibleElements.point.coordinates,
buffer: deepClone2(visibleElements.buffer),
},
};
} else {
return {
type: 'simple-point',
style: currentPointStyle.type,
size: currentSize * pointSizeMultiplier,
color: currentColor,
visibleElements: {
showCoordinates: visibleElements.point.coordinates,
buffer: deepClone2(visibleElements.buffer),
},
};
}
});
let polylineSymbol = $derived<PolylineSymbol>({
type: 'simple-polyline',
style: currentLineStyle,
width: currentSize,
color: currentColor,
visibleElements: {
lengthUnit: visibleElements.polyline.showLength ? visibleElements.polyline.length : undefined,
buffer: deepClone2(visibleElements.buffer),
},
});
let polygonSymbol = $derived<PolygonSymbol>({
type: 'simple-polygon',
color: hexToRgb(currentColor, currentOpacity),
border: {
type: 'simple-polyline',
width: currentSize,
color: currentColor,
style: LineStyle.SOLID,
},
visibleElements: {
lengthUnit: visibleElements.polygon.showLength ? visibleElements.polygon.length : undefined,
areaUnit: visibleElements.polygon.showArea ? visibleElements.polygon.area : undefined,
buffer: deepClone2(visibleElements.buffer),
},
});
let textSymbol = $derived<TextSymbol>({
type: 'text',
color: currentColor,
size: currentSize * textSizeMultiplier,
angle: currentTextAngle,
text: currentText,
});
function startDraw() {
if (currentDrawToolModel.type === 'selection') {
return;
}
currentDrawType = currentDrawToolModel.type;
drawTool.create({
type: currentDrawToolModel.type,
mode: currentDrawToolModel.mode,
onDrawComplete: (feature: ApiFeature) => {
if (initialDrawService.id === drawTool.layer.id && !initialDrawService.toc.visible) {
initialDrawService.toc.visible = true;
highlightServiceInToc(initialDrawService.id, widgetManager, mapManager);
}
feature.attributes = { ...feature.attributes, ...currentAttributes };
startDraw();
},
});
}
function removeCurrentUpdatedFeature() {
drawTool.delete(currentUpdatedFeature!);
currentUpdatedFeature = null;
}
// Handle map interaction when drawing/selecting
$effect(() => {
if (currentDrawToolModel.type === 'selection') {
drawTool.stop();
mapClickListener?.();
const onFeatureClicked = (layer: ApiGraphicsMapService, features: ApiFeature[]) => {
if (!features.length) {
drawTool.stop();
currentUpdatedFeature = null;
return;
}
drawTool.layer = layer;
currentUpdatedFeature = features[0];
drawTool.update(currentUpdatedFeature, {
onDrawComplete: () => (currentUpdatedFeature = null),
});
if (currentUpdatedFeature.attributes) {
currentAttributes = currentUpdatedFeature.attributes;
}
};
if (allowMultiSelection) {
mapClickListener = mapManager.tools.events.listenToAnyGraphicMapServiceClick((results) => {
const firstResults = results.entries().next()?.value;
if (firstResults) {
const [layer, features] = firstResults;
onFeatureClicked(layer, features);
}
});
} else {
mapClickListener = mapManager.tools.events.hitGraphicMapService({
event: 'click',
mapService: drawTool.layer,
cb: ({ features }) => onFeatureClicked(drawTool.layer, features),
});
}
} else {
startDraw();
mapClickListener?.();
currentUpdatedFeature = null;
}
});
// Handle symbol UI update by feature selection
$effect(() => {
const currentSymbol = currentUpdatedFeature?.symbol;
if (currentSymbol) {
const toHexColor = (color: ApiColor) => {
return (Array.isArray(color) ? rgbToHex(color[0], color[1], color[2]) : color).toUpperCase();
};
switch (currentSymbol.type) {
case 'simple-point':
case 'text':
currentColor = toHexColor(currentSymbol.color);
if (currentSymbol.type === 'simple-point') {
currentPointStyle = pointStyles.find((ps) => ps.type === currentSymbol.style)!;
currentSize = currentSymbol.size / pointSizeMultiplier;
currentDrawType = 'point';
if (!isNullOrUndefined(currentSymbol.visibleElements?.showCoordinates)) {
visibleElements.point.coordinates = currentSymbol.visibleElements?.showCoordinates;
}
if (currentSymbol.visibleElements?.buffer) {
visibleElements.buffer = currentSymbol.visibleElements.buffer;
}
} else if (currentSymbol.type === 'text') {
currentText = currentSymbol.text;
currentSize = currentSymbol.size / textSizeMultiplier;
currentTextAngle = currentSymbol.angle ?? 0;
currentDrawType = 'text';
}
break;
case 'simple-polyline':
currentColor = toHexColor(currentSymbol.color);
currentSize = currentSymbol.width;
currentDrawType = 'polyline';
if (currentSymbol.visibleElements?.buffer) {
visibleElements.buffer = currentSymbol.visibleElements.buffer;
}
if (currentSymbol.visibleElements?.lengthUnit) {
visibleElements.polyline.showLength = true;
visibleElements.polyline.length = currentSymbol.visibleElements.lengthUnit;
} else {
visibleElements.polyline.showLength = false;
}
break;
case 'simple-polygon':
currentColor = toHexColor(currentSymbol.color);
currentSize = currentSymbol.border.width;
currentOpacity = findOpacityFromColor(currentSymbol.color) || 0;
currentDrawType = 'polygon';
if (currentSymbol.visibleElements?.buffer) {
visibleElements.buffer = currentSymbol.visibleElements.buffer;
}
if (currentSymbol.visibleElements?.lengthUnit) {
visibleElements.polygon.showLength = true;
visibleElements.polygon.length = currentSymbol.visibleElements.lengthUnit;
} else {
visibleElements.polygon.showLength = false;
}
if (currentSymbol.visibleElements?.areaUnit) {
visibleElements.polygon.showArea = true;
visibleElements.polygon.area = currentSymbol.visibleElements.areaUnit;
} else {
visibleElements.polygon.showArea = false;
}
break;
}
}
});
// Handle symbol update from UI
$effect(() => {
const updateSymbol = (symbol: DrawSymbol) => {
if (currentUpdatedFeature) {
currentUpdatedFeature.symbol = symbol;
} else {
drawTool.updateSymbol(symbol);
}
};
switch (currentDrawType) {
case 'polyline':
updateSymbol(polylineSymbol);
break;
case 'polygon':
case 'circle':
updateSymbol(polygonSymbol);
break;
case 'point':
updateSymbol(pointSymbol);
break;
case 'text':
updateSymbol(textSymbol);
break;
}
});
// Handle attributes update
$effect(() => {
if (currentAttributes && currentUpdatedFeature) {
currentUpdatedFeature.attributes = currentAttributes;
}
});
export function initFromService(service: ApiGraphicsMapService) {
drawTool.layer = service;
if (initialDrawService.isEmpty()) {
mapManager.removeMapService(serviceIdentifier);
}
}
onDestroy(() => {
drawTool.destroy();
mapClickListener?.();
if (initialDrawService.isEmpty()) {
mapManager.removeMapService(serviceIdentifier);
}
});
function setSize(v: number | null) {
if (v !== null) {
currentSize = v;
}
}
</script>
<div class="gv-p-5 gv-flex gv-flex-col gv-gap-4">
<p class="gv-text-2xl gv-font-extrabold gv-pb-1 gv-border-b gv-border-muted-600">{i18n('title')}</p>
<div class="gv-flex gv-flex-col gv-gap-1">
<span class="gv-font-bold">{i18n('active-layer')}</span>
{#if config.showLayerSwitch}
<ApiSelect
bind:value={drawTool.layer}
options={graphicServices}
getKey={(s) => s.id}
dataTestId="Draw-SelectLayer"
/>
{/if}
{#if config.showLayerNameInput}
<Input bind:value={drawTool.layer.label} />
{/if}
</div>
<div class="gv-flex gv-flex-wrap gv-justify-between">
{#each drawTools as drawType}
<ApiSimpleTooltip text={i18n(`tooltip-${drawType.type}${drawType.mode ? `-${drawType.mode}` : ''}`)}>
<Button
data-state={drawType === currentDrawToolModel ? 'active' : ''}
size="icon"
variant={drawType.type === 'selection' ? 'default' : 'outline'}
class="gv-rounded-xs"
onclick={() => (currentDrawToolModel = drawType)}
data-test-id={drawType.mode === 'freehand'
? `Draw-Tool-${drawType.type}-freehand-btn`
: `Draw-Tool-${drawType.type}-btn`}
>
<Icon icon={drawType.icon} class="gv-size-4" />
</Button>
</ApiSimpleTooltip>
{/each}
</div>
{#if currentDrawToolModel.type === 'selection'}
<div class="gv-flex gv-flex-col gv-gap-4">
<div>{i18n('select-on-map')}</div>
<div class="gv-flex gv-items-center gv-gap-2">
<Checkbox bind:checked={allowMultiSelection} id="allowMultiSelection" />
<Label for="allowMultiSelection">{i18n('allow-multi-selection')}</Label>
</div>
</div>
{/if}
{#if currentDrawToolModel.type !== 'selection' || currentUpdatedFeature}
<ColorChooser bind:currentColor />
<div class="gv-flex gv-items-center gv-gap-4">
<Label class="gv-font-extrabold">{i18n('size')}</Label>
<Slider
bind:value={() => [currentSize], (v) => setSize(v[0])}
max={30}
min={1}
step={1}
class="gv-flex-1"
/>
<Input
class="gv-text-primary gv-text-center gv-w-[6ch] gv-border gv-p-0.5"
bind:value={currentSize}
type="number"
min="0"
max="30"
step="1"
data-test-id="Draw-Size-input"
/>
</div>
{#if currentDrawType === 'polygon'}
<div class="gv-flex gv-items-center gv-gap-4">
<Label class="gv-font-extrabold">{i18n('common.opacity')}</Label>
<OpacitySlider
bind:opacity={currentOpacity}
class="gv-flex-1"
showOpacityNumber
showBoundaries={false}
input-data-test-id="Draw-Opacity-input"
/>
</div>
{/if}
{#if currentDrawType === 'text'}
<div class="gv-flex gv-flex-col gv-gap-1">
<span class="gv-font-bold">{i18n('common.content')}</span>
<Input bind:value={currentText} maxlength={100} />
</div>
{/if}
<Accordion bind:value={accordionOpenedItem}>
<AccordionItem value="advanced-options">
<AccordionTrigger class="gv-font-extrabold" data-test-id="Draw-Advanced-Options-Trigger">
{i18n('common.advanced-options')}
</AccordionTrigger>
<AccordionContent>
<div class="gv-grid gv-grid-cols-1 gv-auto-rows-fr gv-gap-2">
{#if currentDrawType === 'point'}
<div class="gv-w-full gv-flex gv-items-center">
<Label class="gv-w-1/2 gv-font-bold">{i18n('shape')}</Label>
<div class="gv-w-1/2 gv-flex gv-justify-between">
{#each pointStyles as pointStyle}
<Button
data-state={pointStyle === currentPointStyle ? 'active' : ''}
size="icon"
variant="outline"
class="gv-rounded-xs"
onclick={() => (currentPointStyle = pointStyle)}
>
<Icon icon={pointStyle.icon} class="gv-size-4" />
</Button>
{/each}
</div>
</div>
<div class="gv-w-full gv-flex gv-items-center">
<Label class="gv-w-1/2 gv-font-bold" for="showCoordinates">
{i18n('show-coordinates')}
</Label>
<Checkbox bind:checked={visibleElements.point.coordinates} id="showCoordinates" />
</div>
{:else if currentDrawType === 'polyline'}
<div class="gv-w-full gv-flex gv-items-center">
<Label class="gv-w-1/2 gv-font-bold">{i18n('shape')}</Label>
<div class="gv-w-1/2 gv-flex gv-gap-3">
<LineStyleSelector bind:value={currentLineStyle} />
</div>
</div>
<div class="gv-w-full gv-flex gv-items-center">
<Label class="gv-w-1/2 gv-font-bold">{i18n('line-length')}</Label>
<div class="gv-w-1/2 gv-flex gv-items-center gv-gap-1 gv-h-7">
<Checkbox
bind:checked={visibleElements.polyline.showLength}
id="polylineShowLength"
/>
{#if visibleElements.polyline.showLength}
<ApiSelect
bind:value={visibleElements.polyline.length}
options={lengthOptions}
class="gv-h-7"
/>
{/if}
</div>
</div>
{:else if currentDrawType === 'polygon' || currentDrawType === 'circle'}
<div class="gv-w-full gv-flex gv-items-center">
<Label class="gv-w-1/2 gv-font-bold">{i18n('polygon-perimeter')}</Label>
<div class="gv-w-1/2 gv-flex gv-items-center gv-gap-1 gv-h-7">
<Checkbox
bind:checked={visibleElements.polygon.showLength}
id="polygonShowLength"
/>
{#if visibleElements.polygon.showLength}
<ApiSelect
bind:value={visibleElements.polygon.length}
options={lengthOptions}
class="gv-h-7"
/>
{/if}
</div>
</div>
<div class="gv-w-full gv-flex gv-items-center gv-h-7">
<Label class="gv-w-1/2 gv-font-bold">{i18n('polygon-area')}</Label>
<div class="gv-w-1/2 gv-flex gv-items-center gv-gap-1">
<Checkbox bind:checked={visibleElements.polygon.showArea} id="polygonShowArea" />
{#if visibleElements.polygon.showArea}
<ApiSelect
bind:value={visibleElements.polygon.area}
options={areaOptions}
class="gv-h-7"
/>
{/if}
</div>
</div>
{:else if currentDrawType === 'text'}
<div class="gv-w-full gv-flex gv-items-center">
<Label class="gv-w-full gv-font-bold">{i18n('angle')}</Label>
<Input
type="number"
class="gv-w-full gv-h-7"
min={0}
max={360}
step={config.text.angleStep}
bind:value={currentTextAngle}
/>
</div>
{/if}
{#if currentDrawType === 'polygon' || currentDrawType === 'circle' || currentDrawType === 'polyline' || currentDrawType === 'point'}
<div class="gv-w-full gv-flex gv-items-center">
<Label class="gv-w-1/2 gv-font-bold" for="showBuffer">
{i18n('show-buffer')}
</Label>
<Checkbox bind:checked={visibleElements.buffer.enabled} id="showBuffer" />
</div>
{#if visibleElements.buffer.enabled}
<div class="gv-w-full gv-flex gv-items-center gv-ml-4">
<Label class="gv-w-[45%] gv-font-bold" for="bufferDistance">
{i18n('buffer-distance')}
</Label>
<Input
id="bufferDistance"
type="number"
class="gv-w-[30%] gv-h-7"
min={1}
max={10000}
step={10}
bind:value={visibleElements.buffer.distance}
/>
<span class="gv-ml-2">{i18n('common.meters')}</span>
</div>
<div class="gv-w-full gv-flex gv-items-center gv-ml-4">
<Label class="gv-w-[45%] gv-font-bold" for="bufferRepeat">
{i18n('buffer-repeat')}
</Label>
<Input
id="bufferRepeat"
type="number"
class="gv-w-[30%] gv-h-7"
min={1}
max={5}
step={1}
bind:value={visibleElements.buffer.repeat}
/>
</div>
<div class="gv-w-full gv-flex gv-items-center gv-ml-4">
<Label class="gv-w-[45%] gv-font-bold" for="showBufferLabel">
{i18n('show-buffer-label')}
</Label>
<Checkbox bind:checked={visibleElements.buffer.showLabel} id="showBufferLabel" />
</div>
{/if}
{/if}
</div>
</AccordionContent>
</AccordionItem>
{#if config.showProperties}
<AccordionItem value="properties">
<AccordionTrigger class="gv-font-extrabold">{i18n('common.properties')}</AccordionTrigger>
<AccordionContent>
<DrawEditAttributes i18nConfig={fullConfig.i18n} bind:attributes={currentAttributes} />
</AccordionContent>
</AccordionItem>
{/if}
</Accordion>
{#if currentDrawToolModel.type === 'selection'}
<Button variant="destructive" class="gv-mt-2 gv-w-fit" onclick={removeCurrentUpdatedFeature}>
<Trash2Icon class="gv-size-4" />
{i18n('common.delete')}
</Button>
{/if}
{/if}
</div>
<MouseTooltip
visible={drawTool.isDrawing &&
currentDrawToolModel.mode !== 'freehand' &&
(currentDrawType === 'polyline' || currentDrawType === 'polygon')}
>
<span class="gv-text-sm">{i18n('common.draw-double-click-to-continue')}</span>
</MouseTooltip>

packages/common/src/lib/widgets/draw/DrawEditAttributes.svelte

packages/common/src/lib/widgets/draw/DrawEditAttributes.svelte
<script lang="ts">
import { Input } from '$lib/components/shadcn/ui/input';
import { Button } from '$lib/components/shadcn/ui/button';
import { Label } from '$lib/components/shadcn/ui/label';
import { PlusIcon, Trash2 } from 'lucide-svelte';
import type { DrawI18n } from '$lib/widgets/draw/draw.config';
import { getI18n } from '$lib/api/managers/i18n';
interface Props {
i18nConfig: DrawI18n;
attributes: Record<string, unknown>;
}
let { i18nConfig, attributes = $bindable() }: Props = $props();
const i18n = getI18n(i18nConfig);
let attributesArray: { key?: string; value?: unknown }[] = $state(
Object.entries(attributes).map(([key, value]) => ({
key,
value,
})),
);
$effect(() => {
if (attributesArray) {
attributes = attributesArray.reduce(
(prev, curr) => (curr.key ? Object.assign(prev, { [curr.key]: curr.value }) : prev),
{},
);
}
});
function addAttribute() {
attributesArray.push({ key: '', value: '' });
}
function removeAttribute(attributeIndex: number) {
attributesArray.splice(attributeIndex, 1);
}
</script>
<div class="gv-grid gv-gap-2 gv-grid-cols-[1fr_1fr_auto]">
<Label class="gv-font-bold">{i18n('attributes')}</Label>
<Label class="gv-font-bold">{i18n('values')}</Label>
<div></div>
{#each attributesArray as attribute, index}
<Input class="gv-border-secondary gv-h-8" placeholder={i18n('attribute-name')} bind:value={attribute.key} />
<Input
class="gv-border-secondary gv-h-8"
placeholder={i18n('value')}
bind:value={attribute.value}
disabled={!attribute.key}
/>
<button onclick={() => removeAttribute(index)}><Trash2 /></button>
{/each}
<Button size="sm" variant="outline" onclick={addAttribute}>
<PlusIcon class="gv-size-4" />
{i18n('add-attribute')}
</Button>
</div>

packages/common/src/lib/widgets/draw/LineStyleSelector.svelte

packages/common/src/lib/widgets/draw/LineStyleSelector.svelte
<script lang="ts">
import { Button } from '$lib/components/shadcn/ui/button';
import { LineStyle } from '$lib/api/symbol';
interface Props {
value: LineStyle;
}
let { value = $bindable() }: Props = $props();
</script>
<Button
data-state={LineStyle.SOLID === value ? 'active' : ''}
onclick={() => (value = LineStyle.SOLID)}
variant="outline"
class="gv-rounded-xs"
size="icon"
>
<svg viewBox="0 0 20 20" width="20" height="20" stroke="currentColor">
<line x1="3" y1="10" x2="17" y2="10" />
</svg>
</Button>
<Button
data-state={LineStyle.DASH === value ? 'active' : ''}
onclick={() => (value = LineStyle.DASH)}
variant="outline"
class="gv-rounded-xs"
size="icon"
>
<svg viewBox="0 0 20 20" width="20" height="20" stroke="currentColor">
<line x1="3" y1="10" x2="17" y2="10" stroke-dasharray="2,2" />
</svg>
</Button>

Aller plus loin