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.tspackages/common/src/lib/widgets/draw/draw.config.tspackages/common/src/lib/widgets/draw/draw.i18n.tspackages/common/src/lib/widgets/draw/draw.model.tspackages/common/src/lib/widgets/draw/Draw.sveltepackages/common/src/lib/widgets/draw/DrawEditAttributes.sveltepackages/common/src/lib/widgets/draw/LineStyleSelector.svelte
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
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
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
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
<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
<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
<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>