Fleet UI: Update previews to look the same (#38241)
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 77 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 81 KiB |
@@ -158,6 +158,8 @@ interface ISoftwareNameCellProps {
|
||||
iconUrl?: string | null;
|
||||
isIosOrIpadosApp?: boolean;
|
||||
isAndroidPlayStoreApp?: boolean;
|
||||
/** Only used on Edit icon modal to render a preview of the chosen unsaved icon */
|
||||
previewIcon?: JSX.Element;
|
||||
}
|
||||
|
||||
const SoftwareNameCell = ({
|
||||
@@ -173,8 +175,11 @@ const SoftwareNameCell = ({
|
||||
iconUrl,
|
||||
isIosOrIpadosApp = false,
|
||||
isAndroidPlayStoreApp = false,
|
||||
previewIcon,
|
||||
}: ISoftwareNameCellProps) => {
|
||||
const icon = <SoftwareIcon name={name} source={source} url={iconUrl} />;
|
||||
const icon = previewIcon || (
|
||||
<SoftwareIcon name={name} source={source} url={iconUrl} />
|
||||
);
|
||||
// My device page > Software fake link as entire row opens a modal
|
||||
if (pageContext === "deviceUser" && !isSelfService) {
|
||||
return (
|
||||
|
||||
+67
-99
@@ -1,6 +1,8 @@
|
||||
import React, { useContext, useEffect, useState, useCallback } from "react";
|
||||
import { useQuery } from "react-query";
|
||||
import { Tab, Tabs, TabList, TabPanel } from "react-tabs";
|
||||
import { noop } from "lodash";
|
||||
|
||||
import {
|
||||
IAppStoreApp,
|
||||
isIpadOrIphoneSoftwareSource,
|
||||
@@ -10,6 +12,7 @@ import {
|
||||
import { IInputFieldParseTarget } from "interfaces/form_field";
|
||||
|
||||
import { NotificationContext } from "context/notification";
|
||||
import { AppContext } from "context/app";
|
||||
import { INotification } from "interfaces/notification";
|
||||
import { getErrorReason } from "interfaces/errors";
|
||||
import softwareAPI from "services/entities/software";
|
||||
@@ -25,17 +28,14 @@ import Card from "components/Card";
|
||||
import Button from "components/buttons/Button";
|
||||
import SoftwareIcon from "pages/SoftwarePage/components/icons/SoftwareIcon";
|
||||
import TableCount from "components/TableContainer/TableCount";
|
||||
import CardHeader from "components/CardHeader";
|
||||
import TooltipTruncatedText from "components/TooltipTruncatedText";
|
||||
import Spinner from "components/Spinner";
|
||||
|
||||
import { isSafeImagePreviewUrl } from "pages/SoftwarePage/helpers";
|
||||
import SoftwareDetailsSummary from "pages/SoftwarePage/components/cards/SoftwareDetailsSummary/SoftwareDetailsSummary";
|
||||
import { SELF_SERVICE_SUBHEADER } from "pages/hosts/details/cards/Software/SelfService/SelfService";
|
||||
import { BasicSoftwareTable } from "pages/SoftwarePage/components/modals/CategoriesEndUserExperienceModal/CategoriesEndUserExperienceModal";
|
||||
import SelfServicePreview from "pages/SoftwarePage/components/cards/SelfServicePreview";
|
||||
|
||||
import { TitleVersionsLastUpdatedInfo } from "../SoftwareSummaryCard/TitleVersionsTable/TitleVersionsTable";
|
||||
import PreviewSelfServiceIcon from "../../../../../assets/images/preview-self-service-icon.png";
|
||||
import PreviewSelfServiceMobileIcon from "../../../../../assets/images/preview-self-service-mobile-icon.png";
|
||||
|
||||
const baseClass = "edit-icon-modal";
|
||||
|
||||
@@ -152,6 +152,7 @@ const EditIconModal = ({
|
||||
previewInfo,
|
||||
}: IEditIconModalProps) => {
|
||||
const { renderFlash, renderMultiFlash } = useContext(NotificationContext);
|
||||
const { config } = useContext(AppContext);
|
||||
|
||||
const isSoftwarePackage = installerType === "package";
|
||||
const isIosOrIpadosApp = isIpadOrIphoneSoftwareSource(
|
||||
@@ -474,111 +475,82 @@ const EditIconModal = ({
|
||||
);
|
||||
};
|
||||
|
||||
const renderPreviewSelfServiceCard = () => (
|
||||
<Card
|
||||
borderRadiusSize="medium"
|
||||
color="grey"
|
||||
className={`${baseClass}__preview-card`}
|
||||
paddingSize="xlarge"
|
||||
>
|
||||
<Card
|
||||
className={`${baseClass}__preview-card__self-service`}
|
||||
borderRadiusSize="xxlarge"
|
||||
>
|
||||
<CardHeader header="Self-service" subheader={SELF_SERVICE_SUBHEADER} />
|
||||
<div className={`${baseClass}__preview-img-container`}>
|
||||
<img
|
||||
className={`${baseClass}__preview-img`}
|
||||
src={PreviewSelfServiceIcon}
|
||||
alt="Preview icon on Fleet Desktop > Self-service"
|
||||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__self-service-preview`}>
|
||||
{iconState.previewUrl &&
|
||||
isSafeImagePreviewUrl(iconState.previewUrl) ? (
|
||||
<img
|
||||
src={iconState.previewUrl}
|
||||
alt="Uploaded self-service icon"
|
||||
style={{
|
||||
width: 20,
|
||||
height: 20,
|
||||
borderRadius: "4px",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
// Known limitation: we cannot see VPP app icons as the fallback when a custom icon
|
||||
// is set as VPP icon is not returned by the API if a custom icon is returned
|
||||
<SoftwareIcon
|
||||
name={previewInfo.titleName}
|
||||
source={previewInfo.source}
|
||||
url={isSoftwarePackage ? undefined : software.icon_url} // fallback PNG icons only exist for VPP apps
|
||||
uploadedAt={iconUploadedAt}
|
||||
/>
|
||||
)}
|
||||
<div className={`${baseClass}__self-service-preview-name`}>
|
||||
<TooltipTruncatedText
|
||||
value={displayName || previewInfo.titleName}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<div
|
||||
className={`${baseClass}__mask-overlay ${baseClass}__mask-overlay--self-service`}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
/**
|
||||
* Preview matches preview in Edit Software modal > Categories End User Experience modal
|
||||
* Non-mobile preview:
|
||||
* - uses HTML/CSS instead for maintainability as the self-service UI changes
|
||||
* - dynamic name/icon
|
||||
*
|
||||
* Mobile preview modal:
|
||||
* - uses a screenshot
|
||||
* - dynamic name/icon/version
|
||||
*/
|
||||
|
||||
const renderPreviewSelfServiceMobileCard = () => (
|
||||
<Card
|
||||
borderRadiusSize="medium"
|
||||
color="white"
|
||||
className={`${baseClass}__preview-card`}
|
||||
paddingSize="xlarge"
|
||||
>
|
||||
<div className={`${baseClass}__preview-img-container--mobile`}>
|
||||
<img
|
||||
className={`${baseClass}__preview-img--mobile`}
|
||||
src={PreviewSelfServiceMobileIcon}
|
||||
alt="Preview icon on Fleet Desktop > Self-service"
|
||||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__self-service-preview--mobile`}>
|
||||
{iconState.previewUrl && isSafeImagePreviewUrl(iconState.previewUrl) ? (
|
||||
const renderPreviewSelfServiceCard = () => (
|
||||
<SelfServicePreview
|
||||
isIosOrIpadosApp={isIosOrIpadosApp}
|
||||
contactUrl={config?.org_info.contact_url || ""}
|
||||
name={previewInfo.name}
|
||||
displayName={displayName || previewInfo.titleName}
|
||||
versionLabel={
|
||||
"latest_version" in software
|
||||
? software.latest_version
|
||||
: software.version ||
|
||||
previewInfo.selfServiceVersion ||
|
||||
"Version (unknown)"
|
||||
}
|
||||
renderIcon={() =>
|
||||
iconState.previewUrl && isSafeImagePreviewUrl(iconState.previewUrl) ? (
|
||||
<img
|
||||
src={iconState.previewUrl}
|
||||
alt="Uploaded self-service icon"
|
||||
style={{
|
||||
width: 20,
|
||||
height: 20,
|
||||
width: 24,
|
||||
height: 24,
|
||||
borderRadius: "4px",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
// Known limitation: we cannot see VPP app icons as the fallback when a custom icon
|
||||
// is set as VPP icon is not returned by the API if a custom icon is returned
|
||||
<SoftwareIcon
|
||||
name={previewInfo.name}
|
||||
source={previewInfo.source}
|
||||
url={isSoftwarePackage ? undefined : software.icon_url} // fallback PNG icons only exist for VPP apps
|
||||
uploadedAt={iconUploadedAt}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={`${baseClass}__self-service-preview-name-version--mobile`}
|
||||
>
|
||||
<div className={`${baseClass}__self-service-preview-name--mobile`}>
|
||||
<TooltipTruncatedText value={displayName || previewInfo.name} />
|
||||
</div>
|
||||
<div className={`${baseClass}__self-service-preview-version--mobile`}>
|
||||
{"latest_version" in software
|
||||
? software.latest_version
|
||||
: software.version || "Version (unknown)"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
renderTable={() => (
|
||||
<BasicSoftwareTable
|
||||
name={displayName || previewInfo.titleName}
|
||||
displayName={displayName || previewInfo.titleName}
|
||||
source={previewInfo.source}
|
||||
iconUrl={isSoftwarePackage ? undefined : software.icon_url}
|
||||
previewIcon={
|
||||
iconState.previewUrl &&
|
||||
isSafeImagePreviewUrl(iconState.previewUrl) ? (
|
||||
<img
|
||||
src={iconState.previewUrl}
|
||||
alt="Uploaded self-service icon"
|
||||
style={{
|
||||
width: 24,
|
||||
height: 24,
|
||||
borderRadius: "4px",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<SoftwareIcon
|
||||
name={previewInfo.titleName}
|
||||
source={previewInfo.source}
|
||||
url={isSoftwarePackage ? undefined : software.icon_url}
|
||||
uploadedAt={iconUploadedAt}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
const renderForm = () => (
|
||||
@@ -623,11 +595,7 @@ const EditIconModal = ({
|
||||
</Tab>
|
||||
</TabList>
|
||||
<TabPanel>{renderPreviewFleetCard()}</TabPanel>
|
||||
<TabPanel>
|
||||
{isIosOrIpadosApp
|
||||
? renderPreviewSelfServiceMobileCard()
|
||||
: renderPreviewSelfServiceCard()}
|
||||
</TabPanel>
|
||||
<TabPanel>{renderPreviewSelfServiceCard()}</TabPanel>
|
||||
</Tabs>
|
||||
</TabNav>
|
||||
</>
|
||||
|
||||
@@ -7,8 +7,12 @@
|
||||
@include vertical-modal-layout;
|
||||
}
|
||||
|
||||
.self-service-preview__preview-card {
|
||||
height: 230px; // Match height of Fleet UI preview
|
||||
}
|
||||
|
||||
&__preview-card {
|
||||
max-height: 203px;
|
||||
height: 230px; // Match height of self-service preview
|
||||
overflow: hidden;
|
||||
|
||||
&__fleet,
|
||||
@@ -28,22 +32,6 @@
|
||||
border-radius: $border-radius-xxlarge;
|
||||
}
|
||||
}
|
||||
|
||||
&__self-service {
|
||||
height: 350px;
|
||||
}
|
||||
}
|
||||
|
||||
&__preview-img-container {
|
||||
img {
|
||||
width: 124%;
|
||||
}
|
||||
}
|
||||
|
||||
&__preview-img-container--mobile {
|
||||
img {
|
||||
width: 100%; // TODO
|
||||
}
|
||||
}
|
||||
|
||||
&__preview-results-count {
|
||||
@@ -78,22 +66,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Specific offsets for mobile in edit modal
|
||||
&__self-service-preview--mobile {
|
||||
position: relative;
|
||||
top: -88px;
|
||||
left: 156px;
|
||||
display: flex;
|
||||
gap: $pad-small;
|
||||
font-size: 12px;
|
||||
align-items: center;
|
||||
max-width: 210px;
|
||||
|
||||
.software-icon__xsmall {
|
||||
border-radius: $border-radius-small;
|
||||
}
|
||||
}
|
||||
|
||||
// Required for tooltip truncating long software names
|
||||
// Required for tooltip truncating long software names (desktop)
|
||||
&__self-service-preview-name {
|
||||
max-width: 180px;
|
||||
white-space: nowrap;
|
||||
@@ -101,22 +81,6 @@
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&__self-service-preview-name-version--mobile {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: $xxx-small;
|
||||
}
|
||||
|
||||
// Required for tooltip truncating long software names
|
||||
&__self-service-preview-name--mobile {
|
||||
max-width: 115px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
position: relative;
|
||||
font-weight: $bold;
|
||||
}
|
||||
|
||||
&__mask-overlay {
|
||||
position: relative;
|
||||
width: 550px;
|
||||
@@ -129,9 +93,10 @@
|
||||
);
|
||||
|
||||
&--fleet {
|
||||
bottom: 156px;
|
||||
bottom: 130px;
|
||||
height: 135px;
|
||||
}
|
||||
|
||||
&--self-service {
|
||||
bottom: 200px;
|
||||
height: 75px;
|
||||
|
||||
+8
-1
@@ -58,6 +58,7 @@ interface IEditSoftwareModalProps {
|
||||
name: string;
|
||||
displayName: string;
|
||||
source?: string;
|
||||
iconUrl?: string | null;
|
||||
}
|
||||
|
||||
const EditSoftwareModal = ({
|
||||
@@ -73,6 +74,7 @@ const EditSoftwareModal = ({
|
||||
name,
|
||||
displayName,
|
||||
source,
|
||||
iconUrl = undefined,
|
||||
}: IEditSoftwareModalProps) => {
|
||||
const { renderFlash } = useContext(NotificationContext);
|
||||
const { config } = useContext(AppContext);
|
||||
@@ -404,9 +406,14 @@ const EditSoftwareModal = ({
|
||||
name={name}
|
||||
displayName={displayName}
|
||||
source={source}
|
||||
iconUrl={softwareInstaller.icon_url || undefined}
|
||||
iconUrl={iconUrl} // Must be software title icon url not installer icon url
|
||||
onCancel={togglePreviewEndUserExperienceModal}
|
||||
isIosOrIpadosApp={isIosOrIpadosApp}
|
||||
mobileVersion={
|
||||
("latest_version" in softwareInstaller &&
|
||||
softwareInstaller.latest_version) ||
|
||||
softwareInstaller.version
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{!!pendingPackageUpdates.software && showFileProgressModal && (
|
||||
|
||||
+1
@@ -202,6 +202,7 @@ const SoftwareSummaryCard = ({
|
||||
name={softwareTitle.name}
|
||||
displayName={softwareTitle.display_name || softwareTitle.name}
|
||||
source={softwareTitle.source}
|
||||
iconUrl={softwareTitle.icon_url}
|
||||
/>
|
||||
)}
|
||||
{showEditConfigurationModal && hasValidTeamId && softwareInstaller && (
|
||||
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
import React from "react";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
|
||||
import SelfServicePreview from "./SelfServicePreview";
|
||||
|
||||
describe("SelfServicePreview", () => {
|
||||
it("renders mobile preview with screenshot, name, version, and icon", () => {
|
||||
const MockIcon = () => <div>Mock icon</div>;
|
||||
|
||||
render(
|
||||
<SelfServicePreview
|
||||
isIosOrIpadosApp
|
||||
contactUrl="https://example.com/help"
|
||||
name="App name"
|
||||
displayName="Display name"
|
||||
versionLabel="1.2.3"
|
||||
renderIcon={() => <MockIcon />}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByAltText("Preview icon on Fleet Desktop > Self-service")
|
||||
).toBeVisible();
|
||||
|
||||
expect(screen.getByText("Mock icon")).toBeVisible();
|
||||
expect(screen.getByText("Display name")).toBeVisible();
|
||||
expect(screen.getByText("1.2.3")).toBeVisible();
|
||||
});
|
||||
|
||||
it("falls back to name when displayName is empty in mobile preview", () => {
|
||||
render(
|
||||
<SelfServicePreview
|
||||
isIosOrIpadosApp
|
||||
contactUrl="https://example.com/help"
|
||||
name="Fallback name"
|
||||
displayName=""
|
||||
versionLabel="1.2.3"
|
||||
renderIcon={() => <div>Icon</div>}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText("Fallback name")).toBeVisible();
|
||||
});
|
||||
|
||||
it("renders desktop preview with header, search field, categories menu, and table", () => {
|
||||
const MockTable = () => <div>Mock table</div>;
|
||||
|
||||
render(
|
||||
<SelfServicePreview
|
||||
isIosOrIpadosApp={false}
|
||||
contactUrl="https://example.com/help"
|
||||
name="App name"
|
||||
displayName="Display name"
|
||||
versionLabel="1.2.3"
|
||||
renderIcon={() => <div>Icon</div>}
|
||||
renderTable={() => <MockTable />}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText(/Self-service/i)).toBeInTheDocument();
|
||||
expect(screen.getByPlaceholderText("Search by name")).toBeInTheDocument();
|
||||
expect(screen.getByText(/Browsers/i)).toBeInTheDocument();
|
||||
expect(screen.getByText("Mock table")).toBeVisible();
|
||||
});
|
||||
});
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Used on Software title > CategoriesEndUserExperienceModal and Software title > Edit Appearance Modal
|
||||
*
|
||||
* Non-mobile preview modal:
|
||||
* - uses HTML/CSS instead for maintainability as the self-service UI changes
|
||||
* - dynamic name/icon
|
||||
*
|
||||
* Mobile preview modal:
|
||||
* - uses a screenshot
|
||||
* - dynamic name/icon/version
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { noop } from "lodash";
|
||||
import Card from "components/Card";
|
||||
import SearchField from "components/forms/fields/SearchField";
|
||||
import TooltipTruncatedText from "components/TooltipTruncatedText";
|
||||
import CategoriesMenu from "pages/hosts/details/cards/Software/SelfService/components/CategoriesMenu";
|
||||
import SelfServiceHeader from "pages/hosts/details/cards/Software/SelfService/components/SelfServiceHeader";
|
||||
import { CATEGORIES_NAV_ITEMS } from "pages/hosts/details/cards/Software/SelfService/helpers";
|
||||
import PreviewSelfServiceMobileIcon from "../../../../../../assets/images/preview-self-service-mobile-icon.png";
|
||||
|
||||
const baseClass = "self-service-preview";
|
||||
|
||||
interface ISelfServicePreviewProps {
|
||||
/** iOS/iPadOS uses screenshot + dynamic overlay; otherwise HTML preview */
|
||||
isIosOrIpadosApp: boolean;
|
||||
/** Shared data for mobile preview */
|
||||
contactUrl: string;
|
||||
name: string;
|
||||
displayName: string;
|
||||
versionLabel: string;
|
||||
/** What to render for the app icon in the list (img or <SoftwareIcon/>) */
|
||||
renderIcon: () => React.ReactNode;
|
||||
/** What to render as the “table” area for desktop (e.g. BasicSoftwareTable) */
|
||||
renderTable?: () => React.ReactNode;
|
||||
}
|
||||
|
||||
const SelfServicePreview = ({
|
||||
isIosOrIpadosApp,
|
||||
contactUrl,
|
||||
name,
|
||||
displayName,
|
||||
versionLabel,
|
||||
renderIcon,
|
||||
renderTable,
|
||||
}: ISelfServicePreviewProps) => {
|
||||
if (isIosOrIpadosApp) {
|
||||
// Mobile preview with screenshot + overlay
|
||||
return (
|
||||
<Card
|
||||
borderRadiusSize="medium"
|
||||
color="white"
|
||||
className={`${baseClass}__preview-card ${baseClass}__preview-card--mobile`}
|
||||
paddingSize="xlarge"
|
||||
>
|
||||
<div className={`${baseClass}__preview-img-container--mobile`}>
|
||||
<img
|
||||
className={`${baseClass}__preview-img--mobile`}
|
||||
src={PreviewSelfServiceMobileIcon}
|
||||
alt="Preview icon on Fleet Desktop > Self-service"
|
||||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__self-service-preview--mobile`}>
|
||||
{renderIcon()}
|
||||
<div
|
||||
className={`${baseClass}__self-service-preview-name-version--mobile`}
|
||||
>
|
||||
<div className={`${baseClass}__self-service-preview-name--mobile`}>
|
||||
<TooltipTruncatedText value={displayName || name} />
|
||||
</div>
|
||||
<div
|
||||
className={`${baseClass}__self-service-preview-version--mobile`}
|
||||
>
|
||||
{versionLabel}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
// Desktop HTML/CSS self-service preview
|
||||
return (
|
||||
<Card
|
||||
borderRadiusSize="medium"
|
||||
color="grey"
|
||||
className={`${baseClass}__preview-card`}
|
||||
paddingSize="xlarge"
|
||||
>
|
||||
<div className={`${baseClass}__disabled-overlay`} />
|
||||
<Card
|
||||
className={`${baseClass}__preview-card__self-service`}
|
||||
borderRadiusSize="xxlarge"
|
||||
>
|
||||
<SelfServiceHeader contactUrl={contactUrl} variant="preview" />
|
||||
<SearchField placeholder="Search by name" onChange={noop} disabled />
|
||||
<div className={`${baseClass}__table`}>
|
||||
<CategoriesMenu
|
||||
categories={CATEGORIES_NAV_ITEMS}
|
||||
queryParams={{
|
||||
query: "",
|
||||
order_direction: "asc",
|
||||
order_key: "name",
|
||||
page: 0,
|
||||
per_page: 100,
|
||||
}}
|
||||
readOnly
|
||||
/>
|
||||
{renderTable && renderTable()}
|
||||
</div>
|
||||
</Card>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelfServicePreview;
|
||||
@@ -0,0 +1,77 @@
|
||||
.self-service-preview {
|
||||
&__preview-card {
|
||||
overflow: hidden;
|
||||
height: 310px;
|
||||
|
||||
&--mobile {
|
||||
height: 230px; // Mobile preview is shorter height than Desktop preview
|
||||
}
|
||||
|
||||
&__self-service {
|
||||
width: 515px;
|
||||
height: 310px;
|
||||
@include vertical-card-layout;
|
||||
}
|
||||
|
||||
.card-header__header {
|
||||
font-size: initial;
|
||||
}
|
||||
|
||||
.categories-menu {
|
||||
top: 0;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.data-table__table {
|
||||
width: 325px;
|
||||
}
|
||||
}
|
||||
|
||||
&__preview-img-container--mobile {
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&__table {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
&__disabled-overlay {
|
||||
position: absolute;
|
||||
width: 540px;
|
||||
height: 335px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
&__self-service-preview--mobile {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
max-width: 210px;
|
||||
left: 154px;
|
||||
top: -85px;
|
||||
gap: $pad-xsmall;
|
||||
|
||||
.software-icon__xsmall {
|
||||
border-radius: $border-radius-small;
|
||||
}
|
||||
}
|
||||
|
||||
&__self-service-preview-name-version--mobile {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 8px;
|
||||
max-width: 110px; // Required for tooltip truncating long software names (mobile)
|
||||
}
|
||||
|
||||
// Required for tooltip truncating long software names
|
||||
&__self-service-preview-name--mobile {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
position: relative;
|
||||
font-weight: $bold;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./SelfServicePreview";
|
||||
+37
-65
@@ -1,15 +1,13 @@
|
||||
/** Mobile preview modal uses a screenshot
|
||||
* Non-mobile now uses HTML/CSS instead for
|
||||
* maintainability as the self-selvice UI changes
|
||||
/**
|
||||
* Previews match preview in Edit Appearance modal for Edit Appearance modal
|
||||
*
|
||||
* Currently only shown from the edit UI, though wired through the Add UI
|
||||
* Users currently can set categories only when editing a curent installer
|
||||
* Users currently can set categories only when editing a current installer
|
||||
*/
|
||||
|
||||
import React, { useContext } from "react";
|
||||
|
||||
import { Column } from "react-table";
|
||||
import { noop } from "lodash";
|
||||
import { AppContext } from "context/app";
|
||||
import { IHeaderProps } from "interfaces/datatable_config";
|
||||
|
||||
@@ -17,14 +15,10 @@ import TableContainer from "components/TableContainer";
|
||||
import SoftwareNameCell from "components/TableContainer/DataTable/SoftwareNameCell";
|
||||
import Modal from "components/Modal";
|
||||
import Button from "components/buttons/Button";
|
||||
import Card from "components/Card";
|
||||
import SelfServiceHeader from "pages/hosts/details/cards/Software/SelfService/components/SelfServiceHeader";
|
||||
import SearchField from "components/forms/fields/SearchField";
|
||||
import HeaderCell from "components/TableContainer/DataTable/HeaderCell";
|
||||
import CategoriesMenu from "pages/hosts/details/cards/Software/SelfService/components/CategoriesMenu";
|
||||
import { CATEGORIES_NAV_ITEMS } from "pages/hosts/details/cards/Software/SelfService/helpers";
|
||||
|
||||
import CategoriesEndUserExperiencePreviewMobile from "../../../../../../assets/images/categories-end-user-experience-preview-mobile@2x.png";
|
||||
import SelfServicePreview from "../../cards/SelfServicePreview";
|
||||
import SoftwareIcon from "../../icons/SoftwareIcon";
|
||||
|
||||
const baseClass = "categories-end-user-experience-preview-modal";
|
||||
|
||||
@@ -58,7 +52,8 @@ const getData = (
|
||||
name: string,
|
||||
displayName: string,
|
||||
iconUrl: string | null,
|
||||
source?: string
|
||||
source?: string,
|
||||
previewIcon?: JSX.Element
|
||||
): ISoftwareRow[] => {
|
||||
const currentSoftwareRow: ISoftwareRow = {
|
||||
name: (
|
||||
@@ -69,6 +64,7 @@ const getData = (
|
||||
iconUrl={iconUrl}
|
||||
pageContext="deviceUser"
|
||||
isSelfService
|
||||
previewIcon={previewIcon}
|
||||
/>
|
||||
),
|
||||
};
|
||||
@@ -97,18 +93,21 @@ interface BasicSoftwareTableProps {
|
||||
displayName: string;
|
||||
source?: string;
|
||||
iconUrl?: string | null;
|
||||
/** Render a preview icon instead for edit icon preview */
|
||||
previewIcon?: JSX.Element;
|
||||
}
|
||||
|
||||
const BasicSoftwareTable = ({
|
||||
export const BasicSoftwareTable = ({
|
||||
name,
|
||||
displayName,
|
||||
source,
|
||||
iconUrl = null,
|
||||
previewIcon,
|
||||
}: BasicSoftwareTableProps) => {
|
||||
return (
|
||||
<TableContainer<ISoftwareRow>
|
||||
columnConfigs={columns}
|
||||
data={getData(name, displayName, iconUrl, source)}
|
||||
data={getData(name, displayName, iconUrl, source, previewIcon)}
|
||||
isLoading={false}
|
||||
emptyComponent={EmptyState}
|
||||
showMarkAllPages={false}
|
||||
@@ -126,8 +125,9 @@ interface ICategoriesEndUserExperienceModal {
|
||||
isIosOrIpadosApp?: boolean;
|
||||
name?: string;
|
||||
displayName?: string;
|
||||
iconUrl?: string;
|
||||
iconUrl?: string | null;
|
||||
source?: string;
|
||||
mobileVersion?: string;
|
||||
}
|
||||
|
||||
const CategoriesEndUserExperienceModal = ({
|
||||
@@ -137,63 +137,35 @@ const CategoriesEndUserExperienceModal = ({
|
||||
displayName = "Software name",
|
||||
iconUrl,
|
||||
source,
|
||||
mobileVersion,
|
||||
}: ICategoriesEndUserExperienceModal): JSX.Element => {
|
||||
const { config } = useContext(AppContext);
|
||||
return (
|
||||
<Modal title="End user experience" onExit={onCancel} className={baseClass}>
|
||||
<>
|
||||
<span>What end users see:</span>
|
||||
|
||||
{isIosOrIpadosApp ? (
|
||||
<div className={`${baseClass}__preview`}>
|
||||
<img
|
||||
src={CategoriesEndUserExperiencePreviewMobile}
|
||||
alt="Categories end user experience preview"
|
||||
<SelfServicePreview
|
||||
isIosOrIpadosApp={isIosOrIpadosApp}
|
||||
contactUrl={config?.org_info.contact_url || ""}
|
||||
name={name}
|
||||
displayName={displayName || name}
|
||||
versionLabel={mobileVersion || "Version (unknown)"}
|
||||
renderIcon={() => (
|
||||
<SoftwareIcon
|
||||
name={name}
|
||||
source={source}
|
||||
url={iconUrl ?? undefined}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Card
|
||||
borderRadiusSize="medium"
|
||||
color="grey"
|
||||
className={`${baseClass}__preview-card`}
|
||||
paddingSize="xlarge"
|
||||
>
|
||||
<div className={`${baseClass}__disabled-overlay`} />
|
||||
<Card
|
||||
className={`${baseClass}__preview-card__self-service`}
|
||||
borderRadiusSize="xxlarge"
|
||||
>
|
||||
<SelfServiceHeader
|
||||
contactUrl={config?.org_info.contact_url || ""}
|
||||
variant="preview"
|
||||
/>
|
||||
<SearchField
|
||||
placeholder="Search by name"
|
||||
onChange={noop}
|
||||
disabled
|
||||
/>
|
||||
<div className={`${baseClass}__table`}>
|
||||
<CategoriesMenu
|
||||
categories={CATEGORIES_NAV_ITEMS}
|
||||
queryParams={{
|
||||
query: "",
|
||||
order_direction: "asc",
|
||||
order_key: "name",
|
||||
page: 0,
|
||||
per_page: 100,
|
||||
}}
|
||||
readOnly
|
||||
/>
|
||||
<BasicSoftwareTable
|
||||
name={name}
|
||||
displayName={displayName}
|
||||
source={source}
|
||||
iconUrl={iconUrl}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</Card>
|
||||
)}
|
||||
)}
|
||||
renderTable={() => (
|
||||
<BasicSoftwareTable
|
||||
name={name}
|
||||
displayName={displayName}
|
||||
source={source}
|
||||
iconUrl={iconUrl}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<div className="modal-cta-wrap">
|
||||
<Button onClick={onCancel}>Done</Button>
|
||||
</div>
|
||||
|
||||
+7
-36
@@ -5,49 +5,20 @@
|
||||
gap: $pad-large;
|
||||
}
|
||||
|
||||
&__preview {
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-cta-wrap {
|
||||
margin-top: 0; // Defer to new gap styling
|
||||
}
|
||||
|
||||
&__preview-card {
|
||||
max-height: 300px;
|
||||
overflow: hidden;
|
||||
|
||||
.card-header__header {
|
||||
font-size: initial;
|
||||
}
|
||||
|
||||
&__self-service {
|
||||
width: 515px;
|
||||
height: 310px;
|
||||
@include vertical-card-layout;
|
||||
}
|
||||
|
||||
.categories-menu {
|
||||
top: 0;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.data-table__table {
|
||||
width: 325px;
|
||||
}
|
||||
}
|
||||
&__table {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
max-height: 290px;
|
||||
padding: $pad-xlarge 0 0;
|
||||
}
|
||||
|
||||
&__disabled-overlay {
|
||||
position: absolute;
|
||||
width: 540px;
|
||||
height: 335px;
|
||||
z-index: 1000;
|
||||
&__self-service-preview--mobile {
|
||||
// Different offsets than edit modal
|
||||
top: -94px;
|
||||
left: 175px;
|
||||
gap: $pad-small;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user