Skip to content
Closed
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
3 changes: 3 additions & 0 deletions public/selected_location_pin.svg

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of duplicating the code for an SVG can you do something so it references the imported SVG? I think src="... somehow definition of the svg here instead of the path... " is possible.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly wasn't, left the comment in the SVG

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
228 changes: 17 additions & 211 deletions src/components/map/LocationMarkers.js
Original file line number Diff line number Diff line change
@@ -1,214 +1,13 @@
import { useEffect, useRef, useState } from 'react'
import { useSelector } from 'react-redux'

import { MapType } from '../../constants/settings'
import { theme } from '../ui/GlobalStyle'

const Z_INDEX = {
SAVED: 2,
DEFAULT: 1,
}

const getDisplayLabel = (typesAccess, id) => {
const type = typesAccess.getType(id)
if (!type) {
return null
}

if (type.cultivar) {
const parentType = typesAccess.getParentType(id)
if (
parentType &&
parentType.commonName &&
parentType.scientificName &&
(!type.commonName ||
type.commonName.toLowerCase() === parentType.commonName.toLowerCase())
) {
return {
text: `${parentType.commonName} '${type.cultivar}'`,
isScientific: false,
typeId: id,
}
}
}

if (type.commonName) {
return {
text: type.commonName,
isScientific: false,
typeId: id,
}
}

if (type.scientificName) {
return {
text: type.scientificName,
isScientific: true,
typeId: id,
}
}

return null
}

const escapeHtml = (text) => {
const div = document.createElement('div')
div.textContent = text
return div.innerHTML
}

const formatLabelHtml = (labelData, selectedTypes) =>
labelData
.map((item) => {
const escapedText = escapeHtml(item.text)
const content = item.isScientific ? `<i>${escapedText}</i>` : escapedText
const isSelected = selectedTypes.includes(item.typeId)
const opacity = isSelected ? '1.0' : '0.5'
return `<span data-type-id="${item.typeId}" style="opacity: ${opacity}">${content}</span>`
})
.join('<br>')

const createTextShadow = (color, size = 1, times = 1) => {
const shadows = []
for (let i = 0; i < times; i++) {
shadows.push(`0px 0px ${size}px ${color}`)
}
return shadows.join(', ')
}

const getLabelStyleConfig = (mapType) => {
const configs = {
[MapType.Hybrid]: {
fontWeight: 500,
color: theme.background,
textShadow: createTextShadow(theme.headerText, 2, 5),
backgroundColor: 'unset',
},
}

return (
configs[mapType] || {
fontWeight: 500,
color: theme.secondaryText,
textShadow: createTextShadow(theme.background, 2, 10),
backgroundColor: 'unset',
}
)
}

const setLabelTextStyle = (div, mapType) => {
const config = getLabelStyleConfig(mapType)
Object.keys(config).forEach((key) => {
div.style[key] = config[key]
})
}

const createBaseLabelDiv = (isSaved) => {
const div = document.createElement('div')
div.style.position = 'absolute'
div.style.padding = '4px 8px'
div.style.fontSize = '12px'
div.style.pointerEvents = 'none'
div.style.marginTop = '5px'
div.style.textAlign = 'center'
div.style.display = 'block'
div.style.zIndex = isSaved ? Z_INDEX.SAVED : Z_INDEX.DEFAULT
return div
}

