diff --git a/skyvern-frontend/src/components/ui/hidden-copyable-input.tsx b/skyvern-frontend/src/components/ui/hidden-copyable-input.tsx index d8e1dda3..b7df5fc1 100644 --- a/skyvern-frontend/src/components/ui/hidden-copyable-input.tsx +++ b/skyvern-frontend/src/components/ui/hidden-copyable-input.tsx @@ -2,6 +2,7 @@ import { useState } from "react"; import { Button } from "./button"; import { Input } from "./input"; import { CheckIcon, CopyIcon } from "@radix-ui/react-icons"; +import { copyText } from "@/util/copyText"; type Props = { value: string; @@ -22,14 +23,15 @@ function HiddenCopyableInput({ value }: Props) { size="sm" variant="secondary" className="cursor-pointer" - onClick={async () => { + onClick={() => { if (hidden) { setHidden(false); return; } - await navigator.clipboard.writeText(value); - setCopied(true); - setTimeout(() => setCopied(false), 3000); + copyText(value).then(() => { + setCopied(true); + setTimeout(() => setCopied(false), 3000); + }); }} > {!hidden && !copied && } diff --git a/skyvern-frontend/src/routes/tasks/create/CreateNewTaskForm.tsx b/skyvern-frontend/src/routes/tasks/create/CreateNewTaskForm.tsx index 69010562..60707486 100644 --- a/skyvern-frontend/src/routes/tasks/create/CreateNewTaskForm.tsx +++ b/skyvern-frontend/src/routes/tasks/create/CreateNewTaskForm.tsx @@ -47,6 +47,7 @@ import { import { OrganizationApiResponse } from "@/api/types"; import { Skeleton } from "@/components/ui/skeleton"; import { MAX_STEPS_DEFAULT } from "../constants"; +import { copyText } from "@/util/copyText"; const createNewTaskFormSchema = z .object({ @@ -487,10 +488,11 @@ function CreateNewTaskForm({ initialValues }: Props) { "x-api-key": apiCredential ?? "", }, }); - await navigator.clipboard.writeText(curl); - toast({ - title: "Copied cURL", - description: "cURL copied to clipboard", + copyText(curl).then(() => { + toast({ + title: "Copied cURL", + description: "cURL copied to clipboard", + }); }); }} > diff --git a/skyvern-frontend/src/routes/tasks/create/SavedTaskForm.tsx b/skyvern-frontend/src/routes/tasks/create/SavedTaskForm.tsx index 469051f1..9bd65e43 100644 --- a/skyvern-frontend/src/routes/tasks/create/SavedTaskForm.tsx +++ b/skyvern-frontend/src/routes/tasks/create/SavedTaskForm.tsx @@ -49,6 +49,7 @@ import { import { OrganizationApiResponse } from "@/api/types"; import { MAX_STEPS_DEFAULT } from "../constants"; import { Skeleton } from "@/components/ui/skeleton"; +import { copyText } from "@/util/copyText"; const savedTaskFormSchema = z .object({ @@ -629,10 +630,11 @@ function SavedTaskForm({ initialValues }: Props) { "x-api-key": apiCredential ?? "", }, }); - await navigator.clipboard.writeText(curl); - toast({ - title: "Copied cURL", - description: "cURL copied to clipboard", + copyText(curl).then(() => { + toast({ + title: "Copied cURL", + description: "cURL copied to clipboard", + }); }); }} > diff --git a/skyvern-frontend/src/routes/tasks/detail/TaskDetails.tsx b/skyvern-frontend/src/routes/tasks/detail/TaskDetails.tsx index ae7c1422..864faf4d 100644 --- a/skyvern-frontend/src/routes/tasks/detail/TaskDetails.tsx +++ b/skyvern-frontend/src/routes/tasks/detail/TaskDetails.tsx @@ -26,6 +26,7 @@ import { taskIsFinalized } from "@/api/utils"; import fetchToCurl from "fetch-to-curl"; import { apiBaseUrl } from "@/util/env"; import { useApiCredential } from "@/hooks/useApiCredential"; +import { copyText } from "@/util/copyText"; function createTaskRequestObject(values: TaskApiResponse) { return { @@ -142,12 +143,13 @@ function TaskDetails() { "x-api-key": apiCredential ?? "", }, }); - navigator.clipboard.writeText(curl); - toast({ - variant: "success", - title: "Copied to Clipboard", - description: - "The cURL command has been copied to your clipboard.", + copyText(curl).then(() => { + toast({ + variant: "success", + title: "Copied to Clipboard", + description: + "The cURL command has been copied to your clipboard.", + }); }); }} > diff --git a/skyvern-frontend/src/util/copyText.ts b/skyvern-frontend/src/util/copyText.ts new file mode 100644 index 00000000..16795e58 --- /dev/null +++ b/skyvern-frontend/src/util/copyText.ts @@ -0,0 +1,25 @@ +/** + * Progressively enhanced text copying + * https://web.dev/patterns/clipboard/copy-text + */ +async function copyText(text: string): Promise { + if ("clipboard" in navigator) { + return navigator.clipboard.writeText(text); + } else { + const textArea = document.createElement("textarea"); + textArea.value = text; + textArea.style.opacity = "0"; + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + const success = document.execCommand("copy"); + document.body.removeChild(textArea); + if (success) { + return Promise.resolve(); + } else { + return Promise.reject(); + } + } +} + +export { copyText };