diff --git a/app/Routes.js b/app/Routes.js
index d989106e..5e8cc2c7 100644
--- a/app/Routes.js
+++ b/app/Routes.js
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useContext } from 'react';
import { HashRouter, Routes, Route } from 'react-router-dom';
import routes from './constants/routes.json';
import App from './containers/App';
@@ -6,6 +6,18 @@ import ProjectPage from './containers/ProjectPage/ProjectPage';
import ConfigurationPage from './containers/ConfigurationPage/ConfigurationPage';
import SearchPage from './containers/SearchPage/SearchPage';
import AboutPage from './containers/AboutPage/AboutPage';
+import ProjectContext from './contexts/Project';
+
+const ProjectPageWithContext = (props) => {
+ const { selectedProjectId, onSelectProject } = useContext(ProjectContext);
+ return (
+
+ );
+};
export default function AppRoutes() {
return (
@@ -15,7 +27,7 @@ export default function AppRoutes() {
} />
} />
} />
- } />
+ } />
diff --git a/app/containers/App.js b/app/containers/App.js
index 033e053e..85ce76db 100644
--- a/app/containers/App.js
+++ b/app/containers/App.js
@@ -25,6 +25,7 @@ import Messages from '../constants/messages';
import routes from '../constants/routes.json';
import UserContext from '../contexts/User';
import SettingsContext from '../contexts/Settings';
+import ProjectContext from '../contexts/Project';
import UserProfileDialog from './UserProfileDialog/UserProfileDialog';
import styles from './App.css';
import GeneralUtil from '../utils/general';
@@ -54,6 +55,7 @@ export default class App extends React.Component {
settings: {},
displayUserProfileDialog: false,
userProfileDialogKey: 0,
+ selectedProjectId: null,
};
this.handleLoadUserInfoResponse = this.handleLoadUserInfoResponse.bind(this);
@@ -62,6 +64,7 @@ export default class App extends React.Component {
this.handleCloseUserProfileDialog = this.handleCloseUserProfileDialog.bind(this);
this.handleOpenUserProfileDialog = this.handleOpenUserProfileDialog.bind(this);
this.handleSaveUserProfile = this.handleSaveUserProfile.bind(this);
+ this.handleSelectProject = this.handleSelectProject.bind(this);
}
componentDidMount() {
@@ -104,7 +107,7 @@ export default class App extends React.Component {
handleUpdateSearchSettingsResponse(sender, response) {
this.setState((prevState) => ({
- settings: {...prevState.settings, searchSettings: response.searchSettings }
+ settings: { ...prevState.settings, searchSettings: response.searchSettings }
}));
}
@@ -129,6 +132,10 @@ export default class App extends React.Component {
this.setState({ displayUserProfileDialog: true });
}
+ handleSelectProject(id) {
+ this.setState({ selectedProjectId: id });
+ }
+
render() {
const { children } = this.props;
const theme = createTheme({
@@ -180,40 +187,47 @@ export default class App extends React.Component {
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {children}
- {userProfileDialog}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {children}
+ {userProfileDialog}
+
diff --git a/app/containers/ProjectPage/ProjectPage.js b/app/containers/ProjectPage/ProjectPage.js
index 0bbb89cd..36efa827 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);
@@ -183,7 +197,18 @@ class ProjectPage extends Component {
}
handleLoadProjectListResponse(sender, response) {
- this.setState({ ...response, loaded: true });
+ this.setState({ ...response, loaded: true }, () => {
+ if (
+ this.props.selectedProjectId &&
+ (!this.state.selectedProject ||
+ this.state.selectedProject.id !== this.props.selectedProjectId)
+ ) {
+ const project = this.state.projects.find((p) => p.id === this.props.selectedProjectId);
+ if (project) {
+ this.loadProject(project);
+ }
+ }
+ });
}
handleLoadConfigurationResponse(sender, response) {
@@ -394,6 +419,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}`);
}
@@ -404,9 +432,32 @@ class ProjectPage extends Component {
// Handle case where user clicks off of all projects (project is null)
if (!project) {
this.setState({ selectedProject: null });
+ if (this.props.onSelectProject) {
+ this.props.onSelectProject(null);
+ }
+ 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;
+ }
+
+ if (this.props.onSelectProject) {
+ this.props.onSelectProject(project.id);
+ }
+ 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 +497,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 +607,7 @@ class ProjectPage extends Component {
configuration={{ assetAttributes: this.state.assetAttributes }}
assetDynamicDetails={this.state.assetDynamicDetails}
scanStatus={this.state.projectScanStatus}
+ onDirtyStateChange={this.handleProjectDirtyStateChange}
/>
@@ -547,12 +621,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}
/>
+
);
}
diff --git a/app/contexts/Project.js b/app/contexts/Project.js
new file mode 100644
index 00000000..09a62fa7
--- /dev/null
+++ b/app/contexts/Project.js
@@ -0,0 +1,4 @@
+import { createContext } from 'react';
+
+const ProjectContext = createContext();
+export default ProjectContext;