Initial commit

This commit is contained in:
Hubert Van De Walle 2020-09-21 15:28:48 +02:00
commit 5f905c578d
21 changed files with 2474 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
node_modules
.idea/
dist
*.local

15
index.html Normal file
View 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
View 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
View File

@ -0,0 +1 @@
/* /index.html 200

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

5
src/App.vue Normal file
View File

@ -0,0 +1,5 @@
<template>
<div class="container">
<router-view/>
</div>
</template>

View 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
View 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>

View 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
View 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
View File

6
src/main.js Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,3 @@
<template>
404
</template>

32
src/views/Route.vue Normal file
View 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
View 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>

2064
yarn.lock Normal file

File diff suppressed because it is too large Load Diff