Add frontend support for svg zone selection

This commit is contained in:
Dana Lambert 2021-12-06 10:26:37 +13:00
parent 0075e8b303
commit 2c2a362b9a
6 changed files with 91 additions and 59 deletions

View file

@ -40,3 +40,15 @@
.selected-segment { .selected-segment {
background-color: #eeeeee55; 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;
}

View file

@ -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 <ImportedIcon {...rest} />;
}
return null;
};

View file

@ -7,6 +7,8 @@ import FormControlLabel from '@mui/material/FormControlLabel';
import FormLabel from '@mui/material/FormLabel'; import FormLabel from '@mui/material/FormLabel';
import FormControl from '@mui/material/FormControl'; import FormControl from '@mui/material/FormControl';
import staticText from '../../../assets/data/staticText.json' import staticText from '../../../assets/data/staticText.json'
import { HabitatSVG } from '../HabitatSVG'
export default function HabitatSelector(props) { export default function HabitatSelector(props) {
const [habitats, setHabitats] = useState([]) const [habitats, setHabitats] = useState([])
@ -79,7 +81,7 @@ export default function HabitatSelector(props) {
return ( return (
<div> <div>
<p>{image.name}</p> <p>{image.name}</p>
<img src={require(`../../../assets/img/habitats/${image.image_filename}`).default} alt={image.name} style={{ "width": "300px" }} /> <HabitatSVG name={image.image_filename} style={{ "height": 'auto', "width": "300px" }}/>
</div>) </div>)
} }

View file

@ -1,36 +1,41 @@
import { useEffect, useState, useRef } from 'react'; import { useEffect, useState, useRef } from 'react';
import SampleBackground from '../../../assets/img/stepBackgrounds/step1.jpg';
import SiteRepository from '../../../repository/SiteRepository'; import SiteRepository from '../../../repository/SiteRepository';
import { HabitatSVG } from '../HabitatSVG'
export default function ZoneSelector(props) { export default function ZoneSelector(props) {
const [habitatImageObject, setHabitatImageObject] = useState({}); const [habitatImageObject, setHabitatImageObject] = useState({});
const [habitatImageFile, setHabitatImageFile] = useState(SampleBackground); const [segmentMapping, setSegmentMapping] = useState({});
const [imageHeight, setImageHeight] = useState(0)
const [selectedZoneSegment, setZoneSegment] = useState(null); const [selectedZoneSegment, setZoneSegment] = useState(null);
const imageContainer = useRef(null)
const getHabitatImage = (findAndSetImageHeightFunc) => { const getHabitatImage = () => {
SiteRepository.getHabitatImage(props.filters.habitatImage).then(response => { SiteRepository.getHabitatImage(props.filters.habitatImage).then(response => {
if (response.status === 200) { if (response.status === 200) {
const imageData = response.data const imageData = response.data
setHabitatImageObject(imageData) setHabitatImageObject(imageData)
const imageSrc = require(`../../../assets/img/habitats/${imageData.image_filename}`).default
setHabitatImageFile(imageSrc)
findAndSetImageHeightFunc(imageSrc)
} }
}).catch(e => {
findAndSetImageHeightFunc(habitatImageFile)
}) })
} }
const setZoneOrRedirect = (zoneSegment) => { const getZones = () => {
const redirectHabitat = zoneSegment && zoneSegment.zone && zoneSegment.zone.redirect_habitat; SiteRepository.getZones().then(response => {
setZoneSegment(zoneSegment); 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 // 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.updateFilterState(newFilterState);
props.setRedirectBack(Boolean(redirectHabitat)); props.setRedirectBack(Boolean(redirectHabitat));
@ -38,53 +43,35 @@ export default function ZoneSelector(props) {
} }
useEffect(() => { useEffect(() => {
// Calculates the image ratio and uses this to calculate the height in pixels based on outer container width // Retrieves the habitat image from the api if it's not loaded already
const findAndSetImageHeight = (imageSource) => { Object.keys(habitatImageObject).length === 0 && getHabitatImage()
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 // 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);
}
});
const stepBackground = { }, [props.filters.zone]);
backgroundImage: `url(${habitatImageFile})`,
backgroundSize: '100% auto', const selectZone = (element) => {
backgroundRepeat: 'none', if (['path', 'rect'].includes(element.tagName) && element.attributes.inkscapelabel && element.attributes.inkscapelabel.nodeValue) {
height: imageHeight, if (selectedZoneSegment) {
width: '100%', selectedZoneSegment.style['fill-opacity'] = 0;
display: 'flex'
} }
return ( setZoneSegment(element)
<div ref={imageContainer} style={{ width: '100%' }}> element.style.fill = "#eeeeee"
<p>{habitatImageObject && habitatImageObject.name}</p> element.style['fill-opacity'] = 0.5;
{Object.keys(habitatImageObject).length === 0 ? (
// Sample segment selector (temporary if no image is available) const zone = segmentMapping[element.attributes.inkscapelabel.nodeValue]
<div style={stepBackground}> setZoneOrRedirect(zone)
<div className='selectable-section' style={{ width: '50%', height: '100%' }}></div> }
<div className='selectable-section' style={{ width: '20%', height: '100%' }}></div> }
<div className='selectable-section' style={{ width: '30%', height: '100%' }}></div>
</div>) : return Object.keys(habitatImageObject).length > 0 ? (
// Actual zone selector <div style={{ width: '100%' }}>
(<div style={stepBackground}> <p>{habitatImageObject.name}</p>
{habitatImageObject && habitatImageObject.image_segments && Array.isArray(habitatImageObject.image_segments) && habitatImageObject.image_segments.map(segment => <div className="zone-selector-svg">
<div key={segment.id} onClick={() => { setZoneOrRedirect(segment) }} className={`selectable-section ${selectedZoneSegment === segment ? 'selected-segment' : ''}`} style={{ width: `${segment.segment_percentage_width}%`, height: '100%' }}></div> <HabitatSVG onClick={(event) => selectZone(event.target)} name={habitatImageObject.image_filename} style={{ "height": 'auto', "width": "100%" }} />
)}
</div> </div>
)} </div>) : <div></div>
</div>
)
} }

View file

@ -2,7 +2,7 @@ import axios from "axios"
// Create the axios object // Create the axios object
const repo = axios.create({ 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"] = "*"; repo.defaults.headers.post["access-control-allow-origin"] = "*";

View file

@ -6,6 +6,10 @@ const SiteRepository = {
return Repository.get(`/habitats/`); return Repository.get(`/habitats/`);
}, },
getZones() {
return Repository.get(`/zones/`);
},
getHabitatImage(habitatImageID) { getHabitatImage(habitatImageID) {
return Repository.get(`/habitatimage/${habitatImageID}/`); return Repository.get(`/habitatimage/${habitatImageID}/`);
} }