Skip to content

Conversation

@latin-panda
Copy link
Collaborator

@latin-panda latin-panda commented Dec 30, 2025

Closes #560

I have verified this PR works in these browsers (latest versions):

  • Chrome
  • Firefox
  • Safari (macOS)
  • Safari (iOS)
  • Chrome for Android
  • Not applicable

What else has been done to verify that this works as intended?

Test case: Errors messages in desktop
Test case: Errors messages in mobile
Test case: Full-screen experience
Test case: readonly map
readonly-map.mp4
Test case: geoshape
test-shape.mp4
Test case: geotrace
test-trace.mp4
Regression test: geopoint with "maps" and "placement-map" appearances
geopoint-regression.mp4
Regression test: select with "map" appearance
select-with-map-regression.mp4
Central smoke test
central-smoke-test.mp4
Bundle size verification

No big difference means OL didn't leak, because OL would inflate the main chunk by 400 or 600.

This branch:

In main branch:

Why is this the best possible solution? Were any other approaches considered?

This is an extension of the existent solution:

  • Follows the same pattern of small components for modularity and ease of maintenance
  • Follows the same pattern of adding new features as configurable capabilities in each mode type.
  • Reuses existing utilities without having side effects on existing features due to small pure functions with defined responsibilities.

How does this change affect users? Describe intentional changes to behavior and behavior that could have accidentally been affected by code changes. In other words, what are the regression risks?

  • Mostly extension of code, regression test is done in case an unintended reactive value state is in the selected feature/vertex

Do we need any specific form for testing your changes? If so, please attach one.

  • It can be tested using existing test forms.

What's changed

  • Adds a dialog component to allow users to paste a value or upload a GeoJSON file
  • Adds an expandable component to display advanced features under the map
  • Updates the status bar component to fit a new button that displays the advanced features panel
  • Adds a new utility function in mapHelpers.ts to perform basic validation and return coordinates based on the expected feature type. This is used to return the correct coordinate format after the user pastes a value or uploads a file.
  • Exposes existing utility functions that parse GeoJSON format for reusability and renames file from createFeatureCollectionAndProps.ts to ‎geojson-parsers.ts
  • Adds utility functions to parse the first feature in a GeoJSON file to geojson-parsers.ts
  • Adds two new capabilities to the mode config of the map: 'canUpdateFeatureCoordinates' and 'canUpdateVertexCoordinates'
  • Adds test coverage and updates visual e2e tests

@changeset-bot
Copy link

changeset-bot bot commented Dec 30, 2025

🦋 Changeset detected

Latest commit: 2bd6a29

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@getodk/web-forms Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

mapFeatures?.saveFeature(feature);
// Refresh selected vertex.
const vertexIndex = feature.get(SELECTED_VERTEX_INDEX_PROPERTY) as number | undefined;
events.onVertexSelect(getVertexByIndex(feature, vertexIndex));
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This refresh updates the status bar and the advanced panel after a vertex is deleted or updated.

@latin-panda
Copy link
Collaborator Author

Testing finished and recorded evidence

@latin-panda latin-panda marked this pull request as ready for review January 19, 2026 08:58
const geojson = JSON.parse(text) as FeatureCollection<LineString | Point | Polygon>;
return geojson?.features?.[0]?.geometry as Geometry | undefined;
} catch {
// eslint-disable-next-line no-console -- Skip silently to match createFeatureCollectionAndProps
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The audience is developers, and it is not intended to be tracked in alert systems like Sentry (bc user error, no system error)

SINGLE_FEATURE_TYPES.TRACE,
] as const;

