Skip to content

Commit

Permalink
Merge pull request #123 from DennisvHest/72-ctrl-and-shift-clicking-t…
Browse files Browse the repository at this point in the history
…o-select-maps

72 ctrl and shift clicking to select maps
  • Loading branch information
DennisvHest committed Jan 28, 2024
2 parents 1b8c27c + b588d44 commit 84dabf1
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 121 deletions.
5 changes: 4 additions & 1 deletion MapMaven.Core/Services/Interfaces/IMapService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@ public interface IMapService
Task RefreshDataAsync(bool reloadMapAndLeaderboardInfo = false, bool forceReloadCachedData = false);
void RemoveMapFilter(MapFilter filter);
void ResetSelectedMaps();
void SelectMaps(IEnumerable<Map> selectedMaps);
void SetSelectedMaps(IEnumerable<Map> selectedMaps);
void SetSelectable(bool selectable);
void SetSelectedMaps(HashSet<Map> selectedMaps);
void ToggleMapSelected(Map map);
void SelectMaps(IEnumerable<Map> maps);
bool MapIsSelected(Map map);
}
}
28 changes: 26 additions & 2 deletions MapMaven.Core/Services/MapService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,31 @@ public void ClearMapFilters()

public void CancelSelection() => SetSelectable(false);

public void SelectMaps(IEnumerable<Map> selectedMaps)
public void SetSelectedMaps(IEnumerable<Map> selectedMaps) => SetSelectedMaps(selectedMaps.ToHashSet());

public void ToggleMapSelected(Map map)
{
_selectedMaps.OnNext(selectedMaps.ToHashSet());
var selectedMaps = _selectedMaps.Value;

if (selectedMaps.Contains(map))
{
selectedMaps.Remove(map);
}
else
{
selectedMaps.Add(map);
}

SetSelectedMaps(selectedMaps);
}

public void SelectMaps(IEnumerable<Map> maps)
{
var selectedMaps = _selectedMaps.Value;

selectedMaps.UnionWith(maps);

SetSelectedMaps(selectedMaps);
}

public void SetSelectable(bool selectable)
Expand All @@ -219,6 +241,8 @@ public void SetSelectable(bool selectable)
ClearSelectedMaps();
}

public bool MapIsSelected(Map map) => _selectedMaps.Value.Contains(map);

