From ec5c7c75062136ab851e8a17d205f2265498542b Mon Sep 17 00:00:00 2001 From: shwetadeshmukh Date: Thu, 30 Apr 2026 14:30:02 +0530 Subject: [PATCH] feat(localities-geocode): add structured response panel - Display geocode summary, address components, and collapsible raw JSON - Add copy-to-clipboard button for raw JSON output --- samples/localities-geocode/index.njk | 27 +++++- samples/localities-geocode/index.ts | 117 +++++++++++++++++++++++++- samples/localities-geocode/style.scss | 107 +++++++++++++++++++++-- shared/scss/_mixins.scss | 39 +++++++++ 4 files changed, 281 insertions(+), 9 deletions(-) diff --git a/samples/localities-geocode/index.njk b/samples/localities-geocode/index.njk index de919713..917d06e4 100644 --- a/samples/localities-geocode/index.njk +++ b/samples/localities-geocode/index.njk @@ -14,9 +14,30 @@ -
- Enter an address to geocode or click on the map to reverse geocode. -
+
diff --git a/samples/localities-geocode/index.ts b/samples/localities-geocode/index.ts index c2ab6083..1a1c4f0a 100644 --- a/samples/localities-geocode/index.ts +++ b/samples/localities-geocode/index.ts @@ -49,6 +49,7 @@ clearSearchBtn.addEventListener("click", () => { marker.setMap(null); infoWindow.close(); } + clearResponse(); inputElement.focus(); }); @@ -92,10 +93,124 @@ function displayLocality( infoWindow.setContent(`${locality.formatted_address}`); infoWindow.open(map, marker); map.setCenter(locality.geometry.location); - map.setZoom(14); + renderResponse(locality); } } +function escapeHtml(value: string): string { + return value + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} + +function formatNameValue(value: string | string[]): string { + return Array.isArray(value) ? value.join(", ") : value; +} + +function renderResponse( + locality: woosmap.map.localities.LocalitiesGeocodeResult, +) { + const panel = document.getElementById("response-panel"); + const summaryEl = panel?.querySelector(".summary") as HTMLElement | null; + const componentsEl = panel?.querySelector( + ".address-components", + ) as HTMLElement | null; + const rawEl = document.getElementById("raw-json"); + if (!panel || !summaryEl || !componentsEl || !rawEl) return; + + panel.dataset.empty = "false"; + + const summaryRows: string[] = []; + summaryRows.push( + `

Formatted address:${escapeHtml(locality.formatted_address)}

`, + ); + if (locality.types && locality.types.length) { + summaryRows.push( + `

Types:${escapeHtml(locality.types.join(", "))}

`, + ); + } + if (locality.public_id) { + summaryRows.push( + `

Public ID:${escapeHtml(locality.public_id)}

`, + ); + } + if (typeof locality.distance === "number") { + summaryRows.push( + `

Distance (m):${locality.distance}

`, + ); + } + if (locality.geometry) { + const { geometry } = locality; + if (geometry.location_type) { + summaryRows.push( + `

Location type:${escapeHtml(geometry.location_type)}

`, + ); + } + summaryRows.push( + `

Latitude:${geometry.location.lat}

`, + `

Longitude:${geometry.location.lng}

`, + ); + if (geometry.viewport) { + const { viewport } = geometry; + summaryRows.push( + `

Viewport NE:${viewport.northeast.lat}, ${viewport.northeast.lng}

`, + `

Viewport SW:${viewport.southwest.lat}, ${viewport.southwest.lng}

`, + ); + } + } + summaryEl.innerHTML = `
Summary
${summaryRows.join("")}`; + + if (locality.address_components && locality.address_components.length) { + const rows = locality.address_components + .map((compo) => { + const label = escapeHtml((compo.types[0] ?? "").replace(/_/g, " ")); + const longName = escapeHtml(formatNameValue(compo.long_name)); + return `

${label}:${longName}

`; + }) + .join(""); + componentsEl.innerHTML = `
Address components
${rows}`; + } else { + componentsEl.innerHTML = ""; + } + + rawEl.textContent = JSON.stringify(locality, null, 2); +} + +const copyBtn = document.getElementById("copy-raw-json"); +if (copyBtn) { + copyBtn.addEventListener("click", (e) => { + e.preventDefault(); + e.stopPropagation(); + const rawEl = document.getElementById("raw-json"); + const text = rawEl?.textContent ?? ""; + if (!text) return; + navigator.clipboard + .writeText(text) + .then(() => { + copyBtn.classList.add("copied"); + window.setTimeout(() => copyBtn.classList.remove("copied"), 1500); + }) + .catch((err) => console.error("Failed to copy raw JSON:", err)); + }); +} + +function clearResponse() { + const panel = document.getElementById("response-panel"); + if (!panel) return; + panel.dataset.empty = "true"; + const summaryEl = panel.querySelector(".summary") as HTMLElement | null; + const componentsEl = panel.querySelector( + ".address-components", + ) as HTMLElement | null; + const rawEl = document.getElementById("raw-json"); + if (summaryEl) summaryEl.innerHTML = ""; + if (componentsEl) componentsEl.innerHTML = ""; + if (rawEl) rawEl.textContent = ""; +} + declare global { interface Window { initMap: () => void; diff --git a/samples/localities-geocode/style.scss b/samples/localities-geocode/style.scss index 881873ab..3f6bbfcf 100644 --- a/samples/localities-geocode/style.scss +++ b/samples/localities-geocode/style.scss @@ -17,11 +17,108 @@ @include actionText($color: #fff); } -#instructions { - max-width: 300px; - font-size: 17px; - padding: 10px; - @include map-panel($bottom: 50px, $left: 0); +#response-panel { + @include map-panel($bottom: 25px, $right: 10px); + width: 360px; + max-width: calc(100vw - 40px); + max-height: calc(40vh - 25px); + overflow-y: auto; + padding: 0; + font-size: 13px; + + .panel-header { + padding: 12px 14px; + border-bottom: 1px solid rgba(0, 0, 0, 0.08); + } + + .panel-title { + font-weight: 700; + font-size: 14px; + margin-bottom: 4px; + } + + .panel-hint { + color: rgba(0, 0, 0, 0.6); + font-size: 12px; + } + + .section-title { + color: rgba(0, 0, 0, 0.5); + font-size: 10px; + text-transform: uppercase; + letter-spacing: 1px; + padding: 14px 12px 8px 12px; + } + + .summary, + .address-components { + background-color: #fff; + } + + .address-components { + background-color: rgba(0, 0, 0, 0.03); + } + + .option-detail { + display: flex; + flex-wrap: wrap; + padding: 0 12px 8px 12px; + margin: 0; + overflow-wrap: anywhere; + word-break: break-word; + + &-label { + color: rgba(0, 0, 0, 0.5); + margin-right: 4px; + flex-shrink: 0; + } + } + + .bold { + font-weight: 700; + } + + .raw-json-details { + border-top: 1px solid rgba(0, 0, 0, 0.08); + + summary { + cursor: pointer; + padding: 10px 14px; + font-weight: 600; + user-select: none; + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + } + + .copy-btn { + @include copy-button; + } + + pre { + margin: 0 10px 10px 10px; + padding: 10px; + border: 1px solid #e0e0e0; + background-color: #fafafa; + border-radius: 6px; + white-space: pre-wrap; + overflow-x: auto; + font-size: 12px; + font-family: Consolas, "Andale Mono WT", "Andale Mono", "Lucida Console", + "Lucida Sans Typewriter", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", + "Liberation Mono", "Nimbus Mono L", Monaco, "Courier New", Courier, + monospace; + } + } + + &[data-empty="true"] { + .summary, + .address-components, + .raw-json-details { + display: none; + } + } } /* [END woosmap_localities_geocode] */ diff --git a/shared/scss/_mixins.scss b/shared/scss/_mixins.scss index 58287c22..108d3fba 100644 --- a/shared/scss/_mixins.scss +++ b/shared/scss/_mixins.scss @@ -75,6 +75,45 @@ } } +@mixin copy-button( + $color: rgba(0, 0, 0, 0.5), + $hover-color: #3d5afe, + $success-color: #2e7d32, + $padding: 2px 4px +) { + background: transparent; + border: 0; + padding: $padding; + cursor: pointer; + color: $color; + display: inline-flex; + align-items: center; + transition: color 0.2s ease-in-out; + + &:hover, + &:focus { + color: $hover-color; + outline: none; + } + + .icon-check { + display: none; + color: $success-color; + } + + &.copied { + color: $success-color; + + .icon-copy { + display: none; + } + + .icon-check { + display: inline-block; + } + } +} + @mixin progress($width: 70px, $height:70px, $borderColor: #f3f3f3, $borderTopColor: #444444) { position: fixed; top: 0;