136 lines
4.4 KiB
Plaintext
136 lines
4.4 KiB
Plaintext
@using Butter.Dtos.Album
|
|
@inject AlbumService albumService
|
|
@inject IJSRuntime JSRuntime
|
|
|
|
<div class="position-relative">
|
|
<input type="text" class="form-control"
|
|
placeholder="@Placeholder"
|
|
value="@Query"
|
|
@ref="albumInputRef"
|
|
@oninput="OnInput"
|
|
@onfocus="OnFocus"
|
|
@onblur="OnBlur"
|
|
@onkeydown="OnKeyDown" />
|
|
@if (showAlbumDropdown) {
|
|
<div class="dropdown-menu show album-dropdown-menu"
|
|
@ref="albumDropdownRef">
|
|
@if (isSearching) {
|
|
<span class="dropdown-item disabled">Searching…</span>
|
|
} else if (Query.Length < 2) {
|
|
<span class="dropdown-item disabled">Type at least 2 characters to search</span>
|
|
} else if (searchResults is null || searchResults.Count == 0) {
|
|
<span class="dropdown-item disabled">No matching albums</span>
|
|
} else {
|
|
@foreach (var album in searchResults) {
|
|
<button type="button"
|
|
class="dropdown-item @(SelectedAlbumId == album.Id ? "active" : "")"
|
|
@onclick="() => SelectAlbum(album)">
|
|
@album.Name
|
|
@if (!string.IsNullOrEmpty(album.PersonName)) {
|
|
<small class="text-muted ms-1">@album.PersonName</small>
|
|
}
|
|
</button>
|
|
}
|
|
}
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
@code {
|
|
/// <summary>
|
|
/// Gets or sets the current search text (two-way bound so the owner can prefill/reset it).
|
|
/// </summary>
|
|
[Parameter] public string Query { get; set; } = "";
|
|
|
|
/// <summary>
|
|
/// Gets or sets the callback fired as <see cref="Query"/> changes.
|
|
/// </summary>
|
|
[Parameter] public EventCallback<string> QueryChanged { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the currently selected album ID, used to highlight the active result.
|
|
/// </summary>
|
|
[Parameter] public Guid? SelectedAlbumId { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the callback fired when an album is selected.
|
|
/// </summary>
|
|
[Parameter] public EventCallback<AlbumPreviewDto> OnAlbumSelected { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the input placeholder text.
|
|
/// </summary>
|
|
[Parameter] public string Placeholder { get; set; } = "Search albums…";
|
|
|
|
bool showAlbumDropdown;
|
|
CancellationTokenSource? blurCts;
|
|
List<AlbumPreviewDto>? searchResults;
|
|
bool isSearching;
|
|
int searchVersion;
|
|
ElementReference albumInputRef;
|
|
ElementReference albumDropdownRef;
|
|
bool pendingPosition;
|
|
|
|
protected override async Task OnAfterRenderAsync(bool firstRender) {
|
|
if (pendingPosition && showAlbumDropdown) {
|
|
pendingPosition = false;
|
|
await JSRuntime.InvokeVoidAsync("masonryObserver.positionDropdown", albumInputRef, albumDropdownRef);
|
|
}
|
|
}
|
|
|
|
void OnFocus() {
|
|
blurCts?.Cancel();
|
|
showAlbumDropdown = true;
|
|
pendingPosition = true;
|
|
if (Query.Length >= 2) {
|
|
_ = Search(Query);
|
|
}
|
|
}
|
|
|
|
async Task OnInput(ChangeEventArgs e) {
|
|
Query = e.Value?.ToString() ?? string.Empty;
|
|
await QueryChanged.InvokeAsync(Query);
|
|
showAlbumDropdown = true;
|
|
pendingPosition = true;
|
|
if (Query.Length >= 2) {
|
|
await Search(Query);
|
|
} else {
|
|
searchResults = null;
|
|
isSearching = false;
|
|
}
|
|
}
|
|
|
|
async Task Search(string query) {
|
|
var version = ++searchVersion;
|
|
isSearching = true;
|
|
searchResults = await albumService.GetAlbumsAsync(page: 0, pageSize: 20, search: query);
|
|
if (version == searchVersion) {
|
|
isSearching = false;
|
|
}
|
|
StateHasChanged();
|
|
}
|
|
|
|
async Task OnBlur() {
|
|
blurCts?.Cancel();
|
|
blurCts = new CancellationTokenSource();
|
|
try {
|
|
await Task.Delay(150, blurCts.Token);
|
|
showAlbumDropdown = false;
|
|
} catch (OperationCanceledException) { }
|
|
}
|
|
|
|
void OnKeyDown(KeyboardEventArgs e) {
|
|
if (e.Key is "Escape") {
|
|
showAlbumDropdown = false;
|
|
}
|
|
}
|
|
|
|
void SelectAlbum(AlbumPreviewDto album) {
|
|
Query = album.Name;
|
|
_ = QueryChanged.InvokeAsync(Query);
|
|
showAlbumDropdown = false;
|
|
searchResults = null;
|
|
_ = OnAlbumSelected.InvokeAsync(album);
|
|
}
|
|
}
|