Skip to content

Commit

Permalink
Merge branch 'jellyfin:master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
mark5231 authored Dec 20, 2022
2 parents 1a9b2dd + 55263e4 commit 6c7fd67
Show file tree
Hide file tree
Showing 35 changed files with 771 additions and 120 deletions.
16 changes: 13 additions & 3 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand All @@ -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: {
Expand Down Expand Up @@ -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']
}
}
]
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ jobs:
- name: Checkout repository
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3
- name: Initialize CodeQL
uses: github/codeql-action/init@c3b6fce4ee2ca25bc1066aa3bf73962fda0e8898 # tag=v2
uses: github/codeql-action/init@a669cc5936cc5e1b6a362ec1ff9e410dc570d190 # v2
with:
languages: ${{ matrix.language }}
queries: +security-extended
- name: Autobuild
uses: github/codeql-action/autobuild@c3b6fce4ee2ca25bc1066aa3bf73962fda0e8898 # tag=v2
uses: github/codeql-action/autobuild@a669cc5936cc5e1b6a362ec1ff9e410dc570d190 # v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@c3b6fce4ee2ca25bc1066aa3bf73962fda0e8898 # tag=v2
uses: github/codeql-action/analyze@a669cc5936cc5e1b6a362ec1ff9e410dc570d190 # v2
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { History } from '@remix-run/router';
import React from 'react';

import { HistoryRouter } from './components/HistoryRouter';
import { ApiProvider } from './hooks/useApi';
import AppRoutes from './routes/index';

const App = ({ history }: { history: History }) => {
return (
<ApiProvider>
<HistoryRouter history={history}>
<AppRoutes />
</HistoryRouter>
</ApiProvider>
);
};

