Initial commit
This commit is contained in:
commit
5f905c578d
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
node_modules
|
||||||
|
.idea/
|
||||||
|
dist
|
||||||
|
*.local
|
||||||
15
index.html
Normal file
15
index.html
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"
|
||||||
|
integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Camp to Camp App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
16
package.json
Normal file
16
package.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "camp2camp",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"vue": "^3.0.0-rc.1",
|
||||||
|
"vue-router": "^4.0.0-beta.11"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vue/compiler-sfc": "^3.0.0-rc.1",
|
||||||
|
"vite": "^1.0.0-rc.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
public/_redirects
Normal file
1
public/_redirects
Normal file
@ -0,0 +1 @@
|
|||||||
|
/* /index.html 200
|
||||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
5
src/App.vue
Normal file
5
src/App.vue
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<router-view/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
18
src/components/Document.vue
Normal file
18
src/components/Document.vue
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<template>
|
||||||
|
title: {{ title }}<br>
|
||||||
|
date: {{ date }}<br>
|
||||||
|
<div v-html="description"/>
|
||||||
|
<img v-for="image in images" :key="image" :src="image" alt="">
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "Document",
|
||||||
|
props: {
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
date: String,
|
||||||
|
images: Array
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
20
src/components/List.vue
Normal file
20
src/components/List.vue
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<template>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">{{ title }}</div>
|
||||||
|
<ul class="list-group">
|
||||||
|
<li class="list-group-item list-group-item-action" v-for="item in items" :key="item.url">
|
||||||
|
<a :href="item.url">{{ item.title }}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "List",
|
||||||
|
props: {
|
||||||
|
title: String,
|
||||||
|
items: Array
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
17
src/components/Outings.vue
Normal file
17
src/components/Outings.vue
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<ul class="list-group">
|
||||||
|
<li v-for="outing in outings" :key="outing.id" class="list-group-item">
|
||||||
|
<a :href="'/outing/' + outing.id">{{ outing.title }}</a>
|
||||||
|
{{ outing }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "Outings",
|
||||||
|
props: {
|
||||||
|
outings: Array
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
42
src/components/Routes.vue
Normal file
42
src/components/Routes.vue
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<template>
|
||||||
|
<ul class="list-group">
|
||||||
|
<li v-for="route in routes" :key="route.id" class="list-group-item">
|
||||||
|
<a :href="'/route/' + route.id">{{ route.title }}</a>
|
||||||
|
{{ info(route) }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "Routes",
|
||||||
|
props: {
|
||||||
|
routes: Array
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
info(route) {
|
||||||
|
return this.height(route) + this.orientations(route) + this.rating(route)
|
||||||
|
},
|
||||||
|
height(route) {
|
||||||
|
return route.height.heightDiffDifficulties ? route.height.heightDiffDifficulties + ' m, ' : '';
|
||||||
|
},
|
||||||
|
orientations(route) {
|
||||||
|
if (route.orientations) {
|
||||||
|
return route.orientations.join(',')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rating(route) {
|
||||||
|
if (route.rating) {
|
||||||
|
let str = route.rating.global + ' ' + route.rating.free
|
||||||
|
if (route.rating.required)
|
||||||
|
str += '>' + route.rating.required
|
||||||
|
if (route.rating.engagement)
|
||||||
|
str += ' ' + route.rating.engagement
|
||||||
|
if (route.rating.equipmentQuality)
|
||||||
|
str += ' ' + route.rating.equipmentQuality
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
0
src/index.css
Normal file
0
src/index.css
Normal file
6
src/main.js
Normal file
6
src/main.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
import router from './router'
|
||||||
|
import './index.css'
|
||||||
|
|
||||||
|
createApp(App).use(router).mount('#app')
|
||||||
16
src/router/index.js
Normal file
16
src/router/index.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import {createRouter, createWebHistory} from 'vue-router';
|
||||||
|
|
||||||
|
import NotFound from '../views/NotFound.vue';
|
||||||
|
import Home from '../views/Home.vue';
|
||||||
|
import Waypoint from '../views/Waypoint.vue';
|
||||||
|
import Route from '../views/Route.vue';
|
||||||
|
|
||||||
|
export default createRouter({
|
||||||
|
history: createWebHistory(),
|
||||||
|
routes: [
|
||||||
|
{path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound},
|
||||||
|
{path: '/', name: 'Home', component: Home},
|
||||||
|
{path: '/waypoint/:id', name: 'Waypoint', component: Waypoint},
|
||||||
|
{path: '/route/:id', name: 'Route', component: Route},
|
||||||
|
],
|
||||||
|
});
|
||||||
67
src/scripts/api.js
Normal file
67
src/scripts/api.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import {extractRoutes} from "./routes";
|
||||||
|
import {extractOutings} from "./outings";
|
||||||
|
|
||||||
|
export async function fetchWaypoint(id) {
|
||||||
|
const url = `https://api.camptocamp.org/waypoints/${id}?cook=fr`
|
||||||
|
return fetch(url)
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((res) => {
|
||||||
|
const images = res.associations.images.map(e => {
|
||||||
|
const lang = e.locales.find(e => e.lang === "fr") || e.locales[0]
|
||||||
|
return ({
|
||||||
|
full: "https://media.camptocamp.org/c2corg_active/" + e.filename,
|
||||||
|
thumbnail: "https://media.camptocamp.org/c2corg_active/" + e.filename.replace(".jpg", "MI.jpg"),
|
||||||
|
title: lang.title
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const lang = res.locales.find(e => e.lang === "fr") || res.locales[0]
|
||||||
|
|
||||||
|
const associatedRoutes = extractRoutes(res.associations['all_routes'])
|
||||||
|
|
||||||
|
const outings = extractOutings(res.associations['recent_outings'])
|
||||||
|
|
||||||
|
return ({
|
||||||
|
title: lang.title,
|
||||||
|
summary: lang.summary,
|
||||||
|
description: lang.description,
|
||||||
|
date: res["date_start"],
|
||||||
|
images,
|
||||||
|
associatedRoutes,
|
||||||
|
outings
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchRoute(id) {
|
||||||
|
const url = `https://api.camptocamp.org/routes/${id}?cook=fr`
|
||||||
|
return fetch(url)
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((res) => {
|
||||||
|
const fr = res["locales"][0]
|
||||||
|
return {
|
||||||
|
title: fr["title_prefix"] + " : " + fr.title,
|
||||||
|
description: res.cooked.description,
|
||||||
|
history: res.cooked['route_history']
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function search(terms) {
|
||||||
|
const url = `https://api.camptocamp.org/search?q=${(encodeURI(terms))}&t=w,r&limit=7`
|
||||||
|
return fetch(url)
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((res) => {
|
||||||
|
const waypoints = res.waypoints.documents
|
||||||
|
.map(e => ({
|
||||||
|
id: e.document_id,
|
||||||
|
title: e.locales[0].title,
|
||||||
|
type: e.waypoint_type,
|
||||||
|
elevation: e.elevation
|
||||||
|
}))
|
||||||
|
const routes = extractRoutes(res.routes)
|
||||||
|
return {
|
||||||
|
waypoints,
|
||||||
|
routes
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
12
src/scripts/outings.js
Normal file
12
src/scripts/outings.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export function extractOutings(outings) {
|
||||||
|
return outings.documents.map(e => {
|
||||||
|
const lang = e.locales.find(e => e.lang === "fr") || e.locales[0]
|
||||||
|
return {
|
||||||
|
id: e["document_id"],
|
||||||
|
title: lang.title,
|
||||||
|
date: e["date_end"],
|
||||||
|
condition: e['condition_rating'],
|
||||||
|
quality: e.quality,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
27
src/scripts/routes.js
Normal file
27
src/scripts/routes.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
export function extractRoutes(routes) {
|
||||||
|
return routes.documents.map(e => {
|
||||||
|
const lang = e.locales.find(e => e.lang === "fr") || e.locales[0]
|
||||||
|
return ({
|
||||||
|
id: e['document_id'],
|
||||||
|
title: lang['title_prefix'] + ' : ' + lang['title'],
|
||||||
|
summary: lang['summary'],
|
||||||
|
activities: e.activities,
|
||||||
|
type: e.type,
|
||||||
|
orientations: e.orientations,
|
||||||
|
height: {
|
||||||
|
heightDiffDifficulties: e['height_diff_difficulties'],
|
||||||
|
up: e['height_diff_up'],
|
||||||
|
down: e['height_diff_down'],
|
||||||
|
min: e['elevation_min'],
|
||||||
|
max: e['elevation_max'],
|
||||||
|
},
|
||||||
|
rating: {
|
||||||
|
global: e['global_rating'],
|
||||||
|
free: e['rock_free_rating'],
|
||||||
|
required: e['rock_required_rating'],
|
||||||
|
engagement: e['engagement_rating'],
|
||||||
|
equipmentQuality: e['equipment_rating']
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
54
src/views/Home.vue
Normal file
54
src/views/Home.vue
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<template>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">Search</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form class="form-inline" @submit.prevent="submit">
|
||||||
|
<div class="form-group">
|
||||||
|
<input
|
||||||
|
class="form-control"
|
||||||
|
v-model="terms"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<button class="ml-2 btn btn-primary" type="submit">Search</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="submitted">
|
||||||
|
<List title="Waypoints" :items="waypoints"/>
|
||||||
|
<List title="routes" :items="routes"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Document from "../components/Document.vue";
|
||||||
|
import List from "../components/List.vue";
|
||||||
|
import {search} from "../scripts/api"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Home',
|
||||||
|
components: {Document, List},
|
||||||
|
data: () => ({
|
||||||
|
terms: '',
|
||||||
|
submitted: false,
|
||||||
|
waypoints: [],
|
||||||
|
routes: [],
|
||||||
|
}),
|
||||||
|
methods: {
|
||||||
|
submit() {
|
||||||
|
search(this.terms).then(e => {
|
||||||
|
this.waypoints = e.waypoints.map(e => ({
|
||||||
|
title: e,
|
||||||
|
url: '/waypoint/' + e.id
|
||||||
|
}))
|
||||||
|
this.routes = e.routes.map(e => ({
|
||||||
|
title: e,
|
||||||
|
url: '/route/' + e.id
|
||||||
|
}))
|
||||||
|
this.submitted = true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
3
src/views/NotFound.vue
Normal file
3
src/views/NotFound.vue
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<template>
|
||||||
|
404
|
||||||
|
</template>
|
||||||
32
src/views/Route.vue
Normal file
32
src/views/Route.vue
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<template>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">{{ title }}</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h2>Description</h2>
|
||||||
|
<div v-html="description"/>
|
||||||
|
<h2>Historique de l'itinéraire</h2>
|
||||||
|
<div v-html="history"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {fetchRoute} from "../scripts/api"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Route",
|
||||||
|
data: () => ({
|
||||||
|
title: '',
|
||||||
|
description: '',
|
||||||
|
history: ''
|
||||||
|
}),
|
||||||
|
mounted() {
|
||||||
|
fetchRoute(this.$route.params.id)
|
||||||
|
.then(e => {
|
||||||
|
this.title = e.title
|
||||||
|
this.description = e.description
|
||||||
|
this.history = e.history
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
55
src/views/Waypoint.vue
Normal file
55
src/views/Waypoint.vue
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<template>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">{{ title }}</div>
|
||||||
|
<div class="card-body">
|
||||||
|
{{ summary }}
|
||||||
|
<h2>Description</h2>
|
||||||
|
<div v-html="description"/>
|
||||||
|
<hr>
|
||||||
|
<h3>Itinéraires associés</h3>
|
||||||
|
<Routes :routes="associatedRoutes"/>
|
||||||
|
<hr>
|
||||||
|
<h3>Dernières sorties</h3>
|
||||||
|
<Outings :outings="outings"/>
|
||||||
|
<hr>
|
||||||
|
<figure v-for="image in images" :key="image.url" class="figure d-block">
|
||||||
|
<img class="img-fluid rounded d-block mx-auto" :src="image.thumbnail">
|
||||||
|
<figcaption class="figure-caption text-center">{{ image.title }}</figcaption>
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {fetchWaypoint} from "../scripts/api"
|
||||||
|
import Routes from '../components/Routes.vue'
|
||||||
|
import Outings from '../components/Outings.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Waypoint",
|
||||||
|
components: {
|
||||||
|
Routes, Outings
|
||||||
|
},
|
||||||
|
data: () => ({
|
||||||
|
title: '',
|
||||||
|
summary: '',
|
||||||
|
description: '',
|
||||||
|
date: '',
|
||||||
|
associatedRoutes: [],
|
||||||
|
outings: [],
|
||||||
|
images: [],
|
||||||
|
}),
|
||||||
|
mounted() {
|
||||||
|
fetchWaypoint(this.$route.params.id)
|
||||||
|
.then(e => {
|
||||||
|
this.title = e.title
|
||||||
|
this.summary = e.summary
|
||||||
|
this.description = e.description
|
||||||
|
this.date = e.date
|
||||||
|
this.associatedRoutes = e.associatedRoutes
|
||||||
|
this.outings = e.outings
|
||||||
|
this.images = e.images
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
Loading…
x
Reference in New Issue
Block a user