1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

Merge remote-tracking branch 'upstream/master' into details-redux

This commit is contained in:
MrTimscampi 2020-06-23 10:35:38 +02:00
commit 9c4a602979
81 changed files with 2690 additions and 754 deletions

View file

@ -192,9 +192,14 @@ button::-moz-focus-inner {
/* Needed in case this is a button */
display: block;
/* Needed in case this is a button */
margin: 0 !important;
border: 0 !important;
padding: 0 !important;
cursor: pointer;
color: inherit;
width: 100%;
font-family: inherit;
font-size: inherit;
/* Needed in safari */
height: 100%;
@ -203,19 +208,12 @@ button::-moz-focus-inner {
contain: strict;
}
.cardContent-button {
border: 0 !important;
padding: 0 !important;
cursor: pointer;
color: inherit;
width: 100%;
vertical-align: middle;
font-family: inherit;
font-size: inherit;
.cardContent:not(.defaultCardBackground) {
background-color: transparent;
}
.cardContent-button:not(.defaultCardBackground) {
background-color: transparent;
.cardBox:not(.visualCardBox) .cardPadder {
background-color: #242424;
}
.visualCardBox .cardContent {
@ -223,7 +221,8 @@ button::-moz-focus-inner {
border-bottom-right-radius: 0;
}
.cardContent-shadow {
.cardContent-shadow,
.cardBox:not(.visualCardBox) .cardPadder {
box-shadow: 0 0.0725em 0.29em 0 rgba(0, 0, 0, 0.37);
}

View file

@ -368,9 +368,7 @@ import 'programStyles';
let apiClient;
let lastServerId;
for (let i = 0; i < items.length; i++) {
let item = items[i];
for (const [i, item] of items.entries()) {
let serverId = item.ServerId || options.serverId;
if (serverId !== lastServerId) {
@ -541,7 +539,7 @@ import 'programStyles';
imgType = 'Backdrop';
imgTag = item.ParentBackdropImageTags[0];
itemId = item.ParentBackdropItemId;
} else if (item.ImageTags && item.ImageTags.Primary) {
} else if (item.ImageTags && item.ImageTags.Primary && (item.Type !== 'Episode' || item.ChildCount !== 0)) {
imgType = 'Primary';
imgTag = item.ImageTags.Primary;
height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null;
@ -556,7 +554,10 @@ import 'programStyles';
coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2;
}
}
} else if (item.SeriesPrimaryImageTag) {
imgType = 'Primary';
imgTag = item.SeriesPrimaryImageTag;
itemId = item.SeriesId;
} else if (item.PrimaryImageTag) {
imgType = 'Primary';
imgTag = item.PrimaryImageTag;
@ -577,10 +578,6 @@ import 'programStyles';
imgType = 'Primary';
imgTag = item.ParentPrimaryImageTag;
itemId = item.ParentPrimaryImageItemId;
} else if (item.SeriesPrimaryImageTag) {
imgType = 'Primary';
imgTag = item.SeriesPrimaryImageTag;
itemId = item.SeriesId;
} else if (item.AlbumId && item.AlbumPrimaryImageTag) {
imgType = 'Primary';
imgTag = item.AlbumPrimaryImageTag;
@ -1370,9 +1367,6 @@ import 'programStyles';
let cardScalableClose = '';
let cardContentClass = 'cardContent';
if (!options.cardLayout) {
cardContentClass += ' cardContent-shadow';
}
let blurhashAttrib = '';
if (blurhash && blurhash.length > 0) {
@ -1386,14 +1380,14 @@ import 'programStyles';
cardImageContainerClose = '</div>';
} else {
// Don't use the IMG tag with safari because it puts a white border around it
cardImageContainerOpen = imgUrl ? ('<button data-action="' + action + '" class="cardContent-button ' + cardImageContainerClass + ' ' + cardContentClass + ' itemAction lazy" data-src="' + imgUrl + '" ' + blurhashAttrib + '>') : ('<button data-action="' + action + '" class="cardContent-button ' + cardImageContainerClass + ' ' + cardContentClass + ' itemAction">');
cardImageContainerOpen = imgUrl ? ('<button data-action="' + action + '" class="' + cardImageContainerClass + ' ' + cardContentClass + ' itemAction lazy" data-src="' + imgUrl + '" ' + blurhashAttrib + '>') : ('<button data-action="' + action + '" class="' + cardImageContainerClass + ' ' + cardContentClass + ' itemAction">');
cardImageContainerClose = '</button>';
}
let cardScalableClass = 'cardScalable';
cardImageContainerOpen = '<div class="' + cardBoxClass + '"><div class="' + cardScalableClass + '"><div class="cardPadder-' + shape + '"></div>' + cardImageContainerOpen;
cardImageContainerOpen = '<div class="' + cardBoxClass + '"><div class="' + cardScalableClass + '"><div class="cardPadder cardPadder-' + shape + '"></div>' + cardImageContainerOpen;
cardBoxClose = '</div>';
cardScalableClose = '</div>';

View file

@ -4,12 +4,8 @@
<div class="formDialogContent smoothScrollY">
<div class="dialogContentInner dialog-content-centered" style="padding-top:1em;padding-bottom: 1em; text-align: center;">
<div class="text">
</div>
<div class="text"></div>
</div>
</div>
<div class="formDialogFooter formDialogFooter-clear formDialogFooter-flex" style="padding-bottom: 1.5em;">
</div>
<div class="formDialogFooter formDialogFooter-clear formDialogFooter-flex" style="margin:1em"></div>

View file

@ -55,7 +55,7 @@
/* Without this emby-checkbox is able to appear on top */
z-index: 1;
align-items: flex-end;
justify-content: flex-end;
justify-content: center;
flex-wrap: wrap;
}

View file

@ -168,7 +168,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
}
function getPortraitShape() {
return enableScrollX() ? 'autooverflow' : 'auto';
return enableScrollX() ? 'overflowPortrait' : 'portrait';
}
function getLibraryButtonsHtml(items) {
@ -254,7 +254,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
return cardBuilder.getCardsHtml({
items: items,
shape: shape,
preferThumb: viewType !== 'movies' && itemType !== 'Channel' && viewType !== 'music' ? 'auto' : null,
preferThumb: viewType !== 'movies' && viewType !== 'tvshows' && itemType !== 'Channel' && viewType !== 'music' ? 'auto' : null,
showUnplayedIndicator: false,
showChildCountIndicator: true,
context: 'home',

View file

@ -20,7 +20,7 @@
</div>
<div>
<div class="imageEditor-dropZone fieldDescription">
<div class="dropImageText">${LabelDropImageHere}</div>
<div id="dropImageText">${LabelDropImageHere}</div>
<output id="imageOutput" class="flex align-items-center justify-content-center" style="position: absolute;top:0;left:0;right:0;bottom:0;width:100%;"></output>
<input type="file" accept="image/*" id="uploadImage" name="uploadImage" style="position: absolute;top:0;left:0;right:0;bottom:0;width:100%;opacity:0;" />
</div>

View file

@ -1,11 +1,11 @@
.lazy-image-fadein {
opacity: 1;
transition: opacity 0.7s;
transition: opacity 0.5s;
}
.lazy-image-fadein-fast {
opacity: 1;
transition: opacity 0.2s;
transition: opacity 0.1s;
}
.lazy-hidden {

View file

@ -403,7 +403,7 @@ define(['apphost', 'globalize', 'connectionManager', 'itemHelper', 'appRouter',
break;
case 'moremediainfo':
require(['itemMediaInfo'], function (itemMediaInfo) {
itemMediaInfo.show(itemId, serverId).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id));
itemMediaInfo.show(itemId, serverId).then(getResolveFunction(resolve, id), getResolveFunction(resolve, id));
});
break;
case 'refresh':

View file

@ -11,9 +11,9 @@
(entries) => {
entries.forEach(entry => {
callback(entry);
},
{rootMargin: '50%'});
});
});
},
{rootMargin: '25%'});
this.observer = observer;
}

View file

@ -1,92 +0,0 @@
define(['loading', 'events', 'dialogHelper', 'dom', 'layoutManager', 'scrollHelper', 'globalize', 'require', 'material-icons', 'emby-button', 'paper-icon-button-light', 'emby-input', 'formDialogStyle', 'flexStyles'], function (loading, events, dialogHelper, dom, layoutManager, scrollHelper, globalize, require) {
'use strict';
function showDialog(instance, options, template) {
var dialogOptions = {
removeOnClose: true,
scrollY: false
};
var enableTvLayout = layoutManager.tv;
if (enableTvLayout) {
dialogOptions.size = 'fullscreen';
}
var dlg = dialogHelper.createDialog(dialogOptions);
var configuredButtons = [];
dlg.classList.add('formDialog');
dlg.innerHTML = globalize.translateHtml(template, 'core');
dlg.classList.add('align-items-center');
dlg.classList.add('justify-items-center');
var formDialogContent = dlg.querySelector('.formDialogContent');
formDialogContent.style['flex-grow'] = 'initial';
formDialogContent.style['max-width'] = '50%';
formDialogContent.style['max-height'] = '60%';
if (enableTvLayout) {
scrollHelper.centerFocus.on(formDialogContent, false);
dlg.querySelector('.formDialogHeader').style.marginTop = '15%';
} else {
dlg.classList.add('dialog-fullscreen-lowres');
}
//dlg.querySelector('.btnCancel').addEventListener('click', function (e) {
// dialogHelper.close(dlg);
//});
dlg.querySelector('.formDialogHeaderTitle').innerHTML = options.title;
dlg.querySelector('.text').innerHTML = options.text;
instance.dlg = dlg;
return dialogHelper.open(dlg).then(function () {
if (enableTvLayout) {
scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false);
}
loading.hide();
});
}
function LoadingDialog(options) {
this.options = options;
}
LoadingDialog.prototype.show = function () {
var instance = this;
loading.show();
return new Promise(function (resolve, reject) {
require(['text!./../dialog/dialog.template.html'], function (template) {
showDialog(instance, instance.options, template);
resolve();
});
});
};
LoadingDialog.prototype.setTitle = function (title) {
};
LoadingDialog.prototype.setText = function (text) {
};
LoadingDialog.prototype.hide = function () {
if (this.dlg) {
dialogHelper.close(this.dlg);
this.dlg = null;
}
};
LoadingDialog.prototype.destroy = function () {
this.dlg = null;
this.options = null;
};
return LoadingDialog;
});

View file

@ -308,8 +308,8 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi
}
});
context.removeEventListener('submit', onEditorClick);
context.addEventListener('submit', onEditorClick);
context.removeEventListener('click', onEditorClick);
context.addEventListener('click', onEditorClick);
var form = context.querySelector('form');
form.removeEventListener('submit', onSubmit);

View file

@ -7,7 +7,6 @@
<div class="formDialogContent smoothScrollY" style="padding-top:2em;">
<form class="popupEditPersonForm dialogContentInner dialog-content-centered">
<div class="inputContainer">
<input type="text" is="emby-input" class="txtPersonName" required="required" label="${LabelName}" />
</div>
@ -23,6 +22,7 @@
<option value="Writer">${Writer}</option>
</select>
</div>
<div class="inputContainer fldRole hide">
<input is="emby-input" type="text" class="txtPersonRole" label="${LabelPersonRole}" />
<div class="fieldDescription">${LabelPersonRoleHelp}</div>
@ -33,6 +33,5 @@
<span>${Save}</span>
</button>
</div>
</form>
</div>

View file

@ -241,6 +241,15 @@ import connectionManager from 'connectionManager';
navigator.mediaSession.setActionHandler('seekforward', function () {
execute('fastForward');
});
/* eslint-disable-next-line compat/compat */
navigator.mediaSession.setActionHandler('seekto', function (object) {
let item = playbackManager.getPlayerState(currentPlayer).NowPlayingItem;
// Convert to ms
let duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0);
let wantedTime = object.seekTime * 1000;
playbackManager.seekPercent(wantedTime / duration * 100, currentPlayer);
});
}
events.on(playbackManager, 'playerchange', function () {

View file

@ -2,12 +2,12 @@
<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1">
<span class="material-icons arrow_back"></span>
</button>
<h3 class="formDialogHeaderTitle"></h3>
</div>
<div class="formDialogContent smoothScrollY">
<div class="dialogContentInner dialog-content-centered" style="padding-top:2em;">
<form>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtInput" label="" />
@ -19,7 +19,6 @@
<span class="submitText"></span>
</button>
</div>
</form>
</div>
</div>

View file

@ -196,7 +196,7 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
context.querySelector('.nowPlayingPageImage').classList.remove('nowPlayingPageImageAudio');
}
} else {
imgContainer.innerHTML = '<div class="nowPlayingPageImageContainerNoAlbum"><button data-action="link" class="cardContent-button cardImageContainer coveredImage ' + cardBuilder.getDefaultBackgroundClass(item.Name) + ' cardContent cardContent-shadow itemAction"><span class="cardImageIcon material-icons album"></span></button></div>';
imgContainer.innerHTML = '<div class="nowPlayingPageImageContainerNoAlbum"><button data-action="link" class="cardImageContainer coveredImage ' + cardBuilder.getDefaultBackgroundClass(item.Name) + ' cardContent cardContent-shadow itemAction"><span class="cardImageIcon material-icons album"></span></button></div>';
}
}

