Skip to content

Commit

Permalink
Add plugin:@typescript-eslint/recommended-type-checked (#735)
Browse files Browse the repository at this point in the history
* Add plugin:@typescript-eslint/recommended-type-checked

* Fix signalr middleware types

* Fix types

* Fix types in auto-match-logic.ts

* Fix types in UtilitiesTable.tsx

* Fix more types and dprint errors

---------

Co-authored-by: Mikal Stordal <mikalstordal@gmail.com>
Co-authored-by: Harshith Mohan <gharshitmohan@ymail.com>
  • Loading branch information
3 people authored Dec 27, 2023
1 parent 8100b4a commit 829d480
Show file tree
Hide file tree
Showing 52 changed files with 262 additions and 183 deletions.
1 change: 1 addition & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"airbnb/hooks",
"plugin:tailwindcss/recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-type-checked",
"plugin:@tanstack/eslint-plugin-query/recommended"
],
"ignorePatterns": [
Expand Down
2 changes: 1 addition & 1 deletion src/components/Collection/DisplaySettingsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const DisplaySettingsModal = ({ onClose, show }: Props) => {

const { list: listSettings, poster: posterSettings } = newSettings.WebUI_Settings.collection;

const handleSave = async () => {
const handleSave = () => {
patchSettings({ newSettings }, {
onSuccess: () => onClose(),
});
Expand Down
5 changes: 3 additions & 2 deletions src/components/Collection/ListViewItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,14 @@ import type { CollectionGroupType } from '@/core/types/api/collection';
import type { SeriesSizesFileSourcesType, SeriesType } from '@/core/types/api/series';
import type { WebuiGroupExtra } from '@/core/types/api/webui';

const typeMap = { Unknown: 'Unk', BluRay: 'BD' };
const isFileType = (type: string): type is keyof typeof typeMap => type in typeMap;
const renderFileSources = (sources: SeriesSizesFileSourcesType): string => {
const output: string[] = [];
const typeMap = { Unknown: 'Unk', BluRay: 'BD' };

Object.entries(sources).forEach(([type, source]) => {
if (source !== 0) {
output.push(typeMap[type] || type);
output.push(isFileType(type) ? typeMap[type] : type);
}
});

Expand Down
6 changes: 3 additions & 3 deletions src/components/Collection/Series/EpisodeFileInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const EpisodeFileInfo = ({ file }: { file: FileType }) => {
if (VideoSource) {
VideoInfo.push(VideoSource);
}
const VideoBitDepth = get(file, 'MediaInfo.Video.0.BitDepth');
const VideoBitDepth = get(file, 'MediaInfo.Video.0.BitDepth', '??');
if (VideoBitDepth) {
VideoInfo.push(`${VideoBitDepth}-bit`);
}
Expand All @@ -30,8 +30,8 @@ const EpisodeFileInfo = ({ file }: { file: FileType }) => {
if (VideoResolution) {
VideoInfo.push(VideoResolution);
}
const VideoWidth = get(file, 'MediaInfo.Video.0.Width');
const VideoHeight = get(file, 'MediaInfo.Video.0.Height');
const VideoWidth = get(file, 'MediaInfo.Video.0.Width', '??');
const VideoHeight = get(file, 'MediaInfo.Video.0.Height', '??');
if (VideoWidth && VideoHeight) {
VideoInfo.push(`${VideoWidth}x${VideoHeight}`);
}
Expand Down
6 changes: 3 additions & 3 deletions src/components/Collection/Series/EpisodeFiles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ const EpisodeFiles = ({ animeId, episodeFiles, episodeId }: Props) => {
[selectedFileToDelete],
);

const handleDelete = async () => {
const handleDelete = () => {
if (!selectedFileToDelete) return;
deleteFile({ fileId: selectedFileToDelete.ID, removeFolder: true }, {
onSuccess: () => {
toast.success('Deleted file!');
invalidateQueries(['episode', 'files', episodeId]);
},
onError: error => toast.error(`Failed to delete file! ${error}`),
onError: error => toast.error(`Failed to delete file! ${error.message}`),
});
};

Expand All @@ -48,7 +48,7 @@ const EpisodeFiles = ({ animeId, episodeFiles, episodeId }: Props) => {
const handleRescan = (id: number) =>
rescanFile(id, {
onSuccess: () => toast.success('Rescanning file!'),
onError: error => toast.error(`Rescan failed for file! ${error}`),
onError: error => toast.error(`Rescan failed for file! ${error.message}`),
});

if (!episodeFiles.length || episodeFiles.length < 1) {
Expand Down
2 changes: 1 addition & 1 deletion src/components/Dashboard/DashboardSettingsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ const DashboardSettingsModal = ({ onClose, show }: Props) => {
setNewSettings(tempSettings);
};

const handleSave = async () => {
const handleSave = () => {
patchSettings({ newSettings }, {
onSuccess: () => onClose(),
});
Expand Down
12 changes: 9 additions & 3 deletions src/components/Dialogs/ActionsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ const actions = {
},
};

const isActionTab = (type: string): type is keyof typeof actions => type in actions;

type Props = {
show: boolean;
onClose: () => void;
Expand All @@ -87,7 +89,7 @@ type Props = {
const Action = ({ actionKey }: { actionKey: string }) => {
const { mutate: runAction } = useRunActionMutation();

const handleAction = async (name: string, action: string) => {
const handleAction = (name: string, action: string) => {
runAction(action, {
onSuccess: () => toast.success(`Running action "${name}"`),
});
Expand All @@ -102,7 +104,10 @@ const Action = ({ actionKey }: { actionKey: string }) => {
<div>{name}</div>
<div className="text-sm opacity-65">{quickActions[actionKey].info}</div>
</div>
<Button onClick={() => handleAction(name, functionName)} className="text-panel-icon-action">
<Button
onClick={() => handleAction(name, functionName)}
className="text-panel-icon-action"
>
<Icon path={mdiPlayCircleOutline} size={1} />
</Button>
</TransitionDiv>
Expand Down Expand Up @@ -138,7 +143,8 @@ function ActionsModal({ onClose, show }: Props) {

<div className="flex grow p-8 pr-6">
<div className="scroll-gutter flex grow flex-col gap-y-4 overflow-y-auto pr-2 ">
{actions[activeTab].data.map((key: string) => <Action actionKey={key} key={key} />)}
{isActionTab(activeTab)
&& actions[activeTab].data.map((key: string) => <Action actionKey={key} key={key} />)}
</div>
</div>
</div>
Expand Down
6 changes: 3 additions & 3 deletions src/components/Dialogs/DeleteFilesModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import type { FileType } from '@/core/types/api/file';
type Props = {
selectedFiles: FileType[];
show: boolean;
removeFile(fileId: number): void;
onConfirm(): void;
onClose(): void;
removeFile(this: void, fileId: number): void;
onConfirm(this: void): void;
onClose(this: void): void;
};

const Title = ({ fileCount }: { fileCount: number }) => (
Expand Down
2 changes: 1 addition & 1 deletion src/components/Dialogs/ImportFolderModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ function ImportFolderModal() {
});
};

const handleSave = async () => {
const handleSave = () => {
if (edit) {
updateFolder(importFolder, {
onSuccess: () => {
Expand Down
4 changes: 2 additions & 2 deletions src/components/Dialogs/LanguagesModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { remove } from 'lodash';
import { keys, map, remove } from 'lodash';

import Button from '@/components/Input/Button';
import Checkbox from '@/components/Input/Checkbox';
Expand Down Expand Up @@ -111,7 +111,7 @@ function LanguagesModal({ onClose, type }: Props) {
>
<div className="w-full rounded-md border border-panel-border bg-panel-input p-4 capitalize">
<div className="flex h-80 flex-col gap-y-1.5 overflow-y-auto rounded-md bg-panel-input px-3 py-2">
{Object.keys(languageDescription).map(key => (
{map(keys(languageDescription), (key: keyof typeof languageDescription) => (
<Checkbox
id={key}
key={key}
Expand Down
18 changes: 9 additions & 9 deletions src/components/Input/SelectEpisodeList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { EpisodeTypeEnum } from '@/core/types/api/episode';

import Input from './Input';

function getOffsetTop(rect, vertical) {
function getOffsetTop(rect: DOMRect, vertical: string | number) {
let offset = 0;

if (typeof vertical === 'number') {
Expand All @@ -24,7 +24,7 @@ function getOffsetTop(rect, vertical) {
return offset;
}

function getOffsetLeft(rect, horizontal) {
function getOffsetLeft(rect: DOMRect, horizontal: string | number) {
let offset = 0;

if (typeof horizontal === 'number') {
Expand Down Expand Up @@ -97,11 +97,11 @@ const SelectEpisodeList = (
{ className, disabled = false, emptyValue = '', onChange, options, rowIdx, value }: Props,
) => {
const [epFilter, setEpFilter] = useState(0);
const [selected, setSelected] = useState(options[0]);
const [selected, setSelected] = useState<Option>(options[0]);
const [portalEl, setPortalEl] = useState<HTMLDivElement | null>(null);
const [displayNode, setDisplayNode] = React.useState<HTMLDivElement | null>(null);
const displayRef = useRef(null);
const buttonRef = useRef(null);
const displayRef = useRef<HTMLDivElement | null>(null);
const buttonRef = useRef<HTMLButtonElement | null>(null);

useEffect(() => {
const modalRoot = document.getElementById('modal-root');
Expand All @@ -114,7 +114,7 @@ const SelectEpisodeList = (
};
}, []);

const handleDisplayRef = useCallback((node) => {
const handleDisplayRef = useCallback((node: HTMLDivElement | null) => {
displayRef.current = node;

if (node) {
Expand All @@ -126,11 +126,11 @@ const SelectEpisodeList = (
setSelected(find(options, ['value', value]) ?? {} as Option);
}, [value, options]);

const handleEpFilter = (event) => {
const handleEpFilter: React.ChangeEventHandler<HTMLInputElement> = (event) => {
setEpFilter(toInteger(event.target.value));
};

const selectOption = (selectedOption) => {
const selectOption = (selectedOption: Option) => {
setSelected(selectedOption);
onChange(selectedOption?.value ?? 0, selectedOption?.label ?? emptyValue);
};
Expand All @@ -142,7 +142,7 @@ const SelectEpisodeList = (
<span className="font-semibold text-panel-text-important">{selected.number}</span>
&nbsp;-&nbsp;
{selected.label}
{selected.type && selected.type !== 'Normal' && (
{selected.type && selected.type !== EpisodeTypeEnum.Normal && (
<span className="mx-2 rounded-md border border-panel-border bg-panel-background px-1 py-0.5 text-sm text-panel-text">
{selected.type}
</span>
Expand Down
5 changes: 4 additions & 1 deletion src/components/Toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,16 @@ const colorClass = {
warning: 'text-panel-text-warning',
};

const isColorClass = (type: string): type is keyof typeof colorClass => type in colorClass;

function Toast(props: Props) {
const { closeToast, header, icon, message, toastProps } = props;
const color = toastProps && 'type' in toastProps && isColorClass(toastProps.type) ? toastProps?.type : 'info';

return (
<div className="flex">
<span>
<Icon path={icon} size={1} className={colorClass[toastProps?.type ?? 'info']} />
<Icon path={icon} size={1} className={colorClass[color]} />
</span>
<div className="ml-4 mr-8 flex grow flex-col">
<div className="font-semibold">{header}</div>
Expand Down
5 changes: 4 additions & 1 deletion src/components/TreeView/TreeNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ function TreeNode(props: Props) {
const foldersQuery = useFolderQuery(path, nodeId !== 0 && expanded);

useEffect(() => {
if (foldersQuery.isError) toast.error(`${foldersQuery.error} - Fetching folder ${path} failed.`);
if (foldersQuery.isError) {
const queryError = foldersQuery.error.toString();
toast.error(`${queryError} - Fetching folder ${path} failed.`);
}
}, [path, foldersQuery.error, foldersQuery.isError]);

const toggleExpanded = (event: React.MouseEvent) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useState } from 'react';
import { mdiOpenInNew } from '@mdi/js';
import { Icon } from '@mdi/react';
import cx from 'classnames';
import { find, forEach, toNumber } from 'lodash';
import { find, forEach, get, toNumber } from 'lodash';

import { useRowSelection } from '@/hooks/useRowSelection';

Expand Down Expand Up @@ -80,7 +80,7 @@ function ManuallyLinkedFilesRow(props: Props) {
</div>
</div>
{files.map((file, index) => {
const episode = find(episodes, item => item.IDs.ID === file.SeriesIDs![0].EpisodeIDs[0].ID)!;
const episode = find(episodes, item => item.IDs.ID === get(file, 'SeriesIDs.0.EpisodeIDs.0.ID', 0))!;
const selected = rowSelection[file.ID];

return (
Expand Down
14 changes: 9 additions & 5 deletions src/components/Utilities/UtilitiesTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ const Row = (
);
};

const isFileSortCriteriaEnum = (type: unknown): type is keyof typeof FileSortCriteriaEnum => typeof type === 'number';

const HeaderItem = (
props: {
className: string;
Expand Down Expand Up @@ -156,8 +158,9 @@ const HeaderItem = (
}

const handleSortCriteriaChange = (headerId: string) => {
const criteria = criteriaMap[headerId];
if (skipSort || !criteria || !setSortCriteria) return;
if (skipSort || !isFileSortCriteriaEnum(criteriaMap[headerId])) return;
const criteria = criteriaMap[headerId] as FileSortCriteriaEnum;
if (!criteria || !setSortCriteria) return;

setSortCriteria((tempCriteria) => {
if (tempCriteria === criteria) return tempCriteria * -1;
Expand All @@ -166,15 +169,16 @@ const HeaderItem = (
};

const sortIndicator = (headerId: string) => {
const criteria = criteriaMap[headerId];
if (skipSort || !criteria || !sortCriteria || Math.abs(sortCriteria) !== criteria) return null;
if (skipSort || !isFileSortCriteriaEnum(criteriaMap[headerId])) return null;
const criteria = criteriaMap[headerId] as number;
if (!criteria || !sortCriteria || Math.abs(sortCriteria) !== criteria) return null;

return (
<Icon
path={mdiMenuUp}
size={1}
className="ml-2 inline text-panel-text-primary transition-transform"
rotate={sortCriteria === (criteria * -1) ? 180 : 0}
rotate={sortCriteria as number === (criteria * -1) ? 180 : 0}
/>
);
};
Expand Down
9 changes: 7 additions & 2 deletions src/core/axios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import axiosDefault from 'axios';

import store from '@/core/store';

import type { RootState } from '@/core/store';
import type { InternalAxiosRequestConfig } from 'axios';

export const axios = axiosDefault.create({
Expand All @@ -26,15 +25,21 @@ export const axiosExternal = axiosDefault.create();

const addApikeyInterceptor = (config: InternalAxiosRequestConfig) => {
const tempConfig = config;
tempConfig.headers.apikey = (store.getState() as RootState).apiSession.apikey;
tempConfig.headers.apikey = (store.getState()).apiSession.apikey;
return tempConfig;
};

axios.interceptors.request.use(addApikeyInterceptor);
axiosV2.interceptors.request.use(addApikeyInterceptor);
axiosPlex.interceptors.request.use(addApikeyInterceptor);

// The type of response.data depends on the endpoint called. It has to be any.
// We are only adding this interceptor so that we don't have to get response.data every time we call axios from react-query
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
axios.interceptors.response.use(response => response.data);
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
axiosV2.interceptors.response.use(response => response.data);
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
axiosPlex.interceptors.response.use(response => response.data);
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
axiosExternal.interceptors.response.use(response => response.data);
2 changes: 1 addition & 1 deletion src/core/buildFilter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { FilterCondition } from '@/core/types/api/filter';

const buildFilter = (filters: FilterCondition[]) => {
const buildFilter = (filters: FilterCondition[]): FilterCondition => {
if (filters.length > 1) {
return {
Type: 'And',
Expand Down
19 changes: 12 additions & 7 deletions src/core/localStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,28 @@
import { get } from 'lodash';

import type { RootState } from './store';
import type { ApiSessionState } from '@/core/types/api';

const { VITE_APPVERSION } = import.meta.env;

const checkVersion = (version: string) => version === VITE_APPVERSION;

const isSerializedState = (data: unknown): data is RootState => checkVersion(get(data, 'apiSession.version', ''));
const isApiSession = (data: unknown): data is ApiSessionState => checkVersion(get(data, 'version', ''));

export const loadState = (): RootState => {
try {
const serializedState = JSON.parse(globalThis.sessionStorage.getItem('state') ?? '{}');
const serializedState: unknown = JSON.parse(globalThis.sessionStorage.getItem('state') ?? '{}');
const apiSessionString = globalThis.localStorage.getItem('apiSession');
if (apiSessionString === null) {
return checkVersion(get(serializedState, 'apiSession.version', '')) ? serializedState : {} as RootState;
return isSerializedState(serializedState) ? serializedState : {} as RootState;
}
const apiSession = JSON.parse(apiSessionString);
if (!checkVersion(get(apiSession, 'version', ''))) {
globalThis.localStorage.clear();
return {} as RootState;
const apiSession: unknown = JSON.parse(apiSessionString);
if (isSerializedState(serializedState) && isApiSession(apiSession)) {
return { ...serializedState, apiSession };
}
return { ...serializedState, apiSession };
globalThis.localStorage.clear();
return {} as RootState;
} catch (err) {
return ({} as RootState);
}
Expand Down
Loading

0 comments on commit 829d480

Please sign in to comment.