@rural-environmental-registry/map_component 1.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +1 -0
  2. package/dist/handlers/measureToolsHandler.d.ts +1 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.mjs +16337 -14954
  6. package/dist/types/index.d.ts +118 -15
  7. package/dist/utils/geometryFileParseResult.d.ts +8 -0
  8. package/dist/utils/layersConfigNormalizer.d.ts +16 -0
  9. package/dist/utils/menuHistory.d.ts +3 -3
  10. package/dist/utils/normalizeGeoJsonToFeatures.d.ts +2 -0
  11. package/dist/utils/parseGeoJsonFile.d.ts +2 -0
  12. package/dist/utils/parseKmlFile.d.ts +2 -0
  13. package/dist/utils/parseShapefileZip.d.ts +2 -8
  14. package/dist/utils/{validateShapefileGeometry.d.ts → validateImportedPolygonGeometry.d.ts} +2 -2
  15. package/package.json +3 -2
  16. package/src/components/MapDPG.vue +90 -2
  17. package/src/components/coordinate/CoordinatePanel.vue +31 -8
  18. package/src/components/fa-icon/FontAwesomeIcon.vue +35 -5
  19. package/src/components/map/LeafletMap.vue +3 -1
  20. package/src/components/menu/ChildMenu.vue +307 -27
  21. package/src/components/menu/LayerMenu.vue +232 -37
  22. package/src/components/menu/ParentMenu.vue +261 -27
  23. package/src/components/menu/SectionMenu.vue +512 -0
  24. package/src/constants/coordinatePanel.ts +6 -5
  25. package/src/handlers/mapToolsHandler.ts +3 -1
  26. package/src/handlers/measureToolsHandler.ts +36 -27
  27. package/src/handlers/toolsConstants.ts +1 -0
  28. package/src/index.ts +47 -0
  29. package/src/types/index.ts +149 -14
  30. package/src/utils/geometryFileParseResult.ts +5 -0
  31. package/src/utils/layersConfigNormalizer.ts +273 -0
  32. package/src/utils/menuHistory.ts +35 -19
  33. package/src/utils/normalizeGeoJsonToFeatures.ts +52 -0
  34. package/src/utils/parseGeoJsonFile.ts +23 -0
  35. package/src/utils/parseKmlFile.ts +31 -0
  36. package/src/utils/parseShapefileZip.ts +4 -31
  37. package/src/utils/validateImportedPolygonGeometry.ts +62 -0
  38. package/src/utils/validateShapefileGeometry.ts +0 -54
@@ -1,7 +1,7 @@
1
1
  import { MapOptions, Layer, PathOptions, ControlOptions, ControlPosition } from 'leaflet';
2
2
  import { GeoJsonObject } from 'geojson';
3
3
  import { PM } from '@geoman-io/leaflet-geoman-free';
