mirror of
https://github.com/idanoo/autobrr
synced 2025-07-22 08:19:12 +00:00
build(web): bump vite and cjs node api refactor (#1276)
* refactor: use ES module. To maintain compatibility with vite 6 and since that's where the web are heading too. Also moved some deps to devDeps, better optimized production builds. Changed some of the script command to match how others run or preview it, I think it was still using CRA. * chore: update-lock.yaml * refactor: since we are using ESM now, .cjs .ts required. Changed the file extensions and refactored the .eslintrc.cjs I think there was a lot of bloat from the previous version and removed most of them and keep it simple for now, we can always expand from here a clean slate. * refactor: added .node.json and refactored. * fix(build): APIClient.ts had few error. ESLint: Unexpected lexical declaration in case block.(no-case-declarations) and TS2554: Expected 0-1 arguments, but got 2 we passed the cause to the constructor which it only takes 1 argument so removed it instead, since it's already in the string "Offline". * fix(build): import never used. * fix(build): add the types for react-dom/client. * fix(build): use ESNext instead. * fix(build): hmm why are we missing the types for the import? Added @types/react-table. * chore(lint): fix lint warnings Don't use * for export. * chore(lint): missing deps. React Hook useEffect has a missing dependency: 'validateForm'. Either include it or remove the dependency array * chore(lint): fix import. * chore(lint): fix import. * chore(lint): fix react hook. error React Hook "useMutation" is called conditionally. React Hooks must be called in the exact same order in every component render react-hooks/rules-of-hooks * chore(lint): value never used. 52:10 error '_regexPattern' is assigned a value but never used * chore(lint): add missing dependency to useEffect * chore(lint): fix imports. * chore(lint): add deps to array. * chore(lint): error Unexpected lexical declaration in case block no-case-declarations * chore(lint): remove any and add types. I am not sure about type CompleteFilterType I know it's being used for JSON so might need to use any?? dunno just test it and see if works. * chore(lint): react-hooks/exhaustive-deps * chore(lint): react-hooks/exhaustive-deps * chore(lint): use type guard instead of any. * chore(lint): react-hooks/exhaustive-deps * Revert "chore(lint): remove any and add types." This reverts commit 7b9168fe7826d63cb00e44df8e15fbde49b59174. * chore(web): ignore sourcemap warnings * chore(web): update vite to 5.0.4 * chore: add the new script `pnpm dev` to start the dev env. * chore: add the curly braces. more info: https://eslint.org/docs/latest/rules/no-case-declarations * chore: remove the extra spaces * chore: remove the extra spaces * chore: add the curly braces. * chore: add curly braces * remove text-shadow property and comment * revert switch case braces for Actions.tsx --------- Co-authored-by: martylukyy <35452459+martylukyy@users.noreply.github.com>
This commit is contained in:
parent
a89a1a55d9
commit
92646dacc8
36 changed files with 1100 additions and 1029 deletions
|
@ -22,9 +22,9 @@ test
|
|||
#!web/yarn.lock
|
||||
#!web/.yarnrc.yml
|
||||
#!web/.yarn/releases
|
||||
#!web/.eslintrc.js
|
||||
#!web/postcss.config.js
|
||||
#!web/tailwind.config.js
|
||||
#!web/.eslintrc.cjs
|
||||
#!web/postcss.config.cts
|
||||
#!web/tailwind.config.ts
|
||||
#!web/tsconfig.json
|
||||
#!web/index.html
|
||||
#!web/vite.config.ts
|
||||
|
|
18
web/.eslintrc.cjs
Normal file
18
web/.eslintrc.cjs
Normal file
|
@ -0,0 +1,18 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2020: true },
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
],
|
||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['react-refresh'],
|
||||
rules: {
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
parser: "@typescript-eslint/parser",
|
||||
plugins: [
|
||||
"@typescript-eslint",
|
||||
],
|
||||
// If we ever decide on a code-style, I'll leave this here.
|
||||
//extends: [
|
||||
// "airbnb",
|
||||
// "airbnb/hooks",
|
||||
// "airbnb-typescript",
|
||||
//],
|
||||
rules: {
|
||||
// Turn off pesky "react not in scope" error while
|
||||
// we transition to proper ESLint support
|
||||
"react/react-in-jsx-scope": "off",
|
||||
// Add a UNIX-style linebreak at the end of each file
|
||||
"linebreak-style": ["error", "unix"],
|
||||
// Allow only double quotes and backticks
|
||||
quotes: ["error", "double"],
|
||||
// Warn if a line isn't indented with a multiple of 2
|
||||
indent: ["warn", 2, { "SwitchCase": 0 }],
|
||||
// Don't enforce any particular brace style
|
||||
curly: "off",
|
||||
// Allow only vars starting with _ to be ununsed vars
|
||||
"no-unused-vars": ["warn", {
|
||||
"varsIgnorePattern": "^_",
|
||||
"argsIgnorePattern": "^_",
|
||||
"caughtErrorsIgnorePattern": "^_",
|
||||
"ignoreRestSiblings": true
|
||||
}],
|
||||
// Let's keep these off for now and
|
||||
// maybe turn these back on sometime in the future
|
||||
"import/prefer-default-export": "off",
|
||||
"react/function-component-definition": "off",
|
||||
"nonblock-statement-body-position": ["warn", "below"]
|
||||
},
|
||||
// Conditionally run the following configuration only for TS files.
|
||||
// Otherwise, this will create inter-op problems with JS files.
|
||||
overrides: [
|
||||
{
|
||||
// Run only .ts and .tsx files
|
||||
files: ["*.ts", "*.tsx"],
|
||||
// Define the @typescript-eslint plugin schemas
|
||||
extends: [
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
// Don't require strict type-checking for now, since we have too many
|
||||
// dubious statements literred in the code.
|
||||
//"plugin:@typescript-eslint/recommended-requiring-type-checking",
|
||||
],
|
||||
parserOptions: {
|
||||
// project: "tsconfig.json",
|
||||
// This is needed so we can always point to the tsconfig.json
|
||||
// file relative to the current .eslintrc.js file.
|
||||
// Generally, a problem occurrs when "npm run lint"
|
||||
// gets ran from another directory. This fixes it.
|
||||
tsconfigRootDir: __dirname,
|
||||
sourceType: "module",
|
||||
},
|
||||
// Override JS rules and apply @typescript-eslint rules
|
||||
// as they might interfere with eachother.
|
||||
rules: {
|
||||
quotes: "off",
|
||||
"@typescript-eslint/quotes": ["error", "double"],
|
||||
semi: "off",
|
||||
"@typescript-eslint/semi": ["warn", "always"],
|
||||
indent: ["warn", 2, { "SwitchCase": 0 }],
|
||||
"@typescript-eslint/indent": "off",
|
||||
"@typescript-eslint/comma-dangle": "warn",
|
||||
"keyword-spacing": "off",
|
||||
"@typescript-eslint/keyword-spacing": ["error"],
|
||||
"object-curly-spacing": "off",
|
||||
"@typescript-eslint/object-curly-spacing": ["warn", "always"],
|
||||
// Allow only vars starting with _ to be ununsed vars
|
||||
"@typescript-eslint/no-unused-vars": ["warn", {
|
||||
"varsIgnorePattern": "^_",
|
||||
"argsIgnorePattern": "^_",
|
||||
"caughtErrorsIgnorePattern": "^_",
|
||||
"ignoreRestSiblings": true
|
||||
}],
|
||||
// We have quite some "Unexpected any. Specify a different type" warnings.
|
||||
// This disables these warnings since they are false positives afaict.
|
||||
"@typescript-eslint/no-explicit-any": "off"
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
|
@ -6,7 +6,7 @@ This project uses React built with Vite.
|
|||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `pnpm start`
|
||||
### `pnpm dev`
|
||||
|
||||
Runs the app in the development mode.\
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
{
|
||||
"name": "web",
|
||||
"version": "0.2.0",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"homepage": ".",
|
||||
"packageManager": "pnpm@8.10.5",
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"serve": "vite preview",
|
||||
"lint": "eslint src/ --ext .js,.jsx,.ts,.tsx --color",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0 --color",
|
||||
"preview": "vite preview",
|
||||
"lint:watch": "pnpm run lint -- --watch"
|
||||
},
|
||||
"browserslist": {
|
||||
|
@ -35,23 +36,9 @@
|
|||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@tanstack/react-query": "^4.36.1",
|
||||
"@tanstack/react-query-devtools": "^4.36.1",
|
||||
"@types/node": "^20.9.1",
|
||||
"@types/react": "^18.2.37",
|
||||
"@types/react-dom": "^18.2.15",
|
||||
"@types/react-portal": "^4.0.6",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/react-table": "^7.7.18",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"buffer": "^6.0.3",
|
||||
"date-fns": "^2.30.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-plugin-import": "^2.29.0",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-watch": "^8.0.0",
|
||||
"formik": "^2.4.5",
|
||||
"http-proxy-middleware": "^2.0.6",
|
||||
"postcss": "^8.4.31",
|
||||
|
@ -72,12 +59,29 @@
|
|||
"stacktracey": "^2.1.8",
|
||||
"tailwind-lerp-colors": "1.2.1",
|
||||
"tailwindcss": "^3.3.5",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "4.5.0",
|
||||
"vite-plugin-pwa": "^0.16.7",
|
||||
"vite-plugin-svgr": "^4.2.0",
|
||||
"workbox-window": "^7.0.0",
|
||||
"zod": "^3.22.4",
|
||||
"zod-formik-adapter": "^1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.9.1",
|
||||
"@types/react": "^18.2.37",
|
||||
"@types/react-dom": "^18.2.15",
|
||||
"@types/react-portal": "^4.0.6",
|
||||
"@types/react-table": "^7.7.18",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-plugin-import": "^2.29.0",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.4",
|
||||
"eslint-watch": "^8.0.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.0.4",
|
||||
"vite-plugin-pwa": "^0.16.7",
|
||||
"vite-plugin-svgr": "^4.2.0"
|
||||
}
|
||||
}
|
||||
|
|
1673
web/pnpm-lock.yaml
generated
1673
web/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -82,25 +82,29 @@ export async function HttpClient<T = unknown>(
|
|||
const response = await window.fetch(`${baseUrl()}${endpoint}`, init);
|
||||
|
||||
switch (response.status) {
|
||||
case 204:
|
||||
case 204: {
|
||||
// 204 contains no data, but indicates success
|
||||
return Promise.resolve<T>({} as T);
|
||||
case 401:
|
||||
}
|
||||
case 401: {
|
||||
// Remove auth info from localStorage
|
||||
AuthContext.reset();
|
||||
}
|
||||
|
||||
// Show an error toast to notify the user what occurred
|
||||
return Promise.reject(new Error(`[401] Unauthorized: "${endpoint}"`));
|
||||
case 404:
|
||||
case 404: {
|
||||
return Promise.reject(new Error(`[404] Not found: "${endpoint}"`));
|
||||
case 500:
|
||||
}
|
||||
case 500: {
|
||||
const health = await window.fetch(`${baseUrl()}api/healthz/liveness`);
|
||||
if (!health.ok) {
|
||||
return Promise.reject(
|
||||
new Error(`[500] Offline (Internal server error): "${endpoint}"`, { cause: "OFFLINE" })
|
||||
new Error(`[500] Offline (Internal server error): "${endpoint}"`)
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
interface IconProps {
|
||||
className?: string;
|
||||
|
|
|
@ -20,11 +20,13 @@ export const ErrorPage = ({ error, resetErrorBoundary }: FallbackProps) => {
|
|||
|
||||
const parseTitle = () => {
|
||||
switch (error?.cause) {
|
||||
case "OFFLINE":
|
||||
case "OFFLINE": {
|
||||
return "Connection to Autobrr failed! Check the application state and verify your connectivity.";
|
||||
default:
|
||||
}
|
||||
default: {
|
||||
return "We caught an unrecoverable error!";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -3,5 +3,5 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
export * from "./ErrorPage";
|
||||
export * from "./Warning";
|
||||
export { ErrorPage } from './ErrorPage';
|
||||
export { WarningAlert } from "./Warning";
|
||||
|
|
|
@ -3,5 +3,5 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
export * from "./Buttons";
|
||||
export * from "./Cells";
|
||||
export { Button, PageButton } from "./Buttons";
|
||||
export { AgeCell, IndexerCell, TitleCell, ReleaseStatusCell } from "./Cells";
|
|
@ -10,7 +10,7 @@ interface DebugProps {
|
|||
values: unknown;
|
||||
}
|
||||
|
||||
const DEBUG: FC<DebugProps> = ({ values }) => {
|
||||
export const DEBUG: FC<DebugProps> = ({ values }) => {
|
||||
const settings = SettingsContext.useValue();
|
||||
|
||||
if (process.env.NODE_ENV !== "development" || !settings.debug) {
|
||||
|
@ -23,5 +23,3 @@ const DEBUG: FC<DebugProps> = ({ values }) => {
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DEBUG;
|
||||
|
|
|
@ -177,7 +177,7 @@ export const RegexField = ({
|
|||
if (useRegex) {
|
||||
validateForm();
|
||||
}
|
||||
}, [useRegex]);
|
||||
}, [useRegex, validateForm]);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -317,7 +317,7 @@ export const RegexTextAreaField = ({
|
|||
if (useRegex) {
|
||||
validateForm();
|
||||
}
|
||||
}, [useRegex]);
|
||||
}, [useRegex, validateForm]);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
|
@ -9,7 +9,7 @@ import { Dialog, Transition } from "@headlessui/react";
|
|||
import { Form, Formik } from "formik";
|
||||
import type { FormikValues, FormikProps } from "formik";
|
||||
|
||||
import DEBUG from "@components/debug";
|
||||
import { DEBUG } from "@components/debug";
|
||||
import { useToggle } from "@hooks/hooks";
|
||||
import { DeleteModal } from "@components/modals";
|
||||
import { classNames } from "@utils";
|
||||
|
|
|
@ -13,7 +13,7 @@ import { Field, Form, Formik, FormikErrors, FormikValues } from "formik";
|
|||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { APIClient } from "@api/APIClient";
|
||||
import DEBUG from "@components/debug";
|
||||
import { DEBUG } from "@components/debug";
|
||||
import Toast from "@components/notifications/Toast";
|
||||
import { filterKeys } from "@screens/filters/List";
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import type { FieldProps } from "formik";
|
|||
import { Field, Form, Formik, FormikErrors, FormikValues } from "formik";
|
||||
|
||||
import { APIClient } from "@api/APIClient";
|
||||
import DEBUG from "@components/debug";
|
||||
import { DEBUG } from "@components/debug";
|
||||
import Toast from "@components/notifications/Toast";
|
||||
import { apiKeys } from "@screens/settings/Api";
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import { Form, Formik, useFormikContext } from "formik";
|
|||
import { toast } from "react-hot-toast";
|
||||
|
||||
import { classNames, sleep } from "@utils";
|
||||
import DEBUG from "@components/debug";
|
||||
import { DEBUG } from "@components/debug";
|
||||
import { APIClient } from "@api/APIClient";
|
||||
import { DownloadClientTypeOptions, DownloadRuleConditionOptions } from "@domain/constants";
|
||||
import Toast from "@components/notifications/Toast";
|
||||
|
|
|
@ -13,7 +13,7 @@ import { XMarkIcon } from "@heroicons/react/24/solid";
|
|||
import { Dialog, Transition } from "@headlessui/react";
|
||||
|
||||
import { classNames, sleep } from "@utils";
|
||||
import DEBUG from "@components/debug";
|
||||
import { DEBUG } from "@components/debug";
|
||||
import { APIClient } from "@api/APIClient";
|
||||
import { SlideOver } from "@components/panels";
|
||||
import Toast from "@components/notifications/Toast";
|
||||
|
@ -35,7 +35,7 @@ function validateField(s: IndexerSetting) {
|
|||
return "Default value, please edit";
|
||||
}
|
||||
}
|
||||
return !!value ? undefined : "Required";
|
||||
return value ? undefined : "Required";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ const IrcSettingFields = (ind: IndexerDefinition, indexer: string) => {
|
|||
|
||||
{ind.irc.settings.map((f: IndexerSetting, idx: number) => {
|
||||
switch (f.type) {
|
||||
case "text":
|
||||
case "text": {
|
||||
return (
|
||||
<TextFieldWide
|
||||
key={idx}
|
||||
|
@ -76,12 +76,14 @@ const IrcSettingFields = (ind: IndexerDefinition, indexer: string) => {
|
|||
}
|
||||
/>
|
||||
);
|
||||
case "secret":
|
||||
}
|
||||
case "secret": {
|
||||
if (f.name === "invite_command") {
|
||||
return <PasswordFieldWide defaultVisible name={`irc.${f.name}`} label={f.label} required={f.required} key={idx} help={f.help} defaultValue={f.default} validate={validateField(f)} />;
|
||||
}
|
||||
return <PasswordFieldWide name={`irc.${f.name}`} label={f.label} required={f.required} key={idx} help={f.help} defaultValue={f.default} validate={validateField(f)} />;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
</div>
|
||||
|
@ -108,11 +110,13 @@ const TorznabFeedSettingFields = (ind: IndexerDefinition, indexer: string) => {
|
|||
|
||||
{ind.torznab.settings.map((f: IndexerSetting, idx: number) => {
|
||||
switch (f.type) {
|
||||
case "text":
|
||||
case "text": {
|
||||
return <TextFieldWide name={`feed.${f.name}`} label={f.label} required={f.required} key={idx} help={f.help} autoComplete="off" validate={validateField(f)} />;
|
||||
case "secret":
|
||||
}
|
||||
case "secret": {
|
||||
return <PasswordFieldWide name={`feed.${f.name}`} label={f.label} required={f.required} key={idx} help={f.help} defaultValue={f.default} validate={validateField(f)} />;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
|
||||
|
@ -147,11 +151,13 @@ const NewznabFeedSettingFields = (ind: IndexerDefinition, indexer: string) => {
|
|||
|
||||
{ind.newznab.settings.map((f: IndexerSetting, idx: number) => {
|
||||
switch (f.type) {
|
||||
case "text":
|
||||
case "text": {
|
||||
return <TextFieldWide name={`feed.${f.name}`} label={f.label} required={f.required} key={idx} help={f.help} autoComplete="off" validate={validateField(f)} />;
|
||||
case "secret":
|
||||
}
|
||||
case "secret": {
|
||||
return <PasswordFieldWide name={`feed.${f.name}`} label={f.label} required={f.required} key={idx} help={f.help} defaultValue={f.default} validate={validateField(f)} />;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
</div>
|
||||
|
@ -178,11 +184,13 @@ const RSSFeedSettingFields = (ind: IndexerDefinition, indexer: string) => {
|
|||
|
||||
{ind.rss.settings.map((f: IndexerSetting, idx: number) => {
|
||||
switch (f.type) {
|
||||
case "text":
|
||||
case "text": {
|
||||
return <TextFieldWide name={`feed.${f.name}`} label={f.label} required={f.required} key={idx} help={f.help} autoComplete="off" validate={validateField(f)} />;
|
||||
case "secret":
|
||||
}
|
||||
case "secret": {
|
||||
return <PasswordFieldWide name={`feed.${f.name}`} label={f.label} required={f.required} key={idx} help={f.help} defaultValue={f.default} validate={validateField(f)} />;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
|
||||
|
@ -206,11 +214,12 @@ const SettingFields = (ind: IndexerDefinition, indexer: string) => {
|
|||
<div key="opt">
|
||||
{ind && ind.settings && ind.settings.map((f, idx: number) => {
|
||||
switch (f.type) {
|
||||
case "text":
|
||||
case "text": {
|
||||
return (
|
||||
<TextFieldWide name={`settings.${f.name}`} label={f.label} required={f.required} key={idx} help={f.help} autoComplete="off" validate={validateField(f)} />
|
||||
);
|
||||
case "secret":
|
||||
}
|
||||
case "secret": {
|
||||
return (
|
||||
<PasswordFieldWide
|
||||
name={`settings.${f.name}`}
|
||||
|
@ -229,6 +238,7 @@ const SettingFields = (ind: IndexerDefinition, indexer: string) => {
|
|||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
<div hidden={true}>
|
||||
|
@ -601,10 +611,6 @@ function TestApiButton({ values, show }: TestApiButtonProps) {
|
|||
const [isSuccessfulTest, setIsSuccessfulTest] = useState(false);
|
||||
const [isErrorTest, setIsErrorTest] = useState(false);
|
||||
|
||||
if (!show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const testApiMutation = useMutation({
|
||||
mutationFn: (req: IndexerTestApiReq) => APIClient.indexers.testApi(req),
|
||||
onMutate: () => {
|
||||
|
@ -638,6 +644,10 @@ function TestApiButton({ values, show }: TestApiButtonProps) {
|
|||
});
|
||||
|
||||
const testApi = () => {
|
||||
if (!show) {
|
||||
return;
|
||||
}
|
||||
|
||||
const req: IndexerTestApiReq = {
|
||||
id: values.id,
|
||||
api_key: values.settings.api_key
|
||||
|
@ -650,6 +660,9 @@ function TestApiButton({ values, show }: TestApiButtonProps) {
|
|||
testApiMutation.mutate(req);
|
||||
};
|
||||
|
||||
if (!show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
|
@ -761,11 +774,12 @@ export function IndexerUpdateForm({ isOpen, toggle, indexer }: UpdateProps) {
|
|||
<div key="opt">
|
||||
{settings.map((f: IndexerSetting, idx: number) => {
|
||||
switch (f.type) {
|
||||
case "text":
|
||||
case "text": {
|
||||
return (
|
||||
<TextFieldWide name={`settings.${f.name}`} label={f.label} key={idx} help={f.help} />
|
||||
);
|
||||
case "secret":
|
||||
}
|
||||
case "secret": {
|
||||
return (
|
||||
<PasswordFieldWide
|
||||
key={idx}
|
||||
|
@ -782,6 +796,7 @@ export function IndexerUpdateForm({ isOpen, toggle, indexer }: UpdateProps) {
|
|||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
</div>
|
||||
|
|
|
@ -15,7 +15,7 @@ import { toast } from "react-hot-toast";
|
|||
import { APIClient } from "@api/APIClient";
|
||||
import { notificationKeys } from "@screens/settings/Notifications";
|
||||
import { EventOptions, NotificationTypeOptions, SelectOption } from "@domain/constants";
|
||||
import DEBUG from "@components/debug";
|
||||
import { DEBUG } from "@components/debug";
|
||||
import { SlideOver } from "@components/panels";
|
||||
import { ExternalLink } from "@components/ExternalLink";
|
||||
import Toast from "@components/notifications/Toast";
|
||||
|
|
|
@ -24,7 +24,6 @@ window.Buffer = Buffer;
|
|||
// Initializes auth and theme contexts
|
||||
InitializeGlobalContext();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const root = createRoot(document.getElementById("root")!);
|
||||
root.render(
|
||||
<StrictMode>
|
||||
|
|
|
@ -49,7 +49,7 @@ export const Logs = () => {
|
|||
|
||||
const [logs, setLogs] = useState<LogEvent[]>([]);
|
||||
const [searchFilter, setSearchFilter] = useState("");
|
||||
const [_regexPattern, setRegexPattern] = useState<RegExp | null>(null);
|
||||
const [, setRegexPattern] = useState<RegExp | null>(null);
|
||||
const [filteredLogs, setFilteredLogs] = useState<LogEvent[]>([]);
|
||||
const [isInvalidRegex, setIsInvalidRegex] = useState(false);
|
||||
|
||||
|
@ -61,7 +61,7 @@ export const Logs = () => {
|
|||
};
|
||||
if (settings.scrollOnNewLog)
|
||||
scrollToBottom();
|
||||
}, [filteredLogs]);
|
||||
}, [filteredLogs, settings.scrollOnNewLog]);
|
||||
|
||||
// Add a useEffect to clear logs div when settings.scrollOnNewLog changes to prevent duplicate entries.
|
||||
useEffect(() => {
|
||||
|
|
|
@ -44,7 +44,7 @@ export const Login = () => {
|
|||
APIClient.auth.canOnboard()
|
||||
.then(() => navigate("/onboard"))
|
||||
.catch(() => { /*don't log to console PAHLLEEEASSSE*/ });
|
||||
}, []);
|
||||
}, [navigate]);
|
||||
|
||||
const loginMutation = useMutation({
|
||||
mutationFn: (data: LoginFormFields) => APIClient.auth.login(data.username, data.password),
|
||||
|
|
|
@ -18,7 +18,7 @@ import { useToggle } from "@hooks/hooks";
|
|||
import { classNames } from "@utils";
|
||||
import { DOWNLOAD_CLIENTS } from "@domain/constants";
|
||||
|
||||
import DEBUG from "@components/debug";
|
||||
import { DEBUG } from "@components/debug";
|
||||
import Toast from "@components/notifications/Toast";
|
||||
import { DeleteModal } from "@components/modals";
|
||||
import { SectionLoader } from "@components/SectionLoader";
|
||||
|
@ -164,7 +164,7 @@ const FormErrorNotification = () => {
|
|||
/>
|
||||
));
|
||||
}
|
||||
}, [isSubmitting, isValid, isValidating]);
|
||||
}, [errors, isSubmitting, isValid, isValidating]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
@ -289,7 +289,6 @@ export const FilterDetails = () => {
|
|||
navigate("/filters");
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const id = parseInt(filterId!);
|
||||
|
||||
const { isLoading, data: filter } = useQuery({
|
||||
|
|
|
@ -64,21 +64,28 @@ type Actions =
|
|||
|
||||
const FilterListReducer = (state: FilterListState, action: Actions): FilterListState => {
|
||||
switch (action.type) {
|
||||
case ActionType.INDEXER_FILTER_CHANGE:
|
||||
case ActionType.INDEXER_FILTER_CHANGE: {
|
||||
return { ...state, indexerFilter: action.payload };
|
||||
case ActionType.INDEXER_FILTER_RESET:
|
||||
}
|
||||
case ActionType.INDEXER_FILTER_RESET: {
|
||||
return { ...state, indexerFilter: [] };
|
||||
case ActionType.SORT_ORDER_CHANGE:
|
||||
}
|
||||
case ActionType.SORT_ORDER_CHANGE: {
|
||||
return { ...state, sortOrder: action.payload };
|
||||
case ActionType.SORT_ORDER_RESET:
|
||||
}
|
||||
case ActionType.SORT_ORDER_RESET: {
|
||||
return { ...state, sortOrder: "" };
|
||||
case ActionType.STATUS_CHANGE:
|
||||
}
|
||||
case ActionType.STATUS_CHANGE: {
|
||||
return { ...state, status: action.payload };
|
||||
case ActionType.STATUS_RESET:
|
||||
}
|
||||
case ActionType.STATUS_RESET: {
|
||||
return { ...state, status: "" };
|
||||
default:
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Unhandled action type: ${action}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export function Filters() {
|
||||
|
|
|
@ -25,7 +25,7 @@ class ParserFilter {
|
|||
}
|
||||
|
||||
switch (key) {
|
||||
case "log_score":
|
||||
case "log_score": {
|
||||
// In this case we need to set 2 properties in autobrr instead of only 1
|
||||
this.values["log"] = true;
|
||||
|
||||
|
@ -35,16 +35,18 @@ class ParserFilter {
|
|||
value = value.slice(0, delim);
|
||||
}
|
||||
break;
|
||||
case "max_downloads_unit":
|
||||
}
|
||||
case "max_downloads_unit": {
|
||||
value = value.toUpperCase();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (key in CONST.FILTER_FIELDS) {
|
||||
switch (CONST.FILTER_FIELDS[key]) {
|
||||
case "number":
|
||||
case "number": {
|
||||
const parsedNum = parseFloat(value);
|
||||
this.values[key] = parsedNum;
|
||||
|
||||
|
@ -53,11 +55,12 @@ class ParserFilter {
|
|||
`[Filter=${this.name}] Failed to convert field '${key}' to a number. Got value: '${value}'`
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
case "boolean":
|
||||
}
|
||||
case "boolean": {
|
||||
this.values[key] = value.toLowerCase() === "true";
|
||||
break;
|
||||
}
|
||||
default:
|
||||
this.values[key] = value;
|
||||
break;
|
||||
|
@ -145,7 +148,7 @@ class ParserIrcNetwork {
|
|||
|
||||
if (key in CONST.IRC_FIELDS) {
|
||||
switch (CONST.IRC_FIELDS[key]) {
|
||||
case "number":
|
||||
case "number": {
|
||||
const parsedNum = parseFloat(value);
|
||||
this.values[key] = parsedNum;
|
||||
|
||||
|
@ -154,14 +157,16 @@ class ParserIrcNetwork {
|
|||
`[IrcNetwork=${this.serverName}] Failed to convert field '${key}' to a number. Got value: '${value}'`
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
case "boolean":
|
||||
}
|
||||
case "boolean": {
|
||||
this.values[key] = value.toLowerCase() === "true";
|
||||
break;
|
||||
default:
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.values[key] = value;
|
||||
}*/
|
||||
|
@ -259,18 +264,22 @@ export class AutodlIrssiConfigParser {
|
|||
}
|
||||
|
||||
switch (match[1]) {
|
||||
case FILTER:
|
||||
case FILTER: {
|
||||
this.releaseFilter = new ParserFilter(rightLeftover);
|
||||
break;
|
||||
case SERVER:
|
||||
}
|
||||
case SERVER: {
|
||||
this.ircNetwork = new ParserIrcNetwork(rightLeftover);
|
||||
break;
|
||||
case CHANNEL:
|
||||
}
|
||||
case CHANNEL: {
|
||||
this.ircChannel = new ParserIrcChannel(rightLeftover);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -134,7 +134,7 @@ const TypeForm = (props: ClientActionProps) => {
|
|||
}
|
||||
|
||||
setPrevActionType(action.type);
|
||||
}, [action.type, idx, setFieldValue]);
|
||||
}, [action.type, idx, prevActionType, setFieldValue]);
|
||||
|
||||
switch (action.type) {
|
||||
// torrent clients
|
||||
|
|
|
@ -253,7 +253,7 @@ interface TypeFormProps {
|
|||
|
||||
const TypeForm = ({ external, idx }: TypeFormProps) => {
|
||||
switch (external.type) {
|
||||
case "EXEC":
|
||||
case "EXEC": {
|
||||
return (
|
||||
<FilterSection.Section
|
||||
title="Execute"
|
||||
|
@ -291,7 +291,8 @@ const TypeForm = ({ external, idx }: TypeFormProps) => {
|
|||
</FilterSection.Layout>
|
||||
</FilterSection.Section>
|
||||
);
|
||||
case "WEBHOOK":
|
||||
}
|
||||
case "WEBHOOK": {
|
||||
return (
|
||||
<>
|
||||
<FilterSection.Section
|
||||
|
@ -363,8 +364,10 @@ const TypeForm = ({ external, idx }: TypeFormProps) => {
|
|||
</FilterSection.Section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
default:
|
||||
default: {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -97,8 +97,6 @@ interface CollapsibleSectionProps {
|
|||
childClassName?: string;
|
||||
}
|
||||
|
||||
// NOTE(stacksmash76): added text-shadow only for the dark theme - light theme is fine contrast-wise when it comes to headings
|
||||
// ideally, this would need a redesign
|
||||
export const CollapsibleSection = ({
|
||||
title,
|
||||
subtitle,
|
||||
|
@ -137,7 +135,7 @@ export const CollapsibleSection = ({
|
|||
"flex"
|
||||
)}
|
||||
>
|
||||
<h3 className="text-xl leading-6 font-bold break-all dark:text-shadow dark:shadow-gray-900 text-gray-900 dark:text-gray-200">
|
||||
<h3 className="text-xl leading-6 font-bold break-all dark:shadow-gray-900 text-gray-900 dark:text-gray-200">
|
||||
{title}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 truncate whitespace-normal break-words">
|
||||
|
|
|
@ -65,17 +65,22 @@ type Actions =
|
|||
|
||||
const TableReducer = (state: TableState, action: Actions): TableState => {
|
||||
switch (action.type) {
|
||||
case ActionType.PAGE_CHANGED:
|
||||
case ActionType.PAGE_CHANGED: {
|
||||
return { ...state, queryPageIndex: action.payload };
|
||||
case ActionType.PAGE_SIZE_CHANGED:
|
||||
}
|
||||
case ActionType.PAGE_SIZE_CHANGED: {
|
||||
return { ...state, queryPageSize: action.payload };
|
||||
case ActionType.FILTER_CHANGED:
|
||||
}
|
||||
case ActionType.FILTER_CHANGED: {
|
||||
return { ...state, queryFilters: action.payload };
|
||||
case ActionType.TOTAL_COUNT_CHANGED:
|
||||
}
|
||||
case ActionType.TOTAL_COUNT_CHANGED: {
|
||||
return { ...state, totalCount: action.payload };
|
||||
default:
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Unhandled action type: ${action}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const ReleaseTable = () => {
|
||||
|
|
|
@ -42,6 +42,10 @@ interface SortConfig {
|
|||
direction: "ascending" | "descending";
|
||||
}
|
||||
|
||||
const isErrorWithMessage = (error: unknown): error is { message: string } => {
|
||||
return typeof error === 'object' && error !== null && 'message' in error;
|
||||
};
|
||||
|
||||
function useSort(items: ListItemProps["feed"][], config?: SortConfig) {
|
||||
const [sortConfig, setSortConfig] = useState(config);
|
||||
|
||||
|
@ -257,8 +261,13 @@ const FeedItemDropdown = ({
|
|||
toast.custom((t) => <Toast type="success" body={`Feed ${feed?.name} was force run successfully.`} t={t} />);
|
||||
toggleForceRunModal();
|
||||
},
|
||||
onError: (error: any) => {
|
||||
toast.custom((t) => <Toast type="error" body={`Failed to force run ${feed?.name}. Error: ${error.message}`} t={t} />, {
|
||||
onError: (error: unknown) => {
|
||||
let errorMessage = 'An unknown error occurred';
|
||||
if (isErrorWithMessage(error)) {
|
||||
errorMessage = error.message;
|
||||
}
|
||||
|
||||
toast.custom((t) => <Toast type="error" body={`Failed to force run ${feed?.name}. Error: ${errorMessage}`} t={t} />, {
|
||||
duration: 10000
|
||||
});
|
||||
toggleForceRunModal();
|
||||
|
|
|
@ -621,7 +621,7 @@ export const Events = ({ network, channel }: EventsProps) => {
|
|||
};
|
||||
|
||||
return () => es.close();
|
||||
}, [settings]);
|
||||
}, [channel, network.id, settings]);
|
||||
|
||||
const [isFullscreen, toggleFullscreen] = useToggle(false);
|
||||
|
||||
|
@ -649,7 +649,7 @@ export const Events = ({ network, channel }: EventsProps) => {
|
|||
};
|
||||
if (settings.scrollOnNewLog)
|
||||
scrollToBottom();
|
||||
}, [logs]);
|
||||
}, [logs, settings.scrollOnNewLog]);
|
||||
|
||||
// Add a useEffect to clear logs div when settings.scrollOnNewLog changes to prevent duplicate entries.
|
||||
useEffect(() => {
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { lerpColors } from "tailwind-lerp-colors";
|
||||
import plugin from "tailwindcss/plugin";
|
||||
import forms from "@tailwindcss/forms";
|
||||
import type { Config } from "tailwindcss";
|
||||
|
||||
const extendedColors = lerpColors();
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
content: [
|
||||
"./src/**/*.{tsx,ts,html,css}",
|
||||
"./src/**/*.{tsx,ts,html,css}"
|
||||
],
|
||||
safelist: [
|
||||
"col-span-1",
|
||||
|
@ -19,7 +20,7 @@ module.exports = {
|
|||
"col-span-9",
|
||||
"col-span-10",
|
||||
"col-span-11",
|
||||
"col-span-12",
|
||||
"col-span-12"
|
||||
],
|
||||
// purge: false,
|
||||
darkMode: "class", // or 'media' or 'class'
|
||||
|
@ -33,7 +34,7 @@ module.exports = {
|
|||
}
|
||||
},
|
||||
margin: { // for the checkmarks used for regex validation in Filters/Advanced
|
||||
"2.5": "0.625rem", // 10px, between mb-2 (8px) and mb-3 (12px)
|
||||
"2.5": "0.625rem" // 10px, between mb-2 (8px) and mb-3 (12px)
|
||||
},
|
||||
textShadow: {
|
||||
DEFAULT: "0 2px 4px var(--tw-shadow-color)"
|
||||
|
@ -41,21 +42,12 @@ module.exports = {
|
|||
boxShadow: {
|
||||
table: "rgba(0, 0, 0, 0.1) 0px 4px 16px 0px"
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
variants: {
|
||||
extend: {},
|
||||
extend: {}
|
||||
},
|
||||
plugins: [
|
||||
require("@tailwindcss/forms"),
|
||||
plugin(function ({ matchUtilities, theme }) {
|
||||
// Pipe --tw-shadow-color (i.e. shadow-cyan-500/50) to our new text-shadow
|
||||
// Credits: https://www.hyperui.dev/blog/text-shadow-with-tailwindcss
|
||||
// Use it like: text-shadow shadow-cyan-500/50
|
||||
matchUtilities(
|
||||
{ "text-shadow": (value) => ({ textShadow: value }) },
|
||||
{ values: theme("textShadow") }
|
||||
);
|
||||
}),
|
||||
],
|
||||
}
|
||||
forms,
|
||||
]
|
||||
} satisfies Config;
|
|
@ -1,33 +1,29 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"lib": [
|
||||
"DOM",
|
||||
"DOM.Iterable",
|
||||
"ESNext"
|
||||
],
|
||||
"types": ["vite/client"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"paths": {
|
||||
"@*": ["./src/*"],
|
||||
"@app*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"./src",
|
||||
"./types",
|
||||
"vite.config.ts"
|
||||
],
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
|
|
10
web/tsconfig.node.json
Normal file
10
web/tsconfig.node.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
|
@ -111,7 +111,15 @@ export default ({ mode }: ConfigEnv) => {
|
|||
}
|
||||
return "assets/[name]-[hash][extname]";
|
||||
}
|
||||
}
|
||||
},
|
||||
// This ignores the sourcemap warnings after vite 5.x.x introduced by rollup - an upstream dep of vite
|
||||
// ref https://github.com/vitejs/vite/issues/15012#issuecomment-1815854072
|
||||
onLog(level, log, handler) {
|
||||
if (log.cause && (log.cause as { message?: string }).message === `Can't resolve original location of error.`) {
|
||||
return;
|
||||
}
|
||||
handler(level, log);
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue