mirror of
https://github.com/jellyfin/jellyfin-web.git
synced 2026-01-15 16:33:35 -03:00
Add constants for app features
This commit is contained in:
@@ -11,6 +11,7 @@ import Typography from '@mui/material/Typography';
|
|||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
|
|
||||||
import { appHost } from 'components/apphost';
|
import { appHost } from 'components/apphost';
|
||||||
|
import { AppFeature } from 'constants/appFeature';
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
import { useThemes } from 'hooks/useThemes';
|
import { useThemes } from 'hooks/useThemes';
|
||||||
import globalize from 'lib/globalize';
|
import globalize from 'lib/globalize';
|
||||||
@@ -32,7 +33,7 @@ export function DisplayPreferences({ onChange, values }: Readonly<DisplayPrefere
|
|||||||
<Stack spacing={3}>
|
<Stack spacing={3}>
|
||||||
<Typography variant='h2'>{globalize.translate('Display')}</Typography>
|
<Typography variant='h2'>{globalize.translate('Display')}</Typography>
|
||||||
|
|
||||||
{ appHost.supports('displaymode') && (
|
{ appHost.supports(AppFeature.DisplayMode) && (
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel id='display-settings-layout-label'>{globalize.translate('LabelDisplayMode')}</InputLabel>
|
<InputLabel id='display-settings-layout-label'>{globalize.translate('LabelDisplayMode')}</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
@@ -124,7 +125,7 @@ export function DisplayPreferences({ onChange, values }: Readonly<DisplayPrefere
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
) }
|
) }
|
||||||
|
|
||||||
{ screensavers.length > 0 && appHost.supports('screensaver') && (
|
{ screensavers.length > 0 && appHost.supports(AppFeature.Screensaver) && (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel id='display-settings-screensaver-label'>{globalize.translate('LabelScreensaver')}</InputLabel>
|
<InputLabel id='display-settings-screensaver-label'>{globalize.translate('LabelScreensaver')}</InputLabel>
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ import React from 'react';
|
|||||||
|
|
||||||
import { DATE_LOCALE_OPTIONS, LANGUAGE_OPTIONS } from 'apps/experimental/features/preferences/constants/locales';
|
import { DATE_LOCALE_OPTIONS, LANGUAGE_OPTIONS } from 'apps/experimental/features/preferences/constants/locales';
|
||||||
import { appHost } from 'components/apphost';
|
import { appHost } from 'components/apphost';
|
||||||
import datetime from 'scripts/datetime';
|
import { AppFeature } from 'constants/appFeature';
|
||||||
import globalize from 'lib/globalize';
|
import globalize from 'lib/globalize';
|
||||||
|
import datetime from 'scripts/datetime';
|
||||||
|
|
||||||
import type { DisplaySettingsValues } from '../types/displaySettingsValues';
|
import type { DisplaySettingsValues } from '../types/displaySettingsValues';
|
||||||
|
|
||||||
@@ -21,14 +22,14 @@ interface LocalizationPreferencesProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function LocalizationPreferences({ onChange, values }: Readonly<LocalizationPreferencesProps>) {
|
export function LocalizationPreferences({ onChange, values }: Readonly<LocalizationPreferencesProps>) {
|
||||||
if (!appHost.supports('displaylanguage') && !datetime.supportsLocalization()) {
|
if (!appHost.supports(AppFeature.DisplayLanguage) && !datetime.supportsLocalization()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Stack spacing={3}>
|
<Stack spacing={3}>
|
||||||
<Typography variant='h2'>{globalize.translate('Localization')}</Typography>
|
<Typography variant='h2'>{globalize.translate('Localization')}</Typography>
|
||||||
|
|
||||||
{ appHost.supports('displaylanguage') && (
|
{ appHost.supports(AppFeature.DisplayLanguage) && (
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel id='display-settings-language-label'>{globalize.translate('LabelDisplayLanguage')}</InputLabel>
|
<InputLabel id='display-settings-language-label'>{globalize.translate('LabelDisplayLanguage')}</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
@@ -46,7 +47,7 @@ export function LocalizationPreferences({ onChange, values }: Readonly<Localizat
|
|||||||
</Select>
|
</Select>
|
||||||
<FormHelperText component={Stack} id='display-settings-language-description'>
|
<FormHelperText component={Stack} id='display-settings-language-description'>
|
||||||
<span>{globalize.translate('LabelDisplayLanguageHelp')}</span>
|
<span>{globalize.translate('LabelDisplayLanguageHelp')}</span>
|
||||||
{ appHost.supports('externallinks') && (
|
{ appHost.supports(AppFeature.ExternalLinks) && (
|
||||||
<Link
|
<Link
|
||||||
href='https://github.com/jellyfin/jellyfin'
|
href='https://github.com/jellyfin/jellyfin'
|
||||||
rel='noopener noreferrer'
|
rel='noopener noreferrer'
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useState } from 'react';
|
|||||||
|
|
||||||
import { appHost } from 'components/apphost';
|
import { appHost } from 'components/apphost';
|
||||||
import layoutManager from 'components/layoutManager';
|
import layoutManager from 'components/layoutManager';
|
||||||
|
import { AppFeature } from 'constants/appFeature';
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
import themeManager from 'scripts/themeManager';
|
import themeManager from 'scripts/themeManager';
|
||||||
import { currentSettings, UserSettings } from 'scripts/settings/userSettings';
|
import { currentSettings, UserSettings } from 'scripts/settings/userSettings';
|
||||||
@@ -120,7 +121,7 @@ async function saveDisplaySettings({
|
|||||||
}: SaveDisplaySettingsParams) {
|
}: SaveDisplaySettingsParams) {
|
||||||
const user = await api.getUser(userId);
|
const user = await api.getUser(userId);
|
||||||
|
|
||||||
if (appHost.supports('displaylanguage')) {
|
if (appHost.supports(AppFeature.DisplayLanguage)) {
|
||||||
userSettings.language(normalizeValue(newDisplaySettings.language));
|
userSettings.language(normalizeValue(newDisplaySettings.language));
|
||||||
}
|
}
|
||||||
userSettings.customCss(normalizeValue(newDisplaySettings.customCss));
|
userSettings.customCss(normalizeValue(newDisplaySettings.customCss));
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { appHost } from 'components/apphost';
|
|||||||
import layoutManager from 'components/layoutManager';
|
import layoutManager from 'components/layoutManager';
|
||||||
import Loading from 'components/loading/LoadingComponent';
|
import Loading from 'components/loading/LoadingComponent';
|
||||||
import Page from 'components/Page';
|
import Page from 'components/Page';
|
||||||
|
import { AppFeature } from 'constants/appFeature';
|
||||||
import LinkButton from 'elements/emby-button/LinkButton';
|
import LinkButton from 'elements/emby-button/LinkButton';
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
import { useQuickConnectEnabled } from 'hooks/useQuickConnect';
|
import { useQuickConnectEnabled } from 'hooks/useQuickConnect';
|
||||||
@@ -185,7 +186,7 @@ const UserSettingsPage: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
|
|
||||||
{appHost.supports('clientsettings') && (
|
{appHost.supports(AppFeature.ClientSettings) && (
|
||||||
<LinkButton
|
<LinkButton
|
||||||
onClick={shell.openClientSettings}
|
onClick={shell.openClientSettings}
|
||||||
className='clientSettings listItem-border'
|
className='clientSettings listItem-border'
|
||||||
@@ -290,7 +291,7 @@ const UserSettingsPage: FC = () => {
|
|||||||
{globalize.translate('HeaderUser')}
|
{globalize.translate('HeaderUser')}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{appHost.supports('multiserver') && (
|
{appHost.supports(AppFeature.MultiServer) && (
|
||||||
<LinkButton
|
<LinkButton
|
||||||
onClick={Dashboard.selectServer}
|
onClick={Dashboard.selectServer}
|
||||||
className='selectServer listItem-border'
|
className='selectServer listItem-border'
|
||||||
@@ -330,7 +331,7 @@ const UserSettingsPage: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
|
|
||||||
{appHost.supports('exitmenu') && (
|
{appHost.supports(AppFeature.ExitMenu) && (
|
||||||
<LinkButton
|
<LinkButton
|
||||||
onClick={appHost.exit}
|
onClick={appHost.exit}
|
||||||
className='exitApp listItem-border'
|
className='exitApp listItem-border'
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import UserPasswordForm from '../../../../components/dashboard/users/UserPasswor
|
|||||||
import loading from '../../../../components/loading/loading';
|
import loading from '../../../../components/loading/loading';
|
||||||
import toast from '../../../../components/toast/toast';
|
import toast from '../../../../components/toast/toast';
|
||||||
import Page from '../../../../components/Page';
|
import Page from '../../../../components/Page';
|
||||||
|
import { AppFeature } from 'constants/appFeature';
|
||||||
|
|
||||||
const UserProfile: FunctionComponent = () => {
|
const UserProfile: FunctionComponent = () => {
|
||||||
const [ searchParams ] = useSearchParams();
|
const [ searchParams ] = useSearchParams();
|
||||||
@@ -61,7 +62,7 @@ const UserProfile: FunctionComponent = () => {
|
|||||||
if (user.PrimaryImageTag) {
|
if (user.PrimaryImageTag) {
|
||||||
(page.querySelector('#btnAddImage') as HTMLButtonElement).classList.add('hide');
|
(page.querySelector('#btnAddImage') as HTMLButtonElement).classList.add('hide');
|
||||||
(page.querySelector('#btnDeleteImage') as HTMLButtonElement).classList.remove('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('#btnDeleteImage') as HTMLButtonElement).classList.add('hide');
|
||||||
(page.querySelector('#btnAddImage') as HTMLButtonElement).classList.remove('hide');
|
(page.querySelector('#btnAddImage') as HTMLButtonElement).classList.remove('hide');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React, { FC, useEffect, useState } from 'react';
|
|||||||
|
|
||||||
import { appHost } from 'components/apphost';
|
import { appHost } from 'components/apphost';
|
||||||
import Page from 'components/Page';
|
import Page from 'components/Page';
|
||||||
|
import { AppFeature } from 'constants/appFeature';
|
||||||
import LinkButton from 'elements/emby-button/LinkButton';
|
import LinkButton from 'elements/emby-button/LinkButton';
|
||||||
import globalize from 'lib/globalize';
|
import globalize from 'lib/globalize';
|
||||||
import { ConnectionState } from 'lib/jellyfin-apiclient';
|
import { ConnectionState } from 'lib/jellyfin-apiclient';
|
||||||
@@ -50,7 +51,7 @@ const ConnectionErrorPage: FC<ConnectionErrorPageProps> = ({
|
|||||||
{message && (
|
{message && (
|
||||||
<p>{message}</p>
|
<p>{message}</p>
|
||||||
)}
|
)}
|
||||||
{appHost.supports('multiserver') && (
|
{appHost.supports(AppFeature.MultiServer) && (
|
||||||
<LinkButton
|
<LinkButton
|
||||||
className='raised'
|
className='raised'
|
||||||
href='/selectserver'
|
href='/selectserver'
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import * as htmlMediaHelper from '../components/htmlMediaHelper';
|
|||||||
import * as webSettings from '../scripts/settings/webSettings';
|
import * as webSettings from '../scripts/settings/webSettings';
|
||||||
import globalize from '../lib/globalize';
|
import globalize from '../lib/globalize';
|
||||||
import profileBuilder from '../scripts/browserDeviceProfile';
|
import profileBuilder from '../scripts/browserDeviceProfile';
|
||||||
|
import { AppFeature } from 'constants/appFeature';
|
||||||
|
|
||||||
const appName = 'Jellyfin Web';
|
const appName = 'Jellyfin Web';
|
||||||
|
|
||||||
@@ -169,14 +170,6 @@ function getDeviceName() {
|
|||||||
return deviceName;
|
return deviceName;
|
||||||
}
|
}
|
||||||
|
|
||||||
function supportsVoiceInput() {
|
|
||||||
if (!browser.tv) {
|
|
||||||
return window.SpeechRecognition || window.webkitSpeechRecognition || window.mozSpeechRecognition || window.oSpeechRecognition || window.msSpeechRecognition;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function supportsFullscreen() {
|
function supportsFullscreen() {
|
||||||
if (browser.tv) {
|
if (browser.tv) {
|
||||||
return false;
|
return false;
|
||||||
@@ -235,77 +228,65 @@ const supportedFeatures = function () {
|
|||||||
const features = [];
|
const features = [];
|
||||||
|
|
||||||
if (navigator.share) {
|
if (navigator.share) {
|
||||||
features.push('sharing');
|
features.push(AppFeature.Sharing);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!browser.edgeUwp && !browser.tv && !browser.xboxOne && !browser.ps4) {
|
if (!browser.edgeUwp && !browser.tv && !browser.xboxOne && !browser.ps4) {
|
||||||
features.push('filedownload');
|
features.push(AppFeature.FileDownload);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (browser.operaTv || browser.tizen || browser.orsay || browser.web0s) {
|
if (browser.operaTv || browser.tizen || browser.orsay || browser.web0s) {
|
||||||
features.push('exit');
|
features.push(AppFeature.Exit);
|
||||||
} else {
|
|
||||||
features.push('plugins');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.ps4) {
|
if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.ps4) {
|
||||||
features.push('externallinks');
|
features.push(AppFeature.ExternalLinks);
|
||||||
features.push('externalpremium');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!browser.operaTv) {
|
|
||||||
features.push('externallinkdisplay');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (supportsVoiceInput()) {
|
|
||||||
features.push('voiceinput');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (supportsHtmlMediaAutoplay()) {
|
if (supportsHtmlMediaAutoplay()) {
|
||||||
features.push('htmlaudioautoplay');
|
features.push(AppFeature.HtmlAudioAutoplay);
|
||||||
features.push('htmlvideoautoplay');
|
features.push(AppFeature.HtmlVideoAutoplay);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (supportsFullscreen()) {
|
if (supportsFullscreen()) {
|
||||||
features.push('fullscreenchange');
|
features.push(AppFeature.Fullscreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (browser.tv || browser.xboxOne || browser.ps4 || browser.mobile || browser.ipad) {
|
if (browser.tv || browser.xboxOne || browser.ps4 || browser.mobile || browser.ipad) {
|
||||||
features.push('physicalvolumecontrol');
|
features.push(AppFeature.PhysicalVolumeControl);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!browser.tv && !browser.xboxOne && !browser.ps4) {
|
if (!browser.tv && !browser.xboxOne && !browser.ps4) {
|
||||||
features.push('remotecontrol');
|
features.push(AppFeature.RemoteControl);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.edgeUwp) {
|
if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.edgeUwp) {
|
||||||
features.push('remotevideo');
|
features.push(AppFeature.RemoteVideo);
|
||||||
}
|
}
|
||||||
|
|
||||||
features.push('displaylanguage');
|
features.push(AppFeature.DisplayLanguage);
|
||||||
features.push('otherapppromotions');
|
features.push(AppFeature.DisplayMode);
|
||||||
features.push('displaymode');
|
features.push(AppFeature.TargetBlank);
|
||||||
features.push('targetblank');
|
features.push(AppFeature.Screensaver);
|
||||||
features.push('screensaver');
|
|
||||||
|
|
||||||
webSettings.getMultiServer().then(enabled => {
|
webSettings.getMultiServer().then(enabled => {
|
||||||
if (enabled) features.push('multiserver');
|
if (enabled) features.push(AppFeature.MultiServer);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!browser.orsay && (browser.firefox || browser.ps4 || browser.edge || supportsCue())) {
|
if (!browser.orsay && (browser.firefox || browser.ps4 || browser.edge || supportsCue())) {
|
||||||
features.push('subtitleappearancesettings');
|
features.push(AppFeature.SubtitleAppearance);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!browser.orsay) {
|
if (!browser.orsay) {
|
||||||
features.push('subtitleburnsettings');
|
features.push(AppFeature.SubtitleBurnIn);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!browser.tv && !browser.ps4 && !browser.xboxOne) {
|
if (!browser.tv && !browser.ps4 && !browser.xboxOne) {
|
||||||
features.push('fileinput');
|
features.push(AppFeature.FileInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (browser.chrome || browser.edgeChromium) {
|
if (browser.chrome || browser.edgeChromium) {
|
||||||
features.push('chromecast');
|
features.push(AppFeature.Chromecast);
|
||||||
}
|
}
|
||||||
|
|
||||||
return features;
|
return features;
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import escapeHtml from 'escape-html';
|
import escapeHtml from 'escape-html';
|
||||||
|
|
||||||
|
import { AppFeature } from 'constants/appFeature';
|
||||||
import browser from '../../scripts/browser';
|
import browser from '../../scripts/browser';
|
||||||
import layoutManager from '../layoutManager';
|
import layoutManager from '../layoutManager';
|
||||||
import { pluginManager } from '../pluginManager';
|
import { pluginManager } from '../pluginManager';
|
||||||
@@ -68,19 +70,19 @@ function showOrHideMissingEpisodesField(context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function loadForm(context, user, userSettings) {
|
function loadForm(context, user, userSettings) {
|
||||||
if (appHost.supports('displaylanguage')) {
|
if (appHost.supports(AppFeature.DisplayLanguage)) {
|
||||||
context.querySelector('.languageSection').classList.remove('hide');
|
context.querySelector('.languageSection').classList.remove('hide');
|
||||||
} else {
|
} else {
|
||||||
context.querySelector('.languageSection').classList.add('hide');
|
context.querySelector('.languageSection').classList.add('hide');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appHost.supports('displaymode')) {
|
if (appHost.supports(AppFeature.DisplayMode)) {
|
||||||
context.querySelector('.fldDisplayMode').classList.remove('hide');
|
context.querySelector('.fldDisplayMode').classList.remove('hide');
|
||||||
} else {
|
} else {
|
||||||
context.querySelector('.fldDisplayMode').classList.add('hide');
|
context.querySelector('.fldDisplayMode').classList.add('hide');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appHost.supports('externallinks')) {
|
if (appHost.supports(AppFeature.ExternalLinks)) {
|
||||||
context.querySelector('.learnHowToContributeContainer').classList.remove('hide');
|
context.querySelector('.learnHowToContributeContainer').classList.remove('hide');
|
||||||
} else {
|
} else {
|
||||||
context.querySelector('.learnHowToContributeContainer').classList.add('hide');
|
context.querySelector('.learnHowToContributeContainer').classList.add('hide');
|
||||||
@@ -88,7 +90,7 @@ function loadForm(context, user, userSettings) {
|
|||||||
|
|
||||||
context.querySelector('.selectDashboardThemeContainer').classList.toggle('hide', !user.Policy.IsAdministrator);
|
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('.selectScreensaverContainer').classList.remove('hide');
|
||||||
context.querySelector('.txtBackdropScreensaverIntervalContainer').classList.remove('hide');
|
context.querySelector('.txtBackdropScreensaverIntervalContainer').classList.remove('hide');
|
||||||
context.querySelector('.txtScreensaverTimeContainer').classList.remove('hide');
|
context.querySelector('.txtScreensaverTimeContainer').classList.remove('hide');
|
||||||
@@ -143,7 +145,7 @@ function loadForm(context, user, userSettings) {
|
|||||||
function saveUser(context, user, userSettingsInstance, apiClient) {
|
function saveUser(context, user, userSettingsInstance, apiClient) {
|
||||||
user.Configuration.DisplayMissingEpisodes = context.querySelector('.chkDisplayMissingEpisodes').checked;
|
user.Configuration.DisplayMissingEpisodes = context.querySelector('.chkDisplayMissingEpisodes').checked;
|
||||||
|
|
||||||
if (appHost.supports('displaylanguage')) {
|
if (appHost.supports(AppFeature.DisplayLanguage)) {
|
||||||
userSettingsInstance.language(context.querySelector('#selectLanguage').value);
|
userSettingsInstance.language(context.querySelector('#selectLanguage').value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { AppFeature } from 'constants/appFeature';
|
||||||
import dom from '../../scripts/dom';
|
import dom from '../../scripts/dom';
|
||||||
import loading from '../loading/loading';
|
import loading from '../loading/loading';
|
||||||
import { appHost } from '../apphost';
|
import { appHost } from '../apphost';
|
||||||
@@ -205,7 +206,7 @@ function getRemoteImageHtml(image, imageType) {
|
|||||||
html += '<div class="cardPadder-' + shape + '"></div>';
|
html += '<div class="cardPadder-' + shape + '"></div>';
|
||||||
html += '<div class="cardContent">';
|
html += '<div class="cardContent">';
|
||||||
|
|
||||||
if (layoutManager.tv || !appHost.supports('externallinks')) {
|
if (layoutManager.tv || !appHost.supports(AppFeature.ExternalLinks)) {
|
||||||
html += '<div class="cardImageContainer lazy" data-src="' + image.Url + '" style="background-position:center center;background-size:contain;"></div>';
|
html += '<div class="cardImageContainer lazy" data-src="' + image.Url + '" style="background-position:center center;background-size:contain;"></div>';
|
||||||
} else {
|
} else {
|
||||||
html += '<a is="emby-linkbutton" target="_blank" href="' + image.Url + '" class="button-link cardImageContainer lazy" data-src="' + image.Url + '" style="background-position:center center;background-size:contain"></a>';
|
html += '<a is="emby-linkbutton" target="_blank" href="' + image.Url + '" class="button-link cardImageContainer lazy" data-src="' + image.Url + '" style="background-position:center center;background-size:contain"></a>';
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { AppFeature } from 'constants/appFeature';
|
||||||
import dialogHelper from '../dialogHelper/dialogHelper';
|
import dialogHelper from '../dialogHelper/dialogHelper';
|
||||||
import loading from '../loading/loading';
|
import loading from '../loading/loading';
|
||||||
import dom from '../../scripts/dom';
|
import dom from '../../scripts/dom';
|
||||||
@@ -339,7 +340,7 @@ function showActionSheet(context, imageCard) {
|
|||||||
|
|
||||||
function initEditor(context, options) {
|
function initEditor(context, options) {
|
||||||
const uploadButtons = context.querySelectorAll('.btnOpenUploadMenu');
|
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++) {
|
for (let i = 0, length = uploadButtons.length; i < length; i++) {
|
||||||
if (isFileInputSupported) {
|
if (isFileInputSupported) {
|
||||||
uploadButtons[i].classList.remove('hide');
|
uploadButtons[i].classList.remove('hide');
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { playbackManager } from './playback/playbackmanager';
|
|||||||
import toast from './toast/toast';
|
import toast from './toast/toast';
|
||||||
import * as userSettings from '../scripts/settings/userSettings';
|
import * as userSettings from '../scripts/settings/userSettings';
|
||||||
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
|
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
|
||||||
|
import { AppFeature } from 'constants/appFeature';
|
||||||
|
|
||||||
function getDeleteLabel(type) {
|
function getDeleteLabel(type) {
|
||||||
switch (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?
|
// CanDownload should probably be updated to return true for these items?
|
||||||
if (user.Policy.EnableContentDownloading && (item.Type === 'Season' || item.Type == 'Series')) {
|
if (user.Policy.EnableContentDownloading && (item.Type === 'Season' || item.Type == 'Series')) {
|
||||||
commands.push({
|
commands.push({
|
||||||
|
|||||||
@@ -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 { getPlaylistsApi } from '@jellyfin/sdk/lib/utils/api/playlists-api';
|
||||||
|
|
||||||
import { appHost } from './apphost';
|
import { appHost } from './apphost';
|
||||||
|
import { AppFeature } from 'constants/appFeature';
|
||||||
import globalize from 'lib/globalize';
|
import globalize from 'lib/globalize';
|
||||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||||
import { toApi } from 'utils/jellyfin-apiclient/compat';
|
import { toApi } from 'utils/jellyfin-apiclient/compat';
|
||||||
@@ -238,7 +239,7 @@ export function canShare (item, user) {
|
|||||||
if (isLocalItem(item)) {
|
if (isLocalItem(item)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return user.Policy.EnablePublicSharing && appHost.supports('sharing');
|
return user.Policy.EnablePublicSharing && appHost.supports(AppFeature.Sharing);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function enableDateAddedDisplay (item) {
|
export function enableDateAddedDisplay (item) {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { AppFeature } from 'constants/appFeature';
|
||||||
import browser from '../../scripts/browser';
|
import browser from '../../scripts/browser';
|
||||||
import { appHost } from '../apphost';
|
import { appHost } from '../apphost';
|
||||||
import loading from '../loading/loading';
|
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
|
// Disabled because there is no callback for this item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { getImageUrl } from 'apps/stable/features/playback/utils/image';
|
import { getImageUrl } from 'apps/stable/features/playback/utils/image';
|
||||||
import { getItemTextLines } from 'apps/stable/features/playback/utils/itemText';
|
import { getItemTextLines } from 'apps/stable/features/playback/utils/itemText';
|
||||||
import { appRouter, isLyricsPage } from 'components/router/appRouter';
|
import { appRouter, isLyricsPage } from 'components/router/appRouter';
|
||||||
|
import { AppFeature } from 'constants/appFeature';
|
||||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||||
|
|
||||||
import datetime from '../../scripts/datetime';
|
import datetime from '../../scripts/datetime';
|
||||||
@@ -244,7 +245,7 @@ function bindEvents(elem) {
|
|||||||
|
|
||||||
toggleRepeatButtonIcon = toggleRepeatButton.querySelector('.material-icons');
|
toggleRepeatButtonIcon = toggleRepeatButton.querySelector('.material-icons');
|
||||||
|
|
||||||
volumeSliderContainer.classList.toggle('hide', appHost.supports('physicalvolumecontrol'));
|
volumeSliderContainer.classList.toggle('hide', appHost.supports(AppFeature.PhysicalVolumeControl));
|
||||||
|
|
||||||
volumeSlider.addEventListener('input', (e) => {
|
volumeSlider.addEventListener('input', (e) => {
|
||||||
if (currentPlayer) {
|
if (currentPlayer) {
|
||||||
@@ -441,7 +442,7 @@ function updatePlayerVolumeState(isMuted, volumeLevel) {
|
|||||||
showVolumeSlider = false;
|
showVolumeSlider = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentPlayer.isLocalPlayer && appHost.supports('physicalvolumecontrol')) {
|
if (currentPlayer.isLocalPlayer && appHost.supports(AppFeature.PhysicalVolumeControl)) {
|
||||||
showMuteButton = false;
|
showMuteButton = false;
|
||||||
showVolumeSlider = false;
|
showVolumeSlider = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import { getItemBackdropImageUrl } from '../../utils/jellyfin-apiclient/backdrop
|
|||||||
import { PlayerEvent } from 'apps/stable/features/playback/constants/playerEvent';
|
import { PlayerEvent } from 'apps/stable/features/playback/constants/playerEvent';
|
||||||
import { bindMediaSegmentManager } from 'apps/stable/features/playback/utils/mediaSegmentManager';
|
import { bindMediaSegmentManager } from 'apps/stable/features/playback/utils/mediaSegmentManager';
|
||||||
import { bindMediaSessionSubscriber } from 'apps/stable/features/playback/utils/mediaSessionSubscriber';
|
import { bindMediaSessionSubscriber } from 'apps/stable/features/playback/utils/mediaSessionSubscriber';
|
||||||
|
import { AppFeature } from 'constants/appFeature';
|
||||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||||
import { MediaError } from 'types/mediaError';
|
import { MediaError } from 'types/mediaError';
|
||||||
import { getMediaError } from 'utils/mediaError';
|
import { getMediaError } from 'utils/mediaError';
|
||||||
@@ -41,7 +42,7 @@ function enableLocalPlaylistManagement(player) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function supportsPhysicalVolumeControl(player) {
|
function supportsPhysicalVolumeControl(player) {
|
||||||
return player.isLocalPlayer && appHost.supports('physicalvolumecontrol');
|
return player.isLocalPlayer && appHost.supports(AppFeature.PhysicalVolumeControl);
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindToFullscreenChange(player) {
|
function bindToFullscreenChange(player) {
|
||||||
@@ -317,7 +318,7 @@ function getAudioStreamUrl(item, transcodingProfile, directPlayContainers, apiCl
|
|||||||
PlaySessionId: startingPlaySession,
|
PlaySessionId: startingPlaySession,
|
||||||
StartTimeTicks: startPosition || 0,
|
StartTimeTicks: startPosition || 0,
|
||||||
EnableRedirection: true,
|
EnableRedirection: true,
|
||||||
EnableRemoteMedia: appHost.supports('remoteaudio'),
|
EnableRemoteMedia: appHost.supports(AppFeature.RemoteAudio),
|
||||||
EnableAudioVbrEncoding: transcodingProfile.EnableAudioVbrEncoding
|
EnableAudioVbrEncoding: transcodingProfile.EnableAudioVbrEncoding
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -598,7 +599,7 @@ function supportsDirectPlay(apiClient, item, mediaSource) {
|
|||||||
const isFolderRip = mediaSource.VideoType === 'BluRay' || mediaSource.VideoType === 'Dvd' || mediaSource.VideoType === 'HdDvd';
|
const isFolderRip = mediaSource.VideoType === 'BluRay' || mediaSource.VideoType === 'Dvd' || mediaSource.VideoType === 'HdDvd';
|
||||||
|
|
||||||
if (mediaSource.SupportsDirectPlay || isFolderRip) {
|
if (mediaSource.SupportsDirectPlay || isFolderRip) {
|
||||||
if (mediaSource.IsRemote && !appHost.supports('remotevideo')) {
|
if (mediaSource.IsRemote && !appHost.supports(AppFeature.RemoteVideo)) {
|
||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3689,7 +3690,7 @@ export class PlaybackManager {
|
|||||||
return streamInfo ? streamInfo.playbackStartTimeTicks : null;
|
return streamInfo ? streamInfo.playbackStartTimeTicks : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (appHost.supports('remotecontrol')) {
|
if (appHost.supports(AppFeature.RemoteControl)) {
|
||||||
import('../../scripts/serverNotifications').then(({ default: serverNotifications }) => {
|
import('../../scripts/serverNotifications').then(({ default: serverNotifications }) => {
|
||||||
Events.on(serverNotifications, 'ServerShuttingDown', self.setDefaultPlayerActive.bind(self));
|
Events.on(serverNotifications, 'ServerShuttingDown', self.setDefaultPlayerActive.bind(self));
|
||||||
Events.on(serverNotifications, 'ServerRestarting', self.setDefaultPlayerActive.bind(self));
|
Events.on(serverNotifications, 'ServerRestarting', self.setDefaultPlayerActive.bind(self));
|
||||||
@@ -4060,7 +4061,7 @@ export class PlaybackManager {
|
|||||||
'PlayTrailers'
|
'PlayTrailers'
|
||||||
];
|
];
|
||||||
|
|
||||||
if (appHost.supports('fullscreenchange')) {
|
if (appHost.supports(AppFeature.Fullscreen)) {
|
||||||
list.push('ToggleFullscreen');
|
list.push('ToggleFullscreen');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { AppFeature } from 'constants/appFeature';
|
||||||
import Events from '../../utils/events.ts';
|
import Events from '../../utils/events.ts';
|
||||||
import browser from '../../scripts/browser';
|
import browser from '../../scripts/browser';
|
||||||
import loading from '../loading/loading';
|
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
|
// 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
|
// 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;
|
menuOptions.enableHistory = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import escapeHTML from 'escape-html';
|
|||||||
|
|
||||||
import { MediaSegmentAction } from 'apps/stable/features/playback/constants/mediaSegmentAction';
|
import { MediaSegmentAction } from 'apps/stable/features/playback/constants/mediaSegmentAction';
|
||||||
import { getId, getMediaSegmentAction } from 'apps/stable/features/playback/utils/mediaSegmentSettings';
|
import { getId, getMediaSegmentAction } from 'apps/stable/features/playback/utils/mediaSegmentSettings';
|
||||||
|
import { AppFeature } from 'constants/appFeature';
|
||||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||||
|
|
||||||
import appSettings from '../../scripts/settings/appSettings';
|
import appSettings from '../../scripts/settings/appSettings';
|
||||||
@@ -147,7 +148,7 @@ function showHideQualityFields(context, user, apiClient) {
|
|||||||
context.querySelector('.videoQualitySection').classList.add('hide');
|
context.querySelector('.videoQualitySection').classList.add('hide');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appHost.supports('multiserver')) {
|
if (appHost.supports(AppFeature.MultiServer)) {
|
||||||
context.querySelector('.fldVideoInNetworkQuality').classList.remove('hide');
|
context.querySelector('.fldVideoInNetworkQuality').classList.remove('hide');
|
||||||
context.querySelector('.fldVideoInternetQuality').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;
|
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');
|
context.querySelector('.fldExternalPlayer').classList.remove('hide');
|
||||||
} else {
|
} else {
|
||||||
context.querySelector('.fldExternalPlayer').classList.add('hide');
|
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)) {
|
if (userId === loggedInUserId && (user.Policy.EnableVideoPlaybackTranscoding || user.Policy.EnableAudioPlaybackTranscoding)) {
|
||||||
context.querySelector('.qualitySections').classList.remove('hide');
|
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');
|
context.querySelector('.fldChromecastQuality').classList.remove('hide');
|
||||||
} else {
|
} else {
|
||||||
context.querySelector('.fldChromecastQuality').classList.add('hide');
|
context.querySelector('.fldChromecastQuality').classList.add('hide');
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import escapeHtml from 'escape-html';
|
|||||||
|
|
||||||
import { getImageUrl } from 'apps/stable/features/playback/utils/image';
|
import { getImageUrl } from 'apps/stable/features/playback/utils/image';
|
||||||
import { getItemTextLines } from 'apps/stable/features/playback/utils/itemText';
|
import { getItemTextLines } from 'apps/stable/features/playback/utils/itemText';
|
||||||
|
import { AppFeature } from 'constants/appFeature';
|
||||||
|
|
||||||
import datetime from '../../scripts/datetime';
|
import datetime from '../../scripts/datetime';
|
||||||
import { clearBackdrop, setBackdrops } from '../backdrop/backdrop';
|
import { clearBackdrop, setBackdrops } from '../backdrop/backdrop';
|
||||||
@@ -371,7 +372,7 @@ export default function () {
|
|||||||
showVolumeSlider = false;
|
showVolumeSlider = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentPlayer.isLocalPlayer && appHost.supports('physicalvolumecontrol')) {
|
if (currentPlayer.isLocalPlayer && appHost.supports(AppFeature.PhysicalVolumeControl)) {
|
||||||
showMuteButton = false;
|
showMuteButton = false;
|
||||||
showVolumeSlider = false;
|
showVolumeSlider = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
* Image viewer component
|
* Image viewer component
|
||||||
* @module components/slideshow/slideshow
|
* @module components/slideshow/slideshow
|
||||||
*/
|
*/
|
||||||
|
import { AppFeature } from 'constants/appFeature';
|
||||||
import dialogHelper from '../dialogHelper/dialogHelper';
|
import dialogHelper from '../dialogHelper/dialogHelper';
|
||||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||||
import inputManager from '../../scripts/inputManager';
|
import inputManager from '../../scripts/inputManager';
|
||||||
@@ -172,10 +173,10 @@ export default function (options) {
|
|||||||
if (actionButtonsOnTop) {
|
if (actionButtonsOnTop) {
|
||||||
html += getIcon('play_arrow', 'btnSlideshowPause slideshowButton', true);
|
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);
|
html += getIcon('file_download', 'btnDownload slideshowButton', true);
|
||||||
}
|
}
|
||||||
if (appHost.supports('sharing')) {
|
if (appHost.supports(AppFeature.Sharing)) {
|
||||||
html += getIcon('share', 'btnShare slideshowButton', true);
|
html += getIcon('share', 'btnShare slideshowButton', true);
|
||||||
}
|
}
|
||||||
if (screenfull.isEnabled) {
|
if (screenfull.isEnabled) {
|
||||||
@@ -190,10 +191,10 @@ export default function (options) {
|
|||||||
html += '<div class="slideshowBottomBar hide">';
|
html += '<div class="slideshowBottomBar hide">';
|
||||||
|
|
||||||
html += getIcon('play_arrow', 'btnSlideshowPause slideshowButton', true, true);
|
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);
|
html += getIcon('file_download', 'btnDownload slideshowButton', true);
|
||||||
}
|
}
|
||||||
if (appHost.supports('sharing')) {
|
if (appHost.supports(AppFeature.Sharing)) {
|
||||||
html += getIcon('share', 'btnShare slideshowButton', true);
|
html += getIcon('share', 'btnShare slideshowButton', true);
|
||||||
}
|
}
|
||||||
if (screenfull.isEnabled) {
|
if (screenfull.isEnabled) {
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import escapeHtml from 'escape-html';
|
import escapeHtml from 'escape-html';
|
||||||
|
|
||||||
|
import { AppFeature } from 'constants/appFeature';
|
||||||
import { appHost } from '../apphost';
|
import { appHost } from '../apphost';
|
||||||
import dialogHelper from '../dialogHelper/dialogHelper';
|
import dialogHelper from '../dialogHelper/dialogHelper';
|
||||||
import layoutManager from '../layoutManager';
|
import layoutManager from '../layoutManager';
|
||||||
@@ -440,7 +442,7 @@ function showEditorInternal(itemId, serverId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Don't allow redirection to other websites from the TV layout
|
// 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();
|
dlg.querySelector('.btnHelp').remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { AppFeature } from 'constants/appFeature';
|
||||||
import globalize from '../../lib/globalize';
|
import globalize from '../../lib/globalize';
|
||||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||||
import { appHost } from '../apphost';
|
import { appHost } from '../apphost';
|
||||||
@@ -40,7 +41,7 @@ function getSubtitleAppearanceObject(context) {
|
|||||||
|
|
||||||
function loadForm(context, user, userSettings, appearanceSettings, apiClient) {
|
function loadForm(context, user, userSettings, appearanceSettings, apiClient) {
|
||||||
apiClient.getCultures().then(function (allCultures) {
|
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');
|
context.querySelector('.fldBurnIn').classList.remove('hide');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,7 +209,7 @@ function embed(options, self) {
|
|||||||
options.element.querySelector('.btnSave').classList.remove('hide');
|
options.element.querySelector('.btnSave').classList.remove('hide');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appHost.supports('subtitleappearancesettings')) {
|
if (appHost.supports(AppFeature.SubtitleAppearance)) {
|
||||||
options.element.querySelector('.subtitleAppearanceSection').classList.remove('hide');
|
options.element.querySelector('.subtitleAppearanceSection').classList.remove('hide');
|
||||||
|
|
||||||
self._fullPreview = options.element.querySelector('.subtitleappearance-fullpreview');
|
self._fullPreview = options.element.querySelector('.subtitleappearance-fullpreview');
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import React, { FC, useCallback } from 'react';
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { appHost } from 'components/apphost';
|
import { appHost } from 'components/apphost';
|
||||||
|
import { AppFeature } from 'constants/appFeature';
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
import { useQuickConnectEnabled } from 'hooks/useQuickConnect';
|
import { useQuickConnectEnabled } from 'hooks/useQuickConnect';
|
||||||
import globalize from 'lib/globalize';
|
import globalize from 'lib/globalize';
|
||||||
@@ -97,7 +98,7 @@ const AppUserMenu: FC<AppUserMenuProps> = ({
|
|||||||
</ListItemText>
|
</ListItemText>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
||||||
{appHost.supports('clientsettings') && ([
|
{appHost.supports(AppFeature.ClientSettings) && ([
|
||||||
<Divider key='client-settings-divider' />,
|
<Divider key='client-settings-divider' />,
|
||||||
<MenuItem
|
<MenuItem
|
||||||
key='client-settings-button'
|
key='client-settings-button'
|
||||||
@@ -156,7 +157,7 @@ const AppUserMenu: FC<AppUserMenuProps> = ({
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{appHost.supports('multiserver') && (
|
{appHost.supports(AppFeature.MultiServer) && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={onSelectServerClick}
|
onClick={onSelectServerClick}
|
||||||
>
|
>
|
||||||
@@ -180,7 +181,7 @@ const AppUserMenu: FC<AppUserMenuProps> = ({
|
|||||||
</ListItemText>
|
</ListItemText>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
||||||
{appHost.supports('exitmenu') && ([
|
{appHost.supports(AppFeature.ExitMenu) && ([
|
||||||
<Divider key='exit-menu-divider' />,
|
<Divider key='exit-menu-divider' />,
|
||||||
<MenuItem
|
<MenuItem
|
||||||
key='exit-menu-button'
|
key='exit-menu-button'
|
||||||
|
|||||||
57
src/constants/appFeature.ts
Normal file
57
src/constants/appFeature.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/** App feature flags */
|
||||||
|
export enum AppFeature {
|
||||||
|
/** The app supports changing the URL hash when the cast menu opens */
|
||||||
|
CastMenuHashChange = 'castmenuhashchange',
|
||||||
|
/** The app supports Chromecast */
|
||||||
|
Chromecast = 'chromecast',
|
||||||
|
/** The app supports showing client settings via a menu entry */
|
||||||
|
ClientSettings = 'clientsettings',
|
||||||
|
/** The app supports configuring the display language */
|
||||||
|
DisplayLanguage = 'displaylanguage',
|
||||||
|
/** The app supports configuring the display mode (TV, Desktop, etc.) */
|
||||||
|
DisplayMode = 'displaymode',
|
||||||
|
/** The app can exit via back navigation */
|
||||||
|
Exit = 'exit',
|
||||||
|
/** The app can be exited via a menu entry */
|
||||||
|
ExitMenu = 'exitmenu',
|
||||||
|
/** The app can open external URLs */
|
||||||
|
ExternalLinks = 'externallinks',
|
||||||
|
/** The app supports enabling external players */
|
||||||
|
ExternalPlayerIntent = 'externalplayerintent',
|
||||||
|
/** The app supports file downloads */
|
||||||
|
FileDownload = 'filedownload',
|
||||||
|
/** The app supports file input elements */
|
||||||
|
FileInput = 'fileinput',
|
||||||
|
/** The app supports enabling fullscreen media playback */
|
||||||
|
Fullscreen = 'fullscreenchange',
|
||||||
|
/** The app supports autoplay on the audio element */
|
||||||
|
HtmlAudioAutoplay = 'htmlaudioautoplay',
|
||||||
|
/** The app supports autoplay on the video element */
|
||||||
|
HtmlVideoAutoplay = 'htmlvideoautoplay',
|
||||||
|
/** The app supports switching servers */
|
||||||
|
MultiServer = 'multiserver',
|
||||||
|
/** The app supports playback of BluRay folders */
|
||||||
|
NativeBluRayPlayback = 'nativeblurayplayback',
|
||||||
|
/** The app supports playback of DVD folders */
|
||||||
|
NativeDvdPlayback = 'nativedvdplayback',
|
||||||
|
/** The app supports playback of ISO files */
|
||||||
|
NativeIsoPlayback = 'nativeisoplayback',
|
||||||
|
/** The app supports physical volume buttons */
|
||||||
|
PhysicalVolumeControl = 'physicalvolumecontrol',
|
||||||
|
/** The app supports playing remote audio */
|
||||||
|
RemoteAudio = 'remoteaudio',
|
||||||
|
/** The app supports the remote control (casting) feature */
|
||||||
|
RemoteControl = 'remotecontrol',
|
||||||
|
/** The app supports playing remote video */
|
||||||
|
RemoteVideo = 'remotevideo',
|
||||||
|
/** The app supports displaying a screensaver */
|
||||||
|
Screensaver = 'screensaver',
|
||||||
|
/** The app supports sharing content */
|
||||||
|
Sharing = 'sharing',
|
||||||
|
/** The app supports configuring subtitle appearance */
|
||||||
|
SubtitleAppearance = 'subtitleappearancesettings',
|
||||||
|
/** The app supports configuring subtitle burn-in */
|
||||||
|
SubtitleBurnIn = 'subtitleburnsettings',
|
||||||
|
/** The app can open URLs in a blank page. */
|
||||||
|
TargetBlank = 'targetblank'
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ import loading from 'components/loading/loading';
|
|||||||
import { playbackManager } from 'components/playback/playbackmanager';
|
import { playbackManager } from 'components/playback/playbackmanager';
|
||||||
import { appRouter } from 'components/router/appRouter';
|
import { appRouter } from 'components/router/appRouter';
|
||||||
import itemShortcuts from 'components/shortcuts';
|
import itemShortcuts from 'components/shortcuts';
|
||||||
|
import { AppFeature } from 'constants/appFeature';
|
||||||
import globalize from 'lib/globalize';
|
import globalize from 'lib/globalize';
|
||||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||||
import browser from 'scripts/browser';
|
import browser from 'scripts/browser';
|
||||||
@@ -636,7 +637,7 @@ function reloadFromItem(instance, page, params, item, user) {
|
|||||||
|
|
||||||
if (item.Type == 'Person' && item.ProductionLocations && item.ProductionLocations.length) {
|
if (item.Type == 'Person' && item.ProductionLocations && item.ProductionLocations.length) {
|
||||||
let location = item.ProductionLocations[0];
|
let location = item.ProductionLocations[0];
|
||||||
if (!layoutManager.tv && appHost.supports('externallinks')) {
|
if (!layoutManager.tv && appHost.supports(AppFeature.ExternalLinks)) {
|
||||||
location = `<a is="emby-linkbutton" class="button-link textlink" target="_blank" href="https://www.openstreetmap.org/search?query=${encodeURIComponent(location)}">${escapeHtml(location)}</a>`;
|
location = `<a is="emby-linkbutton" class="button-link textlink" target="_blank" href="https://www.openstreetmap.org/search?query=${encodeURIComponent(location)}">${escapeHtml(location)}</a>`;
|
||||||
} else {
|
} else {
|
||||||
location = escapeHtml(location);
|
location = escapeHtml(location);
|
||||||
@@ -650,7 +651,7 @@ function reloadFromItem(instance, page, params, item, user) {
|
|||||||
setPeopleHeader(page, item);
|
setPeopleHeader(page, item);
|
||||||
loading.hide();
|
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);
|
hideAll(page, 'btnDownload', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1083,7 +1084,7 @@ function renderDetails(page, item, apiClient, context) {
|
|||||||
renderLyricsContainer(page, item, apiClient);
|
renderLyricsContainer(page, item, apiClient);
|
||||||
|
|
||||||
// Don't allow redirection to other websites from the TV layout
|
// 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);
|
renderLinks(page, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
import escapeHtml from 'escape-html';
|
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 { playbackManager } from '../../../components/playback/playbackmanager';
|
||||||
import browser from '../../../scripts/browser';
|
import browser from '../../../scripts/browser';
|
||||||
import dom from '../../../scripts/dom';
|
import dom from '../../../scripts/dom';
|
||||||
@@ -27,9 +33,6 @@ import LibraryMenu from '../../../scripts/libraryMenu';
|
|||||||
import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components/backdrop/backdrop';
|
import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components/backdrop/backdrop';
|
||||||
import { pluginManager } from '../../../components/pluginManager';
|
import { pluginManager } from '../../../components/pluginManager';
|
||||||
import { PluginType } from '../../../types/plugin.ts';
|
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() {
|
function getOpenedDialog() {
|
||||||
return document.querySelector('.dialogContainer .dialog.opened');
|
return document.querySelector('.dialogContainer .dialog.opened');
|
||||||
@@ -872,7 +875,7 @@ export default function (view) {
|
|||||||
showVolumeSlider = false;
|
showVolumeSlider = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (player.isLocalPlayer && appHost.supports('physicalvolumecontrol')) {
|
if (player.isLocalPlayer && appHost.supports(AppFeature.PhysicalVolumeControl)) {
|
||||||
showMuteButton = false;
|
showMuteButton = false;
|
||||||
showVolumeSlider = false;
|
showVolumeSlider = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import DOMPurify from 'dompurify';
|
import DOMPurify from 'dompurify';
|
||||||
import markdownIt from 'markdown-it';
|
import markdownIt from 'markdown-it';
|
||||||
|
|
||||||
|
import { AppFeature } from 'constants/appFeature';
|
||||||
|
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||||
|
|
||||||
import { appHost } from '../../../components/apphost';
|
import { appHost } from '../../../components/apphost';
|
||||||
import appSettings from '../../../scripts/settings/appSettings';
|
import appSettings from '../../../scripts/settings/appSettings';
|
||||||
import dom from '../../../scripts/dom';
|
import dom from '../../../scripts/dom';
|
||||||
@@ -15,7 +19,6 @@ import toast from '../../../components/toast/toast';
|
|||||||
import dialogHelper from '../../../components/dialogHelper/dialogHelper';
|
import dialogHelper from '../../../components/dialogHelper/dialogHelper';
|
||||||
import baseAlert from '../../../components/alert';
|
import baseAlert from '../../../components/alert';
|
||||||
import { getDefaultBackgroundClass } from '../../../components/cardbuilder/cardBuilderUtils';
|
import { getDefaultBackgroundClass } from '../../../components/cardbuilder/cardBuilderUtils';
|
||||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
|
||||||
|
|
||||||
import './login.scss';
|
import './login.scss';
|
||||||
|
|
||||||
@@ -263,7 +266,7 @@ export default function (view, params) {
|
|||||||
loading.show();
|
loading.show();
|
||||||
libraryMenu.setTransparentMenu(true);
|
libraryMenu.setTransparentMenu(true);
|
||||||
|
|
||||||
if (!appHost.supports('multiserver')) {
|
if (!appHost.supports(AppFeature.MultiServer)) {
|
||||||
view.querySelector('.btnSelectServer').classList.add('hide');
|
view.querySelector('.btnSelectServer').classList.add('hide');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import React, { AnchorHTMLAttributes, DetailedHTMLProps, MouseEvent, useCallback } from 'react';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import layoutManager from '../../components/layoutManager';
|
import React, { AnchorHTMLAttributes, DetailedHTMLProps, MouseEvent, useCallback } from 'react';
|
||||||
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 shell from 'scripts/shell';
|
||||||
|
|
||||||
import './emby-button.scss';
|
import './emby-button.scss';
|
||||||
|
|
||||||
interface LinkButtonProps extends DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>,
|
interface LinkButtonProps extends DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>,
|
||||||
@@ -28,7 +31,7 @@ const LinkButton: React.FC<LinkButtonProps> = ({
|
|||||||
const url = href || '';
|
const url = href || '';
|
||||||
if (url !== '#') {
|
if (url !== '#') {
|
||||||
if (target) {
|
if (target) {
|
||||||
if (!appHost.supports('targetblank')) {
|
if (!appHost.supports(AppFeature.TargetBlank)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
shell.openUrl(url);
|
shell.openUrl(url);
|
||||||
}
|
}
|
||||||
@@ -45,7 +48,7 @@ const LinkButton: React.FC<LinkButtonProps> = ({
|
|||||||
onClick?.(e);
|
onClick?.(e);
|
||||||
}, [ href, target, onClick ]);
|
}, [ href, target, onClick ]);
|
||||||
|
|
||||||
if (isAutoHideEnabled === true && !appHost.supports('externallinks')) {
|
if (isAutoHideEnabled === true && !appHost.supports(AppFeature.ExternalLinks)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import 'webcomponents.js/webcomponents-lite';
|
import 'webcomponents.js/webcomponents-lite';
|
||||||
import { removeEventListener, addEventListener } from '../../scripts/dom';
|
|
||||||
import layoutManager from '../../components/layoutManager';
|
import { appHost } from 'components/apphost';
|
||||||
import shell from '../../scripts/shell';
|
import layoutManager from 'components/layoutManager';
|
||||||
import { appRouter } from '../../components/router/appRouter';
|
import { appRouter } from 'components/router/appRouter';
|
||||||
import { appHost } from '../../components/apphost';
|
import { AppFeature } from 'constants/appFeature';
|
||||||
|
import { removeEventListener, addEventListener } from 'scripts/dom';
|
||||||
|
import shell from 'scripts/shell';
|
||||||
|
|
||||||
import './emby-button.scss';
|
import './emby-button.scss';
|
||||||
|
|
||||||
const EmbyButtonPrototype = Object.create(HTMLButtonElement.prototype);
|
const EmbyButtonPrototype = Object.create(HTMLButtonElement.prototype);
|
||||||
@@ -13,7 +16,7 @@ function onAnchorClick(e) {
|
|||||||
const href = this.getAttribute('href') || '';
|
const href = this.getAttribute('href') || '';
|
||||||
if (href !== '#') {
|
if (href !== '#') {
|
||||||
if (this.getAttribute('target')) {
|
if (this.getAttribute('target')) {
|
||||||
if (!appHost.supports('targetblank')) {
|
if (!appHost.supports(AppFeature.TargetBlank)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
shell.openUrl(href);
|
shell.openUrl(href);
|
||||||
}
|
}
|
||||||
@@ -46,7 +49,7 @@ EmbyButtonPrototype.attachedCallback = function () {
|
|||||||
addEventListener(this, 'click', onAnchorClick, {});
|
addEventListener(this, 'click', onAnchorClick, {});
|
||||||
|
|
||||||
if (this.getAttribute('data-autohide') === 'true') {
|
if (this.getAttribute('data-autohide') === 'true') {
|
||||||
if (appHost.supports('externallinks')) {
|
if (appHost.supports(AppFeature.ExternalLinks)) {
|
||||||
this.classList.remove('hide');
|
this.classList.remove('hide');
|
||||||
} else {
|
} else {
|
||||||
this.classList.add('hide');
|
this.classList.add('hide');
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import autoFocuser from './components/autoFocuser';
|
|||||||
import loading from 'components/loading/loading';
|
import loading from 'components/loading/loading';
|
||||||
import { pluginManager } from './components/pluginManager';
|
import { pluginManager } from './components/pluginManager';
|
||||||
import { appRouter } from './components/router/appRouter';
|
import { appRouter } from './components/router/appRouter';
|
||||||
|
import { AppFeature } from 'constants/appFeature';
|
||||||
import globalize from './lib/globalize';
|
import globalize from './lib/globalize';
|
||||||
import { loadCoreDictionary } from 'lib/globalize/loader';
|
import { loadCoreDictionary } from 'lib/globalize/loader';
|
||||||
import { initialize as initializeAutoCast } from 'scripts/autocast';
|
import { initialize as initializeAutoCast } from 'scripts/autocast';
|
||||||
@@ -136,7 +137,7 @@ async function loadPlugins() {
|
|||||||
console.dir(pluginManager);
|
console.dir(pluginManager);
|
||||||
|
|
||||||
let list = await getPlugins();
|
let list = await getPlugins();
|
||||||
if (!appHost.supports('remotecontrol')) {
|
if (!appHost.supports(AppFeature.RemoteControl)) {
|
||||||
// Disable remote player plugins if not supported
|
// Disable remote player plugins if not supported
|
||||||
list = list.filter(plugin => !plugin.startsWith('sessionPlayer')
|
list = list.filter(plugin => !plugin.startsWith('sessionPlayer')
|
||||||
&& !plugin.startsWith('chromecastPlayer'));
|
&& !plugin.startsWith('chromecastPlayer'));
|
||||||
@@ -165,12 +166,12 @@ function loadPlatformFeatures() {
|
|||||||
import('./components/nowPlayingBar/nowPlayingBar');
|
import('./components/nowPlayingBar/nowPlayingBar');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appHost.supports('remotecontrol')) {
|
if (appHost.supports(AppFeature.RemoteControl)) {
|
||||||
import('./components/playback/playerSelectionMenu');
|
import('./components/playback/playerSelectionMenu');
|
||||||
import('./components/playback/remotecontrolautoplay');
|
import('./components/playback/remotecontrolautoplay');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!appHost.supports('physicalvolumecontrol') || browser.touch) {
|
if (!appHost.supports(AppFeature.PhysicalVolumeControl) || browser.touch) {
|
||||||
import('./components/playback/volumeosd');
|
import('./components/playback/volumeosd');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { AppFeature } from 'constants/appFeature';
|
||||||
|
import { MediaError } from 'types/mediaError';
|
||||||
|
|
||||||
import browser from '../../scripts/browser';
|
import browser from '../../scripts/browser';
|
||||||
import { appHost } from '../../components/apphost';
|
import { appHost } from '../../components/apphost';
|
||||||
import * as htmlMediaHelper from '../../components/htmlMediaHelper';
|
import * as htmlMediaHelper from '../../components/htmlMediaHelper';
|
||||||
@@ -5,7 +8,6 @@ import profileBuilder from '../../scripts/browserDeviceProfile';
|
|||||||
import { getIncludeCorsCredentials } from '../../scripts/settings/webSettings';
|
import { getIncludeCorsCredentials } from '../../scripts/settings/webSettings';
|
||||||
import { PluginType } from '../../types/plugin.ts';
|
import { PluginType } from '../../types/plugin.ts';
|
||||||
import Events from '../../utils/events.ts';
|
import Events from '../../utils/events.ts';
|
||||||
import { MediaError } from 'types/mediaError';
|
|
||||||
|
|
||||||
function getDefaultProfile() {
|
function getDefaultProfile() {
|
||||||
return profileBuilder({});
|
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.
|
// 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();
|
elem.volume = htmlMediaHelper.getSavedVolume();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
import DOMPurify from 'dompurify';
|
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 browser from '../../scripts/browser';
|
||||||
import appSettings from '../../scripts/settings/appSettings';
|
import appSettings from '../../scripts/settings/appSettings';
|
||||||
@@ -27,9 +33,7 @@ import {
|
|||||||
getBufferedRanges
|
getBufferedRanges
|
||||||
} from '../../components/htmlMediaHelper';
|
} from '../../components/htmlMediaHelper';
|
||||||
import itemHelper from '../../components/itemHelper';
|
import itemHelper from '../../components/itemHelper';
|
||||||
import Screenfull from 'screenfull';
|
|
||||||
import globalize from '../../lib/globalize';
|
import globalize from '../../lib/globalize';
|
||||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
|
||||||
import profileBuilder, { canPlaySecondaryAudio } from '../../scripts/browserDeviceProfile';
|
import profileBuilder, { canPlaySecondaryAudio } from '../../scripts/browserDeviceProfile';
|
||||||
import { getIncludeCorsCredentials } from '../../scripts/settings/webSettings';
|
import { getIncludeCorsCredentials } from '../../scripts/settings/webSettings';
|
||||||
import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../components/backdrop/backdrop';
|
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 Events from '../../utils/events.ts';
|
||||||
import { includesAny } from '../../utils/container.ts';
|
import { includesAny } from '../../utils/container.ts';
|
||||||
import { isHls } from '../../utils/mediaSource.ts';
|
import { isHls } from '../../utils/mediaSource.ts';
|
||||||
import debounce from 'lodash-es/debounce';
|
|
||||||
import { MediaError } from 'types/mediaError';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns resolved URL.
|
* Returns resolved URL.
|
||||||
@@ -1667,7 +1669,7 @@ export class HtmlVideoPlayer {
|
|||||||
const cssClass = 'htmlvideoplayer';
|
const cssClass = 'htmlvideoplayer';
|
||||||
|
|
||||||
// Can't autoplay in these browsers so we need to use the full controls, at least until playback starts
|
// 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 += '<video class="' + cssClass + '" preload="metadata" autoplay="autoplay" controls="controls" webkit-playsinline playsinline>';
|
html += '<video class="' + cssClass + '" preload="metadata" autoplay="autoplay" controls="controls" webkit-playsinline playsinline>';
|
||||||
} else if (browser.web0s) {
|
} else if (browser.web0s) {
|
||||||
// in webOS, setting preload auto allows resuming videos
|
// in webOS, setting preload auto allows resuming videos
|
||||||
@@ -1683,7 +1685,7 @@ export class HtmlVideoPlayer {
|
|||||||
const videoElement = playerDlg.querySelector('video');
|
const videoElement = playerDlg.querySelector('video');
|
||||||
|
|
||||||
// TODO: Move volume control to PlaybackManager. Player should just be a wrapper that translates commands into API calls.
|
// 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)) {
|
||||||
videoElement.volume = getSavedVolume();
|
videoElement.volume = getSavedVolume();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { appHost } from '../../../components/apphost';
|
import { appHost } from 'components/apphost';
|
||||||
|
import { AppFeature } from 'constants/appFeature';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an audio element that plays a silent sound.
|
* Creates an audio element that plays a silent sound.
|
||||||
@@ -35,7 +36,7 @@ class PlaybackPermissionManager {
|
|||||||
* @returns {Promise} Promise that resolves succesfully if playback permission is allowed.
|
* @returns {Promise} Promise that resolves succesfully if playback permission is allowed.
|
||||||
*/
|
*/
|
||||||
check () {
|
check () {
|
||||||
if (appHost.supports('htmlaudioautoplay')) {
|
if (appHost.supports(AppFeature.HtmlAudioAutoplay)) {
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { playbackManager } from '../components/playback/playbackmanager';
|
import { appHost } from 'components/apphost';
|
||||||
import focusManager from '../components/focusManager';
|
import focusManager from 'components/focusManager';
|
||||||
import { appRouter } from '../components/router/appRouter';
|
import { playbackManager } from 'components/playback/playbackmanager';
|
||||||
import dom from './dom';
|
import { appRouter } from 'components/router/appRouter';
|
||||||
import { appHost } from '../components/apphost';
|
import { AppFeature } from 'constants/appFeature';
|
||||||
|
import dom from 'scripts/dom';
|
||||||
|
|
||||||
let lastInputTime = new Date().getTime();
|
let lastInputTime = new Date().getTime();
|
||||||
|
|
||||||
@@ -111,7 +112,7 @@ export function handleCommand(commandName, options) {
|
|||||||
'back': () => {
|
'back': () => {
|
||||||
if (appRouter.canGoBack()) {
|
if (appRouter.canGoBack()) {
|
||||||
appRouter.back();
|
appRouter.back();
|
||||||
} else if (appHost.supports('exit')) {
|
} else if (appHost.supports(AppFeature.Exit)) {
|
||||||
appHost.exit();
|
appHost.exit();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ import Headroom from 'headroom.js';
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
import { ApiClient } from 'jellyfin-apiclient';
|
import { ApiClient } from 'jellyfin-apiclient';
|
||||||
|
|
||||||
|
import { AppFeature } from 'constants/appFeature';
|
||||||
import { getUserViewsQuery } from 'hooks/useUserViews';
|
import { getUserViewsQuery } from 'hooks/useUserViews';
|
||||||
|
import globalize from 'lib/globalize';
|
||||||
|
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||||
import { EventType } from 'types/eventType';
|
import { EventType } from 'types/eventType';
|
||||||
import { toApi } from 'utils/jellyfin-apiclient/compat';
|
import { toApi } from 'utils/jellyfin-apiclient/compat';
|
||||||
import { queryClient } from 'utils/query/queryClient';
|
import { queryClient } from 'utils/query/queryClient';
|
||||||
@@ -19,8 +22,6 @@ import { playbackManager } from '../components/playback/playbackmanager';
|
|||||||
import { pluginManager } from '../components/pluginManager';
|
import { pluginManager } from '../components/pluginManager';
|
||||||
import groupSelectionMenu from '../plugins/syncPlay/ui/groupSelectionMenu';
|
import groupSelectionMenu from '../plugins/syncPlay/ui/groupSelectionMenu';
|
||||||
import browser from './browser';
|
import browser from './browser';
|
||||||
import globalize from 'lib/globalize';
|
|
||||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
|
||||||
import imageHelper from '../utils/image';
|
import imageHelper from '../utils/image';
|
||||||
import { getMenuLinks } from '../scripts/settings/webSettings';
|
import { getMenuLinks } from '../scripts/settings/webSettings';
|
||||||
import Dashboard, { pageClassOn } from '../utils/dashboard';
|
import Dashboard, { pageClassOn } from '../utils/dashboard';
|
||||||
@@ -348,14 +349,14 @@ function refreshLibraryInfoInDrawer(user) {
|
|||||||
html += globalize.translate('HeaderUser');
|
html += globalize.translate('HeaderUser');
|
||||||
html += '</h3>';
|
html += '</h3>';
|
||||||
|
|
||||||
if (appHost.supports('multiserver')) {
|
if (appHost.supports(AppFeature.MultiServer)) {
|
||||||
html += `<a is="emby-linkbutton" class="navMenuOption lnkMediaFolder btnSelectServer" data-itemid="selectserver" href="#"><span class="material-icons navMenuOptionIcon storage" aria-hidden="true"></span><span class="navMenuOptionText">${globalize.translate('SelectServer')}</span></a>`;
|
html += `<a is="emby-linkbutton" class="navMenuOption lnkMediaFolder btnSelectServer" data-itemid="selectserver" href="#"><span class="material-icons navMenuOptionIcon storage" aria-hidden="true"></span><span class="navMenuOptionText">${globalize.translate('SelectServer')}</span></a>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
html += `<a is="emby-linkbutton" class="navMenuOption lnkMediaFolder btnSettings" data-itemid="settings" href="#"><span class="material-icons navMenuOptionIcon settings" aria-hidden="true"></span><span class="navMenuOptionText">${globalize.translate('Settings')}</span></a>`;
|
html += `<a is="emby-linkbutton" class="navMenuOption lnkMediaFolder btnSettings" data-itemid="settings" href="#"><span class="material-icons navMenuOptionIcon settings" aria-hidden="true"></span><span class="navMenuOptionText">${globalize.translate('Settings')}</span></a>`;
|
||||||
html += `<a is="emby-linkbutton" class="navMenuOption lnkMediaFolder btnLogout" data-itemid="logout" href="#"><span class="material-icons navMenuOptionIcon exit_to_app" aria-hidden="true"></span><span class="navMenuOptionText">${globalize.translate('ButtonSignOut')}</span></a>`;
|
html += `<a is="emby-linkbutton" class="navMenuOption lnkMediaFolder btnLogout" data-itemid="logout" href="#"><span class="material-icons navMenuOptionIcon exit_to_app" aria-hidden="true"></span><span class="navMenuOptionText">${globalize.translate('ButtonSignOut')}</span></a>`;
|
||||||
|
|
||||||
if (appHost.supports('exitmenu')) {
|
if (appHost.supports(AppFeature.ExitMenu)) {
|
||||||
html += `<a is="emby-linkbutton" class="navMenuOption lnkMediaFolder exitApp" data-itemid="exitapp" href="#"><span class="material-icons navMenuOptionIcon close" aria-hidden="true"></span><span class="navMenuOptionText">${globalize.translate('ButtonExitApp')}</span></a>`;
|
html += `<a is="emby-linkbutton" class="navMenuOption lnkMediaFolder exitApp" data-itemid="exitapp" href="#"><span class="material-icons navMenuOptionIcon close" aria-hidden="true"></span><span class="navMenuOptionText">${globalize.translate('ButtonExitApp')}</span></a>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user