View file

@ -1,158 +0,0 @@
define(['loading', 'events', 'dialogHelper', 'dom', 'layoutManager', 'scrollHelper', 'globalize', 'require', 'material-icons', 'emby-button', 'paper-icon-button-light', 'emby-input', 'formDialogStyle', 'flexStyles'], function (loading, events, dialogHelper, dom, layoutManager, scrollHelper, globalize, require) {
'use strict';
var currentApiClient;
var currentDlg;
var currentInstance;
function reloadPageWhenServerAvailable(retryCount) {
var apiClient = currentApiClient;
if (!apiClient) {
return;
}
// Don't use apiclient method because we don't want it reporting authentication under the old version
apiClient.getJSON(apiClient.getUrl('System/Info')).then(function (info) {
// If this is back to false, the restart completed
if (!info.IsShuttingDown) {
currentInstance.restarted = true;
dialogHelper.close(currentDlg);
} else {
retryReload(retryCount);
}
}, function () {
retryReload(retryCount);
});
}
function retryReload(retryCount) {
setTimeout(function () {
retryCount = retryCount || 0;
retryCount++;
if (retryCount < 150) {
reloadPageWhenServerAvailable(retryCount);
}
}, 500);
}
function startRestart(instance, apiClient, dlg) {
currentApiClient = apiClient;
currentDlg = dlg;
currentInstance = instance;
apiClient.restartServer().then(function () {
setTimeout(reloadPageWhenServerAvailable, 250);
});
}
function showDialog(instance, options, template) {
var dialogOptions = {
removeOnClose: true,
scrollY: false
};
var enableTvLayout = layoutManager.tv;
if (enableTvLayout) {
dialogOptions.size = 'fullscreen';
}
var dlg = dialogHelper.createDialog(dialogOptions);
var configuredButtons = [];
dlg.classList.add('formDialog');
dlg.innerHTML = globalize.translateHtml(template, 'core');
dlg.classList.add('align-items-center');
dlg.classList.add('justify-items-center');
var formDialogContent = dlg.querySelector('.formDialogContent');
formDialogContent.style['flex-grow'] = 'initial';
if (enableTvLayout) {
formDialogContent.style['max-width'] = '50%';
formDialogContent.style['max-height'] = '60%';
scrollHelper.centerFocus.on(formDialogContent, false);
} else {
formDialogContent.style.maxWidth = (Math.min((configuredButtons.length * 150) + 200, dom.getWindowSize().innerWidth - 50)) + 'px';
dlg.classList.add('dialog-fullscreen-lowres');
}
dlg.querySelector('.formDialogHeaderTitle').innerHTML = globalize.translate('HeaderRestartingServer');
dlg.querySelector('.text').innerHTML = globalize.translate('RestartPleaseWaitMessage');
var i;
var length;
var html = '';
for (i = 0, length = configuredButtons.length; i < length; i++) {
var item = configuredButtons[i];
var autoFocus = i === 0 ? ' autofocus' : '';
var buttonClass = 'btnOption raised formDialogFooterItem formDialogFooterItem-autosize';
if (item.type) {
buttonClass += ' button-' + item.type;
}
html += '<button is="emby-button" type="button" class="' + buttonClass + '" data-id="' + item.id + '"' + autoFocus + '>' + item.name + '</button>';
}
dlg.querySelector('.formDialogFooter').innerHTML = html;
function onButtonClick() {
dialogHelper.close(dlg);
}
var buttons = dlg.querySelectorAll('.btnOption');
for (i = 0, length = buttons.length; i < length; i++) {
buttons[i].addEventListener('click', onButtonClick);
}
var dlgPromise = dialogHelper.open(dlg);
startRestart(instance, options.apiClient, dlg);
return dlgPromise.then(function () {
if (enableTvLayout) {
scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false);
}
instance.destroy();
loading.hide();
if (instance.restarted) {
events.trigger(instance, 'restarted');
}
});
}
function ServerRestartDialog(options) {
this.options = options;
}
ServerRestartDialog.prototype.show = function () {
var instance = this;
loading.show();
return new Promise(function (resolve, reject) {
require(['text!./../dialog/dialog.template.html'], function (template) {
showDialog(instance, instance.options, template).then(resolve, reject);
});
});
};
ServerRestartDialog.prototype.destroy = function () {
currentApiClient = null;
currentDlg = null;
currentInstance = null;
this.options = null;
};
return ServerRestartDialog;
});

