mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Merge branch 'master' into master
This commit is contained in:
commit
7810ff464b
12 changed files with 55 additions and 68 deletions
|
@ -12,7 +12,7 @@ import { playbackManager } from 'components/playback/playbackmanager';
|
||||||
import React, { FC, useCallback, useState } from 'react';
|
import React, { FC, useCallback, useState } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { enable, isEnabled, supported } from 'scripts/autocast';
|
import { enable, isEnabled } from 'scripts/autocast';
|
||||||
import globalize from 'scripts/globalize';
|
import globalize from 'scripts/globalize';
|
||||||
|
|
||||||
interface RemotePlayActiveMenuProps extends MenuProps {
|
interface RemotePlayActiveMenuProps extends MenuProps {
|
||||||
|
@ -43,11 +43,10 @@ const RemotePlayActiveMenu: FC<RemotePlayActiveMenuProps> = ({
|
||||||
}, [ isDisplayMirrorEnabled, setIsDisplayMirrorEnabled ]);
|
}, [ isDisplayMirrorEnabled, setIsDisplayMirrorEnabled ]);
|
||||||
|
|
||||||
const [ isAutoCastEnabled, setIsAutoCastEnabled ] = useState(isEnabled());
|
const [ isAutoCastEnabled, setIsAutoCastEnabled ] = useState(isEnabled());
|
||||||
const isAutoCastSupported = supported();
|
|
||||||
const toggleAutoCast = useCallback(() => {
|
const toggleAutoCast = useCallback(() => {
|
||||||
enable(!isAutoCastEnabled);
|
enable(!isAutoCastEnabled);
|
||||||
setIsAutoCastEnabled(!isAutoCastEnabled);
|
setIsAutoCastEnabled(!isAutoCastEnabled);
|
||||||
}, [ isAutoCastEnabled, setIsAutoCastEnabled ]);
|
}, [ isAutoCastEnabled ]);
|
||||||
|
|
||||||
const remotePlayerName = playerInfo?.deviceName || playerInfo?.name;
|
const remotePlayerName = playerInfo?.deviceName || playerInfo?.name;
|
||||||
|
|
||||||
|
@ -117,20 +116,18 @@ const RemotePlayActiveMenu: FC<RemotePlayActiveMenuProps> = ({
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isAutoCastSupported && (
|
<MenuItem onClick={toggleAutoCast}>
|
||||||
<MenuItem onClick={toggleAutoCast}>
|
{isAutoCastEnabled && (
|
||||||
{isAutoCastEnabled && (
|
<ListItemIcon>
|
||||||
<ListItemIcon>
|
<Check />
|
||||||
<Check />
|
</ListItemIcon>
|
||||||
</ListItemIcon>
|
)}
|
||||||
)}
|
<ListItemText inset={!isAutoCastEnabled}>
|
||||||
<ListItemText inset={!isAutoCastEnabled}>
|
{globalize.translate('EnableAutoCast')}
|
||||||
{globalize.translate('EnableAutoCast')}
|
</ListItemText>
|
||||||
</ListItemText>
|
</MenuItem>
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{(isDisplayMirrorSupported || isAutoCastSupported) && <Divider />}
|
<Divider />
|
||||||
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
component={Link}
|
component={Link}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { RouteObject, redirect } from 'react-router-dom';
|
import { Navigate, RouteObject } from 'react-router-dom';
|
||||||
|
|
||||||
import { REDIRECTS } from 'apps/dashboard/routes/_redirects';
|
import { REDIRECTS } from 'apps/dashboard/routes/_redirects';
|
||||||
import ConnectionRequired from 'components/ConnectionRequired';
|
import ConnectionRequired from 'components/ConnectionRequired';
|
||||||
|
@ -35,7 +35,7 @@ export const EXPERIMENTAL_APP_ROUTES: RouteObject[] = [
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Public routes */
|
/* Public routes */
|
||||||
{ index: true, loader: () => redirect('/home.html') },
|
{ index: true, element: <Navigate replace to='/home.html' /> },
|
||||||
...LEGACY_PUBLIC_ROUTES.map(toViewManagerPageRoute)
|
...LEGACY_PUBLIC_ROUTES.map(toViewManagerPageRoute)
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { RouteObject, redirect } from 'react-router-dom';
|
import { Navigate, RouteObject } from 'react-router-dom';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import ConnectionRequired from 'components/ConnectionRequired';
|
import ConnectionRequired from 'components/ConnectionRequired';
|
||||||
|
@ -30,7 +30,7 @@ export const STABLE_APP_ROUTES: RouteObject[] = [
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Public routes */
|
/* Public routes */
|
||||||
{ index: true, loader: () => redirect('/home.html') },
|
{ index: true, element: <Navigate replace to='/home.html' /> },
|
||||||
...LEGACY_PUBLIC_ROUTES.map(toViewManagerPageRoute)
|
...LEGACY_PUBLIC_ROUTES.map(toViewManagerPageRoute)
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -83,7 +83,7 @@ const ConnectionRequired: FunctionComponent<ConnectionRequiredProps> = ({
|
||||||
if (firstConnection.State === ConnectionState.ServerSignIn) {
|
if (firstConnection.State === ConnectionState.ServerSignIn) {
|
||||||
// Verify the wizard is complete
|
// Verify the wizard is complete
|
||||||
try {
|
try {
|
||||||
const infoResponse = await fetch(`${firstConnection.ApiClient.serverAddress()}/System/Info/Public`);
|
const infoResponse = await fetch(`${firstConnection.ApiClient.serverAddress()}/System/Info/Public`, { cache: 'no-cache' });
|
||||||
if (!infoResponse.ok) {
|
if (!infoResponse.ok) {
|
||||||
throw new Error('Public system info request failed');
|
throw new Error('Public system info request failed');
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { pluginManager } from '../pluginManager';
|
||||||
import { appRouter } from '../router/appRouter';
|
import { appRouter } from '../router/appRouter';
|
||||||
import globalize from '../../scripts/globalize';
|
import globalize from '../../scripts/globalize';
|
||||||
import { appHost } from '../apphost';
|
import { appHost } from '../apphost';
|
||||||
import { enable, isEnabled, supported } from '../../scripts/autocast';
|
import { enable, isEnabled } from '../../scripts/autocast';
|
||||||
import '../../elements/emby-checkbox/emby-checkbox';
|
import '../../elements/emby-checkbox/emby-checkbox';
|
||||||
import '../../elements/emby-button/emby-button';
|
import '../../elements/emby-button/emby-button';
|
||||||
import dialog from '../dialog/dialog';
|
import dialog from '../dialog/dialog';
|
||||||
|
@ -200,13 +200,11 @@ function showActivePlayerMenuInternal(playerInfo) {
|
||||||
|
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
|
|
||||||
if (supported()) {
|
html += '<div><label class="checkboxContainer">';
|
||||||
html += '<div><label class="checkboxContainer">';
|
const checkedHtmlAC = isEnabled() ? ' checked' : '';
|
||||||
const checkedHtmlAC = isEnabled() ? ' checked' : '';
|
html += '<input type="checkbox" is="emby-checkbox" class="chkAutoCast"' + checkedHtmlAC + '/>';
|
||||||
html += '<input type="checkbox" is="emby-checkbox" class="chkAutoCast"' + checkedHtmlAC + '/>';
|
html += '<span>' + globalize.translate('EnableAutoCast') + '</span>';
|
||||||
html += '<span>' + globalize.translate('EnableAutoCast') + '</span>';
|
html += '</label></div>';
|
||||||
html += '</label></div>';
|
|
||||||
}
|
|
||||||
|
|
||||||
html += '<div style="margin-top:1em;display:flex;justify-content: flex-end;">';
|
html += '<div style="margin-top:1em;display:flex;justify-content: flex-end;">';
|
||||||
|
|
||||||
|
|
|
@ -387,7 +387,7 @@ class AppRouter {
|
||||||
if (firstResult) {
|
if (firstResult) {
|
||||||
if (firstResult.State === ConnectionState.ServerSignIn) {
|
if (firstResult.State === ConnectionState.ServerSignIn) {
|
||||||
const url = firstResult.ApiClient.serverAddress() + '/System/Info/Public';
|
const url = firstResult.ApiClient.serverAddress() + '/System/Info/Public';
|
||||||
fetch(url).then(response => {
|
fetch(url, { cache: 'no-cache' }).then(response => {
|
||||||
if (!response.ok) return Promise.reject(new Error('fetch failed'));
|
if (!response.ok) return Promise.reject(new Error('fetch failed'));
|
||||||
return response.json();
|
return response.json();
|
||||||
}).then(data => {
|
}).then(data => {
|
||||||
|
|
|
@ -23,7 +23,7 @@ export const getSystemInfoQuery = (
|
||||||
api?: Api
|
api?: Api
|
||||||
) => queryOptions({
|
) => queryOptions({
|
||||||
queryKey: [ 'SystemInfo' ],
|
queryKey: [ 'SystemInfo' ],
|
||||||
queryFn: ({ signal }) => fetchSystemInfo(api, { signal }),
|
queryFn: ({ signal }) => fetchSystemInfo(api, { signal, headers: { 'Cache-Control': 'no-cache' } }),
|
||||||
// Allow for query reuse in legacy javascript.
|
// Allow for query reuse in legacy javascript.
|
||||||
staleTime: 1000, // 1 second
|
staleTime: 1000, // 1 second
|
||||||
enabled: !!api
|
enabled: !!api
|
||||||
|
|
|
@ -24,6 +24,7 @@ import packageManager from './components/packageManager';
|
||||||
import './components/playback/displayMirrorManager.ts';
|
import './components/playback/displayMirrorManager.ts';
|
||||||
import { appRouter } from './components/router/appRouter';
|
import { appRouter } from './components/router/appRouter';
|
||||||
import './elements/emby-button/emby-button';
|
import './elements/emby-button/emby-button';
|
||||||
|
import { initialize as initializeAutoCast } from 'scripts/autocast';
|
||||||
import './scripts/autoThemes';
|
import './scripts/autoThemes';
|
||||||
import './components/themeMediaPlayer';
|
import './components/themeMediaPlayer';
|
||||||
import { pageClassOn, serverAddress } from './utils/dashboard';
|
import { pageClassOn, serverAddress } from './utils/dashboard';
|
||||||
|
@ -79,6 +80,8 @@ build: ${__JF_BUILD_VERSION__}`);
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
console.debug('initAfterDependencies promises resolved');
|
console.debug('initAfterDependencies promises resolved');
|
||||||
|
|
||||||
|
initializeAutoCast(ServerConnections.currentApiClient());
|
||||||
|
|
||||||
loadCoreDictionary().then(function () {
|
loadCoreDictionary().then(function () {
|
||||||
onGlobalizeInit();
|
onGlobalizeInit();
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,6 +7,7 @@ import ServerConnections from '../../components/ServerConnections';
|
||||||
import Screenfull from 'screenfull';
|
import Screenfull from 'screenfull';
|
||||||
import TableOfContents from './tableOfContents';
|
import TableOfContents from './tableOfContents';
|
||||||
import { translateHtml } from '../../scripts/globalize';
|
import { translateHtml } from '../../scripts/globalize';
|
||||||
|
import browser from 'scripts/browser';
|
||||||
import * as userSettings from '../../scripts/settings/userSettings';
|
import * as userSettings from '../../scripts/settings/userSettings';
|
||||||
import TouchHelper from 'scripts/touchHelper';
|
import TouchHelper from 'scripts/touchHelper';
|
||||||
import { PluginType } from '../../types/plugin.ts';
|
import { PluginType } from '../../types/plugin.ts';
|
||||||
|
@ -45,6 +46,7 @@ export class BookPlayer {
|
||||||
this.previous = this.previous.bind(this);
|
this.previous = this.previous.bind(this);
|
||||||
this.next = this.next.bind(this);
|
this.next = this.next.bind(this);
|
||||||
this.onWindowKeyUp = this.onWindowKeyUp.bind(this);
|
this.onWindowKeyUp = this.onWindowKeyUp.bind(this);
|
||||||
|
this.addSwipeGestures = this.addSwipeGestures.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
play(options) {
|
play(options) {
|
||||||
|
@ -155,6 +157,12 @@ export class BookPlayer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addSwipeGestures(element) {
|
||||||
|
this.touchHelper = new TouchHelper(element);
|
||||||
|
Events.on(this.touchHelper, 'swipeleft', () => this.next());
|
||||||
|
Events.on(this.touchHelper, 'swiperight', () => this.previous());
|
||||||
|
}
|
||||||
|
|
||||||
onDialogClosed() {
|
onDialogClosed() {
|
||||||
this.stop();
|
this.stop();
|
||||||
}
|
}
|
||||||
|
@ -179,10 +187,12 @@ export class BookPlayer {
|
||||||
document.addEventListener('keyup', this.onWindowKeyUp);
|
document.addEventListener('keyup', this.onWindowKeyUp);
|
||||||
this.rendition?.on('keyup', this.onWindowKeyUp);
|
this.rendition?.on('keyup', this.onWindowKeyUp);
|
||||||
|
|
||||||
const player = document.getElementById('bookPlayerContainer');
|
if (browser.safari) {
|
||||||
this.touchHelper = new TouchHelper(player);
|
const player = document.getElementById('bookPlayerContainer');
|
||||||
Events.on(this.touchHelper, 'swipeleft', () => this.next());
|
this.addSwipeGestures(player);
|
||||||
Events.on(this.touchHelper, 'swiperight', () => this.previous());
|
} else {
|
||||||
|
this.rendition?.on('rendered', (e, i) => this.addSwipeGestures(i.document.documentElement));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unbindMediaElementEvents() {
|
unbindMediaElementEvents() {
|
||||||
|
@ -207,6 +217,10 @@ export class BookPlayer {
|
||||||
document.removeEventListener('keyup', this.onWindowKeyUp);
|
document.removeEventListener('keyup', this.onWindowKeyUp);
|
||||||
this.rendition?.off('keyup', this.onWindowKeyUp);
|
this.rendition?.off('keyup', this.onWindowKeyUp);
|
||||||
|
|
||||||
|
if (!browser.safari) {
|
||||||
|
this.rendition?.off('rendered', (e, i) => this.addSwipeGestures(i.document.documentElement));
|
||||||
|
}
|
||||||
|
|
||||||
this.touchHelper?.destroy();
|
this.touchHelper?.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,7 @@
|
||||||
import { playbackManager } from '../components/playback/playbackmanager';
|
import { playbackManager } from '../components/playback/playbackmanager';
|
||||||
import ServerConnections from '../components/ServerConnections';
|
|
||||||
import Events from '../utils/events.ts';
|
import Events from '../utils/events.ts';
|
||||||
|
|
||||||
export function supported() {
|
|
||||||
return typeof(Storage) !== 'undefined';
|
|
||||||
}
|
|
||||||
|
|
||||||
export function enable(enabled) {
|
export function enable(enabled) {
|
||||||
if (!supported()) return;
|
|
||||||
|
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
const currentPlayerInfo = playbackManager.getPlayerInfo();
|
const currentPlayerInfo = playbackManager.getPlayerInfo();
|
||||||
|
|
||||||
|
@ -21,8 +14,6 @@ export function enable(enabled) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isEnabled() {
|
export function isEnabled() {
|
||||||
if (!supported()) return false;
|
|
||||||
|
|
||||||
const playerId = localStorage.getItem('autocastPlayerId');
|
const playerId = localStorage.getItem('autocastPlayerId');
|
||||||
const currentPlayerInfo = playbackManager.getPlayerInfo();
|
const currentPlayerInfo = playbackManager.getPlayerInfo();
|
||||||
|
|
||||||
|
@ -42,12 +33,10 @@ function onOpen() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
export function initialize(apiClient) {
|
||||||
const apiClient = ServerConnections.currentApiClient();
|
if (apiClient) {
|
||||||
|
|
||||||
if (apiClient && supported()) {
|
|
||||||
Events.on(apiClient, 'websocketopen', onOpen);
|
Events.on(apiClient, 'websocketopen', onOpen);
|
||||||
|
} else {
|
||||||
|
console.warn('[autoCast] cannot initialize missing apiClient');
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
|
||||||
console.warn('Could not get current apiClient', ex);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,19 +27,11 @@ function fallback(urls) {
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
function sameDomain(url) {
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.href = url;
|
|
||||||
|
|
||||||
return window.location.hostname === a.hostname && window.location.protocol === a.protocol;
|
|
||||||
}
|
|
||||||
|
|
||||||
function download(url) {
|
function download(url) {
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.download = '';
|
a.download = '';
|
||||||
a.href = url;
|
a.href = url;
|
||||||
// firefox doesn't support `a.click()`...
|
a.click();
|
||||||
a.dispatchEvent(new MouseEvent('click'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function (urls) {
|
export default function (urls) {
|
||||||
|
@ -47,19 +39,13 @@ export default function (urls) {
|
||||||
throw new Error('`urls` required');
|
throw new Error('`urls` required');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof document.createElement('a').download === 'undefined') {
|
if (typeof document.createElement('a').download === 'undefined' || browser.iOS) {
|
||||||
return fallback(urls);
|
return fallback(urls);
|
||||||
}
|
}
|
||||||
|
|
||||||
let delay = 0;
|
let delay = 0;
|
||||||
|
|
||||||
urls.forEach(function (url) {
|
urls.forEach(function (url) {
|
||||||
// the download init has to be sequential for firefox if the urls are not on the same domain
|
setTimeout(download.bind(null, url), 100 * ++delay);
|
||||||
if (browser.firefox && !sameDomain(url)) {
|
|
||||||
setTimeout(download.bind(null, url), 100 * ++delay);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
download(url);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ export async function serverAddress() {
|
||||||
console.debug('URL candidates:', urls);
|
console.debug('URL candidates:', urls);
|
||||||
|
|
||||||
const promises = urls.map(url => {
|
const promises = urls.map(url => {
|
||||||
return fetch(`${url}/System/Info/Public`)
|
return fetch(`${url}/System/Info/Public`, { cache: 'no-cache' })
|
||||||
.then(async resp => {
|
.then(async resp => {
|
||||||
if (!resp.ok) {
|
if (!resp.ok) {
|
||||||
return;
|
return;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue