Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: New Global Controls in the Layer Picker #352

Merged
merged 20 commits into from
Jan 22, 2025
Merged
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
2 changes: 1 addition & 1 deletion frontend/Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
script-src 'self' https://www2.gov.bc.ca https://*.openstreetmap.org;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://use.fontawesome.com https://*.openstreetmap.org;
font-src 'self' https://fonts.gstatic.com https://*.openstreetmap.org;
img-src 'self' data: https://fonts.googleapis.com http://www.w3.org https://*.gov.bc.ca https://*.openstreetmap.org https://*.stadiamaps.com https://server.arcgisonline.com https://api.maptiler.com;
img-src 'self' data: https://fonts.googleapis.com http://www.w3.org https://*.gov.bc.ca https://*.google.com https://*.openstreetmap.org https://*.stadiamaps.com https://server.arcgisonline.com https://api.maptiler.com;
frame-ancestors 'none';
form-action 'self' {$BACKEND_URL};
frame-src 'none';
Expand Down
102 changes: 59 additions & 43 deletions frontend/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
# Organics Info Frontend

Refer to the project README file in the parent directory for details
on the architecture, libraries, and how to run the whole application.
Refer to the project README file in the parent directory for details on the
architecture, libraries, and how to run the whole application.

The information in this README file will be specific to the frontend only.

## Scripts

See the `scripts` section of the `frontend/package.json` file to see
what command line scripts can be run. The most used ones are:
See the `scripts` section of the `frontend/package.json` file to see what
command line scripts can be run. The most used ones are:

- `npm start` - Starts the local application
- `npm run dev` - Starts the app with network support
- `npm run lint` - Runs the eslint to check for linting issues
Expand All @@ -17,82 +18,97 @@ what command line scripts can be run. The most used ones are:
- `npm run test:cov` - Runs unit tests and generates coverage report
- `npm run test-e2e` - Runs Playwright end to end tests

There is also a `scripts` folder that contains other utility scripts
that can be run. See the README file in that directory for more details.
There is also a `scripts` folder that contains other utility scripts that can be
run. See the README file in that directory for more details.

## Tests - Playwright and Testing Library