public async Task<Map> GetMapDetails(Map map)
{
var beatMap = await _beatSaver.BeatmapByHash(map.Hash);
Expand Down
232 changes: 118 additions & 114 deletions MapMaven/Components/Maps/MapBrowser.razor
Original file line number Diff line number Diff line change
Expand Up @@ -3,123 +3,127 @@
@using MapMaven.Core.Utilities.Scoresaber
@using MapMaven.Core.Models.DynamicPlaylists

<MudDataGrid @ref="TableRef"
T="Map"
Items="Maps"
Height="@Height"
Class="full-height full-width background-transparent"
Loading="LoadingMapInfo"
QuickFilter="Filter"
FixedHeader="true"
MultiSelection="true"
SortMode="SortMode.Single"
SelectedItems="SelectedMaps"
SelectOnRowClick="false"
SelectedItemsChanged="OnSelectedItemsChanged"
RowsPerPage="25"
Style="@Style"
RowStyle="height: 88px;">
<ToolBarContent>
<MudGrid>
<MudItem xs="12" Class="d-flex align-center">
@if (SelectedPlaylist != null)
{
<MudChip Variant="Variant.Outlined">
@if (SelectedPlaylist.DynamicPlaylistConfiguration?.MapPool == MapPool.Improvement && SelectedPlaylist.DynamicPlaylistConfiguration?.LeaderboardProvider is not null)
{
<img src="@LeaderboardUtility.GetLogoPath(SelectedPlaylist.DynamicPlaylistConfiguration.LeaderboardProvider.Value)" class="mr-1" style="width: 24px;" />
}
@SelectedPlaylist.Title
</MudChip>
}
@foreach (var mapFilter in MapFilters.Where(f => f.Visible))
{
<MudChip OnClose="() => RemoveMapFilter(mapFilter)">@mapFilter.Name</MudChip>
}
<MudTextField @bind-Value="SearchString"
DebounceInterval="300"
Immediate="true"
Clearable="true"
Placeholder="Search map name, song author, map author..."
Variant="Variant.Outlined"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Search"
IconSize="Size.Small"
Class="mt-0">
</MudTextField>
@if (!RankedMaps)
{
<MudTooltip Text="Bulk&nbsp;edit">
<MudIconButton Icon="@(Selectable ? Icons.Material.Filled.LibraryAddCheck : Icons.Material.Outlined.LibraryAddCheck)" Class="mx-5" OnClick="ToggleSelectable" />
</MudTooltip>
}
</MudItem>
</MudGrid>
</ToolBarContent>
<LoadingContent>
@if (InitialMapLoad)
{
<MudProgressLinear Color="Color.Primary" Indeterminate="true" Style="position: absolute; top: 0;" />
<MudIcon Icon="@Icons.Material.Filled.ManageSearch" Style="vertical-align: middle; margin-bottom: 4px;"></MudIcon> @:Loading maps for first use. Depdending on the number of maps, this might take a minute.
}
else if (TableRef.GetFilteredItemsCount() == 0 && SelectedPlaylist?.IsDynamicPlaylist == true && string.IsNullOrEmpty(SearchString))
{
<MudProgressLinear Color="Color.Primary" Indeterminate="true" Style="position: absolute; top: 0;" />
<MudIcon Icon="@Icons.Material.Filled.ManageSearch" Style="vertical-align: middle; margin-bottom: 4px;"></MudIcon> @:Nothing yet. Dynamic playlist is still being generated. Maps will appear once all the maps have been downloaded.
}
</LoadingContent>
<Columns>
@if (RowContent is not null)
{
@RowContent(new Core.Models.MapRowContext
{
FilteredMaps = TableRef.FilteredItems,
Map = null
})
}
</Columns>
<PagerContent>
@if (!RankedMaps && Selectable)
{
<MudPaper Elevation="25" Class="bulk-actions-toolbar">
<MudToolBar Class="pr-0 border-l-2">
<MudChip Variant="Variant.Outlined">@SelectedMaps.Count maps</MudChip>
<MudButton Variant="Variant.Text" Class="px-5 full-height" StartIcon="@Icons.Material.Filled.Close" OnClick="CancelSelection">
Cancel selection
</MudButton>
<MudDivider Vertical="true" FlexItem="true" />
<MudButton Variant="Variant.Text" DisableElevation="true" Class="px-5 full-height" StartIcon="@Icons.Material.Filled.PlaylistAdd" Disabled="!SelectedMaps.Any()" OnClick="AddSelectedMapsToPlaylist">
Add to playlist
</MudButton>
@if (SelectedPlaylist is not null && !SelectedPlaylist.IsDynamicPlaylist)
<div @onkeydown="OnKeyDown" class="d-inline" tabindex="-1">
<MudDataGrid @ref="TableRef"
T="Map"
Items="Maps"
Height="@Height"
Class="map-browser full-height full-width background-transparent"
Loading="LoadingMapInfo"
QuickFilter="Filter"
FixedHeader="true"
MultiSelection="true"
SortMode="SortMode.Single"
SelectedItems="SelectedMaps"
SelectOnRowClick="false"
SelectedItemsChanged="OnSelectedItemsChanged"
RowClick="OnRowClick"
RowsPerPage="25"
Style="@Style"
RowStyle="height: 88px;"
RowClassFunc="RowClassFunc">
<ToolBarContent>
<MudGrid>
<MudItem xs="12" Class="d-flex align-center">
@if (SelectedPlaylist != null)
{
<MudButton Variant="Variant.Text" DisableElevation="true" Class="px-5 full-height" StartIcon="@Icons.Material.Filled.PlaylistRemove" Disabled="!SelectedMaps.Any()" OnClick="RemoveSelectedMapsFromSelectedPlaylist">
Remove from playlist
</MudButton>
<MudChip Variant="Variant.Outlined">
@if (SelectedPlaylist.DynamicPlaylistConfiguration?.MapPool == MapPool.Improvement && SelectedPlaylist.DynamicPlaylistConfiguration?.LeaderboardProvider is not null)
{
<img src="@LeaderboardUtility.GetLogoPath(SelectedPlaylist.DynamicPlaylistConfiguration.LeaderboardProvider.Value)" class="mr-1" style="width: 24px;" />
}
@SelectedPlaylist.Title
</MudChip>
}
@if (SelectedPlaylist is null || !SelectedPlaylist.IsDynamicPlaylist)
@foreach (var mapFilter in MapFilters.Where(f => f.Visible))
{
<MudButton Variant="Variant.Text" Color="Color.Secondary" Class="px-5 full-height" DisableElevation="true" StartIcon="@Icons.Material.Filled.Delete" Disabled="!SelectedMaps.Any()" OnClick="DeleteSelectedMaps">
Delete
</MudButton>
<MudChip OnClose="() => RemoveMapFilter(mapFilter)">@mapFilter.Name</MudChip>
}
</MudToolBar>
</MudPaper>
}
<div class="absolute full-height d-flex flex-row align-center px-3" style="height: 60px; z-index: 999">
@if (DifficultyModifier != 0)
<MudTextField @bind-Value="SearchString"
DebounceInterval="300"
Immediate="true"
Clearable="true"
Placeholder="Search map name, song author, map author..."
Variant="Variant.Outlined"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Search"
IconSize="Size.Small"
Class="mt-0">
</MudTextField>
@if (!RankedMaps)
{
<MudTooltip Text="Bulk&nbsp;edit">
<MudIconButton Icon="@(Selectable ? Icons.Material.Filled.LibraryAddCheck : Icons.Material.Outlined.LibraryAddCheck)" Class="mx-5" OnClick="ToggleSelectable" />
</MudTooltip>
}
</MudItem>
</MudGrid>
</ToolBarContent>
<LoadingContent>
@if (InitialMapLoad)
{
var chipColor = DifficultyModifier >= 0 ? Colors.Purple.Darken3 : Colors.Green.Darken3;
var chipStyle = $"border: 1px solid {chipColor}";

<MudTooltip Text="Difficulty modifier">
<MudChip Variant="Variant.Outlined" Style="@chipStyle">
<MudIcon Icon="@Icons.Filled.Speed" Class="mr-2"></MudIcon>
@(DifficultyModifier >= 0 ? "+" : string.Empty)@DifficultyModifier%
</MudChip>
</MudTooltip>
<MudProgressLinear Color="Color.Primary" Indeterminate="true" Style="position: absolute; top: 0;" />
<MudIcon Icon="@Icons.Material.Filled.ManageSearch" Style="vertical-align: middle; margin-bottom: 4px;"></MudIcon> @:Loading maps for first use. Depdending on the number of maps, this might take a minute.
}
</div>
<MudDataGridPager T="Map" />
</PagerContent>
</MudDataGrid>
else if (TableRef.GetFilteredItemsCount() == 0 && SelectedPlaylist?.IsDynamicPlaylist == true && string.IsNullOrEmpty(SearchString))
{
<MudProgressLinear Color="Color.Primary" Indeterminate="true" Style="position: absolute; top: 0;" />
<MudIcon Icon="@Icons.Material.Filled.ManageSearch" Style="vertical-align: middle; margin-bottom: 4px;"></MudIcon> @:Nothing yet. Dynamic playlist is still being generated. Maps will appear once all the maps have been downloaded.
}
</LoadingContent>
<Columns>
@if (RowContent is not null)
{
@RowContent(new Core.Models.MapRowContext
{
FilteredMaps = TableRef.FilteredItems,
Map = null
})
}
</Columns>
<PagerContent>
@if (!RankedMaps && Selectable)
{
<MudPaper Elevation="25" Class="bulk-actions-toolbar">
<MudToolBar Class="pr-0 border-l-2">
<MudChip Variant="Variant.Outlined">@SelectedMaps.Count maps</MudChip>
<MudButton Variant="Variant.Text" Class="px-5 full-height" StartIcon="@Icons.Material.Filled.Close" OnClick="CancelSelection">
Cancel selection
</MudButton>
<MudDivider Vertical="true" FlexItem="true" />
<MudButton Variant="Variant.Text" DisableElevation="true" Class="px-5 full-height" StartIcon="@Icons.Material.Filled.PlaylistAdd" Disabled="!SelectedMaps.Any()" OnClick="AddSelectedMapsToPlaylist">
Add to playlist
</MudButton>
@if (SelectedPlaylist is not null && !SelectedPlaylist.IsDynamicPlaylist)
{
<MudButton Variant="Variant.Text" DisableElevation="true" Class="px-5 full-height" StartIcon="@Icons.Material.Filled.PlaylistRemove" Disabled="!SelectedMaps.Any()" OnClick="RemoveSelectedMapsFromSelectedPlaylist">
Remove from playlist
</MudButton>
}
@if (SelectedPlaylist is null || !SelectedPlaylist.IsDynamicPlaylist)
{
<MudButton Variant="Variant.Text" Color="Color.Secondary" Class="px-5 full-height" DisableElevation="true" StartIcon="@Icons.Material.Filled.Delete" Disabled="!SelectedMaps.Any()" OnClick="DeleteSelectedMaps">
Delete
</MudButton>
}
</MudToolBar>
</MudPaper>
}
<div class="absolute full-height d-flex flex-row align-center px-3" style="height: 60px; z-index: 999">
@if (DifficultyModifier != 0)
{
var chipColor = DifficultyModifier >= 0 ? Colors.Purple.Darken3 : Colors.Green.Darken3;
var chipStyle = $"border: 1px solid {chipColor}";

<MudTooltip Text="Difficulty modifier">
<MudChip Variant="Variant.Outlined" Style="@chipStyle">
<MudIcon Icon="@Icons.Filled.Speed" Class="mr-2"></MudIcon>
@(DifficultyModifier >= 0 ? "+" : string.Empty)@DifficultyModifier%
</MudChip>
</MudTooltip>
}
</div>
<MudDataGridPager T="Map" />
</PagerContent>
</MudDataGrid>
</div>
71 changes: 70 additions & 1 deletion MapMaven/Components/Maps/MapBrowser.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using MapMaven.Components.Shared;
using MapMaven.Components.Playlists;
using MapMaven.Core.Services.Leaderboards.ScoreEstimation;
using KeyboardEventArgs = Microsoft.AspNetCore.Components.Web.KeyboardEventArgs;

namespace MapMaven.Components.Maps
{
Expand Down Expand Up @@ -78,6 +79,8 @@ public partial class MapBrowser : IDisposable

private int DifficultyModifier = 0;

private int? LastSelectedRowIndex = null;

protected override void OnInitialized()
{
NavigationManager.LocationChanged += LocationChanged;
Expand Down Expand Up @@ -105,7 +108,13 @@ protected override void OnInitialized()
});
SubscribeAndBind(MapService.MapFilters, mapFilters => MapFilters = mapFilters);
SubscribeAndBind(MapService.SelectedMaps, selectedMaps => SelectedMaps = selectedMaps);
SubscribeAndBind(MapService.Selectable, selectable => Selectable = selectable);
SubscribeAndBind(MapService.Selectable, selectable =>
{
Selectable = selectable;
if (!selectable)
LastSelectedRowIndex = null;
});

SubscribeAndBind(ScoreEstimationSettings.DifficultyModifierValue, difficultyModifierValue => DifficultyModifier = difficultyModifierValue);
}
Expand Down Expand Up @@ -244,6 +253,66 @@ async Task RemoveSelectedMapsFromSelectedPlaylist()

