From fe892996e777c7f6f19bb089e5206218abaf8542 Mon Sep 17 00:00:00 2001 From: dibyanshu-pal-kushwaha Date: Wed, 4 Feb 2026 15:30:02 +0530 Subject: [PATCH 1/2] Guard against losing project description changes --- app/components/Project/About/About.js | 19 ++++++ app/components/Project/Project.js | 23 ++++--- app/containers/ProjectPage/ProjectPage.js | 79 ++++++++++++++++++++++- 3 files changed, 109 insertions(+), 12 deletions(-) diff --git a/app/components/Project/About/About.js b/app/components/Project/About/About.js index 7d2a9544..55dfc5e6 100644 --- a/app/components/Project/About/About.js +++ b/app/components/Project/About/About.js @@ -177,6 +177,24 @@ function About(props) { JSON.stringify(notes) !== JSON.stringify(initialState.notes) ); }; + + useEffect(() => { + // Only check for description changes + const isDescriptionDirty = + descriptionEditor !== initialState.descriptionEditor || + descriptionText !== initialState.descriptionText || + descriptionUri !== initialState.descriptionUri; + + if (props.onDirtyStateChange) { + props.onDirtyStateChange(isDescriptionDirty); + } + }, [ + descriptionEditor, + descriptionText, + descriptionUri, + initialState, + props.onDirtyStateChange, + ]); const updatedNoteHandler = (note, text) => { if (note) { @@ -368,6 +386,7 @@ About.propTypes = { onAddedNote: PropTypes.func, onDeletedNote: PropTypes.func, onClickUpdatesLink: PropTypes.func, + onDirtyStateChange: PropTypes.func, }; export default About; diff --git a/app/components/Project/Project.js b/app/components/Project/Project.js index 852f534d..0cde8ca4 100644 --- a/app/components/Project/Project.js +++ b/app/components/Project/Project.js @@ -22,7 +22,9 @@ import GeneralUtil from '../../utils/general'; import styles from './Project.css'; import UserContext from '../../contexts/User'; -type Props = {}; +type Props = { + onDirtyStateChange?: (boolean) => void, +}; class Project extends Component { props: Props; @@ -40,7 +42,7 @@ class Project extends Component { // Safely get and check the previous project ID and the current project ID from // the properties. If the project selection changes, we want the Dashboard tab // to be selected. This is the component-class response to useEffect(). - var previousId = (prevProps && prevProps.project ) ? prevProps.project.id : null; + var previousId = (prevProps && prevProps.project) ? prevProps.project.id : null; var currentId = (this.props && this.props.project) ? this.props.project.id : null; if (previousId != currentId) { this.setState({ selectedTab: 'about' }); @@ -297,7 +299,7 @@ class Project extends Component { * @param {object} note Optional parameter if there is an existing note being updated. If not provided, a new note is assumed. */ checklistUpsertNoteHandler = (checklistItem, text, note) => { - if (this.unchangedNote(note,text)) { + if (this.unchangedNote(note, text)) { return; } @@ -732,7 +734,7 @@ class Project extends Component { const user = this.context; const currentProject = { ...this.props.project }; const oldExternalAsset = cloneDeep(this.props.project.externalAssets.children ? - this.props.project.externalAssets.children.find(x => x.uri === asset.uri) : null); + this.props.project.externalAssets.children.find(x => x.uri === asset.uri) : null); const action = { type: ActionType.EXTERNAL_ASSET_UPDATED, title: ActionType.EXTERNAL_ASSET_UPDATED, @@ -792,6 +794,7 @@ class Project extends Component { project={this.props.project} updates={this.props.logs ? this.props.logs.updates : null} onClickUpdatesLink={this.clickUpdatesLinkHandler} + onDirtyStateChange={this.props.onDirtyStateChange} /> ) : null; const assets = this.props.project ? ( @@ -883,11 +886,11 @@ class Project extends Component {
this.props.onFavoriteClick(this.props.project.id)} - > - {this.props.project && this.props.project.favorite ? : } - + color="inherit" + onClick={() => this.props.onFavoriteClick(this.props.project.id)} + > + {this.props.project && this.props.project.favorite ? : } +
{name} {projectPath} @@ -948,6 +951,7 @@ Project.propTypes = { onUpdated: PropTypes.func, onAssetSelected: PropTypes.func, onChecklistUpdated: PropTypes.func, + onDirtyStateChange: PropTypes.func, // This object has the following structure: // { // logs: array - the actual log data @@ -975,6 +979,7 @@ Project.defaultProps = { onUpdated: null, onAssetSelected: null, onChecklistUpdated: null, + onDirtyStateChange: null, logs: null, checklistResponse: null, configuration: null, diff --git a/app/containers/ProjectPage/ProjectPage.js b/app/containers/ProjectPage/ProjectPage.js index 0bbb89cd..bf08d05b 100644 --- a/app/containers/ProjectPage/ProjectPage.js +++ b/app/containers/ProjectPage/ProjectPage.js @@ -11,6 +11,14 @@ import UserContext from '../../contexts/User'; import Messages from '../../constants/messages'; import ChecklistUtil from '../../utils/checklist'; import { PanelGroup, Panel, PanelResizeHandle } from 'react-resizable-panels'; +import { + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + Button, +} from '@mui/material'; class ProjectPage extends Component { constructor(props) { @@ -54,7 +62,13 @@ class ProjectPage extends Component { // Show progress on scanning details about the project. Allows the user to access what information // is available, yet be aware that more information may be arriving later. projectScanStatus: '', + isProjectDirty: false, + showDirtyConfirmation: false, + pendingProject: null, }; + this.handleProjectDirtyStateChange = this.handleProjectDirtyStateChange.bind(this); + this.handleDiscardChanges = this.handleDiscardChanges.bind(this); + this.handleCancelSwitch = this.handleCancelSwitch.bind(this); this.handleLoadProjectListResponse = this.handleLoadProjectListResponse.bind(this); this.refreshProjectsHandler = this.refreshProjectsHandler.bind(this); @@ -394,6 +408,9 @@ class ProjectPage extends Component { case Messages.REMOVE_PROJECT_LIST_ENTRY_REQUEST: ipcRenderer.send(Messages.REMOVE_PROJECT_LIST_ENTRY_REQUEST, projectId); break; + case Messages.SHOW_ITEM_IN_FOLDER: + ipcRenderer.send(Messages.SHOW_ITEM_IN_FOLDER, projectId); + break; default: console.warn(`Unknown project list entry menu event: ${event}`); } @@ -407,6 +424,23 @@ class ProjectPage extends Component { return; } + // If the user clicks the project that is already selected, ignore it. + if (this.state.selectedProject && this.state.selectedProject.id === project.id) { + return; + } + + if (this.state.isProjectDirty && this.state.selectedProject && this.state.selectedProject.id !== project.id) { + this.setState({ + showDirtyConfirmation: true, + pendingProject: project + }); + return; + } + + this.loadProject(project); + } + + loadProject(project) { // If the project is offline (has loadError), try to refresh its status if (project.loadError) { // First update the UI to show we're trying to reconnect @@ -446,6 +480,28 @@ class ProjectPage extends Component { } } + handleProjectDirtyStateChange(isDirty) { + this.setState({ isProjectDirty: isDirty }); + } + + handleDiscardChanges() { + this.setState({ + showDirtyConfirmation: false, + isProjectDirty: false + }, () => { + if (this.state.pendingProject) { + this.loadProject(this.state.pendingProject); + } + }); + } + + handleCancelSwitch() { + this.setState({ + showDirtyConfirmation: false, + pendingProject: null + }); + } + handleProjectUpdate(project, actionType, entityType, entityKey, title, description, details) { // Update our cached list of projects from which we get the selected projects. We want to ensure // these are kept in sync with any updates. @@ -534,6 +590,7 @@ class ProjectPage extends Component { configuration={{ assetAttributes: this.state.assetAttributes }} assetDynamicDetails={this.state.assetDynamicDetails} scanStatus={this.state.projectScanStatus} + onDirtyStateChange={this.handleProjectDirtyStateChange} /> @@ -547,12 +604,28 @@ class ProjectPage extends Component { anchorElement={ this.state.projectListMenuAnchor ? this.state.projectListMenuAnchor.element : null } - project={ - this.state.projectListMenuAnchor ? this.state.projectListMenuAnchor.project : null - } onClose={this.handleCloseProjectListMenu} onMenuClick={this.handleClickProjectListMenu} /> + + Discard Changes? + + + You have unsaved changes in the current project. Switching to another project will discard these changes. Are you sure you want to proceed? + + + + + + +
); } From 034a033b7a84015c553ad983b596af89481dc644 Mon Sep 17 00:00:00 2001 From: dibyanshu-pal-kushwaha Date: Thu, 5 Feb 2026 00:30:19 +0530 Subject: [PATCH 2/2] Fixing Discard changes UI --- app/containers/ProjectPage/ProjectPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/containers/ProjectPage/ProjectPage.js b/app/containers/ProjectPage/ProjectPage.js index bf08d05b..cca5e135 100644 --- a/app/containers/ProjectPage/ProjectPage.js +++ b/app/containers/ProjectPage/ProjectPage.js @@ -611,7 +611,7 @@ class ProjectPage extends Component { open={this.state.showDirtyConfirmation} onClose={this.handleCancelSwitch} > - Discard Changes? + Discard Changes You have unsaved changes in the current project. Switching to another project will discard these changes. Are you sure you want to proceed?