feat(04-10): create TemplatesViewModel and TemplatesView
- TemplatesViewModel: list, capture with 5 options, apply, rename, delete, refresh - TemplatesView: capture section with checkboxes, apply section, template DataGrid - RenameInputDialog: simple WPF dialog (no Microsoft.VisualBasic dependency) - Capture/Apply are separate async commands from RunCommand
This commit is contained in:
218
SharepointToolbox/ViewModels/Tabs/TemplatesViewModel.cs
Normal file
218
SharepointToolbox/ViewModels/Tabs/TemplatesViewModel.cs
Normal file
@@ -0,0 +1,218 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog;
|
||||
using SharepointToolbox.Core.Models;
|
||||
using SharepointToolbox.Infrastructure.Persistence;
|
||||
using SharepointToolbox.Services;
|
||||
|
||||
namespace SharepointToolbox.ViewModels.Tabs;
|
||||
|
||||
public partial class TemplatesViewModel : FeatureViewModelBase
|
||||
{
|
||||
private readonly ITemplateService _templateService;
|
||||
private readonly TemplateRepository _templateRepo;
|
||||
private readonly ISessionManager _sessionManager;
|
||||
private readonly ILogger<FeatureViewModelBase> _logger;
|
||||
private TenantProfile? _currentProfile;
|
||||
|
||||
// Template list
|
||||
private ObservableCollection<SiteTemplate> _templates = new();
|
||||
public ObservableCollection<SiteTemplate> Templates
|
||||
{
|
||||
get => _templates;
|
||||
private set { _templates = value; OnPropertyChanged(); }
|
||||
}
|
||||
|
||||
[ObservableProperty] private SiteTemplate? _selectedTemplate;
|
||||
|
||||
// Capture options
|
||||
[ObservableProperty] private string _captureSiteUrl = string.Empty;
|
||||
[ObservableProperty] private string _templateName = string.Empty;
|
||||
[ObservableProperty] private bool _captureLibraries = true;
|
||||
[ObservableProperty] private bool _captureFolders = true;
|
||||
[ObservableProperty] private bool _capturePermissions = true;
|
||||
[ObservableProperty] private bool _captureLogo = true;
|
||||
[ObservableProperty] private bool _captureSettings = true;
|
||||
|
||||
// Apply options
|
||||
[ObservableProperty] private string _newSiteTitle = string.Empty;
|
||||
[ObservableProperty] private string _newSiteAlias = string.Empty;
|
||||
|
||||
public IAsyncRelayCommand CaptureCommand { get; }
|
||||
public IAsyncRelayCommand ApplyCommand { get; }
|
||||
public IAsyncRelayCommand RenameCommand { get; }
|
||||
public IAsyncRelayCommand DeleteCommand { get; }
|
||||
public IAsyncRelayCommand RefreshCommand { get; }
|
||||
|
||||
public TenantProfile? CurrentProfile => _currentProfile;
|
||||
|
||||
// Factory for rename dialog — set by View code-behind
|
||||
public Func<string, string?>? RenameDialogFactory { get; set; }
|
||||
|
||||
public TemplatesViewModel(
|
||||
ITemplateService templateService,
|
||||
TemplateRepository templateRepo,
|
||||
ISessionManager sessionManager,
|
||||
ILogger<FeatureViewModelBase> logger)
|
||||
: base(logger)
|
||||
{
|
||||
_templateService = templateService;
|
||||
_templateRepo = templateRepo;
|
||||
_sessionManager = sessionManager;
|
||||
_logger = logger;
|
||||
|
||||
CaptureCommand = new AsyncRelayCommand(CaptureAsync, () => !IsRunning);
|
||||
ApplyCommand = new AsyncRelayCommand(ApplyAsync, () => !IsRunning && SelectedTemplate != null);
|
||||
RenameCommand = new AsyncRelayCommand(RenameAsync, () => SelectedTemplate != null);
|
||||
DeleteCommand = new AsyncRelayCommand(DeleteAsync, () => SelectedTemplate != null);
|
||||
RefreshCommand = new AsyncRelayCommand(RefreshListAsync);
|
||||
}
|
||||
|
||||
protected override async Task RunOperationAsync(CancellationToken ct, IProgress<OperationProgress> progress)
|
||||
{
|
||||
// Not used directly — Capture and Apply have their own async commands
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task CaptureAsync()
|
||||
{
|
||||
if (_currentProfile == null)
|
||||
throw new InvalidOperationException("No tenant connected.");
|
||||
if (string.IsNullOrWhiteSpace(CaptureSiteUrl))
|
||||
throw new InvalidOperationException("Site URL is required.");
|
||||
if (string.IsNullOrWhiteSpace(TemplateName))
|
||||
throw new InvalidOperationException("Template name is required.");
|
||||
|
||||
try
|
||||
{
|
||||
IsRunning = true;
|
||||
StatusMessage = "Capturing template...";
|
||||
|
||||
var profile = new TenantProfile
|
||||
{
|
||||
Name = _currentProfile.Name,
|
||||
TenantUrl = CaptureSiteUrl,
|
||||
ClientId = _currentProfile.ClientId,
|
||||
};
|
||||
var ctx = await _sessionManager.GetOrCreateContextAsync(profile, CancellationToken.None);
|
||||
|
||||
var options = new SiteTemplateOptions
|
||||
{
|
||||
CaptureLibraries = CaptureLibraries,
|
||||
CaptureFolders = CaptureFolders,
|
||||
CapturePermissionGroups = CapturePermissions,
|
||||
CaptureLogo = CaptureLogo,
|
||||
CaptureSettings = CaptureSettings,
|
||||
};
|
||||
|
||||
var progress = new Progress<OperationProgress>(p => StatusMessage = p.Message);
|
||||
var template = await _templateService.CaptureTemplateAsync(ctx, options, progress, CancellationToken.None);
|
||||
template.Name = TemplateName;
|
||||
|
||||
await _templateRepo.SaveAsync(template);
|
||||
Log.Information("Template captured: {Name} from {Url}", template.Name, CaptureSiteUrl);
|
||||
|
||||
await RefreshListAsync();
|
||||
StatusMessage = $"Template captured successfully.";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusMessage = $"Capture failed: {ex.Message}";
|
||||
Log.Error(ex, "Template capture failed");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ApplyAsync()
|
||||
{
|
||||
if (_currentProfile == null || SelectedTemplate == null) return;
|
||||
if (string.IsNullOrWhiteSpace(NewSiteTitle))
|
||||
throw new InvalidOperationException("New site title is required.");
|
||||
if (string.IsNullOrWhiteSpace(NewSiteAlias))
|
||||
throw new InvalidOperationException("New site alias is required.");
|
||||
|
||||
try
|
||||
{
|
||||
IsRunning = true;
|
||||
StatusMessage = $"Applying template...";
|
||||
|
||||
var ctx = await _sessionManager.GetOrCreateContextAsync(_currentProfile, CancellationToken.None);
|
||||
var progress = new Progress<OperationProgress>(p => StatusMessage = p.Message);
|
||||
|
||||
var siteUrl = await _templateService.ApplyTemplateAsync(
|
||||
ctx, SelectedTemplate, NewSiteTitle, NewSiteAlias,
|
||||
progress, CancellationToken.None);
|
||||
|
||||
StatusMessage = $"Template applied. Site created at: {siteUrl}";
|
||||
Log.Information("Template applied. New site: {Url}", siteUrl);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusMessage = $"Apply failed: {ex.Message}";
|
||||
Log.Error(ex, "Template apply failed");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RenameAsync()
|
||||
{
|
||||
if (SelectedTemplate == null) return;
|
||||
|
||||
if (RenameDialogFactory != null)
|
||||
{
|
||||
var newName = RenameDialogFactory(SelectedTemplate.Name);
|
||||
if (!string.IsNullOrWhiteSpace(newName))
|
||||
{
|
||||
await _templateRepo.RenameAsync(SelectedTemplate.Id, newName);
|
||||
await RefreshListAsync();
|
||||
Log.Information("Template renamed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteAsync()
|
||||
{
|
||||
if (SelectedTemplate == null) return;
|
||||
|
||||
await _templateRepo.DeleteAsync(SelectedTemplate.Id);
|
||||
await RefreshListAsync();
|
||||
Log.Information("Template deleted.");
|
||||
}
|
||||
|
||||
private async Task RefreshListAsync()
|
||||
{
|
||||
var templates = await _templateRepo.GetAllAsync();
|
||||
await Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
Templates = new ObservableCollection<SiteTemplate>(templates);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void OnTenantSwitched(TenantProfile profile)
|
||||
{
|
||||
_currentProfile = profile;
|
||||
CaptureSiteUrl = string.Empty;
|
||||
TemplateName = string.Empty;
|
||||
NewSiteTitle = string.Empty;
|
||||
NewSiteAlias = string.Empty;
|
||||
StatusMessage = string.Empty;
|
||||
|
||||
_ = RefreshListAsync();
|
||||
}
|
||||
|
||||
partial void OnSelectedTemplateChanged(SiteTemplate? value)
|
||||
{
|
||||
ApplyCommand.NotifyCanExecuteChanged();
|
||||
RenameCommand.NotifyCanExecuteChanged();
|
||||
DeleteCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user