[frontend/components] Add global user dialog
This commit is contained in:
parent
03def5b290
commit
1777ec248b
6 changed files with 247 additions and 0 deletions
|
@ -5,5 +5,6 @@
|
||||||
<PromptDialog />
|
<PromptDialog />
|
||||||
<SelectDialog />
|
<SelectDialog />
|
||||||
<StreamingStatus />
|
<StreamingStatus />
|
||||||
|
<UserDialog />
|
||||||
@code {
|
@code {
|
||||||
}
|
}
|
||||||
|
|
149
Iceshrimp.Frontend/Components/UserDialog.razor
Normal file
149
Iceshrimp.Frontend/Components/UserDialog.razor
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
@using Iceshrimp.Frontend.Core.Miscellaneous
|
||||||
|
@using Iceshrimp.Frontend.Core.Services
|
||||||
|
@using Iceshrimp.Frontend.Localization
|
||||||
|
@using Iceshrimp.Shared.Schemas.Web
|
||||||
|
@using Microsoft.Extensions.Localization
|
||||||
|
@inject ApiService Api;
|
||||||
|
@inject GlobalComponentSvc GlobalComponentSvc;
|
||||||
|
@inject IJSRuntime Js;
|
||||||
|
@inject IStringLocalizer<Localization> Loc;
|
||||||
|
|
||||||
|
<dialog @onkeyup="HandleKeys" class="dialog" @ref="Dialog">
|
||||||
|
<div class="select @(Waiting ? "waiting" : "")">
|
||||||
|
@if (Waiting)
|
||||||
|
{
|
||||||
|
<LoadingSpinner Scale="1.5"/>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>@Text</span>
|
||||||
|
<div class="search">
|
||||||
|
<input type="text" class="input" id="username" @bind="Username" @bind:event="oninput" @bind:after="Search" placeholder="@Loc["Username"]" aria-label="username"/>
|
||||||
|
<input type="text" class="input" id="host" @bind="Host" @bind:event="oninput" @bind:after="Search" placeholder="@Loc["Host"]" aria-label="host"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@foreach (var user in Users)
|
||||||
|
{
|
||||||
|
<div @onclick="() => { Selected = user; }" class="user-entry @(user == Selected ? "selected" : "")">
|
||||||
|
<UserAvatar User="@user"/>
|
||||||
|
<div class="name-section">
|
||||||
|
<span class="display-name"><UserDisplayName User="@user"/></span>
|
||||||
|
<span class="identifier">
|
||||||
|
@@@user.Username@(user.Host != null ? $"@{user.Host}" : "")
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="buttons">
|
||||||
|
<button class="button submit-btn" @onclick="ConfirmAction" disabled="@(Selected == null)">@(ButtonText ?? Loc["Submit"])</button>
|
||||||
|
<button class="button" @onclick="CancelAction">@Loc["Cancel"]</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private ElementReference Dialog { get; set; }
|
||||||
|
private IJSObjectReference _module = null!;
|
||||||
|
private EventCallback<UserResponse?> Action { get; set; }
|
||||||
|
private string Text { get; set; } = "";
|
||||||
|
private List<UserResponse> Users { get; set; } = [];
|
||||||
|
private UserResponse? Selected { get; set; }
|
||||||
|
private string? ButtonText { get; set; }
|
||||||
|
private string Username { get; set; } = "";
|
||||||
|
private string Host { get; set; } = "";
|
||||||
|
private bool Waiting { get; set; }
|
||||||
|
private bool Searching { get; set; }
|
||||||
|
private bool SearchQueue { get; set; }
|
||||||
|
|
||||||
|
private async Task CloseDialog()
|
||||||
|
{
|
||||||
|
await _module.InvokeVoidAsync("closeDialog", Dialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Select(EventCallback<UserResponse?> action, string text, string? buttonText = null)
|
||||||
|
{
|
||||||
|
Action = action;
|
||||||
|
Text = text;
|
||||||
|
Users = [];
|
||||||
|
Selected = null;
|
||||||
|
ButtonText = buttonText;
|
||||||
|
Username = "";
|
||||||
|
Host = "";
|
||||||
|
Waiting = false;
|
||||||
|
Searching = false;
|
||||||
|
SearchQueue = false;
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
await _module.InvokeVoidAsync("openDialog", Dialog);
|
||||||
|
await Search();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ConfirmAction()
|
||||||
|
{
|
||||||
|
if (Selected == null) return;
|
||||||
|
Waiting = true;
|
||||||
|
await Action.InvokeAsync(Selected);
|
||||||
|
Waiting = false;
|
||||||
|
await CloseDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CancelAction()
|
||||||
|
{
|
||||||
|
await Action.InvokeAsync(null);
|
||||||
|
await CloseDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Search()
|
||||||
|
{
|
||||||
|
if (Searching)
|
||||||
|
{
|
||||||
|
SearchQueue = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
Searching = true;
|
||||||
|
SearchQueue = false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var res = await Api.Search.SearchAcctsAsync(string.IsNullOrWhiteSpace(Username) ? null : Username.Trim(), string.IsNullOrWhiteSpace(Host) ? null : Host.Trim());
|
||||||
|
Users = res;
|
||||||
|
|
||||||
|
if (Selected != null && Users.All(p => p.Id != Selected.Id))
|
||||||
|
{
|
||||||
|
Selected = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ApiException) { }
|
||||||
|
|
||||||
|
Searching = false;
|
||||||
|
} while (SearchQueue);
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleKeys(KeyboardEventArgs e)
|
||||||
|
{
|
||||||
|
if (e is { Code: "Enter" })
|
||||||
|
{
|
||||||
|
if (!Waiting && Selected != null) await ConfirmAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e is { Code: "Escape" })
|
||||||
|
{
|
||||||
|
if (!Waiting) await CancelAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
_module = await Js.InvokeAsync<IJSObjectReference>("import",
|
||||||
|
"./Components/UserDialog.razor.js");
|
||||||
|
GlobalComponentSvc.UserDialog = this;
|
||||||
|
}
|
||||||
|
}
|
82
Iceshrimp.Frontend/Components/UserDialog.razor.css
Normal file
82
Iceshrimp.Frontend/Components/UserDialog.razor.css
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
.dialog {
|
||||||
|
margin: auto;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog::backdrop {
|
||||||
|
opacity: 50%;
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
background-color: var(--background-color);
|
||||||
|
border-radius: 1rem;
|
||||||
|
margin: auto;
|
||||||
|
padding: 1rem;
|
||||||
|
width: max-content;
|
||||||
|
max-width: 45rem;
|
||||||
|
color: var(--font-color);
|
||||||
|
text-align: center;
|
||||||
|
text-wrap: wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select:not(.waiting) {
|
||||||
|
min-width: 12rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select .search {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-entry {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-entry.selected {
|
||||||
|
background-color: var(--hover-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-entry .name-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: clip;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
.identifier {
|
||||||
|
font-size: 0.8em;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: clip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn {
|
||||||
|
background: var(--accent-color);
|
||||||
|
color: var(--font-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1000px) {
|
||||||
|
.select {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select .search {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
7
Iceshrimp.Frontend/Components/UserDialog.razor.js
Normal file
7
Iceshrimp.Frontend/Components/UserDialog.razor.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export function openDialog(element) {
|
||||||
|
element.showModal()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function closeDialog(element) {
|
||||||
|
element.close()
|
||||||
|
}
|
|
@ -16,4 +16,11 @@ internal class SearchControllerModel(ApiClient api)
|
||||||
|
|
||||||
public Task<RedirectResponse?> LookupAsync(string target) =>
|
public Task<RedirectResponse?> LookupAsync(string target) =>
|
||||||
api.CallNullableAsync<RedirectResponse>(HttpMethod.Get, "/search/lookup", QueryString.Create("target", target));
|
api.CallNullableAsync<RedirectResponse>(HttpMethod.Get, "/search/lookup", QueryString.Create("target", target));
|
||||||
|
|
||||||
|
public Task<List<UserResponse>> SearchAcctsAsync(string? username, string? host) =>
|
||||||
|
api.CallAsync<List<UserResponse>>(HttpMethod.Get, "/search/acct",
|
||||||
|
(username != null
|
||||||
|
? QueryString.Create("username", username)
|
||||||
|
: QueryString.Empty)
|
||||||
|
+ (host != null ? QueryString.Create("host", host) : QueryString.Empty));
|
||||||
}
|
}
|
|
@ -10,4 +10,5 @@ public class GlobalComponentSvc
|
||||||
public NoticeDialog? NoticeDialog { get; set; }
|
public NoticeDialog? NoticeDialog { get; set; }
|
||||||
public PromptDialog? PromptDialog { get; set; }
|
public PromptDialog? PromptDialog { get; set; }
|
||||||
public SelectDialog? SelectDialog { get; set; }
|
public SelectDialog? SelectDialog { get; set; }
|
||||||
|
public UserDialog? UserDialog { get; set; }
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue