From 173d05da8f2d7ca357159d4c51e960fd3a374dfa Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Sat, 11 Jan 2025 16:28:29 -0500 Subject: [PATCH 1/2] Add noUncheckedIndexedAccess typescript option --- tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/tsconfig.json b/tsconfig.json index d3541c6df12..d2385b1664d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,7 @@ "skipLibCheck": true, "esModuleInterop": true, "noImplicitAny": true, + "noUncheckedIndexedAccess": true, "allowSyntheticDefaultImports": true, "strict": true, "module": "ESNext", From 422fe86e02b47e0b14ace82e97cbe8d7371f2449 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Sun, 12 Jan 2025 03:51:53 -0500 Subject: [PATCH 2/2] Fix unchecked index access issues --- .../routes/users/parentalcontrol.tsx | 4 ++- .../components/buttons/MoreCommandsButton.tsx | 5 ++- src/apps/experimental/routes/home.tsx | 14 ++++----- .../experimental/routes/homevideos/index.tsx | 2 +- src/apps/experimental/routes/livetv/index.tsx | 2 +- src/apps/experimental/routes/movies/index.tsx | 2 +- src/apps/experimental/routes/music/index.tsx | 2 +- src/apps/experimental/routes/shows/index.tsx | 2 +- .../playback/utils/mediaSegmentManager.ts | 2 +- .../features/playback/utils/mediaSegments.ts | 12 +++---- src/components/actionSheet/actionSheet.ts | 4 +++ src/components/cardbuilder/Card/cardHelper.ts | 2 +- .../homesections/sections/libraryButtons.ts | 4 +-- src/components/listview/List/listHelper.ts | 8 +++-- src/elements/emby-scrollbuttons/utils.ts | 31 +++++++++++-------- src/utils/file.ts | 3 ++ src/utils/number.ts | 2 +- 17 files changed, 60 insertions(+), 41 deletions(-) diff --git a/src/apps/dashboard/routes/users/parentalcontrol.tsx b/src/apps/dashboard/routes/users/parentalcontrol.tsx index 8af285d8369..14c6822b6bb 100644 --- a/src/apps/dashboard/routes/users/parentalcontrol.tsx +++ b/src/apps/dashboard/routes/users/parentalcontrol.tsx @@ -79,10 +79,12 @@ const UserParentalControl = () => { for (let i = 0, length = allParentalRatings.length; i < length; i++) { rating = allParentalRatings[i]; + if (!rating) continue; + if (ratings.length) { const lastRating = ratings[ratings.length - 1]; - if (lastRating.Value === rating.Value) { + if (lastRating && lastRating.Value === rating.Value) { lastRating.Name += '/' + rating.Name; continue; } diff --git a/src/apps/experimental/features/details/components/buttons/MoreCommandsButton.tsx b/src/apps/experimental/features/details/components/buttons/MoreCommandsButton.tsx index 99db01378d7..1cea4139954 100644 --- a/src/apps/experimental/features/details/components/buttons/MoreCommandsButton.tsx +++ b/src/apps/experimental/features/details/components/buttons/MoreCommandsButton.tsx @@ -29,12 +29,15 @@ function playAllFromHere(opts: PlayAllFromHereOptions) { let startIndex; for (let i = 0, length = items?.length; i < length; i++) { + if (!items[i]) continue; + if (items[i] === item) { foundCard = true; startIndex = i; } + if (foundCard || !queue) { - ids.push(items[i].Id); + ids.push(items[i]?.Id); } } diff --git a/src/apps/experimental/routes/home.tsx b/src/apps/experimental/routes/home.tsx index b8aace9ac6c..bbdf815db70 100644 --- a/src/apps/experimental/routes/home.tsx +++ b/src/apps/experimental/routes/home.tsx @@ -67,15 +67,13 @@ const Home = () => { } return import(/* webpackChunkName: "[request]" */ `../../../controllers/${depends}`).then(({ default: ControllerFactory }) => { - let controller = tabControllers[index]; + const controller = tabControllers[index]; + if (controller) return controller; - if (!controller) { - const tabContent = element.current?.querySelector(".tabContent[data-index='" + index + "']"); - controller = new ControllerFactory(tabContent, null); - tabControllers[index] = controller; - } - - return controller; + const tabContent = element.current?.querySelector(".tabContent[data-index='" + index + "']"); + const newController = new ControllerFactory(tabContent, null); + tabControllers[index] = newController; + return newController; }); }, [ tabControllers ]); diff --git a/src/apps/experimental/routes/homevideos/index.tsx b/src/apps/experimental/routes/homevideos/index.tsx index 2f318c218a2..5ee20112531 100644 --- a/src/apps/experimental/routes/homevideos/index.tsx +++ b/src/apps/experimental/routes/homevideos/index.tsx @@ -39,7 +39,7 @@ const homevideosTabMapping: LibraryTabMapping = { const HomeVideos: FC = () => { const { libraryId, activeTab } = useCurrentTab(); - const currentTab = homevideosTabMapping[activeTab]; + const currentTab = homevideosTabMapping[activeTab] || photosTabContent; return ( { const { libraryId, activeTab } = useCurrentTab(); - const currentTab = liveTvTabMapping[activeTab]; + const currentTab = liveTvTabMapping[activeTab] || programsTabContent; return ( { const { libraryId, activeTab } = useCurrentTab(); - const currentTab = moviesTabMapping[activeTab]; + const currentTab = moviesTabMapping[activeTab] || moviesTabContent; return ( { const { libraryId, activeTab } = useCurrentTab(); - const currentTab = musicTabMapping[activeTab]; + const currentTab = musicTabMapping[activeTab] || albumsTabContent; return ( { const { libraryId, activeTab } = useCurrentTab(); - const currentTab = tvShowsTabMapping[activeTab]; + const currentTab = tvShowsTabMapping[activeTab] || seriesTabContent; return ( { +const isBeforeSegment = (segment: MediaSegmentDto | undefined, time: number, direction: number) => { if (direction === -1) { return ( - typeof segment.EndTicks !== 'undefined' + typeof segment?.EndTicks !== 'undefined' && segment.EndTicks <= time ); } return ( - typeof segment.StartTicks !== 'undefined' + typeof segment?.StartTicks !== 'undefined' && segment.StartTicks > time ); }; -export const isInSegment = (segment: MediaSegmentDto, time: number) => ( - typeof segment.StartTicks !== 'undefined' +export const isInSegment = (segment: MediaSegmentDto | undefined, time: number) => ( + typeof segment?.StartTicks !== 'undefined' && segment.StartTicks <= time && (typeof segment.EndTicks === 'undefined' || segment.EndTicks > time) ); @@ -26,7 +26,7 @@ export const findCurrentSegment = (segments: MediaSegmentDto[], time: number, la } let direction = 1; - if (lastIndex > 0 && lastSegment.StartTicks && lastSegment.StartTicks > time) { + if (lastIndex > 0 && lastSegment?.StartTicks && lastSegment.StartTicks > time) { direction = -1; } diff --git a/src/components/actionSheet/actionSheet.ts b/src/components/actionSheet/actionSheet.ts index 54bf0804df0..e06b53e89b4 100644 --- a/src/components/actionSheet/actionSheet.ts +++ b/src/components/actionSheet/actionSheet.ts @@ -93,6 +93,8 @@ function getPosition(positionTo: Element, options: Options, dlg: HTMLElement) { const pos = getOffsets([positionTo])[0]; + if (!pos) return; + if (options.positionY !== 'top') { pos.top += (pos.height || 0) / 2; } @@ -250,6 +252,8 @@ export function show(options: Options) { for (let i = 0; i < options.items.length; i++) { const item = options.items[i]; + if (!item) continue; + if (item.divider) { html += '
'; continue; diff --git a/src/components/cardbuilder/Card/cardHelper.ts b/src/components/cardbuilder/Card/cardHelper.ts index c7e7bc92808..708bebb23d2 100644 --- a/src/components/cardbuilder/Card/cardHelper.ts +++ b/src/components/cardbuilder/Card/cardHelper.ts @@ -209,7 +209,7 @@ function getParentTitle( serverId: NullableString, item: ItemDto ) { - if (isOuterFooter && item.AlbumArtists?.length) { + if (isOuterFooter && item.AlbumArtists?.length && item.AlbumArtists[0]) { (item.AlbumArtists[0] as ItemDto).Type = ItemKind.MusicArtist; (item.AlbumArtists[0] as ItemDto).IsFolder = true; return getTextActionButton(item.AlbumArtists[0], null, serverId); diff --git a/src/components/homesections/sections/libraryButtons.ts b/src/components/homesections/sections/libraryButtons.ts index 7b06b55e8e8..f904482cc33 100644 --- a/src/components/homesections/sections/libraryButtons.ts +++ b/src/components/homesections/sections/libraryButtons.ts @@ -17,8 +17,8 @@ function getLibraryButtonsHtml(items: BaseItemDto[]) { // library card background images for (let i = 0, length = items.length; i < length; i++) { const item = items[i]; - const icon = imageHelper.getLibraryIcon(item.CollectionType); - html += '' + escapeHtml(item.Name) + ''; + const icon = imageHelper.getLibraryIcon(item?.CollectionType); + html += '' + escapeHtml(item?.Name || '') + ''; } html += ''; diff --git a/src/components/listview/List/listHelper.ts b/src/components/listview/List/listHelper.ts index e9c05f66482..6cc3779617b 100644 --- a/src/components/listview/List/listHelper.ts +++ b/src/components/listview/List/listHelper.ts @@ -12,7 +12,9 @@ const sortBySortName = (item: ItemDto): string => { } // SortName - const name = (item.SortName ?? item.Name ?? '?')[0].toUpperCase(); + const name = (item.SortName ?? item.Name ?? '?')[0]?.toUpperCase(); + + if (!name) return ''; const code = name.charCodeAt(0); if (code < 65 || code > 90) { @@ -48,7 +50,9 @@ const sortByAlbumArtist = (item: ItemDto): string => { return ''; } - const name = item.AlbumArtist[0].toUpperCase(); + const name = item.AlbumArtist[0]?.toUpperCase(); + + if (!name) return ''; const code = name.charCodeAt(0); if (code < 65 || code > 90) { diff --git a/src/elements/emby-scrollbuttons/utils.ts b/src/elements/emby-scrollbuttons/utils.ts index 0d925957a61..3eb3f7643ea 100644 --- a/src/elements/emby-scrollbuttons/utils.ts +++ b/src/elements/emby-scrollbuttons/utils.ts @@ -45,7 +45,7 @@ function getFirstAndLastVisible(scrollFrame: HTMLElement, items: HTMLElement[], const currentScrollPos = scrollPosition * localeModifier; const scrollerWidth = scrollFrame.offsetWidth; - const itemWidth = items[0].offsetWidth; + const itemWidth = items[0]?.offsetWidth || 1; // Rounding down here will give us the first item index which is fully visible. We want the first partially visible // index so we'll subtract one. @@ -53,7 +53,7 @@ function getFirstAndLastVisible(scrollFrame: HTMLElement, items: HTMLElement[], // Rounding up will give us the last index which is at least partially visible (overflows at container end). const lastVisibleIndex = Math.floor((currentScrollPos + scrollerWidth) / itemWidth); - return [firstVisibleIndex, lastVisibleIndex]; + return { firstVisibleIndex, lastVisibleIndex }; } function scrollToWindow({ @@ -71,27 +71,32 @@ function scrollToWindow({ // factory functions on it, but is not a true scroller factory. For legacy, we need to pass `scroller` directly // instead of getting the frame from the factory instance. const frame = scroller.getScrollFrame?.() ?? scroller; - const [firstVisibleIndex, lastVisibleIndex] = getFirstAndLastVisible(frame, items, scrollState); + const { firstVisibleIndex, lastVisibleIndex } = getFirstAndLastVisible(frame, items, scrollState); - let scrollToPosition: number; + let scrollToPosition = 0; if (direction === ScrollDirection.RIGHT) { const nextItem = items[lastVisibleIndex]; + if (nextItem) { // This will be the position to anchor the item at `lastVisibleIndex` to the start of the view window. - const nextItemScrollOffset = lastVisibleIndex * nextItem.offsetWidth; - scrollToPosition = nextItemScrollOffset * localeModifier; + const nextItemScrollOffset = lastVisibleIndex * nextItem.offsetWidth; + scrollToPosition = nextItemScrollOffset * localeModifier; + } } else { const previousItem = items[firstVisibleIndex]; - const previousItemScrollOffset = firstVisibleIndex * previousItem.offsetWidth; - // Find the total number of items that can fit in a view window and subtract one to account for item at - // `firstVisibleIndex`. The total width of these items is the amount that we need to adjust the scroll position by - // to anchor item at `firstVisibleIndex` to the end of the view window. - const offsetAdjustment = (Math.floor(frame.offsetWidth / previousItem.offsetWidth) - 1) * previousItem.offsetWidth; + if (previousItem) { + const previousItemScrollOffset = firstVisibleIndex * previousItem.offsetWidth; - // This will be the position to anchor the item at `firstVisibleIndex` to the end of the view window. - scrollToPosition = (previousItemScrollOffset - offsetAdjustment) * localeModifier; + // Find the total number of items that can fit in a view window and subtract one to account for item at + // `firstVisibleIndex`. The total width of these items is the amount that we need to adjust the scroll position by + // to anchor item at `firstVisibleIndex` to the end of the view window. + const offsetAdjustment = (Math.floor(frame.offsetWidth / previousItem.offsetWidth) - 1) * previousItem.offsetWidth; + + // This will be the position to anchor the item at `firstVisibleIndex` to the end of the view window. + scrollToPosition = (previousItemScrollOffset - offsetAdjustment) * localeModifier; + } } if (scroller.slideTo) { diff --git a/src/utils/file.ts b/src/utils/file.ts index 0019a5068f2..62773b4eaa9 100644 --- a/src/utils/file.ts +++ b/src/utils/file.ts @@ -7,6 +7,9 @@ export function readFileAsBase64(file: File): Promise { reader.onload = (e) => { // Split by a comma to remove the url: prefix const data = (e.target?.result as string)?.split?.(',')[1]; + if (!data) { + return reject(new Error('No file data')); + } resolve(data); }; reader.onerror = reject; diff --git a/src/utils/number.ts b/src/utils/number.ts index 553280c1fef..fb45d500ec8 100644 --- a/src/utils/number.ts +++ b/src/utils/number.ts @@ -43,5 +43,5 @@ export function decimalCount(value: number): number { const arr = value.toString().split('.'); - return arr[1].length; + return arr[1]?.length || 0; }