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:
stacksmash76 2023-09-10 12:35:43 +02:00 committed by GitHub
parent cbf668e87c
commit 2fed48e0dd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 845 additions and 737 deletions

View file

@ -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>