feat: add basemap preference persistence and layer switcher control support
This commit is contained in:
parent
9bdd68bea2
commit
e65c402395
11 changed files with 116 additions and 15 deletions
|
|
@ -748,6 +748,14 @@ function applyInitialPreferences() {
|
||||||
window._restoredMapCenter = prefs.map_center;
|
window._restoredMapCenter = prefs.map_center;
|
||||||
window._restoredMapZoom = prefs.map_zoom;
|
window._restoredMapZoom = prefs.map_zoom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Basemap preference
|
||||||
|
if (prefs.basemap) {
|
||||||
|
window._restoredBasemap = prefs.basemap;
|
||||||
|
try {
|
||||||
|
localStorage.setItem('loko_active_basemap', prefs.basemap);
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
} else if (DEFAULT_THEMATIC_CODE && DEFAULT_THEMATIC_CODE in THEMATIC_CONFIG) {
|
} else if (DEFAULT_THEMATIC_CODE && DEFAULT_THEMATIC_CODE in THEMATIC_CONFIG) {
|
||||||
// First visit: pre-select the user's default thematic
|
// First visit: pre-select the user's default thematic
|
||||||
activeThematics = new Set([DEFAULT_THEMATIC_CODE]);
|
activeThematics = new Set([DEFAULT_THEMATIC_CODE]);
|
||||||
|
|
@ -775,6 +783,9 @@ function savePreferences() {
|
||||||
).map(cb => cb.value);
|
).map(cb => cb.value);
|
||||||
const includeNoGeomCheckbox = document.getElementById('include-no-geom');
|
const includeNoGeomCheckbox = document.getElementById('include-no-geom');
|
||||||
const includeNoGeom = includeNoGeomCheckbox ? includeNoGeomCheckbox.checked : true;
|
const includeNoGeom = includeNoGeomCheckbox ? includeNoGeomCheckbox.checked : true;
|
||||||
|
const activeBasemap = (window.unifiedLayerSwitcher && window.unifiedLayerSwitcher.activeBasemapId)
|
||||||
|
|| localStorage.getItem('loko_active_basemap')
|
||||||
|
|| 'default';
|
||||||
const prefs = {
|
const prefs = {
|
||||||
thematics: Array.from(activeThematics),
|
thematics: Array.from(activeThematics),
|
||||||
categories: Array.from(activeCategories),
|
categories: Array.from(activeCategories),
|
||||||
|
|
@ -787,6 +798,7 @@ function savePreferences() {
|
||||||
include_no_geom: includeNoGeom,
|
include_no_geom: includeNoGeom,
|
||||||
map_center: (typeof map !== 'undefined' && map.getCenter) ? [map.getCenter().lng, map.getCenter().lat] : null,
|
map_center: (typeof map !== 'undefined' && map.getCenter) ? [map.getCenter().lng, map.getCenter().lat] : null,
|
||||||
map_zoom: (typeof map !== 'undefined' && map.getZoom) ? map.getZoom() : null,
|
map_zoom: (typeof map !== 'undefined' && map.getZoom) ? map.getZoom() : null,
|
||||||
|
basemap: activeBasemap,
|
||||||
};
|
};
|
||||||
fetch(SAVE_ASSETS_PREFS_URL, {
|
fetch(SAVE_ASSETS_PREFS_URL, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
@ -2929,7 +2941,12 @@ maploader.addEventListener('mapLoaded', () => {
|
||||||
|
|
||||||
// Layer switcher — Urbis & Vlaanderen orthos (top-right, topmost)
|
// Layer switcher — Urbis & Vlaanderen orthos (top-right, topmost)
|
||||||
if (typeof LayerSwitcherControl !== 'undefined') {
|
if (typeof LayerSwitcherControl !== 'undefined') {
|
||||||
|
const initialBasemap = window._restoredBasemap || localStorage.getItem('loko_active_basemap') || 'default';
|
||||||
const layerSwitcher = new LayerSwitcherControl({
|
const layerSwitcher = new LayerSwitcherControl({
|
||||||
|
initialBasemapId: initialBasemap,
|
||||||
|
onBasemapChange: function (basemapId) {
|
||||||
|
schedulePreferencesSave();
|
||||||
|
},
|
||||||
layers: [{
|
layers: [{
|
||||||
id: 'basemap',
|
id: 'basemap',
|
||||||
name: 'Fonds de plan & Orthophotos',
|
name: 'Fonds de plan & Orthophotos',
|
||||||
|
|
@ -2937,7 +2954,10 @@ maploader.addEventListener('mapLoaded', () => {
|
||||||
beforeLayer: (map && map.getLayer('territory-mask') ? 'territory-mask' : 'locations'),
|
beforeLayer: (map && map.getLayer('territory-mask') ? 'territory-mask' : 'locations'),
|
||||||
basemaps: window.MAP_BACKGROUNDS || [],
|
basemaps: window.MAP_BACKGROUNDS || [],
|
||||||
}],
|
}],
|
||||||
});// Reset view — fly back to initial Brussels Region view
|
});
|
||||||
|
window.unifiedLayerSwitcher = layerSwitcher;
|
||||||
|
|
||||||
|
// Reset view — fly back to initial Brussels Region view
|
||||||
map.addControl({
|
map.addControl({
|
||||||
onAdd(m) {
|
onAdd(m) {
|
||||||
this._map = m;
|
this._map = m;
|
||||||
|
|
|
||||||
|
|
@ -1315,7 +1315,7 @@
|
||||||
|
|
||||||
{% block script %}
|
{% block script %}
|
||||||
<script src="{% static 'common/mapbox-gl-draw.js' %}"></script>
|
<script src="{% static 'common/mapbox-gl-draw.js' %}"></script>
|
||||||
<script src="{% static 'common/layer-switcher-control.js' %}"></script>
|
<script src="{% static 'common/layer-switcher-control.js' %}?v=20261009_basemap"></script>
|
||||||
<script>
|
<script>
|
||||||
window.TERRITORY_MASK_URL = "{% url 'common:get_territory_mask_geojson' %}";
|
window.TERRITORY_MASK_URL = "{% url 'common:get_territory_mask_geojson' %}";
|
||||||
const preventive_draft_id = "{{ preventive_draft_id|default:'' }}";
|
const preventive_draft_id = "{{ preventive_draft_id|default:'' }}";
|
||||||
|
|
@ -1374,5 +1374,5 @@
|
||||||
<script src="{% static 'assets/sign_panels_shared.js' %}?v=20260703c"></script>
|
<script src="{% static 'assets/sign_panels_shared.js' %}?v=20260703c"></script>
|
||||||
<script src="{% static 'assets/asset_symbology.js' %}?v=20261009_tree_icons"></script>
|
<script src="{% static 'assets/asset_symbology.js' %}?v=20261009_tree_icons"></script>
|
||||||
<script src="{% static 'assets/unified_map_layers.js' %}?v=20261009_tree_icons"></script>
|
<script src="{% static 'assets/unified_map_layers.js' %}?v=20261009_tree_icons"></script>
|
||||||
<script src="{% static 'assets/unified_index.js' %}?v=20261009_tree_icons"></script>
|
<script src="{% static 'assets/unified_index.js' %}?v=20261009_basemap"></script>
|
||||||
{% endblock script %}
|
{% endblock script %}
|
||||||
Binary file not shown.
|
|
@ -553,3 +553,8 @@ msgstr "Zoom in"
|
||||||
#: common/templates/common/components/document_viewer.html
|
#: common/templates/common/components/document_viewer.html
|
||||||
msgid "Réinitialiser"
|
msgid "Réinitialiser"
|
||||||
msgstr "Reset"
|
msgstr "Reset"
|
||||||
|
|
||||||
|
#: common/views.py:683
|
||||||
|
msgid "Format de fond de plan invalide"
|
||||||
|
msgstr "Invalid basemap format"
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -539,3 +539,8 @@ msgstr "Zoomer"
|
||||||
#: common/templates/common/components/document_viewer.html
|
#: common/templates/common/components/document_viewer.html
|
||||||
msgid "Réinitialiser"
|
msgid "Réinitialiser"
|
||||||
msgstr "Réinitialiser"
|
msgstr "Réinitialiser"
|
||||||
|
|
||||||
|
#: common/views.py:683
|
||||||
|
msgid "Format de fond de plan invalide"
|
||||||
|
msgstr "Format de fond de plan invalide"
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -539,3 +539,8 @@ msgstr "Inzoomen"
|
||||||
#: common/templates/common/components/document_viewer.html
|
#: common/templates/common/components/document_viewer.html
|
||||||
msgid "Réinitialiser"
|
msgid "Réinitialiser"
|
||||||
msgstr "Herstellen"
|
msgstr "Herstellen"
|
||||||
|
|
||||||
|
#: common/views.py:683
|
||||||
|
msgid "Format de fond de plan invalide"
|
||||||
|
msgstr "Ongeldig basiskaartformaat"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -381,7 +381,8 @@
|
||||||
|
|
||||||
// Extract or build basemaps list
|
// Extract or build basemaps list
|
||||||
this.basemaps = this._resolveBasemaps();
|
this.basemaps = this._resolveBasemaps();
|
||||||
this.activeBasemapId = localStorage.getItem('loko_active_basemap') || 'default';
|
this.activeBasemapId = options.initialBasemapId || localStorage.getItem('loko_active_basemap') || 'default';
|
||||||
|
this.onBasemapChange = typeof options.onBasemapChange === 'function' ? options.onBasemapChange : null;
|
||||||
|
|
||||||
this._injectStyles();
|
this._injectStyles();
|
||||||
this._handleOutsideClick = this._handleOutsideClick.bind(this);
|
this._handleOutsideClick = this._handleOutsideClick.bind(this);
|
||||||
|
|
@ -442,13 +443,26 @@
|
||||||
this.popover = this._createPopover();
|
this.popover = this._createPopover();
|
||||||
map.getContainer().appendChild(this.popover);
|
map.getContainer().appendChild(this.popover);
|
||||||
|
|
||||||
// Initialize active basemap on map load
|
// Initialize active basemap on map load (checking map._loaded, isStyleLoaded, or loaded)
|
||||||
if (map.loaded()) {
|
const isMapReady = Boolean(
|
||||||
|
map._loaded ||
|
||||||
|
(typeof map.isStyleLoaded === 'function' && map.isStyleLoaded()) ||
|
||||||
|
map.loaded()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isMapReady) {
|
||||||
this._applyInitialBasemap();
|
this._applyInitialBasemap();
|
||||||
} else {
|
} else {
|
||||||
map.on('load', () => this._applyInitialBasemap());
|
map.once('load', () => this._applyInitialBasemap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Re-apply if the style reloads dynamically
|
||||||
|
map.on('style.load', () => {
|
||||||
|
if (this.activeBasemapId && this.activeBasemapId !== 'default') {
|
||||||
|
this._applyInitialBasemap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
document.addEventListener('click', this._handleOutsideClick);
|
document.addEventListener('click', this._handleOutsideClick);
|
||||||
|
|
||||||
return this.container;
|
return this.container;
|
||||||
|
|
@ -660,8 +674,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
selectBasemap(id) {
|
selectBasemap(id) {
|
||||||
|
const previousId = this.activeBasemapId;
|
||||||
this.activeBasemapId = id;
|
this.activeBasemapId = id;
|
||||||
localStorage.setItem('loko_active_basemap', id);
|
try {
|
||||||
|
localStorage.setItem('loko_active_basemap', id);
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
// Update UI list items
|
// Update UI list items
|
||||||
if (this.popover) {
|
if (this.popover) {
|
||||||
|
|
@ -682,9 +699,20 @@
|
||||||
this._applyRasterBasemap(bgConfig);
|
this._applyRasterBasemap(bgConfig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.onBasemapChange && previousId !== id) {
|
||||||
|
try {
|
||||||
|
this.onBasemapChange(id);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[LayerSwitcherControl] onBasemapChange error:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_applyInitialBasemap() {
|
_applyInitialBasemap() {
|
||||||
|
if (!this.basemaps || this.basemaps.length === 0) {
|
||||||
|
this.basemaps = this._resolveBasemaps();
|
||||||
|
}
|
||||||
if (this.activeBasemapId && this.activeBasemapId !== 'default') {
|
if (this.activeBasemapId && this.activeBasemapId !== 'default') {
|
||||||
const bgConfig = this.basemaps.find(b => b.id === this.activeBasemapId);
|
const bgConfig = this.basemaps.find(b => b.id === this.activeBasemapId);
|
||||||
if (bgConfig) {
|
if (bgConfig) {
|
||||||
|
|
@ -693,13 +721,15 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Check if any basemap has is_default
|
// Check if any basemap has is_default only if activeBasemapId is not set
|
||||||
const defaultBg = this.basemaps.find(b => b.is_default);
|
if (!this.activeBasemapId) {
|
||||||
if (defaultBg) {
|
const defaultBg = this.basemaps.find(b => b.is_default);
|
||||||
this.selectBasemap(defaultBg.id);
|
if (defaultBg) {
|
||||||
} else {
|
this.selectBasemap(defaultBg.id);
|
||||||
this._updateButtonState();
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
this._updateButtonState();
|
||||||
}
|
}
|
||||||
|
|
||||||
_removeCurrentRasterLayer() {
|
_removeCurrentRasterLayer() {
|
||||||
|
|
|
||||||
|
|
@ -324,6 +324,35 @@ class AssetsIndexPreferencesTests(TestCase):
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 400)
|
||||||
self.assertIn("error", response.json())
|
self.assertIn("error", response.json())
|
||||||
|
|
||||||
|
def test_save_assets_index_preferences_with_basemap(self):
|
||||||
|
self.client.force_login(self.superuser)
|
||||||
|
save_url = reverse("common:save_assets_index_preferences")
|
||||||
|
payload = {
|
||||||
|
"thematics": ["sign"],
|
||||||
|
"basemap": "urbis_ortho",
|
||||||
|
}
|
||||||
|
response = self.client.post(save_url, json.dumps(payload), content_type="application/json")
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertTrue(response.json().get("success"))
|
||||||
|
|
||||||
|
from common.models import UserConfig
|
||||||
|
config = UserConfig.objects.get(user=self.superuser)
|
||||||
|
self.assertEqual(
|
||||||
|
config.assets_index_preferences.get("basemap"),
|
||||||
|
"urbis_ortho"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_save_assets_index_preferences_invalid_basemap_returns_400(self):
|
||||||
|
self.client.force_login(self.superuser)
|
||||||
|
save_url = reverse("common:save_assets_index_preferences")
|
||||||
|
payload = {
|
||||||
|
"basemap": 9999,
|
||||||
|
}
|
||||||
|
response = self.client.post(save_url, json.dumps(payload), content_type="application/json")
|
||||||
|
self.assertEqual(response.status_code, 400)
|
||||||
|
self.assertIn("error", response.json())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class UserConfigCopyTests(TestCase):
|
class UserConfigCopyTests(TestCase):
|
||||||
|
|
|
||||||
|
|
@ -663,6 +663,7 @@ def save_assets_index_preferences(request):
|
||||||
include_no_geom = data.get('include_no_geom', True)
|
include_no_geom = data.get('include_no_geom', True)
|
||||||
map_center = data.get('map_center')
|
map_center = data.get('map_center')
|
||||||
map_zoom = data.get('map_zoom')
|
map_zoom = data.get('map_zoom')
|
||||||
|
basemap = data.get('basemap')
|
||||||
|
|
||||||
if not isinstance(thematics, list):
|
if not isinstance(thematics, list):
|
||||||
return JsonResponse({'error': _('Format de thématiques invalide')}, status=400)
|
return JsonResponse({'error': _('Format de thématiques invalide')}, status=400)
|
||||||
|
|
@ -680,13 +681,18 @@ def save_assets_index_preferences(request):
|
||||||
return JsonResponse({'error': _('Format de filtres statut invalide')}, status=400)
|
return JsonResponse({'error': _('Format de filtres statut invalide')}, status=400)
|
||||||
if not isinstance(validation_status_filters, list):
|
if not isinstance(validation_status_filters, list):
|
||||||
return JsonResponse({'error': _('Format de filtres statut de validation invalide')}, status=400)
|
return JsonResponse({'error': _('Format de filtres statut de validation invalide')}, status=400)
|
||||||
|
if basemap is not None and not isinstance(basemap, str):
|
||||||
|
return JsonResponse({'error': _('Format de fond de plan invalide')}, status=400)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
user_config, _created = UserConfig.objects.get_or_create(user=request.user)
|
user_config, _created = UserConfig.objects.get_or_create(user=request.user)
|
||||||
existing_prefs = user_config.assets_index_preferences or {}
|
existing_prefs = user_config.assets_index_preferences or {}
|
||||||
existing_visible_columns = existing_prefs.get('visible_columns', []) if isinstance(existing_prefs, dict) else []
|
if not isinstance(existing_prefs, dict):
|
||||||
|
existing_prefs = {}
|
||||||
|
existing_visible_columns = existing_prefs.get('visible_columns', [])
|
||||||
if not isinstance(existing_visible_columns, list):
|
if not isinstance(existing_visible_columns, list):
|
||||||
existing_visible_columns = []
|
existing_visible_columns = []
|
||||||
|
existing_basemap = existing_prefs.get('basemap', 'default')
|
||||||
|
|
||||||
user_config.assets_index_preferences = {
|
user_config.assets_index_preferences = {
|
||||||
'thematics': thematics,
|
'thematics': thematics,
|
||||||
|
|
@ -700,6 +706,7 @@ def save_assets_index_preferences(request):
|
||||||
'include_no_geom': include_no_geom,
|
'include_no_geom': include_no_geom,
|
||||||
'map_center': map_center,
|
'map_center': map_center,
|
||||||
'map_zoom': map_zoom,
|
'map_zoom': map_zoom,
|
||||||
|
'basemap': basemap if basemap is not None else existing_basemap,
|
||||||
}
|
}
|
||||||
user_config.save(update_fields=['assets_index_preferences'])
|
user_config.save(update_fields=['assets_index_preferences'])
|
||||||
return JsonResponse({'success': True})
|
return JsonResponse({'success': True})
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue