bloxstrap/Bloxstrap/UI/Elements/Dialogs/AddCustomThemeDialog.xaml.cs
Matt 9d356b0b71
Some checks are pending
CI (Debug) / build (push) Waiting to run
CI (Release) / build (push) Waiting to run
CI (Release) / release (push) Blocked by required conditions
CI (Release) / release-test (push) Blocked by required conditions
Custom bootstrapper themes (#4380)
* add custom bootstrappers

* add avalonedit to licenses page

* add gif support

* add stretch & stretchdirection to images

* dont create a bitmapimage for gifs

* remove maxheight and maxwidth sets

* remove comment

* add isenabled

* add more textblock properties

* add markdowntextblocks

* update how transform elements are stored

* overhaul textbox content

* dont set fontsize if not set

* fix warnings

* add foreground property to control

* add background property to textblock

* count descendants and increase element cap

* add auto complete

* dont display completion window if there is no data

* sort schema elements and types

* make ! close the completion window

* add end tag auto complete

* fix pos being wrong

* dont treat comments as elements

* add imagebrushes

* follow same conventions as brushes

* fix exception messages

* fix them again

* update schema

* fix crash

* now it works

* wrong attribute name

* add solidcolorbrush

* move converters into a separate file

* add lineargradientbrushes

* unify handlers

* update schema

* add fake BloxstrapCustomBootstrapper

* stop adding an extra end character

* add property element auto-complete

* add title attribute to custombloxstrapbootstrapper

* add shapes

* add string translation support

* use default wpf size instead of 100x100

* update min height of window

* fix verticalalignment not working

* uncap height and width

* add effects

* move transformation handler inside frameworkelement

* fix title bar effect & transformation removal

* add more frameworkelement properties

* add layout transform

* add font properties to control

* improve window border stuff

* make sure file contents are in CRLF

* add cornerradius to progress bar

* add progressring

* Update wpfui

* update schema

* update function names

* add children check to content

* make sure only one content is defined

* add fontfamily

* update schema

* only allow file uris for images

* disable backdrop

* move text setter to textblock handler from base

* split up creator into multiple files

* turn version into a constant

* add grids

* cleanup converters

* add IgnoreTitleBarInset

* add Version to schema

* reveal custom bootstrapper stuff on selection

* increase listbox height

* only set statustext binding in textblock

* update ui

* rename ZIndex to Panel.ZIndex

* add stackpanel

* add border

* fix being unable to apply transforms on grids

* rearrange and add new editor button

* use snackbars for saving

* add close confirmation message

* use viewmodel variable

* remove pointless onpropertychanged call

* add version string format

* start editor window in the centre

* update licenses page

also resized the about window so everything could fit nicely

* fix border not inheriting frameworkelement

* add WindowCornerPreference

* add the import dialog

* add an export theme button

* update version number

* localise CustomDialog exceptions

* localise custom theme editor

* localise custom theme add dialog

* localise frontend

* localise appearance menu page

* change customtheme error strings namespace

* change icons on appearance page

* update button margin on appearance page
2025-03-11 19:18:54 +00:00

231 lines
7.1 KiB
C#

using Bloxstrap.UI.Elements.Base;
using Bloxstrap.UI.ViewModels.Dialogs;
using Microsoft.Win32;
using System.IO.Compression;
using System.Windows;
namespace Bloxstrap.UI.Elements.Dialogs
{
/// <summary>
/// Interaction logic for AddCustomThemeDialog.xaml
/// </summary>
public partial class AddCustomThemeDialog : WpfUiWindow
{
private const int CreateNewTabId = 0;
private const int ImportTabId = 1;
private readonly AddCustomThemeViewModel _viewModel;
public bool Created { get; private set; } = false;
public string ThemeName { get; private set; } = "";
public bool OpenEditor { get; private set; } = false;
public AddCustomThemeDialog()
{
_viewModel = new AddCustomThemeViewModel();
_viewModel.Name = GenerateRandomName();
DataContext = _viewModel;
InitializeComponent();
}
private static string GetThemePath(string name)
{
return Path.Combine(Paths.CustomThemes, name, "Theme.xml");
}
private static string GenerateRandomName()
{
int count = Directory.GetDirectories(Paths.CustomThemes).Count();
string name = $"Custom Theme {count + 1}";
// TODO: this sucks
if (File.Exists(GetThemePath(name)))
name += " " + Random.Shared.Next(1, 100000).ToString(); // easy
return name;
}
private static string GetUniqueName(string name)
{
const int maxTries = 100;
if (!File.Exists(GetThemePath(name)))
return name;
for (int i = 1; i <= maxTries; i++)
{
string newName = $"{name}_{i}";
if (!File.Exists(GetThemePath(newName)))
return newName;
}
// last resort
return $"{name}_{Random.Shared.Next(maxTries+1, 1_000_000)}";
}
private static void CreateCustomTheme(string name, CustomThemeTemplate template)
{
string dir = Path.Combine(Paths.CustomThemes, name);
if (Directory.Exists(dir))
Directory.Delete(dir, true);
Directory.CreateDirectory(dir);
string themeFilePath = Path.Combine(dir, "Theme.xml");
string templateContent = Encoding.UTF8.GetString(Resource.Get(template.GetFileName()).Result);
File.WriteAllText(themeFilePath, templateContent);
}
private bool ValidateCreateNew()
{
const string LOG_IDENT = "AddCustomThemeDialog::ValidateCreateNew";
if (string.IsNullOrEmpty(_viewModel.Name))
{
_viewModel.NameError = Strings.CustomTheme_Add_Errors_NameEmpty;
return false;
}
var validationResult = PathValidator.IsFileNameValid(_viewModel.Name);
if (validationResult != PathValidator.ValidationResult.Ok)
{
switch (validationResult)
{
case PathValidator.ValidationResult.IllegalCharacter:
_viewModel.NameError = Strings.CustomTheme_Add_Errors_NameIllegalCharacters;
break;
case PathValidator.ValidationResult.ReservedFileName:
_viewModel.NameError = Strings.CustomTheme_Add_Errors_NameReserved;
break;
default:
App.Logger.WriteLine(LOG_IDENT, $"Got unhandled PathValidator::ValidationResult {validationResult}");
Debug.Assert(false);
_viewModel.NameError = Strings.CustomTheme_Add_Errors_Unknown;
break;
}
return false;
}
// better to check for the file instead of the directory so broken themes can be overwritten
string path = Path.Combine(Paths.CustomThemes, _viewModel.Name, "Theme.xml");
if (File.Exists(path))
{
_viewModel.NameError = Strings.CustomTheme_Add_Errors_NameTaken;
return false;
}
return true;
}
private bool ValidateImport()
{
const string LOG_IDENT = "AddCustomThemeDialog::ValidateImport";
if (!_viewModel.FilePath.EndsWith(".zip"))
{
_viewModel.FileError = Strings.CustomTheme_Add_Errors_FileNotZip;
return false;
}
try
{
using var zipFile = ZipFile.OpenRead(_viewModel.FilePath);
var entries = zipFile.Entries;
bool foundThemeFile = false;
foreach (var entry in entries)
{
if (entry.FullName == "Theme.xml")
{
foundThemeFile = true;
break;
}
}
if (!foundThemeFile)
{
_viewModel.FileError = Strings.CustomTheme_Add_Errors_ZipMissingThemeFile;
return false;
}
return true;
}
catch (InvalidDataException ex)
{
App.Logger.WriteLine(LOG_IDENT, "Got invalid data");
App.Logger.WriteException(LOG_IDENT, ex);
_viewModel.FileError = Strings.CustomTheme_Add_Errors_ZipInvalidData;
return false;
}
}
private void CreateNew()
{
if (!ValidateCreateNew())
return;
CreateCustomTheme(_viewModel.Name, _viewModel.Template);
Created = true;
ThemeName = _viewModel.Name;
OpenEditor = true;
Close();
}
private void Import()
{
if (!ValidateImport())
return;
string fileName = Path.GetFileNameWithoutExtension(_viewModel.FilePath);
string name = GetUniqueName(fileName);
string directory = Path.Combine(Paths.CustomThemes, name);
if (Directory.Exists(directory))
Directory.Delete(directory, true);
Directory.CreateDirectory(directory);
var fastZip = new ICSharpCode.SharpZipLib.Zip.FastZip();
fastZip.ExtractZip(_viewModel.FilePath, directory, null);
Created = true;
ThemeName = name;
OpenEditor = false;
Close();
}
private void OnOkButtonClicked(object sender, RoutedEventArgs e)
{
if (_viewModel.SelectedTab == CreateNewTabId)
CreateNew();
else
Import();
}
private void OnImportButtonClicked(object sender, RoutedEventArgs e)
{
var dialog = new OpenFileDialog
{
Filter = $"{Strings.FileTypes_ZipArchive}|*.zip"
};
if (dialog.ShowDialog() != true)
return;
_viewModel.FilePath = dialog.FileName;
}
}
}