Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: emui package details page #1873

Merged
merged 7 commits into from Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
60 changes: 36 additions & 24 deletions enclave-manager/web/src/components/KurtosisBreadcrumbs.tsx
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
@@ -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
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
@@ -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
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
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