Skip to content

Commit

Permalink
MF2025 - Config List View - First version (#1934)
Browse files Browse the repository at this point in the history
* Frontend shows config table = WIP

* First refactoring done for the ConfigItems

* WIP - ConfigItem View receiving Config

* 1st working version of the table with acutal config data

* Orphaned Serials Dialog is working correctly.

* Reverted accidental conflict of Name and DeviceName for config

* Fixed wrong clone

* Fixed facetted filtering

* Hide Status and Tags

* Filters are working correctly for the table

* Communication back and forth works for ConfigItem

* Improve the JSON serialization including unit tests

* Config Wizard opens correctly for OutputConfigItems

* Remove unused property "name"

* Serialize InputAction correctly

* Use dynamic way of serializeing ConfigItem and ModifierBase

* Clone InputConfigItem correctly

* fix the property name for ConfigEdit message

* Correctly open InputConfigWizard

* Fixed some visuals for the table

* Use short device type name

* Improved table rendering and active state can be toggled.

* Add output/input config items is possible

* Copy ConfigItem is possible

* All context menu actions are working.

* Fix config ref title, code cleanup

* Update workflow for PR build

* Add xml attrib deserialization info for backward compatibility

* Add status icons and inline name editing for config items

* Fix the table width

* Improved responsivness

* Working playwright tests for the config list

* Improving edit styles

* Fixing linter errors

* Status icons are working

* Fixed playwright test

* Fix failing build

* Fix xml unit tests to preserve temporary backward compatiblity

* Fixed Equals method for ConfigItem

* Fix the update after input config item is edited

* Display test mode status for config items
  • Loading branch information
DocMoebiuz authored Feb 7, 2025
1 parent 61eac0c commit c91fd53
Show file tree
Hide file tree
Showing 124 changed files with 6,285 additions and 6,880 deletions.
8 changes: 6 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: RunTests
on:
workflow_dispatch:
pull_request:
branches: [main]
branches: [main, mf2025/main]

env:
# Path to the solution file relative to the root of the project.
Expand Down Expand Up @@ -42,7 +42,11 @@ jobs:
run: "type Properties/AssemblyInfo.cs"
shell: pwsh

- name: Build
- name: Build frontend
working-directory: ${{env.GITHUB_WORKSPACE}}
run: cd frontend; npm install; npm run build

- name: Build core
working-directory: ${{env.GITHUB_WORKSPACE}}
# Add additional options to the MSBuild command line here (like platform or verbosity level).
# See https://docs.microsoft.com/visualstudio/msbuild/msbuild-command-line-reference
Expand Down
8 changes: 4 additions & 4 deletions Base/AppTelemetry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,12 @@ public void ConfigLoaded(ConfigFile configFile)
{
// Track config loaded event
EventTelemetry trackingEvent = new EventTelemetry("ConfigLoaded");
List<OutputConfigItem> outputConfigs = configFile.GetOutputConfigItems();
List<InputConfigItem> inputConfigs = configFile.GetInputConfigItems();
List<OutputConfigItem> outputConfigs = configFile.OutputConfigItems;
List<InputConfigItem> inputConfigs = configFile.InputConfigItems;

foreach (OutputConfigItem item in outputConfigs)
{
String key = "output." + item.DisplayType;
String key = "output." + item.DeviceType;
if (!trackingEvent.Metrics.ContainsKey(key)) trackingEvent.Metrics[key] = 0;
trackingEvent.Metrics[key] += 1;

Expand All @@ -86,7 +86,7 @@ public void ConfigLoaded(ConfigFile configFile)

foreach (InputConfigItem item in inputConfigs)
{
String key = "input." + item.Type;
String key = "input." + item.DeviceType;
if (item.ModuleSerial.Contains(Joystick.SerialPrefix))
{
key += ".joystick";
Expand Down
246 changes: 128 additions & 118 deletions Base/ConfigFile.cs
Original file line number Diff line number Diff line change
@@ -1,82 +1,72 @@
using System;
using MobiFlight.Base;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Data;
using System.IO;
using MobiFlight.InputConfig;
using System.Xml;
using System.Xml.Serialization;

namespace MobiFlight
{
[XmlRoot("MobiFlightConnector")]
public class ConfigFile
{
[XmlElement("outputs")]
[JsonIgnore]
public List<OutputConfigItem> OutputConfigItems { get; set; } = new List<OutputConfigItem>();

[XmlElement("inputs")]
[JsonIgnore]
public List<InputConfigItem> InputConfigItems { get; set; } = new List<InputConfigItem>();

[XmlIgnore]
public String FileName { get; set; }

// create read only property to get the output config items
[XmlIgnore]
public List<IConfigItem> ConfigItems
{
get { return GetConfigItems(); }
}

System.Xml.XmlDocument xmlConfig = new System.Xml.XmlDocument();

public ConfigFile() { }

public ConfigFile(String FileName)
{
this.FileName = FileName;
}

public void OpenFile()
{
if (FileName == null) throw new Exception("File yet not set");

xmlConfig.Load(FileName);
OutputConfigItems = GetOutputConfigItems();
InputConfigItems = GetInputConfigItems();
}

public void SaveFile(DataSet outputConfig, DataSet inputConfig)
public List<IConfigItem> GetConfigItems()
{
xmlConfig.RemoveAll();
XmlDeclaration xmlDeclaration = xmlConfig.CreateXmlDeclaration("1.0", "UTF-8", "yes");
xmlConfig.InsertBefore(xmlDeclaration, xmlConfig.DocumentElement);

XmlElement root = xmlConfig.CreateElement("MobiflightConnector");
// Create a new element and add it to the document.
StringWriter sw = new StringWriter();
outputConfig.WriteXml(sw, XmlWriteMode.IgnoreSchema);
string s = sw.ToString();
XmlDocument tmpDoc = new XmlDocument();
tmpDoc.LoadXml(s);

XmlElement outputs = xmlConfig.CreateElement("outputs");
outputs.InnerXml = tmpDoc.DocumentElement.SelectSingleNode("/outputs").InnerXml;

sw = new StringWriter();
inputConfig.WriteXml(sw, XmlWriteMode.IgnoreSchema);
s = sw.ToString();
tmpDoc = new XmlDocument();
tmpDoc.LoadXml(s);

XmlElement inputs = xmlConfig.CreateElement("inputs");
inputs.InnerXml = tmpDoc.DocumentElement.SelectSingleNode("/inputs").InnerXml;

root.AppendChild(outputs);
root.AppendChild(inputs);
xmlConfig.AppendChild(root);
xmlConfig.Save(FileName);
}
var result = new List<IConfigItem>();
OutputConfigItems.ForEach(item => result.Add(item));
InputConfigItems.ForEach(item => result.Add(item));

private XmlReader getConfig(String xpath)
{
// first try the new way... if this fails try the old way
System.Xml.XmlNode outputConfig = xmlConfig.DocumentElement.SelectSingleNode(xpath);
if (outputConfig == null) throw new InvalidExpressionException();

System.IO.StringReader reader = new System.IO.StringReader(outputConfig.OuterXml);
System.Xml.XmlReader xReader = System.Xml.XmlReader.Create(reader);
return xReader;
return result;
}

public List<OutputConfigItem> GetOutputConfigItems()
protected List<OutputConfigItem> GetOutputConfigItems()
{
List<OutputConfigItem> result = new List<OutputConfigItem>();

XmlNodeList outputs = xmlConfig.DocumentElement.SelectNodes("outputs/config/settings");
foreach(XmlNode item in outputs)
XmlNodeList outputs = xmlConfig.DocumentElement.SelectNodes("outputs/config");
foreach (XmlNode item in outputs)
{
OutputConfigItem config = new OutputConfigItem();
System.IO.StringReader reader = new System.IO.StringReader(item.OuterXml);
config.GUID = item.Attributes["guid"].Value;
config.Active = item.SelectSingleNode("active").InnerText == "true";
config.Name = item.SelectSingleNode("description").InnerText;

System.IO.StringReader reader = new System.IO.StringReader(item.SelectSingleNode("settings").OuterXml);
System.Xml.XmlReader xReader = System.Xml.XmlReader.Create(reader);
config.ReadXml(xReader);
result.Add(config);
Expand All @@ -85,15 +75,19 @@ public List<OutputConfigItem> GetOutputConfigItems()
return result;
}

internal List<InputConfigItem> GetInputConfigItems()
protected List<InputConfigItem> GetInputConfigItems()
{
List<InputConfigItem> result = new List<InputConfigItem>();

XmlNodeList inputs = xmlConfig.DocumentElement.SelectNodes("inputs/config/settings");
XmlNodeList inputs = xmlConfig.DocumentElement.SelectNodes("inputs/config");
foreach (XmlNode item in inputs)
{
InputConfigItem config = new InputConfigItem();
System.IO.StringReader reader = new System.IO.StringReader(item.OuterXml);
config.GUID = item.Attributes["guid"].Value;
config.Active = item.SelectSingleNode("active").InnerText == "true";
config.Name = item.SelectSingleNode("description").InnerText;

System.IO.StringReader reader = new System.IO.StringReader(item.SelectSingleNode("settings").OuterXml);
System.Xml.XmlReader xReader = System.Xml.XmlReader.Create(reader);
xReader.Read();
config.ReadXml(xReader);
Expand All @@ -103,75 +97,91 @@ internal List<InputConfigItem> GetInputConfigItems()
return result;
}

public XmlReader getInputConfig()
public void SaveFile()
{
XmlReader result = null;

try
{
if (xmlConfig.DocumentElement == null) OpenFile();

result = getConfig("/MobiflightConnector/inputs");
}
catch (InvalidExpressionException e)
{
throw e;
}
catch (Exception e)
{
throw new ConfigErrorException("Error reading config", e);
}
return result;
SaveFile(OutputConfigItems, InputConfigItems);
}

public XmlReader getOutputConfig()
private void SaveFile(List<OutputConfigItem> outputConfigItems, List<InputConfigItem> inputConfigItems)
{
XmlReader result = null;

bool fallback = false;
try
{
if (xmlConfig.DocumentElement == null) OpenFile();
result = getConfig("/MobiflightConnector/outputs");
}
catch (InvalidExpressionException e)
{
fallback = true;
}
catch (Exception e)
XmlSerializer serializer = new XmlSerializer(typeof(ConfigFileWrapperXML));
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
namespaces.Add("", "");
//namespaces.Add("xsd", "https://www.w3.org/2001/XMLSchema");

var XmlConfig = new ConfigFileWrapperXML();
XmlConfig.outputConfigs = new List<OutputConfigFileXmlElement>();
outputConfigItems.ForEach(item => XmlConfig.outputConfigs.Add(new OutputConfigFileXmlElement() { guid = item.GUID, active = item.Active, description = item.Name, settings = item }));
XmlConfig.inputConfigs = new List<InputConfigFileXmlElement>();
inputConfigItems.ForEach(item => XmlConfig.inputConfigs.Add(new InputConfigFileXmlElement() { guid = item.GUID, active = item.Active, description = item.Name, settings = item }));

using (StreamWriter writer = new StreamWriter(FileName))
{
throw new ConfigErrorException("Error reading config", e);
serializer.Serialize(writer, XmlConfig, namespaces);
}

if (fallback)
{
fallback = false;
// fallback for old configs
try
{
result = getConfig("/MobiflightConnector");
}
catch (Exception ex)
{
fallback = true;
}
}

if (fallback)
{
fallback = false;
// fallback for old configs
try
{
result = getConfig("/ArcazeUsbConnector");
}
catch (Exception ex)
{
throw new Exception("Error: Loading config");
}
}

return result;
}

// <summary>
// due to the new settings-node there must be some routine to load
// data from legacy config files
// </summary>
//private void _applyBackwardCompatibilityLoading()
//{
// foreach (DataRow row in outputConfigPanel.ConfigDataTable.Rows)
// {
// if (row["settings"].GetType() == typeof(System.DBNull))
// {
// OutputConfigItem cfgItem = new OutputConfigItem();

// if (row["fsuipcOffset"].GetType() != typeof(System.DBNull))
// cfgItem.FSUIPC.Offset = Int32.Parse(row["fsuipcOffset"].ToString().Replace("0x", ""), System.Globalization.NumberStyles.HexNumber);

// if (row["fsuipcSize"].GetType() != typeof(System.DBNull))
// cfgItem.FSUIPC.Size = Byte.Parse(row["fsuipcSize"].ToString());

// if (row["mask"].GetType() != typeof(System.DBNull))
// cfgItem.FSUIPC.Mask = (row["mask"].ToString() != "") ? Int32.Parse(row["mask"].ToString()) : Int32.MaxValue;

// comparison
// if (row["comparison"].GetType() != typeof(System.DBNull))
// {
// cfgItem.Modifiers.Comparison.Active = true;
// cfgItem.Modifiers.Comparison.Operand = row["comparison"].ToString();
// }

// if (row["comparisonValue"].GetType() != typeof(System.DBNull))
// {
// cfgItem.Modifiers.Comparison.Value = row["comparisonValue"].ToString();
// }

// if (row["converter"].GetType() != typeof(System.DBNull))
// {
// if (row["converter"].ToString() == "Boolean")
// {
// cfgItem.Modifiers.Comparison.IfValue = "1";
// cfgItem.Modifiers.Comparison.ElseValue = "0";
// }
// }

// if (row["trigger"].GetType() != typeof(System.DBNull))
// {
// cfgItem.DisplayTrigger = row["trigger"].ToString();
// }

// if (row["usbArcazePin"].GetType() != typeof(System.DBNull))
// {
// cfgItem.DisplayType = MobiFlightOutput.TYPE;
// cfgItem.Pin.DisplayPin = row["usbArcazePin"].ToString();
// }

// if (row["arcazeSerial"].GetType() != typeof(System.DBNull))
// {
// cfgItem.DisplaySerial = row["arcazeSerial"].ToString();
// }

// row["settings"] = cfgItem;
// }
// }
//}
}
}
}
44 changes: 44 additions & 0 deletions Base/ConfigFileWrapperXML.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System.Collections.Generic;
using System.Xml.Serialization;

namespace MobiFlight.Base
{
[XmlRoot("config")]
public class ConfigFileXmlItem
{

[XmlAttribute("guid")]
public string guid { get; set; }

[XmlElement]
public bool active { get; set; }

[XmlElement]
public string description { get; set; }
}

public class OutputConfigFileXmlElement : ConfigFileXmlItem
{
[XmlElement]
public OutputConfigItem settings { get; set; }
}

public class InputConfigFileXmlElement : ConfigFileXmlItem
{
[XmlElement]
public InputConfigItem settings { get; set; }
}


[XmlRoot("MobiflightConnector")]
public class ConfigFileWrapperXML
{
[XmlArray("outputs")]
[XmlArrayItem("config")]
public List<OutputConfigFileXmlElement> outputConfigs;

[XmlArray("inputs")]
[XmlArrayItem("config")]
public List<InputConfigFileXmlElement> inputConfigs;
}
}
Loading

0 comments on commit c91fd53

Please sign in to comment.