diff --git a/samples/place-photos/README.md b/samples/place-photos/README.md new file mode 100644 index 00000000..0c34bbae --- /dev/null +++ b/samples/place-photos/README.md @@ -0,0 +1,41 @@ +# Google Maps JavaScript Sample + +## place-photos + +This sample demonstrates the use of the Places API to display photos of a place. + +## Setup + +### Before starting run: + +`npm i` + +### Run an example on a local web server + +`cd samples/place-photos` +`npm start` + +### Build an individual example + +`cd samples/place-photos` +`npm run build` + +From 'samples': + +`npm run build --workspace=place-photos/` + +### Build all of the examples. + +From 'samples': + +`npm run build-all` + +### Run lint to check for problems + +`cd samples/place-photos` +`npx eslint index.ts` + +## Feedback + +For feedback related to this sample, please open a new issue on +[GitHub](https://github.com/googlemaps-samples/js-api-samples/issues). diff --git a/samples/place-photos/index.html b/samples/place-photos/index.html new file mode 100644 index 00000000..80cce717 --- /dev/null +++ b/samples/place-photos/index.html @@ -0,0 +1,31 @@ + + + + + + Place Photos + + + + + + + +
+
+
+

+
+
+ +
+
+
+ + + diff --git a/samples/place-photos/index.ts b/samples/place-photos/index.ts new file mode 100644 index 00000000..bfeb8727 --- /dev/null +++ b/samples/place-photos/index.ts @@ -0,0 +1,95 @@ +/** + * @license + * Copyright 2026 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +// [START maps_place_photos] +async function init() { + const { Place } = (await google.maps.importLibrary( + 'places' + )) as google.maps.PlacesLibrary; + + // Use a place ID to create a new Place instance. + const place = new Place({ + id: 'ChIJydSuSkkUkFQRsqhB-cEtYnw', // Woodland Park Zoo, Seattle WA + }); + + // Call fetchFields, passing the desired data fields. + await place.fetchFields({ + fields: ['displayName', 'photos', 'editorialSummary'], + }); + + // Get the various HTML elements. + let heading = document.getElementById('heading') as HTMLElement; + let summary = document.getElementById('summary') as HTMLElement; + let gallery = document.getElementById('gallery') as HTMLElement; + let expandedImageDiv = document.getElementById( + 'expanded-image' + ) as HTMLElement; + let attributionLabel; + + // Show the display name and summary for the place. + heading.textContent = place.displayName as string; + summary.textContent = place.editorialSummary as string; + + // Add photos to the gallery. + if (place.photos) { + place.photos?.forEach((photo) => { + const altText = 'Photo of ' + place.displayName; + const img = document.createElement('img'); + const imgButton = document.createElement('button'); + const expandedImage = document.createElement('img'); + img.src = photo?.getURI({ maxHeight: 380 }); + img.alt = altText; + imgButton.addEventListener('click', (event) => { + centerSelectedThumbnail(imgButton); + event.preventDefault(); + expandedImage.src = img.src; + expandedImage.alt = altText; + expandedImageDiv.innerHTML = ''; + expandedImageDiv.appendChild(expandedImage); + attributionLabel = createAttribution(photo.authorAttributions); + expandedImageDiv.appendChild(attributionLabel); + }); + + imgButton.addEventListener('focus', ()=> { + centerSelectedThumbnail(imgButton); + }); + + imgButton.appendChild(img); + gallery.appendChild(imgButton); + }); + } + + // Display the first photo. + if (place.photos && place.photos.length > 0) { + const img = document.createElement('img'); + img.alt = 'Photo of ' + place.displayName; + img.src = place.photos![0].getURI(); + expandedImageDiv.appendChild(img); + attributionLabel = createAttribution(place.photos![0].authorAttributions); + expandedImageDiv.appendChild(attributionLabel); + } + + // Helper function to create attribution DIV. + function createAttribution(attribution) { + const attributionLabel = document.createElement('a'); + attributionLabel.classList.add('attribution-label'); + if (attribution && attribution[0]) { + attributionLabel.textContent = attribution[0].displayName; + attributionLabel.href = attribution[0].uri; + attributionLabel.target = '_blank'; + attributionLabel.rel = 'noopener noreferrer'; + return attributionLabel; + } + } + + // Helper function to center the selected thumbnail in the gallery. + function centerSelectedThumbnail(element: HTMLElement) { + element.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' }); + } +} + +init(); +// [END maps_place_photos] diff --git a/samples/place-photos/package.json b/samples/place-photos/package.json new file mode 100644 index 00000000..6b47f1c6 --- /dev/null +++ b/samples/place-photos/package.json @@ -0,0 +1,11 @@ +{ + "name": "@js-api-samples/place-photos", + "version": "1.0.0", + "scripts": { + "build": "tsc && bash ../jsfiddle.sh place-photos && bash ../app.sh place-photos && bash ../docs.sh place-photos && npm run build:vite --workspace=. && bash ../dist.sh place-photos", + "test": "tsc && npm run build:vite --workspace=.", + "start": "tsc && vite build --base './' && vite", + "build:vite": "vite build --base './'", + "preview": "vite preview" + } +} diff --git a/samples/place-photos/style.css b/samples/place-photos/style.css new file mode 100644 index 00000000..2703d426 --- /dev/null +++ b/samples/place-photos/style.css @@ -0,0 +1,99 @@ +/** + * @license + * Copyright 2026 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +/* [START maps_place_photos] */ +/* + * Optional: Makes the sample page fill the window. + */ +html, +body { + height: 100%; + margin: 0; + padding: 0; +} + +#container { + display: flex; + border: 2px solid black; + border-radius: 10px; + padding: 10px; + max-width: 950px; + height: 100%; + max-height: 400px; + box-sizing: border-box; +} + +.place-overview { + width: 400px; + height: 380px; + overflow-x: auto; + position: relative; + margin-right: 20px; +} + +#info { + font-family: sans-serif; + position: sticky; + position: -webkit-sticky; + left: 0; + padding-bottom: 10px; +} + +#heading { + width: 500px; + font-size: x-large; + margin-bottom: 20px; +} + +#summary { + width: 100%; +} + +#gallery { + display: flex; + padding-top: 10px; +} + +#gallery img { + width: 200px; + height: 200px; + margin: 10px; + border-radius: 10px; + cursor: pointer; + object-fit: cover; /* fill the area without distorting the image */ +} + +#expanded-image { + display: flex; + height: 370px; + overflow: hidden; + background-color: #000; + border-radius: 10px; + margin: 0 auto; +} + +.attribution-label { + background-color: rgba(255, 255, 255, 0.7); + font-size: 10px; + font-family: sans-serif; + margin: 2px; + position: absolute; +} + +button { + display: flex; + outline: none; + border: none; + padding: 0; + background: none; + cursor: pointer; +} + +button:focus { + border: 2px solid blue; + border-radius: 10px; +} + +/* [END maps_place_photos] */ diff --git a/samples/place-photos/tsconfig.json b/samples/place-photos/tsconfig.json new file mode 100644 index 00000000..366aabb0 --- /dev/null +++ b/samples/place-photos/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "module": "esnext", + "target": "esnext", + "strict": true, + "noImplicitAny": false, + "lib": [ + "es2015", + "esnext", + "es6", + "dom", + "dom.iterable" + ], + "moduleResolution": "Node", + "jsx": "preserve" + } +}