diff --git a/src/apps/experimental/features/preferences/components/DisplayPreferences.tsx b/src/apps/experimental/features/preferences/components/DisplayPreferences.tsx index 6977459c6b..e5e940b7aa 100644 --- a/src/apps/experimental/features/preferences/components/DisplayPreferences.tsx +++ b/src/apps/experimental/features/preferences/components/DisplayPreferences.tsx @@ -11,6 +11,7 @@ import Typography from '@mui/material/Typography'; import React, { Fragment } from 'react'; import { appHost } from 'components/apphost'; +import { AppFeature } from 'constants/appFeature'; import { useApi } from 'hooks/useApi'; import { useThemes } from 'hooks/useThemes'; import globalize from 'lib/globalize'; @@ -32,7 +33,7 @@ export function DisplayPreferences({ onChange, values }: Readonly {globalize.translate('Display')} - { appHost.supports('displaymode') && ( + { appHost.supports(AppFeature.DisplayMode) && ( {globalize.translate('LabelDisplayMode')} {globalize.translate('LabelDisplayLanguageHelp')} - { appHost.supports('externallinks') && ( + { appHost.supports(AppFeature.ExternalLinks) && ( { - {appHost.supports('clientsettings') && ( + {appHost.supports(AppFeature.ClientSettings) && ( { {globalize.translate('HeaderUser')} - {appHost.supports('multiserver') && ( + {appHost.supports(AppFeature.MultiServer) && ( { - {appHost.supports('exitmenu') && ( + {appHost.supports(AppFeature.ExitMenu) && ( { const [ searchParams ] = useSearchParams(); @@ -61,7 +62,7 @@ const UserProfile: FunctionComponent = () => { if (user.PrimaryImageTag) { (page.querySelector('#btnAddImage') as HTMLButtonElement).classList.add('hide'); (page.querySelector('#btnDeleteImage') as HTMLButtonElement).classList.remove('hide'); - } else if (appHost.supports('fileinput') && (loggedInUser?.Policy?.IsAdministrator || user.Policy.EnableUserPreferenceAccess)) { + } else if (appHost.supports(AppFeature.FileInput) && (loggedInUser?.Policy?.IsAdministrator || user.Policy.EnableUserPreferenceAccess)) { (page.querySelector('#btnDeleteImage') as HTMLButtonElement).classList.add('hide'); (page.querySelector('#btnAddImage') as HTMLButtonElement).classList.remove('hide'); } diff --git a/src/components/ConnectionErrorPage.tsx b/src/components/ConnectionErrorPage.tsx index 3dae182b70..3dd69494bd 100644 --- a/src/components/ConnectionErrorPage.tsx +++ b/src/components/ConnectionErrorPage.tsx @@ -2,6 +2,7 @@ import React, { FC, useEffect, useState } from 'react'; import { appHost } from 'components/apphost'; import Page from 'components/Page'; +import { AppFeature } from 'constants/appFeature'; import LinkButton from 'elements/emby-button/LinkButton'; import globalize from 'lib/globalize'; import { ConnectionState } from 'lib/jellyfin-apiclient'; @@ -50,7 +51,7 @@ const ConnectionErrorPage: FC = ({ {message && (

{message}

)} - {appHost.supports('multiserver') && ( + {appHost.supports(AppFeature.MultiServer) && ( { - if (enabled) features.push('multiserver'); + if (enabled) features.push(AppFeature.MultiServer); }); if (!browser.orsay && (browser.firefox || browser.ps4 || browser.edge || supportsCue())) { - features.push('subtitleappearancesettings'); + features.push(AppFeature.SubtitleAppearance); } if (!browser.orsay) { - features.push('subtitleburnsettings'); + features.push(AppFeature.SubtitleBurnIn); } if (!browser.tv && !browser.ps4 && !browser.xboxOne) { - features.push('fileinput'); + features.push(AppFeature.FileInput); } if (browser.chrome || browser.edgeChromium) { - features.push('chromecast'); + features.push(AppFeature.Chromecast); } return features; diff --git a/src/components/displaySettings/displaySettings.js b/src/components/displaySettings/displaySettings.js index 21eef9f286..7ca0e11b6d 100644 --- a/src/components/displaySettings/displaySettings.js +++ b/src/components/displaySettings/displaySettings.js @@ -1,4 +1,6 @@ import escapeHtml from 'escape-html'; + +import { AppFeature } from 'constants/appFeature'; import browser from '../../scripts/browser'; import layoutManager from '../layoutManager'; import { pluginManager } from '../pluginManager'; @@ -68,19 +70,19 @@ function showOrHideMissingEpisodesField(context) { } function loadForm(context, user, userSettings) { - if (appHost.supports('displaylanguage')) { + if (appHost.supports(AppFeature.DisplayLanguage)) { context.querySelector('.languageSection').classList.remove('hide'); } else { context.querySelector('.languageSection').classList.add('hide'); } - if (appHost.supports('displaymode')) { + if (appHost.supports(AppFeature.DisplayMode)) { context.querySelector('.fldDisplayMode').classList.remove('hide'); } else { context.querySelector('.fldDisplayMode').classList.add('hide'); } - if (appHost.supports('externallinks')) { + if (appHost.supports(AppFeature.ExternalLinks)) { context.querySelector('.learnHowToContributeContainer').classList.remove('hide'); } else { context.querySelector('.learnHowToContributeContainer').classList.add('hide'); @@ -88,7 +90,7 @@ function loadForm(context, user, userSettings) { context.querySelector('.selectDashboardThemeContainer').classList.toggle('hide', !user.Policy.IsAdministrator); - if (appHost.supports('screensaver')) { + if (appHost.supports(AppFeature.Screensaver)) { context.querySelector('.selectScreensaverContainer').classList.remove('hide'); context.querySelector('.txtBackdropScreensaverIntervalContainer').classList.remove('hide'); context.querySelector('.txtScreensaverTimeContainer').classList.remove('hide'); @@ -143,7 +145,7 @@ function loadForm(context, user, userSettings) { function saveUser(context, user, userSettingsInstance, apiClient) { user.Configuration.DisplayMissingEpisodes = context.querySelector('.chkDisplayMissingEpisodes').checked; - if (appHost.supports('displaylanguage')) { + if (appHost.supports(AppFeature.DisplayLanguage)) { userSettingsInstance.language(context.querySelector('#selectLanguage').value); } diff --git a/src/components/imageDownloader/imageDownloader.js b/src/components/imageDownloader/imageDownloader.js index b4ddaf85da..77d9f17068 100644 --- a/src/components/imageDownloader/imageDownloader.js +++ b/src/components/imageDownloader/imageDownloader.js @@ -1,3 +1,4 @@ +import { AppFeature } from 'constants/appFeature'; import dom from '../../scripts/dom'; import loading from '../loading/loading'; import { appHost } from '../apphost'; @@ -205,7 +206,7 @@ function getRemoteImageHtml(image, imageType) { html += '
'; html += '
'; - if (layoutManager.tv || !appHost.supports('externallinks')) { + if (layoutManager.tv || !appHost.supports(AppFeature.ExternalLinks)) { html += '
'; } else { html += ''; diff --git a/src/components/imageeditor/imageeditor.js b/src/components/imageeditor/imageeditor.js index ab5f7832d4..28716bfa34 100644 --- a/src/components/imageeditor/imageeditor.js +++ b/src/components/imageeditor/imageeditor.js @@ -1,3 +1,4 @@ +import { AppFeature } from 'constants/appFeature'; import dialogHelper from '../dialogHelper/dialogHelper'; import loading from '../loading/loading'; import dom from '../../scripts/dom'; @@ -339,7 +340,7 @@ function showActionSheet(context, imageCard) { function initEditor(context, options) { const uploadButtons = context.querySelectorAll('.btnOpenUploadMenu'); - const isFileInputSupported = appHost.supports('fileinput'); + const isFileInputSupported = appHost.supports(AppFeature.FileInput); for (let i = 0, length = uploadButtons.length; i < length; i++) { if (isFileInputSupported) { uploadButtons[i].classList.remove('hide'); diff --git a/src/components/itemContextMenu.js b/src/components/itemContextMenu.js index 959b391d88..172e77e5e7 100644 --- a/src/components/itemContextMenu.js +++ b/src/components/itemContextMenu.js @@ -11,6 +11,7 @@ import { playbackManager } from './playback/playbackmanager'; import toast from './toast/toast'; import * as userSettings from '../scripts/settings/userSettings'; import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; +import { AppFeature } from 'constants/appFeature'; function getDeleteLabel(type) { switch (type) { @@ -169,7 +170,7 @@ export async function getCommands(options) { }); } - if (appHost.supports('filedownload')) { + if (appHost.supports(AppFeature.FileDownload)) { // CanDownload should probably be updated to return true for these items? if (user.Policy.EnableContentDownloading && (item.Type === 'Season' || item.Type == 'Series')) { commands.push({ diff --git a/src/components/itemHelper.js b/src/components/itemHelper.js index fa8aadbdd5..f21ce6e063 100644 --- a/src/components/itemHelper.js +++ b/src/components/itemHelper.js @@ -6,6 +6,7 @@ import { MediaType } from '@jellyfin/sdk/lib/generated-client/models/media-type' import { getPlaylistsApi } from '@jellyfin/sdk/lib/utils/api/playlists-api'; import { appHost } from './apphost'; +import { AppFeature } from 'constants/appFeature'; import globalize from 'lib/globalize'; import { ServerConnections } from 'lib/jellyfin-apiclient'; import { toApi } from 'utils/jellyfin-apiclient/compat'; @@ -238,7 +239,7 @@ export function canShare (item, user) { if (isLocalItem(item)) { return false; } - return user.Policy.EnablePublicSharing && appHost.supports('sharing'); + return user.Policy.EnablePublicSharing && appHost.supports(AppFeature.Sharing); } export function enableDateAddedDisplay (item) { diff --git a/src/components/multiSelect/multiSelect.js b/src/components/multiSelect/multiSelect.js index 95e59d9ca8..d6ad634cf3 100644 --- a/src/components/multiSelect/multiSelect.js +++ b/src/components/multiSelect/multiSelect.js @@ -1,3 +1,4 @@ +import { AppFeature } from 'constants/appFeature'; import browser from '../../scripts/browser'; import { appHost } from '../apphost'; import loading from '../loading/loading'; @@ -198,7 +199,7 @@ function showMenuForSelectedItems(e) { }); } - if (user.Policy.EnableContentDownloading && appHost.supports('filedownload')) { + if (user.Policy.EnableContentDownloading && appHost.supports(AppFeature.FileDownload)) { // Disabled because there is no callback for this item } diff --git a/src/components/nowPlayingBar/nowPlayingBar.js b/src/components/nowPlayingBar/nowPlayingBar.js index 0184cec0b2..9dd5d09525 100644 --- a/src/components/nowPlayingBar/nowPlayingBar.js +++ b/src/components/nowPlayingBar/nowPlayingBar.js @@ -1,6 +1,7 @@ import { getImageUrl } from 'apps/stable/features/playback/utils/image'; import { getItemTextLines } from 'apps/stable/features/playback/utils/itemText'; import { appRouter, isLyricsPage } from 'components/router/appRouter'; +import { AppFeature } from 'constants/appFeature'; import { ServerConnections } from 'lib/jellyfin-apiclient'; import datetime from '../../scripts/datetime'; @@ -244,7 +245,7 @@ function bindEvents(elem) { toggleRepeatButtonIcon = toggleRepeatButton.querySelector('.material-icons'); - volumeSliderContainer.classList.toggle('hide', appHost.supports('physicalvolumecontrol')); + volumeSliderContainer.classList.toggle('hide', appHost.supports(AppFeature.PhysicalVolumeControl)); volumeSlider.addEventListener('input', (e) => { if (currentPlayer) { @@ -441,7 +442,7 @@ function updatePlayerVolumeState(isMuted, volumeLevel) { showVolumeSlider = false; } - if (currentPlayer.isLocalPlayer && appHost.supports('physicalvolumecontrol')) { + if (currentPlayer.isLocalPlayer && appHost.supports(AppFeature.PhysicalVolumeControl)) { showMuteButton = false; showVolumeSlider = false; } diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index b3c519111e..1b5aee732b 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -24,6 +24,7 @@ import { getItemBackdropImageUrl } from '../../utils/jellyfin-apiclient/backdrop import { PlayerEvent } from 'apps/stable/features/playback/constants/playerEvent'; import { bindMediaSegmentManager } from 'apps/stable/features/playback/utils/mediaSegmentManager'; import { bindMediaSessionSubscriber } from 'apps/stable/features/playback/utils/mediaSessionSubscriber'; +import { AppFeature } from 'constants/appFeature'; import { ServerConnections } from 'lib/jellyfin-apiclient'; import { MediaError } from 'types/mediaError'; import { getMediaError } from 'utils/mediaError'; @@ -41,7 +42,7 @@ function enableLocalPlaylistManagement(player) { } function supportsPhysicalVolumeControl(player) { - return player.isLocalPlayer && appHost.supports('physicalvolumecontrol'); + return player.isLocalPlayer && appHost.supports(AppFeature.PhysicalVolumeControl); } function bindToFullscreenChange(player) { @@ -317,7 +318,7 @@ function getAudioStreamUrl(item, transcodingProfile, directPlayContainers, apiCl PlaySessionId: startingPlaySession, StartTimeTicks: startPosition || 0, EnableRedirection: true, - EnableRemoteMedia: appHost.supports('remoteaudio'), + EnableRemoteMedia: appHost.supports(AppFeature.RemoteAudio), EnableAudioVbrEncoding: transcodingProfile.EnableAudioVbrEncoding }); } @@ -598,7 +599,7 @@ function supportsDirectPlay(apiClient, item, mediaSource) { const isFolderRip = mediaSource.VideoType === 'BluRay' || mediaSource.VideoType === 'Dvd' || mediaSource.VideoType === 'HdDvd'; if (mediaSource.SupportsDirectPlay || isFolderRip) { - if (mediaSource.IsRemote && !appHost.supports('remotevideo')) { + if (mediaSource.IsRemote && !appHost.supports(AppFeature.RemoteVideo)) { return Promise.resolve(false); } @@ -3689,7 +3690,7 @@ export class PlaybackManager { return streamInfo ? streamInfo.playbackStartTimeTicks : null; }; - if (appHost.supports('remotecontrol')) { + if (appHost.supports(AppFeature.RemoteControl)) { import('../../scripts/serverNotifications').then(({ default: serverNotifications }) => { Events.on(serverNotifications, 'ServerShuttingDown', self.setDefaultPlayerActive.bind(self)); Events.on(serverNotifications, 'ServerRestarting', self.setDefaultPlayerActive.bind(self)); @@ -4060,7 +4061,7 @@ export class PlaybackManager { 'PlayTrailers' ]; - if (appHost.supports('fullscreenchange')) { + if (appHost.supports(AppFeature.Fullscreen)) { list.push('ToggleFullscreen'); } diff --git a/src/components/playback/playerSelectionMenu.js b/src/components/playback/playerSelectionMenu.js index 36de970a82..9aedf8217a 100644 --- a/src/components/playback/playerSelectionMenu.js +++ b/src/components/playback/playerSelectionMenu.js @@ -1,3 +1,4 @@ +import { AppFeature } from 'constants/appFeature'; import Events from '../../utils/events.ts'; import browser from '../../scripts/browser'; import loading from '../loading/loading'; @@ -96,7 +97,7 @@ export function show(button) { // Unfortunately we can't allow the url to change or chromecast will throw a security error // Might be able to solve this in the future by moving the dialogs to hashbangs - if (!(!browser.chrome && !browser.edgeChromium || appHost.supports('castmenuhashchange'))) { + if (!(!browser.chrome && !browser.edgeChromium || appHost.supports(AppFeature.CastMenuHashChange))) { menuOptions.enableHistory = false; } diff --git a/src/components/playbackSettings/playbackSettings.js b/src/components/playbackSettings/playbackSettings.js index 0050e0c666..a98b3ab3fe 100644 --- a/src/components/playbackSettings/playbackSettings.js +++ b/src/components/playbackSettings/playbackSettings.js @@ -3,6 +3,7 @@ import escapeHTML from 'escape-html'; import { MediaSegmentAction } from 'apps/stable/features/playback/constants/mediaSegmentAction'; import { getId, getMediaSegmentAction } from 'apps/stable/features/playback/utils/mediaSegmentSettings'; +import { AppFeature } from 'constants/appFeature'; import { ServerConnections } from 'lib/jellyfin-apiclient'; import appSettings from '../../scripts/settings/appSettings'; @@ -147,7 +148,7 @@ function showHideQualityFields(context, user, apiClient) { context.querySelector('.videoQualitySection').classList.add('hide'); } - if (appHost.supports('multiserver')) { + if (appHost.supports(AppFeature.MultiServer)) { context.querySelector('.fldVideoInNetworkQuality').classList.remove('hide'); context.querySelector('.fldVideoInternetQuality').classList.remove('hide'); @@ -204,7 +205,7 @@ function loadForm(context, user, userSettings, systemInfo, apiClient) { context.querySelector('.chkEpisodeAutoPlay').checked = user.Configuration.EnableNextEpisodeAutoPlay || false; }); - if (appHost.supports('externalplayerintent') && userId === loggedInUserId) { + if (appHost.supports(AppFeature.ExternalPlayerIntent) && userId === loggedInUserId) { context.querySelector('.fldExternalPlayer').classList.remove('hide'); } else { context.querySelector('.fldExternalPlayer').classList.add('hide'); @@ -213,7 +214,7 @@ function loadForm(context, user, userSettings, systemInfo, apiClient) { if (userId === loggedInUserId && (user.Policy.EnableVideoPlaybackTranscoding || user.Policy.EnableAudioPlaybackTranscoding)) { context.querySelector('.qualitySections').classList.remove('hide'); - if (appHost.supports('chromecast') && user.Policy.EnableVideoPlaybackTranscoding) { + if (appHost.supports(AppFeature.Chromecast) && user.Policy.EnableVideoPlaybackTranscoding) { context.querySelector('.fldChromecastQuality').classList.remove('hide'); } else { context.querySelector('.fldChromecastQuality').classList.add('hide'); diff --git a/src/components/remotecontrol/remotecontrol.js b/src/components/remotecontrol/remotecontrol.js index a2d32ed24d..bbef1931bc 100644 --- a/src/components/remotecontrol/remotecontrol.js +++ b/src/components/remotecontrol/remotecontrol.js @@ -2,6 +2,7 @@ import escapeHtml from 'escape-html'; import { getImageUrl } from 'apps/stable/features/playback/utils/image'; import { getItemTextLines } from 'apps/stable/features/playback/utils/itemText'; +import { AppFeature } from 'constants/appFeature'; import datetime from '../../scripts/datetime'; import { clearBackdrop, setBackdrops } from '../backdrop/backdrop'; @@ -371,7 +372,7 @@ export default function () { showVolumeSlider = false; } - if (currentPlayer.isLocalPlayer && appHost.supports('physicalvolumecontrol')) { + if (currentPlayer.isLocalPlayer && appHost.supports(AppFeature.PhysicalVolumeControl)) { showMuteButton = false; showVolumeSlider = false; } diff --git a/src/components/slideshow/slideshow.js b/src/components/slideshow/slideshow.js index 49bf8dc1e2..04f728c37d 100644 --- a/src/components/slideshow/slideshow.js +++ b/src/components/slideshow/slideshow.js @@ -2,6 +2,7 @@ * Image viewer component * @module components/slideshow/slideshow */ +import { AppFeature } from 'constants/appFeature'; import dialogHelper from '../dialogHelper/dialogHelper'; import { ServerConnections } from 'lib/jellyfin-apiclient'; import inputManager from '../../scripts/inputManager'; @@ -172,10 +173,10 @@ export default function (options) { if (actionButtonsOnTop) { html += getIcon('play_arrow', 'btnSlideshowPause slideshowButton', true); - if (appHost.supports('filedownload') && slideshowOptions.user?.Policy.EnableContentDownloading) { + if (appHost.supports(AppFeature.FileDownload) && slideshowOptions.user?.Policy.EnableContentDownloading) { html += getIcon('file_download', 'btnDownload slideshowButton', true); } - if (appHost.supports('sharing')) { + if (appHost.supports(AppFeature.Sharing)) { html += getIcon('share', 'btnShare slideshowButton', true); } if (screenfull.isEnabled) { @@ -190,10 +191,10 @@ export default function (options) { html += '
'; html += getIcon('play_arrow', 'btnSlideshowPause slideshowButton', true, true); - if (appHost.supports('filedownload') && slideshowOptions?.user.Policy.EnableContentDownloading) { + if (appHost.supports(AppFeature.FileDownload) && slideshowOptions?.user.Policy.EnableContentDownloading) { html += getIcon('file_download', 'btnDownload slideshowButton', true); } - if (appHost.supports('sharing')) { + if (appHost.supports(AppFeature.Sharing)) { html += getIcon('share', 'btnShare slideshowButton', true); } if (screenfull.isEnabled) { diff --git a/src/components/subtitleeditor/subtitleeditor.js b/src/components/subtitleeditor/subtitleeditor.js index fa4d71e846..9a110c2061 100644 --- a/src/components/subtitleeditor/subtitleeditor.js +++ b/src/components/subtitleeditor/subtitleeditor.js @@ -1,4 +1,6 @@ import escapeHtml from 'escape-html'; + +import { AppFeature } from 'constants/appFeature'; import { appHost } from '../apphost'; import dialogHelper from '../dialogHelper/dialogHelper'; import layoutManager from '../layoutManager'; @@ -440,7 +442,7 @@ function showEditorInternal(itemId, serverId) { } // Don't allow redirection to other websites from the TV layout - if (layoutManager.tv || !appHost.supports('externallinks')) { + if (layoutManager.tv || !appHost.supports(AppFeature.ExternalLinks)) { dlg.querySelector('.btnHelp').remove(); } diff --git a/src/components/subtitlesettings/subtitlesettings.js b/src/components/subtitlesettings/subtitlesettings.js index ee8b2c3ed1..d16662943c 100644 --- a/src/components/subtitlesettings/subtitlesettings.js +++ b/src/components/subtitlesettings/subtitlesettings.js @@ -1,3 +1,4 @@ +import { AppFeature } from 'constants/appFeature'; import globalize from '../../lib/globalize'; import { ServerConnections } from 'lib/jellyfin-apiclient'; import { appHost } from '../apphost'; @@ -40,7 +41,7 @@ function getSubtitleAppearanceObject(context) { function loadForm(context, user, userSettings, appearanceSettings, apiClient) { apiClient.getCultures().then(function (allCultures) { - if (appHost.supports('subtitleburnsettings') && user.Policy.EnableVideoPlaybackTranscoding) { + if (appHost.supports(AppFeature.SubtitleBurnIn) && user.Policy.EnableVideoPlaybackTranscoding) { context.querySelector('.fldBurnIn').classList.remove('hide'); } @@ -208,7 +209,7 @@ function embed(options, self) { options.element.querySelector('.btnSave').classList.remove('hide'); } - if (appHost.supports('subtitleappearancesettings')) { + if (appHost.supports(AppFeature.SubtitleAppearance)) { options.element.querySelector('.subtitleAppearanceSection').classList.remove('hide'); self._fullPreview = options.element.querySelector('.subtitleappearance-fullpreview'); diff --git a/src/components/toolbar/AppUserMenu.tsx b/src/components/toolbar/AppUserMenu.tsx index 69a0ad7f8a..c374d83140 100644 --- a/src/components/toolbar/AppUserMenu.tsx +++ b/src/components/toolbar/AppUserMenu.tsx @@ -16,6 +16,7 @@ import React, { FC, useCallback } from 'react'; import { Link } from 'react-router-dom'; import { appHost } from 'components/apphost'; +import { AppFeature } from 'constants/appFeature'; import { useApi } from 'hooks/useApi'; import { useQuickConnectEnabled } from 'hooks/useQuickConnect'; import globalize from 'lib/globalize'; @@ -97,7 +98,7 @@ const AppUserMenu: FC = ({ - {appHost.supports('clientsettings') && ([ + {appHost.supports(AppFeature.ClientSettings) && ([ , = ({ )} - {appHost.supports('multiserver') && ( + {appHost.supports(AppFeature.MultiServer) && ( @@ -180,7 +181,7 @@ const AppUserMenu: FC = ({ - {appHost.supports('exitmenu') && ([ + {appHost.supports(AppFeature.ExitMenu) && ([ , ${escapeHtml(location)}`; } else { location = escapeHtml(location); @@ -650,7 +651,7 @@ function reloadFromItem(instance, page, params, item, user) { setPeopleHeader(page, item); loading.hide(); - if (item.Type === 'Book' && item.CanDownload && appHost.supports('filedownload')) { + if (item.Type === 'Book' && item.CanDownload && appHost.supports(AppFeature.FileDownload)) { hideAll(page, 'btnDownload', true); } @@ -1083,7 +1084,7 @@ function renderDetails(page, item, apiClient, context) { renderLyricsContainer(page, item, apiClient); // Don't allow redirection to other websites from the TV layout - if (!layoutManager.tv && appHost.supports('externallinks')) { + if (!layoutManager.tv && appHost.supports(AppFeature.ExternalLinks)) { renderLinks(page, item); } diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js index e0d51005f4..c0be4c579c 100644 --- a/src/controllers/playback/video/index.js +++ b/src/controllers/playback/video/index.js @@ -1,4 +1,10 @@ import escapeHtml from 'escape-html'; + +import { PlayerEvent } from 'apps/stable/features/playback/constants/playerEvent'; +import { AppFeature } from 'constants/appFeature'; +import { TICKS_PER_MINUTE, TICKS_PER_SECOND } from 'constants/time'; +import { EventType } from 'types/eventType'; + import { playbackManager } from '../../../components/playback/playbackmanager'; import browser from '../../../scripts/browser'; import dom from '../../../scripts/dom'; @@ -27,9 +33,6 @@ import LibraryMenu from '../../../scripts/libraryMenu'; import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components/backdrop/backdrop'; import { pluginManager } from '../../../components/pluginManager'; import { PluginType } from '../../../types/plugin.ts'; -import { EventType } from 'types/eventType'; -import { TICKS_PER_MINUTE, TICKS_PER_SECOND } from 'constants/time'; -import { PlayerEvent } from 'apps/stable/features/playback/constants/playerEvent'; function getOpenedDialog() { return document.querySelector('.dialogContainer .dialog.opened'); @@ -872,7 +875,7 @@ export default function (view) { showVolumeSlider = false; } - if (player.isLocalPlayer && appHost.supports('physicalvolumecontrol')) { + if (player.isLocalPlayer && appHost.supports(AppFeature.PhysicalVolumeControl)) { showMuteButton = false; showVolumeSlider = false; } diff --git a/src/controllers/session/login/index.js b/src/controllers/session/login/index.js index e99d14da41..2ba0ad6e39 100644 --- a/src/controllers/session/login/index.js +++ b/src/controllers/session/login/index.js @@ -1,5 +1,9 @@ import DOMPurify from 'dompurify'; import markdownIt from 'markdown-it'; + +import { AppFeature } from 'constants/appFeature'; +import { ServerConnections } from 'lib/jellyfin-apiclient'; + import { appHost } from '../../../components/apphost'; import appSettings from '../../../scripts/settings/appSettings'; import dom from '../../../scripts/dom'; @@ -15,7 +19,6 @@ import toast from '../../../components/toast/toast'; import dialogHelper from '../../../components/dialogHelper/dialogHelper'; import baseAlert from '../../../components/alert'; import { getDefaultBackgroundClass } from '../../../components/cardbuilder/cardBuilderUtils'; -import { ServerConnections } from 'lib/jellyfin-apiclient'; import './login.scss'; @@ -263,7 +266,7 @@ export default function (view, params) { loading.show(); libraryMenu.setTransparentMenu(true); - if (!appHost.supports('multiserver')) { + if (!appHost.supports(AppFeature.MultiServer)) { view.querySelector('.btnSelectServer').classList.add('hide'); } diff --git a/src/elements/emby-button/LinkButton.tsx b/src/elements/emby-button/LinkButton.tsx index 359bc0d6b0..713aa0cb35 100644 --- a/src/elements/emby-button/LinkButton.tsx +++ b/src/elements/emby-button/LinkButton.tsx @@ -1,9 +1,12 @@ -import React, { AnchorHTMLAttributes, DetailedHTMLProps, MouseEvent, useCallback } from 'react'; import classNames from 'classnames'; -import layoutManager from '../../components/layoutManager'; -import shell from '../../scripts/shell'; -import { appRouter } from '../../components/router/appRouter'; -import { appHost } from '../../components/apphost'; +import React, { AnchorHTMLAttributes, DetailedHTMLProps, MouseEvent, useCallback } from 'react'; + +import { appHost } from 'components/apphost'; +import layoutManager from 'components/layoutManager'; +import { appRouter } from 'components/router/appRouter'; +import { AppFeature } from 'constants/appFeature'; +import shell from 'scripts/shell'; + import './emby-button.scss'; interface LinkButtonProps extends DetailedHTMLProps, @@ -28,7 +31,7 @@ const LinkButton: React.FC = ({ const url = href || ''; if (url !== '#') { if (target) { - if (!appHost.supports('targetblank')) { + if (!appHost.supports(AppFeature.TargetBlank)) { e.preventDefault(); shell.openUrl(url); } @@ -45,7 +48,7 @@ const LinkButton: React.FC = ({ onClick?.(e); }, [ href, target, onClick ]); - if (isAutoHideEnabled === true && !appHost.supports('externallinks')) { + if (isAutoHideEnabled === true && !appHost.supports(AppFeature.ExternalLinks)) { return null; } diff --git a/src/elements/emby-button/emby-button.js b/src/elements/emby-button/emby-button.js index 0349bd38a9..d41474c364 100644 --- a/src/elements/emby-button/emby-button.js +++ b/src/elements/emby-button/emby-button.js @@ -1,9 +1,12 @@ import 'webcomponents.js/webcomponents-lite'; -import { removeEventListener, addEventListener } from '../../scripts/dom'; -import layoutManager from '../../components/layoutManager'; -import shell from '../../scripts/shell'; -import { appRouter } from '../../components/router/appRouter'; -import { appHost } from '../../components/apphost'; + +import { appHost } from 'components/apphost'; +import layoutManager from 'components/layoutManager'; +import { appRouter } from 'components/router/appRouter'; +import { AppFeature } from 'constants/appFeature'; +import { removeEventListener, addEventListener } from 'scripts/dom'; +import shell from 'scripts/shell'; + import './emby-button.scss'; const EmbyButtonPrototype = Object.create(HTMLButtonElement.prototype); @@ -13,7 +16,7 @@ function onAnchorClick(e) { const href = this.getAttribute('href') || ''; if (href !== '#') { if (this.getAttribute('target')) { - if (!appHost.supports('targetblank')) { + if (!appHost.supports(AppFeature.TargetBlank)) { e.preventDefault(); shell.openUrl(href); } @@ -46,7 +49,7 @@ EmbyButtonPrototype.attachedCallback = function () { addEventListener(this, 'click', onAnchorClick, {}); if (this.getAttribute('data-autohide') === 'true') { - if (appHost.supports('externallinks')) { + if (appHost.supports(AppFeature.ExternalLinks)) { this.classList.remove('hide'); } else { this.classList.add('hide'); diff --git a/src/index.jsx b/src/index.jsx index 8d57c445fa..30e8b8aa6e 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -12,6 +12,7 @@ import autoFocuser from './components/autoFocuser'; import loading from 'components/loading/loading'; import { pluginManager } from './components/pluginManager'; import { appRouter } from './components/router/appRouter'; +import { AppFeature } from 'constants/appFeature'; import globalize from './lib/globalize'; import { loadCoreDictionary } from 'lib/globalize/loader'; import { initialize as initializeAutoCast } from 'scripts/autocast'; @@ -136,7 +137,7 @@ async function loadPlugins() { console.dir(pluginManager); let list = await getPlugins(); - if (!appHost.supports('remotecontrol')) { + if (!appHost.supports(AppFeature.RemoteControl)) { // Disable remote player plugins if not supported list = list.filter(plugin => !plugin.startsWith('sessionPlayer') && !plugin.startsWith('chromecastPlayer')); @@ -165,12 +166,12 @@ function loadPlatformFeatures() { import('./components/nowPlayingBar/nowPlayingBar'); } - if (appHost.supports('remotecontrol')) { + if (appHost.supports(AppFeature.RemoteControl)) { import('./components/playback/playerSelectionMenu'); import('./components/playback/remotecontrolautoplay'); } - if (!appHost.supports('physicalvolumecontrol') || browser.touch) { + if (!appHost.supports(AppFeature.PhysicalVolumeControl) || browser.touch) { import('./components/playback/volumeosd'); } diff --git a/src/plugins/htmlAudioPlayer/plugin.js b/src/plugins/htmlAudioPlayer/plugin.js index b3900aad3c..07f7e2d2f3 100644 --- a/src/plugins/htmlAudioPlayer/plugin.js +++ b/src/plugins/htmlAudioPlayer/plugin.js @@ -1,3 +1,6 @@ +import { AppFeature } from 'constants/appFeature'; +import { MediaError } from 'types/mediaError'; + import browser from '../../scripts/browser'; import { appHost } from '../../components/apphost'; import * as htmlMediaHelper from '../../components/htmlMediaHelper'; @@ -5,7 +8,6 @@ import profileBuilder from '../../scripts/browserDeviceProfile'; import { getIncludeCorsCredentials } from '../../scripts/settings/webSettings'; import { PluginType } from '../../types/plugin.ts'; import Events from '../../utils/events.ts'; -import { MediaError } from 'types/mediaError'; function getDefaultProfile() { return profileBuilder({}); @@ -278,7 +280,7 @@ class HtmlAudioPlayer { } // TODO: Move volume control to PlaybackManager. Player should just be a wrapper that translates commands into API calls. - if (!appHost.supports('physicalvolumecontrol')) { + if (!appHost.supports(AppFeature.PhysicalVolumeControl)) { elem.volume = htmlMediaHelper.getSavedVolume(); } diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index fcaff8f323..541a13e91e 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -1,4 +1,10 @@ import DOMPurify from 'dompurify'; +import debounce from 'lodash-es/debounce'; +import Screenfull from 'screenfull'; + +import { AppFeature } from 'constants/appFeature'; +import { ServerConnections } from 'lib/jellyfin-apiclient'; +import { MediaError } from 'types/mediaError'; import browser from '../../scripts/browser'; import appSettings from '../../scripts/settings/appSettings'; @@ -27,9 +33,7 @@ import { getBufferedRanges } from '../../components/htmlMediaHelper'; import itemHelper from '../../components/itemHelper'; -import Screenfull from 'screenfull'; import globalize from '../../lib/globalize'; -import { ServerConnections } from 'lib/jellyfin-apiclient'; import profileBuilder, { canPlaySecondaryAudio } from '../../scripts/browserDeviceProfile'; import { getIncludeCorsCredentials } from '../../scripts/settings/webSettings'; import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../components/backdrop/backdrop'; @@ -37,8 +41,6 @@ import { PluginType } from '../../types/plugin.ts'; import Events from '../../utils/events.ts'; import { includesAny } from '../../utils/container.ts'; import { isHls } from '../../utils/mediaSource.ts'; -import debounce from 'lodash-es/debounce'; -import { MediaError } from 'types/mediaError'; /** * Returns resolved URL. @@ -1667,7 +1669,7 @@ export class HtmlVideoPlayer { const cssClass = 'htmlvideoplayer'; // Can't autoplay in these browsers so we need to use the full controls, at least until playback starts - if (!appHost.supports('htmlvideoautoplay')) { + if (!appHost.supports(AppFeature.HtmlVideoAutoplay)) { html += '