Skip to content

Commit 5e71552

Browse files
authored
Add a clone button to the popup (#13)
* Fix animations breaking links * Implement a clone button for the preview page * Bump up version
1 parent 23fef7f commit 5e71552

15 files changed

Lines changed: 152 additions & 93 deletions

File tree

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
{
22
"name": "clone-with-devpod-extension",
33
"private": true,
4-
"version": "0.2.0",
4+
"version": "0.3.0",
55
"type": "module",
66
"license": "MIT",
77
"scripts": {
88
"dev": "vite dev",
9-
"preview": "PREVIEW=false vite dev",
9+
"preview": "PREVIEW=true vite dev",
1010
"build": "tsc && vite build"
1111
},
1212
"dependencies": {

src/icons/devpod.tsx

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,6 @@
1-
import LogoImage from "@public/icon/devpod.svg";
2-
import { DetailedHTMLProps, forwardRef } from "react";
1+
import { forwardRef } from "react";
32

4-
export const DevPodLogoImage = forwardRef<
5-
HTMLImageElement,
6-
DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>
7-
>((props, ref) => {
8-
return <img ref={ref} src={LogoImage} {...props} />;
9-
});
10-
DevPodLogoImage.displayName = "DevPodLogoImage";
11-
12-
export const DevPodLogoIcon = forwardRef<
3+
export const DevPodLogo = forwardRef<
134
SVGSVGElement,
145
React.SVGProps<SVGSVGElement>
156
>((props, ref) => {
@@ -32,4 +23,4 @@ export const DevPodLogoIcon = forwardRef<
3223
</svg>
3324
);
3425
});
35-
DevPodLogoIcon.displayName = "DevPodLogoIcon";
26+
DevPodLogo.displayName = "DevPodLogo";

src/lib/components/Button.tsx

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { clsx } from "@lib/utils/clsx";
22
import { WithRequired } from "@lib/utils/typeUtils/WithRequired";
33
import { HTMLMotionProps, motion, useAnimate } from "framer-motion";
4-
import { forwardRef, useImperativeHandle, useMemo, useRef } from "react";
4+
import { forwardRef, useMemo } from "react";
55

66
type ButtonStyleProps = {
77
variant?: "solid" | "outline" | "link";
@@ -61,21 +61,17 @@ function useCommonProps<Type extends "a" | "button">(
6161
// eslint-disable-next-line @typescript-eslint/no-explicit-any
6262
onClick: async (e: any) => {
6363
props.onClick?.(e);
64-
await animate(
65-
scope.current,
66-
{ backgroundColor: "#FF00A6" },
67-
{ duration: 0.1 },
68-
);
69-
await animate(
70-
scope.current,
71-
{ backgroundColor: "#DB0082" },
72-
{ duration: 0.1 },
73-
);
64+
await animate(scope.current, { opacity: 0.95 }, { duration: 0.1 });
65+
await animate(scope.current, { opacity: 0.8 }, { duration: 0.1 });
7466
},
75-
initial: { backgroundColor: "#DB0082" },
76-
whileHover: { backgroundColor: "#AC0067" },
77-
whileFocus: { backgroundColor: "#AC0067" },
78-
className: clsx(variantClasses(props), className, "cursor-pointer"),
67+
initial: { opacity: 1 },
68+
whileHover: { opacity: 0.8 },
69+
whileFocus: { opacity: 0.8 },
70+
className: clsx(
71+
variantClasses(props),
72+
className,
73+
"cursor-pointer disabled:opacity-60 disabled:cursor-not-allowed",
74+
),
7975
...props,
8076
ref: mergeRefs(ref, scope),
8177
}),
@@ -96,24 +92,17 @@ Button.displayName = "Button";
9692

9793
export const ButtonLink = forwardRef<HTMLAnchorElement, LinkProps>(
9894
({ children, ...props }, ref) => {
99-
const common = useCommonProps<"a">(props);
95+
const common = useCommonProps<"a">(props, ref);
10096

101-
return (
102-
<motion.a ref={ref} {...common}>
103-
{children}
104-
</motion.a>
105-
);
97+
return <motion.a {...common}>{children}</motion.a>;
10698
},
10799
);
108100
ButtonLink.displayName = "ButtonLink";
109101

110102
export const Link = forwardRef<HTMLAnchorElement, Omit<LinkProps, "variant">>(
111103
({ children, ...props }, ref) => {
112104
return (
113-
<motion.a
114-
ref={ref}
115-
{...useCommonProps<"a">({ variant: "link", ...props })}
116-
>
105+
<motion.a {...useCommonProps<"a">({ variant: "link", ...props }, ref)}>
117106
{children}
118107
</motion.a>
119108
);

src/lib/integrations/preview.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ import { Integration } from "./core";
22

33
/** A dummy integration just for testing and development.
44
*
5-
* This is always supported on every page on dev, and never on production.
5+
* This is always supported on every page in preview mode, and never on production.
66
*/
77
export const Preview: Integration = {
88
platform: "Github",
99
supports() {
10-
return NODE_ENV === "development";
10+
console.log("PREVIEW", PREVIEW);
11+
return !!PREVIEW;
1112
},
1213
getButtonTarget(document: Document) {
1314
return document.body;

src/lib/wrappers/ErrorBoundary.tsx

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ import { EError } from "../utils/error";
44
import { Modal } from "@lib/components/Modal";
55
import { Button, Link } from "@lib/components/Button";
66

7-
function Fallback({ error, resetErrorBoundary }: FallbackProps) {
7+
function FallbackContent({
8+
error,
9+
resetErrorBoundary,
10+
dismiss,
11+
}: FallbackProps & { dismiss?: () => void }) {
812
const { errorEncoded, message, serialized } = useMemo(() => {
913
return {
1014
errorEncoded: EError.encode(error),
@@ -15,22 +19,16 @@ function Fallback({ error, resetErrorBoundary }: FallbackProps) {
1519
const onCopy = useCallback(() => {
1620
navigator.clipboard.writeText(errorEncoded);
1721
}, [errorEncoded]);
18-
const [dismissed, setDismissed] = useState(false);
19-
const dismiss = useCallback(() => {
20-
setDismissed(true);
21-
}, []);
2222

2323
return (
24-
<Modal
25-
isOpen={!dismissed}
26-
onClose={dismiss}
27-
className="text-text flex flex-col gap-4 max-w-[80vw] lg:max-w-2xl z-50"
28-
>
24+
<>
2925
<div className="flex flex-row w-full justify-between">
3026
<h2 className="font-bold text-xl">Error: {message}</h2>
31-
<Button variant="outline" onClick={dismiss}>
32-
X
33-
</Button>
27+
{dismiss ? (
28+
<Button variant="outline" onClick={dismiss}>
29+
X
30+
</Button>
31+
) : null}
3432
</div>
3533
<p>
3634
Please send the following code along with a description of what you were
@@ -94,10 +92,55 @@ function Fallback({ error, resetErrorBoundary }: FallbackProps) {
9492
<Button variant="outline" onClick={resetErrorBoundary}>
9593
Retry
9694
</Button>
95+
</>
96+
);
97+
}
98+
99+
function FallbackModal({ error, resetErrorBoundary }: FallbackProps) {
100+
const [dismissed, setDismissed] = useState(false);
101+
const dismiss = useCallback(() => {
102+
setDismissed(true);
103+
}, []);
104+
105+
return (
106+
<Modal
107+
isOpen={!dismissed}
108+
onClose={dismiss}
109+
className="text-text flex flex-col gap-4 max-w-[80vw] lg:max-w-2xl z-50"
110+
>
111+
<FallbackContent
112+
error={error}
113+
resetErrorBoundary={resetErrorBoundary}
114+
dismiss={dismiss}
115+
/>
97116
</Modal>
98117
);
99118
}
100119

101-
export function ErrorBoundaryProvider({ children }: { children: ReactNode }) {
102-
return <ErrorBoundary FallbackComponent={Fallback}>{children}</ErrorBoundary>;
120+
function FallbackInline({ error, resetErrorBoundary }: FallbackProps) {
121+
return (
122+
<div className="p-8">
123+
<FallbackContent error={error} resetErrorBoundary={resetErrorBoundary} />
124+
</div>
125+
);
126+
}
127+
128+
export function InlineErrorBoundaryProvider({
129+
children,
130+
}: {
131+
children: ReactNode;
132+
}) {
133+
return (
134+
<ErrorBoundary FallbackComponent={FallbackInline}>{children}</ErrorBoundary>
135+
);
136+
}
137+
138+
export function ModalErrorBoundaryProvider({
139+
children,
140+
}: {
141+
children: ReactNode;
142+
}) {
143+
return (
144+
<ErrorBoundary FallbackComponent={FallbackModal}>{children}</ErrorBoundary>
145+
);
103146
}

src/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"{{chrome}}.service_worker": "src/background.ts",
2121
"{{firefox}}.scripts": ["src/background.ts"]
2222
},
23-
"permissions": ["webNavigation", "storage"],
23+
"permissions": ["webNavigation", "storage", "activeTab"],
2424
"content_scripts": [
2525
{
2626
"matches": ["https://github.com/*", "https://gitlab.com/*"],

src/pages/Popup.tsx

Lines changed: 0 additions & 10 deletions
This file was deleted.

src/pages/PreviewContent.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Button } from "@lib/components/Button";
1+
import { Button, Link } from "@lib/components/Button";
22
import { CloneButton } from "./content/CloneButton";
33

44
export function Preview() {
@@ -7,6 +7,7 @@ export function Preview() {
77
<div className="m-4 bg-white p-8 rounded flex flex-col gap-4">
88
<CloneButton portal={document.body} />
99
<Button>Test</Button>
10+
<Link href="/src/popup.html">Preview Popup</Link>
1011
</div>
1112
</div>
1213
);

src/pages/content/CloneButton.tsx

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import { EIntegrationParseError } from "@lib/integrations/error";
55
import { clsx } from "@lib/utils/clsx";
66
import { PortalProps } from "@lib/utils/dom/portal";
77
import { EError, ENoIntegrationError } from "@lib/utils/error";
8-
import { ErrorBoundaryProvider } from "@lib/wrappers/ErrorBoundary";
9-
import { DevPodLogoIcon } from "@src/icons/devpod";
8+
import { ModalErrorBoundaryProvider } from "@lib/wrappers/ErrorBoundary";
9+
import { DevPodLogo } from "@src/icons/devpod";
1010
import { StrictMode } from "react";
1111
import { createPortal } from "react-dom";
1212

13-
function getDevPodUrl(url: string) {
13+
export function getDevPodUrl(url: string) {
1414
try {
1515
const integration = getSupportedIntegration(url);
1616
if (!integration) {
@@ -57,10 +57,7 @@ function CloneButtonInner({
5757
{...bindTarget}
5858
className={className}
5959
>
60-
<DevPodLogoIcon
61-
aria-label=""
62-
className="w-6 h-6 text-primary-contrast"
63-
/>{" "}
60+
<DevPodLogo aria-label="" className="w-6 h-6 text-primary-contrast" />{" "}
6461
DevPod
6562
</ButtonLink>
6663
{createPortal(
@@ -85,9 +82,9 @@ export function CloneButton({
8582
}: PortalProps & { className?: string }) {
8683
return (
8784
<StrictMode>
88-
<ErrorBoundaryProvider>
85+
<ModalErrorBoundaryProvider>
8986
<CloneButtonInner className={className} portal={portal} />
90-
</ErrorBoundaryProvider>
87+
</ModalErrorBoundaryProvider>
9188
</StrictMode>
9289
);
9390
}

0 commit comments

Comments
 (0)