diff --git a/.eslintrc.js b/.eslintrc.js index 10366dc746..53dd01ff25 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -56,6 +56,7 @@ module.exports = { 'no-return-assign': ['error'], 'no-return-await': ['error'], 'no-sequences': ['error', { 'allowInParentheses': false }], + 'no-shadow': ['error'], 'no-trailing-spaces': ['error'], '@babel/no-unused-expressions': ['error', { 'allowShortCircuit': true, 'allowTernary': true, 'allowTaggedTemplates': true }], 'no-useless-constructor': ['error'], @@ -70,12 +71,15 @@ module.exports = { 'space-before-blocks': ['error'], 'space-infix-ops': 'error', 'yoda': 'error', - '@typescript-eslint/no-shadow': 'error', 'react/jsx-filename-extension': ['error', { 'extensions': ['.jsx', '.tsx'] }], + 'react/jsx-no-bind': ['error'], + 'react/jsx-no-constructed-context-values': ['error'], + 'react/no-array-index-key': ['error'], - 'sonarjs/cognitive-complexity': ['warn'], + 'sonarjs/no-inverted-boolean-check': ['error'], // TODO: Enable the following rules and fix issues + 'sonarjs/cognitive-complexity': ['off'], 'sonarjs/no-duplicate-string': ['off'] }, settings: { @@ -255,8 +259,14 @@ module.exports = { 'plugin:jsx-a11y/recommended' ], rules: { + // Use TypeScript equivalent rules when required + 'no-shadow': ['off'], + '@typescript-eslint/no-shadow': ['error'], 'no-useless-constructor': ['off'], - '@typescript-eslint/no-useless-constructor': ['error'] + '@typescript-eslint/no-useless-constructor': ['error'], + + 'max-params': ['error', 7], + 'sonarjs/cognitive-complexity': ['warn'] } } ] diff --git a/src/components/common/GenresItemsContainer.tsx b/src/components/common/GenresItemsContainer.tsx index e4098d3e77..b6a06bb26a 100644 --- a/src/components/common/GenresItemsContainer.tsx +++ b/src/components/common/GenresItemsContainer.tsx @@ -90,8 +90,8 @@ const GenresItemsContainer: FC = ({

{globalize.translate('MessageNothingHere')}

{globalize.translate('MessageNoGenresAvailable')}

- ) : items.map((item, index) => ( -
+ ) : items.map(item => ( +
= 700) { lastUpdateTime = now; const player = this; currentRuntimeTicks = playbackManager.duration(player); diff --git a/src/components/search/LiveTVSearchResults.tsx b/src/components/search/LiveTVSearchResults.tsx index 4fb0af1ee3..5b4c097c5c 100644 --- a/src/components/search/LiveTVSearchResults.tsx +++ b/src/components/search/LiveTVSearchResults.tsx @@ -125,7 +125,7 @@ const LiveTVSearchResults: FunctionComponent = ({ serv 'searchResults', 'padded-bottom-page', 'padded-top', - { 'hide': !query || !(collectionType === 'livetv') } + { 'hide': !query || collectionType !== 'livetv' } )} > = ({ onSearch = () => { }; }, [debouncedOnSearch]); - const onAlphaPicked = (e: Event) => { + const onAlphaPicked = useCallback((e: Event) => { const value = (e as CustomEvent).detail.value; const searchInput = getSearchInput(); @@ -70,7 +70,7 @@ const SearchFields: FunctionComponent = ({ onSearch = () => { } searchInput.dispatchEvent(new CustomEvent('input', { bubbles: true })); - }; + }, []); return (
= 700) { lastUpdateTime = now; const player = this; currentRuntimeTicks = playbackManager.duration(player); diff --git a/src/elements/emby-button/LinkButton.tsx b/src/elements/emby-button/LinkButton.tsx index 2a175bd177..b4a692a762 100644 --- a/src/elements/emby-button/LinkButton.tsx +++ b/src/elements/emby-button/LinkButton.tsx @@ -1,4 +1,4 @@ -import React, { AnchorHTMLAttributes, DetailedHTMLProps, MouseEvent } from 'react'; +import React, { AnchorHTMLAttributes, DetailedHTMLProps, MouseEvent, useCallback } from 'react'; import classNames from 'classnames'; import layoutManager from '../../components/layoutManager'; import shell from '../../scripts/shell'; @@ -23,7 +23,7 @@ const LinkButton: React.FC = ({ children, ...rest }) => { - const onAnchorClick = (e: MouseEvent) => { + const onAnchorClick = useCallback((e: MouseEvent) => { const url = href || ''; if (url !== '#') { if (target) { @@ -38,7 +38,7 @@ const LinkButton: React.FC = ({ } else { e.preventDefault(); } - }; + }, [ href, target ]); if (isAutoHideEnabled === true && !appHost.supports('externallinks')) { return null; diff --git a/src/elements/emby-scrollbuttons/ScrollButtons.tsx b/src/elements/emby-scrollbuttons/ScrollButtons.tsx index 85c6f03a0e..6f4bce3826 100644 --- a/src/elements/emby-scrollbuttons/ScrollButtons.tsx +++ b/src/elements/emby-scrollbuttons/ScrollButtons.tsx @@ -29,7 +29,7 @@ const ScrollButtons: FC = ({ scrollerFactoryRef, scrollState } }, [scrollerFactoryRef]); - const onScrollButtonClick = (direction: Direction) => { + const onScrollButtonClick = useCallback((direction: Direction) => { let newPos; if (direction === Direction.LEFT) { newPos = Math.max(0, scrollState.scrollPos - scrollState.scrollSize); @@ -44,7 +44,10 @@ const ScrollButtons: FC = ({ scrollerFactoryRef, scrollState } scrollToPosition(newPos, false); - }; + }, [ scrollState.scrollPos, scrollState.scrollSize, scrollToPosition ]); + + const triggerScrollLeft = useCallback(() => onScrollButtonClick(Direction.LEFT), [ onScrollButtonClick ]); + const triggerScrollRight = useCallback(() => onScrollButtonClick(Direction.RIGHT), [ onScrollButtonClick ]); useEffect(() => { const parent = scrollButtonsRef.current?.parentNode as HTMLDivElement; @@ -63,7 +66,7 @@ const ScrollButtons: FC = ({ scrollerFactoryRef, scrollState onScrollButtonClick(Direction.LEFT)} + onClick={triggerScrollLeft} icon='chevron_left' disabled={localeScrollPos > 0 ? false : true} /> @@ -71,7 +74,7 @@ const ScrollButtons: FC = ({ scrollerFactoryRef, scrollState onScrollButtonClick(Direction.RIGHT)} + onClick={triggerScrollRight} icon='chevron_right' disabled={scrollState.scrollWidth > 0 && localeScrollPos + scrollState.scrollSize >= scrollState.scrollWidth ? true : false} /> diff --git a/src/hooks/useApi.tsx b/src/hooks/useApi.tsx index 7828db6cd8..ff94b7a537 100644 --- a/src/hooks/useApi.tsx +++ b/src/hooks/useApi.tsx @@ -1,7 +1,7 @@ import type { Api } from '@jellyfin/sdk'; import type { UserDto } from '@jellyfin/sdk/lib/generated-client'; import type { ApiClient, Event } from 'jellyfin-apiclient'; -import React, { createContext, FC, useContext, useEffect, useState } from 'react'; +import React, { createContext, FC, useContext, useEffect, useMemo, useState } from 'react'; import ServerConnections from '../components/ServerConnections'; import events from '../utils/events'; @@ -21,6 +21,12 @@ export const ApiProvider: FC = ({ children }) => { const [ api, setApi ] = useState(); const [ user, setUser ] = useState(); + const context = useMemo(() => ({ + __legacyApiClient__: legacyApiClient, + api, + user + }), [ api, legacyApiClient, user ]); + useEffect(() => { ServerConnections.currentApiClient() ?.getCurrentUser() @@ -56,11 +62,7 @@ export const ApiProvider: FC = ({ children }) => { }, [ legacyApiClient, setApi ]); return ( - + {children} ); diff --git a/src/routes/movies/SuggestionsView.tsx b/src/routes/movies/SuggestionsView.tsx index 605d40062c..362fb7ee91 100644 --- a/src/routes/movies/SuggestionsView.tsx +++ b/src/routes/movies/SuggestionsView.tsx @@ -143,8 +143,8 @@ const SuggestionsView: FC = ({topParentId}) => { {!recommendations.length ?

{globalize.translate('MessageNothingHere')}

{globalize.translate('MessageNoMovieSuggestionsAvailable')}

-
: recommendations.map((recommendation, index) => { - return ; +
: recommendations.map(recommendation => { + return ; })}
); diff --git a/src/routes/user/userparentalcontrol.tsx b/src/routes/user/userparentalcontrol.tsx index 2edb981cda..51cb4b341e 100644 --- a/src/routes/user/userparentalcontrol.tsx +++ b/src/routes/user/userparentalcontrol.tsx @@ -379,9 +379,9 @@ const UserParentalControl: FunctionComponent = () => { isLinkVisible={false} />
- {blockedTags.map((tag, index) => { + {blockedTags.map(tag => { return ; })} @@ -401,7 +401,7 @@ const UserParentalControl: FunctionComponent = () => {
{accessSchedules.map((accessSchedule, index) => { return