From 3ba90518e3a38a3cada0500219f346033736e635 Mon Sep 17 00:00:00 2001 From: latin-panda <66472237+latin-panda@users.noreply.github.com> Date: Thu, 16 Oct 2025 23:40:11 +0700 Subject: [PATCH 01/25] Initial input setup and loading saved feature --- .changeset/true-snakes-hear.md | 5 + .../src/fixtures/geopoint/geopoint-map.xml | 61 ++++++++++ .../src/components/common/map/AsyncMap.vue | 3 +- .../map/createFeatureCollectionAndProps.ts | 37 ++++-- .../form-elements/input/InputControl.vue | 4 +- .../input/InputGeopointWithMap.vue | 19 +++ .../createFeatureCollectionAndProps.test.ts | 115 ++++++++++++++---- 7 files changed, 207 insertions(+), 37 deletions(-) create mode 100644 .changeset/true-snakes-hear.md create mode 100644 packages/common/src/fixtures/geopoint/geopoint-map.xml create mode 100644 packages/web-forms/src/components/form-elements/input/InputGeopointWithMap.vue diff --git a/.changeset/true-snakes-hear.md b/.changeset/true-snakes-hear.md new file mode 100644 index 000000000..3ebb057c2 --- /dev/null +++ b/.changeset/true-snakes-hear.md @@ -0,0 +1,5 @@ +--- +'@getodk/web-forms': minor +--- + +Adds support for Geopoint with a "maps" appearance. diff --git a/packages/common/src/fixtures/geopoint/geopoint-map.xml b/packages/common/src/fixtures/geopoint/geopoint-map.xml new file mode 100644 index 000000000..e4379e0aa --- /dev/null +++ b/packages/common/src/fixtures/geopoint/geopoint-map.xml @@ -0,0 +1,61 @@ + + + + Geopoint + + + + + Where are you? + + + Your age + + + Where are you again? + + + + + Où es-tu? + + + Ton âge + + + Où es-tu encore? + + + + + + 40.7128 -74.0060 100 5 + + + + + + + + + + + + + + + + + diff --git a/packages/web-forms/src/components/common/map/AsyncMap.vue b/packages/web-forms/src/components/common/map/AsyncMap.vue index cac6e5e87..4f0a205c4 100644 --- a/packages/web-forms/src/components/common/map/AsyncMap.vue +++ b/packages/web-forms/src/components/common/map/AsyncMap.vue @@ -20,8 +20,7 @@ type MapBlockComponent = DefineComponent<{ }>; interface AsyncMapProps { - // ToDo: Expand typing when implementing Geo Point/Shape/Trace question types. - features: readonly SelectItem[]; + features: readonly SelectItem[] | readonly string[]; disabled: boolean; savedFeatureValue: string | undefined; } diff --git a/packages/web-forms/src/components/common/map/createFeatureCollectionAndProps.ts b/packages/web-forms/src/components/common/map/createFeatureCollectionAndProps.ts index 048206e14..a5a2f0053 100644 --- a/packages/web-forms/src/components/common/map/createFeatureCollectionAndProps.ts +++ b/packages/web-forms/src/components/common/map/createFeatureCollectionAndProps.ts @@ -61,18 +61,37 @@ const getGeoJSONGeometry = (coords: Coordinates[]): Geometry => { return { type: 'LineString', coordinates: coords }; }; -export const createFeatureCollectionAndProps = (odkFeatures: readonly SelectItem[] | undefined) => { +const normalizeODKFeature = (odkFeature: SelectItem | string) => { + if (typeof odkFeature === 'string') { + return { + label: odkFeature, + value: odkFeature, + properties: [['geometry', odkFeature]], + }; + } + + return { + ...odkFeature, + label: odkFeature.label?.asString, + }; +}; + +export const createFeatureCollectionAndProps = ( + odkFeatures: readonly SelectItem[] | readonly string[] | undefined +) => { const orderedExtraPropsMap = new Map>(); const features: Feature[] = []; - odkFeatures?.forEach((option) => { + odkFeatures?.forEach((odkFeature) => { + const normalizedFeature = normalizeODKFeature(odkFeature); + const orderedProps: Array<[string, string]> = []; const reservedProps: Record = { - [PROPERTY_PREFIX + 'label']: option.label?.asString, - [PROPERTY_PREFIX + 'value']: option.value, + [PROPERTY_PREFIX + 'label']: normalizedFeature.label, + [PROPERTY_PREFIX + 'value']: normalizedFeature.value, }; - option.properties.forEach(([key, value]) => { + normalizedFeature.properties.forEach(([key, value]) => { if (RESERVED_MAP_PROPERTIES.includes(key)) { reservedProps[PROPERTY_PREFIX + key] = value.trim(); } else { @@ -80,19 +99,21 @@ export const createFeatureCollectionAndProps = (odkFeatures: readonly SelectItem } }); - orderedExtraPropsMap.set(option.value, orderedProps); + if (orderedProps.length) { + orderedExtraPropsMap.set(normalizedFeature.value, orderedProps); + } const geometry = reservedProps[PROPERTY_PREFIX + 'geometry']; if (!geometry?.length) { // eslint-disable-next-line no-console -- Skip silently to match Collect behaviour. - console.warn(`Missing or empty geometry for option: ${option.value}`); + console.warn(`Missing or empty geometry for option: ${normalizedFeature.value}`); return; } const geoJSONCoords = getGeoJSONCoordinates(geometry); if (!geoJSONCoords?.length) { // eslint-disable-next-line no-console -- Skip silently to match Collect behaviour. - console.warn(`Missing geo points for option: ${option.value}`); + console.warn(`Missing geo points for option: ${normalizedFeature.value}`); return; } diff --git a/packages/web-forms/src/components/form-elements/input/InputControl.vue b/packages/web-forms/src/components/form-elements/input/InputControl.vue index e5889a69b..d6402cbe4 100644 --- a/packages/web-forms/src/components/form-elements/input/InputControl.vue +++ b/packages/web-forms/src/components/form-elements/input/InputControl.vue @@ -2,6 +2,7 @@ import ValidationMessage from '@/components/common/ValidationMessage.vue'; import ControlText from '@/components/form-elements/ControlText.vue'; import InputGeopoint from '@/components/form-elements/input/geopoint/InputGeopoint.vue'; +import InputGeopointWithMap from '@/components/form-elements/input/InputGeopointWithMap.vue'; import InputDate from '@/components/form-elements/input/InputDate.vue'; import InputDecimal from '@/components/form-elements/input/InputDecimal.vue'; import InputInt from '@/components/form-elements/input/InputInt.vue'; @@ -30,7 +31,8 @@ defineProps();