mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 16:59:12 +00:00
feat(web): improve irc view (#989)
* inherit font-size * inherit cursor-pointer and bubble click event * improve react keys using ids instead of array position * fix scrollbar corner not being transparent * change irc view height calculation --------- Co-authored-by: martylukyy <35452459+martylukyy@users.noreply.github.com>
This commit is contained in:
parent
a5e05284d0
commit
8d20d2cf39
2 changed files with 60 additions and 44 deletions
|
@ -125,6 +125,9 @@ code {
|
||||||
|
|
||||||
/* Style scrollbars for webkit enabled browsers i.e. Chrome, Edge, and Safari */
|
/* Style scrollbars for webkit enabled browsers i.e. Chrome, Edge, and Safari */
|
||||||
@layer base {
|
@layer base {
|
||||||
|
*::-webkit-scrollbar-corner {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
*::-webkit-scrollbar {
|
*::-webkit-scrollbar {
|
||||||
height: 8px;
|
height: 8px;
|
||||||
width: 8px;
|
width: 8px;
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Fragment, useRef, useState, useMemo, useEffect } from "react";
|
import { Fragment, useRef, useState, useMemo, useEffect, MouseEvent } from "react";
|
||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { LockClosedIcon, LockOpenIcon } from "@heroicons/react/24/solid";
|
import { LockClosedIcon, LockOpenIcon } from "@heroicons/react/24/solid";
|
||||||
import { Menu, Switch, Transition } from "@headlessui/react";
|
import { Menu, Switch, Transition } from "@headlessui/react";
|
||||||
|
@ -53,7 +53,7 @@ function useSort(items: ListItemProps["network"][], config?: SortConfig) {
|
||||||
sortableItems.sort((a, b) => {
|
sortableItems.sort((a, b) => {
|
||||||
const aValue = sortConfig.key === "enabled" ? (a[sortConfig.key] ?? false) as number | boolean | string : a[sortConfig.key] as number | boolean | string;
|
const aValue = sortConfig.key === "enabled" ? (a[sortConfig.key] ?? false) as number | boolean | string : a[sortConfig.key] as number | boolean | string;
|
||||||
const bValue = sortConfig.key === "enabled" ? (b[sortConfig.key] ?? false) as number | boolean | string : b[sortConfig.key] as number | boolean | string;
|
const bValue = sortConfig.key === "enabled" ? (b[sortConfig.key] ?? false) as number | boolean | string : b[sortConfig.key] as number | boolean | string;
|
||||||
|
|
||||||
if (aValue < bValue) {
|
if (aValue < bValue) {
|
||||||
return sortConfig.direction === "ascending" ? -1 : 1;
|
return sortConfig.direction === "ascending" ? -1 : 1;
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ function useSort(items: ListItemProps["network"][], config?: SortConfig) {
|
||||||
return sortConfig.direction === "ascending" ? 1 : -1;
|
return sortConfig.direction === "ascending" ? 1 : -1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
return sortableItems;
|
return sortableItems;
|
||||||
}, [items, sortConfig]);
|
}, [items, sortConfig]);
|
||||||
|
@ -77,13 +77,13 @@ function useSort(items: ListItemProps["network"][], config?: SortConfig) {
|
||||||
}
|
}
|
||||||
setSortConfig({ key, direction });
|
setSortConfig({ key, direction });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const getSortIndicator = (key: keyof ListItemProps["network"]) => {
|
const getSortIndicator = (key: keyof ListItemProps["network"]) => {
|
||||||
if (!sortConfig || sortConfig.key !== key) {
|
if (!sortConfig || sortConfig.key !== key) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
return sortConfig.direction === "ascending" ? "↑" : "↓";
|
return sortConfig.direction === "ascending" ? "↑" : "↓";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -104,7 +104,7 @@ const IrcSettings = () => {
|
||||||
const sortedNetworks = useSort(data || []);
|
const sortedNetworks = useSort(data || []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="lg:col-span-9">
|
<div className="text-sm lg:col-span-9">
|
||||||
<IrcNetworkAddForm isOpen={addNetworkIsOpen} toggle={toggleAddNetwork} />
|
<IrcNetworkAddForm isOpen={addNetworkIsOpen} toggle={toggleAddNetwork} />
|
||||||
|
|
||||||
<div className="py-6 px-4 md:p-6 lg:pb-8">
|
<div className="py-6 px-4 md:p-6 lg:pb-8">
|
||||||
|
@ -113,7 +113,7 @@ const IrcSettings = () => {
|
||||||
<h3 className="text-lg leading-6 font-medium text-gray-900 dark:text-white">
|
<h3 className="text-lg leading-6 font-medium text-gray-900 dark:text-white">
|
||||||
IRC
|
IRC
|
||||||
</h3>
|
</h3>
|
||||||
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
<p className="mt-1 text-gray-500 dark:text-gray-400">
|
||||||
IRC networks and channels. Click on a network to view channel
|
IRC networks and channels. Click on a network to view channel
|
||||||
status.
|
status.
|
||||||
</p>
|
</p>
|
||||||
|
@ -122,7 +122,7 @@ const IrcSettings = () => {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={toggleAddNetwork}
|
onClick={toggleAddNetwork}
|
||||||
className="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"
|
className="relative inline-flex items-center px-4 py-2 border border-transparent shadow-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"
|
||||||
>
|
>
|
||||||
Add new
|
Add new
|
||||||
</button>
|
</button>
|
||||||
|
@ -139,7 +139,7 @@ const IrcSettings = () => {
|
||||||
<span className="animate-ping inline-flex h-full w-full rounded-full bg-green-400 opacity-75" />
|
<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 className="inline-flex absolute rounded-full h-4 w-4 bg-green-500" />
|
||||||
</span>
|
</span>
|
||||||
<span className="text-sm text-gray-800 dark:text-gray-500">Network healthy</span>
|
<span className="text-gray-800 dark:text-gray-500">Network healthy</span>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li className="flex items-center md:pl-2">
|
<li className="flex items-center md:pl-2">
|
||||||
|
@ -147,7 +147,7 @@ const IrcSettings = () => {
|
||||||
className="mr-2 flex h-4 w-4 rounded-full opacity-75 bg-yellow-400 over:text-yellow-600"
|
className="mr-2 flex h-4 w-4 rounded-full opacity-75 bg-yellow-400 over:text-yellow-600"
|
||||||
title="Network unhealthy"
|
title="Network unhealthy"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm text-gray-800 dark:text-gray-500">Network unhealthy</span>
|
<span className="text-gray-800 dark:text-gray-500">Network unhealthy</span>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li className="flex items-center md:pl-2">
|
<li className="flex items-center md:pl-2">
|
||||||
|
@ -156,11 +156,15 @@ const IrcSettings = () => {
|
||||||
title="Network disabled"
|
title="Network disabled"
|
||||||
>
|
>
|
||||||
</span>
|
</span>
|
||||||
<span className="text-sm text-gray-800 dark:text-gray-500">Network disabled</span>
|
<span className="text-gray-800 dark:text-gray-500">Network disabled</span>
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
<div className="flex gap-x-2">
|
<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"}>
|
<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"
|
||||||
|
onClick={toggleExpand}
|
||||||
|
title={expandNetworks ? "collapse" : "expand"}
|
||||||
|
>
|
||||||
{expandNetworks
|
{expandNetworks
|
||||||
? <span className="flex items-center">Collapse <ArrowsPointingInIcon className="ml-1 w-4 h-4"/></span>
|
? <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>
|
: <span className="flex items-center">Expand <ArrowsPointingOutIcon className="ml-1 w-4 h-4"/></span>
|
||||||
|
@ -190,8 +194,8 @@ const IrcSettings = () => {
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{data &&
|
{data &&
|
||||||
sortedNetworks.items.map((network, idx) => (
|
sortedNetworks.items.map((network) => (
|
||||||
<ListItem key={idx} idx={idx} expanded={expandNetworks} network={network} />
|
<ListItem key={network.id} expanded={expandNetworks} network={network} />
|
||||||
))}
|
))}
|
||||||
</ol>
|
</ol>
|
||||||
</section>
|
</section>
|
||||||
|
@ -209,12 +213,11 @@ const IrcSettings = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ListItemProps {
|
interface ListItemProps {
|
||||||
idx: number;
|
|
||||||
network: IrcNetworkWithHealth;
|
network: IrcNetworkWithHealth;
|
||||||
expanded: boolean;
|
expanded: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ListItem = ({ idx, network, expanded }: ListItemProps) => {
|
const ListItem = ({ network, expanded }: ListItemProps) => {
|
||||||
const [updateIsOpen, toggleUpdate] = useToggle(false);
|
const [updateIsOpen, toggleUpdate] = useToggle(false);
|
||||||
const [edit, toggleEdit] = useToggle(false);
|
const [edit, toggleEdit] = useToggle(false);
|
||||||
|
|
||||||
|
@ -236,20 +239,27 @@ const ListItem = ({ idx, network, expanded }: ListItemProps) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li key={idx}>
|
<li>
|
||||||
<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")}>
|
<div
|
||||||
|
className={classNames(
|
||||||
|
"grid grid-cols-12 gap-2 lg:gap-4 items-center py-2 cursor-pointer",
|
||||||
|
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"
|
||||||
|
)}
|
||||||
|
onClick={toggleEdit}
|
||||||
|
>
|
||||||
<IrcNetworkUpdateForm
|
<IrcNetworkUpdateForm
|
||||||
isOpen={updateIsOpen}
|
isOpen={updateIsOpen}
|
||||||
toggle={toggleUpdate}
|
toggle={toggleUpdate}
|
||||||
network={network}
|
network={network}
|
||||||
/>
|
/>
|
||||||
<div className="col-span-2 md:col-span-1 flex pl-5 text-sm text-gray-500 dark:text-gray-400 cursor-pointer">
|
<div className="col-span-2 md:col-span-1 flex pl-5 text-gray-500 dark:text-gray-400">
|
||||||
<Switch
|
<Switch
|
||||||
|
onClick={(e: MouseEvent) => e.stopPropagation()}
|
||||||
checked={network.enabled}
|
checked={network.enabled}
|
||||||
onChange={onToggleMutation}
|
onChange={onToggleMutation}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
network.enabled ? "bg-blue-500" : "bg-gray-200 dark:bg-gray-600",
|
network.enabled ? "bg-blue-500" : "bg-gray-200 dark:bg-gray-600",
|
||||||
"items-center relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
"items-center relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span className="sr-only">Enable</span>
|
<span className="sr-only">Enable</span>
|
||||||
|
@ -262,10 +272,7 @@ const ListItem = ({ idx, network, expanded }: ListItemProps) => {
|
||||||
/>
|
/>
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div className="col-span-8 xs:col-span-3 md:col-span-3 items-center pl-8 font-medium text-gray-900 dark:text-white cursor-pointer">
|
||||||
className="col-span-8 xs:col-span-3 md:col-span-3 items-center pl-8 text-sm font-medium text-gray-900 dark:text-white cursor-pointer"
|
|
||||||
onClick={toggleEdit}
|
|
||||||
>
|
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<span className="relative inline-flex items-center ml-1">
|
<span className="relative inline-flex items-center ml-1">
|
||||||
{network.enabled ? (
|
{network.enabled ? (
|
||||||
|
@ -294,10 +301,7 @@ const ListItem = ({ idx, network, expanded }: ListItemProps) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div className="hidden md:flex col-span-4 md:pl-6 text-gray-500 dark:text-gray-400">
|
||||||
className="hidden md:flex col-span-4 md:pl-6 text-sm text-gray-500 dark:text-gray-400 cursor-pointer"
|
|
||||||
onClick={toggleEdit}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className="overflow-x-auto flex items-center"
|
className="overflow-x-auto flex items-center"
|
||||||
title={network.tls ? "Secured using TLS" : "Insecure, not using TLS"}
|
title={network.tls ? "Secured using TLS" : "Insecure, not using TLS"}
|
||||||
|
@ -322,15 +326,12 @@ const ListItem = ({ idx, network, expanded }: ListItemProps) => {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div className="hidden md:flex col-span-3 items-center md:pl-6 text-gray-500 dark:text-gray-400">
|
||||||
className="hidden md:flex col-span-3 items-center md:pl-6 text-sm text-gray-500 dark:text-gray-400 cursor-pointer"
|
|
||||||
onClick={toggleEdit}
|
|
||||||
>
|
|
||||||
<div className="block truncate">
|
<div className="block truncate">
|
||||||
{network.nick}
|
{network.nick}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-1 text-sm text-gray-500 dark:text-gray-400">
|
<div className="col-span-1 text-gray-500 dark:text-gray-400">
|
||||||
<ListItemDropdown network={network} toggleUpdate={toggleUpdate} />
|
<ListItemDropdown network={network} toggleUpdate={toggleUpdate} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -351,7 +352,7 @@ const ListItem = ({ idx, network, expanded }: ListItemProps) => {
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{network.channels.map((c) => (
|
{network.channels.map((c) => (
|
||||||
<ChannelItem network={network} channel={c} />
|
<ChannelItem key={`${network.id}.${c.id}`} network={network} channel={c} />
|
||||||
))}
|
))}
|
||||||
</ol>
|
</ol>
|
||||||
) : (
|
) : (
|
||||||
|
@ -375,9 +376,17 @@ const ChannelItem = ({ network, channel }: ChannelItemProps) => {
|
||||||
const [viewChannel, toggleView] = useToggle(false);
|
const [viewChannel, toggleView] = useToggle(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li key={channel.id} className={classNames("mb-2 text-gray-500 dark:text-gray-400", viewChannel ? "bg-gray-200 dark:bg-gray-800 rounded-md" : "")}>
|
<li
|
||||||
<div className="grid grid-cols-12 gap-4 items-center py-4 hover:bg-gray-300 dark:hover:bg-gray-800 hover:cursor-pointer rounded-md" onClick={toggleView}>
|
className={classNames(
|
||||||
<div className="col-span-4 flex items-center md:px-6 ">
|
"mb-2 text-gray-500 dark:text-gray-400",
|
||||||
|
viewChannel ? "bg-gray-200 dark:bg-gray-800 rounded-md" : ""
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="grid grid-cols-12 gap-4 items-center py-4 hover:bg-gray-300 dark:hover:bg-gray-800 hover:cursor-pointer rounded-md"
|
||||||
|
onClick={toggleView}
|
||||||
|
>
|
||||||
|
<div className="col-span-4 flex items-center md:px-6">
|
||||||
<span className="relative inline-flex items-center">
|
<span className="relative inline-flex items-center">
|
||||||
{network.enabled ? (
|
{network.enabled ? (
|
||||||
channel.monitoring ? (
|
channel.monitoring ? (
|
||||||
|
@ -385,8 +394,7 @@ const ChannelItem = ({ network, channel }: ChannelItemProps) => {
|
||||||
className="mr-3 flex h-3 w-3 relative"
|
className="mr-3 flex h-3 w-3 relative"
|
||||||
title="monitoring"
|
title="monitoring"
|
||||||
>
|
>
|
||||||
<span
|
<span className="animate-ping inline-flex h-full w-full rounded-full bg-green-400 opacity-75"/>
|
||||||
className="animate-ping inline-flex h-full w-full rounded-full bg-green-400 opacity-75"/>
|
|
||||||
<span className="inline-flex absolute rounded-full h-3 w-3 bg-green-500"/>
|
<span className="inline-flex absolute rounded-full h-3 w-3 bg-green-500"/>
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
|
@ -398,18 +406,20 @@ const ChannelItem = ({ network, channel }: ChannelItemProps) => {
|
||||||
{channel.name}
|
{channel.name}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-4 flex items-center md:px-6 ">
|
<div className="col-span-4 flex items-center md:px-6">
|
||||||
<span title={simplifyDate(channel.monitoring_since)}>
|
<span title={simplifyDate(channel.monitoring_since)}>
|
||||||
{IsEmptyDate(channel.monitoring_since)}
|
{IsEmptyDate(channel.monitoring_since)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-3 flex items-center md:px-6 ">
|
<div className="col-span-3 flex items-center md:px-6">
|
||||||
<span title={simplifyDate(channel.last_announce)}>
|
<span title={simplifyDate(channel.last_announce)}>
|
||||||
{IsEmptyDate(channel.last_announce)}
|
{IsEmptyDate(channel.last_announce)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-1 flex items-center">
|
<div className="col-span-1 flex items-center">
|
||||||
<button className="hover:text-gray-500 px-2 py-1 dark:bg-gray-800 rounded dark:border-gray-900">{viewChannel ? "Hide" : "View"}</button>
|
<button className="hover:text-gray-500 px-2 py-1 dark:bg-gray-800 rounded dark:border-gray-900">
|
||||||
|
{viewChannel ? "Hide" : "View"}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{viewChannel && (
|
{viewChannel && (
|
||||||
|
@ -459,7 +469,10 @@ const ListItemDropdown = ({
|
||||||
const restart = (id: number) => restartMutation.mutate(id);
|
const restart = (id: number) => restartMutation.mutate(id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu as="div">
|
<Menu
|
||||||
|
as="div"
|
||||||
|
onClick={(e: MouseEvent) => e.stopPropagation()}
|
||||||
|
>
|
||||||
<DeleteModal
|
<DeleteModal
|
||||||
isOpen={deleteModalIsOpen}
|
isOpen={deleteModalIsOpen}
|
||||||
toggle={toggleDeleteModal}
|
toggle={toggleDeleteModal}
|
||||||
|
@ -652,7 +665,7 @@ export const Events = ({ network, channel }: EventsProps) => {
|
||||||
<div className="flex relative">
|
<div className="flex relative">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="overflow-y-auto px-2 rounded-lg h-[60vh] min-w-full bg-gray-100 dark:bg-gray-900 overflow-auto">
|
<div className="overflow-y-auto px-2 rounded-lg min-w-full aspect-[2/1] bg-gray-100 dark:bg-gray-900 overflow-auto">
|
||||||
{logs.map((entry, idx) => (
|
{logs.map((entry, idx) => (
|
||||||
<div
|
<div
|
||||||
key={idx}
|
key={idx}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue