feat(11-04): add logo management commands to SettingsViewModel and ProfileManagementViewModel
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,118 @@
|
|||||||
|
using System.IO;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
using Moq;
|
||||||
|
using SharepointToolbox.Core.Models;
|
||||||
|
using SharepointToolbox.Infrastructure.Auth;
|
||||||
|
using SharepointToolbox.Infrastructure.Persistence;
|
||||||
|
using SharepointToolbox.Services;
|
||||||
|
using SharepointToolbox.ViewModels;
|
||||||
|
|
||||||
|
namespace SharepointToolbox.Tests.ViewModels;
|
||||||
|
|
||||||
|
[Trait("Category", "Unit")]
|
||||||
|
public class ProfileManagementViewModelLogoTests : IDisposable
|
||||||
|
{
|
||||||
|
private readonly string _tempFile;
|
||||||
|
private readonly Mock<IBrandingService> _mockBranding;
|
||||||
|
private readonly GraphClientFactory _graphClientFactory;
|
||||||
|
private readonly ILogger<ProfileManagementViewModel> _logger;
|
||||||
|
|
||||||
|
public ProfileManagementViewModelLogoTests()
|
||||||
|
{
|
||||||
|
_tempFile = Path.GetTempFileName();
|
||||||
|
File.Delete(_tempFile);
|
||||||
|
_mockBranding = new Mock<IBrandingService>();
|
||||||
|
_graphClientFactory = new GraphClientFactory(new MsalClientFactory());
|
||||||
|
_logger = NullLogger<ProfileManagementViewModel>.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (File.Exists(_tempFile)) File.Delete(_tempFile);
|
||||||
|
if (File.Exists(_tempFile + ".tmp")) File.Delete(_tempFile + ".tmp");
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProfileManagementViewModel CreateViewModel()
|
||||||
|
{
|
||||||
|
var profileService = new ProfileService(new ProfileRepository(_tempFile));
|
||||||
|
return new ProfileManagementViewModel(
|
||||||
|
profileService,
|
||||||
|
_mockBranding.Object,
|
||||||
|
_graphClientFactory,
|
||||||
|
_logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_BrowseClientLogoCommand_IsNotNull()
|
||||||
|
{
|
||||||
|
var vm = CreateViewModel();
|
||||||
|
Assert.NotNull(vm.BrowseClientLogoCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_ClearClientLogoCommand_IsNotNull()
|
||||||
|
{
|
||||||
|
var vm = CreateViewModel();
|
||||||
|
Assert.NotNull(vm.ClearClientLogoCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_AutoPullClientLogoCommand_IsNotNull()
|
||||||
|
{
|
||||||
|
var vm = CreateViewModel();
|
||||||
|
Assert.NotNull(vm.AutoPullClientLogoCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BrowseClientLogoCommand_CannotExecute_WhenNoProfileSelected()
|
||||||
|
{
|
||||||
|
var vm = CreateViewModel();
|
||||||
|
Assert.False(vm.BrowseClientLogoCommand.CanExecute(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ClearClientLogoCommand_CannotExecute_WhenNoProfileSelected()
|
||||||
|
{
|
||||||
|
var vm = CreateViewModel();
|
||||||
|
Assert.False(vm.ClearClientLogoCommand.CanExecute(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AutoPullClientLogoCommand_CannotExecute_WhenNoProfileSelected()
|
||||||
|
{
|
||||||
|
var vm = CreateViewModel();
|
||||||
|
Assert.False(vm.AutoPullClientLogoCommand.CanExecute(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ClearClientLogoCommand_ClearsClientLogo_AndPersists()
|
||||||
|
{
|
||||||
|
var profileService = new ProfileService(new ProfileRepository(_tempFile));
|
||||||
|
var profile = new TenantProfile
|
||||||
|
{
|
||||||
|
Name = "TestTenant",
|
||||||
|
TenantUrl = "https://test.sharepoint.com",
|
||||||
|
ClientId = "00000000-0000-0000-0000-000000000001",
|
||||||
|
ClientLogo = new LogoData { Base64 = "dGVzdA==", MimeType = "image/png" }
|
||||||
|
};
|
||||||
|
await profileService.AddProfileAsync(profile);
|
||||||
|
|
||||||
|
var vm = new ProfileManagementViewModel(
|
||||||
|
profileService,
|
||||||
|
_mockBranding.Object,
|
||||||
|
_graphClientFactory,
|
||||||
|
_logger);
|
||||||
|
|
||||||
|
vm.SelectedProfile = profile;
|
||||||
|
|
||||||
|
await vm.ClearClientLogoCommand.ExecuteAsync(null);
|
||||||
|
|
||||||
|
Assert.Null(profile.ClientLogo);
|
||||||
|
|
||||||
|
// Verify persisted
|
||||||
|
var profiles = await profileService.GetProfilesAsync();
|
||||||
|
var persisted = profiles.First(p => p.Name == "TestTenant");
|
||||||
|
Assert.Null(persisted.ClientLogo);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
using System.IO;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
using Moq;
|
||||||
|
using SharepointToolbox.Core.Models;
|
||||||
|
using SharepointToolbox.Infrastructure.Persistence;
|
||||||
|
using SharepointToolbox.Services;
|
||||||
|
using SharepointToolbox.ViewModels;
|
||||||
|
using SharepointToolbox.ViewModels.Tabs;
|
||||||
|
|
||||||
|
namespace SharepointToolbox.Tests.ViewModels;
|
||||||
|
|
||||||
|
[Trait("Category", "Unit")]
|
||||||
|
public class SettingsViewModelLogoTests : IDisposable
|
||||||
|
{
|
||||||
|
private readonly string _tempFile;
|
||||||
|
|
||||||
|
public SettingsViewModelLogoTests()
|
||||||
|
{
|
||||||
|
_tempFile = Path.GetTempFileName();
|
||||||
|
File.Delete(_tempFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (File.Exists(_tempFile)) File.Delete(_tempFile);
|
||||||
|
if (File.Exists(_tempFile + ".tmp")) File.Delete(_tempFile + ".tmp");
|
||||||
|
}
|
||||||
|
|
||||||
|
private SettingsViewModel CreateViewModel(IBrandingService? brandingService = null)
|
||||||
|
{
|
||||||
|
var settingsService = new SettingsService(new SettingsRepository(_tempFile));
|
||||||
|
var mockBranding = brandingService ?? new Mock<IBrandingService>().Object;
|
||||||
|
var logger = NullLogger<FeatureViewModelBase>.Instance;
|
||||||
|
return new SettingsViewModel(settingsService, mockBranding, logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_BrowseMspLogoCommand_IsNotNull()
|
||||||
|
{
|
||||||
|
var vm = CreateViewModel();
|
||||||
|
Assert.NotNull(vm.BrowseMspLogoCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_ClearMspLogoCommand_IsNotNull()
|
||||||
|
{
|
||||||
|
var vm = CreateViewModel();
|
||||||
|
Assert.NotNull(vm.ClearMspLogoCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_MspLogoPreview_IsNullByDefault()
|
||||||
|
{
|
||||||
|
var vm = CreateViewModel();
|
||||||
|
Assert.Null(vm.MspLogoPreview);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ClearMspLogoCommand_CallsClearMspLogoAsync_AndSetsMspLogoPreviewToNull()
|
||||||
|
{
|
||||||
|
var mockBranding = new Mock<IBrandingService>();
|
||||||
|
mockBranding.Setup(b => b.ClearMspLogoAsync()).Returns(Task.CompletedTask);
|
||||||
|
|
||||||
|
var vm = CreateViewModel(mockBranding.Object);
|
||||||
|
|
||||||
|
await vm.ClearMspLogoCommand.ExecuteAsync(null);
|
||||||
|
|
||||||
|
mockBranding.Verify(b => b.ClearMspLogoAsync(), Times.Once);
|
||||||
|
Assert.Null(vm.MspLogoPreview);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -89,6 +89,7 @@ public partial class App : Application
|
|||||||
services.AddSingleton<ProfileService>();
|
services.AddSingleton<ProfileService>();
|
||||||
services.AddSingleton<SettingsService>();
|
services.AddSingleton<SettingsService>();
|
||||||
services.AddSingleton<MainWindowViewModel>();
|
services.AddSingleton<MainWindowViewModel>();
|
||||||
|
// Phase 11-04: ProfileManagementViewModel and SettingsViewModel now receive IBrandingService and GraphClientFactory
|
||||||
services.AddTransient<ProfileManagementViewModel>();
|
services.AddTransient<ProfileManagementViewModel>();
|
||||||
services.AddTransient<SettingsViewModel>();
|
services.AddTransient<SettingsViewModel>();
|
||||||
services.AddTransient<ProfileManagementDialog>();
|
services.AddTransient<ProfileManagementDialog>();
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.IO;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Win32;
|
||||||
using SharepointToolbox.Core.Models;
|
using SharepointToolbox.Core.Models;
|
||||||
using SharepointToolbox.Services;
|
using SharepointToolbox.Services;
|
||||||
|
using AppGraphClientFactory = SharepointToolbox.Infrastructure.Auth.GraphClientFactory;
|
||||||
|
|
||||||
namespace SharepointToolbox.ViewModels;
|
namespace SharepointToolbox.ViewModels;
|
||||||
|
|
||||||
public partial class ProfileManagementViewModel : ObservableObject
|
public partial class ProfileManagementViewModel : ObservableObject
|
||||||
{
|
{
|
||||||
private readonly ProfileService _profileService;
|
private readonly ProfileService _profileService;
|
||||||
|
private readonly IBrandingService _brandingService;
|
||||||
|
private readonly AppGraphClientFactory _graphClientFactory;
|
||||||
private readonly ILogger<ProfileManagementViewModel> _logger;
|
private readonly ILogger<ProfileManagementViewModel> _logger;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
@@ -32,15 +37,27 @@ public partial class ProfileManagementViewModel : ObservableObject
|
|||||||
public IAsyncRelayCommand AddCommand { get; }
|
public IAsyncRelayCommand AddCommand { get; }
|
||||||
public IAsyncRelayCommand RenameCommand { get; }
|
public IAsyncRelayCommand RenameCommand { get; }
|
||||||
public IAsyncRelayCommand DeleteCommand { get; }
|
public IAsyncRelayCommand DeleteCommand { get; }
|
||||||
|
public IAsyncRelayCommand BrowseClientLogoCommand { get; }
|
||||||
|
public IAsyncRelayCommand ClearClientLogoCommand { get; }
|
||||||
|
public IAsyncRelayCommand AutoPullClientLogoCommand { get; }
|
||||||
|
|
||||||
public ProfileManagementViewModel(ProfileService profileService, ILogger<ProfileManagementViewModel> logger)
|
public ProfileManagementViewModel(
|
||||||
|
ProfileService profileService,
|
||||||
|
IBrandingService brandingService,
|
||||||
|
AppGraphClientFactory graphClientFactory,
|
||||||
|
ILogger<ProfileManagementViewModel> logger)
|
||||||
{
|
{
|
||||||
_profileService = profileService;
|
_profileService = profileService;
|
||||||
|
_brandingService = brandingService;
|
||||||
|
_graphClientFactory = graphClientFactory;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
|
||||||
AddCommand = new AsyncRelayCommand(AddAsync, CanAdd);
|
AddCommand = new AsyncRelayCommand(AddAsync, CanAdd);
|
||||||
RenameCommand = new AsyncRelayCommand(RenameAsync, () => SelectedProfile != null && !string.IsNullOrWhiteSpace(NewName));
|
RenameCommand = new AsyncRelayCommand(RenameAsync, () => SelectedProfile != null && !string.IsNullOrWhiteSpace(NewName));
|
||||||
DeleteCommand = new AsyncRelayCommand(DeleteAsync, () => SelectedProfile != null);
|
DeleteCommand = new AsyncRelayCommand(DeleteAsync, () => SelectedProfile != null);
|
||||||
|
BrowseClientLogoCommand = new AsyncRelayCommand(BrowseClientLogoAsync, () => SelectedProfile != null);
|
||||||
|
ClearClientLogoCommand = new AsyncRelayCommand(ClearClientLogoAsync, () => SelectedProfile != null);
|
||||||
|
AutoPullClientLogoCommand = new AsyncRelayCommand(AutoPullClientLogoAsync, () => SelectedProfile != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task LoadAsync()
|
public async Task LoadAsync()
|
||||||
@@ -62,6 +79,15 @@ public partial class ProfileManagementViewModel : ObservableObject
|
|||||||
partial void OnNewTenantUrlChanged(string value) => NotifyCommandsCanExecuteChanged();
|
partial void OnNewTenantUrlChanged(string value) => NotifyCommandsCanExecuteChanged();
|
||||||
partial void OnNewClientIdChanged(string value) => NotifyCommandsCanExecuteChanged();
|
partial void OnNewClientIdChanged(string value) => NotifyCommandsCanExecuteChanged();
|
||||||
|
|
||||||
|
partial void OnSelectedProfileChanged(TenantProfile? value)
|
||||||
|
{
|
||||||
|
BrowseClientLogoCommand.NotifyCanExecuteChanged();
|
||||||
|
ClearClientLogoCommand.NotifyCanExecuteChanged();
|
||||||
|
AutoPullClientLogoCommand.NotifyCanExecuteChanged();
|
||||||
|
RenameCommand.NotifyCanExecuteChanged();
|
||||||
|
DeleteCommand.NotifyCanExecuteChanged();
|
||||||
|
}
|
||||||
|
|
||||||
private void NotifyCommandsCanExecuteChanged()
|
private void NotifyCommandsCanExecuteChanged()
|
||||||
{
|
{
|
||||||
AddCommand.NotifyCanExecuteChanged();
|
AddCommand.NotifyCanExecuteChanged();
|
||||||
@@ -132,4 +158,88 @@ public partial class ProfileManagementViewModel : ObservableObject
|
|||||||
_logger.LogError(ex, "Failed to delete profile.");
|
_logger.LogError(ex, "Failed to delete profile.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task BrowseClientLogoAsync()
|
||||||
|
{
|
||||||
|
if (SelectedProfile == null) return;
|
||||||
|
var dialog = new OpenFileDialog
|
||||||
|
{
|
||||||
|
Title = "Select client logo",
|
||||||
|
Filter = "Image files (*.png;*.jpg;*.jpeg)|*.png;*.jpg;*.jpeg",
|
||||||
|
};
|
||||||
|
if (dialog.ShowDialog() != true) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var logo = await _brandingService.ImportLogoAsync(dialog.FileName);
|
||||||
|
SelectedProfile.ClientLogo = logo;
|
||||||
|
await _profileService.UpdateProfileAsync(SelectedProfile);
|
||||||
|
ValidationMessage = string.Empty;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ValidationMessage = ex.Message;
|
||||||
|
_logger.LogError(ex, "Failed to import client logo.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ClearClientLogoAsync()
|
||||||
|
{
|
||||||
|
if (SelectedProfile == null) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SelectedProfile.ClientLogo = null;
|
||||||
|
await _profileService.UpdateProfileAsync(SelectedProfile);
|
||||||
|
ValidationMessage = string.Empty;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ValidationMessage = ex.Message;
|
||||||
|
_logger.LogError(ex, "Failed to clear client logo.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AutoPullClientLogoAsync()
|
||||||
|
{
|
||||||
|
if (SelectedProfile == null) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var graphClient = await _graphClientFactory.CreateClientAsync(
|
||||||
|
SelectedProfile.ClientId, CancellationToken.None);
|
||||||
|
|
||||||
|
var orgs = await graphClient.Organization.GetAsync();
|
||||||
|
var orgId = orgs?.Value?.FirstOrDefault()?.Id;
|
||||||
|
if (orgId is null)
|
||||||
|
{
|
||||||
|
ValidationMessage = "Could not determine organization ID.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var stream = await graphClient.Organization[orgId]
|
||||||
|
.Branding.Localizations["default"].SquareLogo.GetAsync();
|
||||||
|
|
||||||
|
if (stream is null || stream.Length == 0)
|
||||||
|
{
|
||||||
|
ValidationMessage = "No branding logo found for this tenant.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var ms = new MemoryStream();
|
||||||
|
await stream.CopyToAsync(ms);
|
||||||
|
var bytes = ms.ToArray();
|
||||||
|
|
||||||
|
var logo = await _brandingService.ImportLogoFromBytesAsync(bytes);
|
||||||
|
SelectedProfile.ClientLogo = logo;
|
||||||
|
await _profileService.UpdateProfileAsync(SelectedProfile);
|
||||||
|
ValidationMessage = "Client logo pulled from Entra branding.";
|
||||||
|
}
|
||||||
|
catch (Microsoft.Graph.Models.ODataErrors.ODataError ex) when (ex.ResponseStatusCode == 404)
|
||||||
|
{
|
||||||
|
ValidationMessage = "No Entra branding configured for this tenant.";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ValidationMessage = $"Failed to pull logo: {ex.Message}";
|
||||||
|
_logger.LogWarning(ex, "Auto-pull client logo failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace SharepointToolbox.ViewModels.Tabs;
|
|||||||
public partial class SettingsViewModel : FeatureViewModelBase
|
public partial class SettingsViewModel : FeatureViewModelBase
|
||||||
{
|
{
|
||||||
private readonly SettingsService _settingsService;
|
private readonly SettingsService _settingsService;
|
||||||
|
private readonly IBrandingService _brandingService;
|
||||||
|
|
||||||
private string _selectedLanguage = "en";
|
private string _selectedLanguage = "en";
|
||||||
public string SelectedLanguage
|
public string SelectedLanguage
|
||||||
@@ -38,13 +39,25 @@ public partial class SettingsViewModel : FeatureViewModelBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public RelayCommand BrowseFolderCommand { get; }
|
private string? _mspLogoPreview;
|
||||||
|
public string? MspLogoPreview
|
||||||
|
{
|
||||||
|
get => _mspLogoPreview;
|
||||||
|
private set { _mspLogoPreview = value; OnPropertyChanged(); }
|
||||||
|
}
|
||||||
|
|
||||||
public SettingsViewModel(SettingsService settingsService, ILogger<FeatureViewModelBase> logger)
|
public RelayCommand BrowseFolderCommand { get; }
|
||||||
|
public IAsyncRelayCommand BrowseMspLogoCommand { get; }
|
||||||
|
public IAsyncRelayCommand ClearMspLogoCommand { get; }
|
||||||
|
|
||||||
|
public SettingsViewModel(SettingsService settingsService, IBrandingService brandingService, ILogger<FeatureViewModelBase> logger)
|
||||||
: base(logger)
|
: base(logger)
|
||||||
{
|
{
|
||||||
_settingsService = settingsService;
|
_settingsService = settingsService;
|
||||||
|
_brandingService = brandingService;
|
||||||
BrowseFolderCommand = new RelayCommand(BrowseFolder);
|
BrowseFolderCommand = new RelayCommand(BrowseFolder);
|
||||||
|
BrowseMspLogoCommand = new AsyncRelayCommand(BrowseMspLogoAsync);
|
||||||
|
ClearMspLogoCommand = new AsyncRelayCommand(ClearMspLogoAsync);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task LoadAsync()
|
public async Task LoadAsync()
|
||||||
@@ -54,6 +67,9 @@ public partial class SettingsViewModel : FeatureViewModelBase
|
|||||||
_dataFolder = settings.DataFolder;
|
_dataFolder = settings.DataFolder;
|
||||||
OnPropertyChanged(nameof(SelectedLanguage));
|
OnPropertyChanged(nameof(SelectedLanguage));
|
||||||
OnPropertyChanged(nameof(DataFolder));
|
OnPropertyChanged(nameof(DataFolder));
|
||||||
|
|
||||||
|
var mspLogo = await _brandingService.GetMspLogoAsync();
|
||||||
|
MspLogoPreview = mspLogo is not null ? $"data:{mspLogo.MimeType};base64,{mspLogo.Base64}" : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ApplyLanguageAsync(string code)
|
private async Task ApplyLanguageAsync(string code)
|
||||||
@@ -86,6 +102,32 @@ public partial class SettingsViewModel : FeatureViewModelBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task BrowseMspLogoAsync()
|
||||||
|
{
|
||||||
|
var dialog = new OpenFileDialog
|
||||||
|
{
|
||||||
|
Title = "Select MSP logo",
|
||||||
|
Filter = "Image files (*.png;*.jpg;*.jpeg)|*.png;*.jpg;*.jpeg",
|
||||||
|
};
|
||||||
|
if (dialog.ShowDialog() != true) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var logo = await _brandingService.ImportLogoAsync(dialog.FileName);
|
||||||
|
await _brandingService.SaveMspLogoAsync(logo);
|
||||||
|
MspLogoPreview = $"data:{logo.MimeType};base64,{logo.Base64}";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
StatusMessage = ex.Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ClearMspLogoAsync()
|
||||||
|
{
|
||||||
|
await _brandingService.ClearMspLogoAsync();
|
||||||
|
MspLogoPreview = null;
|
||||||
|
}
|
||||||
|
|
||||||
protected override Task RunOperationAsync(CancellationToken ct, IProgress<OperationProgress> progress)
|
protected override Task RunOperationAsync(CancellationToken ct, IProgress<OperationProgress> progress)
|
||||||
{
|
{
|
||||||
// Settings tab has no long-running operation
|
// Settings tab has no long-running operation
|
||||||
|
|||||||
Reference in New Issue
Block a user