45 Commits

Author SHA1 Message Date
09eb797534 Better check for global access token, if else fallback to static 2021-09-13 09:49:38 +02:00
dcfa0f9918 Fallback for mapbox accesstoken if not defined on window 2021-09-13 09:44:56 +02:00
d82f779b23 Merge pull request #2 from KevinMidboe/feat/trips-and-areas
Feat/trips and areas
2021-09-07 19:27:18 +02:00
567e6f948b Updated compiled files 2021-09-07 17:07:37 +00:00
ffe41aeca3 Get mapbox api token from window 2021-09-07 19:05:12 +02:00
f5f8ab9333 Moved route type header 2021-09-07 15:45:19 +02:00
c50b1846fe Trigger change when updating selected route type. 2021-09-07 15:37:19 +02:00
ac52186b38 Fixed incorrect variable name 2021-09-07 15:28:35 +02:00
e416e97f1d Set default values on beforeMount 2021-09-07 15:25:30 +02:00
c15cc02a09 Manually handle radio state 2021-09-07 15:22:35 +02:00
9078300037 Create navigation route w/o l-control 2021-09-07 15:14:33 +02:00
24eabaec3a Imported l-control 2021-09-07 15:11:20 +02:00
4c0f2750fd Added control for selecting walk or driving directions 2021-09-07 15:00:45 +02:00
0c1abff773 Use localvalue to separate markers and navigation route 2021-09-06 16:47:14 +02:00
2e7602a928 Updated delimiter for navigation cords query 2021-09-06 16:28:40 +02:00
0debcb02a1 Dynamic cords from markers 2021-09-06 16:26:21 +02:00
8c8c21b432 Deconstruct all the way to coords 2021-09-06 16:19:35 +02:00
06fc0a8291 Transform navigation response before bubbling input latlng 2021-09-06 16:17:21 +02:00
a8756bee8b Updated mapbox api url 2021-09-06 16:10:02 +02:00
a327c7a40f Updated compiled files 2021-09-06 16:07:04 +02:00
ea32ebbdc0 Remove custom icon from route 2021-09-06 16:06:29 +02:00
ef3149d0f1 Import global polyline component 2021-09-06 16:06:16 +02:00
9fc1f312c5 Corrected import statement 2021-09-06 15:56:48 +02:00
4726aedd1f Merge branch 'master' of github.com:kevinmidboe/nova-map-fields 2021-09-06 15:53:35 +02:00
8415f8a940 Updated field class name 2021-09-06 15:53:20 +02:00
7b94c0d795 Updated compiled files 2021-09-06 13:51:46 +00:00
28aef8d328 Testing new navigation route 2021-09-06 15:50:56 +02:00
7545c4ffc9 Linting 2021-09-06 15:50:36 +02:00
c205e8bf23 Show highlighted marker for the last added marker 2021-09-06 11:06:45 +00:00
cd2c3484bf Updated compiled files 2021-09-06 10:55:29 +00:00
82b5f9f32f Update a marker position of polygon when dragged 2021-09-06 10:53:57 +00:00
3bf7d511e5 Debug: triggerChange stop drag marker 2021-09-06 09:19:54 +00:00
1a2987154e Added button styling for marker controls 2021-09-05 23:06:57 +00:00
6532d68f54 Updated compiled files 2021-09-05 22:56:44 +00:00
ff4aeb273e Increased with of mapfield 2021-09-05 22:51:46 +00:00
48b476a238 Update icon of polygon markers 2021-09-05 22:43:11 +00:00
ad52007642 Updated compiled files 2021-09-05 22:14:44 +00:00
5b2bcb110e Only show control when edit 2021-09-05 22:13:37 +00:00
0c22a4127f Remove last & all marker buttons 2021-09-05 22:08:02 +00:00
218bea2aeb Updated compiled files 2021-09-05 22:02:05 +00:00
d1080bc6b1 Updated component register 2021-09-05 22:00:02 +00:00
f346aa72e0 Merge branch 'master' of github.com:kevinmidboe/nova-map-fields 2021-09-05 21:57:32 +00:00
c48e06c80e New polygon field 2021-09-05 21:57:23 +00:00
64e592ba3c New polygon field 2021-09-05 23:45:39 +02:00
Lasse S. Haslev
024010b867 Refactor Polyline resolveAttribute. 2021-01-26 15:18:54 +01:00
14 changed files with 498 additions and 25625 deletions

