start destructuring ZeroVue class

This commit is contained in:
2021-12-03 15:49:34 +01:00
parent b0c958ffb3
commit 3ad9714618
15 changed files with 264 additions and 84 deletions
@@ -0,0 +1,11 @@
namespace zero.Backoffice;
public class BackofficeAssetFileSystem : PhysicalFileSystem, IBackofficeAssetFileSystem
{
public BackofficeAssetFileSystem(string root) : base(root) { }
}
public interface IBackofficeAssetFileSystem : IFileSystem
{
}
@@ -2,6 +2,11 @@
public class BackofficeOptions
{
/// <summary>
/// Public path to zero assets
/// </summary>
public string AssetPath { get; set; }
/// <summary>
/// Paths in the backoffice which are not handled by zero
/// </summary>
@@ -0,0 +1,17 @@
using Microsoft.AspNetCore.Mvc;
using zero.Api.Filters;
using zero.Backoffice.Services;
using zero.Identity.Models;
namespace zero.Backoffice;
[ZeroSystemApi]
[ZeroAuthorize(false)]
public class DebugController : ZeroBackofficeController
{
[HttpGet("icons")]
public async Task<ActionResult> GetIcons([FromServices] IIconRepository icons)
{
return Ok(await icons.GetSets());
}
}
+14 -1
View File
@@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using System.IO;
using zero.Backoffice.Endpoints.Account;
using zero.Backoffice.Services;
namespace zero.Backoffice;
@@ -29,6 +30,15 @@ public class ZeroBackofficePlugin : ZeroPlugin
services.AddTransient<IZeroVue, ZeroVue>();
services.AddSingleton<IMapperProfile, AccountMapperProfile>();
services.AddSingleton<IIconRepository, IconRepository>();
services.AddSingleton<IBackofficeAssetFileSystem, BackofficeAssetFileSystem>(svc =>
{
IOptions<BackofficeOptions> options = svc.GetRequiredService<IOptions<BackofficeOptions>>();
IWebHostEnvironment env = svc.GetRequiredService<IWebHostEnvironment>();
return new(Path.Combine(env.WebRootPath, options.Value.AssetPath));
});
services.AddZeroBackofficeUIComposition();
//services.AddTransient<ISectionsApi, SectionsApi>();
@@ -43,13 +53,16 @@ public class ZeroBackofficePlugin : ZeroPlugin
protected void ConfigureOptions(BackofficeOptions options, IWebHostEnvironment env)
{
options.ExcludedPaths.Add("resources");
options.AssetPath = "zero/resources";
options.DevServer.WorkingDirectory = Path.Combine(env.ContentRootPath, "..", "Zero.Web.UI", "App");
options.IconSets.Add(new BackofficeIconSet()
{
Alias = "feather",
Name = "Feather",
SpritePath = "/assets/icons/feather.svg",
SpritePath = "icons/feather.svg",
Prefix = "fth"
});
+25
View File
@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="robots" content="noindex,nofollow">
<meta name="author" content="brothers">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta http-equiv="expires" content="0">
<link rel="icon" type="image/png" href="/assets/zero-icon.png" sizes="32x32">
<link rel="image_src" type="image/png" href="/assets/zero-icon.png" sizes="192x192" />
<link rel="shortcut icon" type="image/png" href="/assets/zero-icon.png" sizes="196x196">
<base href="~/">
{css}
<title>welcome to zero</title>
</head>
<body class="theme-light theme-rounded">
<div id="app"></div>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">{svg}</svg>
{js}
</body>
</html>
@@ -0,0 +1,12 @@
namespace zero.Backoffice.Services;
public class BackofficeIconSetPresentation
{
public string Alias { get; set; }
public string Name { get; set; }
public string Prefix { get; set; }
public IEnumerable<string> Icons { get; set; }
}
@@ -0,0 +1,113 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Collections.Concurrent;
using System.IO;
using System.Text;
using System.Xml.Linq;
namespace zero.Backoffice.Services;
public class IconRepository : IIconRepository
{
protected IOptions<BackofficeOptions> Options { get; set; }
protected IBackofficeAssetFileSystem FileSystem { get; set; }
protected ILogger<IconRepository> Logger { get; set; }
protected ConcurrentBag<BackofficeIconSetPresentation> CachedSets { get; private set; } = new();
protected string CachedSvg { get; set; }
protected bool IsLoaded { get; set; }
public IconRepository(IOptions<BackofficeOptions> options, IBackofficeAssetFileSystem fileSystem, ILogger<IconRepository> logger)
{
Options = options;
FileSystem = fileSystem;
Logger = logger;
}
/// <inheritdoc />
public async Task<IEnumerable<BackofficeIconSetPresentation>> GetSets()
{
await EnsureIconsAreLoaded();
return CachedSets;
}
/// <inheritdoc />
public async Task<string> GetCompiledSvg()
{
await EnsureIconsAreLoaded();
return CachedSvg;
}
async Task EnsureIconsAreLoaded()
{
if (IsLoaded)
{
return;
}
StringBuilder svg = new();
foreach (BackofficeIconSet set in Options.Value.IconSets)
{
if (!await FileSystem.Exists(set.SpritePath))
{
Logger.LogWarning("Could not load icon set {alias} from path {path}", set.Alias, FileSystem.Map(set.SpritePath));
continue;
}
using Stream stream = await FileSystem.StreamFile(set.SpritePath);
XDocument xml = XDocument.Load(stream);
IEnumerable<XElement> symbols = xml.Descendants().Where(x => x.Name.LocalName == "symbol");
if (!symbols.Any())
{
Logger.LogWarning("Icon set {alias} does not contain any <symbol>", set.Alias);
continue;
}
HashSet<string> icons = new();
foreach (XElement symbol in symbols)
{
string symbolAlias = set.Prefix + "-" + symbol.Attribute("id").Value.ToString();
symbol.SetAttributeValue("id", symbolAlias);
svg.Append(symbol.ToString().RemoveNewLines());
icons.Add(symbolAlias);
}
CachedSets.Add(new()
{
Alias = set.Alias,
Name = set.Name,
Prefix = set.Prefix,
Icons = icons
});
}
CachedSvg = svg.ToString();
IsLoaded = true;
}
}
public interface IIconRepository
{
/// <summary>
/// Get all registered icon sets with all their containing icons
/// </summary>
Task<IEnumerable<BackofficeIconSetPresentation>> GetSets();
/// <summary>
/// Get compiled SVG string for alle registered icon sets
/// </summary>
Task<string> GetCompiledSvg();
}
+36 -8
View File
@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using System.IO;
using zero.Api.Controllers;
using zero.Backoffice.Services;
namespace zero.Backoffice.Controllers;
@@ -10,13 +11,17 @@ public class ZeroIndexController : Controller
{
IZeroVue ZeroVue { get; set; }
IZeroOptions Options { get; set; }
IAuthenticationService AuthService { get; set; }
IIconRepository IconRepository { get; set; }
string[] JsDevModulePaths = new[] { "/vite/client", "/app/app.js" };
public ZeroIndexController(IZeroVue zeroVue, IZeroOptions options)
public ZeroIndexController(IZeroVue zeroVue, IZeroOptions options, IAuthenticationService authService, IIconRepository iconRepository)
{
ZeroVue = zeroVue;
Options = options;
AuthService = authService;
IconRepository = iconRepository;
}
@@ -28,21 +33,18 @@ public class ZeroIndexController : Controller
}
string config = await ZeroVue.ConfigAsJson();
bool isLoggedIn = AuthService.IsLoggedIn();
int port = Options.For<BackofficeOptions>().DevServer.Port;
string resourceName = $"zero.Backoffice.Resources.index.tpl.html";
string resourceName = $"zero.Backoffice.Resources.backoffice.tpl.html";
using Stream stream = GetType().Assembly.GetManifestResourceStream(resourceName);
using StreamReader reader = new(stream);
string template = await reader.ReadToEndAsync();
string template = await LoadTemplate(resourceName);
string content = TokenReplacement.Apply(template, new()
{
{ "css", String.Empty },
{ "js", String.Join(String.Empty, GetJsModules("http://localhost:" + port)) },
{ "svg", ZeroVue.GetIconSvg() },
{ "svg", await IconRepository.GetCompiledSvg() },
{ "config", config }
});
@@ -50,10 +52,36 @@ public class ZeroIndexController : Controller
}
//async Task<ActionResult> Login()
//{
// string content = TokenReplacement.Apply(await LoadTemplate("zero.Backoffice.Resources.backoffice.tpl.html"), new()
// {
// { "css", String.Empty },
// { "js", String.Join(String.Empty, GetJsModules("http://localhost:" + port)) },
// { "svg", ZeroVue.GetIconSvg() },
// { "config", config }
// });
//}
//async Task<ActionResult> Backoffice()
//{
//}
IEnumerable<string> GetJsModules(string domain)
{
return JsDevModulePaths.Select(path => $"<script type='module' src='{domain.TrimEnd('/')}{path.EnsureStartsWith('/')}'></script>");
}
async Task<string> LoadTemplate(string resourceName)
{
using Stream stream = GetType().Assembly.GetManifestResourceStream(resourceName);
using StreamReader reader = new(stream);
return await reader.ReadToEndAsync();
}
}
public class ZeroBackofficeModel
-65
View File
@@ -63,7 +63,6 @@ public class ZeroVue : IZeroVue
config.Alias = CreateAliases();
config.AppId = Context.AppId;
//config.SharedAppId = Constants.Database.SharedAppId; // TODO appx
config.Icons = CreateIconSets();
config.MultiApps = Options.For<ApplicationOptions>().EnableMultiple;
ZeroUser user = await AuthenticationApi.GetUser();
@@ -349,57 +348,6 @@ public class ZeroVue : IZeroVue
}
IList<ZeroVueIconSet> CreateIconSets()
{
List<ZeroVueIconSet> result = new();
IEnumerable<BackofficeIconSet> sets = Options.For<BackofficeOptions>().IconSets;
StringBuilder svg = new();
foreach (BackofficeIconSet set in sets)
{
string path = Paths.Map(set.SpritePath.TrimStart('/'));
if (!File.Exists(path))
{
Logger.LogWarning("Could not load icon set {alias} from path {path}", set.Alias, path);
continue;
}
string svgContent = File.ReadAllText(path, Encoding.UTF8);
XDocument xml = XDocument.Parse(svgContent);
IEnumerable<XElement> symbols = xml.Descendants().Where(x => x.Name.LocalName == "symbol"); // ("symbol");
if (!symbols.Any())
{
Logger.LogWarning("Icon set {alias} does not contain any <symbol>", set.Alias);
continue;
}
ZeroVueIconSet iconSet = new()
{
Alias = set.Alias,
Name = set.Name,
Prefix = set.Prefix
};
foreach (XElement symbol in symbols)
{
string symbolAlias = set.Prefix + "-" + symbol.Attribute("id").Value.ToString();
symbol.SetAttributeValue("id", symbolAlias);
svg.Append(symbol.ToString().RemoveNewLines());
iconSet.Icons.Add(symbolAlias);
}
result.Add(iconSet);
}
IconSymbolsSvg = svg.ToString();
return result;
}
List<ZeroVueBlueprint> CreateBlueprints()
{
List<ZeroVueBlueprint> items = new();
@@ -489,8 +437,6 @@ public class ZeroVueConfig
public Dictionary<string, object> Overrides { get; set; } = new Dictionary<string, object>();
public IList<ZeroVueIconSet> Icons { get; set; } = new List<ZeroVueIconSet>();
public ZeroVueServices Services { get; set; } = new();
public IList<ZeroVueBlueprint> Blueprints { get; set; } = new List<ZeroVueBlueprint>();
@@ -558,17 +504,6 @@ public class ZeroVueApplication
public string Image { get; set; }
}
public class ZeroVueIconSet
{
public string Alias { get; set; }
public string Name { get; set; }
public string Prefix { get; set; }
public HashSet<string> Icons { get; set; } = new();
}
public class ZeroVueServices
{
public string YouTubeApiKey { get; set; }
+2 -1
View File
@@ -18,7 +18,8 @@
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\index.tpl.html" />
<EmbeddedResource Include="Resources\login.tpl.html" />
<EmbeddedResource Include="Resources\backoffice.tpl.html" />
</ItemGroup>
<ItemGroup>
@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace zero.FileStorage;
+10
View File
@@ -4,6 +4,16 @@ namespace zero.FileStorage;
public interface IFileSystem
{
/// <summary>
/// Map a relative path to an absolute internal path
/// </summary>
string Map(string path);
/// <summary>
/// Map an internal path to a public accessible path
/// </summary>
string MapToPublicPath(string path);
/// <summary>
/// Determines whether a file or directory at the given path already exists
/// </summary>
+16 -1
View File
@@ -14,6 +14,20 @@ public class PhysicalFileSystem : IFileSystem
}
/// <inheritdoc />
public virtual string Map(string path)
{
return ResolvePath(path);
}
/// <inheritdoc />
public virtual string MapToPublicPath(string path)
{
return path.EnsureStartsWith('/');
}
/// <inheritdoc />
public virtual Task<bool> Exists(string path, CancellationToken cancellationToken = default)
{
@@ -268,10 +282,11 @@ public class PhysicalFileSystem : IFileSystem
path = path.Replace('\\', '/').FullTrim().Trim('/');
string physicalPath = Path.Combine(_root, path);
string rootPath = Path.GetFullPath(_root);
// do not allow paths which are outside this file system
// the boundaries are set be the basePath which is assigned in the file system constructor
if (!Path.GetFullPath(physicalPath).StartsWith(_root, StringComparison.InvariantCultureIgnoreCase))
if (!Path.GetFullPath(physicalPath).StartsWith(rootPath, StringComparison.InvariantCultureIgnoreCase))
{
throw new FileSystemException($"The path '{path}' is outside the file system");
}
+2 -8
View File
@@ -12,17 +12,11 @@ public class MediaFileSystem : PhysicalFileSystem, IMediaFileSystem
/// <inheritdoc />
public virtual string MapToPublicPath(string path)
public override string MapToPublicPath(string path)
{
return PublicPathPrefix + path.TrimStart('/');
}
}
public interface IMediaFileSystem : IFileSystem
{
/// <summary>
/// Map an internal path to a public accessible path
/// </summary>
string MapToPublicPath(string path);
}
public interface IMediaFileSystem : IFileSystem { }