4
- export type FaIconName = 'check' | 'xmark' | 'chevron-right' | 'chevron-left' | 'terminal' | 'file-lines' | 'upload' | 'pencil' | 'trash' | 'chevron-down';
4
+ export type FaIconName = 'check' | 'xmark' | 'chevron-right' | 'chevron-left' | 'terminal' | 'file-lines' | 'upload' | 'pencil' | 'trash' | 'chevron-down' | 'eye' | 'eye-slash' | 'circle-info' | 'circle-exclamation' | 'triangle-exclamation' | 'circle-check' | 'circle-xmark' | 'ban' | 'lock' | 'unlock' | 'thumbtack' | 'thumb-tack' | 'map-pin';
5
5
  export type DrawnArea = {
6
6
  m2: number;
7
7
  km2: number;
@@ -10,37 +10,135 @@ export type DrawnArea = {
10
10
  export type IncrementedLayer = Layer & {
11
11
  drawnArea: DrawnArea;
12
12
  };
13
- export type LayerData = {
14
- baseUrl: string;
15
- geojson?: GeoJsonObject | GeoJsonObject[];
16
- layers: string;
17
- format: string;
18
- transparent: boolean;
13
+ export type LayerActionStyle = {
14
+ backgroundColor: string;
15
+ color: string;
16
+ borderColor?: string;
17
+ };
18
+ export type LayerActionDefaultItem = {
19
+ key?: string;
19
20
  name: string;
20
- activeDefault: boolean;
21
- cqlFilter?: string;
21
+ icon?: FaIconName;
22
+ style?: LayerActionStyle;
23
+ disabled?: boolean;
24
+ visible?: boolean;
25
+ };
26
+ export type LayerActionDefaults = {
27
+ edit?: LayerActionDefaultItem;
28
+ import?: LayerActionDefaultItem;
29
+ delete?: LayerActionDefaultItem;
30
+ cancel?: LayerActionDefaultItem;
31
+ conclude?: LayerActionDefaultItem;
32
+ };
33
+ export type LayerActionConfig = {
34
+ key: string;
35
+ name: string;
36
+ icon?: FaIconName;
37
+ type?: 'edit' | 'import' | 'delete' | 'visibility' | 'custom' | 'cancel' | 'conclude';
38
+ disabled?: boolean;
39
+ visible?: boolean;
40
+ style?: LayerActionStyle;
41
+ };
42
+ export type LayerMetricsConfig = {
43
+ title: string;
44
+ type: string;
45
+ value: string | number;
46
+ style?: LayerActionStyle;
47
+ };
48
+ /** @deprecated Use LayerMetricsConfig */
49
+ export type LayerMetrics = LayerMetricsConfig;
50
+ export type LayerMetricsMap = Record<string, LayerMetricsConfig[]>;
51
+ export type LayerInfoIconConfig = {
52
+ key?: string;
53
+ icon: FaIconName;
22
54
  active: boolean;
55
+ tooltip?: string;
56
+ style?: Partial<Pick<LayerActionStyle, 'color' | 'backgroundColor'>>;
57
+ };
58
+ export type LayerInfoIconsMap = Record<string, LayerInfoIconConfig[]>;
59
+ export type GroupActionConfig = LayerActionConfig & {
60
+ active: boolean;
61
+ };
62
+ export type GroupActionsMap = Record<string, GroupActionConfig[]>;
63
+ export type SectionConfig = {
64
+ name?: string;
23
65
  key: string;
24
- toggle: {
66
+ groups: GroupLayerData[];
67
+ actionDefaults?: LayerActionDefaults;
68
+ };
69
+ /** @deprecated Use SectionConfig */
70
+ export type SectionData = SectionConfig;
71
+ export type LayerData = {
72
+ key: string;
73
+ name: string;
74
+ active: boolean;
75
+ activeDefault: boolean;
76
+ role?: 'layer' | 'separator';
77
+ tooltip?: string;
78
+ toggle?: {
25
79
  active: string;
26
80
  inactive: string;
27
81
  };
28
- style: {
82
+ visibility?: {
83
+ labelShow?: string;
84
+ labelHide?: string;
85
+ show?: boolean;
86
+ };
87
+ style?: {
29
88
  color: string;
30
89
  fillColor: string;
90
+ icon?: string;
31
91
  };
32
- options?: any;
92
+ baseUrl?: string;
93
+ layers?: string;
94
+ format?: string;
95
+ transparent?: boolean;
96
+ geojson?: GeoJsonObject | GeoJsonObject[];
97
+ cqlFilter?: string;
98
+ options?: Record<string, unknown>;
99
+ mapSource?: 'wms' | 'geojson' | 'none' | 'consumer';
100
+ actions?: LayerActionConfig[];
101
+ metrics?: LayerMetricsConfig[];
102
+ infoIcons?: LayerInfoIconConfig[];
103
+ required?: boolean;
104
+ editActions?: LayerActionConfig[];
105
+ meta?: Record<string, unknown>;
33
106
  };
34
107
  export type GroupLayerData = {
35
108
  name: string;
36
109
  key: string;
37
- toggle: {
110
+ toggle?: {
38
111
  active: string;
39
112
  inactive: string;
40
113
  };
114
+ visibility?: {
115
+ show?: boolean;
116
+ labelShow?: string;
117
+ labelHide?: string;
118
+ };
119
+ collapsed?: boolean;
120
+ actions?: GroupActionConfig[];
41
121
  layers: LayerData[];
122
+ meta?: Record<string, unknown>;
123
+ };
124
+ export type LayersConfig = GroupLayerData[] | SectionConfig[];
125
+ export type LayerActionPayload = {
126
+ actionKey: string;
127
+ actionType?: LayerActionConfig['type'];
128
+ sectionKey?: string;
129
+ groupKey?: string;
130
+ layerKey?: string;
131
+ layer?: LayerData;
132
+ source: 'child-menu' | 'edit-panel' | 'group-menu';
133
+ };
134
+ export type SectionEditStatePayload = {
135
+ sectionKey: string | null;
136
+ layerKey: string | null;
137
+ layer: LayerData | null;
138
+ };
139
+ export type SectionSelectPayload = {
140
+ sectionKey: string;
42
141
  };
43
- export type LayersConfig = GroupLayerData[];
44
142
  export type DrawingEvent = {
45
143
  type: 'created' | 'edited' | 'deleted';
46
144
  layer: IncrementedLayer;
@@ -66,7 +164,6 @@ export type MapConfigConfig = MapOptions & {
66
164
  id: string;
67
165
  removeControlLayers?: boolean;
68
166
  zoomControlPosition?: ControlPosition;
69
- /** Reposiciona marcadores após zoom (evita drift de divIcon). Padrão: true. */
70
167
  stabilizeMarkersOnZoom?: boolean;
71
168
  };
72
169
  export type MapConfig = {
@@ -76,6 +173,10 @@ export type LayersMenuConfig = {
76
173
  size: 'small' | 'medium' | 'large';
77
174
  removeMenu?: boolean;
78
175
  persist: boolean;
176
+ editingLayerKey?: string | null;
177
+ selectedSectionKey?: string | null;
178
+ visibilityMode?: 'switch' | 'eye';
179
+ defaultOpen?: boolean;
79
180
  };
80
181
  export type MemorialConfig = {
81
182
  show: boolean;
@@ -113,6 +214,7 @@ export type MapToolsConfig = {
113
214
  show?: boolean;
114
215
  title?: string;
115
216
  };
217
+ showInteractionPanel?: boolean;
116
218
  texts?: {
117
219
  measureResult?: string;
118
220
  measureLength?: string;
@@ -211,6 +313,7 @@ export type CoordinatePanelTexts = {
211
313
  dragShapefileZip?: string;
212
314
  shapefileZipInfo?: string;
213
315
  shapefileAppliedSuccess?: string;
316
+ geometryImportUnsupportedFormat?: string;
214
317
  placeholderLongitude?: string;
215
318
  placeholderLatitude?: string;
216
319
  placeholderAzimuth?: string;
@@ -0,0 +1,8 @@
1
+ import type { Feature } from 'geojson';
2
+ export type GeometryFileParseResult = {
3
+ ok: true;
4
+ features: Feature[];
5
+ } | {
6
+ ok: false;
7
+ error: string;
8
+ };
@@ -0,0 +1,16 @@
1
+ import { GroupActionConfig, GroupActionsMap, GroupLayerData, LayerActionConfig, LayerActionDefaults, LayerData, LayerInfoIconConfig, LayerInfoIconsMap, LayerMetricsConfig, LayerMetricsMap, LayersConfig, SectionConfig } from '../types';
2
+ export declare const isSectionConfig: (item: unknown) => item is SectionConfig;
3
+ export declare const isGroupConfig: (item: unknown) => item is GroupLayerData;
4
+ export declare const normalizeLayersConfig: (config?: LayersConfig | null) => SectionConfig[];
5
+ export declare const resolveLayerActions: (layer: LayerData, defaults?: LayerActionDefaults) => LayerActionConfig[];
6
+ export declare const resolveEditControlAction: (defaults: LayerActionDefaults | undefined, kind: "cancel" | "conclude") => LayerActionConfig;
7
+ export declare const isMenuSeparator: (layer: LayerData | null | undefined) => boolean;
8
+ export declare const shouldApplyMapOverlay: (layer: LayerData) => boolean;
9
+ export declare const collectLayerMetricsMap: (config?: LayersConfig | null) => LayerMetricsMap;
10
+ export declare const resolveLayerMetrics: (layer: LayerData | null | undefined) => LayerMetricsConfig[];
11
+ export declare const collectLayerInfoIconsMap: (config?: LayersConfig | null) => LayerInfoIconsMap;
12
+ export declare const resolveActiveLayerInfoIcons: (layer: LayerData | null | undefined) => LayerInfoIconConfig[];
13
+ export declare const collectGroupActionsMap: (config?: LayersConfig | null) => GroupActionsMap;
14
+ export declare const resolveActiveGroupActions: (group: GroupLayerData | null | undefined) => GroupActionConfig[];
15
+ export declare const withLayerInfoIconActive: (infoIcons: LayerInfoIconConfig[] | undefined, iconRef: string, active: boolean) => LayerInfoIconConfig[] | null;
16
+ export declare const withGroupActionActive: (actions: GroupActionConfig[] | undefined, actionKey: string, active: boolean) => GroupActionConfig[] | null;
@@ -1,8 +1,8 @@
1
1
  import { LayerData } from '../types';
2
2
  export declare const setHistory: (layer: LayerData) => void;
3
- /** Retorna o estado salvo da camada ou `undefined` se não houver histórico. */
4
3
  export declare const getHistory: (layerKey: string) => boolean | undefined;
5
- /** Resolve `active`/`activeDefault` sem mutar o objeto original (mesma regra do fluxo legado). */
4
+ export declare const setGroupCollapsedHistory: (groupKey: string, collapsed: boolean) => void;
5
+ export declare const getGroupCollapsedHistory: (groupKey: string) => boolean | undefined;
6
6
  export declare const resolveLayerActiveState: (layer: LayerData, persist: boolean) => LayerData;
7
- /** Indica se a camada deve ser aplicada ao mapa na inicialização. */
7
+ export declare const resolveGroupCollapsedState: (groupKey: string, collapsedFromConfig: boolean | undefined, persist: boolean) => boolean;
8
8
  export declare const shouldInitLayerOnMap: (layer: LayerData, persist: boolean) => boolean;
@@ -0,0 +1,2 @@
1
+ import type { Feature } from 'geojson';
2
+ export declare function normalizeGeoJsonToFeatures(geojson: unknown): Feature[];
@@ -0,0 +1,2 @@
1
+ import type { GeometryFileParseResult } from './geometryFileParseResult';
2
+ export declare function parseGeoJsonFile(file: File): Promise<GeometryFileParseResult>;
@@ -0,0 +1,2 @@
1
+ import type { GeometryFileParseResult } from './geometryFileParseResult';
2
+ export declare function parseKmlFile(file: File): Promise<GeometryFileParseResult>;
@@ -1,9 +1,3 @@
1
- import type { Feature } from 'geojson';
2
- export type ShapefileParseResult = {
3
- ok: true;
4
- features: Feature[];
5
- } | {
6
- ok: false;
7
- error: string;
8
- };
1
+ import type { GeometryFileParseResult } from './geometryFileParseResult';
2
+ export type ShapefileParseResult = GeometryFileParseResult;
9
3
  export declare function parseShapefileZip(file: File): Promise<ShapefileParseResult>;
@@ -1,9 +1,9 @@
1
1
  import type { Feature, MultiPolygon, Polygon } from 'geojson';
2
- export type ShapefileValidationResult = {
2
+ export type ImportedPolygonValidationResult = {
3
3
  ok: true;
4
4
  feature: Feature<Polygon | MultiPolygon>;
5
5
  } | {
6
6
  ok: false;
7
7
  error: string;
8
8
  };
9
- export declare function validateShapefileGeometry(features: Feature[]): ShapefileValidationResult;
9
+ export declare function validateImportedPolygonGeometry(features: Feature[]): ImportedPolygonValidationResult;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rural-environmental-registry/map_component",
3
- "version": "1.1.0",
3
+ "version": "2.0.0",
4
4
  "description": "Map component for Vue.js applications",
5
5
  "main": "dist/index.mjs",
6
6
  "module": "dist/index.mjs",
@@ -47,7 +47,8 @@
47
47
  "leaflet": "1.9.4",
48
48
  "leaflet-draw": "1.0.4",
49
49
  "papaparse": "5.5.3",
50
- "shpjs": "6.1.0"
50
+ "shpjs": "6.1.0",
51
+ "@tmcw/togeojson": "7.1.2"
51
52
  },
52
53
  "devDependencies": {
53
54
  "@types/geojson": "7946.0.16",
@@ -25,6 +25,9 @@
25
25
  :options="options.layersMenu"
26
26
  @onChildLayerToggle="emit('onChildLayerToggle', $event)"
27
27
  @onGroupLayerToggle="emit('onGroupLayerToggle', $event)"
28
+ @onLayerAction="emit('onLayerAction', $event)"
29
+ @onSectionEditStateChange="emit('onSectionEditStateChange', $event)"
30
+ @onSelectedSectionChange="emit('onSelectedSectionChange', $event)"
28
31
  @startLoading="isLoading = true"
29
32
  @stopLoading="isLoading = false"
30
33
  />
@@ -46,13 +49,22 @@
46
49
  import { computed, ref } from 'vue'
47
50
  import {
48
51
  DrawingEvent,
52
+ GroupActionConfig,
53
+ GroupActionsMap,
49
54
  GroupLayerData,
50
55
  IncrementedLayer,
56
+ LayerActionPayload,
51
57
  LayerData,
58
+ LayerInfoIconConfig,
59
+ LayerInfoIconsMap,
60
+ LayerMetricsConfig,
61
+ LayerMetricsMap,
52
62
  MapLayers,
53
63
  MapOptionsConfig,
54
64
  DescriptiveMemorial,
55
- MeasureCompleteEvent
65
+ MeasureCompleteEvent,
66
+ SectionEditStatePayload,
67
+ SectionSelectPayload
56
68
  } from '../types'
57
69
  import Loading from './loading/Loading.vue'
58
70
  import Map from './map/LeafletMap.vue'
@@ -61,6 +73,14 @@
61
73
  import { isMemorialLayer, MEMORIAL_KEY } from '../utils/memorialLayer'
62
74
  import { calculateLayerArea } from '../utils/geometryCalculator'
63
75
  import { resolveDrawingPathOptions } from '../utils/drawingPathOptions'
76
+ import {
77
+ collectGroupActionsMap,
78
+ collectLayerInfoIconsMap,
79
+ collectLayerMetricsMap,
80
+ normalizeLayersConfig,
81
+ withGroupActionActive,
82
+ withLayerInfoIconActive
83
+ } from '../utils/layersConfigNormalizer'
64
84
  import type { MemorialDrawShape } from '../utils/drawingPathOptions'
65
85
  import type { Feature, MultiPolygon, Polygon } from 'geojson'
66
86
 
@@ -88,6 +108,12 @@
88
108
  const emit = defineEmits<{
89
109
  (e: 'onGroupLayerToggle', data: GroupLayerData): void
90
110
  (e: 'onChildLayerToggle', data: LayerData): void
111
+ (e: 'onLayerAction', payload: LayerActionPayload): void
112
+ (e: 'onSectionEditStateChange', payload: SectionEditStatePayload): void
113
+ (e: 'onSelectedSectionChange', payload: SectionSelectPayload): void
114
+ (e: 'onLayerMetricsUpdate', payload: { layerKey: string; metrics: LayerMetricsConfig[] }): void
115
+ (e: 'onLayerInfoIconsUpdate', payload: { layerKey: string; infoIcons: LayerInfoIconConfig[] }): void
116
+ (e: 'onGroupActionsUpdate', payload: { groupKey: string; actions: GroupActionConfig[] }): void
91
117
  (e: 'onDrawing', data: DrawingEvent): void
92
118
  (e: 'onCoordinateSystemChange', system: string): void
93
119
  (e: 'onFullscreenChange', active: boolean): void
@@ -112,6 +138,60 @@
112
138
  const coordinatePanelRef = ref()
113
139
  const isLoading = ref<boolean>(false)
114
140
 
141
+ const metrics = computed((): LayerMetricsMap =>
142
+ collectLayerMetricsMap(props.layers?.customLayers)
143
+ )
144
+
145
+ const infoIcons = computed((): LayerInfoIconsMap =>
146
+ collectLayerInfoIconsMap(props.layers?.customLayers)
147
+ )
148
+
149
+ const groupActions = computed((): GroupActionsMap =>
150
+ collectGroupActionsMap(props.layers?.customLayers)
151
+ )
152
+
153
+ const setLayerMetrics = (layerKey: string, nextMetrics: LayerMetricsConfig[]): void => {
154
+ emit('onLayerMetricsUpdate', { layerKey, metrics: nextMetrics })
155
+ }
156
+
157
+ const findLayerInfoIcons = (layerKey: string): LayerInfoIconConfig[] | undefined => {
158
+ for (const section of normalizeLayersConfig(props.layers?.customLayers)) {
159
+ for (const group of section.groups) {
160
+ const layer = group.layers?.find((item) => item.key === layerKey)
161
+ if (layer) return layer.infoIcons
162
+ }
163
+ }
164
+ return undefined
165
+ }
166
+
167
+ const findGroupActions = (groupKey: string): GroupActionConfig[] | undefined => {
168
+ for (const section of normalizeLayersConfig(props.layers?.customLayers)) {
169
+ const group = section.groups.find((item) => item.key === groupKey)
170
+ if (group) return group.actions
171
+ }
172
+ return undefined
173
+ }
174
+
175
+ const setLayerInfoIconActive = (layerKey: string, iconRef: string, active: boolean): void => {
176
+ const next = withLayerInfoIconActive(findLayerInfoIcons(layerKey), iconRef, active)
177
+ if (!next) return
178
+ emit('onLayerInfoIconsUpdate', { layerKey, infoIcons: next })
179
+ }
180
+
181
+ const setLayerInfoIcons = (layerKey: string, nextInfoIcons: LayerInfoIconConfig[]): void => {
182
+ emit('onLayerInfoIconsUpdate', { layerKey, infoIcons: nextInfoIcons })
183
+ }
184
+
185
+ const setGroupActionActive = (groupKey: string, actionKey: string, active: boolean): void => {
186
+ const next = withGroupActionActive(findGroupActions(groupKey), actionKey, active)
187
+ if (!next) return
188
+ emit('onGroupActionsUpdate', { groupKey, actions: next })
189
+ }
190
+
191
+ const setGroupActions = (groupKey: string, nextActions: GroupActionConfig[]): void => {
192
+ emit('onGroupActionsUpdate', { groupKey, actions: nextActions })
193
+ }
194
+
115
195
  const getMemorialPathOptions = (shape: MemorialDrawShape): L.PathOptions =>
116
196
  mapRef.value?.getDrawingPathOptions(shape) ??
117
197
  resolveDrawingPathOptions(mapRef.value?.map, props.options.drawing, shape)
@@ -276,7 +356,15 @@
276
356
  enterFullscreen,
277
357
  exitFullscreen,
278
358
  toggleFullscreen,
279
- toggleMeasureArea
359
+ toggleMeasureArea,
360
+ metrics,
361
+ setLayerMetrics,
362
+ infoIcons,
363
+ setLayerInfoIcons,
364
+ setLayerInfoIconActive,
365
+ groupActions,
366
+ setGroupActions,
367
+ setGroupActionActive
280
368
  })
281
369
  </script>
282
370
 
@@ -320,9 +320,9 @@
320
320
  <input
321
321
  ref="shapefileInputRef"
322
322
  type="file"
323
- accept=".zip"
323
+ accept=".zip,.kml,.geojson,.json"
324
324
  class="shapefile-input-hidden"
325
- @change="handleShapefileInputChange"
325
+ @change="handleGeometryFileInputChange"
326
326
  />
327
327
  </div>
328
328
  </template>
@@ -353,7 +353,10 @@
353
353
  import MemorialButtonControl from '../../handlers/memorialButtonControl'
354
354
  import type { DescriptiveMemorial } from '../../types'
355
355
  import { parseShapefileZip } from '../../utils/parseShapefileZip'
356
- import { validateShapefileGeometry } from '../../utils/validateShapefileGeometry'
356
+ import { parseGeoJsonFile } from '../../utils/parseGeoJsonFile'
357
+ import { parseKmlFile } from '../../utils/parseKmlFile'
358
+ import { validateImportedPolygonGeometry } from '../../utils/validateImportedPolygonGeometry'
359
+ import type { GeometryFileParseResult } from '../../utils/geometryFileParseResult'
357
360
  import type { Feature, MultiPolygon, Polygon } from 'geojson'
358
361
 
359
362
  interface CSVRow {
@@ -669,18 +672,38 @@
669
672
  shapefileInputRef.value?.click()
670
673
  }
671
674
 
672
- const processShapefile = async (file: File) => {
675
+ const getGeometryFileExtension = (fileName: string): 'zip' | 'kml' | 'geojson' | null => {
676
+ const lower = fileName.toLowerCase()
677
+
678
+ if (lower.endsWith('.zip')) return 'zip'
679
+ if (lower.endsWith('.kml')) return 'kml'
680
+ if (lower.endsWith('.geojson') || lower.endsWith('.json')) return 'geojson'
681
+
682
+ return null
683
+ }
684
+
685
+ const parseGeometryFile = async (file: File): Promise<GeometryFileParseResult> => {
686
+ const extension = getGeometryFileExtension(file.name)
687
+
688
+ if (extension === 'zip') return parseShapefileZip(file)
689
+ if (extension === 'kml') return parseKmlFile(file)
690
+ if (extension === 'geojson') return parseGeoJsonFile(file)
691
+
692
+ return { ok: false, error: texts.value.geometryImportUnsupportedFormat || 'Unsupported file format' }
693
+ }
694
+
695
+ const processGeometryFile = async (file: File) => {
673
696
  emit('geometryRemoved')
674
697
  csvData.value = []
675
698
  shapefileLoaded.value = false
676
699
 
677
- const parseResult = await parseShapefileZip(file)
700
+ const parseResult = await parseGeometryFile(file)
678
701
  if (!parseResult.ok) {
679
702
  ElMessage.error(parseResult.error)
680
703
  return
681
704
  }
682
705
 
683
- const validationResult = validateShapefileGeometry(parseResult.features)
706
+ const validationResult = validateImportedPolygonGeometry(parseResult.features)
684
707
  if (!validationResult.ok) {
685
708
  ElMessage.error(validationResult.error)
686
709
  return
@@ -691,12 +714,12 @@
691
714
  ElMessage.success(texts.value.shapefileAppliedSuccess)
692
715
  }
693
716
 
694
- const handleShapefileInputChange = async (event: Event) => {
717
+ const handleGeometryFileInputChange = async (event: Event) => {
695
718
  const input = event.target as HTMLInputElement
696
719
  const file = input.files?.[0]
697
720
  if (!file) return
698
721
 
699
- await processShapefile(file)
722
+ await processGeometryFile(file)
700
723
  input.value = ''
701
724
  }
702
725
 
@@ -1,8 +1,9 @@
1
1
  <template>
2
- <FontAwesomeIcon :icon="['fas', iconName]" />
2
+ <FontAwesomeIcon :icon="['fas', resolvedIconName]" />
3
3
  </template>
4
4
 
5
5
  <script setup lang="ts">
6
+ import { computed } from 'vue'
6
7
  import { library } from '@fortawesome/fontawesome-svg-core'
7
8
  import {
8
9
  faCheck,
@@ -14,16 +15,33 @@
14
15
  faUpload,
15
16
  faPencil,
16
17
  faTrash,
17
- faChevronDown
18
+ faChevronDown,
19
+ faEye,
20
+ faEyeSlash,
21
+ faCircleInfo,
22
+ faCircleExclamation,
23
+ faTriangleExclamation,
24
+ faCircleCheck,
25
+ faCircleXmark,
26
+ faBan,
27
+ faLock,
28
+ faUnlock,
29
+ faThumbtack,
30
+ faMapPin
18
31
  } from '@fortawesome/free-solid-svg-icons'
19
32
  import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
20
33
  import { FaIconName } from '../../types'
21
34
 
22
35
  type IconProps = {
23
- iconName: FaIconName
36
+ iconName: FaIconName | string
24
37
  }
25
38
 
26
- defineProps<IconProps>()
39
+ const props = defineProps<IconProps>()
40
+
41
+ const resolvedIconName = computed(() => {
42
+ const raw = props.iconName?.trim() ?? ''
43
+ return raw.replace(/^(fas|far|fal|fab)\s+/i, '').replace(/^fa-/i, '')
44
+ })
27
45
 
28
46
  library.add(
29
47
  faCheck,
@@ -35,7 +53,19 @@
35
53
  faUpload,
36
54
  faPencil,
37
55
  faTrash,
38
- faChevronDown
56
+ faChevronDown,
57
+ faEye,
58
+ faEyeSlash,
59
+ faCircleInfo,
60
+ faCircleExclamation,
61
+ faTriangleExclamation,
62
+ faCircleCheck,
63
+ faCircleXmark,
64
+ faBan,
65
+ faLock,
66
+ faUnlock,
67
+ faThumbtack,
68
+ faMapPin
39
69
  )
40
70
  </script>
41
71
 
@@ -81,10 +81,12 @@
81
81
 
82
82
  const initMap = (): void => {
83
83
  const { config } = props.mapOptions
84
+ const toolsConfig = resolveMapToolsConfig(props.toolsOptions)
85
+ const useToolsZoom = !!toolsConfig && toolsConfig.zoom?.show !== false
84
86
  const mapConfig: MapConfigConfig = {
85
87
  ...DEFAULT_MAP_OPTIONS,
86
88
  ...config,
87
- ...(resolveMapToolsConfig(props.toolsOptions) ? { zoomControl: false } : {})
89
+ ...(useToolsZoom ? { zoomControl: false } : {})
88
90
  }
89
91
 
90
92
  mapHandlerInstance = new MapHandler(mapConfig)