Skip to content

Commit

Permalink
task/WG-206: Add layout to map project page (#200)
Browse files Browse the repository at this point in the history
* Add initial layout

* Move zoom control to bottom-right

* Use query param to activate panel

* Fix prettier

* Fix linting

* Fix linting issue

* Use QueryNavItem

* Refactor into navbar component

* Imrove styling

* Rename top nav bar to be more precise

* Use asset images for navbar

* Rework properites in AssetPanel and MangeMapProjectModal

* Make isPublic no longer optional

* Add unit test

* Update query param for panel but do not alter other query params

* Add workaround so Wizard doesn't fail tests

* Remove console.log statement

* Fix prettier issue
  • Loading branch information
nathanfranklin authored Feb 29, 2024
1 parent 41a026c commit f33c3ed
Show file tree
Hide file tree
Showing 23 changed files with 1,785 additions and 3,054 deletions.
1 change: 1 addition & 0 deletions react/jest.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ module.exports = {

// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
moduleNameMapper: {
'\\.(jpg|jpeg|png|gif|webp|svg)$': 'jest-transform-stub',
'.*\\.(css|scss|sass)$': 'identity-obj-proxy',
'^utils(.*)$': '<rootDir>/src/utils$1',
'^hooks(.*)$': '<rootDir>/src/hooks$1',
Expand Down
4,499 changes: 1,458 additions & 3,041 deletions react/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
"identity-obj-proxy": "^3.0.0",
"jest": "^29.0.5",
"jest-environment-jsdom": "^29.0.5",
"jest-transform-stub": "^2.0.0",
"ts-jest": "^29.0.5",
"typescript": "^4.6.4",
"vite": "^3.0.7"
Expand Down
Binary file added react/src/assets/assets.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added react/src/assets/filters.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added react/src/assets/layers.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added react/src/assets/point-clouds.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added react/src/assets/streetview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added react/src/assets/users-solid.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions react/src/components/AssetsPanel/AssetsPanel.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.root {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
}
20 changes: 20 additions & 0 deletions react/src/components/AssetsPanel/AssetsPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';
import styles from './AssetsPanel.module.css';

interface Props {
/**
* Whether or not the map project is public.
*/
isPublic: boolean;
}

/**
* A component that displays a map project (a map and related data)
*/
const AssetsPanel: React.FC<Props> = ({ isPublic }) => {
return (
<div className={styles.root}>Assets Panel TODO, isPublic: {isPublic}</div>
);
};

export default AssetsPanel;
1 change: 1 addition & 0 deletions react/src/components/AssetsPanel/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './AssetsPanel';
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.root {
display: flex;
flex-direction: column;
width: 100%;
height: 200px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import styles from './ManageMapProjectModal.module.css';
import { Modal, ModalHeader, ModalBody } from 'reactstrap';

interface ManageMapProjectModalProps {
isPublic: boolean;
}

const ManageMapProjectModal: React.FC<ManageMapProjectModalProps> = ({
isPublic,
}) => {
const navigate = useNavigate();
const location = useLocation();

const closeModal = () => {
const params = new URLSearchParams(location.search);
params.delete('panel'); // Remove the panel query parameter
navigate(`${location.pathname}?${params.toString()}`); // Update the URL
};

return (
<Modal isOpen toggle={closeModal}>
<ModalHeader toggle={closeModal}>TODO</ModalHeader>
<ModalBody>
<div className={styles.root}>
Manage Map Project TODO, isPublic: {isPublic}
</div>
</ModalBody>
</Modal>
);
};

export default ManageMapProjectModal;
1 change: 1 addition & 0 deletions react/src/components/ManageMapProjectModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './ManageMapProjectModal';
5 changes: 4 additions & 1 deletion react/src/components/Map/Map.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React, { useEffect } from 'react';
import {
MapContainer,
TileLayer,
ZoomControl,
Marker,
Popup,
TileLayer,
WMSTileLayer,
useMap,
} from 'react-leaflet';
Expand Down Expand Up @@ -88,6 +89,7 @@ const LeafletMap: React.FC<LeafletMapProps> = ({
center={startingCenterPosition}
zoom={3}
style={{ width: '100%', height: '100%' }}
zoomControl={false}
minZoom={2} // 2 typically prevents zooming out to far to see multiple earths
maxBounds={maxBounds}
>
Expand Down Expand Up @@ -133,6 +135,7 @@ const LeafletMap: React.FC<LeafletMapProps> = ({
})}
</MarkerClusterGroup>
<FitBoundsOnInitialLoad featureCollection={featureCollection} />
<ZoomControl position="bottomright" />
</MapContainer>
);
};
Expand Down
13 changes: 13 additions & 0 deletions react/src/components/MapProjectNavBar/MapProjectNavBar.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.root {
height: 100%;
min-width: 150px;
padding-top: var(--global-space--section-top);
}

.root > a > div {
padding-left: var(--global-space--section-left);
}

.image {
margin-right: 1rem;
}
37 changes: 37 additions & 0 deletions react/src/components/MapProjectNavBar/MapProjectNavBar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';
import { render } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import MapProjectNavBar from './MapProjectNavBar';

describe('MapProjectNavBar', () => {
it('renders the public nav items for public maps', () => {
const { getByText, queryByText } = render(
<BrowserRouter>
<MapProjectNavBar isPublic={true} />
</BrowserRouter>
);
expect(getByText('Assets')).toBeDefined();
expect(getByText('Layers')).toBeDefined();
expect(getByText('Filters')).toBeDefined();
expect(getByText('Streetview')).toBeDefined();
expect(getByText('Manage')).toBeDefined();
// Point Clouds should not be rendered for public maps
expect(queryByText('Point Clouds')).toBeNull();
});

it('renders all nav items for non-public maps', () => {
const { getByText } = render(
<BrowserRouter>
<MapProjectNavBar isPublic={false} />
</BrowserRouter>
);

// All items should be rendered
expect(getByText('Assets')).toBeDefined();
expect(getByText('Point Clouds')).toBeDefined();
expect(getByText('Layers')).toBeDefined();
expect(getByText('Filters')).toBeDefined();
expect(getByText('Streetview')).toBeDefined();
expect(getByText('Manage')).toBeDefined();
});
});
107 changes: 107 additions & 0 deletions react/src/components/MapProjectNavBar/MapProjectNavBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import React from 'react';
import { useLocation } from 'react-router-dom';
import styles from './MapProjectNavBar.module.css';
import { QueryNavItem } from '../../core-wrappers';
import { queryPanelKey, Panel } from '../../utils/panels';

import assetsImage from '../../assets/assets.png';
import pointCloudImage from '../../assets/point-clouds.png';
import layersImage from '../../assets/layers.png';
import filtersImage from '../../assets/filters.png';
import streetviewImage from '../../assets/streetview.png';
import manageImage from '../../assets/users-solid.png';

interface NavItem {
label: string;
imagePath: string;
panel: Panel;
showWhenPublic: boolean;
}

const navItems: NavItem[] = [
{
label: 'Assets',
imagePath: assetsImage,
panel: Panel.Assets,
showWhenPublic: true,
},
{
label: 'Point Clouds',
imagePath: pointCloudImage,
panel: Panel.PointClouds,
showWhenPublic: false,
},
{
label: 'Layers',
imagePath: layersImage,
panel: Panel.Layers,
showWhenPublic: true,
},
{
label: 'Filters',
imagePath: filtersImage,
panel: Panel.Filters,
showWhenPublic: true,
},
{
label: 'Streetview',
imagePath: streetviewImage,
panel: Panel.Streetview,
showWhenPublic: true,
},
{
label: 'Manage',
imagePath: manageImage,
panel: Panel.Manage,
showWhenPublic: true,
},
];

interface NavBarPanelProps {
isPublic?: boolean;
}

const MapProjectNavBar: React.FC<NavBarPanelProps> = ({ isPublic = false }) => {
const location = useLocation();
const queryParams = new URLSearchParams(location.search);
const activePanel = queryParams.get(queryPanelKey);

return (
<div className={styles.root}>
{navItems
.filter((item) => (isPublic ? item.showWhenPublic : true))
.map((item) => {
const updatedQueryParams = new URLSearchParams(location.search);

if (activePanel === item.panel) {
// If already active, we want to remove queryPanel key if user clicks again
updatedQueryParams.delete(queryPanelKey);
} else {
// Set the queryPanelKey to the current item's panel
updatedQueryParams.set(queryPanelKey, item.panel);
}

// Construct the `to` prop with updated query params
const to = `${location.pathname}?${updatedQueryParams.toString()}`;

return (
<QueryNavItem
key={item.panel}
to={to}
active={activePanel === item.panel}
>
<img
src={item.imagePath}
alt={item.label}
className={styles.image}
width="32px"
/>
{item.label}
</QueryNavItem>
);
})}
</div>
);
};

export default MapProjectNavBar;
1 change: 1 addition & 0 deletions react/src/components/MapProjectNavBar/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './MapProjectNavBar';
44 changes: 44 additions & 0 deletions react/src/pages/MapProject/MapProject.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
.root {
display: flex;
flex-direction: column;
width: 100vw;
height: 100vh;
}

.topNavbar {
background-color: black;
color: white;
height: 40px;
display: flex;
align-items: center;
}

.mapControlBar {
background-color: white;
height: 27px;
display: flex;
align-items: center;
padding: 0 10px;
}

.container {
height: 100vh;
width: 100%;
display: flex;
flex: 1;
}

.panelContainer {
/* todo place into a PanelContainer and then the PanelNavigation where the buttons live either horizonal or vertical */
width: 200px;
height: 95%;
background-color: var(--global-color-primary--xx-light);
position: absolute;
left: 150px;
z-index: 5000;
}

.map {
width: 100%;
height: 100%;
}
Loading

0 comments on commit f33c3ed

Please sign in to comment.