25554
dist/js/field.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -2,7 +2,11 @@
<div>
<panel-item :field="field">
<template slot="value">
<component :is="'field-' + field.map.type" :field="field" v-model="field.value"></component>
<component
:is="'field-' + field.map.type"
:field="field"
v-model="field.value"
></component>
</template>
</panel-item>
</div>
@@ -10,6 +14,6 @@
<script>
export default {
props: ['resource', 'resourceName', 'resourceId', 'field'],
}
props: ["resource", "resourceName", "resourceId", "field"],
};
</script>

View File

@@ -1,22 +1,20 @@
<template>
<div :style="{'height': field.map.height}">
<div :style="{ height: field.map.height }">
<l-map
style="z-index:0"
style="z-index: 0"
:zoom="field.map.zoom"
:center="center"
:bounds="bounds"
@click="onMapClick"
>
<l-tile-layer
:url="url"
:attribution="attribution"/>
>
<l-tile-layer :url="url" :attribution="attribution" />
<slot></slot>
</l-map>
</div>
</template>
<script>
import {LMap, LTileLayer} from 'vue2-leaflet'
import { LMap, LTileLayer } from "vue2-leaflet";
export default {
props: {
@@ -36,24 +34,25 @@ export default {
data() {
return {
attribution: '&copy; <a target="_blank" href="http://osm.org/copyright">OpenStreetMap</a> contributors',
url: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
}
attribution:
'&copy; <a target="_blank" href="http://osm.org/copyright">OpenStreetMap</a> contributors',
url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
};
},
methods: {
onMapClick(evt) {
this.$emit('click', evt);
}
this.$emit("click", evt);
},
},
components: {
LMap,
LTileLayer,
}
}
},
};
</script>
<style scoped>
@import "../../../node_modules/leaflet/dist/leaflet.css"
@import "../../../node_modules/leaflet/dist/leaflet.css";
</style>

View File

@@ -1,25 +1,31 @@
<template>
<default-field :field="field" :errors="errors">
<default-field :field="field" :errors="errors" :full-width-content="true">
<template slot="field">
<component v-if="value" :is="'field-' + field.map.type" :field="field" :edit="true" v-model="value"></component>
<component
v-if="value"
:is="'field-' + field.map.type"
:field="field"
:edit="true"
v-model="value"
></component>
</template>
</default-field>
</template>
<script>
import { FormField, HandlesValidationErrors } from 'laravel-nova'
import { FormField, HandlesValidationErrors } from "laravel-nova";
export default {
mixins: [FormField, HandlesValidationErrors],
props: ['resourceName', 'resourceId', 'field'],
props: ["resourceName", "resourceId", "field"],
methods: {
/*
* Set the initial, internal value for the field.
*/
setInitialValue() {
this.value = this.field.value || {}
this.value = this.field.value || {};
},
/**
@@ -33,8 +39,8 @@ export default {
* Update the field's internal value.
*/
handleChange(value) {
this.value = value
this.value = value;
},
},
}
};
</script>

View File

@@ -4,6 +4,6 @@
<script>
export default {
props: ['resourceName', 'field'],
}
props: ["resourceName", "field"],
};
</script>

View File

