Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
05e4210
Fix the Save/Save As menu items hardcoded IsEnabled="False" in XAML
edwin-vasquez-ucaldas Jul 28, 2026
302d8c9
Adding unit tests
edwin-vasquez-ucaldas Jul 29, 2026
9db01b0
Fix unit test
edwin-vasquez-ucaldas Jul 29, 2026
eb2fb46
call OnWorkspaceOpened/raise RequestEnableShortcutBarItems(true) fro…
edwin-vasquez-ucaldas Jul 29, 2026
acdb691
Sync Save/Save As enablement across menu, hotkey, and toolbar via sha…
edwin-vasquez-ucaldas Jul 30, 2026
91f0700
Correctly reverted — this file is now staged with exactly the inverse…
edwin-vasquez-ucaldas Jul 30, 2026
7d50b3e
Merge branch 'master' into DYN-10717_Fix_disabled_Save_and_Save_As_op…
RobertGlobant20 Jul 30, 2026
aed88c5
Fixing tests were failing. Add new test focus into the Guided Tour
edwin-vasquez-ucaldas Jul 31, 2026
b59c24b
Removed the duplicated isGuidedTourActive field and SetGuidedTourActi…
edwin-vasquez-ucaldas Aug 3, 2026
f93e6f3
Fixing unit tests. Stopped relying on ICommandSource's built-in coerc…
edwin-vasquez-ucaldas Aug 3, 2026
48702f4
Fix: Check for whether the model's position/size/any property actuall…
edwin-vasquez-ucaldas Aug 5, 2026
313ef50
Fix: passing each path segment separately, which resolves the analyze…
edwin-vasquez-ucaldas Aug 5, 2026
cd63a7b
Fix: raw stack depth conflated "position in undo history" with "conte…
edwin-vasquez-ucaldas Aug 5, 2026
f4ecaa5
Fix SonarQube analysis issues.
edwin-vasquez-ucaldas Aug 5, 2026
f886a97
Fix SonarQube analysis issues.
edwin-vasquez-ucaldas Aug 6, 2026
e2395b8
Apply minor fixes
edwin-vasquez-ucaldas Aug 6, 2026
936d742
Fix: UpdateHasUnsavedChangesFromSavedStateAffectingDepth() overwrites…
edwin-vasquez-ucaldas Aug 6, 2026
fc8f8d0
Apply all fixes for the independentDirtyFlag/MarkAsIndependentlyModif…
edwin-vasquez-ucaldas Aug 6, 2026
828a04a
Removing some comments
edwin-vasquez-ucaldas Aug 6, 2026
d648411
Merge remote-tracking branch 'origin/master' into DYN-10717_Fix_disab…
edwin-vasquez-ucaldas Aug 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 0 additions & 14 deletions src/DynamoCoreWpf/Controls/ShortcutToolbar.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ public ShortcutToolbar(DynamoViewModel dynamoViewModel)

private void ShortcutToolbar_Loaded(object sender, RoutedEventArgs e)
{
IsSaveButtonEnabled = false;
IsExportMenuEnabled = false;
IsLoginMenuEnabled = !DynamoViewModel.Model.NoNetworkMode;
DynamoViewModel.OnRequestShorcutToolbarLoaded(RightMenu.ActualWidth);
Expand Down Expand Up @@ -245,19 +244,6 @@ internal bool IsOpenButtonEnabled
}
}

internal bool IsSaveButtonEnabled
{
set
{
Button saveButton = GetButton("SAVE");
if (saveButton != null)
{
saveButton.IsEnabled = value;
saveButton.Opacity = value ? 1 : 0.5;
}
}
}

internal bool IsLoginMenuEnabled
{
set
Expand Down
2 changes: 2 additions & 0 deletions src/DynamoCoreWpf/UI/GuidedTour/GuidesManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ internal void LaunchTour(string tourName)
GuideFlowEvents.OnGuidedTourStart(tourName);
dynamoViewModel.ToastManager?.CloseRealTimeInfoWindow();
dynamoViewModel.OnEnableShortcutBarItems(false);
dynamoViewModel.SetGuidedTourActive(true);
Logging.Analytics.TrackScreenView("InteractiveGuidedTours");
Logging.Analytics.TrackEvent(Logging.Actions.Start, Logging.Categories.GuidedTourOperations, Resources.ResourceManager.GetString(currentGuide.GuideNameResource, System.Globalization.CultureInfo.InvariantCulture).Replace("_", ""), currentGuide.SequenceOrder);
}
Expand Down Expand Up @@ -257,6 +258,7 @@ internal void ExitTour()
}

dynamoViewModel.OnEnableShortcutBarItems(true);
dynamoViewModel.SetGuidedTourActive(false);

