Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
7 changes: 7 additions & 0 deletions src/web/Jordnaer/Components/App.razor
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@
@* Leaflet.js for map search functionality *@
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="" />
@* Leaflet.markercluster for group location clustering *@
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.css"
integrity="sha384-pmjIAcz2bAn0xukfxADbZIb3t8oRT9Sv0rvO+BR5Csr6Dhqq+nZs59P0pPKQJkEV" crossorigin="" />
<link rel="stylesheet" href="@Assets["css/marker-cluster.css"]" />

@* Cropper.js for image cropping functionality *@
<link rel="stylesheet" href="@Assets["lib/cropperjs/cropper.min.css"]" />
Expand Down Expand Up @@ -75,6 +79,9 @@
@* Leaflet.js for map search functionality *@
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
@* Leaflet.markercluster for group location clustering *@
<script src="https://unpkg.com/leaflet.markercluster@1.5.3/dist/leaflet.markercluster.js"
integrity="sha384-eXVCORTRlv4FUUgS/xmOyr66XBVraen8ATNLMESp92FKXLAMiKkerixTiBvXriZr" crossorigin=""></script>
<script src="@Assets["js/leaflet-interop.js"]"></script>

@* Cropper.js for image cropping functionality *@
Expand Down
39 changes: 38 additions & 1 deletion src/web/Jordnaer/Features/GroupSearch/GroupSearchForm.razor
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
@* New map-based search experience *@
<MudItem xs="12">
<MapSearchFilter @ref="_mapSearchFilter" RadiusKm="@(Filter.WithinRadiusKilometers ?? 10)"
RadiusKmChanged="OnRadiusChanged" OnLocationSearchChanged="OnLocationSearchChanged" />
RadiusKmChanged="OnRadiusChanged" OnLocationSearchChanged="OnLocationSearchChanged"
Groups="@_groupMarkers" />
</MudItem>
}
else
Expand Down Expand Up @@ -82,11 +83,47 @@
[Parameter]
public required EventCallback OnValidSubmit { get; set; }

/// <summary>
/// Groups to display as markers on the map
/// </summary>
[Parameter]
public IEnumerable<GroupSlim>? Groups { get; set; }

private static readonly GroupSearchFilter DefaultFilter = new();

private bool _recentlyClearedForm = false;
private bool _disableSmartCompletionForZipCode => _recentlyClearedForm || Filter != DefaultFilter;

private List<GroupMarkerData>? _groupMarkers;
private IEnumerable<GroupSlim>? _previousGroups;

protected override void OnParametersSet()
{
// Early return if Groups reference hasn't changed
if (ReferenceEquals(Groups, _previousGroups))
{
return;
}

// Convert GroupSlim to GroupMarkerData for the map and materialize
_groupMarkers = Groups?
.Where(g => g.Latitude.HasValue && g.Longitude.HasValue)
.Select(g => new GroupMarkerData
{
Id = g.Id,
Name = g.Name,
ProfilePictureUrl = g.ProfilePictureUrl,
ShortDescription = g.ShortDescription,
ZipCode = g.ZipCode,
City = g.City,
Latitude = g.Latitude!.Value,
Longitude = g.Longitude!.Value
})
.ToList();
Comment thread
NielsPilgaard marked this conversation as resolved.

_previousGroups = Groups;
}

protected override async Task OnInitializedAsync()
{
_mapSearchEnabled = await FeatureManager.IsEnabledAsync(FeatureFlags.MapSearch);
Expand Down
39 changes: 39 additions & 0 deletions src/web/Jordnaer/Features/Map/LeafletMap.razor
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,45 @@
await LeafletMapInterop.RemoveSearchRadiusAsync(MapId);
}

/// <summary>
/// Updates group markers on the map with clustering
/// </summary>
public async Task UpdateGroupMarkersAsync(IEnumerable<GroupMarkerData> groups)
{
if (!_isInitialized)
{
return;
}

await LeafletMapInterop.UpdateGroupMarkersAsync(MapId, groups);
}

/// <summary>
/// Clears all group markers from the map
/// </summary>
public async Task ClearGroupMarkersAsync()
{
if (!_isInitialized)
{
return;
}

await LeafletMapInterop.ClearGroupMarkersAsync(MapId);
}

/// <summary>
/// Fits the map view to show all group markers
/// </summary>
public async Task FitBoundsToMarkersAsync(int padding = 50)
{
if (!_isInitialized)
{
return;
}

await LeafletMapInterop.FitBoundsToMarkersAsync(MapId, padding);
}
Comment thread
NielsPilgaard marked this conversation as resolved.
Outdated