public void ToggleSelectable() => MapService.SetSelectable(!Selectable);

public void OnRowClick(DataGridRowClickEventArgs<Map> args)
{
if (args.MouseEventArgs.CtrlKey || args.MouseEventArgs.ShiftKey)
{
if (!Selectable)
ToggleSelectable();

if (args.MouseEventArgs.CtrlKey)
MapService.ToggleMapSelected(args.Item);

if (args.MouseEventArgs.ShiftKey)
{
var lastSelectedRowIndex = LastSelectedRowIndex ?? args.RowIndex;

var mapsToSelect = TableRef.FilteredItems
.Skip(Math.Min(lastSelectedRowIndex, args.RowIndex))
.Take(Math.Abs(args.RowIndex - lastSelectedRowIndex) + 1)
.ToList();

MapService.SelectMaps(mapsToSelect);
}
}

if (Selectable)
LastSelectedRowIndex = args.RowIndex;
}

/// <summary>
/// Keyboard shortcuts
/// </summary>
public async Task OnKeyDown(KeyboardEventArgs args)
{
if (args.Code == "Escape")
MapService.CancelSelection();

if (args.Code == "KeyA" && args.CtrlKey)
{
if (!Selectable)
ToggleSelectable();

MapService.SelectMaps(TableRef.FilteredItems);
}
}

public string RowClassFunc(Map map, int index)
{
if (!Selectable)
return string.Empty;

string classes = string.Empty;

if (MapService.MapIsSelected(map))
classes += "row-selected";

if (index == LastSelectedRowIndex)
classes += " row-selected-active";

return classes;
}

public void Dispose()
{
NavigationManager.LocationChanged -= LocationChanged;
Expand Down
Loading

0 comments on commit 84dabf1

Please sign in to comment.