Merge pull request #7491 from PeachesMLG/fix_jellyfin_sdk_type_errors

This commit is contained in:
Bill Thornton
2026-01-23 23:22:27 -05:00
committed by GitHub
14 changed files with 47 additions and 42 deletions
+4 -4
View File
@@ -18,7 +18,7 @@
"@fontsource/noto-sans-sc": "5.2.6", "@fontsource/noto-sans-sc": "5.2.6",
"@fontsource/noto-sans-tc": "5.2.6", "@fontsource/noto-sans-tc": "5.2.6",
"@jellyfin/libass-wasm": "4.2.3", "@jellyfin/libass-wasm": "4.2.3",
"@jellyfin/sdk": "0.0.0-unstable.202512091852", "@jellyfin/sdk": "0.0.0-unstable.202601090504",
"@jellyfin/ux-web": "1.0.0", "@jellyfin/ux-web": "1.0.0",
"@mui/icons-material": "6.4.12", "@mui/icons-material": "6.4.12",
"@mui/material": "6.4.12", "@mui/material": "6.4.12",
@@ -4130,9 +4130,9 @@
"license": "LGPL-2.1-or-later AND (FTL OR GPL-2.0-or-later) AND MIT AND MIT-Modern-Variant AND ISC AND NTP AND Zlib AND BSL-1.0" "license": "LGPL-2.1-or-later AND (FTL OR GPL-2.0-or-later) AND MIT AND MIT-Modern-Variant AND ISC AND NTP AND Zlib AND BSL-1.0"
}, },
"node_modules/@jellyfin/sdk": { "node_modules/@jellyfin/sdk": {
"version": "0.0.0-unstable.202512091852", "version": "0.0.0-unstable.202601090504",
"resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202512091852.tgz", "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202601090504.tgz",
"integrity": "sha512-N+QEsrKk4KculkV6KMBb7XpzTLWcXEzqTHbS+b4rov0VYVwR6DIsJkmUzB3hM2YZsrLIHEFKhFRy/r4itkFeHw==", "integrity": "sha512-ZTtSUMYjyfin54dXGZ5i9fZxLkKbMPxxsFxnmLoSVrjNrvL8JMV3Thb0fevCgd5gpkJpLVJXpx5YjwNbrze6DQ==",
"license": "MPL-2.0", "license": "MPL-2.0",
"peerDependencies": { "peerDependencies": {
"axios": "^1.12.0" "axios": "^1.12.0"
+1 -1
View File
@@ -85,7 +85,7 @@
"@fontsource/noto-sans-sc": "5.2.6", "@fontsource/noto-sans-sc": "5.2.6",
"@fontsource/noto-sans-tc": "5.2.6", "@fontsource/noto-sans-tc": "5.2.6",
"@jellyfin/libass-wasm": "4.2.3", "@jellyfin/libass-wasm": "4.2.3",
"@jellyfin/sdk": "0.0.0-unstable.202512091852", "@jellyfin/sdk": "0.0.0-unstable.202601090504",
"@jellyfin/ux-web": "1.0.0", "@jellyfin/ux-web": "1.0.0",
"@mui/icons-material": "6.4.12", "@mui/icons-material": "6.4.12",
"@mui/material": "6.4.12", "@mui/material": "6.4.12",
@@ -1,35 +1,36 @@
import type { FolderStorageDto } from '@jellyfin/sdk/lib/generated-client/models/folder-storage-dto';
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
import { calculateUsedPercentage, calculateTotal } from './space'; import { calculateUsedPercentage, calculateTotal } from './space';
describe('calculateTotal()', () => { describe('calculateTotal()', () => {
it('should return the total', () => { it('should return the total', () => {
expect(calculateTotal({ FreeSpace: 1, UsedSpace: 2 })).toBe(3); expect(calculateTotal({ FreeSpace: 1, UsedSpace: 2 } as FolderStorageDto)).toBe(3);
}); });
it('should return -1 for invalid used space values', () => { it('should return -1 for invalid used space values', () => {
expect(calculateTotal({ FreeSpace: 1 })).toBe(-1); expect(calculateTotal({ FreeSpace: 1 } as FolderStorageDto)).toBe(-1);
expect(calculateTotal({ FreeSpace: 1, UsedSpace: -1 })).toBe(-1); expect(calculateTotal({ FreeSpace: 1, UsedSpace: -1 } as FolderStorageDto)).toBe(-1);
}); });
it('should treat invalid free space values as 0', () => { it('should treat invalid free space values as 0', () => {
expect(calculateTotal({ UsedSpace: 1 })).toBe(1); expect(calculateTotal({ UsedSpace: 1 } as FolderStorageDto)).toBe(1);
expect(calculateTotal({ FreeSpace: -1, UsedSpace: 1 })).toBe(1); expect(calculateTotal({ FreeSpace: -1, UsedSpace: 1 } as FolderStorageDto)).toBe(1);
}); });
}); });
describe('calculateUsedPercentage', () => { describe('calculateUsedPercentage', () => {
it('should return the percentage used', () => { it('should return the percentage used', () => {
expect(calculateUsedPercentage({ FreeSpace: 1, UsedSpace: 3 })).toBe(75); expect(calculateUsedPercentage({ FreeSpace: 1, UsedSpace: 3 } as FolderStorageDto)).toBe(75);
}); });
it('should return 0 for invalid used space values', () => { it('should return 0 for invalid used space values', () => {
expect(calculateUsedPercentage({ FreeSpace: 1 })).toBe(0); expect(calculateUsedPercentage({ FreeSpace: 1 } as FolderStorageDto)).toBe(0);
expect(calculateUsedPercentage({ FreeSpace: 1, UsedSpace: -1 })).toBe(0); expect(calculateUsedPercentage({ FreeSpace: 1, UsedSpace: -1 } as FolderStorageDto)).toBe(0);
}); });
it('should return 100 for invalid free space values', () => { it('should return 100 for invalid free space values', () => {
expect(calculateUsedPercentage({ UsedSpace: 1 })).toBe(100); expect(calculateUsedPercentage({ UsedSpace: 1 } as FolderStorageDto)).toBe(100);
expect(calculateUsedPercentage({ FreeSpace: -1, UsedSpace: 1 })).toBe(100); expect(calculateUsedPercentage({ FreeSpace: -1, UsedSpace: 1 } as FolderStorageDto)).toBe(100);
}); });
}); });
@@ -15,7 +15,7 @@ import type { NullableString } from 'types/base/common/shared/types';
import type { ItemDto } from 'types/base/models/item-dto'; import type { ItemDto } from 'types/base/models/item-dto';
interface PlayAllFromHereOptions { interface PlayAllFromHereOptions {
item: ItemDto; item?: ItemDto;
items: ItemDto[]; items: ItemDto[];
serverId: NullableString; serverId: NullableString;
queue?: boolean; queue?: boolean;
@@ -166,7 +166,7 @@ const MoreCommandsButton: FC<MoreCommandsButtonProps> = ({
serverId: item?.ServerId serverId: item?.ServerId
}); });
playAllFromHere({ playAllFromHere({
item: item || {}, item: item,
items: items || [], items: items || [],
serverId: item?.ServerId serverId: item?.ServerId
}).catch(err => { }).catch(err => {
@@ -174,7 +174,7 @@ const MoreCommandsButton: FC<MoreCommandsButtonProps> = ({
}); });
} else if (result.command === 'queueallfromhere') { } else if (result.command === 'queueallfromhere') {
playAllFromHere({ playAllFromHere({
item: item || {}, item: item,
items: items || [], items: items || [],
serverId: item?.ServerId, serverId: item?.ServerId,
queue: true queue: true
@@ -47,7 +47,7 @@ export function getImageUrl(item: ItemDto, options: ImageOptions = {}) {
const itemId = item.PrimaryImageItemId || item.Id; const itemId = item.PrimaryImageItemId || item.Id;
if (itemId && item.ImageTags?.[options.type]) { if (itemId && item.ImageTags?.[options.type]) {
options.tag = item.ImageTags[options.type]; options.tag = item.ImageTags[options.type] ?? undefined;
return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(itemId, options); return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(itemId, options);
} }
@@ -7,7 +7,7 @@ import { getItemTextLines } from './itemText';
describe('getItemTextLines', () => { describe('getItemTextLines', () => {
it('Should return undefined if item is invalid', () => { it('Should return undefined if item is invalid', () => {
let lines = getItemTextLines({}); let lines = getItemTextLines({} as ItemDto);
expect(lines).toBeUndefined(); expect(lines).toBeUndefined();
lines = getItemTextLines(null); lines = getItemTextLines(null);
expect(lines).toBeUndefined(); expect(lines).toBeUndefined();
@@ -16,9 +16,9 @@ describe('getItemTextLines', () => {
}); });
it('Should return the name and index number', () => { it('Should return the name and index number', () => {
const item: ItemDto = { const item = {
Name: 'Item Name' Name: 'Item Name'
}; } as ItemDto;
let lines = getItemTextLines(item); let lines = getItemTextLines(item);
expect(lines).toBeDefined(); expect(lines).toBeDefined();
expect(lines).toHaveLength(1); expect(lines).toHaveLength(1);
@@ -39,13 +39,13 @@ describe('getItemTextLines', () => {
}); });
it('Should add artist names', () => { it('Should add artist names', () => {
let item: ItemDto = { let item = {
Name: 'Item Name', Name: 'Item Name',
ArtistItems: [ ArtistItems: [
{ Name: 'Artist 1' }, { Name: 'Artist 1' },
{ Name: 'Artist 2' } { Name: 'Artist 2' }
] ]
}; } as ItemDto;
let lines = getItemTextLines(item); let lines = getItemTextLines(item);
expect(lines).toBeDefined(); expect(lines).toBeDefined();
expect(lines).toHaveLength(2); expect(lines).toHaveLength(2);
@@ -58,7 +58,7 @@ describe('getItemTextLines', () => {
'Artist 1', 'Artist 1',
'Artist 2' 'Artist 2'
] ]
}; } as ItemDto;
lines = getItemTextLines(item); lines = getItemTextLines(item);
expect(lines).toBeDefined(); expect(lines).toBeDefined();
expect(lines).toHaveLength(2); expect(lines).toHaveLength(2);
@@ -67,10 +67,10 @@ describe('getItemTextLines', () => {
}); });
it('Should add album or series name', () => { it('Should add album or series name', () => {
let item: ItemDto = { let item = {
Name: 'Item Name', Name: 'Item Name',
SeriesName: 'Series' SeriesName: 'Series'
}; } as ItemDto;
let lines = getItemTextLines(item); let lines = getItemTextLines(item);
expect(lines).toBeDefined(); expect(lines).toBeDefined();
expect(lines).toHaveLength(2); expect(lines).toHaveLength(2);
@@ -80,7 +80,7 @@ describe('getItemTextLines', () => {
item = { item = {
Name: 'Item Name', Name: 'Item Name',
Album: 'Album' Album: 'Album'
}; } as ItemDto;
lines = getItemTextLines(item); lines = getItemTextLines(item);
expect(lines).toBeDefined(); expect(lines).toBeDefined();
expect(lines).toHaveLength(2); expect(lines).toHaveLength(2);
@@ -92,7 +92,7 @@ describe('getItemTextLines', () => {
const item = { const item = {
Name: 'Item Name', Name: 'Item Name',
ProductionYear: 2025 ProductionYear: 2025
}; } as ItemDto;
const lines = getItemTextLines(item); const lines = getItemTextLines(item);
expect(lines).toBeDefined(); expect(lines).toBeDefined();
expect(lines).toHaveLength(2); expect(lines).toHaveLength(2);
+2 -2
View File
@@ -7,11 +7,11 @@ import type { CardOptions } from 'types/cardOptions';
import type { ItemDto } from 'types/base/models/item-dto'; import type { ItemDto } from 'types/base/models/item-dto';
interface CardProps { interface CardProps {
item?: ItemDto; item: ItemDto;
cardOptions: CardOptions; cardOptions: CardOptions;
} }
const Card: FC<CardProps> = ({ item = {}, cardOptions }) => { const Card: FC<CardProps> = ({ item, cardOptions }) => {
const { getCardWrapperProps, getCardBoxProps } = useCard({ item, cardOptions } ); const { getCardWrapperProps, getCardBoxProps } = useCard({ item, cardOptions } );
const cardWrapperProps = getCardWrapperProps(); const cardWrapperProps = getCardWrapperProps();
const cardBoxProps = getCardBoxProps(); const cardBoxProps = getCardBoxProps();
@@ -217,6 +217,7 @@ function getParentTitle(
.map(artist => { .map(artist => {
const artistItem: ItemDto = { const artistItem: ItemDto = {
...artist, ...artist,
Key: artist.Id ?? '',
Type: BaseItemKind.MusicArtist, Type: BaseItemKind.MusicArtist,
IsFolder: true IsFolder: true
}; };
@@ -511,6 +512,7 @@ function getChannelName(item: ItemDto) {
if (item.ChannelId) { if (item.ChannelId) {
return getTextActionButton( return getTextActionButton(
{ {
Key: item.ChannelId,
Id: item.ChannelId, Id: item.ChannelId,
ServerId: item.ServerId, ServerId: item.ServerId,
Name: item.ChannelName, Name: item.ChannelName,
@@ -598,6 +600,7 @@ function getMediaTitle(cardOptions: CardOptions, item: ItemDto): TextLine {
}); });
return getTextActionButton({ return getTextActionButton({
Key: item.Id ?? '',
Id: item.Id, Id: item.Id,
ServerId: item.ServerId, ServerId: item.ServerId,
Name: name, Name: name,
@@ -620,6 +623,7 @@ function getParentTitleOrTitle(
) { ) {
if (item.SeriesId) { if (item.SeriesId) {
return getTextActionButton({ return getTextActionButton({
Key: item.SeriesId,
Id: item.SeriesId, Id: item.SeriesId,
ServerId: item.ServerId, ServerId: item.ServerId,
Name: item.SeriesName, Name: item.SeriesName,
+1 -1
View File
@@ -39,7 +39,7 @@ function useCard({ item, cardOptions }: UseCardProps) {
shape shape
}); });
const imgUrl = imgInfo.imgUrl; const imgUrl = imgInfo.imgUrl;
const blurhash = imgInfo.blurhash; const blurhash = imgInfo.blurhash ?? undefined;
const forceName = imgInfo.forceName; const forceName = imgInfo.forceName;
const coveredImage = cardOptions.coverImage ?? imgInfo.coverImage; const coveredImage = cardOptions.coverImage ?? imgInfo.coverImage;
const overlayText = cardOptions.overlayText; const overlayText = cardOptions.overlayText;
@@ -111,7 +111,7 @@ function shouldShowPreferBanner(
} }
function shouldShowPreferDisc( function shouldShowPreferDisc(
imageTagsDisc: string | undefined, imageTagsDisc: string | null | undefined,
cardOptions: CardOptions cardOptions: CardOptions
): boolean { ): boolean {
return cardOptions.preferDisc === true && Boolean(imageTagsDisc); return cardOptions.preferDisc === true && Boolean(imageTagsDisc);
+5 -5
View File
@@ -154,8 +154,8 @@ const useIndicator = (item: ItemDto) => {
const getPlayedIndicator = () => { const getPlayedIndicator = () => {
if (enablePlayedIndicator(item)) { if (enablePlayedIndicator(item)) {
const userData = item.UserData || {}; const userData = item.UserData;
if (userData.UnplayedItemCount) { if (userData?.UnplayedItemCount) {
return ( return (
<Box className='countIndicator indicator unplayedItemCount'> <Box className='countIndicator indicator unplayedItemCount'>
{formatCountIndicator(userData.UnplayedItemCount)} {formatCountIndicator(userData.UnplayedItemCount)}
@@ -164,9 +164,9 @@ const useIndicator = (item: ItemDto) => {
} }
if ( if (
(userData.PlayedPercentage (userData?.PlayedPercentage
&& userData.PlayedPercentage >= 100) && userData?.PlayedPercentage >= 100)
|| userData.Played || userData?.Played
) { ) {
return ( return (
<Box className='playedIndicator indicator'> <Box className='playedIndicator indicator'>
@@ -28,7 +28,7 @@ interface ListImageContainerProps {
} }
const ListImageContainer: FC<ListImageContainerProps> = ({ const ListImageContainer: FC<ListImageContainerProps> = ({
item = {}, item,
listOptions, listOptions,
action, action,
isLargeStyle, isLargeStyle,
@@ -73,7 +73,7 @@ const ListImageContainer: FC<ListImageContainerProps> = ({
className={imageClass} className={imageClass}
> >
<Media item={item} imgUrl={imgUrl} blurhash={blurhash} defaultCardImageIcon={defaultCardImageIcon} /> <Media item={item} imgUrl={imgUrl} blurhash={blurhash ?? undefined} defaultCardImageIcon={defaultCardImageIcon} />
{disableIndicators !== true && mediaSourceIndicator} {disableIndicators !== true && mediaSourceIndicator}
@@ -22,7 +22,7 @@ interface ListItemBodyProps {
} }
const ListItemBody: FC<ListItemBodyProps> = ({ const ListItemBody: FC<ListItemBodyProps> = ({
item = {}, item,
listOptions = {}, listOptions = {},
action, action,
isLargeStyle, isLargeStyle,
@@ -95,7 +95,7 @@ function createPlaylist(dlg: DialogElement) {
return getPlaylistsApi(api) return getPlaylistsApi(api)
.createPlaylist({ .createPlaylist({
createPlaylistDto: { createPlaylistDto: {
Name: name, Name: name ?? '',
IsPublic: dlg.querySelector<HTMLInputElement>('#chkPlaylistPublic')?.checked, IsPublic: dlg.querySelector<HTMLInputElement>('#chkPlaylistPublic')?.checked,
Ids: itemIds?.split(','), Ids: itemIds?.split(','),
UserId: apiClient.getCurrentUserId() UserId: apiClient.getCurrentUserId()