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.tsunchanged:project/:projectId/map/:mapIdcontinues to renderapp/routes/project-map.tsx. - Preserve the existing
Route.LoaderArgs,Route.ActionArgs, and component contracts. The loader already returnsmap.imageOverlaysand 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 existingactiveAccessToken(context)path before calling persistence code. - Preserve the existing
ImageOverlayLayershape inapp/lib/projects.server.ts:id,label,path,center,scale,rotation,opacity, andminZoom. - Keep Leaflet client-only. The editor state and form may manage plain overlay data, but route modules must continue lazy-loading
ConfiguredImageLayerthrough 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/*.jsonfiles are present in this workspace. Do not plan a new Directus field or collection; verify the deployedmap_projects.datapayload 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, andminZoom, 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, andpath; 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 typecheckpasses.
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 anupdate-image-overlayaction intent alongside the existing map-object and settings intents. - In: the same route, parse
overlayId,latitude,longitude,scale,rotation,opacity, andminZoomfrom 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, opacity0..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 return404when 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
404rather 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 typecheckpasses.
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 currentImageOverlayLayer[]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, passimageOverlaysinto 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 typecheckpasses.
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 typecheckpasses.
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
useFetcherform using theupdate-image-overlayintent; 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 typecheckpasses.
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 renderedimageOverlaysdata is refreshed or updated after a successful action; avoid duplicating a second image-layer configuration model in the client. - In: preserve
ConfiguredImageLayerbehavior:minZoomcontrols visibility by zoom,opacityconfigures the overlay, and center/scale/rotation determine its bounds and transform. - In: preserve
useImageLayerSelectionas 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
ImageOverlayLayervalues.
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 typecheckpasses. -
pnpm verify formatcompletes successfully. -
pnpm buildpasses. - 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
404and do not modify another overlay. - Verify local JSON persistence and the deployed Directus
map_projects.datapayload without introducing a new Directus schema field.
Risks / open questions¶
- No
directus/schema/*.jsonsnapshot exists in the repository. The implementation should verify the deployedmap_projects.datapayload 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.