♻️ Switch to Angular project

pull/1/head
Alexis Feron 4 months ago
parent 3685155a1d
commit c33d62deaf

@ -0,0 +1,17 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
ij_typescript_use_double_quotes = false
[*.md]
max_line_length = off
trim_trailing_whitespace = false

190
.gitignore vendored

@ -1,157 +1,45 @@
# ---> VisualStudioCode
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
# VS Code
.vscode
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
# ---> Node
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# System files
.DS_Store
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
.env.*.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
Thumbs.db

@ -1,9 +0,0 @@
MIT License
Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@ -1,27 +1,59 @@
# Memory Map
# Memory Map Front
Front du projet MemoryMap
This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 19.0.6.
## Project setup
## Development server
```
npm install
To start a local development server, run:
```bash
ng serve
```
### Compiles and hot-reloads for development
Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files.
```
npm run serve
## Code scaffolding
Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
```bash
ng generate component component-name
```
### Compiles and minifies for production
For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
```bash
ng generate --help
```
npm run build
## Building
To build the project run:
```bash
ng build
```
### Lints and fixes files
This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed.
## Running unit tests
To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
```bash
ng test
```
npm run lint
## Running end-to-end tests
For end-to-end (e2e) testing, run:
```bash
ng e2e
```
Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
## Additional Resources
For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.

@ -0,0 +1,90 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"frontv2": {
"projectType": "application",
"schematics": {},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/frontv2",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "tsconfig.app.json",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"styles": ["src/styles.css"],
"scripts": [
"node_modules/flowbite/dist/flowbite.min.js",
"node_modules/leaflet/dist/leaflet.js"
]
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kB",
"maximumError": "1MB"
},
{
"type": "anyComponentStyle",
"maximumWarning": "4kB",
"maximumError": "8kB"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "frontv2:build:production"
},
"development": {
"buildTarget": "frontv2:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": ["zone.js", "zone.js/testing"],
"tsConfig": "tsconfig.spec.json",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"styles": ["src/styles.css"],
"scripts": []
}
}
}
}
}
}

@ -1,5 +0,0 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

@ -1,19 +0,0 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}

16103
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,48 +1,44 @@
{
"name": "memory_map",
"name": "memory-map",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"private": true,
"dependencies": {
"core-js": "^3.8.3",
"flowbite": "^2.5.1",
"@angular/animations": "^19.0.0",
"@angular/common": "^19.0.0",
"@angular/compiler": "^19.0.0",
"@angular/core": "^19.0.0",
"@angular/forms": "^19.0.0",
"@angular/platform-browser": "^19.0.0",
"@angular/platform-browser-dynamic": "^19.0.0",
"@angular/router": "^19.0.0",
"flowbite": "^2.5.2",
"leaflet": "^1.9.4",
"vue": "^3.2.13"
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.15.0"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"@angular-devkit/build-angular": "^19.0.6",
"@angular/cli": "^19.0.6",
"@angular/compiler-cli": "^19.0.0",
"@types/jasmine": "~5.1.0",
"@types/leaflet": "^1.9.15",
"autoprefixer": "^10.4.20",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3",
"postcss": "^8.4.47",
"tailwindcss": "^3.4.12"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/vue3-essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "@babel/eslint-parser"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"not ie 11"
]
"jasmine-core": "~5.4.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.17",
"typescript": "~5.6.2"
}
}

@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

@ -1,21 +0,0 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<title>Memory Map</title>
</head>
<body>
<noscript>
<strong
>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
properly without JavaScript enabled. Please enable it to
continue.</strong
>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

Before

Width:  |  Height:  |  Size: 244 KiB

After

Width:  |  Height:  |  Size: 244 KiB

@ -1,19 +0,0 @@
<template>
<div class="container mx-auto h-screen">
<NavBar />
<LeafletMap />
</div>
</template>
<script>
import LeafletMap from "./components/LeafletMap.vue";
import NavBar from "./components/NavBar.vue";
export default {
name: "App",
components: {
NavBar,
LeafletMap,
},
};
</script>

@ -0,0 +1,4 @@
<app-navbar></app-navbar>
<app-leaflet-map></app-leaflet-map>
<router-outlet />

@ -0,0 +1,29 @@
import { TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AppComponent],
}).compileComponents();
});
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have the 'frontv2' title`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('frontv2');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, frontv2');
});
});

@ -0,0 +1,13 @@
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { LeafletMapComponent } from './components/leaflet-map/leaflet-map.component';
import { NavbarComponent } from './components/navbar/navbar.component';
@Component({
selector: 'app-root',
imports: [RouterOutlet, NavbarComponent, LeafletMapComponent],
templateUrl: './app.component.html',
})
export class AppComponent {
title = 'frontv2';
}

@ -0,0 +1,8 @@
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)]
};

@ -0,0 +1,3 @@
import { Routes } from '@angular/router';
export const routes: Routes = [];

@ -0,0 +1,3 @@
<div class="map-container h-[calc(100vh_-_72px)]">
<div id="map" class="h-full w-full"></div>
</div>

@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LeafletMapComponent } from './leaflet-map.component';
describe('LeafletmapComponent', () => {
let component: LeafletMapComponent;
let fixture: ComponentFixture<LeafletMapComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [LeafletMapComponent],
}).compileComponents();
fixture = TestBed.createComponent(LeafletMapComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,73 @@
import { Component, OnInit, ViewContainerRef } from '@angular/core';
import * as L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import { monuments } from '../../data/stub';
import { MonumentMarkerComponent } from '../monument-marker/monument-marker.component';
@Component({
selector: 'app-leaflet-map',
templateUrl: './leaflet-map.component.html',
})
export class LeafletMapComponent implements OnInit {
private map!: L.Map;
constructor(private viewContainerRef: ViewContainerRef) {}
ngOnInit(): void {
this.initializeMap();
}
private initializeMap(): void {
// Initialize the map
this.map = L.map('map').setView([46.603354, 1.888334], 6);
// Add OpenStreetMap tiles
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '',
}).addTo(this.map);
this.map.attributionControl.setPrefix('');
// Define custom icons
const visitedIcon = this.createDivIcon(`
<svg class="w-6 h-6 text-gray-800" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24">
<path fill-rule="evenodd" d="M11.906 1.994a8.002 8.002 0 0 1 8.09 8.421 7.996 7.996 0 0 1-1.297 3.957.996.996 0 0 1-.133.204l-.108.129c-.178.243-.37.477-.573.699l-5.112 6.224a1 1 0 0 1-1.545 0L5.982 15.26l-.002-.002a18.146 18.146 0 0 1-.309-.38l-.133-.163a.999.999 0 0 1-.13-.202 7.995 7.995 0 0 1 6.498-12.518ZM15 9.997a3 3 0 1 1-5.999 0 3 3 0 0 1 5.999 0Z" clip-rule="evenodd"/>
</svg>
`);
const notVisitedIcon = this.createDivIcon(`
<svg class="w-6 h-6 text-gray-800" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<path stroke="currentColor" stroke-width="3" d="M11.083 5.104c.35-.8 1.485-.8 1.834 0l1.752 4.022a1 1 0 0 0 .84.597l4.463.342c.9.069 1.255 1.2.556 1.771l-3.33 2.723a1 1 0 0 0-.337 1.016l1.03 4.119c.214.858-.71 1.552-1.474 1.106l-3.913-2.281a1 1 0 0 0-1.008 0L7.583 20.8c-.764.446-1.688-.248-1.474-1.106l1.03-4.119A1 1 0 0 0 6.8 14.56l-3.33-2.723c-.698-.571-.342-1.702.557-1.771l4.462-.342a1 1 0 0 0 .84-.597l1.753-4.022Z"/>
</svg>
`);
// Add markers
monuments.forEach((monument) => {
const icon = monument.visited ? visitedIcon : notVisitedIcon;
const marker = L.marker(monument.coords as [number, number], {
icon,
}).addTo(this.map);
// Dynamically create Angular component and attach it to popup
const popupDiv = document.createElement('div');
const componentRef = this.viewContainerRef.createComponent(
MonumentMarkerComponent
);
componentRef.instance.monument = monument;
popupDiv.appendChild(componentRef.location.nativeElement);
marker.bindPopup(popupDiv);
});
}
private createDivIcon(htmlContent: string): L.DivIcon {
return L.divIcon({
html: htmlContent,
className: 'text-2xl text-blue-500',
iconSize: [24, 24],
iconAnchor: [12, 24],
popupAnchor: [0, -24],
});
}
}

@ -0,0 +1,81 @@
<div class="text-center">
<strong>{{ monument.name }}</strong>
<div
*ngIf="monument.images.length > 0"
class="relative carousel overflow-hidden"
>
<!-- Carousel wrapper -->
<div
class="relative h-40 mt-2 overflow-hidden rounded-lg flex items-center justify-center"
>
<div
*ngFor="let image of monument.images; let index = index"
[class]="
'absolute inset-0 transition-opacity duration-700 ease-in-out' +
(index === currentIndex ? ' opacity-100' : ' opacity-0')
"
>
<img
[src]="image"
[alt]="monument.name"
class="object-contain max-h-full max-w-full h-full w-auto mx-auto"
/>
</div>
</div>
<!-- Slider controls -->
<div *ngIf="monument.images.length > 1">
<button
type="button"
class="absolute top-0 left-0 z-30 flex items-center justify-center h-full cursor-pointer group focus:outline-none"
(click)="prevSlide()"
>
<span
class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-black/30 dark:bg-black-800/30 group-hover:bg-black/50 dark:group-hover:bg-black-800/60"
>
<svg
class="w-4 h-4 text-white rtl:rotate-180"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 6 10"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M5 1 1 5l4 4"
/>
</svg>
<span class="sr-only">Previous</span>
</span>
</button>
<button
type="button"
class="absolute top-0 right-0 z-30 flex items-center justify-center h-full cursor-pointer group focus:outline-none"
(click)="nextSlide()"
>
<span
class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-black/30 dark:bg-black-800/30 group-hover:bg-black/50 dark:group-hover:bg-black-800/60"
>
<svg
class="w-4 h-4 text-white rtl:rotate-180"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 6 10"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M1 9l4-4-4-4"
/>
</svg>
<span class="sr-only">Next</span>
</span>
</button>
</div>
</div>
<p [innerHTML]="formattedDescription"></p>
</div>

@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MonumentMarkerComponent } from './monument-marker.component';
describe('MonumentmarkerComponent', () => {
let component: MonumentMarkerComponent;
let fixture: ComponentFixture<MonumentMarkerComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [MonumentMarkerComponent],
}).compileComponents();
fixture = TestBed.createComponent(MonumentMarkerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,36 @@
import { CommonModule } from '@angular/common';
import { Component, Input } from '@angular/core';
import { Monument } from '../../model/Monument';
@Component({
selector: 'app-monument-marker',
templateUrl: './monument-marker.component.html',
imports: [CommonModule],
})
export class MonumentMarkerComponent {
@Input() monument!: Monument;
currentIndex: number = 0;
get formattedDescription(): string {
return this.formatDescription(this.monument.description);
}
formatDescription(description: string): string {
const regex = /@(\w+(-\w+)*(\.\w+(-\w+)*)*)/g;
return description.replace(
regex,
`<a href="/profile/$1" class="text-blue-500 hover:underline">@$1</a>`
);
}
prevSlide(): void {
this.currentIndex =
(this.currentIndex - 1 + this.monument.images.length) %
this.monument.images.length;
}
nextSlide(): void {
this.currentIndex = (this.currentIndex + 1) % this.monument.images.length;
}
}

@ -0,0 +1,156 @@
<nav class="bg-white border-gray-200 dark:bg-gray-900">
<div
class="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4"
>
<a class="flex items-center space-x-3 rtl:space-x-reverse">
<img src="./logo.png" class="h-10" alt="Memory Map Logo" />
<span
class="self-center text-2xl font-semibold whitespace-nowrap dark:text-white"
>Memory Map</span
>
</a>
<div class="flex lg:order-2">
<button
type="button"
data-collapse-toggle="navbar-search"
aria-controls="navbar-search"
aria-expanded="false"
class="lg:hidden text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-2.5 me-1"
>
<svg
class="w-5 h-5"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 20 20"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"
/>
</svg>
<span class="sr-only">Search</span>
</button>
<div class="relative hidden lg:block">
<div
class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none"
>
<svg
class="w-4 h-4 text-gray-500 dark:text-gray-400"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 20 20"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"
/>
</svg>
<span class="sr-only">Search icon</span>
</div>
<input
type="text"
id="search-navbar"
class="block w-full p-2 ps-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="Search..."
/>
</div>
<button
data-collapse-toggle="navbar-search"
type="button"
class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg lg:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600"
aria-controls="navbar-search"
aria-expanded="false"
>
<span class="sr-only">Open main menu</span>
<svg
class="w-5 h-5"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 17 14"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M1 1h15M1 7h15M1 13h15"
/>
</svg>
</button>
</div>
<div
class="items-center justify-between hidden w-full lg:flex lg:w-auto lg:order-1"
id="navbar-search"
>
<div class="relative mt-3 lg:hidden">
<div
class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none"
>
<svg
class="w-4 h-4 text-gray-500 dark:text-gray-400"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 20 20"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"
/>
</svg>
</div>
<input
type="text"
id="search-navbar"
class="block w-full p-2 ps-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="Search..."
/>
</div>
<ul
class="flex flex-col p-4 lg:p-0 mt-4 font-medium border border-gray-100 rounded-lg bg-gray-50 lg:space-x-8 rtl:space-x-reverse lg:flex-row lg:mt-0 lg:border-0 lg:bg-white dark:bg-gray-800 lg:dark:bg-gray-900 dark:border-gray-700"
>
<li class="flex items-center space-x-2">
<a
href="#"
class="block py-2 text-gray-900 dark:text-white hover:text-gray-700 dark:hover:text-gray-300"
aria-current="page"
>Home</a
>
</li>
<li class="flex items-center space-x-2">
<a
href="#"
class="block py-2 text-gray-900 dark:text-white hover:text-gray-700 dark:hover:text-gray-300"
>Quests</a
>
</li>
<li class="flex items-center space-x-2">
<a
href="#"
class="block py-2 text-gray-900 dark:text-white hover:text-gray-700 dark:hover:text-gray-300"
>Add a pin</a
>
</li>
<li class="flex items-center space-x-2">
<a
href="#"
class="block py-2 text-gray-900 dark:text-white hover:text-gray-700 dark:hover:text-gray-300"
>Friends</a
>
</li>
</ul>
</div>
</div>
</nav>

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NavbarComponent } from './navbar.component';
describe('NavbarComponent', () => {
let component: NavbarComponent;
let fixture: ComponentFixture<NavbarComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [NavbarComponent]
})
.compileComponents();
fixture = TestBed.createComponent(NavbarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,8 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-navbar',
imports: [],
templateUrl: './navbar.component.html',
})
export class NavbarComponent {}

@ -0,0 +1,32 @@
import { Monument } from '../model/Monument';
export const monuments: Monument[] = [
{
coords: [48.85837, 2.294481],
name: 'Tour Eiffel',
images: [
'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQGGib245tSFiK1Qcx0cB0dZsoVyJElwsY3kA&s',
'https://encrypted-tbn2.gstatic.com/licensed-image?q=tbn:ANd9GcTLB9B0j50rJbcSbdja9_hySHS6_KATbhTK_iCeWeNKtA92hTmTX5nTW3udjjovZrnU1JxqAjMS_VqHnMwHGhTs35-sU-7B29_X_T3uLV8',
],
description: 'Visité en 2020 avec la famille, un moment inoubliable.',
visited: true,
},
{
coords: [43.296482, 5.36978],
name: 'Vieux Port de Marseille',
images: [
'https://upload.wikimedia.org/wikipedia/commons/thumb/7/74/Marseille_Old_Port.jpg/390px-Marseille_Old_Port.jpg',
],
description:
"Découvert lors d'un week-end ensoleillé en 2019 avec @John-Doe.",
visited: true,
},
{
coords: [48.636063, -1.511457],
name: 'Mont Saint-Michel',
images: [],
description: '',
visited: false,
},
// ...
];

@ -0,0 +1,7 @@
export interface Monument {
coords: number[];
name: string;
images: string[];
description: string;
visited: boolean;
}

@ -1,75 +0,0 @@
<template>
<div class="map-container h-[calc(100vh_-_72px)]">
<div id="map" class="h-full w-full"></div>
</div>
</template>
<script>
import L from "leaflet";
import "leaflet/dist/leaflet.css";
import { createApp, h, onMounted } from "vue";
import { monuments } from "../data/stub";
import MonumentMarker from "./MonumentMarker.vue";
export default {
name: "LeafletMap",
setup() {
onMounted(() => {
// Initialiser la carte
const map = L.map("map").setView([46.603354, 1.888334], 6); // TODO: A modifier pour centrer par rapport aux points de la carte
// Supprimer les attributions de la carte Leaflet
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: "",
}).addTo(map);
map.attributionControl.setPrefix("");
// Définir une fonction pour créer des icônes avec Heroicons
const createDivIcon = (htmlContent) => {
return L.divIcon({
html: htmlContent,
className: "text-2xl text-blue-500", // Utilise les classes CSS de Flowbite ou Tailwind pour styliser les icônes
iconSize: [24, 24], // Taille approximative du conteneur de l'icône
iconAnchor: [12, 24], // Point d'ancrage de l'icône (centre-bas)
popupAnchor: [0, -24], // Point d'ancrage du popup (juste au-dessus du marqueur)
});
};
// Définir les icônes Heroicons
const visitedIcon = createDivIcon(`
<svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
<path fill-rule="evenodd" d="M11.906 1.994a8.002 8.002 0 0 1 8.09 8.421 7.996 7.996 0 0 1-1.297 3.957.996.996 0 0 1-.133.204l-.108.129c-.178.243-.37.477-.573.699l-5.112 6.224a1 1 0 0 1-1.545 0L5.982 15.26l-.002-.002a18.146 18.146 0 0 1-.309-.38l-.133-.163a.999.999 0 0 1-.13-.202 7.995 7.995 0 0 1 6.498-12.518ZM15 9.997a3 3 0 1 1-5.999 0 3 3 0 0 1 5.999 0Z" clip-rule="evenodd"/>
</svg>
`);
const notVisitedIcon = createDivIcon(`
<svg class="w-[24px] h-[24px] text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
<path stroke="currentColor" stroke-width="3" d="M11.083 5.104c.35-.8 1.485-.8 1.834 0l1.752 4.022a1 1 0 0 0 .84.597l4.463.342c.9.069 1.255 1.2.556 1.771l-3.33 2.723a1 1 0 0 0-.337 1.016l1.03 4.119c.214.858-.71 1.552-1.474 1.106l-3.913-2.281a1 1 0 0 0-1.008 0L7.583 20.8c-.764.446-1.688-.248-1.474-1.106l1.03-4.119A1 1 0 0 0 6.8 14.56l-3.33-2.723c-.698-.571-.342-1.702.557-1.771l4.462-.342a1 1 0 0 0 .84-.597l1.753-4.022Z"/>
</svg>
`);
// Ajouter les marqueurs pour chaque monument
monuments.forEach((monument) => {
// Choisir l'icône en fonction de la propriété `visited`
const icon = monument.visited ? visitedIcon : notVisitedIcon;
// Ajouter un marqueur avec l'icône appropriée
const marker = L.marker(monument.coords, { icon }).addTo(map);
// Créer un conteneur DOM pour monter le composant Vue
const popupContainer = document.createElement("div");
// Monter le composant Vue MonumentMarker sur le conteneur
const app = createApp({
render() {
return h(MonumentMarker, { monument });
},
});
app.mount(popupContainer);
// Attacher le popup au marqueur avec le contenu monté
marker.bindPopup(popupContainer);
});
});
},
};
</script>

@ -1,130 +0,0 @@
<template>
<div class="text-center">
<strong>{{ monument.name }}</strong>
<div
class="relative carousel overflow-hidden"
v-if="monument.images.length > 0"
>
<!-- Carousel wrapper -->
<div
class="relative h-40 mt-2 overflow-hidden rounded-lg flex items-center justify-center"
>
<div
v-for="(image, index) in monument.images"
:key="index"
:class="[
'absolute inset-0 transition-opacity duration-700 ease-in-out',
{
'opacity-100': index === currentIndex,
'opacity-0': index !== currentIndex,
},
]"
>
<img
:src="image"
:alt="monument.name"
class="object-contain max-h-full max-w-full h-full w-auto mx-auto"
/>
</div>
</div>
<!-- Slider controls -->
<div v-if="monument.images.length > 1">
<button
type="button"
class="absolute top-0 left-0 z-30 flex items-center justify-center h-full cursor-pointer group focus:outline-none"
@click="prevSlide"
>
<span
class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-black/30 dark:bg-black-800/30 group-hover:bg-black/50 dark:group-hover:bg-black-800/60"
>
<svg
class="w-4 h-4 text-white dark:text-gray-800 rtl:rotate-180"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 6 10"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M5 1 1 5l4 4"
/>
</svg>
<span class="sr-only">Previous</span>
</span>
</button>
<button
type="button"
class="absolute top-0 right-0 z-30 flex items-center justify-center h-full cursor-pointer group focus:outline-none"
@click="nextSlide"
>
<span
class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-black/30 dark:bg-black-800/30 group-hover:bg-black/50 dark:group-hover:bg-black-800/60"
>
<svg
class="w-4 h-4 text-white dark:text-gray-800 rtl:rotate-180"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 6 10"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M1 9l4-4-4-4"
/>
</svg>
<span class="sr-only">Next</span>
</span>
</button>
</div>
</div>
<p v-html="formattedDescription"></p>
</div>
</template>
<script>
export default {
name: "MonumentMarker",
props: {
monument: {
type: Object,
required: true,
},
},
data() {
return {
currentIndex: 0,
};
},
computed: {
formattedDescription() {
return this.formatDescription(this.monument.description);
},
},
methods: {
// Remplace les mentions d'utilisateurs par des liens cliquables
formatDescription(description) {
const regex = /@(\w+(-\w+)*(\.\w+(-\w+)*)*)/g;
return description.replace(
regex,
'<a href="/profile/$1" class="text-blue-500 hover:underline">@$1</a>'
);
},
prevSlide() {
this.currentIndex =
(this.currentIndex - 1 + this.monument.images.length) %
this.monument.images.length;
},
nextSlide() {
this.currentIndex = (this.currentIndex + 1) % this.monument.images.length;
},
},
// mounted() {
// setInterval(this.nextSlide, 3000);
// }
};
</script>

@ -1,152 +0,0 @@
<template>
<nav class="bg-white border-gray-200 dark:bg-gray-900">
<div
class="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4"
>
<a class="flex items-center space-x-3 rtl:space-x-reverse">
<img src="../assets/logo.png" class="h-10" alt="Memory Map Logo" />
<span
class="self-center text-2xl font-semibold whitespace-nowrap dark:text-white"
>Memory Map</span
>
</a>
<div class="flex lg:order-2">
<button
type="button"
data-collapse-toggle="navbar-search"
aria-controls="navbar-search"
aria-expanded="false"
class="lg:hidden text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-2.5 me-1"
>
<svg
class="w-5 h-5"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 20 20"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"
/>
</svg>
<span class="sr-only">Search</span>
</button>
<div class="relative hidden lg:block">
<div
class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none"
>
<svg
class="w-4 h-4 text-gray-500 dark:text-gray-400"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 20 20"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"
/>
</svg>
<span class="sr-only">Search icon</span>
</div>
<input
type="text"
id="search-navbar"
class="block w-full p-2 ps-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="Search..."
/>
</div>
<button
data-collapse-toggle="navbar-search"
type="button"
class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg lg:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600"
aria-controls="navbar-search"
aria-expanded="false"
>
<span class="sr-only">Open main menu</span>
<svg
class="w-5 h-5"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 17 14"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M1 1h15M1 7h15M1 13h15"
/>
</svg>
</button>
</div>
<div
class="items-center justify-between hidden w-full lg:flex lg:w-auto lg:order-1"
id="navbar-search"
>
<div class="relative mt-3 lg:hidden">
<div
class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none"
>
<svg
class="w-4 h-4 text-gray-500 dark:text-gray-400"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 20 20"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"
/>
</svg>
</div>
<input
type="text"
id="search-navbar"
class="block w-full p-2 ps-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="Search..."
/>
</div>
<ul
class="flex flex-col p-4 lg:p-0 mt-4 font-medium border border-gray-100 rounded-lg bg-gray-50 lg:space-x-8 rtl:space-x-reverse lg:flex-row lg:mt-0 lg:border-0 lg:bg-white dark:bg-gray-800 lg:dark:bg-gray-900 dark:border-gray-700"
>
<li class="flex items-center space-x-2">
<a
href="#"
class="block py-2 px-3 text-white bg-blue-700 rounded md:bg-transparent md:text-blue-700 md:p-0 dark:text-white md:dark:text-blue-500"
aria-current="page"
>Home</a
>
</li>
<li class="flex items-center space-x-2">
<a href="#" class="block py-2 text-gray-900 dark:text-white"
>Quests</a
>
</li>
<li class="flex items-center space-x-2">
<a href="#" class="block py-2 text-gray-900 dark:text-white"
>Add a pin</a
>
</li>
<li class="flex items-center space-x-2">
<a href="#" class="block py-2 text-gray-900 dark:text-white"
>Friends</a
>
</li>
</ul>
</div>
</div>
</nav>
</template>

@ -1,30 +0,0 @@
export const monuments = [
{
coords: [48.85837, 2.294481],
name: "Tour Eiffel",
images: [
"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQGGib245tSFiK1Qcx0cB0dZsoVyJElwsY3kA&s",
"https://encrypted-tbn2.gstatic.com/licensed-image?q=tbn:ANd9GcTLB9B0j50rJbcSbdja9_hySHS6_KATbhTK_iCeWeNKtA92hTmTX5nTW3udjjovZrnU1JxqAjMS_VqHnMwHGhTs35-sU-7B29_X_T3uLV8",
],
description: "Visité en 2020 avec la famille, un moment inoubliable.",
visited: true,
},
{
coords: [43.296482, 5.36978],
name: "Vieux Port de Marseille",
images: [
"https://upload.wikimedia.org/wikipedia/commons/thumb/7/74/Marseille_Old_Port.jpg/390px-Marseille_Old_Port.jpg",
],
description:
"Découvert lors d'un week-end ensoleillé en 2019 avec @John-Doe.",
visited: true,
},
{
coords: [48.636063, -1.511457],
name: "Mont Saint-Michel",
images: [],
description: "",
visited: false,
},
// ...
];

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Memory Map</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>
<body>
<app-root></app-root>
</body>
</html>

@ -1,6 +0,0 @@
import "flowbite";
import { createApp } from "vue";
import App from "./App.vue";
import "./assets/main.css";
createApp(App).mount("#app");

@ -0,0 +1,6 @@
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, appConfig)
.catch((err) => console.error(err));

@ -1,12 +1,8 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./index.html",
"./src/**/*.{vue,js,ts,jsx,tsx}",
"./node_modules/flowbite/**/*.js",
],
content: ["./src/**/*.{html,ts}"],
theme: {
extend: {},
},
plugins: [require("flowbite/plugin")],
plugins: [],
};

@ -0,0 +1,15 @@
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": [
"src/main.ts"
],
"include": [
"src/**/*.d.ts"
]
}

@ -0,0 +1,27 @@
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
"compileOnSave": false,
"compilerOptions": {
"outDir": "./dist/out-tsc",
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"isolatedModules": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"moduleResolution": "bundler",
"importHelpers": true,
"target": "ES2022",
"module": "ES2022"
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}

@ -0,0 +1,15 @@
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jasmine"
]
},
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}

@ -1,5 +0,0 @@
const { defineConfig } = require("@vue/cli-service");
module.exports = defineConfig({
transpileDependencies: true,
});
Loading…
Cancel
Save