Line data Source code
1 : /*
2 : * Copyright (C) 2017 Red Hat, Inc.
3 : * SPDX-License-Identifier: LGPL-2.1-or-later
4 : */
5 :
6 7 : import cockpit from "cockpit";
7 7 : import React from "react";
8 : import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
9 : import { Breadcrumb, BreadcrumbItem } from "@patternfly/react-core/dist/esm/components/Breadcrumb/index.js";
10 : import { Card, CardBody, CardHeader, CardTitle } from '@patternfly/react-core/dist/esm/components/Card/index.js';
11 : import { Flex } from "@patternfly/react-core/dist/esm/layouts/Flex/index.js";
12 : import { Page, PageBreadcrumb, PageSection } from "@patternfly/react-core/dist/esm/components/Page/index.js";
13 : import { Stack } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js";
14 : import { ExternalLinkAltIcon } from '@patternfly/react-icons';
15 :
16 : import { icon_url, launch, ProgressBar, CancelButton, reload_bridge_packages, ProgressReporter } from "./utils";
17 :
18 : import "./application.scss";
19 : import { getPackageManager } from "packagemanager.js";
20 :
21 7 : const _ = cockpit.gettext;
22 :
23 2 : async function install_package(pkgname, progress_cb) {
24 2 : const packagemanager = await getPackageManager();
25 2 : await packagemanager.install_packages([pkgname], progress_cb);
26 2 : await reload_bridge_packages();
27 2 : }
28 :
29 2 : async function remove_package(filename, progress_cb) {
30 2 : const progress = new ProgressReporter(0, 1, progress_cb);
31 2 : const packagemanager = await getPackageManager();
32 2 : const pkgnames = await packagemanager.find_file_packages([filename], progress.progress_reporter);
33 2 : progress.base = 1;
34 2 : progress.range = 99;
35 2 : await packagemanager.remove_packages(pkgnames, progress.progress_reporter);
36 2 : await reload_bridge_packages();
37 2 : }
38 :
39 7 : export const ActionButton = ({ comp, progress, action }) => {
40 2 : function install(comp) {
41 2 : action(install_package, comp.pkgname, _("Installing"), comp.id);
42 2 : }
43 :
44 2 : function remove(comp) {
45 2 : action(remove_package, comp.file, _("Removing"), comp.id);
46 2 : }
47 :
48 3 : if (progress) {
49 3 : return <CancelButton data={progress} />;
50 3 : } else if (comp.installed) {
51 2 : return <Button variant="danger" onClick={() => remove(comp)}>{_("Remove")}</Button>;
52 5 : } else {
53 2 : return <Button variant="secondary" onClick={() => install(comp)}>{_("Install")}</Button>;
54 7 : }
55 7 : };
56 :
57 3 : export const Application = ({ metainfo_db, id, progress, action }) => {
58 3 : if (!id)
59 1 : return null;
60 :
61 3 : const comp = metainfo_db.components[id];
62 :
63 3 : function render_homepage_link(urls) {
64 2 : return urls.map((url, index) => {
65 2 : if (url.type == 'homepage') {
66 2 : return (
67 2 : <Button isInline variant="link" component='a' href={url.link}
68 2 : key={"project-url-" + index}
69 2 : target="_blank" rel="noopener noreferrer"
70 2 : icon={<ExternalLinkAltIcon />}
71 2 : iconPosition="right">
72 2 : {_("View project website")}
73 2 : </Button>
74 : );
75 0 : } else {
76 0 : return null;
77 0 : }
78 2 : });
79 3 : }
80 :
81 : // Render a description in the form returned by the AppStream
82 : // parser, which is a list of paragraphs and lists.
83 :
84 3 : function render_description(description) {
85 3 : if (!description)
86 1 : return <p>{_("No description provided.")}</p>;
87 :
88 3 : return description.map((paragraph, index) => {
89 1 : if (paragraph.tag == 'ul') {
90 0 : return <ul key={`paragraph-${index}`}>{paragraph.items.map(item => <li key={item}>{item}</li>)}</ul>;
91 1 : } else if (paragraph.tag == 'ol') {
92 0 : return <ol key={`paragraph-${index}`}>{paragraph.items.map(item => <li key={item}>{item}</li>)}</ol>;
93 1 : } else {
94 3 : return <p key={`paragraph-${index}`}>{paragraph}</p>;
95 3 : }
96 3 : });
97 3 : }
98 :
99 : // Render the icon, name, homepage link, summary, description, and screenshots of the component,
100 : // plus the UI for installing and removing it.
101 :
102 3 : function render_comp() {
103 3 : if (!comp)
104 1 : return <div>{_("Unknown application")}</div>;
105 :
106 3 : let progress_or_launch;
107 1 : if (progress) {
108 1 : progress_or_launch = <ProgressBar data={progress} />;
109 1 : } else if (comp.installed) {
110 0 : progress_or_launch = <Button variant="link" onClick={() => launch(comp)}>{_("Go to application")}</Button>;
111 1 : } else {
112 3 : progress_or_launch = null;
113 3 : }
114 :
115 3 : return (
116 3 : <Card isPlain>
117 3 : <CardHeader actions={{
118 3 : actions: <>{progress_or_launch}<ActionButton comp={comp} progress={progress} action={action} /></>,
119 3 : }}>
120 3 : <CardTitle>
121 3 : <Flex alignItems={{ default: 'alignItemsCenter' }}>
122 3 : <img src={icon_url(comp.icon)} alt="" />
123 3 : <span>{comp.summary}</span>
124 3 : </Flex>
125 3 : </CardTitle>
126 3 : </CardHeader>
127 3 : <CardBody>
128 3 : <Stack hasGutter>
129 3 : {render_homepage_link(comp.urls)}
130 3 : <div className="app-description">{render_description(comp.description)}</div>
131 3 : {comp.screenshots.length
132 1 : ? <div className="text-center">
133 0 : { comp.screenshots.map((s, index) => <img key={`comp-${index}`} className="app-screenshot" alt="" src={s.full} />) }
134 1 : </div>
135 3 : : null}
136 3 : </Stack>
137 3 : </CardBody>
138 3 : </Card>
139 : );
140 3 : }
141 :
142 3 : return (
143 3 : <Page id="app-page"
144 3 : className="application-details pf-m-no-sidebar">
145 3 : <PageBreadcrumb hasBodyWrapper={false} stickyOnBreakpoint={{ default: "top" }}>
146 3 : <Breadcrumb>
147 3 : <BreadcrumbItem to="#/">{_("Applications")}</BreadcrumbItem>
148 1 : <BreadcrumbItem isActive>{comp ? comp.name : id}</BreadcrumbItem>
149 3 : </Breadcrumb>
150 3 : </PageBreadcrumb>
151 3 : <PageSection hasBodyWrapper={false}>
152 3 : {render_comp()}
153 3 : </PageSection>
154 3 : </Page>
155 : );
156 3 : };
|