diff --git a/ci/bundle_js.sh b/ci/bundle_js.sh index aa879e2..f764e2d 100644 --- a/ci/bundle_js.sh +++ b/ci/bundle_js.sh @@ -8,7 +8,7 @@ css_deps=( npm/bootstrap-vue@2/dist/bootstrap-vue.min.css # Other packages - npm/leaflet@1.5.1/dist/leaflet.min.css + npm/leaflet@1.6.0/dist/leaflet.min.css ) js_deps=( @@ -18,10 +18,22 @@ js_deps=( # Other packages npm/axios@0.19.0/dist/axios.min.js - npm/leaflet@1.5.1/dist/leaflet.min.js + npm/leaflet@1.6.0/dist/leaflet.min.js npm/moment@2.24.0/min/moment.min.js ) +leaflet_images=( + marker-icon.png + marker-icon-2x.png + marker-shadow.png +) + +rm -rf frontend/leaflet +mkdir -p frontend/leaflet +for img in "${leaflet_images[@]}"; do + curl -sSfLo "frontend/leaflet/${img}" "https://cdn.jsdelivr.net/npm/leaflet@1.5.1/dist/images/${img}" +done + IFS=',' curl -sSfLo frontend/combine.js "https://cdn.jsdelivr.net/combine/${js_deps[*]}" diff --git a/frontend/.gitignore b/frontend/.gitignore index c6d9f73..0fd7aef 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -1,3 +1,4 @@ combine.css combine.js fontawesome +leaflet diff --git a/frontend/app.css b/frontend/app.css new file mode 100644 index 0000000..e768d53 --- /dev/null +++ b/frontend/app.css @@ -0,0 +1,6 @@ +html, body, #app, #map { + height: 100%; + margin: 0; + padding: 0; + width: 100%; +} diff --git a/frontend/app.js b/frontend/app.js new file mode 100644 index 0000000..ffd2ec9 --- /dev/null +++ b/frontend/app.js @@ -0,0 +1,117 @@ +const app = new Vue({ + + created() { + // Use defaults with custom icon paths + this.icon = L.icon({ + ...L.Icon.Default.prototype.options, + iconUrl: '/asset/leaflet/marker-icon.png', + iconRetinaUrl: '/asset/leaflet/marker-icon-2x.png', + shadowUrl: '/asset/leaflet/marker-shadow.png', + }) + + // This is only to detect another user updated the location + // therefore this is NOT cryptographically safe! + this.browserID = localStorage.getItem('browserID') + if (!this.browserID) { + this.browserID = Math.random().toString(16).substr(2) + localStorage.setItem('browserID', this.browserID) + } + }, + + data: { + browserID: null, + icon: null, + loc: null, + map: null, + marker: null, + shareActive: false, + shareSettings: { + continuous: true, + retained: false, + }, + shareSettingsOpen: false, + socket: null, + }, + + el: '#app', + + methods: { + initMap() { + this.map = L.map('map') + .setView([0,0], 13) + + L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: '© OpenStreetMap contributors' + }).addTo(this.map) + + }, + + shareLocation() { + this.shareActive = true + + const opts = { + enableHighAccuracy: true, + timeout: 5000, + maximumAge: 0, + } + + if (!this.shareSettings.continuous) { + navigator.geolocation.getCurrentPosition(this.updateLocation, err => console.error(err), opts) + return + } + + navigator.geolocation.watchPosition(this.updateLocation, err => console.error(err), opts) + }, + + subscribe() { + if (this.socket) { + // Dispose old socket + this.socket.close() + this.socket = null + } + + this.socket = new WebSocket(`${window.location.href.split('#')[0].replace(/^http/, 'ws')}/ws`) + this.socket.onclose = () => window.setTimeout(this.subscribe, 1000) // Restart socket + this.socket.onmessage = evt => { + let loc= JSON.parse(evt.data) + loc.time = new Date(loc.time) + this.loc= loc + } + }, + + updateLocation(pos) { + const data = { + lat: pos.coords.latitude, + lon: pos.coords.longitude, + retained: this.shareSettings.retained, + sender_id: this.browserID, + } + + return axios.put(window.location.href.split('#')[0], data) + .catch(err => console.error(err)) + }, + + updateMap() { + const center = [this.loc.lat, this.loc.lon] + + if (!this.marker) { + this.marker = L.marker(center, {icon:this.icon}) + .addTo(this.map) + } + + this.map.panTo(center) + this.marker.setLatLng(center) + }, + }, + + mounted() { + this.initMap() + this.subscribe() + }, + + watch: { + loc() { + this.updateMap() + }, + }, +}) diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..629bcae --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,60 @@ + + MapShare + + + + + +
+
+ + +
+

+
+ Waiting for location... +

+

+ + + Share my location! + + + + + + +

+
+
+ + + + Keep sending location
(when enabled location is updated as long as this window is open) +
+ + Retain location on server
(new viewers instantly see your location) +
+
+
+ + + +