From 4449df66aa8c786523338000e326cf8a5a3a0684 Mon Sep 17 00:00:00 2001 From: soup Date: Sun, 19 Mar 2023 21:22:07 +0100 Subject: [PATCH] feat(filters): import/export functionality (#755) * initial commit * import working * some more changes * import is working * added text field for import * made exported json look pretty * use filter name as title in export takes the name of the exported filter and add it as title to the json wont be used for anything on import * snake case for title * visual improvements * added export function to filter dropdown * added import to filter list * include empty values on export this is needed for the import to work * styled the add button * reduced needed values for const defaultFilter this is the minimum required for successful import * reduced defaultFilter to bits * Made export and import require minimum values added "version": "1.0", to export json * changed filter name * made the import textfield dynamic * incremental numbering for imported filter names Updated the filter import logic to check for existing filter names and appending incremental number to the filter name if a conflict is found * reverted changes in details.tsx * Improved code comments a bit * add icon and tooltip to filter.actions_count === 0 * changed the 0-action icon to a red animate-ping - made the tooltip trigger on both the name and the animate-ping hover - improved colors a bit * fixed bg color for textarea made the focus ring less intrusive --- web/src/screens/filters/details.tsx | 6 +- web/src/screens/filters/list.tsx | 267 ++++++++++++++++++++++++++-- 2 files changed, 254 insertions(+), 19 deletions(-) diff --git a/web/src/screens/filters/details.tsx b/web/src/screens/filters/details.tsx index be484b9..d325de7 100644 --- a/web/src/screens/filters/details.tsx +++ b/web/src/screens/filters/details.tsx @@ -25,8 +25,6 @@ import { APIClient } from "../../api/APIClient"; import { useToggle } from "../../hooks/hooks"; import { classNames } from "../../utils"; -import { CustomTooltip } from "../../components/tooltips/CustomTooltip"; - import { CheckboxField, IndexerMultiSelect, @@ -319,7 +317,7 @@ export default function FilterDetails() { ); } -export function General() { +export function General(){ const { isLoading, data: indexers } = useQuery( ["filters", "indexer_list"], () => APIClient.indexers.getOptions(), @@ -334,7 +332,6 @@ export function General() { return (
-
@@ -360,7 +357,6 @@ export function General() {
-
); } diff --git a/web/src/screens/filters/list.tsx b/web/src/screens/filters/list.tsx index 1856968..f679cc4 100644 --- a/web/src/screens/filters/list.tsx +++ b/web/src/screens/filters/list.tsx @@ -3,15 +3,22 @@ import { Link } from "react-router-dom"; import { toast } from "react-hot-toast"; import { Listbox, Menu, Switch, Transition } from "@headlessui/react"; import { useMutation, useQuery, useQueryClient } from "react-query"; +import { FormikValues } from "formik"; +import { useCallback } from "react"; + +import { Tooltip } from "react-tooltip"; + import { ArrowsRightLeftIcon, CheckIcon, ChevronDownIcon, + PlusIcon, DocumentDuplicateIcon, EllipsisHorizontalIcon, PencilSquareIcon, - TrashIcon + TrashIcon, + ExclamationTriangleIcon } from "@heroicons/react/24/outline"; import { queryClient } from "../../App"; @@ -22,6 +29,7 @@ import { APIClient } from "../../api/APIClient"; import Toast from "../../components/notifications/Toast"; import { EmptyListState } from "../../components/emptystates"; import { DeleteModal } from "../../components/modals"; +import { ArrowDownTrayIcon } from "@heroicons/react/24/solid"; type FilterListState = { indexerFilter: string[], @@ -71,9 +79,69 @@ const FilterListReducer = (state: FilterListState, action: Actions): FilterListS } }; -export default function Filters() { +interface FilterProps { + values?: FormikValues; +} + +export default function Filters({}: FilterProps){ + + const queryClient = useQueryClient(); + const [createFilterIsOpen, toggleCreateFilter] = useToggle(false); + const [showImportModal, setShowImportModal] = useState(false); + const [importJson, setImportJson] = useState(""); + + // This function handles the import of a filter from a JSON string + const handleImportJson = async () => { + try { + const importedData = JSON.parse(importJson); + + // Extract the filter data and name from the imported object + const importedFilter = importedData.data; + const filterName = importedData.name; + + // Check if the required properties are present and add them with default values if they are missing + const requiredProperties = ["resolutions", "sources", "codecs", "containers"]; + requiredProperties.forEach((property) => { + if (!importedFilter.hasOwnProperty(property)) { + importedFilter[property] = []; + } + }); + + // Fetch existing filters from the API + const existingFilters = await APIClient.filters.getAll(); + + // Create a unique filter title by appending an incremental number if title is taken by another filter + let nameCounter = 0; + let uniqueFilterName = filterName; + while (existingFilters.some((filter) => filter.name === uniqueFilterName)) { + nameCounter++; + uniqueFilterName = `${filterName}-${nameCounter}`; + } + + // Create a new filter using the API + const newFilter: Filter = { + ...importedFilter, + name: uniqueFilterName + }; + + await APIClient.filters.create(newFilter); + + // Update the filter list + queryClient.invalidateQueries("filters"); + + toast.custom((t) => ); + setShowImportModal(false); + } catch (error) { + // Log the error and show an error toast message + console.error("Error:", error); + toast.custom((t) => ); + } + }; + + const [showDropdown, setShowDropdown] = useState(false); + return (
@@ -81,19 +149,68 @@ export default function Filters() {

- Filters + Filters

-
+
+ + {showDropdown && ( +
+ +
+ )}
+ {showImportModal && ( +
+
+

Import Filter JSON

+