Skip to content

Commit

Permalink
Update add software UI to move software package modal into new tabbed…
Browse files Browse the repository at this point in the history
… layout (#22553)

Co-authored-by: Gabriel Hernandez <ghernandez345@gmail.com>
  • Loading branch information
gillespi314 and ghernandez345 authored Oct 2, 2024
1 parent 83c44b9 commit 3727474
Show file tree
Hide file tree
Showing 27 changed files with 509 additions and 240 deletions.
1 change: 1 addition & 0 deletions changes/20308-file-uploader
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Update UI for software uploads to include upload progress bar.
29 changes: 23 additions & 6 deletions frontend/components/FileDetails/FileDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import React from "react";

import { IFileDetails } from "utilities/file/fileUtils";

import Button from "components/buttons/Button";
import { ISupportedGraphicNames } from "components/FileUploader/FileUploader";
import Graphic from "components/Graphic";
import Button from "components/buttons/Button";
import Icon from "components/Icon";

interface IFileDetailsProps {
graphicNames: ISupportedGraphicNames | ISupportedGraphicNames[];
fileDetails: {
name: string;
platform?: string;
};
fileDetails: IFileDetails;
canEdit: boolean;
onFileSelect: (e: React.ChangeEvent<HTMLInputElement>) => void;
accept?: string;
progress?: number;
}

const baseClass = "file-details";
Expand All @@ -24,6 +24,7 @@ const FileDetails = ({
canEdit,
onFileSelect,
accept,
progress,
}: IFileDetailsProps) => {
return (
<div className={baseClass}>
Expand All @@ -42,7 +43,7 @@ const FileDetails = ({
)}
</div>
</div>
{canEdit && (
{!progress && canEdit && (
<div className={`${baseClass}__edit`}>
<Button className={`${baseClass}__edit-button`} variant="icon">
<label htmlFor="edit-file">
Expand All @@ -57,6 +58,22 @@ const FileDetails = ({
/>
</div>
)}
{!!progress && (
<div className={`${baseClass}__progress-wrapper`}>
<div className={`${baseClass}__progress-bar`}>
<div
className={`${baseClass}__progress-bar--uploaded`}
style={{
width: `${progress * 100}%`,
}}
title="upload progress bar"
/>
</div>
<div className={`${baseClass}__progress-text`}>
{Math.round(progress * 100)}%
</div>
</div>
)}
</div>
);
};
Expand Down
28 changes: 28 additions & 0 deletions frontend/components/FileDetails/_styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
display: flex;
justify-content: space-between;
width: 100%;
gap: $pad-medium;

&__info {
display: flex;
Expand Down Expand Up @@ -34,4 +35,31 @@
cursor: pointer;
}
}

&__progress-wrapper {
display: flex;
width: 184px; // using fixed width to prevent jitter when text changes: 144px + $pad-medium + 24px for text
justify-content: space-between;
align-items: center;
gap: $pad-small; // shouldn't be necessary, but just in case
}

&__progress-bar {
display: inline-block;
height: 6px;
width: 144px;
background-color: $ui-fleet-black-10;
border-radius: 32px;
overflow: hidden;
}

&__progress-bar--uploaded {
background-color: $core-vibrant-blue;
height: 100%;
}

&__progress-text {
font-size: $x-small;
color: $ui-fleet-black-50;
}
}
41 changes: 41 additions & 0 deletions frontend/components/FileProgressModal/FileProgressModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import Card from "components/Card";
import FileDetails from "components/FileDetails";
import Modal from "components/Modal";
import { noop } from "lodash";
import React from "react";

import { ISupportedGraphicNames } from "components/FileUploader/FileUploader";

import { IFileDetails } from "utilities/file/fileUtils";

const baseClass = "file-progress-modal";

const FileProgressModal = ({
graphicNames = "file-pkg",
fileDetails,
fileProgress,
}: {
graphicNames?: ISupportedGraphicNames | ISupportedGraphicNames[];
fileDetails: IFileDetails;
fileProgress: number;
}) => (
<Modal
className={baseClass}
title="Add software"
width="large"
onExit={noop}
disableClosingModal
>
<Card color="gray" className={`${baseClass}__card`}>
<FileDetails
graphicNames={graphicNames}
fileDetails={fileDetails}
progress={fileProgress}
canEdit={false}
onFileSelect={noop}
/>
</Card>
</Modal>
);

export default FileProgressModal;
7 changes: 7 additions & 0 deletions frontend/components/FileProgressModal/_styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.file-progress-modal {
// TODO: should we update the card component with this padding option?
&__card {
padding-top: $pad-medium;
padding-bottom: $pad-medium;
}
}
1 change: 1 addition & 0 deletions frontend/components/FileProgressModal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./FileProgressModal";
94 changes: 61 additions & 33 deletions frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAddPage.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import React, { useCallback } from "react";
import React, { useCallback, useContext } from "react";
import { Tab, TabList, Tabs } from "react-tabs";
import { InjectedRouter } from "react-router";
import { Location } from "history";

import PATHS from "router/paths";
import { buildQueryStringFromParams } from "utilities/url";
import { QueryContext } from "context/query";
import useToggleSidePanel from "hooks/useToggleSidePanel";
import { APP_CONTEXT_NO_TEAM_ID } from "interfaces/team";

import MainContent from "components/MainContent";
import BackLink from "components/BackLink";
import TabsWrapper from "components/TabsWrapper";
import { APP_CONTEXT_NO_TEAM_ID } from "interfaces/team";
import SidePanelContent from "components/SidePanelContent";
import QuerySidePanel from "components/side_panels/QuerySidePanel";

const baseClass = "software-add-page";

Expand Down Expand Up @@ -59,8 +63,14 @@ const SoftwareAddPage = ({
location,
router,
}: ISoftwareAddPageProps) => {
const { selectedOsqueryTable, setSelectedOsqueryTable } = useContext(
QueryContext
);
const { isSidePanelOpen, setSidePanelOpen } = useToggleSidePanel(false);

const navigateToNav = useCallback(
(i: number): void => {
setSidePanelOpen(false);
// Only query param to persist between tabs is team id
const teamIdParam = buildQueryStringFromParams({
team_id: location.query.team_id,
Expand All @@ -69,7 +79,7 @@ const SoftwareAddPage = ({
const navPath = addSoftwareSubNav[i].pathname.concat(`?${teamIdParam}`);
router.replace(navPath);
},
[location, router]
[location.query.team_id, router, setSidePanelOpen]
);

// Quick exit if no team_id param. This page must have a team id to function
Expand All @@ -84,41 +94,59 @@ const SoftwareAddPage = ({
return null;
}

const onOsqueryTableSelect = (tableName: string) => {
setSelectedOsqueryTable(tableName);
};

const backUrl = `${PATHS.SOFTWARE_TITLES}?${buildQueryStringFromParams({
team_id: location.query.team_id,
})}`;

return (
<MainContent className={baseClass}>
<>
<BackLink
text="Back to software"
path={backUrl}
className={`${baseClass}__back-to-software`}
/>
<h1>Add Software</h1>
<TabsWrapper>
<Tabs
selectedIndex={getTabIndex(location?.pathname || "")}
onSelect={navigateToNav}
>
<TabList>
{addSoftwareSubNav.map((navItem) => {
return (
<Tab key={navItem.name} data-text={navItem.name}>
{navItem.name}
</Tab>
);
})}
</TabList>
</Tabs>
</TabsWrapper>
{React.cloneElement(children, {
router,
currentTeamId: parseInt(location.query.team_id, 10),
})}
</>
</MainContent>
<>
<MainContent className={baseClass}>
<>
<BackLink
text="Back to software"
path={backUrl}
className={`${baseClass}__back-to-software`}
/>
<h1>Add Software</h1>
<TabsWrapper>
<Tabs
selectedIndex={getTabIndex(location?.pathname || "")}
onSelect={navigateToNav}
>
<TabList>
{addSoftwareSubNav.map((navItem) => {
return (
<Tab key={navItem.name} data-text={navItem.name}>
{navItem.name}
</Tab>
);
})}
</TabList>
</Tabs>
</TabsWrapper>
{React.cloneElement(children, {
router,
currentTeamId: parseInt(location.query.team_id, 10),
isSidePanelOpen,
setSidePanelOpen,
})}
</>
</MainContent>
{isSidePanelOpen && (
<SidePanelContent>
<QuerySidePanel
key="query-side-panel"
onOsqueryTableSelect={onOsqueryTableSelect}
selectedOsqueryTable={selectedOsqueryTable}
onClose={() => setSidePanelOpen(false)}
/>
</SidePanelContent>
)}
</>
);
};

Expand Down
Loading

0 comments on commit 3727474

Please sign in to comment.