Merge pull request #3563 from thornbill/history-router

Use history for app router
This commit is contained in:
Bill Thornton 2022-04-21 12:21:30 -04:00 committed by GitHub
commit 6412156210
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 62 additions and 120 deletions

35
package-lock.json generated
View File

@ -2083,7 +2083,6 @@
"version": "7.13.10", "version": "7.13.10",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz",
"integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==",
"dev": true,
"requires": { "requires": {
"regenerator-runtime": "^0.13.4" "regenerator-runtime": "^0.13.4"
} }
@ -7020,6 +7019,14 @@
"resolved": "https://registry.npmjs.org/headroom.js/-/headroom.js-0.12.0.tgz", "resolved": "https://registry.npmjs.org/headroom.js/-/headroom.js-0.12.0.tgz",
"integrity": "sha512-iXnAafUm3FdzfJ91uixLws2hkKI1jC8bAKK/pt7XYr8Ie1jO7xbK48Ycpl9tUPyBgkzuj1p/PhJS0fy4E/5anA==" "integrity": "sha512-iXnAafUm3FdzfJ91uixLws2hkKI1jC8bAKK/pt7XYr8Ie1jO7xbK48Ycpl9tUPyBgkzuj1p/PhJS0fy4E/5anA=="
}, },
"history": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz",
"integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==",
"requires": {
"@babel/runtime": "^7.7.6"
}
},
"hls.js": { "hls.js": {
"version": "0.14.17", "version": "0.14.17",
"resolved": "https://registry.npmjs.org/hls.js/-/hls.js-0.14.17.tgz", "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-0.14.17.tgz",
@ -8902,14 +8909,6 @@
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true "dev": true
}, },
"page": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/page/-/page-1.11.6.tgz",
"integrity": "sha512-P6e2JfzkBrPeFCIPplLP7vDDiU84RUUZMrWdsH4ZBGJ8OosnwFkcUkBHp1DTIjuipLliw9yQn/ZJsXZvarsO+g==",
"requires": {
"path-to-regexp": "~1.2.1"
}
},
"pako": { "pako": {
"version": "1.0.11", "version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
@ -9016,21 +9015,6 @@
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true "dev": true
}, },
"path-to-regexp": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.2.1.tgz",
"integrity": "sha1-szcFwUAjTYc8hyHHuf2LVB7Tr/k=",
"requires": {
"isarray": "0.0.1"
},
"dependencies": {
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
}
}
},
"path-type": { "path-type": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
@ -10841,8 +10825,7 @@
"regenerator-runtime": { "regenerator-runtime": {
"version": "0.13.7", "version": "0.13.7",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
"integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew=="
"dev": true
}, },
"regenerator-transform": { "regenerator-transform": {
"version": "0.14.5", "version": "0.14.5",

View File

@ -80,6 +80,7 @@
"fast-text-encoding": "1.0.3", "fast-text-encoding": "1.0.3",
"flv.js": "1.6.2", "flv.js": "1.6.2",
"headroom.js": "0.12.0", "headroom.js": "0.12.0",
"history": "5.3.0",
"hls.js": "0.14.17", "hls.js": "0.14.17",
"intersection-observer": "0.12.0", "intersection-observer": "0.12.0",
"jellyfin-apiclient": "1.10.0", "jellyfin-apiclient": "1.10.0",
@ -91,7 +92,6 @@
"marked": "4.0.10", "marked": "4.0.10",
"material-design-icons-iconfont": "6.1.1", "material-design-icons-iconfont": "6.1.1",
"native-promise-only": "0.8.1", "native-promise-only": "0.8.1",
"page": "1.11.6",
"pdfjs-dist": "2.12.313", "pdfjs-dist": "2.12.313",
"react": "17.0.2", "react": "17.0.2",
"react-dom": "17.0.2", "react-dom": "17.0.2",

View File

@ -1,20 +1,22 @@
import { Events } from 'jellyfin-apiclient';
import { Action, createHashHistory } from 'history';
import { appHost } from './apphost'; import { appHost } from './apphost';
import appSettings from '../scripts/settings/appSettings'; import appSettings from '../scripts/settings/appSettings';
import { clearBackdrop, setBackdropTransparency } from './backdrop/backdrop'; import { clearBackdrop, setBackdropTransparency } from './backdrop/backdrop';
import browser from '../scripts/browser';
import { Events } from 'jellyfin-apiclient';
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 page from 'page';
import viewManager from './viewManager/viewManager'; import viewManager from './viewManager/viewManager';
import Dashboard from '../utils/dashboard'; 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();
class AppRouter { class AppRouter {
allRoutes = []; allRoutes = new Map();
backdropContainer; backdropContainer;
backgroundContainer; backgroundContainer;
currentRouteInfo; currentRouteInfo;
@ -23,7 +25,6 @@ class AppRouter {
forcedLogoutMsg; forcedLogoutMsg;
isDummyBackToHome; isDummyBackToHome;
msgTimeout; msgTimeout;
popstateOccurred = false;
promiseShow; promiseShow;
resolveOnNextShow; resolveOnNextShow;
previousRoute = {}; previousRoute = {};
@ -33,58 +34,24 @@ class AppRouter {
startPages = ['home', 'login', 'selectserver']; startPages = ['home', 'login', 'selectserver'];
constructor() { constructor() {
// WebKit fires a popstate event on document load
// Skip it using boolean
// For Tizen 2.x
// See `page` node module
let loaded = document.readyState === 'complete';
if (!loaded) {
window.addEventListener('load', () => {
setTimeout(() => {
loaded = true;
}, 0);
});
}
window.addEventListener('popstate', () => {
if (!loaded) return;
this.popstateOccurred = true;
});
document.addEventListener('viewshow', () => this.onViewShow()); document.addEventListener('viewshow', () => this.onViewShow());
// 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('://')) {
this.baseRoute = this.baseRoute.substring(0, this.baseRoute.length - 1); this.baseRoute = this.baseRoute.substring(0, this.baseRoute.length - 1);
} }
}
this.setBaseRoute(); addRoute(path, route) {
this.allRoutes.set(path, {
// paths that start with a hashbang (i.e. /#!/page.html) get transformed to starting with // route,
// we need to strip one "/" for our routes to work handler: this.#getHandler(route)
page('//*', (ctx) => {
page.redirect(ctx.path.substring(1));
}); });
} }
/**
* @private
*/
setBaseRoute() {
let baseRoute = window.location.pathname.replace(this.getRequestFile(), '');
if (baseRoute.lastIndexOf('/') === baseRoute.length - 1) {
baseRoute = baseRoute.substring(0, baseRoute.length - 1);
}
console.debug('setting page base to ' + baseRoute);
page.base(baseRoute);
}
addRoute(path, newRoute) {
page(path, this.getHandler(newRoute));
this.allRoutes.push(newRoute);
}
showLocalLogin(serverId) { showLocalLogin(serverId) {
Dashboard.navigate('login.html?serverid=' + serverId); Dashboard.navigate('login.html?serverid=' + serverId);
} }
@ -124,7 +91,7 @@ class AppRouter {
this.promiseShow = new Promise((resolve) => { this.promiseShow = new Promise((resolve) => {
this.resolveOnNextShow = resolve; this.resolveOnNextShow = resolve;
page.back(); history.back();
}); });
return this.promiseShow; return this.promiseShow;
@ -155,7 +122,7 @@ class AppRouter {
this.promiseShow = new Promise((resolve) => { this.promiseShow = new Promise((resolve) => {
this.resolveOnNextShow = resolve; this.resolveOnNextShow = resolve;
// Schedule a call to return the promise // Schedule a call to return the promise
setTimeout(() => page.show(path, options), 0); setTimeout(() => history.push(path, options), 0);
}); });
return this.promiseShow; return this.promiseShow;
@ -167,27 +134,50 @@ class AppRouter {
this.promiseShow = new Promise((resolve) => { this.promiseShow = new Promise((resolve) => {
this.resolveOnNextShow = resolve; this.resolveOnNextShow = resolve;
// Schedule a call to return the promise // Schedule a call to return the promise
setTimeout(() => page.show(this.baseUrl() + path), 0); setTimeout(() => history.push(this.baseUrl() + path), 0);
}); });
return this.promiseShow; return this.promiseShow;
} }
start(options) { #goToRoute({ location, action }) {
// Strip the leading "!" if present
const normalizedPath = location.pathname.replace(/^!/, '');
const route = this.allRoutes.get(normalizedPath);
if (route) {
console.debug('[appRouter] "%s" route found', normalizedPath, location, route);
route.handler({
// Recreate the default context used by page.js: https://github.com/visionmedia/page.js#context
path: normalizedPath + location.search,
pathname: normalizedPath,
querystring: location.search.replace(/^\?/, ''),
state: location.state,
// Custom context variables
isBack: action === Action.Pop
});
} else {
console.warn('[appRouter] "%s" route not found', normalizedPath, location);
}
}
start() {
loading.show(); loading.show();
this.initApiClients(); this.initApiClients();
Events.on(appHost, 'beforeexit', this.onBeforeExit);
Events.on(appHost, 'resume', this.onAppResume); Events.on(appHost, 'resume', this.onAppResume);
ServerConnections.connect({ ServerConnections.connect({
enableAutoLogin: appSettings.enableAutoLogin() enableAutoLogin: appSettings.enableAutoLogin()
}).then((result) => { }).then((result) => {
this.firstConnectionResult = result; this.firstConnectionResult = result;
options = options || {};
page({ // Handle the initial route
click: options.click !== false, this.#goToRoute({ location: history.location });
hashbang: options.hashbang !== false
// Handle route changes
history.listen(params => {
this.#goToRoute(params);
}); });
}).catch().then(() => { }).catch().then(() => {
loading.hide(); loading.hide();
@ -262,19 +252,6 @@ class AppRouter {
setBackdropTransparency(level); setBackdropTransparency(level);
} }
getRoutes() {
return this.allRoutes;
}
pushState(state, title, url) {
state.navigate = false;
window.history.pushState(state, title, url);
}
enableNativeHistory() {
return false;
}
handleConnectionResult(result) { handleConnectionResult(result) {
switch (result.State) { switch (result.State) {
case 'SignedIn': case 'SignedIn':
@ -452,12 +429,6 @@ class AppRouter {
} }
} }
onBeforeExit() {
if (browser.web0s) {
page.restorePreviousState();
}
}
normalizeImageOptions(options) { normalizeImageOptions(options) {
let setQuality; let setQuality;
if (options.maxWidth || options.width || options.maxHeight || options.height || options.fillWidth || options.fillHeight) { if (options.maxWidth || options.width || options.maxHeight || options.height || options.fillWidth || options.fillHeight) {
@ -544,12 +515,12 @@ class AppRouter {
const apiClient = ServerConnections.currentApiClient(); const apiClient = ServerConnections.currentApiClient();
const pathname = ctx.pathname.toLowerCase(); const pathname = ctx.pathname.toLowerCase();
console.debug('processing path request: ' + pathname); console.debug('[appRouter] processing path request: ' + pathname);
const isCurrentRouteStartup = this.currentRouteInfo ? this.currentRouteInfo.route.startup : true; const isCurrentRouteStartup = this.currentRouteInfo ? this.currentRouteInfo.route.startup : true;
const shouldExitApp = ctx.isBack && route.isDefaultRoute && isCurrentRouteStartup; const shouldExitApp = ctx.isBack && route.isDefaultRoute && isCurrentRouteStartup;
if (!shouldExitApp && (!apiClient || !apiClient.isLoggedIn()) && !route.anonymous) { if (!shouldExitApp && (!apiClient || !apiClient.isLoggedIn()) && !route.anonymous) {
console.debug('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;
} }
@ -563,10 +534,10 @@ class AppRouter {
} }
if (apiClient && apiClient.isLoggedIn()) { if (apiClient && apiClient.isLoggedIn()) {
console.debug('user is authenticated'); console.debug('[appRouter] user is authenticated');
if (route.isDefaultRoute) { if (route.isDefaultRoute) {
console.debug('loading home page'); console.debug('[appRouter] loading home page');
this.goHome(); this.goHome();
return; return;
} else if (route.roles) { } else if (route.roles) {
@ -577,7 +548,7 @@ class AppRouter {
} }
} }
console.debug('proceeding to page: ' + pathname); console.debug('[appRouter] proceeding to page: ' + pathname);
callback(); callback();
} }
@ -632,11 +603,8 @@ class AppRouter {
return path; return path;
} }
getHandler(route) { #getHandler(route) {
return (ctx, next) => { return (ctx, next) => {
ctx.isBack = this.popstateOccurred;
this.popstateOccurred = false;
const ignore = route.dummyRoute === true || this.previousRoute.dummyRoute === true; const ignore = route.dummyRoute === true || this.previousRoute.dummyRoute === true;
this.previousRoute = route; this.previousRoute = route;
if (ignore) { if (ignore) {

View File

@ -566,13 +566,7 @@ import { appRouter } from '../components/appRouter';
}); });
defineRoute({ defineRoute({
path: '', path: '/',
isDefaultRoute: true,
autoFocus: false
});
defineRoute({
path: 'index.html',
autoFocus: false, autoFocus: false,
isDefaultRoute: true isDefaultRoute: true
}); });

View File

@ -163,10 +163,7 @@ async function onAppReady() {
import('../assets/css/ios.scss'); import('../assets/css/ios.scss');
} }
appRouter.start({ appRouter.start();
click: false,
hashbang: true
});
if (!browser.tv && !browser.xboxOne && !browser.ps4) { if (!browser.tv && !browser.xboxOne && !browser.ps4) {
import('../components/nowPlayingBar/nowPlayingBar'); import('../components/nowPlayingBar/nowPlayingBar');