Skip to content

Commit

Permalink
Refactored routing (#69)
Browse files Browse the repository at this point in the history
  • Loading branch information
mlhaufe authored Jan 8, 2024
1 parent 1f921aa commit 2bef947
Show file tree
Hide file tree
Showing 20 changed files with 121 additions and 148 deletions.
9 changes: 9 additions & 0 deletions src/lib/zipWith.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Zips two arrays together with a function.
* @param xs The first array.
* @param ys The second array.
* @param f The function to zip the arrays with.
* @returns An array of the merged values.
*/
export default <X, Y, Z>(xs: X[], ys: Y[], f: (x: X, y: Y) => Z) =>
xs.map((x, i) => f(x, ys[i]));
77 changes: 53 additions & 24 deletions src/presentation/Application.mts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type Page from './pages/Page.mjs';
import Router from './Router.mjs';
import NotFoundPage from './pages/NotFoundPage.mjs';
import html from './lib/html.mjs';
import { Breadcrumb, Container, GlobalNav } from '~components/index.mjs';

Expand All @@ -8,16 +8,14 @@ export default class Application extends Container {
customElements.define('x-application', this);
}
#currentPage: Page | null = null;
#router!: Router;
#pages!: typeof Page[];

constructor() {
super({}, []);

document.body.innerHTML = '';
this._installOrUpdateServiceWorker();
this._initRouter().then(() => {
self.navigation.navigate(location.pathname);
});
this._initPages().then(() => self.navigation.navigate(location.href));
}

protected override _initShadowHtml() {
Expand Down Expand Up @@ -62,25 +60,27 @@ export default class Application extends Container {
};
}

protected async _initRouter() {
this.#router = new Router([
['/', (await import('./pages/Home.mjs')).Home],
['/not-found', (await import('./pages/NotFound.mjs')).NotFound],
['/projects', (await import('./pages/projects/Projects.mjs')).Projects],
['/environments', (await import('./pages/environments/Environments.mjs')).Environments],
['/environments/new-entry', (await import('./pages/environments/NewEnvironment.mjs')).NewEnvironment],
['/environments/:slug', (await import('./pages/environments/Environment.mjs')).Environment],
['/environments/:slug/glossary', (await import('./pages/environments/Glossary.mjs')).Glossary],
['/goals', (await import('./pages/goals/Goals.mjs')).Goals],
['/goals/new-entry', (await import('./pages/goals/NewGoals.mjs')).NewGoals],
['/goals/:slug', (await import('./pages/goals/Goal.mjs')).Goal],
['/goals/:slug/rationale', (await import('./pages/goals/Rationale.mjs')).Rationale],
['/goals/:slug/functionality', (await import('./pages/goals/Functionality.mjs')).Functionality],
['/goals/:slug/stakeholders', (await import('./pages/goals/Stakeholders.mjs')).Stakeholders],
['/goals/:slug/use-cases', (await import('./pages/goals/UseCases.mjs')).UseCases],
['/goals/:slug/limitations', (await import('./pages/goals/Limitations.mjs')).Limitations],
]);
this.#router.addEventListener('route', this);
protected async _initPages() {
this.#pages = [
(await import('./pages/HomePage.mjs')).default,
NotFoundPage,
(await import('./pages/projects/ProjectsPage.mjs')).default,
(await import('./pages/environments/NewEnvironmentPage.mjs')).default,
(await import('./pages/environments/EnvironmentPage.mjs')).default,
(await import('./pages/environments/EnvironmentsPage.mjs')).default,
(await import('./pages/environments/GlossaryPage.mjs')).default,
(await import('./pages/goals/NewGoalsPage.mjs')).default,
(await import('./pages/goals/GoalPage.mjs')).default,
(await import('./pages/goals/GoalsPage.mjs')).default,
(await import('./pages/goals/RationalePage.mjs')).default,
(await import('./pages/goals/FunctionalityPage.mjs')).default,
(await import('./pages/goals/StakeholdersPage.mjs')).default,
(await import('./pages/goals/UseCasesPage.mjs')).default,
(await import('./pages/goals/LimitationsPage.mjs')).default,
];

self.navigation.addEventListener('navigate', this);
this.addEventListener('route', this);
}

protected async _installServiceWorker() {
Expand Down Expand Up @@ -122,6 +122,35 @@ export default class Application extends Container {
}
}

onNavigate(event: NavigateEvent): void {
if (!event.canIntercept || event.hashChange)
return;

const origin = document.location.origin,
url = new URL(event.destination.url, origin),
Page = this.#pages.find(Page => {
const route = Page.route,
pattern = route.split('/'),
pathname = url.pathname.split('/');

if (pattern.length !== pathname.length)
return false;

return pattern.every((segment, index) => {
if (segment.startsWith(':'))
return true;

return segment === pathname[index];
});
}) ?? NotFoundPage;
event.intercept({
handler: async () => {
event.preventDefault();
this.dispatchEvent(new CustomEvent('route', { detail: Page }));
}
});
}

onRoute(event: CustomEvent<typeof Page>) {
const Cons = event.detail;
this.#currentPage = new Cons({}, []);
Expand Down
63 changes: 0 additions & 63 deletions src/presentation/Router.mts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import Page from './Page.mjs';

const { p } = html;

export class Home extends Page {
export default class HomePage extends Page {
static override route = '/';
static {
customElements.define('x-page-home', this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import html from '../lib/html.mjs';

const { h1, p, a } = html;

export class NotFound extends Page {
export default class NotFoundPage extends Page {
static override route = '/not-found';
static {
customElements.define('x-page-not-found', this);
}
Expand Down
21 changes: 15 additions & 6 deletions src/presentation/pages/Page.mts
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
import zipWith from '~/lib/zipWith.mjs';
import type { Properties } from '~/types/Properties.mjs';
import buttonTheme from '~/presentation/theme/buttonTheme.mjs';
import { Container } from '~components/index.mjs';
import type { Theme } from '~/types/Theme.mjs';

export default class Page extends Container {
protected override _initShadowStyle() {
return {
...super._initShadowStyle()
};
}
static route = '{undefined}';

constructor(properties: Properties<Page>, children: (Element | string)[]) {
urlParams;

constructor(properties: Exclude<Properties<Page>, 'urlParams'>, children: (Element | string)[]) {
super(properties, children);

const url = new URL(location.href, document.location.origin),
pattern = (this.constructor as typeof Page).route.split('/'),
pathname = url.pathname.split('/');
this.urlParams = Object.fromEntries(url.searchParams.entries());

zipWith(pattern, pathname, (p, n) => {
if (p.startsWith(':'))
this.urlParams[p.slice(1)] = n;
});

const sheet = new CSSStyleSheet(),
pageStyle = this._initPageStyle(),
styleElement = document.createElement('style');
Expand Down
27 changes: 0 additions & 27 deletions src/presentation/pages/SlugPage.mts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { MiniCards, MiniCard } from '~/presentation/components/index.mjs';
import Page from '../Page.mjs';

export class Environment extends Page {
export default class EnvironmentPage extends Page {
static override route = '/environments/:slug';
static {
customElements.define('x-environment-page', this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import EnvironmentRepository from '~/data/EnvironmentRepository.mjs';

const { p } = html;

export class Environments extends Page {
export default class EnvironmentsPage extends Page {
static override route = '/environments';
static {
customElements.define('x-environments-page', this);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import html from '~/presentation/lib/html.mjs';
import { DataTable } from '~/presentation/components/DataTable.mjs';
import SlugPage from '../SlugPage.mjs';
import GlossaryTerm from '~/domain/GlossaryTerm.mjs';
import EnvironmentRepository from '~/data/EnvironmentRepository.mjs';
import GlossaryRepository from '~/data/GlossaryRepository.mjs';
import type Environment from '~/domain/Environment.mjs';
import Page from '../Page.mjs';

const { p } = html;

export class Glossary extends SlugPage {
export default class GlossaryPage extends Page {
static override route = '/environments/:slug/glossary';
static {
customElements.define('x-glossary-page', this);
}
Expand Down Expand Up @@ -59,7 +60,7 @@ export class Glossary extends SlugPage {

this.#environmentRepository.addEventListener('update', () => dataTable.renderData());
this.#glossaryRepository.addEventListener('update', () => dataTable.renderData());
this.#environmentRepository.getBySlug(this.slug).then(environment => {
this.#environmentRepository.getBySlug(this.urlParams['slug']).then(environment => {
this.#environment = environment;
dataTable.renderData();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import requiredTheme from '~/presentation/theme/requiredTheme.mjs';

const { form, label, input, span, button } = html;

export class NewEnvironment extends Page {
export default class NewEnvironmentPage extends Page {
static override route = '/environments/new-entry';
static {
customElements.define('x-new-environment-page', this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import GoalsRepository from '~/data/GoalsRepository.mjs';
import BehaviorRepository from '~/data/BehaviorRepository.mjs';
import html from '~/presentation/lib/html.mjs';
import { DataTable } from '~/presentation/components/DataTable.mjs';
import SlugPage from '../SlugPage.mjs';
import Page from '../Page.mjs';

const { p, strong } = html;

export class Functionality extends SlugPage {
export default class FunctionalityPage extends Page {
static override route = '/goals/:slug/functionality';
static {
customElements.define('x-functionality-page', this);
}
Expand Down Expand Up @@ -58,7 +59,7 @@ export class Functionality extends SlugPage {

this.#goalsRepository.addEventListener('update', () => dataTable.renderData());
this.#behaviorRepository.addEventListener('update', () => dataTable.renderData());
this.#goalsRepository.getBySlug(this.slug).then(goals => {
this.#goalsRepository.getBySlug(this.urlParams['slug']).then(goals => {
this.#goals = goals;
dataTable.renderData();
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import Page from '../Page.mjs';
import { MiniCards, MiniCard } from '~/presentation/components/index.mjs';

export class Goal extends Page {
export default class GoalPage extends Page {
static override route = '/goals/:slug';
static {
customElements.define('x-goal-page', this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import GoalsRepository from '~/data/GoalsRepository.mjs';

const { p } = html;

export class Goals extends Page {
export default class GoalsPage extends Page {
static override route = '/goals';
static {
customElements.define('x-goals-page', this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import GoalsRepository from '~/data/GoalsRepository.mjs';
import LimitRepository from '~/data/LimitRepository.mjs';
import html from '~/presentation/lib/html.mjs';
import { DataTable } from '~/presentation/components/DataTable.mjs';
import SlugPage from '../SlugPage.mjs';
import Page from '../Page.mjs';

const { p } = html;

export class Limitations extends SlugPage {
export default class LimitationsPage extends Page {
static override route = '/goals/:slug/limitations';
static {
customElements.define('x-limitations-page', this);
}
Expand Down Expand Up @@ -60,7 +61,7 @@ export class Limitations extends SlugPage {

this.#goalsRepository.addEventListener('update', () => dataTable.renderData());
this.#limitRepository.addEventListener('update', () => dataTable.renderData());
this.#goalsRepository.getBySlug(this.slug).then(goals => {
this.#goalsRepository.getBySlug(this.urlParams['slug']).then(goals => {
this.#goals = goals;
dataTable.renderData();
});
Expand Down
Loading

0 comments on commit 2bef947

Please sign in to comment.