mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Merge branch 'master' into hadicharara/added-support-for-rtl-layouts
This commit is contained in:
commit
32f103b852
178 changed files with 25310 additions and 7347 deletions
|
@ -390,6 +390,9 @@ import browser from './browser';
|
|||
if (supportsMp3VideoAudio && (browser.chrome || browser.edgeChromium || (browser.firefox && browser.versionMajor >= 83))) {
|
||||
supportsMp2VideoAudio = true;
|
||||
}
|
||||
if (browser.android) {
|
||||
supportsMp2VideoAudio = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* eslint-disable compat/compat */
|
||||
|
|
|
@ -19,7 +19,7 @@ import globalize from './globalize';
|
|||
// "00", "00", ".000", "Z", undefined, undefined, undefined]
|
||||
|
||||
if (!d) {
|
||||
throw "Couldn't parse ISO 8601 date string '" + s + "'";
|
||||
throw new Error("Couldn't parse ISO 8601 date string '" + s + "'");
|
||||
}
|
||||
|
||||
// parse strings, leading zeros into proper ints
|
||||
|
|
|
@ -355,14 +355,14 @@ import '../assets/css/flexstyles.scss';
|
|||
}
|
||||
}
|
||||
|
||||
function refreshDashboardInfoInDrawer(apiClient) {
|
||||
function refreshDashboardInfoInDrawer(page, apiClient) {
|
||||
currentDrawerType = 'admin';
|
||||
loadNavDrawer();
|
||||
|
||||
if (navDrawerScrollContainer.querySelector('.adminDrawerLogo')) {
|
||||
updateDashboardMenuSelectedItem();
|
||||
updateDashboardMenuSelectedItem(page);
|
||||
} else {
|
||||
createDashboardMenu(apiClient);
|
||||
createDashboardMenu(page, apiClient);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -370,9 +370,9 @@ import '../assets/css/flexstyles.scss';
|
|||
return window.location.href.toString().toLowerCase().indexOf(url.toLowerCase()) !== -1;
|
||||
}
|
||||
|
||||
function updateDashboardMenuSelectedItem() {
|
||||
function updateDashboardMenuSelectedItem(page) {
|
||||
const links = navDrawerScrollContainer.querySelectorAll('.navMenuOption');
|
||||
const currentViewId = viewManager.currentView().id;
|
||||
const currentViewId = page.id;
|
||||
|
||||
for (let i = 0, length = links.length; i < length; i++) {
|
||||
let link = links[i];
|
||||
|
@ -590,7 +590,7 @@ import '../assets/css/flexstyles.scss';
|
|||
});
|
||||
}
|
||||
|
||||
function createDashboardMenu(apiClient) {
|
||||
function createDashboardMenu(page, apiClient) {
|
||||
return getToolsMenuHtml(apiClient).then(function (toolsMenuHtml) {
|
||||
let html = '';
|
||||
html += '<a class="adminDrawerLogo clearLink" is="emby-linkbutton" href="#/home.html">';
|
||||
|
@ -598,7 +598,7 @@ import '../assets/css/flexstyles.scss';
|
|||
html += '</a>';
|
||||
html += toolsMenuHtml;
|
||||
navDrawerScrollContainer.innerHTML = html;
|
||||
updateDashboardMenuSelectedItem();
|
||||
updateDashboardMenuSelectedItem(page);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1017,7 +1017,7 @@ import '../assets/css/flexstyles.scss';
|
|||
mainDrawerButton.classList.remove('hide');
|
||||
}
|
||||
|
||||
refreshDashboardInfoInDrawer(apiClient);
|
||||
refreshDashboardInfoInDrawer(page, apiClient);
|
||||
} else {
|
||||
if (mainDrawerButton) {
|
||||
if (enableLibraryNavDrawer || (isHomePage && enableLibraryNavDrawerHome)) {
|
||||
|
|
|
@ -77,13 +77,6 @@ import { appRouter } from '../components/appRouter';
|
|||
controller: 'user/menu/index'
|
||||
});
|
||||
|
||||
defineRoute({
|
||||
alias: '/myprofile.html',
|
||||
path: 'user/profile/index.html',
|
||||
autoFocus: false,
|
||||
pageComponent: 'UserProfilePage'
|
||||
});
|
||||
|
||||
defineRoute({
|
||||
alias: '/mypreferencescontrols.html',
|
||||
path: 'user/controls/index.html',
|
||||
|
@ -300,14 +293,6 @@ import { appRouter } from '../components/appRouter';
|
|||
controller: 'dashboard/plugins/repositories/index'
|
||||
});
|
||||
|
||||
defineRoute({
|
||||
alias: '/home.html',
|
||||
path: 'home.html',
|
||||
autoFocus: false,
|
||||
controller: 'home',
|
||||
type: 'home'
|
||||
});
|
||||
|
||||
defineRoute({
|
||||
alias: '/list.html',
|
||||
path: 'list.html',
|
||||
|
@ -429,53 +414,6 @@ import { appRouter } from '../components/appRouter';
|
|||
controller: 'shows/tvrecommended'
|
||||
});
|
||||
|
||||
defineRoute({
|
||||
alias: '/useredit.html',
|
||||
path: 'dashboard/users/useredit.html',
|
||||
autoFocus: false,
|
||||
roles: 'admin',
|
||||
pageComponent: 'UserEditPage'
|
||||
});
|
||||
|
||||
defineRoute({
|
||||
alias: '/userlibraryaccess.html',
|
||||
path: 'dashboard/users/userlibraryaccess.html',
|
||||
autoFocus: false,
|
||||
roles: 'admin',
|
||||
pageComponent: 'UserLibraryAccessPage'
|
||||
});
|
||||
|
||||
defineRoute({
|
||||
alias: '/usernew.html',
|
||||
path: 'dashboard/users/usernew.html',
|
||||
autoFocus: false,
|
||||
roles: 'admin',
|
||||
pageComponent: 'NewUserPage'
|
||||
});
|
||||
|
||||
defineRoute({
|
||||
alias: '/userparentalcontrol.html',
|
||||
path: 'dashboard/users/userparentalcontrol.html',
|
||||
autoFocus: false,
|
||||
roles: 'admin',
|
||||
pageComponent: 'UserParentalControl'
|
||||
});
|
||||
|
||||
defineRoute({
|
||||
alias: '/userpassword.html',
|
||||
path: 'dashboard/users/userpassword.html',
|
||||
autoFocus: false,
|
||||
pageComponent: 'UserPasswordPage'
|
||||
});
|
||||
|
||||
defineRoute({
|
||||
alias: '/userprofiles.html',
|
||||
path: 'dashboard/users/userprofiles.html',
|
||||
autoFocus: false,
|
||||
roles: 'admin',
|
||||
pageComponent: 'UserProfilesPage'
|
||||
});
|
||||
|
||||
defineRoute({
|
||||
alias: '/wizardremoteaccess.html',
|
||||
path: 'wizard/remote/index.html',
|
||||
|
|
|
@ -116,7 +116,7 @@ function ScreenSaverManager() {
|
|||
return;
|
||||
}
|
||||
|
||||
if (getFunctionalEventIdleTime < getMinIdleTime()) {
|
||||
if (getFunctionalEventIdleTime() < getMinIdleTime()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,11 @@ const defaultSubtitleAppearanceSettings = {
|
|||
verticalPosition: -3
|
||||
};
|
||||
|
||||
const defaultComicsPlayerSettings = {
|
||||
langDir: 'ltr',
|
||||
pagesPerView: 1
|
||||
};
|
||||
|
||||
export class UserSettings {
|
||||
/**
|
||||
* Bind UserSettings instance to user.
|
||||
|
@ -516,6 +521,27 @@ export class UserSettings {
|
|||
return this.set(key, JSON.stringify(value), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get comics player settings.
|
||||
* @param {string} mediaSourceId - Media Source Id.
|
||||
* @return {Object} Comics player settings.
|
||||
*/
|
||||
getComicsPlayerSettings(mediaSourceId) {
|
||||
const settings = JSON.parse(this.get('comicsPlayerSettings', false) || '{}');
|
||||
return Object.assign(defaultComicsPlayerSettings, settings[mediaSourceId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set comics player settings.
|
||||
* @param {Object} value - Comics player settings.
|
||||
* @param {string} mediaSourceId - Media Source Id.
|
||||
*/
|
||||
setComicsPlayerSettings(value, mediaSourceId) {
|
||||
const settings = JSON.parse(this.get('comicsPlayerSettings', false) || '{}');
|
||||
settings[mediaSourceId] = value;
|
||||
return this.set('comicsPlayerSettings', JSON.stringify(settings), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set filter.
|
||||
* @param {string} key - Filter key.
|
||||
|
@ -572,6 +598,8 @@ export const loadQuerySettings = currentSettings.loadQuerySettings.bind(currentS
|
|||
export const saveQuerySettings = currentSettings.saveQuerySettings.bind(currentSettings);
|
||||
export const getSubtitleAppearanceSettings = currentSettings.getSubtitleAppearanceSettings.bind(currentSettings);
|
||||
export const setSubtitleAppearanceSettings = currentSettings.setSubtitleAppearanceSettings.bind(currentSettings);
|
||||
export const getComicsPlayerSettings = currentSettings.getComicsPlayerSettings.bind(currentSettings);
|
||||
export const setComicsPlayerSettings = currentSettings.setComicsPlayerSettings.bind(currentSettings);
|
||||
export const setFilter = currentSettings.setFilter.bind(currentSettings);
|
||||
export const getFilter = currentSettings.getFilter.bind(currentSettings);
|
||||
export const customCss = currentSettings.customCss.bind(currentSettings);
|
||||
|
|
|
@ -1,307 +0,0 @@
|
|||
import 'core-js/stable';
|
||||
import 'regenerator-runtime/runtime';
|
||||
import 'jquery';
|
||||
import 'fast-text-encoding';
|
||||
import 'intersection-observer';
|
||||
import 'classlist.js';
|
||||
import 'whatwg-fetch';
|
||||
import 'resize-observer-polyfill';
|
||||
import '../assets/css/site.scss';
|
||||
import React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { Events } from 'jellyfin-apiclient';
|
||||
import ServerConnections from '../components/ServerConnections';
|
||||
import globalize from './globalize';
|
||||
import browser from './browser';
|
||||
import keyboardNavigation from './keyboardNavigation';
|
||||
import './mouseManager';
|
||||
import autoFocuser from '../components/autoFocuser';
|
||||
import { appHost } from '../components/apphost';
|
||||
import { getPlugins } from './settings/webSettings';
|
||||
import { pluginManager } from '../components/pluginManager';
|
||||
import packageManager from '../components/packageManager';
|
||||
import { appRouter, history } from '../components/appRouter';
|
||||
import '../elements/emby-button/emby-button';
|
||||
import './autoThemes';
|
||||
import './libraryMenu';
|
||||
import './routes';
|
||||
import '../components/themeMediaPlayer';
|
||||
import './autoBackdrops';
|
||||
import { pageClassOn, serverAddress } from '../utils/dashboard';
|
||||
import './screensavermanager';
|
||||
import './serverNotifications';
|
||||
import '../components/playback/playerSelectionMenu';
|
||||
import '../legacy/domParserTextHtml';
|
||||
import '../legacy/focusPreventScroll';
|
||||
import '../legacy/htmlMediaElement';
|
||||
import '../legacy/vendorStyles';
|
||||
import SyncPlay from '../components/syncPlay/core';
|
||||
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 './taskbutton';
|
||||
import { HistoryRouter } from '../components/HistoryRouter.tsx';
|
||||
import AppRoutes from '../routes/index.tsx';
|
||||
|
||||
function loadCoreDictionary() {
|
||||
const languages = ['af', 'ar', 'be-by', 'bg-bg', 'bn_bd', 'ca', 'cs', 'cy', 'da', 'de', 'el', 'en-gb', 'en-us', 'eo', 'es', 'es-419', 'es-ar', 'es_do', 'es-mx', 'et', 'fa', 'fi', 'fil', 'fr', 'fr-ca', 'gl', 'gsw', 'he', 'hi-in', 'hr', 'hu', 'id', 'it', 'ja', 'kk', 'ko', 'lt-lt', 'lv', 'mr', 'ms', 'nb', 'nl', 'nn', 'pl', 'pr', 'pt', 'pt-br', 'pt-pt', 'ro', 'ru', 'sk', 'sl-si', 'sq', 'sv', 'ta', 'th', 'tr', 'uk', 'ur_pk', 'vi', 'zh-cn', 'zh-hk', 'zh-tw'];
|
||||
const translations = languages.map(function (language) {
|
||||
return {
|
||||
lang: language,
|
||||
path: language + '.json'
|
||||
};
|
||||
});
|
||||
globalize.defaultModule('core');
|
||||
return globalize.loadStrings({
|
||||
name: 'core',
|
||||
translations: translations
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
// This is used in plugins
|
||||
window.Events = Events;
|
||||
window.TaskButton = taskButton;
|
||||
|
||||
serverAddress().then(server => {
|
||||
if (server) {
|
||||
ServerConnections.initApiClient(server);
|
||||
}
|
||||
}).then(() => {
|
||||
console.debug('initAfterDependencies promises resolved');
|
||||
|
||||
loadCoreDictionary().then(function () {
|
||||
onGlobalizeInit();
|
||||
});
|
||||
|
||||
keyboardNavigation.enable();
|
||||
autoFocuser.enable();
|
||||
|
||||
Events.on(ServerConnections, 'localusersignedin', globalize.updateCurrentCulture);
|
||||
});
|
||||
}
|
||||
|
||||
function onGlobalizeInit() {
|
||||
if (window.appMode === 'android') {
|
||||
if (window.location.href.toString().toLowerCase().indexOf('start=backgroundsync') !== -1) {
|
||||
return onAppReady();
|
||||
}
|
||||
}
|
||||
|
||||
document.title = globalize.translateHtml(document.title, 'core');
|
||||
|
||||
if (browser.tv && !browser.android) {
|
||||
console.debug('using system fonts with explicit sizes');
|
||||
import('../assets/css/fonts.sized.scss');
|
||||
} else {
|
||||
console.debug('using default fonts');
|
||||
import('../assets/css/fonts.scss');
|
||||
}
|
||||
|
||||
import('../assets/css/librarybrowser.scss');
|
||||
|
||||
loadPlugins().then(function () {
|
||||
initSyncPlay();
|
||||
onAppReady();
|
||||
});
|
||||
}
|
||||
|
||||
function loadPlugins() {
|
||||
console.groupCollapsed('loading installed plugins');
|
||||
console.dir(pluginManager);
|
||||
return getPlugins().then(function (list) {
|
||||
if (!appHost.supports('remotecontrol')) {
|
||||
// Disable remote player plugins if not supported
|
||||
list = list.filter(plugin => !plugin.startsWith('sessionPlayer')
|
||||
&& !plugin.startsWith('chromecastPlayer'));
|
||||
} else if (!browser.chrome && !browser.edgeChromium && !browser.opera) {
|
||||
// Disable chromecast player in unsupported browsers
|
||||
list = list.filter(plugin => !plugin.startsWith('chromecastPlayer'));
|
||||
}
|
||||
|
||||
// add any native plugins
|
||||
if (window.NativeShell) {
|
||||
list = list.concat(window.NativeShell.getPlugins());
|
||||
}
|
||||
|
||||
Promise.all(list.map(plugin => pluginManager.loadPlugin(plugin)))
|
||||
.then(() => console.debug('finished loading plugins'))
|
||||
.catch(e => console.warn('failed loading plugins', e))
|
||||
.finally(() => {
|
||||
console.groupEnd('loading installed plugins');
|
||||
packageManager.init();
|
||||
})
|
||||
;
|
||||
});
|
||||
}
|
||||
|
||||
function initSyncPlay() {
|
||||
// Register player wrappers.
|
||||
SyncPlay.PlayerFactory.setDefaultWrapper(SyncPlayNoActivePlayer);
|
||||
SyncPlay.PlayerFactory.registerWrapper(SyncPlayHtmlVideoPlayer);
|
||||
SyncPlay.PlayerFactory.registerWrapper(SyncPlayHtmlAudioPlayer);
|
||||
|
||||
// Listen for player changes.
|
||||
Events.on(playbackManager, 'playerchange', (event, newPlayer, newTarget, oldPlayer) => {
|
||||
SyncPlay.Manager.onPlayerChange(newPlayer, newTarget, oldPlayer);
|
||||
});
|
||||
|
||||
// Start SyncPlay.
|
||||
const apiClient = ServerConnections.currentApiClient();
|
||||
if (apiClient) SyncPlay.Manager.init(apiClient);
|
||||
|
||||
// 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()));
|
||||
}
|
||||
|
||||
async function onAppReady() {
|
||||
console.debug('begin onAppReady');
|
||||
|
||||
console.debug('onAppReady: loading dependencies');
|
||||
|
||||
if (browser.iOS) {
|
||||
import('../assets/css/ios.scss');
|
||||
}
|
||||
|
||||
Events.on(appHost, 'resume', () => {
|
||||
ServerConnections.currentApiClient()?.ensureWebSocket();
|
||||
});
|
||||
|
||||
await appRouter.start();
|
||||
|
||||
ReactDOM.render(
|
||||
<HistoryRouter history={history}>
|
||||
<AppRoutes />
|
||||
</HistoryRouter>,
|
||||
document.getElementById('reactRoot')
|
||||
);
|
||||
|
||||
if (!browser.tv && !browser.xboxOne && !browser.ps4) {
|
||||
import('../components/nowPlayingBar/nowPlayingBar');
|
||||
}
|
||||
|
||||
if (appHost.supports('remotecontrol')) {
|
||||
import('../components/playback/playerSelectionMenu');
|
||||
import('../components/playback/remotecontrolautoplay');
|
||||
}
|
||||
|
||||
if (!appHost.supports('physicalvolumecontrol') || browser.touch) {
|
||||
import('../components/playback/volumeosd');
|
||||
}
|
||||
|
||||
/* eslint-disable-next-line compat/compat */
|
||||
if (navigator.mediaSession || window.NativeShell) {
|
||||
import('../components/playback/mediasession');
|
||||
}
|
||||
|
||||
if (!browser.tv && !browser.xboxOne) {
|
||||
import('../components/playback/playbackorientation');
|
||||
registerServiceWorker();
|
||||
|
||||
if (window.Notification) {
|
||||
import('../components/notifications/notifications');
|
||||
}
|
||||
}
|
||||
|
||||
const apiClient = ServerConnections.currentApiClient();
|
||||
if (apiClient) {
|
||||
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();
|
||||
})
|
||||
.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 = '';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleLanguageChange = () => {
|
||||
const locale = globalize.getCurrentLocale();
|
||||
|
||||
document.documentElement.setAttribute('lang', locale);
|
||||
};
|
||||
|
||||
const handleUserChange = () => {
|
||||
handleStyleChange();
|
||||
handleLanguageChange();
|
||||
};
|
||||
|
||||
Events.on(ServerConnections, 'localusersignedin', handleUserChange);
|
||||
Events.on(ServerConnections, 'localusersignedout', handleUserChange);
|
||||
Events.on(currentSettings, 'change', (e, prop) => {
|
||||
if (prop == 'disableCustomCss' || prop == 'customCss') {
|
||||
handleStyleChange();
|
||||
} else if (prop == 'language') {
|
||||
handleLanguageChange();
|
||||
}
|
||||
});
|
||||
|
||||
style.then(updateStyle);
|
||||
}
|
||||
}
|
||||
|
||||
function registerServiceWorker() {
|
||||
/* eslint-disable compat/compat */
|
||||
if (navigator.serviceWorker && window.appMode !== 'cordova' && window.appMode !== 'android') {
|
||||
navigator.serviceWorker.register('serviceworker.js').then(() =>
|
||||
console.log('serviceWorker registered')
|
||||
).catch(error =>
|
||||
console.log('error registering serviceWorker: ' + error)
|
||||
);
|
||||
} else {
|
||||
console.warn('serviceWorker unsupported');
|
||||
}
|
||||
/* eslint-enable compat/compat */
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
pageClassOn('viewshow', 'standalonePage', function () {
|
||||
document.querySelector('.skinHeader').classList.add('noHeaderRight');
|
||||
});
|
||||
|
||||
pageClassOn('viewhide', 'standalonePage', function () {
|
||||
document.querySelector('.skinHeader').classList.remove('noHeaderRight');
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue