Skip to content

Commit

Permalink
feat: emui package details page (#1873)
Browse files Browse the repository at this point in the history
## Description:
This PR implements the emui package details page.

The change @adschwartz to refactor keyboard listener code requested in
#1865 is also included.

It also fixes the poor rendering of the catalog logos on chrome. 

Additionally it implements feedback Tise left on the designs for me to
pick up, specifically:
* Value card icon appearance
* Use preferred round star icon
* Update the run button appearance and interaction behaviour to match
the designs.

Finally, quite a large addition is included to support the combination
of `react-markdown` and `chakra` in the `KurtosisMarkdown` component
which includes basic chakra implementations of the html components. This
is required because `CSSReset` (part of chakra theme) unsets all of the
styling that would apply to markdown.

### Short demo


https://github.com/kurtosis-tech/kurtosis/assets/4419574/19826393-10b2-40de-832b-6970f305a2ce

## Is this change user facing?
YES

## References (if applicable):
* Figma
  • Loading branch information
Dartoxian authored Nov 30, 2023
1 parent b695e27 commit e2b75b2
Show file tree
Hide file tree
Showing 26 changed files with 533 additions and 188 deletions.
60 changes: 36 additions & 24 deletions enclave-manager/web/src/components/KurtosisBreadcrumbs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import { ReactElement, useMemo } from "react";
import { BsCaretDownFill } from "react-icons/bs";
import { Link, Params, UIMatch, useMatches } from "react-router-dom";
import { CatalogState, useCatalogContext } from "../emui/catalog/CatalogContext";
import { EnclavesState, useEnclavesContext } from "../emui/enclaves/EnclavesContext";
import { isDefined } from "../utils";
import { RemoveFunctions } from "../utils/types";
Expand All @@ -33,7 +34,7 @@ export type KurtosisEnclavesBreadcrumbsHandle = KurtosisBaseBreadcrumbsHandle &

export type KurtosisCatalogBreadcrumbsHandle = {
type: "catalogHandle";
crumb?: () => KurtosisBreadcrumb | KurtosisBreadcrumb[];
crumb?: (state: RemoveFunctions<CatalogState>, params: Params<string>) => KurtosisBreadcrumb | KurtosisBreadcrumb[];
};

export type KurtosisBreadcrumbsHandle = KurtosisEnclavesBreadcrumbsHandle | KurtosisCatalogBreadcrumbsHandle;
Expand Down Expand Up @@ -151,16 +152,18 @@ type KurtosisCatalogBreadcrumbsProps = {
};

const KurtosisCatalogBreadcrumbs = ({ matches }: KurtosisCatalogBreadcrumbsProps) => {
const { catalog, savedPackages } = useCatalogContext();

const matchCrumbs = useMemo(
() =>
matches.flatMap((match) => {
if (isDefined(match.handle?.crumb)) {
const r = match.handle.crumb();
const r = match.handle.crumb({ catalog, savedPackages }, match.params);
return Array.isArray(r) ? r : [r];
}
return [];
}),
[matches],
[matches, catalog, savedPackages],
);

return <KurtosisBreadcrumbsImpl matchCrumbs={matchCrumbs} />;
Expand All @@ -173,27 +176,36 @@ type KurtosisBreadcrumbsImplProps = {

const KurtosisBreadcrumbsImpl = ({ matchCrumbs, extraControls }: KurtosisBreadcrumbsImplProps) => {
return (
<Flex h={BREADCRUMBS_HEIGHT}>
<Flex w={MAIN_APP_MAX_WIDTH_WITHOUT_PADDING} alignItems={"center"} justifyContent={"space-between"}>
<Flex>
<Breadcrumb
variant={"topNavigation"}
separator={
<Text as={"span"} fontSize={"lg"}>
/
</Text>
}
>
{matchCrumbs.map((crumb, i, arr) => (
<BreadcrumbItem key={i} isCurrentPage={i === arr.length - 1}>
<KurtosisBreadcrumbItem {...crumb} key={i} isLastItem={i === arr.length - 1} />
</BreadcrumbItem>
))}
</Breadcrumb>
&nbsp;
</Flex>
<Flex>{extraControls}</Flex>
<Flex
flex={"none"}
h={BREADCRUMBS_HEIGHT}
w={MAIN_APP_MAX_WIDTH_WITHOUT_PADDING}
alignItems={"center"}
justifyContent={"space-between"}
>
<Flex>
<Breadcrumb
variant={"topNavigation"}
separator={
<Text as={"span"} fontSize={"lg"}>
/
</Text>
}
>
<BreadcrumbItem>
<Text fontSize={"xs"} fontWeight={"semibold"} p={"0px 8px"}>
Kurtosis
</Text>
</BreadcrumbItem>
{matchCrumbs.map((crumb, i, arr) => (
<BreadcrumbItem key={i} isCurrentPage={i === arr.length - 1}>
<KurtosisBreadcrumbItem {...crumb} key={i} isLastItem={i === arr.length - 1} />
</BreadcrumbItem>
))}
</Breadcrumb>
&nbsp;
</Flex>
<Flex>{extraControls}</Flex>
</Flex>
);
};
Expand All @@ -205,7 +217,7 @@ type KurtosisBreadcrumbItemProps = KurtosisBreadcrumb & {
const KurtosisBreadcrumbItem = ({ name, destination, alternatives, isLastItem }: KurtosisBreadcrumbItemProps) => {
if (isLastItem) {
return (
<Text fontSize={"xs"} fontWeight={"semibold"} color={"gray.400"} p={"0px 8px"}>
<Text fontSize={"xs"} fontWeight={"semibold"} p={"2px 8px"} borderRadius={"6px"} bg={"gray.650"}>
{name}
</Text>
);
Expand Down
81 changes: 81 additions & 0 deletions enclave-manager/web/src/components/KurtosisMarkdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Code, Divider, Heading, Image, Link, Table, Tbody, Td, Text, Th, Thead, Tr } from "@chakra-ui/react";
import { DetailedHTMLProps, HTMLAttributes } from "react";
import Markdown, { Components } from "react-markdown";

const heading =
(level: 1 | 2 | 3 | 4 | 5 | 6) =>
({ children }: DetailedHTMLProps<HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>) => {
const sizes = ["xl", "lg", "md", "sm", "xs", "xs"];
return (
<Heading my={4} as={`h${level}`} size={sizes[`${level - 1}`]}>
{children}
</Heading>
);
};

const componentStrategy: Components = {
h1: heading(1),
h2: heading(2),
h3: heading(3),
h4: heading(4),
h5: heading(5),
h6: heading(6),
p: (props) => {
const { children } = props;
return <Text mb={2}>{children}</Text>;
},
em: (props) => {
const { children } = props;
return <Text as="em">{children}</Text>;
},
blockquote: (props) => {
const { children } = props;
return (
<Code as="blockquote" p={2}>
{children}
</Code>
);
},
code: ({ children }) => {
return <Code children={children} />;
},
del: (props) => {
const { children } = props;
return <Text as="del">{children}</Text>;
},
hr: (props) => {
return <Divider />;
},
a: Link,
img: (props) => <Image src={props.src} />,
text: (props) => {
const { children } = props;
return <Text as="span">{children}</Text>;
},
pre: (props) => {
const { children } = props;
return (
<Text margin={1} as={"pre"}>
{children}
</Text>
);
},
table: Table,
thead: Thead,
tbody: Tbody,
tr: (props) => <Tr>{props.children}</Tr>,
td: (props) => <Td>{props.children}</Td>,
th: (props) => <Th>{props.children}</Th>,
};

type KurtosisMarkdownProps = {
children?: string;
};

export const KurtosisMarkdown = ({ children }: KurtosisMarkdownProps) => {
return (
<Markdown components={componentStrategy} skipHtml>
{children}
</Markdown>
);
};
12 changes: 10 additions & 2 deletions enclave-manager/web/src/components/KurtosisThemeProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,16 @@ const theme = extendTheme({
color: `${props.colorScheme}.400`,
borderColor: "gray.300",
}),
solidOutline: (props: StyleFunctionProps) => {
const outline = theme.components.Button.variants!.outline(props);
return {
...outline,
_hover: { bg: `${props.colorScheme}.400`, color: "gray.900" },
_active: { bg: `${props.colorScheme}.400`, color: "gray.900" },
color: `${props.colorScheme}.400`,
borderColor: `${props.colorScheme}.400`,
};
},
kurtosisGroupOutline: (props: StyleFunctionProps) => {
const outline = theme.components.Button.variants!.outline(props);
return {
Expand Down Expand Up @@ -210,13 +220,11 @@ const theme = extendTheme({
},
titledCard: {
container: {
height: "100%",
bgColor: "none",
borderColor: "gray.500",
borderStyle: "solid",
borderWidth: "1px",
borderRadius: "6px",
overflow: "clip",
},
header: {
bg: "gray.850",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { Button, ButtonGroup, ButtonProps, Icon, Spinner, Tag, Tooltip } from "@chakra-ui/react";
import { Button, ButtonGroup, ButtonProps, Icon, Link, Spinner, Tag, Tooltip } from "@chakra-ui/react";
import { PropsWithChildren } from "react";
import { IoLogoGithub } from "react-icons/io";
import { useKurtosisPackageIndexerClient } from "../../../client/packageIndexer/KurtosisPackageIndexerClientContext";
import { isDefined, wrapResult } from "../../../utils";
import { CopyButton } from "../../CopyButton";
import { useKurtosisPackageIndexerClient } from "../client/packageIndexer/KurtosisPackageIndexerClientContext";
import { isDefined, wrapResult } from "../utils";
import { CopyButton } from "./CopyButton";

type EnclaveSourceProps = ButtonProps & {
source: "loading" | string | null;
};
type EnclaveSourceProps = PropsWithChildren<
ButtonProps & {
source: "loading" | string | null;
hideCopy?: boolean;
}
>;

export const EnclaveSourceButton = ({ source, ...buttonProps }: EnclaveSourceProps) => {
export const PackageSourceButton = ({ source, hideCopy, children, ...buttonProps }: EnclaveSourceProps) => {
const kurtosisIndexer = useKurtosisPackageIndexerClient();

if (!isDefined(source)) {
Expand All @@ -20,11 +24,11 @@ export const EnclaveSourceButton = ({ source, ...buttonProps }: EnclaveSourcePro
}

let button = (
<a href={`https://${source}`} target="_blank" rel="noopener noreferrer">
<Link href={`https://${source}`} target="_blank" rel="noopener noreferrer" w={buttonProps.w || buttonProps.width}>
<Button variant={"ghost"} size={"xs"} {...buttonProps}>
{source}
{children || source}
</Button>
</a>
</Link>
);
if (source.startsWith("github.com/")) {
const repositoryResult = wrapResult(() => kurtosisIndexer.parsePackageUrl(source));
Expand All @@ -35,23 +39,23 @@ export const EnclaveSourceButton = ({ source, ...buttonProps }: EnclaveSourcePro
}`;

button = (
<a href={url} target="_blank" rel="noopener noreferrer">
<Link href={url} target="_blank" rel="noopener noreferrer" w={buttonProps.w || buttonProps.width}>
<Button
leftIcon={<Icon as={IoLogoGithub} color={"gray.400"} />}
variant={"ghost"}
size={"xs"}
{...buttonProps}
>
{source.replace("github.com/", "")}
{children || source.replace("github.com/", "")}
</Button>
</a>
</Link>
);
} else {
button = (
<Tooltip shouldWrapChildren label={repositoryResult.error}>
<a href={`https://${source}`} target="_blank" rel="noopener noreferrer">
<Button variant={"ghost"} size={"xs"} {...buttonProps} colorScheme={"red"}>
{source}
{children || source}
</Button>
</a>
</Tooltip>
Expand All @@ -62,13 +66,15 @@ export const EnclaveSourceButton = ({ source, ...buttonProps }: EnclaveSourcePro
return (
<ButtonGroup>
{button}
<CopyButton
contentName={"package id"}
valueToCopy={source}
isIconButton
aria-label={"Copy package id"}
size={buttonProps.size || "xs"}
/>
{!hideCopy && (
<CopyButton
contentName={"package id"}
valueToCopy={source}
isIconButton
aria-label={"Copy package id"}
size={buttonProps.size || "xs"}
/>
)}
</ButtonGroup>
);
};
14 changes: 11 additions & 3 deletions enclave-manager/web/src/components/TitledCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,21 @@ import { PropsWithChildren, ReactElement } from "react";
type TitledCardProps = CardProps &
PropsWithChildren<{
title: string;
fillContainer?: boolean;
controls?: ReactElement;
rightControls?: ReactElement;
}>;

export const TitledCard = ({ title, controls, rightControls, children, ...cardProps }: TitledCardProps) => {
export const TitledCard = ({
title,
fillContainer,
controls,
rightControls,
children,
...cardProps
}: TitledCardProps) => {
return (
<Card variant={"titledCard"} {...cardProps}>
<Card variant={"titledCard"} overflow={fillContainer ? "clip" : undefined} {...cardProps}>
<CardHeader
display={"flex"}
justifyContent={"space-between"}
Expand All @@ -28,7 +36,7 @@ export const TitledCard = ({ title, controls, rightControls, children, ...cardPr
</Flex>
<Flex>{rightControls}</Flex>
</CardHeader>
<CardBody overflow={"auto"}>{children}</CardBody>
<CardBody overflow={fillContainer ? "auto" : undefined}>{children}</CardBody>
</Card>
);
};
4 changes: 4 additions & 0 deletions enclave-manager/web/src/components/ValueCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@ export const ValueCard = ({ title, value, copyEnabled, copyValue }: ValueCardPro
</Text>
{copyEnabled && (
<CopyButton
isIconButton
aria-label={"Copy this value"}
valueToCopy={isDefined(copyValue) ? copyValue : typeof value === "string" ? value : null}
contentName={title}
color={"gray.400"}
colorScheme={"gray"}
/>
)}
</CardHeader>
Expand Down
Loading

0 comments on commit e2b75b2

Please sign in to comment.