mirror of
https://github.com/idanoo/autobrr
synced 2025-07-25 09:49:13 +00:00
enhancement(web): add react suspense and improve DX (#1089)
* add react suspense, fix broken stuff, clean up code, improve DX enhancement: added react suspense + spinner to show loading (still can be added in certain places) chore: cleaned up Header/NavBar code chore: cleaned up DeleteModal code chore: cleaned up other relevant code enhancement: changed remove button style to be much more pleasant (see e.g. filter tabs) fix: made active tab on filters page to be blue (as it should've been) when active fix: fixed ghost delimiter which was only visible when DeleteModal was active in FormButtonGroup chore: removed most of linter warnings/errors fix: fixed incorrect/double modal transition in FilterExternalItem fix: fixed incorrect z-height on Options popover in Settings/IRC (would've been visible when Add new was clicked) enhancement: improved robustness of all Context classes to support seamless new-feature expansion (#866) enhancement: improved expand logic (see #994 comments) * reverted irc expand view to previous design * forgot to propagate previous z-height fix * jinxed it * add license header to new files --------- Co-authored-by: martylukyy <35452459+martylukyy@users.noreply.github.com> Co-authored-by: Kyle Sanderson <kyle.leet@gmail.com>
This commit is contained in:
parent
cbf668e87c
commit
2fed48e0dd
23 changed files with 845 additions and 737 deletions
|
@ -119,6 +119,7 @@ function APIListItem({ apikey }: ApiKeyItemProps) {
|
|||
<li className="text-gray-500 dark:text-gray-400">
|
||||
<DeleteModal
|
||||
isOpen={deleteModalIsOpen}
|
||||
isLoading={deleteMutation.isLoading}
|
||||
toggle={toggleDeleteModal}
|
||||
buttonRef={cancelModalButtonRef}
|
||||
deleteAction={() => {
|
||||
|
|
|
@ -23,7 +23,7 @@ import { DeleteModal } from "@components/modals";
|
|||
import { FeedUpdateForm } from "@forms/settings/FeedForms";
|
||||
import { EmptySimple } from "@components/emptystates";
|
||||
import { ImplementationBadges } from "./Indexer";
|
||||
import {ArrowPathIcon} from "@heroicons/react/24/solid";
|
||||
import { ArrowPathIcon } from "@heroicons/react/24/solid";
|
||||
|
||||
export const feedKeys = {
|
||||
all: ["feeds"] as const,
|
||||
|
@ -64,7 +64,8 @@ function useSort(items: ListItemProps["feed"][], config?: SortConfig) {
|
|||
return sortableItems;
|
||||
}, [items, sortConfig]);
|
||||
|
||||
const requestSort = (key: keyof ListItemProps["feed"] | "enabled") => { let direction: "ascending" | "descending" = "ascending";
|
||||
const requestSort = (key: keyof ListItemProps["feed"] | "enabled") => {
|
||||
let direction: "ascending" | "descending" = "ascending";
|
||||
if (
|
||||
sortConfig &&
|
||||
sortConfig.key === key &&
|
||||
|
@ -133,13 +134,13 @@ function FeedSettings() {
|
|||
Last run <span className="sort-indicator">{sortedFeeds.getSortIndicator("last_run")}</span>
|
||||
</div>
|
||||
<div
|
||||
className="hidden md:flex col-span-2 px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer"
|
||||
onClick={() => sortedFeeds.requestSort("next_run")}>
|
||||
className="hidden md:flex col-span-2 px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer"
|
||||
onClick={() => sortedFeeds.requestSort("next_run")}>
|
||||
Next run <span className="sort-indicator">{sortedFeeds.getSortIndicator("next_run")}</span>
|
||||
</div>
|
||||
</li>
|
||||
{sortedFeeds.items.map((feed) => (
|
||||
<ListItem key={feed.id} feed={feed}/>
|
||||
<ListItem key={feed.id} feed={feed} />
|
||||
))}
|
||||
</ol>
|
||||
</section>
|
||||
|
@ -150,7 +151,7 @@ function FeedSettings() {
|
|||
}
|
||||
|
||||
interface ListItemProps {
|
||||
feed: Feed;
|
||||
feed: Feed;
|
||||
}
|
||||
|
||||
function ListItem({ feed }: ListItemProps) {
|
||||
|
@ -165,7 +166,7 @@ function ListItem({ feed }: ListItemProps) {
|
|||
queryClient.invalidateQueries({ queryKey: feedKeys.lists() });
|
||||
queryClient.invalidateQueries({ queryKey: feedKeys.detail(feed.id) });
|
||||
|
||||
toast.custom((t) => <Toast type="success" body={`${feed.name} was ${!enabled ? "disabled" : "enabled"} successfully.`} t={t}/>);
|
||||
toast.custom((t) => <Toast type="success" body={`${feed.name} was ${!enabled ? "disabled" : "enabled"} successfully.`} t={t} />);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -176,7 +177,7 @@ function ListItem({ feed }: ListItemProps) {
|
|||
|
||||
return (
|
||||
<li key={feed.id} className="text-gray-500 dark:text-gray-400">
|
||||
<FeedUpdateForm isOpen={updateFormIsOpen} toggle={toggleUpdateForm} feed={feed}/>
|
||||
<FeedUpdateForm isOpen={updateFormIsOpen} toggle={toggleUpdateForm} feed={feed} />
|
||||
|
||||
<div className="grid grid-cols-12 items-center">
|
||||
<div className="col-span-2 sm:col-span-1 px-6 flex items-center">
|
||||
|
@ -230,9 +231,9 @@ function ListItem({ feed }: ListItemProps) {
|
|||
}
|
||||
|
||||
interface FeedItemDropdownProps {
|
||||
feed: Feed;
|
||||
onToggle: (newState: boolean) => void;
|
||||
toggleUpdate: () => void;
|
||||
feed: Feed;
|
||||
onToggle: (newState: boolean) => void;
|
||||
toggleUpdate: () => void;
|
||||
}
|
||||
|
||||
const FeedItemDropdown = ({
|
||||
|
@ -254,14 +255,14 @@ const FeedItemDropdown = ({
|
|||
queryClient.invalidateQueries({ queryKey: feedKeys.lists() });
|
||||
queryClient.invalidateQueries({ queryKey: feedKeys.detail(feed.id) });
|
||||
|
||||
toast.custom((t) => <Toast type="success" body={`Feed ${feed?.name} was deleted`} t={t}/>);
|
||||
toast.custom((t) => <Toast type="success" body={`Feed ${feed?.name} was deleted`} t={t} />);
|
||||
}
|
||||
});
|
||||
|
||||
const deleteCacheMutation = useMutation({
|
||||
mutationFn: (id: number) => APIClient.feeds.deleteCache(id),
|
||||
onSuccess: () => {
|
||||
toast.custom((t) => <Toast type="success" body={`Feed ${feed?.name} cache was cleared!`} t={t}/>);
|
||||
toast.custom((t) => <Toast type="success" body={`Feed ${feed?.name} cache was cleared!`} t={t} />);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -269,6 +270,7 @@ const FeedItemDropdown = ({
|
|||
<Menu as="div">
|
||||
<DeleteModal
|
||||
isOpen={deleteModalIsOpen}
|
||||
isLoading={deleteMutation.isLoading}
|
||||
toggle={toggleDeleteModal}
|
||||
buttonRef={cancelModalButtonRef}
|
||||
deleteAction={() => {
|
||||
|
@ -279,14 +281,15 @@ const FeedItemDropdown = ({
|
|||
text="Are you sure you want to remove this feed? This action cannot be undone."
|
||||
/>
|
||||
<DeleteModal
|
||||
isOpen={deleteCacheModalIsOpen}
|
||||
toggle={toggleDeleteCacheModal}
|
||||
buttonRef={cancelCacheModalButtonRef}
|
||||
deleteAction={() => {
|
||||
deleteCacheMutation.mutate(feed.id);
|
||||
}}
|
||||
title={`Remove feed cache: ${feed.name}`}
|
||||
text="Are you sure you want to remove the feed cache? This action cannot be undone."
|
||||
isOpen={deleteCacheModalIsOpen}
|
||||
isLoading={deleteMutation.isLoading}
|
||||
toggle={toggleDeleteCacheModal}
|
||||
buttonRef={cancelCacheModalButtonRef}
|
||||
deleteAction={() => {
|
||||
deleteCacheMutation.mutate(feed.id);
|
||||
}}
|
||||
title={`Remove feed cache: ${feed.name}`}
|
||||
text="Are you sure you want to remove the feed cache? This action cannot be undone."
|
||||
/>
|
||||
<Menu.Button className="px-4 py-2">
|
||||
<EllipsisHorizontalIcon
|
||||
|
@ -349,49 +352,49 @@ const FeedItemDropdown = ({
|
|||
</Menu.Item>
|
||||
</div>
|
||||
<div>
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<a
|
||||
href={`${baseUrl()}api/feeds/${feed.id}/latest`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
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"
|
||||
)}
|
||||
>
|
||||
<DocumentTextIcon
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<a
|
||||
href={`${baseUrl()}api/feeds/${feed.id}/latest`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={classNames(
|
||||
active ? "text-white" : "text-blue-500",
|
||||
"w-5 h-5 mr-2"
|
||||
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"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
View latest run
|
||||
</a>
|
||||
)}
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<button
|
||||
>
|
||||
<DocumentTextIcon
|
||||
className={classNames(
|
||||
active ? "bg-red-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"
|
||||
active ? "text-white" : "text-blue-500",
|
||||
"w-5 h-5 mr-2"
|
||||
)}
|
||||
onClick={() => toggleDeleteCacheModal()}
|
||||
title="Manually clear all feed cache"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
View latest run
|
||||
</a>
|
||||
)}
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<button
|
||||
className={classNames(
|
||||
active ? "bg-red-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={() => toggleDeleteCacheModal()}
|
||||
title="Manually clear all feed cache"
|
||||
>
|
||||
<ArrowPathIcon
|
||||
className={classNames(
|
||||
active ? "text-white" : "text-red-500",
|
||||
"w-5 h-5 mr-2"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
className={classNames(
|
||||
active ? "text-white" : "text-red-500",
|
||||
"w-5 h-5 mr-2"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Clear feed cache
|
||||
</button>
|
||||
)}
|
||||
</Menu.Item>
|
||||
)}
|
||||
</Menu.Item>
|
||||
</div>
|
||||
<div className="px-1 py-1">
|
||||
<Menu.Item>
|
||||
|
|
|
@ -171,7 +171,7 @@ const IrcSettings = () => {
|
|||
? <span className="flex items-center">Collapse <ArrowsPointingInIcon className="ml-1 w-4 h-4"/></span>
|
||||
: <span className="flex items-center">Expand <ArrowsPointingOutIcon className="ml-1 w-4 h-4"/></span>
|
||||
}</button>
|
||||
<div className="relative z-10"><IRCLogsDropdown/></div>
|
||||
<IRCLogsDropdown/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -478,6 +478,7 @@ const ListItemDropdown = ({
|
|||
>
|
||||
<DeleteModal
|
||||
isOpen={deleteModalIsOpen}
|
||||
isLoading={deleteMutation.isLoading}
|
||||
toggle={toggleDeleteModal}
|
||||
buttonRef={cancelModalButtonRef}
|
||||
deleteAction={() => {
|
||||
|
@ -665,33 +666,11 @@ export const Events = ({ network, channel }: EventsProps) => {
|
|||
setLogs([]);
|
||||
}, [settings.scrollOnNewLog]);
|
||||
|
||||
// const { handleSubmit, register , resetField } = useForm<IrcMsg>({
|
||||
// defaultValues: { msg: "" },
|
||||
// mode: "onBlur"
|
||||
// });
|
||||
|
||||
// const cmdMutation = useMutation({
|
||||
// mutationFn: (data: SendIrcCmdRequest) => APIClient.irc.sendCmd(data),
|
||||
// onSuccess: (_, _variables) => {
|
||||
// resetField("msg");
|
||||
// },
|
||||
// onError: () => {
|
||||
// toast.custom((t) => (
|
||||
// <Toast type="error" body="Error sending IRC cmd" t={t} />
|
||||
// ));
|
||||
// }
|
||||
// });
|
||||
|
||||
// const onSubmit = (msg: IrcMsg) => {
|
||||
// const payload = { network_id: network.id, nick: network.nick, server: network.server, channel: channel, msg: msg.msg };
|
||||
// cmdMutation.mutate(payload);
|
||||
// };
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
"dark:bg-gray-800 rounded-lg shadow-lg p-2",
|
||||
isFullscreen ? "fixed top-0 left-0 w-screen h-screen z-50" : ""
|
||||
isFullscreen ? "fixed top-0 left-0 right-0 bottom-0 w-screen h-screen z-50" : ""
|
||||
)}
|
||||
>
|
||||
<div className="flex relative">
|
||||
|
@ -725,21 +704,6 @@ export const Events = ({ network, channel }: EventsProps) => {
|
|||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/*<div>*/}
|
||||
{/* <form onSubmit={handleSubmit(onSubmit)}>*/}
|
||||
{/* <input*/}
|
||||
{/* id="msg"*/}
|
||||
{/* {...(register && register("msg"))}*/}
|
||||
{/* type="text"*/}
|
||||
{/* minLength={2}*/}
|
||||
{/* className={classNames(*/}
|
||||
{/* "focus:ring-blue-500 dark:focus:ring-blue-500 focus:border-blue-500 dark:focus:border-blue-500 border-gray-300 dark:border-gray-700",*/}
|
||||
{/* "block w-full dark:bg-gray-900 shadow-sm dark:text-gray-100 sm:text-sm rounded-md"*/}
|
||||
{/* )}*/}
|
||||
{/* />*/}
|
||||
{/* </form>*/}
|
||||
{/*</div>*/}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -758,7 +722,7 @@ const IRCLogsDropdown = () => {
|
|||
}));
|
||||
|
||||
return (
|
||||
<Menu as="div">
|
||||
<Menu as="div" className="relative">
|
||||
<Menu.Button>
|
||||
<button className="flex items-center text-gray-800 dark:text-gray-400 p-1 px-2 rounded shadow bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600">
|
||||
<span className="flex items-center">Options <Cog6ToothIcon className="ml-1 w-4 h-4"/></span>
|
||||
|
@ -774,7 +738,7 @@ const IRCLogsDropdown = () => {
|
|||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items
|
||||
className="absolute right-0 mt-2 bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700 rounded-md shadow-lg ring-1 ring-black ring-opacity-10 focus:outline-none"
|
||||
className="absolute z-10 right-0 mt-2 bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700 rounded-md shadow-lg ring-1 ring-black ring-opacity-10 focus:outline-none"
|
||||
>
|
||||
<div className="p-3">
|
||||
<Menu.Item>
|
||||
|
@ -786,7 +750,6 @@ const IRCLogsDropdown = () => {
|
|||
/>
|
||||
)}
|
||||
</Menu.Item>
|
||||
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
|
|
|
@ -101,6 +101,7 @@ function DeleteReleases() {
|
|||
<div className="flex justify-between items-center rounded-md shadow-sm">
|
||||
<DeleteModal
|
||||
isOpen={deleteModalIsOpen}
|
||||
isLoading={deleteOlderMutation.isLoading}
|
||||
toggle={toggleDeleteModal}
|
||||
buttonRef={cancelModalButtonRef}
|
||||
deleteAction={deleteOlderReleases}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue