From 2c2a362b9af1c2902f6894bad6afd7af48d7f56a Mon Sep 17 00:00:00 2001 From: Dana Lambert Date: Mon, 6 Dec 2021 10:26:37 +1300 Subject: [PATCH] Add frontend support for svg zone selection --- frontend/src/assets/styles/step.scss | 12 +++ frontend/src/components/steps/HabitatSVG.jsx | 27 +++++ .../steps/habitat/HabitatSelector.js | 4 +- .../src/components/steps/zone/ZoneSelector.js | 101 ++++++++---------- frontend/src/repository/Repository.js | 2 +- frontend/src/repository/SiteRepository.js | 4 + 6 files changed, 91 insertions(+), 59 deletions(-) create mode 100644 frontend/src/components/steps/HabitatSVG.jsx diff --git a/frontend/src/assets/styles/step.scss b/frontend/src/assets/styles/step.scss index c561ed0..2fef5ea 100644 --- a/frontend/src/assets/styles/step.scss +++ b/frontend/src/assets/styles/step.scss @@ -40,3 +40,15 @@ .selected-segment { background-color: #eeeeee55; } + +.zone-selector-svg path:hover { + fill: #eeeeee !important; + fill-opacity: 0.5 !important; + cursor: pointer; +} + +.zone-selector-svg rect:hover { + fill: #eeeeee !important; + fill-opacity: 0.5 !important; + cursor: pointer; +} diff --git a/frontend/src/components/steps/HabitatSVG.jsx b/frontend/src/components/steps/HabitatSVG.jsx new file mode 100644 index 0000000..4fce2a0 --- /dev/null +++ b/frontend/src/components/steps/HabitatSVG.jsx @@ -0,0 +1,27 @@ +import React from 'react'; + +export const HabitatSVG = ({ name, ...rest }) => { + const ImportedIconRef = React.useRef(null); + const [loading, setLoading] = React.useState(false); + + React.useEffect(() => { + setLoading(true); + const importIcon = async () => { + try { + ImportedIconRef.current = (await import(`!!@svgr/webpack?-svgo,+titleProp,+ref!../../assets/img/habitatSVG/${name}.svg`)).default; + } catch (err) { + throw err; + } finally { + setLoading(false); + } + }; + importIcon(); + }, [name]); + + if (!loading && ImportedIconRef.current) { + const { current: ImportedIcon } = ImportedIconRef; + return ; + } + + return null; +}; diff --git a/frontend/src/components/steps/habitat/HabitatSelector.js b/frontend/src/components/steps/habitat/HabitatSelector.js index 6137bb8..1f308ca 100644 --- a/frontend/src/components/steps/habitat/HabitatSelector.js +++ b/frontend/src/components/steps/habitat/HabitatSelector.js @@ -7,6 +7,8 @@ import FormControlLabel from '@mui/material/FormControlLabel'; import FormLabel from '@mui/material/FormLabel'; import FormControl from '@mui/material/FormControl'; import staticText from '../../../assets/data/staticText.json' +import { HabitatSVG } from '../HabitatSVG' + export default function HabitatSelector(props) { const [habitats, setHabitats] = useState([]) @@ -79,7 +81,7 @@ export default function HabitatSelector(props) { return (

{image.name}

- {image.name} +
) } diff --git a/frontend/src/components/steps/zone/ZoneSelector.js b/frontend/src/components/steps/zone/ZoneSelector.js index 7b47b33..bf02244 100644 --- a/frontend/src/components/steps/zone/ZoneSelector.js +++ b/frontend/src/components/steps/zone/ZoneSelector.js @@ -1,36 +1,41 @@ import { useEffect, useState, useRef } from 'react'; -import SampleBackground from '../../../assets/img/stepBackgrounds/step1.jpg'; import SiteRepository from '../../../repository/SiteRepository'; - +import { HabitatSVG } from '../HabitatSVG' export default function ZoneSelector(props) { const [habitatImageObject, setHabitatImageObject] = useState({}); - const [habitatImageFile, setHabitatImageFile] = useState(SampleBackground); - const [imageHeight, setImageHeight] = useState(0) + const [segmentMapping, setSegmentMapping] = useState({}); const [selectedZoneSegment, setZoneSegment] = useState(null); - const imageContainer = useRef(null) - const getHabitatImage = (findAndSetImageHeightFunc) => { + const getHabitatImage = () => { SiteRepository.getHabitatImage(props.filters.habitatImage).then(response => { if (response.status === 200) { const imageData = response.data setHabitatImageObject(imageData) - const imageSrc = require(`../../../assets/img/habitats/${imageData.image_filename}`).default - setHabitatImageFile(imageSrc) - findAndSetImageHeightFunc(imageSrc) } - }).catch(e => { - findAndSetImageHeightFunc(habitatImageFile) }) } - const setZoneOrRedirect = (zoneSegment) => { - const redirectHabitat = zoneSegment && zoneSegment.zone && zoneSegment.zone.redirect_habitat; - setZoneSegment(zoneSegment); + const getZones = () => { + SiteRepository.getZones().then(response => { + if (response.status === 200) { + const zoneData = response.data + let newSegmentMapping = {} + zoneData.forEach(zone => { + newSegmentMapping[zone.related_svg_segment] = zone + }); + setSegmentMapping(newSegmentMapping); + } + }) + } + + const setZoneOrRedirect = (zone) => { + const redirectHabitat = zone && zone.redirect_habitat; // If there is a redirect habitat set then redirect otherwise set the zone as state - const newFilterState = redirectHabitat ? { "habitat": { "id": redirectHabitat.id, "name": redirectHabitat.name }} : { "zone": zoneSegment.zone }; + const newFilterState = redirectHabitat ? { "habitat": { "id": redirectHabitat.id, "name": redirectHabitat.name } } : { "zone": zone }; + props.updateFilterState(newFilterState); props.setRedirectBack(Boolean(redirectHabitat)); @@ -38,53 +43,35 @@ export default function ZoneSelector(props) { } useEffect(() => { - // Calculates the image ratio and uses this to calculate the height in pixels based on outer container width - const findAndSetImageHeight = (imageSource) => { - let img = new Image(); - img.src = imageSource; - const newHeight = imageContainer.current ? imageContainer.current.clientWidth * (img.height / img.width) : 0; - setImageHeight(newHeight); - } + // Retrieves the habitat image from the api if it's not loaded already + Object.keys(habitatImageObject).length === 0 && getHabitatImage() // Retrieves the habitat image from the api if it's not loaded already - Object.keys(habitatImageObject).length === 0 && getHabitatImage(findAndSetImageHeight) + Object.keys(segmentMapping).length === 0 && getZones() - // Sets the image height such that the full image always displays on page resize - window.addEventListener("resize", () => findAndSetImageHeight(habitatImageFile), false); - // Temporarily bypass if there is no image available - if (props.filters.zone || !props.filters.habitatImage || (habitatImageObject && habitatImageObject.image_segments)) { - props.setNextDisabled(false); + }, [props.filters.zone]); + + const selectZone = (element) => { + if (['path', 'rect'].includes(element.tagName) && element.attributes.inkscapelabel && element.attributes.inkscapelabel.nodeValue) { + if (selectedZoneSegment) { + selectedZoneSegment.style['fill-opacity'] = 0; + } + + setZoneSegment(element) + element.style.fill = "#eeeeee" + element.style['fill-opacity'] = 0.5; + + const zone = segmentMapping[element.attributes.inkscapelabel.nodeValue] + setZoneOrRedirect(zone) } - }); - - - const stepBackground = { - backgroundImage: `url(${habitatImageFile})`, - backgroundSize: '100% auto', - backgroundRepeat: 'none', - height: imageHeight, - width: '100%', - display: 'flex' } - return ( -
-

{habitatImageObject && habitatImageObject.name}

- {Object.keys(habitatImageObject).length === 0 ? ( - // Sample segment selector (temporary if no image is available) -
-
-
-
-
) : - // Actual zone selector - (
- {habitatImageObject && habitatImageObject.image_segments && Array.isArray(habitatImageObject.image_segments) && habitatImageObject.image_segments.map(segment => -
{ setZoneOrRedirect(segment) }} className={`selectable-section ${selectedZoneSegment === segment ? 'selected-segment' : ''}`} style={{ width: `${segment.segment_percentage_width}%`, height: '100%' }}>
- )} -
- )} -
- ) + return Object.keys(habitatImageObject).length > 0 ? ( +
+

{habitatImageObject.name}

+
+ selectZone(event.target)} name={habitatImageObject.image_filename} style={{ "height": 'auto', "width": "100%" }} /> +
+
) :
} diff --git a/frontend/src/repository/Repository.js b/frontend/src/repository/Repository.js index 9848651..555a907 100644 --- a/frontend/src/repository/Repository.js +++ b/frontend/src/repository/Repository.js @@ -2,7 +2,7 @@ import axios from "axios" // Create the axios object const repo = axios.create({ - baseURL: window.location.hostname == 'localhost' ? "http://localhost:9000/api" : "/api", + baseURL: window.location.hostname === 'localhost' ? "http://localhost:9000/api" : "/api", }); repo.defaults.headers.post["access-control-allow-origin"] = "*"; diff --git a/frontend/src/repository/SiteRepository.js b/frontend/src/repository/SiteRepository.js index 08a589f..a3c2b6d 100644 --- a/frontend/src/repository/SiteRepository.js +++ b/frontend/src/repository/SiteRepository.js @@ -6,6 +6,10 @@ const SiteRepository = { return Repository.get(`/habitats/`); }, + getZones() { + return Repository.get(`/zones/`); + }, + getHabitatImage(habitatImageID) { return Repository.get(`/habitatimage/${habitatImageID}/`); }