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:
KaiserBh 2023-12-16 09:36:16 +11:00 committed by GitHub
parent a89a1a55d9
commit 92646dacc8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 1100 additions and 1029 deletions

View file

@ -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
View 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 },
],
},
}

View file

@ -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"
},
},
],
};

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

@ -3,7 +3,6 @@
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import * as React from "react";
interface IconProps {
className?: string;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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(() => {

View file

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

View file

@ -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({

View file

@ -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() {

View file

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

View file

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

View file

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

View file

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

View file

@ -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 = () => {

View file

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

View file

@ -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(() => {

View file

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

View file

@ -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
View file

@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

View file

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