<template>
<div class='flex-parent flex-parent--column viewport-third h-auto-ml bg-white round-ml shadow-darken10' style="pointer-events:auto; max-height: calc(100% - 80px);">
<template v-if='delta'>
<div class='flex-child px12 py12'>
<h3 class='fl py6 txt-m txt-bold'>Delta #<span v-text="delta.id"></span></h3>
<button @click="delta = false" class='fr btn round bg-gray-light bg-darken25-on-hover color-gray-dark fl'><svg class='icon'><use href='#icon-arrow-left'/></button>
</div>
<div class="flex-child py12 px12 bg-gray-faint txt-s align-center">
<span v-text="delta.props.message ? delta.props.message : '<No Delta Message>'"></span>
</div>
<div class="flex-child scroll-auto">
<div v-for="(feat, feat_it) in delta.features.features" class="px12 py3 clearfix bg-white bg-darken25-on-hover cursor-pointer clearfix">
<span v-if="feat.geometry && feat.geometry.type === 'Point'" class="fl py6 px6"><svg class='icon'><use href='#icon-marker'/></span>
<span v-else-if="feat.geometry && feat.geometry.type === 'MultiPoint'" class="fl px6 py6"><svg class='icon'><use href='#icon-marker'/></span>
<span v-else-if="feat.geometry && feat.geometry.type === 'LineString'" class="fl px6 py6"><svg class='icon'><use href='#icon-polyline'/></span>
<span v-else-if="feat.geometry && feat.geometry.type === 'MultiLineString'" class="fl px6 py6"><svg class='icon'><use href='#icon-polyline'/></span>
<span v-else-if="feat.geometry && feat.geometry.type === 'Polygon'" class="fl px6 py6"><svg class='icon'><use href='#icon-polygon'/></span>
<span v-else-if="feat.geometry && feat.geometry.type === 'MultiPolygon'" class="fl px6 py6"><svg class='icon'><use href='#icon-polygon'/></span>
<span v-else class="fl px6 py6"><svg class='icon'><use href='#icon-circle'/></span>
<span class="fl" v-text="feat.id"></span>
<span class="fl px6" v-text="feat.action"></span>
</div>
</div>
</template>
<template v-else>
<div class='flex-child px12 py12 border--gray-light border-b'>
<h3 class='fl py6 txt-m txt-bold'>Deltas</h3>
<button @click="getDeltas" class='btn round bg-gray-light bg-darken25-on-hover color-gray-dark fr'><svg class='icon'><use href='#icon-refresh'/></button>
</div>
<template v-if='loading'>
<div class='flex-child loading h60 py12'></div>
</template>
<template v-else>
<template v-if='deltas.length'>
<div class="flex-child scroll-auto">
<div v-for="delta in deltas" @click="getDelta(delta.id)" class="px12 py12 border-b bg-darken10-on-hover border--gray-light cursor-pointer clearfix">
<div class="clearfix">
<div @click.stop='userClick(delta.uid)' class="fl txt-bold txt-underline-on-hover" v-text="delta.username"></div>
<div class="fr txt-em" v-text="moment(delta.created).add(moment().utcOffset(), 'minutes').fromNow()"></div>
</div>
<div class='txt-s' style="word-wrap: break-word;" v-text="delta.props.message ? delta.props.message : (delta.props.comment ? delta.props.comment : '<No Delta Message>')"></div>
<span class='bg-blue-faint color-blue inline-block px6 py3 my3 my3 txt-xs txt-bold round fr' v-text="delta.id"></span>
</div>
<div class="grid col px12 py12 wfull txt-l color-gray">
<div class='col--4'>
<span @click='getDeltas("left")'class='fr cursor-pointer color-gray-dark-on-hover'><svg class='icon'><use href='#icon-arrow-left'/></svg></span>
</div>
<div class='col--4 flex-parent flex-parent--center-main'>
<span @click='getDeltas("home")'class='cursor-pointer color-gray-dark-on-hover'><svg class='icon'><use href='#icon-home'/></svg></span>
</div>
<div class='col--4'>
<span @click='getDeltas("right")'class='fl cursor-pointer color-gray-dark-on-hover'><svg class='icon'><use href='#icon-arrow-right'/></svg></span>
</div>
</div>
</div>
</template>
<template v-else>
<div class="px12 py12 clearfix bg-white">
<div align="center">No Deltas</div>
</div>
</template>
</template>
</template>
<foot/>
</div>
</template>
<script>
import Moment from 'moment';
import Foot from '../components/Foot.vue';
import { bbox } from '@turf/turf';
export default {
name: 'deltas',
data: function() {
return {
loading: true,
maxoffset: false,
offset: false,
deltas: [],
delta: false
}
},
mounted: function() {
this.moment = Moment;
},
created: function() {
this.getDeltas();
},
components: {
foot: Foot
},
methods: {
userClick(uid) {
if (!uid) return;
this.$emit('user', parseInt(uid));
},
getDeltas: function(action) {
let off = '';
if (action === 'right') { //Back in time
this.offset = this.offset + 20;
off = `?offset=${this.offset}`;
} else if (action === 'home') { //Current time
this.offset = false;
} else if (action === 'left') { //Forward in time
if (this.offset < 10) return;
this.offset = this.offset - 20;
off = `?offset=${this.offset}`;
}
fetch(`${window.location.protocol}//${window.location.host}/api/deltas${off}`, {
method: 'GET',
credentials: 'same-origin'
}).then((response) => {
return response.json();
}).then((body) => {
this.loading = false;
this.deltas.splice(0, this.deltas.length);
this.deltas = this.deltas.concat(body);
if (this.deltas[0]) this.offset = this.deltas[0].id;
}).catch((err) => {
console.error(err);
});
},
getDelta: function(delta_id) {
if (!delta_id) return;
fetch(`${window.location.protocol}//${window.location.host}/api/delta/${delta_id}`, {
method: 'GET',
credentials: 'same-origin'
}).then((response) => {
return response.json();
}).then((body) => {
body.features.features = body.features.features.map(feat => {
feat.properties._action = feat.action;
return feat;
});
this.delta = body;
});
},
style: function() {
let action_create = '#008000';
let action_modify = '#FFFF00';
let action_delete = '#FF0000';
this.map.layers.push('hecate-delta-polygons');
this.map.gl.addLayer({
id: 'hecate-delta-polygons',
type: 'fill',
source: 'hecate-delta',
filter: ['==', '$type', 'Polygon'],
paint: {
'fill-opacity': 0.4,
'fill-color': [ 'match', [ 'get', '_action' ], 'create', action_create, 'modify', action_modify, 'delete', action_delete, action_create ]
}
});
this.map.layers.push('hecate-delta-polygon-outlines');
this.map.gl.addLayer({
id: 'hecate-delta-polygon-outlines',
type: 'line',
source: 'hecate-delta',
filter: ['==', '$type', 'Polygon'],
layout: {
'line-join': 'round',
'line-cap': 'round'
},
paint: {
'line-color': [ 'match', [ 'get', '_action' ], 'create', action_create, 'modify', action_modify, 'delete', action_delete, action_create ],
'line-width': 0.75
}
})
this.map.layers.push('hecate-delta-lines');
this.map.gl.addLayer({
id: 'hecate-delta-lines',
type: 'line',
source: 'hecate-delta',
filter: ['==', '$type', 'LineString'],
layout: {
'line-join': 'round',
'line-cap': 'round'
},
paint: {
'line-color': [ 'match', [ 'get', '_action' ], 'create', action_create, 'modify', action_modify, 'delete', action_delete, action_create ],
'line-width': 1.25
}
});
this.map.layers.push('hecate-delta-points');
this.map.gl.addLayer({
id: 'hecate-delta-points',
type: 'circle',
source: 'hecate-delta',
filter: ['==', '$type', 'Point'],
paint: {
'circle-color': [ 'match', [ 'get', '_action' ], 'create', action_create, 'modify', action_modify, 'delete', action_delete, action_create ],
'circle-radius': 4
}
});
}
},
watch: {
delta: function() {
if (!this.delta) { // Reset to normal map
this.map.unstyle();
this.map.show();
this.map.gl.getSource('hecate-delta').setData({ type: 'FeatureCollection', features: [] });
} else { // Display Delta Map
//Deletes don't have a geometry property and as such
//should not be dislayed or used to calc. bbox
const noDeletes = {
type: 'FeatureCollection',
features: this.delta.features.features.filter((feat) => {
if (!feat.geometry) return false;
return true;
})
};
this.map.gl.getSource('hecate-delta').setData(noDeletes);
this.map.hide();
this.style();
this.delta.bbox = bbox(noDeletes);
this.map.gl.fitBounds(this.delta.bbox);
}
}
},
render: h => h(App),
props: [ 'map' ]
}
</script>