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-icon>more_vert</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-list>
|
<v-list>
|
||||||
<v-list-tile @click="">
|
<v-list-tile @click="goToMap">
|
||||||
<v-list-tile-title @click="goToAbout">{{ $t("menu.About") }}</v-list-tile-title>
|
<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-tile>
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
@ -36,6 +42,12 @@ export default {
|
|||||||
goToAbout() {
|
goToAbout() {
|
||||||
this.$router.push({ name: 'About' });
|
this.$router.push({ name: 'About' });
|
||||||
},
|
},
|
||||||
|
goToMap() {
|
||||||
|
this.$router.push({ name: 'Map' });
|
||||||
|
},
|
||||||
|
goToSettings() {
|
||||||
|
this.$router.push({ name: 'Settings' });
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-container fluid>
|
<v-container fluid>
|
||||||
<v-layout row>
|
<v-layout row>
|
||||||
<v-flex xs6 offset-xs3>
|
<v-flex xs12 sm6 offset-sm3>
|
||||||
<p>{{ $t('about.summary') }}</p>
|
<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>
|
<h2 class="body-2">{{ $t('about.availableReportsTitle') }}</h2>
|
||||||
<ul class="ml-3">
|
<ul class="ml-3">
|
||||||
<li><strong>{{ $t('reportLabels.gcum') }}</strong>{{ $t('misc.spaceBeforeDoublePunctuations') }}: {{ $t('about.gcumDescription') }}</li>
|
<li><strong>{{ $t('reportLabels.gcum') }}</strong>{{ $t('misc.spaceBeforeDoublePunctuations') }}: {{ $t('about.gcumDescription') }}</li>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="fill-height fill-width">
|
<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-ltilelayer :url="tileServer" :attribution="attribution"></v-ltilelayer>
|
||||||
|
|
||||||
<v-lts v-if="heading" :lat-lng="latlng" :options="markerOptions"></v-lts>
|
<v-lts v-if="heading" :lat-lng="latlng" :options="markerOptions"></v-lts>
|
||||||
@ -44,6 +44,7 @@ export default {
|
|||||||
lat: Number,
|
lat: Number,
|
||||||
lng: Number,
|
lng: Number,
|
||||||
markers: Array,
|
markers: Array,
|
||||||
|
onLongPress: Function,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
radiusFromAccuracy() {
|
radiusFromAccuracy() {
|
||||||
@ -84,6 +85,13 @@ export default {
|
|||||||
tileServer: constants.TILE_SERVER,
|
tileServer: constants.TILE_SERVER,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
|
handleLongPress(event) {
|
||||||
|
if (this.onLongPress) {
|
||||||
|
this.onLongPress(event.latlng);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</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>.',
|
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.',
|
potholeDescription: 'A pothole in the ground.',
|
||||||
summary: 'This app lets you track and share issues with bike lanes.',
|
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: {
|
geolocation: {
|
||||||
fetching: 'Fetching current position…',
|
fetching: 'Fetching current position…',
|
||||||
unavailable: 'Sorry, geolocation is not available in your browser.',
|
unavailable: 'Sorry, geolocation is not available in your browser.',
|
||||||
},
|
},
|
||||||
menu: {
|
menu: {
|
||||||
About: 'About',
|
About: 'Help',
|
||||||
|
Map: 'Map',
|
||||||
|
Settings: 'Settings',
|
||||||
},
|
},
|
||||||
misc: {
|
misc: {
|
||||||
spaceBeforeDoublePunctuations: '',
|
spaceBeforeDoublePunctuations: '',
|
||||||
@ -23,4 +27,8 @@ export default {
|
|||||||
interrupt: 'Interruption',
|
interrupt: 'Interruption',
|
||||||
pothole: 'Pothole',
|
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>.",
|
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.',
|
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.',
|
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: {
|
geolocation: {
|
||||||
fetching: 'En attente de votre position…',
|
fetching: 'En attente de votre position…',
|
||||||
unavailable: "Désolé, la géolocalisation n'est pas disponible dans votre navigateur.",
|
unavailable: "Désolé, la géolocalisation n'est pas disponible dans votre navigateur.",
|
||||||
},
|
},
|
||||||
menu: {
|
menu: {
|
||||||
About: 'À-propos',
|
About: 'Aide',
|
||||||
|
Map: 'Carte',
|
||||||
|
Settings: 'Préférences',
|
||||||
},
|
},
|
||||||
misc: {
|
misc: {
|
||||||
spaceBeforeDoublePunctuations: ' ',
|
spaceBeforeDoublePunctuations: ' ',
|
||||||
@ -23,4 +27,8 @@ export default {
|
|||||||
interrupt: 'Interruption',
|
interrupt: 'Interruption',
|
||||||
pothole: 'Nid de poule',
|
pothole: 'Nid de poule',
|
||||||
},
|
},
|
||||||
|
settings: {
|
||||||
|
locale: 'Langue',
|
||||||
|
save: 'Sauver',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import VueI18n from 'vue-i18n';
|
import VueI18n from 'vue-i18n';
|
||||||
|
|
||||||
|
import { storageAvailable } from '@/tools';
|
||||||
|
|
||||||
import en from './en';
|
import en from './en';
|
||||||
import fr from './fr';
|
import fr from './fr';
|
||||||
|
|
||||||
@ -31,22 +33,30 @@ export function getBrowserLocales() {
|
|||||||
return locales;
|
return locales;
|
||||||
}
|
}
|
||||||
|
|
||||||
const messages = {
|
export const messages = {
|
||||||
en,
|
en,
|
||||||
fr,
|
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();
|
const locales = getBrowserLocales();
|
||||||
|
|
||||||
let locale = 'en'; // Safe default
|
|
||||||
// Get best matching locale
|
|
||||||
for (let i = 0; i < locales.length; i += 1) {
|
for (let i = 0; i < locales.length; i += 1) {
|
||||||
if (messages[locales[i]]) {
|
if (messages[locales[i]]) {
|
||||||
locale = locales[i];
|
locale = locales[i];
|
||||||
break; // Break at first matching locale
|
break; // Break at first matching locale
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (!locale) {
|
||||||
|
locale = 'en'; // Safe default
|
||||||
|
}
|
||||||
|
|
||||||
export default new VueI18n({
|
export default new VueI18n({
|
||||||
locale,
|
locale,
|
||||||
|
@ -2,20 +2,26 @@ import Vue from 'vue';
|
|||||||
import Router from 'vue-router';
|
import Router from 'vue-router';
|
||||||
import About from '@/components/About.vue';
|
import About from '@/components/About.vue';
|
||||||
import Map from '@/views/Map.vue';
|
import Map from '@/views/Map.vue';
|
||||||
|
import Settings from '@/views/Settings.vue';
|
||||||
|
|
||||||
Vue.use(Router);
|
Vue.use(Router);
|
||||||
|
|
||||||
export default new Router({
|
export default new Router({
|
||||||
routes: [
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/about',
|
||||||
|
name: 'About',
|
||||||
|
component: About,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'Map',
|
name: 'Map',
|
||||||
component: Map,
|
component: Map,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/about',
|
path: '/settings',
|
||||||
name: 'About',
|
name: 'Settings',
|
||||||
component: About,
|
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-container fluid fill-height class="no-padding">
|
||||||
<v-layout row wrap fill-height>
|
<v-layout row wrap fill-height>
|
||||||
<v-flex xs12 fill-height v-if="lat && lng">
|
<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
|
<v-btn
|
||||||
fixed
|
fixed
|
||||||
dark
|
dark
|
||||||
@ -11,11 +11,11 @@
|
|||||||
right
|
right
|
||||||
color="orange"
|
color="orange"
|
||||||
class="overlayButton"
|
class="overlayButton"
|
||||||
@click.native.stop="dialog = !dialog"
|
@click.native.stop="() => showReportDialog()"
|
||||||
>
|
>
|
||||||
<v-icon>report_problem</v-icon>
|
<v-icon>report_problem</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<ReportDialog v-model="dialog" :lat="lat" :lng="lng"></ReportDialog>
|
<ReportDialog v-model="dialog" :lat="reportLat" :lng="reportLng"></ReportDialog>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
<v-flex xs12 fill-height v-else class="pa-3">
|
<v-flex xs12 fill-height v-else class="pa-3">
|
||||||
<template v-if="error">
|
<template v-if="error">
|
||||||
@ -49,10 +49,12 @@ export default {
|
|||||||
this.initializePositionWatching();
|
this.initializePositionWatching();
|
||||||
this.setNoSleep();
|
this.setNoSleep();
|
||||||
this.$store.dispatch('fetchReports');
|
this.$store.dispatch('fetchReports');
|
||||||
|
window.addEventListener('keydown', this.hideReportDialogOnEsc);
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.disableNoSleep();
|
this.disableNoSleep();
|
||||||
this.disablePositionWatching();
|
this.disablePositionWatching();
|
||||||
|
window.removeEventListener('keydown', this.hideReportDialogOnEsc);
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
reportsMarkers() {
|
reportsMarkers() {
|
||||||
@ -72,6 +74,8 @@ export default {
|
|||||||
lat: null,
|
lat: null,
|
||||||
lng: null,
|
lng: null,
|
||||||
noSleep: null,
|
noSleep: null,
|
||||||
|
reportLat: null,
|
||||||
|
reportLng: null,
|
||||||
watchID: null,
|
watchID: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -137,6 +141,27 @@ export default {
|
|||||||
this.noSleep.disable();
|
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>
|
</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