feat(irc): improve list view (#466)

* feat(irc): add irc status examples

* feat(irc): add dropdown menu to list

* feat(irc): update heroicons and add expand button

* feat(irc): update heroicons and add expand button
This commit is contained in:
ze0s 2022-09-22 16:39:05 +02:00 committed by GitHub
parent f5faf066a9
commit 300418b9f1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 478 additions and 258 deletions

View file

@ -1,14 +1,28 @@
import { useQuery } from "react-query";
import { useMutation, useQuery, useQueryClient } from "react-query";
import { simplifyDate, IsEmptyDate, classNames } from "../../utils";
import { classNames, IsEmptyDate, simplifyDate } from "../../utils";
import { IrcNetworkAddForm, IrcNetworkUpdateForm } from "../../forms";
import { useToggle } from "../../hooks/hooks";
import { APIClient } from "../../api/APIClient";
import { EmptySimple } from "../../components/emptystates";
import { ExclamationCircleIcon } from "@heroicons/react/outline";
import { LockClosedIcon, LockOpenIcon } from "@heroicons/react/solid";
import { LockClosedIcon, LockOpenIcon } from "@heroicons/react/24/solid";
import { Menu, Transition } from "@headlessui/react";
import { Fragment, useRef } from "react";
import { DeleteModal } from "../../components/modals";
import { toast } from "react-hot-toast";
import Toast from "../../components/notifications/Toast";
import {
ArrowsPointingInIcon,
ArrowsPointingOutIcon,
EllipsisHorizontalIcon,
ExclamationCircleIcon,
PencilSquareIcon,
TrashIcon
} from "@heroicons/react/24/outline";
export const IrcSettings = () => {
const [expandNetworks, toggleExpand] = useToggle(false);
const [addNetworkIsOpen, toggleAddNetwork] = useToggle(false);
const { data } = useQuery("networks", () => APIClient.irc.getNetworks(), {
@ -42,23 +56,63 @@ export const IrcSettings = () => {
</button>
</div>
</div>
<div className="flex justify-between flex-col sm:flex-row mt-10 px-1">
<ol className="flex flex-col sm:flex-row sm:gap-2 pb-4 sm:pb-0 sm:divide-x sm:divide-gray-200 sm:dark:divide-gray-700">
<li className="flex items-center">
<span
className="mr-2 flex h-4 w-4 relative"
title="Network healthy"
>
<span className="animate-ping inline-flex h-full w-full rounded-full bg-green-400 opacity-75" />
<span className="inline-flex absolute rounded-full h-4 w-4 bg-green-500" />
</span>
<span className="text-sm text-gray-800 dark:text-gray-500">Network healthy</span>
</li>
<li className="flex items-center sm:pl-2">
<span
className="mr-2 flex h-4 w-4 rounded-full opacity-75 bg-yellow-400 over:text-yellow-600"
title="Network unhealthy"
/>
<span className="text-sm text-gray-800 dark:text-gray-500">Network unhealthy</span>
</li>
<li className="flex items-center sm:pl-2">
<span
className="mr-2 flex h-4 w-4 rounded-full opacity-75 bg-gray-500"
title="Network disabled"
>
</span>
<span className="text-sm text-gray-800 dark:text-gray-500">Network disabled</span>
</li>
</ol>
<div className="flex gap-x-2">
<button className="flex items-center text-sm 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" onClick={toggleExpand} title={expandNetworks ? "collapse" : "expand"}>
{expandNetworks
? <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>
</div>
{data && data.length > 0 ? (
<section className="mt-6 light:bg-white dark:bg-gray-800 light:shadow sm:rounded-md">
<ol className="min-w-full">
<ol className="min-w-full relative">
<li className="grid grid-cols-12 gap-4 border-b border-gray-200 dark:border-gray-700">
<div className="col-span-3 px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Network
</div>
<div className="col-span-5 px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
<div className="hidden sm:flex col-span-5 px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Server
</div>
<div className="col-span-3 px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
<div className="hidden sm:flex col-span-3 px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Nick
</div>
</li>
{data &&
data.map((network, idx) => (
<ListItem key={idx} idx={idx} network={network} />
<ListItem key={idx} idx={idx} expanded={expandNetworks} network={network} />
))}
</ol>
</section>
@ -78,15 +132,16 @@ export const IrcSettings = () => {
interface ListItemProps {
idx: number;
network: IrcNetworkWithHealth;
expanded: boolean;
}
const ListItem = ({ idx, network }: ListItemProps) => {
const ListItem = ({ idx, network, expanded }: ListItemProps) => {
const [updateIsOpen, toggleUpdate] = useToggle(false);
const [edit, toggleEdit] = useToggle(false);
return (
<li key={idx}>
<div className={classNames("grid grid-cols-12 gap-2 lg:gap-4 items-center py-4", network.enabled && !network.healthy ? "bg-red-50 dark:bg-red-900 hover:bg-red-100 dark:hover:bg-red-800" : "hover:bg-gray-50 dark:hover:bg-gray-700 ")}>
<div className={classNames("grid grid-cols-12 gap-2 lg:gap-4 items-center py-2", network.enabled && !network.healthy ? "bg-red-50 dark:bg-red-900 hover:bg-red-100 dark:hover:bg-red-800" : "hover:bg-gray-50 dark:hover:bg-gray-700 ")}>
<IrcNetworkUpdateForm
isOpen={updateIsOpen}
toggle={toggleUpdate}
@ -94,7 +149,7 @@ const ListItem = ({ idx, network }: ListItemProps) => {
/>
<div
className="col-span-3 items-center sm:px-6 text-sm font-medium text-gray-900 dark:text-white cursor-pointer"
className="col-span-10 xs:col-span-3 sm:col-span-3 items-center sm:px-6 text-sm font-medium text-gray-900 dark:text-white cursor-pointer"
onClick={toggleEdit}
>
<div className="flex">
@ -126,7 +181,7 @@ const ListItem = ({ idx, network }: ListItemProps) => {
</div>
</div>
<div
className="col-span-5 sm:px-6 text-sm text-gray-500 dark:text-gray-400 cursor-pointer"
className="hidden sm:flex col-span-5 sm:px-6 text-sm text-gray-500 dark:text-gray-400 cursor-pointer"
onClick={toggleEdit}
>
<div
@ -155,7 +210,7 @@ const ListItem = ({ idx, network }: ListItemProps) => {
</div>
{network.nickserv && network.nickserv.account ? (
<div
className="col-span-3 items-center sm:px-6 text-sm text-gray-500 dark:text-gray-400 cursor-pointer"
className="hidden sm:flex col-span-3 items-center sm:px-6 text-sm text-gray-500 dark:text-gray-400 cursor-pointer"
onClick={toggleEdit}
>
<div className="overflow-x-auto flex">
@ -166,15 +221,10 @@ const ListItem = ({ idx, network }: ListItemProps) => {
<div className="col-span-3" />
)}
<div className="col-span-1 text-sm text-gray-500 dark:text-gray-400">
<span
className="text-indigo-600 dark:text-gray-300 hover:text-indigo-900 cursor-pointer"
onClick={toggleUpdate}
>
Edit
</span>
<ListItemDropdown network={network} toggleUpdate={toggleUpdate} />
</div>
</div>
{edit && (
{(edit || expanded) && (
<div className="px-4 py-4 flex border-b border-x-0 dark:border-gray-600 dark:bg-gray-700">
<div className="min-w-full">
{network.channels.length > 0 ? (
@ -238,3 +288,168 @@ const ListItem = ({ idx, network }: ListItemProps) => {
</li>
);
};
interface ListItemDropdownProps {
network: IrcNetwork;
toggleUpdate: () => void;
}
const ListItemDropdown = ({
network,
toggleUpdate
}: ListItemDropdownProps) => {
const cancelModalButtonRef = useRef(null);
const queryClient = useQueryClient();
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false);
const deleteMutation = useMutation(
(id: number) => APIClient.irc.deleteNetwork(id),
{
onSuccess: () => {
queryClient.invalidateQueries(["networks"]);
queryClient.invalidateQueries(["networks", network.id]);
toast.custom((t) => <Toast type="success" body={`Network ${network.name} was deleted`} t={t}/>);
}
}
);
const restartMutation = useMutation(
(id: number) => APIClient.irc.restartNetwork(id),
{
onSuccess: () => {
toast.custom((t) => <Toast type="success"
body={`${network.name} was successfully restarted`}
t={t}/>);
queryClient.invalidateQueries(["networks"]);
queryClient.invalidateQueries(["networks", network.id]);
}
}
);
const restart = (id: number) => {
restartMutation.mutate(id);
};
return (
<Menu as="div">
<DeleteModal
isOpen={deleteModalIsOpen}
toggle={toggleDeleteModal}
buttonRef={cancelModalButtonRef}
deleteAction={() => {
deleteMutation.mutate(network.id);
toggleDeleteModal();
}}
title={`Remove network: ${network.name}`}
text="Are you sure you want to remove this network? This action cannot be undone."
/>
<Menu.Button className="px-4 py-2">
<EllipsisHorizontalIcon
className="w-5 h-5 text-gray-700 hover:text-gray-900 dark:text-gray-100 dark:hover:text-gray-400"
aria-hidden="true"
/>
</Menu.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items
className="absolute right-0 w-32 sm:w-56 mt-2 origin-top-right 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="px-1 py-1">
<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={() => toggleUpdate()}
>
<PencilSquareIcon
className={classNames(
active ? "text-white" : "text-blue-500",
"w-5 h-5 mr-2"
)}
aria-hidden="true"
/>
Edit
</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={() => onToggle(!network.enabled)}*/}
{/* >*/}
{/* <SwitchHorizontalIcon*/}
{/* className={classNames(*/}
{/* active ? "text-white" : "text-blue-500",*/}
{/* "w-5 h-5 mr-2"*/}
{/* )}*/}
{/* aria-hidden="true"*/}
{/* />*/}
{/* {network.enabled ? "Disable" : "Enable"}*/}
{/* </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={() => restart(network.id)}
>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className={classNames(
active ? "text-white" : "text-blue-500",
"w-5 h-5 mr-2"
)}>
<path strokeLinecap="round" strokeLinejoin="round" d="M5.636 5.636a9 9 0 1012.728 0M12 3v9" />
</svg>
Restart
</button>
)}
</Menu.Item>
</div>
<div className="px-1 py-1">
<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={() => toggleDeleteModal()}
>
<TrashIcon
className={classNames(
active ? "text-white" : "text-red-500",
"w-5 h-5 mr-2"
)}
aria-hidden="true"
/>
Delete
</button>
)}
</Menu.Item>
</div>
</Menu.Items>
</Transition>
</Menu>
);
};