Add the ability to add a report anywhere through long press + preferences section to set the locale
This commit is contained in:
parent
e682c9818e
commit
a4564ad053
16
src/App.vue
16
src/App.vue
@ -12,8 +12,14 @@
|
||||
<v-icon>more_vert</v-icon>
|
||||
</v-btn>
|
||||
<v-list>
|
||||
<v-list-tile @click="">
|
||||
<v-list-tile-title @click="goToAbout">{{ $t("menu.About") }}</v-list-tile-title>
|
||||
<v-list-tile @click="goToMap">
|
||||
<v-list-tile-title>{{ $t("menu.Map") }}</v-list-tile-title>
|
||||
</v-list-tile>
|
||||
<v-list-tile @click="goToAbout">
|
||||
<v-list-tile-title>{{ $t("menu.About") }}</v-list-tile-title>
|
||||
</v-list-tile>
|
||||
<v-list-tile @click="goToSettings">
|
||||
<v-list-tile-title>{{ $t("menu.Settings") }}</v-list-tile-title>
|
||||
</v-list-tile>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
@ -36,6 +42,12 @@ export default {
|
||||
goToAbout() {
|
||||
this.$router.push({ name: 'About' });
|
||||
},
|
||||
goToMap() {
|
||||
this.$router.push({ name: 'Map' });
|
||||
},
|
||||
goToSettings() {
|
||||
this.$router.push({ name: 'Settings' });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -1,9 +1,12 @@
|
||||
<template>
|
||||
<v-container fluid>
|
||||
<v-layout row>
|
||||
<v-flex xs6 offset-xs3>
|
||||
<v-flex xs12 sm6 offset-sm3>
|
||||
<p>{{ $t('about.summary') }}</p>
|
||||
|
||||
<h2 class="body-2">{{ $t('about.usage') }}</h2>
|
||||
<p>{{ $t('about.usageDescription') }}</p>
|
||||
|
||||
<h2 class="body-2">{{ $t('about.availableReportsTitle') }}</h2>
|
||||
<ul class="ml-3">
|
||||
<li><strong>{{ $t('reportLabels.gcum') }}</strong>{{ $t('misc.spaceBeforeDoublePunctuations') }}: {{ $t('about.gcumDescription') }}</li>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="fill-height fill-width">
|
||||
<v-lmap :center="latlng" :zoom="this.zoom" :minZoom="this.minZoom" :maxZoom="this.maxZoom" :options="{ zoomControl: false }">
|
||||
<v-lmap :center="latlng" :zoom="this.zoom" :minZoom="this.minZoom" :maxZoom="this.maxZoom" :options="{ zoomControl: false }" @contextmenu="handleLongPress">
|
||||
<v-ltilelayer :url="tileServer" :attribution="attribution"></v-ltilelayer>
|
||||
|
||||
<v-lts v-if="heading" :lat-lng="latlng" :options="markerOptions"></v-lts>
|
||||
@ -44,6 +44,7 @@ export default {
|
||||
lat: Number,
|
||||
lng: Number,
|
||||
markers: Array,
|
||||
onLongPress: Function,
|
||||
},
|
||||
computed: {
|
||||
radiusFromAccuracy() {
|
||||
@ -84,6 +85,13 @@ export default {
|
||||
tileServer: constants.TILE_SERVER,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleLongPress(event) {
|
||||
if (this.onLongPress) {
|
||||
this.onLongPress(event.latlng);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -7,13 +7,17 @@ export default {
|
||||
license: 'It is released under an <a href="https://opensource.org/licenses/MIT">MIT license</a>. The map background is using tiles from <a href="https://www.opencyclemap.org/docs/">OpenCycleMap</a>, thanks to <a href="https://www.openstreetmap.org/copyright">OpenStreetMap contributors</a> and <a href="http://leafletjs.com/">Leaflet</a>.',
|
||||
potholeDescription: 'A pothole in the ground.',
|
||||
summary: 'This app lets you track and share issues with bike lanes.',
|
||||
usage: 'How to use',
|
||||
usageDescription: 'Use the button in the lower right corner to add a new report at your current location. To add a report elsewhere, do a long press (or right click) where you want the report to be shown.',
|
||||
},
|
||||
geolocation: {
|
||||
fetching: 'Fetching current position…',
|
||||
unavailable: 'Sorry, geolocation is not available in your browser.',
|
||||
},
|
||||
menu: {
|
||||
About: 'About',
|
||||
About: 'Help',
|
||||
Map: 'Map',
|
||||
Settings: 'Settings',
|
||||
},
|
||||
misc: {
|
||||
spaceBeforeDoublePunctuations: '',
|
||||
@ -23,4 +27,8 @@ export default {
|
||||
interrupt: 'Interruption',
|
||||
pothole: 'Pothole',
|
||||
},
|
||||
settings: {
|
||||
locale: 'Language',
|
||||
save: 'Save',
|
||||
},
|
||||
};
|
||||
|
@ -7,13 +7,17 @@ export default {
|
||||
license: "Le code source est sous <a href='https://opensource.org/licenses/MIT'>licence MIT license</a>. Les tuiles de fond de carte proviennent de chez <a href='https://www.opencyclemap.org/docs/'>OpenCycleMap</a>, grace aux <a href='https://www.openstreetmap.org/copyright'>contributeurs OpenStreetMap</a> et à <a href='http://leafletjs.com/'>Leaflet</a>.",
|
||||
potholeDescription: 'Un nid de poule dans la route.',
|
||||
summary: 'Cette application vous permet de signaler et de partager des problèmes avec les itinéraires cyclables.',
|
||||
usage: 'Utilisation',
|
||||
usageDescription: "Utilisez le bouton en bas à droite pour ajouter un signalement à votre emplacement actuel. Pour ajouter un signalement ailleurs, faites un appui long (ou clic droit) à l'emplacement souhaité sur la carte.",
|
||||
},
|
||||
geolocation: {
|
||||
fetching: 'En attente de votre position…',
|
||||
unavailable: "Désolé, la géolocalisation n'est pas disponible dans votre navigateur.",
|
||||
},
|
||||
menu: {
|
||||
About: 'À-propos',
|
||||
About: 'Aide',
|
||||
Map: 'Carte',
|
||||
Settings: 'Préférences',
|
||||
},
|
||||
misc: {
|
||||
spaceBeforeDoublePunctuations: ' ',
|
||||
@ -23,4 +27,8 @@ export default {
|
||||
interrupt: 'Interruption',
|
||||
pothole: 'Nid de poule',
|
||||
},
|
||||
settings: {
|
||||
locale: 'Langue',
|
||||
save: 'Sauver',
|
||||
},
|
||||
};
|
||||
|
@ -1,6 +1,8 @@
|
||||
import Vue from 'vue';
|
||||
import VueI18n from 'vue-i18n';
|
||||
|
||||
import { storageAvailable } from '@/tools';
|
||||
|
||||
import en from './en';
|
||||
import fr from './fr';
|
||||
|
||||
@ -31,22 +33,30 @@ export function getBrowserLocales() {
|
||||
return locales;
|
||||
}
|
||||
|
||||
const messages = {
|
||||
export const messages = {
|
||||
en,
|
||||
fr,
|
||||
};
|
||||
|
||||
|
||||
let locale = null;
|
||||
if (storageAvailable('localStorage')) {
|
||||
locale = localStorage.getItem('i18nSetting');
|
||||
if (!messages[locale]) {
|
||||
locale = null;
|
||||
}
|
||||
} else {
|
||||
// Get best matching locale from browser
|
||||
const locales = getBrowserLocales();
|
||||
|
||||
let locale = 'en'; // Safe default
|
||||
// Get best matching locale
|
||||
for (let i = 0; i < locales.length; i += 1) {
|
||||
if (messages[locales[i]]) {
|
||||
locale = locales[i];
|
||||
break; // Break at first matching locale
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!locale) {
|
||||
locale = 'en'; // Safe default
|
||||
}
|
||||
|
||||
export default new VueI18n({
|
||||
locale,
|
||||
|
@ -2,20 +2,26 @@ import Vue from 'vue';
|
||||
import Router from 'vue-router';
|
||||
import About from '@/components/About.vue';
|
||||
import Map from '@/views/Map.vue';
|
||||
import Settings from '@/views/Settings.vue';
|
||||
|
||||
Vue.use(Router);
|
||||
|
||||
export default new Router({
|
||||
routes: [
|
||||
{
|
||||
path: '/about',
|
||||
name: 'About',
|
||||
component: About,
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
name: 'Map',
|
||||
component: Map,
|
||||
},
|
||||
{
|
||||
path: '/about',
|
||||
name: 'About',
|
||||
component: About,
|
||||
path: '/settings',
|
||||
name: 'Settings',
|
||||
component: Settings,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@ -30,3 +30,27 @@ export function mockLocation() {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function storageAvailable(type) {
|
||||
let storage;
|
||||
try {
|
||||
storage = window[type];
|
||||
const x = '__storage_test__';
|
||||
storage.setItem(x, x);
|
||||
storage.removeItem(x);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return e instanceof DOMException && (
|
||||
// everything except Firefox
|
||||
e.code === 22 ||
|
||||
// Firefox
|
||||
e.code === 1014 ||
|
||||
// test name field too, because code might not be present
|
||||
// everything except Firefox
|
||||
e.name === 'QuotaExceededError' ||
|
||||
// Firefox
|
||||
e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
|
||||
// acknowledge QuotaExceededError only if there's something already stored
|
||||
storage.length !== 0;
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
<v-container fluid fill-height class="no-padding">
|
||||
<v-layout row wrap fill-height>
|
||||
<v-flex xs12 fill-height v-if="lat && lng">
|
||||
<Map :lat="lat" :lng="lng" :heading="heading" :accuracy="accuracy" :markers="reportsMarkers"></Map>
|
||||
<Map :lat="lat" :lng="lng" :heading="heading" :accuracy="accuracy" :markers="reportsMarkers" :onLongPress="showReportDialog"></Map>
|
||||
<v-btn
|
||||
fixed
|
||||
dark
|
||||
@ -11,11 +11,11 @@
|
||||
right
|
||||
color="orange"
|
||||
class="overlayButton"
|
||||
@click.native.stop="dialog = !dialog"
|
||||
@click.native.stop="() => showReportDialog()"
|
||||
>
|
||||
<v-icon>report_problem</v-icon>
|
||||
</v-btn>
|
||||
<ReportDialog v-model="dialog" :lat="lat" :lng="lng"></ReportDialog>
|
||||
<ReportDialog v-model="dialog" :lat="reportLat" :lng="reportLng"></ReportDialog>
|
||||
</v-flex>
|
||||
<v-flex xs12 fill-height v-else class="pa-3">
|
||||
<template v-if="error">
|
||||
@ -49,10 +49,12 @@ export default {
|
||||
this.initializePositionWatching();
|
||||
this.setNoSleep();
|
||||
this.$store.dispatch('fetchReports');
|
||||
window.addEventListener('keydown', this.hideReportDialogOnEsc);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.disableNoSleep();
|
||||
this.disablePositionWatching();
|
||||
window.removeEventListener('keydown', this.hideReportDialogOnEsc);
|
||||
},
|
||||
computed: {
|
||||
reportsMarkers() {
|
||||
@ -72,6 +74,8 @@ export default {
|
||||
lat: null,
|
||||
lng: null,
|
||||
noSleep: null,
|
||||
reportLat: null,
|
||||
reportLng: null,
|
||||
watchID: null,
|
||||
};
|
||||
},
|
||||
@ -137,6 +141,27 @@ export default {
|
||||
this.noSleep.disable();
|
||||
}
|
||||
},
|
||||
showReportDialog(latlng) {
|
||||
if (latlng) {
|
||||
this.reportLat = latlng.lat;
|
||||
this.reportLng = latlng.lng;
|
||||
} else {
|
||||
this.reportLat = this.lat;
|
||||
this.reportLng = this.lng;
|
||||
}
|
||||
this.dialog = !this.dialog;
|
||||
},
|
||||
hideReportDialogOnEsc(event) {
|
||||
let isEscape = false;
|
||||
if ('key' in event) {
|
||||
isEscape = (event.key === 'Escape' || event.key === 'Esc');
|
||||
} else {
|
||||
isEscape = (event.keyCode === 27);
|
||||
}
|
||||
if (isEscape) {
|
||||
this.dialog = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
43
src/views/Settings.vue
Normal file
43
src/views/Settings.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<v-container fluid class="no-padding">
|
||||
<v-layout row wrap>
|
||||
<v-flex xs12 class="text-xs-center">
|
||||
<h2>{{ $t('menu.Settings') }}</h2>
|
||||
<form>
|
||||
<v-select
|
||||
:items="i18nItems"
|
||||
v-model="i18nSelect"
|
||||
:label="$t('settings.locale')"
|
||||
required
|
||||
></v-select>
|
||||
|
||||
<v-btn @click="submit">{{ $t('settings.save') }}</v-btn>
|
||||
</form>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue';
|
||||
|
||||
import { messages } from '@/i18n';
|
||||
import { storageAvailable } from '@/tools';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
i18nItems: Object.keys(messages),
|
||||
i18nSelect: this.$i18n.locale,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
if (storageAvailable('localStorage')) {
|
||||
localStorage.setItem('i18nSetting', this.i18nSelect);
|
||||
}
|
||||
this.$i18n.locale = this.i18nSelect;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
Loading…
Reference in New Issue
Block a user