Skip to content

Image Layer Adjustments — Implementation Plan

Decisions

Decision Choice Why
Editable fields Center latitude/longitude, scale, rotation, opacity, and minimum zoom These are the existing ImageOverlayLayer display and placement settings currently consumed by ConfiguredImageLayer.
Read-only fields Label and asset path This task adjusts an existing overlay; renaming, replacing, and layer lifecycle belong to the broader Map Layers backlog item.
Layer scope Existing image overlays only Adding, removing, reordering, and asset management are explicitly out of scope.
Editor selection Select an overlay for editing without changing visibility The left edit sidebar chooses the right-panel subject; the existing map layer control remains the visibility authority.
Save behavior Explicit Save through the existing project-map action It matches the established right-panel form pattern and avoids persisting every intermediate numeric edit.
Preview behavior Apply saved form values to the selected overlay after a successful save without a page reload Editors need immediate visual feedback while keeping server persistence authoritative.
Validation Latitude -90..90, longitude -180..180, scale >= 0, rotation -180..180, opacity 0..1, and finite minimum zoom These rules prevent invalid map geometry and rendering values while allowing negative map zooms used by flat maps.
Persistence boundary Update one overlay inside the existing MapProject payload Both local JSON and Directus-backed storage already persist the complete project object through projects.server.ts.
Directus schema No schema change assumed No directus/schema/*.json snapshot exists, and image overlays are nested in the existing project data payload rather than a separate collection.

Prerequisites

  • Keep the protected route declaration in app/routes.ts unchanged: project/:projectId/map/:mapId continues to render app/routes/project-map.tsx.
  • Preserve the existing Route.LoaderArgs, Route.ActionArgs, and component contracts. The loader already returns map.imageOverlays and the project map data required by both sidebars.
  • Keep filesystem and Directus access inside app/lib/projects.server.ts. The route action must use the existing activeAccessToken(context) path before calling persistence code.
  • Preserve the existing ImageOverlayLayer shape in app/lib/projects.server.ts: id, label, path, center, scale, rotation, opacity, and minZoom.
  • Keep Leaflet client-only. The editor state and form may manage plain overlay data, but route modules must continue lazy-loading ConfiguredImageLayer through the existing map component boundary.
  • Do not change the existing localStorage visibility key or useImageLayerSelection; selecting an overlay for editing is separate from enabling/disabling it in the map view.
  • No directus/schema/*.json files are present in this workspace. Do not plan a new Directus field or collection; verify the deployed map_projects.data payload accepts the existing nested shape during implementation/release checks.

Step 1 — Add targeted image-overlay persistence

Goal: Update exactly one existing image overlay through the established local/Directus project persistence boundary.

Scope

  • In: app/lib/projects.server.ts, add an update function that accepts the access token, project id, map id, overlay id, and the editable adjustment fields.
  • In: the same module, load the project, find the requested map and overlay, replace only center, scale, rotation, opacity, and minZoom, and write through the existing project-row persistence path.
  • In: the same module, return the updated overlay or an absent result when the project, map, or overlay does not exist.
  • In: preserve id, label, and path; do not add image assets, remove overlays, reorder arrays, or mutate another map.
  • Out: Route parsing, UI validation, client state, and Directus schema changes.

Acceptance criteria

  • A valid update persists all five editable values on the targeted overlay.
  • The overlay label and asset path remain unchanged.
  • Missing project, map, or overlay ids return an absent result without writing.
  • Other overlays, maps, and project fields remain unchanged.
  • Local JSON and Directus-backed storage use the existing project persistence implementation.
  • pnpm verify typecheck passes.

Step 2 — Add the protected route action and validation

Goal: Accept explicit image-overlay saves from the map route and reject invalid or out-of-scope updates before persistence.

Scope

  • In: app/routes/project-map.tsx, add an update-image-overlay action intent alongside the existing map-object and settings intents.
  • In: the same route, parse overlayId, latitude, longitude, scale, rotation, opacity, and minZoom from form data as numbers; do not accept label or path fields.
  • In: the same route, validate latitude -90..90, longitude -180..180, scale non-negative, rotation -180..180, opacity 0..1, and finite minimum zoom, returning structured field errors without persistence when invalid.
  • In: the same route, call the new server function with activeAccessToken(context) and return 404 when the project, map, or overlay is absent.
  • In: preserve the route's existing action contracts and successful { ok: true } response pattern; do not add a new route or bypass the protected layout.
  • Out: Client-side-only validation, silent clamping, visibility changes, and image-layer creation/removal.

Acceptance criteria

  • Invalid submissions identify the affected field and never reach persistence.
  • A valid submission updates only the requested overlay and returns success.
  • Unknown project, map, or overlay ids produce 404 rather than mutating another record.
  • The action does not accept or persist label/path changes.
  • The existing access-token and protected-route behavior remains intact.
  • pnpm verify typecheck passes.

Step 3 — Extend editor state for image-overlay selection

Goal: Give edit mode one selected image overlay that can drive the left-sidebar selection and right-sidebar form.

Scope

  • In: app/features/map-editor/use-map-editing-state.ts, accept the current ImageOverlayLayer[] and store a selected/editing overlay id using the same mode-owned state pattern as POIs, lines, and areas.
  • In: the same hook, expose the selected overlay object and a selection handler for sidebar requests; clear the selection when leaving edit mode or when the selected id no longer exists in the current map data.
  • In: the same hook, ensure selecting an overlay does not clear or alter the existing POI/line/area selection semantics beyond the existing mutually exclusive right-panel behavior.
  • In: app/routes/project-map.tsx, pass imageOverlays into the hook and provide the selected overlay state/handler to both editor sidebars.
  • Out: Leaflet event selection, overlay visibility state, and persistence of selection in localStorage.

Acceptance criteria

  • Edit mode can represent no selected overlay or exactly one selected existing overlay.
  • Selecting an overlay updates the right-panel subject without changing its visibility.
  • Leaving edit mode clears transient overlay selection consistently with other edit selections.
  • Removing or changing the current map data cannot leave a stale selected overlay object active.
  • Existing POI, line, and area selection/editing flows retain their behavior.
  • pnpm verify typecheck passes.

Step 4 — Add the image-overlay list to the left edit sidebar

Goal: Let editors choose which existing image overlay to adjust using the established left-sidebar pattern.

Scope

  • In: app/features/map-editor/edit-sidebar.tsx, accept the image overlays, selected overlay id, and selection handler.
  • In: the same component, render every overlay by its label in the Layers section with a clear selected state and an accessible button target.
  • In: preserve the existing layer section placement and the disabled tile/image layer buttons only where they do not conflict with the new existing-overlay list; the implementation should make the distinction between selecting an existing overlay and adding a layer clear in the final UI.
  • In: show a concise empty state when no image overlays exist; do not create a generic invalid selection.
  • Out: Changing visibility, editing labels/assets, adding/removing/reordering layers, and map rendering changes.

Acceptance criteria

  • Every existing image overlay appears exactly once in the left edit sidebar.
  • The selected overlay has a visible selected state and remains keyboard accessible.
  • Clicking an overlay selects it for the right panel without toggling map visibility.
  • Empty overlay collections show an appropriate empty state.
  • Existing object-creation controls and unrelated sidebar sections remain available.
  • The list remains usable at the existing narrow and wide sidebar widths.
  • pnpm verify typecheck passes.

Step 5 — Build the right image-overlay adjustment form

Goal: Expose and explicitly save every editable image-overlay configuration value in the established right edit sidebar.

Scope

  • In: app/features/map-editor/edit-detail-panel.tsx, add a selected-image-overlay branch alongside the existing POI, line, and area branches.
  • In: the same component, display the read-only overlay label/path context as appropriate and provide inputs for center latitude, center longitude, scale, rotation, opacity, and minimum zoom.
  • In: the same component, submit the selected overlay id and editable values through a dedicated useFetcher form using the update-image-overlay intent; use existing field, input, button, and error presentation primitives.
  • In: app/routes/project-map.tsx, create/pass the image-overlay update fetcher and wire successful submission to the refreshed loader data or equivalent route state update so the selected overlay preview uses the saved values immediately.
  • In: preserve explicit Save semantics: editing fields alone does not persist, and a successful save is visibly acknowledged without navigating away.
  • Out: Label/path editing, a delete action, add-layer controls, drag handles, or automatic save on blur/change.

Acceptance criteria

  • Selecting an overlay opens all five editable values populated from the current overlay.
  • Label and asset path are not editable in this task.
  • Invalid values show field-level feedback and do not replace the current saved overlay.
  • Save submits the selected overlay id and all five editable values.
  • A successful save is acknowledged and the form reflects the persisted values.
  • The right panel continues to show the existing POI/line/area forms when those subjects are active.
  • pnpm verify typecheck passes.

Step 6 — Apply saved values to the map preview

Goal: Ensure the selected overlay visibly reflects persisted adjustments after saving while preserving existing rendering and visibility behavior.

Scope

  • In: app/features/map-layers/image-layers.tsx, retain the existing dependency-driven overlay recreation behavior so changes to center, scale, rotation, opacity, or minimum zoom update the Leaflet overlay.
  • In: app/routes/project-map.tsx, ensure the rendered imageOverlays data is refreshed or updated after a successful action; avoid duplicating a second image-layer configuration model in the client.
  • In: preserve ConfiguredImageLayer behavior: minZoom controls visibility by zoom, opacity configures the overlay, and center/scale/rotation determine its bounds and transform.
  • In: preserve useImageLayerSelection as the independent visibility source; editing a hidden overlay must not silently enable it.
  • Out: New Leaflet editing plugins, drag-to-position controls, layer ordering, and changes to tile/base-layer rendering.

Acceptance criteria

  • Saving a changed center moves the overlay preview to the new center.
  • Saving scale changes the rendered bounds without a page reload.
  • Saving rotation changes the rendered image rotation.
  • Saving opacity changes the rendered transparency.
  • Saving minimum zoom changes the zoom threshold at which the overlay renders.
  • An overlay hidden by the existing layer control remains hidden after editing.
  • View-mode and edit-mode rendering use the same persisted ImageOverlayLayer values.

Step 7 — Verify persistence, validation, and editor behavior

Goal: Confirm the complete adjustment workflow across local and Directus-backed persistence paths and the existing map editor modes.

Acceptance criteria

  • pnpm verify typecheck passes.
  • pnpm verify format completes successfully.
  • pnpm build passes.
  • Manual check: enter edit mode on a map with multiple image overlays and confirm every overlay appears in the left sidebar.
  • Manual check: select each overlay and confirm the right panel shows its own label context and current center, scale, rotation, opacity, and minimum zoom.
  • Manual check: change each field, save, and confirm the map preview updates without a page reload.
  • Manual check: reload the map and confirm all saved values are restored.
  • Manual check: submit values outside each defined range and confirm field errors with unchanged persisted values.
  • Manual check: select a hidden overlay, edit and save it, and confirm it remains hidden until changed through the existing visibility control.
  • Manual check: select an overlay, switch to a POI/line/area edit flow, and confirm the right panel and selection state remain mutually coherent.
  • Manual check: verify missing/invalid overlay ids return 404 and do not modify another overlay.
  • Verify local JSON persistence and the deployed Directus map_projects.data payload without introducing a new Directus schema field.

Risks / open questions

  • No directus/schema/*.json snapshot exists in the repository. The implementation should verify the deployed map_projects.data payload accepts updates to nested image-overlay values before release; no new schema field is planned.
  • The current route uses loader-returned project data for rendering. The implementation must choose the smallest refresh mechanism consistent with existing fetcher behavior so a successful save cannot leave the form or Leaflet layer showing stale values.
  • The editor currently has one right-panel slot shared by object forms. Image-overlay selection must participate in that same precedence model rather than introducing a second competing detail panel.
  • Numeric HTML inputs can emit empty or non-finite values. Server validation remains authoritative even if the form adds matching client-side constraints.