mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-11 09:41:40 -05:00
Merge pull request #1861 from Freika/feature/remember-enabled-map-layers
Remember enabled map layers for users
This commit is contained in:
commit
b2f831c9fa
5 changed files with 169 additions and 43 deletions
File diff suppressed because one or more lines are too long
|
|
@ -30,7 +30,8 @@ class Api::V1::SettingsController < ApiController
|
||||||
:time_threshold_minutes, :merge_threshold_minutes, :route_opacity,
|
:time_threshold_minutes, :merge_threshold_minutes, :route_opacity,
|
||||||
:preferred_map_layer, :points_rendering_mode, :live_map_enabled,
|
:preferred_map_layer, :points_rendering_mode, :live_map_enabled,
|
||||||
:immich_url, :immich_api_key, :photoprism_url, :photoprism_api_key,
|
:immich_url, :immich_api_key, :photoprism_url, :photoprism_api_key,
|
||||||
:speed_colored_routes, :speed_color_scale, :fog_of_war_threshold
|
:speed_colored_routes, :speed_color_scale, :fog_of_war_threshold,
|
||||||
|
enabled_map_layers: []
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -463,6 +463,9 @@ export default class extends BaseController {
|
||||||
|
|
||||||
// Add event listeners for overlay layer changes to keep routes/tracks selector in sync
|
// Add event listeners for overlay layer changes to keep routes/tracks selector in sync
|
||||||
this.map.on('overlayadd', (event) => {
|
this.map.on('overlayadd', (event) => {
|
||||||
|
// Save enabled layers whenever a layer is added
|
||||||
|
this.saveEnabledLayers();
|
||||||
|
|
||||||
if (event.name === 'Routes') {
|
if (event.name === 'Routes') {
|
||||||
this.handleRouteLayerToggle('routes');
|
this.handleRouteLayerToggle('routes');
|
||||||
// Re-establish event handlers when routes are manually added
|
// Re-establish event handlers when routes are manually added
|
||||||
|
|
@ -518,6 +521,9 @@ export default class extends BaseController {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.map.on('overlayremove', (event) => {
|
this.map.on('overlayremove', (event) => {
|
||||||
|
// Save enabled layers whenever a layer is removed
|
||||||
|
this.saveEnabledLayers();
|
||||||
|
|
||||||
if (event.name === 'Routes' || event.name === 'Tracks') {
|
if (event.name === 'Routes' || event.name === 'Tracks') {
|
||||||
// Don't auto-switch when layers are manually turned off
|
// Don't auto-switch when layers are manually turned off
|
||||||
// Just update the radio button state to reflect current visibility
|
// Just update the radio button state to reflect current visibility
|
||||||
|
|
@ -551,9 +557,12 @@ export default class extends BaseController {
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePreferredBaseLayer(selectedLayerName) {
|
updatePreferredBaseLayer(selectedLayerName) {
|
||||||
fetch(`/api/v1/settings?api_key=${this.apiKey}`, {
|
fetch('/api/v1/settings', {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${this.apiKey}`
|
||||||
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
settings: {
|
settings: {
|
||||||
preferred_map_layer: selectedLayerName
|
preferred_map_layer: selectedLayerName
|
||||||
|
|
@ -570,6 +579,71 @@ export default class extends BaseController {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
saveEnabledLayers() {
|
||||||
|
const enabledLayers = [];
|
||||||
|
const layerNames = [
|
||||||
|
'Points', 'Routes', 'Tracks', 'Heatmap', 'Fog of War',
|
||||||
|
'Scratch map', 'Areas', 'Photos', 'Suggested Visits', 'Confirmed Visits'
|
||||||
|
];
|
||||||
|
|
||||||
|
const controlsLayer = {
|
||||||
|
'Points': this.markersLayer,
|
||||||
|
'Routes': this.polylinesLayer,
|
||||||
|
'Tracks': this.tracksLayer,
|
||||||
|
'Heatmap': this.heatmapLayer,
|
||||||
|
'Fog of War': this.fogOverlay,
|
||||||
|
'Scratch map': this.scratchLayerManager?.getLayer(),
|
||||||
|
'Areas': this.areasLayer,
|
||||||
|
'Photos': this.photoMarkers,
|
||||||
|
'Suggested Visits': this.visitsManager?.getVisitCirclesLayer(),
|
||||||
|
'Confirmed Visits': this.visitsManager?.getConfirmedVisitCirclesLayer()
|
||||||
|
};
|
||||||
|
|
||||||
|
layerNames.forEach(name => {
|
||||||
|
const layer = controlsLayer[name];
|
||||||
|
if (layer && this.map.hasLayer(layer)) {
|
||||||
|
enabledLayers.push(name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add family member layers
|
||||||
|
if (window.familyController && window.familyController.familyLayers) {
|
||||||
|
Object.keys(window.familyController.familyLayers).forEach(memberName => {
|
||||||
|
const layer = window.familyController.familyLayers[memberName];
|
||||||
|
if (layer && this.map.hasLayer(layer)) {
|
||||||
|
enabledLayers.push(memberName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch('/api/v1/settings', {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${this.apiKey}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
settings: {
|
||||||
|
enabled_map_layers: enabledLayers
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (data.status === 'success') {
|
||||||
|
console.log('Enabled layers saved:', enabledLayers);
|
||||||
|
showFlashMessage('notice', 'Map layer preferences saved');
|
||||||
|
} else {
|
||||||
|
console.error('Failed to save enabled layers:', data.message);
|
||||||
|
showFlashMessage('error', `Failed to save layer preferences: ${data.message}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error saving enabled layers:', error);
|
||||||
|
showFlashMessage('error', 'Error saving layer preferences');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
deletePoint(id, apiKey) {
|
deletePoint(id, apiKey) {
|
||||||
fetch(`/api/v1/points/${id}`, {
|
fetch(`/api/v1/points/${id}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
|
|
@ -910,9 +984,12 @@ export default class extends BaseController {
|
||||||
const opacityValue = event.target.route_opacity.value.replace('%', '');
|
const opacityValue = event.target.route_opacity.value.replace('%', '');
|
||||||
const decimalOpacity = parseFloat(opacityValue) / 100;
|
const decimalOpacity = parseFloat(opacityValue) / 100;
|
||||||
|
|
||||||
fetch(`/api/v1/settings?api_key=${this.apiKey}`, {
|
fetch('/api/v1/settings', {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${this.apiKey}`
|
||||||
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
settings: {
|
settings: {
|
||||||
route_opacity: decimalOpacity.toString(),
|
route_opacity: decimalOpacity.toString(),
|
||||||
|
|
@ -1297,45 +1374,80 @@ export default class extends BaseController {
|
||||||
// Initialize layer visibility based on user settings or defaults
|
// Initialize layer visibility based on user settings or defaults
|
||||||
// This method sets up the initial state of overlay layers
|
// This method sets up the initial state of overlay layers
|
||||||
|
|
||||||
// Note: Don't automatically add layers to map here - let the layer control and user preferences handle it
|
// Get enabled layers from user settings
|
||||||
// The layer control will manage which layers are visible based on user interaction
|
const enabledLayers = this.userSettings.enabled_map_layers || ['Points', 'Routes', 'Heatmap'];
|
||||||
|
console.log('Initializing layers from settings:', enabledLayers);
|
||||||
|
|
||||||
// Initialize photos layer if user wants it visible
|
const controlsLayer = {
|
||||||
if (this.userSettings.photos_enabled) {
|
'Points': this.markersLayer,
|
||||||
console.log('Photos layer enabled via user settings');
|
'Routes': this.polylinesLayer,
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
'Tracks': this.tracksLayer,
|
||||||
const startDate = urlParams.get('start_at') || new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
|
'Heatmap': this.heatmapLayer,
|
||||||
const endDate = urlParams.get('end_at') || new Date().toISOString();
|
'Fog of War': this.fogOverlay,
|
||||||
|
'Scratch map': this.scratchLayerManager?.getLayer(),
|
||||||
|
'Areas': this.areasLayer,
|
||||||
|
'Photos': this.photoMarkers,
|
||||||
|
'Suggested Visits': this.visitsManager?.getVisitCirclesLayer(),
|
||||||
|
'Confirmed Visits': this.visitsManager?.getConfirmedVisitCirclesLayer()
|
||||||
|
};
|
||||||
|
|
||||||
console.log('Auto-fetching photos for date range:', { startDate, endDate });
|
// Add family member layers if available
|
||||||
fetchAndDisplayPhotos({
|
if (window.familyController && window.familyController.familyLayers) {
|
||||||
map: this.map,
|
Object.entries(window.familyController.familyLayers).forEach(([memberName, layer]) => {
|
||||||
photoMarkers: this.photoMarkers,
|
controlsLayer[memberName] = layer;
|
||||||
apiKey: this.apiKey,
|
|
||||||
startDate: startDate,
|
|
||||||
endDate: endDate,
|
|
||||||
userSettings: this.userSettings
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize fog of war if enabled in settings
|
// Apply saved layer preferences
|
||||||
if (this.userSettings.fog_of_war_enabled) {
|
Object.entries(controlsLayer).forEach(([name, layer]) => {
|
||||||
this.updateFog(this.markers, this.clearFogRadius, this.fogLineThreshold);
|
if (!layer) return;
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize visits manager functionality
|
const shouldBeEnabled = enabledLayers.includes(name);
|
||||||
// Check if any visits layers are enabled by default and load data
|
const isCurrentlyEnabled = this.map.hasLayer(layer);
|
||||||
if (this.visitsManager && typeof this.visitsManager.fetchAndDisplayVisits === 'function') {
|
|
||||||
// Check if confirmed visits layer is enabled by default (it's added to map in constructor)
|
|
||||||
const confirmedVisitsEnabled = this.map.hasLayer(this.visitsManager.getConfirmedVisitCirclesLayer());
|
|
||||||
|
|
||||||
console.log('Visits initialization - confirmedVisitsEnabled:', confirmedVisitsEnabled);
|
if (shouldBeEnabled && !isCurrentlyEnabled) {
|
||||||
|
// Add layer to map
|
||||||
|
layer.addTo(this.map);
|
||||||
|
console.log(`Enabled layer: ${name}`);
|
||||||
|
|
||||||
if (confirmedVisitsEnabled) {
|
// Trigger special initialization for certain layers
|
||||||
console.log('Confirmed visits layer enabled by default - fetching visits data');
|
if (name === 'Photos') {
|
||||||
this.visitsManager.fetchAndDisplayVisits();
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const startDate = urlParams.get('start_at') || new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
|
||||||
|
const endDate = urlParams.get('end_at') || new Date().toISOString();
|
||||||
|
fetchAndDisplayPhotos({
|
||||||
|
map: this.map,
|
||||||
|
photoMarkers: this.photoMarkers,
|
||||||
|
apiKey: this.apiKey,
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate,
|
||||||
|
userSettings: this.userSettings
|
||||||
|
});
|
||||||
|
} else if (name === 'Fog of War') {
|
||||||
|
this.updateFog(this.markers, this.clearFogRadius, this.fogLineThreshold);
|
||||||
|
} else if (name === 'Suggested Visits' || name === 'Confirmed Visits') {
|
||||||
|
if (this.visitsManager && typeof this.visitsManager.fetchAndDisplayVisits === 'function') {
|
||||||
|
this.visitsManager.fetchAndDisplayVisits();
|
||||||
|
}
|
||||||
|
} else if (name === 'Scratch map') {
|
||||||
|
if (this.scratchLayerManager) {
|
||||||
|
this.scratchLayerManager.addToMap();
|
||||||
|
}
|
||||||
|
} else if (name === 'Routes') {
|
||||||
|
// Re-establish event handlers for routes layer
|
||||||
|
reestablishPolylineEventHandlers(this.polylinesLayer, this.map, this.userSettings, this.distanceUnit);
|
||||||
|
} else if (name === 'Areas') {
|
||||||
|
// Show draw control when Areas layer is enabled
|
||||||
|
if (this.drawControl && !this.map._controlCorners.topleft.querySelector('.leaflet-draw')) {
|
||||||
|
this.map.addControl(this.drawControl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!shouldBeEnabled && isCurrentlyEnabled) {
|
||||||
|
// Remove layer from map
|
||||||
|
this.map.removeLayer(layer);
|
||||||
|
console.log(`Disabled layer: ${name}`);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleRightPanel() {
|
toggleRightPanel() {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,8 @@ class Users::SafeSettings
|
||||||
'photoprism_url' => nil,
|
'photoprism_url' => nil,
|
||||||
'photoprism_api_key' => nil,
|
'photoprism_api_key' => nil,
|
||||||
'maps' => { 'distance_unit' => 'km' },
|
'maps' => { 'distance_unit' => 'km' },
|
||||||
'visits_suggestions_enabled' => 'true'
|
'visits_suggestions_enabled' => 'true',
|
||||||
|
'enabled_map_layers' => ['Routes', 'Heatmap']
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
def initialize(settings = {})
|
def initialize(settings = {})
|
||||||
|
|
@ -47,7 +48,8 @@ class Users::SafeSettings
|
||||||
distance_unit: distance_unit,
|
distance_unit: distance_unit,
|
||||||
visits_suggestions_enabled: visits_suggestions_enabled?,
|
visits_suggestions_enabled: visits_suggestions_enabled?,
|
||||||
speed_color_scale: speed_color_scale,
|
speed_color_scale: speed_color_scale,
|
||||||
fog_of_war_threshold: fog_of_war_threshold
|
fog_of_war_threshold: fog_of_war_threshold,
|
||||||
|
enabled_map_layers: enabled_map_layers
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
# rubocop:enable Metrics/MethodLength
|
# rubocop:enable Metrics/MethodLength
|
||||||
|
|
@ -127,4 +129,8 @@ class Users::SafeSettings
|
||||||
def fog_of_war_threshold
|
def fog_of_war_threshold
|
||||||
settings['fog_of_war_threshold']
|
settings['fog_of_war_threshold']
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def enabled_map_layers
|
||||||
|
settings['enabled_map_layers']
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,8 @@ RSpec.describe Users::SafeSettings do
|
||||||
distance_unit: 'km',
|
distance_unit: 'km',
|
||||||
visits_suggestions_enabled: true,
|
visits_suggestions_enabled: true,
|
||||||
speed_color_scale: nil,
|
speed_color_scale: nil,
|
||||||
fog_of_war_threshold: nil
|
fog_of_war_threshold: nil,
|
||||||
|
enabled_map_layers: ['Routes', 'Heatmap']
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
@ -53,7 +54,8 @@ RSpec.describe Users::SafeSettings do
|
||||||
'photoprism_url' => 'https://photoprism.example.com',
|
'photoprism_url' => 'https://photoprism.example.com',
|
||||||
'photoprism_api_key' => 'photoprism-key',
|
'photoprism_api_key' => 'photoprism-key',
|
||||||
'maps' => { 'name' => 'custom', 'url' => 'https://custom.example.com' },
|
'maps' => { 'name' => 'custom', 'url' => 'https://custom.example.com' },
|
||||||
'visits_suggestions_enabled' => false
|
'visits_suggestions_enabled' => false,
|
||||||
|
'enabled_map_layers' => ['Points', 'Routes', 'Areas', 'Photos']
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
let(:safe_settings) { described_class.new(settings) }
|
let(:safe_settings) { described_class.new(settings) }
|
||||||
|
|
@ -76,7 +78,8 @@ RSpec.describe Users::SafeSettings do
|
||||||
"photoprism_url" => "https://photoprism.example.com",
|
"photoprism_url" => "https://photoprism.example.com",
|
||||||
"photoprism_api_key" => "photoprism-key",
|
"photoprism_api_key" => "photoprism-key",
|
||||||
"maps" => { "name" => "custom", "url" => "https://custom.example.com" },
|
"maps" => { "name" => "custom", "url" => "https://custom.example.com" },
|
||||||
"visits_suggestions_enabled" => false
|
"visits_suggestions_enabled" => false,
|
||||||
|
"enabled_map_layers" => ['Points', 'Routes', 'Areas', 'Photos']
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
@ -102,7 +105,8 @@ RSpec.describe Users::SafeSettings do
|
||||||
distance_unit: nil,
|
distance_unit: nil,
|
||||||
visits_suggestions_enabled: false,
|
visits_suggestions_enabled: false,
|
||||||
speed_color_scale: nil,
|
speed_color_scale: nil,
|
||||||
fog_of_war_threshold: nil
|
fog_of_war_threshold: nil,
|
||||||
|
enabled_map_layers: ['Points', 'Routes', 'Areas', 'Photos']
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
@ -132,6 +136,7 @@ RSpec.describe Users::SafeSettings do
|
||||||
expect(safe_settings.photoprism_api_key).to be_nil
|
expect(safe_settings.photoprism_api_key).to be_nil
|
||||||
expect(safe_settings.maps).to eq({ "distance_unit" => "km" })
|
expect(safe_settings.maps).to eq({ "distance_unit" => "km" })
|
||||||
expect(safe_settings.visits_suggestions_enabled?).to be true
|
expect(safe_settings.visits_suggestions_enabled?).to be true
|
||||||
|
expect(safe_settings.enabled_map_layers).to eq(['Routes', 'Heatmap'])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -153,7 +158,8 @@ RSpec.describe Users::SafeSettings do
|
||||||
'photoprism_url' => 'https://photoprism.example.com',
|
'photoprism_url' => 'https://photoprism.example.com',
|
||||||
'photoprism_api_key' => 'photoprism-key',
|
'photoprism_api_key' => 'photoprism-key',
|
||||||
'maps' => { 'name' => 'custom', 'url' => 'https://custom.example.com' },
|
'maps' => { 'name' => 'custom', 'url' => 'https://custom.example.com' },
|
||||||
'visits_suggestions_enabled' => false
|
'visits_suggestions_enabled' => false,
|
||||||
|
'enabled_map_layers' => ['Points', 'Tracks', 'Fog of War', 'Suggested Visits']
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -174,6 +180,7 @@ RSpec.describe Users::SafeSettings do
|
||||||
expect(safe_settings.photoprism_api_key).to eq('photoprism-key')
|
expect(safe_settings.photoprism_api_key).to eq('photoprism-key')
|
||||||
expect(safe_settings.maps).to eq({ 'name' => 'custom', 'url' => 'https://custom.example.com' })
|
expect(safe_settings.maps).to eq({ 'name' => 'custom', 'url' => 'https://custom.example.com' })
|
||||||
expect(safe_settings.visits_suggestions_enabled?).to be false
|
expect(safe_settings.visits_suggestions_enabled?).to be false
|
||||||
|
expect(safe_settings.enabled_map_layers).to eq(['Points', 'Tracks', 'Fog of War', 'Suggested Visits'])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue