Skip to content

Commit 7c1cbb4

Browse files
authored
feat(ui): add progress dialog with step-by-step indication (#32)
1 parent 18525fb commit 7c1cbb4

File tree

4 files changed

+298
-21
lines changed

4 files changed

+298
-21
lines changed

src/CodingWithCalvin.ProjectRenamifier/CodingWithCalvin.ProjectRenamifier.csproj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@
6969
<Compile Include="Dialogs\RenameProjectDialog.xaml.cs">
7070
<DependentUpon>RenameProjectDialog.xaml</DependentUpon>
7171
</Compile>
72+
<Compile Include="Dialogs\RenameProgressDialog.xaml.cs">
73+
<DependentUpon>RenameProgressDialog.xaml</DependentUpon>
74+
</Compile>
7275
<Compile Include="ProjectRenamifierPackage.cs" />
7376
<Compile Include="Services\ProjectFileService.cs" />
7477
<Compile Include="Services\ProjectReferenceService.cs" />
@@ -90,6 +93,10 @@
9093
<SubType>Designer</SubType>
9194
<Generator>MSBuild:Compile</Generator>
9295
</Page>
96+
<Page Include="Dialogs\RenameProgressDialog.xaml">
97+
<SubType>Designer</SubType>
98+
<Generator>MSBuild:Compile</Generator>
99+
</Page>
93100
</ItemGroup>
94101
<ItemGroup>
95102
<None Include="Resources\extension.manifest.json" />

src/CodingWithCalvin.ProjectRenamifier/Commands/RenamifyProjectCommand.cs

Lines changed: 97 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.ComponentModel.Design;
22
using System.IO;
33
using System.Windows.Interop;
4+
using System.Windows.Threading;
45
using CodingWithCalvin.ProjectRenamifier.Dialogs;
56
using CodingWithCalvin.ProjectRenamifier.Services;
67
using EnvDTE;
@@ -80,41 +81,116 @@ private void RenameProject(Project project, DTE2 dte)
8081
var newName = dialog.NewProjectName;
8182
var projectFilePath = project.FullName;
8283

83-
// Collect projects that reference this project before removal
84-
var referencingProjects = ProjectReferenceService.FindProjectsReferencingTarget(dte.Solution, projectFilePath);
84+
// Show progress dialog
85+
var progressDialog = new RenameProgressDialog(currentName);
86+
var progressHelper = new WindowInteropHelper(progressDialog)
87+
{
88+
Owner = dte.MainWindow.HWnd
89+
};
90+
progressDialog.Show();
91+
92+
var stepIndex = 0;
93+
94+
// Step 1: Collect projects that reference this project before removal
95+
ExecuteStep(progressDialog, stepIndex++, () =>
96+
{
97+
return ProjectReferenceService.FindProjectsReferencingTarget(dte.Solution, projectFilePath);
98+
}, out var referencingProjects);
8599
var oldProjectFilePath = projectFilePath;
86100

87-
// Capture the parent solution folder before removal
88-
var parentSolutionFolder = SolutionFolderService.GetParentSolutionFolder(project);
101+
// Step 2: Capture the parent solution folder before removal
102+
ExecuteStep(progressDialog, stepIndex++, () =>
103+
{
104+
return SolutionFolderService.GetParentSolutionFolder(project);
105+
}, out var parentSolutionFolder);
89106

90-
// Remove project from solution before file operations
91-
dte.Solution.Remove(project);
107+
// Step 3: Remove project from solution before file operations
108+
ExecuteStep(progressDialog, stepIndex++, () =>
109+
{
110+
dte.Solution.Remove(project);
111+
});
112+
113+
// Step 4: Update RootNamespace and AssemblyName in .csproj
114+
ExecuteStep(progressDialog, stepIndex++, () =>
115+
{
116+
ProjectFileService.UpdateProjectFile(projectFilePath, currentName, newName);
117+
});
92118

93-
// Update RootNamespace and AssemblyName in .csproj
94-
ProjectFileService.UpdateProjectFile(projectFilePath, currentName, newName);
119+
// Step 5: Update namespace declarations in source files
120+
ExecuteStep(progressDialog, stepIndex++, () =>
121+
{
122+
SourceFileService.UpdateNamespacesInProject(projectFilePath, currentName, newName);
123+
});
95124

96-
// Update namespace declarations in source files
97-
SourceFileService.UpdateNamespacesInProject(projectFilePath, currentName, newName);
125+
// Step 6: Rename the project file on disk
126+
ExecuteStep(progressDialog, stepIndex++, () =>
127+
{
128+
return ProjectFileService.RenameProjectFile(projectFilePath, newName);
129+
}, out projectFilePath);
98130

99-
// Rename the project file on disk
100-
projectFilePath = ProjectFileService.RenameProjectFile(projectFilePath, newName);
131+
// Step 7: Rename parent directory if it matches the old project name
132+
ExecuteStep(progressDialog, stepIndex++, () =>
133+
{
134+
return ProjectFileService.RenameParentDirectoryIfMatches(projectFilePath, currentName, newName);
135+
}, out projectFilePath);
101136

102-
// Rename parent directory if it matches the old project name
103-
projectFilePath = ProjectFileService.RenameParentDirectoryIfMatches(projectFilePath, currentName, newName);
137+
// Step 8: Update references in projects that referenced this project
138+
ExecuteStep(progressDialog, stepIndex++, () =>
139+
{
140+
ProjectReferenceService.UpdateProjectReferences(referencingProjects, oldProjectFilePath, projectFilePath);
141+
});
104142

105-
// Update references in projects that referenced this project
106-
ProjectReferenceService.UpdateProjectReferences(referencingProjects, oldProjectFilePath, projectFilePath);
143+
// Step 9: Re-add project to solution, preserving solution folder location
144+
ExecuteStep(progressDialog, stepIndex++, () =>
145+
{
146+
SolutionFolderService.AddProjectToSolution(dte.Solution, projectFilePath, parentSolutionFolder);
147+
});
107148

108-
// Re-add project to solution, preserving solution folder location
109-
SolutionFolderService.AddProjectToSolution(dte.Solution, projectFilePath, parentSolutionFolder);
149+
// Step 10: Update using statements across the entire solution
150+
ExecuteStep(progressDialog, stepIndex++, () =>
151+
{
152+
SourceFileService.UpdateUsingStatementsInSolution(dte.Solution, currentName, newName);
153+
});
110154

111-
// Update using statements across the entire solution
112-
SourceFileService.UpdateUsingStatementsInSolution(dte.Solution, currentName, newName);
155+
// Mark as complete and close after a brief delay
156+
progressDialog.Complete();
157+
DoEvents();
158+
System.Threading.Thread.Sleep(500);
159+
progressDialog.Close();
113160

114161
// TODO: Implement remaining rename operations
115162
// See open issues for requirements:
116-
// - #12: Progress indication
117163
// - #13: Error handling and rollback
118164
}
165+
166+
private void ExecuteStep(RenameProgressDialog dialog, int stepIndex, Action action)
167+
{
168+
dialog.StartStep(stepIndex);
169+
DoEvents();
170+
action();
171+
dialog.CompleteStep(stepIndex);
172+
DoEvents();
173+
}
174+
175+
private void ExecuteStep<T>(RenameProgressDialog dialog, int stepIndex, Func<T> func, out T result)
176+
{
177+
dialog.StartStep(stepIndex);
178+
DoEvents();
179+
result = func();
180+
dialog.CompleteStep(stepIndex);
181+
DoEvents();
182+
}
183+
184+
private void DoEvents()
185+
{
186+
var frame = new DispatcherFrame();
187+
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
188+
new DispatcherOperationCallback(obj =>
189+
{
190+
((DispatcherFrame)obj).Continue = false;
191+
return null;
192+
}), frame);
193+
Dispatcher.PushFrame(frame);
194+
}
119195
}
120196
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<Window x:Class="CodingWithCalvin.ProjectRenamifier.Dialogs.RenameProgressDialog"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
Title="Renaming Project"
5+
Width="400"
6+
Height="340"
7+
ResizeMode="NoResize"
8+
WindowStartupLocation="CenterOwner"
9+
ShowInTaskbar="False">
10+
<Grid Margin="16">
11+
<Grid.RowDefinitions>
12+
<RowDefinition Height="Auto"/>
13+
<RowDefinition Height="*"/>
14+
<RowDefinition Height="Auto"/>
15+
</Grid.RowDefinitions>
16+
17+
<TextBlock Grid.Row="0"
18+
x:Name="HeaderText"
19+
Text="Renaming project..."
20+
FontWeight="SemiBold"
21+
FontSize="14"
22+
Margin="0,0,0,16"/>
23+
24+
<ItemsControl Grid.Row="1"
25+
x:Name="StepsList"
26+
Margin="0,0,0,16">
27+
<ItemsControl.ItemTemplate>
28+
<DataTemplate>
29+
<Grid Margin="0,4">
30+
<Grid.ColumnDefinitions>
31+
<ColumnDefinition Width="24"/>
32+
<ColumnDefinition Width="*"/>
33+
</Grid.ColumnDefinitions>
34+
<TextBlock Grid.Column="0"
35+
Text="{Binding StatusIcon}"
36+
FontFamily="Segoe UI Symbol"
37+
VerticalAlignment="Center"/>
38+
<TextBlock Grid.Column="1"
39+
Text="{Binding Description}"
40+
Foreground="{Binding TextColor}"
41+
VerticalAlignment="Center"/>
42+
</Grid>
43+
</DataTemplate>
44+
</ItemsControl.ItemTemplate>
45+
</ItemsControl>
46+
47+
<ProgressBar Grid.Row="2"
48+
x:Name="ProgressBar"
49+
Height="8"
50+
IsIndeterminate="False"
51+
Minimum="0"
52+
Maximum="100"/>
53+
</Grid>
54+
</Window>
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
using System.Collections.ObjectModel;
2+
using System.ComponentModel;
3+
using System.Windows;
4+
using System.Windows.Media;
5+
6+
namespace CodingWithCalvin.ProjectRenamifier.Dialogs
7+
{
8+
public partial class RenameProgressDialog : Window
9+
{
10+
public ObservableCollection<ProgressStep> Steps { get; }
11+
12+
public RenameProgressDialog(string projectName)
13+
{
14+
InitializeComponent();
15+
16+
HeaderText.Text = $"Renaming '{projectName}'...";
17+
18+
Steps = new ObservableCollection<ProgressStep>
19+
{
20+
new ProgressStep("Analyzing project references"),
21+
new ProgressStep("Capturing solution structure"),
22+
new ProgressStep("Removing project from solution"),
23+
new ProgressStep("Updating project file"),
24+
new ProgressStep("Updating namespace declarations"),
25+
new ProgressStep("Renaming project file"),
26+
new ProgressStep("Renaming project directory"),
27+
new ProgressStep("Updating project references"),
28+
new ProgressStep("Re-adding project to solution"),
29+
new ProgressStep("Updating using statements")
30+
};
31+
32+
StepsList.ItemsSource = Steps;
33+
}
34+
35+
public void StartStep(int stepIndex)
36+
{
37+
if (stepIndex >= 0 && stepIndex < Steps.Count)
38+
{
39+
Steps[stepIndex].Status = StepStatus.InProgress;
40+
UpdateProgress(stepIndex);
41+
}
42+
}
43+
44+
public void CompleteStep(int stepIndex)
45+
{
46+
if (stepIndex >= 0 && stepIndex < Steps.Count)
47+
{
48+
Steps[stepIndex].Status = StepStatus.Completed;
49+
UpdateProgress(stepIndex + 1);
50+
}
51+
}
52+
53+
public void FailStep(int stepIndex, string error)
54+
{
55+
if (stepIndex >= 0 && stepIndex < Steps.Count)
56+
{
57+
Steps[stepIndex].Status = StepStatus.Failed;
58+
Steps[stepIndex].Description += $" - {error}";
59+
}
60+
}
61+
62+
public void Complete()
63+
{
64+
HeaderText.Text = "Rename completed successfully!";
65+
ProgressBar.Value = 100;
66+
}
67+
68+
private void UpdateProgress(int completedSteps)
69+
{
70+
ProgressBar.Value = (completedSteps * 100.0) / Steps.Count;
71+
}
72+
}
73+
74+
public enum StepStatus
75+
{
76+
Pending,
77+
InProgress,
78+
Completed,
79+
Failed
80+
}
81+
82+
public class ProgressStep : INotifyPropertyChanged
83+
{
84+
private string _description;
85+
private StepStatus _status;
86+
87+
public ProgressStep(string description)
88+
{
89+
_description = description;
90+
_status = StepStatus.Pending;
91+
}
92+
93+
public string Description
94+
{
95+
get => _description;
96+
set
97+
{
98+
_description = value;
99+
OnPropertyChanged(nameof(Description));
100+
}
101+
}
102+
103+
public StepStatus Status
104+
{
105+
get => _status;
106+
set
107+
{
108+
_status = value;
109+
OnPropertyChanged(nameof(Status));
110+
OnPropertyChanged(nameof(StatusIcon));
111+
OnPropertyChanged(nameof(TextColor));
112+
}
113+
}
114+
115+
public string StatusIcon => _status switch
116+
{
117+
StepStatus.Pending => "\u2022", // Bullet
118+
StepStatus.InProgress => "\u25B6", // Play arrow
119+
StepStatus.Completed => "\u2714", // Check mark
120+
StepStatus.Failed => "\u2716", // X mark
121+
_ => "\u2022"
122+
};
123+
124+
public Brush TextColor => _status switch
125+
{
126+
StepStatus.Pending => Brushes.Gray,
127+
StepStatus.InProgress => Brushes.Black,
128+
StepStatus.Completed => Brushes.Green,
129+
StepStatus.Failed => Brushes.Red,
130+
_ => Brushes.Gray
131+
};
132+
133+
public event PropertyChangedEventHandler PropertyChanged;
134+
135+
protected void OnPropertyChanged(string propertyName)
136+
{
137+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
138+
}
139+
}
140+
}

0 commit comments

Comments
 (0)