Merge pull request #3585 from thornbill/moar-router-cleanup

This commit is contained in:
Bill Thornton 2022-04-25 17:20:05 -04:00 committed by GitHub
commit 2c02c1c8ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 134 additions and 198 deletions

View File

@ -1,20 +1,49 @@
import { ConnectionManager, Credentials, ApiClient, Events } from 'jellyfin-apiclient'; import { ConnectionManager, Credentials, ApiClient, Events } from 'jellyfin-apiclient';
import { appHost } from './apphost'; import { appHost } from './apphost';
import Dashboard from '../utils/dashboard'; import Dashboard from '../utils/dashboard';
import { setUserInfo } from '../scripts/settings/userSettings'; import { setUserInfo } from '../scripts/settings/userSettings';
import appSettings from '../scripts/settings/appSettings';
const normalizeImageOptions = options => {
if (!options.quality && (options.maxWidth || options.width || options.maxHeight || options.height || options.fillWidth || options.fillHeight)) {
options.quality = 90;
}
};
const getMaxBandwidth = () => {
/* eslint-disable compat/compat */
if (navigator.connection) {
let max = navigator.connection.downlinkMax;
if (max && max > 0 && max < Number.POSITIVE_INFINITY) {
max /= 8;
max *= 1000000;
max *= 0.7;
return parseInt(max, 10);
}
}
/* eslint-enable compat/compat */
return null;
};
class ServerConnections extends ConnectionManager { class ServerConnections extends ConnectionManager {
constructor() { constructor() {
super(...arguments); super(...arguments);
this.localApiClient = null; this.localApiClient = null;
Events.on(this, 'localusersignedout', function (eventName, logoutInfo) { Events.on(this, 'localusersignedout', (_e, logoutInfo) => {
setUserInfo(null, null); setUserInfo(null, null);
if (window.NativeShell && typeof window.NativeShell.onLocalUserSignedOut === 'function') { if (window.NativeShell && typeof window.NativeShell.onLocalUserSignedOut === 'function') {
window.NativeShell.onLocalUserSignedOut(logoutInfo); window.NativeShell.onLocalUserSignedOut(logoutInfo);
} }
}); });
Events.on(this, 'apiclientcreated', (_e, apiClient) => {
apiClient.getMaxBandwidth = getMaxBandwidth;
apiClient.normalizeImageOptions = normalizeImageOptions;
});
} }
initApiClient(server) { initApiClient(server) {
@ -38,6 +67,13 @@ class ServerConnections extends ConnectionManager {
console.debug('loaded ApiClient singleton'); console.debug('loaded ApiClient singleton');
} }
connect(options) {
return super.connect({
enableAutoLogin: appSettings.enableAutoLogin(),
...options
});
}
setLocalApiClient(apiClient) { setLocalApiClient(apiClient) {
if (apiClient) { if (apiClient) {
this.localApiClient = apiClient; this.localApiClient = apiClient;

View File

@ -2,42 +2,38 @@ import { Events } from 'jellyfin-apiclient';
import { Action, createHashHistory } from 'history'; import { Action, createHashHistory } from 'history';
import { appHost } from './apphost'; import { appHost } from './apphost';
import appSettings from '../scripts/settings/appSettings';
import { clearBackdrop, setBackdropTransparency } from './backdrop/backdrop'; import { clearBackdrop, setBackdropTransparency } from './backdrop/backdrop';
import globalize from '../scripts/globalize'; import globalize from '../scripts/globalize';
import itemHelper from './itemHelper'; import itemHelper from './itemHelper';
import loading from './loading/loading'; import loading from './loading/loading';
import viewManager from './viewManager/viewManager'; import viewManager from './viewManager/viewManager';
import Dashboard from '../utils/dashboard';
import ServerConnections from './ServerConnections'; import ServerConnections from './ServerConnections';
import alert from './alert'; import alert from './alert';
import reactControllerFactory from './reactControllerFactory'; import reactControllerFactory from './reactControllerFactory';
const history = createHashHistory(); const history = createHashHistory();
/**
* Page types of "no return" (when "Go back" should behave differently, probably quitting the application).
*/
const START_PAGE_TYPES = ['home', 'login', 'selectserver'];
class AppRouter { class AppRouter {
allRoutes = new Map(); allRoutes = new Map();
backdropContainer;
backgroundContainer;
currentRouteInfo; currentRouteInfo;
currentViewLoadRequest; currentViewLoadRequest;
firstConnectionResult; firstConnectionResult;
forcedLogoutMsg; forcedLogoutMsg;
isDummyBackToHome;
msgTimeout; msgTimeout;
promiseShow; promiseShow;
resolveOnNextShow; resolveOnNextShow;
previousRoute = {}; previousRoute = {};
/**
* Pages of "no return" (when "Go back" should behave differently, probably quitting the application).
*/
startPages = ['home', 'login', 'selectserver'];
constructor() { constructor() {
document.addEventListener('viewshow', () => this.onViewShow()); document.addEventListener('viewshow', () => this.onViewShow());
// TODO: Can this baseRoute logic be simplified? // TODO: Can this baseRoute logic be simplified?
this.baseRoute = window.location.href.split('?')[0].replace(this.getRequestFile(), ''); this.baseRoute = window.location.href.split('?')[0].replace(this.#getRequestFile(), '');
// support hashbang // support hashbang
this.baseRoute = this.baseRoute.split('#')[0]; this.baseRoute = this.baseRoute.split('#')[0];
if (this.baseRoute.endsWith('/') && !this.baseRoute.endsWith('://')) { if (this.baseRoute.endsWith('/') && !this.baseRoute.endsWith('://')) {
@ -52,33 +48,11 @@ class AppRouter {
}); });
} }
showLocalLogin(serverId) { #beginConnectionWizard() {
Dashboard.navigate('login.html?serverid=' + serverId);
}
showVideoOsd() {
return Dashboard.navigate('video');
}
showSelectServer() {
Dashboard.navigate('selectserver.html');
}
showSettings() {
Dashboard.navigate('mypreferencesmenu.html');
}
showNowPlaying() {
this.show('queue');
}
beginConnectionWizard() {
clearBackdrop(); clearBackdrop();
loading.show(); loading.show();
ServerConnections.connect({ ServerConnections.connect().then(result => {
enableAutoLogin: appSettings.enableAutoLogin() this.#handleConnectionResult(result);
}).then((result) => {
this.handleConnectionResult(result);
}); });
} }
@ -128,18 +102,6 @@ class AppRouter {
return this.promiseShow; return this.promiseShow;
} }
async showDirect(path) {
if (this.promiseShow) await this.promiseShow;
this.promiseShow = new Promise((resolve) => {
this.resolveOnNextShow = resolve;
// Schedule a call to return the promise
setTimeout(() => history.push(this.baseUrl() + path), 0);
});
return this.promiseShow;
}
#goToRoute({ location, action }) { #goToRoute({ location, action }) {
// Strip the leading "!" if present // Strip the leading "!" if present
const normalizedPath = location.pathname.replace(/^!/, ''); const normalizedPath = location.pathname.replace(/^!/, '');
@ -163,13 +125,18 @@ class AppRouter {
start() { start() {
loading.show(); loading.show();
this.initApiClients();
Events.on(appHost, 'resume', this.onAppResume); ServerConnections.getApiClients().forEach(apiClient => {
Events.off(apiClient, 'requestfail', this.onRequestFail);
Events.on(apiClient, 'requestfail', this.onRequestFail);
});
ServerConnections.connect({ Events.on(ServerConnections, 'apiclientcreated', (_e, apiClient) => {
enableAutoLogin: appSettings.enableAutoLogin() Events.off(apiClient, 'requestfail', this.onRequestFail);
}).then((result) => { Events.on(apiClient, 'requestfail', this.onRequestFail);
});
ServerConnections.connect().then(result => {
this.firstConnectionResult = result; this.firstConnectionResult = result;
// Handle the initial route // Handle the initial route
@ -189,40 +156,18 @@ class AppRouter {
} }
canGoBack() { canGoBack() {
const curr = this.current(); const curr = this.currentRouteInfo?.route;
if (!curr) { if (!curr) {
return false; return false;
} }
if (!document.querySelector('.dialogContainer') && this.startPages.indexOf(curr.type) !== -1) { if (!document.querySelector('.dialogContainer') && START_PAGE_TYPES.includes(curr.type)) {
return false; return false;
} }
return window.history.length > 1; return window.history.length > 1;
} }
current() {
return this.currentRouteInfo ? this.currentRouteInfo.route : null;
}
invokeShortcut(id) {
if (id.indexOf('library-') === 0) {
id = id.replace('library-', '');
id = id.split('_');
this.showItem(id[0], id[1]);
} else if (id.indexOf('item-') === 0) {
id = id.replace('item-', '');
id = id.split('_');
this.showItem(id[0], id[1]);
} else {
id = id.split('_');
this.show(this.getRouteUrl(id[0], {
serverId: id[1]
}));
}
}
showItem(item, serverId, options) { showItem(item, serverId, options) {
// TODO: Refactor this so it only gets items, not strings. // TODO: Refactor this so it only gets items, not strings.
if (typeof (item) === 'string') { if (typeof (item) === 'string') {
@ -236,9 +181,7 @@ class AppRouter {
} }
const url = this.getRouteUrl(item, options); const url = this.getRouteUrl(item, options);
this.show(url, { this.show(url, { item });
item: item
});
} }
} }
@ -252,20 +195,14 @@ class AppRouter {
setBackdropTransparency(level); setBackdropTransparency(level);
} }
handleConnectionResult(result) { #handleConnectionResult(result) {
switch (result.State) { switch (result.State) {
case 'SignedIn': case 'SignedIn':
loading.hide(); loading.hide();
this.goHome(); this.goHome();
break; break;
case 'ServerSignIn': case 'ServerSignIn':
result.ApiClient.getPublicUsers().then((users) => { this.showLocalLogin(result.ApiClient.serverId());
if (users.length) {
this.showLocalLogin(result.Servers[0].Id);
} else {
this.showLocalLogin(result.Servers[0].Id, true);
}
});
break; break;
case 'ServerSelection': case 'ServerSelection':
this.showSelectServer(); this.showSelectServer();
@ -283,7 +220,7 @@ class AppRouter {
} }
} }
loadContentUrl(ctx, next, route, request) { #loadContentUrl(ctx, _next, route, request) {
let url; let url;
if (route.contentPath && typeof (route.contentPath) === 'function') { if (route.contentPath && typeof (route.contentPath) === 'function') {
url = route.contentPath(ctx.querystring); url = route.contentPath(ctx.querystring);
@ -305,19 +242,19 @@ class AppRouter {
} }
promise.then((html) => { promise.then((html) => {
this.loadContent(ctx, route, html, request); this.#loadContent(ctx, route, html, request);
}); });
} }
handleRoute(ctx, next, route) { #handleRoute(ctx, next, route) {
this.authenticate(ctx, route, () => { this.#authenticate(ctx, route, () => {
this.initRoute(ctx, next, route); this.#initRoute(ctx, next, route);
}); });
} }
initRoute(ctx, next, route) { #initRoute(ctx, next, route) {
const onInitComplete = (controllerFactory) => { const onInitComplete = (controllerFactory) => {
this.sendRouteToViewManager(ctx, next, route, controllerFactory); this.#sendRouteToViewManager(ctx, next, route, controllerFactory);
}; };
if (route.pageComponent) { if (route.pageComponent) {
@ -329,20 +266,15 @@ class AppRouter {
} }
} }
cancelCurrentLoadRequest() { #cancelCurrentLoadRequest() {
const currentRequest = this.currentViewLoadRequest; const currentRequest = this.currentViewLoadRequest;
if (currentRequest) { if (currentRequest) {
currentRequest.cancel = true; currentRequest.cancel = true;
} }
} }
sendRouteToViewManager(ctx, next, route, controllerFactory) { #sendRouteToViewManager(ctx, next, route, controllerFactory) {
if (this.isDummyBackToHome && route.type === 'home') { this.#cancelCurrentLoadRequest();
this.isDummyBackToHome = false;
return;
}
this.cancelCurrentLoadRequest();
const isBackNav = ctx.isBack; const isBackNav = ctx.isBack;
const currentRequest = { const currentRequest = {
@ -364,7 +296,7 @@ class AppRouter {
const onNewViewNeeded = () => { const onNewViewNeeded = () => {
if (typeof route.path === 'string') { if (typeof route.path === 'string') {
this.loadContentUrl(ctx, next, route, currentRequest); this.#loadContentUrl(ctx, next, route, currentRequest);
} else { } else {
next(); next();
} }
@ -413,7 +345,7 @@ class AppRouter {
this.msgTimeout = setTimeout(this.onForcedLogoutMessageTimeout, 100); this.msgTimeout = setTimeout(this.onForcedLogoutMessageTimeout, 100);
} }
onRequestFail(e, data) { onRequestFail(_e, data) {
const apiClient = this; const apiClient = this;
if (data.status === 403) { if (data.status === 403) {
@ -429,62 +361,7 @@ class AppRouter {
} }
} }
normalizeImageOptions(options) { #authenticate(ctx, route, callback) {
let setQuality;
if (options.maxWidth || options.width || options.maxHeight || options.height || options.fillWidth || options.fillHeight) {
setQuality = true;
}
if (setQuality && !options.quality) {
options.quality = 90;
}
}
getMaxBandwidth() {
/* eslint-disable compat/compat */
if (navigator.connection) {
let max = navigator.connection.downlinkMax;
if (max && max > 0 && max < Number.POSITIVE_INFINITY) {
max /= 8;
max *= 1000000;
max *= 0.7;
return parseInt(max, 10);
}
}
/* eslint-enable compat/compat */
return null;
}
onApiClientCreated(e, newApiClient) {
newApiClient.normalizeImageOptions = this.normalizeImageOptions;
newApiClient.getMaxBandwidth = this.getMaxBandwidth;
Events.off(newApiClient, 'requestfail', this.onRequestFail);
Events.on(newApiClient, 'requestfail', this.onRequestFail);
}
initApiClient(apiClient, instance) {
instance.onApiClientCreated({}, apiClient);
}
initApiClients() {
ServerConnections.getApiClients().forEach((apiClient) => {
this.initApiClient(apiClient, this);
});
Events.on(ServerConnections, 'apiclientcreated', this.onApiClientCreated);
}
onAppResume() {
const apiClient = ServerConnections.currentApiClient();
if (apiClient) {
apiClient.ensureWebSocket();
}
}
authenticate(ctx, route, callback) {
const firstResult = this.firstConnectionResult; const firstResult = this.firstConnectionResult;
this.firstConnectionResult = null; this.firstConnectionResult = null;
@ -497,9 +374,9 @@ class AppRouter {
}).then(data => { }).then(data => {
if (data !== null && data.StartupWizardCompleted === false) { if (data !== null && data.StartupWizardCompleted === false) {
ServerConnections.setLocalApiClient(firstResult.ApiClient); ServerConnections.setLocalApiClient(firstResult.ApiClient);
Dashboard.navigate('wizardstart.html'); this.show('wizardstart.html');
} else { } else {
this.handleConnectionResult(firstResult); this.#handleConnectionResult(firstResult);
} }
}).catch(error => { }).catch(error => {
console.error(error); console.error(error);
@ -507,7 +384,7 @@ class AppRouter {
return; return;
} else if (firstResult.State !== 'SignedIn') { } else if (firstResult.State !== 'SignedIn') {
this.handleConnectionResult(firstResult); this.#handleConnectionResult(firstResult);
return; return;
} }
} }
@ -521,7 +398,7 @@ class AppRouter {
if (!shouldExitApp && (!apiClient || !apiClient.isLoggedIn()) && !route.anonymous) { if (!shouldExitApp && (!apiClient || !apiClient.isLoggedIn()) && !route.anonymous) {
console.debug('[appRouter] route does not allow anonymous access: redirecting to login'); console.debug('[appRouter] route does not allow anonymous access: redirecting to login');
this.beginConnectionWizard(); this.#beginConnectionWizard();
return; return;
} }
@ -541,9 +418,9 @@ class AppRouter {
this.goHome(); this.goHome();
return; return;
} else if (route.roles) { } else if (route.roles) {
this.validateRoles(apiClient, route.roles).then(() => { this.#validateRoles(apiClient, route.roles).then(() => {
callback(); callback();
}, this.beginConnectionWizard); }, this.#beginConnectionWizard.bind(this));
return; return;
} }
} }
@ -552,13 +429,13 @@ class AppRouter {
callback(); callback();
} }
validateRoles(apiClient, roles) { #validateRoles(apiClient, roles) {
return Promise.all(roles.split(',').map((role) => { return Promise.all(roles.split(',').map((role) => {
return this.validateRole(apiClient, role); return this.#validateRole(apiClient, role);
})); }));
} }
validateRole(apiClient, role) { #validateRole(apiClient, role) {
if (role === 'admin') { if (role === 'admin') {
return apiClient.getCurrentUser().then((user) => { return apiClient.getCurrentUser().then((user) => {
if (user.Policy.IsAdministrator) { if (user.Policy.IsAdministrator) {
@ -572,7 +449,7 @@ class AppRouter {
return Promise.resolve(); return Promise.resolve();
} }
loadContent(ctx, route, html, request) { #loadContent(ctx, route, html, request) {
html = globalize.translateHtml(html, route.dictionary); html = globalize.translateHtml(html, route.dictionary);
request.view = html; request.view = html;
@ -586,7 +463,7 @@ class AppRouter {
ctx.handled = true; ctx.handled = true;
} }
getRequestFile() { #getRequestFile() {
let path = window.location.pathname || ''; let path = window.location.pathname || '';
const index = path.lastIndexOf('/'); const index = path.lastIndexOf('/');
@ -613,34 +490,10 @@ class AppRouter {
return; return;
} }
this.handleRoute(ctx, next, route); this.#handleRoute(ctx, next, route);
}; };
} }
showGuide() {
Dashboard.navigate('livetv.html?tab=1');
}
goHome() {
Dashboard.navigate('home.html');
}
showSearch() {
Dashboard.navigate('search.html');
}
showLiveTV() {
Dashboard.navigate('livetv.html');
}
showRecordedTV() {
Dashboard.navigate('livetv.html?tab=3');
}
showFavorites() {
Dashboard.navigate('home.html?tab=1');
}
getRouteUrl(item, options) { getRouteUrl(item, options) {
if (!item) { if (!item) {
throw new Error('item cannot be null'); throw new Error('item cannot be null');
@ -835,10 +688,53 @@ class AppRouter {
return '#!/details?id=' + id + '&serverId=' + serverId; return '#!/details?id=' + id + '&serverId=' + serverId;
} }
showLocalLogin(serverId) {
return this.show('login.html?serverid=' + serverId);
}
showVideoOsd() {
return this.show('video');
}
showSelectServer() {
return this.show('selectserver.html');
}
showSettings() {
return this.show('mypreferencesmenu.html');
}
showNowPlaying() {
return this.show('queue');
}
showGuide() {
return this.show('livetv.html?tab=1');
}
goHome() {
return this.show('home.html');
}
showSearch() {
return this.show('search.html');
}
showLiveTV() {
return this.show('livetv.html');
}
showRecordedTV() {
return this.show('livetv.html?tab=3');
}
showFavorites() {
return this.show('home.html?tab=1');
}
} }
export const appRouter = new AppRouter(); export const appRouter = new AppRouter();
window.Emby = window.Emby || {}; window.Emby = window.Emby || {};
window.Emby.Page = appRouter; window.Emby.Page = appRouter;

View File

@ -163,6 +163,10 @@ async function onAppReady() {
import('../assets/css/ios.scss'); import('../assets/css/ios.scss');
} }
Events.on(appHost, 'resume', () => {
ServerConnections.currentApiClient()?.ensureWebSocket();
});
appRouter.start(); appRouter.start();
if (!browser.tv && !browser.xboxOne && !browser.ps4) { if (!browser.tv && !browser.xboxOne && !browser.ps4) {