Files

934 lines
31 KiB
Plaintext

@page "/print"
@attribute [Authorize]
@implements IAsyncDisposable
@using Core.Printing
@inject INotesService NotesService
@inject INotePrintService NotePrintService
@inject IPrintPresetService PrintPresetService
@inject IConfiguration Configuration
@inject ISnackbar Snackbar
@inject IDialogService DialogService
@inject IJSRuntime JSRuntime
@inject NavigationManager NavigationManager
@inject MarkdownTablePasteService MarkdownTablePasteService
<div class="no-print">
<PageHeader Title="Page printer"
Description="Write a markdown template, merge it onto students, teams, or events, and print."
Icon="@Icons.Material.Filled.Print" />
<MudPaper Elevation="2" Class="pa-3 pa-md-6 mb-4">
@if (_isLoading)
{
<MudProgressLinear Color="Color.Primary" Indeterminate="true" Class="mb-4" />
}
<MudGrid>
<MudItem xs="12" md="5">
<MudSelect T="int?"
Label="Print preset"
Value="_selectedPresetId"
ValueChanged="OnPresetSelected"
Clearable="true"
Variant="Variant.Outlined">
@foreach (var preset in _presets)
{
<MudSelectItem T="int?" Value="@preset.Id">@preset.Name</MudSelectItem>
}
</MudSelect>
</MudItem>
<MudItem xs="12" md="7" Class="d-flex align-center gap-2 flex-wrap">
<MudButton Variant="Variant.Outlined"
OnClick="NewTemplate"
Disabled="@_isBusy">
New
</MudButton>
<MudButton Variant="Variant.Outlined"
StartIcon="@Icons.Material.Filled.Save"
OnClick="SavePreset"
Disabled="@_isBusy">
Save
</MudButton>
<MudButton Variant="Variant.Outlined"
Color="Color.Error"
OnClick="DeletePreset"
Disabled="@(_isBusy || !_selectedPresetId.HasValue)">
Delete
</MudButton>
</MudItem>
<MudItem xs="12" md="4">
<MudSelect T="PrintEntityType"
Label="Entity"
Value="_entityType"
ValueChanged="OnEntityTypeChanged"
Variant="Variant.Outlined">
<MudSelectItem Value="PrintEntityType.Student">Students</MudSelectItem>
<MudSelectItem Value="PrintEntityType.Team">Teams</MudSelectItem>
<MudSelectItem Value="PrintEntityType.Event">Events</MudSelectItem>
</MudSelect>
</MudItem>
@if (_entityType == PrintEntityType.Student)
{
<MudItem xs="12" sm="4" md="2">
<MudNumericField T="int?"
Label="Grade"
Value="_grade"
ValueChanged="OnGradeChanged"
Variant="Variant.Outlined"
Min="5"
Max="12"
Clearable="true" />
</MudItem>
<MudItem xs="12" sm="4" md="2">
<MudNumericField T="int?"
Label="TSA year"
Value="_tsaYear"
ValueChanged="OnTsaYearChanged"
Variant="Variant.Outlined"
Min="1"
Max="12"
Clearable="true" />
</MudItem>
<MudItem xs="12" sm="4" md="4">
<MudSelect T="string"
Label="Officer"
Value="@_officerChoice"
ValueChanged="OnOfficerChanged"
Variant="Variant.Outlined">
<MudSelectItem Value="@("any")">Any</MudSelectItem>
<MudSelectItem Value="@("yes")">Officers only</MudSelectItem>
<MudSelectItem Value="@("no")">Non-officers only</MudSelectItem>
</MudSelect>
</MudItem>
}
else if (_entityType == PrintEntityType.Team)
{
<MudItem xs="12" md="8">
<MudTextField T="string"
Value="@_teamIdentifierContains"
ValueChanged="OnTeamIdentifierChanged"
Label="Identifier contains"
Variant="Variant.Outlined"
Immediate="true" />
</MudItem>
}
else
{
<MudItem xs="12" sm="4">
<MudTextField T="string"
Value="@_eventNameContains"
ValueChanged="OnEventNameChanged"
Label="Name contains"
Variant="Variant.Outlined"
Immediate="true" />
</MudItem>
<MudItem xs="12" sm="4">
<MudSelect T="EventFormat?"
Label="Event format"
Value="_eventFormat"
ValueChanged="OnEventFormatChanged"
Variant="Variant.Outlined"
Clearable="true">
@foreach (var format in Enum.GetValues<EventFormat>())
{
<MudSelectItem T="EventFormat?" Value="@format">@format</MudSelectItem>
}
</MudSelect>
</MudItem>
<MudItem xs="12" sm="4">
<MudSelect T="string"
Label="Regional"
Value="@_regionalChoice"
ValueChanged="OnRegionalChanged"
Variant="Variant.Outlined">
<MudSelectItem Value="@("any")">Any</MudSelectItem>
<MudSelectItem Value="@("yes")">Regional only</MudSelectItem>
<MudSelectItem Value="@("no")">Non-regional only</MudSelectItem>
</MudSelect>
</MudItem>
}
<MudItem xs="12">
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center" Class="flex-wrap mb-2">
<MudButton Variant="Variant.Outlined"
StartIcon="@Icons.Material.Filled.DataObject"
OnClick="OpenTokenDialog"
Disabled="@_isBusy">
Insert token
</MudButton>
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Visibility"
OnClick="Preview"
Disabled="@_isBusy">
Preview
</MudButton>
<MudButton Variant="Variant.Outlined"
StartIcon="@Icons.Material.Filled.Print"
OnClick="Print"
Disabled="@(_isBusy || _previewStale || _pages.Count == 0)">
Print
</MudButton>
<MudCheckBox T="bool"
Value="@_newPagePerRecord"
ValueChanged="OnNewPagePerRecordChanged"
Label="New page per record"
Dense="true" />
<MudNumericField T="int"
Label="Font size (pt)"
Value="_fontSizePt"
ValueChanged="OnFontSizeChanged"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Min="PrintPresetFilters.MinFontSizePt"
Max="PrintPresetFilters.MaxFontSizePt"
Style="max-width: 8rem;" />
<MudNumericField T="int"
Label="Answer lines"
Value="_answerSpaceLines"
ValueChanged="OnAnswerSpaceLinesChanged"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Min="PrintPresetFilters.MinAnswerSpaceLines"
Max="PrintPresetFilters.MaxAnswerSpaceLines"
Style="max-width: 8rem;" />
@if (IsDirty())
{
<MudText Typo="Typo.caption" Color="Color.Warning">Unsaved changes</MudText>
}
@if (!_previewStale && _pages.Count > 0)
{
<MudText Typo="Typo.body2">@_pages.Count page@(_pages.Count == 1 ? "" : "s")</MudText>
}
else if (!_previewStale && _didPreview)
{
<MudText Typo="Typo.body2">No matches</MudText>
}
</MudStack>
</MudItem>
<MudItem xs="12">
<MudText Typo="Typo.subtitle2" Class="mb-2">Template (Markdown)</MudText>
<div id="@EditorElementId" @key="_editorGeneration">
<MarkdownEditor Value="@_templateMarkdown"
ValueChanged="OnMarkdownChanged"
Placeholder="Write the printable page. Insert tokens for student, team, or event values."
AutoSaveEnabled="false"
NativeSpellChecker="false"
HideIcons="@HiddenEditorIcons" />
</div>
<MudText Typo="Typo.subtitle2" Class="mt-4 mb-1">Template preview</MudText>
<MudText Typo="Typo.caption" Class="mud-text-secondary mb-2">Markdown only — tokens are not merged here.</MudText>
@if (string.IsNullOrWhiteSpace(_templateMarkdown))
{
<MudText Typo="Typo.body2" Class="mud-text-secondary">The template will preview here as you type.</MudText>
}
else
{
<MudPaper Elevation="0" Class="pa-3 note-print-page" Style="@($"background-color: var(--mud-palette-background-grey);{PrintPageStyle}")">
<div class="markdown-content">
@((MarkupString)MarkdownHelper.ToHtml(_templateMarkdown))
</div>
</MudPaper>
}
</MudItem>
</MudGrid>
</MudPaper>
</div>
<div class="print-only">
<PrintPageStack Pages="_pages"
NewPagePerRecord="_newPagePerRecord"
PrintPageStyle="@PrintPageStyle" />
</div>
@code {
private const string EditorElementId = "page-printer-editor";
private static readonly string[] HiddenEditorIcons = ["preview", "side-by-side", "fullscreen"];
private CancellationTokenSource? _cancellationTokenSource;
private bool _isDisposed;
private bool _isLoading = true;
private bool _isBusy;
private bool _previewStale = true;
private bool _didPreview;
private bool _pasteInitialized;
private int _editorGeneration;
private List<PrintPreset> _presets = [];
private List<string> _importedTokenNames = [];
private IReadOnlyList<NotePrintPage> _pages = [];
private IDialogReference? _previewDialog;
private IDisposable? _navigationRegistration;
private int? _selectedPresetId;
private string _templateMarkdown = string.Empty;
private PrintEntityType _entityType = PrintEntityType.Student;
private int? _grade;
private int? _tsaYear;
private string _officerChoice = "any";
private string? _teamIdentifierContains;
private string? _eventNameContains;
private EventFormat? _eventFormat;
private string _regionalChoice = "any";
private bool _newPagePerRecord = true;
private int _fontSizePt = PrintPresetFilters.DefaultFontSizePt;
private int _answerSpaceLines = PrintPresetFilters.DefaultAnswerSpaceLines;
private string _snapshotMarkdown = string.Empty;
private PrintEntityType _snapshotEntityType = PrintEntityType.Student;
private string _snapshotFiltersJson = string.Empty;
private string PrintPageStyle =>
$"--print-font-size:{_fontSizePt}pt;--print-answer-lines:{_answerSpaceLines};";
private bool CanPreview => !string.IsNullOrWhiteSpace(_templateMarkdown);
protected override void OnInitialized()
{
_cancellationTokenSource = new CancellationTokenSource();
CaptureSnapshot();
_navigationRegistration = NavigationManager.RegisterLocationChangingHandler(OnLocationChanging);
}
protected override async Task OnInitializedAsync()
{
await LoadAsync();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (_pasteInitialized || _isDisposed)
return;
await Task.Delay(150);
if (_isDisposed)
return;
await MarkdownTablePasteService.InitializeAsync(EditorElementId);
_pasteInitialized = true;
}
private async Task LoadAsync()
{
if (_isDisposed)
return;
_isLoading = true;
try
{
var token = _cancellationTokenSource?.Token ?? CancellationToken.None;
await RefreshImportedTokenNamesAsync(token);
_presets = [.. await PrintPresetService.GetAllAsync(token)];
}
catch (TaskCanceledException)
{
}
catch (JSDisconnectedException)
{
}
finally
{
if (!_isDisposed)
_isLoading = false;
}
}
private async Task MarkStaleAsync()
{
_previewStale = true;
_pages = [];
await ClosePreviewDialogAsync();
}
private void MarkStale() => _ = MarkStaleAsync();
private async Task ClosePreviewDialogAsync()
{
var dialog = _previewDialog;
_previewDialog = null;
if (dialog is null)
return;
try
{
dialog.Close();
}
catch (InvalidOperationException)
{
}
}
private async Task OnEntityTypeChanged(PrintEntityType value)
{
_entityType = value;
await MarkStaleAsync();
}
private void OnMarkdownChanged(string? value)
{
_templateMarkdown = value ?? string.Empty;
MarkStale();
}
private void OnGradeChanged(int? value)
{
_grade = value;
MarkStale();
}
private void OnTsaYearChanged(int? value)
{
_tsaYear = value;
MarkStale();
}
private void OnOfficerChanged(string value)
{
_officerChoice = value;
MarkStale();
}
private void OnTeamIdentifierChanged(string? value)
{
_teamIdentifierContains = value;
MarkStale();
}
private void OnEventNameChanged(string? value)
{
_eventNameContains = value;
MarkStale();
}
private void OnEventFormatChanged(EventFormat? value)
{
_eventFormat = value;
MarkStale();
}
private void OnRegionalChanged(string value)
{
_regionalChoice = value;
MarkStale();
}
private void OnNewPagePerRecordChanged(bool value)
{
_newPagePerRecord = value;
}
private void OnFontSizeChanged(int value)
{
_fontSizePt = Math.Clamp(value, PrintPresetFilters.MinFontSizePt, PrintPresetFilters.MaxFontSizePt);
}
private void OnAnswerSpaceLinesChanged(int value)
{
_answerSpaceLines = Math.Clamp(value, PrintPresetFilters.MinAnswerSpaceLines, PrintPresetFilters.MaxAnswerSpaceLines);
}
private async Task RefreshImportedTokenNamesAsync(CancellationToken token)
{
var indexFields = WebApp.Models.ChapterSettings.ReadIndexNoteFields(Configuration);
var discovered = await NotesService.GetImportedFieldNamesAsync(token);
_importedTokenNames =
[
.. indexFields
.Concat(discovered)
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(n => n, StringComparer.OrdinalIgnoreCase)
];
}
private async Task FlushEditorAsync()
{
var value = await MarkdownTablePasteService.GetValueAsync(EditorElementId);
if (value is not null && value != _templateMarkdown)
{
_templateMarkdown = value;
await MarkStaleAsync();
}
}
private bool IsDirty()
{
return !string.Equals(_templateMarkdown, _snapshotMarkdown, StringComparison.Ordinal)
|| _entityType != _snapshotEntityType
|| !string.Equals(BuildFilters().ToJson(), _snapshotFiltersJson, StringComparison.Ordinal);
}
private void CaptureSnapshot()
{
_snapshotMarkdown = _templateMarkdown;
_snapshotEntityType = _entityType;
_snapshotFiltersJson = BuildFilters().ToJson();
}
private async Task<bool> ConfirmDiscardIfDirtyAsync()
{
await FlushEditorAsync();
if (!IsDirty())
return true;
var confirmed = await DialogService.ShowMessageBox(
"Unsaved changes",
"Discard unsaved template or filter changes?",
yesText: "Discard",
cancelText: "Stay");
return confirmed == true && !_isDisposed;
}
private async ValueTask OnLocationChanging(LocationChangingContext context)
{
if (_isDisposed)
return;
if (!await ConfirmDiscardIfDirtyAsync())
context.PreventNavigation();
}
private async Task OnPresetSelected(int? id)
{
if (id == _selectedPresetId)
return;
if (!await ConfirmDiscardIfDirtyAsync())
return;
_selectedPresetId = id;
if (!id.HasValue)
{
await MarkStaleAsync();
return;
}
var preset = _presets.FirstOrDefault(p => p.Id == id.Value);
if (preset is null)
return;
ApplyPreset(preset);
CaptureSnapshot();
await MarkStaleAsync();
}
private async Task NewTemplate()
{
if (_isDisposed || !await ConfirmDiscardIfDirtyAsync())
return;
_selectedPresetId = null;
_templateMarkdown = string.Empty;
_entityType = PrintEntityType.Student;
_grade = null;
_tsaYear = null;
_officerChoice = "any";
_teamIdentifierContains = null;
_eventNameContains = null;
_eventFormat = null;
_regionalChoice = "any";
_newPagePerRecord = true;
_fontSizePt = PrintPresetFilters.DefaultFontSizePt;
_answerSpaceLines = PrintPresetFilters.DefaultAnswerSpaceLines;
_editorGeneration++;
_pasteInitialized = false;
CaptureSnapshot();
await MarkStaleAsync();
}
private void ApplyPreset(PrintPreset preset)
{
_entityType = preset.EntityType;
_templateMarkdown = preset.TemplateMarkdown ?? string.Empty;
var filters = PrintPresetFilters.FromJson(preset.FiltersJson);
_grade = filters.Grade;
_tsaYear = filters.TsaYear;
_officerChoice = PrintPresetFilters.ToTriState(filters.IsOfficer);
_teamIdentifierContains = filters.TeamIdentifierContains;
_eventNameContains = filters.EventNameContains;
_eventFormat = filters.EventFormat;
_regionalChoice = PrintPresetFilters.ToTriState(filters.RegionalOnly);
_newPagePerRecord = filters.NewPagePerRecord;
_fontSizePt = filters.FontSizePt;
_answerSpaceLines = filters.AnswerSpaceLines;
_editorGeneration++;
_pasteInitialized = false;
}
private PrintPresetFilters BuildFilters() =>
new()
{
Grade = _entityType == PrintEntityType.Student ? _grade : null,
TsaYear = _entityType == PrintEntityType.Student ? _tsaYear : null,
IsOfficer = _entityType == PrintEntityType.Student ? PrintPresetFilters.FromTriState(_officerChoice) : null,
TeamIdentifierContains = _entityType == PrintEntityType.Team ? _teamIdentifierContains : null,
EventNameContains = _entityType == PrintEntityType.Event ? _eventNameContains : null,
EventFormat = _entityType == PrintEntityType.Event ? _eventFormat : null,
RegionalOnly = _entityType == PrintEntityType.Event ? PrintPresetFilters.FromTriState(_regionalChoice) : null,
NewPagePerRecord = _newPagePerRecord,
FontSizePt = _fontSizePt,
AnswerSpaceLines = _answerSpaceLines
};
private async Task OpenTokenDialog()
{
if (_isDisposed)
return;
var token = _cancellationTokenSource?.Token ?? CancellationToken.None;
await RefreshImportedTokenNamesAsync(token);
var parameters = new DialogParameters<PrintTokenInsertDialog>
{
{ x => x.EntityType, _entityType },
{ x => x.ImportedFieldNames, _importedTokenNames },
{ x => x.OnInsert, EventCallback.Factory.Create<string>(this, InsertToken) }
};
var options = new DialogOptions
{
MaxWidth = MaxWidth.Large,
FullWidth = true,
CloseButton = true
};
await DialogService.ShowAsync<PrintTokenInsertDialog>("Insert token", parameters, options);
}
private async Task InsertToken(string tokenName)
{
if (_isDisposed)
return;
await FlushEditorAsync();
var wrapped = "{{" + tokenName + "}}";
var inserted = await MarkdownTablePasteService.InsertAtCursorAsync(EditorElementId, wrapped);
if (!inserted)
{
_templateMarkdown += wrapped;
await MarkdownTablePasteService.SetValueAsync(EditorElementId, _templateMarkdown);
}
else
{
var value = await MarkdownTablePasteService.GetValueAsync(EditorElementId);
if (value is not null)
_templateMarkdown = value;
}
await MarkStaleAsync();
}
private async Task Preview()
{
if (_isDisposed)
return;
await FlushEditorAsync();
if (!CanPreview)
{
if (!_isDisposed)
Snackbar.Add("Write a template before previewing.", Severity.Warning);
return;
}
if (!_previewStale && _didPreview)
{
await OpenPreviewDialogAsync();
return;
}
_isBusy = true;
try
{
var token = _cancellationTokenSource?.Token ?? CancellationToken.None;
await RefreshImportedTokenNamesAsync(token);
_pages = await NotePrintService.PreviewAsync(
new NotePrintRequest
{
EntityType = _entityType,
TemplateMarkdown = _templateMarkdown,
Filters = BuildFilters(),
ImportedFieldCatalog = _importedTokenNames
},
token);
_previewStale = false;
_didPreview = true;
if (!_isDisposed)
await OpenPreviewDialogAsync();
}
catch (TaskCanceledException)
{
}
catch (JSDisconnectedException)
{
}
catch (Exception ex)
{
if (!_isDisposed)
Snackbar.Add($"Preview failed: {ex.Message}", Severity.Error);
}
finally
{
if (!_isDisposed)
_isBusy = false;
}
}
private async Task OpenPreviewDialogAsync()
{
await ClosePreviewDialogAsync();
var parameters = new DialogParameters<PrintPreviewDialog>
{
{ x => x.Pages, _pages },
{ x => x.NewPagePerRecord, _newPagePerRecord },
{ x => x.PrintPageStyle, PrintPageStyle },
{ x => x.OnPrint, EventCallback.Factory.Create(this, Print) }
};
var options = new DialogOptions
{
MaxWidth = MaxWidth.ExtraLarge,
FullWidth = true,
CloseButton = true
};
var dialog = await DialogService.ShowAsync<PrintPreviewDialog>("Print preview", parameters, options);
_previewDialog = dialog;
_ = TrackPreviewDialog(dialog);
}
private async Task TrackPreviewDialog(IDialogReference dialog)
{
try
{
await dialog.Result;
}
catch (TaskCanceledException)
{
}
finally
{
if (_previewDialog == dialog)
_previewDialog = null;
}
}
private async Task Print()
{
if (_isDisposed || _previewStale || _pages.Count == 0)
return;
try
{
await JSRuntime.InvokeVoidAsync("window.print");
}
catch (JSDisconnectedException)
{
}
catch (TaskCanceledException)
{
}
}
private async Task SavePreset()
{
if (_isDisposed)
return;
await FlushEditorAsync();
if (!CanPreview)
{
if (!_isDisposed)
Snackbar.Add("Write a template before saving.", Severity.Warning);
return;
}
if (_selectedPresetId.HasValue)
{
await UpdateSelectedPresetAsync();
return;
}
var name = await PromptForPresetNameAsync();
if (string.IsNullOrWhiteSpace(name) || _isDisposed)
return;
_isBusy = true;
try
{
var token = _cancellationTokenSource?.Token ?? CancellationToken.None;
if (await PrintPresetService.NameExistsAsync(name, null, token))
{
Snackbar.Add($"A print preset named '{name}' already exists.", Severity.Warning);
return;
}
var created = await PrintPresetService.CreateAsync(
new PrintPreset
{
Name = name,
TemplateMarkdown = _templateMarkdown,
EntityType = _entityType,
FiltersJson = BuildFilters().ToJson()
},
token);
_presets = [.. await PrintPresetService.GetAllAsync(token)];
_selectedPresetId = created.Id;
CaptureSnapshot();
if (!_isDisposed)
Snackbar.Add($"Saved print preset '{name}'.", Severity.Success);
}
catch (TaskCanceledException)
{
}
catch (JSDisconnectedException)
{
}
catch (Exception ex)
{
if (!_isDisposed)
Snackbar.Add($"Could not save: {ex.Message}", Severity.Error);
}
finally
{
if (!_isDisposed)
_isBusy = false;
}
}
private async Task<string?> PromptForPresetNameAsync()
{
var options = new DialogOptions
{
MaxWidth = MaxWidth.Small,
FullWidth = true,
CloseButton = true
};
var dialog = await DialogService.ShowAsync<PrintPresetNameDialog>("Save print preset", options);
var result = await dialog.Result;
if (result is null || result.Canceled || result.Data is not string name)
return null;
return name.Trim();
}
private async Task UpdateSelectedPresetAsync()
{
if (_isDisposed || !_selectedPresetId.HasValue)
return;
_isBusy = true;
try
{
var token = _cancellationTokenSource?.Token ?? CancellationToken.None;
var existing = _presets.FirstOrDefault(p => p.Id == _selectedPresetId.Value);
if (existing is null)
return;
await PrintPresetService.UpdateAsync(
new PrintPreset
{
Id = existing.Id,
Name = existing.Name,
TemplateMarkdown = _templateMarkdown,
EntityType = _entityType,
FiltersJson = BuildFilters().ToJson()
},
token);
_presets = [.. await PrintPresetService.GetAllAsync(token)];
CaptureSnapshot();
if (!_isDisposed)
Snackbar.Add($"Saved print preset '{existing.Name}'.", Severity.Success);
}
catch (TaskCanceledException)
{
}
catch (JSDisconnectedException)
{
}
catch (Exception ex)
{
if (!_isDisposed)
Snackbar.Add($"Could not save: {ex.Message}", Severity.Error);
}
finally
{
if (!_isDisposed)
_isBusy = false;
}
}
private async Task DeletePreset()
{
if (_isDisposed || !_selectedPresetId.HasValue)
return;
var existing = _presets.FirstOrDefault(p => p.Id == _selectedPresetId.Value);
if (existing is null)
return;
var confirmed = await DialogService.ShowMessageBox(
"Delete print preset",
(MarkupString)$"Delete <b>{existing.Name}</b>? This cannot be undone.",
yesText: "Delete",
cancelText: "Cancel");
if (confirmed != true || _isDisposed)
return;
_isBusy = true;
try
{
var token = _cancellationTokenSource?.Token ?? CancellationToken.None;
await PrintPresetService.DeleteAsync(existing.Id, token);
_presets = [.. await PrintPresetService.GetAllAsync(token)];
_selectedPresetId = null;
CaptureSnapshot();
if (!_isDisposed)
Snackbar.Add($"Deleted print preset '{existing.Name}'.", Severity.Info);
}
catch (TaskCanceledException)
{
}
catch (JSDisconnectedException)
{
}
catch (Exception ex)
{
if (!_isDisposed)
Snackbar.Add($"Could not delete: {ex.Message}", Severity.Error);
}
finally
{
if (!_isDisposed)
_isBusy = false;
}
}
public async ValueTask DisposeAsync()
{
if (!_isDisposed)
{
_isDisposed = true;
_navigationRegistration?.Dispose();
_navigationRegistration = null;
await ClosePreviewDialogAsync();
_cancellationTokenSource?.Cancel();
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
}
await ValueTask.CompletedTask;
}
}