diff --git a/src/apps/experimental/components/library/GenresItemsContainer.tsx b/src/apps/experimental/components/library/GenresItemsContainer.tsx index de7f5b53488..ee77eeef2a9 100644 --- a/src/apps/experimental/components/library/GenresItemsContainer.tsx +++ b/src/apps/experimental/components/library/GenresItemsContainer.tsx @@ -2,7 +2,7 @@ import type { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/bas import type { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type'; import React, { FC } from 'react'; import { useGetGenres } from 'hooks/useFetchItems'; -import globalize from 'lib/globalize'; +import NoItemsMessage from 'components/common/NoItemsMessage'; import Loading from 'components/loading/LoadingComponent'; import GenresSectionContainer from './GenresSectionContainer'; import type { ParentId } from 'types/library'; @@ -25,27 +25,18 @@ const GenresItemsContainer: FC = ({ } if (!genresResult?.Items?.length) { - return ( -
-

{globalize.translate('MessageNothingHere')}

-

{globalize.translate('MessageNoGenresAvailable')}

-
- ); + return ; } - return ( - <> - {genresResult.Items.map((genre) => ( - - ))} - - ); + return genresResult.Items.map((genre) => ( + + )); }; export default GenresItemsContainer; diff --git a/src/apps/experimental/components/library/GenresSectionContainer.tsx b/src/apps/experimental/components/library/GenresSectionContainer.tsx index 0cef257a885..08083ac6202 100644 --- a/src/apps/experimental/components/library/GenresSectionContainer.tsx +++ b/src/apps/experimental/components/library/GenresSectionContainer.tsx @@ -8,7 +8,7 @@ import React, { type FC } from 'react'; import { useGetItems } from 'hooks/useFetchItems'; import Loading from 'components/loading/LoadingComponent'; import { appRouter } from 'components/router/appRouter'; -import SectionContainer from './SectionContainer'; +import SectionContainer from 'components/common/SectionContainer'; import { CardShape } from 'utils/card'; import type { ParentId } from 'types/library'; import type { ItemDto } from 'types/base/models/item-dto'; @@ -59,9 +59,12 @@ const GenresSectionContainer: FC = ({ } return = ({ const getItems = useCallback(() => { if (!itemsResult?.Items?.length) { - return ; + return ; } if (libraryViewSettings.ViewMode === ViewMode.ListView) { diff --git a/src/apps/experimental/components/library/ProgramsSectionView.tsx b/src/apps/experimental/components/library/ProgramsSectionView.tsx index 33accce51be..d1a7bfdb8a1 100644 --- a/src/apps/experimental/components/library/ProgramsSectionView.tsx +++ b/src/apps/experimental/components/library/ProgramsSectionView.tsx @@ -3,7 +3,8 @@ import { useGetProgramsSectionsWithItems, useGetTimers } from 'hooks/useFetchIte import { appRouter } from 'components/router/appRouter'; import globalize from 'lib/globalize'; import Loading from 'components/loading/LoadingComponent'; -import SectionContainer from './SectionContainer'; +import NoItemsMessage from 'components/common/NoItemsMessage'; +import SectionContainer from 'components/common/SectionContainer'; import { CardShape } from 'utils/card'; import type { ParentId } from 'types/library'; import type { Section, SectionType } from 'types/sections'; @@ -30,14 +31,7 @@ const ProgramsSectionView: FC = ({ } if (!sectionsWithItems?.length && !upcomingRecordings?.length) { - return ( -
-

{globalize.translate('MessageNothingHere')}

-

- {globalize.translate('MessageNoItemsAvailable')} -

-
- ); + return ; } const getRouteUrl = (section: Section) => { @@ -58,23 +52,33 @@ const ProgramsSectionView: FC = ({ {sectionsWithItems?.map(({ section, items }) => ( - ))} {upcomingRecordings?.map((group) => ( void; -} - -const SectionContainer: FC = ({ - sectionTitle, - url, - items, - cardOptions, - reloadItems -}) => { - return ( -
-
- {url && items.length > 5 ? ( - -

- {sectionTitle} -

- -
- ) : ( -

- {sectionTitle} -

- )} -
- - - - - - -
- ); -}; - -export default SectionContainer; diff --git a/src/apps/experimental/components/library/SuggestionsSectionView.tsx b/src/apps/experimental/components/library/SuggestionsSectionView.tsx index 9dc480e98c8..c036451e7f5 100644 --- a/src/apps/experimental/components/library/SuggestionsSectionView.tsx +++ b/src/apps/experimental/components/library/SuggestionsSectionView.tsx @@ -8,7 +8,8 @@ import { import { appRouter } from 'components/router/appRouter'; import globalize from 'lib/globalize'; import Loading from 'components/loading/LoadingComponent'; -import SectionContainer from './SectionContainer'; +import NoItemsMessage from 'components/common/NoItemsMessage'; +import SectionContainer from '../../../../components/common/SectionContainer'; import { CardShape } from 'utils/card'; import type { ParentId } from 'types/library'; import type { Section, SectionType } from 'types/sections'; @@ -38,12 +39,7 @@ const SuggestionsSectionView: FC = ({ } if (!sectionsWithItems?.length && !movieRecommendationsItems?.length) { - return ( -
-

{globalize.translate('MessageNothingHere')}

-

{globalize.translate('MessageNoItemsAvailable')}

-
- ); + return ; } const getRouteUrl = (section: Section) => { @@ -96,9 +92,14 @@ const SuggestionsSectionView: FC = ({ {sectionsWithItems?.map(({ section, items }) => ( = ({ = ({ parentId }) => { - const { isLoading, data: groupsUpcomingEpisodes } = useGetGroupsUpcomingEpisodes(parentId); + const { isLoading, data: groupsUpcomingEpisodes } = + useGetGroupsUpcomingEpisodes(parentId); if (isLoading) return ; - return ( - - {!groupsUpcomingEpisodes?.length ? ( -
-

{globalize.translate('MessageNothingHere')}

-

- {globalize.translate( - 'MessagePleaseEnsureInternetMetadata' - )} -

-
- ) : ( - groupsUpcomingEpisodes?.map((group) => ( - - )) - )} -
- ); + if (!groupsUpcomingEpisodes?.length) { + return ; + } + + return groupsUpcomingEpisodes?.map((group) => ( + + )); }; export default UpcomingView; diff --git a/src/components/common/NoItemsMessage.tsx b/src/components/common/NoItemsMessage.tsx index 1098039e555..9f02703b961 100644 --- a/src/components/common/NoItemsMessage.tsx +++ b/src/components/common/NoItemsMessage.tsx @@ -4,19 +4,19 @@ import Typography from '@mui/material/Typography'; import globalize from 'lib/globalize'; interface NoItemsMessageProps { - noItemsMessage?: string; + message?: string; } const NoItemsMessage: FC = ({ - noItemsMessage = 'MessageNoItemsAvailable' + message = 'MessageNoItemsAvailable' }) => { return ( - + {globalize.translate('MessageNothingHere')} - - {globalize.translate(noItemsMessage)} + + {globalize.translate(message)} ); diff --git a/src/components/common/SectionContainer.tsx b/src/components/common/SectionContainer.tsx new file mode 100644 index 00000000000..3b5af60640c --- /dev/null +++ b/src/components/common/SectionContainer.tsx @@ -0,0 +1,145 @@ +import React, { type FC, type PropsWithChildren } from 'react'; +import Box from '@mui/material/Box'; +import Link from '@mui/material/Link'; +import Typography from '@mui/material/Typography'; +import ChevronRightIcon from '@mui/icons-material/ChevronRight'; +import classNames from 'classnames'; +import ItemsContainer, { + type ItemsContainerProps +} from 'elements/emby-itemscontainer/ItemsContainer'; +import Scroller, { type ScrollerProps } from 'elements/emby-scroller/Scroller'; +import Cards from 'components/cardbuilder/Card/Cards'; +import Lists from 'components/listview/List/Lists'; +import type { CardOptions } from 'types/cardOptions'; +import type { ListOptions } from 'types/listOptions'; +import type { ItemDto } from 'types/base/models/item-dto'; + +interface SectionHeaderProps { + className?: string; + itemsLength?: number; + url?: string; + title: string; +} + +const SectionHeader: FC = ({ + title, + className, + itemsLength = 0, + url +}) => { + const sectionHeaderClass = classNames( + 'sectionTitleContainer sectionTitleContainer-cards', + 'padded-left', + className + ); + + return ( + + {url && itemsLength > 5 ? ( + + + {title} + + + + ) : ( + + {title} + + )} + + ); +}; + +interface SectionContainerProps { + className?: string; + items?: ItemDto[]; + sectionHeaderProps?: Omit; + scrollerProps?: ScrollerProps; + itemsContainerProps?: ItemsContainerProps; + isListMode?: boolean; + isScrollerMode?: boolean; + noPadding?: boolean; + cardOptions?: CardOptions; + listOptions?: ListOptions; +} + +const SectionContainer: FC> = ({ + className, + sectionHeaderProps, + scrollerProps, + itemsContainerProps, + isListMode = false, + isScrollerMode = true, + noPadding = false, + items = [], + cardOptions = {}, + listOptions = {}, + children +}) => { + const sectionClass = classNames('verticalSection', className); + + const renderItems = () => { + if (React.isValidElement(children)) { + return children; + } + + if (isListMode && !isScrollerMode) { + return ; + } else { + return ; + } + }; + + const content = ( + + {renderItems()} + + ); + + return ( + + {sectionHeaderProps?.title && ( + + )} + {isScrollerMode && !isListMode ? ( + + {content} + + ) : ( + content + )} + + ); +}; + +export default SectionContainer; diff --git a/src/elements/emby-itemscontainer/ItemsContainer.tsx b/src/elements/emby-itemscontainer/ItemsContainer.tsx index b247d63a6c6..04a90a06a57 100644 --- a/src/elements/emby-itemscontainer/ItemsContainer.tsx +++ b/src/elements/emby-itemscontainer/ItemsContainer.tsx @@ -36,7 +36,7 @@ function getShortcutOptions() { }; } -interface ItemsContainerProps { +export interface ItemsContainerProps { className?: string; isContextMenuEnabled?: boolean; isMultiSelectEnabled?: boolean; @@ -136,14 +136,13 @@ const ItemsContainer: FC> = ({ } if (!itemId) throw new Error('null itemId'); - if (!newIndex) throw new Error('null newIndex'); try { loading.show(); await playlistsMoveItemMutation({ playlistId, itemId, - newIndex + newIndex: newIndex || 0 }); loading.hide(); } catch (error) { diff --git a/src/elements/emby-scroller/Scroller.tsx b/src/elements/emby-scroller/Scroller.tsx index 666935d66e2..d50d4d61b67 100644 --- a/src/elements/emby-scroller/Scroller.tsx +++ b/src/elements/emby-scroller/Scroller.tsx @@ -9,7 +9,7 @@ import ScrollerFactory from 'lib/scroller'; import ScrollButtons from '../emby-scrollbuttons/ScrollButtons'; import './emby-scroller.scss'; -interface ScrollerProps { +export interface ScrollerProps { className?: string; isHorizontalEnabled?: boolean; isMouseWheelEnabled?: boolean; @@ -23,14 +23,14 @@ interface ScrollerProps { const Scroller: FC> = ({ className, - isHorizontalEnabled, - isMouseWheelEnabled, - isCenterFocusEnabled, - isScrollButtonsEnabled, - isSkipFocusWhenVisibleEnabled, - isScrollEventEnabled, - isHideScrollbarEnabled, - isAllowNativeSmoothScrollEnabled, + isHorizontalEnabled = true, + isMouseWheelEnabled = false, + isCenterFocusEnabled = false, + isScrollButtonsEnabled = true, + isSkipFocusWhenVisibleEnabled = false, + isScrollEventEnabled = false, + isHideScrollbarEnabled = false, + isAllowNativeSmoothScrollEnabled = false, children }) => { const [scrollRef, size] = useElementSize(); @@ -158,27 +158,23 @@ const Scroller: FC> = ({ return; } - const horizontal = isHorizontalEnabled !== false; - const scrollbuttons = isScrollButtonsEnabled !== false; - const mousewheel = isMouseWheelEnabled !== false; - - const enableScrollButtons = layoutManager.desktop && horizontal && scrollbuttons; + const enableScrollButtons = layoutManager.desktop && isHorizontalEnabled && isScrollButtonsEnabled; const options = { - horizontal: horizontal, + horizontal: isHorizontalEnabled, mouseDragging: 1, - mouseWheel: mousewheel, + mouseWheel: isMouseWheelEnabled, touchDragging: 1, slidee: scrollRef.current?.querySelector('.scrollSlider'), scrollBy: 200, - speed: horizontal ? 270 : 240, + speed: isHorizontalEnabled ? 270 : 240, elasticBounds: 1, dragHandle: 1, autoImmediate: true, - skipSlideToWhenVisible: isSkipFocusWhenVisibleEnabled === true, - dispatchScrollEvent: enableScrollButtons || isScrollEventEnabled === true, - hideScrollbar: enableScrollButtons || isHideScrollbarEnabled === true, - allowNativeSmoothScroll: isAllowNativeSmoothScrollEnabled === true && !enableScrollButtons, + skipSlideToWhenVisible: isSkipFocusWhenVisibleEnabled, + dispatchScrollEvent: enableScrollButtons || isScrollEventEnabled, + hideScrollbar: enableScrollButtons || isHideScrollbarEnabled, + allowNativeSmoothScroll: isAllowNativeSmoothScrollEnabled && !enableScrollButtons, allowNativeScroll: !enableScrollButtons, forceHideScrollbars: enableScrollButtons, // In edge, with the native scroll, the content jumps around when hovering over the buttons