From 6aa533d49295597fa8a61c6bfa7c6fe6904e5cbc Mon Sep 17 00:00:00 2001 From: reddyashish <43763136+reddyashish@users.noreply.github.com> Date: Wed, 15 May 2024 11:03:20 -0700 Subject: [PATCH 01/10] DYN-6857: Repurposing homepage preferences changes(#15139) (#15215) Co-authored-by: Deyan Nenov --- .../Configuration/PreferenceSettings.cs | 15 +++ src/DynamoCore/PublicAPI.Unshipped.txt | 6 +- src/DynamoCoreWpf/PublicAPI.Unshipped.txt | 3 +- .../Views/HomePage/HomePage.xaml.cs | 99 ++++++++++++++----- .../Configuration/PreferenceSettingsTests.cs | 22 +++++ test/DynamoCoreWpfTests/HomePageTests.cs | 30 ++++++ test/settings/DynamoSettings-NewSettings.xml | 1 + 7 files changed, 150 insertions(+), 26 deletions(-) diff --git a/src/DynamoCore/Configuration/PreferenceSettings.cs b/src/DynamoCore/Configuration/PreferenceSettings.cs index b004a7937bc..bb493588e62 100644 --- a/src/DynamoCore/Configuration/PreferenceSettings.cs +++ b/src/DynamoCore/Configuration/PreferenceSettings.cs @@ -395,6 +395,21 @@ public bool IsBackgroundPreviewActive /// public bool UseHardwareAcceleration { get; set; } + /// + /// Persistence for Dynamo HomePage + /// + [XmlIgnore] + internal Dictionary HomePageSettings { get; set; } + + /// + /// A helper intermediary string to allow the serialization of the HomePageSettings dictionary + /// + public string HomePageSettingsSerialized + { + get => Newtonsoft.Json.JsonConvert.SerializeObject(HomePageSettings); + set => HomePageSettings = Newtonsoft.Json.JsonConvert.DeserializeObject>(value); + } + #endregion #region Dynamo application settings diff --git a/src/DynamoCore/PublicAPI.Unshipped.txt b/src/DynamoCore/PublicAPI.Unshipped.txt index 6acf7363347..9866b58ae7b 100644 --- a/src/DynamoCore/PublicAPI.Unshipped.txt +++ b/src/DynamoCore/PublicAPI.Unshipped.txt @@ -1,4 +1,4 @@ -abstract Dynamo.Extensions.LinterExtensionBase.Name.get -> string +abstract Dynamo.Extensions.LinterExtensionBase.Name.get -> string abstract Dynamo.Extensions.LinterExtensionBase.Shutdown() -> void abstract Dynamo.Extensions.LinterExtensionBase.UniqueId.get -> string abstract Dynamo.Graph.ModelBase.DeserializeCore(System.Xml.XmlElement nodeElement, Dynamo.Graph.SaveContext context) -> void @@ -171,6 +171,8 @@ Dynamo.Configuration.PreferenceSettings.HideAutocompleteMethodOptions.get -> boo Dynamo.Configuration.PreferenceSettings.HideAutocompleteMethodOptions.set -> void Dynamo.Configuration.PreferenceSettings.HideNodesBelowSpecificConfidenceLevel.get -> bool Dynamo.Configuration.PreferenceSettings.HideNodesBelowSpecificConfidenceLevel.set -> void +Dynamo.Configuration.PreferenceSettings.HomePageSettingsSerialized.get -> string +Dynamo.Configuration.PreferenceSettings.HomePageSettingsSerialized.set -> void Dynamo.Configuration.PreferenceSettings.IronPythonResolveTargetVersion.get -> string Dynamo.Configuration.PreferenceSettings.IronPythonResolveTargetVersion.set -> void Dynamo.Configuration.PreferenceSettings.IsADPAnalyticsReportingApproved.get -> bool @@ -3133,4 +3135,4 @@ virtual Dynamo.Search.SearchElements.NodeSearchElement.FullName.get -> string virtual Dynamo.Search.SearchElements.NodeSearchElement.GenerateInputParameters() -> System.Collections.Generic.IEnumerable> virtual Dynamo.Search.SearchElements.NodeSearchElement.GenerateOutputParameters() -> System.Collections.Generic.IEnumerable virtual Dynamo.Search.SearchElements.NodeSearchElement.OnItemProduced(Dynamo.Graph.Nodes.NodeModel obj) -> void -virtual Dynamo.Search.SearchElements.SearchElementBase.CreationName.get -> string \ No newline at end of file +virtual Dynamo.Search.SearchElements.SearchElementBase.CreationName.get -> string diff --git a/src/DynamoCoreWpf/PublicAPI.Unshipped.txt b/src/DynamoCoreWpf/PublicAPI.Unshipped.txt index 641cd6755a4..6ae526a235b 100644 --- a/src/DynamoCoreWpf/PublicAPI.Unshipped.txt +++ b/src/DynamoCoreWpf/PublicAPI.Unshipped.txt @@ -1576,7 +1576,8 @@ Dynamo.UI.Views.ScriptHomeObject.NewCustomNodeWorkspace() -> void Dynamo.UI.Views.ScriptHomeObject.NewWorkspace() -> void Dynamo.UI.Views.ScriptHomeObject.OpenFile(string path) -> void Dynamo.UI.Views.ScriptHomeObject.OpenWorkspace() -> void -Dynamo.UI.Views.ScriptHomeObject.ScriptHomeObject(System.Action requestOpenFile, System.Action requestNewWorkspace, System.Action requestOpenWorkspace, System.Action requestNewCustomNodeWorkspace, System.Action requestApplicationLoaded, System.Action requestShowGuidedTour, System.Action requestShowSampleFilesInFolder, System.Action requestShowBackupFilesInFolder, System.Action requestShowTemplate) -> void +Dynamo.UI.Views.ScriptHomeObject.SaveSettings(string settings) -> void +Dynamo.UI.Views.ScriptHomeObject.ScriptHomeObject(System.Action requestOpenFile, System.Action requestNewWorkspace, System.Action requestOpenWorkspace, System.Action requestNewCustomNodeWorkspace, System.Action requestApplicationLoaded, System.Action requestShowGuidedTour, System.Action requestShowSampleFilesInFolder, System.Action requestShowBackupFilesInFolder, System.Action requestShowTemplate, System.Action requestSaveSettings) -> void Dynamo.UI.Views.ScriptHomeObject.ShowBackupFilesInFolder() -> void Dynamo.UI.Views.ScriptHomeObject.ShowSampleFilesInFolder() -> void Dynamo.UI.Views.ScriptHomeObject.ShowTempalte() -> void diff --git a/src/DynamoCoreWpf/Views/HomePage/HomePage.xaml.cs b/src/DynamoCoreWpf/Views/HomePage/HomePage.xaml.cs index 84685fe5def..f0cb415207a 100644 --- a/src/DynamoCoreWpf/Views/HomePage/HomePage.xaml.cs +++ b/src/DynamoCoreWpf/Views/HomePage/HomePage.xaml.cs @@ -53,6 +53,7 @@ public partial class HomePage : UserControl, IDisposable internal Action RequestShowSampleFilesInFolder; internal Action RequestShowBackupFilesInFolder; internal Action RequestShowTemplate; + internal Action RequestSaveSettings; internal List GuidedTourItems; @@ -83,6 +84,7 @@ public HomePage() RequestShowBackupFilesInFolder = ShowBackupFilesInFolder; RequestShowTemplate = ShowTemplate; RequestApplicationLoaded = ApplicationLoaded; + RequestSaveSettings = SaveSettings; DataContextChanged += OnDataContextChanged; @@ -196,7 +198,8 @@ private async void UserControl_Loaded(object sender, System.Windows.RoutedEventA RequestShowGuidedTour, RequestShowSampleFilesInFolder, RequestShowBackupFilesInFolder, - RequestShowTemplate)); + RequestShowTemplate, + RequestSaveSettings)); } catch (ObjectDisposedException ex) { @@ -241,29 +244,14 @@ internal bool ProcessUri(string uri) return false; } - internal async void LoadingDone() + internal void LoadingDone() { - SendGuidesData(); - if (startPage == null) { return; } + SendGuidesData(); SendSamplesData(); - - var recentFiles = startPage.RecentFiles; - if (recentFiles == null || !recentFiles.Any()) { return; } - - // Subscribe to the DynamoViewModel refresh file changed event in order to refresh the Recent File metadata - // There is no way to track if the metadata has changed specifically, so we refresh in any change to the recent files - startPage.DynamoViewModel.RecentFiles.CollectionChanged += RecentFiles_CollectionChanged; - - LoadGraphs(recentFiles); - - var userLocale = CultureInfo.CurrentCulture.Name; - - if (dynWebView?.CoreWebView2 != null) - { - await dynWebView.CoreWebView2.ExecuteScriptAsync(@$"window.setLocale('{userLocale}');"); - } + SendRecentGraphsData(); + SetLocale(); } private void RecentFiles_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) @@ -301,6 +289,37 @@ private async void SendSamplesData() } } + private async void SendRecentGraphsData() + { + // Send user preferences + if (dynWebView?.CoreWebView2 != null) + { + if (startPage.DynamoViewModel.PreferenceSettings.HomePageSettings != null) + { + var settingsJson = Newtonsoft.Json.JsonConvert.SerializeObject(startPage.DynamoViewModel.PreferenceSettings.HomePageSettings); + settingsJson = System.Web.HttpUtility.JavaScriptStringEncode(settingsJson); + + await dynWebView.CoreWebView2.ExecuteScriptAsync(@$"window.setHomePageSettings('{settingsJson}');"); + } + } + + // Load recent files + var recentFiles = startPage.RecentFiles; + if (recentFiles != null && recentFiles.Any()) + { + LoadGraphs(recentFiles); + } + } + + private async void SetLocale() + { + var userLocale = CultureInfo.CurrentCulture.Name; + + if (dynWebView?.CoreWebView2 != null) + { + await dynWebView.CoreWebView2.ExecuteScriptAsync(@$"window.setLocale('{userLocale}');"); + } + } /// /// Sends guided tour data to react app @@ -414,6 +433,34 @@ internal void StartGuidedTour(string path) ShowGuidedTour(path); } + internal void SaveSettings(string settingsJson) + { + if (!string.IsNullOrEmpty(settingsJson) && this.startPage != null) + { + var settingsDict = Newtonsoft.Json.JsonConvert.DeserializeObject>(settingsJson); + + // If the HomePageSettings have not been previously created, initialize it now + if (startPage.DynamoViewModel.PreferenceSettings.HomePageSettings == null) + { + startPage.DynamoViewModel.PreferenceSettings.HomePageSettings = new Dictionary(); + } + + // Change existing values, or add new ones + foreach (var setting in settingsDict) + { + if (startPage.DynamoViewModel.PreferenceSettings.HomePageSettings.ContainsKey(setting.Key) + && startPage.DynamoViewModel.PreferenceSettings.HomePageSettings[setting.Key] != setting.Value) + { + startPage.DynamoViewModel.PreferenceSettings.HomePageSettings[setting.Key] = setting.Value; + } + else + { + startPage.DynamoViewModel.PreferenceSettings.HomePageSettings.Add(setting.Key, setting.Value); + } + } + } + } + internal void NewWorkspace() { this.startPage?.DynamoViewModel?.NewHomeWorkspaceCommand.Execute(null); @@ -528,6 +575,7 @@ public class ScriptHomeObject readonly Action RequestShowSampleFilesInFolder; readonly Action RequestShowBackupFilesInFolder; readonly Action RequestShowTemplate; + readonly Action RequestSaveSettings; public ScriptHomeObject(Action requestOpenFile, Action requestNewWorkspace, @@ -537,7 +585,8 @@ public ScriptHomeObject(Action requestOpenFile, Action requestShowGuidedTour, Action requestShowSampleFilesInFolder, Action requestShowBackupFilesInFolder, - Action requestShowTemplate) + Action requestShowTemplate, + Action requestSaveSettings) { RequestOpenFile = requestOpenFile; RequestNewWorkspace = requestNewWorkspace; @@ -548,7 +597,7 @@ public ScriptHomeObject(Action requestOpenFile, RequestShowSampleFilesInFolder = requestShowSampleFilesInFolder; RequestShowBackupFilesInFolder = requestShowBackupFilesInFolder; RequestShowTemplate = requestShowTemplate; - + RequestSaveSettings = requestSaveSettings; } [DynamoJSInvokable] public void OpenFile(string path) @@ -596,7 +645,11 @@ public void ApplicationLoaded() { RequestApplicationLoaded(); } - + [DynamoJSInvokable] + public void SaveSettings(string settings) + { + RequestSaveSettings(settings); + } } public enum GuidedTourType diff --git a/test/DynamoCoreTests/Configuration/PreferenceSettingsTests.cs b/test/DynamoCoreTests/Configuration/PreferenceSettingsTests.cs index 9d0ae696e33..e92b1e4c408 100644 --- a/test/DynamoCoreTests/Configuration/PreferenceSettingsTests.cs +++ b/test/DynamoCoreTests/Configuration/PreferenceSettingsTests.cs @@ -479,5 +479,27 @@ public void TestSanitizeValues() Assert.IsTrue(allTheGroupStylesHaveAValidFontSize, $"All the GroupStyles have a valid Font size : {allTheGroupStylesHaveAValidFontSize}"); } + + [Test] + [Category("UnitTests")] + public void TestSerializingHomePageSettings() + { + string tempPath = System.IO.Path.GetTempPath(); + tempPath = Path.Combine(tempPath, "homePagePreference.xml"); + + PreferenceSettings settings = new PreferenceSettings(); + + // Assert defaults + Assert.AreEqual(settings.HomePageSettings, null); + Assert.AreEqual(settings.HomePageSettingsSerialized, "null"); + + settings.HomePageSettings = new Dictionary { { "greeting", "Hello World" } }; + + // Save + settings.Save(tempPath); + settings = PreferenceSettings.Load(tempPath); + + Assert.AreEqual(settings.HomePageSettings["greeting"], "Hello World"); + } } } diff --git a/test/DynamoCoreWpfTests/HomePageTests.cs b/test/DynamoCoreWpfTests/HomePageTests.cs index 079f1af90b2..072a8da8983 100644 --- a/test/DynamoCoreWpfTests/HomePageTests.cs +++ b/test/DynamoCoreWpfTests/HomePageTests.cs @@ -53,6 +53,36 @@ public void ActionDelegates_ShouldBeProperlySetAfterConstruction() Assert.IsNotNull(homePage.RequestShowBackupFilesInFolder); Assert.IsNotNull(homePage.RequestShowTemplate); } + + [Test] + public void HomePage_NewSettingsAreAddedCorrectly() + { + // Arrange + var vm = View.DataContext as DynamoViewModel; + var startPage = new StartPageViewModel(vm, true); + var preferences = startPage.DynamoViewModel.PreferenceSettings; + var homePage = new HomePage(); + + homePage.DataContext = startPage; + + Assert.IsNull(preferences.HomePageSettings); + + // Act + var pair1 = @"{""Name"": ""Alice""}"; + homePage.SaveSettings(pair1); + + // Assert + Assert.IsNotNull(preferences.HomePageSettings); + Assert.AreEqual(preferences.HomePageSettings.Count, 1); + + // Act + var pair2 = @"{""Number"": 12 }"; + homePage.SaveSettings(pair2); + + // Assert + Assert.AreEqual(preferences.HomePageSettings.Count, 2); + } + #endregion #region integration tests diff --git a/test/settings/DynamoSettings-NewSettings.xml b/test/settings/DynamoSettings-NewSettings.xml index 49a99913d1d..63940d89790 100644 --- a/test/settings/DynamoSettings-NewSettings.xml +++ b/test/settings/DynamoSettings-NewSettings.xml @@ -32,6 +32,7 @@ 1936 1056 false + {"recentPageViewMode":"grid","samplesViewMode":"list"} f4 12 From 49a3f7c3025d350405d9af28b0e5c3ab8ad59b3a Mon Sep 17 00:00:00 2001 From: Jorgen Dahl Date: Wed, 15 May 2024 14:49:18 -0400 Subject: [PATCH 02/10] DYN-6984: Supress build warnings in CI/CD builds. (#15219) --- src/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/build.xml b/src/build.xml index 44c8a7e92a0..ee0ce629f4f 100644 --- a/src/build.xml +++ b/src/build.xml @@ -12,7 +12,7 @@ - Configuration=Release;Platform=$(Platform);DotNet=$(DotNet) + Configuration=Release;Platform=$(Platform);DotNet=$(DotNet);WarningLevel=0 From dfa5af64fc4a8ef5983d24b1d6af498bc5f3f1a0 Mon Sep 17 00:00:00 2001 From: "Aaron (Qilong)" <173288704@qq.com> Date: Wed, 15 May 2024 21:01:29 -0400 Subject: [PATCH 03/10] DYN-6839 Dynamo Home Analytics (#15218) --- .../Views/HomePage/HomePage.xaml.cs | 32 +++++++++++++------ src/NodeServices/IAnalyticsClient.cs | 10 +++++- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/DynamoCoreWpf/Views/HomePage/HomePage.xaml.cs b/src/DynamoCoreWpf/Views/HomePage/HomePage.xaml.cs index f0cb415207a..cace7300fff 100644 --- a/src/DynamoCoreWpf/Views/HomePage/HomePage.xaml.cs +++ b/src/DynamoCoreWpf/Views/HomePage/HomePage.xaml.cs @@ -67,11 +67,12 @@ public HomePage() InitializeComponent(); InitializeGuideTourItems(); - dynWebView = new DynamoWebView2(); + dynWebView = new DynamoWebView2 + { + Margin = new System.Windows.Thickness(0), // Set margin to zero + ZoomFactor = 1.0 // Set zoom factor (optional) + }; - dynWebView.Margin = new System.Windows.Thickness(0); // Set margin to zero - dynWebView.ZoomFactor = 1.0; // Set zoom factor (optional) - HostGrid.Children.Add(dynWebView); // Bind event handlers @@ -82,7 +83,7 @@ public HomePage() RequestNewCustomNodeWorkspace = NewCustomNodeWorkspace; RequestShowSampleFilesInFolder = ShowSampleFilesInFolder; RequestShowBackupFilesInFolder = ShowBackupFilesInFolder; - RequestShowTemplate = ShowTemplate; + RequestShowTemplate = OpenTemplate; RequestApplicationLoaded = ApplicationLoaded; RequestSaveSettings = SaveSettings; @@ -129,7 +130,7 @@ private void DynamoViewModel_PropertyChanged(object sender, System.ComponentMode /// This is used before DynamoModel initialization specifically to get user data dir /// /// - private string GetUserDirectory() + private static string GetUserDirectory() { var version = AssemblyHelper.GetDynamoVersion(); @@ -233,12 +234,14 @@ internal bool ProcessUri(string uri) if (filePath.EndsWith(".dyn") || filePath.EndsWith(".dyf")) { OpenFile(filePath); + Logging.Analytics.TrackEvent(Logging.Actions.Open, Logging.Categories.DynamoHomeOperations, "Workspace"); return true; } } else { Process.Start(new ProcessStartInfo(uri) { UseShellExecute = true }); + Logging.Analytics.TrackEvent(Logging.Actions.Open, Logging.Categories.DynamoHomeOperations, "Hyper Link: "+ uri); } return false; @@ -431,6 +434,8 @@ internal void StartGuidedTour(string path) } ShowGuidedTour(path); + Logging.Analytics.TrackEvent(Logging.Actions.Start, Logging.Categories.DynamoHomeOperations, "Guided Tour: " + path); + } internal void SaveSettings(string settingsJson) @@ -464,6 +469,7 @@ internal void SaveSettings(string settingsJson) internal void NewWorkspace() { this.startPage?.DynamoViewModel?.NewHomeWorkspaceCommand.Execute(null); + Logging.Analytics.TrackEvent(Logging.Actions.New, Logging.Categories.DynamoHomeOperations, "Workspace"); } internal void OpenWorkspace() @@ -475,6 +481,7 @@ internal void OpenWorkspace() } this.startPage?.DynamoViewModel?.ShowOpenDialogAndOpenResultCommand.Execute(null); + Logging.Analytics.TrackEvent(Logging.Actions.Open, Logging.Categories.DynamoHomeOperations, "Workspace"); } internal void NewCustomNodeWorkspace() @@ -486,6 +493,7 @@ internal void NewCustomNodeWorkspace() } this.startPage?.DynamoViewModel?.ShowNewFunctionDialogCommand.Execute(null); + Logging.Analytics.TrackEvent(Logging.Actions.New, Logging.Categories.DynamoHomeOperations, "Custom Node Workspace"); } internal void ShowSampleFilesInFolder() @@ -500,6 +508,8 @@ internal void ShowSampleFilesInFolder() Process.Start(new ProcessStartInfo("explorer.exe", "/select," + this.startPage.SampleFolderPath) { UseShellExecute = true }); + Logging.Analytics.TrackEvent(Logging.Actions.Show, Logging.Categories.DynamoHomeOperations, "Sample Files"); + } internal void ShowBackupFilesInFolder() @@ -513,9 +523,10 @@ internal void ShowBackupFilesInFolder() Process.Start(new ProcessStartInfo("explorer.exe", this.startPage.DynamoViewModel.Model.PathManager.BackupDirectory) { UseShellExecute = true }); + Logging.Analytics.TrackEvent(Logging.Actions.Show, Logging.Categories.DynamoHomeOperations, "Backup Files"); } - internal void ShowTemplate() + internal void OpenTemplate() { if (DynamoModel.IsTestMode) { @@ -524,12 +535,15 @@ internal void ShowTemplate() } // Equivalent to CommandParameter="Template" - this.startPage?.DynamoViewModel?.ShowOpenTemplateDialogCommand.Execute("Template"); + this.startPage?.DynamoViewModel?.ShowOpenTemplateDialogCommand.Execute("Template"); + Logging.Analytics.TrackEvent(Logging.Actions.Open, Logging.Categories.DynamoHomeOperations, "Template"); } internal void ApplicationLoaded() { - LoadingDone(); + LoadingDone(); + Logging.Analytics.TrackEvent(Logging.Actions.Load, Logging.Categories.DynamoHomeOperations); + } #endregion diff --git a/src/NodeServices/IAnalyticsClient.cs b/src/NodeServices/IAnalyticsClient.cs index 6f5e4081a26..67e979a5182 100644 --- a/src/NodeServices/IAnalyticsClient.cs +++ b/src/NodeServices/IAnalyticsClient.cs @@ -9,6 +9,9 @@ namespace Dynamo.Logging /// public enum Categories { + /// XXXOperations usually means actions from Dynamo users + /// v.s. XXX usually means actions from the Dynamo component itself + /// /// Events Category related to application lifecycle /// @@ -142,7 +145,12 @@ public enum Categories /// /// Events Category related to DynamoMLDataPipeline /// - DynamoMLDataPipelineOperations + DynamoMLDataPipelineOperations, + + /// + /// Events Category related to DynamoHome + /// + DynamoHomeOperations } /// From 39690f71fa8bae85e45f796ec407f94f9d9d40b9 Mon Sep 17 00:00:00 2001 From: AlexisErazoGlobant <168037709+AlexisErazoGlobant@users.noreply.github.com> Date: Thu, 16 May 2024 08:36:12 -0500 Subject: [PATCH 04/10] DYN-6975: Test solution for TestImportDefaultScaleFactor Test. (#15222) Co-authored-by: Alexis Erazo --- test/DynamoCoreWpfTests/CoreUITests.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/DynamoCoreWpfTests/CoreUITests.cs b/test/DynamoCoreWpfTests/CoreUITests.cs index 38fc861882c..7bdfeb586a4 100644 --- a/test/DynamoCoreWpfTests/CoreUITests.cs +++ b/test/DynamoCoreWpfTests/CoreUITests.cs @@ -729,8 +729,7 @@ public void PreferenceSettingsConnectorTypeRevertsToBezier() [Test] public void TestImportDefaultScaleFactor() { - string settingDirectory = Path.Combine(GetTestDirectory(ExecutingDirectory), "settings"); - string newSettingslFilePath = Path.Combine(settingDirectory, "DynamoSettings-NewSettings.xml"); + string newSettingslFilePath = Path.Combine(TempFolder, "DynamoSettings-NewSettings.xml"); var defaultSettings = new PreferenceSettings(); defaultSettings.DefaultScaleFactor = GeometryScalingOptions.ConvertUIToScaleFactor((int)GeometryScaleSize.ExtraLarge); From 3df920e5c8698fa887e74ff3cd8595dbdfef1732 Mon Sep 17 00:00:00 2001 From: Deyan Nenov Date: Fri, 17 May 2024 15:14:46 +0100 Subject: [PATCH 05/10] DYN-6857 homepage settings refactor (#15223) --- .../Configuration/PreferenceSettings.cs | 18 ++----- src/DynamoCore/PublicAPI.Unshipped.txt | 4 +- .../Views/HomePage/HomePage.xaml.cs | 53 +++++++++++++++---- .../Configuration/PreferenceSettingsTests.cs | 7 ++- test/DynamoCoreWpfTests/HomePageTests.cs | 2 +- test/settings/DynamoSettings-NewSettings.xml | 5 +- 6 files changed, 57 insertions(+), 32 deletions(-) diff --git a/src/DynamoCore/Configuration/PreferenceSettings.cs b/src/DynamoCore/Configuration/PreferenceSettings.cs index bb493588e62..f341f5dc387 100644 --- a/src/DynamoCore/Configuration/PreferenceSettings.cs +++ b/src/DynamoCore/Configuration/PreferenceSettings.cs @@ -397,19 +397,8 @@ public bool IsBackgroundPreviewActive /// /// Persistence for Dynamo HomePage - /// - [XmlIgnore] - internal Dictionary HomePageSettings { get; set; } - - /// - /// A helper intermediary string to allow the serialization of the HomePageSettings dictionary - /// - public string HomePageSettingsSerialized - { - get => Newtonsoft.Json.JsonConvert.SerializeObject(HomePageSettings); - set => HomePageSettings = Newtonsoft.Json.JsonConvert.DeserializeObject>(value); - } - + /// + public List HomePageSettings { get; set; } #endregion #region Dynamo application settings @@ -997,6 +986,7 @@ public PreferenceSettings() backupLocation = string.Empty; GraphChecksumItemsList = new List(); isMLAutocompleteTOUApproved = true; + HomePageSettings = new List(); } /// @@ -1114,7 +1104,7 @@ public static PreferenceSettings LoadContent(string content) return new PreferenceSettings() { isCreatedFromValidFile = false }; } } - + settings.CustomPackageFolders = settings.CustomPackageFolders.Distinct().ToList(); settings.GroupStyleItemsList = settings.GroupStyleItemsList.GroupBy(entry => entry.Name).Select(result => result.First()).ToList(); MigrateStdLibTokenToBuiltInToken(settings); diff --git a/src/DynamoCore/PublicAPI.Unshipped.txt b/src/DynamoCore/PublicAPI.Unshipped.txt index 9866b58ae7b..7a74b2752ec 100644 --- a/src/DynamoCore/PublicAPI.Unshipped.txt +++ b/src/DynamoCore/PublicAPI.Unshipped.txt @@ -171,8 +171,8 @@ Dynamo.Configuration.PreferenceSettings.HideAutocompleteMethodOptions.get -> boo Dynamo.Configuration.PreferenceSettings.HideAutocompleteMethodOptions.set -> void Dynamo.Configuration.PreferenceSettings.HideNodesBelowSpecificConfidenceLevel.get -> bool Dynamo.Configuration.PreferenceSettings.HideNodesBelowSpecificConfidenceLevel.set -> void -Dynamo.Configuration.PreferenceSettings.HomePageSettingsSerialized.get -> string -Dynamo.Configuration.PreferenceSettings.HomePageSettingsSerialized.set -> void +Dynamo.Configuration.PreferenceSettings.HomePageSettings.get -> System.Collections.Generic.List +Dynamo.Configuration.PreferenceSettings.HomePageSettings.set -> void Dynamo.Configuration.PreferenceSettings.IronPythonResolveTargetVersion.get -> string Dynamo.Configuration.PreferenceSettings.IronPythonResolveTargetVersion.set -> void Dynamo.Configuration.PreferenceSettings.IsADPAnalyticsReportingApproved.get -> bool diff --git a/src/DynamoCoreWpf/Views/HomePage/HomePage.xaml.cs b/src/DynamoCoreWpf/Views/HomePage/HomePage.xaml.cs index cace7300fff..40bee217b4a 100644 --- a/src/DynamoCoreWpf/Views/HomePage/HomePage.xaml.cs +++ b/src/DynamoCoreWpf/Views/HomePage/HomePage.xaml.cs @@ -299,7 +299,8 @@ private async void SendRecentGraphsData() { if (startPage.DynamoViewModel.PreferenceSettings.HomePageSettings != null) { - var settingsJson = Newtonsoft.Json.JsonConvert.SerializeObject(startPage.DynamoViewModel.PreferenceSettings.HomePageSettings); + var dict = GetDictFromListStrings(startPage.DynamoViewModel.PreferenceSettings.HomePageSettings); + var settingsJson = Newtonsoft.Json.JsonConvert.SerializeObject(dict); settingsJson = System.Web.HttpUtility.JavaScriptStringEncode(settingsJson); await dynWebView.CoreWebView2.ExecuteScriptAsync(@$"window.setHomePageSettings('{settingsJson}');"); @@ -442,30 +443,62 @@ internal void SaveSettings(string settingsJson) { if (!string.IsNullOrEmpty(settingsJson) && this.startPage != null) { - var settingsDict = Newtonsoft.Json.JsonConvert.DeserializeObject>(settingsJson); + var settingsDict = Newtonsoft.Json.JsonConvert.DeserializeObject>(settingsJson); // If the HomePageSettings have not been previously created, initialize it now if (startPage.DynamoViewModel.PreferenceSettings.HomePageSettings == null) { - startPage.DynamoViewModel.PreferenceSettings.HomePageSettings = new Dictionary(); - } + startPage.DynamoViewModel.PreferenceSettings.HomePageSettings = new List(); + } + + var dict = GetDictFromListStrings(startPage.DynamoViewModel.PreferenceSettings.HomePageSettings); // Change existing values, or add new ones foreach (var setting in settingsDict) { - if (startPage.DynamoViewModel.PreferenceSettings.HomePageSettings.ContainsKey(setting.Key) - && startPage.DynamoViewModel.PreferenceSettings.HomePageSettings[setting.Key] != setting.Value) + if (dict.ContainsKey(setting.Key)) { - startPage.DynamoViewModel.PreferenceSettings.HomePageSettings[setting.Key] = setting.Value; + if (dict[setting.Key] == setting.Value) continue; + dict[setting.Key] = setting.Value; } else { - startPage.DynamoViewModel.PreferenceSettings.HomePageSettings.Add(setting.Key, setting.Value); - } + dict.Add(setting.Key, setting.Value); + } } + + SetListStringsFromDict(dict, startPage.DynamoViewModel.PreferenceSettings.HomePageSettings); + } + } + + private void SetListStringsFromDict(Dictionary dict, List homePageSettings) + { + homePageSettings.Clear(); + foreach (var kvp in dict) + { + homePageSettings.Add($"{kvp.Key}:{kvp.Value}"); + } + } + + private Dictionary GetDictFromListStrings(List homePageSettings) + { + var dict = new Dictionary(); + if (!homePageSettings.Any()) return dict; + + try + { + dict = homePageSettings.Select(item => item.Split(':')) + .Where(parts => parts.Length == 2) + .ToDictionary(parts => parts[0], parts => parts[1]); + } + catch(Exception ex) + { + this.startPage.DynamoViewModel.Model.Logger.Log("Failed to load HomePageSettings: " + ex.Message); } + + return dict; } - + internal void NewWorkspace() { this.startPage?.DynamoViewModel?.NewHomeWorkspaceCommand.Execute(null); diff --git a/test/DynamoCoreTests/Configuration/PreferenceSettingsTests.cs b/test/DynamoCoreTests/Configuration/PreferenceSettingsTests.cs index e92b1e4c408..40a116ecb0a 100644 --- a/test/DynamoCoreTests/Configuration/PreferenceSettingsTests.cs +++ b/test/DynamoCoreTests/Configuration/PreferenceSettingsTests.cs @@ -490,16 +490,15 @@ public void TestSerializingHomePageSettings() PreferenceSettings settings = new PreferenceSettings(); // Assert defaults - Assert.AreEqual(settings.HomePageSettings, null); - Assert.AreEqual(settings.HomePageSettingsSerialized, "null"); + Assert.IsEmpty(settings.HomePageSettings); - settings.HomePageSettings = new Dictionary { { "greeting", "Hello World" } }; + settings.HomePageSettings = new List { { String.Concat("greeting", "Hello World") } }; // Save settings.Save(tempPath); settings = PreferenceSettings.Load(tempPath); - Assert.AreEqual(settings.HomePageSettings["greeting"], "Hello World"); + Assert.IsTrue(settings.HomePageSettings.Contains(String.Concat("greeting", "Hello World"))); } } } diff --git a/test/DynamoCoreWpfTests/HomePageTests.cs b/test/DynamoCoreWpfTests/HomePageTests.cs index 072a8da8983..2bfa3e36291 100644 --- a/test/DynamoCoreWpfTests/HomePageTests.cs +++ b/test/DynamoCoreWpfTests/HomePageTests.cs @@ -65,7 +65,7 @@ public void HomePage_NewSettingsAreAddedCorrectly() homePage.DataContext = startPage; - Assert.IsNull(preferences.HomePageSettings); + Assert.IsEmpty(preferences.HomePageSettings); // Act var pair1 = @"{""Name"": ""Alice""}"; diff --git a/test/settings/DynamoSettings-NewSettings.xml b/test/settings/DynamoSettings-NewSettings.xml index 63940d89790..a9b3802e249 100644 --- a/test/settings/DynamoSettings-NewSettings.xml +++ b/test/settings/DynamoSettings-NewSettings.xml @@ -32,7 +32,10 @@ 1936 1056 false - {"recentPageViewMode":"grid","samplesViewMode":"list"} + + recentPageViewMode:list + samplesViewMode:list + f4 12 From 7d45f53467b65df835f8d51e7ed8fe881680e46e Mon Sep 17 00:00:00 2001 From: pinzart90 <46732933+pinzart90@users.noreply.github.com> Date: Fri, 17 May 2024 13:04:40 -0400 Subject: [PATCH 06/10] fix extension assembly paths for linux (#15227) --- src/DynamoCore/Extensions/ExtensionLoader.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/DynamoCore/Extensions/ExtensionLoader.cs b/src/DynamoCore/Extensions/ExtensionLoader.cs index e734a8815bb..b641702e22a 100644 --- a/src/DynamoCore/Extensions/ExtensionLoader.cs +++ b/src/DynamoCore/Extensions/ExtensionLoader.cs @@ -74,7 +74,9 @@ public IExtension Load(string extensionPath) { if (item.Name == "AssemblyPath") { - path = Path.Combine(path, item.InnerText); + // Usually the extension configs are written on a Windows system, so we only need to make them compatible with linux + string assemblyRelativePath = OSHelper.IsWindows() ? item.InnerText : item.InnerText.Replace('\\', Path.DirectorySeparatorChar); + path = Path.Combine(path, assemblyRelativePath); definition.AssemblyPath = path; } else if (item.Name == "TypeName") From fd7d21bfa21c06ed735c240efd13b292fb792f8c Mon Sep 17 00:00:00 2001 From: "Aaron (Qilong)" <173288704@qq.com> Date: Fri, 17 May 2024 13:51:39 -0400 Subject: [PATCH 07/10] DYN-6967 Crash when placing List selector archi-lab Dynamo nodes in canvas (#15224) --- .../Properties/Resources.Designer.cs | 9 +++++++++ src/DynamoCoreWpf/Properties/Resources.en-US.resx | 7 +++++-- src/DynamoCoreWpf/Properties/Resources.resx | 7 +++++-- src/DynamoCoreWpf/PublicAPI.Unshipped.txt | 1 + .../Search/NodeSearchElementViewModel.cs | 15 +++++++++++++-- 5 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/DynamoCoreWpf/Properties/Resources.Designer.cs b/src/DynamoCoreWpf/Properties/Resources.Designer.cs index 678040e808e..fea4f5cea3c 100644 --- a/src/DynamoCoreWpf/Properties/Resources.Designer.cs +++ b/src/DynamoCoreWpf/Properties/Resources.Designer.cs @@ -5026,6 +5026,15 @@ public static string NodeHelpWindowNodeType { } } + /// + /// Looks up a localized string similar to Failed to create node: . + /// + public static string NodeInCanvasSearchCreationError { + get { + return ResourceManager.GetString("NodeInCanvasSearchCreationError", resourceCulture); + } + } + /// /// Looks up a localized string similar to Dismisses the info messages on this node. Utilize when you want to design in graph failures, or the info message will not be relevant during graph execution.. /// diff --git a/src/DynamoCoreWpf/Properties/Resources.en-US.resx b/src/DynamoCoreWpf/Properties/Resources.en-US.resx index 249a2a95c4b..1d3c271eb00 100644 --- a/src/DynamoCoreWpf/Properties/Resources.en-US.resx +++ b/src/DynamoCoreWpf/Properties/Resources.en-US.resx @@ -582,7 +582,7 @@ Don't worry, you'll have the option to save your work. _Select All Edit menu | Select all nodes - + _Unpin All Preview Bubbles Edit menu | Unpin preview bubbles @@ -3983,4 +3983,7 @@ To make this file into a new template, save it to a different folder, then move #Learn more=https://primer2.dynamobim.org/1_developer_primer_intro/3_developing_for_dynamo/updating-your-packages-and-dynamo-libraries-for-dynamo-3x-net8 - + + Failed to create node: + + \ No newline at end of file diff --git a/src/DynamoCoreWpf/Properties/Resources.resx b/src/DynamoCoreWpf/Properties/Resources.resx index dbf2af5204f..1938f65dbc9 100644 --- a/src/DynamoCoreWpf/Properties/Resources.resx +++ b/src/DynamoCoreWpf/Properties/Resources.resx @@ -330,7 +330,7 @@ _Select All Edit menu | Select all nodes - + _Unpin All Preview Bubbles Edit menu | Unpin preview bubbles @@ -3970,4 +3970,7 @@ To make this file into a new template, save it to a different folder, then move #Learn more=https://primer2.dynamobim.org/1_developer_primer_intro/3_developing_for_dynamo/updating-your-packages-and-dynamo-libraries-for-dynamo-3x-net8 - + + Failed to create node: + + \ No newline at end of file diff --git a/src/DynamoCoreWpf/PublicAPI.Unshipped.txt b/src/DynamoCoreWpf/PublicAPI.Unshipped.txt index 6ae526a235b..6ff50196f54 100644 --- a/src/DynamoCoreWpf/PublicAPI.Unshipped.txt +++ b/src/DynamoCoreWpf/PublicAPI.Unshipped.txt @@ -4878,6 +4878,7 @@ static Dynamo.Wpf.Properties.Resources.NodeHelpWindowNodeDescription.get -> stri static Dynamo.Wpf.Properties.Resources.NodeHelpWindowNodeInput.get -> string static Dynamo.Wpf.Properties.Resources.NodeHelpWindowNodeOutput.get -> string static Dynamo.Wpf.Properties.Resources.NodeHelpWindowNodeType.get -> string +static Dynamo.Wpf.Properties.Resources.NodeInCanvasSearchCreationError.get -> string static Dynamo.Wpf.Properties.Resources.NodeInfoDismissButtonToolTip.get -> string static Dynamo.Wpf.Properties.Resources.NodeInformationalStateDismiss.get -> string static Dynamo.Wpf.Properties.Resources.NodeInformationalStateDismissAll.get -> string diff --git a/src/DynamoCoreWpf/ViewModels/Search/NodeSearchElementViewModel.cs b/src/DynamoCoreWpf/ViewModels/Search/NodeSearchElementViewModel.cs index 1222d2bbd9c..0ecaaec4f04 100644 --- a/src/DynamoCoreWpf/ViewModels/Search/NodeSearchElementViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/Search/NodeSearchElementViewModel.cs @@ -362,8 +362,19 @@ protected virtual void OnClicked() { if (Clicked != null) { - var nodeModel = Model.CreateNode(); - Clicked(nodeModel, Position); + // Try to create the node based on the search element from in-Canvas search + // The node creation can fail if the node constructor dependencies are not found or other reasons. + // This is a best effort to create the node and log the error both in console and toast notification if it fails. + try + { + var nodeModel = Model.CreateNode(); + Clicked(nodeModel, Position); + } + catch (Exception ex) + { + searchViewModel.dynamoViewModel.Model.Logger.Log("Failed to create node from search element: " + Model.Name + "\n" + ex.Message); + searchViewModel.dynamoViewModel.MainGuideManager.CreateRealTimeInfoWindow(Wpf.Properties.Resources.NodeInCanvasSearchCreationError + Model.Name, true); + } } } From d980f2dcf3294d00b50a4a1592313647d3c24627 Mon Sep 17 00:00:00 2001 From: Ashish Aggarwal Date: Mon, 20 May 2024 12:54:02 -0400 Subject: [PATCH 08/10] DYN-6693 Alert user for duplicate files when publishing package (#15225) Co-authored-by: Aaron (Qilong) <173288704@qq.com> --- .../Properties/Resources.Designer.cs | 18 ++++++++ .../Properties/Resources.en-US.resx | 6 +++ src/DynamoCoreWpf/Properties/Resources.resx | 6 +++ src/DynamoCoreWpf/PublicAPI.Unshipped.txt | 6 +++ .../UI/Prompts/DynamoMessageBox.xaml | 16 ++++++- .../UI/Prompts/DynamoMessageBox.xaml.cs | 45 +++++++++++++++++++ .../Utilities/MessageBoxUtilities.cs | 9 ++++ .../PackageManager/PublishPackageViewModel.cs | 16 +++++++ 8 files changed, 120 insertions(+), 2 deletions(-) diff --git a/src/DynamoCoreWpf/Properties/Resources.Designer.cs b/src/DynamoCoreWpf/Properties/Resources.Designer.cs index fea4f5cea3c..8ff65455544 100644 --- a/src/DynamoCoreWpf/Properties/Resources.Designer.cs +++ b/src/DynamoCoreWpf/Properties/Resources.Designer.cs @@ -1333,6 +1333,24 @@ public static string DownloadWarningMessageBoxTitle { } } + /// + /// Looks up a localized string similar to Files with same name will be overwritten in the final package. To avoid this, rename or discard the duplicate files, or enable the Retain Folder Structure option.\n\n{0} duplicate file(s) found:\n{1}. + /// + public static string DuplicateFilesInPublishWarningMessage { + get { + return ResourceManager.GetString("DuplicateFilesInPublishWarningMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Duplicate Files. + /// + public static string DuplicateFilesInPublishWarningTitle { + get { + return ResourceManager.GetString("DuplicateFilesInPublishWarningTitle", resourceCulture); + } + } + /// /// Looks up a localized string similar to Show File Trust Warning. /// diff --git a/src/DynamoCoreWpf/Properties/Resources.en-US.resx b/src/DynamoCoreWpf/Properties/Resources.en-US.resx index 1d3c271eb00..f40410b0fd0 100644 --- a/src/DynamoCoreWpf/Properties/Resources.en-US.resx +++ b/src/DynamoCoreWpf/Properties/Resources.en-US.resx @@ -3983,6 +3983,12 @@ To make this file into a new template, save it to a different folder, then move #Learn more=https://primer2.dynamobim.org/1_developer_primer_intro/3_developing_for_dynamo/updating-your-packages-and-dynamo-libraries-for-dynamo-3x-net8 + + Files with same name will be overwritten in the final package. To avoid this, rename or discard the duplicate files, or enable the Retain Folder Structure option.\n\n{0} duplicate file(s) found:\n{1} + + + Duplicate Files + Failed to create node: diff --git a/src/DynamoCoreWpf/Properties/Resources.resx b/src/DynamoCoreWpf/Properties/Resources.resx index 1938f65dbc9..aedca471753 100644 --- a/src/DynamoCoreWpf/Properties/Resources.resx +++ b/src/DynamoCoreWpf/Properties/Resources.resx @@ -3970,6 +3970,12 @@ To make this file into a new template, save it to a different folder, then move #Learn more=https://primer2.dynamobim.org/1_developer_primer_intro/3_developing_for_dynamo/updating-your-packages-and-dynamo-libraries-for-dynamo-3x-net8 + + Files with same name will be overwritten in the final package. To avoid this, rename or discard the duplicate files, or enable the Retain Folder Structure option.\n\n{0} duplicate file(s) found:\n{1} + + + Duplicate Files + Failed to create node: diff --git a/src/DynamoCoreWpf/PublicAPI.Unshipped.txt b/src/DynamoCoreWpf/PublicAPI.Unshipped.txt index 6ff50196f54..38059d120a9 100644 --- a/src/DynamoCoreWpf/PublicAPI.Unshipped.txt +++ b/src/DynamoCoreWpf/PublicAPI.Unshipped.txt @@ -1514,6 +1514,8 @@ Dynamo.UI.IViewModelView.ViewModel.get -> T Dynamo.UI.Prompts.DynamoMessageBox Dynamo.UI.Prompts.DynamoMessageBox.BodyText.get -> string Dynamo.UI.Prompts.DynamoMessageBox.BodyText.set -> void +Dynamo.UI.Prompts.DynamoMessageBox.DialogFlags +Dynamo.UI.Prompts.DynamoMessageBox.DialogFlags.Scrollable = 0 -> Dynamo.UI.Prompts.DynamoMessageBox.DialogFlags Dynamo.UI.Prompts.DynamoMessageBox.DialogResult.get -> bool? Dynamo.UI.Prompts.DynamoMessageBox.DialogResult.set -> void Dynamo.UI.Prompts.DynamoMessageBox.DynamoMessageBox() -> void @@ -4288,6 +4290,7 @@ static Dynamo.UI.Prompts.DynamoMessageBox.Show(string messageBoxText, string cap static Dynamo.UI.Prompts.DynamoMessageBox.Show(string messageBoxText, string caption, System.Windows.MessageBoxButton button) -> System.Windows.MessageBoxResult static Dynamo.UI.Prompts.DynamoMessageBox.Show(string messageBoxText, string caption, System.Windows.MessageBoxButton button, System.Windows.MessageBoxImage icon, string tooltip = "") -> System.Windows.MessageBoxResult static Dynamo.UI.Prompts.DynamoMessageBox.Show(System.Windows.Window owner, string messageBoxText, string caption, bool showRichTextBox, System.Windows.MessageBoxButton button, System.Windows.MessageBoxImage icon) -> System.Windows.MessageBoxResult +static Dynamo.UI.Prompts.DynamoMessageBox.Show(System.Windows.Window owner, string messageBoxText, string caption, System.Collections.Generic.Dictionary flags, System.Windows.MessageBoxButton button, System.Windows.MessageBoxImage icon) -> System.Windows.MessageBoxResult static Dynamo.UI.Prompts.DynamoMessageBox.Show(System.Windows.Window owner, string messageBoxText, string caption, System.Windows.MessageBoxButton button, System.Windows.MessageBoxImage icon) -> System.Windows.MessageBoxResult static Dynamo.UI.SharedDictionaryManager.ConnectorsDictionary.get -> System.Windows.ResourceDictionary static Dynamo.UI.SharedDictionaryManager.ConnectorsDictionaryUri.get -> System.Uri @@ -4473,6 +4476,8 @@ static Dynamo.Wpf.Properties.Resources.DisablePackageInstallIconTooltip.get -> s static Dynamo.Wpf.Properties.Resources.DiscardChangesWarningPopupCaption.get -> string static Dynamo.Wpf.Properties.Resources.DiscardChangesWarningPopupMessage.get -> string static Dynamo.Wpf.Properties.Resources.DownloadWarningMessageBoxTitle.get -> string +static Dynamo.Wpf.Properties.Resources.DuplicateFilesInPublishWarningMessage.get -> string +static Dynamo.Wpf.Properties.Resources.DuplicateFilesInPublishWarningTitle.get -> string static Dynamo.Wpf.Properties.Resources.DynamoShowFileTrustWarning.get -> string static Dynamo.Wpf.Properties.Resources.DynamoUpdateAvailableToolTip.get -> string static Dynamo.Wpf.Properties.Resources.DynamoViewCancelButtonTooltip.get -> string @@ -5502,6 +5507,7 @@ static Dynamo.Wpf.Utilities.MessageBoxService.Show(string msg, string title, boo static Dynamo.Wpf.Utilities.MessageBoxService.Show(string msg, string title, System.Windows.MessageBoxButton button, System.Collections.Generic.IEnumerable buttonNames, System.Windows.MessageBoxImage img) -> System.Windows.MessageBoxResult static Dynamo.Wpf.Utilities.MessageBoxService.Show(string msg, string title, System.Windows.MessageBoxButton button, System.Windows.MessageBoxImage img) -> System.Windows.MessageBoxResult static Dynamo.Wpf.Utilities.MessageBoxService.Show(System.Windows.Window owner, string msg, string title, bool showRichTextBox, System.Windows.MessageBoxButton button, System.Windows.MessageBoxImage img) -> System.Windows.MessageBoxResult +static Dynamo.Wpf.Utilities.MessageBoxService.Show(System.Windows.Window owner, string msg, string title, System.Collections.Generic.Dictionary flags, System.Windows.MessageBoxButton button, System.Windows.MessageBoxImage img) -> System.Windows.MessageBoxResult static Dynamo.Wpf.Utilities.MessageBoxService.Show(System.Windows.Window owner, string msg, string title, System.Windows.MessageBoxButton button, System.Collections.Generic.IEnumerable buttonNames, System.Windows.MessageBoxImage img) -> System.Windows.MessageBoxResult static Dynamo.Wpf.Utilities.MessageBoxService.Show(System.Windows.Window owner, string msg, string title, System.Windows.MessageBoxButton button, System.Windows.MessageBoxImage img) -> System.Windows.MessageBoxResult static Dynamo.Wpf.Utilities.WebView2Utilities.ValidateWebView2RuntimeInstalled() -> bool diff --git a/src/DynamoCoreWpf/UI/Prompts/DynamoMessageBox.xaml b/src/DynamoCoreWpf/UI/Prompts/DynamoMessageBox.xaml index 434fcead8db..a5c05c44a15 100644 --- a/src/DynamoCoreWpf/UI/Prompts/DynamoMessageBox.xaml +++ b/src/DynamoCoreWpf/UI/Prompts/DynamoMessageBox.xaml @@ -139,8 +139,18 @@ + + + + + public string Tooltip { get; private set; } + /// + /// A list of customization options for dialog box + /// + public enum DialogFlags + { + //Enables scrollable text in the message box + Scrollable = 0, + } + #endregion /// @@ -204,6 +213,7 @@ public static MessageBoxResult Show(Window owner, string messageBoxText, string dynamoMessageBox.ShowDialog(); return dynamoMessageBox.CustomDialogResult; } + /// /// Displays a dialog to the user and returns their choice as a MessageBoxResult. /// @@ -234,6 +244,41 @@ public static MessageBoxResult Show(Window owner,string messageBoxText, string c return dynamoMessageBox.CustomDialogResult; } + /// + /// Displays a dialog to the user and returns their choice as a MessageBoxResult. + /// + /// owning window of the messagebox + /// Content of the message + /// MessageBox title + /// Provide a list of flags that can be used to customize the dialog box, e.g Scrollable + /// Type of button shown in the MessageBox: Ok, OkCancel; etc + /// Type of message: Warning, Error + /// + public static MessageBoxResult Show(Window owner, string messageBoxText, string caption, Dictionary flags, MessageBoxButton button, + MessageBoxImage icon) + { + var dynamoMessageBox = new DynamoMessageBox + { + BodyText = messageBoxText, + TitleText = caption, + MessageBoxButton = button, + MessageBoxImage = icon + }; + if (owner != null && owner.IsLoaded) + { + dynamoMessageBox.Owner = owner; + } + + if (flags.TryGetValue(DialogFlags.Scrollable, out bool scrollable) && scrollable) + { + dynamoMessageBox.BodyTextBlock.Visibility = Visibility.Collapsed; + dynamoMessageBox.ScrollableBodyTextBlock.Visibility = Visibility.Visible; + } + dynamoMessageBox.ConfigureButtons(button); + dynamoMessageBox.ShowDialog(); + return dynamoMessageBox.CustomDialogResult; + } + /// /// Displays a dialog to the user and returns their choice as a MessageBoxResult. /// diff --git a/src/DynamoCoreWpf/Utilities/MessageBoxUtilities.cs b/src/DynamoCoreWpf/Utilities/MessageBoxUtilities.cs index 7bb7dde8969..c3da445ea1f 100644 --- a/src/DynamoCoreWpf/Utilities/MessageBoxUtilities.cs +++ b/src/DynamoCoreWpf/Utilities/MessageBoxUtilities.cs @@ -12,6 +12,7 @@ internal interface IMessageBox MessageBoxResult Show(string msg, string title, MessageBoxButton button, MessageBoxImage img); MessageBoxResult Show(string msg, string title, bool showRichTextBox, MessageBoxButton button, MessageBoxImage img); MessageBoxResult Show(Window owner, string msg, string title, bool showRichTextBox, MessageBoxButton button, MessageBoxImage img); + MessageBoxResult Show(Window owner, string msg, string title, Dictionary flags, MessageBoxButton button, MessageBoxImage img); MessageBoxResult Show(Window owner,string msg, string title, MessageBoxButton button, MessageBoxImage img); MessageBoxResult Show(Window owner, string msg, string title, MessageBoxButton button, IEnumerable buttonNames, MessageBoxImage img); MessageBoxResult Show(string msg, string title, MessageBoxButton button, IEnumerable buttonNames, MessageBoxImage img); @@ -34,6 +35,10 @@ MessageBoxResult IMessageBox.Show(Window owner, string msg, string title, bool s { return DynamoMessageBox.Show(owner,msg, title, showRichTextBox, button, img); } + MessageBoxResult IMessageBox.Show(Window owner, string msg, string title, Dictionary flags, MessageBoxButton button, MessageBoxImage img) + { + return DynamoMessageBox.Show(owner, msg, title, flags, button, img); + } public MessageBoxResult Show(Window owner, string msg, string title, MessageBoxButton button, MessageBoxImage img) { @@ -68,6 +73,10 @@ public static MessageBoxResult Show(Window owner, string msg, string title, bool { return (msg_box ?? (msg_box = new DefaultMessageBox())).Show(owner,msg, title, showRichTextBox, button, img); } + public static MessageBoxResult Show(Window owner, string msg, string title, Dictionary flags, MessageBoxButton button, MessageBoxImage img) + { + return (msg_box ?? (msg_box = new DefaultMessageBox())).Show(owner, msg, title, flags, button, img); + } public static MessageBoxResult Show(Window owner,string msg, string title, MessageBoxButton button, MessageBoxImage img) { return (msg_box ?? (msg_box = new DefaultMessageBox())).Show(owner,msg, title, button, img); diff --git a/src/DynamoCoreWpf/ViewModels/PackageManager/PublishPackageViewModel.cs b/src/DynamoCoreWpf/ViewModels/PackageManager/PublishPackageViewModel.cs index 869aced73fe..3cb1025dafb 100644 --- a/src/DynamoCoreWpf/ViewModels/PackageManager/PublishPackageViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/PackageManager/PublishPackageViewModel.cs @@ -2649,6 +2649,22 @@ private void PreviewPackageBuild() return; var files = GetAllFiles().ToList(); + if (!RetainFolderStructureOverride) + { + //Look for duplicate filenames to alert user + var duplicateFiles = files.GroupBy(x => Path.GetFileName(x)) + .Where(x => x.Count() > 1) + .ToList(); + if (duplicateFiles.Count > 0) + { + if (!DynamoModel.IsTestMode) + { + var DialogOptions = new Dictionary() { { Dynamo.UI.Prompts.DynamoMessageBox.DialogFlags.Scrollable, true } }; + MessageBoxService.Show(System.Windows.Application.Current?.MainWindow, string.Format(Resources.DuplicateFilesInPublishWarningMessage.Replace("\\n", Environment.NewLine), duplicateFiles.Count, string.Join("\n", duplicateFiles.Select(x => x.Key).ToList())), Resources.DuplicateFilesInPublishWarningTitle, DialogOptions, MessageBoxButton.OK, MessageBoxImage.Warning); + } + } + } + files = files.GroupBy(file => Path.GetFileName(file), StringComparer.OrdinalIgnoreCase) .Select(group => group.First()) .ToList(); From 9d4a31caa25949038c7d869e9e323cc4e9661223 Mon Sep 17 00:00:00 2001 From: Craig Long Date: Mon, 20 May 2024 14:06:16 -0400 Subject: [PATCH 09/10] DYN-6427 & DYN-6828 Graph Properties UI Fixes (#15200) Co-authored-by: Trygve Wastvedt Co-authored-by: pinzart90 --- .../GraphMetadataViewModel.cs | 42 +++++++++-- .../GraphMetadataViewExtensionTests.cs | 73 +++++++++++++++++-- .../GraphNodeManagerViewExtensionTests.cs | 4 +- test/core/CustompropertyTest.dyn | 14 +++- 4 files changed, 116 insertions(+), 17 deletions(-) diff --git a/src/GraphMetadataViewExtension/GraphMetadataViewModel.cs b/src/GraphMetadataViewExtension/GraphMetadataViewModel.cs index 60709819e7b..b1451a07e7c 100644 --- a/src/GraphMetadataViewExtension/GraphMetadataViewModel.cs +++ b/src/GraphMetadataViewExtension/GraphMetadataViewModel.cs @@ -1,13 +1,13 @@ using System; using System.Collections.ObjectModel; using System.IO; +using System.Linq; using System.Windows.Media.Imaging; using Dynamo.Core; using Dynamo.Graph.Workspaces; using Dynamo.GraphMetadata.Controls; using Dynamo.Linting; using Dynamo.UI.Commands; -using Dynamo.ViewModels; using Dynamo.Wpf.Extensions; namespace Dynamo.GraphMetadata @@ -114,10 +114,9 @@ public GraphMetadataViewModel(ViewLoadedParams viewLoadedParams, GraphMetadataVi this.linterManager = viewLoadedParams.StartupParams.LinterManager; this.viewLoadedParams.CurrentWorkspaceChanged += OnCurrentWorkspaceChanged; - // using this as CurrentWorkspaceChanged wont trigger if you: - // Close a saved workspace and open a New homeworkspace.. - // This means that properties defined in the previous opened workspace will still be showed in the extension. - // CurrentWorkspaceCleared will trigger everytime a graph is closed which allows us to reset the properties. + // Using this as CurrentWorkspaceChanged won't trigger if you close a saved workspace and open a new homeworkspace. + // This means that properties defined in the previous opened workspace will still be shown in the extension. + // CurrentWorkspaceCleared will trigger every time a graph is closed which allows us to reset the properties. this.viewLoadedParams.CurrentWorkspaceCleared += OnCurrentWorkspaceChanged; if (linterManager != null) { @@ -128,14 +127,26 @@ public GraphMetadataViewModel(ViewLoadedParams viewLoadedParams, GraphMetadataVi InitializeCommands(); } + /// + /// This event is triggered when a new workspace is opened or when the current workspace is cleared. + /// This event manages state of the workspace properties (ie GraphDescription, GraphAuthor, HelpLink, Thumbnail) + /// as well as the custom properties in the extension which do not live in the workspace model. + /// private void OnCurrentWorkspaceChanged(Graph.Workspaces.IWorkspaceModel obj) { - if (!(obj is HomeWorkspaceModel hwm)) + //Todo review if the workspace properties should be managed in the Workspace model. + //Due to the fact that Dynamo often leaves the workspace objects in memory and resets their properties when you open a new workspace, + //the management of state is not straightforward. However it may make more sense to update those properties with the clearing logic. + + //Handle the case of a custom workspace model opening + if (obj is not HomeWorkspaceModel hwm) { extension.Closed(); return; } + //Handle workspace change cases in UI. First is a new workspace or template opening + //In this case the properties should be cleared if (!hwm.IsTemplate && string.IsNullOrEmpty(hwm.FileName) ) { GraphDescription = string.Empty; @@ -143,6 +154,14 @@ private void OnCurrentWorkspaceChanged(Graph.Workspaces.IWorkspaceModel obj) HelpLink = null; Thumbnail = null; } + //Second is switching between an open workspace and open custom node and no state changes are required. + //This case can also be true if you close an open workspace while focused on a custom node. + //However in that scenario the first case will be triggered first due to empty filename. + else if(hwm == currentWorkspace) + { + return; + } + //Third is a new workspace opening from a saved file else { currentWorkspace = hwm; @@ -152,6 +171,7 @@ private void OnCurrentWorkspaceChanged(Graph.Workspaces.IWorkspaceModel obj) RaisePropertyChanged(nameof(Thumbnail)); } + //Clear custom properties for cases one and two. CustomProperties.Clear(); } @@ -216,7 +236,15 @@ private void OpenGraphStatusExecute(object obj) private void AddCustomPropertyExecute(object obj) { - var propName = Properties.Resources.CustomPropertyControl_CustomPropertyDefault + " " + (CustomProperties.Count + 1); + int increment = CustomProperties.Count + 1; + string propName; + do + { + propName = Properties.Resources.CustomPropertyControl_CustomPropertyDefault + " " + increment; + increment++; + } + while (CustomProperties.Any(x => x.PropertyName == propName)); + AddCustomProperty(propName, string.Empty); } diff --git a/test/DynamoCoreWpfTests/ViewExtensions/GraphMetadataViewExtensionTests.cs b/test/DynamoCoreWpfTests/ViewExtensions/GraphMetadataViewExtensionTests.cs index 125a20dce05..988c7bd3785 100644 --- a/test/DynamoCoreWpfTests/ViewExtensions/GraphMetadataViewExtensionTests.cs +++ b/test/DynamoCoreWpfTests/ViewExtensions/GraphMetadataViewExtensionTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -8,6 +8,7 @@ using Dynamo.Graph.Workspaces; using Dynamo.GraphMetadata; using Dynamo.GraphMetadata.Controls; +using Dynamo.Models; using NUnit.Framework; namespace DynamoCoreWpfTests.ViewExtensions @@ -29,15 +30,15 @@ public void SettingPropertiesInExtensionUpdatesWorkspace() var extensionManager = View.viewExtensionManager; var propertiesExt = extensionManager.ViewExtensions - .FirstOrDefault(x => x as GraphMetadataViewExtension != null ) + .FirstOrDefault(x => x as GraphMetadataViewExtension != null) as GraphMetadataViewExtension; var hwm = this.ViewModel.CurrentSpace as HomeWorkspaceModel; // Act - var graphDescriptionBefore = hwm.Description; - var graphAuthorBefore = hwm.Author; - var graphHelpLinkBefore = hwm.GraphDocumentationURL; + var graphDescriptionBefore = hwm.Description; + var graphAuthorBefore = hwm.Author; + var graphHelpLinkBefore = hwm.GraphDocumentationURL; var graphThumbnailBefore = hwm.Thumbnail; propertiesExt.viewModel.GraphDescription = graphDescription; @@ -107,7 +108,7 @@ public void ExistingGraphWithCustomPropertiesWillBeAddedToExtension() // Arrange var expectedCP1Key = "My prop 1"; var expectedCP2Key = "My prop 2"; - var expectedCP3Key = "Custom Property 3"; + var expectedCP3Key = "Custom Property 4"; var expectedCP1Value = "My value 1"; var expectedCP2Value = "My Value 2"; @@ -134,6 +135,48 @@ public void ExistingGraphWithCustomPropertiesWillBeAddedToExtension() Assert.That(propertiesExt.viewModel.CustomProperties[2].PropertyValue == expectedCP3Value); } + [Test] + public void ExistingGraphWithCustomPropertiesKeepsPropertiesWhenCustomNodesAreOpened() + { + // Arrange + var expectedCP1Key = "My prop 1"; + var expectedCP2Key = "My prop 2"; + var expectedCP3Key = "Custom Property 4"; + + var expectedCP1Value = "My value 1"; + var expectedCP2Value = "My Value 2"; + var expectedCP3Value = ""; + + // Act + var extensionManager = View.viewExtensionManager; + var propertiesExt = extensionManager.ViewExtensions + .FirstOrDefault(x => x as GraphMetadataViewExtension != null) + as GraphMetadataViewExtension; + + Open(@"core\CustompropertyTest.dyn"); + + Open(@"core\CustomNodes\add.dyf"); + + ViewModel.UIDispatcher.Invoke(new Action(() => + { + DynamoModel.SwitchTabCommand switchCommand = + new DynamoModel.SwitchTabCommand(0); + + ViewModel.ExecuteCommand(switchCommand); + })); + + Model.Logger.Log(ViewModel.CurrentSpace.Name); + + // Assert + Assert.That(propertiesExt.viewModel.CustomProperties.Count == 3); + Assert.That(propertiesExt.viewModel.CustomProperties[0].PropertyName == expectedCP1Key); + Assert.That(propertiesExt.viewModel.CustomProperties[0].PropertyValue == expectedCP1Value); + Assert.That(propertiesExt.viewModel.CustomProperties[1].PropertyName == expectedCP2Key); + Assert.That(propertiesExt.viewModel.CustomProperties[1].PropertyValue == expectedCP2Value); + Assert.That(propertiesExt.viewModel.CustomProperties[2].PropertyName == expectedCP3Key); + Assert.That(propertiesExt.viewModel.CustomProperties[2].PropertyValue == expectedCP3Value); + } + [Test] public void ExistingGraphOpenModifiedAndClosedWillSetAndClearModifiedFlag() { @@ -157,5 +200,23 @@ public void ExistingGraphOpenModifiedAndClosedWillSetAndClearModifiedFlag() Assert.IsFalse(ViewModel.HomeSpace.HasUnsavedChanges); } + + [Test] + public void AddingNewPropertiesHaveUniquePropertyNames() + { + var extensionManager = View.viewExtensionManager; + var propertiesExt = extensionManager.ViewExtensions + .FirstOrDefault(x => x as GraphMetadataViewExtension != null) + as GraphMetadataViewExtension; + + var customPropertiesBeforeOpen = propertiesExt.viewModel.CustomProperties.Count; + Open(@"core\CustompropertyTest.dyn"); + + propertiesExt.viewModel.AddCustomPropertyCommand.Execute(null); + + Assert.That(propertiesExt.viewModel.CustomProperties.Count == 4); + Assert.That(propertiesExt.viewModel.CustomProperties[3].PropertyName == "Custom Property 5"); + Assert.That(propertiesExt.viewModel.CustomProperties[3].PropertyValue == ""); + } } } diff --git a/test/DynamoCoreWpfTests/ViewExtensions/GraphNodeManagerViewExtensionTests.cs b/test/DynamoCoreWpfTests/ViewExtensions/GraphNodeManagerViewExtensionTests.cs index 4353c4ae4ce..a867ed4e7c5 100644 --- a/test/DynamoCoreWpfTests/ViewExtensions/GraphNodeManagerViewExtensionTests.cs +++ b/test/DynamoCoreWpfTests/ViewExtensions/GraphNodeManagerViewExtensionTests.cs @@ -214,10 +214,12 @@ public void ContainsEmptyListOrNullTest() int emptyListNodesCount = hwm.Nodes.Count(ContainsAnyEmptyLists); var view = viewExt.ManagerView; - var images = WpfUtilities.ChildrenOfType(view.NodesInfoDataGrid); + IEnumerable images = []; Utility.DispatcherUtil.DoEventsLoop(() => { + images = WpfUtilities.ChildrenOfType(view.NodesInfoDataGrid); + int nullNodesImageCount = GetImageCount(images, "Null"); int emptyListNodesImageCount = GetImageCount(images, "EmptyList"); diff --git a/test/core/CustompropertyTest.dyn b/test/core/CustompropertyTest.dyn index 109e205a708..fcc44a578e3 100644 --- a/test/core/CustompropertyTest.dyn +++ b/test/core/CustompropertyTest.dyn @@ -12,6 +12,7 @@ "Connectors": [], "Dependencies": [], "NodeLibraryDependencies": [], + "EnableLegacyPolyCurveBehavior": true, "Thumbnail": "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D/8ACK+Ip760sftCXaTafIqSpIgYKcsrD+I9q+P9S/4JH2Ul0z6d8T7i2t+0V1oazsP+BLOn8qAOE+On/BTvXvGWjXOieANIk8JQTq0cmsXU6yX3ln/nmq/LE3+1l/bbXzx+zb+z3r37R/xGtdFsEkh0mJ1l1bVvL+Wzh78/89G/gX/2Wvtvwb/wSf8ACml3yS+JvGmp6/Av/LCws47FX/3iWkP5V9jfD34b+GvhX4at9B8LaPb6NpcI3CG3X7zd2dj8zt/tNzQBp+GPDeneDfD+naHpFtHZ6Zp1tHa2sCdEjRdqj9K1qKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP//Z", "GraphDocumentationURL": "https://dynamobim.org/", "ExtensionWorkspaceData": [ @@ -22,23 +23,29 @@ "Data": { "My prop 1": "My value 1", "My prop 2": "My Value 2", - "Custom Property 3": "" + "Custom Property 4": "" } } ], "Author": "Name of author", + "Linting": { + "activeLinter": "None", + "activeLinterId": "7b75fb44-43fd-4631-a878-29f4d5d8399a", + "warningCount": 0, + "errorCount": 0 + }, "Bindings": [], "View": { "Dynamo": { "ScaleFactor": 1.0, "HasRunWithoutCrash": true, "IsVisibleInDynamoLibrary": true, - "Version": "2.12.0.4955", + "Version": "3.2.0.5025", "RunType": "Automatic", "RunPeriod": "1000" }, "Camera": { - "Name": "Background Preview", + "Name": "_Background Preview", "EyeX": -17.0, "EyeY": 24.0, "EyeZ": 50.0, @@ -49,6 +56,7 @@ "UpY": 1.0, "UpZ": 0.0 }, + "ConnectorPins": [], "NodeViews": [], "Annotations": [], "X": 0.0, From fa05e2290c3057f2ad5fcd1e0d7ee76e064264b7 Mon Sep 17 00:00:00 2001 From: Ashish Aggarwal Date: Mon, 20 May 2024 15:14:42 -0400 Subject: [PATCH 10/10] DYN-6990 Crash when selecting node with view extension (#15231) --- src/DynamoCore/Models/DynamoModelCommands.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/DynamoCore/Models/DynamoModelCommands.cs b/src/DynamoCore/Models/DynamoModelCommands.cs index eae0f04c19a..032e750e1c4 100644 --- a/src/DynamoCore/Models/DynamoModelCommands.cs +++ b/src/DynamoCore/Models/DynamoModelCommands.cs @@ -293,8 +293,15 @@ private void SelectModelImpl(SelectModelCommand command) private void AddSelectionAndRecordUndo(ModelBase model) { - WorkspaceModel.RecordModelsForModification(new List() { model }, CurrentWorkspace.UndoRecorder); - DynamoSelection.Instance.Selection.AddUnique(model); + try + { + WorkspaceModel.RecordModelsForModification(new List() { model }, CurrentWorkspace.UndoRecorder); + DynamoSelection.Instance.Selection.AddUnique(model); + } + catch (Exception ex) + { + Logger.LogError("Failed to add model(s) to selection." + "\n" + ex.Message); + } } private void ClearSelectionAndRecordUndo()