Skip to content

Commit

Permalink
Move the PatternsSelector to a pop-up
Browse files Browse the repository at this point in the history
  • Loading branch information
imobachgs committed Mar 21, 2024
1 parent bcbfb4e commit e8adad1
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 38 deletions.
32 changes: 28 additions & 4 deletions web/src/client/software.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.<string, number>} 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
Expand Down Expand Up @@ -177,7 +193,7 @@ class SoftwareBaseClient {
/**
* Returns how much space installation takes on disk
*
* @return {Promise<object>}
* @return {Promise<SoftwareProposal>}
*/
getProposal() {
return this.client.get("/software/proposal");
Expand All @@ -186,10 +202,18 @@ class SoftwareBaseClient {
/**
* Returns available patterns
*
* @return {Promise<object>}
* @return {Promise<Pattern[]>}
*/
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,
}));
}

/**
Expand Down
1 change: 0 additions & 1 deletion web/src/components/software/PatternItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down
177 changes: 145 additions & 32 deletions web/src/components/software/SoftwarePage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,67 +19,180 @@
* 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.<string, number>} 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 (
<Popup title={_("Software selection")} isOpen={isOpen}>
<PatternSelector patterns={patterns} />

<Popup.Actions>
<Popup.PrimaryAction
onClick={() => onFinish()}
>
{_("Close")}
</Popup.PrimaryAction>
</Popup.Actions>
</Popup>
);
};

const SelectPatternsButton = ({ patterns }) => {
const [isPopupOpen, setIsPopupOpen] = useState(false);

const openPopup = () => setIsPopupOpen(true);
const closePopup = () => setIsPopupOpen(false);

return (
<>
<Button
variant="primary"
onClick={openPopup}
>
{_("Change")}
</Button>
<PatternsSelectorPopup
patterns={patterns}
isOpen={isPopupOpen}
onFinish={closePopup}
/>
</>
);
};

/**
* 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 (
<Center>
<Skeleton width="20%" />
<Skeleton width="35%" />
<Skeleton width="70%" />
<Skeleton width="65%" />
<Skeleton width="80%" />
<Skeleton width="75%" />
</Center>
);
default:
return <PatternSelector />;
const Content = ({ patterns, status, used }) => {
if (status === BUSY) {
return (
<Center>
<Skeleton width="20%" />
<Skeleton width="35%" />
<Skeleton width="70%" />
<Skeleton width="65%" />
<Skeleton width="80%" />
<Skeleton width="75%" />
</Center>
);
}
}

// return <PatternSelector />;
return (
<Section title={_("Software selection")} aria-label={_("List of software patterns")}>
<UsedSize size={used} />
<ul>
{patterns.filter((p) => p.selected_by !== 2).map((pattern) => (
<li key={pattern.name}>
{pattern.summary}
</li>
))}
</ul>
<SelectPatternsButton patterns={patterns} />
</Section>
);
};

/**
* Software page component
* @component
* @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
<Page icon="apps" title={_("Software")}>
<Content status={status} />
<Content patterns={patterns} status={status} used={used} />
</Page>
);
}
Expand Down
7 changes: 6 additions & 1 deletion web/src/components/software/UsedSize.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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}<Em>{size}</Em>{msg2}</>
<>
{msg1}
<Em>{size}</Em>
{msg2}
</>
);
}

0 comments on commit e8adad1

Please sign in to comment.