diff --git a/src/NexusMods.App.UI/WorkspaceSystem/Page/APageViewModel.cs b/src/NexusMods.App.UI/WorkspaceSystem/Page/APageViewModel.cs
index d131c48503..83b922ef3e 100644
--- a/src/NexusMods.App.UI/WorkspaceSystem/Page/APageViewModel.cs
+++ b/src/NexusMods.App.UI/WorkspaceSystem/Page/APageViewModel.cs
@@ -85,4 +85,7 @@ public string TabTitle
public PanelTabId TabId { get; set; }
protected PageIdBundle IdBundle => new(WindowId, WorkspaceId, PanelId, TabId);
+
+ ///
+ public virtual bool CanClose() => true;
}
diff --git a/src/NexusMods.App.UI/WorkspaceSystem/Page/IPageViewModelInterface.cs b/src/NexusMods.App.UI/WorkspaceSystem/Page/IPageViewModelInterface.cs
index 2468b98b71..dffb0412ed 100644
--- a/src/NexusMods.App.UI/WorkspaceSystem/Page/IPageViewModelInterface.cs
+++ b/src/NexusMods.App.UI/WorkspaceSystem/Page/IPageViewModelInterface.cs
@@ -1,37 +1,49 @@
+using JetBrains.Annotations;
using NexusMods.App.UI.Windows;
using NexusMods.Icons;
namespace NexusMods.App.UI.WorkspaceSystem;
+[PublicAPI]
public interface IPageViewModelInterface : IViewModelInterface
{
///
/// Gets or sets the icon of this page to be shown in the tab header.
///
- public IconValue TabIcon { get; }
+ IconValue TabIcon { get; }
///
- /// Gets or sets the title of this page in the tab header.
+ /// Gets the title of this page in the tab header.
///
- public string TabTitle { get; }
+ string TabTitle { get; }
///
/// Gets or sets the ID of the window this page is in.
///
- public WindowId WindowId { get; set; }
+ WindowId WindowId { get; set; }
///
/// Gets or sets the ID of the workspace this page is in.
///
- public WorkspaceId WorkspaceId { get; set; }
+ WorkspaceId WorkspaceId { get; set; }
///
/// Gets or sets the ID of the panel this page is in.
///
- public PanelId PanelId { get; set; }
+ PanelId PanelId { get; set; }
///
/// Gets or sets the ID of the tab this page is in.
///
- public PanelTabId TabId { get; set; }
+ PanelTabId TabId { get; set; }
+
+ ///
+ /// Called before the tab is closed.
+ ///
+ ///
+ /// Use this method for pages that might contain unsaved data or need to run
+ /// logic on close.
+ ///
+ /// true if the tab can be closed, false if not.
+ bool CanClose();
}
diff --git a/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelViewModel.cs b/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelViewModel.cs
index 980e5ee8da..49f598a1b7 100644
--- a/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelViewModel.cs
+++ b/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelViewModel.cs
@@ -193,6 +193,11 @@ public void SelectTab(PanelTabId tabId)
public void CloseTab(PanelTabId id)
{
+ var tab = _tabs.FirstOrDefault(tab => tab.Id == id);
+ if (tab is null) return;
+
+ if (!tab.Contents.ViewModel.CanClose()) return;
+
_tabsList.Edit(updater =>
{
var index = updater.LinearSearch(item => item.Id == id);
diff --git a/src/NexusMods.App.UI/WorkspaceSystem/Workspace/WorkspaceViewModel.cs b/src/NexusMods.App.UI/WorkspaceSystem/Workspace/WorkspaceViewModel.cs
index ac1f67a8c0..256b143bdc 100644
--- a/src/NexusMods.App.UI/WorkspaceSystem/Workspace/WorkspaceViewModel.cs
+++ b/src/NexusMods.App.UI/WorkspaceSystem/Workspace/WorkspaceViewModel.cs
@@ -343,6 +343,8 @@ private void OpenPageReplaceTab(PageData pageData, OpenPageBehavior.ReplaceTab r
return;
}
+ if (!tab.Contents.ViewModel.CanClose()) return;
+
// Replace the tab contents
var newTabPage = _factoryController.Create(pageData, WindowId, Id, panel.Id, tab.Id);
tab.Header.Icon = newTabPage.ViewModel.TabIcon;