@@ -1,11 +1,11 @@
<template>
<field-map :center="position" :field="field">
<l-marker :lat-lng="position" :draggable="edit" @dragend="onDragEnd"/>
<l-marker :lat-lng="position" :draggable="edit" @dragend="onDragEnd" />
</field-map>
</template>
<script>
import {LMarker} from 'vue2-leaflet'
import { LMarker } from "vue2-leaflet";
export default {
props: {
@@ -20,13 +20,16 @@ export default {
edit: {
type: Boolean,
default: false,
}
},
},
computed: {
position() {
return L.latLng(this.value.lat || this.field.map.center.latitude, this.value.lng || this.field.map.center.longitude);
}
return L.latLng(
this.value.lat || this.field.map.center.latitude,
this.value.lng || this.field.map.center.longitude
);
},
},
mounted() {
@@ -37,36 +40,46 @@ export default {
onDragEnd(evt) {
let latLng = evt.target.getLatLng();
this.$emit('input', {
this.$emit("input", {
lat: latLng.lat,
lng: latLng.lng,
});
},
registerWatchers() {
this.getGlobalFieldComponents().forEach(component => {
if ([this.field.latitudeField, this.field.longitudeField].includes(component.field.attribute)) {
component.$watch('value', (value) => {
this.value[component.field.attribute] = value;
}, {immediate: true})
this.getGlobalFieldComponents().forEach((component) => {
if (
[
this.field.latitudeField,
this.field.longitudeField,
].includes(component.field.attribute)
) {
component.$watch(
"value",
(value) => {
this.value[component.field.attribute] = value;
},
{ immediate: true }
);
}
});
},
getGlobalFieldComponents(components = null) {
if (! components) {
if (!components) {
components = this.$root.$children;
}
let returnArray = [];
components.forEach(component => {
components.forEach((component) => {
if (component.field) {
return returnArray.push(component);
}
returnArray = returnArray.concat(this.getGlobalFieldComponents(component.$children));
returnArray = returnArray.concat(
this.getGlobalFieldComponents(component.$children)
);
});
return returnArray;
@@ -75,6 +88,6 @@ export default {
components: {
LMarker,
}
}
},
};
</script>

View File

@@ -0,0 +1,111 @@
<template>
<div>
<field-polyline
:edit="edit"
:field="field"
:value="localValue"
@input="triggerChange"
/>
<p v-if="edit"><b>Velg rutetype:</b></p>
<div v-if="edit" class="flex block my-2">
<div v-for="option in routeTypes" class="ml-2">
<input
type="radio"
:id="option.value"
:checked="option.value === selectedRouteType"
:value="option.value"
@change="updateRouteType">
<label :for="option.value">{{ option.name }}</label>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
value: {
type: Object,
default: null,
},
field: {
type: Object,
required: true,
},
edit: {
type: Boolean,
default: false,
},
},
data() {
return {
localValue: {},
routeTypes: [{
value: 'driving',
name: 'Bil'
},
{
value: 'walking',
name: 'Gange'
}],
selectedRouteType: null
};
},
created() {
this.selectedRouteType = this.routeTypes[0].value;
if (this.value !== null) {
this.localValue = this.value;
}
},
methods: {
queryNavigation(markers) {
const cords = markers
.map((marker) => `${marker.lng},${marker.lat}`)
.join(";");
const API_URL = 'https://api.mapbox.com/directions/v5'
let ACCESS_TOKEN = 'pk.eyJ1Ijoia2V2aW5taWRib2UiLCJhIjoiY2pydWhlamQyMHJ2NTRhdGN1em5ndXVyMyJ9.Ejdo_3iuuGOD662Bh6es4w';
if (window.fjordmap && window.fjordmap.mapbox_access_token) {
ACCESS_TOKEN = window.fjordmap.mapbox_access_token;
}
const URL = `${API_URL}/mapbox/${this.selectedRouteType}/${cords}?geometries=geojson&access_token=${ACCESS_TOKEN}`;
return fetch(URL)
.then((resp) => resp.json())
.then((response) => response.routes[0].geometry.coordinates)
.then(this.mapNavigationRoute);
},
mapNavigationRoute(coords) {
return coords.map(([lng, lat]) => {
return { lat, lng };
});
},
updateRouteType(event) {
this.selectedRouteType = event.target.value;
this.triggerChange(this.localValue);
},
async triggerChange(markers) {
if (markers.length === 0) {
return;
}
this.localValue = markers;
console.log("forwarding markers:", markers);
const navigationRouteCoords = await this.queryNavigation(markers);
console.log("got navigationRouteCoords:", navigationRouteCoords);
this.$emit("input", navigationRouteCoords);
},
},
};
</script>

View File

@@ -0,0 +1,150 @@
<template>
<div @keydown.backspace="removeLastMarker">
<field-map
:bounds="bounds"
:center="center"
:field="field"
@click="createMarker"
>
<l-marker
v-for="(marker, index) in markers"
:lat-lng="marker"
:draggable="edit"
@dragstart="saveDraggingMarker"
@dragend="updateMarkerPosition"
>
<l-icon
:icon-size="[24, 24]"
:icon-anchor="[12, 12]"
:icon-url="
index === markers.length - 1
? '/images/highlighted-point_marker.svg'
: '/images/point_marker.svg'
"
/>
</l-marker>
<l-polygon :lat-lngs="value" :visible="true" />
</field-map>
<l-control position="bottomleft" v-if="edit" class="block my-2">
<button
@click="removeLastMarker"
class="btn btn-default btn-primary"
>
Slett forrige
</button>
<button
@click="removeAllMarkers"
class="btn btn-default btn-primary"
>
Slett alle
</button>
</l-control>
</div>
</template>
<script>
import { LMarker, LIcon, LPolygon, LControl } from "vue2-leaflet";
export default {
props: {
value: {
type: Object,
default: null,
},
field: {
type: Object,
required: true,
},
edit: {
type: Boolean,
default: false,
},
},
data() {
return {
startBounds: undefined,
markerBeingDragged: undefined,
lastMarkerAddedTime: 0,
};
},
computed: {
markers() {
if (!this.value[0]) {
return [];
}
return this.value;
},
center() {
if (!this.markers.length) {
return {
lat: this.field.map.center.latitude,
lng: this.field.map.center.longitude,
};
}
return this.bounds.getCenter();
},
bounds() {
if (this.startBounds !== undefined) {
return this.startBounds;
}
if (!this.markers.length) {
return null;
}
return (this.startBounds = new L.LatLngBounds(this.markers));
},
},
methods: {
triggerChange(evt) {
this.$emit("input", this.markers);
},
saveDraggingMarker(evt) {
this.markerBeingDragged = evt.target._latlng;
},
removeLastMarker(event) {
this.markers.splice(-1, 1);
this.triggerChange();
event.preventDefault();
},
removeAllMarkers(event) {
this.value = [];
this.triggerChange();
event.preventDefault();
},
createMarker(evt) {
if (new Date().getTime() - this.lastMarkerAddedTime < 20) {
// Don't want to double register clicks from dragend, if within 20 ms return
return;
}
this.markers.push(evt.latlng);
this.triggerChange();
},
updateMarkerPosition(evt) {
const newMarker = evt.target._latlng;
const { lat, lng } = this.markerBeingDragged;
const markerIndex = this.value.findIndex(
(marker) => marker.lat === lat && marker.lng === lng
);
this.value.splice(markerIndex, 1, newMarker);
this.lastMarkerAddedTime = new Date().getTime();
},
},
components: {
LMarker,
LIcon,
LPolygon,
LControl,
},
};
</script>

View File

@@ -1,14 +1,40 @@
<template>
<div @keydown.backspace="removeLastMarker">
<field-map :bounds="bounds" :center="center" :field="field" @click="createMarker">
<l-marker v-for="marker in markers" :lat-lng="marker" :draggable="edit" @dragend="triggerChange" />
<l-polyline :lat-lngs="value" :visible="true" />
<field-map
:bounds="bounds"
:center="center"
:field="field"
@click="createMarker"
>
<l-marker
v-for="(marker, index) in markers"
:lat-lng="marker"
:draggable="edit"
@dragstart="saveDraggingMarker"
@dragend="updateMarkerPosition"
/>
<l-polyline :lat-lngs="value" :visible="true" :fill="false" />
</field-map>
<l-control position="bottomleft" v-if="edit" class="block my-2">
<button
@click="removeLastMarker"
class="btn btn-default btn-primary"
>
Slett forrige
</button>
<button
@click="removeAllMarkers"
class="btn btn-default btn-primary"
>
Slett alle
</button>
</l-control>
</div>
</template>
<script>
import {LMarker, LPolyline} from 'vue2-leaflet'
import { LMarker, LIcon, LPolyline } from "vue2-leaflet";
export default {
props: {
@@ -23,13 +49,15 @@ export default {
edit: {
type: Boolean,
default: false,
}
},
},
data() {
return {
startBounds: undefined,
}
markerBeingDragged: undefined,
lastMarkerAddedTime: 0,
};
},
computed: {
@@ -61,27 +89,51 @@ export default {
return null;
}
return this.startBounds = new L.LatLngBounds(this.markers);
return (this.startBounds = new L.LatLngBounds(this.markers));
},
},
methods: {
triggerChange() {
this.$emit('input', this.markers);
triggerChange(evt) {
this.$emit("input", this.markers);
},
removeLastMarker() {
this.markers.splice(-1,1);
saveDraggingMarker(evt) {
this.markerBeingDragged = evt.target._latlng;
},
removeLastMarker(event) {
this.markers.splice(-1, 1);
this.triggerChange();
event.preventDefault();
},
removeAllMarkers(event) {
this.value = [];
this.triggerChange();
event.preventDefault();
},
createMarker(evt) {
if (new Date().getTime() - this.lastMarkerAddedTime < 20) {
// Don't want to double register clicks from dragend, if within 20 ms return
return;
}
this.markers.push(evt.latlng);
this.triggerChange();
}
},
updateMarkerPosition(evt) {
const newMarker = evt.target._latlng;
const { lat, lng } = this.markerBeingDragged;
const markerIndex = this.value.findIndex(
(marker) => marker.lat === lat && marker.lng === lng
);
this.value.splice(markerIndex, 1, newMarker);
this.lastMarkerAddedTime = new Date().getTime();
},
},
components: {
LMarker,
LIcon,
LPolyline,
}
}
},
};
</script>

View File

@@ -1,12 +1,21 @@
Nova.booting((Vue, router) => {
Vue.component('index-nova-map-fields', require('./components/IndexField'));
Vue.component('detail-nova-map-fields', require('./components/DetailField'));
Vue.component('form-nova-map-fields', require('./components/FormField'));
Vue.component("index-nova-map-fields", require("./components/IndexField"));
Vue.component(
"detail-nova-map-fields",
require("./components/DetailField")
);
Vue.component("form-nova-map-fields", require("./components/FormField"));
Vue.component('field-map', require('./components/FieldMap'));
Vue.component('field-marker', require('./components/fields/Marker'));
Vue.component('field-polyline', require('./components/fields/Polyline'));
Vue.component("field-map", require("./components/FieldMap"));
Vue.component("field-marker", require("./components/fields/Marker"));
Vue.component("field-polyline", require("./components/fields/Polyline"));
Vue.component("field-polygon", require("./components/fields/Polygon"));
Vue.component(
"field-navigation-route",
require("./components/fields/NavigationRoute")
);
// Config leaflet images to load images from CDN
L.Icon.Default.imagePath = 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.3.4/images/';
})
L.Icon.Default.imagePath =
"https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.3.4/images/";
});

View File

@@ -31,7 +31,7 @@ abstract class MapField extends Field
*/
public function __construct($name, $attribute = null, $resolveCallback = null)
{
$this->setHeight('250px');
$this->setHeight('400px');
$this->setCenter(0, 0);
$this->setZoom(13);

40
src/NavigationRoute.php Normal file
View File

@@ -0,0 +1,40 @@
<?php
namespace Lassehaslev\NovaMapFields;
class NavigationRoute extends MapField
{
/**
* Create a new field.
* And set default properties.
*
* @param string $name
* @param string|null $attribute
* @param mixed|null $resolveCallback
*/
public function __construct($name, $attribute = null, $resolveCallback = null)
{
$this->help('Click on the map to create a new point. Drag a marker to change the point. When the map is selected, you can press [backspace] to remove markers.');
parent::__construct($name, $attribute = null, $resolveCallback);
}
/**
* Resolve the attribute before sending to frontend.
*
* @param mixed $resource
* @param mixed|null $attribute
*
* @return array
*/
public function resolveAttribute($resource, $attribute = null)
{
$value = $resource->{$attribute};
if (is_array($value)) {
return $value;
}
return json_decode($value);
}
}

40
src/Polygon.php Normal file
View File

@@ -0,0 +1,40 @@
<?php
namespace Lassehaslev\NovaMapFields;
class Polygon extends MapField
{
/**
* Create a new field.
* And set default properties.
*
* @param string $name
* @param string|null $attribute
* @param mixed|null $resolveCallback
*/
public function __construct($name, $attribute = null, $resolveCallback = null)
{
$this->help('Click on the map to create a new point. Drag a marker to change the point. When the map is selected, you can press [backspace] to remove markers.');
parent::__construct($name, $attribute = null, $resolveCallback);
}
/**
* Resolve the attribute before sending to frontend.
*
* @param mixed $resource
* @param mixed|null $attribute
*
* @return array
*/
public function resolveAttribute($resource, $attribute = null)
{
$value = $resource->{$attribute};
if (is_array($value)) {
return $value;
}
return json_decode($value);
}
}

View File

@@ -29,11 +29,12 @@ class Polyline extends MapField
*/
public function resolveAttribute($resource, $attribute = null)
{
return json_decode($resource->{$attribute});
$value = $resource->{$attribute};
return [
'lat' => $resource->{$this->meta['latitudeField']},
'lng' => $resource->{$this->meta['longitudeField']},
];
if (is_array($value)) {
return $value;
}
return json_decode($value);
}
}