This commit is contained in:
Jose Conde
2025-09-29 16:02:00 +02:00
parent dc57da8136
commit 5e16f781b4
74 changed files with 1621 additions and 1856 deletions

View File

@@ -0,0 +1,58 @@
using System;
using System.Windows.Forms;
namespace ModVersionChecker.controls
{
public class DirectoryPickerControl : UserControl
{
private readonly TextBox _textBox;
private readonly Button _browseButton;
public string SelectedPath
{
get => _textBox.Text;
set => _textBox.Text = value;
}
public DirectoryPickerControl()
{
_textBox = new TextBox
{
Width = 300,
ReadOnly = true,
Dock = DockStyle.Fill
};
_browseButton = new Button
{
Text = "Browse",
Width = 80,
Dock = DockStyle.Right
};
_browseButton.Click += (s, e) =>
{
using var folderDialog = new FolderBrowserDialog();
folderDialog.Description = "Select directory";
if (folderDialog.ShowDialog() == DialogResult.OK)
{
SelectedPath = folderDialog.SelectedPath;
}
};
var panel = new TableLayoutPanel
{
ColumnCount = 2,
Dock = DockStyle.Fill,
AutoSize = true
};
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 80));
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 20));
panel.Controls.Add(_textBox, 0, 0);
panel.Controls.Add(_browseButton, 1, 0);
Controls.Add(panel);
AutoSize = true;
}
}
}

View File

@@ -0,0 +1,74 @@
using ModVersionChecker.managers.interfaces;
using ModVersionChecker.service.interfaces;
namespace ModVersionChecker.ui.forms
{
public class AppDetailsForm : Form
{
private TextBox _searchBox;
private ListBox _resultsList;
private Button _addButton;
private readonly IApiService _apiService;
private readonly IAppsManager _appsManager;
public App? SelectedApp { get; private set; }
public event EventHandler<string> OnAppAdded;
public AppDetailsForm(IApiService apiService, IAppsManager appsManager)
{
_apiService = apiService ?? throw new ArgumentNullException(nameof(apiService));
_appsManager = appsManager ?? throw new ArgumentNullException(nameof(appsManager));
InitializeComponent();
}
private void InitializeComponent()
{
Text = "Search and Add App";
Width = 400;
Height = 500;
Padding = new Padding(20);
_searchBox = new TextBox { Dock = DockStyle.Top, PlaceholderText = "Search..." };
_resultsList = new ListBox { Dock = DockStyle.Fill };
_addButton = new Button { Text = "Add Selected", Dock = DockStyle.Bottom, Enabled = false };
_searchBox.TextChanged += async (s, e) => await UpdateResultsAync();
_resultsList.SelectedIndexChanged += (s, e) => _addButton.Enabled = _resultsList.SelectedItem != null;
_resultsList.DoubleClick += (s, e) => AddSelected();
_addButton.Click += (s, e) => AddSelected();
Controls.Add(_resultsList);
Controls.Add(_addButton);
Controls.Add(_searchBox);
}
private async Task UpdateResultsAync()
{
var query = _searchBox.Text.Trim().ToLower();
if (query.Length < 3)
{
return;
}
if (string.IsNullOrEmpty(query))
{
_resultsList.DataSource = null;
return;
}
var results = await _apiService.SearchApps(query);
_resultsList.DataSource = results;
_resultsList.DisplayMember = "Name";
}
private void AddSelected()
{
if (_resultsList.SelectedItem is App app)
{
SelectedApp = app;
_appsManager.Insert(app);
DialogResult = DialogResult.OK;
OnAppAdded?.Invoke(this, "App saved");
Close();
}
}
}
}

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,70 @@
using Microsoft.Extensions.DependencyInjection;
using ModVersionChecker.managers.interfaces;
using ModVersionChecker.service.interfaces;
namespace ModVersionChecker.ui.forms
{
public class FormFactory : IFormFactory
{
private readonly IServiceProvider _serviceProvider;
public FormFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
}
public AppDetailsForm CreateAppDetailsForm(App? app, bool isEditable, EventHandler<string>? onAppSaved)
{
var apiService = _serviceProvider.GetRequiredService<IApiService>();
var appsManager = _serviceProvider.GetRequiredService<IAppsManager>();
var form = new AppDetailsForm(apiService, appsManager);
if (onAppSaved != null)
{
form.OnAppAdded += onAppSaved;
}
return form;
}
public GlobalConfigForm CreateGlobalConfigForm()
{
var configManager = _serviceProvider.GetRequiredService<IConfigManager>();
return new GlobalConfigForm(configManager);
}
public TypeConfigForm CreateTypeConfigForm()
{
var typeManager = _serviceProvider.GetRequiredService<ITypeManager>();
var apiService = _serviceProvider.GetRequiredService<IApiService>();
var configManager = _serviceProvider.GetRequiredService<IConfigManager>();
return new TypeConfigForm(typeManager, apiService, configManager);
}
//public SourcesConfigForm CreateSourcesConfigForm(EventHandler<string>? onSourcesChanged)
//{
// var sourcesDefManager = _serviceProvider.GetRequiredService<ISourcesDefManager>();
// var formFactory = _serviceProvider.GetRequiredService<IFormFactory>();
// var form = new SourcesConfigForm(formFactory, sourcesDefManager);
// if (onSourcesChanged != null)
// {
// form.OnSourcesChanged += onSourcesChanged;
// }
// return form;
//}
//public SourceDetailForm CreateSourceDetailForm(SourceDef? sourceDef, EventHandler<string>? onSourceChanged)
//{
// var sourcesDefManager = _serviceProvider.GetRequiredService<ISourcesDefManager>();
// var checkerTypesDefManager = _serviceProvider.GetRequiredService<ICheckerTypesDefManager>();
// var formFactory = _serviceProvider.GetRequiredService<IFormFactory>();
// var form = new SourceDetailForm(formFactory, sourcesDefManager);
// form.SourceDef = sourceDef;
// if (onSourceChanged != null)
// {
// form.UpdateFields();
// form.OnSourceChanged += onSourceChanged;
// }
// return form;
//}
}
}

