-
Notifications
You must be signed in to change notification settings - Fork 7
Update Places UI Kit Nearby Search #880
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
1d26ef7
36a294c
96bc678
03e510d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,39 +7,67 @@ | |
| <!DOCTYPE html> | ||
| <html> | ||
| <head> | ||
| <title>Place List Nearby Search with Google Maps</title> | ||
| <meta charset="utf-8"> | ||
| <link rel="stylesheet" type="text/css" href="style.css"> | ||
| <script type="module" src="./index.js"></script> | ||
| <title>Place Search Nearby with Google Maps</title> | ||
| <meta charset="utf-8" /> | ||
| <link rel="stylesheet" type="text/css" href="style.css" /> | ||
| <script type="module" src="./index.js" defer></script> | ||
| <!-- prettier-ignore --> | ||
| <script> | ||
| (g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))}) | ||
| ({key: "AIzaSyA6myHzS10YXdcazAFalmXvDkrYCp5cLc8", v: "weekly", internalUsageAttributionIds: "gmp_mcp_codeassist_v0.1_github"}); | ||
| </script> | ||
| </head> | ||
| <body> | ||
| <!--[START maps_ui_kit_place_search_nearby_map] --> | ||
| <gmp-map center="-37.813,144.963" zoom="10" map-id="DEMO_MAP_ID"> | ||
| <div class="overlay" slot="control-inline-start-block-start"> | ||
| <div class="container"> | ||
| <gmp-map center="-37.813,144.963" zoom="16" map-id="DEMO_MAP_ID" disable-default-ui></gmp-map> | ||
| <div class="ui-panel"> | ||
| <div class="controls"> | ||
| <select name="types" class="type-select"> | ||
| <option value="">Select a place type</option> | ||
| <option value="cafe">Cafe</option> | ||
| <label for="type-select">Select a place type:</label> | ||
| <select id="type-select" class="type-select"> | ||
| <option value="cafe" selected>Cafe</option> | ||
| <option value="restaurant">Restaurant</option> | ||
| <option value="electric_vehicle_charging_station"> | ||
| EV charging station | ||
| </option> | ||
| <option value="electric_vehicle_charging_station" | ||
| >EV charging station</option | ||
| > | ||
|
||
| </select> | ||
| </div> | ||
| <div class="list-container"> | ||
| <gmp-place-list selectable style="display: none;"></gmp-place-list> | ||
| <gmp-place-search orientation="vertical" selectable> | ||
| <gmp-place-all-content></gmp-place-all-content> | ||
| <gmp-place-nearby-search-request | ||
| location-restriction="1000@-37.813, 144.963"> | ||
| </gmp-place-nearby-search-request> | ||
| </gmp-place-search> | ||
| </div> | ||
| </div> | ||
| </gmp-map> | ||
| </div> | ||
|
|
||
| <gmp-place-details style="display: none;"> | ||
| <gmp-place-details-place-request></gmp-place-details-place-request> | ||
| <gmp-place-all-content></gmp-place-all-content> | ||
| </gmp-place-details> | ||
| <div id="details-container"> | ||
| <gmp-place-details-compact orientation="horizontal" | ||
| truncation-preferred | ||
| style="width: 400px; | ||
| padding: 0; | ||
|
||
| margin: 0; | ||
| border: none; | ||
| background-color: transparent; | ||
| color-scheme: light;"> | ||
| <gmp-place-details-place-request | ||
| place="places/ChIJ3S-JXmauEmsRUcIaWtf4MzE"></gmp-place-details-place-request> | ||
|
||
| <gmp-place-content-config> | ||
| <gmp-place-media></gmp-place-media> | ||
| <gmp-place-rating></gmp-place-rating> | ||
| <gmp-place-price></gmp-place-price> | ||
| <gmp-place-accessible-entrance-icon></gmp-place-accessible-entrance-icon> | ||
| <gmp-place-open-now-status></gmp-place-open-now-status> | ||
| <gmp-place-attribution | ||
| light-scheme-color="gray" | ||
| dark-scheme-color="white"></gmp-place-attribution> | ||
|
||
| </gmp-place-content-config> | ||
| </gmp-place-details-compact> | ||
| </div> | ||
|
|
||
| <!--[END maps_ui_kit_place_search_nearby_map] --> | ||
| <script>(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))}) | ||
| ({key: "AIzaSyA6myHzS10YXdcazAFalmXvDkrYCp5cLc8", v: "alpha"});</script> | ||
| </body> | ||
| </html> | ||
| <!--[END maps_ui_kit_place_search_nearby] --> | ||
| <!--[END maps_ui_kit_place_search_nearby] --> | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -6,132 +6,166 @@ | |||||
| /* [START maps_ui_kit_place_search_nearby] */ | ||||||
|
|
||||||
| /* [START maps_ui_kit_place_search_nearby_query_selectors] */ | ||||||
| const map = document.querySelector("gmp-map") as any; | ||||||
| const placeList = document.querySelector("gmp-place-list") as any; | ||||||
| const typeSelect = document.querySelector(".type-select") as any; | ||||||
| const placeDetails = document.querySelector("gmp-place-details") as any; | ||||||
| const placeDetailsRequest = document.querySelector('gmp-place-details-place-request') as any; | ||||||
| // Query selectors for various elements in the HTML file. | ||||||
| const map = document.querySelector('gmp-map') as google.maps.MapElement | ||||||
| const placeSearch = document.querySelector('gmp-place-search') as any | ||||||
| const placeSearchQuery = document.querySelector( | ||||||
| 'gmp-place-nearby-search-request' | ||||||
| ) as any | ||||||
| const placeDetails = document.querySelector('gmp-place-details-compact') as any | ||||||
| const placeRequest = document.querySelector( | ||||||
| 'gmp-place-details-place-request' | ||||||
| ) as any | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||
| const typeSelect = document.querySelector('.type-select') as HTMLSelectElement | ||||||
| /* [END maps_ui_kit_place_search_nearby_query_selectors] */ | ||||||
| let markers = {}; | ||||||
| let infoWindow; | ||||||
|
|
||||||
| async function initMap(): Promise<void> { | ||||||
| await google.maps.importLibrary('places'); | ||||||
| const { LatLngBounds } = await google.maps.importLibrary('core') as google.maps.CoreLibrary; | ||||||
| const { InfoWindow } = await google.maps.importLibrary('maps') as google.maps.MapsLibrary; | ||||||
| const { spherical } = await google.maps.importLibrary('geometry') as google.maps.GeometryLibrary; | ||||||
|
|
||||||
| infoWindow = new InfoWindow; | ||||||
| let marker; | ||||||
|
|
||||||
| function getContainingCircle(bounds) { | ||||||
| const diameter = spherical.computeDistanceBetween( | ||||||
| bounds.getNorthEast(), | ||||||
| bounds.getSouthWest() | ||||||
| ); | ||||||
| const calculatedRadius = diameter / 2; | ||||||
| const cappedRadius = Math.min(calculatedRadius, 50000); // Radius cannot be more than 50000. | ||||||
| return { center: bounds.getCenter(), radius: cappedRadius }; | ||||||
| } | ||||||
|
|
||||||
| findCurrentLocation(); | ||||||
|
|
||||||
| // Global variables for the map, markers, and info window. | ||||||
| const markers: Map<string, google.maps.marker.AdvancedMarkerElement> = new Map() | ||||||
| let infoWindow | ||||||
|
|
||||||
| let AdvancedMarkerElement: typeof google.maps.marker.AdvancedMarkerElement | ||||||
| let LatLngBounds: typeof google.maps.LatLngBounds | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. consider - do we need all these to be globals? |
||||||
|
|
||||||
| // The init function is called when the page loads. | ||||||
| async function init(): Promise<void> { | ||||||
| // Import the necessary libraries from the Google Maps API. | ||||||
| const { InfoWindow } = (await google.maps.importLibrary( | ||||||
| 'maps' | ||||||
| )) as google.maps.MapsLibrary | ||||||
| await google.maps.importLibrary('places') | ||||||
| const markerLib = (await google.maps.importLibrary( | ||||||
| 'marker' | ||||||
| )) as google.maps.MarkerLibrary | ||||||
| const coreLib = (await google.maps.importLibrary( | ||||||
| 'core' | ||||||
| )) as google.maps.CoreLibrary | ||||||
|
Comment on lines
31
to
42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe have these load in parallel, instead of in sequence? |
||||||
|
|
||||||
| AdvancedMarkerElement = markerLib.AdvancedMarkerElement | ||||||
| LatLngBounds = coreLib.LatLngBounds | ||||||
|
Comment on lines
+44
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggest |
||||||
|
|
||||||
| // Create a new info window and set its content to the place details element. | ||||||
| infoWindow = new InfoWindow({ | ||||||
| content: placeDetails, | ||||||
| ariaLabel: 'Place Details', | ||||||
| headerDisabled: true, | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's check with a11y - what's the impact of disabling the header without offering our a custom close button? |
||||||
| pixelOffset: { width: 0, height: -40 } as google.maps.Size, | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. curious: why is a typecast needed here? |
||||||
| }) | ||||||
|
|
||||||
| // Set the map options. | ||||||
| map.innerMap.setOptions({ | ||||||
| mapTypeControl: false, | ||||||
| clickableIcons: false, | ||||||
| }); | ||||||
| mapTypeControl: false, | ||||||
| streetViewControl: false, | ||||||
| }) | ||||||
|
|
||||||
| // Add a click listener to the map to hide the info window when the map is clicked. | ||||||
| map.innerMap.addListener('click', () => { | ||||||
| hideInfoWindow() | ||||||
| }) | ||||||
| /* [START maps_ui_kit_place_search_nearby_event] */ | ||||||
| placeDetails.addEventListener('gmp-load', (event) => { | ||||||
| // Center the info window on the map. | ||||||
| map.innerMap.fitBounds(placeDetails.place.viewport, { top: 500, left: 400 }); | ||||||
| }); | ||||||
|
|
||||||
| typeSelect.addEventListener('change', (event) => { | ||||||
| // First remove all existing markers. | ||||||
| for(marker in markers){ | ||||||
| markers[marker].map = null; | ||||||
| // Add event listeners to the type select and place search elements. | ||||||
| typeSelect.addEventListener('change', searchPlaces) | ||||||
|
|
||||||
| placeSearch.addEventListener('gmp-select', (event: Event) => { | ||||||
| const { place } = event as any | ||||||
| if (markers.has(place.id)) { | ||||||
| markers.get(place.id)!.click() | ||||||
| } | ||||||
| markers = {}; | ||||||
|
|
||||||
| if (typeSelect.value) { | ||||||
| placeList.style.display = 'block'; | ||||||
| placeList.configureFromSearchNearbyRequest({ | ||||||
| locationRestriction: getContainingCircle( | ||||||
| map.innerMap.getBounds() | ||||||
| ), | ||||||
| includedPrimaryTypes: [typeSelect.value], | ||||||
| }).then(addMarkers); | ||||||
| // Handle user selection in Place Details. | ||||||
| placeList.addEventListener('gmp-placeselect', ({ place }) => { | ||||||
| markers[place.id].click(); | ||||||
| }); | ||||||
| }) | ||||||
| placeSearch.addEventListener( | ||||||
| 'gmp-load', | ||||||
| () => { | ||||||
| searchPlaces() | ||||||
| }, | ||||||
| { once: true } | ||||||
| ) | ||||||
| } | ||||||
| /* [END maps_ui_kit_place_search_nearby_event] */ | ||||||
| // The searchPlaces function is called when the user changes the type select or when the page loads. | ||||||
| function searchPlaces() { | ||||||
| // Get the map bounds and center. | ||||||
| const bounds = map.innerMap.getBounds()! | ||||||
| const cent = map.innerMap.getCenter()! | ||||||
| const ne = bounds.getNorthEast() | ||||||
| const sw = bounds.getSouthWest() | ||||||
| // Calculate the diameter of the map bounds and cap the radius at 50000. | ||||||
| const diameter = google.maps.geometry.spherical.computeDistanceBetween( | ||||||
| ne, | ||||||
| sw | ||||||
| ) | ||||||
| const cappedRadius = Math.min(diameter / 2, 50000) // Radius cannot be more than 50000. | ||||||
|
|
||||||
| // Close the info window and clear the markers. | ||||||
| infoWindow.close() | ||||||
|
|
||||||
| for (const marker of markers.values()) { | ||||||
| marker.map = null | ||||||
|
||||||
| marker.map = null | |
| marker.remove(); |
tiny tiny bit better practice, I think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| if (placeSearch.places.length > 0) { | |
placeSearch.places.forEach is a no-op if len=0. (i.e. safe to call)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| marker.addListener('click', () => { | |
| marker.addEventListener('click', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider attaching the info window to the marker instead? I'd need to check, but I think it will look a tad nicer, taking certain offsets into account
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
interesting - does this particular set of calls produce a particularly desired effect? (I usually just see fitBounds or setBounds called - but no extra setCenter)
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
once the placeDetails is moved to inside the IW, it will naturally only be shown when the IW is visible.
consider removing this function, omitting code that handles placeDetails's visibility, and just controlling the set via the IW
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
internalUsageAttributionIds maybe should be removed? (leave that to show external usage of MCP?)