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