View File

@@ -0,0 +1,151 @@
using ModVersionChecker.managers.interfaces;
using ModVersionChecker.model;
namespace ModVersionChecker.ui.forms
{
public class GlobalConfigForm : Form
{
private IConfigManager _configManager;
private Config _config;
private Label _millislabel, _checkStartupLabel, _runOnStartupLabel;
private TrackBar _millisField;
private CheckBox _checkStartupField, _runOnStartupField;
private Button _saveButton, _cancelButton;
private TableLayoutPanel _mainLayout, _configsPanel;
private FlowLayoutPanel _buttonPanel;
public GlobalConfigForm(IConfigManager configManager)
{
_configManager = configManager;
_config = _configManager.GetConfig();
this.Load += GlobalConfigForm_Load;
}
private void GlobalConfigForm_Load(object sender, EventArgs e)
{
// Load existing configurations if needed
InitializeComponent();
}
private void InitializeComponent()
{
SuspendLayout();
ClientSize = new System.Drawing.Size(600, 250);
Name = "GlobalConfigForm";
Text = "Global Configuration";
StartPosition = FormStartPosition.CenterParent;
Padding = new Padding(10, 20, 10, 20 );
_mainLayout = GetMainLayout();
_configsPanel = GetConfigsPanel();
_buttonPanel = GetButtonsPanel();
_mainLayout.Controls.Add(_configsPanel, 0, 0);
_mainLayout.Controls.Add(_buttonPanel, 0, 1);
Controls.Add(_mainLayout);
ResumeLayout(false);
}
private FlowLayoutPanel GetButtonsPanel()
{
var buttonsPanel = new FlowLayoutPanel { FlowDirection = FlowDirection.RightToLeft, AutoSize = true, Dock = DockStyle.Fill };
_saveButton = new Button { Text = "Save", AutoSize = true };
_saveButton.Click += (sender, e) =>
{
_config.IntervalMinutes = _millisField.Value;
_config.CheckOnStartup = _checkStartupField.Checked;
_config.RunOnStartup = _runOnStartupField.Checked;
_configManager.Save(_config);
DialogResult = DialogResult.OK;
UpdateStartupSetting(_config.RunOnStartup);
Close();
};
_cancelButton = new Button { Text = "Cancel", AutoSize = true };
_cancelButton.Click += (sender, e) =>
{
DialogResult = DialogResult.Cancel;
Close();
};
buttonsPanel.Controls.Add(_saveButton);
buttonsPanel.Controls.Add(_cancelButton);
return buttonsPanel;
}
private TableLayoutPanel GetConfigsPanel()
{
// Initialize the configurations panel
var configsPanel = new TableLayoutPanel
{
AutoSize = true,
Dock = DockStyle.Fill,
ColumnCount = 2,
RowCount = 2,
ColumnStyles = { new ColumnStyle(SizeType.Absolute, 150), new ColumnStyle(SizeType.Percent, 100) }
};
_millislabel = new Label { Text = "Millis", Width = 150};
_millisField = new TrackBar { Minimum = 0, Maximum = 120, Value= _config.IntervalMinutes, Width = 300, TickStyle = TickStyle.None };
FlowLayoutPanel millisPanel = new FlowLayoutPanel { FlowDirection = FlowDirection.LeftToRight, AutoSize = true };
Label millisValue = new Label { Text = _millisField.Value.ToString() + " minutes", AutoSize = true, Padding = new Padding(10, 10, 0, 0) };
millisPanel.Controls.Add(_millisField);
millisPanel.Controls.Add(millisValue);
_millisField.Scroll += (sender, e) => { millisValue.Text = _millisField.Value.ToString() + " minutes"; };
_checkStartupLabel = new Label { Text = "Check on Application Start:", Width = 150 };
_checkStartupField = new CheckBox { Checked = _config.CheckOnStartup };
_runOnStartupLabel= new Label { Text = "Run on Windows Startup:", Width = 150 };
_runOnStartupField = new CheckBox { Checked = _config.RunOnStartup };
configsPanel.Controls.Add(_millislabel, 0, 0);
configsPanel.Controls.Add(millisPanel, 1, 0);
configsPanel.Controls.Add(_checkStartupLabel, 0, 1);
configsPanel.Controls.Add(_checkStartupField, 1, 1);
configsPanel.Controls.Add(_runOnStartupLabel, 0, 2);
configsPanel.Controls.Add(_runOnStartupField, 1, 2);
return configsPanel;
}
private TableLayoutPanel GetMainLayout()
{
// Initialize the main layout panel
var mainLayout = new TableLayoutPanel
{
Dock = DockStyle.Fill,
RowCount = 2,
ColumnCount = 1
};
mainLayout.RowStyles.Add(new RowStyle(SizeType.Absolute, 150)); // Paths panel height
mainLayout.RowStyles.Add(new RowStyle(SizeType.Absolute, 50)); // Button panel height
Controls.Add(mainLayout);
return mainLayout;
}
private void UpdateStartupSetting(bool runOnStartup)
{
using (var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Run", true))
{
if (runOnStartup)
{
key?.SetValue("XintanalabsUpdateChecker", $"\"{Application.ExecutablePath}\"");
}
else
{
key?.DeleteValue("XintanalabsUpdateChecker", false); // Remove if unchecked
}
}
}
// Add methods and properties for global configuration management here
}
}

View File

@@ -0,0 +1,14 @@

namespace ModVersionChecker.ui.forms
{
public interface IFormFactory
{
AppDetailsForm CreateAppDetailsForm(App? app, bool isEditable, EventHandler<string>? onAppChanged);
GlobalConfigForm CreateGlobalConfigForm();
TypeConfigForm CreateTypeConfigForm();
//SourcesConfigForm CreateSourcesConfigForm(EventHandler<string>? onSourcesChanged);
//SourceDetailForm CreateSourceDetailForm(SourceDef? sourceDef, EventHandler<string>? onSourceChanged);
}
}

View File

@@ -0,0 +1,316 @@
using ModVersionChecker.enums;
using ModVersionChecker.managers.interfaces;
using ModVersionChecker.model;
using ModVersionChecker.repository.api.dto;
using ModVersionChecker.service.interfaces;
using ModVersionChecker.utils;
namespace ModVersionChecker.ui.forms
{
public class MainForm : Form
{
private readonly IAppsManager _appsManager;
private readonly IFormFactory _formFactory;
private readonly IFlightSimsManager _fsManager;
private readonly IApiService _apiService;
private readonly IVersionService _versionService;
private readonly TableLayoutPanel _mainLayout;
private readonly Config _globalConfig;
private List<App> _apps = new List<App>();
private ListView _listView;
private ImageList _statusImageList = new ImageList();
public event EventHandler<EventArgs> OnConfigChanged;
public event EventHandler<string> OnRecheck;
private EventHandler<string> onAppSavedHandler;
private MenuStrip _menuStrip;
private List<TypeResponse> _fsMods;
private readonly Dictionary<string, TextBox> _fsModPathTextBoxes = new Dictionary<string, TextBox>();
private List<SourceResponse> _sources = new List<SourceResponse>();
private List<TypeResponse> _typesDef = new List<TypeResponse>();
public MainForm(
IConfigManager configManager,
IAppsManager appsManager,
IFormFactory formFactory,
IFlightSimsManager fsManager,
IApiService apiService,
ITypeManager typeConfigManager,
IVersionService versionService)
{
_appsManager = appsManager ?? throw new ArgumentNullException(nameof(appsManager));
_formFactory = formFactory ?? throw new ArgumentNullException(nameof(formFactory));
_fsManager = fsManager ?? throw new ArgumentNullException(nameof(fsManager));
_apiService = apiService ?? throw new ArgumentNullException(nameof(apiService));
_versionService = versionService ?? throw new ArgumentNullException(nameof(versionService));
_fsMods = _fsManager.Load();
_statusImageList.Images.Add("none", new Icon("Resources/ok-icon.ico"));
_statusImageList.Images.Add("update", new Icon("Resources/up-icon.ico"));
_statusImageList.Images.Add("error", new Icon("Resources/error-icon.ico"));
Text = "Update Checker Configuration";
Size = new Size(600, 800);
StartPosition = FormStartPosition.CenterScreen;
_globalConfig = configManager.Load() ?? new Config();
_mainLayout = GetMainLayout();
_listView = GetListView();
_listView.SmallImageList = _statusImageList;
_mainLayout.Controls.Add(_listView , 0, 0);
_mainLayout.Controls.Add(GetButtonsPanel(), 0, 1);
onAppSavedHandler = (s2, e) =>
{
var form = s2 as AppDetailsForm;
if (form == null || form.SelectedApp == null) return;
var app = form.SelectedApp;
UpdateCurrentVersion(app);
UpdateListView();
OnConfigChanged?.Invoke(this, EventArgs.Empty);
};
this.Load += MainForm_LoadAsync;
}
private void UpdateCurrentVersion(App app)
{
TypeConfig? typeConfig = _globalConfig.Types.FirstOrDefault(tc => app.Type == tc.ShortName);
if (typeConfig == null) return;
var versionInDisk = VersionUtils.GetCurrentVersion(app, typeConfig);
if (!string.IsNullOrEmpty(versionInDisk))
{
app.CurrentVersion = versionInDisk;
app.LastCheckedAt = TimeUtils.GetUnixTimeMillis(DateTime.Now);
_versionService.CheckApp(app);
}
}
private async void MainForm_LoadAsync(object? sender, EventArgs e)
{
await _apiService.AuthenticateAsync("user", "user");
_apps = await _apiService.GetAppsByIds([]);
_sources = await _apiService.GetSources();
_typesDef = await _apiService.GetTypes();
UpdateListView();
}
private TableLayoutPanel GetMainLayout()
{
// Initialize the main layout panel
var mainLayout = new TableLayoutPanel
{
Dock = DockStyle.Fill,
RowCount = 2,
ColumnCount = 1
};
// mainLayout.RowStyles.Add(new RowStyle(SizeType.Absolute, 150)); // Paths panel height
mainLayout.RowStyles.Add(new RowStyle(SizeType.Percent, 70)); // ListView takes remaining space
mainLayout.RowStyles.Add(new RowStyle(SizeType.Absolute, 50)); // Button panel height
Controls.Add(mainLayout);
InitializeMenu();
return mainLayout;
}
private ListView GetListView()
{
var listView = new ListView
{
Dock = DockStyle.Fill,
View = View.Details,
FullRowSelect = true,
MultiSelect = false,
Visible = true,
Sorting = SortOrder.Ascending
};
listView.Columns.Add("Name", 150);
listView.Columns.Add("MSFS Versions", 100);
listView.Columns.Add("Current", 80);
listView.Columns.Add("Latest", 80);
listView.Columns.Add("Last Checked", 150);
listView.DoubleClick += (s, e) =>
{
if (listView.SelectedItems.Count > 0)
{
ListViewItem selectedItem = listView.SelectedItems[0];
App? app = selectedItem.Tag as App;
if (app == null) return;
if (app.Status == AppStatus.UPDATE_AVAILABLE)
{
if (string.IsNullOrEmpty(app.DownloadUrl))
{
MessageBox.Show("No download URL specified for this app.");
return;
}
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
FileName = app.DownloadUrl,
UseShellExecute = true
});
} else
{
}
}
};
return listView;
}
private FlowLayoutPanel GetButtonsPanel() {
var buttonPanel = new FlowLayoutPanel { Dock = DockStyle.Fill };
var addButton = new Button { Text = "Add App" };
var deleteButton = new Button { Text = "Delete App", Enabled = false };
var recheckButton = new Button { Text = "Recheck Versions" };
addButton.Click += (s, e) =>
{
var form = _formFactory.CreateAppDetailsForm(null, true, onAppSavedHandler); // Use factory
form.ShowDialog();
};
deleteButton.Click += (s, e) =>
{
if (_listView.SelectedItems.Count > 0 && _listView.SelectedItems[0].Tag != null)
{
var app = _listView.SelectedItems[0].Tag as App;
DeleteApp(app);
}
};
_listView.SelectedIndexChanged += (s, e) =>
{
deleteButton.Enabled = _listView.SelectedItems.Count > 0;
};
// Add recheck logic here
recheckButton.Click += async (s, e) =>
{
recheckButton.Enabled = false;
await _versionService.CheckAllApps();
UpdateListView();
//OnRecheck.Invoke(this, "User initiated recheck from ConfigForm");
recheckButton.Enabled = true;
};
buttonPanel.Controls.AddRange(new[] { addButton, deleteButton, recheckButton });
return buttonPanel;
}
public void UpdateListView()
{
_apps = _appsManager.Load();
_listView.Items.Clear();
foreach (var app in _apps)
{
var item = new ListViewItem(app.Name);
try {
item.Tag = app;
item.SubItems.Add(app.Type);
var currentVersion = app.CurrentVersion;
var latestVersion = app.LatestVersion;
var lastChecked = TimeUtils.ToFriendlyTime(app.LastCheckedAt);
item.SubItems.Add(currentVersion);
item.SubItems.Add(latestVersion);
item.SubItems.Add(lastChecked);
}
catch (Exception ex)
{
item.SubItems.Add($"Error: {ex.Message}");
}
switch (app.Status)
{
case AppStatus.UPDATE_AVAILABLE:
item.ImageKey = "update";
break;
case AppStatus.ERROR:
item.ImageKey = "error";
break;
default:
item.ImageKey = "none";
break;
}
_listView.Items.Add(item);
}
}
private void DeleteApp(App app)
{
var result = MessageBox.Show($"Are you sure you want to delete '{app?.Name ?? ""}'?", "Confirm Delete", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
if (result == DialogResult.Yes)
{
_appsManager.Delete((_listView.SelectedItems[0].Tag as App).Id);
UpdateListView();
OnConfigChanged?.Invoke(this, EventArgs.Empty);
}
}
private void InitializeMenu()
{
_menuStrip = new MenuStrip();
// Create top-level menu
var configMenu = new ToolStripMenuItem("Configuration");
// Add sub-menu items
var settingsItem = new ToolStripMenuItem("Settings");
settingsItem.Click += (s, e) => ShowGlobalConfigDialog();
var typesItem = new ToolStripMenuItem("Types");
typesItem.Click += (s, e) => ShowTypesConfigForm();
//var sourcesConfigItem = new ToolStripMenuItem("Sources");
//sourcesConfigItem.Click += (s, e) => ShowSourcesConfigDialog();
//var FlightSimsConfigItem = new ToolStripMenuItem("Flight Sims");
//FlightSimsConfigItem.Click += (s, e) => MessageBox.Show("Flight Sims configuration dialog would open here.");
configMenu.DropDownItems.Add(settingsItem);
configMenu.DropDownItems.Add(typesItem);
// configMenu.DropDownItems.Add(sourcesConfigItem);
// configMenu.DropDownItems.Add(FlightSimsConfigItem);
_menuStrip.Items.Add(configMenu);
// Add the menu to the form
Controls.Add(_menuStrip);
MainMenuStrip = _menuStrip;
}
private void ShowTypesConfigForm()
{
var typesConfigForm = _formFactory.CreateTypeConfigForm();
typesConfigForm.ShowDialog();
}
private void ShowGlobalConfigDialog()
{
// Show your global config form/dialog here
var globalConfigForm = _formFactory.CreateGlobalConfigForm();
globalConfigForm.ShowDialog();
}
//private void ShowSourcesConfigDialog()
//{
// EventHandler<string> onSourcesChanged = (s, e) => MessageBox.Show("Sources Changed");
// var form = _formFactory.CreateSourcesConfigForm(onSourcesChanged);
// form.ShowDialog();
//}
}
}

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,145 @@
using ModVersionChecker.controls;
using ModVersionChecker.managers.interfaces;
using ModVersionChecker.model;
using ModVersionChecker.repository.api.dto;
using ModVersionChecker.service.interfaces;
using ModVersionChecker.utils;
namespace ModVersionChecker.ui.forms
{
public class TypeConfigForm : Form
{
private List<TypeResponse> _typeDefs;
private List<TypeConfig> _typeConfigs;
private Config _globalConfig;
private readonly Dictionary<string, CheckBox> _typeCheckBoxes = new();
private readonly Dictionary<string, Panel> _typePanels = new();
private readonly Dictionary<(string typeId, string fieldName), Control> _fieldControls = new();
private FlowLayoutPanel _mainPanel;
private Button _saveButton, _cancelButton;
private readonly IApiService _apiService;
private readonly IConfigManager _configManager;
private readonly ITypeManager _typeManager;
public TypeConfigForm(
ITypeManager typeManager,
IApiService apiService,
IConfigManager configManager
)
{
_apiService = apiService ?? throw new ArgumentNullException(nameof(apiService));
_configManager = configManager ?? throw new ArgumentNullException(nameof(configManager));
_typeManager = typeManager ?? throw new ArgumentNullException(nameof(typeManager));
Load += TypeConfigForm_LoadAsync;
}
public async void TypeConfigForm_LoadAsync(object sender, EventArgs e)
{
_typeDefs = await _apiService.GetTypes();
_typeConfigs = _typeManager.GetTypeConfigs();
_globalConfig = _configManager.Load();
InitializeComponent();
}
private void InitializeComponent()
{
Text = "Configure Types";
Width = 600;
Height = 700;
StartPosition = FormStartPosition.CenterParent;
_mainPanel = new FlowLayoutPanel { Dock = DockStyle.Top, AutoScroll = true, FlowDirection = FlowDirection.TopDown, WrapContents = false, Width = 580, Height = 600 };
foreach (var typeDef in _typeDefs)
{
var isInConfig = _globalConfig.Types.Any(t => t.Id == typeDef.Id);
var typeConfig = _globalConfig.Types.FirstOrDefault(tc => tc.Id == typeDef.Id);
var checkBox = new CheckBox {
Text = typeDef.Name,
Checked = isInConfig,
Enabled = !isInConfig,
AutoSize = true
};
_typeCheckBoxes[typeDef.Id] = checkBox;
var panel = new Panel {
Visible = checkBox.Checked,
AutoSize = false,
BorderStyle = BorderStyle.FixedSingle,
Width = _mainPanel.Width - 20, // leave some margin
Height = 0 // will be set by content
};
_typePanels[typeDef.Id] = panel;
checkBox.CheckedChanged += (s, e) => { panel.Visible = checkBox.Checked; };
var fieldsLayout = new TableLayoutPanel {
ColumnCount = 2,
AutoSize = true,
Dock = DockStyle.Top
};
foreach (var field in typeDef.ConfigFields)
{
fieldsLayout.RowCount++;
fieldsLayout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
fieldsLayout.Controls.Add(new Label {
Text = $"{field.Label ?? field.Name} {field.ControlType}",
AutoSize = true
}, 0, fieldsLayout.RowCount - 1);
var filedInput = InputControlsFactory.CreateControl(field, typeConfig, !isInConfig);
_fieldControls[(typeDef.Id, field.Name)] = filedInput;
fieldsLayout.Controls.Add(filedInput, 1, fieldsLayout.RowCount - 1);
}
panel.Controls.Add(fieldsLayout);
panel.Height = fieldsLayout.PreferredSize.Height + 10;
_mainPanel.Controls.Add(checkBox);
_mainPanel.Controls.Add(panel);
}
_saveButton = new Button { Text = "Save", AutoSize = true };
_saveButton.Click += (s, e) => { SaveConfigs(); };
_cancelButton = new Button { Text = "Cancel", AutoSize = true };
_cancelButton.Click += (s, e) => { DialogResult = DialogResult.Cancel; Close(); };
var buttonPanel = new FlowLayoutPanel { FlowDirection = FlowDirection.RightToLeft, Dock = DockStyle.Bottom, AutoSize = true };
buttonPanel.Controls.Add(_saveButton);
buttonPanel.Controls.Add(_cancelButton);
Controls.Add(_mainPanel);
Controls.Add(buttonPanel);
}
private void SaveConfigs()
{
var result = new List<TypeConfig>();
foreach (var typeDef in _typeDefs)
{
if (_typeCheckBoxes[typeDef.Id].Checked)
{
var config = new TypeConfig { Id = typeDef.Id, Name = typeDef.Name, ShortName = typeDef.ShortName, ConfigValues = new Dictionary<string, string>() };
foreach (var field in typeDef.ConfigFields)
{
if (_fieldControls.TryGetValue((typeDef.Id, field.Name), out var ctrl))
{
if (ctrl is DirectoryPickerControl)
{
config.ConfigValues[field.Name] = (ctrl as DirectoryPickerControl).SelectedPath;
}
else
{
config.ConfigValues[field.Name] = ctrl.Text;
}
}
}
result.Add(config);
}
}
_globalConfig.Types = result;
_configManager.Save(_globalConfig);
DialogResult = DialogResult.OK;
Close();
}
}
}