diff --git a/web/package/agama-web-ui.changes b/web/package/agama-web-ui.changes index 24f7d653b3..20d8c9de15 100644 --- a/web/package/agama-web-ui.changes +++ b/web/package/agama-web-ui.changes @@ -1,3 +1,13 @@ +------------------------------------------------------------------- +Tue Feb 25 13:08:43 UTC 2025 - Imobach Gonzalez Sosa + +- Use the backend's language as fallback, ignoring the browser's one + It reduces the chances to produce unwanted side-effects when connecting + to the web user interface (gh#agama-project/agama#2071). +- Do not block when connecting during system installation. +- Make some small fixes/improvements to the overview, localization + and software pages markup. + ------------------------------------------------------------------- Tue Feb 25 12:36:50 UTC 2025 - Ancor Gonzalez Sosa diff --git a/web/src/App.tsx b/web/src/App.tsx index 52cdc7e723..d53fa8ebb1 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -41,7 +41,9 @@ function App() { const location = useLocation(); const { isBusy, phase } = useInstallerStatus({ suspense: true }); const { connected, error } = useInstallerClientStatus(); - const { selectedProduct, products } = useProduct({ suspense: true }); + const { selectedProduct, products } = useProduct({ + suspense: phase !== InstallationPhase.Install, + }); const { language } = useInstallerL10n(); useL10nConfigChanges(); useProductChanges(); diff --git a/web/src/components/l10n/KeyboardSelection.tsx b/web/src/components/l10n/KeyboardSelection.tsx index 9ad48eb1d9..febcd26890 100644 --- a/web/src/components/l10n/KeyboardSelection.tsx +++ b/web/src/components/l10n/KeyboardSelection.tsx @@ -21,12 +21,11 @@ */ import React, { useState } from "react"; -import { Content, Form, FormGroup, Radio } from "@patternfly/react-core"; +import { Content, Flex, Form, FormGroup, Radio } from "@patternfly/react-core"; import { useNavigate } from "react-router-dom"; import { ListSearch, Page } from "~/components/core"; import { _ } from "~/i18n"; import { useConfigMutation, useL10n } from "~/queries/l10n"; -import textStyles from "@patternfly/react-styles/css/utilities/Text/text"; // TODO: Add documentation // TODO: Evaluate if worth it extracting the selector @@ -55,12 +54,10 @@ export default function KeyboardSelection() { name="keymap" onChange={() => setSelected(id)} label={ - <> - - {name} - {" "} + + {name} {id} - + } value={id} isChecked={id === selected} @@ -80,11 +77,9 @@ export default function KeyboardSelection() { - -
- {keymapsList} -
-
+
+ {keymapsList} +
diff --git a/web/src/components/l10n/LocaleSelection.tsx b/web/src/components/l10n/LocaleSelection.tsx index d75e6897f7..08e1219984 100644 --- a/web/src/components/l10n/LocaleSelection.tsx +++ b/web/src/components/l10n/LocaleSelection.tsx @@ -54,15 +54,9 @@ export default function LocaleSelection() { onChange={() => setSelected(id)} label={ - - {name} - - - {territory} - - - {id} - + {name} + {territory} + {id} } value={id} @@ -83,11 +77,9 @@ export default function LocaleSelection() { - -
- {localesList} -
-
+
+ {localesList} +
diff --git a/web/src/components/l10n/TimezoneSelection.tsx b/web/src/components/l10n/TimezoneSelection.tsx index 54d5d0522c..61db7b4414 100644 --- a/web/src/components/l10n/TimezoneSelection.tsx +++ b/web/src/components/l10n/TimezoneSelection.tsx @@ -21,13 +21,13 @@ */ import React, { useState } from "react"; -import { Content, Divider, Flex, Form, FormGroup, Radio } from "@patternfly/react-core"; +import { Content, Flex, Form, FormGroup, Radio } from "@patternfly/react-core"; import { useNavigate } from "react-router-dom"; import { ListSearch, Page } from "~/components/core"; import { timezoneTime } from "~/utils"; import { useConfigMutation, useL10n } from "~/queries/l10n"; import { Timezone } from "~/types/l10n"; -import textStyles from "@patternfly/react-styles/css/utilities/Text/text"; +import spacingStyles from "@patternfly/react-styles/css/utilities/Spacing/spacing"; import { _ } from "~/i18n"; type TimezoneWithDetails = Timezone & { details: string }; @@ -89,18 +89,17 @@ export default function TimezoneSelection() { name="timezone" onChange={() => setSelected(id)} label={ - <> - - {parts.join("-")} - {" "} + + + {parts.join("-")} + {country} - + } description={ - + {timezoneTime(id, date) || ""} - -
{details}
+ {details}
} value={id} @@ -126,11 +125,9 @@ export default function TimezoneSelection() { - -
- {timezonesList} -
-
+
+ {timezonesList} +
diff --git a/web/src/components/layout/Layout.tsx b/web/src/components/layout/Layout.tsx index df901d97b9..27239170cd 100644 --- a/web/src/components/layout/Layout.tsx +++ b/web/src/components/layout/Layout.tsx @@ -74,13 +74,10 @@ const Layout = ({ mountSidebar && setIsSidebarOpen(newWindowSize >= agamaWidthBreakpoints.lg); }; - const pageProps: Omit> = { - isManagedSidebar: true, - }; + const pageProps: Omit> = {}; if (mountSidebar) { - pageProps.sidebar = ; - pageProps.isManagedSidebar = false; + pageProps.sidebar = ; } if (mountHeader) { pageProps.masthead = ( diff --git a/web/src/components/overview/OverviewPage.tsx b/web/src/components/overview/OverviewPage.tsx index 052c19a7f4..0852b55ac6 100644 --- a/web/src/components/overview/OverviewPage.tsx +++ b/web/src/components/overview/OverviewPage.tsx @@ -38,18 +38,16 @@ export default function OverviewPage() { - - - - - - - + + + {_( + "These are the most relevant installation settings. Feel free to browse the sections in the menu for further details.", + )} + + + + + diff --git a/web/src/components/software/SoftwarePage.tsx b/web/src/components/software/SoftwarePage.tsx index 9c4c66b1a8..a770a649e9 100644 --- a/web/src/components/software/SoftwarePage.tsx +++ b/web/src/components/software/SoftwarePage.tsx @@ -173,7 +173,7 @@ function SoftwarePage(): React.ReactNode { {proposal.size && ( - + diff --git a/web/src/context/installerL10n.test.tsx b/web/src/context/installerL10n.test.tsx index b8f3153090..3fc32e88d4 100644 --- a/web/src/context/installerL10n.test.tsx +++ b/web/src/context/installerL10n.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright (c) [2023] SUSE LLC + * Copyright (c) [2023-2025] SUSE LLC * * All Rights Reserved. * @@ -120,37 +120,6 @@ describe("InstallerL10nProvider", () => { }); }); - describe("when the language is set to an unsupported language", () => { - beforeEach(() => { - document.cookie = "agamaLang=de-DE; path=/;"; - mockFetchConfigFn.mockResolvedValue({ uiLocale: "de_DE.UTF-8" }); - }); - - it("uses the first supported language from the browser", async () => { - render( - - - - - , - ); - - await waitFor(() => expect(utils.locationReload).toHaveBeenCalled()); - - // renders again after reloading - render( - - - - - , - ); - - await waitFor(() => screen.getByText("hola")); - expect(mockUpdateConfigFn).toHaveBeenCalledWith({ uiLocale: "es_ES.UTF-8" }); - }); - }); - describe("when the language is not set", () => { beforeEach(() => { // Ensure both, UI and backend mock languages, are in sync since @@ -159,7 +128,7 @@ describe("InstallerL10nProvider", () => { mockFetchConfigFn.mockResolvedValue({ uiLocale: "es_ES.UTF-8" }); }); - it("sets the preferred language from browser and reloads", async () => { + it("sets the language from backend", async () => { render( @@ -180,34 +149,6 @@ describe("InstallerL10nProvider", () => { ); await waitFor(() => screen.getByText("hola")); }); - - describe("when the browser language does not contain the full locale", () => { - beforeEach(() => { - jest.spyOn(window.navigator, "languages", "get").mockReturnValue(["es", "cs-CZ"]); - }); - - it("sets the first which language matches", async () => { - render( - - - - - , - ); - - await waitFor(() => expect(utils.locationReload).toHaveBeenCalled()); - - // renders again after reloading - render( - - - - - , - ); - await waitFor(() => screen.getByText("hola!")); - }); - }); }); }); diff --git a/web/src/context/installerL10n.tsx b/web/src/context/installerL10n.tsx index a01a320099..e7f8127b73 100644 --- a/web/src/context/installerL10n.tsx +++ b/web/src/context/installerL10n.tsx @@ -134,6 +134,16 @@ function languageToLocale(language: string): string { return `${locale}.UTF-8`; } +/** + * Returns the language tag from the backend. + * + * @return Language tag from the backend locale. + */ +async function languageFromBackend(): Promise { + const config = await fetchConfig(); + return languageFromLocale(config.uiLocale); +} + /** * Returns the first supported language from the given list. * @@ -224,14 +234,19 @@ async function loadTranslations(locale: string) { * * @see useInstallerL10n */ -function InstallerL10nProvider({ children }: { children?: React.ReactNode }) { +function InstallerL10nProvider({ + initialLanguage, + children, +}: { + initialLanguage?: string; + children?: React.ReactNode; +}) { const { connected } = useInstallerClientStatus(); - const [language, setLanguage] = useState(undefined); + const [language, setLanguage] = useState(initialLanguage); const [keymap, setKeymap] = useState(undefined); const syncBackendLanguage = useCallback(async () => { - const config = await fetchConfig(); - const backendLanguage = languageFromLocale(config.uiLocale); + const backendLanguage = await languageFromBackend(); if (backendLanguage === language) return; // FIXME: fallback to en-US if the language is not supported. @@ -240,7 +255,7 @@ function InstallerL10nProvider({ children }: { children?: React.ReactNode }) { const changeLanguage = useCallback( async (lang?: string) => { - const wanted = lang || languageFromQuery(); + const wanted = lang || languageFromQuery() || (await languageFromBackend()); // Just for development purposes if (wanted === "xx" || wanted === "xx-XX") { @@ -253,7 +268,6 @@ function InstallerL10nProvider({ children }: { children?: React.ReactNode }) { wanted, wanted?.split("-")[0], // fallback to the language (e.g., "es" for "es-AR") agamaLanguage(), - ...navigator.languages, ].filter((l) => l); const newLanguage = findSupportedLanguage(candidateLanguages) || "en-US"; const mustReload = storeAgamaLanguage(newLanguage); diff --git a/web/src/queries/software.ts b/web/src/queries/software.ts index 34710f40fa..71d3aa8c66 100644 --- a/web/src/queries/software.ts +++ b/web/src/queries/software.ts @@ -197,7 +197,10 @@ const useProduct = ( }) as [{ data: string; isPending: boolean }, { data: Product[]; isPending: boolean }]; if (isSelectedPending || isProductsPending) { - return {}; + return { + products: [], + selectedProduct: undefined, + }; } const selectedProduct = products.find((p: Product) => p.id === selected); diff --git a/web/src/test-utils.tsx b/web/src/test-utils.tsx index 1cdf7db152..b0f36e8ee8 100644 --- a/web/src/test-utils.tsx +++ b/web/src/test-utils.tsx @@ -114,7 +114,7 @@ const Providers = ({ children, withL10n }) => { if (withL10n) { return ( - {children} + {children} ); }