mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-09 08:47:11 -05:00
* fix: move foreman to global gems to fix startup crash (#1971) * Update exporting code to stream points data to file in batches to red… (#1980) * Update exporting code to stream points data to file in batches to reduce memory usage * Update changelog * Update changelog * Feature/maplibre frontend (#1953) * Add a plan to use MapLibre GL JS for the frontend map rendering, replacing Leaflet * Implement phase 1 * Phases 1-3 + part of 4 * Fix e2e tests * Phase 6 * Implement fog of war * Phase 7 * Next step: fix specs, phase 7 done * Use our own map tiles * Extract v2 map logic to separate manager classes * Update settings panel on v2 map * Update v2 e2e tests structure * Reimplement location search in maps v2 * Update speed routes * Implement visits and places creation in v2 * Fix last failing test * Implement visits merging * Fix a routes e2e test and simplify the routes layer styling. * Extract js to modules from maps_v2_controller.js * Implement area creation * Fix spec problem * Fix some e2e tests * Implement live mode in v2 map * Update icons and panel * Extract some styles * Remove unused file * Start adding dark theme to popups on MapLibre maps * Make popups respect dark theme * Move v2 maps to maplibre namespace * Update v2 references to maplibre * Put place, area and visit info into side panel * Update API to use safe settings config method * Fix specs * Fix method name to config in SafeSettings and update usages accordingly * Add missing public files * Add handling for real time points * Fix remembering enabled/disabled layers of the v2 map * Fix lots of e2e tests * Add settings to select map version * Use maps/v2 as main path for MapLibre maps * Update routing * Update live mode * Update maplibre controller * Update changelog * Remove some console.log statements --------- Co-authored-by: Robin Tuszik <mail@robin.gg>
281 lines
8 KiB
JavaScript
281 lines
8 KiB
JavaScript
import { SettingsManager } from 'maps_maplibre/utils/settings_manager'
|
|
import { Toast } from 'maps_maplibre/components/toast'
|
|
|
|
/**
|
|
* Manages places-related operations for Maps V2
|
|
* Including place creation, tag filtering, and layer management
|
|
*/
|
|
export class PlacesManager {
|
|
constructor(controller) {
|
|
this.controller = controller
|
|
this.layerManager = controller.layerManager
|
|
this.api = controller.api
|
|
this.dataLoader = controller.dataLoader
|
|
this.settings = controller.settings
|
|
}
|
|
|
|
/**
|
|
* Toggle places layer
|
|
*/
|
|
togglePlaces(event) {
|
|
const enabled = event.target.checked
|
|
SettingsManager.updateSetting('placesEnabled', enabled)
|
|
|
|
const placesLayer = this.layerManager.getLayer('places')
|
|
if (placesLayer) {
|
|
if (enabled) {
|
|
placesLayer.show()
|
|
if (this.controller.hasPlacesFiltersTarget) {
|
|
this.controller.placesFiltersTarget.style.display = 'block'
|
|
}
|
|
this.initializePlaceTagFilters()
|
|
} else {
|
|
placesLayer.hide()
|
|
if (this.controller.hasPlacesFiltersTarget) {
|
|
this.controller.placesFiltersTarget.style.display = 'none'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize place tag filters (enable all by default or restore saved state)
|
|
*/
|
|
initializePlaceTagFilters() {
|
|
const savedFilters = this.settings.placesTagFilters
|
|
|
|
if (savedFilters && savedFilters.length > 0) {
|
|
this.restoreSavedTagFilters(savedFilters)
|
|
} else {
|
|
this.enableAllTagsInitial()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Restore saved tag filters
|
|
*/
|
|
restoreSavedTagFilters(savedFilters) {
|
|
const tagCheckboxes = document.querySelectorAll('input[name="place_tag_ids[]"]')
|
|
|
|
tagCheckboxes.forEach(checkbox => {
|
|
const value = checkbox.value === 'untagged' ? checkbox.value : parseInt(checkbox.value)
|
|
const shouldBeChecked = savedFilters.includes(value)
|
|
|
|
if (checkbox.checked !== shouldBeChecked) {
|
|
checkbox.checked = shouldBeChecked
|
|
|
|
const badge = checkbox.nextElementSibling
|
|
const color = badge.style.borderColor
|
|
|
|
if (shouldBeChecked) {
|
|
badge.classList.remove('badge-outline')
|
|
badge.style.backgroundColor = color
|
|
badge.style.color = 'white'
|
|
} else {
|
|
badge.classList.add('badge-outline')
|
|
badge.style.backgroundColor = 'transparent'
|
|
badge.style.color = color
|
|
}
|
|
}
|
|
})
|
|
|
|
this.syncEnableAllTagsToggle()
|
|
this.loadPlacesWithTags(savedFilters)
|
|
}
|
|
|
|
/**
|
|
* Enable all tags initially
|
|
*/
|
|
enableAllTagsInitial() {
|
|
if (this.controller.hasEnableAllPlaceTagsToggleTarget) {
|
|
this.controller.enableAllPlaceTagsToggleTarget.checked = true
|
|
}
|
|
|
|
const tagCheckboxes = document.querySelectorAll('input[name="place_tag_ids[]"]')
|
|
const allTagIds = []
|
|
|
|
tagCheckboxes.forEach(checkbox => {
|
|
checkbox.checked = true
|
|
|
|
const badge = checkbox.nextElementSibling
|
|
const color = badge.style.borderColor
|
|
badge.classList.remove('badge-outline')
|
|
badge.style.backgroundColor = color
|
|
badge.style.color = 'white'
|
|
|
|
const value = checkbox.value === 'untagged' ? checkbox.value : parseInt(checkbox.value)
|
|
allTagIds.push(value)
|
|
})
|
|
|
|
SettingsManager.updateSetting('placesTagFilters', allTagIds)
|
|
this.loadPlacesWithTags(allTagIds)
|
|
}
|
|
|
|
/**
|
|
* Get selected place tag IDs
|
|
*/
|
|
getSelectedPlaceTags() {
|
|
return Array.from(
|
|
document.querySelectorAll('input[name="place_tag_ids[]"]:checked')
|
|
).map(cb => {
|
|
const value = cb.value
|
|
return value === 'untagged' ? value : parseInt(value)
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Filter places by selected tags
|
|
*/
|
|
filterPlacesByTags(event) {
|
|
const badge = event.target.nextElementSibling
|
|
const color = badge.style.borderColor
|
|
|
|
if (event.target.checked) {
|
|
badge.classList.remove('badge-outline')
|
|
badge.style.backgroundColor = color
|
|
badge.style.color = 'white'
|
|
} else {
|
|
badge.classList.add('badge-outline')
|
|
badge.style.backgroundColor = 'transparent'
|
|
badge.style.color = color
|
|
}
|
|
|
|
this.syncEnableAllTagsToggle()
|
|
|
|
const checkedTags = this.getSelectedPlaceTags()
|
|
SettingsManager.updateSetting('placesTagFilters', checkedTags)
|
|
this.loadPlacesWithTags(checkedTags)
|
|
}
|
|
|
|
/**
|
|
* Sync "Enable All Tags" toggle with individual tag states
|
|
*/
|
|
syncEnableAllTagsToggle() {
|
|
if (!this.controller.hasEnableAllPlaceTagsToggleTarget) return
|
|
|
|
const tagCheckboxes = document.querySelectorAll('input[name="place_tag_ids[]"]')
|
|
const allChecked = Array.from(tagCheckboxes).every(cb => cb.checked)
|
|
|
|
this.controller.enableAllPlaceTagsToggleTarget.checked = allChecked
|
|
}
|
|
|
|
/**
|
|
* Load places filtered by tags
|
|
*/
|
|
async loadPlacesWithTags(tagIds = []) {
|
|
try {
|
|
let places = []
|
|
|
|
if (tagIds.length > 0) {
|
|
places = await this.api.fetchPlaces({ tag_ids: tagIds })
|
|
}
|
|
|
|
const placesGeoJSON = this.dataLoader.placesToGeoJSON(places)
|
|
|
|
const placesLayer = this.layerManager.getLayer('places')
|
|
if (placesLayer) {
|
|
placesLayer.update(placesGeoJSON)
|
|
}
|
|
} catch (error) {
|
|
console.error('[Maps V2] Failed to load places:', error)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Toggle all place tags on/off
|
|
*/
|
|
toggleAllPlaceTags(event) {
|
|
const enableAll = event.target.checked
|
|
const tagCheckboxes = document.querySelectorAll('input[name="place_tag_ids[]"]')
|
|
|
|
tagCheckboxes.forEach(checkbox => {
|
|
if (checkbox.checked !== enableAll) {
|
|
checkbox.checked = enableAll
|
|
|
|
const badge = checkbox.nextElementSibling
|
|
const color = badge.style.borderColor
|
|
|
|
if (enableAll) {
|
|
badge.classList.remove('badge-outline')
|
|
badge.style.backgroundColor = color
|
|
badge.style.color = 'white'
|
|
} else {
|
|
badge.classList.add('badge-outline')
|
|
badge.style.backgroundColor = 'transparent'
|
|
badge.style.color = color
|
|
}
|
|
}
|
|
})
|
|
|
|
const selectedTags = this.getSelectedPlaceTags()
|
|
SettingsManager.updateSetting('placesTagFilters', selectedTags)
|
|
this.loadPlacesWithTags(selectedTags)
|
|
}
|
|
|
|
/**
|
|
* Start create place mode
|
|
*/
|
|
startCreatePlace() {
|
|
console.log('[Maps V2] Starting create place mode')
|
|
|
|
if (this.controller.hasSettingsPanelTarget && this.controller.settingsPanelTarget.classList.contains('open')) {
|
|
this.controller.toggleSettings()
|
|
}
|
|
|
|
this.controller.map.getCanvas().style.cursor = 'crosshair'
|
|
Toast.info('Click on the map to place a place')
|
|
|
|
this.handleCreatePlaceClick = (e) => {
|
|
const { lng, lat } = e.lngLat
|
|
|
|
document.dispatchEvent(new CustomEvent('place:create', {
|
|
detail: { latitude: lat, longitude: lng }
|
|
}))
|
|
|
|
this.controller.map.getCanvas().style.cursor = ''
|
|
}
|
|
|
|
this.controller.map.once('click', this.handleCreatePlaceClick)
|
|
}
|
|
|
|
/**
|
|
* Handle place creation event - reload places and update layer
|
|
*/
|
|
async handlePlaceCreated(event) {
|
|
console.log('[Maps V2] Place created, reloading places...', event.detail)
|
|
|
|
try {
|
|
const selectedTags = this.getSelectedPlaceTags()
|
|
|
|
const places = await this.api.fetchPlaces({
|
|
tag_ids: selectedTags
|
|
})
|
|
|
|
console.log('[Maps V2] Fetched places:', places.length)
|
|
|
|
const placesGeoJSON = this.dataLoader.placesToGeoJSON(places)
|
|
|
|
console.log('[Maps V2] Converted to GeoJSON:', placesGeoJSON.features.length, 'features')
|
|
|
|
const placesLayer = this.layerManager.getLayer('places')
|
|
if (placesLayer) {
|
|
placesLayer.update(placesGeoJSON)
|
|
console.log('[Maps V2] Places layer updated successfully')
|
|
} else {
|
|
console.warn('[Maps V2] Places layer not found, cannot update')
|
|
}
|
|
} catch (error) {
|
|
console.error('[Maps V2] Failed to reload places:', error)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle place update event - reload places and update layer
|
|
*/
|
|
async handlePlaceUpdated(event) {
|
|
console.log('[Maps V2] Place updated, reloading places...', event.detail)
|
|
|
|
// Reuse the same logic as creation
|
|
await this.handlePlaceCreated(event)
|
|
}
|
|
}
|