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

Merge branch 'master' of https://github.com/jellyfin/jellyfin-web into fix-accese

# Conflicts:
#	src/scripts/site.js
This commit is contained in:
grafixeyehero 2021-09-08 02:38:25 +03:00
commit 8e724d3119
168 changed files with 12834 additions and 5898 deletions

View file

@ -667,12 +667,13 @@ import browser from './browser';
}
}
if (canPlayVp8) {
if (webmAudioCodecs.length && webmVideoCodecs.length) {
profile.TranscodingProfiles.push({
Container: 'webm',
Type: 'Video',
AudioCodec: 'vorbis',
VideoCodec: 'vpx',
AudioCodec: webmAudioCodecs.join(','),
// TODO: Remove workaround when servers migrate away from 'vpx' for transcoding profiles.
VideoCodec: (canPlayVp8 ? webmVideoCodecs.concat('vpx') : webmVideoCodecs).join(','),
Context: 'Streaming',
Protocol: 'http',
// If audio transcoding is needed, limit channels to number of physical audio channels

View file

@ -26,8 +26,13 @@ export async function serverAddress() {
// Use servers specified in config.json
const urls = await webSettings.getServers();
// Otherwise use computed base URL
if (urls.length == 0) {
if (urls.length === 0) {
// Don't use app URL as server URL
if (window.NativeShell) {
return Promise.resolve();
}
// Otherwise use computed base URL
const index = window.location.href.toLowerCase().lastIndexOf('/web');
if (index != -1) {
urls.push(window.location.href.substring(0, index));

View file

@ -13,6 +13,7 @@
case 'Kodi':
return baseUrl + 'kodi.svg';
case 'Jellyfin Android':
case 'AndroidTV':
case 'Android TV':
return baseUrl + 'android.svg';
case 'Jellyfin Web':

View file

@ -5,6 +5,7 @@
import inputManager from './inputManager';
import layoutManager from '../components/layoutManager';
import appSettings from './settings/appSettings';
/**
* Key name mapping.
@ -160,7 +161,7 @@ function attachGamepadScript() {
}
// No need to check for gamepads manually at load time, the eventhandler will be fired for that
if (navigator.getGamepads) { /* eslint-disable-line compat/compat */
if (navigator.getGamepads && appSettings.enableGamepad()) { /* eslint-disable-line compat/compat */
window.addEventListener('gamepadconnected', attachGamepadScript);
}

View file

@ -6,11 +6,11 @@ import viewManager from '../components/viewManager/viewManager';
import { appRouter } from '../components/appRouter';
import { appHost } from '../components/apphost';
import { playbackManager } from '../components/playback/playbackmanager';
import SyncPlay from '../components/syncPlay/core';
import groupSelectionMenu from '../components/syncPlay/ui/groupSelectionMenu';
import browser from './browser';
import globalize from './globalize';
import imageHelper from './imagehelper';
import { getMenuLinks } from '../scripts/settings/webSettings';
import '../elements/emby-button/paper-icon-button-light';
import 'material-design-icons-iconfont';
import '../assets/css/scrollstyles.scss';
@ -32,7 +32,7 @@ import Headroom from 'headroom.js';
html += '</div>';
html += '<div class="headerRight">';
html += '<span class="headerSelectedPlayer"></span>';
html += '<button is="paper-icon-button-light" class="headerSyncButton syncButton headerButton headerButtonRight hide"><span class="material-icons sync_disabled"></span></button>';
html += '<button is="paper-icon-button-light" class="headerSyncButton syncButton headerButton headerButtonRight hide"><span class="material-icons groups"></span></button>';
html += '<button is="paper-icon-button-light" class="headerAudioPlayerButton audioPlayerButton headerButton headerButtonRight hide"><span class="material-icons music_note"></span></button>';
html += '<button is="paper-icon-button-light" class="headerCastButton castButton headerButton headerButtonRight hide"><span class="material-icons cast"></span></button>';
html += '<button type="button" is="paper-icon-button-light" class="headerButton headerButtonRight headerSearchButton hide"><span class="material-icons search"></span></button>';
@ -134,7 +134,7 @@ import Headroom from 'headroom.js';
const policy = user.Policy ? user.Policy : user.localUser.Policy;
const apiClient = getCurrentApiClient();
if (headerSyncButton && policy && policy.SyncPlayAccess !== 'None' && apiClient.isMinServerVersion('10.6.0')) {
if (headerSyncButton && policy?.SyncPlayAccess !== 'None' && apiClient.isMinServerVersion('10.6.0')) {
headerSyncButton.classList.remove('hide');
}
} else {
@ -233,26 +233,6 @@ import Headroom from 'headroom.js';
groupSelectionMenu.show(btn);
}
function onSyncPlayEnabled(event, enabled) {
const icon = headerSyncButton.querySelector('span');
icon.classList.remove('sync', 'sync_disabled', 'sync_problem');
if (enabled) {
icon.classList.add('sync');
} else {
icon.classList.add('sync_disabled');
}
}
function onSyncPlaySyncing(event, is_syncing) {
const icon = headerSyncButton.querySelector('span');
icon.classList.remove('sync', 'sync_disabled', 'sync_problem');
if (is_syncing) {
icon.classList.add('sync_problem');
} else {
icon.classList.add('sync');
}
}
function getItemHref(item, context) {
return appRouter.getRouteUrl(item, {
context: context
@ -294,9 +274,11 @@ import Headroom from 'headroom.js';
html += '<div style="height:.5em;"></div>';
html += '<a is="emby-linkbutton" class="navMenuOption lnkMediaFolder" href="#!/home.html"><span class="material-icons navMenuOptionIcon home"></span><span class="navMenuOptionText">' + globalize.translate('Home') + '</span></a>';
// placeholder for custom menu links
html += '<div class="customMenuOptions"></div>';
// libraries are added here
html += '<div class="libraryMenuOptions">';
html += '</div>';
html += '<div class="libraryMenuOptions"></div>';
if (user.localUser && user.localUser.Policy.IsAdministrator) {
html += '<div class="adminMenuOptions">';
@ -430,12 +412,6 @@ import Headroom from 'headroom.js';
pageIds: ['devicesPage', 'devicePage'],
icon: 'devices'
});
links.push({
name: globalize.translate('QuickConnect'),
href: '#!/quickConnect.html',
pageIds: ['quickConnectPage'],
icon: 'tap_and_play'
});
links.push({
name: globalize.translate('HeaderActivity'),
href: '#!/serveractivity.html',
@ -659,6 +635,32 @@ import Headroom from 'headroom.js';
const userId = Dashboard.getCurrentUserId();
const apiClient = getCurrentApiClient();
const customMenuOptions = document.querySelector('.customMenuOptions');
if (customMenuOptions) {
getMenuLinks().then(links => {
links.forEach(link => {
const option = document.createElement('a');
option.setAttribute('is', 'emby-linkbutton');
option.className = 'navMenuOption lnkMediaFolder';
option.rel = 'noopener noreferrer';
option.target = '_blank';
option.href = link.url;
const icon = document.createElement('span');
icon.className = `material-icons navMenuOptionIcon ${link.icon || 'link'}`;
option.appendChild(icon);
const label = document.createElement('span');
label.className = 'navMenuOptionText';
label.textContent = link.name;
option.appendChild(label);
customMenuOptions.appendChild(option);
});
});
}
const libraryMenuOptions = document.querySelector('.libraryMenuOptions');
if (libraryMenuOptions) {
@ -1023,9 +1025,6 @@ import Headroom from 'headroom.js';
Events.on(playbackManager, 'playerchange', updateCastIcon);
Events.on(SyncPlay.Manager, 'enabled', onSyncPlayEnabled);
Events.on(SyncPlay.Manager, 'syncing', onSyncPlaySyncing);
loadNavDrawer();
const LibraryMenu = {

View file

@ -54,8 +54,8 @@ import dom from '../scripts/dom';
let lastPointerMoveData;
function onPointerMove(e) {
const eventX = e.screenX;
const eventY = e.screenY;
const eventX = e.screenX || e.clientX;
const eventY = e.screenY || e.clientY;
// if coord don't exist how could it move
if (typeof eventX === 'undefined' && typeof eventY === 'undefined') {

View file

@ -189,7 +189,7 @@ export default function (view) {
reloadItems();
});
view.querySelector('.btnNewPlaylist').addEventListener('click', function () {
import('playlistEditor').then(({default: playlistEditor}) => {
import('../components/playlisteditor/playlisteditor').then(({default: playlistEditor}) => {
const serverId = ApiClient.serverInfo().Id;
new playlistEditor({
items: [],

View file

@ -84,6 +84,13 @@ import { appRouter } from '../components/appRouter';
controller: 'user/profile/index'
});
defineRoute({
alias: '/mypreferencescontrols.html',
path: 'user/controls/index.html',
autoFocus: false,
controller: 'user/controls/index'
});
defineRoute({
alias: '/mypreferencesdisplay.html',
path: 'user/display/index.html',
@ -304,7 +311,7 @@ import { appRouter } from '../components/appRouter';
defineRoute({
alias: '/search.html',
path: 'search.html',
controller: 'searchpage'
pageComponent: 'SearchPage'
});
defineRoute({
@ -553,6 +560,11 @@ import { appRouter } from '../components/appRouter';
serverRequest: true
});
defineRoute({
path: '/dialog',
dummyRoute: true
});
defineRoute({
path: '',
isDefaultRoute: true,

View file

@ -103,6 +103,8 @@ function centerOnFocusVertical(e) {
export const centerFocus = {
on: function (element, horizontal) {
element.setAttribute(`data-scroll-mode-${horizontal ? 'x' : 'y'}`, 'custom');
if (horizontal) {
dom.addEventListener(element, 'focus', centerOnFocusHorizontal, {
capture: true,
@ -116,6 +118,8 @@ export const centerFocus = {
}
},
off: function (element, horizontal) {
element.removeAttribute(`data-scroll-mode-${horizontal ? 'x' : 'y'}`);
if (horizontal) {
dom.removeEventListener(element, 'focus', centerOnFocusHorizontal, {
capture: true,

View file

@ -18,6 +18,19 @@ class AppSettings {
return this.get('enableAutoLogin') !== 'false';
}
/**
* Get or set 'Enable Gamepad' state.
* @param {boolean|undefined} val - Flag to enable 'Enable Gamepad' or undefined.
* @return {boolean} 'Enable Gamepad' state.
*/
enableGamepad(val) {
if (val !== undefined) {
return this.set('enableGamepad', val.toString());
}
return this.get('enableGamepad') === 'true';
}
enableSystemExternalPlayers(val) {
if (val !== undefined) {
this.set('enableSystemExternalPlayers', val.toString());

View file

@ -169,6 +169,19 @@ export class UserSettings {
return val !== 'false';
}
/**
* Get or set 'SetUsingLastTracks' state.
* @param {boolean|undefined} val - Flag to enable 'SetUsingLastTracks' or undefined.
* @return {boolean} 'SetUsingLastTracks' state.
*/
enableSetUsingLastTracks(val) {
if (val !== undefined) {
return this.set('enableSetUsingLastTracks', val.toString());
}
return this.get('enableSetUsingLastTracks', false) !== 'false';
}
/**
* Get or set 'Theme Songs' state.
* @param {boolean|undefined} val - Flag to enable 'Theme Songs' or undefined.
@ -239,6 +252,32 @@ export class UserSettings {
return val === 'true';
}
/**
* Get or set 'disableCustomCss' state.
* @param {boolean|undefined} val - Flag to enable 'disableCustomCss' or undefined.
* @return {boolean} 'disableCustomCss' state.
*/
disableCustomCss(val) {
if (val !== undefined) {
return this.set('disableCustomCss', val.toString(), false);
}
return this.get('disableCustomCss', false) === 'true';
}
/**
* Get or set customCss.
* @param {string|undefined} val - Language.
* @return {string} Language.
*/
customCss(val) {
if (val !== undefined) {
return this.set('customCss', val.toString(), false);
}
return this.get('customCss', false);
}
/**
* Get or set 'Details Banner' state.
* @param {boolean|undefined} val - Flag to enable 'Details Banner' or undefined.
@ -253,6 +292,20 @@ export class UserSettings {
return val !== 'false';
}
/**
* Get or set 'Use Episode Images in Next Up and Continue Watching' state.
* @param {string|boolean|undefined} val - Flag to enable 'Use Episode Images in Next Up and Continue Watching' or undefined.
* @return {boolean} 'Use Episode Images in Next Up' state.
*/
useEpisodeImagesInNextUpAndResume(val) {
if (val !== undefined) {
return this.set('useEpisodeImagesInNextUpAndResume', val.toString(), true);
}
val = this.get('useEpisodeImagesInNextUpAndResume', true);
return val === 'true';
}
/**
* Get or set language.
* @param {string|undefined} val - Language.
@ -488,12 +541,14 @@ export const allowedAudioChannels = currentSettings.allowedAudioChannels.bind(cu
export const preferFmp4HlsContainer = currentSettings.preferFmp4HlsContainer.bind(currentSettings);
export const enableCinemaMode = currentSettings.enableCinemaMode.bind(currentSettings);
export const enableNextVideoInfoOverlay = currentSettings.enableNextVideoInfoOverlay.bind(currentSettings);
export const enableSetUsingLastTracks = currentSettings.enableSetUsingLastTracks.bind(currentSettings);
export const enableThemeSongs = currentSettings.enableThemeSongs.bind(currentSettings);
export const enableThemeVideos = currentSettings.enableThemeVideos.bind(currentSettings);
export const enableFastFadein = currentSettings.enableFastFadein.bind(currentSettings);
export const enableBlurhash = currentSettings.enableBlurhash.bind(currentSettings);
export const enableBackdrops = currentSettings.enableBackdrops.bind(currentSettings);
export const detailsBanner = currentSettings.detailsBanner.bind(currentSettings);
export const useEpisodeImagesInNextUpAndResume = currentSettings.useEpisodeImagesInNextUpAndResume.bind(currentSettings);
export const language = currentSettings.language.bind(currentSettings);
export const dateTimeLocale = currentSettings.dateTimeLocale.bind(currentSettings);
export const chromecastVersion = currentSettings.chromecastVersion.bind(currentSettings);
@ -511,3 +566,5 @@ export const getSubtitleAppearanceSettings = currentSettings.getSubtitleAppearan
export const setSubtitleAppearanceSettings = currentSettings.setSubtitleAppearanceSettings.bind(currentSettings);
export const setFilter = currentSettings.setFilter.bind(currentSettings);
export const getFilter = currentSettings.getFilter.bind(currentSettings);
export const customCss = currentSettings.customCss.bind(currentSettings);
export const disableCustomCss = currentSettings.disableCustomCss.bind(currentSettings);

View file

@ -126,6 +126,18 @@ export function getThemes() {
export const getDefaultTheme = () => internalDefaultTheme;
export function getMenuLinks() {
return getConfig().then(config => {
if (!config.menuLinks) {
console.error('web config is invalid, missing menuLinks:', config);
}
return config.menuLinks || [];
}).catch(error => {
console.log('cannot get web config:', error);
return [];
});
}
export function getPlugins() {
return getConfig().then(config => {
if (!config.plugins) {

View file

@ -1,30 +1,40 @@
// TODO: This seems like a good candidate for deprecation
export default {
enableFullscreen: function() {
window.NativeShell?.enableFullscreen();
if (window.NativeShell?.enableFullscreen) {
window.NativeShell.enableFullscreen();
}
},
disableFullscreen: function() {
window.NativeShell?.disableFullscreen();
if (window.NativeShell?.disableFullscreen) {
window.NativeShell.disableFullscreen();
}
},
openUrl: function(url, target) {
if (window.NativeShell) {
if (window.NativeShell?.openUrl) {
window.NativeShell.openUrl(url, target);
} else {
window.open(url, target || '_blank');
}
},
updateMediaSession(mediaInfo) {
window.NativeShell?.updateMediaSession(mediaInfo);
if (window.NativeShell?.updateMediaSession) {
window.NativeShell.updateMediaSession(mediaInfo);
}
},
hideMediaSession() {
window.NativeShell?.hideMediaSession();
if (window.NativeShell?.hideMediaSession) {
window.NativeShell.hideMediaSession();
}
},
/**
* Notify the NativeShell about volume level changes.
* Useful for e.g. remote playback.
*/
updateVolumeLevel(volume) {
window.NativeShell?.updateVolumeLevel(volume);
if (window.NativeShell?.updateVolumeLevel) {
window.NativeShell.updateVolumeLevel(volume);
}
},
/**
* Download specified files with NativeShell if possible
@ -32,7 +42,7 @@ export default {
* @returns true on success
*/
downloadFiles(items) {
if (window.NativeShell) {
if (window.NativeShell?.downloadFile) {
items.forEach(item => window.NativeShell.downloadFile(item));
return true;
}

View file

@ -36,6 +36,7 @@ import { playbackManager } from '../components/playback/playbackmanager';
import SyncPlayNoActivePlayer from '../components/syncPlay/ui/players/NoActivePlayer';
import SyncPlayHtmlVideoPlayer from '../components/syncPlay/ui/players/HtmlVideoPlayer';
import SyncPlayHtmlAudioPlayer from '../components/syncPlay/ui/players/HtmlAudioPlayer';
import { currentSettings } from './settings/userSettings';
import taskButton from '../scripts/taskbutton';
// TODO: Move this elsewhere
@ -176,9 +177,11 @@ function initSyncPlay() {
// FIXME: Multiple apiClients?
Events.on(ServerConnections, 'apiclientcreated', (e, newApiClient) => SyncPlay.Manager.init(newApiClient));
Events.on(ServerConnections, 'localusersignedin', () => SyncPlay.Manager.updateApiClient(ServerConnections.currentApiClient()));
Events.on(ServerConnections, 'localusersignedout', () => SyncPlay.Manager.updateApiClient(ServerConnections.currentApiClient()));
}
function onAppReady() {
async function onAppReady() {
console.debug('begin onAppReady');
console.debug('onAppReady: loading dependencies');
@ -221,27 +224,63 @@ function onAppReady() {
const apiClient = ServerConnections.currentApiClient();
if (apiClient) {
fetch(apiClient.getUrl('Branding/Css'))
const updateStyle = (css) => {
let style = document.querySelector('#cssBranding');
if (!style) {
// Inject the branding css as a dom element in body so it will take
// precedence over other stylesheets
style = document.createElement('style');
style.id = 'cssBranding';
document.body.appendChild(style);
}
style.textContent = css;
};
const style = fetch(apiClient.getUrl('Branding/Css'))
.then(function(response) {
if (!response.ok) {
throw new Error(response.status + ' ' + response.statusText);
}
return response.text();
})
.then(function(css) {
let style = document.querySelector('#cssBranding');
if (!style) {
// Inject the branding css as a dom element in body so it will take
// precedence over other stylesheets
style = document.createElement('style');
style.id = 'cssBranding';
document.body.appendChild(style);
}
style.textContent = css;
})
.catch(function(err) {
console.warn('Error applying custom css', err);
});
const handleStyleChange = async () => {
if (currentSettings.disableCustomCss()) {
updateStyle('');
} else {
updateStyle(await style);
}
const localCss = currentSettings.customCss();
let localStyle = document.querySelector('#localCssBranding');
if (localCss) {
if (!localStyle) {
// Inject the branding css as a dom element in body so it will take
// precedence over other stylesheets
localStyle = document.createElement('style');
localStyle.id = 'localCssBranding';
document.body.appendChild(localStyle);
}
localStyle.textContent = localCss;
} else {
if (localStyle) {
localStyle.textContent = '';
}
}
};
Events.on(ServerConnections, 'localusersignedin', handleStyleChange);
Events.on(ServerConnections, 'localusersignedout', handleStyleChange);
Events.on(currentSettings, 'change', (e, prop) => {
if (prop == 'disableCustomCss' || prop == 'customCss') {
handleStyleChange();
}
});
style.then(updateStyle);
}
}

View file

@ -0,0 +1,27 @@
/**
* Gets the value of a string as boolean.
* @param {string} name The value as a string.
* @param {boolean} defaultValue The default value if the string is invalid.
* @returns {boolean} The value.
*/
export function toBoolean(value, defaultValue = false) {
if (value !== 'true' && value !== 'false') {
return defaultValue;
} else {
return value !== 'false';
}
}
/**
* Gets the value of a string as float number.
* @param {string} value The value as a string.
* @param {number} defaultValue The default value if the string is invalid.
* @returns {number} The value.
*/
export function toFloat(value, defaultValue = 0) {
if (value === null || value === '' || isNaN(value)) {
return defaultValue;
} else {
return parseFloat(value);
}
}