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

Merge branch 'master' into Search

This commit is contained in:
Nathan G 2023-10-20 08:48:26 -07:00 committed by GitHub
commit 5700219762
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 856 additions and 478 deletions

View file

@ -11,8 +11,8 @@ import browser from 'scripts/browser';
import datetime from 'scripts/datetime';
import dom from 'scripts/dom';
import globalize from 'scripts/globalize';
import imageHelper from 'scripts/imagehelper';
import { getBackdropShape, getPortraitShape, getSquareShape } from 'utils/card';
import imageHelper from 'utils/image';
import { randomInt } from 'utils/number';
import focusManager from '../focusManager';

View file

@ -102,7 +102,15 @@ function onInputCommand(e) {
break;
}
}
function saveValues(context, settings, settingsKey, setfilters) {
function saveValues(context, settings, settingsKey) {
context.querySelectorAll('.simpleFilter').forEach(elem => {
if (elem.tagName === 'INPUT') {
setBasicFilter(context, settingsKey + '-filter-' + elem.getAttribute('data-settingname'), elem);
} else {
setBasicFilter(context, settingsKey + '-filter-' + elem.getAttribute('data-settingname'), elem.querySelector('input'));
}
});
// Video type
const videoTypes = [];
context.querySelectorAll('.chkVideoTypeFilter').forEach(elem => {
@ -111,6 +119,8 @@ function saveValues(context, settings, settingsKey, setfilters) {
}
});
userSettings.setFilter(settingsKey + '-filter-VideoTypes', videoTypes.join(','));
// Series status
const seriesStatuses = [];
context.querySelectorAll('.chkSeriesStatus').forEach(elem => {
@ -119,6 +129,8 @@ function saveValues(context, settings, settingsKey, setfilters) {
}
});
userSettings.setFilter(`${settingsKey}-filter-SeriesStatus`, seriesStatuses.join(','));
// Genres
const genres = [];
context.querySelectorAll('.chkGenreFilter').forEach(elem => {
@ -127,38 +139,7 @@ function saveValues(context, settings, settingsKey, setfilters) {
}
});
if (setfilters) {
setfilters((prevState) => ({
...prevState,
StartIndex: 0,
IsPlayed: context.querySelector('.chkPlayed').checked,
IsUnplayed: context.querySelector('.chkUnplayed').checked,
IsFavorite: context.querySelector('.chkFavorite').checked,
IsResumable: context.querySelector('.chkResumable').checked,
Is4K: context.querySelector('.chk4KFilter').checked,
IsHD: context.querySelector('.chkHDFilter').checked,
IsSD: context.querySelector('.chkSDFilter').checked,
Is3D: context.querySelector('.chk3DFilter').checked,
VideoTypes: videoTypes.join(','),
SeriesStatus: seriesStatuses.join(','),
HasSubtitles: context.querySelector('.chkSubtitle').checked,
HasTrailer: context.querySelector('.chkTrailer').checked,
HasSpecialFeature: context.querySelector('.chkSpecialFeature').checked,
HasThemeSong: context.querySelector('.chkThemeSong').checked,
HasThemeVideo: context.querySelector('.chkThemeVideo').checked,
GenreIds: genres.join(',')
}));
} else {
context.querySelectorAll('.simpleFilter').forEach(elem => {
if (elem.tagName === 'INPUT') {
setBasicFilter(context, settingsKey + '-filter-' + elem.getAttribute('data-settingname'), elem);
} else {
setBasicFilter(context, settingsKey + '-filter-' + elem.getAttribute('data-settingname'), elem.querySelector('input'));
}
});
userSettings.setFilter(settingsKey + '-filter-GenreIds', genres.join(','));
}
userSettings.setFilter(settingsKey + '-filter-GenreIds', genres.join(','));
}
function bindCheckboxInput(context, on) {
const elems = context.querySelectorAll('.checkboxList-verticalwrap');
@ -289,7 +270,7 @@ class FilterMenu {
}
if (submitted) {
saveValues(dlg, options.settings, options.settingsKey, options.setfilters);
saveValues(dlg, options.settings, options.settingsKey);
return resolve();
}
return resolve();

View file

@ -291,7 +291,7 @@ function Guide(options) {
showPremiereIndicator: allowIndicators && userSettings.get('guide-indicator-premiere') !== 'false',
showNewIndicator: allowIndicators && userSettings.get('guide-indicator-new') !== 'false',
showRepeatIndicator: allowIndicators && userSettings.get('guide-indicator-repeat') === 'true',
showEpisodeTitle: layoutManager.tv ? false : true
showEpisodeTitle: !layoutManager.tv
};
apiClient.getLiveTvChannels(channelQuery).then(function (channelsResult) {

View file

@ -4,7 +4,7 @@ import escapeHtml from 'escape-html';
import imageLoader from 'components/images/imageLoader';
import { appRouter } from 'components/router/appRouter';
import globalize from 'scripts/globalize';
import imageHelper from 'scripts/imagehelper';
import imageHelper from 'utils/image';
function getLibraryButtonsHtml(items: BaseItemDto[]) {
let html = '';

View file

@ -9,6 +9,7 @@ import itemHelper from './itemHelper';
import { playbackManager } from './playback/playbackmanager';
import ServerConnections from './ServerConnections';
import toast from './toast/toast';
import * as userSettings from '../scripts/settings/userSettings';
export function getCommands(options) {
const item = options.item;
@ -589,9 +590,16 @@ function play(item, resume, queue, queueNext) {
serverId: item.ServerId
});
} else {
const sortParentId = 'items-' + (item.IsFolder ? item.Id : item.ParentId) + '-Folder';
const sortValues = userSettings.getSortValuesLegacy(sortParentId);
playbackManager[method]({
items: [item],
startPositionTicks: startPosition
startPositionTicks: startPosition,
queryOptions: {
SortBy: sortValues.sortBy,
SortOrder: sortValues.sortOrder
}
});
}
}

View file

@ -177,7 +177,7 @@ export function getListViewHtml(options) {
const isLargeStyle = options.imageSize === 'large';
const enableOverview = options.enableOverview;
const clickEntireItem = layoutManager.tv ? true : false;
const clickEntireItem = layoutManager.tv;
const outerTagName = clickEntireItem ? 'button' : 'div';
const enableSideMediaInfo = options.enableSideMediaInfo != null ? options.enableSideMediaInfo : true;

View file

@ -183,9 +183,9 @@ export function getMediaInfoHtml(item, options = {}) {
if (item.EndDate) {
try {
const endYear = datetime.toLocaleString(datetime.parseISO8601Date(item.EndDate).getFullYear(), { useGrouping: false });
if (endYear !== item.ProductionYear) {
text += `-${endYear}`;
/* At this point, text will contain only the start year */
if (endYear !== text) {
text += ` - ${endYear}`;
}
} catch (e) {
console.error('error parsing date:', item.EndDate);

View file

@ -15,6 +15,7 @@ import { PluginType } from '../../types/plugin.ts';
import { includesAny } from '../../utils/container.ts';
import { getItems } from '../../utils/jellyfin-apiclient/getItems.ts';
import { getItemBackdropImageUrl } from '../../utils/jellyfin-apiclient/backdropImage';
import merge from 'lodash-es/merge';
const UNLIMITED_ITEMS = -1;
@ -145,7 +146,7 @@ function createStreamInfoFromUrlItem(item) {
}
function mergePlaybackQueries(obj1, obj2) {
const query = Object.assign(obj1, obj2);
const query = merge({}, obj1, obj2);
const filters = query.Filters ? query.Filters.split(',') : [];
if (filters.indexOf('IsNotFolder') === -1) {
@ -1798,15 +1799,15 @@ class PlaybackManager {
SortBy: options.shuffle ? 'Random' : null
});
} else if (firstItem.Type === 'MusicArtist') {
promise = getItemsForPlayback(serverId, {
promise = getItemsForPlayback(serverId, mergePlaybackQueries({
ArtistIds: firstItem.Id,
Filters: 'IsNotFolder',
Recursive: true,
SortBy: options.shuffle ? 'Random' : 'SortName',
MediaTypes: 'Audio'
});
}, queryOptions));
} else if (firstItem.MediaType === 'Photo') {
promise = getItemsForPlayback(serverId, {
promise = getItemsForPlayback(serverId, mergePlaybackQueries({
ParentId: firstItem.ParentId,
Filters: 'IsNotFolder',
// Setting this to true may cause some incorrect sorting
@ -1814,7 +1815,7 @@ class PlaybackManager {
SortBy: options.shuffle ? 'Random' : 'SortName',
MediaTypes: 'Photo,Video',
Limit: UNLIMITED_ITEMS
}).then(function (result) {
}, queryOptions)).then(function (result) {
const playbackItems = result.Items;
let index = playbackItems.map(function (i) {
@ -1830,7 +1831,7 @@ class PlaybackManager {
return Promise.resolve(result);
});
} else if (firstItem.Type === 'PhotoAlbum') {
promise = getItemsForPlayback(serverId, {
promise = getItemsForPlayback(serverId, mergePlaybackQueries({
ParentId: firstItem.Id,
Filters: 'IsNotFolder',
// Setting this to true may cause some incorrect sorting
@ -1839,15 +1840,15 @@ class PlaybackManager {
// Only include Photos because we do not handle mixed queues currently
MediaTypes: 'Photo',
Limit: UNLIMITED_ITEMS
});
}, queryOptions));
} else if (firstItem.Type === 'MusicGenre') {
promise = getItemsForPlayback(serverId, {
promise = getItemsForPlayback(serverId, mergePlaybackQueries({
GenreIds: firstItem.Id,
Filters: 'IsNotFolder',
Recursive: true,
SortBy: options.shuffle ? 'Random' : 'SortName',
MediaTypes: 'Audio'
});
}, queryOptions));
} else if (firstItem.Type === 'Series' || firstItem.Type === 'Season') {
const apiClient = ServerConnections.getApiClient(firstItem.ServerId);
@ -2773,6 +2774,12 @@ class PlaybackManager {
});
});
} else {
if (item.AlbumId != null) {
return apiClient.getItem(apiClient.getCurrentUserId(), item.AlbumId).then(function(result) {
mediaSource.albumLUFS = result.LUFS;
return mediaSource;
});
}
return mediaSource;
}
} else {

View file

@ -46,7 +46,7 @@ function showQualityMenu(player, btn) {
const bitrate = parseInt(id, 10);
if (bitrate !== selectedBitrate) {
playbackManager.setMaxStreamingBitrate({
enableAutomaticBitrateDetection: bitrate ? false : true,
enableAutomaticBitrateDetection: !bitrate,
maxBitrate: bitrate
}, player);
}

View file

@ -173,7 +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('#selectAudioNormalization').value = userSettings.selectAudioNormalization();
context.querySelector('.chkEnableNextVideoOverlay').checked = userSettings.enableNextVideoInfoOverlay();
context.querySelector('.chkRememberAudioSelections').checked = user.Configuration.RememberAudioSelections || false;
context.querySelector('.chkRememberSubtitleSelections').checked = user.Configuration.RememberSubtitleSelections || false;
@ -218,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.selectAudioNormalization(context.querySelector('#selectAudioNormalization').value);
userSettingsInstance.enableNextVideoInfoOverlay(context.querySelector('.chkEnableNextVideoOverlay').checked);
user.Configuration.RememberAudioSelections = context.querySelector('.chkRememberAudioSelections').checked;
user.Configuration.RememberSubtitleSelections = context.querySelector('.chkRememberSubtitleSelections').checked;

View file

@ -72,12 +72,13 @@
${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 class="selectContainer">
<select is="emby-select" id="selectAudioNormalization" label="${LabelSelectAudioNormalization}">
<option value="Off">${Off}</option>
<option value="TrackGain">${LabelTrackGain}</option>
<option value="AlbumGain">${LabelAlbumGain}</option>
</select>
<div class="fieldDescription">${SelectAudioNormalizationHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">

View file

@ -11,6 +11,7 @@ import dom from '../scripts/dom';
import recordingHelper from './recordingcreator/recordinghelper';
import ServerConnections from './ServerConnections';
import toast from './toast/toast';
import * as userSettings from '../scripts/settings/userSettings';
function playAllFromHere(card, serverId, queue) {
const parent = card.parentNode;
@ -177,6 +178,10 @@ function executeAction(card, target, action) {
const item = getItemInfoFromCard(card);
const itemsContainer = dom.parentWithClass(card, 'itemsContainer');
const sortParentId = 'items-' + (item.IsFolder ? item.Id : itemsContainer?.getAttribute('data-parentid')) + '-Folder';
const serverId = item.ServerId;
const type = item.Type;
@ -200,12 +205,17 @@ function executeAction(card, target, action) {
});
} else if (action === 'play' || action === 'resume') {
const startPositionTicks = parseInt(card.getAttribute('data-positionticks') || '0', 10);
const sortValues = userSettings.getSortValuesLegacy(sortParentId, 'SortName');
if (playbackManager.canPlay(item)) {
playbackManager.play({
ids: [playableItemId],
startPositionTicks: startPositionTicks,
serverId: serverId
serverId: serverId,
queryOptions: {
SortBy: sortValues.sortBy,
SortOrder: sortValues.sortOrder
}
});
} else {
console.warn('Unable to play item', item);

View file

@ -18,8 +18,8 @@ function onSubmit(e) {
function initEditor(context, settings) {
context.querySelector('form').addEventListener('submit', onSubmit);
context.querySelector('.selectSortOrder').value = settings.SortOrder;
context.querySelector('.selectSortBy').value = settings.SortBy;
context.querySelector('.selectSortOrder').value = settings.sortOrder;
context.querySelector('.selectSortBy').value = settings.sortBy;
}
function centerFocus(elem, horiz, on) {
@ -37,18 +37,9 @@ function fillSortBy(context, options) {
}).join('');
}
function saveValues(context, settingsKey, setSortValues) {
if (setSortValues) {
setSortValues((prevState) => ({
...prevState,
StartIndex: 0,
SortBy: context.querySelector('.selectSortBy').value,
SortOrder: context.querySelector('.selectSortOrder').value
}));
} else {
userSettings.setFilter(settingsKey + '-sortorder', context.querySelector('.selectSortOrder').value);
userSettings.setFilter(settingsKey + '-sortby', context.querySelector('.selectSortBy').value);
}
function saveValues(context, settingsKey) {
userSettings.setFilter(settingsKey + '-sortorder', context.querySelector('.selectSortOrder').value);
userSettings.setFilter(settingsKey + '-sortby', context.querySelector('.selectSortBy').value);
}
class SortMenu {
@ -104,7 +95,7 @@ class SortMenu {
}
if (submitted) {
saveValues(dlg, options.settingsKey, options.setSortValues);
saveValues(dlg, options.settingsKey);
resolve();
return;
}

View file

@ -6,7 +6,7 @@ import Toolbar from '@mui/material/Toolbar';
import Tooltip from '@mui/material/Tooltip';
import Typography from '@mui/material/Typography';
import React, { FC, ReactNode } from 'react';
import { Link } from 'react-router-dom';
import { Link, useLocation } from 'react-router-dom';
import appIcon from 'assets/img/icon-transparent.png';
import { appRouter } from 'components/router/appRouter';
@ -38,9 +38,13 @@ const AppToolbar: FC<AppToolbarProps> = ({
}) => {
const { user } = useApi();
const isUserLoggedIn = Boolean(user);
const currentLocation = useLocation();
const isBackButtonAvailable = appRouter.canGoBack();
// Handles a specific case to hide the user menu on the select server page while authenticated
const isUserMenuAvailable = currentLocation.pathname !== '/selectserver.html';
return (
<Toolbar
variant='dense'
@ -111,7 +115,7 @@ const AppToolbar: FC<AppToolbarProps> = ({
{children}
{isUserLoggedIn && (
{isUserLoggedIn && isUserMenuAvailable && (
<>
<Box sx={{ display: 'flex', flexGrow: 1, justifyContent: 'flex-end' }}>
{buttons}

View file

@ -29,24 +29,13 @@ function initEditor(context, settings) {
context.querySelector('.selectImageType').value = settings.imageType || 'primary';
}
function saveValues(context, settings, settingsKey, setviewsettings) {
if (setviewsettings) {
setviewsettings((prevState) => ({
...prevState,
StartIndex: 0,
imageType: context.querySelector('.selectImageType').value,
showTitle: context.querySelector('.chkShowTitle').checked || false,
showYear: context.querySelector('.chkShowYear').checked || false,
cardLayout: context.querySelector('.chkEnableCardLayout').checked || false
}));
} else {
const elems = context.querySelectorAll('.viewSetting-checkboxContainer');
for (const elem of elems) {
userSettings.set(settingsKey + '-' + elem.getAttribute('data-settingname'), elem.querySelector('input').checked);
}
userSettings.set(settingsKey + '-imageType', context.querySelector('.selectImageType').value);
function saveValues(context, settings, settingsKey) {
const elems = context.querySelectorAll('.viewSetting-checkboxContainer');
for (const elem of elems) {
userSettings.set(settingsKey + '-' + elem.getAttribute('data-settingname'), elem.querySelector('input').checked);
}
userSettings.set(settingsKey + '-imageType', context.querySelector('.selectImageType').value);
}
function centerFocus(elem, horiz, on) {
@ -112,7 +101,6 @@ class ViewSettings {
dlg.querySelector('.selectImageType').addEventListener('change', function () {
showIfAllowed(dlg, '.chkTitleContainer', this.value !== 'list' && this.value !== 'banner');
showIfAllowed(dlg, '.chkYearContainer', this.value !== 'list' && this.value !== 'banner');
showIfAllowed(dlg, '.chkCardLayoutContainer', this.value !== 'list' && this.value !== 'banner');
});
dlg.querySelector('.btnCancel').addEventListener('click', function () {
@ -137,7 +125,7 @@ class ViewSettings {
}
if (submitted) {
saveValues(dlg, options.settings, options.settingsKey, options.setviewsettings);
saveValues(dlg, options.settings, options.settingsKey);
return resolve();
}

View file

@ -35,13 +35,6 @@
<span>${GroupBySeries}</span>
</label>
</div>
<div class="checkboxContainer viewSetting viewSetting-checkboxContainer hide chkCardLayoutContainer" data-settingname="cardLayout">
<label>
<input is="emby-checkbox" type="checkbox" class="chkEnableCardLayout" />
<span>${EnableCardLayout}</span>
</label>
</div>
</div>
</form>
</div>