From e8adad1353d601b7a3347ba22694094f78028770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 21 Mar 2024 07:24:18 +0000 Subject: [PATCH] Move the PatternsSelector to a pop-up --- web/src/client/software.js | 32 +++- web/src/components/software/PatternItem.jsx | 1 - web/src/components/software/SoftwarePage.jsx | 177 +++++++++++++++---- web/src/components/software/UsedSize.jsx | 7 +- 4 files changed, 179 insertions(+), 38 deletions(-) diff --git a/web/src/client/software.js b/web/src/client/software.js index 4e9907ef5b..1493c5a1a1 100644 --- a/web/src/client/software.js +++ b/web/src/client/software.js @@ -49,6 +49,22 @@ const REGISTRATION_IFACE = "org.opensuse.Agama1.Registration"; * @property {string} message - Result message. */ +/** + * @typedef {object} SoftwareProposal + * @property {string} size - Used space in human-readable form. + * @property {Object.} patterns - Selected patterns and the reason. + */ + +/** + * @typedef {Object} Pattern + * @property {string} name - pattern name (internal ID) + * @property {string} category - pattern category + * @property {string} summary - pattern name (user visible) + * @property {string} description - long description of the pattern + * @property {number} order - display order (string!) + * @property {string} icon - icon name (not path or file name!) + */ + /** * Product manager. * @ignore @@ -177,7 +193,7 @@ class SoftwareBaseClient { /** * Returns how much space installation takes on disk * - * @return {Promise} + * @return {Promise} */ getProposal() { return this.client.get("/software/proposal"); @@ -186,10 +202,18 @@ class SoftwareBaseClient { /** * Returns available patterns * - * @return {Promise} + * @return {Promise} */ - getPatterns() { - return this.client.get("/software/patterns"); + async getPatterns() { + const patterns = await this.client.get("/software/patterns"); + return patterns.map((pattern) => ({ + name: pattern.name, + category: pattern.category, + summary: pattern.summary, + description: pattern.description, + order: parseInt(pattern.order), + icon: pattern.icon, + })); } /** diff --git a/web/src/components/software/PatternItem.jsx b/web/src/components/software/PatternItem.jsx index 75cdc464de..36806a2cc5 100644 --- a/web/src/components/software/PatternItem.jsx +++ b/web/src/components/software/PatternItem.jsx @@ -24,7 +24,6 @@ import { sprintf } from "sprintf-js"; import cockpit from "../../lib/cockpit"; -import { useInstallerClient } from "~/context/installer"; import { Icon } from "~/components/layout"; import { _ } from "~/i18n"; diff --git a/web/src/components/software/SoftwarePage.jsx b/web/src/components/software/SoftwarePage.jsx index ae57d48d97..2fc1be63ac 100644 --- a/web/src/components/software/SoftwarePage.jsx +++ b/web/src/components/software/SoftwarePage.jsx @@ -19,42 +19,137 @@ * find current contact information at www.suse.com. */ -import React, { useState, useEffect } from "react"; -import { Skeleton } from "@patternfly/react-core"; +// @ts-check -import { Page } from "~/components/core"; +import React, { useEffect, useState } from "react"; +import { Button, Skeleton } from "@patternfly/react-core"; + +import { Page, Popup, Section } from "~/components/core"; import { Center } from "~/components/layout"; -import { PatternSelector } from "~/components/software"; +import { PatternSelector, UsedSize } from "~/components/software"; import { useInstallerClient } from "~/context/installer"; -import { useCancellablePromise } from "~/utils"; +import { noop, useCancellablePromise } from "~/utils"; import { BUSY } from "~/client/status"; import { _ } from "~/i18n"; +/** + * @typedef {Object} Pattern + * @property {string} name - pattern name (internal ID) + * @property {string} category - pattern category + * @property {string} summary - pattern name (user visible) + * @property {string} description - long description of the pattern + * @property {number} order - display order (string!) + * @property {number} selected_by - who selected the pattern + */ + +/** + * Builds a list of patterns include its selection status + * + * @param {import("~/client/software").Pattern[]} patterns - Patterns from the HTTP API + * @param {Object.} selection - Patterns selection + * @return {Pattern[]} List of patterns including its selection status + */ +function buildPatterns(patterns, selection) { + return patterns.map((pattern) => { + const selected_by = (selection[pattern.name] !== undefined) ? selection[pattern.name] : 2; + return { + ...pattern, + selected_by, + }; + }).sort((a, b) => a.order - b.order); +} + +/** + * Popup for selecting software patterns. + * @component + * + * @param {object} props + * @param {Pattern[]} props.patterns - List of patterns + * @param {boolean} props.isOpen - Whether the pop-up should be open + * @param {function} props.onFinish - Callback to be called when the selection is finished + */ +const PatternsSelectorPopup = ({ + patterns, + isOpen = false, + onFinish = noop, +}) => { + console.log("isOpen", isOpen); + return ( + + + + + onFinish()} + > + {_("Close")} + + + + ); +}; + +const SelectPatternsButton = ({ patterns }) => { + const [isPopupOpen, setIsPopupOpen] = useState(false); + + const openPopup = () => setIsPopupOpen(true); + const closePopup = () => setIsPopupOpen(false); + + return ( + <> + + + + ); +}; + /** * Software page content depending on the current service state * @component - * @param {number} status current backend service status + * @param {object} props + * @param {number} props.status - current backend service status + * @param {string} props.used- used space in human readable format + * @param {Pattern[]} props.patterns - patterns * @returns {JSX.Element} */ -function Content({ status }) { - switch (status) { - case undefined: - return null; - case BUSY: - return ( -
- - - - - - -
- ); - default: - return ; +const Content = ({ patterns, status, used }) => { + if (status === BUSY) { + return ( +
+ + + + + + +
+ ); } -} + + // return ; + return ( +
+ +
    + {patterns.filter((p) => p.selected_by !== 2).map((pattern) => ( +
  • + {pattern.summary} +
  • + ))} +
+ +
+ ); +}; /** * Software page component @@ -62,24 +157,42 @@ function Content({ status }) { * @returns {JSX.Element} */ function SoftwarePage() { - const [status, setStatus] = useState(); + const [status, setStatus] = useState(BUSY); + const [patterns, setPatterns] = useState([]); + const [used, setUsed] = useState(""); const client = useInstallerClient(); const { cancellablePromise } = useCancellablePromise(); - const updateStatus = (status) => { - setStatus(status); - }; - useEffect(() => { - cancellablePromise(client.software.getStatus().then(updateStatus)); + cancellablePromise(client.software.getStatus().then(setStatus)); - return client.software.onStatusChange(updateStatus); + return client.software.onStatusChange(setStatus); }, [client, cancellablePromise]); + useEffect(() => { + if (!patterns) return; + + return client.software.onSelectedPatternsChanged((selection) => { + client.software.getProposal().then(({ size }) => setUsed(size)); + setPatterns(buildPatterns(patterns, selection)); + }); + }, [client.software, patterns]); + + useEffect(() => { + const loadPatterns = async () => { + const patterns = await cancellablePromise(client.software.getPatterns()); + const { patterns: selection, size } = await cancellablePromise(client.software.getProposal()); + setUsed(size); + setPatterns(buildPatterns(patterns, selection)); + }; + + loadPatterns(); + }, [client.software, cancellablePromise]); + return ( // TRANSLATORS: page title - + ); } diff --git a/web/src/components/software/UsedSize.jsx b/web/src/components/software/UsedSize.jsx index 6e68738244..036b5230f7 100644 --- a/web/src/components/software/UsedSize.jsx +++ b/web/src/components/software/UsedSize.jsx @@ -25,12 +25,17 @@ import { Em } from "~/components/core"; import { _ } from "~/i18n"; export default function UsedSize({ size }) { + console.log("size", size); if (size === undefined || size === "" || size === "0 B") return null; // TRANSLATORS: %s will be replaced by the estimated installation size, // example: "728.8 MiB" const [msg1, msg2] = _("Installation will take %s").split("%s"); return ( - <>{msg1}{size}{msg2} + <> + {msg1} + {size} + {msg2} + ); }