//Hide guide background overlay
guideBackgroundElement.Visibility = Visibility.Hidden;
Expand Down
56 changes: 54 additions & 2 deletions src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ public partial class DynamoViewModel : ViewModelBase, IDynamoViewModel
private readonly DynamoModel model;
private Point transformOrigin;
private bool showStartPage = false;
private bool isGuidedTourActive = false;
Comment thread
edwin-vasquez-ucaldas marked this conversation as resolved.
Outdated
private WorkspaceModel saveCommandsTrackedWorkspace;
private PreferencesViewModel preferencesViewModel;
private string dynamoMLDataPath = string.Empty;
private const string dynamoMLDataFileName = "DynamoMLDataPipeline.json";
Expand Down Expand Up @@ -428,6 +430,9 @@ public bool ShowStartPage

if(ShowInsertDialogAndInsertResultCommand != null)
ShowInsertDialogAndInsertResultCommand.RaiseCanExecuteChanged();

ShowSaveDialogIfNeededAndSaveResultCommand?.RaiseCanExecuteChanged();
ShowSaveDialogAndSaveResultCommand?.RaiseCanExecuteChanged();
}
}

Expand Down Expand Up @@ -898,6 +903,7 @@ protected DynamoViewModel(StartConfiguration startConfiguration)
SubscribeModelUiEvents();
SubscribeModelChangedHandlers();
SubscribeModelBackupFileSaveEvent();
TrackWorkspaceForSaveCommands(model.CurrentWorkspace);

InitializeAutomationSettings(startConfiguration.CommandFilePath);

Expand Down Expand Up @@ -1304,6 +1310,32 @@ private void UnsubscribeModelChangedEvents()
model.PropertyChanged -= _model_PropertyChanged;
model.WorkspaceCleared -= ModelWorkspaceCleared;
model.RequestCancelActiveStateForNode -= this.CancelActiveState;
TrackWorkspaceForSaveCommands(null);
}

/// <summary>
/// Keeps the Save command's CanExecute in sync with the current workspace's dirty
/// flag: unsubscribes from the previously tracked workspace and subscribes to the
/// new one, then re-evaluates CanExecute immediately (the new workspace may already
/// differ in HasUnsavedChanges from the old one).
/// </summary>
private void TrackWorkspaceForSaveCommands(WorkspaceModel workspace)
{
if (saveCommandsTrackedWorkspace != null)
saveCommandsTrackedWorkspace.PropertyChanged -= SaveCommandsTrackedWorkspace_PropertyChanged;

saveCommandsTrackedWorkspace = workspace;

if (saveCommandsTrackedWorkspace != null)
saveCommandsTrackedWorkspace.PropertyChanged += SaveCommandsTrackedWorkspace_PropertyChanged;

ShowSaveDialogIfNeededAndSaveResultCommand?.RaiseCanExecuteChanged();
}

private void SaveCommandsTrackedWorkspace_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(WorkspaceModel.HasUnsavedChanges))
ShowSaveDialogIfNeededAndSaveResultCommand.RaiseCanExecuteChanged();
}

private void SubscribeDispatcherHandlers()
Expand Down Expand Up @@ -1482,6 +1514,7 @@ void _model_PropertyChanged(object sender, PropertyChangedEventArgs e)
RaisePropertyChanged("ViewingHomespace");
if (this.PublishCurrentWorkspaceCommand != null)
this.PublishCurrentWorkspaceCommand.RaiseCanExecuteChanged();
TrackWorkspaceForSaveCommands(model.CurrentWorkspace);
RaisePropertyChanged("IsPanning");
RaisePropertyChanged("IsOrbiting");
//RaisePropertyChanged("RunEnabled");
Expand Down Expand Up @@ -3245,9 +3278,28 @@ public void ShowSaveDialogIfNeededAndSaveResult(object parameter)
}
}

/// <summary>
/// "Save" is only meaningful when there is something new to persist, so it is also
/// gated on the current workspace's dirty flag (unlike "Save As", which can always
/// save a copy regardless of whether anything changed).
/// </summary>
internal bool CanShowSaveDialogIfNeededAndSaveResultCommand(object parameter)
{
return true;
return !isGuidedTourActive && !ShowStartPage && (Model.CurrentWorkspace?.HasUnsavedChanges ?? false);
Comment thread
edwin-vasquez-ucaldas marked this conversation as resolved.
Outdated
}

/// <summary>
/// Blocks or unblocks the Save/Save As commands (menu items, shortcut bar, and
/// Ctrl+S/Ctrl+Shift+S) while a guided tour is active. Unlike ShowStartPage, this is
/// not tied to workspace-creation flows, so it can safely gate CanExecute without
/// resurrecting DYN-10717 (Save/Save As stuck disabled on a fresh workspace).
/// </summary>
/// <param name="isActive">Whether a guided tour is currently active.</param>
internal void SetGuidedTourActive(bool isActive)
{
isGuidedTourActive = isActive;
ShowSaveDialogIfNeededAndSaveResultCommand.RaiseCanExecuteChanged();
ShowSaveDialogAndSaveResultCommand.RaiseCanExecuteChanged();
}

public void ShowSaveDialogAndSaveResult(object parameter)
Expand Down Expand Up @@ -3371,7 +3423,7 @@ private bool ShowWarningDialogOnSaveWithUnresolvedIssues()

