mirror of
https://github.com/idanoo/autobrr
synced 2025-07-26 02:09:13 +00:00
chore(web): relocations and cleanups (#957)
* delete manifest (vite-plugin-pwa generates it) * fix upper case letter on screen components * fix imports of screens components missing upper case * remove default export from Base.tsx * move RegexPlayground to settings import * replace some relative path imports * remove React and ununsed imports * small alignments on vite.config.ts * move Dashboard and Releases to screens * move filters/index.tsx to filters/index.ts * remove default export from APIKeyAddForm * remove default export from FilterAddForm * organize imports and exports for the router * add .vscode workspace to gitignore * some touchs on .gitignore file * fix some eslint rules
This commit is contained in:
parent
72bb2ddadb
commit
c7ec93722b
41 changed files with 187 additions and 230 deletions
|
@ -3,7 +3,7 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import React, { Fragment } from "react";
|
||||
import { Fragment } from "react";
|
||||
import { Link, NavLink, Outlet } from "react-router-dom";
|
||||
import { Disclosure, Menu, Transition } from "@headlessui/react";
|
||||
import { ArrowTopRightOnSquareIcon, UserIcon } from "@heroicons/react/24/solid";
|
||||
|
@ -30,7 +30,7 @@ const nav: Array<NavItem> = [
|
|||
{ name: "Logs", path: "/logs" }
|
||||
];
|
||||
|
||||
export default function Base() {
|
||||
export const Base = () => {
|
||||
const authContext = AuthContext.useValue();
|
||||
|
||||
const { data } = useQuery({
|
||||
|
@ -250,4 +250,4 @@ export default function Base() {
|
|||
<Outlet />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import { Stats } from "./Stats";
|
||||
import { ActivityTable } from "./ActivityTable";
|
||||
import { Stats } from "./dashboard/Stats";
|
||||
import { ActivityTable } from "./dashboard/ActivityTable";
|
||||
|
||||
export const Dashboard = () => (
|
||||
<main className="py-10">
|
||||
|
@ -13,4 +13,4 @@ export const Dashboard = () => (
|
|||
<ActivityTable />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
);
|
|
@ -4,7 +4,6 @@
|
|||
*/
|
||||
|
||||
import { Fragment, useEffect, useRef, useState } from "react";
|
||||
import { ExclamationTriangleIcon } from "@heroicons/react/24/solid";
|
||||
import format from "date-fns/format";
|
||||
import { DebounceInput } from "react-debounce-input";
|
||||
import {
|
||||
|
@ -49,7 +48,7 @@ export const Logs = () => {
|
|||
|
||||
const [logs, setLogs] = useState<LogEvent[]>([]);
|
||||
const [searchFilter, setSearchFilter] = useState("");
|
||||
const [regexPattern, setRegexPattern] = useState<RegExp | null>(null);
|
||||
const [_regexPattern, setRegexPattern] = useState<RegExp | null>(null);
|
||||
const [filteredLogs, setFilteredLogs] = useState<LogEvent[]>([]);
|
||||
const [isInvalidRegex, setIsInvalidRegex] = useState(false);
|
||||
|
||||
|
@ -170,7 +169,7 @@ export const Logs = () => {
|
|||
};
|
||||
|
||||
export const LogFiles = () => {
|
||||
const { isLoading, data } = useQuery({
|
||||
const { data } = useQuery({
|
||||
queryKey: ["log-files"],
|
||||
queryFn: () => APIClient.logs.files(),
|
||||
retry: false,
|
||||
|
@ -323,7 +322,7 @@ const LogsDropdown = () => {
|
|||
>
|
||||
<div className="p-3">
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
{() => (
|
||||
<Checkbox
|
||||
label="Scroll to bottom on new message"
|
||||
value={settings.scrollOnNewLog}
|
||||
|
@ -332,7 +331,7 @@ const LogsDropdown = () => {
|
|||
)}
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
{() => (
|
||||
<Checkbox
|
||||
label="Indent log lines"
|
||||
description="Indent each log line according to their respective starting position."
|
||||
|
@ -342,7 +341,7 @@ const LogsDropdown = () => {
|
|||
)}
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
{() => (
|
||||
<Checkbox
|
||||
label="Hide wrapped text"
|
||||
description="Hides text that is meant to be wrapped."
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import { ReleaseTable } from "./ReleaseTable";
|
||||
import { ReleaseTable } from "./releases/ReleaseTable";
|
||||
|
||||
export const Releases = () => (
|
||||
<main>
|
||||
|
@ -16,4 +16,4 @@ export const Releases = () => (
|
|||
<ReleaseTable />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
);
|
|
@ -83,7 +83,7 @@ function SidebarNav({ subNavigation }: SidebarNavProps) {
|
|||
);
|
||||
}
|
||||
|
||||
export default function Settings() {
|
||||
export function Settings() {
|
||||
return (
|
||||
<main>
|
||||
<header className="py-10">
|
||||
|
@ -103,4 +103,3 @@ export default function Settings() {
|
|||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ export const Onboarding = () => {
|
|||
|
||||
if (!values.username)
|
||||
obj.username = "Required";
|
||||
|
||||
|
||||
if (!values.password1)
|
||||
obj.password1 = "Required";
|
||||
|
||||
|
@ -32,7 +32,7 @@ export const Onboarding = () => {
|
|||
|
||||
if (values.password1 !== values.password2)
|
||||
obj.password2 = "Passwords don't match!";
|
||||
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
|
@ -83,4 +83,3 @@ export const Onboarding = () => {
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -3,5 +3,5 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
export { default as Filters } from "./list";
|
||||
export { default as FilterDetails } from "./details";
|
||||
export { Login } from "./Login";
|
||||
export { Onboarding } from "./Onboarding";
|
|
@ -3,7 +3,7 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import React, { Fragment, useEffect, useRef, useState } from "react";
|
||||
import { Fragment, useEffect, useRef, useState } from "react";
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { Field, FieldArray, FieldProps, FormikValues, useFormikContext } from "formik";
|
||||
import { Dialog, Switch as SwitchBasic, Transition } from "@headlessui/react";
|
||||
|
@ -24,7 +24,7 @@ import { EmptyListState } from "@components/emptystates";
|
|||
import { useToggle } from "@hooks/hooks";
|
||||
import { classNames } from "@utils";
|
||||
import { DeleteModal } from "@components/modals";
|
||||
import { CollapsableSection } from "./details";
|
||||
import { CollapsableSection } from "./Details";
|
||||
import { TextArea } from "@components/inputs/input";
|
||||
import Toast from "@components/notifications/Toast";
|
||||
|
||||
|
@ -121,7 +121,7 @@ interface TypeFormProps {
|
|||
|
||||
const TypeForm = ({ action, idx, clients }: TypeFormProps) => {
|
||||
const { setFieldValue } = useFormikContext();
|
||||
|
||||
|
||||
const resetClientField = (action: Action, idx: number, prevActionType: string): void => {
|
||||
const fieldName = `actions.${idx}.client_id`;
|
||||
|
||||
|
@ -728,4 +728,4 @@ function FilterActionsItem({ action, clients, idx, initialEdit, remove }: Filter
|
|||
)}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { useEffect, useRef, ReactNode } from "react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { NavLink, Route, Routes, useLocation, useNavigate, useParams } from "react-router-dom";
|
||||
import { toast } from "react-hot-toast";
|
||||
|
@ -11,7 +11,6 @@ import { Form, Formik, FormikValues, useFormikContext } from "formik";
|
|||
import { z } from "zod";
|
||||
import { toFormikValidationSchema } from "zod-formik-adapter";
|
||||
import { ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/24/solid";
|
||||
import TextareaAutosize from "react-textarea-autosize";
|
||||
|
||||
import {
|
||||
CODECS_OPTIONS,
|
||||
|
@ -47,10 +46,9 @@ import DEBUG from "@components/debug";
|
|||
import Toast from "@components/notifications/Toast";
|
||||
import { DeleteModal } from "@components/modals";
|
||||
import { TitleSubtitle } from "@components/headings";
|
||||
import { RegexTextAreaField, TextArea } from "@components/inputs/input";
|
||||
import { TextAreaAutoResize } from "../../components/inputs/input";
|
||||
import { FilterActions } from "./action";
|
||||
import { filterKeys } from "./list";
|
||||
import { RegexTextAreaField, TextArea, TextAreaAutoResize } from "@components/inputs/input";
|
||||
import { FilterActions } from "./Action";
|
||||
import { filterKeys } from "./List";
|
||||
|
||||
interface tabType {
|
||||
name: string;
|
||||
|
@ -214,7 +212,7 @@ const schema = z.object({
|
|||
actions: z.array(actionSchema)
|
||||
});
|
||||
|
||||
export default function FilterDetails() {
|
||||
export function FilterDetails() {
|
||||
const queryClient = useQueryClient();
|
||||
const navigate = useNavigate();
|
||||
const { filterId } = useParams<{ filterId: string }>();
|
||||
|
@ -223,6 +221,7 @@ export default function FilterDetails() {
|
|||
navigate("/filters");
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const id = parseInt(filterId!);
|
||||
|
||||
const { isLoading, data: filter } = useQuery({
|
||||
|
@ -695,7 +694,7 @@ function WarningAlert({ text, alert, colors }: WarningAlertProps) {
|
|||
interface CollapsableSectionProps {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
children: React.ReactNode;
|
||||
children: ReactNode;
|
||||
defaultOpen?: boolean;
|
||||
}
|
||||
|
||||
|
@ -795,4 +794,3 @@ export function External() {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -77,26 +77,26 @@ const FilterListReducer = (state: FilterListState, action: Actions): FilterListS
|
|||
}
|
||||
};
|
||||
|
||||
export default function Filters() {
|
||||
export function Filters() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const [createFilterIsOpen, setCreateFilterIsOpen] = useState(false);
|
||||
const toggleCreateFilter = () => {
|
||||
setCreateFilterIsOpen(!createFilterIsOpen);
|
||||
};
|
||||
};
|
||||
|
||||
const [showImportModal, setShowImportModal] = useState(false);
|
||||
const [importJson, setImportJson] = useState("");
|
||||
|
||||
|
||||
// This function handles the import of a filter from a JSON string
|
||||
const handleImportJson = async () => {
|
||||
try {
|
||||
const importedData = JSON.parse(importJson);
|
||||
|
||||
|
||||
// Extract the filter data and name from the imported object
|
||||
const importedFilter = importedData.data;
|
||||
const filterName = importedData.name;
|
||||
|
||||
|
||||
// Check if the required properties are present and add them with default values if they are missing
|
||||
const requiredProperties = ["resolutions", "sources", "codecs", "containers"];
|
||||
requiredProperties.forEach((property) => {
|
||||
|
@ -104,10 +104,10 @@ export default function Filters() {
|
|||
importedFilter[property] = [];
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Fetch existing filters from the API
|
||||
const existingFilters = await APIClient.filters.getAll();
|
||||
|
||||
|
||||
// Create a unique filter title by appending an incremental number if title is taken by another filter
|
||||
let nameCounter = 0;
|
||||
let uniqueFilterName = filterName;
|
||||
|
@ -115,18 +115,18 @@ export default function Filters() {
|
|||
nameCounter++;
|
||||
uniqueFilterName = `${filterName}-${nameCounter}`;
|
||||
}
|
||||
|
||||
|
||||
// Create a new filter using the API
|
||||
const newFilter: Filter = {
|
||||
...importedFilter,
|
||||
name: uniqueFilterName
|
||||
};
|
||||
|
||||
|
||||
await APIClient.filters.create(newFilter);
|
||||
|
||||
|
||||
// Update the filter list
|
||||
queryClient.invalidateQueries({ queryKey: filterKeys.lists() });
|
||||
|
||||
|
||||
toast.custom((t) => <Toast type="success" body="Filter imported successfully." t={t} />);
|
||||
setShowImportModal(false);
|
||||
} catch (error) {
|
||||
|
@ -135,7 +135,7 @@ export default function Filters() {
|
|||
toast.custom((t) => <Toast type="error" body="Failed to import JSON data. Please check your input." t={t} />);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<main>
|
||||
<FilterAddForm isOpen={createFilterIsOpen} toggle={toggleCreateFilter} />
|
||||
|
@ -369,9 +369,9 @@ const FilterItemDropdown = ({ filter, onToggle }: FilterItemDropdownProps) => {
|
|||
external_webhook_data: any;
|
||||
external_webhook_expect_status: any;
|
||||
};
|
||||
|
||||
|
||||
const completeFilter = await APIClient.filters.getByID(filter.id) as Partial<CompleteFilterType>;
|
||||
|
||||
|
||||
// Extract the filter name and remove unwanted properties
|
||||
const title = completeFilter.name;
|
||||
delete completeFilter.name;
|
||||
|
@ -389,7 +389,7 @@ const FilterItemDropdown = ({ filter, onToggle }: FilterItemDropdownProps) => {
|
|||
delete completeFilter.external_webhook_host;
|
||||
delete completeFilter.external_webhook_data;
|
||||
delete completeFilter.external_webhook_expect_status;
|
||||
|
||||
|
||||
// Remove properties with default values from the exported filter to minimize the size of the JSON string
|
||||
["enabled", "priority", "smart_episode", "resolutions", "sources", "codecs", "containers", "tags_match_logic", "except_tags_match_logic"].forEach((key) => {
|
||||
const value = completeFilter[key as keyof CompleteFilterType];
|
||||
|
@ -400,7 +400,7 @@ const FilterItemDropdown = ({ filter, onToggle }: FilterItemDropdownProps) => {
|
|||
} else if (["tags_match_logic", "except_tags_match_logic"].includes(key) && value === "ANY") {
|
||||
delete completeFilter[key as keyof CompleteFilterType];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Create a JSON string from the filter data, including a name and version
|
||||
const json = JSON.stringify(
|
||||
|
@ -412,7 +412,7 @@ const FilterItemDropdown = ({ filter, onToggle }: FilterItemDropdownProps) => {
|
|||
null,
|
||||
4
|
||||
);
|
||||
|
||||
|
||||
const finalJson = discordFormat ? "```JSON\n" + json + "\n```" : json;
|
||||
|
||||
const copyTextToClipboard = (text: string) => {
|
||||
|
@ -423,7 +423,7 @@ const FilterItemDropdown = ({ filter, onToggle }: FilterItemDropdownProps) => {
|
|||
document.body.appendChild(textarea);
|
||||
textarea.focus();
|
||||
textarea.select();
|
||||
|
||||
|
||||
try {
|
||||
const successful = document.execCommand("copy");
|
||||
if (successful) {
|
||||
|
@ -435,10 +435,10 @@ const FilterItemDropdown = ({ filter, onToggle }: FilterItemDropdownProps) => {
|
|||
console.error("Unable to copy text", err);
|
||||
toast.custom((t) => <Toast type="error" body="Failed to copy JSON to clipboard." t={t} />);
|
||||
}
|
||||
|
||||
|
||||
document.body.removeChild(textarea);
|
||||
};
|
||||
|
||||
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(finalJson).then(() => {
|
||||
toast.custom((t) => <Toast type="success" body="Filter copied to clipboard." t={t} />);
|
||||
|
@ -448,7 +448,7 @@ const FilterItemDropdown = ({ filter, onToggle }: FilterItemDropdownProps) => {
|
|||
} else {
|
||||
copyTextToClipboard(finalJson);
|
||||
}
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.custom((t) => <Toast type="error" body="Failed to get filter data." t={t} />);
|
||||
|
@ -567,7 +567,7 @@ const FilterItemDropdown = ({ filter, onToggle }: FilterItemDropdownProps) => {
|
|||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Export JSON to Discord
|
||||
Export JSON to Discord
|
||||
</button>
|
||||
)}
|
||||
</Menu.Item>
|
||||
|
@ -726,7 +726,7 @@ function FilterListItem({ filter, values, idx }: FilterListItemProps) {
|
|||
)
|
||||
}
|
||||
>
|
||||
Actions: {filter.actions_count}
|
||||
Actions: {filter.actions_count}
|
||||
</span>
|
||||
</span>
|
||||
{filter.actions_count === 0 && (
|
||||
|
@ -802,7 +802,7 @@ function FilterIndexers({ indexers }: FilterIndexersProps) {
|
|||
className="mr-2 inline-flex items-center px-2.5 py-0.5 rounded-md text-sm font-medium bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-400"
|
||||
title={res.map(v => v.name).toString()}
|
||||
>
|
||||
+{indexers.length - 2}
|
||||
+{indexers.length - 2}
|
||||
</span>
|
||||
</>
|
||||
);
|
7
web/src/screens/filters/index.ts
Normal file
7
web/src/screens/filters/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright (c) 2021 - 2023, Ludvig Lundgren and the autobrr contributors.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
export { Filters } from "./List";
|
||||
export { FilterDetails } from "./Details";
|
|
@ -75,4 +75,4 @@ function ActionSettings() {
|
|||
);
|
||||
}
|
||||
|
||||
export default ActionSettings;
|
||||
export default ActionSettings;
|
||||
|
|
|
@ -10,7 +10,7 @@ import { TrashIcon } from "@heroicons/react/24/outline";
|
|||
|
||||
import { KeyField } from "@components/fields/text";
|
||||
import { DeleteModal } from "@components/modals";
|
||||
import APIKeyAddForm from "@forms/settings/APIKeyAddForm";
|
||||
import { APIKeyAddForm } from "@forms/settings/APIKeyAddForm";
|
||||
import Toast from "@components/notifications/Toast";
|
||||
import { APIClient } from "@api/APIClient";
|
||||
import { useToggle } from "@hooks/hooks";
|
||||
|
|
|
@ -51,7 +51,7 @@ function useSort(items: ListItemProps["clients"][], config?: SortConfig) {
|
|||
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 bValue = sortConfig.key === "enabled" ? (b[sortConfig.key] ?? false) as number | boolean | string : b[sortConfig.key] as number | boolean | string;
|
||||
|
||||
|
||||
if (aValue < bValue) {
|
||||
return sortConfig.direction === "ascending" ? -1 : 1;
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ function useSort(items: ListItemProps["clients"][], config?: SortConfig) {
|
|||
return sortConfig.direction === "ascending" ? 1 : -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
});
|
||||
|
||||
return sortableItems;
|
||||
}, [items, sortConfig]);
|
||||
|
@ -80,7 +80,7 @@ function useSort(items: ListItemProps["clients"][], config?: SortConfig) {
|
|||
if (!sortConfig || sortConfig.key !== key) {
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
return sortConfig.direction === "ascending" ? "↑" : "↓";
|
||||
};
|
||||
|
||||
|
@ -194,7 +194,7 @@ function DownloadClientSettings() {
|
|||
onClick={() => sortedClients.requestSort("enabled")}>
|
||||
Enabled <span className="sort-indicator">{sortedClients.getSortIndicator("enabled")}</span>
|
||||
</div>
|
||||
<div
|
||||
<div
|
||||
className="col-span-6 sm:col-span-4 lg:col-span-4 pl-12 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer"
|
||||
onClick={() => sortedClients.requestSort("name")}
|
||||
>
|
||||
|
@ -225,4 +225,4 @@ function DownloadClientSettings() {
|
|||
);
|
||||
}
|
||||
|
||||
export default DownloadClientSettings;
|
||||
export default DownloadClientSettings;
|
||||
|
|
|
@ -50,7 +50,7 @@ function useSort(items: ListItemProps["feed"][], config?: SortConfig) {
|
|||
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 bValue = sortConfig.key === "enabled" ? (b[sortConfig.key] ?? false) as number | boolean | string : b[sortConfig.key] as number | boolean | string;
|
||||
|
||||
|
||||
if (aValue < bValue) {
|
||||
return sortConfig.direction === "ascending" ? -1 : 1;
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ function useSort(items: ListItemProps["feed"][], config?: SortConfig) {
|
|||
return sortConfig.direction === "ascending" ? 1 : -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
});
|
||||
|
||||
return sortableItems;
|
||||
}, [items, sortConfig]);
|
||||
|
@ -73,13 +73,13 @@ function useSort(items: ListItemProps["feed"][], config?: SortConfig) {
|
|||
}
|
||||
setSortConfig({ key, direction });
|
||||
};
|
||||
|
||||
|
||||
|
||||
const getSortIndicator = (key: keyof ListItemProps["feed"]) => {
|
||||
if (!sortConfig || sortConfig.key !== key) {
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
return sortConfig.direction === "ascending" ? "↑" : "↓";
|
||||
};
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ function useSort(items: ListItemProps["indexer"][], config?: SortConfig) {
|
|||
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 bValue = sortConfig.key === "enabled" ? (b[sortConfig.key] ?? false) as number | boolean | string : b[sortConfig.key] as number | boolean | string;
|
||||
|
||||
|
||||
if (aValue < bValue) {
|
||||
return sortConfig.direction === "ascending" ? -1 : 1;
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ function useSort(items: ListItemProps["indexer"][], config?: SortConfig) {
|
|||
return sortConfig.direction === "ascending" ? 1 : -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
});
|
||||
|
||||
return sortableItems;
|
||||
}, [items, sortConfig]);
|
||||
|
@ -69,7 +69,7 @@ function useSort(items: ListItemProps["indexer"][], config?: SortConfig) {
|
|||
if (!sortConfig || sortConfig.key !== key) {
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
return sortConfig.direction === "ascending" ? "↑" : "↓";
|
||||
};
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import { APIClient } from "@api/APIClient";
|
|||
import { GithubRelease } from "@app/types/Update";
|
||||
import Toast from "@components/notifications/Toast";
|
||||
import { LogLevelOptions, SelectOption } from "@domain/constants";
|
||||
import { LogFiles } from "../Logs";
|
||||
import { LogFiles } from "@screens/Logs";
|
||||
|
||||
interface RowItemProps {
|
||||
label: string;
|
||||
|
@ -195,4 +195,4 @@ function LogSettings() {
|
|||
);
|
||||
}
|
||||
|
||||
export default LogSettings;
|
||||
export default LogSettings;
|
||||
|
|
|
@ -123,13 +123,13 @@ function ListItem({ notification }: ListItemProps) {
|
|||
queryClient.invalidateQueries({ queryKey: notificationKeys.lists() });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const onToggleMutation = (newState: boolean) => {
|
||||
mutation.mutate({
|
||||
...notification,
|
||||
enabled: newState
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<li key={notification.id} className="text-gray-500 dark:text-gray-400">
|
||||
|
|
|
@ -5,14 +5,14 @@
|
|||
|
||||
import { useRef, useState } from "react";
|
||||
|
||||
export const RegexPlayground = () => {
|
||||
const RegexPlayground = () => {
|
||||
const regexRef = useRef<HTMLInputElement>(null);
|
||||
const [output, setOutput] = useState<Array<React.ReactElement>>();
|
||||
|
||||
const onInput = (text: string) => {
|
||||
if (!regexRef || !regexRef.current)
|
||||
return;
|
||||
|
||||
|
||||
const regexp = new RegExp(regexRef.current.value, "g");
|
||||
|
||||
const results: Array<React.ReactElement> = [];
|
||||
|
@ -50,7 +50,7 @@ export const RegexPlayground = () => {
|
|||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (lastIndex > 0)
|
||||
results.push(<br key={`line-delim-${index}`}/>);
|
||||
});
|
||||
|
@ -107,4 +107,6 @@ export const RegexPlayground = () => {
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export default RegexPlayground;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import React, { useRef, useState } from "react";
|
||||
import { useRef, useState } from "react";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { toast } from "react-hot-toast";
|
||||
|
||||
|
@ -147,4 +147,4 @@ function DeleteReleases() {
|
|||
);
|
||||
}
|
||||
|
||||
export default ReleaseSettings;
|
||||
export default ReleaseSettings;
|
||||
|
|
|
@ -3,12 +3,13 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
export { default as APISettings } from "./Api";
|
||||
export { default as ApplicationSettings } from "./Application";
|
||||
export { default as DownloadClientSettings } from "./DownloadClient";
|
||||
export { default as FeedSettings } from "./Feed";
|
||||
export { default as IndexerSettings } from "./Indexer";
|
||||
export { default as IrcSettings } from "./Irc";
|
||||
export { default as LogSettings } from "./Logs";
|
||||
export { default as NotificationSettings } from "./Notifications";
|
||||
export { default as ReleaseSettings } from "./Releases";
|
||||
export { default as Api } from "./Api";
|
||||
export { default as Application } from "./Application";
|
||||
export { default as DownloadClient } from "./DownloadClient";
|
||||
export { default as Feed } from "./Feed";
|
||||
export { default as Indexer } from "./Indexer";
|
||||
export { default as Irc } from "./Irc";
|
||||
export { default as Logs } from "./Logs";
|
||||
export { default as Notification } from "./Notifications";
|
||||
export { default as Release } from "./Releases";
|
||||
export { default as RegexPlayground } from "./RegexPlayground";
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue