Skip to content
Snippets Groups Projects
Commit f5581c78 authored by Reiter, Christoph's avatar Reiter, Christoph :snake:
Browse files

app-shell: allow trailing paths in the router

In case the path ending with the activity ID has more trailing path elements, we don't fall
back to the default route but just ignore them (and save them in the router state).

This allows passing in extra information via the path without breaking the routing,
and in case we want to forward the routing to the activities in the future we can use
the extra path elements we store for that.

For this to work with our current logic we have to stop comparing path strings and compare
the resulting computed state everywhere instead.
parent 9be5c627
No related branches found
No related tags found
1 merge request!179app-shell: allow trailing paths in the router
Pipeline #106352 passed
...@@ -62,6 +62,7 @@ export class AppShell extends ScopedElementsMixin(DBPLitElement) { ...@@ -62,6 +62,7 @@ export class AppShell extends ScopedElementsMixin(DBPLitElement) {
this._roles = []; this._roles = [];
this._i18n = createInstance(); this._i18n = createInstance();
this.lang = this._i18n.language; this.lang = this._i18n.language;
this._extra = [];
this.matomoUrl = ''; this.matomoUrl = '';
this.matomoSiteId = -1; this.matomoSiteId = -1;
...@@ -179,6 +180,7 @@ export class AppShell extends ScopedElementsMixin(DBPLitElement) { ...@@ -179,6 +180,7 @@ export class AppShell extends ScopedElementsMixin(DBPLitElement) {
return { return {
lang: this.lang, lang: this.lang,
component: '', component: '',
extra: [],
}; };
}, },
}, },
...@@ -191,18 +193,20 @@ export class AppShell extends ScopedElementsMixin(DBPLitElement) { ...@@ -191,18 +193,20 @@ export class AppShell extends ScopedElementsMixin(DBPLitElement) {
return { return {
lang: params.lang, lang: params.lang,
component: '', component: '',
extra: [],
}; };
}, },
}, },
{ {
name: 'mainRoute', name: 'mainRoute',
path: '/:component', path: ['/:component', '/:component/(.*)'],
action: (context, params) => { action: (context, params) => {
// remove the additional parameters added by Keycloak let componentTag = params.component.toLowerCase();
let componentTag = params.component.toLowerCase().replace(/&.+/, ''); let extra = params[0] ? params[0].split('/') : [];
return { return {
lang: params.lang, lang: params.lang,
component: componentTag, component: componentTag,
extra: extra,
}; };
}, },
}, },
...@@ -218,17 +222,20 @@ export class AppShell extends ScopedElementsMixin(DBPLitElement) { ...@@ -218,17 +222,20 @@ export class AppShell extends ScopedElementsMixin(DBPLitElement) {
let state = { let state = {
component: this.activeView, component: this.activeView,
lang: this.lang, lang: this.lang,
extra: this._extra,
}; };
return state; return state;
}, },
setState: (state) => { setState: (state) => {
this.updateLangIfChanged(state.lang); this.updateLangIfChanged(state.lang);
this.switchComponent(state.component); this.switchComponent(state.component);
this._extra = state.extra;
}, },
getDefaultState: () => { getDefaultState: () => {
return { return {
lang: 'de', lang: 'de',
component: this.routes[0], component: this.routes[0],
extra: [],
}; };
}, },
}, },
...@@ -270,8 +277,10 @@ export class AppShell extends ScopedElementsMixin(DBPLitElement) { ...@@ -270,8 +277,10 @@ export class AppShell extends ScopedElementsMixin(DBPLitElement) {
connectedCallback() { connectedCallback() {
super.connectedCallback(); super.connectedCallback();
if (this.src) this.fetchMetadata(this.src);
this.initRouter(); this.initRouter();
if (this.src) {
this.fetchMetadata(this.src);
}
} }
/** /**
......
...@@ -61,7 +61,7 @@ class AppShellWelcome extends ScopedElementsMixin(LitElement) { ...@@ -61,7 +61,7 @@ class AppShellWelcome extends ScopedElementsMixin(LitElement) {
cursor: pointer; cursor: pointer;
text-decoration: none; text-decoration: none;
} }
h2 a { h2 a {
white-space: nowrap; white-space: nowrap;
} }
......
import UniversalRouter from 'universal-router'; import UniversalRouter from 'universal-router';
import generateUrls from 'universal-router/generateUrls'; import generateUrls from 'universal-router/generateUrls';
function stateMatches(a, b) {
return JSON.stringify(a, Object.keys(a).sort()) === JSON.stringify(b, Object.keys(b).sort());
}
/** /**
* A wrapper around UniversalRouter which adds history integration * A wrapper around UniversalRouter which adds history integration
*/ */
...@@ -31,28 +35,24 @@ export class Router { ...@@ -31,28 +35,24 @@ export class Router {
window.addEventListener('popstate', (event) => { window.addEventListener('popstate', (event) => {
this.setStateFromCurrentLocation(); this.setStateFromCurrentLocation();
this.dispatchLocationChanged(); this._dispatchLocationChanged();
}); });
} }
async _getStateForPath(pathname) {
let isBasePath = pathname.replace(/\/$/, '') === this.router.baseUrl.replace(/\/$/, '');
if (isBasePath) {
return this.getDefaultState();
}
return this.router.resolve({pathname: pathname});
}
/** /**
* In case something else has changed the location, update the app state accordingly. * In case something else has changed the location, update the app state accordingly.
*/ */
setStateFromCurrentLocation() { setStateFromCurrentLocation() {
const oldPathName = location.pathname; this._getStateForPath(location.pathname)
this.router
.resolve({pathname: oldPathName})
.then((page) => { .then((page) => {
const newPathname = this.getPathname(page);
// In case of a router redirect, set the new location
if (newPathname !== oldPathName) {
const referrerUrl = location.href;
window.history.replaceState({}, '', newPathname);
this.dispatchLocationChanged(referrerUrl);
} else if (this.isBasePath(oldPathName)) {
page = this.getDefaultState();
}
this.setState(page); this.setState(page);
}) })
.catch((e) => { .catch((e) => {
...@@ -61,10 +61,6 @@ export class Router { ...@@ -61,10 +61,6 @@ export class Router {
}); });
} }
isBasePath(pathname) {
return pathname.replace(/\/$/, '') === this.router.baseUrl.replace(/\/$/, '');
}
/** /**
* Update the router after some internal state change. * Update the router after some internal state change.
*/ */
...@@ -72,18 +68,21 @@ export class Router { ...@@ -72,18 +68,21 @@ export class Router {
// Queue updates so we can call this multiple times when changing state // Queue updates so we can call this multiple times when changing state
// without it resulting in multiple location changes // without it resulting in multiple location changes
setTimeout(() => { setTimeout(() => {
const newPathname = this.getPathname(); this._getStateForPath(location.pathname)
const oldPathname = location.pathname; .then((page) => {
if (newPathname === oldPathname) return; const newState = this.getState();
// if the state has changed we update
const defaultPathname = this.getPathname(this.getDefaultState()); if (!stateMatches(newState, page)) {
if (newPathname === defaultPathname && this.isBasePath(oldPathname)) { const newPathname = this.getPathname();
return; const referrerUrl = location.href;
} window.history.pushState({}, '', newPathname);
this._dispatchLocationChanged(referrerUrl);
const referrerUrl = location.href; }
window.history.pushState({}, '', newPathname); })
this.dispatchLocationChanged(referrerUrl); .catch((e) => {
// In case we can't resolve the location, just leave things as is.
// This happens when a user enters a wrong URL or when testing with karma.
});
}); });
} }
...@@ -93,14 +92,15 @@ export class Router { ...@@ -93,14 +92,15 @@ export class Router {
* @param {string} pathname * @param {string} pathname
*/ */
updateFromPathname(pathname) { updateFromPathname(pathname) {
this.router this._getStateForPath(pathname)
.resolve({pathname: pathname})
.then((page) => { .then((page) => {
if (location.pathname === pathname) return; const oldState = this.getState();
const referrerUrl = location.href; if (!stateMatches(oldState, page)) {
window.history.pushState({}, '', pathname); const referrerUrl = location.href;
this.setState(page); window.history.pushState({}, '', pathname);
this.dispatchLocationChanged(referrerUrl); this.setState(page);
this._dispatchLocationChanged(referrerUrl);
}
}) })
.catch((err) => { .catch((err) => {
throw new Error(`Route not found: ${pathname}: ${err}`); throw new Error(`Route not found: ${pathname}: ${err}`);
...@@ -117,7 +117,9 @@ export class Router { ...@@ -117,7 +117,9 @@ export class Router {
*/ */
getPathname(partialState) { getPathname(partialState) {
const currentState = this.getState(); const currentState = this.getState();
if (partialState === undefined) partialState = {}; if (partialState === undefined) {
partialState = {};
}
let combined = {...currentState, ...partialState}; let combined = {...currentState, ...partialState};
try { try {
...@@ -128,7 +130,7 @@ export class Router { ...@@ -128,7 +130,7 @@ export class Router {
} }
} }
dispatchLocationChanged(referrerUrl = '') { _dispatchLocationChanged(referrerUrl = '') {
// fire a locationchanged event // fire a locationchanged event
window.dispatchEvent( window.dispatchEvent(
new CustomEvent('locationchanged', { new CustomEvent('locationchanged', {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment