diff --git a/src/RootAppRouter.tsx b/src/RootAppRouter.tsx
index bed7a9e4a0..37d3cca651 100644
--- a/src/RootAppRouter.tsx
+++ b/src/RootAppRouter.tsx
@@ -17,7 +17,7 @@ import { createRouterHistory } from 'components/router/routerHistory';
import UserThemeProvider from 'themes/UserThemeProvider';
const layoutMode = localStorage.getItem('layout');
-const isExperimentalLayout = layoutMode === 'experimental';
+const isExperimentalLayout = layoutMode === 'experimental' || !layoutMode;
const router = createHashRouter([
{
diff --git a/src/apps/dashboard/AppLayout.tsx b/src/apps/dashboard/AppLayout.tsx
index 44b8ad2caf..26db00d0e8 100644
--- a/src/apps/dashboard/AppLayout.tsx
+++ b/src/apps/dashboard/AppLayout.tsx
@@ -9,6 +9,7 @@ import { Outlet, useLocation } from 'react-router-dom';
import AppBody from 'components/AppBody';
import AppToolbar from 'components/toolbar/AppToolbar';
+import ServerButton from 'components/toolbar/ServerButton';
import ElevationScroll from 'components/ElevationScroll';
import { DRAWER_WIDTH } from 'components/ResponsiveDrawer';
import ThemeCss from 'components/ThemeCss';
@@ -22,8 +23,6 @@ import { DASHBOARD_APP_PATHS } from './routes/routes';
import './AppOverrides.scss';
-const DRAWERLESS_PATHS = [ DASHBOARD_APP_PATHS.MetadataManager ];
-
export const Component: FC = () => {
const [ isDrawerActive, setIsDrawerActive ] = useState(false);
const location = useLocation();
@@ -31,8 +30,8 @@ export const Component: FC = () => {
const { dateFnsLocale } = useLocale();
const isMediumScreen = useMediaQuery((t: Theme) => t.breakpoints.up('md'));
- const isDrawerAvailable = Boolean(user)
- && !DRAWERLESS_PATHS.some(path => location.pathname.startsWith(`/${path}`));
+ const isMetadataManager = location.pathname.startsWith(`/${DASHBOARD_APP_PATHS.MetadataManager}`);
+ const isDrawerAvailable = Boolean(user) && !isMetadataManager;
const isDrawerOpen = isDrawerActive && isDrawerAvailable;
const onToggleDrawer = useCallback(() => {
@@ -74,6 +73,10 @@ export const Component: FC = () => {
}
>
+ {isMetadataManager && (
+
+ )}
+
diff --git a/src/apps/experimental/AppLayout.tsx b/src/apps/experimental/AppLayout.tsx
index 6412115302..8d5e04ed8f 100644
--- a/src/apps/experimental/AppLayout.tsx
+++ b/src/apps/experimental/AppLayout.tsx
@@ -8,7 +8,6 @@ import { Outlet, useLocation } from 'react-router-dom';
import AppBody from 'components/AppBody';
import CustomCss from 'components/CustomCss';
import ElevationScroll from 'components/ElevationScroll';
-import { DRAWER_WIDTH } from 'components/ResponsiveDrawer';
import ThemeCss from 'components/ThemeCss';
import { useApi } from 'hooks/useApi';
@@ -23,7 +22,7 @@ export const Component = () => {
const location = useLocation();
const isMediumScreen = useMediaQuery((t: Theme) => t.breakpoints.up('md'));
- const isDrawerAvailable = isDrawerPath(location.pathname) && Boolean(user);
+ const isDrawerAvailable = isDrawerPath(location.pathname) && Boolean(user) && !isMediumScreen;
const isDrawerOpen = isDrawerActive && isDrawerAvailable;
const onToggleDrawer = useCallback(() => {
@@ -38,14 +37,8 @@ export const Component = () => {
{
return params;
};
-interface SearchButtonProps {
- isTabsAvailable: boolean;
-}
-
-const SearchButton: FC = ({ isTabsAvailable }) => {
+const SearchButton: FC = () => {
const location = useLocation();
const [searchParams] = useSearchParams();
const isSearchPath = location.pathname === '/search';
- const createSearchLink = isTabsAvailable ?
+ const search = createSearchParams(getUrlParams(searchParams));
+ const createSearchLink =
{
pathname: '/search',
- search: `?${createSearchParams(getUrlParams(searchParams))}`
- } :
- '/search';
+ search: search ? `?${search}` : undefined
+ };
return (
diff --git a/src/apps/experimental/components/AppToolbar/index.tsx b/src/apps/experimental/components/AppToolbar/index.tsx
index df46cc1233..308b96826a 100644
--- a/src/apps/experimental/components/AppToolbar/index.tsx
+++ b/src/apps/experimental/components/AppToolbar/index.tsx
@@ -1,11 +1,14 @@
+import Stack from '@mui/material/Stack';
import React, { type FC } from 'react';
import { useLocation } from 'react-router-dom';
+
import AppToolbar from 'components/toolbar/AppToolbar';
-import AppTabs from '../tabs/AppTabs';
+import ServerButton from 'components/toolbar/ServerButton';
+
import RemotePlayButton from './RemotePlayButton';
import SyncPlayButton from './SyncPlayButton';
import SearchButton from './SearchButton';
-import { isTabPath } from '../tabs/tabRoutes';
+import UserViewNav from './userViews/UserViewNav';
interface AppToolbarProps {
isDrawerAvailable: boolean
@@ -31,7 +34,6 @@ const ExperimentalAppToolbar: FC = ({
// The video osd does not show the standard toolbar
if (location.pathname === '/video') return null;
- const isTabsAvailable = isTabPath(location.pathname);
const isPublicPath = PUBLIC_PATHS.includes(location.pathname);
return (
@@ -40,7 +42,7 @@ const ExperimentalAppToolbar: FC = ({
<>
-
+
>
)}
isDrawerAvailable={isDrawerAvailable}
@@ -48,7 +50,18 @@ const ExperimentalAppToolbar: FC = ({
onDrawerButtonClick={onDrawerButtonClick}
isUserMenuAvailable={!isPublicPath}
>
- {isTabsAvailable && ()}
+ {!isDrawerAvailable && (
+
+
+
+ {!isPublicPath && (
+
+ )}
+
+ )}
);
};
diff --git a/src/apps/experimental/components/AppToolbar/userViews/UserViewNav.tsx b/src/apps/experimental/components/AppToolbar/userViews/UserViewNav.tsx
new file mode 100644
index 0000000000..351f95fcc9
--- /dev/null
+++ b/src/apps/experimental/components/AppToolbar/userViews/UserViewNav.tsx
@@ -0,0 +1,150 @@
+import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto';
+import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type';
+import ArrowDropDown from '@mui/icons-material/ArrowDropDown';
+import Favorite from '@mui/icons-material/Favorite';
+import Button from '@mui/material/Button/Button';
+import { Theme } from '@mui/material/styles';
+import useMediaQuery from '@mui/material/useMediaQuery';
+import React, { useCallback, useMemo, useState } from 'react';
+import { Link, useLocation, useSearchParams } from 'react-router-dom';
+
+import LibraryIcon from 'apps/experimental/components/LibraryIcon';
+import { isTabPath } from 'apps/experimental/components/tabs/tabRoutes';
+import { PseudoUserViews } from 'apps/experimental/constants/PseudoUserViews';
+import { appRouter } from 'components/router/appRouter';
+import { useApi } from 'hooks/useApi';
+import useCurrentTab from 'hooks/useCurrentTab';
+import { useUserViews } from 'hooks/useUserViews';
+import globalize from 'lib/globalize';
+
+import UserViewsMenu from './UserViewsMenu';
+
+const MAX_USER_VIEWS_MD = 3;
+const MAX_USER_VIEWS_LG = 5;
+const MAX_USER_VIEWS_XL = 8;
+
+const OVERFLOW_MENU_ID = 'user-view-overflow-menu';
+
+const HOME_PATH = '/home';
+const LIST_PATH = '/list';
+
+const getCurrentUserView = (
+ userViews: BaseItemDto[] | undefined,
+ pathname: string,
+ libraryId: string | null,
+ collectionType: string | null,
+ tab: number
+) => {
+ const isUserViewPath = isTabPath(pathname) || [HOME_PATH, LIST_PATH].includes(pathname);
+ if (!isUserViewPath) return undefined;
+
+ if (collectionType === CollectionType.Livetv) {
+ return userViews?.find(({ CollectionType: type }) => type === CollectionType.Livetv);
+ }
+
+ if (pathname === HOME_PATH && tab === 1) {
+ return PseudoUserViews.Favorites;
+ }
+
+ // eslint-disable-next-line sonarjs/different-types-comparison
+ return userViews?.find(({ Id: id }) => id === libraryId);
+};
+
+const UserViewNav = () => {
+ const location = useLocation();
+ const [ searchParams ] = useSearchParams();
+ const libraryId = searchParams.get('topParentId') || searchParams.get('parentId');
+ const collectionType = searchParams.get('collectionType');
+ const { activeTab } = useCurrentTab();
+
+ const isExtraLargeScreen = useMediaQuery((t: Theme) => t.breakpoints.up('xl'));
+ const isLargeScreen = useMediaQuery((t: Theme) => t.breakpoints.up('lg'));
+ const maxViews = useMemo(() => {
+ if (isExtraLargeScreen) return MAX_USER_VIEWS_XL;
+ if (isLargeScreen) return MAX_USER_VIEWS_LG;
+ return MAX_USER_VIEWS_MD;
+ }, [ isExtraLargeScreen, isLargeScreen ]);
+
+ const { user } = useApi();
+ const {
+ data: userViews,
+ isPending
+ } = useUserViews(user?.Id);
+
+ const primaryViews = useMemo(() => (
+ userViews?.Items?.slice(0, maxViews)
+ ), [ maxViews, userViews ]);
+
+ const overflowViews = useMemo(() => (
+ userViews?.Items?.slice(maxViews)
+ ), [ maxViews, userViews ]);
+
+ const [ overflowAnchorEl, setOverflowAnchorEl ] = useState(null);
+ const isOverflowMenuOpen = Boolean(overflowAnchorEl);
+
+ const onOverflowButtonClick = useCallback((event: React.MouseEvent) => {
+ setOverflowAnchorEl(event.currentTarget);
+ }, []);
+
+ const onOverflowMenuClose = useCallback(() => {
+ setOverflowAnchorEl(null);
+ }, []);
+
+ const currentUserView = useMemo(() => (
+ getCurrentUserView(userViews?.Items, location.pathname, libraryId, collectionType, activeTab)
+ ), [ activeTab, collectionType, libraryId, location.pathname, userViews ]);
+
+ if (isPending) return null;
+
+ return (
+ <>
+ }
+ component={Link}
+ to='/home.html?tab=1'
+ >
+ {globalize.translate(PseudoUserViews.Favorites.Name)}
+
+
+ {primaryViews?.map(view => (
+ }
+ component={Link}
+ to={appRouter.getRouteUrl(view, { context: view.CollectionType }).substring(1)}
+ >
+ {view.Name}
+
+ ))}
+ {overflowViews && overflowViews.length > 0 && (
+ <>
+ }
+ aria-controls={OVERFLOW_MENU_ID}
+ aria-haspopup='true'
+ onClick={onOverflowButtonClick}
+ >
+ {globalize.translate('ButtonMore')}
+
+
+
+ >
+ )}
+ >
+ );
+};
+
+export default UserViewNav;
diff --git a/src/apps/experimental/components/AppToolbar/userViews/UserViewsMenu.tsx b/src/apps/experimental/components/AppToolbar/userViews/UserViewsMenu.tsx
new file mode 100644
index 0000000000..e95617e741
--- /dev/null
+++ b/src/apps/experimental/components/AppToolbar/userViews/UserViewsMenu.tsx
@@ -0,0 +1,51 @@
+import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client';
+import ListItemIcon from '@mui/material/ListItemIcon/ListItemIcon';
+import ListItemText from '@mui/material/ListItemText/ListItemText';
+import Menu, { type MenuProps } from '@mui/material/Menu/Menu';
+import MenuItem from '@mui/material/MenuItem/MenuItem';
+import React, { FC } from 'react';
+import { Link } from 'react-router-dom';
+
+import LibraryIcon from 'apps/experimental/components/LibraryIcon';
+import { appRouter } from 'components/router/appRouter';
+
+interface UserViewsMenuProps extends MenuProps {
+ userViews: BaseItemDto[]
+ selectedId?: string
+ includeGlobalViews?: boolean
+ onMenuClose: () => void
+}
+
+const UserViewsMenu: FC = ({
+ userViews,
+ selectedId,
+ onMenuClose,
+ ...props
+}) => {
+ return (
+
+ );
+};
+
+export default UserViewsMenu;
diff --git a/src/apps/experimental/components/LibraryIcon.tsx b/src/apps/experimental/components/LibraryIcon.tsx
index 846e3f887c..6a5b2db61a 100644
--- a/src/apps/experimental/components/LibraryIcon.tsx
+++ b/src/apps/experimental/components/LibraryIcon.tsx
@@ -1,5 +1,6 @@
import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client';
import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type';
+import Favorite from '@mui/icons-material/Favorite';
import Movie from '@mui/icons-material/Movie';
import MusicNote from '@mui/icons-material/MusicNote';
import Photo from '@mui/icons-material/Photo';
@@ -14,6 +15,8 @@ import VideoLibrary from '@mui/icons-material/VideoLibrary';
import Folder from '@mui/icons-material/Folder';
import React, { FC } from 'react';
+import { PseudoUserViews } from '../constants/PseudoUserViews';
+
interface LibraryIconProps {
item: BaseItemDto
}
@@ -21,6 +24,10 @@ interface LibraryIconProps {
const LibraryIcon: FC = ({
item
}) => {
+ if (item.Id === PseudoUserViews.Favorites.Id) {
+ return ;
+ }
+
switch (item.CollectionType) {
case CollectionType.Movies:
return ;
diff --git a/src/apps/experimental/components/drawers/MainDrawerContent.tsx b/src/apps/experimental/components/drawers/MainDrawerContent.tsx
index e15b833616..b135694411 100644
--- a/src/apps/experimental/components/drawers/MainDrawerContent.tsx
+++ b/src/apps/experimental/components/drawers/MainDrawerContent.tsx
@@ -1,5 +1,3 @@
-import Dashboard from '@mui/icons-material/Dashboard';
-import Edit from '@mui/icons-material/Edit';
import Favorite from '@mui/icons-material/Favorite';
import Home from '@mui/icons-material/Home';
import Divider from '@mui/material/Divider';
@@ -111,38 +109,6 @@ const MainDrawerContent = () => {
>
)}
-
- {/* ADMIN LINKS */}
- {user?.Policy?.IsAdministrator && (
- <>
-
-
- >
- )}
>
);
};
diff --git a/src/apps/experimental/components/library/ItemsView.tsx b/src/apps/experimental/components/library/ItemsView.tsx
index dbe1753fd3..db6303324d 100644
--- a/src/apps/experimental/components/library/ItemsView.tsx
+++ b/src/apps/experimental/components/library/ItemsView.tsx
@@ -14,6 +14,15 @@ import { CardShape } from 'utils/card';
import Loading from 'components/loading/LoadingComponent';
import { playbackManager } from 'components/playback/playbackmanager';
import ItemsContainer from 'elements/emby-itemscontainer/ItemsContainer';
+import NoItemsMessage from 'components/common/NoItemsMessage';
+import Lists from 'components/listview/List/Lists';
+import Cards from 'components/cardbuilder/Card/Cards';
+import { LibraryTab } from 'types/libraryTab';
+import { type LibraryViewSettings, type ParentId, ViewMode } from 'types/library';
+import type { CardOptions } from 'types/cardOptions';
+import type { ListOptions } from 'types/listOptions';
+import { useItem } from 'hooks/useItem';
+
import AlphabetPicker from './AlphabetPicker';
import FilterButton from './filter/FilterButton';
import NewCollectionButton from './NewCollectionButton';
@@ -23,14 +32,7 @@ import QueueButton from './QueueButton';
import ShuffleButton from './ShuffleButton';
import SortButton from './SortButton';
import GridListViewButton from './GridListViewButton';
-import NoItemsMessage from 'components/common/NoItemsMessage';
-import Lists from 'components/listview/List/Lists';
-import Cards from 'components/cardbuilder/Card/Cards';
-import { LibraryTab } from 'types/libraryTab';
-import { type LibraryViewSettings, type ParentId, ViewMode } from 'types/library';
-import type { CardOptions } from 'types/cardOptions';
-import type { ListOptions } from 'types/listOptions';
-import { useItem } from 'hooks/useItem';
+import LibraryViewMenu from './LibraryViewMenu';
interface ItemsViewProps {
viewType: LibraryTab;
@@ -225,17 +227,21 @@ const ItemsView: FC = ({
'vertical-list' :
'vertical-wrap'
);
+
return (
-
- {isPaginationEnabled && (
-
+
+
+
{isBtnPlayAllEnabled && (
= ({
libraryViewSettings={libraryViewSettings}
/>
)}
+ {isBtnShuffleEnabled && totalRecordCount > 1 && (
+
+ )}
{isBtnQueueEnabled
&& item
&& playbackManager.canQueue(item) && (
@@ -255,15 +270,6 @@ const ItemsView: FC = ({
hasFilters={hasFilters}
/>
)}
- {isBtnShuffleEnabled && totalRecordCount > 1 && (
-
- )}
{isBtnSortEnabled && (
= ({
setLibraryViewSettings={setLibraryViewSettings}
/>
)}
+
+ {isPaginationEnabled && (
+
+
+
+ )}
{isAlphabetPickerEnabled && hasSortName && (
@@ -312,7 +336,16 @@ const ItemsView: FC = ({
)}
{isPaginationEnabled && (
-
+
{
+ const location = useLocation();
+ const [ searchParams, setSearchParams ] = useSearchParams();
+ const { activeTab } = useCurrentTab();
+
+ const [ menuAnchorEl, setMenuAnchorEl ] = useState(null);
+ const isMenuOpen = Boolean(menuAnchorEl);
+
+ const onMenuButtonClick = useCallback((event: React.MouseEvent) => {
+ setMenuAnchorEl(event.currentTarget);
+ }, []);
+
+ const onMenuClose = useCallback(() => {
+ setMenuAnchorEl(null);
+ }, []);
+
+ const currentRoute = TabRoutes.find(({ path }) => path === location.pathname);
+ const currentTab = currentRoute?.tabs.find(({ index }) => index === activeTab);
+
+ if (!currentTab) return null;
+
+ return (
+ <>
+ }
+ aria-controls={LIBRARY_VIEW_MENU_ID}
+ aria-haspopup='true'
+ onClick={onMenuButtonClick}
+ >
+ {globalize.translate(currentTab.label)}
+
+
+
+ >
+ );
+};
+
+export default LibraryViewMenu;
diff --git a/src/apps/experimental/components/library/PageTabContent.tsx b/src/apps/experimental/components/library/PageTabContent.tsx
index 6cd99c5575..10a0d22960 100644
--- a/src/apps/experimental/components/library/PageTabContent.tsx
+++ b/src/apps/experimental/components/library/PageTabContent.tsx
@@ -1,3 +1,4 @@
+import Box from '@mui/material/Box/Box';
import React, { type FC } from 'react';
import SuggestionsSectionView from './SuggestionsSectionView';
import UpcomingView from './UpcomingView';
@@ -8,6 +9,7 @@ import ProgramsSectionView from './ProgramsSectionView';
import { LibraryTab } from 'types/libraryTab';
import type { ParentId } from 'types/library';
import type { LibraryTabContent } from 'types/libraryTabContent';
+import LibraryViewMenu from './LibraryViewMenu';
interface PageTabContentProps {
parentId: ParentId;
@@ -17,46 +19,86 @@ interface PageTabContentProps {
const PageTabContent: FC = ({ parentId, currentTab }) => {
if (currentTab.viewType === LibraryTab.Suggestions) {
return (
-
+ <>
+
+
+
+
+
+ >
);
}
if (currentTab.viewType === LibraryTab.Programs || currentTab.viewType === LibraryTab.Recordings || currentTab.viewType === LibraryTab.Schedule) {
return (
-
+ <>
+
+
+
+
+
+ >
);
}
if (currentTab.viewType === LibraryTab.Upcoming) {
- return ;
+ return (
+ <>
+
+
+
+
+
+ >
+ );
}
if (currentTab.viewType === LibraryTab.Genres) {
return (
-
+ <>
+
+
+
+
+
+ >
);
}
if (currentTab.viewType === LibraryTab.Guide) {
- return ;
+ return (
+ <>
+
+
+
+
+
+ >
+ );
}
return (
diff --git a/src/apps/experimental/components/library/Pagination.tsx b/src/apps/experimental/components/library/Pagination.tsx
index ea3cd1cfe7..477a982312 100644
--- a/src/apps/experimental/components/library/Pagination.tsx
+++ b/src/apps/experimental/components/library/Pagination.tsx
@@ -47,10 +47,15 @@ const Pagination: FC = ({
}, [limit, setLibraryViewSettings, startIndex]);
return (
-
+
{globalize.translate(
diff --git a/src/apps/experimental/components/library/PlayAllButton.tsx b/src/apps/experimental/components/library/PlayAllButton.tsx
index 9c87d0056b..b2c3da4181 100644
--- a/src/apps/experimental/components/library/PlayAllButton.tsx
+++ b/src/apps/experimental/components/library/PlayAllButton.tsx
@@ -51,6 +51,12 @@ const PlayAllButton: FC = ({ item, items, viewType, hasFilte
title={globalize.translate('HeaderPlayAll')}
className='paper-icon-button-light btnPlay autoSize'
onClick={play}
+ sx={{
+ order: {
+ xs: 1,
+ sm: 'unset'
+ }
+ }}
>
diff --git a/src/apps/experimental/components/library/QueueButton.tsx b/src/apps/experimental/components/library/QueueButton.tsx
index 31abe4d7b2..305594dd14 100644
--- a/src/apps/experimental/components/library/QueueButton.tsx
+++ b/src/apps/experimental/components/library/QueueButton.tsx
@@ -34,6 +34,12 @@ const QueueButton: FC = ({ item, items, hasFilters }) => {
title={globalize.translate('AddToPlayQueue')}
className='paper-icon-button-light btnQueue autoSize'
onClick={queue}
+ sx={{
+ order: {
+ xs: 3,
+ sm: 'unset'
+ }
+ }}
>
diff --git a/src/apps/experimental/components/library/ShuffleButton.tsx b/src/apps/experimental/components/library/ShuffleButton.tsx
index d20c401e5c..9c11ef00eb 100644
--- a/src/apps/experimental/components/library/ShuffleButton.tsx
+++ b/src/apps/experimental/components/library/ShuffleButton.tsx
@@ -42,6 +42,12 @@ const ShuffleButton: FC = ({ item, items, viewType, hasFilte
title={globalize.translate('Shuffle')}
className='paper-icon-button-light btnShuffle autoSize'
onClick={shuffle}
+ sx={{
+ order: {
+ xs: 2,
+ sm: 'unset'
+ }
+ }}
>
diff --git a/src/apps/experimental/components/tabs/AppTabs.tsx b/src/apps/experimental/components/tabs/AppTabs.tsx
deleted file mode 100644
index d5f772d31d..0000000000
--- a/src/apps/experimental/components/tabs/AppTabs.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import { Theme } from '@mui/material/styles';
-import Tab from '@mui/material/Tab';
-import Tabs from '@mui/material/Tabs';
-import useMediaQuery from '@mui/material/useMediaQuery';
-import { debounce } from 'lodash-es';
-import React, { FC, useCallback, useEffect } from 'react';
-import { Route, Routes } from 'react-router-dom';
-
-import TabRoutes from './tabRoutes';
-import useCurrentTab from 'hooks/useCurrentTab';
-import globalize from 'lib/globalize';
-
-interface AppTabsParams {
- isDrawerOpen: boolean
-}
-
-const handleResize = debounce(() => window.dispatchEvent(new Event('resize')), 100);
-
-const AppTabs: FC = ({
- isDrawerOpen
-}) => {
- const isBigScreen = useMediaQuery((theme: Theme) => theme.breakpoints.up('sm'));
- const { searchParams, setSearchParams, activeTab } = useCurrentTab();
-
- // HACK: Force resizing to workaround upstream bug with tab resizing
- // https://github.com/mui/material-ui/issues/24011
- useEffect(() => {
- handleResize();
- }, [ isDrawerOpen ]);
-
- const onTabClick = useCallback((event: React.MouseEvent) => {
- event.preventDefault();
-
- const tabIndex = event.currentTarget.dataset.tabIndex;
-
- if (tabIndex) {
- searchParams.set('tab', tabIndex);
- setSearchParams(searchParams);
- }
- }, [ searchParams, setSearchParams ]);
-
- return (
-
- {
- TabRoutes.map(route => (
-
- {
- route.tabs.map(({ index, label }) => (
-
- ))
- }
-
- }
- />
- ))
- }
-
- );
-};
-
-export default AppTabs;
diff --git a/src/apps/experimental/constants/PseudoUserViews.ts b/src/apps/experimental/constants/PseudoUserViews.ts
new file mode 100644
index 0000000000..ee1f4bebe8
--- /dev/null
+++ b/src/apps/experimental/constants/PseudoUserViews.ts
@@ -0,0 +1,11 @@
+import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto';
+
+/**
+ * Views in the web app that we treat as UserViews.
+ */
+export const PseudoUserViews: Record = {
+ Favorites: {
+ Id: 'favorites',
+ Name: 'Favorites'
+ }
+};
diff --git a/src/apps/experimental/routes/video/index.tsx b/src/apps/experimental/routes/video/index.tsx
index b3ded898a8..f9322352d5 100644
--- a/src/apps/experimental/routes/video/index.tsx
+++ b/src/apps/experimental/routes/video/index.tsx
@@ -46,7 +46,6 @@ const VideoPage: FC = () => {
diff --git a/src/components/toolbar/AppToolbar.tsx b/src/components/toolbar/AppToolbar.tsx
index 4c34aaf3c1..8e46f07693 100644
--- a/src/components/toolbar/AppToolbar.tsx
+++ b/src/components/toolbar/AppToolbar.tsx
@@ -16,8 +16,8 @@ interface AppToolbarProps {
buttons?: ReactNode
isDrawerAvailable: boolean
isDrawerOpen: boolean
- onDrawerButtonClick?: (event: React.MouseEvent) => void,
- isFullscreen?: boolean,
+ onDrawerButtonClick?: (event: React.MouseEvent) => void
+ isBackButtonAvailable?: boolean
isUserMenuAvailable?: boolean
}
@@ -34,31 +34,21 @@ const AppToolbar: FC> = ({
isDrawerAvailable,
isDrawerOpen,
onDrawerButtonClick = () => { /* no-op */ },
- isFullscreen = false,
+ isBackButtonAvailable = false,
isUserMenuAvailable = true
}) => {
const { user } = useApi();
const isUserLoggedIn = Boolean(user);
- const isBackButtonAvailable = appRouter.canGoBack();
-
- // Only use the left safe area padding when the drawer is not pinned or in a fullscreen view
- const useSafeAreaLeft = isDrawerAvailable || isFullscreen;
-
return (
{
+ const {
+ data: systemInfo,
+ isPending
+ } = useSystemInfo();
+
+ return (
+
+ }
+ component={Link}
+ to='/'
+ >
+ {isPending ? '' : (systemInfo?.ServerName || 'Jellyfin')}
+
+ );
+};
+
+export default ServerButton;