diff --git a/examples/examples.gen.json b/examples/examples.gen.json index ea2ec0abc1..bd52f85f89 100644 --- a/examples/examples.gen.json +++ b/examples/examples.gen.json @@ -5,7 +5,7 @@ "url": "", "name": "AWS EC2 (Devcontainer)", "description": "Provision AWS EC2 VMs with a devcontainer as Coder workspaces", - "icon": "/icon/aws.png", + "icon": "/icon/aws.svg", "tags": [ "vm", "linux", @@ -20,7 +20,7 @@ "url": "", "name": "AWS EC2 (Linux)", "description": "Provision AWS EC2 VMs as Coder workspaces", - "icon": "/icon/aws.png", + "icon": "/icon/aws.svg", "tags": [ "vm", "linux", @@ -34,7 +34,7 @@ "url": "", "name": "AWS EC2 (Windows)", "description": "Provision AWS EC2 VMs as Coder workspaces", - "icon": "/icon/aws.png", + "icon": "/icon/aws.svg", "tags": [ "vm", "windows", diff --git a/examples/templates/aws-devcontainer/README.md b/examples/templates/aws-devcontainer/README.md index 7267f62928..0fb6d753bb 100644 --- a/examples/templates/aws-devcontainer/README.md +++ b/examples/templates/aws-devcontainer/README.md @@ -1,7 +1,7 @@ --- display_name: AWS EC2 (Devcontainer) description: Provision AWS EC2 VMs with a devcontainer as Coder workspaces -icon: ../../../site/static/icon/aws.png +icon: ../../../site/static/icon/aws.svg maintainer_github: coder verified: true tags: [vm, linux, aws, persistent, devcontainer] diff --git a/examples/templates/aws-linux/README.md b/examples/templates/aws-linux/README.md index 1854381f47..eab4a60ab0 100644 --- a/examples/templates/aws-linux/README.md +++ b/examples/templates/aws-linux/README.md @@ -1,7 +1,7 @@ --- display_name: AWS EC2 (Linux) description: Provision AWS EC2 VMs as Coder workspaces -icon: ../../../site/static/icon/aws.png +icon: ../../../site/static/icon/aws.svg maintainer_github: coder verified: true tags: [vm, linux, aws, persistent-vm] diff --git a/examples/templates/aws-windows/README.md b/examples/templates/aws-windows/README.md index 05cb4e1dae..adb16b755b 100644 --- a/examples/templates/aws-windows/README.md +++ b/examples/templates/aws-windows/README.md @@ -1,7 +1,7 @@ --- display_name: AWS EC2 (Windows) description: Provision AWS EC2 VMs as Coder workspaces -icon: ../../../site/static/icon/aws.png +icon: ../../../site/static/icon/aws.svg maintainer_github: coder verified: true tags: [vm, windows, aws] diff --git a/site/src/AppRouter.tsx b/site/src/AppRouter.tsx index 245e263586..d06068100b 100644 --- a/site/src/AppRouter.tsx +++ b/site/src/AppRouter.tsx @@ -21,8 +21,6 @@ import WorkspacesPage from "./pages/WorkspacesPage/WorkspacesPage"; import UserSettingsLayout from "./pages/UserSettingsPage/Layout"; import { TemplateSettingsLayout } from "./pages/TemplateSettingsPage/TemplateSettingsLayout"; import { WorkspaceSettingsLayout } from "./pages/WorkspaceSettingsPage/WorkspaceSettingsLayout"; -import { ThemeOverride } from "contexts/ThemeProvider"; -import themes from "theme"; // Lazy load pages // - Pages that are secondary, not in the main navigation or not usually accessed @@ -421,11 +419,7 @@ export const AppRouter: FC = () => { /> - - - } + element={} /> } /> } /> diff --git a/site/src/__mocks__/react-markdown.tsx b/site/src/__mocks__/react-markdown.tsx index f94c0fbe80..35af964260 100644 --- a/site/src/__mocks__/react-markdown.tsx +++ b/site/src/__mocks__/react-markdown.tsx @@ -1,6 +1,6 @@ import { FC, PropsWithChildren } from "react"; -const ReactMarkdown: FC> = ({ children }) => { +const ReactMarkdown: FC = ({ children }) => { return
{children}
; }; diff --git a/site/src/api/queries/users.ts b/site/src/api/queries/users.ts index 0249315071..7f265b1c0f 100644 --- a/site/src/api/queries/users.ts +++ b/site/src/api/queries/users.ts @@ -131,6 +131,7 @@ export const me = (): UseQueryOptions & { queryKey: meKey, initialData: initialUserData, queryFn: API.getAuthenticatedUser, + refetchOnWindowFocus: true, }; }; diff --git a/site/src/components/Avatar/Avatar.tsx b/site/src/components/Avatar/Avatar.tsx index ebc234e848..574636da72 100644 --- a/site/src/components/Avatar/Avatar.tsx +++ b/site/src/components/Avatar/Avatar.tsx @@ -4,8 +4,9 @@ import MuiAvatar, { type AvatarProps as MuiAvatarProps, } from "@mui/material/Avatar"; import { type FC, useId } from "react"; -import { css, type Interpolation, type Theme } from "@emotion/react"; +import { css, type Interpolation, type Theme, useTheme } from "@emotion/react"; import { visuallyHidden } from "@mui/utils"; +import { getExternalImageStylesFromUrl } from "theme/externalImages"; export type AvatarProps = MuiAvatarProps & { size?: "xs" | "sm" | "md" | "xl"; @@ -67,6 +68,17 @@ export const Avatar: FC = ({ ); }; +export const ExternalAvatar: FC = (props) => { + const theme = useTheme(); + + return ( + + ); +}; + type AvatarIconProps = { src: string; alt: string; diff --git a/site/src/components/AvatarCard/AvatarCard.tsx b/site/src/components/AvatarCard/AvatarCard.tsx index bd029108ef..b55608657d 100644 --- a/site/src/components/AvatarCard/AvatarCard.tsx +++ b/site/src/components/AvatarCard/AvatarCard.tsx @@ -1,4 +1,4 @@ -import { type ReactNode } from "react"; +import { type FC, type ReactNode } from "react"; import { Avatar } from "components/Avatar/Avatar"; import { type CSSObject, useTheme } from "@emotion/react"; @@ -12,14 +12,14 @@ type AvatarCardProps = { maxWidth?: number | "none"; }; -export function AvatarCard({ +export const AvatarCard: FC = ({ header, imgUrl, altText, background, subtitle, maxWidth = "none", -}: AvatarCardProps) { +}) => { const theme = useTheme(); return ( @@ -77,4 +77,4 @@ export function AvatarCard({ ); -} +}; diff --git a/site/src/components/ExternalIcon/ExternalIcon.tsx b/site/src/components/ExternalIcon/ExternalIcon.tsx deleted file mode 100644 index 91eace58be..0000000000 --- a/site/src/components/ExternalIcon/ExternalIcon.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { type Interpolation, type Theme } from "@emotion/react"; -import { type FC, type ImgHTMLAttributes } from "react"; - -interface ExternalIconProps extends ImgHTMLAttributes { - size?: number; -} - -export const ExternalIcon: FC = ({ - size = 36, - ...attrs -}) => { - return ( -
- -
- ); -}; - -const styles = { - container: { - borderRadius: 9999, - overflow: "clip", - }, - icon: { - backgroundColor: "#000", - objectFit: "contain", - }, -} satisfies Record>; diff --git a/site/src/components/ExternalImage/ExternalImage.tsx b/site/src/components/ExternalImage/ExternalImage.tsx new file mode 100644 index 0000000000..268cc2e533 --- /dev/null +++ b/site/src/components/ExternalImage/ExternalImage.tsx @@ -0,0 +1,19 @@ +import { useTheme } from "@emotion/react"; +import { type ImgHTMLAttributes, forwardRef } from "react"; +import { getExternalImageStylesFromUrl } from "theme/externalImages"; + +export const ExternalImage = forwardRef< + HTMLImageElement, + ImgHTMLAttributes +>((attrs, ref) => { + const theme = useTheme(); + + return ( + + ); +}); diff --git a/site/src/components/FullPageLayout/Topbar.tsx b/site/src/components/FullPageLayout/Topbar.tsx index a882825d19..27457faa47 100644 --- a/site/src/components/FullPageLayout/Topbar.tsx +++ b/site/src/components/FullPageLayout/Topbar.tsx @@ -2,17 +2,17 @@ import { css } from "@emotion/css"; import Button, { ButtonProps } from "@mui/material/Button"; import IconButton, { IconButtonProps } from "@mui/material/IconButton"; import { useTheme } from "@mui/material/styles"; -import { Avatar, AvatarProps } from "components/Avatar/Avatar"; +import { AvatarProps, ExternalAvatar } from "components/Avatar/Avatar"; import { - ForwardedRef, - HTMLAttributes, - PropsWithChildren, - ReactElement, + type FC, + type ForwardedRef, + type HTMLAttributes, + type ReactElement, cloneElement, forwardRef, } from "react"; -export const Topbar = (props: HTMLAttributes) => { +export const Topbar: FC> = (props) => { const theme = useTheme(); return ( @@ -70,7 +70,7 @@ export const TopbarButton = forwardRef( }, ); -export const TopbarData = (props: HTMLAttributes) => { +export const TopbarData: FC> = (props) => { return (
) => { ); }; -export const TopbarDivider = (props: HTMLAttributes) => { +export const TopbarDivider: FC> = (props) => { const theme = useTheme(); return ( @@ -93,9 +93,9 @@ export const TopbarDivider = (props: HTMLAttributes) => { ); }; -export const TopbarAvatar = (props: AvatarProps) => { +export const TopbarAvatar: FC = (props) => { return ( - { ); }; -type TopbarIconProps = PropsWithChildren>; +type TopbarIconProps = HTMLAttributes; export const TopbarIcon = forwardRef( (props: TopbarIconProps, ref) => { diff --git a/site/src/components/GroupAvatar/GroupAvatar.tsx b/site/src/components/GroupAvatar/GroupAvatar.tsx index 811e9b7ad9..7083799ca6 100644 --- a/site/src/components/GroupAvatar/GroupAvatar.tsx +++ b/site/src/components/GroupAvatar/GroupAvatar.tsx @@ -19,7 +19,7 @@ export const GroupAvatar: FC = ({ name, avatarURL }) => { badgeContent={} classes={{ badge }} > - + {name} diff --git a/site/src/components/IconField/IconField.tsx b/site/src/components/IconField/IconField.tsx index aaf9fa096c..d96d65e823 100644 --- a/site/src/components/IconField/IconField.tsx +++ b/site/src/components/IconField/IconField.tsx @@ -12,6 +12,7 @@ import { PopoverContent, PopoverTrigger, } from "components/Popover/Popover"; +import { ExternalImage } from "components/ExternalImage/ExternalImage"; // See: https://github.com/missive/emoji-mart/issues/51#issuecomment-287353222 const urlFromUnifiedCode = (unified: string) => @@ -60,7 +61,7 @@ export const IconField: FC = ({ }, }} > - = ({ resource }) => { const avatarSrc = resource.icon || getResourceIconPath(resource.type); + const altId = useId(); return ( - + +
+ {resource.name} +
); }; diff --git a/site/src/components/RichParameterInput/RichParameterInput.stories.tsx b/site/src/components/RichParameterInput/RichParameterInput.stories.tsx index 8d1d5212fa..4dcfda0cca 100644 --- a/site/src/components/RichParameterInput/RichParameterInput.stories.tsx +++ b/site/src/components/RichParameterInput/RichParameterInput.stories.tsx @@ -96,7 +96,7 @@ export const Options: Story = { name: "Third option", value: "third_option", description: "", - icon: "/icon/aws.png", + icon: "/icon/aws.svg", }, ], }), @@ -138,7 +138,7 @@ Very big. > Wow, that description is straight up large. –Some guy, probably `, - icon: "/icon/aws.png", + icon: "/icon/aws.svg", }, ], }), diff --git a/site/src/components/RichParameterInput/RichParameterInput.tsx b/site/src/components/RichParameterInput/RichParameterInput.tsx index ca8573a532..331c67864a 100644 --- a/site/src/components/RichParameterInput/RichParameterInput.tsx +++ b/site/src/components/RichParameterInput/RichParameterInput.tsx @@ -9,6 +9,7 @@ import { TemplateVersionParameter } from "api/typesGenerated"; import { MemoizedMarkdown } from "components/Markdown/Markdown"; import { Stack } from "components/Stack/Stack"; import { MultiTextField } from "./MultiTextField"; +import { ExternalImage } from "components/ExternalImage/ExternalImage"; const isBoolean = (parameter: TemplateVersionParameter) => { return parameter.type === "bool"; @@ -106,7 +107,7 @@ const ParameterLabel: FC = ({ parameter }) => { {parameter.icon && ( - Parameter icon = ({ label={ {option.icon && ( - Parameter icon = { + title: "components/TemplateExampleCard", + parameters: { chromatic }, + component: TemplateExampleCard, + args: { + example: MockTemplateExample, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Example: Story = {}; + +export const ByTag: Story = { + args: { + activeTag: "cloud", + }, +}; + +export const LotsOfTags: Story = { + args: { + example: { + ...MockTemplateExample2, + tags: ["omg", "so many tags", "look at all these", "so cool"], + }, + }, +}; diff --git a/site/src/components/TemplateExampleCard/TemplateExampleCard.tsx b/site/src/components/TemplateExampleCard/TemplateExampleCard.tsx index 1a92a18ed6..ba8a92048a 100644 --- a/site/src/components/TemplateExampleCard/TemplateExampleCard.tsx +++ b/site/src/components/TemplateExampleCard/TemplateExampleCard.tsx @@ -1,80 +1,40 @@ +import { type Interpolation, type Theme } from "@emotion/react"; import Button from "@mui/material/Button"; import Link from "@mui/material/Link"; import type { TemplateExample } from "api/typesGenerated"; +import { ExternalImage } from "components/ExternalImage/ExternalImage"; import { Pill } from "components/Pill/Pill"; -import { HTMLProps } from "react"; +import { type FC, type HTMLAttributes } from "react"; import { Link as RouterLink } from "react-router-dom"; -type TemplateExampleCardProps = { +type TemplateExampleCardProps = HTMLAttributes & { example: TemplateExample; activeTag?: string; -} & HTMLProps; +}; -export const TemplateExampleCard = (props: TemplateExampleCardProps) => { - const { example, activeTag, ...divProps } = props; +export const TemplateExampleCard: FC = ({ + example, + activeTag, + ...divProps +}) => { return ( -
({ - width: "320px", - padding: 24, - borderRadius: 6, - border: `1px solid ${theme.palette.divider}`, - textAlign: "left", - textDecoration: "none", - color: "inherit", - display: "flex", - flexDirection: "column", - })} - {...divProps} - > -
-
- +
+
+
-
- {example.tags.map((tag) => { - const isActive = activeTag === tag; - - return ( - - ({ - borderColor: isActive - ? theme.palette.primary.main - : theme.palette.divider, - cursor: "pointer", - backgroundColor: isActive - ? theme.palette.primary.dark - : undefined, - "&: hover": { - borderColor: theme.palette.primary.main, - }, - })} - > - {tag} - - - ); - })} +
+ {example.tags.map((tag) => ( + + + {tag} + + + ))}
@@ -82,14 +42,7 @@ export const TemplateExampleCard = (props: TemplateExampleCardProps) => {

{example.name}

- ({ - fontSize: 13, - color: theme.palette.text.secondary, - lineHeight: "1.6", - display: "block", - })} - > + {example.description}{" "} {
-
+
); }; + +const styles = { + card: (theme) => ({ + width: "320px", + padding: 24, + borderRadius: 6, + border: `1px solid ${theme.palette.divider}`, + textAlign: "left", + color: "inherit", + display: "flex", + flexDirection: "column", + }), + + header: { + display: "flex", + alignItems: "center", + justifyContent: "space-between", + marginBottom: 24, + }, + + icon: { + flexShrink: 0, + paddingTop: 4, + width: 32, + height: 32, + }, + + tags: { + display: "flex", + flexWrap: "wrap", + gap: 8, + justifyContent: "end", + }, + + tag: (theme) => ({ + borderColor: theme.palette.divider, + textDecoration: "none", + cursor: "pointer", + "&: hover": { + borderColor: theme.palette.primary.main, + }, + }), + + activeTag: (theme) => ({ + borderColor: theme.experimental.roles.active.outline, + backgroundColor: theme.experimental.roles.active.background, + }), + + description: (theme) => ({ + fontSize: 13, + color: theme.palette.text.secondary, + lineHeight: "1.6", + display: "block", + }), + + useButtonContainer: { + display: "flex", + gap: 12, + flexDirection: "column", + paddingTop: 24, + marginTop: "auto", + alignItems: "center", + }, +} satisfies Record>; diff --git a/site/src/pages/CreateTemplatePage/CreateTemplatePage.tsx b/site/src/pages/CreateTemplatePage/CreateTemplatePage.tsx index 5b98964d5c..7051a5ab95 100644 --- a/site/src/pages/CreateTemplatePage/CreateTemplatePage.tsx +++ b/site/src/pages/CreateTemplatePage/CreateTemplatePage.tsx @@ -1,8 +1,8 @@ -import { FullPageHorizontalForm } from "components/FullPageForm/FullPageHorizontalForm"; -import { FC } from "react"; +import { type FC } from "react"; import { Helmet } from "react-helmet-async"; import { useNavigate, useSearchParams } from "react-router-dom"; import { pageTitle } from "utils/page"; +import { FullPageHorizontalForm } from "components/FullPageForm/FullPageHorizontalForm"; import { DuplicateTemplateView } from "./DuplicateTemplateView"; import { ImportStarterTemplateView } from "./ImportStarterTemplateView"; import { UploadTemplateView } from "./UploadTemplateView"; diff --git a/site/src/pages/CreateWorkspacePage/SelectedTemplate.tsx b/site/src/pages/CreateWorkspacePage/SelectedTemplate.tsx index 257b3fb8cc..08bd2b71c5 100644 --- a/site/src/pages/CreateWorkspacePage/SelectedTemplate.tsx +++ b/site/src/pages/CreateWorkspacePage/SelectedTemplate.tsx @@ -1,6 +1,6 @@ import { type FC } from "react"; import type { Template, TemplateExample } from "api/typesGenerated"; -import { Avatar } from "components/Avatar/Avatar"; +import { ExternalAvatar } from "components/Avatar/Avatar"; import { Stack } from "components/Stack/Stack"; import { type Interpolation, type Theme } from "@emotion/react"; @@ -16,13 +16,13 @@ export const SelectedTemplate: FC = ({ template }) => { css={styles.template} alignItems="center" > - {template.name} - + diff --git a/site/src/pages/IconsPage/IconsPage.stories.tsx b/site/src/pages/IconsPage/IconsPage.stories.tsx new file mode 100644 index 0000000000..4f012d4425 --- /dev/null +++ b/site/src/pages/IconsPage/IconsPage.stories.tsx @@ -0,0 +1,17 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { chromatic } from "testHelpers/chromatic"; +import { IconsPage } from "./IconsPage"; + +const meta: Meta = { + title: "pages/IconsPage", + parameters: { chromatic }, + component: IconsPage, + args: {}, +}; + +export default meta; +type Story = StoryObj; + +const Example: Story = {}; + +export { Example as IconsPage }; diff --git a/site/src/pages/IconsPage/IconsPage.tsx b/site/src/pages/IconsPage/IconsPage.tsx index 4122a828ba..53e378f846 100644 --- a/site/src/pages/IconsPage/IconsPage.tsx +++ b/site/src/pages/IconsPage/IconsPage.tsx @@ -19,6 +19,10 @@ import { } from "components/PageHeader/PageHeader"; import { Stack } from "components/Stack/Stack"; import icons from "theme/icons.json"; +import { + defaultParametersForBuiltinIcons, + parseImageParameters, +} from "theme/externalImages"; import { pageTitle } from "utils/page"; const iconsWithoutSuffix = icons.map((icon) => icon.split(".")[0]); @@ -163,13 +167,19 @@ export const IconsPage: FC = () => { {icon.url}
= { title: "pages/StarterTemplatePage", + parameters: { chromatic }, component: StarterTemplatePageView, }; diff --git a/site/src/pages/StarterTemplatePage/StarterTemplatePageView.tsx b/site/src/pages/StarterTemplatePage/StarterTemplatePageView.tsx index 91a0c44614..2907f97b5b 100644 --- a/site/src/pages/StarterTemplatePage/StarterTemplatePageView.tsx +++ b/site/src/pages/StarterTemplatePage/StarterTemplatePageView.tsx @@ -15,6 +15,7 @@ import { Stack } from "components/Stack/Stack"; import { Link } from "react-router-dom"; import { ErrorAlert } from "components/Alert/ErrorAlert"; import type { TemplateExample } from "api/typesGenerated"; +import { ExternalImage } from "components/ExternalImage/ExternalImage"; export interface StarterTemplatePageViewProps { starterTemplate?: TemplateExample; @@ -78,7 +79,7 @@ export const StarterTemplatePageView: FC = ({ }, }} > - +
{starterTemplate.name} diff --git a/site/src/pages/StarterTemplatesPage/StarterTemplatesPageView.stories.tsx b/site/src/pages/StarterTemplatesPage/StarterTemplatesPageView.stories.tsx index 6f648f376b..b57a8b60ee 100644 --- a/site/src/pages/StarterTemplatesPage/StarterTemplatesPageView.stories.tsx +++ b/site/src/pages/StarterTemplatesPage/StarterTemplatesPageView.stories.tsx @@ -1,3 +1,4 @@ +import { chromatic } from "testHelpers/chromatic"; import { mockApiError, MockTemplateExample, @@ -9,6 +10,7 @@ import type { Meta, StoryObj } from "@storybook/react"; const meta: Meta = { title: "pages/StarterTemplatesPage", + parameters: { chromatic }, component: StarterTemplatesPageView, }; diff --git a/site/src/pages/TemplateSettingsPage/Sidebar.tsx b/site/src/pages/TemplateSettingsPage/Sidebar.tsx index 4a7944a5d7..306b02bc05 100644 --- a/site/src/pages/TemplateSettingsPage/Sidebar.tsx +++ b/site/src/pages/TemplateSettingsPage/Sidebar.tsx @@ -10,6 +10,7 @@ import { SidebarHeader, SidebarNavItem, } from "components/Sidebar/Sidebar"; +import { ExternalImage } from "components/ExternalImage/ExternalImage"; interface SidebarProps { template: Template; @@ -19,7 +20,11 @@ export const Sidebar: FC = ({ template }) => { return ( } + avatar={ + + + + } title={template.display_name || template.name} linkTo={`/templates/${template.name}`} subtitle={template.name} diff --git a/site/src/pages/TemplatesPage/TemplatesPageView.tsx b/site/src/pages/TemplatesPage/TemplatesPageView.tsx index d0321d11a7..276a77ea5f 100644 --- a/site/src/pages/TemplatesPage/TemplatesPageView.tsx +++ b/site/src/pages/TemplatesPage/TemplatesPageView.tsx @@ -39,7 +39,7 @@ import { EmptyTemplates } from "./EmptyTemplates"; import { useClickableTableRow } from "hooks/useClickableTableRow"; import type { Template, TemplateExample } from "api/typesGenerated"; import ArrowForwardOutlined from "@mui/icons-material/ArrowForwardOutlined"; -import { Avatar } from "components/Avatar/Avatar"; +import { ExternalAvatar } from "components/Avatar/Avatar"; import { ErrorAlert } from "components/Alert/ErrorAlert"; import { docs } from "utils/docs"; import Skeleton from "@mui/material/Skeleton"; @@ -108,7 +108,9 @@ const TemplateRow: FC = ({ template }) => { } subtitle={template.description} avatar={ - hasIcon && + hasIcon && ( + + ) } /> diff --git a/site/src/pages/TerminalPage/TerminalPage.tsx b/site/src/pages/TerminalPage/TerminalPage.tsx index 0271c1ec24..e86d874006 100644 --- a/site/src/pages/TerminalPage/TerminalPage.tsx +++ b/site/src/pages/TerminalPage/TerminalPage.tsx @@ -33,6 +33,8 @@ import { PopoverContent, PopoverTrigger, } from "components/Popover/Popover"; +import { ThemeOverride } from "contexts/ThemeProvider"; +import themes from "theme"; export const Language = { workspaceErrorMessagePrefix: "Unable to fetch workspace: ", @@ -293,7 +295,7 @@ const TerminalPage: FC = () => { ]); return ( - <> + {workspace.data @@ -314,7 +316,7 @@ const TerminalPage: FC = () => { <BottomBar proxy={selectedProxy} latency={latency.latencyMS} /> )} </div> - </> + </ThemeOverride> ); }; diff --git a/site/src/pages/WorkspacePage/Workspace.tsx b/site/src/pages/WorkspacePage/Workspace.tsx index a8f989b346..e19f485aa9 100644 --- a/site/src/pages/WorkspacePage/Workspace.tsx +++ b/site/src/pages/WorkspacePage/Workspace.tsx @@ -66,7 +66,7 @@ export interface WorkspaceProps { /** * Workspace is the top-level component for viewing an individual workspace */ -export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({ +export const Workspace: FC<WorkspaceProps> = ({ handleStart, handleStop, handleRestart, diff --git a/site/src/pages/WorkspacePage/WorkspaceTopbar.tsx b/site/src/pages/WorkspacePage/WorkspaceTopbar.tsx index 0a01c1bab4..375e47a22b 100644 --- a/site/src/pages/WorkspacePage/WorkspaceTopbar.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceTopbar.tsx @@ -29,7 +29,7 @@ import PersonOutline from "@mui/icons-material/PersonOutline"; import { Popover, PopoverTrigger } from "components/Popover/Popover"; import { HelpTooltipContent } from "components/HelpTooltip/HelpTooltip"; import { AvatarData } from "components/AvatarData/AvatarData"; -import { Avatar } from "components/Avatar/Avatar"; +import { ExternalAvatar } from "components/Avatar/Avatar"; export type WorkspaceError = | "getBuildsError" @@ -176,7 +176,7 @@ export const WorkspaceTopbar = (props: WorkspaceProps) => { } avatar={ workspace.template_icon !== "" && ( - <Avatar + <ExternalAvatar src={workspace.template_icon} variant="square" fitImage diff --git a/site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx b/site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx index ea71dc8d0b..1b0bf8c814 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx @@ -165,6 +165,25 @@ export const AllStates: Story = { }, }; +const icons = [ + "/icon/code.svg", + "/icon/aws.svg", + "/icon/docker-white.svg", + "/icon/docker.svg", + "", + "/icon/doesntexist.svg", +]; + +export const Icons: Story = { + args: { + workspaces: allWorkspaces.map((workspace, i) => ({ + ...workspace, + template_icon: icons[i % icons.length], + })), + count: allWorkspaces.length, + }, +}; + export const OwnerHasNoWorkspaces: Story = { args: { workspaces: [], diff --git a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx index afe6372b16..9a32bf5af3 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx @@ -17,7 +17,7 @@ import { } from "components/TableLoader/TableLoader"; import { useClickableTableRow } from "hooks/useClickableTableRow"; import { AvatarData } from "components/AvatarData/AvatarData"; -import { Avatar } from "components/Avatar/Avatar"; +import { ExternalAvatar } from "components/Avatar/Avatar"; import { Stack } from "components/Stack/Stack"; import { LastUsed } from "pages/WorkspacesPage/LastUsed"; import { WorkspaceOutdatedTooltip } from "components/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip"; @@ -165,7 +165,7 @@ export const WorkspacesTable: FC<WorkspacesTableProps> = ({ } subtitle={workspace.owner_name} avatar={ - <Avatar + <ExternalAvatar src={workspace.template_icon} variant={ workspace.template_icon ? "square" : undefined @@ -173,7 +173,7 @@ export const WorkspacesTable: FC<WorkspacesTableProps> = ({ fitImage={Boolean(workspace.template_icon)} > {workspace.name} - </Avatar> + </ExternalAvatar> } /> </div> diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 1f303f4ee8..51f2d7f7d8 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -2244,7 +2244,7 @@ export const MockTemplateExample: TypesGen.TemplateExample = { description: "Get started with Linux development on AWS ECS.", markdown: "\n# aws-ecs\n\nThis is a sample template for running a Coder workspace on ECS. It assumes there\nis a pre-existing ECS cluster with EC2-based compute to host the workspace.\n\n## Architecture\n\nThis workspace is built using the following AWS resources:\n\n- Task definition - the container definition, includes the image, command, volume(s)\n- ECS service - manages the task definition\n\n## code-server\n\n`code-server` is installed via the `startup_script` argument in the `coder_agent`\nresource block. The `coder_app` resource is defined to access `code-server` through\nthe dashboard UI over `localhost:13337`.\n", - icon: "/icon/aws.png", + icon: "/icon/aws.svg", tags: ["aws", "cloud"], }; @@ -2255,7 +2255,7 @@ export const MockTemplateExample2: TypesGen.TemplateExample = { description: "Get started with Linux development on AWS EC2.", markdown: '\n# aws-linux\n\nTo get started, run `coder templates init`. When prompted, select this template.\nFollow the on-screen instructions to proceed.\n\n## Authentication\n\nThis template assumes that coderd is run in an environment that is authenticated\nwith AWS. For example, run `aws configure import` to import credentials on the\nsystem and user running coderd. For other ways to authenticate [consult the\nTerraform docs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication-and-configuration).\n\n## Required permissions / policy\n\nThe following sample policy allows Coder to create EC2 instances and modify\ninstances provisioned by Coder:\n\n```json\n{\n "Version": "2012-10-17",\n "Statement": [\n {\n "Sid": "VisualEditor0",\n "Effect": "Allow",\n "Action": [\n "ec2:GetDefaultCreditSpecification",\n "ec2:DescribeIamInstanceProfileAssociations",\n "ec2:DescribeTags",\n "ec2:CreateTags",\n "ec2:RunInstances",\n "ec2:DescribeInstanceCreditSpecifications",\n "ec2:DescribeImages",\n "ec2:ModifyDefaultCreditSpecification",\n "ec2:DescribeVolumes"\n ],\n "Resource": "*"\n },\n {\n "Sid": "CoderResources",\n "Effect": "Allow",\n "Action": [\n "ec2:DescribeInstances",\n "ec2:DescribeInstanceAttribute",\n "ec2:UnmonitorInstances",\n "ec2:TerminateInstances",\n "ec2:StartInstances",\n "ec2:StopInstances",\n "ec2:DeleteTags",\n "ec2:MonitorInstances",\n "ec2:CreateTags",\n "ec2:RunInstances",\n "ec2:ModifyInstanceAttribute",\n "ec2:ModifyInstanceCreditSpecification"\n ],\n "Resource": "arn:aws:ec2:*:*:instance/*",\n "Condition": {\n "StringEquals": {\n "aws:ResourceTag/Coder_Provisioned": "true"\n }\n }\n }\n ]\n}\n```\n\n## code-server\n\n`code-server` is installed via the `startup_script` argument in the `coder_agent`\nresource block. The `coder_app` resource is defined to access `code-server` through\nthe dashboard UI over `localhost:13337`.\n', - icon: "/icon/aws.png", + icon: "/icon/aws.svg", tags: ["aws", "cloud"], }; diff --git a/site/src/theme/dark/index.ts b/site/src/theme/dark/index.ts index 7c487ee146..6f72e1bad4 100644 --- a/site/src/theme/dark/index.ts +++ b/site/src/theme/dark/index.ts @@ -1,9 +1,11 @@ import experimental from "./experimental"; import monaco from "./monaco"; import muiTheme from "./mui"; +import { forDarkThemes } from "../externalImages"; export default { ...muiTheme, experimental, monaco, + externalImages: forDarkThemes, }; diff --git a/site/src/theme/darkBlue/index.ts b/site/src/theme/darkBlue/index.ts index 7c487ee146..6f72e1bad4 100644 --- a/site/src/theme/darkBlue/index.ts +++ b/site/src/theme/darkBlue/index.ts @@ -1,9 +1,11 @@ import experimental from "./experimental"; import monaco from "./monaco"; import muiTheme from "./mui"; +import { forDarkThemes } from "../externalImages"; export default { ...muiTheme, experimental, monaco, + externalImages: forDarkThemes, }; diff --git a/site/src/theme/externalImages.test.ts b/site/src/theme/externalImages.test.ts new file mode 100644 index 0000000000..ee2f83771d --- /dev/null +++ b/site/src/theme/externalImages.test.ts @@ -0,0 +1,85 @@ +import { + forDarkThemes, + forLightThemes, + getExternalImageStylesFromUrl, + parseImageParameters, +} from "./externalImages"; + +describe("externalImage parameters", () => { + test("default parameters", () => { + // Correctly selects default + const widgetsStyles = getExternalImageStylesFromUrl( + forDarkThemes, + "/icon/widgets.svg", + ); + expect(widgetsStyles).toBe(forDarkThemes.monochrome); + + // Allows overrides + const overrideStyles = getExternalImageStylesFromUrl( + forDarkThemes, + "/icon/widgets.svg?fullcolor", + ); + expect(overrideStyles).toBe(forDarkThemes.fullcolor); + + // Not actually a built-in + const someoneElsesWidgetsStyles = getExternalImageStylesFromUrl( + forDarkThemes, + "https://example.com/icon/widgets.svg", + ); + expect(someoneElsesWidgetsStyles).toBeUndefined(); + }); + + test("blackWithColor brightness", () => { + const tryCase = (params: string) => + parseImageParameters(forDarkThemes, params); + + const withDecimalValue = tryCase("?blackWithColor&brightness=1.5"); + expect(withDecimalValue?.filter).toBe( + "invert(1) hue-rotate(180deg) brightness(1.5)", + ); + + const withPercentageValue = tryCase("?blackWithColor&brightness=150%"); + expect(withPercentageValue?.filter).toBe( + "invert(1) hue-rotate(180deg) brightness(150%)", + ); + + // Sketchy `brightness` value will be ignored. + const niceTry = tryCase( + "?blackWithColor&brightness=</style><script>alert('leet hacking');</script>", + ); + expect(niceTry?.filter).toBe("invert(1) hue-rotate(180deg)"); + + const withLightTheme = parseImageParameters( + forLightThemes, + "?blackWithColor&brightness=1.5", + ); + expect(withLightTheme).toBeUndefined(); + }); + + test("whiteWithColor brightness", () => { + const tryCase = (params: string) => + parseImageParameters(forLightThemes, params); + + const withDecimalValue = tryCase("?whiteWithColor&brightness=1.5"); + expect(withDecimalValue?.filter).toBe( + "invert(1) hue-rotate(180deg) brightness(1.5)", + ); + + const withPercentageValue = tryCase("?whiteWithColor&brightness=150%"); + expect(withPercentageValue?.filter).toBe( + "invert(1) hue-rotate(180deg) brightness(150%)", + ); + + // Sketchy `brightness` value will be ignored. + const niceTry = tryCase( + "?whiteWithColor&brightness=</style><script>alert('leet hacking');</script>", + ); + expect(niceTry?.filter).toBe("invert(1) hue-rotate(180deg)"); + + const withDarkTheme = parseImageParameters( + forDarkThemes, + "?whiteWithColor&brightness=1.5", + ); + expect(withDarkTheme).toBeUndefined(); + }); +}); diff --git a/site/src/theme/externalImages.ts b/site/src/theme/externalImages.ts new file mode 100644 index 0000000000..9d55cd20d4 --- /dev/null +++ b/site/src/theme/externalImages.ts @@ -0,0 +1,163 @@ +import { type CSSObject } from "@emotion/react"; + +export type ExternalImageMode = keyof ExternalImageModeStyles; + +export interface ExternalImageModeStyles { + /** + * monochrome icons will be flattened to a neutral, theme-appropriate color. + * eg. white, light gray, dark gray, black + */ + monochrome?: CSSObject; + /** + * @default + * fullcolor icons should look their best of any background, with distinct colors + * and good contrast. This is the default, and won't alter the image. + */ + fullcolor?: CSSObject; + /** + * whiteWithColor is useful for icons that are primarily white, or contain white text, + * which are hard to see or look incorrect on light backgrounds. This setting will apply + * a color-respecting inversion filter to turn white into black when appropriate to + * improve contrast. + * You can also specify a `brightness` level if your icon still doesn't look quite right. + * eg. /icon/aws.svg?blackWithColor&brightness=1.5 + */ + whiteWithColor?: CSSObject; + /** + * blackWithColor is useful for icons that are primarily black, or contain black text, + * which are hard to see or look incorrect on dark backgrounds. This setting will apply + * a color-respecting inversion filter to turn black into white when appropriate to + * improve contrast. + * You can also specify a `brightness` level if your icon still doesn't look quite right. + * eg. /icon/aws.svg?blackWithColor&brightness=1.5 + */ + blackWithColor?: CSSObject; +} + +export const forDarkThemes: ExternalImageModeStyles = { + // brighten icons a little to make sure they have good contrast with the background + monochrome: { filter: "grayscale(100%) contrast(0%) brightness(250%)" }, + // do nothing to full-color icons + fullcolor: undefined, + // white on a dark background βœ… + whiteWithColor: undefined, + // black on a dark background πŸ†˜: invert, and then correct colors + blackWithColor: { filter: "invert(1) hue-rotate(180deg)" }, +}; + +export const forLightThemes: ExternalImageModeStyles = { + // darken icons a little to make sure they have good contrast with the background + monochrome: { filter: "grayscale(100%) contrast(0%) brightness(70%)" }, + // do nothing to full-color icons + fullcolor: undefined, + // black on a dark background πŸ†˜: invert, and then correct colors + whiteWithColor: { filter: "invert(1) hue-rotate(180deg)" }, + // black on a light background βœ… + blackWithColor: undefined, +}; + +// multiplier matches the beginning of the string (^), a number, optionally followed +// followed by a decimal portion, optionally followed by a percent symbol, and the +// end of the string ($). +const multiplier = /^\d+(\.\d+)?%?$/; + +/** + * Used with `whiteWithColor` and `blackWithColor` to allow for finer tuning + */ +const parseInvertFilterParameters = ( + params: URLSearchParams, + baseStyles?: CSSObject, +) => { + // Only apply additional styles if the current theme supports this mode + if (!baseStyles) { + return; + } + + let extraStyles: CSSObject | undefined; + + const brightness = params.get("brightness"); + if (multiplier.test(brightness!)) { + let filter = baseStyles.filter ?? ""; + filter += ` brightness(${brightness})`; + extraStyles = { ...extraStyles, filter }; + } + + if (!extraStyles) { + return baseStyles; + } + + return { + ...baseStyles, + ...extraStyles, + }; +}; + +export function parseImageParameters( + modes: ExternalImageModeStyles, + searchString: string, +): CSSObject | undefined { + const params = new URLSearchParams(searchString); + + let styles: CSSObject | undefined = modes.fullcolor; + + if (params.has("monochrome")) { + styles = modes.monochrome; + } else if (params.has("whiteWithColor")) { + styles = parseInvertFilterParameters(params, modes.whiteWithColor); + } else if (params.has("blackWithColor")) { + styles = parseInvertFilterParameters(params, modes.blackWithColor); + } + + return styles; +} + +export function getExternalImageStylesFromUrl( + modes: ExternalImageModeStyles, + urlString?: string, +) { + if (!urlString) { + return undefined; + } + + const url = new URL(urlString, location.origin); + + if (url.search) { + return parseImageParameters(modes, url.search); + } + + if ( + url.origin === location.origin && + defaultParametersForBuiltinIcons.has(url.pathname) + ) { + return parseImageParameters( + modes, + defaultParametersForBuiltinIcons.get(url.pathname)!, + ); + } + + return undefined; +} + +/** + * defaultModeForBuiltinIcons contains modes for all of our built-in icons that + * don't look their best in all of our themes with the default fullcolor mode. + */ +export const defaultParametersForBuiltinIcons = new Map<string, string>([ + ["/icon/apple-black.svg", "monochrome"], + ["/icon/aws.png", "whiteWithColor&brightness=1.5"], + ["/icon/aws.svg", "blackWithColor&brightness=1.5"], + ["/icon/aws-monochrome.svg", "monochrome"], + ["/icon/coder.svg", "monochrome"], + ["/icon/container.svg", "monochrome"], + ["/icon/database.svg", "monochrome"], + ["/icon/docker-white.svg", "monochrome"], + ["/icon/folder.svg", "monochrome"], + ["/icon/github.svg", "monochrome"], + ["/icon/image.svg", "monochrome"], + ["/icon/jupyter.svg", "blackWithColor"], + ["/icon/kasmvnc.svg", "whiteWithColor"], + ["/icon/memory.svg", "monochrome"], + ["/icon/rust.svg", "monochrome"], + ["/icon/terminal.svg", "monochrome"], + ["/icon/widgets.svg", "monochrome"], +]); diff --git a/site/src/theme/icons.json b/site/src/theme/icons.json index 6dd21ba4e6..374efc2937 100644 --- a/site/src/theme/icons.json +++ b/site/src/theme/icons.json @@ -5,6 +5,7 @@ "apple-grey.svg", "aws-dark.svg", "aws-light.svg", + "aws-monochrome.svg", "aws.png", "aws.svg", "azure-devops.svg", @@ -53,6 +54,7 @@ "memory.svg", "microsoft.svg", "node.svg", + "nodejs.svg", "nomad.svg", "novnc.svg", "okta.svg", diff --git a/site/src/theme/index.ts b/site/src/theme/index.ts index bb7a620582..1edbd51688 100644 --- a/site/src/theme/index.ts +++ b/site/src/theme/index.ts @@ -4,10 +4,12 @@ import dark from "./dark"; import darkBlue from "./darkBlue"; import light from "./light"; import type { NewTheme } from "./experimental"; +import type { ExternalImageModeStyles } from "./externalImages"; export interface Theme extends MuiTheme { experimental: NewTheme; monaco: monaco.editor.IStandaloneThemeData; + externalImages: ExternalImageModeStyles; } export const DEFAULT_THEME = "dark"; diff --git a/site/src/theme/light/index.ts b/site/src/theme/light/index.ts index 7c487ee146..2a421171c6 100644 --- a/site/src/theme/light/index.ts +++ b/site/src/theme/light/index.ts @@ -1,9 +1,11 @@ import experimental from "./experimental"; import monaco from "./monaco"; import muiTheme from "./mui"; +import { forLightThemes } from "../externalImages"; export default { ...muiTheme, experimental, monaco, + externalImages: forLightThemes, }; diff --git a/site/static/icon/aws-monochrome.svg b/site/static/icon/aws-monochrome.svg new file mode 100644 index 0000000000..d915493de6 --- /dev/null +++ b/site/static/icon/aws-monochrome.svg @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 19.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + viewBox="0 0 304 182" style="enable-background:new 0 0 304 182;" xml:space="preserve"> +<style type="text/css"> + .st0{fill:#fff;} + .st1{fill-rule:evenodd;clip-rule:evenodd;fill:#fff;} +</style> +<g> + <path class="st0" d="M86.4,66.4c0,3.7,0.4,6.7,1.1,8.9c0.8,2.2,1.8,4.6,3.2,7.2c0.5,0.8,0.7,1.6,0.7,2.3c0,1-0.6,2-1.9,3l-6.3,4.2 + c-0.9,0.6-1.8,0.9-2.6,0.9c-1,0-2-0.5-3-1.4C76.2,90,75,88.4,74,86.8c-1-1.7-2-3.6-3.1-5.9c-7.8,9.2-17.6,13.8-29.4,13.8 + c-8.4,0-15.1-2.4-20-7.2c-4.9-4.8-7.4-11.2-7.4-19.2c0-8.5,3-15.4,9.1-20.6c6.1-5.2,14.2-7.8,24.5-7.8c3.4,0,6.9,0.3,10.6,0.8 + c3.7,0.5,7.5,1.3,11.5,2.2v-7.3c0-7.6-1.6-12.9-4.7-16c-3.2-3.1-8.6-4.6-16.3-4.6c-3.5,0-7.1,0.4-10.8,1.3c-3.7,0.9-7.3,2-10.8,3.4 + c-1.6,0.7-2.8,1.1-3.5,1.3c-0.7,0.2-1.2,0.3-1.6,0.3c-1.4,0-2.1-1-2.1-3.1v-4.9c0-1.6,0.2-2.8,0.7-3.5c0.5-0.7,1.4-1.4,2.8-2.1 + c3.5-1.8,7.7-3.3,12.6-4.5c4.9-1.3,10.1-1.9,15.6-1.9c11.9,0,20.6,2.7,26.2,8.1c5.5,5.4,8.3,13.6,8.3,24.6V66.4z M45.8,81.6 + c3.3,0,6.7-0.6,10.3-1.8c3.6-1.2,6.8-3.4,9.5-6.4c1.6-1.9,2.8-4,3.4-6.4c0.6-2.4,1-5.3,1-8.7v-4.2c-2.9-0.7-6-1.3-9.2-1.7 + c-3.2-0.4-6.3-0.6-9.4-0.6c-6.7,0-11.6,1.3-14.9,4c-3.3,2.7-4.9,6.5-4.9,11.5c0,4.7,1.2,8.2,3.7,10.6 + C37.7,80.4,41.2,81.6,45.8,81.6z M126.1,92.4c-1.8,0-3-0.3-3.8-1c-0.8-0.6-1.5-2-2.1-3.9L96.7,10.2c-0.6-2-0.9-3.3-0.9-4 + c0-1.6,0.8-2.5,2.4-2.5h9.8c1.9,0,3.2,0.3,3.9,1c0.8,0.6,1.4,2,2,3.9l16.8,66.2l15.6-66.2c0.5-2,1.1-3.3,1.9-3.9c0.8-0.6,2.2-1,4-1 + h8c1.9,0,3.2,0.3,4,1c0.8,0.6,1.5,2,1.9,3.9l15.8,67l17.3-67c0.6-2,1.3-3.3,2-3.9c0.8-0.6,2.1-1,3.9-1h9.3c1.6,0,2.5,0.8,2.5,2.5 + c0,0.5-0.1,1-0.2,1.6c-0.1,0.6-0.3,1.4-0.7,2.5l-24.1,77.3c-0.6,2-1.3,3.3-2.1,3.9c-0.8,0.6-2.1,1-3.8,1h-8.6c-1.9,0-3.2-0.3-4-1 + c-0.8-0.7-1.5-2-1.9-4L156,23l-15.4,64.4c-0.5,2-1.1,3.3-1.9,4c-0.8,0.7-2.2,1-4,1H126.1z M254.6,95.1c-5.2,0-10.4-0.6-15.4-1.8 + c-5-1.2-8.9-2.5-11.5-4c-1.6-0.9-2.7-1.9-3.1-2.8c-0.4-0.9-0.6-1.9-0.6-2.8v-5.1c0-2.1,0.8-3.1,2.3-3.1c0.6,0,1.2,0.1,1.8,0.3 + c0.6,0.2,1.5,0.6,2.5,1c3.4,1.5,7.1,2.7,11,3.5c4,0.8,7.9,1.2,11.9,1.2c6.3,0,11.2-1.1,14.6-3.3c3.4-2.2,5.2-5.4,5.2-9.5 + c0-2.8-0.9-5.1-2.7-7c-1.8-1.9-5.2-3.6-10.1-5.2L246,52c-7.3-2.3-12.7-5.7-16-10.2c-3.3-4.4-5-9.3-5-14.5c0-4.2,0.9-7.9,2.7-11.1 + c1.8-3.2,4.2-6,7.2-8.2c3-2.3,6.4-4,10.4-5.2c4-1.2,8.2-1.7,12.6-1.7c2.2,0,4.5,0.1,6.7,0.4c2.3,0.3,4.4,0.7,6.5,1.1 + c2,0.5,3.9,1,5.7,1.6c1.8,0.6,3.2,1.2,4.2,1.8c1.4,0.8,2.4,1.6,3,2.5c0.6,0.8,0.9,1.9,0.9,3.3v4.7c0,2.1-0.8,3.2-2.3,3.2 + c-0.8,0-2.1-0.4-3.8-1.2c-5.7-2.6-12.1-3.9-19.2-3.9c-5.7,0-10.2,0.9-13.3,2.8c-3.1,1.9-4.7,4.8-4.7,8.9c0,2.8,1,5.2,3,7.1 + c2,1.9,5.7,3.8,11,5.5l14.2,4.5c7.2,2.3,12.4,5.5,15.5,9.6c3.1,4.1,4.6,8.8,4.6,14c0,4.3-0.9,8.2-2.6,11.6 + c-1.8,3.4-4.2,6.4-7.3,8.8c-3.1,2.5-6.8,4.3-11.1,5.6C264.4,94.4,259.7,95.1,254.6,95.1z"/> + <g> + <path class="st1" d="M273.5,143.7c-32.9,24.3-80.7,37.2-121.8,37.2c-57.6,0-109.5-21.3-148.7-56.7c-3.1-2.8-0.3-6.6,3.4-4.4 + c42.4,24.6,94.7,39.5,148.8,39.5c36.5,0,76.6-7.6,113.5-23.2C274.2,133.6,278.9,139.7,273.5,143.7z"/> + <path class="st1" d="M287.2,128.1c-4.2-5.4-27.8-2.6-38.5-1.3c-3.2,0.4-3.7-2.4-0.8-4.5c18.8-13.2,49.7-9.4,53.3-5 + c3.6,4.5-1,35.4-18.6,50.2c-2.7,2.3-5.3,1.1-4.1-1.9C282.5,155.7,291.4,133.4,287.2,128.1z"/> + </g> +</g> +</svg> diff --git a/site/static/icon/coder.svg b/site/static/icon/coder.svg index 3bb941d9e9..f77e5cbb92 100644 --- a/site/static/icon/coder.svg +++ b/site/static/icon/coder.svg @@ -1,8 +1,8 @@ -<svg width="66" height="48" viewBox="0 0 66 48" fill="none" xmlns="http://www.w3.org/2000/svg"> -<path d="M64.3029 20.8302C62.9894 20.8302 62.1144 20.0449 62.1144 18.4331V9.17517C62.1144 3.26504 59.7268 0 53.5592 0H50.6941V6.24078H51.5697C53.9968 6.24078 55.1508 7.60467 55.1508 10.0431V18.2264C55.1508 21.7807 56.1853 23.2273 58.4535 23.9713C56.1853 24.6739 55.1508 26.1617 55.1508 29.716C55.1508 31.7412 55.1508 33.7663 55.1508 35.7916C55.1508 37.4861 55.1508 39.1393 54.7131 40.8337C54.2754 42.4044 53.5592 43.8922 52.5644 45.1733C52.0073 45.9174 51.3707 46.5373 50.6545 47.116V47.9425H53.5193C59.687 47.9425 62.0746 44.6774 62.0746 38.7672V29.5094C62.0746 27.8562 62.9103 27.1123 64.2634 27.1123H65.8944V20.8714H64.3029V20.8302Z" fill="#D9D9D9"/> -<path d="M44.8049 9.42443H35.9712C35.7722 9.42443 35.6131 9.25912 35.6131 9.05247V8.34987C35.6131 8.14322 35.7722 7.97791 35.9712 7.97791H44.8447C45.0436 7.97791 45.2028 8.14322 45.2028 8.34987V9.05247C45.2028 9.25912 45.0038 9.42443 44.8049 9.42443Z" fill="#D9D9D9"/> -<path d="M46.3171 18.3513H39.871C39.672 18.3513 39.5128 18.1859 39.5128 17.9792V17.2767C39.5128 17.0701 39.672 16.9047 39.871 16.9047H46.3171C46.5161 16.9047 46.6752 17.0701 46.6752 17.2767V17.9792C46.6752 18.1446 46.5161 18.3513 46.3171 18.3513Z" fill="#D9D9D9"/> -<path d="M48.8636 13.8879H35.9712C35.7722 13.8879 35.6131 13.7226 35.6131 13.5159V12.8133C35.6131 12.6067 35.7722 12.4413 35.9712 12.4413H48.8237C49.0228 12.4413 49.182 12.6067 49.182 12.8133V13.5159C49.182 13.6812 49.0626 13.8879 48.8636 13.8879Z" fill="#D9D9D9"/> -<path d="M25.7449 11.4483C26.6203 11.4483 27.4958 11.531 28.3313 11.7377V10.0431C28.3313 7.64602 29.5251 6.24078 31.9126 6.24078H32.7879V0H29.923C23.7552 0 21.3679 3.26504 21.3679 9.17517V12.2336C22.7605 11.7377 24.2329 11.4483 25.7449 11.4483Z" fill="#D9D9D9"/> -<path d="M51.5695 33.9308C50.9329 28.6819 47.0333 24.3009 42.0196 23.3089C40.6269 23.0197 39.2342 22.9783 37.8813 23.2263C37.8415 23.2263 37.8415 23.1849 37.8018 23.1849C35.6132 18.4321 30.9179 15.291 25.8246 15.291C20.7313 15.291 16.0757 18.3494 13.8474 23.1023C13.8076 23.1023 13.8076 23.1437 13.7678 23.1437C12.3353 22.9783 10.9028 23.0609 9.47035 23.433C4.5362 24.6728 0.795835 28.9711 0.119377 34.1786C0.039787 34.7159 0 35.2532 0 35.7492C0 37.3196 1.03457 38.7662 2.54664 38.9729C4.41683 39.2623 6.04827 37.7743 6.00848 35.8732C6.00848 35.5838 6.00848 35.2532 6.04827 34.9639C6.36659 32.3188 8.31638 30.087 10.863 29.467C11.6589 29.2604 12.4547 29.2191 13.2107 29.3432C15.638 29.6738 18.0255 28.3925 19.06 26.1607C19.8161 24.5075 21.0098 23.0609 22.6015 22.2757C24.3522 21.4077 26.3418 21.2838 28.1723 21.9452C30.0822 22.6477 31.5146 24.1355 32.3901 25.9953C33.3053 27.814 33.743 29.0951 35.6928 29.3432C36.4886 29.467 38.7169 29.4257 39.5526 29.3844C41.184 29.3844 42.8154 29.963 43.9694 31.1616C44.7254 31.9881 45.2825 33.0214 45.5213 34.1786C45.8793 36.0385 45.4417 37.8983 44.3673 39.3035C43.6112 40.2954 42.5767 41.0394 41.4227 41.37C40.8656 41.5354 40.3085 41.5766 39.7514 41.5766C39.4332 41.5766 38.9955 41.5766 38.4782 41.5766C36.8866 41.5766 33.5043 41.5766 30.9576 41.5766C29.2069 41.5766 27.8141 40.1302 27.8141 38.3116V26.2019C27.8141 25.7061 27.4162 25.2928 26.9387 25.2928H25.7052C23.2778 25.334 21.3281 28.1446 21.3281 31.1202C21.3281 34.096 21.3281 41.99 21.3281 41.99C21.3281 45.2137 23.8349 47.8175 26.9387 47.8175C26.9387 47.8175 40.7464 47.7761 40.9452 47.7761C44.1285 47.4454 47.0731 45.751 49.0626 43.1472C51.0522 40.6261 51.9674 37.3196 51.5695 33.9308Z" fill="#D9D9D9"/> +<svg width="66" height="48" viewBox="0 0 66 48" fill="#fff" xmlns="http://www.w3.org/2000/svg"> +<path d="M64.3029 20.8302C62.9894 20.8302 62.1144 20.0449 62.1144 18.4331V9.17517C62.1144 3.26504 59.7268 0 53.5592 0H50.6941V6.24078H51.5697C53.9968 6.24078 55.1508 7.60467 55.1508 10.0431V18.2264C55.1508 21.7807 56.1853 23.2273 58.4535 23.9713C56.1853 24.6739 55.1508 26.1617 55.1508 29.716C55.1508 31.7412 55.1508 33.7663 55.1508 35.7916C55.1508 37.4861 55.1508 39.1393 54.7131 40.8337C54.2754 42.4044 53.5592 43.8922 52.5644 45.1733C52.0073 45.9174 51.3707 46.5373 50.6545 47.116V47.9425H53.5193C59.687 47.9425 62.0746 44.6774 62.0746 38.7672V29.5094C62.0746 27.8562 62.9103 27.1123 64.2634 27.1123H65.8944V20.8714H64.3029V20.8302Z" /> +<path d="M44.8049 9.42443H35.9712C35.7722 9.42443 35.6131 9.25912 35.6131 9.05247V8.34987C35.6131 8.14322 35.7722 7.97791 35.9712 7.97791H44.8447C45.0436 7.97791 45.2028 8.14322 45.2028 8.34987V9.05247C45.2028 9.25912 45.0038 9.42443 44.8049 9.42443Z" /> +<path d="M46.3171 18.3513H39.871C39.672 18.3513 39.5128 18.1859 39.5128 17.9792V17.2767C39.5128 17.0701 39.672 16.9047 39.871 16.9047H46.3171C46.5161 16.9047 46.6752 17.0701 46.6752 17.2767V17.9792C46.6752 18.1446 46.5161 18.3513 46.3171 18.3513Z" /> +<path d="M48.8636 13.8879H35.9712C35.7722 13.8879 35.6131 13.7226 35.6131 13.5159V12.8133C35.6131 12.6067 35.7722 12.4413 35.9712 12.4413H48.8237C49.0228 12.4413 49.182 12.6067 49.182 12.8133V13.5159C49.182 13.6812 49.0626 13.8879 48.8636 13.8879Z" /> +<path d="M25.7449 11.4483C26.6203 11.4483 27.4958 11.531 28.3313 11.7377V10.0431C28.3313 7.64602 29.5251 6.24078 31.9126 6.24078H32.7879V0H29.923C23.7552 0 21.3679 3.26504 21.3679 9.17517V12.2336C22.7605 11.7377 24.2329 11.4483 25.7449 11.4483Z" /> +<path d="M51.5695 33.9308C50.9329 28.6819 47.0333 24.3009 42.0196 23.3089C40.6269 23.0197 39.2342 22.9783 37.8813 23.2263C37.8415 23.2263 37.8415 23.1849 37.8018 23.1849C35.6132 18.4321 30.9179 15.291 25.8246 15.291C20.7313 15.291 16.0757 18.3494 13.8474 23.1023C13.8076 23.1023 13.8076 23.1437 13.7678 23.1437C12.3353 22.9783 10.9028 23.0609 9.47035 23.433C4.5362 24.6728 0.795835 28.9711 0.119377 34.1786C0.039787 34.7159 0 35.2532 0 35.7492C0 37.3196 1.03457 38.7662 2.54664 38.9729C4.41683 39.2623 6.04827 37.7743 6.00848 35.8732C6.00848 35.5838 6.00848 35.2532 6.04827 34.9639C6.36659 32.3188 8.31638 30.087 10.863 29.467C11.6589 29.2604 12.4547 29.2191 13.2107 29.3432C15.638 29.6738 18.0255 28.3925 19.06 26.1607C19.8161 24.5075 21.0098 23.0609 22.6015 22.2757C24.3522 21.4077 26.3418 21.2838 28.1723 21.9452C30.0822 22.6477 31.5146 24.1355 32.3901 25.9953C33.3053 27.814 33.743 29.0951 35.6928 29.3432C36.4886 29.467 38.7169 29.4257 39.5526 29.3844C41.184 29.3844 42.8154 29.963 43.9694 31.1616C44.7254 31.9881 45.2825 33.0214 45.5213 34.1786C45.8793 36.0385 45.4417 37.8983 44.3673 39.3035C43.6112 40.2954 42.5767 41.0394 41.4227 41.37C40.8656 41.5354 40.3085 41.5766 39.7514 41.5766C39.4332 41.5766 38.9955 41.5766 38.4782 41.5766C36.8866 41.5766 33.5043 41.5766 30.9576 41.5766C29.2069 41.5766 27.8141 40.1302 27.8141 38.3116V26.2019C27.8141 25.7061 27.4162 25.2928 26.9387 25.2928H25.7052C23.2778 25.334 21.3281 28.1446 21.3281 31.1202C21.3281 34.096 21.3281 41.99 21.3281 41.99C21.3281 45.2137 23.8349 47.8175 26.9387 47.8175C26.9387 47.8175 40.7464 47.7761 40.9452 47.7761C44.1285 47.4454 47.0731 45.751 49.0626 43.1472C51.0522 40.6261 51.9674 37.3196 51.5695 33.9308Z"/> </svg> diff --git a/site/static/icon/debian.svg b/site/static/icon/debian.svg index 99f210168a..50dcb70c8f 100644 --- a/site/static/icon/debian.svg +++ b/site/static/icon/debian.svg @@ -1,8 +1,86 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg xmlns="http://www.w3.org/2000/svg" width="108.76" height="144.13" viewBox="0 0 108.758 144.133"> - <path fill="#D70751" d="M60.969 47.645c-1.494.02.281.768 2.232 1.069.541-.422 1.027-.846 1.463-1.26-1.213.297-2.449.304-3.695.191m8.017-1.999c.893-1.229 1.541-2.573 1.77-3.963-.201.99-.736 1.845-1.244 2.749-2.793 1.759-.264-1.044-.002-2.111-3.002 3.783-.414 2.268-.524 3.325m2.963-7.704c.182-2.691-.529-1.839-.768-.814.278.146.499 1.898.768.814M55.301 1.163c.798.142 1.724.252 1.591.443.876-.193 1.073-.367-1.591-.443m1.592.443-.561.117.523-.048.038-.069"/> - <path fill="#D70751" d="M81.762 38.962c.09 2.416-.705 3.59-1.424 5.666l-1.293.643c-1.057 2.054.105 1.304-.652 2.937-1.652 1.467-5.006 4.589-6.08 4.875-.785-.017.531-.926.703-1.281-2.209 1.516-1.773 2.276-5.152 3.199l-.098-.221c-8.33 3.92-19.902-3.847-19.75-14.443-.088.672-.253.504-.437.774-.43-5.451 2.518-10.926 7.49-13.165 4.863-2.406 10.564-1.42 14.045 1.829-1.912-2.506-5.721-5.163-10.232-4.917-4.421.072-8.558 2.881-9.938 5.932-2.264 1.425-2.528 5.496-3.514 6.242-1.329 9.76 2.497 13.975 8.97 18.936 1.016.686.286.791.422 1.313-2.15-1.006-4.118-2.526-5.738-4.387.86 1.257 1.787 2.479 2.986 3.439-2.029-.685-4.738-4.913-5.527-5.085 3.495 6.258 14.178 10.975 19.775 8.634-2.59.096-5.879.053-8.787-1.022-1.225-.629-2.884-1.93-2.587-2.173 7.636 2.851 15.522 2.158 22.128-3.137 1.682-1.31 3.518-3.537 4.049-3.567-.799 1.202.137.578-.477 1.639 1.672-2.701-.729-1.1 1.73-4.664l.908 1.25c-.34-2.244 2.785-4.966 2.467-8.512.717-1.084.799 1.168.039 3.662 1.055-2.767.279-3.212.549-5.496.291.768.678 1.583.875 2.394-.688-2.675.703-4.503 1.049-6.058-.342-.15-1.061 1.182-1.227-1.976.025-1.372.383-.719.52-1.057-.268-.155-.975-1.207-1.404-3.224.309-.475.832 1.229 1.256 1.298-.273-1.603-.742-2.826-.762-4.057-1.24-2.59-.439.346-1.443-1.112-1.32-4.114 1.094-.955 1.258-2.823 1.998 2.895 3.137 7.385 3.662 9.244-.4-2.267-1.045-4.464-1.834-6.589.609.257-.979-4.663.791-1.405C87.189 15.552 81 9.062 75.305 6.018c.695.637 1.574 1.437 1.26 1.563-2.834-1.685-2.336-1.818-2.742-2.53-2.305-.939-2.459.077-3.984.002-4.35-2.308-5.188-2.063-9.191-3.507l.182.852c-2.881-.96-3.357.362-6.47.002-.189-.147.998-.536 1.976-.677-2.786.368-2.656-.55-5.382.101.671-.471 1.383-.784 2.099-1.184-2.271.138-5.424 1.322-4.451.244-3.705 1.654-10.286 3.975-13.979 7.438l-.116-.776c-1.692 2.031-7.379 6.066-7.832 8.699l-.453.105c-.879 1.491-1.45 3.18-2.148 4.713-1.151 1.963-1.688.756-1.524 1.064-2.265 4.592-3.392 8.45-4.363 11.616.692 1.035.017 6.232.278 10.391-1.136 20.544 14.418 40.489 31.42 45.093 2.492.893 6.197.861 9.349.949-3.718-1.064-4.198-.563-7.822-1.826-2.613-1.232-3.185-2.637-5.037-4.244l.733 1.295c-3.63-1.285-2.111-1.59-5.065-2.525l.783-1.021c-1.177-.09-3.117-1.982-3.647-3.033l-1.288.051c-1.546-1.906-2.371-3.283-2.31-4.35l-.416.742c-.471-.809-5.691-7.158-2.983-5.68-.503-.458-1.172-.747-1.897-2.066l.551-.629c-1.301-1.677-2.398-3.826-2.314-4.542.695.938 1.177 1.114 1.655 1.275-3.291-8.164-3.476-.449-5.967-8.31l.526-.042c-.403-.611-.65-1.27-.974-1.919l.23-2.285c-2.368-2.736-.662-11.645-.319-16.53.235-1.986 1.977-4.101 3.3-7.418l-.806-.138c1.542-2.688 8.802-10.799 12.166-10.383 1.629-2.046-.324-.008-.643-.522 3.579-3.703 4.704-2.616 7.119-3.283 2.603-1.545-2.235.604-1.001-.589 4.503-1.149 3.19-2.614 9.063-3.197.62.352-1.437.544-1.953 1.001 3.75-1.836 11.869-1.417 17.145 1.018 6.117 2.861 12.994 11.314 13.266 19.267l.309.083c-.156 3.162.484 6.819-.627 10.177l.751-1.591"/> - <path fill="#D70751" d="m44.658 49.695-.211 1.047c.983 1.335 1.763 2.781 3.016 3.821-.902-1.759-1.571-2.486-2.805-4.868m2.321-.09c-.52-.576-.826-1.268-1.172-1.956.33 1.211 1.006 2.252 1.633 3.312l-.461-1.356m41.084-8.93-.219.552c-.402 2.858-1.273 5.686-2.605 8.309 1.472-2.767 2.421-5.794 2.824-8.861M55.598.446C56.607.077 58.08.243 59.154 0c-1.398.117-2.789.187-4.162.362l.606.084M20.127 19.308c.233 2.154-1.62 2.991.41 1.569 1.09-2.454-.424-.677-.41-1.569m-2.388 9.974c.469-1.437.553-2.299.732-3.132-1.293 1.654-.596 2.007-.732 3.132"/> - <path d="M13.437 125.51c-.045.047-.045 7.506-.138 9.453-.092 1.574-.232 4.957-3.568 4.957-3.429 0-4.263-3.939-4.541-5.652-.324-1.9-.324-3.477-.324-4.17 0-2.225.139-8.436 5.375-8.436 1.576 0 2.456.465 3.151.834l.045 3.02zM0 130.98c0 13.066 6.951 13.066 7.97 13.066 2.873 0 4.727-1.576 5.514-4.309l.093 4.123c.881-.047 1.761-.139 3.197-.139.51 0 .926 0 1.298.047.371 0 .741.045 1.158.092-.741-1.482-1.297-4.818-1.297-12.049 0-7.043 0-18.951.602-22.566-1.667.789-3.105 1.299-6.256 1.576 1.251 1.344 1.251 2.039 1.251 8.154-.879-.277-1.992-.602-3.892-.602-8.294 0-9.638 7.23-9.638 12.61m25.13-2.373c.047-3.846.835-7.275 4.124-7.275 3.615 0 3.891 3.984 3.799 7.275H25.13zm12.51.46c0-5.422-1.065-10.752-7.923-10.752-9.452 0-9.452 10.475-9.452 12.697 0 9.406 4.216 13.113 11.306 13.113 3.149 0 4.68-.461 5.514-.695-.046-1.668.185-2.734.465-4.17-.975.604-2.226 1.391-5.006 1.391-7.229 0-7.322-6.582-7.322-8.852H37.55l.09-2.74m15.075 2.008c0 4.309-.787 10.102-6.162 10.102-.742 0-1.668-.141-2.27-.279-.093-1.668-.093-4.541-.093-7.877 0-3.986.416-6.068.742-7.09.972-3.289 3.15-3.334 3.566-3.334 3.522 0 4.217 4.86 4.217 8.48zm-13.298 5.05c0 3.43 0 5.375-.556 6.857 1.9.742 4.262 1.158 7.09 1.158 1.807 0 7.043 0 9.869-5.791 1.344-2.688 1.807-6.303 1.807-9.037 0-1.668-.186-5.328-1.529-7.646-1.296-2.176-3.382-3.289-5.605-3.289-4.449 0-5.746 3.707-6.44 5.607 0-2.363.045-10.611.415-14.828-3.011 1.391-4.866 1.621-6.857 1.807 1.807.74 1.807 3.801 1.807 13.764v11.397m27.117 7.741c-.928-.139-1.578-.232-2.922-.232-1.48 0-2.502.094-3.566.232.463-.881.648-1.299.787-4.309.186-4.125.232-15.154-.092-17.471-.232-1.762-.648-2.039-1.297-2.502 3.799-.371 4.865-.648 6.625-1.482-.369 2.037-.418 3.059-.418 6.162-.091 15.98-.138 17.7.883 19.6m14.838-13.118c-.092 2.92-.139 4.959-.928 6.58-.973 2.086-2.594 2.688-3.799 2.688-2.783 0-3.383-2.316-3.383-4.586 0-4.355 3.893-4.682 5.652-4.682h2.458zm-12.744 5.7c0 2.92.881 5.838 3.477 7.09 1.158.51 2.316.51 2.688.51 4.264 0 5.699-3.152 6.58-5.098-.047 2.039 0 3.289.139 4.912.834-.047 1.668-.139 3.059-.139.787 0 1.529.092 2.316.139-.51-.787-.787-1.252-.928-3.059-.092-1.76-.092-3.521-.092-5.977l.047-9.453c0-3.523-.928-6.998-7.879-6.998-4.586 0-7.273 1.391-8.617 2.086.557 1.02 1.02 1.898 1.436 3.893 1.809-1.576 4.172-2.41 6.58-2.41 3.848 0 3.848 2.549 3.848 6.162-.881-.045-1.623-.137-2.875-.137-5.887.01-9.779 2.28-9.779 8.49m39.431 2.819c.047 1.576.047 3.244.695 4.588-1.021-.092-1.623-.232-3.521-.232-1.113 0-1.715.094-2.596.232.184-.602.279-.834.371-1.623.139-1.064.232-4.633.232-5.885v-5.004c0-2.178 0-5.33-.141-6.441-.092-.787-.322-2.918-3.012-2.918-2.641 0-3.521 1.945-3.846 3.521-.369 1.621-.369 3.383-.369 10.24.045 5.932.045 6.486.508 8.109-.787-.092-1.76-.184-3.15-.184-1.113 0-1.854.045-2.779.184.324-.742.51-1.113.602-3.707.094-2.549.279-15.061-.141-18.025-.23-1.809-.695-2.225-1.203-2.688 3.754-.186 4.957-.789 6.117-1.389v4.91c.555-1.438 1.713-4.635 6.348-4.635 5.793 0 5.838 4.217 5.885 6.996v13.928"/> - <path fill="#D70751" d="m66.926 111.53-3.838 3.836-3.836-3.836 3.836-3.836 3.838 3.84"/> -</svg> \ No newline at end of file +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 10.0, SVG Export Plug-In . SVG Version: 3.0.0 Build 77) --> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd" [ + <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/"> + <!ENTITY ns_extend "http://ns.adobe.com/Extensibility/1.0/"> + <!ENTITY ns_ai "http://ns.adobe.com/AdobeIllustrator/10.0/"> + <!ENTITY ns_graphs "http://ns.adobe.com/Graphs/1.0/"> + <!ENTITY ns_vars "http://ns.adobe.com/Variables/1.0/"> + <!ENTITY ns_imrep "http://ns.adobe.com/ImageReplacement/1.0/"> + <!ENTITY ns_sfw "http://ns.adobe.com/SaveForWeb/1.0/"> + <!ENTITY ns_custom "http://ns.adobe.com/GenericCustomNamespace/1.0/"> + <!ENTITY ns_adobe_xpath "http://ns.adobe.com/XPath/1.0/"> + <!ENTITY ns_svg "http://www.w3.org/2000/svg"> + <!ENTITY ns_xlink "http://www.w3.org/1999/xlink"> +]> +<svg + xmlns:x="&ns_extend;" xmlns:i="&ns_ai;" xmlns:graph="&ns_graphs;" i:viewOrigin="262 450" i:rulerOrigin="0 0" i:pageBounds="0 792 612 0" + xmlns="&ns_svg;" xmlns:xlink="&ns_xlink;" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/" + width="87.041" height="108.445" viewBox="0 0 87.041 108.445" overflow="visible" enable-background="new 0 0 87.041 108.445" + xml:space="preserve"> + <metadata> + <variableSets xmlns="&ns_vars;"> + <variableSet varSetName="binding1" locked="none"> + <variables></variables> + <v:sampleDataSets xmlns="&ns_custom;" xmlns:v="&ns_vars;"></v:sampleDataSets> + </variableSet> + </variableSets> + <sfw xmlns="&ns_sfw;"> + <slices></slices> + <sliceSourceBounds y="341.555" x="262" width="87.041" height="108.445" bottomLeftOrigin="true"></sliceSourceBounds> + </sfw> + </metadata> + <g id="Layer_1" i:layer="yes" i:dimmedPercent="50" i:rgbTrio="#4F008000FFFF"> + <g> + <path i:knockout="Off" fill="#A80030" d="M51.986,57.297c-1.797,0.025,0.34,0.926,2.686,1.287 + c0.648-0.506,1.236-1.018,1.76-1.516C54.971,57.426,53.484,57.434,51.986,57.297"/> + <path i:knockout="Off" fill="#A80030" d="M61.631,54.893c1.07-1.477,1.85-3.094,2.125-4.766c-0.24,1.192-0.887,2.221-1.496,3.307 + c-3.359,2.115-0.316-1.256-0.002-2.537C58.646,55.443,61.762,53.623,61.631,54.893"/> + <path i:knockout="Off" fill="#A80030" d="M65.191,45.629c0.217-3.236-0.637-2.213-0.924-0.978 + C64.602,44.825,64.867,46.932,65.191,45.629"/> + <path i:knockout="Off" fill="#A80030" d="M45.172,1.399c0.959,0.172,2.072,0.304,1.916,0.533 + C48.137,1.702,48.375,1.49,45.172,1.399"/> + <path i:knockout="Off" fill="#A80030" d="M47.088,1.932l-0.678,0.14l0.631-0.056L47.088,1.932"/> + <path i:knockout="Off" fill="#A80030" d="M76.992,46.856c0.107,2.906-0.85,4.316-1.713,6.812l-1.553,0.776 + c-1.271,2.468,0.123,1.567-0.787,3.53c-1.984,1.764-6.021,5.52-7.313,5.863c-0.943-0.021,0.639-1.113,0.846-1.541 + c-2.656,1.824-2.131,2.738-6.193,3.846l-0.119-0.264c-10.018,4.713-23.934-4.627-23.751-17.371 + c-0.107,0.809-0.304,0.607-0.526,0.934c-0.517-6.557,3.028-13.143,9.007-15.832c5.848-2.895,12.704-1.707,16.893,2.197 + c-2.301-3.014-6.881-6.209-12.309-5.91c-5.317,0.084-10.291,3.463-11.951,7.131c-2.724,1.715-3.04,6.611-4.227,7.507 + C31.699,56.271,36.3,61.342,44.083,67.307c1.225,0.826,0.345,0.951,0.511,1.58c-2.586-1.211-4.954-3.039-6.901-5.277 + c1.033,1.512,2.148,2.982,3.589,4.137c-2.438-0.826-5.695-5.908-6.646-6.115c4.203,7.525,17.052,13.197,23.78,10.383 + c-3.113,0.115-7.068,0.064-10.566-1.229c-1.469-0.756-3.467-2.322-3.11-2.615c9.182,3.43,18.667,2.598,26.612-3.771 + c2.021-1.574,4.229-4.252,4.867-4.289c-0.961,1.445,0.164,0.695-0.574,1.971c2.014-3.248-0.875-1.322,2.082-5.609l1.092,1.504 + c-0.406-2.696,3.348-5.97,2.967-10.234c0.861-1.304,0.961,1.403,0.047,4.403c1.268-3.328,0.334-3.863,0.66-6.609 + c0.352,0.923,0.814,1.904,1.051,2.878c-0.826-3.216,0.848-5.416,1.262-7.285c-0.408-0.181-1.275,1.422-1.473-2.377 + c0.029-1.65,0.459-0.865,0.625-1.271c-0.324-0.186-1.174-1.451-1.691-3.877c0.375-0.57,1.002,1.478,1.512,1.562 + c-0.328-1.929-0.893-3.4-0.916-4.88c-1.49-3.114-0.527,0.415-1.736-1.337c-1.586-4.947,1.316-1.148,1.512-3.396 + c2.404,3.483,3.775,8.881,4.404,11.117c-0.48-2.726-1.256-5.367-2.203-7.922c0.73,0.307-1.176-5.609,0.949-1.691 + c-2.27-8.352-9.715-16.156-16.564-19.818c0.838,0.767,1.896,1.73,1.516,1.881c-3.406-2.028-2.807-2.186-3.295-3.043 + c-2.775-1.129-2.957,0.091-4.795,0.002c-5.23-2.774-6.238-2.479-11.051-4.217l0.219,1.023c-3.465-1.154-4.037,0.438-7.782,0.004 + c-0.228-0.178,1.2-0.644,2.375-0.815c-3.35,0.442-3.193-0.66-6.471,0.122c0.808-0.567,1.662-0.942,2.524-1.424 + c-2.732,0.166-6.522,1.59-5.352,0.295c-4.456,1.988-12.37,4.779-16.811,8.943l-0.14-0.933c-2.035,2.443-8.874,7.296-9.419,10.46 + l-0.544,0.127c-1.059,1.793-1.744,3.825-2.584,5.67c-1.385,2.36-2.03,0.908-1.833,1.278c-2.724,5.523-4.077,10.164-5.246,13.97 + c0.833,1.245,0.02,7.495,0.335,12.497c-1.368,24.704,17.338,48.69,37.785,54.228c2.997,1.072,7.454,1.031,11.245,1.141 + c-4.473-1.279-5.051-0.678-9.408-2.197c-3.143-1.48-3.832-3.17-6.058-5.102l0.881,1.557c-4.366-1.545-2.539-1.912-6.091-3.037 + l0.941-1.229c-1.415-0.107-3.748-2.385-4.386-3.646l-1.548,0.061c-1.86-2.295-2.851-3.949-2.779-5.23l-0.5,0.891 + c-0.567-0.973-6.843-8.607-3.587-6.83c-0.605-0.553-1.409-0.9-2.281-2.484l0.663-0.758c-1.567-2.016-2.884-4.6-2.784-5.461 + c0.836,1.129,1.416,1.34,1.99,1.533c-3.957-9.818-4.179-0.541-7.176-9.994l0.634-0.051c-0.486-0.732-0.781-1.527-1.172-2.307 + l0.276-2.75C4.667,58.121,6.719,47.409,7.13,41.534c0.285-2.389,2.378-4.932,3.97-8.92l-0.97-0.167 + c1.854-3.234,10.586-12.988,14.63-12.486c1.959-2.461-0.389-0.009-0.772-0.629c4.303-4.453,5.656-3.146,8.56-3.947 + c3.132-1.859-2.688,0.725-1.203-0.709c5.414-1.383,3.837-3.144,10.9-3.846c0.745,0.424-1.729,0.655-2.35,1.205 + c4.511-2.207,14.275-1.705,20.617,1.225c7.359,3.439,15.627,13.605,15.953,23.17l0.371,0.1 + c-0.188,3.802,0.582,8.199-0.752,12.238L76.992,46.856"/> + <path i:knockout="Off" fill="#A80030" d="M32.372,59.764l-0.252,1.26c1.181,1.604,2.118,3.342,3.626,4.596 + C34.661,63.502,33.855,62.627,32.372,59.764"/> + <path i:knockout="Off" fill="#A80030" d="M35.164,59.654c-0.625-0.691-0.995-1.523-1.409-2.352 + c0.396,1.457,1.207,2.709,1.962,3.982L35.164,59.654"/> + <path i:knockout="Off" fill="#A80030" d="M84.568,48.916l-0.264,0.662c-0.484,3.438-1.529,6.84-3.131,9.994 + C82.943,56.244,84.088,52.604,84.568,48.916"/> + <path i:knockout="Off" fill="#A80030" d="M45.527,0.537C46.742,0.092,48.514,0.293,49.803,0c-1.68,0.141-3.352,0.225-5.003,0.438 + L45.527,0.537"/> + <path i:knockout="Off" fill="#A80030" d="M2.872,23.219c0.28,2.592-1.95,3.598,0.494,1.889 + C4.676,22.157,2.854,24.293,2.872,23.219"/> + <path i:knockout="Off" fill="#A80030" d="M0,35.215c0.563-1.728,0.665-2.766,0.88-3.766C-0.676,33.438,0.164,33.862,0,35.215"/> + </g> + </g> +</svg> diff --git a/site/static/icon/nodejs.svg b/site/static/icon/nodejs.svg new file mode 100644 index 0000000000..11f4f963c8 --- /dev/null +++ b/site/static/icon/nodejs.svg @@ -0,0 +1,46 @@ +<svg width="121" height="121" viewBox="0 0 121 121" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M62.8005 6.66987C62.0404 6.23103 61.1782 6 60.3005 6C59.4228 6 58.5606 6.23103 57.8005 6.66987L15.5005 31.0699C14.73 31.5146 14.0922 32.1568 13.6527 32.9302C13.2133 33.7037 12.9881 34.5804 13.0005 35.4699V84.0699C12.9881 84.9594 13.2133 85.8361 13.6527 86.6095C14.0922 87.383 14.73 88.0252 15.5005 88.4699L57.6005 112.77C58.3606 113.209 59.2228 113.44 60.1005 113.44C60.9782 113.44 61.8404 113.209 62.6005 112.77L104.8 88.4699C105.59 88.0381 106.248 87.4015 106.705 86.6272C107.163 85.8528 107.403 84.9693 107.4 84.0699V35.3699C107.403 34.4704 107.163 33.587 106.705 32.8126C106.248 32.0382 105.59 31.4016 104.8 30.9699L62.8005 6.66987Z" fill="url(#paint0_linear_1_2)"/> +<mask id="mask0_1_2" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="13" y="6" width="95" height="108"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M62.8005 6.66987C62.0404 6.23103 61.1782 6 60.3005 6C59.4228 6 58.5606 6.23103 57.8005 6.66987L15.5005 31.0699C14.73 31.5146 14.0922 32.1568 13.6527 32.9302C13.2133 33.7037 12.9881 34.5804 13.0005 35.4699V84.0699C12.9881 84.9594 13.2133 85.8361 13.6527 86.6095C14.0922 87.383 14.73 88.0252 15.5005 88.4699L57.6005 112.77C58.3606 113.209 59.2228 113.44 60.1005 113.44C60.9782 113.44 61.8404 113.209 62.6005 112.77L104.8 88.4699C105.59 88.0381 106.248 87.4015 106.705 86.6272C107.163 85.8528 107.403 84.9693 107.4 84.0699V35.3699C107.403 34.4704 107.163 33.587 106.705 32.8126C106.248 32.0382 105.59 31.4016 104.8 30.9699L62.8005 6.66987Z" fill="white"/> +</mask> +<g mask="url(#mask0_1_2)"> +<path d="M105 31.0699L62.8005 6.66988C62.3926 6.44337 61.955 6.27507 61.5005 6.16988L14.0005 87.2699C14.4025 87.7396 14.8748 88.1444 15.4005 88.4699L57.8005 112.77C58.9849 113.441 60.3851 113.62 61.7005 113.27L106.2 31.8699C105.84 31.5209 105.437 31.2185 105 30.9699V31.0699Z" fill="url(#paint1_linear_1_2)"/> +<path d="M105 88.4699C105.607 88.127 106.14 87.6679 106.569 87.1188C106.998 86.5697 107.315 85.9414 107.5 85.2699L61.2005 6.06987C59.9674 5.82836 58.6886 6.04149 57.6005 6.66987L15.7005 30.8699L61.0005 113.47C61.6736 113.363 62.3191 113.125 62.9005 112.77L105 88.4699Z" fill="url(#paint2_linear_1_2)"/> +<path d="M105 88.4699L62.9005 112.77C62.3191 113.125 61.6736 113.363 61.0005 113.47L61.8005 114.97L108.7 87.8699V87.1699L107.5 85.1699C107.327 85.8596 107.016 86.5071 106.586 87.0741C106.157 87.641 105.617 88.1157 105 88.4699Z" fill="url(#paint3_linear_1_2)"/> +<path d="M105 88.4699L62.9005 112.77C62.3191 113.125 61.6736 113.363 61.0005 113.47L61.8005 114.97L108.7 87.8699V87.1699L107.5 85.1699C107.327 85.8596 107.016 86.5071 106.586 87.0741C106.157 87.641 105.617 88.1157 105 88.4699Z" fill="url(#paint4_linear_1_2)"/> +</g> +<defs> +<linearGradient id="paint0_linear_1_2" x1="77.4005" y1="24.7699" x2="39.3005" y2="102.47" gradientUnits="userSpaceOnUse"> +<stop stop-color="#3F873F"/> +<stop offset="0.3" stop-color="#3F8B3D"/> +<stop offset="0.6" stop-color="#3E9637"/> +<stop offset="0.9" stop-color="#3DA92E"/> +<stop offset="1" stop-color="#3DAE2B"/> +</linearGradient> +<linearGradient id="paint1_linear_1_2" x1="53.9005" y1="65.3699" x2="160.7" y2="-13.5301" gradientUnits="userSpaceOnUse"> +<stop offset="0.1" stop-color="#3F873F"/> +<stop offset="0.4" stop-color="#529F44"/> +<stop offset="0.7" stop-color="#63B649"/> +<stop offset="0.9" stop-color="#6ABF4B"/> +</linearGradient> +<linearGradient id="paint2_linear_1_2" x1="11.6005" y1="59.7699" x2="109" y2="59.7699" gradientUnits="userSpaceOnUse"> +<stop offset="0.1" stop-color="#6ABF4B"/> +<stop offset="0.3" stop-color="#63B649"/> +<stop offset="0.6" stop-color="#529F44"/> +<stop offset="0.9" stop-color="#3F873F"/> +</linearGradient> +<linearGradient id="paint3_linear_1_2" x1="11.6005" y1="100.07" x2="109" y2="100.07" gradientUnits="userSpaceOnUse"> +<stop offset="0.1" stop-color="#6ABF4B"/> +<stop offset="0.3" stop-color="#63B649"/> +<stop offset="0.6" stop-color="#529F44"/> +<stop offset="0.9" stop-color="#3F873F"/> +</linearGradient> +<linearGradient id="paint4_linear_1_2" x1="123.2" y1="22.4699" x2="63.1005" y2="145.07" gradientUnits="userSpaceOnUse"> +<stop stop-color="#3F873F"/> +<stop offset="0.3" stop-color="#3F8B3D"/> +<stop offset="0.6" stop-color="#3E9637"/> +<stop offset="0.9" stop-color="#3DA92E"/> +<stop offset="1" stop-color="#3DAE2B"/> +</linearGradient> +</defs> +</svg>