internal bool CanShowSaveDialogAndSaveResult(object parameter)
{
return true;
return !isGuidedTourActive && !ShowStartPage;
}

public void ToggleFullscreenWatchShowing(object parameter)
Expand Down
6 changes: 2 additions & 4 deletions src/DynamoCoreWpf/Views/Core/DynamoView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -354,13 +354,11 @@
<MenuItem Name="saveThisButton"
Command="{Binding ShowSaveDialogIfNeededAndSaveResultCommand}"
Header="{x:Static p:Resources.DynamoViewFileMenuSave}"
InputGestureText="Ctrl + S"
IsEnabled="False" />
Comment thread
edwin-vasquez-ucaldas marked this conversation as resolved.
InputGestureText="Ctrl + S" />
<MenuItem Name="saveButton"
Command="{Binding ShowSaveDialogAndSaveResultCommand}"
Header="{x:Static p:Resources.DynamoViewFileMenuSaveAs}"
InputGestureText="Ctrl + Shift + S"
IsEnabled="False" />
InputGestureText="Ctrl + Shift + S" />
<Separator />
<MenuItem Name="importLibrary"
Command="{Binding Path=DataContext.ImportLibraryCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type controls:DynamoView}}}"
Expand Down
13 changes: 0 additions & 13 deletions src/DynamoCoreWpf/Views/Core/DynamoView.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -435,11 +435,6 @@ void DynamoView_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)

private void DynamoViewModel_RequestEnableShortcutBarItems(bool enable)
{
if (!(saveThisButton is null))
{
saveThisButton.IsEnabled = enable;
saveButton.IsEnabled = enable;
}
if (!(exportMenu is null))
{
exportMenu.IsEnabled = enable;
Comment thread
edwin-vasquez-ucaldas marked this conversation as resolved.
Expand All @@ -449,7 +444,6 @@ private void DynamoViewModel_RequestEnableShortcutBarItems(bool enable)
{
shortcutBar.IsNewButtonEnabled = enable;
shortcutBar.IsOpenButtonEnabled = enable;
shortcutBar.IsSaveButtonEnabled = enable;
shortcutBar.IsLoginMenuEnabled = enable;
shortcutBar.IsExportMenuEnabled = enable;
shortcutBar.IsNotificationCenterEnabled = enable;
Expand All @@ -471,19 +465,12 @@ private void DynamoViewModel_RequestEnableShortcutBarItems(bool enable)

private void OnWorkspaceOpened(WorkspaceModel workspace)
{
if (!(saveThisButton is null))
{
saveThisButton.IsEnabled = true;
saveButton.IsEnabled = true;
}

if (!(exportMenu is null))
{
exportMenu.IsEnabled = true;
}
if (!(shortcutBar is null))
{
ShortcutBar.IsSaveButtonEnabled = true;
shortcutBar.IsExportMenuEnabled = true;
}

Expand Down
41 changes: 41 additions & 0 deletions test/DynamoCoreWpfTests/DynamoViewTests.cs
Comment thread
edwin-vasquez-ucaldas marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,47 @@ public void TestHomeWorkspaceClosedBeforeCustomNode()
Assert.IsTrue(View.saveButton.IsEnabled);
}

[Test]
public void WhenDynamoLaunchesThenSaveAsIsEnabledButSaveIsDisabledUntilDirty()
{
// Regression test for DYN-10717, updated per Jira thread consensus (comments on
// 2026-07-29): the bug was the File > Save/Save As menu items being hardcoded
// IsEnabled="False" forever, independent of the underlying command state. The
// fix keeps them driven by a shared CanExecute instead. "Save As" is enabled on
// a fresh workspace; plain "Save" is additionally gated on the dirty flag, since
// there is nothing new to persist until the graph changes.
Assert.IsFalse(View.saveThisButton.IsEnabled);
Assert.IsTrue(View.saveButton.IsEnabled);

ViewModel.HomeSpace.HasUnsavedChanges = true;

Assert.IsTrue(View.saveThisButton.IsEnabled);
}

[Test]
public void WhenLastWorkspaceIsClosedThenSaveMenuItemsAreDisabled()
{
// Regression test for DYN-10717, updated per Jira thread consensus (comments on
// 2026-07-29): closing the only open workspace returns to the Start Page (Home
// tab), where Save/Save As should be disabled -- confirmed as intended behavior,
// not the bug. The bug was the inconsistency between the menu, hotkey, and
// toolbar, which is now resolved via the shared CanExecute.
var wasTestMode = DynamoModel.IsTestMode;
try
{
DynamoModel.IsTestMode = false;
ViewModel.CloseHomeWorkspaceCommand.Execute(null);
}
finally
{
DynamoModel.IsTestMode = wasTestMode;
}

Assert.IsTrue(ViewModel.ShowStartPage);
Assert.IsFalse(View.saveThisButton.IsEnabled);
Assert.IsFalse(View.saveButton.IsEnabled);
}

[Test]
public void ElementBinding_SaveAs()
{
Expand Down
Loading