export default App;
1 change: 1 addition & 0 deletions src/apiclient.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ declare module 'jellyfin-apiclient' {
getCountries(): Promise<CountryInfo[]>;
getCriticReviews(itemId: string, options?: any): Promise<BaseItemDtoQueryResult>;
getCultures(): Promise<CultureDto[]>;
getCurrentUser(cache?: boolean): Promise<UserDto>;
getCurrentUserId(): string;
getDateParamValue(date: Date): string;
getDefaultImageQuality(imageType: ImageType): number;
Expand Down
4 changes: 2 additions & 2 deletions src/components/ConnectionRequired.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ const ConnectionRequired: FunctionComponent<ConnectionRequiredProps> = ({
// If this is an admin route, ensure the user has access
if (isAdminRequired) {
try {
const user = await client.getCurrentUser();
if (!user.Policy.IsAdministrator) {
const user = await client?.getCurrentUser();
if (!user?.Policy?.IsAdministrator) {
console.warn('[ConnectionRequired] normal user attempted to access admin route');
bounce(await ServerConnections.connect());
return;
Expand Down
4 changes: 4 additions & 0 deletions src/components/ServerConnections.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ class ServerConnections extends ConnectionManager {
return this.localApiClient;
}

/**
* Gets the ApiClient that is currently connected.
* @returns {ApiClient|undefined} apiClient
*/
currentApiClient() {
let apiClient = this.getLocalApiClient();

Expand Down
2 changes: 1 addition & 1 deletion src/components/ServerContentPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const ServerContentPage: FunctionComponent<ServerContentPageProps> = ({ view })
const apiClient = ServerConnections.currentApiClient();

// Fetch the view html from the server and translate it
const viewHtml = await apiClient.get(apiClient.getUrl(view + location.search))
const viewHtml = await apiClient?.get(apiClient.getUrl(view + location.search))
.then((html: string) => globalize.translateHtml(html));

viewManager.loadView({
Expand Down
4 changes: 2 additions & 2 deletions src/components/common/GenresItemsContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ const GenresItemsContainer: FC<GenresItemsContainerProps> = ({
<h1>{globalize.translate('MessageNothingHere')}</h1>
<p>{globalize.translate('MessageNoGenresAvailable')}</p>
</div>
) : items.map((item, index) => (
<div key={index} className='verticalSection'>
) : items.map(item => (
<div key={item.Id} className='verticalSection'>
<div
className='sectionTitleContainer sectionTitleContainer-cards padded-left'
dangerouslySetInnerHTML={createLinkElement({
Expand Down
2 changes: 1 addition & 1 deletion src/components/remotecontrol/remotecontrol.js
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ export default function () {
function onTimeUpdate() {
const now = new Date().getTime();

if (!(now - lastUpdateTime < 700)) {
if (now - lastUpdateTime >= 700) {
lastUpdateTime = now;
const player = this;
currentRuntimeTicks = playbackManager.duration(player);
Expand Down
2 changes: 1 addition & 1 deletion src/components/search/LiveTVSearchResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ const LiveTVSearchResults: FunctionComponent<LiveTVSearchResultsProps> = ({ serv
'searchResults',
'padded-bottom-page',
'padded-top',
{ 'hide': !query || !(collectionType === 'livetv') }
{ 'hide': !query || collectionType !== 'livetv' }
)}
>
<SearchResultsRow
Expand Down
6 changes: 3 additions & 3 deletions src/components/search/SearchFields.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import debounce from 'lodash-es/debounce';
import React, { FunctionComponent, useEffect, useMemo, useRef } from 'react';
import React, { FunctionComponent, useCallback, useEffect, useMemo, useRef } from 'react';

import AlphaPicker from '../alphaPicker/AlphaPickerComponent';
import globalize from '../../scripts/globalize';
Expand Down Expand Up @@ -53,7 +53,7 @@ const SearchFields: FunctionComponent<SearchFieldsProps> = ({ onSearch = () => {
};
}, [debouncedOnSearch]);

const onAlphaPicked = (e: Event) => {
const onAlphaPicked = useCallback((e: Event) => {
const value = (e as CustomEvent).detail.value;
const searchInput = getSearchInput();

Expand All @@ -70,7 +70,7 @@ const SearchFields: FunctionComponent<SearchFieldsProps> = ({ onSearch = () => {
}

searchInput.dispatchEvent(new CustomEvent('input', { bubbles: true }));
};
}, []);

return (
<div
Expand Down
38 changes: 22 additions & 16 deletions src/components/search/SearchSuggestions.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client';
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api/items-api';
import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by';
import escapeHtml from 'escape-html';
import React, { FunctionComponent, useEffect, useState } from 'react';

import { appRouter } from '../appRouter';
import { useApi } from '../../hooks/useApi';
import globalize from '../../scripts/globalize';
import ServerConnections from '../ServerConnections';

import '../../elements/emby-button/emby-button';

Expand All @@ -21,27 +24,30 @@ const createSuggestionLink = ({ name, href }: { name: string, href: string }) =>
});

type SearchSuggestionsProps = {
serverId?: string;
parentId?: string | null;
}

const SearchSuggestions: FunctionComponent<SearchSuggestionsProps> = ({ serverId = window.ApiClient.serverId(), parentId }: SearchSuggestionsProps) => {
const SearchSuggestions: FunctionComponent<SearchSuggestionsProps> = ({ parentId }: SearchSuggestionsProps) => {
const [ suggestions, setSuggestions ] = useState<BaseItemDto[]>([]);
const { api, user } = useApi();

useEffect(() => {
const apiClient = ServerConnections.getApiClient(serverId);

apiClient.getItems(apiClient.getCurrentUserId(), {
SortBy: 'IsFavoriteOrLiked,Random',
IncludeItemTypes: 'Movie,Series,MusicArtist',
Limit: 20,
Recursive: true,
ImageTypeLimit: 0,
EnableImages: false,
ParentId: parentId,
EnableTotalRecordCount: false
}).then(result => setSuggestions(result.Items || []));
}, [parentId, serverId]);
if (api && user?.Id) {
getItemsApi(api)
.getItemsByUserId({
userId: user.Id,
sortBy: [ItemSortBy.IsFavoriteOrLiked, ItemSortBy.Random],
includeItemTypes: [BaseItemKind.Movie, BaseItemKind.Series, BaseItemKind.MusicArtist],
limit: 20,
recursive: true,
imageTypeLimit: 0,
enableImages: false,
parentId: parentId || undefined,
enableTotalRecordCount: false
})
.then(result => setSuggestions(result.data.Items || []));
}
}, [ api, parentId, user ]);

return (
<div
Expand Down
7 changes: 3 additions & 4 deletions src/controllers/favorites.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,10 +294,9 @@ class FavoritesTab {
}

onPause() {
const elems = this.sectionsContainer.querySelectorAll('.itemsContainer');

for (const elem of elems) {
elem.pause();
if (this.sectionsContainer) {
Array.from(this.sectionsContainer.querySelectorAll('.itemsContainer'))
.forEach(e => { e.pause(); });
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/controllers/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -1106,7 +1106,7 @@ class ItemsView {
const filters = [];
const params = this.params;

if (!(params.type === 'nextup')) {
if (params.type !== 'nextup') {
if (params.type === 'Programs') {
filters.push('Genres');
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/playback/video/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components
if (isEnabled && currentItem) {
const now = new Date().getTime();

if (!(now - lastUpdateTime < 700)) {
if (now - lastUpdateTime >= 700) {
lastUpdateTime = now;
const player = this;
currentRuntimeTicks = playbackManager.duration(player);
Expand Down
53 changes: 53 additions & 0 deletions src/elements/emby-button/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React, { ButtonHTMLAttributes, DetailedHTMLProps } from 'react';
import classNames from 'classnames';
import layoutManager from '../../components/layoutManager';
import './emby-button.scss';

enum IconPosition {
RIGHT = 'RIGHT',
LEFT = 'LEFT',
}

interface ButtonProps extends DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>,
HTMLButtonElement
> {
icon?: string;
iconClassName?: string;
iconPos?: string;
}

const Button: React.FC<ButtonProps> = ({
className,
title,
icon,
iconClassName,
iconPos,
onClick,
...rest
}) => {
const btnClass = classNames(
'emby-button',
className,
{ 'show-focus': layoutManager.tv }
);

const iconClass = classNames(
'material-icons',
iconClassName,
icon
);

return (
<button
className={btnClass}
onClick={onClick}
{...rest}
>
{icon && iconPos === IconPosition.LEFT && <span className={iconClass} aria-hidden='true' />}
<span>{title}</span>
{icon && iconPos === IconPosition.RIGHT && <span className={iconClass} aria-hidden='true' />}
</button>
);
};

export default Button;
47 changes: 47 additions & 0 deletions src/elements/emby-button/IconButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React, { ButtonHTMLAttributes, DetailedHTMLProps } from 'react';
import classNames from 'classnames';
import layoutManager from '../../components/layoutManager';
import './emby-button.scss';

interface IconButtonProps extends DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>,
HTMLButtonElement
> {
icon?: string;
iconClassName?: string;
}

const IconButton: React.FC<IconButtonProps> = ({
className,
title,
icon,
iconClassName,
disabled = false,
onClick,
...rest
}) => {
const btnClass = classNames(
'paper-icon-button-light',
className,
{ 'show-focus': layoutManager.tv }
);

const iconClass = classNames(
'material-icons',
iconClassName,
icon
);

return (
<button
className={btnClass}
title={title}
disabled={disabled}
onClick={onClick}
{...rest}
>
<span className={iconClass} aria-hidden='true' />
</button>
);
};

export default IconButton;
Loading

0 comments on commit 6c7fd67

Please sign in to comment.