mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-18 05:18:05 -04:00
Tweak removable section UI in web app (#1404)
This commit is contained in:
@@ -30,7 +30,7 @@ import { useVaultMutate } from '@/entrypoints/popup/hooks/useVaultMutate';
|
||||
|
||||
import { SKIP_FORM_RESTORE_KEY } from '@/utils/Constants';
|
||||
import type { Item, ItemField, ItemType, FieldType, Attachment, TotpCode } from '@/utils/dist/core/models/vault';
|
||||
import { FieldCategories, FieldTypes, ItemTypes, getSystemFieldsForItemType, getOptionalFieldsForItemType, isFieldShownByDefault } from '@/utils/dist/core/models/vault';
|
||||
import { FieldCategories, FieldTypes, ItemTypes, getSystemFieldsForItemType, getOptionalFieldsForItemType, isFieldShownByDefault, getSystemField, fieldAppliesToType } from '@/utils/dist/core/models/vault';
|
||||
import { FaviconService } from '@/utils/FaviconService';
|
||||
|
||||
import { browser } from '#imports';
|
||||
@@ -758,26 +758,58 @@ const ItemAddEdit: React.FC = () => {
|
||||
|
||||
/**
|
||||
* Handle item type change from dropdown.
|
||||
* Clears field values that don't apply to the new item type.
|
||||
*/
|
||||
const handleTypeChange = useCallback((newType: ItemType) => {
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
// When switching FROM Alias type to another type, clear alias and login fields (except URL)
|
||||
if (!isEditMode && item.ItemType === ItemTypes.Alias && newType !== ItemTypes.Alias) {
|
||||
const oldType = item.ItemType;
|
||||
|
||||
// Clear field values that don't apply to the new type
|
||||
if (!isEditMode && oldType !== newType) {
|
||||
setFieldValues(prev => {
|
||||
const newValues: Record<string, string | string[]> = {};
|
||||
// Only preserve non-alias and non-login fields, plus login.url
|
||||
Object.entries(prev).forEach(([key, value]) => {
|
||||
if (key === 'login.url') {
|
||||
newValues[key] = value;
|
||||
} else if (!key.startsWith('alias.') && !key.startsWith('login.')) {
|
||||
// Check if this field applies to the new type
|
||||
const systemField = getSystemField(key);
|
||||
if (systemField) {
|
||||
// Keep the field only if it applies to the new type
|
||||
if (fieldAppliesToType(systemField, newType)) {
|
||||
newValues[key] = value;
|
||||
}
|
||||
} else {
|
||||
// Custom fields are always kept
|
||||
newValues[key] = value;
|
||||
}
|
||||
});
|
||||
return newValues;
|
||||
});
|
||||
|
||||
// Clear manually added fields that don't apply to new type
|
||||
setManuallyAddedFields(prev => {
|
||||
const newSet = new Set<string>();
|
||||
prev.forEach(fieldKey => {
|
||||
const systemField = getSystemField(fieldKey);
|
||||
if (!systemField || fieldAppliesToType(systemField, newType)) {
|
||||
newSet.add(fieldKey);
|
||||
}
|
||||
});
|
||||
return newSet;
|
||||
});
|
||||
|
||||
// Clear initially visible fields that don't apply to new type
|
||||
setInitiallyVisibleFields(prev => {
|
||||
const newSet = new Set<string>();
|
||||
prev.forEach(fieldKey => {
|
||||
const systemField = getSystemField(fieldKey);
|
||||
if (!systemField || fieldAppliesToType(systemField, newType)) {
|
||||
newSet.add(fieldKey);
|
||||
}
|
||||
});
|
||||
return newSet;
|
||||
});
|
||||
}
|
||||
|
||||
// Reset alias generated flag, so alias fields will be filled (again) if they are shown by the new type
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<input type="@(_internalShowPassword ? "text" : "password")" id="@Id" autocomplete="off" class="outline-0 shadow-sm bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-l-lg block w-full p-2.5 pr-16 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white" value="@Value" @oninput="OnInputChanged" placeholder="@Placeholder">
|
||||
</div>
|
||||
<div class="flex">
|
||||
<button type="button" class="px-3 text-gray-500 dark:text-white bg-gray-200 hover:bg-gray-300 focus:ring-4 focus:outline-none focus:ring-gray-300 font-medium text-sm dark:bg-gray-600 dark:hover:bg-gray-700 dark:focus:ring-gray-800" @onclick="TogglePasswordVisibility">
|
||||
<button type="button" class="@GetVisibilityButtonClasses()" @onclick="TogglePasswordVisibility">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
@if (_internalShowPassword)
|
||||
{
|
||||
@@ -20,17 +20,20 @@
|
||||
}
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" class="px-3 text-gray-500 dark:text-white bg-gray-200 hover:bg-gray-300 focus:ring-4 focus:outline-none focus:ring-gray-300 font-medium text-sm border-l border-gray-300 dark:border-gray-700 dark:bg-gray-600 dark:hover:bg-gray-700 dark:focus:ring-gray-800" @onclick="ShowPasswordSettings">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" class="px-3 text-gray-500 dark:text-white bg-gray-200 hover:bg-gray-300 focus:ring-4 focus:outline-none focus:ring-gray-300 font-medium rounded-r-lg text-sm border-l border-gray-300 dark:border-gray-700 dark:bg-gray-600 dark:hover:bg-gray-700 dark:focus:ring-gray-800" @onclick="GeneratePassword">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
||||
</svg>
|
||||
</button>
|
||||
@if (ShowGenerateButtons)
|
||||
{
|
||||
<button type="button" class="px-3 text-gray-500 dark:text-white bg-gray-200 hover:bg-gray-300 focus:ring-4 focus:outline-none focus:ring-gray-300 font-medium text-sm border-l border-gray-300 dark:border-gray-700 dark:bg-gray-600 dark:hover:bg-gray-700 dark:focus:ring-gray-800" @onclick="ShowPasswordSettings">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" class="px-3 text-gray-500 dark:text-white bg-gray-200 hover:bg-gray-300 focus:ring-4 focus:outline-none focus:ring-gray-300 font-medium rounded-r-lg text-sm border-l border-gray-300 dark:border-gray-700 dark:bg-gray-600 dark:hover:bg-gray-700 dark:focus:ring-gray-800" @onclick="GeneratePassword">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
||||
</svg>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -80,6 +83,13 @@
|
||||
[Parameter]
|
||||
public bool ShowPassword { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Controls whether the generate and settings buttons are shown.
|
||||
/// Set to false for fields like CVV or card number that shouldn't be generated.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool ShowGenerateButtons { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the password settings popup is visible.
|
||||
/// </summary>
|
||||
@@ -120,6 +130,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the CSS classes for the visibility toggle button.
|
||||
/// When generate buttons are hidden, this button needs rounded-r-lg to be the last button.
|
||||
/// </summary>
|
||||
private string GetVisibilityButtonClasses()
|
||||
{
|
||||
var baseClasses = "px-3 text-gray-500 dark:text-white bg-gray-200 hover:bg-gray-300 focus:ring-4 focus:outline-none focus:ring-gray-300 font-medium text-sm dark:bg-gray-600 dark:hover:bg-gray-700 dark:focus:ring-gray-800";
|
||||
return ShowGenerateButtons ? baseClasses : $"{baseClasses} rounded-r-lg";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the password plain text visibility.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
@using Microsoft.Extensions.Localization
|
||||
|
||||
@inject IStringLocalizerFactory LocalizerFactory
|
||||
|
||||
<div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800 relative">
|
||||
@if (!string.IsNullOrEmpty(Title))
|
||||
{
|
||||
<h3 class="mb-4 text-xl font-semibold dark:text-white">@Title</h3>
|
||||
}
|
||||
<div class="grid gap-6">
|
||||
@ChildContent
|
||||
</div>
|
||||
@if (CanRemove)
|
||||
{
|
||||
<button type="button" @onclick="HandleRemove" @onclick:preventDefault="true"
|
||||
class="absolute top-3 right-3 text-gray-400 hover:text-red-500 transition-colors" title="@SharedLocalizer["Delete"]">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private IStringLocalizer SharedLocalizer => LocalizerFactory.Create("SharedResources", "AliasVault.Client");
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the section title.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the section can be removed.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool CanRemove { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the child content to render inside the section.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the callback when the remove button is clicked.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public EventCallback OnRemove { get; set; }
|
||||
|
||||
private async Task HandleRemove()
|
||||
{
|
||||
await OnRemove.InvokeAsync();
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
@using TotpGenerator
|
||||
@using Microsoft.Extensions.Localization
|
||||
|
||||
<div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
|
||||
<div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800 relative">
|
||||
<div class="flex justify-between">
|
||||
<div>
|
||||
<h3 class="mb-4 text-xl font-semibold dark:text-white">@Localizer["TwoFactorAuthenticationTitle"]</h3>
|
||||
@@ -20,6 +20,15 @@
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (CanRemove)
|
||||
{
|
||||
<button type="button" @onclick="HandleRemove" @onclick:preventDefault="true"
|
||||
class="absolute top-3 right-3 text-gray-400 hover:text-red-500 transition-colors" title="@Localizer["Delete"]">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
}
|
||||
|
||||
@if ((TotpCodeList.Count == 0 || TotpCodeList.All(t => t.IsDeleted)) && !IsAddFormVisible)
|
||||
{
|
||||
@@ -105,6 +114,18 @@
|
||||
[Parameter]
|
||||
public EventCallback<List<TotpCode>> TotpCodesChanged { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the section can be removed.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool CanRemove { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the callback when the remove button is clicked.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public EventCallback OnRemove { get; set; }
|
||||
|
||||
private bool IsAddFormVisible { get; set; } = false;
|
||||
private TotpCodeEdit NewTotpCode { get; set; } = new();
|
||||
private List<Guid> OriginalTotpCodeIds { get; set; } = [];
|
||||
@@ -206,4 +227,12 @@
|
||||
await TotpCodesChanged.InvokeAsync(TotpCodeList);
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the remove section button click.
|
||||
/// </summary>
|
||||
private async Task HandleRemove()
|
||||
{
|
||||
await OnRemove.InvokeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
@inject AliasVault.Client.Services.QuickCreateStateService QuickCreateStateService
|
||||
@using AliasVault.Client.Services.JsInterop.Models
|
||||
@using AliasVault.Client.Main.Components.Items
|
||||
@using AliasVault.Client.Main.Components.Forms
|
||||
@using Microsoft.Extensions.Localization
|
||||
@using AliasClientDb
|
||||
@using AliasClientDb.Models
|
||||
@@ -65,7 +66,7 @@ else
|
||||
@if (Show2FA && HasLoginFields())
|
||||
{
|
||||
<div class="col-span-1 md:col-span-1 lg:col-span-1">
|
||||
<TotpCodes TotpCodeList="@Obj.TotpCodes" TotpCodesChanged="HandleTotpCodesChanged" />
|
||||
<TotpCodes TotpCodeList="@Obj.TotpCodes" TotpCodesChanged="HandleTotpCodesChanged" CanRemove="@CanRemove2FASection()" OnRemove="Remove2FASection" />
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -73,22 +74,11 @@ else
|
||||
@if (ShouldShowField(FieldKey.NotesContent))
|
||||
{
|
||||
<div class="col-span-1 md:col-span-1 lg:col-span-1">
|
||||
<div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800 relative">
|
||||
<div class="grid gap-6">
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Type="textarea" Id="notes" Label="@Localizer["NotesLabel"]" LabelStyle="EditFormRow.FormLabelStyle.Header" @bind-Value="Obj.Notes"></EditFormRow>
|
||||
</div>
|
||||
<RemovableSection CanRemove="@CanRemoveField(FieldKey.NotesContent)" OnRemove="() => RemoveOptionalField(FieldKey.NotesContent)">
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Type="textarea" Id="notes" Label="@Localizer["NotesLabel"]" LabelStyle="EditFormRow.FormLabelStyle.Header" @bind-Value="Obj.Notes"></EditFormRow>
|
||||
</div>
|
||||
@if (CanRemoveField(FieldKey.NotesContent))
|
||||
{
|
||||
<button type="button" @onclick="() => RemoveOptionalField(FieldKey.NotesContent)" @onclick:preventDefault="true"
|
||||
class="absolute top-3 right-3 text-gray-400 hover:text-red-500 transition-colors" title="@Localizer["RemoveField"]">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</RemovableSection>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -96,16 +86,13 @@ else
|
||||
@if (ShowAttachments)
|
||||
{
|
||||
<div class="col-span-1 md:col-span-1 lg:col-span-1">
|
||||
<div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
|
||||
<h3 class="mb-4 text-xl font-semibold dark:text-white">@Localizer["AttachmentsSectionHeader"]</h3>
|
||||
<div class="grid gap-6">
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<AttachmentUploader
|
||||
Attachments="@Obj.Attachments"
|
||||
AttachmentsChanged="@HandleAttachmentsChanged" />
|
||||
</div>
|
||||
<RemovableSection Title="@Localizer["AttachmentsSectionHeader"]" CanRemove="@CanRemoveAttachmentsSection()" OnRemove="RemoveAttachmentsSection">
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<AttachmentUploader
|
||||
Attachments="@Obj.Attachments"
|
||||
AttachmentsChanged="@HandleAttachmentsChanged" />
|
||||
</div>
|
||||
</div>
|
||||
</RemovableSection>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -300,7 +287,7 @@ else
|
||||
<EditFormRow Id="cardholder-name" Label="@Localizer["CardholderNameLabel"]" @bind-Value="Obj.CardCardholderName"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6">
|
||||
<EditPasswordFormRow Id="card-number" Label="@Localizer["CardNumberLabel"]" @bind-Value="Obj.CardNumber" ShowPassword="false"></EditPasswordFormRow>
|
||||
<EditPasswordFormRow Id="card-number" Label="@Localizer["CardNumberLabel"]" @bind-Value="Obj.CardNumber" ShowPassword="false" ShowGenerateButtons="false"></EditPasswordFormRow>
|
||||
</div>
|
||||
<div class="col-span-3">
|
||||
<EditFormRow Id="expiry-month" Label="@Localizer["ExpiryMonthLabel"]" Placeholder="MM" @bind-Value="Obj.CardExpiryMonth"></EditFormRow>
|
||||
@@ -309,7 +296,7 @@ else
|
||||
<EditFormRow Id="expiry-year" Label="@Localizer["ExpiryYearLabel"]" Placeholder="YYYY" @bind-Value="Obj.CardExpiryYear"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditPasswordFormRow Id="card-cvv" Label="@Localizer["CardCvvLabel"]" @bind-Value="Obj.CardCvv" ShowPassword="false"></EditPasswordFormRow>
|
||||
<EditPasswordFormRow Id="card-cvv" Label="@Localizer["CardCvvLabel"]" @bind-Value="Obj.CardCvv" ShowPassword="false" ShowGenerateButtons="false"></EditPasswordFormRow>
|
||||
</div>
|
||||
@if (ShouldShowField(FieldKey.CardPin))
|
||||
{
|
||||
@@ -379,8 +366,8 @@ else
|
||||
HasLoginFields="@HasLoginFields()"
|
||||
OnAddSystemField="AddOptionalField"
|
||||
OnAddCustomField="AddCustomField"
|
||||
OnAdd2FA="() => Show2FA = true"
|
||||
OnAddAttachments="() => ShowAttachments = true" />
|
||||
OnAdd2FA="Add2FASection"
|
||||
OnAddAttachments="AddAttachmentsSection" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -405,6 +392,8 @@ else
|
||||
private bool ShowTypeDropdown { get; set; } = false;
|
||||
private bool Show2FA { get; set; } = false;
|
||||
private bool ShowAttachments { get; set; } = false;
|
||||
private bool Show2FAAddedManually { get; set; } = false;
|
||||
private bool ShowAttachmentsAddedManually { get; set; } = false;
|
||||
private ItemEdit Obj { get; set; } = new();
|
||||
private IJSObjectReference? Module;
|
||||
|
||||
@@ -560,12 +549,20 @@ else
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a field can be removed (was manually added).
|
||||
/// Determines if a field can be removed (is an optional field).
|
||||
/// </summary>
|
||||
private bool CanRemoveField(string fieldKey)
|
||||
{
|
||||
// A field can be removed if it was manually added via the + menu
|
||||
return ManuallyAddedFields.Contains(fieldKey);
|
||||
// A field can be removed if it's an optional field for the current item type
|
||||
var fieldDef = SystemFieldRegistry.GetSystemField(fieldKey);
|
||||
if (fieldDef == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if it's an optional field (not shown by default)
|
||||
var config = fieldDef.ApplicableToTypes.GetValueOrDefault(Obj.ItemType);
|
||||
return config != null && !config.ShowByDefault;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -604,15 +601,128 @@ else
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an optional field.
|
||||
/// Removes an optional field (hides it and clears the value).
|
||||
/// </summary>
|
||||
private void RemoveOptionalField(string fieldKey)
|
||||
{
|
||||
ManuallyAddedFields.Remove(fieldKey);
|
||||
InitiallyVisibleFields.Remove(fieldKey);
|
||||
Obj.SetFieldValue(fieldKey, string.Empty);
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the 2FA section.
|
||||
/// </summary>
|
||||
private void Add2FASection()
|
||||
{
|
||||
Show2FA = true;
|
||||
Show2FAAddedManually = true;
|
||||
|
||||
// Restore any soft-deleted TOTP codes (undo previous removal)
|
||||
foreach (var totpCode in Obj.TotpCodes)
|
||||
{
|
||||
if (totpCode.IsDeleted)
|
||||
{
|
||||
totpCode.IsDeleted = false;
|
||||
totpCode.UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the 2FA section and marks existing TOTP codes for deletion.
|
||||
/// </summary>
|
||||
private void Remove2FASection()
|
||||
{
|
||||
Show2FA = false;
|
||||
Show2FAAddedManually = false;
|
||||
|
||||
// Mark all TOTP codes for deletion (soft delete for existing, remove new ones)
|
||||
foreach (var totpCode in Obj.TotpCodes.ToList())
|
||||
{
|
||||
if (totpCode.Id != Guid.Empty)
|
||||
{
|
||||
// Existing code - mark for soft delete
|
||||
totpCode.IsDeleted = true;
|
||||
totpCode.UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
else
|
||||
{
|
||||
// New code - just remove from list
|
||||
Obj.TotpCodes.Remove(totpCode);
|
||||
}
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the 2FA section can be removed (always true when visible).
|
||||
/// </summary>
|
||||
private bool CanRemove2FASection()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the attachments section.
|
||||
/// </summary>
|
||||
private void AddAttachmentsSection()
|
||||
{
|
||||
ShowAttachments = true;
|
||||
ShowAttachmentsAddedManually = true;
|
||||
|
||||
// Restore any soft-deleted attachments (undo previous removal)
|
||||
foreach (var attachment in Obj.Attachments)
|
||||
{
|
||||
if (attachment.IsDeleted)
|
||||
{
|
||||
attachment.IsDeleted = false;
|
||||
attachment.UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the attachments section and marks existing attachments for deletion.
|
||||
/// </summary>
|
||||
private void RemoveAttachmentsSection()
|
||||
{
|
||||
ShowAttachments = false;
|
||||
ShowAttachmentsAddedManually = false;
|
||||
|
||||
// Mark all attachments for deletion (soft delete for existing, remove new ones)
|
||||
foreach (var attachment in Obj.Attachments.ToList())
|
||||
{
|
||||
if (attachment.Id != Guid.Empty)
|
||||
{
|
||||
// Existing attachment - mark for soft delete
|
||||
attachment.IsDeleted = true;
|
||||
attachment.UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
else
|
||||
{
|
||||
// New attachment - just remove from list
|
||||
Obj.Attachments.Remove(attachment);
|
||||
}
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the attachments section can be removed (always true when visible).
|
||||
/// </summary>
|
||||
private bool CanRemoveAttachmentsSection()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a custom field.
|
||||
/// </summary>
|
||||
@@ -652,6 +762,7 @@ else
|
||||
|
||||
/// <summary>
|
||||
/// Handles item type change.
|
||||
/// Clears field values that don't apply to the new item type.
|
||||
/// </summary>
|
||||
private async Task HandleItemTypeChange(string newType)
|
||||
{
|
||||
@@ -663,25 +774,13 @@ else
|
||||
var oldType = Obj.ItemType;
|
||||
Obj.ItemType = newType;
|
||||
|
||||
// Clear fields that don't apply to the new type
|
||||
if (oldType == ItemTypes.Alias && newType != ItemTypes.Alias)
|
||||
// Clear all system fields that don't apply to the new type
|
||||
foreach (var fieldDef in SystemFieldRegistry.Fields.Values)
|
||||
{
|
||||
// Clear alias fields when switching away from Alias
|
||||
Obj.AliasFirstName = string.Empty;
|
||||
Obj.AliasLastName = string.Empty;
|
||||
Obj.AliasGender = string.Empty;
|
||||
Obj.AliasBirthDate = string.Empty;
|
||||
}
|
||||
|
||||
if (oldType == ItemTypes.CreditCard && newType != ItemTypes.CreditCard)
|
||||
{
|
||||
// Clear card fields when switching away from CreditCard
|
||||
Obj.CardNumber = string.Empty;
|
||||
Obj.CardCardholderName = string.Empty;
|
||||
Obj.CardExpiryMonth = string.Empty;
|
||||
Obj.CardExpiryYear = string.Empty;
|
||||
Obj.CardCvv = string.Empty;
|
||||
Obj.CardPin = string.Empty;
|
||||
if (!SystemFieldRegistry.FieldAppliesToType(fieldDef, newType))
|
||||
{
|
||||
Obj.SetFieldValue(fieldDef.FieldKey, string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear manually added fields that don't apply to new type
|
||||
@@ -696,6 +795,18 @@ else
|
||||
ManuallyAddedFields.Remove(fk);
|
||||
}
|
||||
|
||||
// Clear initially visible fields that don't apply to new type
|
||||
var initialFieldsToRemove = InitiallyVisibleFields
|
||||
.Where(fk => {
|
||||
var def = SystemFieldRegistry.GetSystemField(fk);
|
||||
return def != null && !SystemFieldRegistry.FieldAppliesToType(def, newType);
|
||||
})
|
||||
.ToList();
|
||||
foreach (var fk in initialFieldsToRemove)
|
||||
{
|
||||
InitiallyVisibleFields.Remove(fk);
|
||||
}
|
||||
|
||||
// Generate alias for Alias type in create mode
|
||||
if (newType == ItemTypes.Alias && !EditMode && !HasAliasValues())
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user