Skip to content

Commit cb9f533

Browse files
authored
feat: Migrates the Place Photos sample. (#1064)
* feat: Migrates the Place Photos sample. * feat: Migrates the Place Photos sample. * Update package.json * Address review comments * Addresses second round of comments * Address third round of review comments
1 parent 48cf32f commit cb9f533

File tree

6 files changed

+302
-0
lines changed

6 files changed

+302
-0
lines changed

samples/place-photos/README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Google Maps JavaScript Sample
2+
3+
## place-photos
4+
5+
This sample demonstrates the use of the Places API to display photos of a place.
6+
7+
## Setup
8+
9+
### Before starting run:
10+
11+
`npm i`
12+
13+
### Run an example on a local web server
14+
15+
`cd samples/place-photos`
16+
`npm start`
17+
18+
### Build an individual example
19+
20+
`cd samples/place-photos`
21+
`npm run build`
22+
23+
From 'samples':
24+
25+
`npm run build --workspace=place-photos/`
26+
27+
### Build all of the examples.
28+
29+
From 'samples':
30+
31+
`npm run build-all`
32+
33+
### Run lint to check for problems
34+
35+
`cd samples/place-photos`
36+
`npx eslint index.ts`
37+
38+
## Feedback
39+
40+
For feedback related to this sample, please open a new issue on
41+
[GitHub](https://github.com/googlemaps-samples/js-api-samples/issues).

samples/place-photos/index.html

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<!doctype html>
2+
<!--
3+
@license
4+
Copyright 2026 Google LLC. All Rights Reserved.
5+
SPDX-License-Identifier: Apache-2.0
6+
-->
7+
<!-- [START maps_place_photos] -->
8+
<html lang="en">
9+
<head>
10+
<title>Place Photos</title>
11+
12+
<link rel="stylesheet" type="text/css" href="./style.css" />
13+
<script type="module" src="./index.js"></script>
14+
<!-- prettier-ignore -->
15+
<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)) })
16+
({ key: "AIzaSyA6myHzS10YXdcazAFalmXvDkrYCp5cLc8", v: "weekly" });</script>
17+
</head>
18+
<body>
19+
<div id="container">
20+
<div class="place-overview">
21+
<div id="info">
22+
<h1 id="heading"></h1>
23+
<div id="summary"></div>
24+
</div>
25+
<div id="gallery"></div>
26+
</div>
27+
<div id="expanded-image"></div>
28+
</div>
29+
</body>
30+
</html>
31+
<!-- [END maps_place_photos] -->

