Add report logos and configurable folder scan depth
Report branding (top-left MSP logo, top-right client logo): - Add MspLogo to AppSettings; client logo already on TenantProfile - IUserSessionService.CurrentBranding composes MSP + active profile logo - New reusable LogoUpload component (InputFile -> base64 LogoData, 512KB cap) - MSP logo upload in Settings; optional client logo in profile create/edit - Wire ReportBranding into all 6 HTML export pages - Fix EditProfile dropping ClientLogo on edit Storage metrics: expose folder scan depth (0-20) in scan options UI, passed to existing StorageScanOptions.FolderDepth recursion. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using SharepointToolbox.Web.Core.Models
|
||||
|
||||
@* Reusable logo picker. Reads an image into a base64 LogoData (no disk/blob storage). *@
|
||||
<div class="logo-upload">
|
||||
@if (Value is not null)
|
||||
{
|
||||
<div class="flex-row" style="gap:12px;align-items:center">
|
||||
<img src="data:@Value.MimeType;base64,@Value.Base64" alt=""
|
||||
style="max-height:60px;max-width:200px;object-fit:contain;border:1px solid var(--border);border-radius:4px;padding:4px;background:#fff" />
|
||||
<button type="button" class="btn btn-secondary btn-sm" @onclick="Remove">Remove</button>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<InputFile OnChange="OnChange" accept="image/png,image/jpeg,image/svg+xml,image/gif" />
|
||||
<small class="text-muted d-block">PNG, JPEG, SVG or GIF — max @(MaxBytes / 1024) KB.</small>
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(_error)) { <div class="alert alert-error mt-8">@_error</div> }
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter] public LogoData? Value { get; set; }
|
||||
[Parameter] public EventCallback<LogoData?> ValueChanged { get; set; }
|
||||
|
||||
/// <summary>Upload size cap. Logos are stored inline as base64 in JSON, so keep small.</summary>
|
||||
[Parameter] public long MaxBytes { get; set; } = 512 * 1024;
|
||||
|
||||
private string _error = string.Empty;
|
||||
|
||||
private async Task OnChange(InputFileChangeEventArgs e)
|
||||
{
|
||||
_error = string.Empty;
|
||||
var file = e.File;
|
||||
if (file is null) return;
|
||||
|
||||
if (file.Size > MaxBytes)
|
||||
{
|
||||
_error = $"File too large ({file.Size / 1024} KB). Max {MaxBytes / 1024} KB.";
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
await file.OpenReadStream(MaxBytes).CopyToAsync(ms);
|
||||
var mime = string.IsNullOrWhiteSpace(file.ContentType) ? "image/png" : file.ContentType;
|
||||
var logo = new LogoData { Base64 = Convert.ToBase64String(ms.ToArray()), MimeType = mime };
|
||||
Value = logo;
|
||||
await ValueChanged.InvokeAsync(logo);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_error = $"Could not read image: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Remove()
|
||||
{
|
||||
Value = null;
|
||||
_error = string.Empty;
|
||||
await ValueChanged.InvokeAsync(null);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user