const createLabel = (
google,
googleMap,
location,
labelHtml,
isHovered,
mapType,
isSaved,
) => {
const label = new google.OverlayView()
label.position = new google.LatLng(location.lat, location.lng)
label.labelHtml = labelHtml
label.locationId = location.id
label.overlayLayerPane = null
label.overlayMouseTargetPane = null
label.isHovered = isHovered
label.mapType = mapType
label.isSaved = isSaved

label.onAdd = function () {
const div = createBaseLabelDiv(this.isSaved)
setLabelTextStyle(div, this.mapType)
div.innerHTML = this.labelHtml

this.div = div
const panes = this.getPanes()
this.overlayLayerPane = panes.overlayLayer
this.overlayMouseTargetPane = panes.overlayMouseTarget
const targetPane = this.isHovered
? this.overlayMouseTargetPane
: this.overlayLayerPane
targetPane.appendChild(div)
}

label.draw = function () {
const projection = this.getProjection()
const position = projection.fromLatLngToDivPixel(this.position)

const div = this.div
div.style.left = `${position.x}px`
div.style.top = `${position.y}px`
div.style.transform = 'translate(-50%, 0)'
}

label.onRemove = function () {
if (this.div) {
this.div.parentNode.removeChild(this.div)
this.div = null
}
}

label.moveToPane = function (isHovered) {
if (!this.div || !this.overlayLayerPane || !this.overlayMouseTargetPane) {
return
}
const targetPane = isHovered
? this.overlayMouseTargetPane
: this.overlayLayerPane
if (this.div.parentNode !== targetPane) {
targetPane.appendChild(this.div)
}
}

label.updateStyle = function (mapType) {
if (!this.div) {
return
}
this.mapType = mapType
setLabelTextStyle(this.div, mapType)
}

label.updatePosition = function (google, lat, lng) {
this.position = new google.LatLng(lat, lng)
this.draw()
}

label.updateHtml = function (newHtml) {
if (!this.div || this.labelHtml === newHtml) {
return
}
this.labelHtml = newHtml
this.div.innerHTML = newHtml
}

label.setMap(googleMap)
return label
}

const getMarkerIcon = (google, isSaved) => ({
url: isSaved ? '/saved_location_dot.svg' : '/location_blue_dot.svg',
anchor: new google.Point(8, 8),
scaledSize: new google.Size(16, 16),
})
import { getDisplayLabel } from '../../utils/getDisplayLabel'
import {
createLabel,
formatLabelHtml,
getMarkerIcon,
Z_INDEX,
} from './locationMarkerHelpers'

const LocationMarkers = ({
locations,
Expand All @@ -222,6 +21,7 @@ const LocationMarkers = ({
const typesAccess = useSelector((state) => state.type.typesAccess)
const { types: selectedTypes } = useSelector((state) => state.filter)
const { mapType } = useSelector((state) => state.settings)
const streetViewOpen = useSelector((state) => state.location.streetViewOpen)

useEffect(() => {
if (!googleMap || !getGoogleMaps) {
Expand All @@ -231,7 +31,12 @@ const LocationMarkers = ({
const google = getGoogleMaps()
const currentMarkers = markersRef.current

const newLocationIds = new Set(locations.map((loc) => loc.id))
// While Street View is open, Google projects the map markers into the
// panorama. Hide them all there; PanoramaHandler draws the dots and
// fruit-name labels (and the selected "here" pin) for the panorama instead.
const visibleLocations = streetViewOpen ? [] : locations

const newLocationIds = new Set(visibleLocations.map((loc) => loc.id))
const existingLocationIds = new Set(currentMarkers.keys())

existingLocationIds.forEach((locationId) => {
Expand All @@ -248,7 +53,7 @@ const LocationMarkers = ({
}
})

locations.forEach((location) => {
visibleLocations.forEach((location) => {
const isSaved = Boolean(location.in_list)

if (!existingLocationIds.has(location.id)) {
Expand All @@ -259,7 +64,7 @@ const LocationMarkers = ({
const marker = new google.Marker({
position: { lat: location.lat, lng: location.lng },
map: googleMap,
optimized: locations.length > 100,
optimized: visibleLocations.length > 100,
icon: getMarkerIcon(google, isSaved),
zIndex: isSaved ? Z_INDEX.SAVED : Z_INDEX.DEFAULT,
})
Expand Down Expand Up @@ -386,6 +191,7 @@ const LocationMarkers = ({
showLabels,
hoveredLocationId,
mapType,
streetViewOpen,
])

useEffect(
Expand Down
Loading
Loading