samples/place-photos/index.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/**
2+
* @license
3+
* Copyright 2026 Google LLC. All Rights Reserved.
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
// [START maps_place_photos]
8+
async function init() {
9+
const { Place } = (await google.maps.importLibrary(
10+
'places'
11+
)) as google.maps.PlacesLibrary;
12+
13+
// Use a place ID to create a new Place instance.
14+
const place = new Place({
15+
id: 'ChIJydSuSkkUkFQRsqhB-cEtYnw', // Woodland Park Zoo, Seattle WA
16+
});
17+
18+
// Call fetchFields, passing the desired data fields.
19+
await place.fetchFields({
20+
fields: ['displayName', 'photos', 'editorialSummary'],
21+
});
22+
23+
// Get the various HTML elements.
24+
const heading = document.getElementById('heading') as HTMLElement;
25+
const summary = document.getElementById('summary') as HTMLElement;
26+
const gallery = document.getElementById('gallery') as HTMLElement;
27+
const expandedImageDiv = document.getElementById(
28+
'expanded-image'
29+
) as HTMLElement;
30+
31+
// Show the display name and summary for the place.
32+
heading.textContent = place.displayName as string;
33+
summary.textContent = place.editorialSummary as string;
34+
35+
// Add photos to the gallery.
36+
place.photos?.forEach((photo) => {
37+
const altText = 'Photo of ' + place.displayName;
38+
const img = document.createElement('img');
39+
const imgButton = document.createElement('button');
40+
const expandedImage = document.createElement('img');
41+
img.src = photo?.getURI({ maxHeight: 380 });
42+
img.alt = altText;
43+
imgButton.addEventListener('click', (event) => {
44+
centerSelectedThumbnail(imgButton);
45+
event.preventDefault();
46+
expandedImage.src = img.src;
47+
expandedImage.alt = altText;
48+
expandedImageDiv.innerHTML = '';
49+
expandedImageDiv.appendChild(expandedImage);
50+
const attributionLabel = createAttribution(
51+
photo.authorAttributions[0]
52+
)!;
53+
expandedImageDiv.appendChild(attributionLabel);
54+
});
55+
56+
imgButton.addEventListener('focus', () => {
57+
centerSelectedThumbnail(imgButton);
58+
});
59+
60+
imgButton.appendChild(img);
61+
gallery.appendChild(imgButton);
62+
});
63+
64+
// Display the first photo.
65+
if (place.photos && place.photos.length > 0) {
66+
const photo = place.photos[0];
67+
const img = document.createElement('img');
68+
img.alt = 'Photo of ' + place.displayName;
69+
img.src = photo.getURI();
70+
expandedImageDiv.appendChild(img);
71+
72+
if (photo.authorAttributions && photo.authorAttributions.length > 0) {
73+
expandedImageDiv.appendChild(
74+
createAttribution(photo.authorAttributions[0])
75+
);
76+
}
77+
}
78+
79+
// Helper function to create attribution DIV.
80+
function createAttribution(
81+
attribution: google.maps.places.AuthorAttribution
82+
) {
83+
const attributionLabel = document.createElement('a');
84+
attributionLabel.classList.add('attribution-label');
85+
attributionLabel.textContent = attribution.displayName;
86+
attributionLabel.href = attribution.uri!;
87+
attributionLabel.target = '_blank';
88+
attributionLabel.rel = 'noopener noreferrer';
89+
return attributionLabel;
90+
}
91+
92+
// Helper function to center the selected thumbnail in the gallery.
93+
function centerSelectedThumbnail(element: HTMLElement) {
94+
element.scrollIntoView({
95+
behavior: 'smooth',
96+
block: 'center',
97+
inline: 'center',
98+
});
99+
}
100+
}
101+
102+
init();
103+
// [END maps_place_photos]

samples/place-photos/package.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "@js-api-samples/place-photos",
3+
"version": "1.0.0",
4+
"scripts": {
5+
"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",
6+
"test": "tsc && npm run build:vite --workspace=.",
7+
"start": "tsc && vite build --base './' && vite",
8+
"build:vite": "vite build --base './'",
9+
"preview": "vite preview"
10+
}
11+
}

samples/place-photos/style.css

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/**
2+
* @license
3+
* Copyright 2026 Google LLC. All Rights Reserved.
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
/* [START maps_place_photos] */
7+
/*
8+
* Optional: Makes the sample page fill the window.
9+
*/
10+
html,
11+
body {
12+
height: 100%;
13+
margin: 0;
14+
padding: 0;
15+
}
16+
17+
#container {
18+
display: flex;
19+
border: 2px solid black;
20+
border-radius: 10px;
21+
padding: 10px;
22+
max-width: 950px;
23+
height: 100%;
24+
max-height: 400px;
25+
box-sizing: border-box;
26+
}
27+
28+
.place-overview {
29+
width: 400px;
30+
height: 380px;
31+
overflow-x: auto;
32+
position: relative;
33+
margin-right: 20px;
34+
}
35+
36+
#info {
37+
font-family: sans-serif;
38+
position: sticky;
39+
position: -webkit-sticky;
40+
left: 0;
41+
padding-bottom: 10px;
42+
}
43+
44+
#heading {
45+
width: 500px;
46+
font-size: x-large;
47+
margin-bottom: 20px;
48+
}
49+
50+
#summary {
51+
width: 100%;
52+
}
53+
54+
#gallery {
55+
display: flex;
56+
padding-top: 10px;
57+
}
58+
59+
#gallery img {
60+
width: 200px;
61+
height: 200px;
62+
margin: 10px;
63+
border-radius: 10px;
64+
cursor: pointer;
65+
object-fit: cover; /* fill the area without distorting the image */
66+
}
67+
68+
#expanded-image {
69+
display: flex;
70+
height: 370px;
71+
overflow: hidden;
72+
background-color: #000;
73+
border-radius: 10px;
74+
margin: 0 auto;
75+
}
76+
77+
.attribution-label {
78+
background-color: rgba(255, 255, 255, 0.7);
79+
font-size: 10px;
80+
font-family: sans-serif;
81+
margin: 2px;
82+
position: absolute;
83+
}
84+
85+
button {
86+
display: flex;
87+
outline: none;
88+
border: none;
89+
padding: 0;
90+
background: none;
91+
cursor: pointer;
92+
}
93+
94+
button:focus {
95+
border: 2px solid blue;
96+
border-radius: 10px;
97+
}
98+
99+
/* [END maps_place_photos] */

samples/place-photos/tsconfig.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"compilerOptions": {
3+
"module": "esnext",
4+
"target": "esnext",
5+
"strict": true,
6+
"noImplicitAny": false,
7+
"lib": [
8+
"es2015",
9+
"esnext",
10+
"es6",
11+
"dom",
12+
"dom.iterable"
13+
],
14+
"moduleResolution": "Node",
15+
"jsx": "preserve"
16+
}
17+
}

0 commit comments

Comments
 (0)