mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Merge branch 'jellyfin:master' into master
This commit is contained in:
commit
13f55130a7
98 changed files with 5871 additions and 3582 deletions
|
@ -134,6 +134,7 @@
|
|||
<option value="reinhard">Reinhard</option>
|
||||
<option value="hable">Hable</option>
|
||||
<option value="mobius">Mobius</option>
|
||||
<option value="bt2390">BT.2390</option>
|
||||
</select>
|
||||
<div class="fieldDescription">
|
||||
<a is="emby-linkbutton" rel="noopener noreferrer" class="button-link" href="http://ffmpeg.org/ffmpeg-all.html#tonemap_005fopencl" target="_blank">${TonemappingAlgorithmHelp}</a>
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
<div id="itemDetailPage" data-role="page" class="page libraryPage itemDetailPage noSecondaryNavPage selfBackdropPage" data-backbutton="true">
|
||||
<div id="itemBackdrop" class="itemBackdrop">
|
||||
</div>
|
||||
<div id="itemBackdrop" class="itemBackdrop"></div>
|
||||
|
||||
<div class="detailLogo"></div>
|
||||
|
||||
<div class="detailPageWrapperContainer padded-bottom-page">
|
||||
<div class="detailPagePrimaryContainer padded-left padded-right">
|
||||
<div class="primaryImageWrapper hide">
|
||||
<img id="primaryImage" />
|
||||
</div>
|
||||
|
||||
<div class="infoWrapper infoText">
|
||||
<div class="infoWrapper">
|
||||
<div class="detailImageContainer padded-left"></div>
|
||||
<div class="nameContainer"></div>
|
||||
<div class="itemMiscInfo itemMiscInfo-primary" style="margin-bottom: 0.6em;"></div>
|
||||
<div class="itemMiscInfo itemMiscInfo-secondary" style="margin-bottom: 0.6em;"></div>
|
||||
|
@ -90,7 +87,6 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="detailPageSecondaryContainer">
|
||||
<div class="detailImageContainer padded-left"></div>
|
||||
<div class="detailPageContent">
|
||||
<div class="detailPagePrimaryContent padded-right">
|
||||
<div class="detailSection">
|
||||
|
|
|
@ -367,6 +367,14 @@ function reloadPlayButtons(page, item) {
|
|||
hideAll(page, 'btnShuffle');
|
||||
}
|
||||
|
||||
const btnResume = page.querySelector('.mainDetailButtons .btnResume');
|
||||
const btnPlay = page.querySelector('.mainDetailButtons .btnPlay');
|
||||
if (layoutManager.tv && !btnResume.classList.contains('hide')) {
|
||||
btnResume.classList.add('fab');
|
||||
} else if (layoutManager.tv && btnResume.classList.contains('hide')) {
|
||||
btnPlay.classList.add('fab');
|
||||
}
|
||||
|
||||
return canPlay;
|
||||
}
|
||||
|
||||
|
@ -552,14 +560,20 @@ function renderBackdrop(item) {
|
|||
}
|
||||
|
||||
function renderDetailPageBackdrop(page, item, apiClient) {
|
||||
// Details banner is disabled in user settings
|
||||
if (!userSettings.detailsBanner()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Disable item backdrop for books and people because they only have primary images
|
||||
if (item.Type === 'Person' || item.Type === 'Book') {
|
||||
return false;
|
||||
}
|
||||
|
||||
let imgUrl;
|
||||
let hasbackdrop = false;
|
||||
const itemBackdropElement = page.querySelector('#itemBackdrop');
|
||||
|
||||
if (layoutManager.mobile || !userSettings.detailsBanner()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (item.BackdropImageTags && item.BackdropImageTags.length) {
|
||||
imgUrl = apiClient.getScaledImageUrl(item.Id, {
|
||||
type: 'Backdrop',
|
||||
|
@ -593,24 +607,6 @@ function renderDetailPageBackdrop(page, item, apiClient) {
|
|||
return hasbackdrop;
|
||||
}
|
||||
|
||||
function renderPrimaryImage(page, item, apiClient) {
|
||||
if (item?.ImageTags?.Primary) {
|
||||
const imageUrl = apiClient.getScaledImageUrl(item.Id, {
|
||||
type: 'Primary',
|
||||
maxWidth: dom.getScreenWidth(),
|
||||
tag: item.ImageTags.Primary
|
||||
});
|
||||
|
||||
const imageElem = page.querySelector('#primaryImage');
|
||||
imageElem.src = imageUrl;
|
||||
imageElem.alt = item.Name;
|
||||
if (item.PrimaryImageAspectRatio === 1) {
|
||||
imageElem.classList.add('aspect-square');
|
||||
}
|
||||
page.querySelector('.primaryImageWrapper')?.classList.remove('hide');
|
||||
}
|
||||
}
|
||||
|
||||
function reloadFromItem(instance, page, params, item, user) {
|
||||
const apiClient = ServerConnections.getApiClient(item.ServerId);
|
||||
|
||||
|
@ -623,9 +619,7 @@ function reloadFromItem(instance, page, params, item, user) {
|
|||
renderLogo(page, item, apiClient);
|
||||
renderDetailPageBackdrop(page, item, apiClient);
|
||||
}
|
||||
if (layoutManager.mobile) {
|
||||
renderPrimaryImage(page, item, apiClient);
|
||||
}
|
||||
|
||||
renderBackdrop(item);
|
||||
|
||||
// Render the main information for the item
|
||||
|
@ -812,8 +806,8 @@ function renderDetailImage(elem, item, imageLoader) {
|
|||
overlayText: false,
|
||||
transition: false,
|
||||
disableIndicators: true,
|
||||
overlayPlayButton: true,
|
||||
action: 'play',
|
||||
overlayPlayButton: layoutManager.mobile ? false : true,
|
||||
action: layoutManager.mobile ? 'none' : 'play',
|
||||
width: dom.getWindowSize().innerWidth * 0.25
|
||||
});
|
||||
|
||||
|
@ -1216,11 +1210,9 @@ function renderMoreFromArtist(view, item, apiClient) {
|
|||
};
|
||||
|
||||
if (item.Type === 'MusicArtist') {
|
||||
query.ContributingArtistIds = item.Id;
|
||||
} else if (apiClient.isMinServerVersion('3.4.1.18')) {
|
||||
query.AlbumArtistIds = item.AlbumArtists[0].Id;
|
||||
query.AlbumArtistIds = item.Id;
|
||||
} else {
|
||||
query.ArtistIds = item.AlbumArtists[0].Id;
|
||||
query.AlbumArtistIds = item.AlbumArtists[0].Id;
|
||||
}
|
||||
|
||||
apiClient.getItems(apiClient.getCurrentUserId(), query).then(function (result) {
|
||||
|
@ -2063,16 +2055,6 @@ export default function (view, params) {
|
|||
function init() {
|
||||
const apiClient = getApiClient();
|
||||
|
||||
const btnResume = view.querySelector('.mainDetailButtons .btnResume');
|
||||
const btnPlay = view.querySelector('.mainDetailButtons .btnPlay');
|
||||
if (layoutManager.tv && !btnResume.classList.contains('hide')) {
|
||||
btnResume.classList.add('fab');
|
||||
btnResume.classList.add('detailFloatingButton');
|
||||
} else if (layoutManager.tv && btnResume.classList.contains('hide')) {
|
||||
btnPlay.classList.add('fab');
|
||||
btnPlay.classList.add('detailFloatingButton');
|
||||
}
|
||||
|
||||
view.querySelectorAll('.btnPlay');
|
||||
bindAll(view, '.btnPlay', 'click', onPlayClick);
|
||||
bindAll(view, '.btnResume', 'click', onPlayClick);
|
||||
|
|
|
@ -1115,7 +1115,11 @@ class ItemsView {
|
|||
let imageType = userSettings.get(basekey + '-imageType');
|
||||
|
||||
if (!imageType && params.type === 'nextup') {
|
||||
imageType = 'thumb';
|
||||
if (userSettings.useEpisodeImagesInNextUpAndResume()) {
|
||||
imageType = 'primary';
|
||||
} else {
|
||||
imageType = 'thumb';
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -313,8 +313,8 @@ import { appRouter } from '../../../components/appRouter';
|
|||
|
||||
function onPointerMove(e) {
|
||||
if ((e.pointerType || (layoutManager.mobile ? 'touch' : 'mouse')) === 'mouse') {
|
||||
const eventX = e.screenX || 0;
|
||||
const eventY = e.screenY || 0;
|
||||
const eventX = e.screenX || e.clientX || 0;
|
||||
const eventY = e.screenY || e.clientY || 0;
|
||||
const obj = lastPointerMoveData;
|
||||
|
||||
if (!obj) {
|
||||
|
@ -716,6 +716,7 @@ import { appRouter } from '../../../components/appRouter';
|
|||
} else {
|
||||
nowPlayingPositionSlider.value = 0;
|
||||
}
|
||||
|
||||
if (runtimeTicks && positionTicks != null && currentRuntimeTicks && !enableProgressByTimeOfDay && currentItem.RunTimeTicks && currentItem.Type !== 'Recording' && playbackRate !== null) {
|
||||
endsAtText.innerHTML = ' ' + mediaInfo.getEndsAtFromPosition(runtimeTicks, positionTicks, playbackRate, true);
|
||||
} else {
|
||||
|
@ -902,7 +903,8 @@ import { appRouter } from '../../../components/appRouter';
|
|||
actionsheet.show({
|
||||
items: menuItems,
|
||||
title: globalize.translate('Audio'),
|
||||
positionTo: positionTo
|
||||
positionTo: positionTo,
|
||||
enableHistory: false
|
||||
}).then(function (id) {
|
||||
const index = parseInt(id);
|
||||
|
||||
|
@ -948,7 +950,8 @@ import { appRouter } from '../../../components/appRouter';
|
|||
actionsheet.show({
|
||||
title: globalize.translate('Subtitles'),
|
||||
items: menuItems,
|
||||
positionTo: positionTo
|
||||
positionTo: positionTo,
|
||||
enableHistory: false
|
||||
}).then(function (id) {
|
||||
const index = parseInt(id);
|
||||
|
||||
|
|
|
@ -260,9 +260,9 @@ import cardBuilder from '../../../components/cardbuilder/cardBuilder';
|
|||
|
||||
const apiClient = getApiClient();
|
||||
|
||||
apiClient.getQuickConnect('Status')
|
||||
.then(status => {
|
||||
if (status !== 'Unavailable') {
|
||||
apiClient.getQuickConnect('Enabled')
|
||||
.then(enabled => {
|
||||
if (enabled === true) {
|
||||
view.querySelector('.btnQuick').classList.remove('hide');
|
||||
}
|
||||
})
|
||||
|
|
|
@ -119,6 +119,7 @@ import autoFocuser from '../../components/autoFocuser';
|
|||
cardBuilder.buildCards(result.Items, {
|
||||
itemsContainer: container,
|
||||
preferThumb: true,
|
||||
inheritThumb: !userSettings.useEpisodeImagesInNextUpAndResume(),
|
||||
shape: getThumbShape(),
|
||||
scalable: true,
|
||||
overlayPlayButton: true,
|
||||
|
@ -197,6 +198,7 @@ import autoFocuser from '../../components/autoFocuser';
|
|||
parentContainer: section,
|
||||
itemsContainer: container,
|
||||
preferThumb: true,
|
||||
inheritThumb: !userSettings.useEpisodeImagesInNextUpAndResume(),
|
||||
shape: 'backdrop',
|
||||
scalable: true,
|
||||
showTitle: true,
|
||||
|
|
|
@ -12,6 +12,15 @@
|
|||
</div>
|
||||
</a>
|
||||
|
||||
<a is="emby-linkbutton" data-ripple="false" href="#" style="display:block;padding:0;margin:0;" class="lnkQuickConnectPreferences listItem-border hide">
|
||||
<div class="listItem">
|
||||
<em class="material-icons listItemIcon listItemIcon-transparent">tap_and_play</em>
|
||||
<div class="listItemBody">
|
||||
<div class="listItemBodyText">${QuickConnect}</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a is="emby-linkbutton" data-ripple="false" href="#" style="display:block;padding:0;margin:0;" class="lnkDisplayPreferences listItem-border">
|
||||
<div class="listItem">
|
||||
<span class="material-icons listItemIcon listItemIcon-transparent tv"></span>
|
||||
|
@ -48,16 +57,6 @@
|
|||
</div>
|
||||
</a>
|
||||
|
||||
|
||||
<a is="emby-linkbutton" data-ripple="false" href="#" style="display:block;padding:0;margin:0;" class="lnkQuickConnectPreferences listItem-border hide">
|
||||
<div class="listItem">
|
||||
<em class="material-icons listItemIcon listItemIcon-transparent">tap_and_play</em>
|
||||
<div class="listItemBody">
|
||||
<div class="listItemBodyText">${QuickConnect}</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a is="emby-linkbutton" data-ripple="false" href="#" style="display:block;padding:0;margin:0;" class="clientSettings listItem-border">
|
||||
<div class="listItem">
|
||||
<span class="material-icons listItemIcon listItemIcon-transparent devices_other"></span>
|
||||
|
|
|
@ -38,16 +38,15 @@ export default function (view, params) {
|
|||
|
||||
page.querySelector('.lnkControlsPreferences').classList.toggle('hide', layoutManager.mobile);
|
||||
|
||||
ApiClient.getQuickConnect('Status')
|
||||
.then(status => {
|
||||
if (status !== 'Unavailable') {
|
||||
ApiClient.getQuickConnect('Enabled')
|
||||
.then(enabled => {
|
||||
if (enabled === true) {
|
||||
page.querySelector('.lnkQuickConnectPreferences').classList.remove('hide');
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
console.debug('Failed to get QuickConnect status');
|
||||
});
|
||||
|
||||
ApiClient.getUser(userId).then(function (user) {
|
||||
page.querySelector('.headerUsername').innerHTML = user.Name;
|
||||
if (user.Policy.IsAdministrator && !layoutManager.tv) {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import globalize from '../../../scripts/globalize';
|
||||
import toast from '../../../components/toast/toast';
|
||||
import Dashboard from '../../../scripts/clientUtils';
|
||||
|
||||
export const authorize = (code) => {
|
||||
const url = ApiClient.getUrl('/QuickConnect/Authorize?Code=' + code);
|
||||
|
@ -16,22 +15,3 @@ export const authorize = (code) => {
|
|||
// prevent bubbling
|
||||
return false;
|
||||
};
|
||||
|
||||
export const activate = () => {
|
||||
const url = ApiClient.getUrl('/QuickConnect/Activate');
|
||||
return ApiClient.ajax({
|
||||
type: 'POST',
|
||||
url: url
|
||||
}).then(() => {
|
||||
toast(globalize.translate('QuickConnectActivationSuccessful'));
|
||||
return true;
|
||||
}).catch((e) => {
|
||||
console.error('Error activating quick connect. Error:', e);
|
||||
Dashboard.alert({
|
||||
title: globalize.translate('HeaderError'),
|
||||
message: globalize.translate('DefaultErrorMessage')
|
||||
});
|
||||
|
||||
throw e;
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,19 +1,15 @@
|
|||
<div id="quickConnectPreferencesPage" data-role="page" class="page libraryPage userPreferencesPage noSecondaryNavPage" data-title="${QuickConnect}" data-backbutton="true" style="margin: 0 auto; max-width: 54em">
|
||||
<div class="settingsContainer padded-left padded-right padded-bottom-page">
|
||||
<button is="emby-button" id="btnQuickConnectActivate" type="button" class="raised button-submit block">
|
||||
<span>${ButtonActivate}</span>
|
||||
</button>
|
||||
|
||||
<form class="quickConnectSettingsContainer">
|
||||
<div style="margin-bottom: 1em">
|
||||
${QuickConnectDescription}
|
||||
</div>
|
||||
<form class="quickConnectSettingsContainer">
|
||||
<div class="verticalSection">
|
||||
<h2 class="sectionTitle">${QuickConnect}</h2>
|
||||
<div>${QuickConnectDescription}</div>
|
||||
<br />
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" type="number" min="0" max="999999" required id="txtQuickConnectCode" label="${LabelQuickConnectCode}" autocomplete="off" />
|
||||
</div>
|
||||
<button id="btnQuickConnectAuthorize" is="emby-button" type="submit" class="raised button-submit block">
|
||||
<span>${Authorize}</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { activate, authorize } from './helper';
|
||||
import { authorize } from './helper';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
import toast from '../../../components/toast/toast';
|
||||
|
||||
|
@ -6,52 +6,16 @@ export default function (view) {
|
|||
view.addEventListener('viewshow', function () {
|
||||
const codeElement = view.querySelector('#txtQuickConnectCode');
|
||||
|
||||
view.querySelector('#btnQuickConnectActivate').addEventListener('click', () => {
|
||||
activate().then(() => {
|
||||
renderPage();
|
||||
});
|
||||
});
|
||||
view.querySelector('.quickConnectSettingsContainer').addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
view.querySelector('#btnQuickConnectAuthorize').addEventListener('click', () => {
|
||||
if (!codeElement.validity.valid) {
|
||||
toast(globalize.translate('QuickConnectInvalidCode'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const code = codeElement.value;
|
||||
authorize(code);
|
||||
authorize(codeElement.value);
|
||||
});
|
||||
|
||||
view.querySelector('.quickConnectSettingsContainer').addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
renderPage();
|
||||
});
|
||||
|
||||
function renderPage(forceActive = false) {
|
||||
ApiClient.getQuickConnect('Status').then((status) => {
|
||||
const btn = view.querySelector('#btnQuickConnectActivate');
|
||||
const container = view.querySelector('.quickConnectSettingsContainer');
|
||||
|
||||
// The activation button should only be visible when quick connect is unavailable (with the text replaced with an error) or when it is available (so it can be activated)
|
||||
// The authorization container is only usable when quick connect is active, so it should be hidden otherwise
|
||||
container.style.display = 'none';
|
||||
|
||||
if (status === 'Unavailable') {
|
||||
btn.textContent = globalize.translate('QuickConnectNotAvailable');
|
||||
btn.disabled = true;
|
||||
btn.classList.remove('button-submit');
|
||||
btn.classList.add('button');
|
||||
} else if (status === 'Active' || forceActive) {
|
||||
container.style.display = '';
|
||||
btn.style.display = 'none';
|
||||
}
|
||||
|
||||
return true;
|
||||
}).catch((e) => {
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue