improve functions#732
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds UI support for removing a recent project via right-click and changes how the “recent projects” list is represented and persisted in the app (moving to an observable collection and updating settings on save).
Changes:
- Added
DeletePreviousProjectCommandtoMainWindowVMand wired it to a newDeletecontext-menu item for recent projects. - Changed
PreviousProjectsfrom aReadOnlyCollectionbacked by aListto aReadOnlyObservableCollectionbacked by anObservableCollection. - Updated recent-project list update/persistence logic to occur during
SaveAsync()(and during deletion).
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/MSDIAL5/MsdialGuiApp/ViewModel/Core/MainWindowVM.cs | Adds a delete command and switches exposed PreviousProjects type to observable. |
| src/MSDIAL5/MsdialGuiApp/View/Core/MainWindow.xaml | Adds context menu for deleting a recent project and introduces a BindingProxy resource. |
| src/MSDIAL5/MsdialGuiApp/Model/Core/ProjectModel.cs | Minor formatting-only change (extra blank lines). |
| src/MSDIAL5/MsdialGuiApp/Model/Core/MainWindowModel.cs | Converts recent projects storage to ObservableCollection, updates dedupe logic, persists recents on save, and adds delete method. |
Comments suppressed due to low confidence (2)
src/MSDIAL5/MsdialGuiApp/Model/Core/MainWindowModel.cs:147
- In
LoadAsync(), the code awaitsProjectModel.LoadAsync(...).ConfigureAwait(false)and then mutatesCurrentProjectand_previousProjects. With_previousProjectsnow being anObservableCollectionbound to the UI, these mutations can run on a thread-pool thread and cause cross-thread exceptions. Resume on the UI thread for the post-load state updates (e.g., removeConfigureAwait(false)here or dispatch the state/collection updates via the Dispatcher).
var loadedProject = await ProjectModel.LoadAsync(projectPath, _broker).ConfigureAwait(false);
if (loadedProject is null) {
_broker.Publish(new ShortMessageRequest("Project loading has failed."));
return;
}
CurrentProject = loadedProject;
var currentCrumb = new ProjectCrumb(loadedProject.Storage.ProjectParameter);
if (_previousProjects.Any(currentCrumb.MaybeSame)) {
var resembleProjects = _previousProjects.Where(currentCrumb.MaybeSame).ToList();
foreach (var resembleProject in resembleProjects)
src/MSDIAL5/MsdialGuiApp/Model/Core/MainWindowModel.cs:97
SaveAsync()removes only the first matching entry (_previousProjects.First(currentCrumb.MaybeSame)) while other code paths (load/new project) remove all matching entries. BecauseProjectCrumb.MaybeSame()compares onlyTitle, this can leave duplicate recent entries when multiple crumbs share a title. Consider removing all matches consistently (same approach as inSetNewProject()/LoadAsync()).
var currentCrumb = new ProjectCrumb(CurrentProject.Storage.ProjectParameter);
if (_previousProjects.Any(currentCrumb.MaybeSame))
{
_previousProjects.Remove(_previousProjects.First(currentCrumb.MaybeSame));
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| using (nowSaving.ProcessStart()) { | ||
| await CurrentProject.SaveAsync().ConfigureAwait(false); | ||
| await CurrentProject.SaveAsync().ConfigureAwait(true); | ||
| var currentCrumb = new ProjectCrumb(CurrentProject.Storage.ProjectParameter); | ||
| if (_previousProjects.Any(currentCrumb.MaybeSame)) | ||
| { | ||
| _previousProjects.Remove(_previousProjects.First(currentCrumb.MaybeSame)); | ||
| } | ||
| _previousProjects.Insert(0, currentCrumb); | ||
| if (_previousProjects.Count > 50) |
| public async Task DeleteProjectAsync(ProjectCrumb projectCrumb){ | ||
| using (nowLoading.ProcessStart()){ | ||
| try | ||
| { | ||
| if (projectCrumb.FilePath is null || !File.Exists(projectCrumb.FilePath)) | ||
| { | ||
| return; | ||
| } | ||
| var resembleProjects = _previousProjects.Where(projectCrumb.MaybeSame).ToList(); |
| <Button.ContextMenu> | ||
| <ContextMenu> | ||
| <MenuItem Header="Delete" | ||
| Command="{Binding Data, Source={StaticResource delete}}" | ||
| CommandParameter="{Binding}"/> | ||
| </ContextMenu> |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…bout this function.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (2)
src/MSDIAL5/MsdialGuiApp/Model/Core/MainWindowModel.cs:193
_previousProjectsis anObservableCollection, but it is modified afterawait ProjectModel.LoadAsync(...).ConfigureAwait(true). UsingConfigureAwait(true)here is inconsistent with the codebase’s generalConfigureAwait(false)usage and still doesn’t guarantee the continuation is on the WPF dispatcher if this method is invoked from a non-UI context. PreferConfigureAwait(false)for the load and explicitly dispatch the_previousProjectsmutations to the UI thread.
var loadedProject = await ProjectModel.LoadAsync(projectCrumb.FilePath, _broker).ConfigureAwait(true);
if (loadedProject is null) {
_broker.Publish(new ShortMessageRequest("Project loading has failed."));
return;
}
CurrentProject = loadedProject;
var currentCrumb = new ProjectCrumb(loadedProject.Storage.ProjectParameter);
if (_previousProjects.Any(currentCrumb.MaybeSame)) {
var resembleProjects = _previousProjects.Where(currentCrumb.MaybeSame).ToList();
foreach (var resembleProject in resembleProjects)
{
_previousProjects.Remove(resembleProject);
}
}
_previousProjects.Insert(0, currentCrumb);
if (_previousProjects.Count > 50) {
while (_previousProjects.Count > 50) {
_previousProjects.RemoveAt(_previousProjects.Count - 1);
}
}
src/MSDIAL5/MsdialGuiApp/Model/Core/MainWindowModel.cs:214
- The method name
DeleteProjectAsyncsuggests deleting a project (potentially on disk), but the UI text is "Remove from recent projects" and the implementation only removes entries from_previousProjects/settings. Renaming this method/command to something likeRemoveRecentProjectAsync(and correspondingRemovePreviousProjectCommand) would avoid confusion and reduce the risk of future misuse.
public async Task DeleteProjectAsync(ProjectCrumb projectCrumb){
using (nowLoading.ProcessStart()){
try
{
var resembleProjects = _previousProjects.Where(projectCrumb.MaybeSame).ToList();
foreach (var resembleProject in resembleProjects)
{
_previousProjects.Remove(resembleProject);
}
_settings.PreviousProjects = PreviousProjects.ToList();
_settings.Save();
| </Button.ToolTip> | ||
| </Button> | ||
| <Button.ContextMenu> | ||
| <ContextMenu> |
| var resembleProjects = _previousProjects.Where(currentCrumb.MaybeSame).ToList(); | ||
| foreach (var resembleProject in resembleProjects) | ||
| { | ||
| _previousProjects.Remove(resembleProject); | ||
| } | ||
| } | ||
| _previousProjects.Insert(0, currentCrumb); | ||
| if (_previousProjects.Count > 50) { | ||
| _previousProjects.RemoveRange(50, _previousProjects.Count - 50); | ||
| while (_previousProjects.Count > 50) { | ||
| _previousProjects.RemoveAt(_previousProjects.Count - 1); | ||
| } |
| await CurrentProject.SaveAsync().ConfigureAwait(true); | ||
| var currentCrumb = new ProjectCrumb(CurrentProject.Storage.ProjectParameter); | ||
| if (_previousProjects.Any(currentCrumb.MaybeSame)) | ||
| { | ||
| _previousProjects.Remove(_previousProjects.First(currentCrumb.MaybeSame)); | ||
| } | ||
| _previousProjects.Insert(0, currentCrumb); | ||
| if (_previousProjects.Count > 50) | ||
| { | ||
| while (_previousProjects.Count > 50) | ||
| { | ||
| _previousProjects.RemoveAt(_previousProjects.Count - 1); | ||
| } | ||
| } | ||
| _settings.PreviousProjects = PreviousProjects.ToList(); | ||
| _settings.Save(); |
| if (_previousProjects.Any(currentCrumb.MaybeSame)) | ||
| { | ||
| _previousProjects.Remove(_previousProjects.First(currentCrumb.MaybeSame)); |
| using (nowLoading.ProcessStart()){ | ||
| try | ||
| { | ||
| var resembleProjects = _previousProjects.Where(projectCrumb.MaybeSame).ToList(); | ||
| foreach (var resembleProject in resembleProjects) | ||
| { | ||
| _previousProjects.Remove(resembleProject); | ||
| } | ||
| _settings.PreviousProjects = PreviousProjects.ToList(); | ||
| _settings.Save(); | ||
| } | ||
| catch | ||
| { | ||
| await Application.Current.Dispatcher.InvokeAsync(() => { | ||
| MessageBox.Show("Failed to delete project.\nPlease check your project."); | ||
| return Task.CompletedTask; | ||
| }); | ||
| } | ||
| } |
Add function to delete current project by right clicking.
Chande the save timing of loded projects.