export interface ModeCapabilities {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are many question types that use maps. This file provides modes for those question types, and each mode has capabilities.
For edit coordinates, this PR adds 2 new capabilities, can update feature coordinates, and can update vertex coordinates

if (Number.isFinite(latitude) && Number.isFinite(longitude)) {
coords.push(latitude, longitude);

if (Number.isFinite(accuracy)) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried extracting this since it's similar (not the same) as the one in geojson-parsers.ts. But decided to leave it like this because

  • It's too small and offers little benefit, and don't want to overmodularize
  • This one is used within the OpenLayers scope, while the other is for GeoJSON cases, which cannot be mixed (to prevent accidental inclusion of OL in the main bundle).

}
</style>

<style lang="scss">
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a separate task to make the css and html of dialogs more reusable.

if (location.altitude != null) {
coords.push(location.altitude);
}
const coords = toGeoJsonCoordinateArray(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Below is a refactor since we have new utility functions, not related to the main edit coordinates work.


const extent = source.getExtent();
if (!extent?.length) {
if (isExtendEmpty(extent)) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I discovered this function that is a safer check :) tiny enhancement

}

geometry?.setCoordinates(coords);
geometry?.setCoordinates(coords, COORDINATE_LAYOUT_XYZM);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issue if the layout of 4 numbers (latitude, longitude, altitude, accuracy) is specified or not, but it's helpful for someone reading the code to know what's expected, so adding it here.

export type Mode = (typeof MODES)[keyof typeof MODES];

// Used when loading a single feature from the map.
export const SINGLE_FEATURE_TYPES = {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's called "single" because we also support maps with a mix of feature types at the same time (select with the map appearance), but for geopoint, geoshape, and geotrace, we only support one type at a time.

@latin-panda
Copy link
Collaborator Author

@sadiqkhoja This is ready for review. I have included some test videos in the PR description and notes. Let me know if you have any questions or if you'd like a walkthrough session. :)

Copy link
Contributor

@sadiqkhoja sadiqkhoja left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few observations while trying the demo:

  • Long press requires quite a long press, is there a way to reduce the time
  • Undo button allows only 1 step back, doesn't it create editing stack?
  • The +/- button in Longitude and Latitude textbox are not super useful, would be best to remove them.
  • If two vertices are exactly same then how can I select one or other? Maybe next/prev button would be helpful

@alyblenkin
Copy link
Collaborator

Great feedback @sadiqkhoja! Jumping in with a few comments:

Long press requires quite a long press, is there a way to reduce the time
Undo button allows only 1 step back, doesn't it create editing stack?

We are fixing these two! Users brought this up as well. So we will be removing the long press entirely and allowing the user to undo all actions.

The +/- button in Longitude and Latitude textbox are not super useful, would be best to remove them.

I don't think I know what you mean here. Do you mean the up/down arrow keys?

If two vertices are exactly same then how can I select one or other? Maybe next/prev button would be helpful

Yes, this is an interesting point that I think we will address with this story.

@alyblenkin
Copy link
Collaborator

Looking great @latin-panda! A few small design notes:

  • Can we tighten the spacing between the text field and error so they look more connected?
  • When importing a file, can we also drag and drop? Is that harder to implement right now? Not a deal breaker!
  • I realized we should probably link to the docs to explain the ODK format. Maybe this link

Fixes to address later:

  • Expand text field for pasting location data so you can view the entire string

Copy link
Contributor

@sadiqkhoja sadiqkhoja left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still reviewing...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meta-comment: maybe we should configure LFS for these images.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice suggestion! I've created an issue and will bring it to the team

for (const coord of geometry.split(';')) {
const [lat, lon] = coord.trim().split(/\s+/).map(Number);
export const isNullLocation = (lat: number | null | undefined, lon: number | null | undefined) => {
return lat === 0 && lon === 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0,0 is a valid location, no?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question! Hopefully null Island isn't common, but it was decided to leave it out to match JR (ref conversation)

@latin-panda
Copy link
Collaborator Author

@sadiqkhoja Thanks for the nice feedback!

Long press requires quite a long press, is there a way to reduce the time

This is under development here: #621

Undo button allows only 1 step back, doesn't it create editing stack?

This is under review here: #619

The +/- button in Longitude and Latitude textbox are not super useful, would be best to remove them.

I've removed the controls from the input type number. Tested in Chrome, Firefox and Safari.

@latin-panda
Copy link
Collaborator Author

latin-panda commented Jan 21, 2026

@alyblenkin Thanks for the feedback!

Can we tighten the spacing between the text field and error so they look more connected?

Sure, fixed, same as Figma

Expand to see fix

When importing a file, can we also drag and drop? Is that harder to implement right now? Not a deal breaker!

No right now, we don't even have it in the upload image question type. It would be good to implement it together. I've created a ticket.

I realized we should probably link to the docs to explain the ODK format. Maybe this link.

It's better to avoid adding links, since they can break easily or change paths, and fixing them will require a release in Web Forms and Central. For now, I've added an icon with a native tooltip that shows this information on hover as a quick solution, but this is only available to desktop users (because of hover). We can explore adding one based on tapping so it's also visible to mobile users, as a separate issue. What do you think?

Expand to see fix

Expand text field for pasting location data so you can view the entire string

This is fixed

Expand to see fix

@alyblenkin
Copy link
Collaborator

We discussed all these changes earlier, but I forgot to ask if it's difficult to display the name of the file once uploaded, @latin-panda. We could always create a ticket for it if so. I'm assuming it would be helpful to have confirmation you've uploaded the right file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Editing coordinates for a vertex

4 participants