This commit is contained in:
parent
8a0aa862c7
commit
3f3690b574
11 changed files with 645 additions and 163 deletions
51
index.html
51
index.html
|
@ -14,54 +14,13 @@
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<header>
|
<header></header>
|
||||||
<div id="welcome-message">Benvenuti a <span class="bold">Pianello Val Tidone</span></div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
<main></main>
|
||||||
<div id="route-cards">
|
|
||||||
<div class="route-card">
|
|
||||||
<div class="route-card-left">
|
|
||||||
<img src="static/images/app-bar-logo.png">
|
|
||||||
</div>
|
|
||||||
<div class="route-card-center">
|
|
||||||
<div class="name">
|
|
||||||
<div>Percorsi</div>
|
|
||||||
<div class="bold">naturalistici</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="route-card-right"></div>
|
|
||||||
</div>
|
|
||||||
<div class="route-card">
|
|
||||||
<div class="route-card-left">
|
|
||||||
<img src="static/images/app-bar-logo.png">
|
|
||||||
</div>
|
|
||||||
<div class="route-card-center">
|
|
||||||
<div class="name">
|
|
||||||
<div>Percorsi</div>
|
|
||||||
<div class="bold">storici</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="route-card-right"></div>
|
|
||||||
</div>
|
|
||||||
<div class="route-card">
|
|
||||||
<div class="route-card-left">
|
|
||||||
<img src="static/images/app-bar-logo.png">
|
|
||||||
</div>
|
|
||||||
<div class="route-card-center">
|
|
||||||
<div class="name">
|
|
||||||
<div>Percorsi</div>
|
|
||||||
<div class="bold">tradizionalistici</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="route-card-right"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
<footer></footer>
|
||||||
<bottom-app-bar></bottom-app-bar>
|
|
||||||
</footer>
|
<!-- <wd-loader></wd-loader> -->
|
||||||
|
|
||||||
<script src="/src/components/bottom-app-bar.js" type="module"></script>
|
<script src="/src/components/bottom-app-bar.js" type="module"></script>
|
||||||
<script src="/src/js/index.js" type="module"></script>
|
<script src="/src/js/index.js" type="module"></script>
|
||||||
|
|
|
@ -68,7 +68,7 @@ class BottomAppBar extends HTMLElement {
|
||||||
* Set all shadow dom selectors.
|
* Set all shadow dom selectors.
|
||||||
*/
|
*/
|
||||||
#setElements() {
|
#setElements() {
|
||||||
this.selectors = {};
|
this.elements = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
#addEventListeners() {}
|
#addEventListeners() {}
|
||||||
|
|
223
src/components/wd-loader.js
Normal file
223
src/components/wd-loader.js
Normal file
|
@ -0,0 +1,223 @@
|
||||||
|
const template = `
|
||||||
|
<div class="background">
|
||||||
|
<div class="loader">
|
||||||
|
<svg viewBox="0 0 32 32" width="32" height="32">
|
||||||
|
<circle id="spinner" cx="16" cy="16" r="14" fill="none"></circle>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div id="message"></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
const style = `
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.background {
|
||||||
|
position: fixed;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
opacity: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
z-index: 99999;
|
||||||
|
transition: opacity 0.333s cubic-bezier(0, 0, 0.21, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader {
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
position: fixed;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#spinner {
|
||||||
|
box-sizing: border-box;
|
||||||
|
stroke: var(--accent-color);
|
||||||
|
stroke-width: 3px;
|
||||||
|
transform-origin: 50%;
|
||||||
|
animation: line 1.6s cubic-bezier(0.4, 0, 0.2, 1) infinite, rotate 1.6s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotate {
|
||||||
|
from {
|
||||||
|
-webkit-transform: rotate(0);
|
||||||
|
transform: rotate(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
-webkit-transform: rotate(450deg);
|
||||||
|
transform: rotate(450deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes line {
|
||||||
|
0% {
|
||||||
|
stroke-dasharray: 2, 85.964;
|
||||||
|
-webkit-transform: rotate(0);
|
||||||
|
transform: rotate(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
stroke-dasharray: 65.973, 21.9911;
|
||||||
|
stroke-dashoffset: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
stroke-dasharray: 2, 85.964;
|
||||||
|
stroke-dashoffset: -65.973;
|
||||||
|
-webkit-transform: rotate(90deg);
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#message {
|
||||||
|
position: absolute;
|
||||||
|
height: 20%;
|
||||||
|
bottom: 0px;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #212121;
|
||||||
|
border-bottom-left-radius: 8px;
|
||||||
|
border-bottom-right-radius: 8px;
|
||||||
|
color: #eee;
|
||||||
|
place-items: center;
|
||||||
|
display: none;
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#message.show {
|
||||||
|
display: grid !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host(.contained) .background {
|
||||||
|
position: absolute;
|
||||||
|
width: 250px;
|
||||||
|
height: 250px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host(.contained) .loader {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spinner-style loader ce.
|
||||||
|
*/
|
||||||
|
class WDLoader extends HTMLElement {
|
||||||
|
#elements = {};
|
||||||
|
isVisible = false;
|
||||||
|
#counter = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.#createShadowDOM();
|
||||||
|
this.#setElements();
|
||||||
|
getComputedStyle(this.#elements.background).opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set message.
|
||||||
|
* @param {String} value
|
||||||
|
*/
|
||||||
|
set message(value) {
|
||||||
|
if (value && value !== '') {
|
||||||
|
this.#elements.message.innerHTML = value;
|
||||||
|
this.#elements.message.classList.add('show');
|
||||||
|
} else {
|
||||||
|
this.#elements.message.classList.remove('show');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {WDLoader}
|
||||||
|
*/
|
||||||
|
static get instance() {
|
||||||
|
let loader = document.querySelector('wd-loader');
|
||||||
|
|
||||||
|
if (loader) {
|
||||||
|
return loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
loader = new WDLoader();
|
||||||
|
document.body.appendChild(loader);
|
||||||
|
return loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create shadow DOM.
|
||||||
|
*/
|
||||||
|
#createShadowDOM() {
|
||||||
|
this.attachShadow({mode: 'open'});
|
||||||
|
this.shadowRoot.innerHTML = `
|
||||||
|
<style>${style}</style>
|
||||||
|
${template}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set all shadow dom selectors.
|
||||||
|
*/
|
||||||
|
#setElements() {
|
||||||
|
this.#elements = {
|
||||||
|
background: this.shadowRoot.querySelector('.background'),
|
||||||
|
message: this.shadowRoot.querySelector('#message'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show loader.
|
||||||
|
* @param {String} message
|
||||||
|
*/
|
||||||
|
show(message = '') {
|
||||||
|
this.#counter++;
|
||||||
|
this.style.display = 'block';
|
||||||
|
this.isVisible = true;
|
||||||
|
this.#elements.background.style.opacity = 1;
|
||||||
|
this.message = message;
|
||||||
|
getComputedStyle(this.#elements.background).opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide loader.
|
||||||
|
*/
|
||||||
|
hide() {
|
||||||
|
this.#counter--;
|
||||||
|
|
||||||
|
if (this.#counter > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const onLoaderTransitionEnd = (evt) => {
|
||||||
|
this.style.display = 'none';
|
||||||
|
};
|
||||||
|
|
||||||
|
this.#elements.background.addEventListener(
|
||||||
|
'transitionend',
|
||||||
|
onLoaderTransitionEnd.bind(this),
|
||||||
|
{once: true});
|
||||||
|
|
||||||
|
this.isVisible = false;
|
||||||
|
this.message = '';
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
this.#elements.background.style.opacity = 0;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('wd-loader', WDLoader);
|
||||||
|
|
||||||
|
export default WDLoader;
|
42
src/css/app.css
Normal file
42
src/css/app.css
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--footer-height: calc(68px + 20px + 20px);
|
||||||
|
--accent-color: #213c8b;
|
||||||
|
--pianello-red: #de0e1b;
|
||||||
|
--pianello-yellow: #f6ae04;
|
||||||
|
--pianello-blue: #213c8b;
|
||||||
|
--card-background-color: #f9f4f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0px;
|
||||||
|
background-color: #fff;
|
||||||
|
font-family: 'Roboto-Regular';
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
grid-template-rows: var(--footer-height) auto var(--footer-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
header,
|
||||||
|
main,
|
||||||
|
footer {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bold {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
76
src/css/home-page.css
Normal file
76
src/css/home-page.css
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
/* @import './app.css'; */
|
||||||
|
|
||||||
|
#welcome-message {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#route-cards {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-card {
|
||||||
|
flex: 1;
|
||||||
|
--route-card-radius: 45px;
|
||||||
|
border-radius: var(--route-card-radius);
|
||||||
|
box-shadow: 0 0 27px #ccc;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
width: 90%;
|
||||||
|
display: flex;
|
||||||
|
background-color: var(--card-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-card > * {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-card-left {
|
||||||
|
flex: 0 1 20%;
|
||||||
|
border-top-left-radius: var(--route-card-radius);
|
||||||
|
border-bottom-left-radius: var(--route-card-radius);
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-card-left img {
|
||||||
|
display: block;
|
||||||
|
width: 80%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-card-right {
|
||||||
|
flex: 0 1 20%;
|
||||||
|
border-top-right-radius: var(--route-card-radius);
|
||||||
|
border-bottom-right-radius: var(--route-card-radius);
|
||||||
|
background-image: url('../../static/images/test-1.jpg');
|
||||||
|
background-position: center;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
#route-cards .route-card:nth-child(1) .route-card-left {
|
||||||
|
background-color: var(--pianello-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
#route-cards .route-card:nth-child(2) .route-card-left {
|
||||||
|
background-color: var(--pianello-yellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
#route-cards .route-card:nth-child(3) .route-card-left {
|
||||||
|
background-color: var(--pianello-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-card .name {
|
||||||
|
display: grid;
|
||||||
|
height: 100%;
|
||||||
|
align-content: center;
|
||||||
|
padding: 20px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-card .name > * {
|
||||||
|
flex: 1;
|
||||||
|
}
|
|
@ -1,116 +1 @@
|
||||||
|
@import './app.css';
|
||||||
:root {
|
|
||||||
--footer-height: calc(68px + 20px + 20px);
|
|
||||||
--pianello-red: #de0e1b;
|
|
||||||
--pianello-yellow: #f6ae04;
|
|
||||||
--pianello-blue: #213c8b;
|
|
||||||
--card-background-color: #f9f4f1;
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
margin: 0px;
|
|
||||||
background-color: #fff;
|
|
||||||
font-family: 'Roboto-Regular';
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
grid-template-rows: var(--footer-height) auto var(--footer-height);
|
|
||||||
}
|
|
||||||
|
|
||||||
header,
|
|
||||||
main,
|
|
||||||
footer {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bold {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
#welcome-message {
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#route-cards {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.route-card {
|
|
||||||
flex: 1;
|
|
||||||
--route-card-radius: 45px;
|
|
||||||
border-radius: var(--route-card-radius);
|
|
||||||
box-shadow: 0 0 27px #ccc;
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
width: 90%;
|
|
||||||
display: flex;
|
|
||||||
background-color: var(--card-background-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.route-card > * {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.route-card-left {
|
|
||||||
flex: 0 1 20%;
|
|
||||||
border-top-left-radius: var(--route-card-radius);
|
|
||||||
border-bottom-left-radius: var(--route-card-radius);
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.route-card-left img {
|
|
||||||
display: block;
|
|
||||||
width: 80%;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.route-card-right {
|
|
||||||
flex: 0 1 20%;
|
|
||||||
border-top-right-radius: var(--route-card-radius);
|
|
||||||
border-bottom-right-radius: var(--route-card-radius);
|
|
||||||
background-image: url('../../static/images/test-1.jpg');
|
|
||||||
background-position: center;
|
|
||||||
background-size: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
#route-cards .route-card:nth-child(1) .route-card-left {
|
|
||||||
background-color: var(--pianello-red);
|
|
||||||
}
|
|
||||||
|
|
||||||
#route-cards .route-card:nth-child(2) .route-card-left {
|
|
||||||
background-color: var(--pianello-yellow);
|
|
||||||
}
|
|
||||||
|
|
||||||
#route-cards .route-card:nth-child(3) .route-card-left {
|
|
||||||
background-color: var(--pianello-blue);
|
|
||||||
}
|
|
||||||
|
|
||||||
.route-card .name {
|
|
||||||
display: grid;
|
|
||||||
height: 100%;
|
|
||||||
align-content: center;
|
|
||||||
padding: 20px;
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.route-card .name > * {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
35
src/js/app.js
Normal file
35
src/js/app.js
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import Router from './router.js';
|
||||||
|
import WDLoader from '../components/wd-loader.js';
|
||||||
|
import robotoFontStyle from '../css/roboto.css?raw';
|
||||||
|
import appStyle from '../css/app.css?raw';
|
||||||
|
const appIconURL = new URL('../../static/images/home-icon.png', import.meta.url).href;
|
||||||
|
|
||||||
|
const app = {
|
||||||
|
title: 'Pianello',
|
||||||
|
meta: {
|
||||||
|
charset: 'utf-8',
|
||||||
|
viewport: 'width=device-width',
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
icon: appIconURL,
|
||||||
|
},
|
||||||
|
page: null,
|
||||||
|
router: new Router(),
|
||||||
|
loader: WDLoader.instance,
|
||||||
|
init: () => {
|
||||||
|
document.head.innerHTML = `
|
||||||
|
<meta charset="${app.meta.charset}">
|
||||||
|
<link rel="icon" href="${app.link.icon}">
|
||||||
|
<meta name="viewport" content="${app.meta.viewport}">
|
||||||
|
`;
|
||||||
|
document.title = app.title;
|
||||||
|
document.adoptedStyleSheets = [robotoFontStyle, appStyle]
|
||||||
|
.map((rawSheet) => {
|
||||||
|
const sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(rawSheet);
|
||||||
|
return sheet;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default app;
|
46
src/js/home-page.js
Normal file
46
src/js/home-page.js
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import app from './app.js';
|
||||||
|
import pageStyle from '../css/home-page.css?raw';
|
||||||
|
import BottomAppBar from '../components/bottom-app-bar.js';
|
||||||
|
|
||||||
|
class HomePage {
|
||||||
|
/**
|
||||||
|
* An object containing useful HTMLElement references.
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
#elements = {};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.#addPageStyle();
|
||||||
|
this.#setElements();
|
||||||
|
this.#addEventListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
get elements() {
|
||||||
|
return this.#elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
#addPageStyle() {
|
||||||
|
const sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(pageStyle);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init `#elements` field that holds element references.
|
||||||
|
*/
|
||||||
|
#setElements() {
|
||||||
|
this.#elements = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
#addEventListeners() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HomePage;
|
||||||
|
|
||||||
|
app.init();
|
||||||
|
app.page = new HomePage();
|
|
@ -1,7 +1,13 @@
|
||||||
|
import app from './app.js';
|
||||||
|
import homePageURL from '../pages/home-page.html?url';
|
||||||
|
|
||||||
|
app.init();
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
// Service worker build output path.
|
// Service worker build output path.
|
||||||
navigator.serviceWorker.register('/service-worker.js');
|
navigator.serviceWorker.register('/service-worker.js');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.router.navigate(homePageURL);
|
||||||
});
|
});
|
||||||
|
|
158
src/js/router.js
Normal file
158
src/js/router.js
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
import app from './app.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Router
|
||||||
|
*/
|
||||||
|
class Router {
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.host = location.host;
|
||||||
|
this.previousURL = null;
|
||||||
|
|
||||||
|
this.initListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add all event listeners handles.
|
||||||
|
*/
|
||||||
|
initListeners() {
|
||||||
|
document.addEventListener('click', this.onLinkClick.bind(this));
|
||||||
|
window.addEventListener('popstate', this.onPopState.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* window popstate event handler.
|
||||||
|
* @param {Event} event
|
||||||
|
*/
|
||||||
|
onPopState(event) {
|
||||||
|
this.navigate(location.href, event.state.scrollTop, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTMLAnchorElement click event handler.
|
||||||
|
* @param {Event} event
|
||||||
|
*/
|
||||||
|
async onLinkClick(event) {
|
||||||
|
const anchor = event.target.closest('a');
|
||||||
|
if (!anchor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anchor.dataset.hasOwnProperty('external')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {href} = anchor;
|
||||||
|
const link = new URL(href);
|
||||||
|
|
||||||
|
// If it’s an external link, just navigate.
|
||||||
|
if (link.host !== this.host) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
this.navigate(link.toString())
|
||||||
|
.catch((error) => console.error(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the navigation.
|
||||||
|
* @param {string} link
|
||||||
|
* @param {number} scrollTop
|
||||||
|
* @param {boolean} pushState
|
||||||
|
* @return {Promise<number>} Fetch fragment status
|
||||||
|
*/
|
||||||
|
async navigate(link, scrollTop = 0, pushState = true) {
|
||||||
|
if (!link) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save current route for history pop event.
|
||||||
|
this.previousURL = location.href;
|
||||||
|
// Manually handle the scroll restoration.
|
||||||
|
history.scrollRestoration = 'manual';
|
||||||
|
|
||||||
|
if (pushState) {
|
||||||
|
history.replaceState({
|
||||||
|
scrollTop: document.scrollingElement.scrollTop,
|
||||||
|
}, '');
|
||||||
|
history.pushState({scrollTop}, '', link);
|
||||||
|
}
|
||||||
|
|
||||||
|
const linkUrl = new URL((link.startsWith('http')) ?
|
||||||
|
link :
|
||||||
|
`${location.origin}${link}`);
|
||||||
|
|
||||||
|
// Fetch new page and switch.
|
||||||
|
const oldHeader = document.querySelector('header');
|
||||||
|
const oldMain = document.querySelector('main');
|
||||||
|
const oldFooter = document.querySelector('footer');
|
||||||
|
const {
|
||||||
|
status,
|
||||||
|
header,
|
||||||
|
main,
|
||||||
|
footer,
|
||||||
|
script: newPageScript,
|
||||||
|
} = await this.fetchFragment(link);
|
||||||
|
|
||||||
|
if (status === 200) {
|
||||||
|
// Replace elements.
|
||||||
|
oldHeader?.parentNode?.replaceChild(header, oldHeader);
|
||||||
|
oldMain?.parentNode?.replaceChild(main, oldMain);
|
||||||
|
oldFooter?.parentNode?.replaceChild(footer, oldFooter);
|
||||||
|
const newScriptFilename = newPageScript?.src.split('/').slice(-1)[0];
|
||||||
|
app.loader.show();
|
||||||
|
try {
|
||||||
|
await app.page?.clear?.();
|
||||||
|
// String manipulation from static analysis.
|
||||||
|
const script = await import(
|
||||||
|
`../js/${newScriptFilename.replace('.js', '')}.js`);
|
||||||
|
// Enable current page re-navigation and avoid double page init.
|
||||||
|
// Check only on url.origin+url.pathname to allow
|
||||||
|
// re-navigation on same page with different url parameters.
|
||||||
|
const prevLinkUrl = new URL(this.previousURL);
|
||||||
|
if (!(app.page instanceof script.default) ||
|
||||||
|
(linkUrl.origin + linkUrl.pathname ===
|
||||||
|
prevLinkUrl.origin + prevLinkUrl.pathname)) {
|
||||||
|
// eslint-disable-next-line new-cap
|
||||||
|
app.page = new script.default();
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
console.error(ex);
|
||||||
|
} finally {
|
||||||
|
app.loader.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.scrollingElement.scrollTop = scrollTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set to auto in case user hits page refresh.
|
||||||
|
history.scrollRestoration = 'auto';
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch page and get main element;
|
||||||
|
* @param {string} link
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
async fetchFragment(link) {
|
||||||
|
const url = (link.startsWith('http')) ? link : `${location.origin}${link}`;
|
||||||
|
link = (new URL(url)).pathname;
|
||||||
|
|
||||||
|
const response = await fetch(link);
|
||||||
|
const template = document.createElement('template');
|
||||||
|
template.innerHTML = await response.text();
|
||||||
|
return {
|
||||||
|
status: response.status,
|
||||||
|
header: template.content.querySelector('header')?.cloneNode(true),
|
||||||
|
main: template.content.querySelector('main')?.cloneNode(true),
|
||||||
|
footer: template.content.querySelector('footer')?.cloneNode(true),
|
||||||
|
script: template.content.querySelector('#page-script')?.cloneNode(true),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Router;
|
52
src/pages/home-page.html
Normal file
52
src/pages/home-page.html
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<div id="welcome-message">Benvenuti a <span class="bold">Pianello Val Tidone</span></div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<div id="route-cards">
|
||||||
|
<div class="route-card">
|
||||||
|
<div class="route-card-left">
|
||||||
|
<img src="../../static/images/app-bar-logo.png">
|
||||||
|
</div>
|
||||||
|
<div class="route-card-center">
|
||||||
|
<div class="name">
|
||||||
|
<div>Percorsi</div>
|
||||||
|
<div class="bold">naturalistici</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="route-card-right"></div>
|
||||||
|
</div>
|
||||||
|
<div class="route-card">
|
||||||
|
<div class="route-card-left">
|
||||||
|
<img src="../../static/images/app-bar-logo.png">
|
||||||
|
</div>
|
||||||
|
<div class="route-card-center">
|
||||||
|
<div class="name">
|
||||||
|
<div>Percorsi</div>
|
||||||
|
<div class="bold">storici</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="route-card-right"></div>
|
||||||
|
</div>
|
||||||
|
<div class="route-card">
|
||||||
|
<div class="route-card-left">
|
||||||
|
<img src="../../static/images/app-bar-logo.png">
|
||||||
|
</div>
|
||||||
|
<div class="route-card-center">
|
||||||
|
<div class="name">
|
||||||
|
<div>Percorsi</div>
|
||||||
|
<div class="bold">tradizionalistici</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="route-card-right"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<bottom-app-bar></bottom-app-bar>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script id="page-script" src="../js/home-page.js" type="module"></script>
|
||||||
|
</body>
|
Loading…
Reference in a new issue