The [Playwright](https://playwright.dev/) library is used for End to End tests.
It requires the backend server and front end client to be running.

[Vitest](https://vitest.dev/) is used for the unit tests, along with
[React Testing Library](https://testing-library.com/).
[MSW](https://mswjs.io/) is also used for mocking the 3 API calls.
Unit tests run using jsdom, which is not the same as a full functioning browser,
and as such some features don't work and need to be mocked.
See the `src/test-setup.ts` file for more details.
[Vitest](https://vitest.dev/) is used for the unit tests, along with
[React Testing Library](https://testing-library.com/). [MSW](https://mswjs.io/)
is also used for mocking the 3 API calls. Unit tests run using jsdom, which is
not the same as a full functioning browser, and as such some features don't work
and need to be mocked. See the `src/test-setup.ts` file for more details.

### Running End To End Tests

**End to end** tests are run using [Playwright](https://playwright.dev/).
Here are the steps to follow to run the end to end tests:
**End to end** tests are run using [Playwright](https://playwright.dev/). Here
are the steps to follow to run the end to end tests:

1. First install Playwright: `npx playwright install`
2. Then run start the server and frontend as shown above in steps 1-4.
3. Run Playwright with this command: `npx playwright test` or `npm run test:e2e`

To configure playwright edit the `playwright.config.ts` file.
End to end tests are located in the `/e2e` folder.
To configure playwright edit the `playwright.config.ts` file. End to end tests
are located in the `/e2e` folder.

It is also really useful to run the `npx playwright test --ui` command if you
want to debug why a test is failing, or to run certain tests on specific browsers.
want to debug why a test is failing, or to run certain tests on specific
browsers.

### Running Unit Tests

**Unit tests** do not require the server or frontend to be running.
You can run the unit tests with this command: `npm run test:unit`
To generate a coverage report, use this command: `npm run test:cov`
The coverage report can be viewed in the `coverage` folder.
Unit tests use [MSW](https://mswjs.io/) to mock API responses, see the
`src/test-setup.ts` file where it is set up.
**Unit tests** do not require the server or frontend to be running. You can run
the unit tests with this command: `npm run test:unit` To generate a coverage
report, use this command: `npm run test:cov` The coverage report can be viewed
in the `coverage` folder. Unit tests use [MSW](https://mswjs.io/) to mock API
responses, see the `src/test-setup.ts` file where it is set up.

## Application Styles - Figma, MUI and CSS

The [Figma design](https://www.figma.com/design/74nNxjyv6JM6hiT1FluULV/OMRR-(ORI)?node-id=675-3797)
The
[Figma design](<https://www.figma.com/design/74nNxjyv6JM6hiT1FluULV/OMRR-(ORI)?node-id=675-3797>)
for this application is the source of truth for the text and styles used.

This application uses a combination of [Material UI](https://mui.com/) components,
custom React components, and CSS stylesheets.
This application uses a combination of [Material UI](https://mui.com/)
components, custom React components, and CSS stylesheets.

Material UI is convenient and very helpful for making the application
responsive to different screen sizes. Therefore, MUI components are often
used whenever styles need to change based on the screen size using the
`sx` or `style` attributes. See the MUI
[Responsive UI](https://mui.com/material-ui/guides/responsive-ui/)
page for more details on how it works.
Material UI is convenient and very helpful for making the application responsive
to different screen sizes. Therefore, MUI components are often used whenever
styles need to change based on the screen size using the `sx` or `style`
attributes. See the MUI
[Responsive UI](https://mui.com/material-ui/guides/responsive-ui/) page for more
details on how it works.

Basic styling that don't need to change with screen size (for example
colors) is handled with plain CSS files.
Basic styling that don't need to change with screen size (for example colors) is
handled with plain CSS files.

[CSS Variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties)
(also called custom properties) are used quite frequently to define
color and layout constants. These come directly from the Figma file
and makes it easy to copy styles directly from Figma.
All the custom properties can be found in the `src/properties.css` file.
(also called custom properties) are used quite frequently to define color and
layout constants. These come directly from the Figma file and makes it easy to
copy styles directly from Figma. All the custom properties can be found in the
`src/properties.css` file.

## Application State - Redux

The application state is stored in
The application state is stored in
[Redux](https://redux.js.org/tutorials/essentials/part-1-overview-concepts).

The state is divided into 4 slices:
- **omrr-slice** - Stores the array of authorizations and all search filters,
and has the API call for loading authorizations.

- **omrr-slice** - Stores the array of authorizations and all search filters,
and has the API call for loading authorizations.
- **applications-slice** - Stores the array of applications
- **documents-slice** - Stores the array of documents
- **map-slice** - Stores all the map related state including selected item,
sidebar width, etc.
- **map-slice** - Stores all the map related state including selected item,
sidebar width, etc.

Each slice defines selectors and hooks to allow extracting each piece of state
in the most efficient way.

It also contains the action functions for changing the state.

## Map Licensing

All software is Open Source and free to use.

The basemaps are supplied by a number of different providers:

- [OpenStreetMap](https://www.openstreetmap.org/)
- [ESRI](https://www.esri.com/)
- [Google Maps](https://www.google.com/maps)

**The only current restriction is that the Google Maps basemap is only available
if the application is not running under the protection of user authentication.**
2 changes: 1 addition & 1 deletion frontend/e2e/pages/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export const map_page = async (page: Page) => {
await page.getByTitle('Close').click({ force: true })

// Test basemap switcher
const basemapButton = page.locator('.basemap-toggle')
const basemapButton = page.locator('.leaflet-control-basemaps')
await expect(basemapButton).toBeVisible()
await basemapButton.click()

Expand Down
Binary file removed frontend/public/Streets.png
Binary file not shown.
Binary file removed frontend/public/custom-1.png
Binary file not shown.
Binary file removed frontend/public/custom-2.png
Binary file not shown.
Binary file removed frontend/public/custom-3.png
Binary file not shown.
Binary file removed frontend/public/custom-4.png
Binary file not shown.
Binary file removed frontend/public/pale-osm.png
Binary file not shown.
Binary file modified frontend/public/streets.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 frontend/public/terrain2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 10 additions & 1 deletion frontend/src/components/DataLayersCheckboxGroup.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,20 @@
padding: 0;
}

button.layer-button {
color: black;
text-transform: none;
text-decoration: none;
}

button.layer-button:hover {
text-decoration: underline;
}

p.data-layers-top-text,
.data-layers-top-link {
color: var(--typography-color-disabled);
font-size: 14px;
font-style: italic;
}

.available-layers-row {
Expand Down
13 changes: 2 additions & 11 deletions frontend/src/components/DataLayersCheckboxGroup.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@ describe('Test suite for DataLayersCheckboxGroup', () => {
})

expect(screen.queryByText('Available Layers')).not.toBeInTheDocument()
expect(screen.queryByText('Reset')).not.toBeInTheDocument()
expect(screen.queryByText('Reset Layers')).not.toBeInTheDocument()

const toggle = screen.getByRole('button', {
name: 'Aquifers and Water Wells',
Expand All @@ -81,12 +79,8 @@ describe('Test suite for DataLayersCheckboxGroup', () => {
expect(state.dataLayers).toHaveLength(1)
expect(state.hasDataLayersOn).toBe(true)

const resetBtn = screen.getByRole('button', { name: 'Reset Layers' })
const resetBtn = screen.getByRole('button', { name: 'Clear All' })
await user.click(resetBtn)

expect(state.dataLayers).toHaveLength(0)
expect(state.hasDataLayersOn).toBe(false)
expect(screen.queryByText('Reset Layers')).not.toBeInTheDocument()
})

it('should render small DataLayersCheckboxGroup', async () => {
Expand All @@ -105,10 +99,7 @@ describe('Test suite for DataLayersCheckboxGroup', () => {
expect(state.dataLayers).toHaveLength(1)
expect(state.hasDataLayersOn).toBe(true)

const resetLink = screen.getByRole('button', { name: 'Reset' })
const resetLink = screen.getByRole('button', { name: 'Clear All' })
await user.click(resetLink)

expect(state.dataLayers).toHaveLength(0)
expect(screen.queryByText('Reset Layers')).not.toBeInTheDocument()
})
})
81 changes: 52 additions & 29 deletions frontend/src/components/DataLayersCheckboxGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { NavLink } from 'react-router-dom'
import { useDispatch } from 'react-redux'
import { Button, Stack, Typography } from '@mui/material'
import clsx from 'clsx'
import { useState } from 'react'

import { DATA_LAYER_GROUPS } from '@/constants/data-layers'
import {
Expand All @@ -26,15 +27,26 @@ export function DataLayersCheckboxGroup({
const dispatch = useDispatch()
const isLarge = !isSmall
const hasDataLayers = useHasDataLayersOn()

const onLayerToggle = (layer: DataLayer) => {
dispatch(toggleDataLayer(layer))
}
const [forceAction, setForceAction] = useState<'collapse' | 'expand' | null>(
null,
)

const onReset = () => {
dispatch(resetDataLayers())
}

const onCollapseAll = () => {
setForceAction('collapse')
}

const onExpandAll = () => {
setForceAction('expand')
}

const onLayerToggle = (layer: DataLayer) => {
dispatch(toggleDataLayer(layer))
}

return (
<Stack
direction="column"
Expand All @@ -47,28 +59,38 @@ export function DataLayersCheckboxGroup({
data-testid="data-layers-checkbox-group"
>
<Stack direction="column" className="data-layers-top-section">
<Typography className="data-layers-top-text">
All data layers sourced from GeoBC.
</Typography>
<NavLink to="/guidance" className="data-layers-top-link">
Click here to read our guidance page about map layers.
</NavLink>
<Stack direction="row" justifyContent="space-between">
<Button
variant="text"
size="small"
className="layer-button"
onClick={onReset}
>
Clear All
</Button>
<Button
variant="text"
size="small"
className="layer-button"
onClick={onCollapseAll}
>
Collapse All
</Button>
<Button
variant="text"
size="small"
onClick={onExpandAll}
className="layer-button"
>
Expand All
</Button>
</Stack>
</Stack>
{isSmall && (
<div className="available-layers-row">
<Typography className="available-layers-text">
Available Layers
</Typography>
{hasDataLayers && (
<Button
variant="text"
size="small"
className="data-layers-reset-link"
onClick={onReset}
>
Reset
</Button>
)}
</div>
)}
{DATA_LAYER_GROUPS.map((group: DataLayerGroup) => (
Expand All @@ -77,17 +99,18 @@ export function DataLayersCheckboxGroup({
group={group}
onLayerToggle={onLayerToggle}
isSmall={isSmall}
forceAction={forceAction}
setForceAction={setForceAction}
/>
))}
{isLarge && hasDataLayers && (
<Button
variant="outlined"
onClick={onReset}
className="data-layers-reset-button"
>
Reset Layers
</Button>
)}
<Stack direction="column" spacing={1}>
<Typography className="data-layers-top-text">
All data layers sourced from GeoBC.
</Typography>
<NavLink to="/guidance" className="data-layers-top-link">
Click here to read our guidance page about map layers.
</NavLink>
</Stack>
</Stack>
)
}
Loading
Loading