mirror of
https://github.com/bloxstraplabs/bloxstrap.git
synced 2025-06-23 14:50:03 -07:00
add the import dialog
This commit is contained in:
parent
ff8466f0f5
commit
239a0e3e1d
@ -31,7 +31,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\CustomBootstrapperSchema.json" />
|
||||
<EmbeddedResource Include="Resources\CustomBootstrapperTemplate.xml" />
|
||||
<EmbeddedResource Include="Resources\CustomBootstrapperTemplate_Blank.xml" />
|
||||
<EmbeddedResource Include="Resources\CustomBootstrapperTemplate_Simple.xml" />
|
||||
<EmbeddedResource Include="Resources\Icon2008.ico" />
|
||||
<EmbeddedResource Include="Resources\Icon2011.ico" />
|
||||
<EmbeddedResource Include="Resources\Icon2017.ico" />
|
||||
|
8
Bloxstrap/Enums/CustomThemeTemplate.cs
Normal file
8
Bloxstrap/Enums/CustomThemeTemplate.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Bloxstrap.Enums
|
||||
{
|
||||
public enum CustomThemeTemplate
|
||||
{
|
||||
Blank,
|
||||
Simple
|
||||
}
|
||||
}
|
10
Bloxstrap/Extensions/CustomThemeTemplateEx.cs
Normal file
10
Bloxstrap/Extensions/CustomThemeTemplateEx.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace Bloxstrap.Extensions
|
||||
{
|
||||
static class CustomThemeTemplateEx
|
||||
{
|
||||
public static string GetFileName(this CustomThemeTemplate template)
|
||||
{
|
||||
return $"CustomBootstrapperTemplate_{template}.xml";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<BloxstrapCustomBootstrapper Version="0" Height="320" Width="520" IgnoreTitleBarInset="True" Theme="Default" Margin="30">
|
||||
<!-- Find more custom bootstrapper examples at https://github.com/bloxstraplabs/custom-bootstrapper-examples -->
|
||||
<TitleBar Title="" ShowMinimize="False" ShowClose="False" />
|
||||
|
||||
<Image Source="{Icon}" Height="100" Width="100" HorizontalAlignment="Center" Margin="0,15,0,0" />
|
||||
<TextBlock HorizontalAlignment="Center" Name="StatusText" FontSize="20" Margin="0,170,0,0" />
|
||||
<ProgressBar Width="450" Height="12" Name="PrimaryProgressBar" HorizontalAlignment="Center" Margin="0,200,0,0" />
|
||||
<Button Content="Cancel" Name="CancelButton" HorizontalAlignment="Center" Margin="0,225,0,0" Height="30" Width="100" />
|
||||
</BloxstrapCustomBootstrapper>
|
18
Bloxstrap/Resources/Strings.Designer.cs
generated
18
Bloxstrap/Resources/Strings.Designer.cs
generated
@ -1170,6 +1170,24 @@ namespace Bloxstrap.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Blank.
|
||||
/// </summary>
|
||||
public static string Enums_CustomThemeTemplate_Blank {
|
||||
get {
|
||||
return ResourceManager.GetString("Enums.CustomThemeTemplate.Blank", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Simple.
|
||||
/// </summary>
|
||||
public static string Enums_CustomThemeTemplate_Simple {
|
||||
get {
|
||||
return ResourceManager.GetString("Enums.CustomThemeTemplate.Simple", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Catmoji.
|
||||
/// </summary>
|
||||
|
@ -1273,4 +1273,10 @@ Please close any applications that may be using Roblox's files, and relaunch.</v
|
||||
<data name="Menu.About.Licenses.Apache" xml:space="preserve">
|
||||
<value>Apache License 2.0</value>
|
||||
</data>
|
||||
<data name="Enums.CustomThemeTemplate.Blank" xml:space="preserve">
|
||||
<value>Blank</value>
|
||||
</data>
|
||||
<data name="Enums.CustomThemeTemplate.Simple" xml:space="preserve">
|
||||
<value>Simple</value>
|
||||
</data>
|
||||
</root>
|
165
Bloxstrap/UI/Elements/Dialogs/AddCustomThemeDialog.xaml
Normal file
165
Bloxstrap/UI/Elements/Dialogs/AddCustomThemeDialog.xaml
Normal file
@ -0,0 +1,165 @@
|
||||
<base:WpfUiWindow
|
||||
x:Class="Bloxstrap.UI.Elements.Dialogs.AddCustomThemeDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:base="clr-namespace:Bloxstrap.UI.Elements.Base"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Bloxstrap.UI.Elements.Dialogs"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:resources="clr-namespace:Bloxstrap.Resources"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
xmlns:viewmodels="clr-namespace:Bloxstrap.UI.ViewModels.Dialogs"
|
||||
Title="Add Custom Theme"
|
||||
Width="480"
|
||||
MinHeight="0"
|
||||
d:DataContext="{d:DesignInstance viewmodels:AddCustomThemeViewModel,
|
||||
IsDesignTimeCreatable=True}"
|
||||
Background="{ui:ThemeResource ApplicationBackgroundBrush}"
|
||||
ExtendsContentIntoTitleBar="True"
|
||||
ResizeMode="NoResize"
|
||||
SizeToContent="Height"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
mc:Ignorable="d">
|
||||
<Grid>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<ui:TitleBar
|
||||
Title="Add Custom Theme"
|
||||
Grid.Row="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Padding="8"
|
||||
CanMaximize="False"
|
||||
KeyboardNavigation.TabNavigation="None"
|
||||
ShowMaximize="False"
|
||||
ShowMinimize="False" />
|
||||
|
||||
<TabControl
|
||||
x:Name="Tabs"
|
||||
Grid.Row="1"
|
||||
Margin="16"
|
||||
SelectedIndex="{Binding Path=SelectedTab, Mode=TwoWay}">
|
||||
<TabItem Header="Create New">
|
||||
<Grid Grid.Row="1" Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid
|
||||
Grid.Row="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="0,0,0,12">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
MinWidth="100"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Strings.Common_Name}" />
|
||||
<TextBox
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Text="{Binding Path=Name, Mode=TwoWay}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Foreground="{DynamicResource SystemFillColorCriticalBrush}"
|
||||
Text="{Binding Path=NameError, Mode=OneWay}"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap"
|
||||
Visibility="{Binding Path=NameErrorVisibility, Mode=OneWay}" />
|
||||
</Grid>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
MinWidth="100"
|
||||
VerticalAlignment="Center"
|
||||
Text="Template" />
|
||||
|
||||
<ComboBox
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
ItemsSource="{Binding Path=Templates, Mode=OneTime}"
|
||||
Text="{Binding Path=Template, Mode=TwoWay}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path=., Converter={StaticResource EnumNameConverter}}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Header="Import">
|
||||
<Grid Margin="11">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
FontSize="14"
|
||||
Text="{Binding Path=FilePath}"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap"
|
||||
Visibility="{Binding Path=FilePathVisibility}" />
|
||||
<ui:Button
|
||||
Grid.Row="1"
|
||||
Margin="4"
|
||||
HorizontalAlignment="Stretch"
|
||||
Click="OnImportButtonClicked"
|
||||
Content="{x:Static resources:Strings.Common_ImportFromFile}"
|
||||
Icon="DocumentArrowUp16" />
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Foreground="{DynamicResource SystemFillColorCriticalBrush}"
|
||||
Text="{Binding Path=FileError}"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap"
|
||||
Visibility="{Binding Path=FileErrorVisibility}" />
|
||||
</Grid>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
|
||||
<Border
|
||||
Grid.Row="2"
|
||||
Margin="0,10,0,0"
|
||||
Padding="15"
|
||||
Background="{ui:ThemeResource SolidBackgroundFillColorSecondaryBrush}">
|
||||
<StackPanel
|
||||
HorizontalAlignment="Right"
|
||||
FlowDirection="LeftToRight"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
MinWidth="100"
|
||||
Click="OnOkButtonClicked"
|
||||
Content="{x:Static resources:Strings.Common_OK}" />
|
||||
<Button
|
||||
MinWidth="100"
|
||||
Margin="12,0,0,0"
|
||||
Content="{x:Static resources:Strings.Common_Cancel}"
|
||||
IsCancel="True" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</base:WpfUiWindow>
|
230
Bloxstrap/UI/Elements/Dialogs/AddCustomThemeDialog.xaml.cs
Normal file
230
Bloxstrap/UI/Elements/Dialogs/AddCustomThemeDialog.xaml.cs
Normal file
@ -0,0 +1,230 @@
|
||||
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 = "Name cannot be empty";
|
||||
return false;
|
||||
}
|
||||
|
||||
var validationResult = PathValidator.IsFileNameValid(_viewModel.Name);
|
||||
|
||||
if (validationResult != PathValidator.ValidationResult.Ok)
|
||||
{
|
||||
switch (validationResult)
|
||||
{
|
||||
case PathValidator.ValidationResult.IllegalCharacter:
|
||||
_viewModel.NameError = "Name contains illegal characters";
|
||||
break;
|
||||
case PathValidator.ValidationResult.ReservedFileName:
|
||||
_viewModel.NameError = "Name cannot be used";
|
||||
break;
|
||||
default:
|
||||
App.Logger.WriteLine(LOG_IDENT, $"Got unhandled PathValidator::ValidationResult {validationResult}");
|
||||
Debug.Assert(false);
|
||||
|
||||
_viewModel.NameError = "Unknown error";
|
||||
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 = "Name is already in use";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ValidateImport()
|
||||
{
|
||||
const string LOG_IDENT = "AddCustomThemeDialog::ValidateImport";
|
||||
|
||||
if (!_viewModel.FilePath.EndsWith(".zip"))
|
||||
{
|
||||
_viewModel.FileError = "File must be a ZIP";
|
||||
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 = "Theme file could not be found in the ZIP file";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (InvalidDataException ex)
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, "Got invalid data");
|
||||
App.Logger.WriteException(LOG_IDENT, ex);
|
||||
|
||||
_viewModel.FileError = "Invalid or corrupted ZIP file";
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
68
Bloxstrap/UI/ViewModels/Dialogs/AddCustomThemeViewModel.cs
Normal file
68
Bloxstrap/UI/ViewModels/Dialogs/AddCustomThemeViewModel.cs
Normal file
@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace Bloxstrap.UI.ViewModels.Dialogs
|
||||
{
|
||||
internal class AddCustomThemeViewModel : NotifyPropertyChangedViewModel
|
||||
{
|
||||
public static CustomThemeTemplate[] Templates => Enum.GetValues<CustomThemeTemplate>();
|
||||
|
||||
public CustomThemeTemplate Template { get; set; } = CustomThemeTemplate.Simple;
|
||||
|
||||
public string Name { get; set; } = "";
|
||||
|
||||
private string _filePath = "";
|
||||
public string FilePath
|
||||
{
|
||||
get => _filePath;
|
||||
set
|
||||
{
|
||||
if (_filePath != value)
|
||||
{
|
||||
_filePath = value;
|
||||
OnPropertyChanged(nameof(FilePath));
|
||||
OnPropertyChanged(nameof(FilePathVisibility));
|
||||
}
|
||||
}
|
||||
}
|
||||
public Visibility FilePathVisibility => string.IsNullOrEmpty(FilePath) ? Visibility.Collapsed : Visibility.Visible;
|
||||
|
||||
public int SelectedTab { get; set; } = 0;
|
||||
|
||||
private string _nameError = "";
|
||||
public string NameError
|
||||
{
|
||||
get => _nameError;
|
||||
set
|
||||
{
|
||||
if (_nameError != value)
|
||||
{
|
||||
_nameError = value;
|
||||
OnPropertyChanged(nameof(NameError));
|
||||
OnPropertyChanged(nameof(NameErrorVisibility));
|
||||
}
|
||||
}
|
||||
}
|
||||
public Visibility NameErrorVisibility => string.IsNullOrEmpty(NameError) ? Visibility.Collapsed : Visibility.Visible;
|
||||
|
||||
private string _fileError = "";
|
||||
public string FileError
|
||||
{
|
||||
get => _fileError;
|
||||
set
|
||||
{
|
||||
if (_fileError != value)
|
||||
{
|
||||
_fileError = value;
|
||||
OnPropertyChanged(nameof(FileError));
|
||||
OnPropertyChanged(nameof(FileErrorVisibility));
|
||||
}
|
||||
}
|
||||
}
|
||||
public Visibility FileErrorVisibility => string.IsNullOrEmpty(FileError) ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
}
|
@ -9,6 +9,8 @@ using Microsoft.Win32;
|
||||
|
||||
using Bloxstrap.UI.Elements.Settings;
|
||||
using Bloxstrap.UI.Elements.Editor;
|
||||
using Bloxstrap.UI.Elements.Dialogs;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Bloxstrap.UI.ViewModels.Settings
|
||||
{
|
||||
@ -131,31 +133,6 @@ namespace Bloxstrap.UI.ViewModels.Settings
|
||||
}
|
||||
}
|
||||
|
||||
private string CreateCustomThemeName()
|
||||
{
|
||||
int count = Directory.GetDirectories(Paths.CustomThemes).Count();
|
||||
|
||||
string name = $"Custom Theme {count + 1}";
|
||||
|
||||
// TODO: this sucks
|
||||
if (Directory.Exists(Path.Combine(Paths.CustomThemes, name))) // DUCK
|
||||
name += " " + Random.Shared.Next(1, 100000).ToString(); // easy
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
private void CreateCustomThemeStructure(string name)
|
||||
{
|
||||
string dir = Path.Combine(Paths.CustomThemes, name);
|
||||
Directory.CreateDirectory(dir);
|
||||
|
||||
string themeFilePath = Path.Combine(dir, "Theme.xml");
|
||||
|
||||
string templateContent = Encoding.UTF8.GetString(Resource.Get("CustomBootstrapperTemplate.xml").Result);
|
||||
|
||||
File.WriteAllText(themeFilePath, templateContent);
|
||||
}
|
||||
|
||||
private void DeleteCustomThemeStructure(string name)
|
||||
{
|
||||
string dir = Path.Combine(Paths.CustomThemes, name);
|
||||
@ -171,24 +148,20 @@ namespace Bloxstrap.UI.ViewModels.Settings
|
||||
|
||||
private void AddCustomTheme()
|
||||
{
|
||||
string name = CreateCustomThemeName();
|
||||
var dialog = new AddCustomThemeDialog();
|
||||
dialog.ShowDialog();
|
||||
|
||||
try
|
||||
if (dialog.Created)
|
||||
{
|
||||
CreateCustomThemeStructure(name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
App.Logger.WriteException("AppearanceViewModel::AddCustomTheme", ex);
|
||||
Frontend.ShowMessageBox($"Failed to create custom theme: {ex.Message}", MessageBoxImage.Error);
|
||||
return;
|
||||
}
|
||||
CustomThemes.Add(dialog.ThemeName);
|
||||
SelectedCustomThemeIndex = CustomThemes.Count - 1;
|
||||
|
||||
CustomThemes.Add(name);
|
||||
SelectedCustomThemeIndex = CustomThemes.Count - 1;
|
||||
OnPropertyChanged(nameof(SelectedCustomThemeIndex));
|
||||
OnPropertyChanged(nameof(IsCustomThemeSelected));
|
||||
|
||||
OnPropertyChanged(nameof(SelectedCustomThemeIndex));
|
||||
OnPropertyChanged(nameof(IsCustomThemeSelected));
|
||||
if (dialog.OpenEditor)
|
||||
EditCustomTheme();
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteCustomTheme()
|
||||
|
104
Bloxstrap/Utility/PathValidator.cs
Normal file
104
Bloxstrap/Utility/PathValidator.cs
Normal file
@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bloxstrap.Utility
|
||||
{
|
||||
internal static class PathValidator
|
||||
{
|
||||
public enum ValidationResult
|
||||
{
|
||||
Ok,
|
||||
IllegalCharacter,
|
||||
ReservedFileName,
|
||||
ReservedDirectoryName
|
||||
}
|
||||
|
||||
private static readonly string[] _reservedNames = new string[]
|
||||
{
|
||||
"CON",
|
||||
"PRN",
|
||||
"AUX",
|
||||
"NUL",
|
||||
"COM1",
|
||||
"COM2",
|
||||
"COM3",
|
||||
"COM4",
|
||||
"COM5",
|
||||
"COM6",
|
||||
"COM7",
|
||||
"COM8",
|
||||
"COM9",
|
||||
"LPT1",
|
||||
"LPT2",
|
||||
"LPT3",
|
||||
"LPT4",
|
||||
"LPT5",
|
||||
"LPT6",
|
||||
"LPT7",
|
||||
"LPT8",
|
||||
"LPT9"
|
||||
};
|
||||
|
||||
private static readonly char[] _directorySeperatorDelimiters = new char[]
|
||||
{
|
||||
Path.DirectorySeparatorChar,
|
||||
Path.AltDirectorySeparatorChar
|
||||
};
|
||||
|
||||
private static readonly char[] _invalidPathChars = GetInvalidPathChars();
|
||||
|
||||
public static char[] GetInvalidPathChars()
|
||||
{
|
||||
char[] invalids = new char[] { '/', '\\', ':', '*', '?', '"', '<', '>', '|' };
|
||||
char[] otherInvalids = Path.GetInvalidPathChars();
|
||||
|
||||
char[] result = new char[invalids.Length + otherInvalids.Length];
|
||||
invalids.CopyTo(result, 0);
|
||||
otherInvalids.CopyTo(result, invalids.Length);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static ValidationResult IsFileNameValid(string fileName)
|
||||
{
|
||||
if (fileName.IndexOfAny(_invalidPathChars) != -1)
|
||||
return ValidationResult.IllegalCharacter;
|
||||
|
||||
string fileNameNoExt = Path.GetFileNameWithoutExtension(fileName).ToUpperInvariant();
|
||||
if (_reservedNames.Contains(fileNameNoExt))
|
||||
return ValidationResult.ReservedFileName;
|
||||
|
||||
return ValidationResult.Ok;
|
||||
}
|
||||
|
||||
public static ValidationResult IsPathValid(string path)
|
||||
{
|
||||
string? pathRoot = Path.GetPathRoot(path);
|
||||
string pathNoRoot = pathRoot != null ? path[pathRoot.Length..] : path;
|
||||
|
||||
string[] pathParts = pathNoRoot.Split(_directorySeperatorDelimiters);
|
||||
|
||||
foreach (var part in pathParts)
|
||||
{
|
||||
if (part.IndexOfAny(_invalidPathChars) != -1)
|
||||
return ValidationResult.IllegalCharacter;
|
||||
|
||||
if (_reservedNames.Contains(part))
|
||||
return ValidationResult.ReservedDirectoryName;
|
||||
}
|
||||
|
||||
string fileName = Path.GetFileName(path);
|
||||
if (fileName.IndexOfAny(_invalidPathChars) != -1)
|
||||
return ValidationResult.IllegalCharacter;
|
||||
|
||||
string fileNameNoExt = Path.GetFileNameWithoutExtension(path).ToUpperInvariant();
|
||||
if (_reservedNames.Contains(fileNameNoExt))
|
||||
return ValidationResult.ReservedFileName;
|
||||
|
||||
return ValidationResult.Ok;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user