View file

@ -2,9 +2,20 @@
* Image viewer component
* @module components/slideshow/slideshow
*/
define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'focusManager', 'browser', 'apphost', 'css!./style', 'material-icons', 'paper-icon-button-light'], function (dialogHelper, inputManager, connectionManager, layoutManager, focusManager, browser, appHost) {
define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'focusManager', 'browser', 'apphost', 'dom', 'css!./style', 'material-icons', 'paper-icon-button-light'], function (dialogHelper, inputManager, connectionManager, layoutManager, focusManager, browser, appHost, dom) {
'use strict';
/**
* Name of transition event.
*/
const transitionEndEventName = dom.whichTransitionEvent();
/**
* Flag to use fake image to fix blurry zoomed image.
* At least WebKit doesn't restore quality for zoomed images.
*/
const useFakeZoomImage = browser.safari;
/**
* Retrieves an item's image URL from the API.
* @param {object|string} item - Item used to generate the image URL.
@ -240,6 +251,41 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
}
}
/**
* Handles zoom changes.
*/
function onZoomChange(scale, imageEl, slideEl) {
const zoomImage = slideEl.querySelector('.swiper-zoom-fakeimg');
if (zoomImage) {
zoomImage.style.width = zoomImage.style.height = scale * 100 + '%';
if (scale > 1) {
if (zoomImage.classList.contains('swiper-zoom-fakeimg-hidden')) {
// Await for Swiper style changes
setTimeout(() => {
const callback = () => {
imageEl.removeEventListener(transitionEndEventName, callback);
zoomImage.classList.remove('swiper-zoom-fakeimg-hidden');
};
// Swiper set 'transition-duration: 300ms' for auto zoom
// and 'transition-duration: 0s' for touch zoom
const transitionDuration = parseFloat(imageEl.style.transitionDuration.replace(/[a-z]/i, ''));
if (transitionDuration > 0) {
imageEl.addEventListener(transitionEndEventName, callback);
} else {
callback();
}
}, 0);
}
} else {
zoomImage.classList.add('swiper-zoom-fakeimg-hidden');
}
}
}
/**
* Initializes the Swiper instance and binds the relevant events.
* @param {HTMLElement} dialog - Element containing the dialog.
@ -260,8 +306,7 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
loop: false,
zoom: {
minRatio: 1,
toggle: true,
containerClass: 'slider-zoom-container'
toggle: true
},
autoplay: !options.interactive,
keyboard: {
@ -288,6 +333,10 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
swiperInstance.on('autoplayStart', onAutoplayStart);
swiperInstance.on('autoplayStop', onAutoplayStop);
if (useFakeZoomImage) {
swiperInstance.on('zoomChange', onZoomChange);
}
});
}
@ -328,7 +377,10 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
function getSwiperSlideHtmlFromSlide(item) {
var html = '';
html += '<div class="swiper-slide" data-original="' + item.originalImage + '" data-itemid="' + item.Id + '" data-serverid="' + item.ServerId + '">';
html += '<div class="slider-zoom-container">';
html += '<div class="swiper-zoom-container">';
if (useFakeZoomImage) {
html += `<div class="swiper-zoom-fakeimg swiper-zoom-fakeimg-hidden" style="background-image: url('${item.originalImage}')"></div>`;
}
html += '<img src="' + item.originalImage + '" class="swiper-slide-img">';
html += '</div>';
if (item.title || item.subtitle) {

View file

@ -40,16 +40,6 @@
text-shadow: 3px 3px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
}
.swiper-slide-img {
max-height: 100%;
max-width: 100%;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
margin: auto;
}
.slideshowButtonIcon {
color: #fff;
opacity: 0.7;
@ -135,13 +125,18 @@
color: #ccc;
}
.swiper-slide {
display: flex;
flex-direction: column;
.swiper-zoom-fakeimg {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-position: 50% 50%;
background-repeat: no-repeat;
background-size: contain;
z-index: 1;
pointer-events: none;
}
.slider-zoom-container {
margin: auto;
max-height: 100%;
max-width: 100%;
.swiper-zoom-fakeimg-hidden {
display: none;
}