mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-01-27 15:29:17 -05:00
248 lines
8.9 KiB
Plaintext
248 lines
8.9 KiB
Plaintext
@using System.ComponentModel.DataAnnotations
|
|
@using Microsoft.Extensions.Localization
|
|
@inherits AliasVault.Client.Main.Pages.MainBase
|
|
@inject IJSRuntime JSRuntime
|
|
@inject CredentialService CredentialService
|
|
@inject AliasVault.Client.Services.QuickCreateStateService QuickCreateStateService
|
|
@inject LanguageService LanguageService
|
|
@implements IAsyncDisposable
|
|
|
|
<button @ref="buttonRef" @onclick="TogglePopup" id="quickIdentityButton" class="px-4 py-2 text-sm font-medium text-white bg-gradient-to-r from-primary-500 to-primary-600 hover:from-primary-600 hover:to-primary-700 focus:outline-none dark:from-primary-400 dark:to-primary-500 dark:hover:from-primary-500 dark:hover:to-primary-600 rounded-md shadow-sm transition duration-150 ease-in-out transform hover:scale-105 active:scale-95 focus:shadow-outline">
|
|
@Localizer["NewAliasButtonShort"] <span class="hidden md:inline">@Localizer["NewAliasButtonText"].Value.Substring(1).Trim()</span>
|
|
</button>
|
|
|
|
@if (IsPopupVisible)
|
|
{
|
|
<ClickOutsideHandler OnClose="ClosePopup" ContentId="quickIdentityPopup,quickIdentityButton">
|
|
<div id="quickIdentityPopup" class="absolute z-50 mt-2 p-4 bg-white rounded-lg shadow-xl border border-gray-300 dark:bg-gray-800 dark:border-gray-400"
|
|
style="@PopupStyle">
|
|
<h3 class="text-lg font-semibold mb-4 text-gray-900 dark:text-white">@Localizer["CreateNewAliasTitle"]</h3>
|
|
<EditForm Model="Model" OnValidSubmit="CreateIdentity">
|
|
<DataAnnotationsValidator />
|
|
<div class="mb-4">
|
|
<EditFormRow Id="serviceName" Label="@Localizer["ServiceNameLabel"]" Placeholder="@Localizer["ServiceNamePlaceholder"]" @bind-Value="Model.ServiceName"></EditFormRow>
|
|
<ValidationMessage For="() => Model.ServiceName"/>
|
|
</div>
|
|
<div class="mb-4">
|
|
<EditFormRow Id="serviceUrl" Label="@Localizer["ServiceUrlLabel"]" OnFocus="OnFocusUrlInput" @bind-Value="Model.ServiceUrl"></EditFormRow>
|
|
<ValidationMessage For="() => Model.ServiceUrl"/>
|
|
</div>
|
|
<div class="flex justify-between items-center">
|
|
<button id="quickIdentitySubmit" type="submit" class="bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded">
|
|
@Localizer["CreateButton"]
|
|
</button>
|
|
</div>
|
|
<div class="pt-2">
|
|
<a href="#" @onclick="OpenAdvancedMode" @onclick:preventDefault class="text-sm text-blue-500 hover:text-blue-700">
|
|
@Localizer["AdvancedModeLink"]
|
|
</a>
|
|
</div>
|
|
</EditForm>
|
|
</div>
|
|
</ClickOutsideHandler>
|
|
}
|
|
|
|
@code {
|
|
private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Widgets.CreateNewIdentityWidget", "AliasVault.Client");
|
|
|
|
private bool IsPopupVisible = false;
|
|
private bool IsCreating = false;
|
|
private CreateModel Model = new();
|
|
private string PopupStyle { get; set; } = string.Empty;
|
|
private ElementReference buttonRef;
|
|
private IJSObjectReference? Module;
|
|
|
|
/// <inheritdoc />
|
|
async ValueTask IAsyncDisposable.DisposeAsync()
|
|
{
|
|
await KeyboardShortcutService.UnregisterShortcutAsync("gc");
|
|
LanguageService.LanguageChanged -= OnLanguageChanged;
|
|
if (Module is not null)
|
|
{
|
|
await Module.DisposeAsync();
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
{
|
|
if (firstRender)
|
|
{
|
|
await KeyboardShortcutService.RegisterShortcutAsync("gc", ShowPopup);
|
|
LanguageService.LanguageChanged += OnLanguageChanged;
|
|
Module = await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./js/modules/newIdentityWidget.js");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles language change events and triggers component refresh.
|
|
/// </summary>
|
|
/// <param name="languageCode">The new language code.</param>
|
|
private void OnLanguageChanged(string languageCode)
|
|
{
|
|
InvokeAsync(StateHasChanged);
|
|
}
|
|
|
|
/// <summary>
|
|
/// When the URL input is focused, place cursor at the end of the default URL to allow for easy typing.
|
|
/// </summary>
|
|
private void OnFocusUrlInput(FocusEventArgs e)
|
|
{
|
|
if (Model.ServiceUrl != CredentialService.DefaultServiceUrl)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Use a small delay to ensure the focus is set after the browser's default behavior.
|
|
Task.Delay(1).ContinueWith(_ =>
|
|
{
|
|
JSRuntime.InvokeVoidAsync("eval", $"document.getElementById('serviceUrl').setSelectionRange({CredentialService.DefaultServiceUrl.Length}, {CredentialService.DefaultServiceUrl.Length})");
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Toggle the popup.
|
|
/// </summary>
|
|
private async Task TogglePopup()
|
|
{
|
|
IsPopupVisible = !IsPopupVisible;
|
|
if (IsPopupVisible)
|
|
{
|
|
await ShowPopup();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Show the popup.
|
|
/// </summary>
|
|
private async Task ShowPopup()
|
|
{
|
|
IsPopupVisible = true;
|
|
|
|
// Clear the input fields
|
|
Model = new();
|
|
Model.ServiceUrl = CredentialService.DefaultServiceUrl;
|
|
|
|
await UpdatePopupStyle();
|
|
await Task.Delay(100); // Give time for the DOM to update
|
|
await JsInteropService.FocusElementById("serviceName");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Close the popup.
|
|
/// </summary>
|
|
private void ClosePopup()
|
|
{
|
|
IsPopupVisible = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update the popup style so that it is positioned correctly.
|
|
/// </summary>
|
|
private async Task UpdatePopupStyle()
|
|
{
|
|
var windowWidth = await JSRuntime.InvokeAsync<int>("getWindowWidth");
|
|
var buttonRect = await JSRuntime.InvokeAsync<BoundingClientRect>("getElementRect", buttonRef);
|
|
|
|
// Constrain the popup width to 400px minus some padding.
|
|
var popupWidth = Math.Min(400, windowWidth - 20);
|
|
|
|
PopupStyle = $"width: {popupWidth}px; top: {buttonRect.Bottom}px;";
|
|
StateHasChanged();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create the new identity.
|
|
/// </summary>
|
|
private async Task CreateIdentity()
|
|
{
|
|
if (IsCreating)
|
|
{
|
|
return;
|
|
}
|
|
|
|
IsCreating = true;
|
|
GlobalLoadingSpinner.Show(Localizer["CreatingNewAliasMessage"]);
|
|
StateHasChanged();
|
|
|
|
var credential = new Credential();
|
|
credential.Alias = new Alias();
|
|
credential.Alias.Email = "@" + CredentialService.GetDefaultEmailDomain();
|
|
credential.Service = new Service();
|
|
credential.Service.Name = Model.ServiceName;
|
|
|
|
if (Model.ServiceUrl != CredentialService.DefaultServiceUrl)
|
|
{
|
|
credential.Service.Url = Model.ServiceUrl;
|
|
}
|
|
|
|
credential.Passwords = new List<Password> { new() };
|
|
await CredentialService.GenerateRandomIdentityAsync(credential);
|
|
|
|
var id = await CredentialService.InsertEntryAsync(credential);
|
|
if (id == Guid.Empty)
|
|
{
|
|
// Error saving.
|
|
IsCreating = false;
|
|
GlobalLoadingSpinner.Hide();
|
|
GlobalNotificationService.AddErrorMessage(Localizer["CreateCredentialErrorMessage"], true);
|
|
return;
|
|
}
|
|
|
|
// No error, add success message.
|
|
GlobalNotificationService.AddSuccessMessage(Localizer["CredentialCreatedSuccessMessage"]);
|
|
|
|
NavigationManager.NavigateTo("/credentials/" + id);
|
|
|
|
IsCreating = false;
|
|
GlobalLoadingSpinner.Hide();
|
|
StateHasChanged();
|
|
ClosePopup();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Open the advanced mode for creating a new identity.
|
|
/// </summary>
|
|
private void OpenAdvancedMode()
|
|
{
|
|
// Store the form data in the state service to prefill in the advanced mode form.
|
|
QuickCreateStateService.ServiceName = Model.ServiceName;
|
|
QuickCreateStateService.ServiceUrl = Model.ServiceUrl;
|
|
|
|
NavigationManager.NavigateTo("/credentials/create");
|
|
ClosePopup();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bounding client rectangle returned from JavaScript.
|
|
/// </summary>
|
|
private sealed class BoundingClientRect
|
|
{
|
|
public double Left { get; set; }
|
|
public double Top { get; set; }
|
|
public double Right { get; set; }
|
|
public double Bottom { get; set; }
|
|
public double Width { get; set; }
|
|
public double Height { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Local model for the form with support for validation.
|
|
/// </summary>
|
|
private sealed class CreateModel
|
|
{
|
|
/// <summary>
|
|
/// The service name.
|
|
/// </summary>
|
|
[Required]
|
|
[Display(Name = "Service Name")]
|
|
public string ServiceName { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// The service URL.
|
|
/// </summary>
|
|
[Display(Name = "Service URL")]
|
|
public string ServiceUrl { get; set; } = string.Empty;
|
|
}
|
|
}
|