start destructuring ZeroVue class
This commit is contained in:
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
});
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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 { }
|
||||
Reference in New Issue
Block a user