-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Matthew Kilgore
committed
Sep 27, 2024
0 parents
commit 6e4cee9
Showing
10 changed files
with
1,034 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
namespace SysAdminsMedia.BlazorIconify.Extensions; | ||
|
||
public static class DictionaryExtension | ||
{ | ||
public static string Get(this Dictionary<string, object> data, string className) | ||
{ | ||
if (data is null) | ||
return string.Empty; | ||
|
||
return data.TryGetValue(className, out var value) ? value.ToString()! : string.Empty; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
using System.Text.Json; | ||
using System.Text.Json.Serialization; | ||
using Blazored.LocalStorage; | ||
using Microsoft.Extensions.DependencyInjection; | ||
|
||
namespace SysAdminsMedia.BlazorIconify.Extensions; | ||
|
||
public static class IconifyExtension | ||
{ | ||
public static IServiceCollection AddBlazorIconify(this IServiceCollection services) => services | ||
.AddScoped<Registry>() | ||
.AddBlazoredLocalStorage( | ||
config => | ||
{ | ||
config.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase; | ||
config.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; | ||
config.JsonSerializerOptions.IgnoreReadOnlyProperties = true; | ||
config.JsonSerializerOptions.PropertyNameCaseInsensitive = true; | ||
config.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; | ||
config.JsonSerializerOptions.ReadCommentHandling = JsonCommentHandling.Skip; | ||
config.JsonSerializerOptions.WriteIndented = false; | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
using System.Text.Json.Serialization; | ||
|
||
namespace SysAdminsMedia.BlazorIconify; | ||
|
||
public class IconMetaData | ||
{ | ||
[JsonPropertyName("name")] public required string Name { get; set; } | ||
[JsonPropertyName("content")] public required string Content { get; set; } | ||
[JsonPropertyName("time_fetched")] public DateTime TimeFetched { get; set; } | ||
|
||
[JsonPropertyName("color")] public string? Color { get; set; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
@using BlazorIconify.Extensions | ||
|
||
<main @attributes="@Attributes" | ||
class="@Attributes.Get("class")" | ||
style="@Attributes.Get("style")"> | ||
@((MarkupString)_svg) | ||
</main> | ||
|
||
<style> | ||
.icon { | ||
display: inline-block; | ||
width: 1em; | ||
height: 1em; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
using System.Text; | ||
using System.Text.Encodings.Web; | ||
using System.Xml; | ||
using Blazored.LocalStorage; | ||
using Microsoft.AspNetCore.Components; | ||
using SysAdminsMedia.BlazorIconify.Extensions; | ||
|
||
namespace SysAdminsMedia.BlazorIconify; | ||
|
||
public partial class Iconify : ComponentBase | ||
{ | ||
private const string API = "https://api.iconify.design/"; | ||
private const string ErrorIcon = "ic:baseline-do-not-disturb"; | ||
|
||
private string _svg = string.Empty; | ||
private bool _initialized; | ||
|
||
[Inject] public HttpClient HttpClient { get; set; } = null!; | ||
[Inject] public ILocalStorageService LocalStorage { get; set; } = null!; | ||
[Inject] public Registry Registry { get; set; } = null!; | ||
|
||
[Parameter(CaptureUnmatchedValues = true)] | ||
public Dictionary<string, object> Attributes { get; set; } = null!; | ||
|
||
[Parameter] public string Icon { get; set; } = string.Empty; | ||
|
||
[Parameter] public string Color { get; set; } = string.Empty; | ||
|
||
|
||
protected override async Task OnAfterRenderAsync(bool firstRender) | ||
{ | ||
_initialized = true; | ||
|
||
if (!firstRender) return; | ||
|
||
if (string.IsNullOrEmpty(Icon)) | ||
{ | ||
// Fallback to error icon if no icon is provided | ||
Icon = ErrorIcon; | ||
return; | ||
} | ||
|
||
// Only fetch the icon if it has changed | ||
if (await Registry.IsCached(Icon, Color)) | ||
{ | ||
var metadata = await Registry.GetIcon(Icon, Color); | ||
if (metadata is null) return; | ||
|
||
var svg = TryParseToXml(metadata.Content); | ||
if (svg is null) return; | ||
|
||
UpdateSvg(svg); | ||
} | ||
else | ||
{ | ||
string iconUrl = $"{API}{Icon.Replace(':', '/')}.svg"; | ||
if (!string.IsNullOrEmpty(Color)) | ||
{ | ||
iconUrl += $"?color={UrlEncoder.Default.Encode(Color)}"; | ||
} | ||
_svg = await FetchIconAsync(iconUrl); | ||
|
||
if (string.IsNullOrEmpty(_svg)) | ||
{ | ||
Console.WriteLine($"Failed to fetch icon {(!string.IsNullOrEmpty(Icon) ? Icon : "\"null\"")}"); | ||
return; | ||
} | ||
|
||
var svg = TryParseToXml(_svg); | ||
if (svg is null) return; | ||
|
||
UpdateSvg(svg); | ||
|
||
await Registry.AddIcon(new IconMetaData | ||
{ | ||
Name = Icon, | ||
Content = _svg, | ||
Color = Color, | ||
TimeFetched = DateTime.Now | ||
}); | ||
} | ||
|
||
if (string.IsNullOrEmpty(_svg)) | ||
Console.WriteLine($"Failed to fetch icon {this}"); | ||
|
||
StateHasChanged(); | ||
Console.WriteLine("RENDER ICONIFY"); | ||
} | ||
|
||
protected override async Task OnParametersSetAsync() | ||
{ | ||
if (!_initialized) return; | ||
await OnAfterRenderAsync(true); | ||
} | ||
|
||
private async Task<string> FetchIconAsync(string url) | ||
{ | ||
if (string.IsNullOrEmpty(Icon)) return string.Empty; | ||
|
||
var response = await HttpClient.GetByteArrayAsync(url); | ||
var iconContents = Encoding.UTF8.GetString(response); | ||
|
||
if (iconContents is not "404" && response is not ({ Length: 0 } or null)) | ||
return iconContents; | ||
|
||
iconContents = string.Empty; | ||
return iconContents; | ||
} | ||
|
||
private static XmlDocument? TryParseToXml(string content) | ||
{ | ||
try | ||
{ | ||
var document = new XmlDocument(); | ||
document.LoadXml(content); | ||
|
||
return document; | ||
} | ||
catch (XmlException ex) | ||
{ | ||
Console.WriteLine("Failed to parse xml from svg file."); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
private void UpdateSvg(XmlDocument document) | ||
{ | ||
var rootElement = document.DocumentElement; | ||
|
||
switch (rootElement) | ||
{ | ||
case null: | ||
Console.WriteLine("No root element."); | ||
return; | ||
case not { Name: "svg" } or null: | ||
Console.WriteLine("Failed to find svg element."); | ||
return; | ||
} | ||
|
||
if (rootElement is null) | ||
{ | ||
Console.WriteLine("Failed to find svg element."); | ||
return; | ||
} | ||
|
||
rootElement.SetAttribute("class", $"{Attributes.Get("i-class")} icon"); | ||
rootElement.SetAttribute("style", Attributes.Get("i-style")); | ||
rootElement.RemoveAttribute("width"); | ||
rootElement.RemoveAttribute("height"); | ||
|
||
foreach (XmlElement child in rootElement.ChildNodes) | ||
{ | ||
if (child.Name.ToLower() is not "path") continue; | ||
} | ||
|
||
_svg = rootElement.OuterXml; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
# SysAdminsMedia.BlazorIconify | ||
BlazorIconify is a Blazor component library for Iconify, a unified icon framework that provides a consistent icon experience across all platforms. BlazorIconify is a wrapper around the Iconify API that allows you to easily add icons to your Blazor applications. | ||
|
||
## Installation | ||
You can install BlazorIconify via NuGet. Run the following command in the Package Manager Console: | ||
``` | ||
Install-Package SysAdminsMedia.BlazorIconify | ||
``` | ||
or via the .NET Core CLI: | ||
``` | ||
dotnet add package SysAdminsMedia.BlazorIconify | ||
``` | ||
|
||
## Usage | ||
First, you need to add the following line to your `_Imports.razor` file: | ||
```csharp | ||
@using SysAdminsMedia.BlazorIconify | ||
``` | ||
|
||
Then, you can use the `Iconify` component in your Blazor components like this: | ||
```html | ||
<Iconify Icon="mdi:home" /> | ||
``` | ||
|
||
You can also adjust the color and other properties of the icon: | ||
```html | ||
<Iconify Icon="mdi:home" Color="red" Class="my-custom-class" Style="align-content: center;" /> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
using Blazored.LocalStorage; | ||
|
||
namespace SysAdminsMedia.BlazorIconify; | ||
|
||
public sealed class Registry(ILocalStorageService LocalStorage) | ||
{ | ||
private const string CachedIconsKey = "cached-icons"; | ||
|
||
private List<IconMetaData> _icons = []; | ||
|
||
public async Task AddIcon(IconMetaData metadata) | ||
{ | ||
if(string.IsNullOrEmpty(metadata.Name)) return; | ||
if (IsRegistered(metadata.Name)) return; | ||
|
||
_icons.Add(metadata); | ||
await LocalStorage.SetItemAsync(CachedIconsKey, _icons); | ||
} | ||
|
||
public async Task<IconMetaData?> GetIcon(string icon, string? color = "") | ||
{ | ||
if (string.IsNullOrEmpty(icon)) return null; | ||
|
||
var icons = await GetCachedIcons(); | ||
return icons.FirstOrDefault(x => x.Name == icon && x.Color == color); | ||
} | ||
|
||
public async Task<bool> IsCached(string icon, string? color = "") | ||
{ | ||
if (string.IsNullOrEmpty(icon)) return false; | ||
|
||
var icons = await GetCachedIcons(); | ||
return icons.Exists(x => x.Name == icon && x.Color == color); | ||
} | ||
|
||
public async Task Clear() | ||
{ | ||
_icons.Clear(); | ||
await LocalStorage.RemoveItemAsync(CachedIconsKey); | ||
} | ||
|
||
private async Task<List<IconMetaData>> GetCachedIcons() | ||
{ | ||
if (_icons.Count > 0) return _icons; | ||
return _icons = await LocalStorage.GetItemAsync<List<IconMetaData>>(CachedIconsKey) ?? []; | ||
} | ||
|
||
private bool IsRegistered(string icon) => | ||
_icons.Exists(x => x.Name == icon); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
<AssemblyName>SysAdminsMedia.BlazorIconify</AssemblyName> | ||
<RootNamespace>SysAdminsMedia.BlazorIconify</RootNamespace> | ||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> | ||
<PackageId>SysAdminsMedia.BlazorIconify</PackageId> | ||
<Authors>SysAdmins Media</Authors> | ||
<RepositoryUrl>https://github.com/sysadminsmedia/BlazorIconify</RepositoryUrl> | ||
<RepositoryType>git</RepositoryType> | ||
<Company>SysAdmins Media</Company> | ||
<PackageLicenseExpression>MIT</PackageLicenseExpression> | ||
<PackageReadmeFile>README.md</PackageReadmeFile> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" /> | ||
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.8" /> | ||
<None Include="README.md" Pack="true" PackagePath="\"/> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
|
||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SysAdminsMedia.BlazorIconify", "SysAdminsMedia.BlazorIconify.csproj", "{B841266B-8C1E-49BC-BF53-5EA4064FCC41}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Release|Any CPU = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{B841266B-8C1E-49BC-BF53-5EA4064FCC41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{B841266B-8C1E-49BC-BF53-5EA4064FCC41}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{B841266B-8C1E-49BC-BF53-5EA4064FCC41}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{B841266B-8C1E-49BC-BF53-5EA4064FCC41}.Release|Any CPU.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
EndGlobal |