Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 16 additions & 15 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ GEM
securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5)
uri (>= 0.13.1)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
addressable (2.8.8)
public_suffix (>= 2.0.2, < 8.0)
amazing_print (2.0.0)
anyway_config (2.7.2)
ruby-next-core (~> 1.0)
Expand Down Expand Up @@ -164,23 +164,23 @@ GEM
dry-configurable (1.3.0)
dry-core (~> 1.1)
zeitwerk (~> 2.6)
dry-core (1.1.0)
dry-core (1.2.0)
concurrent-ruby (~> 1.0)
logger
zeitwerk (~> 2.6)
dry-inflector (1.2.0)
dry-inflector (1.3.1)
dry-initializer (3.2.0)
dry-logic (1.6.0)
bigdecimal
concurrent-ruby (~> 1.0)
dry-core (~> 1.1)
zeitwerk (~> 2.6)
dry-schema (1.14.1)
dry-schema (1.15.0)
concurrent-ruby (~> 1.0)
dry-configurable (~> 1.0, >= 1.0.1)
dry-core (~> 1.1)
dry-initializer (~> 3.2)
dry-logic (~> 1.5)
dry-logic (~> 1.6)
dry-types (~> 1.8)
zeitwerk (~> 2.6)
dry-types (1.9.0)
Expand Down Expand Up @@ -210,14 +210,14 @@ GEM
webrick (~> 1.7)
websocket-driver (~> 0.7)
ffi (1.17.3-x86_64-linux-gnu)
flay (2.13.3)
flay (2.14.2)
erubi (~> 1.10)
path_expander (~> 1.0)
ruby_parser (~> 3.0)
path_expander (~> 2.0)
prism (~> 1.7)
sexp_processor (~> 4.0)
flog (4.8.0)
path_expander (~> 1.0)
ruby_parser (~> 3.1, > 3.1.0)
flog (4.9.4)
path_expander (~> 2.0)
prism (~> 1.7)
sexp_processor (~> 4.8)
globalid (1.3.0)
activesupport (>= 6.1)
Expand Down Expand Up @@ -354,7 +354,7 @@ GEM
parser (3.3.10.1)
ast (~> 2.4.1)
racc
path_expander (1.1.3)
path_expander (2.0.1)
pp (0.6.3)
prettyprint
prettyprint (0.2.0)
Expand All @@ -364,7 +364,7 @@ GEM
psych (5.3.1)
date
stringio
public_suffix (6.0.2)
public_suffix (7.0.2)
puma (7.2.0)
nio4r (~> 2.0)
puppeteer-ruby (0.45.6)
Expand Down Expand Up @@ -516,11 +516,12 @@ GEM
ruby_parser (3.22.0)
racc (~> 1.5)
sexp_processor (~> 4.16)
rubycritic (4.11.0)
rubycritic (4.12.0)
flay (~> 2.13)
flog (~> 4.7)
launchy (>= 2.5.2)
parser (>= 3.3.0.5)
prism (>= 1.6.0)
rainbow (~> 3.1.1)
reek (~> 6.5.0, < 7.0)
rexml
Expand Down
44 changes: 22 additions & 22 deletions app/javascript/controllers/feature/edit_controller.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Controller } from '@hotwired/stimulus'
import { mapChannel } from 'channels/map_channel'
import { geojsonData, redrawGeojson } from 'maplibre/map'
import { redrawGeojson } from 'maplibre/map'
import { featureIcon, featureImage, uploadImageToFeature, confirmImageLocation } from 'maplibre/feature'
import { handleDelete, draw } from 'maplibre/edit'
import { featureColor, featureOutlineColor } from 'maplibre/styles'
Expand All @@ -21,14 +21,14 @@ export default class extends Controller {
delete_feature (e) {
if (dom.isInputElement(e.target)) return // Don't trigger if typing in input

const feature = this.getFeature()
const feature = this.getEditFeature()
if (confirm(`Really delete this ${feature.geometry.type}?`)) {
handleDelete({ features: [feature] })
}
}

update_feature_raw () {
const feature = this.getFeature()
const feature = this.getEditFeature()
document.querySelector('#feature-edit-raw .error').innerHTML = ''
try {
feature.properties = JSON.parse(document.querySelector('#feature-edit-raw textarea').value)
Expand All @@ -42,15 +42,15 @@ export default class extends Controller {
}

updateTitle () {
const feature = this.getFeature()
const feature = this.getEditFeature()
const title = document.querySelector('#feature-title-input input').value
feature.properties.title = title
document.querySelector('#feature-title').textContent = title
functions.debounce(() => { this.saveFeature() }, 'title')
}

updateLabel () {
const feature = this.getFeature()
const feature = this.getEditFeature()
const label = document.querySelector('#feature-label input').value
feature.properties.label = label
redrawGeojson(false)
Expand All @@ -59,7 +59,7 @@ export default class extends Controller {

// called as preview on slider change
updatePointSize () {
const feature = this.getFeature()
const feature = this.getEditFeature()
const size = document.querySelector('#point-size').value
document.querySelector('#point-size-val').textContent = size
feature.properties['marker-size'] = size
Expand All @@ -69,7 +69,7 @@ export default class extends Controller {
}

updatePointScaling() {
const feature = this.getFeature()
const feature = this.getEditFeature()
const val = document.querySelector('#point-scaling').checked
feature.properties['marker-scaling'] = val
// draw layer feature properties aren't getting updated by draw.set()
Expand All @@ -79,7 +79,7 @@ export default class extends Controller {

// called as preview on slider change
updateLineWidth () {
const feature = this.getFeature()
const feature = this.getEditFeature()
const size = document.querySelector('#line-width').value
document.querySelector('#line-width-val').textContent = size
feature.properties['stroke-width'] = size
Expand All @@ -90,7 +90,7 @@ export default class extends Controller {

// called as preview on slider change
updateOutLineWidth () {
const feature = this.getFeature()
const feature = this.getEditFeature()
const size = document.querySelector('#outline-width').value
document.querySelector('#outline-width-val').textContent = size
feature.properties['stroke-width'] = size
Expand All @@ -101,7 +101,7 @@ export default class extends Controller {

// called as preview on slider change
updateFillExtrusionHeight () {
const feature = this.getFeature()
const feature = this.getEditFeature()
const size = document.querySelector('#fill-extrusion-height').value
document.querySelector('#fill-extrusion-height-val').textContent = size + 'm'
feature.properties['fill-extrusion-height'] = Number(size)
Expand All @@ -112,7 +112,7 @@ export default class extends Controller {
}

updateOpacity () {
const feature = this.getFeature()
const feature = this.getEditFeature()
const opacity = document.querySelector('#opacity').value / 10
document.querySelector('#opacity-val').textContent = opacity * 100 + '%'
feature.properties['fill-opacity'] = opacity
Expand All @@ -122,7 +122,7 @@ export default class extends Controller {
}

updateStrokeColor () {
const feature = this.getFeature()
const feature = this.getEditFeature()
const color = document.querySelector('#stroke-color').value
feature.properties.stroke = color
// draw layer feature properties aren't getting updated by draw.set()
Expand All @@ -131,7 +131,7 @@ export default class extends Controller {
}

updateStrokeColorTransparent () {
const feature = this.getFeature()
const feature = this.getEditFeature()
let color
if (document.querySelector('#stroke-color-transparent').checked) {
color = 'transparent'
Expand All @@ -146,15 +146,15 @@ export default class extends Controller {
}

updateFillColor () {
const feature = this.getFeature()
const feature = this.getEditFeature()
const color = document.querySelector('#fill-color').value
if (feature.geometry.type === 'Polygon' || feature.geometry.type === 'MultiPolygon') { feature.properties.fill = color }
if (feature.geometry.type === 'Point') { feature.properties['marker-color'] = color }
redrawGeojson(true)
}

updateFillColorTransparent () {
const feature = this.getFeature()
const feature = this.getEditFeature()
let color
if (document.querySelector('#fill-color-transparent').checked) {
color = 'transparent'
Expand All @@ -170,7 +170,7 @@ export default class extends Controller {
}

updateShowKmMarkers () {
const feature = this.getFeature()
const feature = this.getEditFeature()
if (document.querySelector('#show-km-markers').checked) {
feature.properties['show-km-markers'] = true
// feature.properties['stroke-image-url'] = "/icons/direction-arrow.png"
Expand All @@ -182,7 +182,7 @@ export default class extends Controller {
}

updateMarkerSymbol () {
const feature = this.getFeature()
const feature = this.getEditFeature()
let symbol = document.querySelector('#marker-symbol').value
document.querySelector('#emoji').textContent = symbol
// strip variation selector (emoji) U+FE0F to match icon file names
Expand All @@ -195,7 +195,7 @@ export default class extends Controller {
}

async updateMarkerImage () {
const feature = this.getFeature()
const feature = this.getEditFeature()
const image = document.querySelector('#marker-image').files[0]
const imageLocation = await confirmImageLocation(image)
if (imageLocation) { feature.geometry.coordinates = imageLocation }
Expand Down Expand Up @@ -265,19 +265,19 @@ export default class extends Controller {
}

saveFeature () {
const feature = this.getFeature()
const feature = this.getEditFeature()
status('Saving feature ' + feature.id)
// send shallow copy of feature to avoid changes during send
mapChannel.send_message('update_feature', { ...feature })
}

addUndo() {
const feature = this.getFeature()
const feature = this.getEditFeature()
addUndoState('Feature property update', feature)
}

getFeature () {
getEditFeature () {
const id = this.featureIdValue
return geojsonData.features.find(f => f.id === id)
return getFeature(id)
}
Comment on lines +279 to 282
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

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

getEditFeature() calls getFeature(id) but this file does not import getFeature, so the controller will crash when invoked. Import getFeature from maplibre/layers/layers (and consider handling null if the feature cannot be found).

Copilot uses AI. Check for mistakes.
}
26 changes: 13 additions & 13 deletions app/javascript/controllers/feature/modal_controller.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Controller } from '@hotwired/stimulus'
import { mapChannel } from 'channels/map_channel'
import { geojsonData } from 'maplibre/map'
import { defaultLineWidth, featureColor, featureOutlineColor } from 'maplibre/styles'
import { AnimateLineAnimation, AnimatePolygonAnimation, animateViewFromProperties } from 'maplibre/animations'
import { status } from 'helpers/status'
import { showFeatureDetails, highlightedFeatureId } from 'maplibre/feature'
import * as functions from 'helpers/functions'
import * as dom from 'helpers/dom'
import { draw, select, unselect } from 'maplibre/edit'
import { getFeature } from 'maplibre/layers/layers'

let easyMDE

Expand All @@ -33,12 +33,12 @@ export default class extends Controller {
this.show_feature_edit_ui()

// add feature to draw
const feature = this.getFeature()
const feature = this.getSelectedFeature()
draw.add(feature)
select(feature)
} else {
// repeated click on the current edit mode returns to feature description
showFeatureDetails(this.getFeature())
showFeatureDetails(this.getSelectedFeature())
unselect()
}
document.querySelector('#feature-edit-raw .error').innerHTML = ''
Expand All @@ -49,7 +49,7 @@ export default class extends Controller {
if (this.element.classList.contains('modal-pull-down')) {
this.pullUpModal(this.element)
}
const feature = this.getFeature()
const feature = this.getSelectedFeature()
dom.showElements(['#feature-edit-ui', '#button-add-label', '#button-add-desc'])
dom.hideElements(['#feature-edit-raw', '#feature-label', '#feature-desc'])
functions.e('em-emoji-picker', e => { e.remove() })
Expand Down Expand Up @@ -134,15 +134,15 @@ export default class extends Controller {
if (this.element.classList.contains('modal-pull-down')) {
this.pullUpModal(this.element)
}
const feature = this.getFeature()
const feature = this.getSelectedFeature()
dom.hideElements(['#feature-edit-ui'])
dom.showElements(['#feature-edit-raw'])
document.querySelector('#feature-edit-raw textarea')
.value = JSON.stringify(feature.properties, undefined, 2)
}

show_add_label () {
document.querySelector('#feature-label input').value = this.getFeature().properties.label || null
document.querySelector('#feature-label input').value = this.getSelectedFeature().properties.label || null
dom.hideElements(['#button-add-label'])
dom.showElements(['#feature-label'])
}
Expand All @@ -153,7 +153,7 @@ export default class extends Controller {
// https://github.com/Ionaru/easy-markdown-editor
await import('easymde') // import EasyMDE UMD bundle
if (easyMDE) { easyMDE.toTextArea() }
document.querySelector('#feature-desc-input').value = this.getFeature().properties.desc || ''
document.querySelector('#feature-desc-input').value = this.getSelectedFeature().properties.desc || ''
easyMDE = new window.EasyMDE({
element: document.getElementById('feature-desc-input'),
placeholder: 'Add a description text',
Expand All @@ -168,7 +168,7 @@ export default class extends Controller {
}

updateDesc () {
const feature = this.getFeature()
const feature = this.getSelectedFeature()
try {
if (easyMDE && feature.properties.desc !== easyMDE.value()) {
feature.properties.desc = easyMDE.value()
Expand All @@ -181,7 +181,7 @@ export default class extends Controller {
}

saveFeature () {
const feature = this.getFeature()
const feature = this.getSelectedFeature()
status('Saving feature ' + feature.id)
// send shallow copy of feature to avoid changes during send
mapChannel.send_message('update_feature', { ...feature })
Expand Down Expand Up @@ -214,16 +214,16 @@ export default class extends Controller {
modal.style.removeProperty('height')
}

getFeature () {
getSelectedFeature () {
const id = this.featureIdValue
return geojsonData.features.find(f => f.id === id)
return getFeature(id)
}
Comment on lines +217 to 220
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

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

getSelectedFeature() calls getFeature(id) but getFeature is not imported in this controller, which will raise a ReferenceError at runtime. Import getFeature from maplibre/layers/layers (and consider handling a null return to avoid passing undefined into draw/select).

Copilot uses AI. Check for mistakes.

async copy(event) {
if (functions.isFormFieldFocused()) { return }
if (!highlightedFeatureId) { return }

const feature = this.getFeature()
const feature = this.getSelectedFeature()
if (feature) {
await navigator.clipboard.writeText(JSON.stringify(feature))
event.preventDefault()
Expand All @@ -234,7 +234,7 @@ export default class extends Controller {
}

animate () {
const feature = this.getFeature()
const feature = this.getSelectedFeature()
console.log('Animating ' + feature.id)
if (feature.geometry.type === 'LineString') {
new AnimateLineAnimation().run(feature)
Expand Down
2 changes: 1 addition & 1 deletion app/javascript/maplibre/animations.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ export function animateViewFromProperties () {
})
}

export function flyToFeature(feature, source='geojson-source') {
export function flyToFeature(feature, source) {
// Calculate the centroid
const center = centroid(feature)
console.log('Fly to: ' + feature.id + ' ' + center.geometry.coordinates)
Expand Down
Loading
Loading