public async ValueTask DisposeAsync()
{
if (_isInitialized)
Expand Down
49 changes: 49 additions & 0 deletions src/web/Jordnaer/Features/Map/LeafletMapInterop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,36 @@ public interface ILeafletMapInterop
/// Disposes of a map instance
/// </summary>
Task<bool> DisposeMapAsync(string mapId);

/// <summary>
/// Updates group markers on the map with clustering support
/// </summary>
Task<bool> UpdateGroupMarkersAsync(string mapId, IEnumerable<GroupMarkerData> groups);

/// <summary>
/// Clears all group markers from the map
/// </summary>
Task<bool> ClearGroupMarkersAsync(string mapId);

/// <summary>
/// Fits the map view to show all group markers
/// </summary>
Task<bool> FitBoundsToMarkersAsync(string mapId, int padding = 50);
}

/// <summary>
/// Data transfer object for group marker information
/// </summary>
public record GroupMarkerData
{
public required Guid Id { get; init; }
public required string Name { get; init; }
public string? ProfilePictureUrl { get; init; }
public string? ShortDescription { get; init; }
public int? ZipCode { get; init; }
public string? City { get; init; }
public required double Latitude { get; init; }
public required double Longitude { get; init; }
}

public class LeafletMapInterop(IJSRuntime jsRuntime) : ILeafletMapInterop
Expand Down Expand Up @@ -103,4 +133,23 @@ public async Task<bool> DisposeMapAsync(string mapId)
return await _jsRuntime.InvokeVoidAsyncWithErrorHandling(
"leafletInterop.disposeMap", mapId);
}

public async Task<bool> UpdateGroupMarkersAsync(string mapId, IEnumerable<GroupMarkerData> groups)
{
var materialized = groups?.ToList() ?? [];
return await _jsRuntime.InvokeVoidAsyncWithErrorHandling(
"leafletInterop.updateGroupMarkers", mapId, materialized);
}

public async Task<bool> ClearGroupMarkersAsync(string mapId)
{
return await _jsRuntime.InvokeVoidAsyncWithErrorHandling(
"leafletInterop.clearGroupMarkers", mapId);
}

public async Task<bool> FitBoundsToMarkersAsync(string mapId, int padding = 50)
{
return await _jsRuntime.InvokeVoidAsyncWithErrorHandling(
"leafletInterop.fitBoundsToMarkers", mapId, padding);
}
}
62 changes: 62 additions & 0 deletions src/web/Jordnaer/Features/Map/MapSearchFilter.razor
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
private string? _addressText;
private bool _isLoadingLocation = false;
private bool _hasAttemptedGeolocation = false;
private IEnumerable<GroupMarkerData>? _previousGroups;

[Parameter]
public string MapId { get; set; } = $"map-search-{Guid.NewGuid()}";
Expand All @@ -80,6 +81,12 @@
set;
}

/// <summary>
/// Group data to display as markers on the map
/// </summary>
[Parameter]
public IEnumerable<GroupMarkerData>? Groups { get; set; }

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && !_hasAttemptedGeolocation)
Expand All @@ -97,6 +104,13 @@
if (_mapComponent?.IsInitialized == true)
{
await TryGetUserLocation();

// Push initial group markers after map is initialized
if (Groups is not null)
{
_previousGroups = Groups;
await _mapComponent.UpdateGroupMarkersAsync(Groups);
}
}
}
}
Expand Down Expand Up @@ -279,4 +293,52 @@
}
}
}

/// <summary>
/// Public method to update group markers on the map
/// </summary>
public async Task UpdateGroupMarkersAsync(IEnumerable<GroupMarkerData> groups)
{
if (_mapComponent?.IsInitialized == true)
{
await _mapComponent.UpdateGroupMarkersAsync(groups);
}
}

/// <summary>
/// Public method to clear group markers from the map
/// </summary>
public async Task ClearGroupMarkersAsync()
{
if (_mapComponent?.IsInitialized == true)
{
await _mapComponent.ClearGroupMarkersAsync();
}
}

protected override async Task OnParametersSetAsync()
{
// Update group markers only when Groups parameter actually changes
if (_mapComponent?.IsInitialized == true)
{
// Handle when groups are cleared (set to null or empty)
if (Groups is null || !Groups.Any())
{
if (_previousGroups is not null)
{
_previousGroups = null;
await _mapComponent.ClearGroupMarkersAsync();
}
}
// Handle when groups are updated
else if (Groups != _previousGroups)
{
_previousGroups = Groups;
await _mapComponent.UpdateGroupMarkersAsync(Groups);

// Automatically fit the map to show all markers
await _mapComponent.FitBoundsToMarkersAsync();
}
}
}
}
2 changes: 1 addition & 1 deletion src/web/Jordnaer/Pages/GroupSearch/GroupSearch.razor
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

<MudLoading @bind-Loading="_isSearching" Darken Overlap>

<GroupSearchForm OnValidSubmit="@Search" @bind-Filter="_filter" />
<GroupSearchForm OnValidSubmit="@Search" @bind-Filter="_filter" Groups="@_searchResult.Groups" />

@if (!_hasSearched)
{
Expand Down
Loading
Loading