From 431eb883c9ada165eee1a44578aec1f334732d3c Mon Sep 17 00:00:00 2001 From: Maurice Date: Mon, 21 Sep 2026 22:27:33 +0000 Subject: [PATCH] feat(help): pictures and translations for the trip's places column All eight guides of Trip > Places now have their pictures, taken from the real app, plus the hero of the screen, and the block is translated into the other 22 languages. Making the pictures showed three texts describing a UI that is not there, and they are corrected: the file import dialog has no Enrich places via Google switch (only the list import has one, and only with a Google key), a GPX import closes with a message while only KML and KMZ leave a summary behind, and both list providers are always offered. The search and the details column need no Google key either: they fill themselves from the TREK index, OpenStreetMap, Wikipedia, Wikivoyage and Wikimedia, and only ratings are Google's. The script needed the same honesty. ConfirmDialog and the two import dialogs are portals of their own with none of Modal's classes, so they get their own locators, and create-place now looks up a place whose entry actually carries a picture, opening hours and a description, instead of photographing an empty details column. --- client/e2e/help/trip-places.guide.ts | 73 ++++++--- client/e2e/help/trip-shared.ts | 17 ++- .../help-media/create-place/result.webp | Bin 0 -> 150186 bytes .../help-media/create-place/step-1.webp | Bin 0 -> 69574 bytes .../help-media/create-place/step-2.webp | Bin 0 -> 34118 bytes .../help-media/create-place/step-3.webp | Bin 0 -> 50054 bytes .../help-media/create-place/step-4.webp | Bin 0 -> 48830 bytes .../help-media/create-place/step-5.webp | Bin 0 -> 27390 bytes client/public/help-media/ctx/trip-places.webp | Bin 0 -> 147342 bytes .../help-media/delete-place/step-1.webp | Bin 0 -> 73962 bytes .../help-media/delete-place/step-2.webp | Bin 0 -> 50672 bytes .../public/help-media/edit-place/step-1.webp | Bin 0 -> 55610 bytes .../public/help-media/edit-place/step-2.webp | Bin 0 -> 52240 bytes .../public/help-media/edit-place/step-3.webp | Bin 0 -> 26868 bytes .../help-media/filter-places/step-1.webp | Bin 0 -> 69752 bytes .../help-media/filter-places/step-2.webp | Bin 0 -> 65024 bytes .../help-media/filter-places/step-3.webp | Bin 0 -> 71066 bytes .../help-media/filter-places/step-4.webp | Bin 0 -> 74326 bytes .../help-media/import-places-file/result.webp | Bin 0 -> 170912 bytes .../help-media/import-places-file/step-1.webp | Bin 0 -> 69588 bytes .../help-media/import-places-file/step-2.webp | Bin 0 -> 53756 bytes .../help-media/import-places-file/step-3.webp | Bin 0 -> 51784 bytes .../help-media/import-places-file/step-4.webp | Bin 0 -> 35068 bytes .../help-media/import-places-list/step-1.webp | Bin 0 -> 69604 bytes .../help-media/import-places-list/step-2.webp | Bin 0 -> 60078 bytes .../help-media/import-places-list/step-3.webp | Bin 0 -> 46276 bytes .../help-media/place-to-open-day/step-1.webp | Bin 0 -> 67968 bytes .../help-media/place-to-open-day/step-2.webp | Bin 0 -> 102570 bytes .../help-media/place-to-open-day/step-3.webp | Bin 0 -> 109576 bytes .../help-media/select-places/result.webp | Bin 0 -> 147342 bytes .../help-media/select-places/step-1.webp | Bin 0 -> 71600 bytes .../help-media/select-places/step-2.webp | Bin 0 -> 78338 bytes .../help-media/select-places/step-3.webp | Bin 0 -> 78338 bytes .../help-media/select-places/step-4.webp | Bin 0 -> 73664 bytes shared/src/i18n/ar/help.ts | 126 +++++++++++++++ shared/src/i18n/br/help.ts | 138 +++++++++++++++++ shared/src/i18n/ca/help.ts | 140 +++++++++++++++++ shared/src/i18n/cs/help.ts | 134 ++++++++++++++++ shared/src/i18n/de/help.ts | 144 ++++++++++++++++++ shared/src/i18n/en/help.ts | 10 +- shared/src/i18n/es/help.ts | 139 +++++++++++++++++ shared/src/i18n/fr/help.ts | 142 +++++++++++++++++ shared/src/i18n/gr/help.ts | 143 +++++++++++++++++ shared/src/i18n/hu/help.ts | 140 +++++++++++++++++ shared/src/i18n/id/help.ts | 139 +++++++++++++++++ shared/src/i18n/it/help.ts | 142 +++++++++++++++++ shared/src/i18n/ja/help.ts | 135 ++++++++++++++++ shared/src/i18n/ko/help.ts | 131 ++++++++++++++++ shared/src/i18n/nl/help.ts | 142 +++++++++++++++++ shared/src/i18n/pl/help.ts | 135 ++++++++++++++++ shared/src/i18n/ru/help.ts | 135 ++++++++++++++++ shared/src/i18n/sv/help.ts | 135 ++++++++++++++++ shared/src/i18n/tr/help.ts | 139 +++++++++++++++++ shared/src/i18n/uk/help.ts | 135 ++++++++++++++++ shared/src/i18n/vi/help.ts | 139 +++++++++++++++++ shared/src/i18n/zh-TW/help.ts | 114 ++++++++++++++ shared/src/i18n/zh/help.ts | 112 ++++++++++++++ 57 files changed, 3048 insertions(+), 31 deletions(-) create mode 100644 client/public/help-media/create-place/result.webp create mode 100644 client/public/help-media/create-place/step-1.webp create mode 100644 client/public/help-media/create-place/step-2.webp create mode 100644 client/public/help-media/create-place/step-3.webp create mode 100644 client/public/help-media/create-place/step-4.webp create mode 100644 client/public/help-media/create-place/step-5.webp create mode 100644 client/public/help-media/ctx/trip-places.webp create mode 100644 client/public/help-media/delete-place/step-1.webp create mode 100644 client/public/help-media/delete-place/step-2.webp create mode 100644 client/public/help-media/edit-place/step-1.webp create mode 100644 client/public/help-media/edit-place/step-2.webp create mode 100644 client/public/help-media/edit-place/step-3.webp create mode 100644 client/public/help-media/filter-places/step-1.webp create mode 100644 client/public/help-media/filter-places/step-2.webp create mode 100644 client/public/help-media/filter-places/step-3.webp create mode 100644 client/public/help-media/filter-places/step-4.webp create mode 100644 client/public/help-media/import-places-file/result.webp create mode 100644 client/public/help-media/import-places-file/step-1.webp create mode 100644 client/public/help-media/import-places-file/step-2.webp create mode 100644 client/public/help-media/import-places-file/step-3.webp create mode 100644 client/public/help-media/import-places-file/step-4.webp create mode 100644 client/public/help-media/import-places-list/step-1.webp create mode 100644 client/public/help-media/import-places-list/step-2.webp create mode 100644 client/public/help-media/import-places-list/step-3.webp create mode 100644 client/public/help-media/place-to-open-day/step-1.webp create mode 100644 client/public/help-media/place-to-open-day/step-2.webp create mode 100644 client/public/help-media/place-to-open-day/step-3.webp create mode 100644 client/public/help-media/select-places/result.webp create mode 100644 client/public/help-media/select-places/step-1.webp create mode 100644 client/public/help-media/select-places/step-2.webp create mode 100644 client/public/help-media/select-places/step-3.webp create mode 100644 client/public/help-media/select-places/step-4.webp diff --git a/client/e2e/help/trip-places.guide.ts b/client/e2e/help/trip-places.guide.ts index 9a27fa3d0..aca703f59 100644 --- a/client/e2e/help/trip-places.guide.ts +++ b/client/e2e/help/trip-places.guide.ts @@ -1,7 +1,7 @@ import { test, expect, type Page, type Locator } from '@playwright/test' import { captureGuide, captureHero, beat, typeInto, settle, VIEWPORT, type GuideScript } from './guide' import { seededTrip, gpxFixture, ensureTrack } from './fixtures' -import { openTrip, modal, dialog } from './trip-shared' +import { openTrip, modal, dialog, confirmDialog, portalDialog } from './trip-shared' import { tripPlacesContext, tripPlacesGuides } from '../../src/help/contexts/tripPlaces' import type { HelpGuide } from '../../src/help/types' @@ -19,6 +19,8 @@ const guide = (id: string): HelpGuide => { } const SPARE = { name: 'Tsukiji Outer Market', lat: 35.6654, lng: 139.7707, address: '4 Chome Tsukiji, Chuo City, Tokyo' } +/** What create-place looks up. See the note in its second step for why this one. */ +const SEARCHED = { query: 'Osaka Castle', match: /^Osaka Castle/ } /** A row of the places column by the place's name (the rows are options of a list). */ const row = (page: Page, name: string) => page.getByRole('option', { name: new RegExp(`^${name}`) }).first() @@ -29,6 +31,13 @@ const addButton = (page: Page) => page.getByRole('button', { name: /^(Add Place\ /** The filter row's controls. */ const filterSelect = (page: Page) => page.getByTestId('places-filter').getByRole('button').first() const searchBox = (page: Page) => page.getByPlaceholder('Search places...').last() +/** FileImportModal: its own portal, recognised by the hidden file input and its heading. */ +const importFileDialog = (page: Page) => + portalDialog(page, page.locator('input[type="file"][accept*=".gpx"]')) +/** ListImportModal: its own portal, recognised by the link box. */ +const listImportDialog = (page: Page) => portalDialog(page, page.getByPlaceholder(/goo\.gl|naver/)) +/** The drop box of the file dialog: the dashed button that opens the file picker. */ +const dropBox = (page: Page) => importFileDialog(page).getByRole('button').first() /** The column itself: the header block around the search box. */ const column = (page: Page) => searchBox(page).locator('xpath=ancestor::div[contains(@class,"scroll") or @data-places-sidebar][1]') @@ -74,29 +83,37 @@ const SCRIPTS: Record = { { prepare: async p => { const box = modal(p).getByPlaceholder('Search places...') - await typeInto(p, box, 'Tokyo Tower') - // The suggestions drop down under the box; the first one is the tower. - const suggestion = modal(p).locator('.shadow-dropdown button').filter({ hasText: /Tokyo Tower/ }).first() + // Osaka Castle on purpose: its entry in the index carries a picture, + // opening hours and a description, so Place details shows what the + // text says it shows. Most results have none of that and the column + // would be an empty state in the picture. + await typeInto(p, box, SEARCHED.query) + const suggestion = modal(p).locator('.shadow-dropdown button').filter({ hasText: SEARCHED.match }).first() await expect(suggestion).toBeVisible({ timeout: 20_000 }) await suggestion.click() - await expect(modal(p).getByPlaceholder('e.g. Eiffel Tower')).toHaveValue(/Tokyo Tower/, { timeout: 20_000 }) + await expect(modal(p).getByPlaceholder('e.g. Eiffel Tower')).toHaveValue(SEARCHED.match, { timeout: 20_000 }) + // The details column answers after the form; wait for it or the + // picture catches the spinner. + await expect(modal(p).getByText('Pick a picture', { exact: false }).first()).toBeVisible({ timeout: 30_000 }) await settle(p) }, target: p => modal(p).getByPlaceholder('Search places...').locator('xpath=../..'), }, - only(p => modal(p).getByText('Place details', { exact: true }).locator('xpath=ancestor::div[2]')), - only(p => modal(p).getByPlaceholder('e.g. Eiffel Tower').locator('xpath=ancestor::div[3]')), + // The details column is an