From d558db231ccdf741e4c6ed9483b23e893043a970 Mon Sep 17 00:00:00 2001 From: ze0s <43699394+zze0s@users.noreply.github.com> Date: Thu, 25 Apr 2024 17:01:30 +0200 Subject: [PATCH] fix(filters): export not working with Safari (#1505) * fix(filters): export not working with Safari * speculative fix for safari --------- Co-authored-by: s0up4200 --- web/src/screens/filters/List.tsx | 61 +++++--------------------------- web/src/utils/index.ts | 36 +++++++++++++++++++ 2 files changed, 44 insertions(+), 53 deletions(-) diff --git a/web/src/screens/filters/List.tsx b/web/src/screens/filters/List.tsx index 4b87d83..99b35be 100644 --- a/web/src/screens/filters/List.tsx +++ b/web/src/screens/filters/List.tsx @@ -23,7 +23,7 @@ import { import { ArrowDownTrayIcon } from "@heroicons/react/24/solid"; import { FilterListContext, FilterListState } from "@utils/Context"; -import { classNames } from "@utils"; +import { classNames, CopyTextToClipboard } from "@utils"; import { FilterAddForm } from "@forms"; import { useToggle } from "@hooks/hooks"; import { APIClient } from "@api/APIClient"; @@ -284,17 +284,6 @@ const FilterItemDropdown = ({ filter, onToggle }: FilterItemDropdownProps) => { actions: any; actions_count: any; actions_enabled_count: number; - external_script_enabled: any; - external_script_cmd: any; - external_script_args: any; - external_script_expect_status: any; - external_webhook_enabled: any; - external_webhook_host: any; - external_webhook_data: any; - external_webhook_expect_status: any; - external_webhook_retry_status: any; - external_webhook_retry_attempts: any; - external_webhook_retry_delay_seconds: any; }; const completeFilter = await APIClient.filters.getByID(filter.id) as Partial; @@ -309,17 +298,6 @@ const FilterItemDropdown = ({ filter, onToggle }: FilterItemDropdownProps) => { delete completeFilter.actions_enabled_count; delete completeFilter.indexers; delete completeFilter.actions; - delete completeFilter.external_script_enabled; - delete completeFilter.external_script_cmd; - delete completeFilter.external_script_args; - delete completeFilter.external_script_expect_status; - delete completeFilter.external_webhook_enabled; - delete completeFilter.external_webhook_host; - delete completeFilter.external_webhook_data; - delete completeFilter.external_webhook_expect_status; - delete completeFilter.external_webhook_retry_status; - delete completeFilter.external_webhook_retry_attempts; - delete completeFilter.external_webhook_retry_delay_seconds; // Remove properties with default values from the exported filter to minimize the size of the JSON string ["enabled", "priority", "smart_episode", "resolutions", "sources", "codecs", "containers", "tags_match_logic", "except_tags_match_logic"].forEach((key) => { @@ -346,40 +324,17 @@ const FilterItemDropdown = ({ filter, onToggle }: FilterItemDropdownProps) => { const finalJson = discordFormat ? "```JSON\n" + json + "\n```" : json; - const copyTextToClipboard = (text: string) => { - const textarea = document.createElement("textarea"); - textarea.style.position = "fixed"; - textarea.style.opacity = "0"; - textarea.value = text; - document.body.appendChild(textarea); - textarea.focus(); - textarea.select(); + // Asynchronously call copyTextToClipboard + CopyTextToClipboard(finalJson) + .then(() => { + toast.custom((t) => ); - try { - const successful = document.execCommand("copy"); - if (successful) { - toast.custom((t) => ); - } else { - toast.custom((t) => ); - } - } catch (err) { - console.error("Unable to copy text", err); - toast.custom((t) => ); - } + }) + .catch((err) => { + console.error("could not copy filter to clipboard", err); - document.body.removeChild(textarea); - }; - - if (navigator.clipboard) { - navigator.clipboard.writeText(finalJson).then(() => { - toast.custom((t) => ); - }, () => { toast.custom((t) => ); }); - } else { - copyTextToClipboard(finalJson); - } - } catch (error) { console.error(error); toast.custom((t) => ); diff --git a/web/src/utils/index.ts b/web/src/utils/index.ts index 8ee2763..04cb5f2 100644 --- a/web/src/utils/index.ts +++ b/web/src/utils/index.ts @@ -152,3 +152,39 @@ export const RandomLinuxIsos = (count: number) => { return Array.from({ length: count }, () => linuxIsos[Math.floor(Math.random() * linuxIsos.length)]); }; + +export async function CopyTextToClipboard(text: string) { + if ("clipboard" in navigator) { + // Safari requires clipboard operations to be directly triggered by a user interaction. + // Using setTimeout with a delay of 0 ensures the clipboard operation is deferred until + // after the current call stack has cleared, effectively placing it outside of the + // immediate execution context of the user interaction event. This workaround allows + // the clipboard operation to bypass Safari's security restrictions. + setTimeout(async () => { + try { + await navigator.clipboard.writeText(text); + console.log("Text copied to clipboard successfully."); + } catch (err) { + console.error("Copy to clipboard unsuccessful: ", err); + } + }, 0); + } else { + // fallback for browsers that do not support the Clipboard API + copyTextToClipboardFallback(text); + } + } + + function copyTextToClipboardFallback(text: string) { + const textarea = document.createElement("textarea"); + textarea.value = text; + document.body.appendChild(textarea); + textarea.select(); + try { + document.execCommand('copy'); + console.log("Text copied to clipboard successfully."); + } catch (err) { + console.error('Failed to copy text using fallback method: ', err); + } + document.body.removeChild(textarea); + } +