mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-08 23:35:43 -04:00
Use new dynamic item icon in web app search and table views (#1976)
This commit is contained in:
committed by
Leendert de Borst
parent
3eaf2ac5c6
commit
f0c8f96a1c
@@ -1,79 +0,0 @@
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<div @onclick="ShowDetails" class="credential-card overflow-hidden p-4 space-y-2 bg-white border border-gray-200 rounded-lg shadow-sm dark:border-gray-700
|
||||
dark:bg-gray-800 cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200">
|
||||
<div class="px-4 py-2 text-gray-400 rounded text-center flex flex-col items-center">
|
||||
<DisplayFavicon FaviconBytes="@Obj.Logo" Padding="true"></DisplayFavicon>
|
||||
<div class="flex items-center justify-center gap-1.5 w-full">
|
||||
<div class="text-gray-900 dark:text-gray-100 break-words">@GetServiceName()</div>
|
||||
@if (Obj.HasPasskey)
|
||||
{
|
||||
<svg class="w-3.5 h-3.5 text-gray-500 dark:text-gray-400 flex-shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-label="Has passkey">
|
||||
<path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4" />
|
||||
</svg>
|
||||
}
|
||||
@if (Obj.HasAttachment)
|
||||
{
|
||||
<svg class="w-3.5 h-3.5 text-gray-500 dark:text-gray-400 flex-shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-label="Has attachments">
|
||||
<path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" />
|
||||
</svg>
|
||||
}
|
||||
</div>
|
||||
<div class="text-gray-500 dark:text-gray-400 break-words w-full text-sm">@GetDisplayText()</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
/// <summary>
|
||||
/// Gets or sets the credentials object to show.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public required ItemListEntry Obj { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display text for the credential, showing username by default,
|
||||
/// falling back to email only if username is null/empty.
|
||||
/// </summary>
|
||||
private string GetDisplayText()
|
||||
{
|
||||
var returnValue = string.Empty;
|
||||
|
||||
// Show username if available
|
||||
if (!string.IsNullOrEmpty(Obj.Username))
|
||||
{
|
||||
returnValue = Obj.Username;
|
||||
}
|
||||
|
||||
// Show email if username is not available
|
||||
if (!string.IsNullOrEmpty(Obj.Email))
|
||||
{
|
||||
returnValue = Obj.Email;
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the service name for a credential.
|
||||
/// </summary>
|
||||
private string GetServiceName()
|
||||
{
|
||||
var returnValue = "Untitled";
|
||||
|
||||
if (!string.IsNullOrEmpty(Obj.Service))
|
||||
{
|
||||
returnValue = Obj.Service;
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigate to the details page of the credential.
|
||||
/// </summary>
|
||||
private void ShowDetails()
|
||||
{
|
||||
// Redirect to view page instead for now.
|
||||
NavigationManager.NavigateTo($"/items/{Obj.Id}");
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
@if (FaviconBytes != null)
|
||||
{
|
||||
<img src="@_faviconDataUrl" style="width: @(Width)px;" class="rounded-lg w-28 @(Padding ? "mb-4 sm:mb-0 xl:mb-4 2xl:mb-0" : "")" alt="Favicon" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<img src="img/service-placeholder.webp" style="width: @(Width)px;" class="@(Padding ? "mb-4 sm:mb-0 xl:mb-4 2xl:mb-0" : "")" alt="Favicon" />
|
||||
}
|
||||
|
||||
@code {
|
||||
/// <summary>
|
||||
/// Byte[] of the favicon.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public byte[]? FaviconBytes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The width (in pixels) to show the icon as. Defaults to 50px.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public int Width { get; set; } = 50;
|
||||
|
||||
/// <summary>
|
||||
/// Boolean indicating whether to add padding to the icon. Defaults to false.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool Padding { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The data URL of the favicon.
|
||||
/// </summary>
|
||||
private string? _faviconDataUrl;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
if (FaviconBytes is not null)
|
||||
{
|
||||
string mimeType = DetectMimeType(FaviconBytes);
|
||||
string base64String = Convert.ToBase64String(FaviconBytes);
|
||||
_faviconDataUrl = $"data:{mimeType};base64,{base64String}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detect the mime type of the favicon.
|
||||
/// </summary>
|
||||
/// <param name="bytes">The bytes of the favicon.</param>
|
||||
/// <returns>The mime type of the favicon.</returns>
|
||||
private static string DetectMimeType(byte[] bytes)
|
||||
{
|
||||
// Check for SVG.
|
||||
if (bytes.Length >= 5)
|
||||
{
|
||||
string header = System.Text.Encoding.ASCII.GetString(bytes.Take(5).ToArray()).ToLower();
|
||||
if (header.Contains("<?xml") || header.Contains("<svg"))
|
||||
{
|
||||
return "image/svg+xml";
|
||||
}
|
||||
}
|
||||
|
||||
// Check for ICO.
|
||||
if (bytes.Length >= 4 &&
|
||||
bytes[0] == 0x00 && bytes[1] == 0x00 &&
|
||||
bytes[2] == 0x01 && bytes[3] == 0x00)
|
||||
{
|
||||
return "image/x-icon";
|
||||
}
|
||||
|
||||
// Check for PNG.
|
||||
if (bytes.Length >= 4 &&
|
||||
bytes[0] == 0x89 && bytes[1] == 0x50 &&
|
||||
bytes[2] == 0x4E && bytes[3] == 0x47)
|
||||
{
|
||||
return "image/png";
|
||||
}
|
||||
|
||||
// Default to x-icon if unknown.
|
||||
return "image/x-icon";
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
@using System.Text.RegularExpressions
|
||||
@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">
|
||||
<h3 class="mb-4 text-xl font-semibold dark:text-white">@SharedLocalizer["Notes"]</h3>
|
||||
<div class="dark:text-gray-300">
|
||||
@((MarkupString)ConvertUrlsToLinks(Notes).Replace(Environment.NewLine, "<br>"))
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private IStringLocalizer SharedLocalizer => LocalizerFactory.Create("SharedResources", "AliasVault.Client");
|
||||
|
||||
/// <summary>
|
||||
/// The notes to display.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string Notes { get; set; } = "";
|
||||
|
||||
private static string ConvertUrlsToLinks(string text)
|
||||
{
|
||||
// HTML-encode the text before processing URLs
|
||||
var encodedText = System.Web.HttpUtility.HtmlEncode(text);
|
||||
|
||||
string urlPattern = @"(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})";
|
||||
return Regex.Replace(encodedText, urlPattern, match =>
|
||||
{
|
||||
string url = match.Value;
|
||||
if (!url.StartsWith("http://") && !url.StartsWith("https://"))
|
||||
{
|
||||
url = "http://" + url;
|
||||
}
|
||||
return $"<a href=\"{url}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-blue-500 hover:underline\">{match.Value}</a>";
|
||||
}, RegexOptions.None, TimeSpan.FromMilliseconds(200));
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,12 @@
|
||||
<SortableTableRow Class="cursor-pointer" OnClick="@(() => NavigateToCredential(credential.Id))">
|
||||
<SortableTableColumn Padding="false">
|
||||
<div class="flex items-center space-x-2 py-2 pl-2">
|
||||
<DisplayFavicon FaviconBytes="@credential.Logo" Width="24" />
|
||||
<ItemIcon
|
||||
ItemType="@credential.ItemType"
|
||||
Logo="@credential.Logo"
|
||||
CardNumber="@credential.CardNumber"
|
||||
AltText="@credential.Service"
|
||||
SizeClass="w-6 h-6" />
|
||||
<span class="font-bold ml-2">@credential.Service</span>
|
||||
</div>
|
||||
</SortableTableColumn>
|
||||
|
||||
@@ -69,7 +69,12 @@
|
||||
<div
|
||||
class="search-result @(i == SelectedIndex ? "bg-gray-100 dark:bg-gray-700" : "") px-4 py-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center"
|
||||
@onclick="() => SelectResult(result)">
|
||||
<DisplayFavicon FaviconBytes="@result.Logo?.FileData" Width="24" />
|
||||
<ItemIcon
|
||||
ItemType="@result.ItemType"
|
||||
Logo="@result.Logo?.FileData"
|
||||
CardNumber="@ItemService.GetFieldValue(result, FieldKey.CardNumber)"
|
||||
AltText="@(result.Name ?? string.Empty)"
|
||||
SizeClass="w-6 h-6" />
|
||||
<div class="ml-2">
|
||||
<div class="font-medium text-gray-900 dark:text-white">
|
||||
@if (result.FolderId.HasValue && FolderPathCache.TryGetValue(result.FolderId.Value, out var folderPath))
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
@using AliasVault.Client.Main.Components
|
||||
@using AliasVault.Client.Main.Components.Alerts
|
||||
@using AliasVault.Client.Main.Components.Attachments
|
||||
@using AliasVault.Client.Main.Components.Credentials
|
||||
@using AliasVault.Client.Main.Components.Email
|
||||
@using AliasVault.Client.Main.Components.Forms
|
||||
@using AliasVault.Client.Main.Components.Items
|
||||
@using AliasVault.Client.Main.Components.Layout
|
||||
@using AliasVault.Client.Main.Components.Loading
|
||||
@using AliasVault.Client.Main.Components.Settings
|
||||
|
||||
@@ -1165,10 +1165,6 @@ video {
|
||||
width: 6rem;
|
||||
}
|
||||
|
||||
.w-28 {
|
||||
width: 7rem;
|
||||
}
|
||||
|
||||
.w-3 {
|
||||
width: 0.75rem;
|
||||
}
|
||||
@@ -3959,11 +3955,6 @@ video {
|
||||
--tw-ring-color: rgb(153 27 27 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.dark\:focus\:ring-red-900:focus:is(.dark *) {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgb(127 29 29 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.dark\:focus\:ring-offset-gray-800:focus:is(.dark *) {
|
||||
--tw-ring-offset-color: #1f2937;
|
||||
}
|
||||
@@ -3991,10 +3982,6 @@ video {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.sm\:mb-0 {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.sm\:ml-1 {
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
@@ -4354,10 +4341,6 @@ video {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.xl\:mb-4 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.xl\:grid-cols-3 {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
@@ -4381,10 +4364,6 @@ video {
|
||||
.\32xl\:col-span-2 {
|
||||
grid-column: span 2 / span 2;
|
||||
}
|
||||
|
||||
.\32xl\:mb-0 {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.\[\&\>svg\]\:h-full>svg {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 115 KiB |
Reference in New Issue
Block a user