Skip to content

Commit

Permalink
fix(filters): export not working with Safari (#1505)
Browse files Browse the repository at this point in the history
* fix(filters): export not working with Safari

* speculative fix for safari

---------

Co-authored-by: s0up4200 <s0up4200@pm.me>
  • Loading branch information
zze0s and s0up4200 committed Apr 25, 2024
1 parent 9d08f14 commit d558db2
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 53 deletions.
61 changes: 8 additions & 53 deletions web/src/screens/filters/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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<CompleteFilterType>;
Expand All @@ -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) => {
Expand All @@ -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();

try {
const successful = document.execCommand("copy");
if (successful) {
toast.custom((t) => <Toast type="success" body="Filter copied to clipboard." t={t} />);
} else {
toast.custom((t) => <Toast type="error" body="Failed to copy JSON to clipboard." t={t} />);
}
} catch (err) {
console.error("Unable to copy text", err);
toast.custom((t) => <Toast type="error" body="Failed to copy JSON to clipboard." t={t} />);
}
// Asynchronously call copyTextToClipboard
CopyTextToClipboard(finalJson)
.then(() => {
toast.custom((t) => <Toast type="success" body="Filter copied to clipboard!" t={t} />);

document.body.removeChild(textarea);
};
})
.catch((err) => {
console.error("could not copy filter to clipboard", err);

if (navigator.clipboard) {
navigator.clipboard.writeText(finalJson).then(() => {
toast.custom((t) => <Toast type="success" body="Filter copied to clipboard." t={t} />);
}, () => {
toast.custom((t) => <Toast type="error" body="Failed to copy JSON to clipboard." t={t} />);
});
} else {
copyTextToClipboard(finalJson);
}

} catch (error) {
console.error(error);
toast.custom((t) => <Toast type="error" body="Failed to get filter data." t={t} />);
Expand Down
36 changes: 36 additions & 0 deletions web/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

0 comments on commit d558db2

Please sign in to comment.