Commit 366159e1 authored by Léonard Treille's avatar Léonard Treille
Browse files

Add matomo and SEO behaviors

parent f1e00feb
......@@ -4811,9 +4811,9 @@
"dev": true
},
"eventemitter3": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz",
"integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz",
"integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==",
"dev": true
},
"events": {
......@@ -5813,9 +5813,9 @@
"dev": true
},
"http-proxy": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.0.tgz",
"integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==",
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
"dev": true,
"requires": {
"eventemitter3": "^4.0.0",
......@@ -7942,6 +7942,11 @@
"integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==",
"dev": true
},
"ngx-matomo": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/ngx-matomo/-/ngx-matomo-0.1.4.tgz",
"integrity": "sha512-AKZMnJGyytZqAxuSh+k/AulyQhgqlnnsmtkfvHMJyNuh5g+wVpIbwac36RyeFU3El6INgZVso2CCLElV3bQnBQ=="
},
"nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
......@@ -9704,9 +9709,9 @@
}
},
"protractor": {
"version": "5.4.4",
"resolved": "https://registry.npmjs.org/protractor/-/protractor-5.4.4.tgz",
"integrity": "sha512-BaL4vePgu3Vfa/whvTUAlgaCAId4uNSGxIFSCXMgj7LMYENPWLp85h5RBi9pdpX/bWQ8SF6flP7afmi2TC4eHw==",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/protractor/-/protractor-7.0.0.tgz",
"integrity": "sha512-UqkFjivi4GcvUQYzqGYNe0mLzfn5jiLmO8w9nMhQoJRLhy2grJonpga2IWhI6yJO30LibWXJJtA4MOIZD2GgZw==",
"dev": true,
"requires": {
"@types/q": "^0.0.32",
......@@ -9722,8 +9727,8 @@
"selenium-webdriver": "3.6.0",
"source-map-support": "~0.4.0",
"webdriver-js-extender": "2.1.0",
"webdriver-manager": "^12.0.6",
"yargs": "^12.0.5"
"webdriver-manager": "^12.1.7",
"yargs": "^15.3.1"
},
"dependencies": {
"@types/q": {
......@@ -9732,6 +9737,12 @@
"integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=",
"dev": true
},
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
"dev": true
},
"ansi-styles": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
......@@ -9751,6 +9762,43 @@
"supports-color": "^2.0.0"
}
},
"cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
"dev": true,
"requires": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^6.2.0"
},
"dependencies": {
"strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"dev": true,
"requires": {
"ansi-regex": "^5.0.0"
}
}
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"del": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz",
......@@ -9766,6 +9814,22 @@
"rimraf": "^2.2.8"
}
},
"find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"dev": true,
"requires": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
}
},
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true
},
"globby": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz",
......@@ -9780,6 +9844,12 @@
"pinkie-promise": "^2.0.0"
}
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true
},
"is-path-cwd": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz",
......@@ -9804,6 +9874,45 @@
"path-is-inside": "^1.0.1"
}
},
"locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"dev": true,
"requires": {
"p-locate": "^4.1.0"
}
},
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dev": true,
"requires": {
"p-try": "^2.0.0"
}
},
"p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"dev": true,
"requires": {
"p-limit": "^2.2.0"
}
},
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true
},
"path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true
},
"pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
......@@ -9816,6 +9925,12 @@
"integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=",
"dev": true
},
"require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"dev": true
},
"rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
......@@ -9846,6 +9961,28 @@
"source-map": "^0.5.6"
}
},
"string-width": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
"dev": true,
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.0"
},
"dependencies": {
"strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"dev": true,
"requires": {
"ansi-regex": "^5.0.0"
}
}
}
},
"supports-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
......@@ -9870,6 +10007,67 @@
"semver": "^5.3.0",
"xml2js": "^0.4.17"
}
},
"wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"dev": true,
"requires": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
"integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
"dev": true,
"requires": {
"@types/color-name": "^1.1.1",
"color-convert": "^2.0.1"
}
},
"strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"dev": true,
"requires": {
"ansi-regex": "^5.0.0"
}
}
}
},
"yargs": {
"version": "15.3.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz",
"integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==",
"dev": true,
"requires": {
"cliui": "^6.0.0",
"decamelize": "^1.2.0",
"find-up": "^4.1.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^4.2.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^18.1.1"
}
},
"yargs-parser": {
"version": "18.1.3",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"dev": true,
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
}
}
},
......
......@@ -15,16 +15,26 @@ const routes: Routes = [
path: '', component: HorairesLignesComponent,
resolve: {
lines: LinesResolver
}
},
data: {
title: `Fréquentation aux arrêts | M - Mesure de l'affluence`
},
},
{
path: 'ligne/:id', component: DetailLigneComponent,
resolve: {
clusters: LineResolver
}
},
data: {
title: `Fréquentation aux arrêts de la ligne @line | M - Mesure de l'affluence`
},
},
{
path: 'contribution', component: ContributionWrapperComponent, data: { totalStep: 3 },
path: 'contribution', component: ContributionWrapperComponent,
data: {
totalStep: 3,
title: `Votre contribution (@step/3) | M - Mesure de l'affluence`
},
children: [
{ path: '', component: ContributionFirstStepComponent, data: { step: 1 } },
{ path: ':cluster', component: ContributionSecondStepComponent, data: { step: 2 }, resolve: { patterns: PatternsResolver } },
......
import { Component } from '@angular/core';
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { environment } from 'src/environments/environment';
import { MatomoInjector, MatomoTracker } from 'ngx-matomo';
import { takeUntil, filter } from 'rxjs/operators';
import { Subject } from 'rxjs';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'm-affluence';
export class AppComponent implements OnInit, OnDestroy {
private unsubscriber = new Subject<void>();
constructor(
private router: Router,
private matomoInjector: MatomoInjector,
private matomoTracker: MatomoTracker
) { }
ngOnInit(): void {
if (environment.matomo) {
this.matomoInjector.init(environment.matomoConfig.url, environment.matomoConfig.id);
}
this.router.events.pipe(
filter(event => event instanceof NavigationEnd),
takeUntil(this.unsubscriber)
).subscribe(event => {
if (environment.matomo) {
this.matomoTracker.trackEvent('navigation', this.router.url);
}
});
}
ngOnDestroy(): void {
this.unsubscriber.next();
this.unsubscriber.complete();
}
}
......@@ -19,6 +19,8 @@ import { MatInputModule } from '@angular/material/input';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatomoModule } from 'ngx-matomo';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { WINDOW_PROVIDERS } from './window.provider';
......@@ -100,6 +102,7 @@ import { IdPipe } from './pipes/id.pipe';
MatProgressBarModule,
MatToolbarModule,
MatSnackBarModule,
MatomoModule,
],
providers: [
WINDOW_PROVIDERS,
......
......@@ -8,6 +8,7 @@ import { trigger, transition, style, animate, keyframes } from '@angular/animati
import { MatSnackBar } from '@angular/material/snack-bar';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { domain } from '@helpers/domain.helpers';
import { MatomoTracker } from 'ngx-matomo';
@Component({
templateUrl: './contribution-final-step.component.html',
......@@ -42,7 +43,8 @@ export class ContributionFinalStepComponent implements OnInit {
private domSanitizer: DomSanitizer,
private snackBar: MatSnackBar,
private router: Router,
private http: HttpClient
private http: HttpClient,
private matomoTracker: MatomoTracker
) {
this.matIconRegistry.addSvgIcon(
'group',
......@@ -83,7 +85,9 @@ export class ContributionFinalStepComponent implements OnInit {
'Content-Type': 'application/json'
});
this.displaySubmitLoader = true;
this.matomoTracker.trackEvent('submit-contribution', JSON.stringify(values));
this.http.post(`${domain}/occupancy/report`, values, { headers }).subscribe(response => {
this.matomoTracker.trackEvent('submit-contribution:success', '');
this.displaySubmitLoader = false;
this.snackBar.openFromTemplate(this.snackBarContent, {
panelClass: 'contribute-submit-message',
......@@ -91,6 +95,7 @@ export class ContributionFinalStepComponent implements OnInit {
});
this.router.navigate(['/']);
}, error => {
this.matomoTracker.trackEvent('submit-contribution:error', '');
console.error(error);
this.displaySubmitLoader = false;
this.snackBar.open(`Une erreur s'est produite, veuillez réessayer plus tard.`, '', {
......
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { takeUntil } from 'rxjs/operators';
import { Router, ActivatedRoute, NavigationEnd } from '@angular/router';
import { takeUntil, filter } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { Title } from '@angular/platform-browser';
@Component({
templateUrl: './contribution-wrapper.component.html',
......@@ -16,15 +17,22 @@ export class ContributionWrapperComponent implements OnInit, OnDestroy {
constructor(
private router: Router,
private activatedRoute: ActivatedRoute
private activatedRoute: ActivatedRoute,
private title: Title
) { }
ngOnInit(): void {
this.total = this.activatedRoute.snapshot.data.totalStep;
const title = this.activatedRoute.snapshot.data.title;
this.step = this.activatedRoute.firstChild.snapshot.data.step;
this.router.events.pipe(takeUntil(this.unsubscriber)).subscribe(event => {
this.step = this.activatedRoute.firstChild.snapshot.data.step;
});
this.title.setTitle(title.replace('@step', this.step));
this.router.events.pipe(
filter(event => event instanceof NavigationEnd),
takeUntil(this.unsubscriber))
.subscribe(event => {
this.step = this.activatedRoute.firstChild.snapshot.data.step;
this.title.setTitle(title.replace('@step', this.step));
});
}
ngOnDestroy(): void {
......
......@@ -5,6 +5,8 @@ import { LinesService } from '@features/lines/lines.service';
import { Subject } from 'rxjs';
import { BreakpointService } from '@services/breakpoint.service';
import { takeUntil } from 'rxjs/operators';
import { Title } from '@angular/platform-browser';
import { Line } from '@features/line/line.model';
@Component({
selector: 'app-detail-ligne',
......@@ -26,7 +28,7 @@ import { takeUntil } from 'rxjs/operators';
export class DetailLigneComponent implements OnInit, OnDestroy {
ligneArrets;
ligne: any;
ligne: Line;
filtreArrets = '';
splitLongName: string[];
......@@ -38,7 +40,8 @@ export class DetailLigneComponent implements OnInit, OnDestroy {
constructor(
private linesService: LinesService,
private route: ActivatedRoute,
private breakpointService: BreakpointService
private breakpointService: BreakpointService,
private title: Title,
) { }
ngOnInit() {
......@@ -52,6 +55,7 @@ export class DetailLigneComponent implements OnInit, OnDestroy {
return arret;
});
this.ligne = this.linesService.find(this.route.snapshot.params.id);
this.title.setTitle(this.route.snapshot.data.title.replace('@line', this.ligne.shortName));
this.splitLongName = [];
if (this.ligne.longName.includes(' / ')) {
this.splitLongName = this.ligne.longName.split(' / ');
......
......@@ -5,6 +5,8 @@ import { BreakpointService } from '@services/breakpoint.service';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { environment } from 'src/environments/environment';
import { Title } from '@angular/platform-browser';
import { ActivatedRoute } from '@angular/router';
@Component({
templateUrl: './horaires-lignes.component.html',
......@@ -21,10 +23,13 @@ export class HorairesLignesComponent implements OnInit, OnDestroy {
constructor(
private linesService: LinesService,
private breakpointService: BreakpointService
private breakpointService: BreakpointService,
private title: Title,
private activatedRoute: ActivatedRoute
) { }
ngOnInit() {
this.title.setTitle(this.activatedRoute.snapshot.data.title);
this.lines = this.linesService.lines.filter(line => ['TRAM', 'CHRONO', 'PROXIMO'].includes(line.type));
this.breakpointService.breakpoint.pipe(takeUntil(this.unsubscriber)).subscribe((result) => {
this.isMobile = !result.matches;
......
......@@ -2,5 +2,9 @@ export const environment = {
production: true,
api: 'testOuProd',
enableContrib: true,
// matomo: false,
matomo: true,
matomoConfig: {
url: '//www.metromobilite.fr/stats/',
id: 16
},
};
......@@ -6,7 +6,11 @@ export const environment = {
production: false,
api: 'test',
enableContrib: true,
// matomo: false,
matomo: false,
matomoConfig: {
url: '//www.metromobilite.fr/stats/',
id: 16
},
};
/*
......
......@@ -9,6 +9,7 @@
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<meta name="description" content="Page web expérimentale d'affichage de l'affluence sur le réseau Tag.">
<style>
@keyframes app-loader-animation {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment