mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Merge branch 'master' into enable-airplay-audioplayer
This commit is contained in:
commit
12cf325e79
116 changed files with 5344 additions and 1453 deletions
|
@ -31,7 +31,7 @@ const ServerContentPage: FunctionComponent<ServerContentPageProps> = ({ view })
|
|||
|
||||
viewManager.tryRestoreView(viewOptions)
|
||||
.catch(async (result?: RestoreViewFailResponse) => {
|
||||
if (!result || !result.cancelled) {
|
||||
if (!result?.cancelled) {
|
||||
const apiClient = ServerConnections.currentApiClient();
|
||||
|
||||
// Fetch the view html from the server and translate it
|
||||
|
|
|
@ -179,7 +179,7 @@ export class AlphaPicker {
|
|||
|
||||
if (item) {
|
||||
const prefix = item.getAttribute('data-prefix');
|
||||
if (prefix && prefix.length) {
|
||||
if (prefix?.length) {
|
||||
itemFocusValue = prefix[0];
|
||||
if (itemFocusTimeout) {
|
||||
clearTimeout(itemFocusTimeout);
|
||||
|
|
|
@ -109,7 +109,7 @@ export function getCommands(options) {
|
|||
});
|
||||
}
|
||||
|
||||
if (itemHelper.supportsAddingToCollection(item) && options.EnableCollectionManagement) {
|
||||
if (itemHelper.supportsAddingToCollection(item) && (user.Policy.IsAdministrator || user.Policy.EnableCollectionManagement)) {
|
||||
commands.push({
|
||||
name: globalize.translate('AddToCollection'),
|
||||
id: 'addtocollection',
|
||||
|
|
|
@ -416,6 +416,8 @@ export function setContentType(parent, contentType) {
|
|||
}
|
||||
}
|
||||
|
||||
parent.querySelector('.chkEnableLUFSScan').classList.toggle('hide', contentType !== 'music');
|
||||
|
||||
if (contentType === 'tvshows') {
|
||||
parent.querySelector('.chkEnableEmbeddedEpisodeInfosContainer').classList.remove('hide');
|
||||
} else {
|
||||
|
@ -512,6 +514,7 @@ export function getLibraryOptions(parent) {
|
|||
EnableArchiveMediaFiles: false,
|
||||
EnablePhotos: parent.querySelector('.chkEnablePhotos').checked,
|
||||
EnableRealtimeMonitor: parent.querySelector('.chkEnableRealtimeMonitor').checked,
|
||||
EnableLUFSScan: parent.querySelector('.chkEnableLUFSScan').checked,
|
||||
ExtractChapterImagesDuringLibraryScan: parent.querySelector('.chkExtractChaptersDuringLibraryScan').checked,
|
||||
EnableChapterImageExtraction: parent.querySelector('.chkExtractChapterImages').checked,
|
||||
EnableInternetProviders: true,
|
||||
|
@ -573,6 +576,7 @@ export function setLibraryOptions(parent, options) {
|
|||
parent.querySelector('#txtSeasonZeroName').value = options.SeasonZeroDisplayName || 'Specials';
|
||||
parent.querySelector('.chkEnablePhotos').checked = options.EnablePhotos;
|
||||
parent.querySelector('.chkEnableRealtimeMonitor').checked = options.EnableRealtimeMonitor;
|
||||
parent.querySelector('.chkEnableLUFSScan').checked = options.EnableLUFSScan;
|
||||
parent.querySelector('.chkExtractChaptersDuringLibraryScan').checked = options.ExtractChapterImagesDuringLibraryScan;
|
||||
parent.querySelector('.chkExtractChapterImages').checked = options.EnableChapterImageExtraction;
|
||||
parent.querySelector('#chkSaveLocal').checked = options.SaveLocalMetadata;
|
||||
|
|
|
@ -55,6 +55,14 @@
|
|||
<div class="fieldDescription checkboxFieldDescription">${LabelEnableRealtimeMonitorHelp}</div>
|
||||
</div>
|
||||
|
||||
<div class="checkboxContainer checkboxContainer-withDescription advanced">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" class="chkEnableLUFSScan" checked />
|
||||
<span>${LabelEnableLUFSScan}</span>
|
||||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">${LabelEnableLUFSScanHelp}</div>
|
||||
</div>
|
||||
|
||||
<div class="checkboxContainer checkboxContainer-withDescription chkAutomaticallyAddToCollectionContainer hide advanced">
|
||||
<label>
|
||||
<input is="emby-checkbox" type="checkbox" id="chkAutomaticallyAddToCollection" />
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<form class="addLibraryForm" style="max-width:100%;">
|
||||
<div class="formDialogHeader">
|
||||
<button type="button" is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1" title="${ButtonBack}"><span class="material-icons arrow_back" aria-hidden="true"></span></button>
|
||||
<h3 class="formDialogHeaderTitle">${ButtonAddMediaLibrary}</h3>
|
||||
</div>
|
||||
<div class="formDialogHeader">
|
||||
<button type="button" is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1" title="${ButtonBack}"><span class="material-icons arrow_back" aria-hidden="true"></span></button>
|
||||
<h3 class="formDialogHeaderTitle">${ButtonAddMediaLibrary}</h3>
|
||||
</div>
|
||||
|
||||
<div class="formDialogContent scrollY" style="padding-top:2em;">
|
||||
<div class="dialogContentInner dialog-content-centered">
|
||||
<div class="formDialogContent scrollY" style="padding-top:2em;">
|
||||
<div class="dialogContentInner dialog-content-centered">
|
||||
<form class="addLibraryForm" style="max-width:100%;">
|
||||
|
||||
<div id="fldCollectionType" class="selectContainer">
|
||||
<select is="emby-select" id="selectCollectionType" data-mini="true" required="required" label="${LabelContentType}"></select>
|
||||
|
@ -28,12 +28,12 @@
|
|||
</div>
|
||||
|
||||
<div class="libraryOptions"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="formDialogFooter">
|
||||
<button is="emby-button" type="submit" class="raised btnSubmit button-submit block formDialogFooterItem">
|
||||
<span>${ButtonOk}</span>
|
||||
</button>
|
||||
<div class="formDialogFooter">
|
||||
<button is="emby-button" type="submit" class="raised btnSubmit button-submit block formDialogFooterItem">
|
||||
<span>${ButtonOk}</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
38
src/components/playback/displayMirrorManager.ts
Normal file
38
src/components/playback/displayMirrorManager.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client';
|
||||
|
||||
import { playbackManager } from './playbackmanager';
|
||||
|
||||
interface PlaybackInfo {
|
||||
item: BaseItemDto;
|
||||
context?: string;
|
||||
}
|
||||
|
||||
function mirrorItem(info: PlaybackInfo, player?: unknown) {
|
||||
const { item } = info;
|
||||
|
||||
playbackManager.displayContent({
|
||||
ItemName: item.Name,
|
||||
ItemId: item.Id,
|
||||
ItemType: item.Type,
|
||||
Context: info.context
|
||||
}, player);
|
||||
}
|
||||
|
||||
function mirrorIfEnabled(info: PlaybackInfo) {
|
||||
if (info && playbackManager.enableDisplayMirroring()) {
|
||||
const playerInfo = playbackManager.getPlayerInfo();
|
||||
|
||||
if (playerInfo && !playerInfo.isLocalPlayer && playerInfo.supportedCommands.indexOf('DisplayContent') !== -1) {
|
||||
mirrorItem(info, playbackManager.getCurrentPlayer());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('viewshow', e => {
|
||||
const state = e.detail.state || {};
|
||||
const { item } = state;
|
||||
|
||||
if (item?.ServerId) {
|
||||
mirrorIfEnabled({ item });
|
||||
}
|
||||
});
|
|
@ -2123,7 +2123,7 @@ class PlaybackManager {
|
|||
const getAdditionalParts = async (items) => {
|
||||
const getOneAdditionalPart = async function (item) {
|
||||
let retVal = [item];
|
||||
if (item.Type === 'Movie') {
|
||||
if (item.Type === 'Movie' || item.Type === 'Episode') {
|
||||
const client = ServerConnections.getApiClient(item.ServerId);
|
||||
const user = await client.getCurrentUser();
|
||||
const additionalParts = await client.getAdditionalVideoParts(user.Id, item.Id);
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import appSettings from '../../scripts/settings/appSettings';
|
||||
import Events from '../../utils/events.ts';
|
||||
import browser from '../../scripts/browser';
|
||||
import loading from '../loading/loading';
|
||||
|
@ -13,32 +12,6 @@ import '../../elements/emby-button/emby-button';
|
|||
import dialog from '../dialog/dialog';
|
||||
import dialogHelper from '../dialogHelper/dialogHelper';
|
||||
|
||||
function mirrorItem(info, player) {
|
||||
const item = info.item;
|
||||
|
||||
playbackManager.displayContent({
|
||||
|
||||
ItemName: item.Name,
|
||||
ItemId: item.Id,
|
||||
ItemType: item.Type,
|
||||
Context: info.context
|
||||
}, player);
|
||||
}
|
||||
|
||||
function mirrorIfEnabled(info) {
|
||||
if (info && playbackManager.enableDisplayMirroring()) {
|
||||
const getPlayerInfo = playbackManager.getPlayerInfo();
|
||||
|
||||
if (getPlayerInfo && !getPlayerInfo.isLocalPlayer && getPlayerInfo.supportedCommands.indexOf('DisplayContent') !== -1) {
|
||||
mirrorItem(info, playbackManager.getCurrentPlayer());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function emptyCallback() {
|
||||
// avoid console logs about uncaught promises
|
||||
}
|
||||
|
||||
function getTargetSecondaryText(target) {
|
||||
if (target.user) {
|
||||
return target.user.Name;
|
||||
|
@ -140,10 +113,14 @@ export function show(button) {
|
|||
})[0];
|
||||
|
||||
playbackManager.trySetActivePlayer(target.playerName, target);
|
||||
|
||||
mirrorIfEnabled();
|
||||
}, emptyCallback);
|
||||
}).catch(() => {
|
||||
// action sheet closed
|
||||
});
|
||||
}).catch(err => {
|
||||
console.error('[playerSelectionMenu] failed to import action sheet', err);
|
||||
});
|
||||
}).catch(err => {
|
||||
console.error('[playerSelectionMenu] failed to get playback targets', err);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -180,6 +157,8 @@ function disconnectFromPlayer(currentDeviceName) {
|
|||
default:
|
||||
break;
|
||||
}
|
||||
}).catch(() => {
|
||||
// dialog closed
|
||||
});
|
||||
} else {
|
||||
playbackManager.setDefaultPlayerActive();
|
||||
|
@ -272,11 +251,13 @@ function showActivePlayerMenuInternal(playerInfo) {
|
|||
|
||||
dialogHelper.open(dlg).then(function () {
|
||||
if (destination === 'nowplaying') {
|
||||
appRouter.showNowPlaying();
|
||||
return appRouter.showNowPlaying();
|
||||
} else if (destination === 'disconnectFromPlayer') {
|
||||
disconnectFromPlayer(currentDeviceName);
|
||||
}
|
||||
}, emptyCallback);
|
||||
}).catch(() => {
|
||||
// dialog closed
|
||||
});
|
||||
}
|
||||
|
||||
function onMirrorChange() {
|
||||
|
@ -287,23 +268,6 @@ function onAutoCastChange() {
|
|||
enable(this.checked);
|
||||
}
|
||||
|
||||
document.addEventListener('viewshow', function (e) {
|
||||
const state = e.detail.state || {};
|
||||
const item = state.item;
|
||||
|
||||
if (item && item.ServerId) {
|
||||
mirrorIfEnabled({
|
||||
item: item
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Events.on(appSettings, 'change', function (e, name) {
|
||||
if (name === 'displaymirror') {
|
||||
mirrorIfEnabled();
|
||||
}
|
||||
});
|
||||
|
||||
Events.on(playbackManager, 'pairing', function () {
|
||||
loading.show();
|
||||
});
|
||||
|
|
|
@ -173,6 +173,7 @@ function loadForm(context, user, userSettings, apiClient) {
|
|||
context.querySelector('.chkPlayDefaultAudioTrack').checked = user.Configuration.PlayDefaultAudioTrack || false;
|
||||
context.querySelector('.chkPreferFmp4HlsContainer').checked = userSettings.preferFmp4HlsContainer();
|
||||
context.querySelector('.chkEnableCinemaMode').checked = userSettings.enableCinemaMode();
|
||||
context.querySelector('.chkEnableAudioNormalization').checked = userSettings.enableAudioNormalization();
|
||||
context.querySelector('.chkEnableNextVideoOverlay').checked = userSettings.enableNextVideoInfoOverlay();
|
||||
context.querySelector('.chkRememberAudioSelections').checked = user.Configuration.RememberAudioSelections || false;
|
||||
context.querySelector('.chkRememberSubtitleSelections').checked = user.Configuration.RememberSubtitleSelections || false;
|
||||
|
@ -217,7 +218,7 @@ function saveUser(context, user, userSettingsInstance, apiClient) {
|
|||
user.Configuration.EnableNextEpisodeAutoPlay = context.querySelector('.chkEpisodeAutoPlay').checked;
|
||||
userSettingsInstance.preferFmp4HlsContainer(context.querySelector('.chkPreferFmp4HlsContainer').checked);
|
||||
userSettingsInstance.enableCinemaMode(context.querySelector('.chkEnableCinemaMode').checked);
|
||||
|
||||
userSettingsInstance.enableAudioNormalization(context.querySelector('.chkEnableAudioNormalization').checked);
|
||||
userSettingsInstance.enableNextVideoInfoOverlay(context.querySelector('.chkEnableNextVideoOverlay').checked);
|
||||
user.Configuration.RememberAudioSelections = context.querySelector('.chkRememberAudioSelections').checked;
|
||||
user.Configuration.RememberSubtitleSelections = context.querySelector('.chkRememberSubtitleSelections').checked;
|
||||
|
|
|
@ -72,6 +72,14 @@
|
|||
${TabAdvanced}
|
||||
</h2>
|
||||
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" class="chkEnableAudioNormalization" />
|
||||
<span>${EnableAudioNormalization}</span>
|
||||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">${EnableAudioNormalizationHelp}</div>
|
||||
</div>
|
||||
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" class="chkPreferFmp4HlsContainer" />
|
||||
|
@ -102,7 +110,7 @@
|
|||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">${RememberAudioSelectionsHelp}</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" class="chkRememberSubtitleSelections" />
|
||||
|
|
|
@ -40,13 +40,13 @@ function getTextStyles(settings, preview) {
|
|||
|
||||
switch (settings.dropShadow || '') {
|
||||
case 'raised':
|
||||
list.push({ name: 'text-shadow', value: '-1px -1px white, 0px -1px white, -1px 0px white, 1px 1px black, 0px 1px black, 1px 0px black' });
|
||||
list.push({ name: 'text-shadow', value: '-0.04em -0.04em #fff, 0px -0.04em #fff, -0.04em 0px #fff, 0.04em 0.04em #000, 0px 0.04em #000, 0.04em 0px #000' });
|
||||
break;
|
||||
case 'depressed':
|
||||
list.push({ name: 'text-shadow', value: '1px 1px white, 0px 1px white, 1px 0px white, -1px -1px black, 0px -1px black, -1px 0px black' });
|
||||
list.push({ name: 'text-shadow', value: '0.04em 0.04em #fff, 0px 0.04em #fff, 0.04em 0px #fff, -0.04em -0.04em #000, 0px -0.04em #000, -0.04em 0px #000' });
|
||||
break;
|
||||
case 'uniform':
|
||||
list.push({ name: 'text-shadow', value: '-1px 0px #000000, 0px 1px #000000, 1px 0px #000000, 0px -1px #000000' });
|
||||
list.push({ name: 'text-shadow', value: '#000 0px 0.03em, #000 0px -0.03em, #000 0px 0.05em, #000 0px -0.05em, #000 0.03em 0px, #000 -0.03em 0px, #000 0.03em 0.03em, #000 -0.03em 0.03em, #000 0.03em -0.03em, #000 -0.03em -0.03em, #000 0.03em 0.05em, #000 -0.03em 0.05em, #000 0.03em -0.05em, #000 -0.03em -0.05em, #000 0.05em 0px, #000 -0.05em 0px, #000 0.05em 0.03em, #000 -0.05em 0.03em, #000 0.05em -0.03em, #000 -0.05em -0.03em' });
|
||||
break;
|
||||
case 'none':
|
||||
list.push({ name: 'text-shadow', value: 'none' });
|
||||
|
@ -93,7 +93,7 @@ function getTextStyles(settings, preview) {
|
|||
list.push({ name: 'font-variant', value: 'small-caps' });
|
||||
break;
|
||||
default:
|
||||
list.push({ name: 'font-family', value: 'inherit' });
|
||||
list.push({ name: 'font-family', value: '-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol' });
|
||||
list.push({ name: 'font-variant', value: 'none' });
|
||||
break;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue