From 8144d2811403b82962274fd10b9e14f9cbb7d0a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20Slez=C3=A1k?= Date: Tue, 26 Sep 2023 16:19:39 +0200 Subject: [PATCH 01/27] initial pattern selector --- web/src/client/software.js | 11 ++++ .../components/overview/SoftwareSection.jsx | 1 + .../software/SoftwareSelectionPage.jsx | 60 ++++++++++++++++++ web/src/index.js | 62 +++++++++++-------- 4 files changed, 107 insertions(+), 27 deletions(-) create mode 100644 web/src/components/software/SoftwareSelectionPage.jsx diff --git a/web/src/client/software.js b/web/src/client/software.js index cc6328eb23..e148f0ea5e 100644 --- a/web/src/client/software.js +++ b/web/src/client/software.js @@ -81,6 +81,17 @@ class SoftwareBaseClient { return proxy.UsedDiskSpace(); } + /** + * Returns available patterns + * + * @param {boolean} filter - `true` = filter the patterns, `false` = all patterns + * @return {Promise<>} + */ + async patterns(filter) { + const proxy = await this.client.proxy(SOFTWARE_IFACE); + return proxy.ListPatterns(filter); + } + /** * Returns the selected product * diff --git a/web/src/components/overview/SoftwareSection.jsx b/web/src/components/overview/SoftwareSection.jsx index f8f93bc8fb..5360a8cea9 100644 --- a/web/src/components/overview/SoftwareSection.jsx +++ b/web/src/components/overview/SoftwareSection.jsx @@ -154,6 +154,7 @@ export default function SoftwareSection({ showErrors }) { // TRANSLATORS: page section title={_("Software")} icon="apps" + path="/software" loading={state.busy} errors={errors} > diff --git a/web/src/components/software/SoftwareSelectionPage.jsx b/web/src/components/software/SoftwareSelectionPage.jsx new file mode 100644 index 0000000000..c495366bbf --- /dev/null +++ b/web/src/components/software/SoftwareSelectionPage.jsx @@ -0,0 +1,60 @@ +/* + * Copyright (c) [2023] SUSE LLC + * + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, contact SUSE LLC. + * + * To contact SUSE LLC about this file by physical or electronic mail, you may + * find current contact information at www.suse.com. + */ + +import React, { useEffect, useState } from "react"; +import { useInstallerClient } from "~/context/installer"; +import { Page } from "~/components/core"; +import { _ } from "~/i18n"; + +function patternList(patterns) { + return Object.keys(patterns).map(pattern =>

{patterns[pattern][3]}

); +} + +function SoftwareSelectionPage() { + const [patterns, setPatterns] = useState(); + + const client = useInstallerClient(); + + useEffect(() => { + // patterns already loaded + if (patterns) return; + + client.software.patterns(true) + .then((pats) => { + setPatterns(pats); + }); + }, [patterns, client.software]); + + console.log(patterns); + + const content = (patterns) + ? <>

{_("Available Software")}

{patternList(patterns)} + : <>; + + return ( + // TRANSLATORS: page title + + { content } + + ); +} + +export default SoftwareSelectionPage; diff --git a/web/src/index.js b/web/src/index.js index 44c1a5570b..0d805ea56a 100644 --- a/web/src/index.js +++ b/web/src/index.js @@ -39,6 +39,9 @@ import "@patternfly/patternfly/patternfly-base.scss"; import App from "~/App"; import Main from "~/Main"; import DevServerWrapper from "~/DevServerWrapper"; +import L10nWrapper from "~/L10nWrapper"; +import L10nBackendWrapper from "~/L10nBackendWrapper"; +import SoftwareSelectionPage from "~/components/software/SoftwareSelectionPage"; import { Overview } from "~/components/overview"; import { ProductSelectionPage } from "~/components/software"; import { ProposalPage as StoragePage, ISCSIPage, DASDPage, ZFCPPage } from "~/components/storage"; @@ -71,31 +74,36 @@ const container = document.getElementById("root"); const root = createRoot(container); root.render( - - - - - - - }> - }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - } /> - - - - - - - + + + + + + + + + }> + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + } /> + + + + + + + + + ); From f9a1aae7152b350e175cd98ed1e6048256ddcf8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20Slez=C3=A1k?= Date: Tue, 26 Sep 2023 18:33:03 +0200 Subject: [PATCH 02/27] Group patterns --- web/src/components/software/PatternGroup.jsx | 59 +++++++++++ .../components/software/PatternSelector.jsx | 97 +++++++++++++++++++ .../software/SoftwareSelectionPage.jsx | 34 ++----- web/src/components/software/index.js | 1 + 4 files changed, 163 insertions(+), 28 deletions(-) create mode 100644 web/src/components/software/PatternGroup.jsx create mode 100644 web/src/components/software/PatternSelector.jsx diff --git a/web/src/components/software/PatternGroup.jsx b/web/src/components/software/PatternGroup.jsx new file mode 100644 index 0000000000..ebea526574 --- /dev/null +++ b/web/src/components/software/PatternGroup.jsx @@ -0,0 +1,59 @@ +/* + * Copyright (c) [2023] SUSE LLC + * + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, contact SUSE LLC. + * + * To contact SUSE LLC about this file by physical or electronic mail, you may + * find current contact information at www.suse.com. + */ + +import React, { useEffect, useState } from "react"; +import { Badge, ExpandableSection } from "@patternfly/react-core"; + +export default function PatternGroup({ + name, + children, + selected, + count, + expanded +}) { + const [isExpanded, setIsExpanded] = useState(expanded); + + const onToggle = (isExpanded) => { + setIsExpanded(isExpanded); + }; + + useEffect(() => { + setIsExpanded(expanded); + }, [expanded]); + + return ( + + {name}{" "} + + {selected} / {count} + + + } + onToggle={onToggle} + isExpanded={isExpanded} + > + {children} + + ); +} diff --git a/web/src/components/software/PatternSelector.jsx b/web/src/components/software/PatternSelector.jsx new file mode 100644 index 0000000000..484b282b44 --- /dev/null +++ b/web/src/components/software/PatternSelector.jsx @@ -0,0 +1,97 @@ +/* + * Copyright (c) [2022] SUSE LLC + * + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, contact SUSE LLC. + * + * To contact SUSE LLC about this file by physical or electronic mail, you may + * find current contact information at www.suse.com. + */ + +import React, { useEffect, useState } from "react"; +import { useInstallerClient } from "~/context/installer"; +import PatternGroup from "./PatternGroup"; + +function groupPatterns(patterns) { + // group patterns + const pattern_groups = {}; + + Object.keys(patterns).forEach((pattern) => { + const pattern_data = patterns[pattern]; + pattern_data.push(pattern); + + if (pattern_groups[pattern_data[0]]) { + pattern_groups[pattern_data[0]].push(pattern_data); + } else { + pattern_groups[pattern_data[0]] = [pattern_data]; + } + }); + + // sort patterns by the "order" value + Object.keys(pattern_groups).forEach((group) => { + pattern_groups[group].sort((p1, p2) => (p1[4] < p2[4] ? -1 : 1)); + }); + + console.log(pattern_groups); + + return pattern_groups; +} + +function patternList(patterns) { + const groups = groupPatterns(patterns); + + const selector = Object.keys(groups).map((group) => { + return ( + + { (groups[group]).map(pattern =>

{pattern[3]} - {pattern[1]}

) } +
+ ); + }); + + return selector; +} + +function PatternSelector() { + const [patterns, setPatterns] = useState(); + const client = useInstallerClient(); + + useEffect(() => { + // patterns already loaded + if (patterns) return; + + client.software.patterns(true) + .then((pats) => { + setPatterns(pats); + }); + }, [patterns, client.software]); + + console.log(patterns); + + const content = (patterns) + ? patternList(patterns) + : <>; + + return ( + <> + { content } + + ); +} + +export default PatternSelector; diff --git a/web/src/components/software/SoftwareSelectionPage.jsx b/web/src/components/software/SoftwareSelectionPage.jsx index c495366bbf..36a2483961 100644 --- a/web/src/components/software/SoftwareSelectionPage.jsx +++ b/web/src/components/software/SoftwareSelectionPage.jsx @@ -19,40 +19,18 @@ * find current contact information at www.suse.com. */ -import React, { useEffect, useState } from "react"; -import { useInstallerClient } from "~/context/installer"; -import { Page } from "~/components/core"; +import React from "react"; +import { Page, Section } from "~/components/core"; +import { PatternSelector } from "~/components/software"; import { _ } from "~/i18n"; -function patternList(patterns) { - return Object.keys(patterns).map(pattern =>

{patterns[pattern][3]}

); -} - function SoftwareSelectionPage() { - const [patterns, setPatterns] = useState(); - - const client = useInstallerClient(); - - useEffect(() => { - // patterns already loaded - if (patterns) return; - - client.software.patterns(true) - .then((pats) => { - setPatterns(pats); - }); - }, [patterns, client.software]); - - console.log(patterns); - - const content = (patterns) - ? <>

{_("Available Software")}

{patternList(patterns)} - : <>; - return ( // TRANSLATORS: page title - { content } +
+ +
); } diff --git a/web/src/components/software/index.js b/web/src/components/software/index.js index deeecb0918..a47dc87e40 100644 --- a/web/src/components/software/index.js +++ b/web/src/components/software/index.js @@ -21,3 +21,4 @@ export { default as ProductSelectionPage } from "./ProductSelectionPage"; export { default as ChangeProductLink } from "./ChangeProductLink"; +export { default as PatternSelector } from "./PatternSelector"; From 23ce6e05d38ba7b3e375c179a67354d43e37440c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20Slez=C3=A1k?= Date: Wed, 27 Sep 2023 15:15:27 +0200 Subject: [PATCH 03/27] Update pattern selector --- web/src/assets/styles/app.scss | 62 +++++++++++++++++ web/src/components/software/PatternGroup.jsx | 6 +- web/src/components/software/PatternItem.jsx | 66 +++++++++++++++++++ .../components/software/PatternSelector.jsx | 36 +++++++--- .../software/SoftwareSelectionPage.jsx | 7 +- 5 files changed, 160 insertions(+), 17 deletions(-) create mode 100644 web/src/components/software/PatternItem.jsx diff --git a/web/src/assets/styles/app.scss b/web/src/assets/styles/app.scss index 96dc4ae0d5..a278bfe0a5 100644 --- a/web/src/assets/styles/app.scss +++ b/web/src/assets/styles/app.scss @@ -109,3 +109,65 @@ button.kebab-toggler { margin-inline-end: 5px; } } + +.pattern-container { + display: grid; + grid-template-columns: 1em auto; + grid-template-rows: auto auto; + gap: 0.2em 1em; + grid-auto-flow: row; + grid-template-areas: + "checkbox label" + "empty summary"; + margin-bottom: 1em; + padding: 0.5em; + border-radius: 5px; +} + +.pattern-container:hover { + background-color: #eee; +} + +.pattern-label { + display: grid; + // grid-template-columns: 32px auto; + grid-template-columns: 0 auto; + grid-template-rows: auto; + // gap: 0 1em; + grid-auto-flow: row; + grid-template-areas: "label-icon label-text"; + grid-area: label; +} + +.pattern-label-icon { + grid-area: label-icon; + align-self: center; +} + +.pattern-label-text { + grid-area: label-text; + font-size: 110%; + font-weight: bold; + justify-self: start; + align-self: center; +} + +.pattern-summary { + grid-area: summary; + color: #666; + max-width: 40em; +} + +.pattern-checkbox { + grid-area: checkbox; + justify-self: center; + align-self: center; +} + +.pattern-group-name { + font-size: 120%; +} + +.search-summary { + color: #666; +} diff --git a/web/src/components/software/PatternGroup.jsx b/web/src/components/software/PatternGroup.jsx index ebea526574..29608ce5e8 100644 --- a/web/src/components/software/PatternGroup.jsx +++ b/web/src/components/software/PatternGroup.jsx @@ -43,12 +43,12 @@ export default function PatternGroup({ - {name}{" "} + + {name}{" "} {selected} / {count} - + } onToggle={onToggle} isExpanded={isExpanded} diff --git a/web/src/components/software/PatternItem.jsx b/web/src/components/software/PatternItem.jsx new file mode 100644 index 0000000000..6dcbe1f664 --- /dev/null +++ b/web/src/components/software/PatternItem.jsx @@ -0,0 +1,66 @@ +/* + * Copyright (c) [2022] SUSE LLC + * + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, contact SUSE LLC. + * + * To contact SUSE LLC about this file by physical or electronic mail, you may + * find current contact information at www.suse.com. + */ + +import React from "react"; +// import React, { useEffect, useState } from "react"; +// import { useInstallerClient } from "~/context/installer"; + +function PatternItem({ name, description, summary, /* icon, */ isSelected }) { + // const [selected, setSelected] = useState(isSelected); + // const client = useInstallerClient(); + const onCheckboxChange = (event) => { + console.log("onCheckboxChange", event.currentTarget.checked); + + // if (event.currentTarget.checked) { + // } else { + // } + }; + + return ( + + ); +} + +export default PatternItem; diff --git a/web/src/components/software/PatternSelector.jsx b/web/src/components/software/PatternSelector.jsx index 484b282b44..d049afea37 100644 --- a/web/src/components/software/PatternSelector.jsx +++ b/web/src/components/software/PatternSelector.jsx @@ -22,25 +22,41 @@ import React, { useEffect, useState } from "react"; import { useInstallerClient } from "~/context/installer"; import PatternGroup from "./PatternGroup"; +import PatternItem from "./PatternItem"; + +function convert(pattern_data) { + const patterns = []; + + Object.keys(pattern_data).forEach((name) => { + const pattern = pattern_data[name]; + patterns.push({ + name, + group: pattern[0], + description: pattern[1], + icon: pattern[2], + summary: pattern[3], + order: pattern[4] + }); + }); + + return patterns; +} function groupPatterns(patterns) { // group patterns const pattern_groups = {}; - Object.keys(patterns).forEach((pattern) => { - const pattern_data = patterns[pattern]; - pattern_data.push(pattern); - - if (pattern_groups[pattern_data[0]]) { - pattern_groups[pattern_data[0]].push(pattern_data); + patterns.forEach((pattern) => { + if (pattern_groups[pattern.group]) { + pattern_groups[pattern.group].push(pattern); } else { - pattern_groups[pattern_data[0]] = [pattern_data]; + pattern_groups[pattern.group] = [pattern]; } }); // sort patterns by the "order" value Object.keys(pattern_groups).forEach((group) => { - pattern_groups[group].sort((p1, p2) => (p1[4] < p2[4] ? -1 : 1)); + pattern_groups[group].sort((p1, p2) => (p1.order < p2.order ? -1 : 1)); }); console.log(pattern_groups); @@ -49,7 +65,7 @@ function groupPatterns(patterns) { } function patternList(patterns) { - const groups = groupPatterns(patterns); + const groups = groupPatterns(convert(patterns)); const selector = Object.keys(groups).map((group) => { return ( @@ -59,7 +75,7 @@ function patternList(patterns) { selected={0} count={groups[group].length} > - { (groups[group]).map(pattern =>

{pattern[3]} - {pattern[1]}

) } + { (groups[group]).map(p => ) } ); }); diff --git a/web/src/components/software/SoftwareSelectionPage.jsx b/web/src/components/software/SoftwareSelectionPage.jsx index 36a2483961..e9aaa4f046 100644 --- a/web/src/components/software/SoftwareSelectionPage.jsx +++ b/web/src/components/software/SoftwareSelectionPage.jsx @@ -20,7 +20,7 @@ */ import React from "react"; -import { Page, Section } from "~/components/core"; +import { Page } from "~/components/core"; import { PatternSelector } from "~/components/software"; import { _ } from "~/i18n"; @@ -28,9 +28,8 @@ function SoftwareSelectionPage() { return ( // TRANSLATORS: page title -
- -
+

{_("Available Software")}

+
); } From 300a77b532fbc19cb7ec8f00f44c182936edd3ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20Slez=C3=A1k?= Date: Fri, 29 Sep 2023 09:42:48 +0200 Subject: [PATCH 04/27] Update state --- web/src/client/software.js | 32 +++++++++++++++ web/src/components/software/PatternItem.jsx | 39 +++++++++++-------- .../components/software/PatternSelector.jsx | 24 +++++++----- 3 files changed, 70 insertions(+), 25 deletions(-) diff --git a/web/src/client/software.js b/web/src/client/software.js index e148f0ea5e..4f0f580314 100644 --- a/web/src/client/software.js +++ b/web/src/client/software.js @@ -92,6 +92,38 @@ class SoftwareBaseClient { return proxy.ListPatterns(filter); } + /** + * Returns selected patterns + * + * @return {Promise<>} + */ + async selectedPatterns() { + const proxy = await this.client.proxy(SOFTWARE_IFACE); + return proxy.SelectedPatterns; + } + + /** + * Select a pattern to install + * + * @param {string} name - name of the pattern + * @return {Promise<>} + */ + async addPattern(name) { + const proxy = await this.client.proxy(SOFTWARE_IFACE); + return proxy.AddPattern(name); + } + + /** + * Deselect a pattern to install + * + * @param {string} name - name of the pattern + * @return {Promise<>} + */ + async removePattern(name) { + const proxy = await this.client.proxy(SOFTWARE_IFACE); + return proxy.RemovePattern(name); + } + /** * Returns the selected product * diff --git a/web/src/components/software/PatternItem.jsx b/web/src/components/software/PatternItem.jsx index 6dcbe1f664..d28bfe7140 100644 --- a/web/src/components/software/PatternItem.jsx +++ b/web/src/components/software/PatternItem.jsx @@ -19,31 +19,38 @@ * find current contact information at www.suse.com. */ -import React from "react"; -// import React, { useEffect, useState } from "react"; -// import { useInstallerClient } from "~/context/installer"; +import React, { useState } from "react"; +import { useInstallerClient } from "~/context/installer"; + +function PatternItem({ pattern }) { + const [selected, setSelected] = useState(pattern.selected !== undefined); + const client = useInstallerClient(); -function PatternItem({ name, description, summary, /* icon, */ isSelected }) { - // const [selected, setSelected] = useState(isSelected); - // const client = useInstallerClient(); const onCheckboxChange = (event) => { - console.log("onCheckboxChange", event.currentTarget.checked); + const target = event.currentTarget; + + if (target.checked) { + console.log("Selecting pattern ", pattern.name); + client.software.addPattern(pattern.name); + } else { + console.log("Removing pattern ", pattern.name); + client.software.removePattern(pattern.name); + } - // if (event.currentTarget.checked) { - // } else { - // } + setSelected(target.checked); }; return ( -