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:
Fabricio Silva 2023-07-21 16:33:51 +01:00 committed by GitHub
parent 72bb2ddadb
commit c7ec93722b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 187 additions and 230 deletions

View file

@ -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>
);
}
};

View file

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

View file

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

View file

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

View file

@ -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>
);
}

View file

@ -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>
);
};

View file

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

View file

@ -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>
);
}
}

View file

@ -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>
);
}

View file

@ -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>
</>
);

View 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";

View file

@ -75,4 +75,4 @@ function ActionSettings() {
);
}
export default ActionSettings;
export default ActionSettings;

View file

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

View file

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

View file

@ -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" ? "↑" : "↓";
};

View file

@ -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" ? "↑" : "↓";
};

View file

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

View file

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

View file

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

View file

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

View file

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