From 4008ec04b9ccb85cdfc5e975955b5d7757f4eb83 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Tue, 4 Nov 2025 10:45:20 -0500 Subject: [PATCH 1/2] Use experimental layout by default --- src/RootAppRouter.tsx | 7 ++- .../components/DisplayPreferences.tsx | 11 ++-- src/components/apphost.js | 5 +- .../displaySettings.template.html | 2 +- src/components/layoutManager.js | 53 +++++++++---------- src/components/router/appRouter.js | 3 +- src/constants/layoutMode.ts | 13 +++++ 7 files changed, 55 insertions(+), 39 deletions(-) create mode 100644 src/constants/layoutMode.ts diff --git a/src/RootAppRouter.tsx b/src/RootAppRouter.tsx index 9c2205d339..bb64480816 100644 --- a/src/RootAppRouter.tsx +++ b/src/RootAppRouter.tsx @@ -13,13 +13,16 @@ import { STABLE_APP_ROUTES } from 'apps/stable/routes/routes'; import { WIZARD_APP_ROUTES } from 'apps/wizard/routes/routes'; import AppHeader from 'components/AppHeader'; import Backdrop from 'components/Backdrop'; +import { SETTING_KEY as LAYOUT_SETTING_KEY } from 'components/layoutManager'; import BangRedirect from 'components/router/BangRedirect'; import { createRouterHistory } from 'components/router/routerHistory'; +import { LayoutMode } from 'constants/layoutMode'; +import browser from 'scripts/browser'; import appTheme from 'themes/themes'; import { ThemeStorageManager } from 'themes/themeStorageManager'; -const layoutMode = localStorage.getItem('layout'); -const isExperimentalLayout = layoutMode === 'experimental'; +const layoutMode = browser.tv ? LayoutMode.Tv : localStorage.getItem(LAYOUT_SETTING_KEY); +const isExperimentalLayout = !layoutMode || layoutMode === LayoutMode.Experimental; const router = createHashRouter([ { diff --git a/src/apps/experimental/features/preferences/components/DisplayPreferences.tsx b/src/apps/experimental/features/preferences/components/DisplayPreferences.tsx index b26f21d111..f2646e923a 100644 --- a/src/apps/experimental/features/preferences/components/DisplayPreferences.tsx +++ b/src/apps/experimental/features/preferences/components/DisplayPreferences.tsx @@ -12,6 +12,7 @@ import React, { Fragment } from 'react'; import { appHost } from 'components/apphost'; import { AppFeature } from 'constants/appFeature'; +import { LayoutMode } from 'constants/layoutMode'; import { useApi } from 'hooks/useApi'; import { useThemes } from 'hooks/useThemes'; import globalize from 'lib/globalize'; @@ -45,11 +46,11 @@ export function DisplayPreferences({ onChange, values }: Readonly - {globalize.translate('Auto')} - {globalize.translate('Desktop')} - {globalize.translate('Mobile')} - {globalize.translate('TV')} - {globalize.translate('Experimental')} + {globalize.translate('Auto')} + {globalize.translate('Experimental')} + {globalize.translate('Desktop')} + {globalize.translate('Mobile')} + {globalize.translate('TV')} {globalize.translate('DisplayModeHelp')} diff --git a/src/components/apphost.js b/src/components/apphost.js index ce88435a50..8b9436021e 100644 --- a/src/components/apphost.js +++ b/src/components/apphost.js @@ -6,6 +6,7 @@ import * as webSettings from '../scripts/settings/webSettings'; import globalize from '../lib/globalize'; import profileBuilder from '../scripts/browserDeviceProfile'; import { AppFeature } from 'constants/appFeature'; +import { LayoutMode } from 'constants/layoutMode'; const appName = 'Jellyfin Web'; @@ -181,7 +182,7 @@ function supportsFullscreen() { } function getDefaultLayout() { - return 'desktop'; + return LayoutMode.Experimental; } function supportsHtmlMediaAutoplay() { @@ -371,7 +372,7 @@ export const appHost = { return getDefaultLayout(); }, - getDeviceProfile: getDeviceProfile, + getDeviceProfile, init: function () { if (window.NativeShell) { return window.NativeShell.AppHost.init(); diff --git a/src/components/displaySettings/displaySettings.template.html b/src/components/displaySettings/displaySettings.template.html index 6b219906c6..238252f156 100644 --- a/src/components/displaySettings/displaySettings.template.html +++ b/src/components/displaySettings/displaySettings.template.html @@ -169,10 +169,10 @@
${DisplayModeHelp}
${LabelPleaseRestart}
diff --git a/src/components/layoutManager.js b/src/components/layoutManager.js index 563d92af3a..abc0aef4fe 100644 --- a/src/components/layoutManager.js +++ b/src/components/layoutManager.js @@ -1,3 +1,4 @@ +import { LayoutMode } from 'constants/layoutMode'; import { appHost } from './apphost'; import browser from '../scripts/browser'; @@ -14,51 +15,47 @@ function setLayout(instance, layout, selectedLayout) { } } +export const SETTING_KEY = 'layout'; + class LayoutManager { tv = false; mobile = false; desktop = false; experimental = false; - setLayout(layout, save) { - if (!layout || layout === 'auto') { + setLayout(layout = '', save = true) { + const layoutValue = (!layout || layout === LayoutMode.Auto) ? '' : layout; + + if (!layoutValue) { this.autoLayout(); - - if (save !== false) { - appSettings.set('layout', ''); - } } else { - setLayout(this, 'mobile', layout); - setLayout(this, 'tv', layout); - setLayout(this, 'desktop', layout); - - this.experimental = layout === 'experimental'; - if (this.experimental) { - const legacyLayoutMode = browser.mobile ? 'mobile' : this.defaultLayout || 'desktop'; - setLayout(this, legacyLayoutMode, legacyLayoutMode); - } - - if (save !== false) { - appSettings.set('layout', layout); - } + setLayout(this, LayoutMode.Mobile, layoutValue); + setLayout(this, LayoutMode.Tv, layoutValue); + setLayout(this, LayoutMode.Desktop, layoutValue); } + console.debug('[LayoutManager] using layout mode', layoutValue); + this.experimental = layoutValue === LayoutMode.Experimental; + if (this.experimental) { + const legacyLayoutMode = browser.mobile ? LayoutMode.Mobile : LayoutMode.Desktop; + console.debug('[LayoutManager] using legacy layout mode', legacyLayoutMode); + setLayout(this, legacyLayoutMode, legacyLayoutMode); + } + + if (save) appSettings.set(SETTING_KEY, layoutValue); + Events.trigger(this, 'modechange'); } getSavedLayout() { - return appSettings.get('layout'); + return appSettings.get(SETTING_KEY); } autoLayout() { - // Take a guess at initial layout. The consuming app can override - if (browser.mobile) { - this.setLayout('mobile', false); - } else if (browser.tv || browser.xboxOne || browser.ps4) { - this.setLayout('tv', false); - } else { - this.setLayout(this.defaultLayout || 'tv', false); - } + // Take a guess at initial layout. The consuming app can override. + // NOTE: The fallback to TV mode seems like an outdated choice. TVs should be detected properly or override the + // default layout. + this.setLayout(browser.tv ? LayoutMode.Tv : this.defaultLayout || LayoutMode.Tv, false); } init() { diff --git a/src/components/router/appRouter.js b/src/components/router/appRouter.js index 032ddfaf1e..8d90531fd8 100644 --- a/src/components/router/appRouter.js +++ b/src/components/router/appRouter.js @@ -6,6 +6,7 @@ import itemHelper from '../itemHelper'; import loading from '../loading/loading'; import alert from '../alert'; +import { LayoutMode } from 'constants/layoutMode'; import { getItemQuery } from 'hooks/useItem'; import { ServerConnections } from 'lib/jellyfin-apiclient'; import { toApi } from 'utils/jellyfin-apiclient/compat'; @@ -434,7 +435,7 @@ class AppRouter { const layoutMode = localStorage.getItem('layout'); - if (layoutMode === 'experimental' && item.CollectionType == CollectionType.Homevideos) { + if (layoutMode === LayoutMode.Experimental && item.CollectionType == CollectionType.Homevideos) { url = '#/homevideos?topParentId=' + item.Id; return url; diff --git a/src/constants/layoutMode.ts b/src/constants/layoutMode.ts new file mode 100644 index 0000000000..1d5a311dff --- /dev/null +++ b/src/constants/layoutMode.ts @@ -0,0 +1,13 @@ +/** The different layout modes supported by the web app. */ +export enum LayoutMode { + /** Automatic layout - the app chose the best layout for the detected device. */ + Auto = 'auto', + /** The legacy desktop layout. */ + Desktop = 'desktop', + /** The modern React based layout. */ + Experimental = 'experimental', + /** The legacy mobile layout. */ + Mobile = 'mobile', + /** The TV layout. */ + Tv = 'tv' +}; From 86db4bd0e149850c6920d666d68128439c799de0 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Fri, 5 Dec 2025 11:57:46 -0500 Subject: [PATCH 2/2] Update layout settings --- .../features/preferences/components/DisplayPreferences.tsx | 1 - src/components/displaySettings/displaySettings.template.html | 1 - src/strings/en-us.json | 5 ++--- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/apps/experimental/features/preferences/components/DisplayPreferences.tsx b/src/apps/experimental/features/preferences/components/DisplayPreferences.tsx index f2646e923a..0668ccdbbe 100644 --- a/src/apps/experimental/features/preferences/components/DisplayPreferences.tsx +++ b/src/apps/experimental/features/preferences/components/DisplayPreferences.tsx @@ -47,7 +47,6 @@ export function DisplayPreferences({ onChange, values }: Readonly {globalize.translate('Auto')} - {globalize.translate('Experimental')} {globalize.translate('Desktop')} {globalize.translate('Mobile')} {globalize.translate('TV')} diff --git a/src/components/displaySettings/displaySettings.template.html b/src/components/displaySettings/displaySettings.template.html index 238252f156..edaf266221 100644 --- a/src/components/displaySettings/displaySettings.template.html +++ b/src/components/displaySettings/displaySettings.template.html @@ -169,7 +169,6 @@