diff --git a/__tests__/components/viewers/__snapshots__/nearby-view.js.snap b/__tests__/components/viewers/__snapshots__/nearby-view.js.snap index a3a1b7bc5..65c9368a3 100644 --- a/__tests__/components/viewers/__snapshots__/nearby-view.js.snap +++ b/__tests__/components/viewers/__snapshots__/nearby-view.js.snap @@ -46,7 +46,7 @@ exports[`components > viewers > nearby view renders nothing on a blank page 1`] className="nearby-view base-color-bg" >
viewers > nearby view renders nothing on a blank page 1`] } >
    viewers > nearby view renders proper scooter dates 1`] = ` className="nearby-view base-color-bg" >
    viewers > nearby view renders proper scooter dates 1`] = ` } >
      viewers > nearby view renders proper scooter dates 1`] = ` >

      viewers > nearby view renders proper scooter dates 1`] = ` >

      viewers > nearby view renders proper scooter dates 1`] = `

      viewers > nearby view renders proper scooter dates 1`] = ` >

      viewers > nearby view renders proper scooter dates 1`] = ` >

      viewers > nearby view renders proper scooter dates 1`] = `

      viewers > nearby view renders proper scooter dates 1`] = ` >

      viewers > nearby view renders proper scooter dates 1`] = ` >

      viewers > nearby view renders proper scooter dates 1`] = `

      viewers > nearby view renders proper scooter dates 1`] = ` >
      viewers > nearby view renders proper scooter dates 1`] = ` >

      viewers > nearby view renders proper scooter dates 1`] = ` >

      viewers > nearby view renders proper scooter dates 1`] = `

      viewers > nearby view renders proper scooter dates 1`] = `
        viewers > nearby view renders proper scooter dates 1`] = ` roundedTop={false} >
      • viewers > nearby view renders proper scooter dates 1`] = ` title="45" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

          viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` iconViewBox="0 0 448 512" > viewers > nearby view renders proper scooter dates 1`] = ` >

        1. viewers > nearby view renders proper scooter dates 1`] = ` title="62" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

            viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` iconViewBox="0 0 448 512" > viewers > nearby view renders proper scooter dates 1`] = ` >

          1. viewers > nearby view renders proper scooter dates 1`] = ` title="79" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

              viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` iconViewBox="0 0 448 512" > viewers > nearby view renders proper scooter dates 1`] = ` >

              viewers > nearby view renders proper scooter dates 1`] = ` >

              viewers > nearby view renders proper scooter dates 1`] = ` >

              viewers > nearby view renders proper scooter dates 1`] = `

              viewers > nearby view renders proper scooter dates 1`] = `
                viewers > nearby view renders proper scooter dates 1`] = ` roundedTop={false} >
              • viewers > nearby view renders proper scooter dates 1`] = ` title="1 Line" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                  viewers > nearby view renders proper scooter dates 1`] = ` roundedTop={false} >

                1. viewers > nearby view renders proper scooter dates 1`] = ` title="1 Line" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                    viewers > nearby view renders proper scooter dates 1`] = ` roundedTop={false} >

                  1. viewers > nearby view renders proper scooter dates 1`] = ` title="1 Line" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                      viewers > nearby view renders proper scooter dates 1`] = ` >

                      viewers > nearby view renders proper scooter dates 1`] = ` >

                      viewers > nearby view renders proper scooter dates 1`] = `

                      viewers > nearby view renders proper scooter dates 1`] = ` >

                      viewers > nearby view renders proper scooter dates 1`] = ` >

                      viewers > nearby view renders proper scooter dates 1`] = `

                      viewers > nearby view renders proper scooter dates 1`] = ` >

                      viewers > nearby view renders proper scooter dates 1`] = ` >

                      viewers > nearby view renders proper scooter dates 1`] = `

                      viewers > nearby view renders proper scooter dates 1`] = ` >
                      viewers > nearby view renders proper scooter dates 1`] = ` >

                      viewers > nearby view renders proper scooter dates 1`] = ` >

                      viewers > nearby view renders proper scooter dates 1`] = `

                      viewers > nearby view renders proper scooter dates 1`] = `
                        viewers > nearby view renders proper scooter dates 1`] = ` roundedTop={false} >
                      • viewers > nearby view renders proper scooter dates 1`] = ` title="45" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                          viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` iconViewBox="0 0 448 512" > viewers > nearby view renders proper scooter dates 1`] = ` >

                        1. viewers > nearby view renders proper scooter dates 1`] = ` title="62" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                            viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` iconViewBox="0 0 448 512" > viewers > nearby view renders proper scooter dates 1`] = ` >

                          1. viewers > nearby view renders proper scooter dates 1`] = ` title="79" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                              viewers > nearby view renders proper scooter dates 1`] = ` roundedTop={false} >

                            1. viewers > nearby view renders proper scooter dates 1`] = ` title="988" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                                viewers > nearby view renders proper scooter dates 1`] = ` >

                                viewers > nearby view renders proper scooter dates 1`] = ` >

                                viewers > nearby view renders proper scooter dates 1`] = `

                                viewers > nearby view renders proper scooter dates 1`] = ` >

                                viewers > nearby view renders proper scooter dates 1`] = ` >

                                viewers > nearby view renders proper scooter dates 1`] = `

                                viewers > nearby view renders proper scooter dates 1`] = ` >

                                viewers > nearby view renders proper scooter dates 1`] = ` >

                                viewers > nearby view renders proper scooter dates 1`] = `

                                viewers > nearby view renders proper scooter dates 1`] = ` >

                                viewers > nearby view renders proper scooter dates 1`] = ` >

                                viewers > nearby view renders proper scooter dates 1`] = `

                                viewers > nearby view renders proper scooter dates 1`] = ` >

                                viewers > nearby view renders proper scooter dates 1`] = ` >

                                viewers > nearby view renders proper scooter dates 1`] = `

                                viewers > nearby view renders proper scooter dates 1`] = ` >
                                viewers > nearby view renders proper scooter dates 1`] = ` >

                                viewers > nearby view renders proper scooter dates 1`] = ` >

                                viewers > nearby view renders proper scooter dates 1`] = `

                                viewers > nearby view renders proper scooter dates 1`] = `
                                  viewers > nearby view renders proper scooter dates 1`] = ` roundedTop={false} >
                                • viewers > nearby view renders proper scooter dates 1`] = ` title="67" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                                    viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` iconViewBox="0 0 448 512" > viewers > nearby view renders proper scooter dates 1`] = ` >

                                  1. viewers > nearby view renders proper scooter dates 1`] = ` title="73" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                                      viewers > nearby view renders proper scooter dates 1`] = ` roundedTop={false} >

                                    1. viewers > nearby view renders proper scooter dates 1`] = ` title="984" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                                        viewers > nearby view renders proper scooter dates 1`] = ` >

                                        viewers > nearby view renders proper scooter dates 1`] = ` >

                                        viewers > nearby view renders proper scooter dates 1`] = `

                                        viewers > nearby view renders proper scooter dates 1`] = ` >
                                        viewers > nearby view renders proper scooter dates 1`] = ` >

                                        viewers > nearby view renders proper scooter dates 1`] = ` >

                                        viewers > nearby view renders proper scooter dates 1`] = `

                                        viewers > nearby view renders proper scooter dates 1`] = `
                                          viewers > nearby view renders proper scooter dates 1`] = ` roundedTop={false} >
                                        • viewers > nearby view renders proper scooter dates 1`] = ` title="1 Line" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                                            viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` iconViewBox="0 0 448 512" > viewers > nearby view renders proper scooter dates 1`] = ` >

                                            viewers > nearby view renders proper scooter dates 1`] = ` >

                                            viewers > nearby view renders proper scooter dates 1`] = ` >

                                            viewers > nearby view renders proper scooter dates 1`] = `

                                            viewers > nearby view renders proper scooter dates 1`] = `
                                              viewers > nearby view renders proper scooter dates 1`] = ` roundedTop={false} >
                                            • viewers > nearby view renders proper scooter dates 1`] = ` title="522" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                                                viewers > nearby view renders proper scooter dates 1`] = ` roundedTop={false} >

                                              1. viewers > nearby view renders proper scooter dates 1`] = ` title="67" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                                                  viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` iconViewBox="0 0 448 512" > viewers > nearby view renders proper scooter dates 1`] = ` >

                                                1. viewers > nearby view renders proper scooter dates 1`] = ` title="522" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                                                    viewers > nearby view renders proper scooter dates 1`] = ` roundedTop={false} >

                                                  1. viewers > nearby view renders proper scooter dates 1`] = ` title="73" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                                                      viewers > nearby view renders proper scooter dates 1`] = ` roundedTop={false} >

                                                    1. viewers > nearby view renders proper scooter dates 1`] = ` title="322" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                                                        viewers > nearby view renders proper scooter dates 1`] = ` roundedTop={false} >

                                                      1. viewers > nearby view renders proper scooter dates 1`] = ` title="322" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                                                          viewers > nearby view renders proper scooter dates 1`] = ` >

                                                          viewers > nearby view renders proper scooter dates 1`] = ` >

                                                          viewers > nearby view renders proper scooter dates 1`] = `

                                                          viewers > nearby view renders proper scooter dates 1`] = ` >
                                                          viewers > nearby view renders proper scooter dates 1`] = ` >

                                                          viewers > nearby view renders proper scooter dates 1`] = ` >

                                                          viewers > nearby view renders proper scooter dates 1`] = `

                                                          viewers > nearby view renders proper scooter dates 1`] = `
                                                            viewers > nearby view renders proper scooter dates 1`] = ` roundedTop={false} >
                                                          • viewers > nearby view renders proper scooter dates 1`] = ` title="45" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                                                              viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` iconViewBox="0 0 448 512" > viewers > nearby view renders proper scooter dates 1`] = ` >

                                                            1. viewers > nearby view renders proper scooter dates 1`] = ` title="62" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                                                                viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` iconViewBox="0 0 448 512" > viewers > nearby view renders proper scooter dates 1`] = ` >

                                                              1. viewers > nearby view renders proper scooter dates 1`] = ` title="79" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                                                                  viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` iconViewBox="0 0 448 512" > viewers > nearby view renders proper scooter dates 1`] = ` > {leadTimeInMinutes} мин до запланированного времени отправления + notifications: за {leadTimeInMinutes} мин до запланированного времени + отправления notificationsDisabled: "Уведомления: отключены" TripTools: copyLink: Скопировать ссылку @@ -693,17 +702,23 @@ components: storeTripHistory: Сохранять историю поездок updating: Обновляется UserSettings: - confirmDeletion: "Недавние поисковые запросы и\_(или) места сохранены. Отключение сохранения недавних мест и\_(или) поисковых запросов приведет к удалению этих элементов. Продолжить?" + confirmDeletion: "Недавние поисковые запросы и (или) места сохранены. Отключение + сохранения недавних мест и (или) поисковых запросов приведет к удалению этих + элементов. Продолжить?" favoriteStops: Избранные остановки myPreferences: Мои параметры mySavedPlaces: Мои сохраненные места (изменить) noFavoriteStops: Избранные остановки recentPlaces: Недавние места - recentSearchSummary: "Режим «{mode}». {from}\_— {to}" + recentSearchSummary: "Режим «{mode}». {from} — {to}" recentSearches: Недавние поисковые запросы - rememberSearches: "Сохранить недавние места\_/ поисковые запросы?" + rememberSearches: "Сохранить недавние места / поисковые запросы?" stopId: "Идентификатор остановки: {stopId}" - storageDisclaimer: "Все сохраненные параметры, места и настройки будут храниться в локальном хранилище браузера. TriMet не получит доступ к данным о вашем доме, месте работы или другим местоположениям. В любой момент вы можете отключить сохранение недавних мест\_/ поисковых запросов и очистить список местоположений дома/работы, а также избранных остановок.\n" + storageDisclaimer: "Все сохраненные параметры, места и настройки будут храниться + в локальном хранилище браузера. TriMet не получит доступ к данным о вашем доме, + месте работы или другим местоположениям. В любой момент вы можете отключить + сохранение недавних мест / поисковых запросов и очистить список местоположений + дома/работы, а также избранных остановок.\n" UserTripSettings: forgetOptions: Удалить варианты rememberOptions: Сохранить варианты поездки @@ -725,12 +740,12 @@ components: prompt: Куда вы хотите отправиться? config: accessModes: - bicycle: "Общественный транспорт\_+\_личный велосипед" - bicycle_rent: "Общественный транспорт\_+\_прокат велосипеда" + bicycle: "Общественный транспорт + личный велосипед" + bicycle_rent: "Общественный транспорт + прокат велосипеда" car_hail: Заказная поездка car_park: Парковочное место - micromobility: "Общественный транспорт\_+\_личный самокат" - micromobility_rent: "Общественный транспорт\_+\_прокат электросамоката" + micromobility: "Общественный транспорт + личный самокат" + micromobility_rent: "Общественный транспорт + прокат электросамоката" bicycleModes: bicycle: Личный велосипед bicycle_rent: Прокат велосипедов diff --git a/i18n/tl.yml b/i18n/tl.yml index 4d48060ef..ad174d969 100644 --- a/i18n/tl.yml +++ b/i18n/tl.yml @@ -45,7 +45,8 @@ actions: confirmDeletePlace: Gusto mo bang alisin ang lugar na ito? emailVerificationResent: Ipinadala ulit ang mensahe ng pag-verify sa email. genericError: "Nagka-error: {err}" - itineraryExistenceCheckFailed: Nagka-error sa pagtingin kung posible ang napili mong biyahe. + itineraryExistenceCheckFailed: Nagka-error sa pagtingin kung posible ang napili + mong biyahe. mustAcceptTermsToSavePlace: >- Pakitanggap ang Mga Tuntunin ng Paggamit (sa ilalim ng Aking Account) para mag-save ng mga lokasyon. @@ -106,13 +107,11 @@ common: submitting: Isinusumite… "yes": Oo itineraryDescriptions: - calories: "{calories, number} Cal" fareUnknown: Walang impormasyon sa pamasahe noItineraryToDisplay: Walang ipapakitang itinerary. relativeCo2: > {co2} {isMore, select, true {mas maraming} other {mas kaunting} } CO₂ kaysa magmaneho nang mag-isa - transfers: "{transfers, plural, =0 {} one {# transfer} other {# transfers}}" linkOpensNewWindow: (Magbubukas sa bagong window) modes: bicycle_rent: Bikeshare @@ -151,7 +150,6 @@ common: enterStartLocation: Ilagay ang lokasyon ng pagsisimula o {mapAction} sa mapa… tap: tap time: - departureArrivalTimes: "{startTime, time, short}—{endTime, time, short}" duration: aFewSeconds: a few seconds nDays: "{days, plural, =1 {one day} other {# days}}" @@ -204,7 +202,8 @@ components: ang pampublikong transportasyon sa pagpili mo ng mode. origin: pinagmulan planTripTooltip: Planuhin ang biyahe - validationMessage: "Ilarawan ang mga sumusunod na field para makapagplano ng biyahe: {issues}" + validationMessage: "Ilarawan ang mga sumusunod na field para makapagplano ng biyahe: + {issues}" BeforeSignInScreen: mainTitle: Sina-sign in ka message: > @@ -263,7 +262,6 @@ components: ariaLabel: Pag-navigate sa form ItinerarySummary: itineraryDetails: Mga detalye ng itinerary - minMaxFare: "{minTotalFare} - {maxTotalFare}" LocationSearch: enterLocation: Ilagay ang lokasyon setDestination: Itakda ang Patutunguhan @@ -396,7 +394,8 @@ components: invalidCode: Maglagay ng 6 na digit para sa code sa pag-validate. invalidPhone: Maglagay ng valid na numero ng telepono. phoneNumberSubmitted: Matagumpay na naisumite ang numero ng teleponong {phoneNumber}. - phoneNumberVerified: Matagumpay na na-verify ang numero ng teleponong {phoneNumber} . + phoneNumberVerified: Matagumpay na na-verify ang numero ng teleponong {phoneNumber} + . placeholder: Ilagay ang numero ng iyong telepono prompt: "Ilagay ang numero ng iyong telepono para sa mga SMS na notification:" requestNewCode: Humiling ng bagong code @@ -585,9 +584,11 @@ components: travelingAt: Bumibiyahe nang {milesPerHour} vehicleName: Sasakyan {vehicleNumber} TripBasicsPane: - checkingItineraryExistence: Tinitingnan kung may itinerary para sa bawat araw ng linggo... + checkingItineraryExistence: Tinitingnan kung may itinerary para sa bawat araw + ng linggo... tripDaysPrompt: Anong mga araw mo ginagawa ang biyaheng ito? - tripIsAvailableOnDaysIndicated: Available ang iyong biyahe sa mga araw ng linggo na nakasaad sa itaas. + tripIsAvailableOnDaysIndicated: Available ang iyong biyahe sa mga araw ng linggo + na nakasaad sa itaas. tripNamePrompt: "Pangalanan ang biyaheng ito:" tripNotAvailableOnDay: Hindi available ang biyahe sa {repeatedDay} unsavedChangesExistingTrip: >- @@ -632,7 +633,8 @@ components: unknownState: Hindi Alam ang Status ng Biyahe untogglePause: Ipagpatuloy inactive: - description: Ipagpatuloy ang pagsubaybay sa biyahe para makita ang updated na status + description: Ipagpatuloy ang pagsubaybay sa biyahe para makita ang updated na + status heading: Naka-pause ang pagsubaybay sa biyahe nextTripNotPossible: description: > @@ -651,7 +653,8 @@ components: description: Hinihintay na makalkula ang biyahe. heading: Hindi pa nakakalkula ang biyahe snoozed: - description: I-unsnooze ang pagsubaybay sa biyahe para makita ang updated na status. + description: I-unsnooze ang pagsubaybay sa biyahe para makita ang updated na + status. heading: Naka-snooze ang pagsubaybay sa biyahe ngayong araw upcoming: nextTripBegins: >- @@ -661,7 +664,8 @@ components: Magsisimula ang biyahe nang {tripStart, time, short}. (Magsisimula ang realtime na pagsubaybay nang {monitoringStart, time, short}.) tripStartIsDelayed: Naantala ang oras ng pagsisimula ng biyahe nang {duration}! - tripStartIsEarly: Nagsisimula na ang biyahe {duration} na mas maaga kaysa sa inaasahan! + tripStartIsEarly: Nagsisimula na ang biyahe {duration} na mas maaga kaysa sa + inaasahan! tripStartsSoonNoUpdates: >- Malapit nang magsimula ang biyahe (walang realtime na update na available). diff --git a/i18n/vi.yml b/i18n/vi.yml index 316b37876..b10292003 100644 --- a/i18n/vi.yml +++ b/i18n/vi.yml @@ -5,7 +5,8 @@ actions: callQuerySaveError: "Lỗi khi lưu trữ các truy vấn cuộc gọi: {err}" callSaveError: "Không thể lưu cuộc gọi: {err}" checkSessionError: "Lỗi khi thiết lập phiên ủy quyền: {err}" - couldNotFindCallError: Không thể tìm thấy cuộc gọi. Đang hủy yêu cầu lưu truy vấn. + couldNotFindCallError: Không thể tìm thấy cuộc gọi. Đang hủy yêu cầu lưu truy + vấn. fetchCallsError: "Lỗi khi tìm nạp cuộc gọi: {err}" queryFetchError: "Lỗi khi tìm nạp các truy vấn: {err}" fieldTrip: @@ -28,7 +29,8 @@ actions: Không thể lưu kế hoạch chuyến đi: Không thể lưu kế hoạch chuyến đi này do thiếu sức chứa trên một hoặc nhiều xe. Vui lòng lên kế hoạch lại chuyến đi của bạn. - maxTripRequestsExceeded: Đã vượt quá số lượng yêu cầu chuyến đi mà không có kết quả hợp lệ + maxTripRequestsExceeded: Đã vượt quá số lượng yêu cầu chuyến đi mà không có kết + quả hợp lệ saveItinerariesError: "Không lưu được hành trình: {err}" setDateError: "Lỗi khi cài đặt ngày:" setGroupSizeError: "Lỗi khi cài đặt kích thước nhóm:" @@ -40,7 +42,8 @@ actions: Để sử dụng địa điểm hiện tại của mình, quý vị hãy cho phép sử dụng vị trí trong trình duyệt và tải lại trang này. - geolocationNotSupportedError: Định vị địa lý không được hỗ trợ bởi trình duyệt của bạn + geolocationNotSupportedError: Định vị địa lý không được hỗ trợ bởi trình duyệt + của bạn unknownPositionError: Lỗi không xác định khi tìm vị trí userDeniedPermission: Người dùng từ chối cấp quyền map: @@ -52,7 +55,8 @@ actions: confirmDeletePlace: Bạn có muốn loại bỏ nơi này không? emailVerificationResent: Thông báo xác minh email đã được gửi lại. genericError: "Phát sinh lỗi: {err}" - itineraryExistenceCheckFailed: Lỗi kiểm tra xem chuyến đi được chọn của bạn là có thể. + itineraryExistenceCheckFailed: Lỗi kiểm tra xem chuyến đi được chọn của bạn là + có thể. mustAcceptTermsToSavePlace: >- Vui lòng chấp nhận Điều Khoản Sử Dụng (trong phần Tài Khoản Của Tôi) để lưu lại địa điểm. @@ -112,12 +116,10 @@ common: submitting: Đang gửi… "yes": Đúng itineraryDescriptions: - calories: "{calories, number} calo" fareUnknown: Không có thông tin giá vé noItineraryToDisplay: Không có hành trình để hiển thị. relativeCo2: | {co2} CO₂ {isMore, select, true {nhiều} other {ít} } hơn so với xe hơi - transfers: "{transfers, plural, =0 {} other {# chuyển}}" linkOpensNewWindow: (Mở khoảng thời gian mới) modes: bicycle_rent: Chia sẻ xe đạp @@ -156,7 +158,6 @@ common: enterStartLocation: Nhập vị trí bắt đầu hoặc {mapAction} vào bản đồ… tap: chạm time: - departureArrivalTimes: "{startTime, time, short}—{endTime, time, short}" duration: aFewSeconds: vài giây nDays: "{days} ngày" @@ -168,12 +169,14 @@ common: {} other {# giây}} components: A11yPrefs: - accessibilityRoutingByDefault: Thích những chuyến đi có thể truy cập theo mặc định + accessibilityRoutingByDefault: Thích những chuyến đi có thể truy cập theo mặc + định AccountSetupFinishPane: message: Bạn đã sẵn sàng để bắt đầu lên kế hoạch cho các chuyến đi của bạn. AddPlaceButton: addPlace: Thêm địa điểm - needOriginDestination: Xác định nguồn gốc hoặc đích đến để thêm các địa điểm trung gian + needOriginDestination: Xác định nguồn gốc hoặc đích đến để thêm các địa điểm trung + gian tooManyPlaces: Địa điểm trung gian tối đa đạt được AdvancedOptions: bannedRoutes: Chọn các tuyến đường bị cấm… @@ -259,14 +262,14 @@ components: editPlaceGeneric: Chỉnh sửa vị trí invalidAddress: Vui lòng cài đặt một vị trí cho nơi này. invalidName: Vui lòng nhập tên cho nơi này. - nameAlreadyUsed: Bạn đã sử dụng tên này cho một nơi khác. Vui lòng nhập một tên khác. + nameAlreadyUsed: Bạn đã sử dụng tên này cho một nơi khác. Vui lòng nhập một tên + khác. placeNotFound: Không tìm thấy địa điểm placeNotFoundDescription: Xin lỗi, địa điểm được yêu cầu không được tìm thấy. FormNavigationButtons: ariaLabel: Điều hướng hình thức ItinerarySummary: itineraryDetails: Chi tiết hành trình - minMaxFare: "{minTotalFare} - {maxTotalFare}" LocationSearch: enterLocation: Nhập vị trí setDestination: Chọn điểm đến @@ -335,8 +338,10 @@ components: description: Nội dung bạn yêu cầu không có sẵn. header: Không tìm thấy nội dung NotificationPrefsPane: - devicesRegistered: "{count, plural, one {# device} other {# devices}} đã đăng ký" - noDeviceForPush: Đăng ký thiết bị của quý vị bằng ứng dụng di động để nhận thông báo đẩy. + devicesRegistered: "{count, plural, one {# device} other {# devices}} đã đăng + ký" + noDeviceForPush: Đăng ký thiết bị của quý vị bằng ứng dụng di động để nhận thông + báo đẩy. notificationChannelPrompt: "Nhận thông báo về các chuyến đi đã lưu bằng:" OTP2ErrorRenderer: LOCATION_NOT_FOUND: @@ -579,16 +584,21 @@ components: travelingAt: di chuyển với tốc độ {milesPerHour} vehicleName: Phương tiện giao thông {vehicleNumber} TripBasicsPane: - checkingItineraryExistence: Kiểm tra sự tồn tại của hành trình cho mỗi ngày trong tuần… + checkingItineraryExistence: Kiểm tra sự tồn tại của hành trình cho mỗi ngày trong + tuần… tripDaysPrompt: Bạn thực hiện chuyến đi này vào những ngày nào? - tripIsAvailableOnDaysIndicated: Chuyến đi của bạn có sẵn vào những ngày trong tuần như đã nêu ở trên. + tripIsAvailableOnDaysIndicated: Chuyến đi của bạn có sẵn vào những ngày trong + tuần như đã nêu ở trên. tripNamePrompt: "Vui lòng cung cấp tên cho chuyến đi này:" tripNotAvailableOnDay: Chuyến đi không có sẵn vào {repeatedDay} - unsavedChangesExistingTrip: Bạn chưa lưu chuyến đi của mình. Nếu bạn rời đi, những thay đổi sẽ bị mất. - unsavedChangesNewTrip: Bạn chưa lưu chuyến đi mới của mình. Nếu bạn rời đi, nó sẽ bị mất. + unsavedChangesExistingTrip: Bạn chưa lưu chuyến đi của mình. Nếu bạn rời đi, những + thay đổi sẽ bị mất. + unsavedChangesNewTrip: Bạn chưa lưu chuyến đi mới của mình. Nếu bạn rời đi, nó + sẽ bị mất. TripNotificationsPane: advancedSettings: Cài đặt nâng cao - altRouteRecommended: Một tuyến đường hoặc điểm trung chuyển thay thế được khuyến nghị + altRouteRecommended: Một tuyến đường hoặc điểm trung chuyển thay thế được khuyến + nghị delaysAboveThreshold: Có sự chậm trễ hoặc gián đoạn của hơn howToReceiveAlerts: > Để nhận thông báo cho các chuyến đi đã lưu của bạn, bật thông báo trong @@ -597,7 +607,8 @@ components: notificationsTurnedOff: Thông báo được tắt cho tài khoản của bạn. notifyViaChannelWhen: "Thông báo cho tôi qua {channel} khi:" oneHour: 1 tiếng - realtimeAlertFlagged: Có một cảnh báo thời gian thực được gắn cờ trên hành trình của tôi + realtimeAlertFlagged: Có một cảnh báo thời gian thực được gắn cờ trên hành trình + của tôi timeBefore: "{time} trước" TripStatus: alerts: "{alerts, plural, one {# cảnh báo!} other {# cảnh báo!}}" @@ -610,7 +621,8 @@ components: earlyHeading: >- Chuyến đi đang diễn ra và sẽ đến sớm hơn {formattedDuration} so với dự kiến! - noDataHeading: Chuyến đi đang được tiến hành (không có cập nhật thời gian thực có sẵn). + noDataHeading: Chuyến đi đang được tiến hành (không có cập nhật thời gian thực + có sẵn). onTimeHeading: Chuyến đi đang được tiến hành và đúng giờ. base: lastCheckedDefaultText: Thời gian được kiểm tra lần cuối không xác định @@ -653,7 +665,8 @@ components: tripStartIsEarly: >- Thời gian bắt đầu chuyến đi đang diễn ra sớm hơn {duration} so với dự kiến! - tripStartsSoonNoUpdates: Chuyến đi đang bắt đầu sớm (không có cập nhật về thời gian thực). + tripStartsSoonNoUpdates: Chuyến đi đang bắt đầu sớm (không có cập nhật về thời + gian thực). tripStartsSoonOnTime: Chuyến đi đang bắt đầu sớm và sắp đúng giờ. TripSummary: arriveAt: "Đến nơi " diff --git a/i18n/zh_Hans.yml b/i18n/zh_Hans.yml index 5d4b6a419..c3012c95a 100644 --- a/i18n/zh_Hans.yml +++ b/i18n/zh_Hans.yml @@ -99,12 +99,10 @@ common: submitting: 正在提交… "yes": 是 itineraryDescriptions: - calories: "{calories, number} 大卡" fareUnknown: 无票价信息 noItineraryToDisplay: 没有显示行程. relativeCo2: | {co2} {isMore, select, true {更多} other {更少} } CO₂ 比单独驾车 - transfers: "{transfers, plural, =0 {} other {# 换乘}}" linkOpensNewWindow: (打开新窗口) modes: bicycle_rent: 共享单车 @@ -143,7 +141,6 @@ common: enterStartLocation: 输入出发地点或{mapAction}地图… tap: 点击 time: - departureArrivalTimes: "{startTime, time, short}—{endTime, time, short}" duration: aFewSeconds: 几秒钟 nDays: "{days} 天" @@ -244,7 +241,6 @@ components: ariaLabel: 表格导航 ItinerarySummary: itineraryDetails: 行程详情 - minMaxFare: "{minTotalFare} - {maxTotalFare}" LocationSearch: enterLocation: 输入位置 setDestination: 设置目的地 diff --git a/i18n/zh_Hant.yml b/i18n/zh_Hant.yml index 4fbc7d8fd..b18d02dc0 100644 --- a/i18n/zh_Hant.yml +++ b/i18n/zh_Hant.yml @@ -99,12 +99,10 @@ common: submitting: 正在提交… "yes": 是 itineraryDescriptions: - calories: "{calories, number}卡" fareUnknown: 無票價資訊 noItineraryToDisplay: 沒有預定行程可顯示。 relativeCo2: | {co2}比單獨開車排放的CO₂{isMore, select, true {要多} other {少}} - transfers: "{transfers}次轉乘" linkOpensNewWindow: (開啟新視窗) modes: bicycle_rent: 自行車共享 @@ -143,7 +141,6 @@ common: enterStartLocation: 輸入開始位置或{mapAction} 地圖…… tap: 輕觸 time: - departureArrivalTimes: "{startTime, time, short}—{endTime, time, short}" duration: aFewSeconds: 幾秒鐘 nDays: "{days}天" @@ -244,7 +241,6 @@ components: ariaLabel: 表單導航 ItinerarySummary: itineraryDetails: 路線詳細資訊 - minMaxFare: "{minTotalFare} - {maxTotalFare}" LocationSearch: enterLocation: 輸入位置 setDestination: 設定目的地 diff --git a/index.css b/index.css index 697d3b1b6..c5a4df16f 100644 --- a/index.css +++ b/index.css @@ -1,5 +1,6 @@ @import url(node_modules/bootstrap/dist/css/bootstrap.min.css); - +@import url(react-sliding-pane/dist/react-sliding-pane.css); +@import url(lib/bike-rental.css); @import url(node_modules/maplibre-gl/dist/maplibre-gl.css); @import url(lib/components/admin/call-taker.css); @@ -12,190 +13,25 @@ @import url(lib/components/user/nav-login-button.css); @import url(lib/components/viewers/viewers.css); -@import url(lib/bike-rental.css); -@import url(react-sliding-pane/dist/react-sliding-pane.css); /* Hide IE/Edge clear button in text input fields. */ input[type="text"]::-ms-clear { display: none; } -/* New app menu */ -.app-menu-icon { - background: none; - border: none; - cursor: pointer; - display: flex; - flex-direction: column; - height: 15px; - justify-content: space-between; - padding: 0; - position: absolute; - top: 16px; - transition: all 1s ease; - width: 21px; - z-index: 10; -} - -@media only screen and (max-width: 768px) { - #locale-selector-wrapper { - display: none; - } - .app-menu-icon { - left: 15px; - } -} - -.app-menu-icon .menu-line { - border-bottom: 3px solid #ffffff; - display: block; - position: relative; - transition: all 0.5s ease; - width: 100%; -} - -.app-menu-icon[aria-expanded="true"] .menu-line.top { - transform: rotate(45deg); - top: 7px; -} -.app-menu-icon[aria-expanded="true"] .menu-line.bottom { - transform: rotate(-45deg); - bottom: 5px; -} -.app-menu-icon[aria-expanded="true"] .menu-line.middle { - display: none; -} - -.slide-pane { - transition: all 0.2s ease-in-out; -} - -.slide-pane_from_left { - margin: 52px auto 0 0; - /* Keep pane from overflowing on smaller screens */ - height: calc(100% - 52px); -} - -.slide-pane__content { - padding: 6px 0; -} - -.slide-pane__overlay { - z-index: 1000; -} - -.app-menu { - margin: 0; - padding: 0.5rem 0; -} - -.app-menu img, -.app-menu svg { - max-height: 1em; - margin: 0 2rem; - vertical-align: middle; - width: 1em; -} - -.app-menu a, -.app-menu button { - background: none; - border: none; - color: inherit; - cursor: pointer; - display: flex; - font-size: 20px; - padding: 0.5rem 0; - text-decoration: none; - width: 100%; -} - -/* Prevents hover styles from getting triggered on mobile */ -@media (hover: hover) { - .app-menu a:hover, - .app-menu button:hover { - color: #4c97f5; - } -} - -.app-menu a:focus, -.app-menu button:focus { - background-color: #ddd; - outline: 2px solid #4c97f5; - outline-offset: -2px; -} - -.app-menu button[aria-selected="true"], -.sort-option button[aria-selected="true"], -#locale-selector button[aria-selected="true"] { - font-weight: bold; -} - -.skip-nav-button { - color: initial; - position: fixed; - top: -30px; -} -.skip-nav-button:focus { - padding: 7px 24px; - top: 7px; - z-index: 100; -} - -.view-switcher { - display: none; -} - -@media (min-width: 768px) { - .view-switcher { - display: flex; - } -} - -.expand-menu-chevron { - flex-grow: 1; - text-align: end; -} - -.dropdown-header { - font-size: inherit; - line-height: normal; - color: inherit; - white-space: nowrap; -} - -.sub-menu-container { - border-top: 1px solid #cccccc; - margin-top: 0.5rem; - padding-left: 2rem; -} - -.app-menu .app-menu-divider { - border-bottom: 1px solid #ccc; - padding: 1rem 0; -} - -/* Header image or title */ -/* If an icon is used, visually-hide the title (but keep it visible to screen readers). */ -.with-icon div.navbar-title { - height: 0; - overflow: hidden; - width: 0; -} - /* Buttons */ button.header, button.step, .clear-button-formatting { background: transparent; - color: inherit; border: 0; + color: inherit; + margin: 0; + padding: 0; text-align: inherit; text-decoration: none; - padding: 0; - margin: 0; } .clear-button-formatting:active { @@ -217,9 +53,9 @@ button.step { } .overflow-ellipsis { - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + white-space: nowrap; } .map-container .leaflet-top { diff --git a/lib/actions/apiV2.js b/lib/actions/apiV2.js index e7deb84e3..9438d5e3d 100644 --- a/lib/actions/apiV2.js +++ b/lib/actions/apiV2.js @@ -505,6 +505,10 @@ export const fetchNearby = (position, radius, currentServiceWeek) => { export const findStopTimesForStop = (params) => function (dispatch, getState) { + // If the stop is already in the store, don't fetch it again, unless we are forcing a refetch + if (!params.forceFetch && getState().otp.transitIndex.stops[params.stopId]) + return + dispatch(fetchingStopTimesForStop(params)) const { date, stopId } = params const timeZone = getState().otp.config.homeTimezone diff --git a/lib/actions/ui.js b/lib/actions/ui.js index cdd0691da..a548a43bc 100644 --- a/lib/actions/ui.js +++ b/lib/actions/ui.js @@ -47,6 +47,9 @@ const viewRoute = createAction('SET_VIEWED_ROUTE') export const toggleAutoRefresh = createAction('TOGGLE_AUTO_REFRESH') const settingItineraryView = createAction('SET_ITINERARY_VIEW') export const setPopupContent = createAction('SET_POPUP_CONTENT') +export const setNetworkConnectionLost = createAction( + 'SET_NETWORK_CONNECTION_LOST' +) // This code-less action calls the reducer code // and thus resets the session timeout. diff --git a/lib/components/app/app.css b/lib/components/app/app.css index 87fe44222..697857947 100644 --- a/lib/components/app/app.css +++ b/lib/components/app/app.css @@ -1,8 +1,8 @@ /* application menu styling */ .app-menu-button { - border: none; background: none; + border: none; color: #fff; font-size: 24px; padding: 0px 0px; @@ -21,10 +21,10 @@ .app-menu .btn-default:active, .app-menu .open > .dropdown-toggle.btn-default:focus, .app-menu .open > .dropdown-toggle.btn-default { - color: #fff; background: none; - box-shadow: none; border: none; + box-shadow: none; + color: #fff; } .app-menu .btn-default:hover { @@ -47,36 +47,266 @@ } } +/* Navbar settings */ + +.navbar .navbar-header { + display: grid; + grid-template-columns: 300px auto 170px; +} + +.navbar .navbar-nav { + grid-column: 3; + margin: 0; +} + +.navbar .navbar-brand { + align-items: center; + display: flex; + gap: 25px; + grid-column: 1 / span 1; + width: 100%; +} + +.view-switcher { + align-items: center; + display: flex; + justify-content: center; +} + +@media (min-width: 992px) { + .view-switcher { + height: 100%; + left: 0; + position: absolute; + width: 100% + } +} + + +@media (max-width: 768px) { + .navbar .navbar-header { + grid-template-columns: 50px auto 70px; + width: auto !important; + + } + + .navbar .navbar-brand { + gap: 15px; + grid-column: 1 / span 2; + padding: 10px; + } + + .view-switcher { + display: none; +} + +} + +/* Mobile navbar */ +.mobile-navbar-container .navbar-header { + display: block; + grid-column: 1; + grid-row: 1; + justify-self: center; + width: 25px; +} + +.mobile-navbar-container .mobile-header { + grid-row: 1; + height: 100%; + justify-self: center; +} + +.mobile-navbar-container .locale-selector-and-login { + grid-row: 1; + height: 50px; + justify-self: flex-end; +} + +.mobile-navbar-container .container-fluid { + align-items: center; + display: grid; + grid-template-columns: 50px auto 70px; + grid-template-rows: 50px; + padding: 0; +} + +/* New app menu */ +.app-menu-icon { + background: none; + border: none; + cursor: pointer; + display: flex; + flex-direction: column; + height: 15px; + justify-content: space-between; + padding: 0; + transition: all 1s ease; + width: 21px; + z-index: 10; +} + +@media only screen and (max-width: 768px) { + #locale-selector-wrapper { + display: none; + } + .app-menu-icon { + left: 15px; + } +} + +.app-menu-icon .menu-line { + border-bottom: 3px solid #ffffff; + display: block; + position: relative; + transition: all 0.5s ease; + width: 100%; +} + +.app-menu-icon[aria-expanded="true"] .menu-line.top { + top: 7px; + transform: rotate(45deg); +} +.app-menu-icon[aria-expanded="true"] .menu-line.bottom { + bottom: 5px; + transform: rotate(-45deg); +} +.app-menu-icon[aria-expanded="true"] .menu-line.middle { + display: none; +} + +.slide-pane { + transition: all 0.2s ease-in-out; +} + +.slide-pane_from_left { + /* Keep pane from overflowing on smaller screens */ + height: calc(100% - 52px); + margin: 52px auto 0 0; +} + +.slide-pane__content { + padding: 6px 0; +} + +.slide-pane__overlay { + z-index: 1000; +} + +.app-menu { + margin: 0; + padding: 0.5rem 0; +} + +.app-menu img, +.app-menu svg { + margin: 0 2rem; + max-height: 1em; + vertical-align: middle; + width: 1em; +} + +.app-menu a, +.app-menu button { + background: none; + border: none; + color: inherit; + cursor: pointer; + display: flex; + font-size: 20px; + padding: 0.5rem 0; + text-decoration: none; + width: 100%; +} + +/* Prevents hover styles from getting triggered on mobile */ +@media (hover: hover) { + .app-menu a:hover, + .app-menu button:hover { + color: #4c97f5; + } +} + +.app-menu a:focus, +.app-menu button:focus { + background-color: #ddd; + outline: 2px solid #4c97f5; + outline-offset: -2px; +} + +.app-menu button[aria-selected="true"], +.sort-option button[aria-selected="true"], +#locale-selector button[aria-selected="true"] { + font-weight: bold; +} + +.skip-nav-button { + color: initial; + position: fixed; + top: -30px; +} +.skip-nav-button:focus { + padding: 7px 24px; + top: 7px; + z-index: 100; +} + +.expand-menu-chevron { + flex-grow: 1; + text-align: end; +} + +.dropdown-header { + color: inherit; + font-size: inherit; + line-height: normal; + white-space: nowrap; +} + +.sub-menu-container { + border-top: 1px solid #cccccc; + margin-top: 0.5rem; + padding-left: 2rem; +} + +.app-menu .app-menu-divider { + border-bottom: 1px solid #ccc; + padding: 1rem 0; +} + +/* Header image or title */ +/* If an icon is used, visually-hide the title (but keep it visible to screen readers). */ +.with-icon div.navbar-title { + height: 0; + overflow: hidden; + width: 0; +} + /* PrintLayout styles */ .otp.print-layout { - max-width: 640px; margin: 30px auto; + max-width: 640px; } .otp.print-layout > .header { - margin-bottom: 30px; border-bottom: 4px solid black; font-size: 36px; font-weight: 600; + margin-bottom: 30px; } .otp.print-layout > .map-container { border: 2px solid black; + box-sizing: border-box; height: 400px; margin-bottom: 30px; - box-sizing: border-box; } /* View Switcher Styling */ -.view-switcher { - align-items: center; - display: flex; - justify-content: center; -} .view-switcher a { - color: rgba(255, 255, 255, 0.85); border-radius: 15px; + color: rgba(255, 255, 255, 0.85); font-size: 14px; padding: 6px 12px; user-select: none; @@ -98,8 +328,8 @@ /* Full screen modal styling */ .fullscreen-modal { - width: 75vw; height: 60vh; + width: 75vw; } .fullscreen-modal .modal-content { height: 90vh; @@ -108,6 +338,9 @@ height: 100%; width: 100%; } +.otp .navbar { + z-index: 25; +} /** These changes kick in when the map is hidden. These rules ensure that the entire "sidebar" (which at this point fills the entire screen) scrolls as a single unit. diff --git a/lib/components/app/desktop-nav.tsx b/lib/components/app/desktop-nav.tsx index f9a265b86..8ce6df8d9 100644 --- a/lib/components/app/desktop-nav.tsx +++ b/lib/components/app/desktop-nav.tsx @@ -13,14 +13,15 @@ import { DEFAULT_APP_TITLE } from '../../util/constants' import InvisibleA11yLabel from '../util/invisible-a11y-label' import NavLoginButtonAuth0 from '../user/nav-login-button-auth0' +import { NetworkConnectionBanner } from './network-connection-banner' import AppMenu, { Icon } from './app-menu' import LocaleSelector from './locale-selector' import NavbarItem from './nav-item' import ViewSwitcher from './view-switcher' const StyledNav = styled(Nav)` - /* Almost override bootstrap's margin-right: -15px */ - margin-right: -5px; + display: flex; + justify-content: end; /* Target only the svgs in the Navbar */ & > li > button > svg, & > li > span > button > span > svg { @@ -41,10 +42,6 @@ const StyledNav = styled(Nav)` padding: 15px; line-height: 20px; - @media (max-width: 768px) { - padding: 10px; - } - &:hover { background: rgba(0, 0, 0, 0.05); color: #ececec; @@ -63,6 +60,7 @@ const NavItemOnLargeScreens = styled(NavbarItem)` // Typscript TODO: otpConfig type export type Props = { locale: string + networkConnectionLost: boolean otpConfig: AppConfig popupTarget?: string setPopupContent: (url: string) => void @@ -83,6 +81,7 @@ export type Props = { */ const DesktopNav = ({ locale, + networkConnectionLost, otpConfig, popupTarget, setPopupContent @@ -99,18 +98,14 @@ const DesktopNav = ({ const BrandingElement = brandClickable ? 'a' : 'div' - const commonStyles = { marginLeft: 50 } - const brandingProps = brandClickable - ? { - href: '/#/', - style: { - ...commonStyles, - display: 'block', - position: 'relative', - zIndex: 10 - } - } - : { style: { ...commonStyles } } + const brandingProps = brandClickable && { + href: '/#/', + style: { + display: 'block', + position: 'relative', + zIndex: 10 + } + } const popupButtonText = popupTarget && intl.formatMessage({ @@ -142,7 +137,7 @@ const DesktopNav = ({ )} - + {popupTarget && ( @@ -166,6 +161,7 @@ const DesktopNav = ({ + ) } @@ -173,8 +169,10 @@ const DesktopNav = ({ // connect to the redux store const mapStateToProps = (state: AppReduxState) => { const { config: otpConfig } = state.otp + const { networkConnectionLost } = state.otp.ui.errors return { locale: state.otp.ui.locale, + networkConnectionLost, otpConfig, popupTarget: otpConfig.popups?.launchers?.toolbar } diff --git a/lib/components/app/nav-item.tsx b/lib/components/app/nav-item.tsx index d6cd2f9f9..9c4c92be8 100644 --- a/lib/components/app/nav-item.tsx +++ b/lib/components/app/nav-item.tsx @@ -19,10 +19,6 @@ export const NavbarButton = styled.button` padding: 15px; transition: all 0.1s ease-in-out; - @media (max-width: 768px) { - padding: 10px; - } - &:hover, &[aria-expanded='true'] { background: rgba(0, 0, 0, 0.05); diff --git a/lib/components/app/network-connection-banner.tsx b/lib/components/app/network-connection-banner.tsx new file mode 100644 index 000000000..a323520ac --- /dev/null +++ b/lib/components/app/network-connection-banner.tsx @@ -0,0 +1,83 @@ +import { CSSTransition, TransitionGroup } from 'react-transition-group' +import { FormattedMessage } from 'react-intl' +import React, { useRef } from 'react' +import styled from 'styled-components' + +import { RED_ON_WHITE } from '../util/colors' +import InvisibleA11yLabel from '../util/invisible-a11y-label' + +const containerClassname = 'network-connection-banner' +const timeout = 250 + +const TransitionStyles = styled.div` + .${containerClassname} { + background: ${RED_ON_WHITE}; + border-left: 1px solid #e7e7e7; + border-right: 1px solid #e7e7e7; + color: #fff; + font-weight: 600; + padding: 5px; + position: absolute; + text-align: center; + top: 50px; + width: 100%; + // When banner is fully loaded, set z-index higher than nav so we're not seeing the nav border. + z-index: 26; + + @media (max-width: 768px) { + border: 0; + } + } + .${containerClassname}-enter { + opacity: 0; + transform: translateY(-100%); + } + .${containerClassname}-enter-active { + opacity: 1; + transform: translateY(0); + transition: opacity ${timeout}ms ease-in; + } + .${containerClassname}-exit { + opacity: 1; + transform: translateY(0); + z-index: 20; + } + .${containerClassname}-exit-active { + opacity: 0; + transform: translateY(-100%); + transition: opacity ${timeout}ms ease-in, transform ${timeout}ms ease-in; + z-index: 20; + } +` + +export const NetworkConnectionBanner = ({ + networkConnectionLost +}: { + networkConnectionLost: boolean +}): JSX.Element => { + const connectionLostBannerRef = useRef(null) + return ( + + + {networkConnectionLost ? ( + + ) : ( + + )} + + + {networkConnectionLost && ( + +

                                                                  + +
                                                                  + + )} + + + ) +} diff --git a/lib/components/app/responsive-webapp.js b/lib/components/app/responsive-webapp.js index 2b2a44dfc..ee4c3c684 100644 --- a/lib/components/app/responsive-webapp.js +++ b/lib/components/app/responsive-webapp.js @@ -154,10 +154,13 @@ class ResponsiveWebapp extends Component { map, matchContentToUrl, parseUrlQueryString, - receivedPositionResponse + receivedPositionResponse, + setNetworkConnectionLost } = this.props // Add on back button press behavior. window.addEventListener('popstate', handleBackButtonPress) + window.addEventListener('online', () => setNetworkConnectionLost(false)) + window.addEventListener('offline', () => setNetworkConnectionLost(true)) // If a URL is detected without hash routing (e.g., http://localhost:9966?sessionId=test), // window.location.search will have a value. In this case, we need to redirect to the URL root with the @@ -180,10 +183,7 @@ class ResponsiveWebapp extends Component { navigator.geolocation.watchPosition( // On success (position) => { - // This object cloning is required to be allowed to read the position info twice - // on webkit browsers. - // See https://github.com/opentripplanner/otp-react-redux/pull/697 for details - receivedPositionResponse({ position: { ...position } }) + receivedPositionResponse({ position }) }, // On error (error) => { @@ -441,6 +441,7 @@ const mapStateToWrapperProps = (state) => { const mapWrapperDispatchToProps = { processSignIn: authActions.processSignIn, setLocale: uiActions.setLocale, + setNetworkConnectionLost: uiActions.setNetworkConnectionLost, showAccessTokenError: authActions.showAccessTokenError, showLoginError: authActions.showLoginError } diff --git a/lib/components/app/view-switcher.tsx b/lib/components/app/view-switcher.tsx index fc500f9b5..0bd47d5e8 100644 --- a/lib/components/app/view-switcher.tsx +++ b/lib/components/app/view-switcher.tsx @@ -3,14 +3,11 @@ import React from 'react' import Link from '../util/link' -type Props = { - sticky?: boolean -} /** * This component is a switcher between * the main views of the application. */ -const ViewSwitcher = ({ sticky }: Props) => { +const ViewSwitcher = (): JSX.Element => { const intl = useIntl() return (
                                                                  { className="view-switcher" id="view-switcher" role="group" - style={ - sticky - ? { - height: '100%', - left: 0, - position: 'absolute', - width: '100%' - } - : {} - } > diff --git a/lib/components/form/call-taker/advanced-options.js b/lib/components/form/call-taker/advanced-options.js index de97254ce..891db3c4b 100644 --- a/lib/components/form/call-taker/advanced-options.js +++ b/lib/components/form/call-taker/advanced-options.js @@ -224,7 +224,7 @@ class AdvancedOptions extends Component { }} > {/* Show the first mode setting */} - {applicableModeSettings.length >= 1 && ( + {applicableModeSettings?.length >= 1 && (
                                                                  {/* Show the remaining items after the first */} - {applicableModeSettings.length > 1 && + {applicableModeSettings?.length > 1 && applicableModeSettings .slice(1) .map((ms) => ( diff --git a/lib/components/mobile/batch-search-screen.tsx b/lib/components/mobile/batch-search-screen.tsx index efa6fadbc..47bff6735 100644 --- a/lib/components/mobile/batch-search-screen.tsx +++ b/lib/components/mobile/batch-search-screen.tsx @@ -40,8 +40,9 @@ const MobileSearchSettings = styled.div<{ top: 50px; transition: ${(props) => `all ${props.transitionDuration}ms ease`}; transition-delay: ${(props) => props.transitionDelay}ms; - /* Must appear under the 'hamburger' dropdown which has z-index of 1000. */ - z-index: 999; + /* Must appear under the 'hamburger' dropdown which has z-index of 1000, and the "network lost" + banner which has a z-index of 10 */ + z-index: 9; ` interface Props { diff --git a/lib/components/mobile/mobile.css b/lib/components/mobile/mobile.css index b1197cfbd..77a8ef225 100644 --- a/lib/components/mobile/mobile.css +++ b/lib/components/mobile/mobile.css @@ -3,29 +3,26 @@ } .otp .mobile-bottom-button-container { - position: fixed; bottom: 0; - right: 0; - left: 0; height: 40px; + left: 0; + position: fixed; + right: 0; } /* Navbar settings */ .otp.mobile .navbar { - margin-bottom: 0; border: none; + margin-bottom: 0; } .otp .navbar .mobile-header { align-items: center; display: flex; - height: 50px; + height: 100%; justify-content: center; - left: 50px; max-width: 90%; - position: fixed; - right: 50px; text-align: center; } @@ -38,7 +35,6 @@ @media (max-width: 768px) { .otp .navbar .mobile-header-text { - margin-top: 2px; word-break: break-all; } } @@ -56,138 +52,132 @@ border: none; color: white; font-size: 18px; - left: 7px; - position: fixed; } .otp .navbar .mobile-close { - position: fixed; - top: 4px; - right: 6px; color: white; font-size: 18px; -} - -.otp .navbar-brand { - padding: 7px 10px; + position: fixed; + right: 6px; + top: 4px; } /* Welcome screen */ .otp.mobile .welcome-location { - position: fixed; - top: 50px; + height: 60px; left: 0; + position: fixed; right: 0; - height: 60px; + top: 50px; } .otp.mobile .welcome-map { - position: fixed; - top: 110px; + bottom: 0; left: 0; + position: fixed; right: 0; - bottom: 0; + top: 110px; } /* Location search screen */ .otp.mobile .location-search { - position: fixed; - top: 50px; + bottom: 0; left: 0; + position: fixed; right: 0; - bottom: 0; + top: 50px; } /* Main search screen */ .otp.mobile .search-settings { - position: fixed; - top: 50px; + box-shadow: 3px 0px 12px #00000052; + height: 250px; left: 0; + position: fixed; right: 0; - height: 250px; - box-shadow: 3px 0px 12px #00000052; + top: 50px; } .otp.mobile .search-map { - position: fixed; - top: 300px; + bottom: 0; left: 0; + position: fixed; right: 0; - bottom: 0; + top: 300px; } /* Batch routing search screen */ .otp.mobile .batch-search-map { - position: fixed; - top: 282px; + bottom: 0; left: 0; + position: fixed; right: 0; - bottom: 0; + top: 282px; } /* Detailed options screen */ .otp.mobile .options-main-content { - position: fixed; - top: 50px; - left: 0; - right: 0; bottom: 55px; - overflow-y: auto; + left: 0; overflow-x: hidden; + overflow-y: auto; + position: fixed; + right: 0; + top: 50px; } .otp.mobile .options-lower-tray { - position: fixed; + bottom: 0; height: 55px; left: 0; + position: fixed; right: 0; - bottom: 0; } /* Results screen: normal display */ .otp.mobile .mobile-narrative-header { - position: fixed; - height: 40px; - left: 0; - right: 0; background-color: #444; color: #fff; - text-align: center; font-size: 20px; font-weight: 500; + height: 40px; + left: 0; + position: fixed; + right: 0; + text-align: center; } .otp.mobile .mobile-narrative-container { - position: fixed; bottom: 20px; - right: 0; left: 0; padding: 8px 12px; + position: fixed; + right: 0; } .otp.mobile .dots-container { - position: fixed; bottom: 0; - right: 0; - left: 0; height: 20px; + left: 0; + position: fixed; + right: 0; text-align: center; } .otp.mobile .dots-container .dot { + background-color: #ddd; + border-radius: 4px; display: inline-block; - width: 8px; height: 8px; - border-radius: 4px; - background-color: #ddd; margin: 0px 3px; vertical-align: 2px; + width: 8px; } .otp.mobile .dots-container .dot.active { @@ -195,64 +185,64 @@ } .otp.mobile .results-map { - position: fixed; - top: 100px; + bottom: 140px; left: 0; + position: fixed; right: 0; - bottom: 140px; + top: 100px; } /* Results screen: error display */ .otp.mobile .results-error-map { - position: fixed; - top: 100px; + height: 200px; left: 0; + position: fixed; right: 0; - height: 200px; + top: 100px; } .otp.mobile .results-error-message { - position: fixed; - left: 0; - right: 0; bottom: 0; + left: 0; padding-top: 12px; + position: fixed; + right: 0; } /* User Settings */ .otp.mobile .user-settings { - box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); background-color: white; - position: fixed; - left: 0; - right: 0; + border-radius: 5px; bottom: 0; - margin-bottom: 18px; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); height: 165px; - z-index: 999999; + left: 0; + margin-bottom: 18px; overflow-y: scroll; - border-radius: 5px; + position: fixed; + right: 0; + z-index: 999999; } /* Stop/Trip/Route Viewer screens */ .otp.mobile .viewer-container { - position: fixed; - top: 250px; bottom: 0; left: 0; - right: 0; overflow-y: auto; + position: fixed; + right: 0; + top: 250px; } .otp.mobile .viewer-map { - position: fixed; - top: 50px; + height: 200px; left: 0; + position: fixed; right: 0; - height: 200px; + top: 50px; } .otp.mobile .route-viewer-header { diff --git a/lib/components/mobile/navigation-bar.js b/lib/components/mobile/navigation-bar.js index 4426a244c..1e7b0079c 100644 --- a/lib/components/mobile/navigation-bar.js +++ b/lib/components/mobile/navigation-bar.js @@ -1,5 +1,6 @@ import { ArrowLeft } from '@styled-icons/fa-solid/ArrowLeft' import { connect } from 'react-redux' +import { injectIntl } from 'react-intl' import { Navbar } from 'react-bootstrap' import PropTypes from 'prop-types' import React, { Component } from 'react' @@ -8,7 +9,7 @@ import styled from 'styled-components' import * as uiActions from '../../actions/ui' import { accountLinks, getAuth0Config } from '../../util/auth' import { ComponentContext } from '../../util/contexts' -import { injectIntl } from 'react-intl' +import { NetworkConnectionBanner } from '../app/network-connection-banner' import { StyledIconWrapper } from '../util/styledIcon' import AppMenu from '../app/app-menu' import LocaleSelector from '../app/locale-selector' @@ -32,6 +33,7 @@ class MobileNavigationBar extends Component { headerText: PropTypes.string, intl: PropTypes.object, locale: PropTypes.string, + networkConnectionLost: PropTypes.bool, onBackClicked: PropTypes.func, setMobileScreen: PropTypes.func, showBackButton: PropTypes.bool @@ -55,6 +57,7 @@ class MobileNavigationBar extends Component { headerText, intl, locale, + networkConnectionLost, showBackButton } = this.props @@ -111,6 +114,9 @@ class MobileNavigationBar extends Component { )} + ) } @@ -123,7 +129,8 @@ const mapStateToProps = (state) => { auth0Config: getAuth0Config(state.otp.config.persistence), configLanguages: state.otp.config.language, extraMenuItems: state.otp?.config?.extraMenuItems, - locale: state.otp.ui.locale + locale: state.otp.ui.locale, + networkConnectionLost: state.otp.ui.errors.networkConnectionLost } } diff --git a/lib/components/narrative/line-itin/connected-itinerary-body.js b/lib/components/narrative/line-itin/connected-itinerary-body.js index 3a42c538c..7ca0c77dd 100644 --- a/lib/components/narrative/line-itin/connected-itinerary-body.js +++ b/lib/components/narrative/line-itin/connected-itinerary-body.js @@ -87,6 +87,8 @@ class ConnectedItineraryBody extends Component { const allowUserAlertCollapsing = config?.itinerary?.allowUserAlertCollapsing const showApproximatePrefixAccessLegs = config?.itinerary?.showApproximatePrefixAccessLegs + const showAlertEffectiveDateTimeText = + config?.itinerary?.showAlertEffectiveDateTimeText // Support OTP1 flex messages in Trip Details // Adding empty pickupBookingInfo and dropOffBookingInfo objects @@ -139,6 +141,7 @@ class ConnectedItineraryBody extends Component { setMainPanelContent(MainPanelContent.TRIP_VIEWER) }} showAgencyInfo + showAlertEffectiveDateTimeText={showAlertEffectiveDateTimeText} showApproximateAccessLegTravelTimes={showApproximatePrefixAccessLegs} showElevationProfile={config.elevationProfile} showLegIcon diff --git a/lib/components/narrative/loading.tsx b/lib/components/narrative/loading.tsx index 6761eeae4..9b4749382 100644 --- a/lib/components/narrative/loading.tsx +++ b/lib/components/narrative/loading.tsx @@ -10,7 +10,7 @@ type Props = { const Loading = ({ extraSmall, small }: Props): JSX.Element => { return ( -
                                                                  +
                                                                  diff --git a/lib/components/narrative/styled.js b/lib/components/narrative/styled.js index 9ca966693..8381eef24 100644 --- a/lib/components/narrative/styled.js +++ b/lib/components/narrative/styled.js @@ -14,6 +14,11 @@ export const NarrativeItinerariesContainer = styled.div` display: flex; flex-direction: column; height: 100%; + + // Prevent the loading spinner from clipping the top of the container + .loading-container { + margin-top: 25px; + } ` export const ULContainer = styled.ul` diff --git a/lib/components/user/monitored-trip/trip-basics-pane.tsx b/lib/components/user/monitored-trip/trip-basics-pane.tsx index dafafcd00..e3685dcec 100644 --- a/lib/components/user/monitored-trip/trip-basics-pane.tsx +++ b/lib/components/user/monitored-trip/trip-basics-pane.tsx @@ -180,9 +180,8 @@ const RenderAvailableDays = ({ title={notAvailableText} > li { display: inline-block; } @@ -26,10 +21,10 @@ } .navBarItem p { - padding: 5px 15px; - margin: 0; - font-weight: 600; font-size: 16px; + font-weight: 600; + margin: 0; + padding: 5px 15px; } .navBarItem ul { @@ -50,5 +45,8 @@ } #user-selector-label { background-color: transparent; + border: 0; + color: white; + height: 50px; } } diff --git a/lib/components/viewers/stop-schedule-viewer.tsx b/lib/components/viewers/stop-schedule-viewer.tsx index 51f85c967..8b62234c8 100644 --- a/lib/components/viewers/stop-schedule-viewer.tsx +++ b/lib/components/viewers/stop-schedule-viewer.tsx @@ -33,7 +33,11 @@ import TimezoneWarning from './timezone-warning' interface Props { calendarMax: string calendarMin: string - findStopTimesForStop: (arg: { date: string; stopId: string }) => void + findStopTimesForStop: (arg: { + date: string + forceFetch?: boolean + stopId: string + }) => void hideBackButton?: boolean homeTimezone: string intl: IntlShape @@ -137,7 +141,7 @@ class StopScheduleViewer extends Component { _findStopTimesForDate = (date: string) => { const { findStopTimesForStop, stopId } = this.props if (stopId) { - findStopTimesForStop({ date, stopId }) + findStopTimesForStop({ date, forceFetch: true, stopId }) } } diff --git a/lib/components/viewers/styled.ts b/lib/components/viewers/styled.ts index 5509975be..8a870a84e 100644 --- a/lib/components/viewers/styled.ts +++ b/lib/components/viewers/styled.ts @@ -50,7 +50,7 @@ export const PatternContainer = styled.div` & > span { width: 85%; - button { + button#headsign-selector-label { align-items: center; display: flex; justify-content: space-between; @@ -69,10 +69,10 @@ export const StopContainer = styled.ol` color: ${(props) => props?.textColor || DARK_TEXT_GREY}; background-color: ${(props) => props?.backgroundColor || '#fff'}; overflow-y: scroll; - height: 100%; - /* 100px bottom padding is needed to ensure all stops - are shown when browsers don't calculate 100% sensibly */ - padding: 15px 0 100px; + /* Calculate the height of the container a little short to ensure all stops + are shown when browsers don't calculate 100% sensibly. */ + height: calc(100% - 140px); + padding: 15px 0 0px; ` export const StopLink = styled.button` color: ${(props) => props?.textColor + 'da' || DARK_TEXT_GREY}; diff --git a/lib/reducers/create-otp-reducer.js b/lib/reducers/create-otp-reducer.js index e753c466c..9d66ab43b 100644 --- a/lib/reducers/create-otp-reducer.js +++ b/lib/reducers/create-otp-reducer.js @@ -225,6 +225,9 @@ export function getInitialState(userDefinedConfig) { }, ui: { diagramLeg: null, + errors: { + networkConnectionLost: !navigator.onLine + }, locale: null, localizedMessages: null, mainPanelContent: null, @@ -1108,6 +1111,14 @@ function createOtpReducer(config) { } } }) + case 'SET_NETWORK_CONNECTION_LOST': + return update(state, { + ui: { + errors: { + networkConnectionLost: { $set: action.payload } + } + } + }) case 'SERVICE_TIME_RANGE_REQUEST': return update(state, { serviceTimeRange: { $set: { pending: true } } diff --git a/package.json b/package.json index 1c98635c5..79c052fed 100644 --- a/package.json +++ b/package.json @@ -50,8 +50,8 @@ "@opentripplanner/geocoder": "^3.0.2", "@opentripplanner/humanize-distance": "^1.2.0", "@opentripplanner/icons": "3.0.1", - "@opentripplanner/itinerary-body": "6.0.3", "@opentripplanner/location-field": "3.1.1", + "@opentripplanner/itinerary-body": "6.1.0", "@opentripplanner/location-icon": "^1.4.1", "@opentripplanner/map-popup": "5.1.1", "@opentripplanner/otp2-tile-overlay": "2.1.1", diff --git a/yarn.lock b/yarn.lock index 214d59710..a5e4a5a90 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2542,10 +2542,10 @@ "@opentripplanner/core-utils" "^11.4.4" prop-types "^15.7.2" -"@opentripplanner/itinerary-body@6.0.3", "@opentripplanner/itinerary-body@^6.0.0": - version "6.0.3" - resolved "https://registry.yarnpkg.com/@opentripplanner/itinerary-body/-/itinerary-body-6.0.3.tgz#84573d20ac9cc1fc7f2d2e032fac5a072ac3e142" - integrity sha512-1qrH8hpR5Rr9KMGNnajI7GJyuoV+rogmyGqd5Z5DErZGp4luVzksIsnyW1IbxRslnV3bqKll+DH8lTXy4QIyZg== +"@opentripplanner/itinerary-body@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@opentripplanner/itinerary-body/-/itinerary-body-6.1.0.tgz#8a1399e8dc15b6918ede51a0f263519e4b891415" + integrity sha512-nv42+hJavdVS5nHtKLDv1Um04patzGD+/xh3zApgxpB9xG/D2Zy4JBqbyMtZ1eHKF4JYkmm+NsAq/7043WPXGA== dependencies: "@opentripplanner/core-utils" "^12.0.0" "@opentripplanner/humanize-distance" "^1.2.0" @@ -2582,6 +2582,24 @@ version "3.1.1" resolved "https://registry.yarnpkg.com/@opentripplanner/location-field/-/location-field-3.1.1.tgz#0658df4cfd47866153c8ae33fd60e991cdf94df0" integrity sha512-Q9yhi3AVlnj8izrpJvxN+uVBjjtEDyFXUsJ0VhE9zevVpGzCh/gcGNzJY5EnP2IRDd/Szx6dPUogt3XJlHkHTQ== + +"@opentripplanner/itinerary-body@^6.0.0": + version "6.0.3" + resolved "https://registry.yarnpkg.com/@opentripplanner/itinerary-body/-/itinerary-body-6.0.3.tgz#84573d20ac9cc1fc7f2d2e032fac5a072ac3e142" + integrity sha512-1qrH8hpR5Rr9KMGNnajI7GJyuoV+rogmyGqd5Z5DErZGp4luVzksIsnyW1IbxRslnV3bqKll+DH8lTXy4QIyZg== + dependencies: + "@opentripplanner/core-utils" "^12.0.0" + "@opentripplanner/humanize-distance" "^1.2.0" + "@opentripplanner/icons" "^3.0.0" + "@opentripplanner/location-icon" "^1.4.1" + "@styled-icons/fa-solid" "^10.34.0" + "@styled-icons/foundation" "^10.34.0" + date-fns "^2.28.0" + date-fns-tz "^1.2.2" + flat "^5.0.2" + react-animate-height "^3.0.4" + react-resize-detector "^4.2.1" + string-similarity "^4.0.4" dependencies: "@conveyal/geocoder-arcgis-geojson" "^0.0.3" "@opentripplanner/core-utils" "^12.0.0"