mirror of
https://github.com/coder/coder.git
synced 2025-07-15 22:20:27 +00:00
feat(site): Add support for insiders channel to "VS Code Desktop" button (#7730)
This commit is contained in:
committed by
GitHub
parent
784696dfa5
commit
b80756e4f5
@ -4,7 +4,7 @@ export const VSCodeIcon = (props: SvgIconProps) => (
|
||||
<SvgIcon {...props} viewBox="0 0 100 100">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="none">
|
||||
<mask
|
||||
id="mask0"
|
||||
id="vscode_mask0"
|
||||
mask-type="alpha"
|
||||
maskUnits="userSpaceOnUse"
|
||||
x="0"
|
||||
@ -19,18 +19,18 @@ export const VSCodeIcon = (props: SvgIconProps) => (
|
||||
fill="white"
|
||||
/>
|
||||
</mask>
|
||||
<g mask="url(#mask0)">
|
||||
<g mask="url(#vscode_mask0)">
|
||||
<path
|
||||
d="M96.4614 10.7962L75.8569 0.875542C73.4719 -0.272773 70.6217 0.211611 68.75 2.08333L1.29858 63.5832C-0.515693 65.2373 -0.513607 68.0937 1.30308 69.7452L6.81272 74.754C8.29793 76.1042 10.5347 76.2036 12.1338 74.9905L93.3609 13.3699C96.086 11.3026 100 13.2462 100 16.6667V16.4275C100 14.0265 98.6246 11.8378 96.4614 10.7962Z"
|
||||
fill="#0065A9"
|
||||
/>
|
||||
<g filter="url(#filter0_d)">
|
||||
<g filter="url(#vscode_filter0_d)">
|
||||
<path
|
||||
d="M96.4614 89.2038L75.8569 99.1245C73.4719 100.273 70.6217 99.7884 68.75 97.9167L1.29858 36.4169C-0.515693 34.7627 -0.513607 31.9063 1.30308 30.2548L6.81272 25.246C8.29793 23.8958 10.5347 23.7964 12.1338 25.0095L93.3609 86.6301C96.086 88.6974 100 86.7538 100 83.3334V83.5726C100 85.9735 98.6246 88.1622 96.4614 89.2038Z"
|
||||
fill="#007ACC"
|
||||
/>
|
||||
</g>
|
||||
<g filter="url(#filter1_d)">
|
||||
<g filter="url(#vscode_filter1_d)">
|
||||
<path
|
||||
d="M75.8578 99.1263C73.4721 100.274 70.6219 99.7885 68.75 97.9166C71.0564 100.223 75 98.5895 75 95.3278V4.67213C75 1.41039 71.0564 -0.223106 68.75 2.08329C70.6219 0.211402 73.4721 -0.273666 75.8578 0.873633L96.4587 10.7807C98.6234 11.8217 100 14.0112 100 16.4132V83.5871C100 85.9891 98.6234 88.1786 96.4586 89.2196L75.8578 99.1263Z"
|
||||
fill="#1F9CF0"
|
||||
@ -46,13 +46,13 @@ export const VSCodeIcon = (props: SvgIconProps) => (
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M70.8511 99.3171C72.4261 99.9306 74.2221 99.8913 75.8117 99.1264L96.4 89.2197C98.5634 88.1787 99.9392 85.9892 99.9392 83.5871V16.4133C99.9392 14.0112 98.5635 11.8217 96.4001 10.7807L75.8117 0.873695C73.7255 -0.13019 71.2838 0.115699 69.4527 1.44688C69.1912 1.63705 68.942 1.84937 68.7082 2.08335L29.2943 38.0414L12.1264 25.0096C10.5283 23.7964 8.29285 23.8959 6.80855 25.246L1.30225 30.2548C-0.513334 31.9064 -0.515415 34.7627 1.29775 36.4169L16.1863 50L1.29775 63.5832C-0.515415 65.2374 -0.513334 68.0937 1.30225 69.7452L6.80855 74.754C8.29285 76.1042 10.5283 76.2036 12.1264 74.9905L29.2943 61.9586L68.7082 97.9167C69.3317 98.5405 70.0638 99.0104 70.8511 99.3171ZM74.9544 27.2989L45.0483 50L74.9544 72.7012V27.2989Z"
|
||||
fill="url(#paint0_linear)"
|
||||
fill="url(#vscode_paint0_linear)"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_d"
|
||||
id="vscode_filter0_d"
|
||||
x="-8.39411"
|
||||
y="15.8291"
|
||||
width="116.727"
|
||||
@ -85,7 +85,7 @@ export const VSCodeIcon = (props: SvgIconProps) => (
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter1_d"
|
||||
id="vscode_filter1_d"
|
||||
x="60.4167"
|
||||
y="-8.07558"
|
||||
width="47.9167"
|
||||
@ -118,7 +118,7 @@ export const VSCodeIcon = (props: SvgIconProps) => (
|
||||
/>
|
||||
</filter>
|
||||
<linearGradient
|
||||
id="paint0_linear"
|
||||
id="vscode_paint0_linear"
|
||||
x1="49.9392"
|
||||
y1="0.257812"
|
||||
x2="49.9392"
|
||||
|
126
site/src/components/Icons/VSCodeInsidersIcon.tsx
Normal file
126
site/src/components/Icons/VSCodeInsidersIcon.tsx
Normal file
@ -0,0 +1,126 @@
|
||||
import SvgIcon, { SvgIconProps } from "@mui/material/SvgIcon"
|
||||
|
||||
export const VSCodeInsidersIcon = (props: SvgIconProps) => (
|
||||
<SvgIcon {...props} viewBox="0 0 256 256">
|
||||
<svg
|
||||
width="256"
|
||||
height="256"
|
||||
viewBox="0 0 256 256"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<mask
|
||||
id="mask0"
|
||||
mask-type="alpha"
|
||||
maskUnits="userSpaceOnUse"
|
||||
x="0"
|
||||
y="0"
|
||||
width="256"
|
||||
height="256"
|
||||
>
|
||||
<path
|
||||
d="M176.049 250.669C180.838 255.459 188.13 256.7 194.234 253.764L246.94 228.419C252.478 225.755 256 220.154 256 214.008V42.1479C256 36.0025 252.478 30.4008 246.94 27.7374L194.234 2.39089C188.13 -0.544416 180.838 0.696607 176.049 5.48572C181.95 -0.41506 192.039 3.76413 192.039 12.1091V244.046C192.039 252.391 181.95 256.57 176.049 250.669Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M181.379 180.646L114.33 128.633L181.379 75.5114V17.794C181.379 10.8477 173.128 7.20673 167.996 11.8862L74.6514 97.8518L31.1994 64.1438C27.1081 61.039 21.3851 61.294 17.5853 64.7476L3.48974 77.5627C-1.15847 81.7893 -1.16367 89.0948 3.47672 93.3292L167.98 244.185C173.107 248.887 181.379 245.249 181.379 238.292V180.646Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M36.6937 134.195L3.47672 162.828C-1.16367 167.062 -1.15847 174.37 3.48974 178.594L17.5853 191.409C21.3851 194.863 27.1081 195.118 31.1994 192.013L69.4472 164.057L36.6937 134.195Z"
|
||||
fill="white"
|
||||
/>
|
||||
</mask>
|
||||
<g mask="url(#mask0)">
|
||||
<path
|
||||
d="M167.996 11.8857C173.128 7.20627 181.379 10.8473 181.379 17.7936V75.5109L104.938 136.073L65.5742 106.211L167.996 11.8857Z"
|
||||
fill="#009A7C"
|
||||
/>
|
||||
<path
|
||||
d="M36.6937 134.194L3.47672 162.827C-1.16367 167.062 -1.15847 174.37 3.48974 178.594L17.5853 191.409C21.3851 194.863 27.1081 195.118 31.1994 192.013L69.4472 164.056L36.6937 134.194Z"
|
||||
fill="#009A7C"
|
||||
/>
|
||||
<g filter="url(#filter0_d)">
|
||||
<path
|
||||
d="M181.379 180.645L31.1994 64.1427C27.1081 61.0379 21.3851 61.2929 17.5853 64.7465L3.48974 77.5616C-1.15847 81.7882 -1.16367 89.0937 3.47672 93.3281L167.972 244.176C173.102 248.881 181.379 245.241 181.379 238.28V180.645Z"
|
||||
fill="#00B294"
|
||||
/>
|
||||
</g>
|
||||
<g filter="url(#filter1_d)">
|
||||
<path
|
||||
d="M194.233 253.766C188.13 256.701 180.837 255.46 176.048 250.671C181.949 256.571 192.039 252.392 192.039 244.047V12.1103C192.039 3.76535 181.949 -0.413839 176.048 5.48694C180.837 0.697824 188.129 -0.543191 194.233 2.3921L246.939 27.7386C252.478 30.402 256 36.0037 256 42.1491V214.009C256 220.155 252.478 225.757 246.939 228.42L194.233 253.766Z"
|
||||
fill="#24BFA5"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_d"
|
||||
x="-21.3333"
|
||||
y="40.6413"
|
||||
width="224.045"
|
||||
height="226.988"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
/>
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="10.6667" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter1_d"
|
||||
x="154.715"
|
||||
y="-20.5169"
|
||||
width="122.618"
|
||||
height="297.191"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
/>
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="10.6667" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="overlay"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
)
|
@ -1,18 +1,27 @@
|
||||
import { makeStyles } from "@mui/styles"
|
||||
import Button, { ButtonProps } from "@mui/material/Button"
|
||||
import { FC, forwardRef } from "react"
|
||||
import { combineClasses } from "utils/combineClasses"
|
||||
|
||||
export const PrimaryAgentButton: FC<ButtonProps> = ({
|
||||
className,
|
||||
...props
|
||||
}) => {
|
||||
const styles = useStyles()
|
||||
|
||||
return (
|
||||
<Button
|
||||
className={combineClasses([styles.primaryButton, className])}
|
||||
color="neutral"
|
||||
{...props}
|
||||
sx={{
|
||||
backgroundColor: (theme) => theme.palette.background.default,
|
||||
"&:hover": {
|
||||
backgroundColor: (theme) => theme.palette.background.paper,
|
||||
},
|
||||
// Making them smaller since those icons don't have a padding around them
|
||||
"& .MuiButton-startIcon": {
|
||||
width: 12,
|
||||
height: 12,
|
||||
"& svg": { width: "100%", height: "100%" },
|
||||
},
|
||||
...props.sx,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@ -20,49 +29,6 @@ export const PrimaryAgentButton: FC<ButtonProps> = ({
|
||||
// eslint-disable-next-line react/display-name -- Name is inferred from variable name
|
||||
export const SecondaryAgentButton = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, ...props }, ref) => {
|
||||
const styles = useStyles()
|
||||
|
||||
return (
|
||||
<Button
|
||||
ref={ref}
|
||||
className={combineClasses([styles.secondaryButton, className])}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
return <Button ref={ref} className={className} {...props} />
|
||||
},
|
||||
)
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
primaryButton: {
|
||||
whiteSpace: "nowrap",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
height: 36,
|
||||
minHeight: 36,
|
||||
borderRadius: 4,
|
||||
fontWeight: 500,
|
||||
fontSize: 14,
|
||||
|
||||
"&:hover": {
|
||||
backgroundColor: `${theme.palette.background.paper} !important`,
|
||||
},
|
||||
|
||||
"& .MuiButton-startIcon": {
|
||||
width: 12,
|
||||
height: 12,
|
||||
marginRight: theme.spacing(1.5),
|
||||
|
||||
"& svg": {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
secondaryButton: {
|
||||
fontSize: 14,
|
||||
fontWeight: 500,
|
||||
height: 36,
|
||||
minHeight: 36,
|
||||
borderRadius: 4,
|
||||
},
|
||||
}))
|
||||
|
@ -45,9 +45,7 @@ export const TerminalLink: FC<React.PropsWithChildren<TerminalLinkProps>> = ({
|
||||
)
|
||||
}}
|
||||
>
|
||||
<SecondaryAgentButton size="small">
|
||||
{Language.linkText}
|
||||
</SecondaryAgentButton>
|
||||
<SecondaryAgentButton>{Language.linkText}</SecondaryAgentButton>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
@ -1,7 +1,13 @@
|
||||
import { FC, PropsWithChildren, useState, useRef } from "react"
|
||||
import { getApiKey } from "api/api"
|
||||
import { VSCodeIcon } from "components/Icons/VSCodeIcon"
|
||||
import { FC, PropsWithChildren, useState } from "react"
|
||||
import { VSCodeInsidersIcon } from "components/Icons/VSCodeInsidersIcon"
|
||||
import { PrimaryAgentButton } from "components/Resources/AgentButton"
|
||||
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"
|
||||
import ButtonGroup from "@mui/material/ButtonGroup"
|
||||
import { useLocalStorage } from "hooks"
|
||||
import Menu from "@mui/material/Menu"
|
||||
import MenuItem from "@mui/material/MenuItem"
|
||||
|
||||
export interface VSCodeDesktopButtonProps {
|
||||
userName: string
|
||||
@ -10,9 +16,103 @@ export interface VSCodeDesktopButtonProps {
|
||||
folderPath?: string
|
||||
}
|
||||
|
||||
type VSCodeVariant = "vscode" | "vscode-insiders"
|
||||
|
||||
const VARIANT_KEY = "vscode-variant"
|
||||
|
||||
export const VSCodeDesktopButton: FC<
|
||||
PropsWithChildren<VSCodeDesktopButtonProps>
|
||||
> = ({ userName, workspaceName, agentName, folderPath }) => {
|
||||
> = (props) => {
|
||||
const [isVariantMenuOpen, setIsVariantMenuOpen] = useState(false)
|
||||
const localStorage = useLocalStorage()
|
||||
const previousVariant = localStorage.getLocal(VARIANT_KEY)
|
||||
const [variant, setVariant] = useState<VSCodeVariant>(() => {
|
||||
if (!previousVariant) {
|
||||
return "vscode"
|
||||
}
|
||||
return previousVariant as VSCodeVariant
|
||||
})
|
||||
const menuAnchorRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const selectVariant = (variant: VSCodeVariant) => {
|
||||
localStorage.saveLocal(VARIANT_KEY, variant)
|
||||
setVariant(variant)
|
||||
setIsVariantMenuOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ButtonGroup
|
||||
ref={menuAnchorRef}
|
||||
variant="outlined"
|
||||
sx={{
|
||||
// Workaround to make the border transitions smmothly on button groups
|
||||
"& > button:hover + button": {
|
||||
borderLeft: "1px solid #FFF",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{variant === "vscode" ? (
|
||||
<VSCodeButton {...props} />
|
||||
) : (
|
||||
<VSCodeInsidersButton {...props} />
|
||||
)}
|
||||
|
||||
<PrimaryAgentButton
|
||||
aria-controls={
|
||||
isVariantMenuOpen ? "vscode-variant-button-menu" : undefined
|
||||
}
|
||||
aria-expanded={isVariantMenuOpen ? "true" : undefined}
|
||||
aria-label="select VSCode variant"
|
||||
aria-haspopup="menu"
|
||||
disableRipple
|
||||
onClick={() => {
|
||||
setIsVariantMenuOpen(true)
|
||||
}}
|
||||
>
|
||||
<KeyboardArrowDownIcon sx={{ fontSize: 16 }} />
|
||||
</PrimaryAgentButton>
|
||||
</ButtonGroup>
|
||||
|
||||
<Menu
|
||||
open={isVariantMenuOpen}
|
||||
anchorEl={menuAnchorRef.current}
|
||||
onClose={() => setIsVariantMenuOpen(false)}
|
||||
sx={{
|
||||
"& .MuiMenu-paper": {
|
||||
width: menuAnchorRef.current?.clientWidth,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
sx={{ fontSize: 14 }}
|
||||
onClick={() => {
|
||||
selectVariant("vscode")
|
||||
}}
|
||||
>
|
||||
<VSCodeIcon sx={{ width: 12, height: 12 }} />
|
||||
VS Code Desktop
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
sx={{ fontSize: 14 }}
|
||||
onClick={() => {
|
||||
selectVariant("vscode-insiders")
|
||||
}}
|
||||
>
|
||||
<VSCodeInsidersIcon sx={{ width: 12, height: 12 }} />
|
||||
VS Code Insiders
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const VSCodeButton = ({
|
||||
userName,
|
||||
workspaceName,
|
||||
agentName,
|
||||
folderPath,
|
||||
}: VSCodeDesktopButtonProps) => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
return (
|
||||
@ -50,3 +150,47 @@ export const VSCodeDesktopButton: FC<
|
||||
</PrimaryAgentButton>
|
||||
)
|
||||
}
|
||||
|
||||
const VSCodeInsidersButton = ({
|
||||
userName,
|
||||
workspaceName,
|
||||
agentName,
|
||||
folderPath,
|
||||
}: VSCodeDesktopButtonProps) => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
return (
|
||||
<PrimaryAgentButton
|
||||
startIcon={<VSCodeInsidersIcon />}
|
||||
disabled={loading}
|
||||
onClick={() => {
|
||||
setLoading(true)
|
||||
getApiKey()
|
||||
.then(({ key }) => {
|
||||
const query = new URLSearchParams({
|
||||
owner: userName,
|
||||
workspace: workspaceName,
|
||||
url: location.origin,
|
||||
token: key,
|
||||
})
|
||||
if (agentName) {
|
||||
query.set("agent", agentName)
|
||||
}
|
||||
if (folderPath) {
|
||||
query.set("folder", folderPath)
|
||||
}
|
||||
|
||||
location.href = `vscode-insiders://coder.coder-remote/open?${query.toString()}`
|
||||
})
|
||||
.catch((ex) => {
|
||||
console.error(ex)
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}}
|
||||
>
|
||||
VS Code Insiders
|
||||
</PrimaryAgentButton>
|
||||
)
|
||||
}
|
||||
|
Reference in New Issue
Block a user