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 {
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 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 (
<div>
<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>)
}

View file

@ -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 (
<div ref={imageContainer} style={{ width: '100%' }}>
<p>{habitatImageObject && habitatImageObject.name}</p>
{Object.keys(habitatImageObject).length === 0 ? (
// Sample segment selector (temporary if no image is available)
<div style={stepBackground}>
<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>) :
// Actual zone selector
(<div style={stepBackground}>
{habitatImageObject && habitatImageObject.image_segments && Array.isArray(habitatImageObject.image_segments) && habitatImageObject.image_segments.map(segment =>
<div key={segment.id} onClick={() => { setZoneOrRedirect(segment) }} className={`selectable-section ${selectedZoneSegment === segment ? 'selected-segment' : ''}`} style={{ width: `${segment.segment_percentage_width}%`, height: '100%' }}></div>
)}
</div>
)}
</div>
)
return Object.keys(habitatImageObject).length > 0 ? (
<div style={{ width: '100%' }}>
<p>{habitatImageObject.name}</p>
<div className="zone-selector-svg">
<HabitatSVG onClick={(event) => selectZone(event.target)} name={habitatImageObject.image_filename} style={{ "height": 'auto', "width": "100%" }} />
</div>
</div>) : <div></div>
}

View file

@ -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"] = "*";

View file

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