Show the reports as soon as they are added in the db
This commit is contained in:
parent
e961a8dbb1
commit
f30d000f92
@ -23,7 +23,8 @@
|
|||||||
"vue-router": "^3.0.1",
|
"vue-router": "^3.0.1",
|
||||||
"vue2-leaflet": "^1.0.2",
|
"vue2-leaflet": "^1.0.2",
|
||||||
"vue2-leaflet-tracksymbol": "^1.0.10",
|
"vue2-leaflet-tracksymbol": "^1.0.10",
|
||||||
"vuetify": "^1.0.0"
|
"vuetify": "^1.0.0",
|
||||||
|
"vuex": "^3.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"autoprefixer": "^7.1.2",
|
"autoprefixer": "^7.1.2",
|
||||||
|
@ -1,12 +1,24 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
import functools
|
||||||
|
import json
|
||||||
|
|
||||||
import bottle
|
import bottle
|
||||||
|
|
||||||
from server import routes
|
from server import routes
|
||||||
|
from server.jsonapi import DateAwareJSONEncoder
|
||||||
from server.models import db, Report
|
from server.models import db, Report
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
db.connect()
|
db.connect()
|
||||||
db.create_tables([Report])
|
db.create_tables([Report])
|
||||||
|
|
||||||
|
# Use DateAwareJSONEncoder to dump JSON strings
|
||||||
|
# From http://stackoverflow.com/questions/21282040/bottle-framework-how-to-return-datetime-in-json-response#comment55718456_21282666. pylint: disable=locally-disabled,line-too-long
|
||||||
|
bottle.install(
|
||||||
|
bottle.JSONPlugin(
|
||||||
|
json_dumps=functools.partial(json.dumps, cls=DateAwareJSONEncoder)
|
||||||
|
)
|
||||||
|
)
|
||||||
bottle.run(host='0.0.0.0', port='8081')
|
bottle.run(host='0.0.0.0', port='8081')
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
Helpers to implement a JSON API with Bottle.
|
Helpers to implement a JSON API with Bottle.
|
||||||
"""
|
"""
|
||||||
|
import datetime
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@ -11,6 +12,16 @@ import bottle
|
|||||||
FILTER_RE = re.compile(r"filter\[([A-z0-9_]+)\]")
|
FILTER_RE = re.compile(r"filter\[([A-z0-9_]+)\]")
|
||||||
|
|
||||||
|
|
||||||
|
class DateAwareJSONEncoder(json.JSONEncoder):
|
||||||
|
"""
|
||||||
|
Extend the default JSON encoder to serialize datetimes to iso strings.
|
||||||
|
"""
|
||||||
|
def default(self, o): # pylint: disable=locally-disabled,E0202
|
||||||
|
if isinstance(o, (datetime.date, datetime.datetime)):
|
||||||
|
return o.isoformat()
|
||||||
|
return json.JSONEncoder.default(self, o)
|
||||||
|
|
||||||
|
|
||||||
@bottle.hook('after_request')
|
@bottle.hook('after_request')
|
||||||
def enable_cors():
|
def enable_cors():
|
||||||
"""
|
"""
|
||||||
|
@ -93,7 +93,7 @@ def post_report():
|
|||||||
return jsonapi.JsonApiError(400, "Invalid JSON payload: " + str(exc))
|
return jsonapi.JsonApiError(400, "Invalid JSON payload: " + str(exc))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
Report.create(
|
r = Report.create(
|
||||||
type=payload['type'],
|
type=payload['type'],
|
||||||
lat=payload['lat'],
|
lat=payload['lat'],
|
||||||
lng=payload['lng']
|
lng=payload['lng']
|
||||||
@ -102,5 +102,5 @@ def post_report():
|
|||||||
return jsonapi.JsonApiError(400, "Invalid report payload: " + str(exc))
|
return jsonapi.JsonApiError(400, "Invalid report payload: " + str(exc))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"status": "ok"
|
"data": r.to_json()
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,8 @@ export function saveReport(type, lat, lng) {
|
|||||||
lng,
|
lng,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(response => response.data)
|
||||||
.catch(exc => console.error(`Unable to post report: ${exc}.`));
|
.catch(exc => console.error(`Unable to post report: ${exc}.`));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,8 +11,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import * as api from '@/api';
|
|
||||||
|
|
||||||
import GCUMIcon from '@/assets/gcum.svg';
|
import GCUMIcon from '@/assets/gcum.svg';
|
||||||
import ObstacleIcon from '@/assets/obstacle.svg';
|
import ObstacleIcon from '@/assets/obstacle.svg';
|
||||||
import PotHoleIcon from '@/assets/pothole.svg';
|
import PotHoleIcon from '@/assets/pothole.svg';
|
||||||
@ -60,7 +58,11 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
saveReport(type) {
|
saveReport(type) {
|
||||||
return api.saveReport(type, this.lat, this.lng)
|
return this.$store.dispatch('saveReport', {
|
||||||
|
type,
|
||||||
|
lat: this.lat,
|
||||||
|
lng: this.lng,
|
||||||
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.isActive = !this.isActive;
|
this.isActive = !this.isActive;
|
||||||
});
|
});
|
||||||
|
@ -10,8 +10,9 @@ import 'leaflet/dist/leaflet.css';
|
|||||||
import 'vuetify/dist/vuetify.min.css';
|
import 'vuetify/dist/vuetify.min.css';
|
||||||
|
|
||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
import router from './router';
|
|
||||||
import i18n from './i18n';
|
import i18n from './i18n';
|
||||||
|
import router from './router';
|
||||||
|
import store from './store';
|
||||||
|
|
||||||
Vue.use(Vuetify);
|
Vue.use(Vuetify);
|
||||||
|
|
||||||
@ -28,6 +29,7 @@ new Vue({
|
|||||||
el: '#app',
|
el: '#app',
|
||||||
router,
|
router,
|
||||||
i18n,
|
i18n,
|
||||||
|
store,
|
||||||
components: { App },
|
components: { App },
|
||||||
template: '<App/>',
|
template: '<App/>',
|
||||||
});
|
});
|
||||||
|
13
src/store/actions.js
Normal file
13
src/store/actions.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import * as api from '@/api';
|
||||||
|
import { PUSH_REPORT, STORE_REPORTS } from './mutations-types';
|
||||||
|
|
||||||
|
export function fetchReports({ commit }) {
|
||||||
|
return api.getReports().then(
|
||||||
|
reports => commit(STORE_REPORTS, { reports }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveReport({ commit }, { type, lat, lng }) {
|
||||||
|
return api.saveReport(type, lat, lng)
|
||||||
|
.then(report => commit(PUSH_REPORT, { report }));
|
||||||
|
}
|
13
src/store/index.js
Normal file
13
src/store/index.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import Vue from 'vue';
|
||||||
|
import Vuex from 'vuex';
|
||||||
|
|
||||||
|
import * as actions from './actions';
|
||||||
|
import { initialState as state, mutations } from './mutations';
|
||||||
|
|
||||||
|
Vue.use(Vuex);
|
||||||
|
|
||||||
|
export default new Vuex.Store({
|
||||||
|
actions,
|
||||||
|
state,
|
||||||
|
mutations,
|
||||||
|
});
|
2
src/store/mutations-types.js
Normal file
2
src/store/mutations-types.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export const PUSH_REPORT = 'PUSH_REPORT';
|
||||||
|
export const STORE_REPORTS = 'STORE_REPORTS';
|
14
src/store/mutations.js
Normal file
14
src/store/mutations.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import * as types from './mutations-types';
|
||||||
|
|
||||||
|
export const initialState = {
|
||||||
|
reports: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mutations = {
|
||||||
|
[types.STORE_REPORTS](state, { reports }) {
|
||||||
|
state.reports = reports;
|
||||||
|
},
|
||||||
|
[types.PUSH_REPORT](state, { report }) {
|
||||||
|
state.reports.push(report);
|
||||||
|
},
|
||||||
|
};
|
@ -30,7 +30,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import NoSleep from 'nosleep.js';
|
import NoSleep from 'nosleep.js';
|
||||||
|
|
||||||
import * as api from '@/api';
|
|
||||||
import Map from '@/components/Map.vue';
|
import Map from '@/components/Map.vue';
|
||||||
import ReportDialog from '@/components/ReportDialog/index.vue';
|
import ReportDialog from '@/components/ReportDialog/index.vue';
|
||||||
import { distance, mockLocation } from '@/tools';
|
import { distance, mockLocation } from '@/tools';
|
||||||
@ -47,7 +46,7 @@ export default {
|
|||||||
created() {
|
created() {
|
||||||
this.initializePositionWatching();
|
this.initializePositionWatching();
|
||||||
this.setNoSleep();
|
this.setNoSleep();
|
||||||
this.fetchReports();
|
this.$store.dispatch('fetchReports');
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.disableNoSleep();
|
this.disableNoSleep();
|
||||||
@ -55,10 +54,7 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
reportsMarkers() {
|
reportsMarkers() {
|
||||||
if (!this.reports) {
|
return this.$store.state.reports.map(report => ({
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return this.reports.map(report => ({
|
|
||||||
id: report.id,
|
id: report.id,
|
||||||
latLng: [report.attributes.lat, report.attributes.lng],
|
latLng: [report.attributes.lat, report.attributes.lng],
|
||||||
}));
|
}));
|
||||||
@ -72,7 +68,6 @@ export default {
|
|||||||
lat: null,
|
lat: null,
|
||||||
lng: null,
|
lng: null,
|
||||||
noSleep: null,
|
noSleep: null,
|
||||||
reports: null,
|
|
||||||
watchID: null,
|
watchID: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -121,7 +116,7 @@ export default {
|
|||||||
[position.coords.latitude, position.coords.longitude],
|
[position.coords.latitude, position.coords.longitude],
|
||||||
);
|
);
|
||||||
if (distanceFromPreviousPoint > UPDATE_REPORTS_DISTANCE_THRESHOLD) {
|
if (distanceFromPreviousPoint > UPDATE_REPORTS_DISTANCE_THRESHOLD) {
|
||||||
this.fetchReports();
|
this.$store.dispatch('fetchReports');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.lat = position.coords.latitude;
|
this.lat = position.coords.latitude;
|
||||||
@ -141,11 +136,6 @@ export default {
|
|||||||
this.noSleep.disable();
|
this.noSleep.disable();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fetchReports() {
|
|
||||||
api.getReports().then((reports) => {
|
|
||||||
this.reports = reports;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
Loading…
Reference in New Issue
Block a user