Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions app/Routes.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
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';
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 (
<ProjectPage
{...props}
selectedProjectId={selectedProjectId}
onSelectProject={onSelectProject}
/>
);
};

export default function AppRoutes() {
return (
Expand All @@ -15,7 +27,7 @@ export default function AppRoutes() {
<Route path={routes.ABOUT} element={<AboutPage />} />
<Route path={routes.CONFIGURATION} element={<ConfigurationPage />} />
<Route path={routes.SEARCH} element={<SearchPage />} />
<Route path={routes.HOME} element={<ProjectPage />} />
<Route path={routes.HOME} element={<ProjectPageWithContext />} />
</Routes>
</App>
</HashRouter>
Expand Down
84 changes: 49 additions & 35 deletions app/containers/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -54,6 +55,7 @@ export default class App extends React.Component {
settings: {},
displayUserProfileDialog: false,
userProfileDialogKey: 0,
selectedProjectId: null,
};

this.handleLoadUserInfoResponse = this.handleLoadUserInfoResponse.bind(this);
Expand All @@ -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() {
Expand Down Expand Up @@ -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 }
}));
}

Expand All @@ -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({
Expand Down Expand Up @@ -180,40 +187,47 @@ export default class App extends React.Component {
<ThemeProvider theme={theme}>
<UserContext.Provider value={this.state.user}>
<SettingsContext.Provider value={this.state.settings}>
<AppBar position="static">
<Toolbar className={styles.toolbar}>
<img alt="StatWrap logo" src="images/banner.png" />
<section className={styles.rightToolbar}>
<Link to={routes.HOME} className={styles.navigation}>
<IconButton aria-label="home">
<HomeIcon />
</IconButton>
</Link>
<Link to={routes.SEARCH} className={styles.navigation}>
<IconButton aria-label="search">
<SearchIcon />
</IconButton>
</Link>
<Link to={routes.CONFIGURATION} className={styles.navigation}>
<IconButton aria-label="settings">
<SettingsIcon />
</IconButton>
</Link>
<Link to={routes.ABOUT} className={styles.navigation}>
<IconButton aria-label="settings">
<AboutIcon />
</IconButton>
</Link>
<div className={styles.user}>
<a className={styles.userButton} onClick={this.handleOpenUserProfileDialog}>
{this.state.displayName}
</a>
</div>
</section>
</Toolbar>
</AppBar>
{children}
{userProfileDialog}
<ProjectContext.Provider
value={{
selectedProjectId: this.state.selectedProjectId,
onSelectProject: this.handleSelectProject,
}}
>
<AppBar position="static">
<Toolbar className={styles.toolbar}>
<img alt="StatWrap logo" src="images/banner.png" />
<section className={styles.rightToolbar}>
<Link to={routes.HOME} className={styles.navigation}>
<IconButton aria-label="home">
<HomeIcon />
</IconButton>
</Link>
<Link to={routes.SEARCH} className={styles.navigation}>
<IconButton aria-label="search">
<SearchIcon />
</IconButton>
</Link>
<Link to={routes.CONFIGURATION} className={styles.navigation}>
<IconButton aria-label="settings">
<SettingsIcon />
</IconButton>
</Link>
<Link to={routes.ABOUT} className={styles.navigation}>
<IconButton aria-label="settings">
<AboutIcon />
</IconButton>
</Link>
<div className={styles.user}>
<a className={styles.userButton} onClick={this.handleOpenUserProfileDialog}>
{this.state.displayName}
</a>
</div>
</section>
</Toolbar>
</AppBar>
{children}
{userProfileDialog}
</ProjectContext.Provider>
</SettingsContext.Provider>
</UserContext.Provider>
</ThemeProvider>
Expand Down
98 changes: 94 additions & 4 deletions app/containers/ProjectPage/ProjectPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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}`);
}
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -534,6 +607,7 @@ class ProjectPage extends Component {
configuration={{ assetAttributes: this.state.assetAttributes }}
assetDynamicDetails={this.state.assetDynamicDetails}
scanStatus={this.state.projectScanStatus}
onDirtyStateChange={this.handleProjectDirtyStateChange}
/>
</Panel>
</PanelGroup>
Expand All @@ -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}
/>
<Dialog
open={this.state.showDirtyConfirmation}
onClose={this.handleCancelSwitch}
>
<DialogTitle>Discard Changes?</DialogTitle>
<DialogContent>
<DialogContentText>
You have unsaved changes in the current project. Switching to another project will discard these changes. Are you sure you want to proceed?
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={this.handleDiscardChanges} color="primary" autoFocus>
Discard Changes
</Button>
<Button onClick={this.handleCancelSwitch} color="secondary">
Cancel
</Button>
</DialogActions>
</Dialog>
</div>
);
}
Expand Down
4 changes: 4 additions & 0 deletions app/contexts/Project.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { createContext } from 'react';

const ProjectContext = createContext();
export default ProjectContext;