Skip to content

Commit

Permalink
feat: Toggle map marker details when clicked (#23)
Browse files Browse the repository at this point in the history
  • Loading branch information
purplebugs authored Jul 16, 2024
1 parent ca1c1ea commit 68391ef
Show file tree
Hide file tree
Showing 2 changed files with 234 additions and 37 deletions.
190 changes: 169 additions & 21 deletions src/alpaca-map.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,11 @@ export default class AlpacaMap extends LitElement {
/* From me */
--almost-black: #333333;
--grey: #666666;
--brown: #83580b;
--green: #7a9a01; /* Pantone 377 C - from my chair */
color: var(--almost-black);
--private-farm: var(--brown);
--public-farm: var(--green);
}
.web-component-container {
Expand Down Expand Up @@ -182,6 +185,98 @@ export default class AlpacaMap extends LitElement {
width: auto;
background-color: var(--pale-blue);
}
/********* Farm styles in unhighlighted state *********/
/* Ref: https://developers.google.com/maps/documentation/javascript/advanced-markers/html-markers#maps_advanced_markers_html-css */
.farm {
display: flex;
align-items: center;
justify-content: center;
background-color: white;
border-radius: 1rem;
box-shadow: 10px 10px 5px #0003;
color: var(--almost-black);
/* Override google map font to avoid flicker when load */
font:
400 1.5em Poppins,
Arial,
sans-serif;
padding: 0.75rem;
width: auto;
max-width: 15rem;
}
.farm::after {
border-left: 9px solid transparent;
border-right: 9px solid transparent;
content: "";
height: 0;
left: 50%;
position: absolute;
top: 100%;
transform: translate(-50%);
width: 0;
z-index: 1;
}
.farm .summary {
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
gap: 0.5rem;
}
.farm .details {
display: none;
flex-direction: column;
flex: 1;
}
/********* Farm styles in highlighted state *********/
/* .farm.highlight {
background-color: #ffffff;
border-radius: 8px;
box-shadow: 10px 10px 5px rgba(0, 0, 0, 0.2);
height: 80px;
padding: 8px 15px;
width: auto;
} */
.farm.highlight .details {
display: flex;
}
/********* Farm category colours *********/
.farm.private {
border: 0.2em solid var(--private-farm);
}
.farm.public {
border: 0.2em solid var(--public-farm);
}
.farm.private::after {
border-top: 9px solid var(--private-farm);
}
.farm.public::after {
border-top: 9px solid var(--public-farm);
}
.farm.private .icon svg {
fill: var(--private-farm);
}
.farm.public .icon svg {
fill: var(--public-farm);
}
`,
];

Expand Down Expand Up @@ -290,25 +385,53 @@ export default class AlpacaMap extends LitElement {
this.map.mapTypes.set("styled_map", styledMapType);
this.map.setMapTypeId("styled_map");

const infoWindow = new google.maps.InfoWindow({
content: "",
disableAutoPan: true,
});

// Add markers to the map
function toggleHighlight(markerView, farm) {
if (markerView.content.classList.contains("highlight")) {
markerView.content.classList.remove("highlight");
markerView.zIndex = null;
} else {
markerView.content.classList.add("highlight");
markerView.zIndex = 1;
}
}

function buildContent(farm) {
const content = document.createElement("div");
content.classList.add("farm");
content.classList.add(farm?.category?.private ? "private" : "public");

content.innerHTML = `
<div class="summary">
<div class="icon">${iconHouseFlag().svgString}</div>
<div class="count">${farm?.count?.alpacas?.status?.active} 🦙</div>
</div>
<div class="details">
<h4>${farm?.name}</h4>
${farm?.city}
<address>${farm?.location?.google?.formatted_address}</address>
<address>
<a href="${farm?.location?.google?.directions_url_href}" target="_blank" rel="noreferrer" title="Google directions">Directions</a>
</address>
</div>
`;

return content;
}

const markers = this.farms.map((farm) => {
const marker = new google.maps.marker.AdvancedMarkerElement({
const marker = new AdvancedMarkerElement({
content: buildContent(farm),
position: farm.location.lat_lng,
title: farm?.name,
});

// markers can only be keyboard focusable when they have click listeners
// open info window when marker is clicked

// toggle marker summary/details when marker is clicked
marker.addListener("click", () => {
infoWindow.setContent(
farm.location.lat_lng.lat + ", " + farm.location.lat_lng.lng
);
infoWindow.open(this.map, marker);
toggleHighlight(marker, farm);
});

farm._marker = marker;
Expand All @@ -317,7 +440,20 @@ export default class AlpacaMap extends LitElement {
});

// Add a marker clusterer to manage the markers.
this.cluster = new MarkerClusterer({ map: this.map });
this.cluster = new MarkerClusterer({
map: this.map,
algorithmOptions: {
radius: 100,
/*
Ref: https://www.npmjs.com/package/supercluster
minPoints: 10,
minZoom: 4,
maxZoom: 16,
radius: 100,
maxZoom: 16,
*/
},
});
this.cluster.addMarkers(markers);
}

Expand Down Expand Up @@ -372,17 +508,23 @@ export default class AlpacaMap extends LitElement {
<div class="toggle-group">
<span class="toggle">
<input type="checkbox" id="public" name="public" checked />
<label for="public"> ${iconHouseFlag()}Public farms</label>
<label for="public">
${iconHouseFlag().htmlObject}Public farms</label
>
</span>
<span class="toggle">
<input type="checkbox" id="private" name="private" checked />
<label for="private">${iconKey()}Private farms</label>
<label for="private"
>${iconKey().htmlObject}Private farms</label
>
</span>
<span class="toggle">
<input type="checkbox" id="alpacaSales" name="alpacaSales" />
<label for="alpacaSales">${iconHandshake()}Alpaca sales</label>
<label for="alpacaSales"
>${iconHandshake().htmlObject}Alpaca sales</label
>
</span>
<span class="toggle">
Expand All @@ -392,18 +534,20 @@ export default class AlpacaMap extends LitElement {
name="alpacaWalking"
/>
<label for="alpacaWalking"
>${iconPersonHiking()}Alpaca walking</label
>${iconPersonHiking().htmlObject}Alpaca walking</label
>
</span>
<span class="toggle">
<input type="checkbox" id="bookable" name="bookable" />
<label for="bookable">${iconCalendarCheck()}Bookable</label>
<label for="bookable"
>${iconCalendarCheck().htmlObject}Bookable</label
>
</span>
<span class="toggle">
<input type="checkbox" id="shop" name="shop" />
<label for="shop">${iconStore()}Shop</label>
<label for="shop">${iconStore().htmlObject}Shop</label>
</span>
<span class="toggle">
Expand All @@ -412,12 +556,16 @@ export default class AlpacaMap extends LitElement {
id="overnightStay"
name="overnightStay"
/>
<label for="overnightStay">${iconBed()}Overnight stay</label>
<label for="overnightStay"
>${iconBed().htmlObject}Overnight stay</label
>
</span>
<span class="toggle">
<input type="checkbox" id="studServices" name="studServices" />
<label for="studServices">${iconMars()}Stud services</label>
<label for="studServices"
>${iconMars().htmlObject}Stud services</label
>
</span>
</div>
</form>
Expand Down
81 changes: 65 additions & 16 deletions src/svg-icons.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { css, svg, html } from "lit";
import { unsafeSVG } from "lit/directives/unsafe-svg.js"; /* This is safe since this is duplicating internal known markup, not markup from user */

export const iconStyles = [
css`
Expand All @@ -11,49 +12,97 @@ export const iconStyles = [
];

export const iconBed = () => {
const svgIcon = svg`<path d="M32 32c17.7 0 32 14.3 32 32V320H288V160c0-17.7 14.3-32 32-32H544c53 0 96 43 96 96V448c0 17.7-14.3 32-32 32s-32-14.3-32-32V416H352 320 64v32c0 17.7-14.3 32-32 32s-32-14.3-32-32V64C0 46.3 14.3 32 32 32zm144 96a80 80 0 1 1 0 160 80 80 0 1 1 0-160z"/>`;
const path = `<path d="M32 32c17.7 0 32 14.3 32 32V320H288V160c0-17.7 14.3-32 32-32H544c53 0 96 43 96 96V448c0 17.7-14.3 32-32 32s-32-14.3-32-32V416H352 320 64v32c0 17.7-14.3 32-32 32s-32-14.3-32-32V64C0 46.3 14.3 32 32 32zm144 96a80 80 0 1 1 0 160 80 80 0 1 1 0-160z"/>`;

return html`<svg class="icon" viewBox="0 0 640 512">${svgIcon}</svg>`;
const svgFragment = unsafeSVG(path);

return {
svgString: toSVGString(path),
htmlObject: toHTMLObject(svgFragment),
};
};

export const iconCalendarCheck = () => {
const svgIcon = svg`<path d="M128 0c17.7 0 32 14.3 32 32V64H288V32c0-17.7 14.3-32 32-32s32 14.3 32 32V64h48c26.5 0 48 21.5 48 48v48H0V112C0 85.5 21.5 64 48 64H96V32c0-17.7 14.3-32 32-32zM0 192H448V464c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V192zM329 305c9.4-9.4 9.4-24.6 0-33.9s-24.6-9.4-33.9 0l-95 95-47-47c-9.4-9.4-24.6-9.4-33.9 0s-9.4 24.6 0 33.9l64 64c9.4 9.4 24.6 9.4 33.9 0L329 305z"/>`;
const path = `<path d="M128 0c17.7 0 32 14.3 32 32V64H288V32c0-17.7 14.3-32 32-32s32 14.3 32 32V64h48c26.5 0 48 21.5 48 48v48H0V112C0 85.5 21.5 64 48 64H96V32c0-17.7 14.3-32 32-32zM0 192H448V464c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V192zM329 305c9.4-9.4 9.4-24.6 0-33.9s-24.6-9.4-33.9 0l-95 95-47-47c-9.4-9.4-24.6-9.4-33.9 0s-9.4 24.6 0 33.9l64 64c9.4 9.4 24.6 9.4 33.9 0L329 305z"/>`;

const svgFragment = unsafeSVG(path);

return html`<svg class="icon" viewBox="0 0 640 512">${svgIcon}</svg>`;
return {
svgString: toSVGString(path),
htmlObject: toHTMLObject(svgFragment),
};
};

export const iconHandshake = () => {
const svgIcon = svg`<path d="M323.4 85.2l-96.8 78.4c-16.1 13-19.2 36.4-7 53.1c12.9 17.8 38 21.3 55.3 7.8l99.3-77.2c7-5.4 17-4.2 22.5 2.8s4.2 17-2.8 22.5l-20.9 16.2L512 316.8V128h-.7l-3.9-2.5L434.8 79c-15.3-9.8-33.2-15-51.4-15c-21.8 0-43 7.5-60 21.2zm22.8 124.4l-51.7 40.2C263 274.4 217.3 268 193.7 235.6c-22.2-30.5-16.6-73.1 12.7-96.8l83.2-67.3c-11.6-4.9-24.1-7.4-36.8-7.4C234 64 215.7 69.6 200 80l-72 48V352h28.2l91.4 83.4c19.6 17.9 49.9 16.5 67.8-3.1c5.5-6.1 9.2-13.2 11.1-20.6l17 15.6c19.5 17.9 49.9 16.6 67.8-2.9c4.5-4.9 7.8-10.6 9.9-16.5c19.4 13 45.8 10.3 62.1-7.5c17.9-19.5 16.6-49.9-2.9-67.8l-134.2-123zM16 128c-8.8 0-16 7.2-16 16V352c0 17.7 14.3 32 32 32H64c17.7 0 32-14.3 32-32V128H16zM48 320a16 16 0 1 1 0 32 16 16 0 1 1 0-32zM544 128V352c0 17.7 14.3 32 32 32h32c17.7 0 32-14.3 32-32V144c0-8.8-7.2-16-16-16H544zm32 208a16 16 0 1 1 32 0 16 16 0 1 1 -32 0z"/>`;
const path = `<path d="M323.4 85.2l-96.8 78.4c-16.1 13-19.2 36.4-7 53.1c12.9 17.8 38 21.3 55.3 7.8l99.3-77.2c7-5.4 17-4.2 22.5 2.8s4.2 17-2.8 22.5l-20.9 16.2L512 316.8V128h-.7l-3.9-2.5L434.8 79c-15.3-9.8-33.2-15-51.4-15c-21.8 0-43 7.5-60 21.2zm22.8 124.4l-51.7 40.2C263 274.4 217.3 268 193.7 235.6c-22.2-30.5-16.6-73.1 12.7-96.8l83.2-67.3c-11.6-4.9-24.1-7.4-36.8-7.4C234 64 215.7 69.6 200 80l-72 48V352h28.2l91.4 83.4c19.6 17.9 49.9 16.5 67.8-3.1c5.5-6.1 9.2-13.2 11.1-20.6l17 15.6c19.5 17.9 49.9 16.6 67.8-2.9c4.5-4.9 7.8-10.6 9.9-16.5c19.4 13 45.8 10.3 62.1-7.5c17.9-19.5 16.6-49.9-2.9-67.8l-134.2-123zM16 128c-8.8 0-16 7.2-16 16V352c0 17.7 14.3 32 32 32H64c17.7 0 32-14.3 32-32V128H16zM48 320a16 16 0 1 1 0 32 16 16 0 1 1 0-32zM544 128V352c0 17.7 14.3 32 32 32h32c17.7 0 32-14.3 32-32V144c0-8.8-7.2-16-16-16H544zm32 208a16 16 0 1 1 32 0 16 16 0 1 1 -32 0z"/>`;

const svgFragment = unsafeSVG(path);

return html`<svg class="icon" viewBox="0 0 640 512">${svgIcon}</svg>`;
return {
svgString: toSVGString(path),
htmlObject: toHTMLObject(svgFragment),
};
};

export const iconHouseFlag = () => {
const svgIcon = svg`<path d="M480 0c-17.7 0-32 14.3-32 32V192 512h64V192H624c8.8 0 16-7.2 16-16V48c0-8.8-7.2-16-16-16H512c0-17.7-14.3-32-32-32zM416 159L276.8 39.7c-12-10.3-29.7-10.3-41.7 0l-224 192C1 240.4-2.7 254.5 2 267.1S18.6 288 32 288H64V480c0 17.7 14.3 32 32 32h64c17.7 0 32-14.3 32-32V384c0-17.7 14.3-32 32-32h64c17.7 0 32 14.3 32 32v96c0 17.7 14.3 32 32 32h64.7l.2 0h-1V159z"/>`;
const path = `<path d="M480 0c-17.7 0-32 14.3-32 32V192 512h64V192H624c8.8 0 16-7.2 16-16V48c0-8.8-7.2-16-16-16H512c0-17.7-14.3-32-32-32zM416 159L276.8 39.7c-12-10.3-29.7-10.3-41.7 0l-224 192C1 240.4-2.7 254.5 2 267.1S18.6 288 32 288H64V480c0 17.7 14.3 32 32 32h64c17.7 0 32-14.3 32-32V384c0-17.7 14.3-32 32-32h64c17.7 0 32 14.3 32 32v96c0 17.7 14.3 32 32 32h64.7l.2 0h-1V159z" />`;

return html`<svg class="icon" viewBox="0 0 640 512">${svgIcon}</svg>`;
const svgFragment = unsafeSVG(path);

return {
svgString: toSVGString(path),
htmlObject: toHTMLObject(svgFragment),
};
};

export const iconKey = () => {
const svgIcon = svg`<path d="M336 352c97.2 0 176-78.8 176-176S433.2 0 336 0S160 78.8 160 176c0 18.7 2.9 36.8 8.3 53.7L7 391c-4.5 4.5-7 10.6-7 17v80c0 13.3 10.7 24 24 24h80c13.3 0 24-10.7 24-24V448h40c13.3 0 24-10.7 24-24V384h40c6.4 0 12.5-2.5 17-7l33.3-33.3c16.9 5.4 35 8.3 53.7 8.3zM376 96a40 40 0 1 1 0 80 40 40 0 1 1 0-80z"/>`;
const path = `<path d="M336 352c97.2 0 176-78.8 176-176S433.2 0 336 0S160 78.8 160 176c0 18.7 2.9 36.8 8.3 53.7L7 391c-4.5 4.5-7 10.6-7 17v80c0 13.3 10.7 24 24 24h80c13.3 0 24-10.7 24-24V448h40c13.3 0 24-10.7 24-24V384h40c6.4 0 12.5-2.5 17-7l33.3-33.3c16.9 5.4 35 8.3 53.7 8.3zM376 96a40 40 0 1 1 0 80 40 40 0 1 1 0-80z"/>`;

const svgFragment = unsafeSVG(path);

return html`<svg class="icon" viewBox="0 0 640 512">${svgIcon}</svg>`;
return {
svgString: toSVGString(path),
htmlObject: toHTMLObject(svgFragment),
};
};

export const iconMars = () => {
const svgIcon = svg`<path d="M289.8 46.8c3.7-9 12.5-14.8 22.2-14.8H424c13.3 0 24 10.7 24 24V168c0 9.7-5.8 18.5-14.8 22.2s-19.3 1.7-26.2-5.2l-33.4-33.4L321 204.2c19.5 28.4 31 62.7 31 99.8c0 97.2-78.8 176-176 176S0 401.2 0 304s78.8-176 176-176c37 0 71.4 11.4 99.8 31l52.6-52.6L295 73c-6.9-6.9-8.9-17.2-5.2-26.2zM400 80l0 0h0v0zM176 416a112 112 0 1 0 0-224 112 112 0 1 0 0 224z"/>`;
const path = `<path d="M289.8 46.8c3.7-9 12.5-14.8 22.2-14.8H424c13.3 0 24 10.7 24 24V168c0 9.7-5.8 18.5-14.8 22.2s-19.3 1.7-26.2-5.2l-33.4-33.4L321 204.2c19.5 28.4 31 62.7 31 99.8c0 97.2-78.8 176-176 176S0 401.2 0 304s78.8-176 176-176c37 0 71.4 11.4 99.8 31l52.6-52.6L295 73c-6.9-6.9-8.9-17.2-5.2-26.2zM400 80l0 0h0v0zM176 416a112 112 0 1 0 0-224 112 112 0 1 0 0 224z"/>`;

const svgFragment = unsafeSVG(path);

return html`<svg class="icon" viewBox="0 0 640 512">${svgIcon}</svg>`;
return {
svgString: toSVGString(path),
htmlObject: toHTMLObject(svgFragment),
};
};

export const iconPersonHiking = () => {
const svgIcon = svg`<path d="M192 48a48 48 0 1 1 96 0 48 48 0 1 1 -96 0zm51.3 182.7L224.2 307l49.7 49.7c9 9 14.1 21.2 14.1 33.9V480c0 17.7-14.3 32-32 32s-32-14.3-32-32V397.3l-73.9-73.9c-15.8-15.8-22.2-38.6-16.9-60.3l20.4-84c8.3-34.1 42.7-54.9 76.7-46.4c19 4.8 35.6 16.4 46.4 32.7L305.1 208H336V184c0-13.3 10.7-24 24-24s24 10.7 24 24v55.8c0 .1 0 .2 0 .2s0 .2 0 .2V488c0 13.3-10.7 24-24 24s-24-10.7-24-24V272H296.6c-16 0-31-8-39.9-21.4l-13.3-20zM81.1 471.9L117.3 334c3 4.2 6.4 8.2 10.1 11.9l41.9 41.9L142.9 488.1c-4.5 17.1-22 27.3-39.1 22.8s-27.3-22-22.8-39.1zm55.5-346L101.4 266.5c-3 12.1-14.9 19.9-27.2 17.9l-47.9-8c-14-2.3-22.9-16.3-19.2-30L31.9 155c9.5-34.8 41.1-59 77.2-59h4.2c15.6 0 27.1 14.7 23.3 29.8z"/>`;
const path = `<path d="M192 48a48 48 0 1 1 96 0 48 48 0 1 1 -96 0zm51.3 182.7L224.2 307l49.7 49.7c9 9 14.1 21.2 14.1 33.9V480c0 17.7-14.3 32-32 32s-32-14.3-32-32V397.3l-73.9-73.9c-15.8-15.8-22.2-38.6-16.9-60.3l20.4-84c8.3-34.1 42.7-54.9 76.7-46.4c19 4.8 35.6 16.4 46.4 32.7L305.1 208H336V184c0-13.3 10.7-24 24-24s24 10.7 24 24v55.8c0 .1 0 .2 0 .2s0 .2 0 .2V488c0 13.3-10.7 24-24 24s-24-10.7-24-24V272H296.6c-16 0-31-8-39.9-21.4l-13.3-20zM81.1 471.9L117.3 334c3 4.2 6.4 8.2 10.1 11.9l41.9 41.9L142.9 488.1c-4.5 17.1-22 27.3-39.1 22.8s-27.3-22-22.8-39.1zm55.5-346L101.4 266.5c-3 12.1-14.9 19.9-27.2 17.9l-47.9-8c-14-2.3-22.9-16.3-19.2-30L31.9 155c9.5-34.8 41.1-59 77.2-59h4.2c15.6 0 27.1 14.7 23.3 29.8z"/>`;

return html`<svg class="icon" viewBox="0 0 640 512">${svgIcon}</svg>`;
const svgFragment = unsafeSVG(path);

return {
svgString: toSVGString(path),
htmlObject: toHTMLObject(svgFragment),
};
};

export const iconStore = () => {
const svgIcon = svg`<path d="M547.6 103.8L490.3 13.1C485.2 5 476.1 0 466.4 0H109.6C99.9 0 90.8 5 85.7 13.1L28.3 103.8c-29.6 46.8-3.4 111.9 51.9 119.4c4 .5 8.1 .8 12.1 .8c26.1 0 49.3-11.4 65.2-29c15.9 17.6 39.1 29 65.2 29c26.1 0 49.3-11.4 65.2-29c15.9 17.6 39.1 29 65.2 29c26.2 0 49.3-11.4 65.2-29c16 17.6 39.1 29 65.2 29c4.1 0 8.1-.3 12.1-.8c55.5-7.4 81.8-72.5 52.1-119.4zM499.7 254.9l-.1 0c-5.3 .7-10.7 1.1-16.2 1.1c-12.4 0-24.3-1.9-35.4-5.3V384H128V250.6c-11.2 3.5-23.2 5.4-35.6 5.4c-5.5 0-11-.4-16.3-1.1l-.1 0c-4.1-.6-8.1-1.3-12-2.3V384v64c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V384 252.6c-4 1-8 1.8-12.3 2.3z"/>`;
const path = `<path d="M547.6 103.8L490.3 13.1C485.2 5 476.1 0 466.4 0H109.6C99.9 0 90.8 5 85.7 13.1L28.3 103.8c-29.6 46.8-3.4 111.9 51.9 119.4c4 .5 8.1 .8 12.1 .8c26.1 0 49.3-11.4 65.2-29c15.9 17.6 39.1 29 65.2 29c26.1 0 49.3-11.4 65.2-29c15.9 17.6 39.1 29 65.2 29c26.2 0 49.3-11.4 65.2-29c16 17.6 39.1 29 65.2 29c4.1 0 8.1-.3 12.1-.8c55.5-7.4 81.8-72.5 52.1-119.4zM499.7 254.9l-.1 0c-5.3 .7-10.7 1.1-16.2 1.1c-12.4 0-24.3-1.9-35.4-5.3V384H128V250.6c-11.2 3.5-23.2 5.4-35.6 5.4c-5.5 0-11-.4-16.3-1.1l-.1 0c-4.1-.6-8.1-1.3-12-2.3V384v64c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V384 252.6c-4 1-8 1.8-12.3 2.3z"/>`;

const svgFragment = unsafeSVG(path);

return {
svgString: toSVGString(path),
htmlObject: toHTMLObject(svgFragment),
};
};

export const toSVGString = (path) => {
return `<svg class="icon" viewBox="0 0 640 512">${path}</svg>`;
};

return html`<svg class="icon" viewBox="0 0 640 512">${svgIcon}</svg>`;
export const toHTMLObject = (svgFragment) => {
return html`<svg class="icon" viewBox="0 0 640 512">${svgFragment}</svg>`;
};

0 comments on commit 68391ef

Please sign in to comment.