Files
Libation/Source/LibationAvalonia/Dialogs/EditTemplateDialog.axaml.cs
2026-03-11 13:45:59 -04:00

182 lines
5.4 KiB
C#

using AppScaffolding;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Documents;
using Avalonia.Media;
using Avalonia.Styling;
using Dinah.Core;
using LibationFileManager;
using LibationFileManager.Templates;
using LibationUiBase.Forms;
using ReactiveUI;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace LibationAvalonia.Dialogs;
public partial class EditTemplateDialog : DialogWindow
{
private readonly EditTemplateViewModel? _viewModel;
public EditTemplateDialog()
{
InitializeComponent();
if (Design.IsDesignMode)
{
var mockInstance = Configuration.CreateMockInstance();
mockInstance.Books = "/Path/To/Books";
RequestedThemeVariant = ThemeVariant.Dark;
var editor = TemplateEditor<Templates.FileTemplate>.CreateFilenameEditor(mockInstance.Books, mockInstance.FileTemplate);
_viewModel = new(mockInstance, editor);
_viewModel.ResetTextBox(editor.EditingTemplate.TemplateText);
Title = $"Edit {editor.TemplateName}";
DataContext = _viewModel;
}
}
public EditTemplateDialog(ITemplateEditor templateEditor) : this()
{
ArgumentValidator.EnsureNotNull(templateEditor, nameof(templateEditor));
_viewModel = new EditTemplateViewModel(Configuration.Instance, templateEditor);
_viewModel.ResetTextBox(templateEditor.EditingTemplate.TemplateText);
Title = $"Edit {templateEditor.TemplateName}";
DataContext = _viewModel;
}
public void EditTemplateViewModel_DoubleTapped(object sender, Avalonia.Input.TappedEventArgs e)
{
var dataGrid = sender as DataGrid;
var item = (dataGrid?.SelectedItem as Tuple<string, string, string>)?.Item3;
if (string.IsNullOrWhiteSpace(item)) return;
var text = userEditTbox.Text;
userEditTbox.Text = text?.Insert(Math.Min(Math.Max(0, userEditTbox.CaretIndex), text.Length), item);
userEditTbox.CaretIndex += item.Length;
}
protected override async Task SaveAndCloseAsync()
{
if (_viewModel is null || !await _viewModel.Validate())
return;
await base.SaveAndCloseAsync();
}
public async void SaveButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> await SaveAndCloseAsync();
internal class EditTemplateViewModel : ViewModels.ViewModelBase
{
private readonly Configuration config;
public InlineCollection Inlines { get; } = new();
public ITemplateEditor TemplateEditor { get; }
public EditTemplateViewModel(Configuration configuration, ITemplateEditor templates)
{
config = configuration;
TemplateEditor = templates;
Description = templates.TemplateDescription;
ListItems
= new AvaloniaList<Tuple<string, string, string>>(
TemplateEditor
.EditingTemplate
.TagsRegistered
.Cast<TemplateTags>()
.Select(
t => new Tuple<string, string, string>(
$"<{t.TagName}>",
t.Description,
t.DefaultValue)
)
);
}
public void GoToNamingTemplateWiki()
=> Go.To.Url(LibationScaffolding.NamingTemplatesDocUrl);
// hold the work-in-progress value. not guaranteed to be valid
public string? UserTemplateText
{
get => field;
set
{
this.RaiseAndSetIfChanged(ref field, value);
templateTb_TextChanged();
}
}
public string? WarningText { get => field; set => this.RaiseAndSetIfChanged(ref field, value); }
public string Description { get; }
public AvaloniaList<Tuple<string, string, string>> ListItems { get; set; }
public void ResetTextBox(string value) => UserTemplateText = value;
public void ResetToDefault() => ResetTextBox(TemplateEditor.DefaultTemplate);
public async Task<bool> Validate()
{
if (TemplateEditor.EditingTemplate.IsValid)
return true;
var errors = string.Join("\r\n", TemplateEditor.EditingTemplate.Errors.Select(err => $"- {err}"));
await MessageBox.Show($"This template text is not valid. Errors:\r\n{errors}", "Invalid", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
private void templateTb_TextChanged()
{
TemplateEditor.SetTemplateText(UserTemplateText);
const char ZERO_WIDTH_SPACE = '\u200B';
var sing = $"{Path.DirectorySeparatorChar}";
// result: can wrap long paths. eg:
// |-- LINE WRAP BOUNDARIES --|
// \books\author with a very <= normal line break on space between words
// long name\narrator narrator
// \title <= line break on the zero-with space we added before slashes
string slashWrap(string? val) => val?.Replace(sing, $"{ZERO_WIDTH_SPACE}{sing}") ?? string.Empty;
WarningText
= !TemplateEditor.EditingTemplate.HasWarnings
? ""
: "Warning:\r\n" +
string.Join("\r\n", TemplateEditor.EditingTemplate.Warnings.Select(err => $"- {err}"));
var bold = FontWeight.Bold;
var reg = FontWeight.Normal;
Inlines.Clear();
if (!TemplateEditor.IsFilePath)
{
Inlines.Add(new Run(TemplateEditor.GetName()) { FontWeight = bold });
return;
}
var folder = TemplateEditor.GetFolderName();
var file = TemplateEditor.GetFileName();
var ext = config.DecryptToLossy ? "mp3" : "m4b";
Inlines.Add(new Run(slashWrap(TemplateEditor.BaseDirectory.PathWithoutPrefix)) { FontWeight = reg });
Inlines.Add(new Run(sing) { FontWeight = reg });
Inlines.Add(new Run(slashWrap(folder)) { FontWeight = TemplateEditor.IsFolder ? bold : reg });
Inlines.Add(new Run(sing));
Inlines.Add(new Run(slashWrap(file)) { FontWeight = TemplateEditor.IsFolder ? reg : bold });
Inlines.Add(new Run($".{ext}"));
}
}
}