mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 16:59:12 +00:00
fix(filters): close add new dropdown of focus (#777)
* hide dropdown when clicking outside it * cleaner code * cleaned up code removed uneccessary div changed to focus:ring-inset on buttons * revert focus-ring-inset change will handle this for the entire app in a separate branch * changed to using headlessui * added transition to the dropdown * feat: add export JSON to Discord button The Discord button exports the filter data in JSON format, but with Discord-specific formatting. as requested on Discord
This commit is contained in:
parent
0564f0bf7a
commit
bcd5128c59
1 changed files with 91 additions and 49 deletions
|
@ -17,8 +17,8 @@ import {
|
|||
DocumentDuplicateIcon,
|
||||
EllipsisHorizontalIcon,
|
||||
PencilSquareIcon,
|
||||
TrashIcon,
|
||||
ExclamationTriangleIcon
|
||||
ChatBubbleBottomCenterTextIcon,
|
||||
TrashIcon
|
||||
} from "@heroicons/react/24/outline";
|
||||
|
||||
import { queryClient } from "../../App";
|
||||
|
@ -87,10 +87,13 @@ export default function Filters({}: FilterProps){
|
|||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const [createFilterIsOpen, toggleCreateFilter] = useToggle(false);
|
||||
const [createFilterIsOpen, setCreateFilterIsOpen] = useState(false);
|
||||
const toggleCreateFilter = () => {
|
||||
setCreateFilterIsOpen(!createFilterIsOpen);
|
||||
};
|
||||
|
||||
const [showImportModal, setShowImportModal] = useState(false);
|
||||
const [importJson, setImportJson] = useState("");
|
||||
const [importJson, setImportJson] = useState("");
|
||||
|
||||
// This function handles the import of a filter from a JSON string
|
||||
const handleImportJson = async () => {
|
||||
|
@ -140,53 +143,71 @@ export default function Filters({}: FilterProps){
|
|||
}
|
||||
};
|
||||
|
||||
const [showDropdown, setShowDropdown] = useState(false);
|
||||
|
||||
return (
|
||||
<main>
|
||||
<FilterAddForm isOpen={createFilterIsOpen} toggle={toggleCreateFilter} />
|
||||
|
||||
<header className="py-10">
|
||||
<div className="max-w-screen-xl mx-auto px-4 sm:px-6 lg:px-8 flex justify-between">
|
||||
<h1 className="text-3xl font-bold text-black dark:text-white">
|
||||
Filters
|
||||
</h1>
|
||||
<h1 className="text-3xl font-bold text-black dark:text-white">Filters</h1>
|
||||
<div className="relative">
|
||||
<button
|
||||
type="button"
|
||||
className="relative inline-flex items-center px-4 py-2 shadow-sm text-sm font-medium rounded-l-md text-white bg-blue-600 dark:bg-blue-600 hover:bg-blue-700 dark:hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-blue-500"
|
||||
onClick={toggleCreateFilter}
|
||||
>
|
||||
<PlusIcon className="h-5 w-5 mr-1" />
|
||||
Add Filter
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="relative inline-flex items-center px-2 py-2 border-l border-spacing-1 dark:border-black shadow-sm text-sm font-medium rounded-r-md text-white bg-blue-600 dark:bg-blue-600 hover:bg-blue-700 dark:hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-blue-500"
|
||||
onClick={() => setShowDropdown(!showDropdown)}
|
||||
>
|
||||
<ChevronDownIcon className="h-5 w-5" />
|
||||
</button>
|
||||
{showDropdown && (
|
||||
<div className="absolute right-0 mt-2 w-46 bg-white dark:bg-gray-700 rounded-md shadow-lg z-50">
|
||||
<button
|
||||
type="button"
|
||||
className="w-full text-left py-2 px-4 text-sm font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-blue-500"
|
||||
onClick={() => setShowImportModal(true)}
|
||||
>
|
||||
Import Filter
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<Menu>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<button
|
||||
className="relative inline-flex items-center px-4 py-2 shadow-sm text-sm font-medium rounded-l-md text-white bg-blue-600 dark:bg-blue-600 hover:bg-blue-700 dark:hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-blue-500"
|
||||
onClick={(e: { stopPropagation: () => void; }) => {
|
||||
if (!open) {
|
||||
e.stopPropagation();
|
||||
toggleCreateFilter();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<PlusIcon className="h-5 w-5 mr-1" />
|
||||
Add Filter
|
||||
</button>
|
||||
<Menu.Button className="relative inline-flex items-center px-2 py-2 border-l border-spacing-1 dark:border-black shadow-sm text-sm font-medium rounded-r-md text-white bg-blue-600 dark:bg-blue-600 hover:bg-blue-700 dark:hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-blue-500">
|
||||
<ChevronDownIcon className="h-5 w-5" />
|
||||
</Menu.Button>
|
||||
<Transition
|
||||
show={open}
|
||||
enter="transition ease-out duration-100 transform"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75 transform"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items className="absolute right-0 mt-0.5 w-46 bg-white dark:bg-gray-700 rounded-md shadow-lg">
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<button
|
||||
type="button"
|
||||
className={`${
|
||||
active
|
||||
? "bg-gray-50 dark:bg-gray-600"
|
||||
: ""
|
||||
} w-full text-left py-2 px-4 text-sm font-medium text-gray-700 dark:text-gray-200 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-blue-500`}
|
||||
onClick={() => setShowImportModal(true)}
|
||||
>
|
||||
Import Filter
|
||||
</button>
|
||||
)}
|
||||
</Menu.Item>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{showImportModal && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="w-1/2 md:w-1/2 bg-white dark:bg-gray-800 p-6 rounded-md shadow-lg">
|
||||
<h2 className="text-lg font-medium mb-4 text-black dark:text-white">Import Filter JSON</h2>
|
||||
<textarea
|
||||
className="form-input block w-full resize-y rounded-md border-gray-300 dark:bg-gray-800 dark:border-gray-600 shadow-sm text-sm font-medium text-gray-700 dark:text-white focus:outline-none focus:ring-2 focus:ring-offset-0 focus:ring-blue-500 dark:focus:ring-blue-500 mb-4"
|
||||
className="form-input block w-full resize-y rounded-md border-gray-300 dark:bg-gray-800 dark:border-gray-600 shadow-sm text-sm font-medium text-gray-700 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-500 mb-4"
|
||||
placeholder="Paste JSON data here"
|
||||
value={importJson}
|
||||
onChange={(event) => setImportJson(event.target.value)}
|
||||
|
@ -198,20 +219,19 @@ export default function Filters({}: FilterProps){
|
|||
className="bg-white dark:bg-gray-700 py-2 px-4 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-blue-500"
|
||||
onClick={() => setShowImportModal(false)}
|
||||
>
|
||||
Cancel
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="ml-4 relative inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 dark:bg-blue-600 hover:bg-blue-700 dark:hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
onClick={handleImportJson}
|
||||
>
|
||||
Import
|
||||
Import
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<FilterList toggleCreateFilter={toggleCreateFilter} />
|
||||
</main>
|
||||
);
|
||||
|
@ -326,8 +346,7 @@ interface FilterItemDropdownProps {
|
|||
const FilterItemDropdown = ({ filter, onToggle }: FilterItemDropdownProps) => {
|
||||
|
||||
// This function handles the export of a filter to a JSON string
|
||||
const handleExportJson = useCallback(async () => {
|
||||
try {
|
||||
const handleExportJson = useCallback(async (discordFormat = false) => { try {
|
||||
type CompleteFilterType = {
|
||||
id: number;
|
||||
name: string;
|
||||
|
@ -386,16 +405,20 @@ const FilterItemDropdown = ({ filter, onToggle }: FilterItemDropdownProps) => {
|
|||
null,
|
||||
4
|
||||
);
|
||||
|
||||
const finalJson = discordFormat ? "```JSON\n" + json + "\n```" : json;
|
||||
|
||||
|
||||
navigator.clipboard.writeText(json).then(() => {
|
||||
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} />);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.custom((t) => <Toast type="error" body="Failed to get filter data." t={t} />);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.custom((t) => <Toast type="error" body="Failed to get filter data." t={t} />);
|
||||
}
|
||||
}, [filter]);
|
||||
|
||||
const cancelModalButtonRef = useRef(null);
|
||||
|
@ -485,8 +508,7 @@ const FilterItemDropdown = ({ filter, onToggle }: FilterItemDropdownProps) => {
|
|||
active ? "bg-blue-600 text-white" : "text-gray-900 dark:text-gray-300",
|
||||
"font-medium group flex rounded-md items-center w-full px-2 py-2 text-sm"
|
||||
)}
|
||||
onClick={handleExportJson}
|
||||
>
|
||||
onClick={() => handleExportJson(false)} >
|
||||
<ArrowDownTrayIcon
|
||||
className={classNames(
|
||||
active ? "text-white" : "text-blue-500",
|
||||
|
@ -498,6 +520,26 @@ const FilterItemDropdown = ({ filter, onToggle }: FilterItemDropdownProps) => {
|
|||
</button>
|
||||
)}
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<button
|
||||
className={classNames(
|
||||
active ? "bg-blue-600 text-white" : "text-gray-900 dark:text-gray-300",
|
||||
"font-medium group flex rounded-md items-center w-full px-2 py-2 text-sm"
|
||||
)}
|
||||
onClick={() => handleExportJson(true)}
|
||||
>
|
||||
<ChatBubbleBottomCenterTextIcon
|
||||
className={classNames(
|
||||
active ? "text-white" : "text-blue-500",
|
||||
"w-5 h-5 mr-2"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Export JSON to Discord
|
||||
</button>
|
||||
)}
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<button
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue