From cd60aefa99feb43ab34466081f7afcf789a3d9fc Mon Sep 17 00:00:00 2001 From: Sylvester Knudsen Date: Fri, 7 May 2021 13:14:51 +0100 Subject: [PATCH] Linter ViewExtension (#11634) * Initial commit * Move event subscription to extension base class + add property changed filter to rules * comment updates * Update LinterExtensionBase.cs * Update InputNodesNotAllowedRule.cs * Add Deactivate method to linterExtensionBase + LinterManager tests * comment updates * Initial commit * Add issues count to WorkspaceSaving * add scrollviewer * Dispose LinterManager * Make concrete issues internal * summaries on IRuleIssue * comment updates * Update severity code icon * Serialize linter manager to dyn file * clear ruleEvaluationResults on workspace change * Remove test extension * Show names instead of GUIDs * add help doc * clean up * Handle GraphRuleEvaluationResults nodeIds changes * Update LintingViewExtension.csproj * Update LintingViewExtension.csproj * Update SerializationConverters.cs * Fix failing serialization test * comment updates * revert changes to test file * fix available linters binding * add rules back in test ext * comment updates * remove output path * Update LinterManagerTests.cs * comment updates * hide LinterViewExtension and LinterExtensionBase * Update LinterViewModel.cs --- src/Dynamo.All.sln | 11 + .../Extensions/LinterExtensionBase.cs | 6 +- src/DynamoCore/Linting/LinterManager.cs | 19 +- .../Linting/Rules/GraphLinterRule.cs | 8 +- .../Linting/Rules/NodeLinterRule.cs | 8 +- src/DynamoCore/Properties/AssemblyInfo.cs | 3 +- .../Controls/GraphRuleIssue.cs | 26 ++ .../Controls/IRuleIssue.cs | 31 ++ .../Controls/IssueGroup.xaml | 73 +++++ .../Controls/IssueGroup.xaml.cs | 72 +++++ .../Controls/NodeRuleIssue.cs | 36 +++ .../CollectionToVisibilityConverter.cs | 39 +++ .../Converters/RuleFromRuleIdConverter.cs | 57 ++++ .../SeverityCodeToColorConverter.cs | 37 +++ .../Converters/SeverityCodeToIconConverter.cs | 36 +++ .../Converters/ToStringConverter.cs | 23 ++ .../Docs/LinterViewHelpDoc.html | 185 ++++++++++++ src/LintingViewExtension/LinterView.xaml | 224 +++++++++++++++ src/LintingViewExtension/LinterView.xaml.cs | 22 ++ src/LintingViewExtension/LinterViewModel.cs | 270 ++++++++++++++++++ .../Linter_ViewExtensionDefinition.xml | 5 + .../LintingViewExtension.cs | 77 +++++ .../LintingViewExtension.csproj | 148 ++++++++++ .../Properties/AssemblyInfo.cs | 13 + .../Properties/Resources.Designer.cs | 90 ++++++ .../Properties/Resources.en-US.Designer.cs | 0 .../Properties/Resources.en-US.resx | 129 +++++++++ .../Properties/Resources.resx | 129 +++++++++ src/LintingViewExtension/Styles.xaml | 110 +++++++ src/LintingViewExtension/packages.config | 4 + .../Linter_ViewExtensionDefinition.xml | 4 + .../LinterRules/DuplicatedNamesRule.cs | 88 ++++++ .../LinterRules/GraphNeedsOutputNodesRule.cs | 59 ++++ .../LinterRules/InputNodesNotAllowedRule.cs | 48 ++++ .../LinterRules/NodesCantBeNamedFooRule.cs | 48 ++++ .../LinterRules/WarningSeverityRule.cs | 58 ++++ .../Properties/AssemblyInfo.cs | 36 +++ .../TestLinterExtension.cs | 68 +++++ .../TestLinterExtension.csproj | 76 +++++ .../TestLinter_ExtensionDefinition.xml | 4 + .../TestLinter_ViewExtensionDefinition.xml | 4 + .../Linting/LinterManagerTests.cs | 8 +- 42 files changed, 2371 insertions(+), 21 deletions(-) create mode 100644 src/LintingViewExtension/Controls/GraphRuleIssue.cs create mode 100644 src/LintingViewExtension/Controls/IRuleIssue.cs create mode 100644 src/LintingViewExtension/Controls/IssueGroup.xaml create mode 100644 src/LintingViewExtension/Controls/IssueGroup.xaml.cs create mode 100644 src/LintingViewExtension/Controls/NodeRuleIssue.cs create mode 100644 src/LintingViewExtension/Converters/CollectionToVisibilityConverter.cs create mode 100644 src/LintingViewExtension/Converters/RuleFromRuleIdConverter.cs create mode 100644 src/LintingViewExtension/Converters/SeverityCodeToColorConverter.cs create mode 100644 src/LintingViewExtension/Converters/SeverityCodeToIconConverter.cs create mode 100644 src/LintingViewExtension/Converters/ToStringConverter.cs create mode 100644 src/LintingViewExtension/Docs/LinterViewHelpDoc.html create mode 100644 src/LintingViewExtension/LinterView.xaml create mode 100644 src/LintingViewExtension/LinterView.xaml.cs create mode 100644 src/LintingViewExtension/LinterViewModel.cs create mode 100644 src/LintingViewExtension/Linter_ViewExtensionDefinition.xml create mode 100644 src/LintingViewExtension/LintingViewExtension.cs create mode 100644 src/LintingViewExtension/LintingViewExtension.csproj create mode 100644 src/LintingViewExtension/Properties/AssemblyInfo.cs create mode 100644 src/LintingViewExtension/Properties/Resources.Designer.cs create mode 100644 src/LintingViewExtension/Properties/Resources.en-US.Designer.cs create mode 100644 src/LintingViewExtension/Properties/Resources.en-US.resx create mode 100644 src/LintingViewExtension/Properties/Resources.resx create mode 100644 src/LintingViewExtension/Styles.xaml create mode 100644 src/LintingViewExtension/packages.config create mode 100644 src/LintingViewExtension/viewExtensions/Linter_ViewExtensionDefinition.xml create mode 100644 src/TestLinterExtension/LinterRules/DuplicatedNamesRule.cs create mode 100644 src/TestLinterExtension/LinterRules/GraphNeedsOutputNodesRule.cs create mode 100644 src/TestLinterExtension/LinterRules/InputNodesNotAllowedRule.cs create mode 100644 src/TestLinterExtension/LinterRules/NodesCantBeNamedFooRule.cs create mode 100644 src/TestLinterExtension/LinterRules/WarningSeverityRule.cs create mode 100644 src/TestLinterExtension/Properties/AssemblyInfo.cs create mode 100644 src/TestLinterExtension/TestLinterExtension.cs create mode 100644 src/TestLinterExtension/TestLinterExtension.csproj create mode 100644 src/TestLinterExtension/TestLinter_ExtensionDefinition.xml create mode 100644 src/TestLinterExtension/TestLinter_ViewExtensionDefinition.xml diff --git a/src/Dynamo.All.sln b/src/Dynamo.All.sln index b79f05e14f2..1bf7d31e7e7 100644 --- a/src/Dynamo.All.sln +++ b/src/Dynamo.All.sln @@ -237,6 +237,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NodeAutoCompleteViewExtensi EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Md2Html", "Tools\Md2Html\Md2Html.csproj", "{0893F745-CB1A-427A-8E87-CA927273039A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LintingViewExtension", "LintingViewExtension\LintingViewExtension.csproj", "{C86F9058-229D-40A9-95D5-D6F081AA9230}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -869,6 +871,14 @@ Global {0893F745-CB1A-427A-8E87-CA927273039A}.Release|Any CPU.Build.0 = Release|Any CPU {0893F745-CB1A-427A-8E87-CA927273039A}.Release|x64.ActiveCfg = Release|Any CPU {0893F745-CB1A-427A-8E87-CA927273039A}.Release|x64.Build.0 = Release|Any CPU + {C86F9058-229D-40A9-95D5-D6F081AA9230}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C86F9058-229D-40A9-95D5-D6F081AA9230}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C86F9058-229D-40A9-95D5-D6F081AA9230}.Debug|x64.ActiveCfg = Debug|x64 + {C86F9058-229D-40A9-95D5-D6F081AA9230}.Debug|x64.Build.0 = Debug|x64 + {C86F9058-229D-40A9-95D5-D6F081AA9230}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C86F9058-229D-40A9-95D5-D6F081AA9230}.Release|Any CPU.Build.0 = Release|Any CPU + {C86F9058-229D-40A9-95D5-D6F081AA9230}.Release|x64.ActiveCfg = Release|x64 + {C86F9058-229D-40A9-95D5-D6F081AA9230}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -946,6 +956,7 @@ Global {10AF430D-0D3A-49CE-A63D-848912959745} = {88D45B00-E564-41DB-B57C-9509646CAA49} {51511AFD-F326-4995-8E27-5D711419EF6F} = {88D45B00-E564-41DB-B57C-9509646CAA49} {0893F745-CB1A-427A-8E87-CA927273039A} = {D114C59C-CF66-4CC2-980F-9301FB4EA4E1} + {C86F9058-229D-40A9-95D5-D6F081AA9230} = {88D45B00-E564-41DB-B57C-9509646CAA49} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {89CB19C6-BF0A-4E6A-BFDA-79D143EAB59D} diff --git a/src/DynamoCore/Extensions/LinterExtensionBase.cs b/src/DynamoCore/Extensions/LinterExtensionBase.cs index d262d0c88c3..8456960be38 100644 --- a/src/DynamoCore/Extensions/LinterExtensionBase.cs +++ b/src/DynamoCore/Extensions/LinterExtensionBase.cs @@ -14,7 +14,7 @@ namespace Dynamo.Extensions /// /// Base class for all LinterExtensions /// - public abstract class LinterExtensionBase : IExtension + internal abstract class LinterExtensionBase : IExtension { private const string NODE_ADDED_PROPERTY = "NodeAdded"; private const string NODE_REMOVED_PROPERTY = "NodeRemoved"; @@ -115,7 +115,7 @@ private void EvaluateGraphRules(NodeModel modifiedNode, string changedProperty) !rule.EvaluationTriggerEvents.Contains(changedProperty)) continue; - rule.Evaluate(currentWorkspace, modifiedNode); + rule.Evaluate(currentWorkspace, changedProperty, modifiedNode); } } @@ -137,7 +137,7 @@ private void EvaluateNodeRules(NodeModel modifiedNode, string changedProperty) if (changedProperty != NODE_ADDED_PROPERTY && !rule.EvaluationTriggerEvents.Contains(changedProperty)) continue; - rule.Evaluate(modifiedNode); + rule.Evaluate(modifiedNode, changedProperty); } } diff --git a/src/DynamoCore/Linting/LinterManager.cs b/src/DynamoCore/Linting/LinterManager.cs index 1a43d9ee12f..4ee33028873 100644 --- a/src/DynamoCore/Linting/LinterManager.cs +++ b/src/DynamoCore/Linting/LinterManager.cs @@ -46,15 +46,17 @@ public LinterExtensionDescriptor ActiveLinter { if (activeLinter == value) return; - if (activeLinter != null) + if (activeLinter != null && + TryGetLinterExtension(activeLinter, out LinterExtensionBase linterExtension)) { - GetLinterExtension(activeLinter).Deactivate(); + linterExtension.Deactivate(); } - var linterExt = GetLinterExtension(value); - if (linterExt is null) return; + if (TryGetLinterExtension(value, out linterExtension)) + { + linterExtension.Activate(); + } - linterExt.Activate(); activeLinter = value; } } @@ -131,13 +133,16 @@ private void OnRuleEvaluated(IRuleEvaluationResult result) } } - private LinterExtensionBase GetLinterExtension(LinterExtensionDescriptor activeLinter) + + internal bool TryGetLinterExtension(LinterExtensionDescriptor activeLinter, out LinterExtensionBase linterExtension) { - return this.extensionManager. + linterExtension = this.extensionManager. Extensions. OfType(). Where(x => x.UniqueId == activeLinter.Id). FirstOrDefault(); + + return linterExtension != null; } public void Dispose() diff --git a/src/DynamoCore/Linting/Rules/GraphLinterRule.cs b/src/DynamoCore/Linting/Rules/GraphLinterRule.cs index 3070b81702d..7511b2072d6 100644 --- a/src/DynamoCore/Linting/Rules/GraphLinterRule.cs +++ b/src/DynamoCore/Linting/Rules/GraphLinterRule.cs @@ -19,10 +19,11 @@ public abstract class GraphLinterRule : LinterRule /// This will use to evaluate the rule. /// /// + /// /// - internal void Evaluate(WorkspaceModel workspaceModel, NodeModel modifiedNode = null) + internal void Evaluate(WorkspaceModel workspaceModel, string changedEvent, NodeModel modifiedNode = null) { - var pair = EvaluateFunction(workspaceModel, modifiedNode); + var pair = EvaluateFunction(workspaceModel, changedEvent, modifiedNode); if (pair is null) return; var result = new GraphRuleEvaluationResult(this.Id, pair.Item1, this.SeverityCode, pair.Item2); @@ -33,9 +34,10 @@ internal void Evaluate(WorkspaceModel workspaceModel, NodeModel modifiedNode = n /// Function used to evaluate this rule /// /// + /// /// /// - protected abstract Tuple> EvaluateFunction(WorkspaceModel workspaceModel, NodeModel modifiedNode = null); + protected abstract Tuple> EvaluateFunction(WorkspaceModel workspaceModel, string changedEvent, NodeModel modifiedNode = null); /// /// The init function is used when the Linter extension implementing this Rule is initialized. diff --git a/src/DynamoCore/Linting/Rules/NodeLinterRule.cs b/src/DynamoCore/Linting/Rules/NodeLinterRule.cs index 99429284b2f..2692063fb1f 100644 --- a/src/DynamoCore/Linting/Rules/NodeLinterRule.cs +++ b/src/DynamoCore/Linting/Rules/NodeLinterRule.cs @@ -20,9 +20,10 @@ public abstract class NodeLinterRule : LinterRule /// This will use to evaluate the rule. /// /// - internal void Evaluate(NodeModel nodeModel) + /// + internal void Evaluate(NodeModel nodeModel, string changedEvent) { - var status = EvaluateFunction(nodeModel); + var status = EvaluateFunction(nodeModel, changedEvent); var result = new NodeRuleEvaluationResult(this.Id, status, this.SeverityCode, nodeModel.GUID.ToString()); OnRuleEvaluated(result); } @@ -31,8 +32,9 @@ internal void Evaluate(NodeModel nodeModel) /// Function used to evaluate this rule /// /// Node to evaluate + /// /// - protected abstract RuleEvaluationStatusEnum EvaluateFunction(NodeModel nodeModel); + protected abstract RuleEvaluationStatusEnum EvaluateFunction(NodeModel nodeModel, string changedEvent); /// /// The init function is used when the Linter extension implementing this Rule is initialized. diff --git a/src/DynamoCore/Properties/AssemblyInfo.cs b/src/DynamoCore/Properties/AssemblyInfo.cs index c60a62fb7db..692ed5ca4cc 100644 --- a/src/DynamoCore/Properties/AssemblyInfo.cs +++ b/src/DynamoCore/Properties/AssemblyInfo.cs @@ -34,4 +34,5 @@ [assembly: InternalsVisibleTo("PythonNodeModelsWpf")] [assembly: InternalsVisibleTo("PythonNodeModels")] [assembly: InternalsVisibleTo("LibraryViewExtensionMSWebBrowser")] -[assembly: InternalsVisibleTo("PythonMigrationViewExtension")] \ No newline at end of file +[assembly: InternalsVisibleTo("PythonMigrationViewExtension")] +[assembly: InternalsVisibleTo("LintingViewExtension")] \ No newline at end of file diff --git a/src/LintingViewExtension/Controls/GraphRuleIssue.cs b/src/LintingViewExtension/Controls/GraphRuleIssue.cs new file mode 100644 index 00000000000..a725069abd2 --- /dev/null +++ b/src/LintingViewExtension/Controls/GraphRuleIssue.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Dynamo.Graph.Nodes; +using Dynamo.Linting.Rules; + +namespace Dynamo.LintingViewExtension.Controls +{ + internal class GraphRuleIssue : IRuleIssue + { + public string Id { get; } + public LinterRule Rule { get; } + public ObservableCollection AffectedNodes { get; private set; } + + public GraphRuleIssue(string id, GraphLinterRule rule) + { + Id = id; + Rule = rule; + AffectedNodes = new ObservableCollection(); + } + + public void AddAffectedNodes(List nodes) + { + nodes.ForEach(x => AffectedNodes.Add(x)); + } + } +} \ No newline at end of file diff --git a/src/LintingViewExtension/Controls/IRuleIssue.cs b/src/LintingViewExtension/Controls/IRuleIssue.cs new file mode 100644 index 00000000000..9bf7c914e66 --- /dev/null +++ b/src/LintingViewExtension/Controls/IRuleIssue.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Dynamo.Graph.Nodes; +using Dynamo.Linting.Rules; + +namespace Dynamo.LintingViewExtension +{ + public interface IRuleIssue + { + /// + /// Collection of nodeIds affected by this rule issue + /// + ObservableCollection AffectedNodes { get; } + + /// + /// Id of the rule this issue comes from + /// + string Id { get; } + + /// + /// Rule this issue comes from + /// + LinterRule Rule { get; } + + /// + /// Adds a list of affected nodes to this issue + /// + /// + void AddAffectedNodes(List nodes); + } +} \ No newline at end of file diff --git a/src/LintingViewExtension/Controls/IssueGroup.xaml b/src/LintingViewExtension/Controls/IssueGroup.xaml new file mode 100644 index 00000000000..22c490580aa --- /dev/null +++ b/src/LintingViewExtension/Controls/IssueGroup.xaml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/LintingViewExtension/Controls/IssueGroup.xaml.cs b/src/LintingViewExtension/Controls/IssueGroup.xaml.cs new file mode 100644 index 00000000000..b08791937be --- /dev/null +++ b/src/LintingViewExtension/Controls/IssueGroup.xaml.cs @@ -0,0 +1,72 @@ +using System.Collections.Generic; +using System.Windows; +using System.Windows.Controls; +using Dynamo.Graph.Nodes; +using Dynamo.Linting.Interfaces; +using Dynamo.Linting.Rules; + +namespace Dynamo.LintingViewExtension.Controls +{ + /// + /// Interaction logic for IssueGroup.xaml + /// + public partial class IssueGroup : UserControl + { + #region DependencyProperties + + internal IEnumerable IssueNodes + { + get { return (IEnumerable)GetValue(IssueNodesProperty); } + set { SetValue(IssueNodesProperty, value); } + } + + public static readonly DependencyProperty IssueNodesProperty = DependencyProperty.Register( + nameof(IssueNodes), + typeof(IEnumerable), + typeof(IssueGroup) + ); + + public string Description + { + get { return (string)GetValue(DescriptionProperty); } + set { SetValue(DescriptionProperty, value); } + } + + public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register( + nameof(Description), + typeof(string), + typeof(IssueGroup) + ); + + public string CallToAction + { + get { return (string)GetValue(CallToActionProperty); } + set { SetValue(CallToActionProperty, value); } + } + + public static readonly DependencyProperty CallToActionProperty = DependencyProperty.Register( + nameof(CallToAction), + typeof(string), + typeof(IssueGroup) + ); + + public SeverityCodesEnum SeverityCode + { + get { return (SeverityCodesEnum)GetValue(SeverityCodeProperty); } + set { SetValue(SeverityCodeProperty, value); } + } + + public static readonly DependencyProperty SeverityCodeProperty = DependencyProperty.Register( + nameof(SeverityCode), + typeof(SeverityCodesEnum), + typeof(IssueGroup) + ); + + #endregion DependencyProperties + + public IssueGroup() + { + InitializeComponent(); + } + } +} diff --git a/src/LintingViewExtension/Controls/NodeRuleIssue.cs b/src/LintingViewExtension/Controls/NodeRuleIssue.cs new file mode 100644 index 00000000000..8835875a8c1 --- /dev/null +++ b/src/LintingViewExtension/Controls/NodeRuleIssue.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using Dynamo.Core; +using Dynamo.Graph.Nodes; +using Dynamo.Linting.Rules; + +namespace Dynamo.LintingViewExtension.Controls +{ + internal class NodeRuleIssue : IRuleIssue + { + public string Id { get; } + public LinterRule Rule { get; } + public ObservableCollection AffectedNodes { get; private set; } + + public NodeRuleIssue(string id, NodeLinterRule rule) + { + Id = id; + Rule = rule; + AffectedNodes = new ObservableCollection(); + } + + public void AddAffectedNodes(List nodes) + { + // Node rules will always only have a single Id + var nodeId = nodes.FirstOrDefault(); + if (nodeId is null) + return; + + if (AffectedNodes.Contains(nodeId)) + return; + + AffectedNodes.Add(nodeId); + } + } +} diff --git a/src/LintingViewExtension/Converters/CollectionToVisibilityConverter.cs b/src/LintingViewExtension/Converters/CollectionToVisibilityConverter.cs new file mode 100644 index 00000000000..9505433c89f --- /dev/null +++ b/src/LintingViewExtension/Converters/CollectionToVisibilityConverter.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Data; + +namespace Dynamo.LintingViewExtension.Converters +{ + internal class CollectionToVisibilityConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null) + return Visibility.Collapsed; + else + { + var collection = value as IEnumerable; + if (collection != null) + { + if (collection.Count() == 0) + return Visibility.Collapsed; + else + return Visibility.Visible; + } + else + return Visibility.Visible; + } + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/LintingViewExtension/Converters/RuleFromRuleIdConverter.cs b/src/LintingViewExtension/Converters/RuleFromRuleIdConverter.cs new file mode 100644 index 00000000000..528822ca913 --- /dev/null +++ b/src/LintingViewExtension/Converters/RuleFromRuleIdConverter.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Data; +using Dynamo.Extensions; +using Dynamo.Linting.Rules; + +namespace Dynamo.LintingViewExtension.Converters +{ + [ValueConversion(typeof(CollectionViewGroup), typeof(LinterRule))] + public class RuleFromRuleIdConverter : DependencyObject, IValueConverter + { + // The property used as a parameter + internal LinterExtensionBase ActiveLinter + { + get { return (LinterExtensionBase)GetValue(ActiveLinterProperty); } + set { SetValue(ActiveLinterProperty, value); } + } + + // The dependency property to allow the property to be used from XAML. + public static readonly DependencyProperty ActiveLinterProperty = + DependencyProperty.Register( + nameof(ActiveLinter), + typeof(LinterExtensionBase), + typeof(RuleFromRuleIdConverter)); + + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (!(value is CollectionViewGroup cvg) || !(cvg.Name is string ruleId)) + return null; + + var linterRule = GetLinterRuleById(ruleId, ActiveLinter); + return linterRule; + + } + private LinterRule GetLinterRuleById(string ruleId, LinterExtensionBase linter) + { + var linterRule = linter.LinterRules.Where(x => x.Id == ruleId).FirstOrDefault(); + return linterRule; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (!(value is Boolean boolean)) + { + return false; + } + + return !boolean; + } + } +} diff --git a/src/LintingViewExtension/Converters/SeverityCodeToColorConverter.cs b/src/LintingViewExtension/Converters/SeverityCodeToColorConverter.cs new file mode 100644 index 00000000000..f38d1bb2d3a --- /dev/null +++ b/src/LintingViewExtension/Converters/SeverityCodeToColorConverter.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Data; +using System.Windows.Media; +using Dynamo.Linting.Interfaces; + +namespace Dynamo.LintingViewExtension.Converters +{ + [ValueConversion(typeof(SeverityCodesEnum), typeof(SolidColorBrush))] + internal class SeverityCodeToColorConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (!(value is SeverityCodesEnum severityCode)) + return null; + + switch (severityCode) + { + case SeverityCodesEnum.Warning: + return new SolidColorBrush(Colors.Yellow); + case SeverityCodesEnum.Error: + return new SolidColorBrush(Colors.Red); + default: + return null; + } + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/LintingViewExtension/Converters/SeverityCodeToIconConverter.cs b/src/LintingViewExtension/Converters/SeverityCodeToIconConverter.cs new file mode 100644 index 00000000000..9fff41b0ab8 --- /dev/null +++ b/src/LintingViewExtension/Converters/SeverityCodeToIconConverter.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Data; +using Dynamo.Linting.Interfaces; + +namespace Dynamo.LintingViewExtension.Converters +{ + [ValueConversion(typeof(SeverityCodesEnum), typeof(FontAwesome.WPF.FontAwesomeIcon))] + internal class SeverityCodeToIconConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (!(value is SeverityCodesEnum severityCode)) + return null; + + switch (severityCode) + { + case SeverityCodesEnum.Warning: + return FontAwesome.WPF.FontAwesomeIcon.ExclamationTriangle; + case SeverityCodesEnum.Error: + return FontAwesome.WPF.FontAwesomeIcon.TimesCircle; + default: + return null; + } + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/LintingViewExtension/Converters/ToStringConverter.cs b/src/LintingViewExtension/Converters/ToStringConverter.cs new file mode 100644 index 00000000000..0d17ec6ecde --- /dev/null +++ b/src/LintingViewExtension/Converters/ToStringConverter.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Data; + +namespace Dynamo.LintingViewExtension.Converters +{ + internal class ToStringConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value.ToString(); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/LintingViewExtension/Docs/LinterViewHelpDoc.html b/src/LintingViewExtension/Docs/LinterViewHelpDoc.html new file mode 100644 index 00000000000..79852bb9493 --- /dev/null +++ b/src/LintingViewExtension/Docs/LinterViewHelpDoc.html @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + +

Dynamo Linting

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur at velit quis lectus placerat pretium vel ac metus. + Nullam pretium tellus eget tempus elementum. Suspendisse dictum mauris purus, quis sagittis ligula consequat quis. + Nam eleifend nunc id sem varius tincidunt. Maecenas et convallis ligula, eu euismod nunc. Nam pulvinar aliquet massa quis viverra. + Fusce suscipit et arcu at venenatis. Morbi semper lorem at justo interdum accumsan. Sed rutrum lectus non scelerisque efficitur. + Pellentesque turpis odio, sodales interdum tortor nec, pretium rutrum turpis. +

+ \ No newline at end of file diff --git a/src/LintingViewExtension/LinterView.xaml b/src/LintingViewExtension/LinterView.xaml new file mode 100644 index 00000000000..21c5d7a3038 --- /dev/null +++ b/src/LintingViewExtension/LinterView.xaml @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/LintingViewExtension/LinterView.xaml.cs b/src/LintingViewExtension/LinterView.xaml.cs new file mode 100644 index 00000000000..19b664486fa --- /dev/null +++ b/src/LintingViewExtension/LinterView.xaml.cs @@ -0,0 +1,22 @@ +using System.Windows.Controls; + +namespace Dynamo.LintingViewExtension +{ + /// + /// Interaction logic for LinterView.xaml + /// + public partial class LinterView : UserControl + { + public LinterView() + { + InitializeComponent(); + } + + private void ScrollViewer_PreviewMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e) + { + ScrollViewer scv = (ScrollViewer)sender; + scv.ScrollToVerticalOffset(scv.VerticalOffset - e.Delta); + e.Handled = true; + } + } +} diff --git a/src/LintingViewExtension/LinterViewModel.cs b/src/LintingViewExtension/LinterViewModel.cs new file mode 100644 index 00000000000..265c7fe6e9e --- /dev/null +++ b/src/LintingViewExtension/LinterViewModel.cs @@ -0,0 +1,270 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; +using Dynamo.Core; +using Dynamo.Extensions; +using Dynamo.Graph.Nodes; +using Dynamo.Linting; +using Dynamo.Linting.Rules; +using Dynamo.LintingViewExtension.Controls; +using Dynamo.Models; +using Dynamo.ViewModels; +using Dynamo.Wpf.Extensions; +using Microsoft.Practices.Prism.Commands; + +namespace Dynamo.LintingViewExtension +{ + public class LinterViewModel : NotificationObject + { + private const string NONE_DESCRIPTOR_GUID = "7b75fb44-43fd-4631-a878-29f4d5d8399a"; + + #region Private Fields + private static Uri linterViewHelpLink = new Uri("LintingViewExtension;LinterViewHelpDoc.html", UriKind.Relative); + private LinterExtensionDescriptor activeLinter; + private LinterManager linterManager; + private ViewLoadedParams viewLoadedParams; + private LinterExtensionDescriptor defaultDescriptor; + #endregion + + #region Public Properties + /// + /// Collection of node issues, this is used to bind to the UI + /// + public ObservableCollection NodeIssues { get; set; } + + /// + /// Collection of graph issues, this is used to bind to the UI + /// + public ObservableCollection GraphIssues { get; set; } + + /// + /// Command to select a node in the workspace from the linter view + /// + public DelegateCommand SelectIssueNodeCommand { get; private set; } + + /// + /// Command to open a help page in the documentation browser + /// + public DelegateCommand OpenDocumentationBrowserCommand { get; private set; } + + /// + /// The selected linter, used to bind to the UI and activate a linter on the linter manager + /// + public LinterExtensionDescriptor ActiveLinter + { + get { return activeLinter; } + set + { + if (activeLinter == value) + return; + + activeLinter = value; + linterManager.ActiveLinter = activeLinter; + RaisePropertyChanged(nameof(ActiveLinter)); + } + } + + /// + /// Collection of linters available on the LinterManager + /// + public List AvailableLinters + { + get => linterManager.AvailableLinters.ToList(); + } + + /// + /// The default descriptor is used if no linter is needed for the graph. This is a dummy descriptor that has no extension associated to it. + /// This property returns the descriptor as a list for binding purposes. + /// + public IList DefaultDescriptor { get => new List { defaultDescriptor }; } + + #endregion + + public LinterViewModel(LinterManager linterManager, ViewLoadedParams viewLoadedParams) + { + this.linterManager = linterManager ?? throw new ArgumentNullException(nameof(linterManager)); + this.viewLoadedParams = viewLoadedParams; + InitializeCommands(); + CreateDefaultDummyLinterDescriptor(); + + // If there are no active linter we set it to the default one. + if (this.linterManager.ActiveLinter is null) + { + ActiveLinter = defaultDescriptor; + } + + NodeIssues = new ObservableCollection(); + GraphIssues = new ObservableCollection(); + this.linterManager.RuleEvaluationResults.CollectionChanged += RuleEvaluationResultsCollectionChanged; + } + + #region Private methods + private void InitializeCommands() + { + this.SelectIssueNodeCommand = new DelegateCommand(this.SelectIssueNodeCommandExecute); + this.OpenDocumentationBrowserCommand = new DelegateCommand(this.OpenDocumentationBrowserCommandExecute); + } + + private void OpenDocumentationBrowserCommandExecute() + { + viewLoadedParams.ViewModelCommandExecutive.OpenDocumentationLinkCommand(linterViewHelpLink); + } + + private void SelectIssueNodeCommandExecute(string nodeId) + { + var nodes = viewLoadedParams.CurrentWorkspaceModel.Nodes; + if (nodes is null || !nodes.Any()) { return; } + + var selectedNode = nodes.Where(x => x.GUID.ToString() == nodeId).FirstOrDefault(); + if (selectedNode is null) { return; } + + var cmd = new DynamoModel.SelectInRegionCommand(selectedNode.Rect, false); + this.viewLoadedParams.CommandExecutive.ExecuteCommand(cmd, null, null); + + (this.viewLoadedParams.DynamoWindow.DataContext as DynamoViewModel).FitViewCommand.Execute(null); + } + + private void AddNewNodeIssue(string issueNodeId, string ruleId) + { + var issueNode = NodeFromId(issueNodeId); + + var issue = NodeIssues.Where(x => x.Id == ruleId).FirstOrDefault(); + if (!(issue is null)) + { + if (issue.AffectedNodes.Contains(issueNode)) + return; + + issue.AffectedNodes.Add(issueNode); + return; + } + + var newIssue = new NodeRuleIssue( + ruleId, GetLinterRule(ruleId) as NodeLinterRule); + newIssue.AddAffectedNodes(new List { issueNode }); + + NodeIssues.Add(newIssue); + } + + private void AddNewGraphIssue(List issueNodeIds, string ruleId) + { + var issueNodes = issueNodeIds.Any() ? + issueNodeIds.Select(i => NodeFromId(i)).ToList() : + new List(); + + var issue = GraphIssues.Where(x => x.Id == ruleId).FirstOrDefault(); + if (!(issue is null)) + { + foreach (var issueNode in issueNodes) + { + if (issue.AffectedNodes.Contains(issueNode)) + continue; + + issue.AffectedNodes.Add(issueNode); + return; + } + } + + var newIssue = new GraphRuleIssue( + ruleId, GetLinterRule(ruleId) as GraphLinterRule); + newIssue.AddAffectedNodes(issueNodes); + + GraphIssues.Add(newIssue); + } + + private void RemoveNodeIssue(string issueNodeId, string ruleId) + { + var issueNode = NodeFromId(issueNodeId); + var issue = NodeIssues.Where(x => x.Id == ruleId).FirstOrDefault(); + if (issue is null || + !issue.AffectedNodes.Any(n => n.GUID.ToString() == issueNodeId)) + return; + + issue.AffectedNodes + .Remove(issue.AffectedNodes + .Where(x => x.GUID.ToString() == issueNodeId) + .FirstOrDefault()); + + if (issue.AffectedNodes.Count == 0) + NodeIssues.Remove(issue); + } + + private void RemoveGraphIssue(string ruleId) + { + var issue = GraphIssues.Where(x => x.Id == ruleId).FirstOrDefault(); + if (issue is null) + return; + + GraphIssues.Remove(issue); + } + + private LinterRule GetLinterRule(string id) + { + if(linterManager.TryGetLinterExtension(ActiveLinter, out LinterExtensionBase linterExt)) + { + return linterExt. + LinterRules. + Where(x => x.Id == id). + FirstOrDefault(); + } + + return null; + } + + private void RuleEvaluationResultsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + foreach (var item in e.NewItems) + { + if (item is NodeRuleEvaluationResult nodeRuleEvaluationResult) + AddNewNodeIssue(nodeRuleEvaluationResult.NodeId, nodeRuleEvaluationResult.RuleId); + else if (item is GraphRuleEvaluationResult graphRuleEvaluationResult) + AddNewGraphIssue(graphRuleEvaluationResult.NodeIds.ToList(), graphRuleEvaluationResult.RuleId); + } + return; + + case NotifyCollectionChangedAction.Remove: + foreach (var item in e.OldItems) + { + if (item is NodeRuleEvaluationResult nodeRuleEvaluationResult) + RemoveNodeIssue(nodeRuleEvaluationResult.NodeId, nodeRuleEvaluationResult.RuleId); + else if (item is GraphRuleEvaluationResult graphRuleEvaluationResult) + RemoveGraphIssue(graphRuleEvaluationResult.RuleId); + } + return; + + case NotifyCollectionChangedAction.Reset: + this.GraphIssues.Clear(); + this.NodeIssues.Clear(); + return; + + default: + break; + } + } + + private NodeModel NodeFromId(string nodeId) + { + if (nodeId is null) + { + throw new ArgumentNullException(nameof(nodeId)); + } + + var node = viewLoadedParams.CurrentWorkspaceModel + .Nodes + .Where(n => n.GUID.ToString() == nodeId) + .FirstOrDefault(); + + return node; + } + + private void CreateDefaultDummyLinterDescriptor() + { + defaultDescriptor = new LinterExtensionDescriptor(NONE_DESCRIPTOR_GUID, Properties.Resources.NoneLinterDescriptorName); + } + #endregion + } +} diff --git a/src/LintingViewExtension/Linter_ViewExtensionDefinition.xml b/src/LintingViewExtension/Linter_ViewExtensionDefinition.xml new file mode 100644 index 00000000000..4543155ab0d --- /dev/null +++ b/src/LintingViewExtension/Linter_ViewExtensionDefinition.xml @@ -0,0 +1,5 @@ + + ..\LintingViewExtension.dll + Dynamo.LintingViewExtension.LintingViewExtension + False + \ No newline at end of file diff --git a/src/LintingViewExtension/LintingViewExtension.cs b/src/LintingViewExtension/LintingViewExtension.cs new file mode 100644 index 00000000000..a30660f6b99 --- /dev/null +++ b/src/LintingViewExtension/LintingViewExtension.cs @@ -0,0 +1,77 @@ +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using Dynamo.Extensions; +using Dynamo.Graph; +using Dynamo.Linting; +using Dynamo.LintingViewExtension.Properties; +using Dynamo.ViewModels; +using Dynamo.Wpf.Extensions; + +namespace Dynamo.LintingViewExtension +{ + public class LintingViewExtension : ViewExtensionBase + { + private const string EXTENSION_NAME = "Graph Status"; + private const string EXTENSION_GUID = "3467481b-d20d-4918-a454-bf19fc5c25d7"; + + private LinterManager linterManager; + private ViewLoadedParams viewLoadedParamsReference; + private MenuItem linterMenuItem; + private LinterViewModel linterViewModel; + private LinterView linterView; + + + public override string UniqueId { get { return EXTENSION_GUID; } } + + public override string Name { get { return EXTENSION_NAME; } } + + public override void Startup(ViewStartupParams viewStartupParams) + { + // Do nothing for now + } + + public override void Loaded(ViewLoadedParams viewLoadedParams) + { + this.linterManager = (viewLoadedParams.DynamoWindow.DataContext as DynamoViewModel).Model.LinterManager; + this.viewLoadedParamsReference = viewLoadedParams; + this.linterViewModel = new LinterViewModel(linterManager, viewLoadedParamsReference); + this.linterView = new LinterView() { DataContext = linterViewModel }; + + // Add a button to Dynamo View menu to manually show the window + this.linterMenuItem = new MenuItem { Header = Resources.MenuItemText, IsCheckable = true }; + this.linterMenuItem.Checked += MenuItemCheckHandler; + this.linterMenuItem.Unchecked += MenuItemUnCheckedHandler; + this.viewLoadedParamsReference.AddExtensionMenuItem(this.linterMenuItem); + } + + public override void Shutdown() + { + // Do nothing for now + } + + public override void Dispose() + { + this.linterMenuItem.Checked -= MenuItemCheckHandler; + this.linterMenuItem.Unchecked -= MenuItemUnCheckedHandler; + } + + public override void Closed() + { + if (this.linterMenuItem is null) + return; + + this.linterMenuItem.IsChecked = false; + } + private void MenuItemUnCheckedHandler(object sender, RoutedEventArgs e) + { + viewLoadedParamsReference.CloseExtensioninInSideBar(this); + } + + private void MenuItemCheckHandler(object sender, RoutedEventArgs e) + { + this.viewLoadedParamsReference?.AddToExtensionsSideBar(this, this.linterView); + } + } +} diff --git a/src/LintingViewExtension/LintingViewExtension.csproj b/src/LintingViewExtension/LintingViewExtension.csproj new file mode 100644 index 00000000000..fc7c42e5cf8 --- /dev/null +++ b/src/LintingViewExtension/LintingViewExtension.csproj @@ -0,0 +1,148 @@ + + + + + + Debug + AnyCPU + {C86F9058-229D-40A9-95D5-D6F081AA9230} + Library + Properties + Dynamo.LintingViewExtension + LintingViewExtension + v4.8 + 512 + true + + + true + full + false + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\FontAwesome.WPF.4.7.0.9\lib\net40\FontAwesome.WPF.dll + False + + + False + ..\..\extern\prism\Microsoft.Practices.Prism.dll + False + + + + + + + + + + + + + + + + + + Properties\AssemblySharedInfo.cs + + + + IssueGroup.xaml + + + + + + + + + LinterView.xaml + + + + + + True + True + Resources.resx + + + + True + True + Resources.en-US.resx + + + + + {51bb6014-43f7-4f31-b8d3-e3c37ebedaf4} + DynamoCoreWpf + False + + + {7858FA8C-475F-4B8E-B468-1F8200778CF8} + DynamoCore + False + + + {B5F435CB-0D8A-40B1-A4F7-5ECB3CE792A9} + DynamoUtilities + False + + + {7a9e0314-966f-4584-baa3-7339cbb849d1} + ProtoCore + False + + + + + PublicResXFileCodeGenerator + Resources.en-US.Designer.cs + + + PublicResXFileCodeGenerator + Resources.Designer.cs + + + + + Designer + MSBuild:Compile + + + Designer + XamlIntelliSenseFileGenerator + + + Designer + MSBuild:Compile + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/LintingViewExtension/Properties/AssemblyInfo.cs b/src/LintingViewExtension/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..d525563f0c7 --- /dev/null +++ b/src/LintingViewExtension/Properties/AssemblyInfo.cs @@ -0,0 +1,13 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("LintingViewExtension")] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c86f9058-229d-40a9-95d5-d6f081aa9230")] + + diff --git a/src/LintingViewExtension/Properties/Resources.Designer.cs b/src/LintingViewExtension/Properties/Resources.Designer.cs new file mode 100644 index 00000000000..e25fcfabd70 --- /dev/null +++ b/src/LintingViewExtension/Properties/Resources.Designer.cs @@ -0,0 +1,90 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Dynamo.LintingViewExtension.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Dynamo.LintingViewExtension.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to What is a Graph Type?. + /// + public static string GraphTypeHelpTooltip { + get { + return ResourceManager.GetString("GraphTypeHelpTooltip", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Graph Status. + /// + public static string MenuItemText { + get { + return ResourceManager.GetString("MenuItemText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to None. + /// + public static string NoneLinterDescriptorName { + get { + return ResourceManager.GetString("NoneLinterDescriptorName", resourceCulture); + } + } + } +} diff --git a/src/LintingViewExtension/Properties/Resources.en-US.Designer.cs b/src/LintingViewExtension/Properties/Resources.en-US.Designer.cs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/LintingViewExtension/Properties/Resources.en-US.resx b/src/LintingViewExtension/Properties/Resources.en-US.resx new file mode 100644 index 00000000000..8815dfc5508 --- /dev/null +++ b/src/LintingViewExtension/Properties/Resources.en-US.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + What is a Graph Type? + + + Graph Status + + + None + + \ No newline at end of file diff --git a/src/LintingViewExtension/Properties/Resources.resx b/src/LintingViewExtension/Properties/Resources.resx new file mode 100644 index 00000000000..8815dfc5508 --- /dev/null +++ b/src/LintingViewExtension/Properties/Resources.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + What is a Graph Type? + + + Graph Status + + + None + + \ No newline at end of file diff --git a/src/LintingViewExtension/Styles.xaml b/src/LintingViewExtension/Styles.xaml new file mode 100644 index 00000000000..5216fd19546 --- /dev/null +++ b/src/LintingViewExtension/Styles.xaml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + +